qctl 0.0.2

Control in-repo YAML work queues
Documentation
{
	"$schema": "https://json-schema.org/draft/2020-12/schema",
	"$id": "https://github.com/victor-software-house/qctl/blob/main/schema/tasks.schema.json",
	"title": "qctl work queue",
	"type": "object",
	"additionalProperties": false,
	"required": ["schema_version", "prefix", "active", "queue", "archive"],
	"properties": {
		"schema_version": {
			"const": 1,
			"description": "Ledger schema version. Increment only with a coordinated schema and ledger migration."
		},
		"prefix": {
			"type": "string",
			"pattern": "^[A-Z][A-Z0-9]{1,7}$",
			"description": "Stable ID prefix for this repository, such as PST, KAI, or OMX. Never encodes priority."
		},
		"active": {
			"description": "The one task currently being executed, or null when work is intentionally paused.",
			"oneOf": [{ "$ref": "#/$defs/taskId" }, { "type": "null" }]
		},
		"queue": {
			"type": "array",
			"description": "Pending work in priority order. Array order is the priority.",
			"items": { "$ref": "#/$defs/task" }
		},
		"archive": {
			"type": "array",
			"description": "Completed or deliberately dropped work, newest first. Archived IDs are never reused.",
			"items": { "$ref": "#/$defs/archiveTask" }
		},
		"horizon": {
			"type": "array",
			"description": "Mapped work that is not on the short-term queue: research, evaluations, or deferred items without a start condition. File order is not priority. active must never name a horizon id.",
			"items": { "$ref": "#/$defs/horizonTask" }
		}
	},
	"$defs": {
		"taskId": {
			"type": "string",
			"pattern": "^[A-Z][A-Z0-9]{1,7}-[0-9]{3,}$"
		},
		"nonEmptyString": {
			"type": "string",
			"minLength": 1
		},
		"nonEmptyStringList": {
			"type": "array",
			"items": { "$ref": "#/$defs/nonEmptyString" },
			"minItems": 1,
			"uniqueItems": true
		},
		"repoPath": {
			"type": "string",
			"pattern": "^(?!/)(?!.*\\.\\.).+\\.md$"
		},
		"task": {
			"type": "object",
			"additionalProperties": false,
			"required": ["id", "title", "scope", "outcome", "blocked_by", "acceptance"],
			"properties": {
				"id": { "$ref": "#/$defs/taskId" },
				"title": { "$ref": "#/$defs/nonEmptyString" },
				"scope": { "$ref": "#/$defs/nonEmptyString" },
				"outcome": { "$ref": "#/$defs/nonEmptyString" },
				"blocked_by": {
					"type": "array",
					"items": { "$ref": "#/$defs/taskId" },
					"uniqueItems": true
				},
				"acceptance": { "$ref": "#/$defs/nonEmptyStringList" },
				"patch": {
					"type": "string",
					"pattern": "^[a-z][a-z0-9-]*$"
				},
				"plan": { "$ref": "#/$defs/repoPath" },
				"links": {
					"type": "array",
					"items": { "type": "string", "format": "uri" },
					"uniqueItems": true
				},
				"notes": { "$ref": "#/$defs/nonEmptyString" }
			}
		},
		"archiveTask": {
			"type": "object",
			"additionalProperties": false,
			"required": ["id", "title", "scope", "completed", "outcome", "evidence"],
			"properties": {
				"id": { "$ref": "#/$defs/taskId" },
				"title": { "$ref": "#/$defs/nonEmptyString" },
				"scope": { "$ref": "#/$defs/nonEmptyString" },
				"completed": { "type": "string", "format": "date" },
				"outcome": { "$ref": "#/$defs/nonEmptyString" },
				"evidence": { "$ref": "#/$defs/nonEmptyStringList" },
				"patch": {
					"type": "string",
					"pattern": "^[a-z][a-z0-9-]*$"
				},
				"plan": { "$ref": "#/$defs/repoPath" },
				"links": {
					"type": "array",
					"items": { "type": "string", "format": "uri" },
					"uniqueItems": true
				},
				"notes": { "$ref": "#/$defs/nonEmptyString" },
				"disposition": {
					"type": "string",
					"enum": ["completed", "dropped"]
				}
			}
		},
		"horizonTask": {
			"type": "object",
			"additionalProperties": false,
			"required": ["id", "title", "scope", "outcome", "kind", "open"],
			"properties": {
				"id": { "$ref": "#/$defs/taskId" },
				"title": { "$ref": "#/$defs/nonEmptyString" },
				"scope": { "$ref": "#/$defs/nonEmptyString" },
				"outcome": { "$ref": "#/$defs/nonEmptyString" },
				"kind": {
					"type": "string",
					"enum": ["research", "evaluation", "deferred"],
					"description": "research = unknown facts; evaluation = known options, no start condition; deferred = start condition is explicit and not met."
				},
				"open": {
					"$ref": "#/$defs/nonEmptyString",
					"description": "The missing start condition or the question that keeps this off the queue."
				},
				"patch": {
					"type": "string",
					"pattern": "^[a-z][a-z0-9-]*$"
				},
				"plan": { "$ref": "#/$defs/repoPath" },
				"links": {
					"type": "array",
					"items": { "type": "string", "format": "uri" },
					"uniqueItems": true
				},
				"notes": { "$ref": "#/$defs/nonEmptyString" }
			}
		}
	}
}