{
"$defs": {
"ArchiveOrder": {
"description": "What order the archive is kept in.",
"oneOf": [
{
"const": "newest_first",
"description": "Most recently completed first, which is what `check` requires.",
"type": "string"
},
{
"const": "as_written",
"description": "However the file has them. `fmt` will not reorder rows.",
"type": "string"
}
]
},
"ArchivedTask": {
"additionalProperties": false,
"description": "A task that has left the queue, either shipped or deliberately dropped.",
"properties": {
"completed": {
"description": "The moment it left the queue, as `YYYY-MM-DDThh:mm:ss`, in the zone\n[`Style::timezone`] declares.",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$",
"type": "string"
},
"disposition": {
"anyOf": [
{
"$ref": "#/$defs/Disposition"
},
{
"type": "null"
}
],
"description": "Whether the work shipped or was dropped."
},
"evidence": {
"description": "Where to look to see that it is true.",
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array",
"uniqueItems": true
},
"id": {
"description": "The id it held on the queue, never reused after this.",
"pattern": "^[A-Z][A-Z0-9]{1,7}-[0-9]{3,6}$",
"type": "string"
},
"links": {
"description": "Anything outside this repository worth following.",
"items": {
"format": "uri",
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"notes": {
"description": "Ordered context items a later reader will want to know and cannot\nreconstruct. Intentional line and paragraph breaks are preserved.",
"items": {
"minLength": 1,
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"outcome": {
"description": "What became true.",
"minLength": 1,
"type": "string"
},
"patch": {
"description": "The changeset that carried its release note.",
"pattern": "^[a-z][a-z0-9-]*$",
"type": [
"string",
"null"
]
},
"plan": {
"description": "The plan it was worked from, if it had one.",
"pattern": "^(?![/\\\\])(?!.*(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)).+\\.md$",
"type": [
"string",
"null"
]
},
"scope": {
"description": "Where the work landed.",
"minLength": 1,
"type": "string"
},
"title": {
"description": "What the task was, in one line.",
"minLength": 1,
"type": "string"
}
},
"required": [
"id",
"title",
"scope",
"completed",
"outcome",
"evidence"
],
"type": "object"
},
"Disposition": {
"description": "How a task left the queue.",
"oneOf": [
{
"const": "completed",
"description": "The outcome was reached.",
"type": "string"
},
{
"const": "dropped",
"description": "The task was abandoned on purpose, and the row says why.",
"type": "string"
}
]
},
"HorizonTask": {
"additionalProperties": false,
"description": "Work that is mapped but has no place on the queue yet.",
"properties": {
"id": {
"description": "Its id, drawn from the same sequence as the queue's.",
"pattern": "^[A-Z][A-Z0-9]{1,7}-[0-9]{3,6}$",
"type": "string"
},
"kind": {
"$ref": "#/$defs/Kind",
"description": "Why it is not on the queue."
},
"links": {
"description": "Anything outside this repository worth following.",
"items": {
"format": "uri",
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"notes": {
"description": "Ordered context items a reader needs and the row cannot carry in its\nother fields. Intentional line and paragraph breaks are preserved.",
"items": {
"minLength": 1,
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"open": {
"description": "The missing start condition, or the question that keeps it off the\nqueue.",
"minLength": 1,
"type": "string"
},
"outcome": {
"description": "What would be true once it is done.",
"minLength": 1,
"type": "string"
},
"patch": {
"description": "The changeset it would carry its release note in.",
"pattern": "^[a-z][a-z0-9-]*$",
"type": [
"string",
"null"
]
},
"plan": {
"description": "A plan document in this repository, when it already has one.",
"pattern": "^(?![/\\\\])(?!.*(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)).+\\.md$",
"type": [
"string",
"null"
]
},
"scope": {
"description": "Where the work would land.",
"minLength": 1,
"type": "string"
},
"title": {
"description": "What it is, in one line.",
"minLength": 1,
"type": "string"
}
},
"required": [
"id",
"title",
"scope",
"outcome",
"kind",
"open"
],
"type": "object"
},
"Kind": {
"description": "Why a task sits on the horizon instead of the queue.",
"oneOf": [
{
"const": "research",
"description": "Facts are missing.",
"type": "string"
},
{
"const": "evaluation",
"description": "The options are known, but nothing says when to choose.",
"type": "string"
},
{
"const": "deferred",
"description": "The start condition is explicit and not met.",
"type": "string"
}
]
},
"QueuedTask": {
"additionalProperties": false,
"description": "A task on the queue.",
"properties": {
"acceptance": {
"description": "Observable end conditions that must be demonstrably true to close it.\nImplementation instructions and procedural steps belong in `plan` or\n`notes`.",
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array",
"uniqueItems": true
},
"blocked_by": {
"description": "Ids that must ship first. Each has to sit earlier in this queue.",
"items": {
"pattern": "^[A-Z][A-Z0-9]{1,7}-[0-9]{3,6}$",
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"id": {
"description": "This task's id, unique for the life of the repository.",
"pattern": "^[A-Z][A-Z0-9]{1,7}-[0-9]{3,6}$",
"type": "string"
},
"links": {
"description": "Anything outside this repository worth following.",
"items": {
"format": "uri",
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"notes": {
"description": "Ordered context items a reader needs and the row cannot carry in its\nother fields. Intentional line and paragraph breaks are preserved.",
"items": {
"minLength": 1,
"type": "string"
},
"type": "array",
"uniqueItems": true
},
"outcome": {
"description": "What is true once this is done.",
"minLength": 1,
"type": "string"
},
"patch": {
"description": "The changeset that will carry this task's release note.",
"pattern": "^[a-z][a-z0-9-]*$",
"type": [
"string",
"null"
]
},
"plan": {
"description": "A plan document in this repository, when the task needs one.",
"pattern": "^(?![/\\\\])(?!.*(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)).+\\.md$",
"type": [
"string",
"null"
]
},
"scope": {
"description": "Where the work lands.",
"minLength": 1,
"type": "string"
},
"title": {
"description": "What the task is, in one line.",
"minLength": 1,
"type": "string"
}
},
"required": [
"id",
"title",
"scope",
"outcome",
"blocked_by",
"acceptance"
],
"type": "object"
},
"RowSpacing": {
"description": "How rows in a list are set apart.",
"oneOf": [
{
"const": "as_written",
"description": "However the file has them. `fmt` still collapses a run of blank lines.",
"type": "string"
},
{
"const": "blank_line",
"description": "Exactly one blank line between consecutive rows, none above the first.",
"type": "string"
}
]
},
"Section": {
"description": "One of a ledger's three lists.",
"enum": [
"queue",
"archive",
"horizon"
],
"type": "string"
},
"Style": {
"additionalProperties": false,
"description": "How a ledger is written, declared by the ledger itself.\n\nEvery option has a default that is what qctl already wrote, so a file with\nno `style` block keeps the shape it has. `qctl fmt` applies these; a verb\nwrites any new text in them and, unless [`Style::normalize_on_write`] says\notherwise, leaves the rest of the file alone.\n\nThis is meant to grow. A new option is a new field with a default equal to\ntoday's behaviour, which is a minor change rather than a migration.",
"properties": {
"archive_order": {
"$ref": "#/$defs/ArchiveOrder",
"default": "newest_first",
"description": "Whether `fmt` sorts the archive newest-first or leaves it as written."
},
"indent": {
"default": 2,
"description": "How far a row is indented under its list's key.",
"format": "uint8",
"maximum": 8,
"minimum": 1,
"type": "integer"
},
"normalize_on_write": {
"default": false,
"description": "Whether a verb normalizes the whole file on the way out.\n\nFalse, and a verb rewrites only the lines it changes — so a diff shows\nthe work and nothing else. True, and every verb leaves the file fully\nnormalized, at the price of touching lines nobody asked about.",
"type": "boolean"
},
"row_spacing": {
"$ref": "#/$defs/RowSpacing",
"default": "as_written",
"description": "Whether `fmt` keeps one blank line between rows or leaves the spacing\nas written."
},
"section_order": {
"default": [
"queue",
"archive",
"horizon"
],
"description": "The order the lists appear in the file. All three, once each, in\nwhatever order reads best here.",
"items": {
"$ref": "#/$defs/Section"
},
"maxItems": 3,
"minItems": 3,
"type": "array",
"uniqueItems": true
},
"timezone": {
"default": "+00:00",
"description": "The fixed offset `completed` is written in. Stamps carry no offset of\ntheir own — this is where they say what zone they are.\n\nChanging it does not rewrite the stamps already written: nothing records\nwhich zone an existing stamp was taken in, so `fmt` cannot know, and\nguessing would move a moment. Change it deliberately, or not at all.",
"pattern": "^[+-][0-9]{2}:[0-9]{2}$",
"type": "string"
}
},
"type": "object"
}
},
"$id": "https://raw.githubusercontent.com/victor-software-house/qctl/main/schema/tasks.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"description": "One repository's work queue.",
"properties": {
"active": {
"description": "The one task currently being executed, or null when work is\nintentionally paused.",
"pattern": "^[A-Z][A-Z0-9]{1,7}-[0-9]{3,6}$",
"type": [
"string",
"null"
]
},
"archive": {
"description": "Completed or deliberately dropped work, newest first. Archived ids are\nnever reused and remain part of the global task corpus.",
"items": {
"$ref": "#/$defs/ArchivedTask"
},
"type": "array"
},
"horizon": {
"description": "Mapped work that is not on the short-term queue: research, evaluations,\nor deferred items without a start condition. File order is not priority,\n`active` must never name a horizon id, and each id still belongs to the\nsame global task corpus.",
"items": {
"$ref": "#/$defs/HorizonTask"
},
"type": "array"
},
"prefix": {
"description": "Stable id prefix for this repository. Never encodes priority.",
"pattern": "^[A-Z][A-Z0-9]{1,7}$",
"type": "string"
},
"queue": {
"description": "Pending work in priority order. The order is the priority. Queue,\narchive, and horizon partition one globally unique, continuous id\ncorpus.",
"items": {
"$ref": "#/$defs/QueuedTask"
},
"type": "array"
},
"schema_version": {
"const": 4,
"description": "Ledger schema version, the one this qctl writes and accepts. Increment\nonly with a coordinated schema and ledger migration.",
"format": "uint32",
"minimum": 0,
"type": "integer"
},
"style": {
"$ref": "#/$defs/Style",
"default": {
"archive_order": "newest_first",
"indent": 2,
"normalize_on_write": false,
"row_spacing": "as_written",
"section_order": [
"queue",
"archive",
"horizon"
],
"timezone": "+00:00"
},
"description": "How this ledger is written. A file that says nothing here gets the\ndefaults, which are the shape qctl already wrote."
}
},
"required": [
"schema_version",
"prefix",
"active",
"queue",
"archive"
],
"title": "qctl work queue",
"type": "object"
}