kadence 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ import{a as ce,b as ee,c as ue,d as N,e as R,f as me,g as he,i as fe,l as pe,m as ge,n as ke,q as ye,r as be,s as te,t as we}from"./chunk-CR5YON2J.js";import{execFileSync as ct}from"node:child_process";import{basename as ve}from"node:path";function O(t,r){try{return ct("git",r,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}}function $e(t){let r=O(t,["rev-parse","--show-toplevel"]);return r===null||r.length===0?null:r}function Se(t){let r=O(t,["rev-parse","--path-format=absolute","--git-dir","--git-common-dir"]);if(r===null)return null;let[n,e]=r.split(`
3
+ `);return n===void 0||e===void 0||n===e?null:ve(n)}function xe(t){let r=O(t,["config","user.email"]);return r===null||r.length===0?null:r}function je(t){let r=O(t,["rev-parse","--abbrev-ref","HEAD"]);return r===null||r.length===0||r==="HEAD"?null:r}function Te(t){if(ut(t,"main"))return"main";let r=O(t,["config","init.defaultBranch"]);return r!==null&&r.length>0?r:"main"}function ut(t,r){return O(t,["rev-parse","--verify","--quiet",`${r}^{commit}`])!==null}function Ce(t,r,n){let e=O(t,["log",`${r}..${n}`,"--name-only","--pretty=format:","--",".kadence/events"]);if(e===null)return null;let s=new Set;for(let a of e.split(`
4
+ `)){let i=a.trim();i.endsWith(".json")&&s.add(ve(i,".json"))}return s}function Ee(t){return`<!-- generated by kadence ${t} \u2014 refresh with \`kadence init\` -->`}function Tn(t){return`${Ee(t)}
5
+ # kadence \u2014 for AI agents
6
+
7
+ This project's tasks live here as plain files. Read them directly, or through
8
+ the CLI. No server required.
9
+
10
+ ## Commands
11
+
12
+ kadence prime start here \u2014 the live state, in 40 lines
13
+ kadence search "\u2026" --json find where something was written down
14
+ kadence ready --json what can be started right now
15
+ kadence task claim KAD-42 take it; no argument takes the top of ready
16
+ kadence board --json the whole board
17
+ kadence task list --json all tasks
18
+ kadence task show KAD-42 --json one task with its history
19
+ kadence task add "title" -d "..." --type bug --estimate 3
20
+ kadence task move KAD-42 in_progress
21
+ kadence task assign KAD-42 you@example.com
22
+ kadence note "text" --task KAD-42 something learned; a choice is a decision
23
+ kadence decision list --json why the current choices were made
24
+ kadence decision add "..." --why "..." a choice, with its reason
25
+
26
+ ## JSON contract
27
+
28
+ Every \`--json\` response carries \`schema: "kadence/v1"\`. stdout holds JSON and
29
+ nothing else; warnings go to stderr. Exit codes: 0 success, 1 runtime error,
30
+ 2 bad arguments.
31
+
32
+ The contract itself is machine-readable \u2014 read it instead of guessing:
33
+
34
+ kadence schema --json
35
+
36
+ It lists every command, the fields you can rely on, and every error code. It
37
+ works outside a repository, so you can read it before \`init\`.
38
+
39
+ ## When something fails
40
+
41
+ A failed \`--json\` call carries \`error.code\` \u2014 branch on that, not on the
42
+ sentence. Where the valid set is knowable it also carries \`allowed\`:
43
+
44
+ { "ok": false,
45
+ "error": { "code": "unknown_status", "received": "shipped",
46
+ "allowed": ["backlog", "in_progress", "done"],
47
+ "hint": "kadence board statuses --json" } }
48
+
49
+ Statuses are configured per project, so \`allowed\` is the only reliable source \u2014
50
+ never assume the defaults. Retrying an identical command never helps: there is no
51
+ network and no lock, so the same input always fails the same way.
52
+
53
+ ## Working as an agent
54
+
55
+ Set \`KADENCE_SOURCE=agent\` so events record your authorship. Without it an
56
+ event is marked as human \u2014 we do not guess.
57
+
58
+ ## Adding a teammate
59
+
60
+ Nothing to invite and no account to create \u2014 the journal travels with git.
61
+
62
+ 1. They install the CLI: \`npm install -g kadence\`.
63
+ 2. They \`git pull\`; \`.kadence/\`, \`AGENTS.md\` and \`CLAUDE.md\` come with it.
64
+ 3. One \`git config user.email\` per person, the same on all their machines \u2014
65
+ that address is who they are in the journal.
66
+ 4. Their agent runs with \`KADENCE_SOURCE=agent\` in its environment.
67
+
68
+ Afterwards \`kadence task show KAD-42\` shows the lead who did what, and whether
69
+ a person or their agent did it.
70
+
71
+ ## Why to ask rather than read the files
72
+
73
+ You cannot find a task by its label in the journal. \`KAD-3\` is derived while
74
+ folding and is never stored in an event, so \`grep -r "KAD-3" .kadence/events/\`
75
+ returns nothing. Folding the journal yourself is the only alternative to asking.
76
+
77
+ Asking is also cheaper, and stays cheap: \`task show --json\` is about 950 bytes
78
+ whether the project holds ten tasks or a thousand, while the journal behind it
79
+ grows past 500 KB.
80
+
81
+ \`board --json\` is the exception: it returns every task in full, which on a large
82
+ project is a lot of description you did not ask for. Narrow it:
83
+
84
+ kadence board --json --fields label,status,assignee
85
+ kadence task list --json --fields id,label,status
86
+
87
+ \`kadence schema --json\` lists every selectable field.
88
+
89
+ ## Recording why
90
+
91
+ The journal holds what happened. \`decision\` holds why, which the code and
92
+ \`git log\` never carry:
93
+
94
+ kadence decision add "Use ULIDs" --why "Clocks disagree between machines" \\
95
+ --rejected "Auto-increment: collides across branches" --task KAD-1
96
+
97
+ \`--why\` is required: without it the record is a changelog line. \`--rejected\` is
98
+ what stops the next agent proposing the thing that was already turned down.
99
+
100
+ When a decision stops being true, do not edit it \u2014 supersede it:
101
+
102
+ kadence decision add "Use signed tokens" --why "..." --supersedes DEC-1
103
+
104
+ One event writes both directions. \`decision list\` then returns only what is
105
+ still in force; pass \`--all\` when you need the history too. **Read the default,
106
+ not \`--all\`** \u2014 a reversed reason quoted as current is worse than no memory.
107
+
108
+ ## Documents
109
+
110
+ \`kadence task doc KAD-1 docs/design.md\` links a document to a task, and
111
+ \`task show --json\` returns the links. kadence does not store the document: it
112
+ stays plain markdown in the repository. The link is the part git cannot express.
113
+
114
+ ## What not to do
115
+
116
+ Do not hand-edit files under \`.kadence/events/\`: the journal is appended to,
117
+ never modified. To correct the state, add a new event through the CLI.
118
+ `}var _e="<!-- kadence:begin -->",ne="<!-- kadence:end -->";function mt(t){return`${_e}
119
+ ${Ee(t)}
120
+ ## Project tasks \u2014 kadence
121
+
122
+ Tasks, notes, decisions and documentation live in \`.kadence/\` as plain files, shared through git.
123
+
124
+ kadence prime start here: sprint, your work, what is ready
125
+ kadence search "\u2026" --json find where something was written down
126
+ kadence board --json --summary the board's state, without the history
127
+ kadence task list --json all tasks
128
+ kadence decision list --json why the current choices were made
129
+ kadence doc list --json documentation: how things work now, revised
130
+ kadence schema --json the contract: commands, fields, error codes
131
+
132
+ kadence task claim take the top of ready before starting
133
+ kadence task move KAD-1 done change state
134
+ kadence note "\u2026" --task KAD-1 something learned, for the next session
135
+ kadence decision add "\u2026" --why "\u2026" a choice between approaches, and its reason
136
+
137
+ \`--json\` responses carry \`schema: "kadence/v1"\`; stdout is JSON only. A failure
138
+ carries \`error.code\` and, where knowable, \`allowed\`. As an agent, set \`KADENCE_SOURCE=agent\`.
139
+ If \`kadence\` is not on PATH, ask the human to run \`npm install -g kadence\` (\`npx kadence prime\` needs network \u2014 their call).
140
+
141
+ Details: \`.kadence/README.md\`
142
+ ${ne}`}function Cn(t,r){let n=mt(r);if(t===null||t.trim().length===0)return`${n}
143
+ `;let e=t.indexOf(_e),s=t.indexOf(ne);if(e!==-1&&s!==-1&&s>e){let i=t.slice(0,e),d=t.slice(s+ne.length);return`${i}${n}${d}`}let a=t.endsWith(`
144
+ `)?`
145
+ `:`
146
+
147
+ `;return`${t}${a}${n}
148
+ `}var F=["id","label","title","description","type","priority","status","labels","assignee","reporter","sprint","milestone","loggedHours","parent","blockedBy","due","claimedBy","contestedBy","criteria","openCriteria","comments","estimate","history"],De=100,Ae=["not_a_repository","not_initialised","no_git_identity","task_not_found","sprint_not_found","decision_not_found","unknown_status","unknown_type","unknown_priority","unknown_field","invalid_argument","template_not_found","conflicting_state","nothing_ready","milestone_not_found","doc_not_found"],ht={not_a_repository:"There is no git repository here. kadence stores its journal inside one.",not_initialised:"The repository has no .kadence/ folder yet. Run `kadence init`.",no_git_identity:"Git has no user.email, so an event would have no author.",task_not_found:"No task carries that ULID or KAD-N label. Nothing was changed.",sprint_not_found:"No sprint carries that name or id.",decision_not_found:"No decision carries that ULID or DEC-N label.",unknown_status:"The status is not one of this project\u2019s columns. They are configurable, so read `allowed`.",unknown_type:"The task type is not one of the four kadence defines.",unknown_priority:"The priority is not one of the four kadence defines.",unknown_field:"A name given to --fields is not a task field. Read `allowed`.",invalid_argument:"An argument was missing or malformed; the command did nothing.",template_not_found:"No template carries that name.",conflicting_state:"The arguments were understood, but the current state does not allow it \u2014 a closed sprint, or one already started.",milestone_not_found:"No milestone carries that name, MS-N label or ULID.",doc_not_found:"No document carries that ULID or DOC-N label.",nothing_ready:"Nothing can be started right now: the board is empty, or every open task is blocked or claimed by someone else. The message says which."};function En(t){return{version:"kadence/v1",tool:{name:"kadence",version:t},stability:"Additive only: fields and error codes may be added, never renamed or removed within kadence/v1.",exitCodes:{0:"success",1:"runtime error \u2014 the command understood you and could not comply",2:"bad arguments \u2014 nothing was attempted"},output:{stdout:"With --json, JSON and nothing else.",stderr:"Warnings and human messages, never part of the contract."},env:{KADENCE_SOURCE:'Set to "agent" so events record agent authorship. Without it an event counts as human \u2014 we do not guess.',KADENCE_ACTOR:'Who holds a claim, when that is not your git email: one of several agents a person runs, e.g. "you@example.com#agent-2". Without it, an agent (KADENCE_SOURCE=agent) in a linked git worktree claims as "email#<worktree>". Events are still authored by the git email.',NO_COLOR:"Any value disables colour."},errors:Ae.map(r=>({code:r,meaning:ht[r]})),shapes:{task:{required:["id","label","title","status","type","priority","estimate","assignee","blockedBy","loggedHours","claimedBy","contestedBy","labels"],selectable:[...F],notes:{id:"ULID. The identity of the task; KAD-N is a label derived while folding and is never stored.",history:"Present in `task show` only \u2014 the events that produced this state. Each entry carries `source`.",comments:'Each comment carries `source`: "human" or "agent", from KADENCE_SOURCE at the time of writing. `actor` is the git identity, which a person and their agent share \u2014 `source` is the only field that separates them.',required:"What a full response carries. With --fields you get exactly what you asked for and nothing else.",paging:"A list command returns one page. <name>Total is how many matched, never how many came back, and <name>Offset is where the page starts \u2014 together they say whether there is more and where to ask for it. The default page is 100; --limit 0 returns everything and --offset N walks. An agent that ignores the total will answer as though the page were the board."}},board:{required:["schema","ok","columns","tasksTotal","tasksOffset"],notes:{columns:"With --summary each task carries only the state fields, never `history` or `comments`. The response without the flag is unchanged \u2014 the contract only ever gains."}},search:{required:["schema","ok","query","hits","hitsTotal"],notes:{hits:"Each: {kind, id, label, title, score, span, lines, at, by}, ordered by score. Never more than --limit and often fewer: a passage that shares only a common word with the question is dropped rather than returned, because a model handed something that merely looks relevant fabricates more than one handed nothing.",id:"ULID of the record the passage came from \u2014 what to pass to task show, decision show or doc show. `label` is KAD-N, DEC-N or DOC-N, derived while folding, and is for a reader to read (I7). A note has no label and comes back null.",span:"{text, start, end} over the record\u2019s own quotable text, with text.length === end - start. It is a quotation, not a summary: an agent citing it is citing the journal.",lines:"For a document: the lines of the section the passage came from, so `doc show` can be read at the right place. Null for every other kind.",coverage:"How much of the question this record holds, 0 to 1, weighted by how rare each word is \u2014 not a ranking and not a probability. Measured over a golden set: where the first hit is right this is 1.00 at the median, where it is wrong 0.62. Below 0.7 the human output says the passage answers part of the question, and an agent should treat it as somewhere to look rather than as the answer.",hitsTotal:"How many passages came back. Zero is an ordinary answer and means the journal has nothing on the question \u2014 not that the command failed."}},ready:{required:["label","title","priority","estimate","labels","claimedBy","contestedBy"],notes:{contestedBy:"Always an array. Non-empty means two people claimed the task and it is in this list on purpose \u2014 it needs a person, not a filter.",labels:"Always an array, empty when there are none. Free-form: kadence attaches no meaning to any label. A team that marks blast radius \u2014 impact-critical and the like \u2014 decides from this how carefully to work; the mapping from a label to how an agent behaves is the team\u2019s, never ours.",priority:"Urgency, from a fixed vocabulary: low, normal, high, urgent. Not blast radius \u2014 that is a label, and the two are different questions."}},milestone:{required:["label","name","due","status","doneTasks","totalTasks","donePoints","totalPoints"],notes:{label:"MS-N, derived from ULID order during the fold, exactly like KAD-N. It can change when a branch merges.",donePoints:"Progress is measured in points, the same unit as velocity and burndown."}},criterion:{required:["n","text","checked","checkedBy"],notes:{n:"A position in the folded list, assigned like KAD-N and never stored. It can change when a branch merges.",checkedBy:"Who checked it, or null. Cleared when it is unchecked."}},branch:{required:["name","base"],notes:{name:"Present on a `task list --branch` response only. A task belongs to the branch when it was created there or any event about it arrived there; nothing is stored, the answer comes from git at read time."}},note:{required:["id","text","task","at","by","source"],notes:{task:"The KAD-N label, or null. A note is not a decision: there is no `why` and no number."}},flow:{required:["schema","ok","report","window","started","unit","wip","finished","perWeek","cycleTime","leadTime","responseTime","sle","aging","blocked","notes"],notes:{cycleTime:"null, or {n, p50, p85, p95} in calendar days. Percentiles only \u2014 there is no mean, on purpose.",started:"The column work counts as started at. A cycle time quoted without it is a number about the default, not the team.",window:"UTC dates, inclusive at both ends. `--since 1` is the current UTC day."}},cfd:{required:["schema","ok","report","window","statuses","days"],notes:{days:"One row per day of the window, each with a count per column at the end of that UTC day. A move backwards subtracts."}},attention:{required:["schema","ok","report","threshold","started","asOf","rows","notes"],notes:{rows:"One row per task, however many signals it carries. `signals[].kind` is one of stalled, unowned, stale_claim, dead_blocker.",threshold:"Days of silence before work counts as stalled, and the age a claim must reach. From `--since`; the default is 7, not the 30 a window report uses.",asOf:"The UTC day the question was asked. This report is the one answer that changes without the journal changing."}},prime:{required:["sprint","mine","mineTotal","documentation","documentationTotal","ready","attention","attentionTotal","decisions","notes","commands"],notes:{mine:"Capped; `mineTotal` is the real count. Each item: {label, title, status, claimedBy, contestedBy}. A task whose `contestedBy` is non-empty comes first; if `claimedBy` is not you, you lost the claim \u2014 talk to the holder before starting. Every free-text field in this payload carries at most 2000 characters and ends with \u201C\u2026\u201D when it was longer; nothing a person writes comes near that.",documentation:"Documents linked to any work you hold, at most three: {label, title, task, bytes}; `documentationTotal` is the real count. No bodies \u2014 `kadence doc show DOC-N` returns one. Always an array.",decisions:"The five in force, newest first: {label, title}. Titles only \u2014 `kadence decision list --json` carries the reasons.",notes:"The last five, newest first: {text, bytes, by, task}. `bytes` is the note\u2019s full length; `text` is capped and ends with \u201C\u2026\u201D when it was longer, so a note too large for a preamble is still known about \u2014 `kadence note list --json` returns it whole.",ready:"A count, not a list. `kadence ready --json` returns the list.",attention:"Work in flight that nobody is moving, at most three, `attentionTotal` is the real count. Empty when there is nothing \u2014 and then the human output says nothing at all. `kadence report attention --json` returns the rest."}},decision:{required:["id","label","title","why","rejected","docs","supersedes","supersededBy","source"],notes:{why:"The reason. Required when recording \u2014 a decision without one is a changelog line.",supersededBy:"Derived while folding from a later decision carrying `supersedes`. Both directions come from one write, so they cannot fall out of step.",docs:"Repository-relative paths. kadence does not store the documents, only the link.",source:'"human" or "agent", from KADENCE_SOURCE at the time of writing. Never guessed: without the variable an event counts as human.',listing:"`decision list` hides superseded decisions unless --all is passed: a reversed reason presented as current is worse than no memory."}},document:{required:["id","label","title","bytes","revisions","tasks","updatedAt","updatedBy","source","conflicted"],notes:{label:"DOC-N, derived from ULID order during the fold, exactly like KAD-N. It can change when a branch merges.",body:"Present in `doc show` only. `doc list` and `task show` carry the size in `bytes` so a caller can decide whether to read it.",conflicts:"Present in `doc show` only, always an array. Non-empty means two revisions were written on top of the same one; `body` shows the later, each entry here carries another. `doc edit` settles it.",tasks:"KAD-N labels of the tasks this document explains.",documentation:"`task show` carries `documentation[]` \u2014 {id, label, title, bytes, updatedAt, conflicted} for each document linked to the task, never bodies."}},error:{required:["code","message"],optional:["received","allowed","hint"],notes:{allowed:"Present when the valid set is knowable \u2014 statuses are project-configurable.",retryable:"Deliberately absent: no network, no lock, so a retry always fails identically."}}},commands:[{name:"schema",summary:"This contract. Works outside a repository."},{name:"init",summary:"Create .kadence/ and the agent instruction files."},{name:"board",summary:"The whole board, grouped by column. Every task in full \u2014 pass --summary for the state without history and comments, which is what makes a large board big.",flags:["--json","--fields","--summary"],json:!0},{name:"prime",summary:"The session preamble: active sprint, your claimed work, how many tasks are ready, decisions in force, recent notes. Capped at 40 lines.",flags:["--json"],json:!0},{name:"ready",summary:"What can be started now: not yet started, unblocked, not claimed by anyone else. Priority first, then age. Work already in the started column or past it is left out \u2014 it has begun, so it cannot be started.",flags:["--json","--assignee","--limit"],json:!0},{name:"search",summary:"One question over everything written down: tasks with their criteria and comments, decisions with their reason and the alternative they rejected, notes, and documents cut at their headings. Lexical, not semantic \u2014 it finds the words that are there, which on a vocabulary of ULIDs and KAD-N is the stronger method rather than the weaker one. Five passages by default, each carrying the ULID of the record and a quoted span. An empty result means the journal has no answer, which is a result and not a failure.",flags:["--json","--kind","--limit","--all"],json:!0},{name:"task claim",summary:'Take a task, or the top of `ready` with no argument. No lock: a second claim is recorded and reported as contested. `claim` in the response is "claimed", "already_yours" or "contested", read from the journal after the write.',args:["ref"],json:!0},{name:"task release",summary:"Give a claimed task back.",args:["ref"],json:!0},{name:"task list",summary:"All tasks, filterable. `--branch` narrows to the work the current branch introduced, measured against `--base` (main by default).",flags:["--json","--fields","--branch","--base"],json:!0},{name:"task show",summary:"One task with its comments and full history.",args:["ref"],json:!0},{name:"task add",summary:"Create a task.",args:["title"],json:!0},{name:"task move",summary:"Change status. Accepts several refs, all or nothing.",args:["refs","status"],json:!0},{name:"task assign",summary:"Assign or unassign.",args:["refs","who"],json:!0},{name:"task comment",summary:"Append a comment.",args:["ref","text"],json:!0},{name:"task edit",summary:"Change fields. Labels move as deltas: --add-label and --remove-label change one and leave the rest, --label makes the set exactly what you pass. Prefer the first two \u2014 a whole set passed from two branches is two people\u2019s intent, and only the difference is recorded (ADR-013).",args:["refs"],flags:["--title","--description","--type","--priority","--due","--estimate","--label","--add-label","--remove-label","--json"],json:!0},{name:"task log",summary:"Log hours.",args:["refs","hours"],json:!0},{name:"task block",summary:"Record a blocker.",args:["ref","blocker"],json:!0},{name:"task ac",summary:'Acceptance criteria: `add <ref> "text"`, `check <ref> <n>`, `uncheck <ref> <n>`, `list <ref>`. The number is a position in the folded list, never stored. Moving to done with unchecked criteria warns and proceeds.',args:["action","ref","value"],json:!0},{name:"note",summary:"Record something learned that was never a choice. For a choice with a rejected alternative, use `decision add`.",args:["text"],flags:["--task","--json"],json:!0},{name:"note list",summary:"Notes, newest first.",flags:["--task","--limit","--json"],json:!0},{name:"decision add",summary:"Record why something was chosen, and what was turned down.",args:["title"],flags:["--why","--rejected","--task","--supersedes","--doc"],json:!0},{name:"decision list",summary:"Current decisions. Superseded ones need --all.",flags:["--all","--task"],json:!0},{name:"decision show",summary:"One decision in full.",args:["ref"],json:!0},{name:"task doc",summary:"Link a document to a task; `task doc add <ref> <path>` creates it from a small template and links it in one call. The file stays plain markdown in the repo and is never overwritten.",args:["ref","path"],json:!0},{name:"doc add",summary:"Write documentation into the journal: the current account of a module, contract or process. Not a file and not a note \u2014 it is revised, and readers get the latest. Text from --body, --file, or --stdin. Past 16 KiB it is written with a warning.",args:["title"],flags:["--body","--stdin","--file","--task","--json"],json:!0},{name:"doc edit",summary:"Write a new revision; earlier ones stay in the journal. A revision names what it was written on top of; two written on top of the same one are a conflict, shown by `doc show`. An edit with new text settles it \u2014 without text it is refused with conflicting_state, so no version is dropped unread. One text source: --body, --file or --stdin.",args:["ref"],flags:["--body","--stdin","--file","--title","--json"],json:!0},{name:"doc show",summary:"One document with its body.",args:["ref"],json:!0},{name:"doc list",summary:"Documentation without bodies: titles, sizes, the tasks each explains.",flags:["--task","--json"],json:!0},{name:"doc link",summary:"Attach a document to a task, so it arrives with `task show`.",args:["ref","task"],json:!0},{name:"report flow",summary:"WIP, throughput per week, cycle / lead / response time as p50 p85 p95 in calendar days, aging work against p85, created vs resolved, blocked days. Names the window and the started boundary it used. `--html` writes the same numbers as one self-contained page with charts, and answers with the path rather than the report.",flags:["--since","--html","--file","--json"],json:!0},{name:"report cfd",summary:"Cumulative flow: tasks per column at the end of each day in the window. `--html` writes it as a stacked area chart in one self-contained page.",flags:["--since","--html","--file","--json"],json:!0},{name:"report attention",summary:"Work past the started boundary that nobody is moving: stalled, unowned, held by a stale claim, or blocked by something already done. `--since` is days of silence, default 7. `--html` writes it as one self-contained page.",flags:["--since","--html","--file","--json"],json:!0},{name:"report burndown",summary:"One sprint against an even burn, day by day. The active sprint by default; `--sprint <name>` for a closed one. The same fold as `sprint burndown`, under the verb the catalogue lives at.",flags:["--sprint","--html","--file","--json"],json:!0},{name:"report velocity",summary:"Points committed against points finished, per closed sprint, newest last. Reported as a range \u2014 low, median, high \u2014 not as an average: the spread between sprints is the forecast, and a series shorter than four sprints says so.",flags:["--html","--file","--json"],json:!0},{name:"report workload",summary:"Open tasks, open points, work in progress and blocked work per owner, with a row for unassigned work. No hours and no capacity: the journal has neither.",flags:["--html","--file","--json"],json:!0},{name:"compact",summary:"Fold months older than --keep-months into one archive file each. Same events, faster cold start; an archived month is one file, so compact on one branch and merge before compacting on another.",flags:["--keep-months","--dry-run","--json"],json:!0},{name:"stats",summary:"Counts by status and assignee, open blockers, contested claims, and the velocity of the last three closed sprints.",flags:["--json"],json:!0},{name:"milestone list",summary:"Milestones with progress in points. Grouping by outcome, where an epic groups by structure and a sprint by time. Closed ones need --all.",flags:["--all","--json"],json:!0},{name:"milestone create",summary:"Create one. A task carries at most one milestone, as a field beside `sprint`.",args:["name"],flags:["--due","--json"],json:!0},{name:"milestone add",summary:"Put a task in a milestone; it leaves the one it was in.",args:["ref"],flags:["--milestone","--json"],json:!0},{name:"milestone close",summary:"Close it. A statement about the calendar, not about the work: open tasks stay open and are named.",args:["ref"],json:!0},{name:"board export",summary:"Write a snapshot: `--html` for one self-contained file that opens from disk, `--md` for a README or a pull request. No server and no network; nothing outlives the command.",flags:["--html","--md","--readme","--file","--json"],json:!0},{name:"board config",summary:"Read or set the columns, the started boundary and the definition of done. `--started` names the column cycle time and actual hours count from; `--dod` criteria are copied into each new task, never referenced.",flags:["--statuses","--started","--dod","--json"],json:!0},{name:"sprint status",summary:"Current sprint progress.",json:!0},{name:"sprint create",summary:"Start a sprint.",args:["name"],json:!0},{name:"sprint close",summary:"Close it and report velocity.",json:!0},{name:"sprint add",summary:"Put a task in a sprint: the active one, or `--sprint <name>`.",args:["ref"],flags:["--sprint","--json"],json:!0},{name:"sprint edit",summary:"Rename a sprint or change its description and dates. The active sprint when no name is given.",args:["name"],flags:["--name","--description","--start","--end","--json"],json:!0},{name:"sprint start",summary:"Start a planned sprint; with no name, the oldest planned one. Fails with conflicting_state while another is active.",args:["name"],json:!0},{name:"sprint list",summary:"Every sprint with its status.",json:!0},{name:"sprint burndown",summary:"One sprint against an even burn, folded from the journal. The active sprint when no name is given; `burndown` is null when there is none.",args:["name"],json:!0},{name:"task parent",summary:"Set a task\u2019s parent; `none` detaches it.",args:["ref","parent"],json:!0},{name:"task unblock",summary:"Remove a recorded blocker.",args:["ref","blocker"],json:!0},{name:"task cancel",summary:"Cancel a task. It stays in history and does not count as missed work. Accepts several refs.",args:["refs"],json:!0},{name:"task delete",summary:"Drop a task from the board. The journal is append-only: this writes a `task.deleted` event and the earlier events stay. Accepts several refs.",args:["refs"],json:!0},{name:"template save",summary:"Save default fields under a name, for `task add --template <name>`.",args:["name"],flags:["--description","--type","--priority","--assignee","--label","--estimate","--json"],json:!0},{name:"template list",summary:"Saved templates and their fields. Also the bare `template`.",json:!0},{name:"template delete",summary:"Remove a template.",args:["name"],json:!0},{name:"completion",summary:"Print the shell completion script for `--shell`, or the shell in $SHELL.",flags:["--shell","--json"],json:!0},{name:"completion install",summary:"Write the completion script where the shell looks. Refuses to overwrite a file kadence did not write unless --force.",flags:["--shell","--force","--json"],json:!0},{name:"ui",summary:"Interactive kanban board for a person at a terminal. No --json: an agent reads `board --json` instead."}]}}import{isAbsolute as qt,relative as Yt,dirname as Gt,resolve as Vt}from"node:path";import{existsSync as wt,mkdirSync as Oe,readdirSync as se,readFileSync as Z,renameSync as Pe,rmSync as vt,writeFileSync as Be}from"node:fs";import{join as D}from"node:path";var ft=["task.created","task.moved","task.assigned","task.commented","task.updated","task.cancelled","task.reopened","task.deleted","task.parent_set","task.blocked_by_added","task.blocked_by_removed","task.label_added","task.label_removed","task.time_logged","task.doc_linked","task.claimed","task.released","task.criterion_added","task.criterion_checked","task.criterion_unchecked","template.saved","template.deleted","board.configured","sprint.created","sprint.started","sprint.updated","sprint.closed","sprint.cancelled","sprint.task_added","milestone.created","milestone.task_added","milestone.closed","decision.recorded","note.recorded","doc.written","doc.linked"],pt=new Set(ft),re=/^[0-9ABCDEFGHJKMNPQRSTVWXYZ]{26}$/,gt=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})$/;function kt(t){return typeof t=="string"&&pt.has(t)}function yt(t){if(typeof t!="object"||t===null)return["event"];let r=t,n=[];if((typeof r.id!="string"||!re.test(r.id))&&n.push("id"),kt(r.type)||n.push("type"),(typeof r.entity!="string"||!re.test(r.entity))&&n.push("entity"),(typeof r.actor!="string"||r.actor.length===0)&&n.push("actor"),(typeof r.ts!="string"||!gt.test(r.ts)||Number.isNaN(Date.parse(r.ts)))&&n.push("ts"),r.source!=="human"&&r.source!=="agent"&&n.push("source"),r.data!==void 0&&(typeof r.data!="object"||r.data===null)&&n.push("data"),r.type==="decision.recorded"){let e=r.data??{};for(let s of["title","why"]){let a=e[s];(typeof a!="string"||a.trim().length===0)&&n.push(`data.${s}`)}}if(r.type==="doc.written"){let e=r.data??{};for(let a of["title","body"]){let i=e[a];(typeof i!="string"||i.trim().length===0)&&n.push(`data.${a}`)}let s=e.parents;(!Array.isArray(s)||!s.every(a=>typeof a=="string"&&re.test(a)))&&n.push("data.parents")}return n}var Ne=["description","body"];function Re(t){let r=Ne.filter(e=>{let s=t.data?.[e];return typeof s=="string"&&s.includes(`
149
+ `)});if(r.length===0)return JSON.stringify(t);let n={...t.data};for(let e of r)n[e]=n[e].split(`
150
+ `);return JSON.stringify({...t,data:n},null,2)}function M(t){let r;try{r=JSON.parse(t)}catch(e){return{event:null,error:`unreadable JSON: ${e.message}`,unknownType:!1}}r=bt(r);let n=yt(r);return n.length===1&&n[0]==="type"?{event:null,error:null,unknownType:!0}:n.length>0?{event:null,error:`invalid fields: ${n.join(", ")}`,unknownType:!1}:{event:r,error:null,unknownType:!1}}function bt(t){if(typeof t!="object"||t===null)return t;let r=t,n=r.data;if(typeof n!="object"||n===null)return t;let e=Ne.filter(a=>Array.isArray(n[a]));if(e.length===0)return t;let s={...n};for(let a of e)s[a]=s[a].filter(i=>typeof i=="string").join(`
151
+ `);return{...r,data:s}}var $t=.2;function U(t){return D(t,".kadence")}function P(t){return D(U(t),"events")}function z(t){return D(P(t),"archive")}function St(t){return t.slice(0,7)}function x(t,r){let n=D(P(t),St(r.ts));Oe(n,{recursive:!0});let e=D(n,`${r.id}.json`),s=D(n,`.${r.id}.tmp`);Be(s,`${Re(r)}
152
+ `,"utf8"),Pe(s,e)}function X(t){let r=P(t),n=[],e=[],s=new Set,a=0,i=Y(z(t)),d=new Set(i);for(let l of Y(r)){let u;try{u=Z(l,"utf8")}catch{e.push(l);continue}if(d.has(l)){let g;try{g=JSON.parse(u)}catch{e.push(l);continue}if(!Array.isArray(g)){e.push(l);continue}for(let f of g){let h=M(JSON.stringify(f));h.unknownType?a++:h.error!==null||h.event===null?e.push(l):s.has(h.event.id)||(s.add(h.event.id),n.push(h.event))}continue}let c=M(u);if(c.unknownType){a++;continue}if(c.error!==null||c.event===null){e.push(l);continue}s.has(c.event.id)||(s.add(c.event.id),n.push(c.event))}n.sort((l,u)=>l.id<u.id?-1:l.id>u.id?1:0);let o=n.length+e.length;return{events:n,corrupted:e,unknownTypes:a,systemicCorruption:o>0&&e.length/o>$t}}function Y(t){let r;try{r=se(t,{withFileTypes:!0})}catch{return[]}let n=[];for(let e of r){let s=D(t,e.name);e.isDirectory()?n.push(...Y(s)):e.name.endsWith(".json")&&n.push(s)}return n}function Ie(t){if(!wt(t))return[];try{let r=JSON.parse(Z(t,"utf8"));return Array.isArray(r)?r.map(n=>M(JSON.stringify(n)).event).filter(n=>n!==null):null}catch{return null}}function On(t,r){let n=P(t),e=[],s=0,a;try{a=se(n,{withFileTypes:!0})}catch{return{months:[],events:0}}for(let i of a){if(!i.isDirectory()||i.name==="archive"||i.name>=r)continue;let d=new Set((Ie(D(z(t),`${i.name}.json`))??[]).map(u=>u.id)),o=0,l=!1;for(let u of Y(D(n,i.name))){let c=M(Z(u,"utf8")).event;c===null?l=!0:d.has(c.id)||(o+=1)}l||o===0||(e.push({month:i.name,events:o}),s+=o)}return e.sort((i,d)=>i.month<d.month?-1:1),{months:e,events:s}}function Pn(t,r){let n=P(t),e=[],s=[],a=0,i;try{i=se(n,{withFileTypes:!0})}catch{return{archivedMonths:[],archivedEvents:0,skipped:[]}}for(let d of i){if(!d.isDirectory()||d.name==="archive"||d.name>=r)continue;let o=D(n,d.name),l=Y(o),u=[],c=!1;for(let b of l){let j=M(Z(b,"utf8"));j.event!==null?u.push(j.event):c=!0}if(c){s.push(o);continue}if(u.length===0)continue;Oe(z(t),{recursive:!0});let g=D(z(t),`${d.name}.json`),f=Ie(g);if(f===null){s.push(g);continue}let h=new Map;for(let b of f)h.set(b.id,b);let m=0;for(let b of u)h.has(b.id)||(m+=1),h.set(b.id,b);let $=[...h.values()].sort((b,j)=>b.id<j.id?-1:1),k=`${g}.tmp`;Be(k,JSON.stringify($),"utf8"),Pe(k,g),vt(o,{recursive:!0,force:!0}),e.push(d.name),a+=m}return{archivedMonths:e.sort(),archivedEvents:a,skipped:s.sort()}}import{randomFillSync as xt}from"node:crypto";var Fe="0123456789ABCDEFGHJKMNPQRSTVWXYZ",jt=10,Tt=16,Le=2**48-1;function Ct(t){let r="",n=t;for(let e=jt-1;e>=0;e--)r=Fe[n%32]+r,n=Math.floor(n/32);return r}function Et(t){let r="",n=0,e=0;for(let s of t){for(n=n<<8|s,e+=8;e>=5;)e-=5,r+=Fe[n>>>e&31];n&=(1<<e)-1}return r.slice(0,Tt)}function _t(t){for(let r=t.length-1;r>=0;r--){let n=t[r];if(n<255){t[r]=n+1;return}t[r]=0}throw new Error("ULID: random space exhausted within a single millisecond")}function Dt(){let t=-1,r=new Uint8Array(10);return function(e=Date.now()){if(!Number.isInteger(e)||e<0||e>Le)throw new RangeError(`ULID: time out of range 0..${Le}: ${e}`);return e>t?(t=e,xt(r)):_t(r),Ct(t)+Et(r)}}var S=Dt();import{readFileSync as At,writeFileSync as Nt,renameSync as Rt,readdirSync as Ot,mkdirSync as Pt,statSync as Bt}from"node:fs";import{join as Ue}from"node:path";var Me="kadence-snapshot/12";function Ke(t){return Ue(U(t),"state.json")}function Je(t,r){let n=It(P(t)),e=Lt(t);if(e!==null&&e.version===Me&&e.lastEventId===n.lastEventId&&e.eventCount===n.count&&e.eventBytes===n.bytes)return{state:e.state,fromCache:!0,incomingEvents:0,health:e.health};let s=X(t),a=me(s.events),i={corrupted:s.corrupted.length,total:s.corrupted.length+s.events.length,unknownTypes:s.unknownTypes,systemic:s.systemicCorruption},d=e===null||r===void 0?0:Mt(s.events,e,r);return i.corrupted===0&&Ft(t,{version:Me,lastEventId:n.lastEventId,eventCount:n.count,eventBytes:n.bytes,health:i,state:a}),{state:a,fromCache:!1,incomingEvents:d,health:i}}function It(t){let r="",n=0,e=0,s=a=>{let i;try{i=Ot(a,{withFileTypes:!0})}catch{return}for(let d of i){let o=Ue(a,d.name);if(d.isDirectory())s(o);else if(d.name.endsWith(".json")){n++;let l=d.name.slice(0,-5);l>r&&(r=l),e+=Bt(o,{throwIfNoEntry:!1})?.size??0}}};return s(t),{lastEventId:r,count:n,bytes:e}}function Lt(t){try{let r=JSON.parse(At(Ke(t),"utf8"));if(typeof r!="object"||r===null)return null;let n=r;if(typeof n.version!="string"||typeof n.lastEventId!="string"||typeof n.eventCount!="number"||typeof n.state!="object")return null;let e=n.health;return e===void 0||typeof e.corrupted!="number"||typeof e.total!="number"||typeof e.unknownTypes!="number"||typeof e.systemic!="boolean"?null:n}catch{return null}}function Ft(t,r){try{Pt(U(t),{recursive:!0});let n=Ke(t),e=`${n}.tmp`;Nt(e,JSON.stringify(r),"utf8"),Rt(e,n)}catch{}}function Mt(t,r,n){let e=0,s=0;for(let i of t)i.id<=r.lastEventId?e++:i.actor!==n&&s++;let a=Math.max(0,e-r.eventCount);return s+a}var K="\x1B[",T={dim:`${K}2m`,bold:`${K}1m`,green:`${K}32m`,yellow:`${K}33m`,blue:`${K}34m`,reset:`${K}0m`};function He(t,r,n=0){let e=t.length,s=Math.max(0,n),a=r??De,i=a<=0?t.slice(s):t.slice(s,s+a);return{shown:[...i],total:e,offset:s,more:s+i.length<e}}function qe(t,r){if(!t.more&&t.offset===0)return null;let n=t.offset+1,e=t.offset+t.shown.length,s=t.more?` (${r} --offset ${e} for the next)`:"";return`Showing ${n}\u2013${e} of ${t.total}.${s}`}function ie(t,r){return t.NO_COLOR!==void 0?!1:r}function A(t,r,n){return n?`${r}${t}${T.reset}`:t}var Ut={done:T.green,in_progress:T.blue,blocked:T.yellow,cancelled:T.dim},Kt=72;function Ye(t,r=Kt){let n=[...t];return n.length<=r?t:`${n.slice(0,r-1).join("")}\u2026`}function oe(t){return[...t].length}function We(t,r){let n=r-oe(t);return n>0?t+" ".repeat(n):t}function Ge(t,r){if(t.length===0)return`No tasks yet.
153
+ Create the first one:
154
+ kadence task add "title"`;let n=Math.max(...t.map(s=>oe(s.label)),2),e=Math.max(...t.map(s=>oe(s.status)),6);return t.map(s=>{let a=A(We(s.label,n),T.bold,r),i=A(We(s.status,e),Ut[s.status]??T.dim,r),d=s.estimate===null?"":A(` (${s.estimate})`,T.dim,r);return`${a} ${i} ${Ye(s.title)}${d}`}).join(`
155
+ `)}function ae(t){return t==="agent"?" [agent]":""}function Ve(t){return t<=0?null:`Merged ${t} ${t===1?"change":"changes"} from another branch, no conflicts.`}var Jt={urgent:"!!",high:"!",normal:"",low:"v"},Wt={bug:"BUG",story:"STORY",task:""};function ze(t,r){let n=[A(t.label,T.bold,r)],e=t.criteria.filter(d=>!d.checked).length;e>0&&n.push(A(`[${t.criteria.length-e}/${t.criteria.length}]`,T.yellow,r));let s=Jt[t.priority];s!==""&&n.push(A(s,t.priority==="low"?T.dim:T.yellow,r));let a=Wt[t.type]??"";a!==""&&n.push(A(a,T.dim,r)),n.push(Ye(t.title));let i=[];return t.assignee!==null&&i.push(`@${t.assignee.split("@")[0]}`),t.estimate!==null&&i.push(`${t.estimate}`),i.length>0&&n.push(A(`(${i.join(", ")})`,T.dim,r)),` ${n.join(" ")}`}function Hn(t,r){if(Object.values(t).reduce((s,a)=>s+a.length,0)===0)return`No tasks yet.
156
+ Create the first one:
157
+ kadence task add "title"`;let e=[];for(let[s,a]of Object.entries(t)){if(a.length===0)continue;let i=a.reduce((o,l)=>o+(l.estimate??0),0),d=i>0?` \u2014 ${i}`:"";e.push(A(`${s} (${a.length}${d})`,T.bold,r),...a.map(o=>ze(o,r)),"")}return e.join(`
158
+ `).trimEnd()}function Ht(t,r=new Date){let n=Date.parse(`${t}T00:00:00.000Z`),e=Date.parse(`${r.toISOString().slice(0,10)}T00:00:00.000Z`);if(Number.isNaN(n))return"";let s=Math.round((n-e)/864e5);return s<0?` (overdue by ${-s} day${s===-1?"":"s"})`:s===0?" (today)":s===1?" (tomorrow)":s<=7?` (in ${s} days)`:""}function Ze(t,r){if(t.length===0)return`No tasks yet.
159
+ Create the first one:
160
+ kadence task add "title"`;let n=new Set(t.map(d=>d.id)),e=new Map;for(let d of t){let o=d.parent!==null&&n.has(d.parent)?d.parent:null,l=e.get(o)??[];l.push(d),e.set(o,l)}let s=[],a=new Set,i=(d,o)=>{if(!(o>10))for(let l of e.get(d)??[]){if(a.has(l.id))continue;a.add(l.id);let u=" ".repeat(o),c=l.blockedBy.length>0?A(" [blocked]",T.yellow,r):"";s.push(`${u}${ze(l,r).trimStart()}${c}`),i(l.id,o+1)}};return i(null,0),s.join(`
161
+ `)}function Xe(t,r=[]){let n=[`${t.label} ${t.title}`,""];if(t.description!==null&&n.push(t.description,""),n.push(` Status: ${t.status}`),n.push(` Type: ${t.type}`),n.push(` Priority: ${t.priority}`),t.labels.length>0&&n.push(` Labels: ${t.labels.join(", ")}`),t.parent!==null&&n.push(` Parent: ${t.parent}`),t.blockedBy.length>0&&n.push(` Blocked by: ${t.blockedBy.length} task(s)`),n.push(` Assignee: ${t.assignee??"\u2014"}`),t.claimedBy!==null){let e=t.contestedBy.length>0?` (contested by ${t.contestedBy.join(", ")})`:"";n.push(` Claimed: ${t.claimedBy}${e}`)}if(n.push(` Reporter: ${t.reporter}`),t.due!==null&&n.push(` Due: ${t.due}${Ht(t.due)}`),n.push(` Estimate: ${t.estimate??"\u2014"}`),t.comments.length>0){n.push("",` Comments (${t.comments.length}):`);for(let e of t.comments){n.push(` ${e.author}${ae(e.source)} \xB7 ${e.ts.slice(0,10)}`);for(let s of e.text.split(`
162
+ `))n.push(` ${s}`)}}if(t.criteria.length>0){let e=t.criteria.filter(s=>s.checked).length;n.push("",` Acceptance criteria (${e}/${t.criteria.length}):`);for(let s of t.criteria)n.push(` ${s.n}. [${s.checked?"x":" "}] ${s.text}`)}if(r.length>0){n.push("",` Notes (${r.length}):`);for(let e of r){n.push(` ${e.by}${ae(e.source)} \xB7 ${e.at.slice(0,10)}`);for(let s of e.text.split(`
163
+ `))n.push(` ${s}`)}}if(t.history.length>0){n.push("",` History (${t.history.length}):`);for(let e of t.history){let s=e.type==="task.moved"?` \u2192 ${String(e.data.to)}`:"";n.push(` ${e.ts.slice(0,16).replace("T"," ")} ${e.actor}${ae(e.source)} ${e.type}${s}`)}}return n.join(`
164
+ `)}import{existsSync as Qe,mkdirSync as zt,writeFileSync as Zt}from"node:fs";var sr=ce;function p(t,r,n,e={}){return{ok:!1,exitCode:t,message:n,error:{code:r,message:n,...e}}}function _(t){return p(1,"task_not_found",`No task ${t}.
165
+ kadence task list`,{received:t,hint:"kadence task list --json"})}function J(t,r,n,e){return p(2,t,`Unknown ${r} "${n}".
166
+ Available: ${e.join(", ")}`,{received:n,allowed:e})}function w(t,r){let n=$e(t);if(n===null)return p(1,"not_a_repository",`kadence lives inside a git repository, and there is none here.
167
+ Create one and try again:
168
+ git init`,{hint:"git init"});if(!Qe(U(n)))return p(1,"not_initialised",`No .kadence/ found here.
169
+ Run:
170
+ npx kadence init`,{hint:"kadence init"});let e=xe(n);if(e===null)return p(1,"no_git_identity",`Git does not know who you are, so there is no author for the event.
171
+ Run:
172
+ git config user.email you@example.com`,{hint:"git config user.email you@example.com"});let s=r.KADENCE_SOURCE==="agent"?"agent":"human";return{root:n,actor:e,source:s}}function et(t,r){let n=r.KADENCE_ACTOR?.trim();if(n!==void 0&&n.length>0)return n;if(t.source!=="agent")return t.actor;let e=Se(t.root);return e===null?t.actor:`${t.actor}#${e}`}function v(t){return"root"in t}function ar(t,r,n,e={}){let s=w(t,r);if(!v(s))return s;let a=n.trim();if(a.length===0)return p(2,"invalid_argument","A task needs a title.",{hint:"kadence task list --json"});if(e.type!==void 0&&!N.includes(e.type))return J("unknown_type","type",e.type,N);if(e.priority!==void 0&&!R.includes(e.priority))return J("unknown_priority","priority",e.priority,R);let i,d=null;if(e.parent!==void 0){let{state:m}=y(s.root,s.actor);d=m;let $=E(m,e.parent);if($===void 0)return{..._(e.parent)};i=$.id}let o=S(),l={id:o,type:"task.created",entity:o,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{title:a,...e.description!==void 0?{description:e.description}:{},...e.type!==void 0?{type:e.type}:{},...e.priority!==void 0?{priority:e.priority}:{},...e.assignee!==void 0?{assignee:e.assignee}:{},...e.labels!==void 0&&e.labels.length>0?{labels:e.labels}:{},...e.due!==void 0?{due:e.due}:{},...i!==void 0?{parent:i}:{},...e.estimate!==void 0?{estimate:e.estimate}:{}}};x(s.root,l);let u=d??(e.noDod!==!0||e.estimate===void 0?y(s.root,s.actor).state:null),c=e.noDod===!0||u===null?[]:u.dod;for(let m of c)x(s.root,{id:S(),type:"task.criterion_added",entity:o,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{text:m}});let g=e.estimate===void 0?u?.sprints.find(m=>m.status==="active"):void 0,f=g===void 0?"":`
173
+ Without an estimate this task adds no points to sprint "${g.name}". Add --estimate.`,h=c.length===0?"":`
174
+ ${c.length} acceptance criteria from the board's definition of done.`;return{ok:!0,exitCode:0,message:`Created: ${a}${h}${f}`,data:{schema:"kadence/v1",ok:!0,task:{id:o,title:a},criteria:c.map((m,$)=>({n:$+1,text:m,checked:!1,checkedBy:null}))}}}function y(t,r){let n=Je(t,r),{health:e}=n,s=[],a=Ve(n.incomingEvents);return a!==null&&s.push(a),e.systemic?s.push(`The journal is badly damaged: ${e.corrupted} of ${e.total} events are unreadable. Try: git checkout .kadence/`):e.corrupted>0&&s.push(`Skipped ${e.corrupted} corrupted event(s).`),e.unknownTypes>0&&s.push(`Skipped ${e.unknownTypes} event(s) from a newer format. Update kadence.`),{state:n.state,warnings:s}}function E(t,r){let n=r.toUpperCase();return t.tasks.find(e=>e.id===n||e.label===n)}function tt(t,r){return p(2,"unknown_status",`Unknown status "${t}".
175
+ Available: ${r.join(", ")}
176
+ kadence board config --statuses "todo,doing,done"`,{received:t,allowed:r,hint:"kadence board config --json"})}function or(t,r,n){let e=w(t,r);if(!v(e))return e;if(n.type!==void 0&&!N.includes(n.type))return J("unknown_type","type",n.type,N);if(n.priority!==void 0&&!R.includes(n.priority))return J("unknown_priority","priority",n.priority,R);let{fields:s,error:a}=Xt(n.fields);if(a!==null)return a;if(n.sort!==void 0&&!we(n.sort))return{...p(2,"invalid_argument",`Unknown sort key "${n.sort}".
177
+ Available: ${te.join(", ")}`,{received:n.sort,allowed:te})};let{state:i,warnings:d}=y(e.root,e.actor);if(n.status!==void 0&&!i.statuses.includes(n.status))return tt(n.status,i.statuses);let{sort:o,tree:l,branch:u,base:c,json:g,...f}=n,h=pe(i.tasks,f),m=null;if(u===!0){let b=je(e.root);if(b===null)return p(1,"conflicting_state",`HEAD is detached, so there is no branch to compare.
178
+ Check one out, or name the range yourself:
179
+ git checkout main`,{hint:"git checkout main"});let j=c??Te(e.root),L=Ce(e.root,j,b);if(L===null)return p(2,"invalid_argument",`There is no branch "${j}" to compare against.
180
+ Name one that exists:
181
+ kadence task list --branch --base <name>`,{received:j,hint:"git branch --list"});m={name:b,base:j},h=h.filter(le=>L.has(le.id)||le.history.some(lt=>L.has(lt.id)))}let $=o===void 0?h:ge(h,o),k=He($,n.limit,n.offset);return $.length===0?{ok:!0,exitCode:0,warnings:d,message:m===null?ke(f):`Branch ${m.name} has introduced no task changes since ${m.base}.
182
+ See the whole board:
183
+ kadence task list`,data:{schema:"kadence/v1",ok:!0,tasks:[],tasksTotal:0,tasksOffset:0,...m===null?{}:{branch:m}}}:{ok:!0,exitCode:0,warnings:d,message:[l===!0?Ze(k.shown,ie(r,process.stdout.isTTY===!0)):Ge(k.shown,ie(r,process.stdout.isTTY===!0)),qe(k,"kadence task list")].filter(b=>b!==null).join(`
184
+ `),data:{schema:"kadence/v1",ok:!0,tasks:k.shown.map(b=>Qt(nt(b,s,i),s)),tasksTotal:k.total,tasksOffset:k.offset,cycles:i.cycles,...m===null?{}:{branch:m}}}}function ir(t,r,n,e){let s=w(t,r);if(!v(s))return s;let{state:a,warnings:i}=y(s.root,s.actor);if(!a.statuses.includes(e))return tt(e,a.statuses);let{tasks:d,error:o}=B(a,n);if(o!==null)return o;let l=[],u=[],c="";for(let h of d){if(h.status===e){u.push(h);continue}let m=h.status;x(s.root,{id:S(),type:"task.moved",entity:h.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{from:m,to:e}}),l.push(h),d.length===1&&a.statuses.indexOf(e)-a.statuses.indexOf(m)>1&&(c=`
185
+ Moved straight from ${m} to ${e}, skipping the stages between.`)}if(l.length===0)return{ok:!0,exitCode:0,warnings:i,message:C("already there",u,`is already ${e}.`)};let g=u.length>0?`
186
+ ${u.length} already ${e}.`:"",f=e===ee?l.filter(h=>h.criteria.some(m=>!m.checked)).map(h=>{let m=h.criteria.filter($=>!$.checked).length;return`${h.label} moved to ${e} with ${m} of ${h.criteria.length} acceptance criteria unchecked. See: kadence task ac list ${h.label}`}):[];return{ok:!0,exitCode:0,warnings:[...i,...f],message:`${C("moved",l,`${l[0].status} \u2192 ${e}`)}${c}${g}`,data:{schema:"kadence/v1",ok:!0,moved:l.map(h=>({id:h.id,label:h.label,to:e}))}}}function Xt(t){if(t===void 0)return{fields:null,error:null};let r=t.split(",").map(e=>e.trim()).filter(e=>e.length>0);if(r.length===0)return{fields:null,error:p(2,"invalid_argument","--fields needs at least one field name.",{allowed:F})};let n=r.find(e=>!F.includes(e));return n!==void 0?{fields:null,error:p(2,"unknown_field",`Unknown field "${n}".
187
+ Available: ${F.join(", ")}`,{received:n,allowed:F})}:{fields:r,error:null}}function Qt(t,r){if(r!==null)return t;let{history:n,...e}=t;return e}function nt(t,r=null,n=null){let e=en(t,n);return r===null?e:Object.fromEntries(r.map(s=>[s,e[s]]))}function en(t,r=null){return{id:t.id,label:t.label,title:t.title,description:t.description,type:t.type,priority:t.priority,status:t.status,labels:t.labels,assignee:t.assignee,reporter:t.reporter,sprint:t.sprint,milestone:t.milestone===null?null:r?.milestones.find(n=>n.id===t.milestone)?.label??null,loggedHours:t.loggedHours,parent:t.parent,blockedBy:t.blockedBy,due:t.due,claimedBy:t.claimedBy,contestedBy:t.contestedBy,comments:t.comments,docs:t.docs,estimate:t.estimate,criteria:t.criteria.map(n=>({n:n.n,text:n.text,checked:n.checked,checkedBy:n.checkedBy})),openCriteria:t.criteria.filter(n=>!n.checked).length,history:t.history}}function dr(t,r,n,e){let s=w(t,r);if(!v(s))return s;let{state:a,warnings:i}=y(s.root,s.actor),{tasks:d,error:o}=B(a,n);if(o!==null)return o;let l=e.trim().toLowerCase()==="none"?null:e.trim(),u=d.filter(c=>c.assignee!==l);if(u.length===0)return{ok:!0,exitCode:0,warnings:i,message:l===null?C("already unassigned",d,"is already unassigned."):C("already assigned",d,`is already assigned to ${l}.`)};for(let c of u)x(s.root,{id:S(),type:"task.assigned",entity:c.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{assignee:l}});return{ok:!0,exitCode:0,warnings:i,message:l===null?C("unassigned",u,"unassigned."):C("assigned",u,`\u2192 ${l}`),data:{schema:"kadence/v1",ok:!0,assigned:u.map(c=>({id:c.id,label:c.label,assignee:l}))}}}function lr(t,r,n){let e=w(t,r);if(!v(e))return e;let{state:s,warnings:a}=y(e.root,e.actor),i=E(s,n);if(i===void 0)return _(n);let d=s.decisions.filter(c=>c.task===i.id&&c.supersededBy===null).map(c=>({id:c.id,label:c.label,title:c.title,why:c.why,rejected:c.rejected,docs:c.docs,source:c.source})),o=s.notes.filter(c=>c.task===i.id),l=s.documents.filter(c=>c.tasks.includes(i.id)).map(c=>({id:c.id,label:c.label,title:c.title,bytes:Buffer.byteLength(c.body,"utf8"),updatedAt:c.updatedAt,conflicted:c.conflicts.length>0})),u=l.length===0?"":`
188
+
189
+ Documentation:
190
+ ${l.map(c=>` ${c.label} ${c.title} kadence doc show ${c.label}`).join(`
191
+ `)}`;return{ok:!0,exitCode:0,warnings:a,message:Xe(i,o)+u,data:{schema:"kadence/v1",ok:!0,task:{...nt(i,null,s),decisions:d,notes:o.map(c=>({id:c.id,text:c.text,at:c.at,by:c.by,source:c.source})),documentation:l}}}}function tn(t,r){let n=a=>a.map(i=>i.trim()).filter(i=>i.length>0),e=r.labels!==void 0?[...new Set(n(r.labels))]:[...new Set([...t.labels.filter(a=>!n(r.removeLabels??[]).includes(a)),...n(r.addLabels??[])])],s=[];for(let a of t.labels)e.includes(a)||s.push({type:"task.label_removed",label:a});for(let a of e)t.labels.includes(a)||s.push({type:"task.label_added",label:a});return s}function cr(t,r,n,e){let s=w(t,r);if(!v(s))return s;if(e.type!==void 0&&!N.includes(e.type))return J("unknown_type","type",e.type,N);if(e.priority!==void 0&&!R.includes(e.priority))return J("unknown_priority","priority",e.priority,R);if(e.due!==void 0&&e.due!==""&&!nn(e.due))return{...p(2,"invalid_argument",`Due date must be YYYY-MM-DD, got "${e.due}".
192
+ kadence task edit KAD-1 --due 2026-09-30`,{received:e.due})};if(e.labels!==void 0&&(e.addLabels!==void 0||e.removeLabels!==void 0))return p(2,"invalid_argument","--label replaces the whole set; --add-label and --remove-label change one. Use one or the other.",{hint:"kadence task edit KAD-1 --add-label impact-high"});let a=(e.addLabels??[]).filter(f=>(e.removeLabels??[]).includes(f));if(a.length>0)return p(2,"invalid_argument",`A label cannot be added and removed in one edit: ${a.join(", ")}.`,{received:a.join(","),hint:"kadence task edit KAD-1 --add-label impact-high"});let{state:i,warnings:d}=y(s.root,s.actor),{tasks:o,error:l}=B(i,n);if(l!==null)return l;if(e.title!==void 0&&o.length>1)return{...p(2,"invalid_argument","A title can only be set on one task at a time.")};let u=[],c=new Set;for(let f of o){let h={},m=[];e.title!==void 0&&e.title!==f.title&&(h.title=e.title,m.push("title")),e.description!==void 0&&e.description!==(f.description??"")&&(h.description=e.description,m.push("description")),e.type!==void 0&&e.type!==f.type&&(h.type=e.type,m.push("type")),e.priority!==void 0&&e.priority!==f.priority&&(h.priority=e.priority,m.push("priority")),e.due!==void 0&&e.due!==(f.due??"")&&(h.due=e.due,m.push("due")),e.estimate!==void 0&&e.estimate!==f.estimate&&(h.estimate=e.estimate,m.push("estimate"));let $=e.labels===void 0&&e.addLabels===void 0&&e.removeLabels===void 0?[]:tn(f,e);if($.length>0&&m.push("labels"),m.length!==0){Object.keys(h).length>0&&x(s.root,{id:S(),type:"task.updated",entity:f.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:h});for(let k of $)x(s.root,{id:S(),type:k.type,entity:f.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{label:k.label}});u.push(f);for(let k of m)c.add(k)}}if(u.length===0)return{ok:!0,exitCode:0,warnings:d,message:C("unchanged",o,"nothing changed.")};let g=[...c].join(", ");return{ok:!0,exitCode:0,warnings:d,message:C("updated",u,`updated ${g}.`),data:{schema:"kadence/v1",ok:!0,updated:u.map(f=>({id:f.id,label:f.label})),changed:[...c]}}}function nn(t){if(!/^\d{4}-\d{2}-\d{2}$/.test(t))return!1;let r=new Date(`${t}T00:00:00.000Z`);return!Number.isNaN(r.getTime())&&r.toISOString().slice(0,10)===t}function ur(t,r,n){let e=w(t,r);if(!v(e))return e;let{state:s,warnings:a}=y(e.root,e.actor),{tasks:i,error:d}=B(s,n);if(d!==null)return d;let o=i.filter(l=>l.status!=="cancelled");if(o.length===0)return{ok:!0,exitCode:0,warnings:a,message:C("already cancelled",i,"is already cancelled.")};for(let l of o)x(e.root,{id:S(),type:"task.cancelled",entity:l.id,actor:e.actor,ts:new Date().toISOString(),source:e.source,data:{}});return{ok:!0,exitCode:0,warnings:a,message:`${C("cancelled",o,"cancelled.")}
193
+ Cancelled work stays in history and does not count as missed.`,data:{schema:"kadence/v1",ok:!0,cancelled:o.map(l=>({id:l.id,label:l.label}))}}}function mr(t,r,n){let e=w(t,r);if(!v(e))return e;let{state:s,warnings:a}=y(e.root,e.actor),{tasks:i,error:d}=B(s,n);if(d!==null)return d;for(let l of i)x(e.root,{id:S(),type:"task.deleted",entity:l.id,actor:e.actor,ts:new Date().toISOString(),source:e.source,data:{title:l.title}});let o=i.length===1?`${i[0].label} deleted: "${i[0].title}".`:`${i.length} tasks deleted: ${i.map(l=>l.label).join(", ")}.`;return{ok:!0,exitCode:0,warnings:a,message:`${o}
194
+ The events stay in the journal \u2014 history is never rewritten.`,data:{schema:"kadence/v1",ok:!0,deleted:i.map(l=>({id:l.id,label:l.label}))}}}function rn(t,r){return[`# ${t}`,"",`Written for ${r}. Link it back with \`kadence task show ${r}\`.`,"",""].join(`
195
+ `)}var rt=/^[a-z][a-z0-9+.-]*:\/\//i;function sn(t,r,n){let e=r.trim();if(e.length===0)return p(2,"invalid_argument","A path is required.",{hint:n});if(rt.test(e))return p(2,"invalid_argument",`${r} is a URL. kadence links documents that live in the repository:
196
+ ${n}`,{received:r,hint:n});let s=Vt(t,e),a=Yt(t,s);return a.length===0||a.startsWith("..")||qt(a)?p(2,"invalid_argument",`${r} is outside the repository.
197
+ Use a path inside it:
198
+ ${n}`,{received:r,hint:n}):{rel:a,full:s}}function hr(t,r,n,e,s=!1){let a=w(t,r);if(!v(a))return a;if(e.trim().length===0)return p(2,"invalid_argument","A link needs a path.",{hint:"kadence task doc KAD-1 docs/design.md"});let{state:i,warnings:d}=y(a.root,a.actor),o=E(i,n);if(o===void 0)return _(n);if(rt.test(e.trim()))return p(2,"invalid_argument",`${e} is a URL. kadence keeps documentation in the repository's journal, not as links:
199
+ kadence doc add "${o.title}" --file <path> --task ${o.label}`,{received:e,hint:`kadence doc add "\u2026" --body "\u2026" --task ${o.label}`});let l=sn(a.root,e,"kadence task doc KAD-1 docs/design.md");if("exitCode"in l)return l;let{rel:u,full:c}=l,g=!Qe(c),f=!1;return s&&g&&(zt(Gt(c),{recursive:!0}),Zt(c,rn(o.title,o.label),"utf8"),g=!1,f=!0),x(a.root,{id:S(),type:"task.doc_linked",entity:o.id,actor:a.actor,ts:new Date().toISOString(),source:a.source,data:{path:u}}),{ok:!0,exitCode:0,warnings:[...d,...g?[`No file at ${u} \u2014 the link is recorded anyway; it may arrive later.`]:[]],message:f?`Created ${u} and linked it to ${o.label}.`:`${u} linked to ${o.label}.`,data:{schema:"kadence/v1",ok:!0,task:{id:o.id,label:o.label},doc:u,created:f}}}function fr(t,r,n,e={}){let s=w(t,r);if(!v(s))return s;let a=et(s,r),{state:i,warnings:d}=y(s.root,s.actor),o;if(n===void 0||n.trim().length===0){let c=e.assignee===void 0?{}:{assignee:e.assignee},g={statuses:i.statuses,started:i.started},f=ye(i.tasks,{viewer:a,...g,...c});if(o=f.find(h=>h.claimedBy===null||h.claimedBy===a),o===void 0){let h=f.filter(m=>m.claimedBy!==null);if(h.length>0){let m=h.map($=>`${$.label} (${$.claimedBy}${$.contestedBy.length>0?", contested":""})`).join(", ");return p(1,"nothing_ready",`Nothing free to claim. ${h.length===1?"The one task":`All ${h.length} tasks`} ready for you ${h.length===1?"is":"are"} already claimed: ${m}.
200
+ Name one to join the contest on purpose, or pick something else:
201
+ kadence task claim ${h[0].label}
202
+ kadence task list`,{hint:`kadence task claim ${h[0].label}`})}return p(1,"nothing_ready",be(i.tasks,a,e.assignee,g),{hint:"kadence task list"})}}else if(o=E(i,n),o===void 0)return _(n);if(o.status===ee||o.status===ue)return p(1,"conflicting_state",`${o.label} is ${o.status}, so there is nothing to claim.
203
+ Reopen it first:
204
+ kadence task move ${o.label} in_progress`,{hint:`kadence task move ${o.label} in_progress`});if(o.claimedBy===a||o.contestedBy.includes(a)){let c=o.claimedBy===a;return{ok:!0,exitCode:0,warnings:d,message:c?`${o.label} is already yours.`:`${o.label} already carries your contested claim; ${o.claimedBy??"nobody"} holds it.`,data:{schema:"kadence/v1",ok:!0,claim:c?"already_yours":"contested",task:{id:o.id,label:o.label,claimedBy:o.claimedBy,contestedBy:o.contestedBy}}}}x(s.root,{id:S(),type:"task.claimed",entity:o.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{by:a}});let l=y(s.root,s.actor).state.tasks.find(c=>c.id===o.id)??o,u=l.claimedBy===a;return{ok:!0,exitCode:0,warnings:u?d:[...d,`${l.label} is contested: ${l.claimedBy??"nobody"} claimed it first and keeps it. Both claims stay in the journal \u2014 talk to them before starting.`],message:u?`${l.label} ${l.title} \u2014 claimed by ${a}.
205
+ Another machine may have claimed it before your push; the merge will show that.`:`${l.label} claim recorded, contested with ${l.claimedBy??"nobody"}.`,data:{schema:"kadence/v1",ok:!0,claim:u?"claimed":"contested",task:{id:l.id,label:l.label,claimedBy:l.claimedBy,contestedBy:l.contestedBy}}}}function pr(t,r,n){let e=w(t,r);if(!v(e))return e;let s=et(e,r),{state:a,warnings:i}=y(e.root,e.actor),d=E(a,n);if(d===void 0)return _(n);let o=d.claimedBy===s,l=d.contestedBy.includes(s);return!o&&!l?{ok:!0,exitCode:0,warnings:i,message:d.claimedBy===null?`${d.label} is not claimed.`:`${d.label} is claimed by ${d.claimedBy}, not by you. Nothing released.`,data:{schema:"kadence/v1",ok:!0,task:{id:d.id,label:d.label,claimedBy:d.claimedBy}}}:(x(e.root,{id:S(),type:"task.released",entity:d.id,actor:e.actor,ts:new Date().toISOString(),source:e.source,data:{by:s}}),{ok:!0,exitCode:0,warnings:i,message:o?`${d.label} released.`:`${d.label} withdrawn from \u2014 ${d.claimedBy??"nobody"} keeps the claim.`,data:{schema:"kadence/v1",ok:!0,task:{id:d.id,label:d.label,claimedBy:o?null:d.claimedBy,contestedBy:o?[]:d.contestedBy.filter(u=>u!==s)}}})}function gr(t,r,n,e){let s=w(t,r);if(!v(s))return s;let a=e.trim();if(a.length===0)return p(2,"invalid_argument","A criterion needs text.",{hint:`kadence task ac add ${n} "tests green"`});let{state:i,warnings:d}=y(s.root,s.actor),o=E(i,n);if(o===void 0)return _(n);x(s.root,{id:S(),type:"task.criterion_added",entity:o.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{text:a}});let l=o.criteria.length+1;return{ok:!0,exitCode:0,warnings:d,message:`${o.label} criterion ${l}: ${a}`,data:{schema:"kadence/v1",ok:!0,task:{id:o.id,label:o.label},criterion:{n:l,text:a,checked:!1,checkedBy:null}}}}function an(t,r){let n=Number(r),e=t.criteria.map(a=>a.n);if(!Number.isInteger(n))return p(2,"invalid_argument",`"${r}" is not a criterion number.`,{received:r,hint:`kadence task ac list ${t.label}`});let s=t.criteria.find(a=>a.n===n);return s===void 0?p(2,"invalid_argument",t.criteria.length===0?`${t.label} has no criteria yet.
206
+ kadence task ac add ${t.label} "tests green"`:`${t.label} has no criterion ${n}.
207
+ Available: ${e.join(", ")}`,{received:r,hint:`kadence task ac list ${t.label}`}):s}function kr(t,r,n,e,s){let a=w(t,r);if(!v(a))return a;let{state:i,warnings:d}=y(a.root,a.actor),o=E(i,n);if(o===void 0)return _(n);let l=an(o,e);if(!("id"in l))return l;let u=!s;if(l.checked===u)return{ok:!0,exitCode:0,warnings:d,message:`${o.label} criterion ${l.n} is already ${u?"checked":"unchecked"}.`,data:{schema:"kadence/v1",ok:!0,task:{id:o.id,label:o.label}}};x(a.root,{id:S(),type:u?"task.criterion_checked":"task.criterion_unchecked",entity:o.id,actor:a.actor,ts:new Date().toISOString(),source:a.source,data:{criterion:l.id}});let c=o.criteria.filter(g=>g.id===l.id?u:g.checked).length;return{ok:!0,exitCode:0,warnings:d,message:`${o.label} criterion ${l.n} ${u?"checked":"unchecked"} \u2014 ${c} of ${o.criteria.length} done.`,data:{schema:"kadence/v1",ok:!0,task:{id:o.id,label:o.label},criterion:{n:l.n,text:l.text,checked:u,checkedBy:u?a.actor:null},done:c,total:o.criteria.length}}}function yr(t,r,n){let e=w(t,r);if(!v(e))return e;let{state:s,warnings:a}=y(e.root,e.actor),i=E(s,n);return i===void 0?_(n):i.criteria.length===0?{ok:!0,exitCode:0,warnings:a,message:`${i.label} has no acceptance criteria.
208
+ Add one:
209
+ kadence task ac add ${i.label} "tests green"`,data:{schema:"kadence/v1",ok:!0,task:{id:i.id,label:i.label},criteria:[]}}:{ok:!0,exitCode:0,warnings:a,message:i.criteria.map(d=>` ${d.n}. [${d.checked?"x":" "}] ${d.text}`).join(`
210
+ `),data:{schema:"kadence/v1",ok:!0,task:{id:i.id,label:i.label},criteria:on(i)}}}function on(t){return t.criteria.map(r=>({n:r.n,text:r.text,checked:r.checked,checkedBy:r.checkedBy}))}function br(t,r,n,e){let s=w(t,r);if(!v(s))return s;let a=e.trim();if(a.length===0)return p(2,"invalid_argument","A comment needs text.",{received:n});let{state:i,warnings:d}=y(s.root,s.actor),o=E(i,n);return o===void 0?_(n):(x(s.root,{id:S(),type:"task.commented",entity:o.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{text:a}}),{ok:!0,exitCode:0,warnings:d,message:`Comment added to ${o.label}.`,data:{schema:"kadence/v1",ok:!0,task:{id:o.id,label:o.label}}})}function B(t,r){let n=r.split(",").map(i=>i.trim()).filter(i=>i.length>0);if(n.length===0)return{tasks:[],error:p(2,"invalid_argument","No task given.")};let e=[],s=[];for(let i of n){let d=E(t,i);d===void 0?s.push(i):e.push(d)}return s.length>0?{tasks:[],error:p(1,"task_not_found",`No task ${s.join(", ")} \u2014 nothing was changed.
211
+ All ids must exist before a bulk change runs.
212
+ kadence task list`,{received:s.join(","),hint:"kadence task list --json"})}:{tasks:[...new Map(e.map(i=>[i.id,i])).values()],error:null}}function C(t,r,n){return r.length===1?`${r[0].label}: ${n}`:`${r.length} tasks ${t}: ${r.map(e=>e.label).join(", ")}`}function wr(t,r,n,e){let s=w(t,r);if(!v(s))return s;let{state:a,warnings:i}=y(s.root,s.actor),{tasks:d,error:o}=B(a,n);if(o!==null)return o;let l=e.trim().toLowerCase()==="none",u=null;if(!l){let f=E(a,e);if(f===void 0)return _(e);if(d.some(h=>h.id===f.id))return p(2,"invalid_argument","A task cannot be its own parent.",{received:n});u=f.id}let c=d.filter(f=>f.parent!==u);if(c.length===0)return{ok:!0,exitCode:0,warnings:i,message:C("unchanged",d,"already there.")};for(let f of c)x(s.root,{id:S(),type:"task.parent_set",entity:f.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{parent:u}});let g=l?"detached":E(a,e).label;return{ok:!0,exitCode:0,warnings:[...i,...st(s.root,s.actor)],message:l?C("detached",c,"detached from its parent."):C("moved",c,`\u2192 child of ${g}`),data:{schema:"kadence/v1",ok:!0,parent:u}}}function vr(t,r,n,e,s){let a=w(t,r);if(!v(a))return a;let{state:i,warnings:d}=y(a.root,a.actor),{tasks:o,error:l}=B(i,n);if(l!==null)return l;let u=E(i,e);if(u===void 0)return _(e);if(o.some(g=>g.id===u.id))return p(2,"invalid_argument","A task cannot block itself.",{received:n});let c=o.filter(g=>s?g.blockedBy.includes(u.id):!g.blockedBy.includes(u.id));if(c.length===0)return{ok:!0,exitCode:0,warnings:d,message:C("unchanged",o,s?"was not blocked by it.":"is already blocked by it.")};for(let g of c)x(a.root,{id:S(),type:s?"task.blocked_by_removed":"task.blocked_by_added",entity:g.id,actor:a.actor,ts:new Date().toISOString(),source:a.source,data:{blocker:u.id}});return{ok:!0,exitCode:0,warnings:[...d,...st(a.root,a.actor)],message:s?C("unblocked",c,`no longer blocked by ${u.label}.`):C("blocked",c,`blocked by ${u.label}.`),data:{schema:"kadence/v1",ok:!0,blocker:u.id}}}function st(t,r){let{state:n}=y(t,r),e=new Map(n.tasks.map(s=>[s.id,s.label]));return n.cycles.map(s=>{let a=s.path.map(i=>e.get(i)??i).join(" \u2192 ");return`Dependency cycle (${s.kind}): ${a}. Both edges were kept \u2014 resolve it when you can.`})}function dn(t){let r=/^(-?\d+(?:\.\d+)?)\s*(h|hours?|m|min|minutes?)?$/i.exec(t.trim());if(r===null)return null;let n=Number(r[1]);return!Number.isFinite(n)||n===0?null:(r[2]??"h").toLowerCase().startsWith("m")?n/60:n}function $r(t,r,n,e){let s=w(t,r);if(!v(s))return s;let a=dn(e);if(a===null)return{...p(2,"invalid_argument",`Cannot read "${e}" as a duration.
213
+ Use hours or minutes:
214
+ kadence task log KAD-1 2h
215
+ kadence task log KAD-1 90m
216
+ kadence task log KAD-1 -30m to correct a mistake`,{received:e})};let{state:i,warnings:d}=y(s.root,s.actor),o=E(i,n);if(o===void 0)return _(n);x(s.root,{id:S(),type:"task.time_logged",entity:o.id,actor:s.actor,ts:new Date().toISOString(),source:s.source,data:{hours:a}});let l=Math.max(0,o.loggedHours+a),u=o.estimate===null?`
217
+ No estimate on this task, so there is nothing to compare against.`:"";return{ok:!0,exitCode:0,warnings:d,message:`${o.label}: ${l.toFixed(1)}h logged in total.${u}`,data:{schema:"kadence/v1",ok:!0,task:{id:o.id,label:o.label,loggedHours:l}}}}function Q(t,r){let n=t.sprints.find(m=>m.id===r);if(n===void 0)return null;let e=t.tasks.filter(m=>m.sprint===r),s=e.filter(m=>m.status==="cancelled"),a=e.filter(m=>m.status!=="cancelled"),i=a.filter(m=>m.status==="done"),d=a.filter(m=>m.status!=="done"),o=W(a.map(m=>m.estimate??0)),l=W(i.map(m=>m.estimate??0)),u=i.filter(m=>m.estimate===null),c=i.map(m=>ln(m,t)).filter(m=>m!==null),g=c.length>0?W(c):null,f=W(i.filter(m=>m.estimate!==null).map(m=>m.estimate)),h=g!==null&&f>0?g/f:null;return{id:n.id,name:n.name,status:n.status,committed:o,velocity:l,actualHours:g,hoursPerPoint:h,done:i,carriedOver:d,cancelled:s,unestimated:u,loggedHours:W(e.map(m=>m.loggedHours))}}function ln(t,r){let n=he(t,r.started,r.statuses,r.startedChanges),e=fe(t);if(n===null||e===null)return null;let s=Date.parse(e)-Date.parse(n);return s>0?s/36e5:null}function W(t){return t.reduce((r,n)=>r+n,0)}var cn=4;function jr(t,r=8){let n=t.sprints.filter(o=>o.status==="closed").sort((o,l)=>(o.closedBy??"")<(l.closedBy??"")?-1:1).slice(-r),e=[];for(let o of n){let l=Q(t,o.id);l!==null&&e.push({name:l.name,committed:l.committed,velocity:l.velocity,carriedOver:W(l.carriedOver.map(u=>u.estimate??0)),unestimated:l.unestimated.length})}let s=[];if(e.length===0)return s.push("No closed sprint yet. Velocity is what a finished sprint leaves behind."),{rows:[],median:null,low:null,high:null,notes:s};let a=e.map(o=>o.velocity).sort((o,l)=>o-l),i=a[Math.floor((a.length-1)/2)];return e.length<cn&&s.push(`${e.length} closed sprint${e.length===1?"":"s"}. Velocity takes four to eight to stabilise, so treat this as a sighting rather than a forecast.`),e.filter(o=>o.committed>o.velocity*1.5).length===e.length&&e.length>=2&&s.push("Every sprint here took in half again more than it finished."),{rows:e,median:i,low:a[0],high:a[a.length-1],notes:s}}function G(t){return new Date(t).toISOString().slice(0,10)}function un(t,r){let n=[];for(let e=Date.parse(`${t}T00:00:00.000Z`);e<=Date.parse(`${r}T00:00:00.000Z`);e+=864e5)n.push(G(e));return n}function at(t,r,n,e=G(Date.now())){let s=t.tasks.filter(k=>k.sprint===n.id&&k.status!=="cancelled"),a=new Map(s.map(k=>[k.id,k.estimate??0])),i=[...a.values()].reduce((k,b)=>k+b,0),d=r.filter(k=>a.has(k.entity));if(d.length===0)return null;let o=n.startDate??G(d[0].ts),l=n.endDate??G(d.at(-1).ts),u=n.status==="closed"||l<e?l:e;if(u<o)return null;let c=new Set,g=new Map;for(let k of d){let b=G(k.ts);if(b<o)continue;let j=k.type==="task.moved"?k.data?.to:void 0,L=a.get(k.entity)??0;j==="done"&&!c.has(k.entity)?(c.add(k.entity),g.set(b,(g.get(b)??0)+L)):j!==void 0&&j!=="done"&&c.has(k.entity)&&(c.delete(k.entity),g.set(b,(g.get(b)??0)-L))}let f=un(o,u),h=Math.max(f.length-1,1),m=i,$=f.map((k,b)=>{let j=g.get(k)??0;return m-=j,{date:k,remaining:Math.max(0,m),ideal:Math.max(0,i-i/h*b),completed:j}});return{sprintName:n.name,committed:i,finalRemaining:n.status==="closed"?$[$.length-1]?.remaining??0:null,days:$}}function ot(t,r=40){if(t.committed===0)return`"${t.sprintName}": nothing to burn down \u2014 no estimated tasks.`;let n=[`"${t.sprintName}" \u2014 ${t.committed} points committed`,""];for(let s of t.days){let a=Math.round(s.remaining/t.committed*r),i=Math.round(s.ideal/t.committed*r),d=[];for(let u=0;u<r;u++)u===i&&u>=a?d.push("\u250A"):u<a?d.push(u===i?"\u2503":"\u2588"):d.push(" ");let o=s.remaining-s.ideal,l=Math.abs(o)<.5?"":o>0?` +${o.toFixed(0)}`:` ${o.toFixed(0)}`;n.push(`${s.date.slice(5)} ${d.join("")} ${String(s.remaining).padStart(3)}${l}`)}let e=t.days.at(-1);return e!==void 0&&(n.push(""),n.push(e.remaining===0?" All committed work is done.":` ${e.remaining} of ${t.committed} points remain.`+(e.remaining>e.ideal?" Behind the ideal line.":" On or ahead of the line."))),n.join(`
218
+ `)}function I(t){return t.sprints.find(r=>r.status==="active")}function mn(t){return t.sprints.filter(r=>r.status==="planned")}function H(t,r){let n=r.trim().toLowerCase();return t.sprints.find(e=>e.name.toLowerCase()===n)}function q(t,r,n,e){x(t.root,{id:S(),type:r,entity:n,actor:t.actor,ts:new Date().toISOString(),source:t.source,data:e})}function Or(t,r,n){let e=w(t,r);if(!v(e))return e;let s=n.trim();if(s.length===0)return p(2,"invalid_argument","A sprint needs a name.",{hint:"kadence sprint list --json"});let{state:a,warnings:i}=y(e.root,e.actor);if(H(a,s)!==void 0)return p(2,"invalid_argument",`Sprint "${s}" already exists.`,{received:s,hint:"kadence sprint list --json"});let d=I(a)!==void 0,o=S();return q(e,"sprint.created",o,{name:s}),d||q(e,"sprint.started",o,{}),{ok:!0,exitCode:0,warnings:i,message:d?`Sprint "${s}" planned.
219
+
220
+ kadence sprint add KAD-1 --sprint "${s}"
221
+ kadence sprint start "${s}"`:`Sprint "${s}" started.
222
+
223
+ kadence sprint add KAD-1
224
+ kadence sprint status`,data:{schema:"kadence/v1",ok:!0,sprint:{id:o,name:s,status:d?"planned":"active"}}}}function Pr(t,r,n,e={}){let s=w(t,r);if(!v(s))return s;let{state:a,warnings:i}=y(s.root,s.actor),d=e.sprint===void 0?I(a):H(a,e.sprint);if(d===void 0)return e.sprint===void 0?{...p(1,"sprint_not_found",`No active sprint.
225
+ kadence sprint create "Sprint 1"`,{hint:"kadence sprint list --json"})}:p(1,"sprint_not_found",`No sprint named "${e.sprint}".
226
+ kadence sprint list`,{received:e.sprint,hint:"kadence sprint list --json"});if(d.status==="closed")return p(1,"conflicting_state",`Sprint "${d.name}" is closed \u2014 its velocity is not rewritten.`,{received:d.name});let o=E(a,n);if(o===void 0)return _(n);if(o.sprint===d.id)return{ok:!0,exitCode:0,warnings:i,message:`${o.label} is already in the sprint.`};q(s,"sprint.task_added",d.id,{task:o.id});let l=o.estimate===null?`
227
+ Without an estimate this task adds no points to sprint "${d.name}". Add --estimate.`:"";return{ok:!0,exitCode:0,warnings:i,message:`${o.label} \u2192 "${d.name}"${l}`,data:{schema:"kadence/v1",ok:!0,task:{id:o.id,label:o.label},sprint:d.id}}}function Br(t,r){let n=w(t,r);if(!v(n))return n;let{state:e,warnings:s}=y(n.root,n.actor),a=I(e);if(a===void 0)return{...p(1,"sprint_not_found",`No active sprint.
228
+ kadence sprint create "Sprint 1"`,{hint:"kadence sprint list --json"})};let i=Q(e,a.id);q(n,"sprint.closed",a.id,{velocity:i.velocity});let d=[...s];return i.unestimated.length>0&&d.push(`${i.unestimated.length} completed task(s) without an estimate were left out of velocity: `+i.unestimated.map(o=>o.label).join(", ")),{ok:!0,exitCode:0,warnings:d,message:hn(i),data:{schema:"kadence/v1",ok:!0,report:it(i)}}}function Ir(t,r){let n=w(t,r);if(!v(n))return n;let{state:e,warnings:s}=y(n.root,n.actor),a=I(e);if(a===void 0)return{ok:!0,exitCode:0,warnings:s,message:`No active sprint.
229
+ kadence sprint create "Sprint 1"`,data:{schema:"kadence/v1",ok:!0,sprint:null}};let i=Q(e,a.id),d=[`"${i.name}" \u2014 ${i.velocity} of ${i.committed} points`,"",...i.done.map(o=>` \u2713 ${o.label} ${o.title}`),...i.carriedOver.map(o=>` \xB7 ${o.label} ${o.title} (${o.status})`)];return{ok:!0,exitCode:0,warnings:s,message:d.join(`
230
+ `),data:{schema:"kadence/v1",ok:!0,report:it(i)}}}function hn(t){let r=[`Sprint "${t.name}" closed.`,""];if(r.push(` Velocity: ${t.velocity} of ${t.committed} points`),t.hoursPerPoint!==null&&t.actualHours!==null&&t.actualHours>=1/60&&r.push(` Actual: ${de(t.actualHours)} \u2014 ${de(t.hoursPerPoint)} per point`),t.loggedHours>0&&r.push(` Logged: ${de(t.loggedHours)} entered by hand`),t.carriedOver.length>0){r.push("",` Carried over (${t.carriedOver.length}):`);for(let n of t.carriedOver)r.push(` \xB7 ${n.label} ${n.title}`)}return t.cancelled.length>0&&r.push("",` Cancelled (${t.cancelled.length}) \u2014 not counted towards velocity`),r.join(`
231
+ `)}function de(t){return t<1?`${Math.round(t*60)}m`:t<10?`${t.toFixed(1)}h`:`${Math.round(t)}h`}function it(t){return{id:t.id,name:t.name,status:t.status,velocity:t.velocity,committed:t.committed,actualHours:t.actualHours,hoursPerPoint:t.hoursPerPoint,done:t.done.map(r=>r.label),carriedOver:t.carriedOver.map(r=>r.label),cancelled:t.cancelled.map(r=>r.label),unestimated:t.unestimated.map(r=>r.label),loggedHours:t.loggedHours}}function Lr(t,r,n){let e=w(t,r);if(!v(e))return e;let{state:s,warnings:a}=y(e.root,e.actor),i=I(s);if(i!==void 0)return{...p(1,"conflicting_state",`Sprint "${i.name}" is still active.
232
+ Close it so velocity can be computed:
233
+ kadence sprint close`,{received:i.name})};let d=mn(s),o=n===void 0?d[0]:H(s,n);return o===void 0?{...p(1,"sprint_not_found",n===void 0?`No planned sprints.
234
+ kadence sprint create "Sprint 2"`:`No sprint named "${n}".
235
+ kadence sprint list`,{...n!==void 0?{received:n}:{},hint:"kadence sprint list --json"})}:o.status!=="planned"?p(1,"conflicting_state",`Sprint "${o.name}" is already ${o.status}.`,{received:o.name}):(q(e,"sprint.started",o.id,{}),{ok:!0,exitCode:0,warnings:a,message:`Sprint "${o.name}" started.
236
+
237
+ kadence sprint status`,data:{schema:"kadence/v1",ok:!0,sprint:{id:o.id,name:o.name}}})}function Fr(t,r){let n=w(t,r);if(!v(n))return n;let{state:e,warnings:s}=y(n.root,n.actor);if(e.sprints.length===0)return{ok:!0,exitCode:0,warnings:s,message:`No sprints yet.
238
+ kadence sprint create "Sprint 1"`,data:{schema:"kadence/v1",ok:!0,sprints:[]}};let a=e.sprints.map(i=>{let d=e.tasks.filter(l=>l.sprint===i.id),o=d.reduce((l,u)=>l+(u.estimate??0),0);return{sprint:i,tasks:d,points:o}});return{ok:!0,exitCode:0,warnings:s,message:a.map(({sprint:i,tasks:d,points:o})=>{let l=i.status==="active"?"\u2192":" ",u=d.length===0?"":` ${d.length} tasks, ${o} points`;return`${l} ${i.name.padEnd(20)} ${i.status.padEnd(8)}${u}`}).join(`
239
+ `),data:{schema:"kadence/v1",ok:!0,sprints:a.map(({sprint:i,tasks:d,points:o})=>({id:i.id,name:i.name,status:i.status,taskIds:i.taskIds,taskCount:d.length,points:o}))}}}function fn(t){if(!/^\d{4}-\d{2}-\d{2}$/.test(t))return!1;let r=new Date(`${t}T00:00:00.000Z`);return!Number.isNaN(r.getTime())&&r.toISOString().slice(0,10)===t}function Mr(t,r,n,e){let s=w(t,r);if(!v(s))return s;for(let[g,f]of[["--start",e.startDate],["--end",e.endDate]])if(f!==void 0&&f!==""&&!fn(f))return{...p(2,"invalid_argument",`${g} must be YYYY-MM-DD, got "${f}".
240
+ kadence sprint edit --start 2026-09-01`,{received:f})};let{state:a,warnings:i}=y(s.root,s.actor),d=n===void 0?I(a):H(a,n);if(d===void 0)return{...p(1,"sprint_not_found",n===void 0?`No active sprint.
241
+ kadence sprint list`:`No sprint named "${n}".
242
+ kadence sprint list`,{...n!==void 0?{received:n}:{},hint:"kadence sprint list --json"})};if(d.status==="closed")return{...p(1,"conflicting_state",`Sprint "${d.name}" is closed \u2014 its record is not rewritten.`,{received:d.name})};if(e.name!==void 0&&e.name!==d.name&&H(a,e.name)!==void 0)return p(2,"invalid_argument",`Sprint "${e.name}" already exists.`,{received:e.name});let o=e.startDate??d.startDate??"",l=e.endDate??d.endDate??"";if(o!==""&&l!==""&&l<o)return{...p(2,"invalid_argument",`The end date (${l}) is before the start date (${o}).`,{received:l})};let u={},c=[];return e.name!==void 0&&e.name!==d.name&&(u.name=e.name,c.push("name")),e.description!==void 0&&e.description!==(d.description??"")&&(u.description=e.description,c.push("description")),e.startDate!==void 0&&e.startDate!==(d.startDate??"")&&(u.startDate=e.startDate,c.push("start")),e.endDate!==void 0&&e.endDate!==(d.endDate??"")&&(u.endDate=e.endDate,c.push("end")),c.length===0?{ok:!0,exitCode:0,warnings:i,message:`"${d.name}": nothing changed.`}:(q(s,"sprint.updated",d.id,u),{ok:!0,exitCode:0,warnings:i,message:`"${d.name}": updated ${c.join(", ")}.`,data:{schema:"kadence/v1",ok:!0,sprint:{id:d.id,changed:c}}})}function pn(t,r,n){let e=n===void 0?I(r):H(r,n);return e===void 0?p(1,"sprint_not_found",n===void 0?`No active sprint.
243
+ kadence sprint list`:`No sprint named "${n}".
244
+ kadence sprint list`,{...n!==void 0?{received:n}:{},hint:"kadence sprint list --json"}):{sprint:e,chart:at(r,X(t).events,e)}}function Ur(t,r,n){let e=w(t,r);if(!v(e))return e;let{state:s,warnings:a}=y(e.root,e.actor),i=pn(e.root,s,n);if("exitCode"in i)return i;let{sprint:d,chart:o}=i;return o===null?{ok:!0,exitCode:0,warnings:a,message:`"${d.name}" has no tasks yet.
245
+ kadence sprint add KAD-1`,data:{schema:"kadence/v1",ok:!0,burndown:null}}:{ok:!0,exitCode:0,warnings:a,message:ot(o),data:{schema:"kadence/v1",ok:!0,burndown:o}}}import{spawnSync as gn}from"node:child_process";import{mkdtempSync as kn,readFileSync as yn,rmSync as bn,writeFileSync as wn}from"node:fs";import{tmpdir as vn}from"node:os";import{join as dt}from"node:path";var V="#";function $n(t){return t.GIT_EDITOR??t.VISUAL??t.EDITOR??"vi"}function Yr(t,r,n){let e=kn(dt(vn(),"kadence-edit-")),s=dt(e,"KADENCE_EDITMSG.md"),a=["",`${V} ${n}`,`${V} Lines starting with '${V}' are ignored.`,`${V} Save an empty file to abort.`].join(`
246
+ `);try{wn(s,`${r}${a}
247
+ `,"utf8");let i=$n(t),d=gn(i,[s],{stdio:"inherit",shell:!0});if(d.error!==void 0||d.status!==null&&d.status!==0)return{text:null,error:`Editor "${i}" exited without saving.
248
+ Set one explicitly:
249
+ export EDITOR=nano`};let l=yn(s,"utf8").split(`
250
+ `).filter(u=>!u.startsWith(V)).join(`
251
+ `).trim();return{text:l.length===0?null:l,error:null}}catch(i){return{text:null,error:`Could not open an editor: ${i.message}`}}finally{bn(e,{recursive:!0,force:!0})}}function Gr(t,r){return t.KADENCE_SOURCE==="agent"?!1:r}export{$e as a,je as b,Te as c,Ce as d,U as e,P as f,x as g,X as h,On as i,Pn as j,Tn as k,Cn as l,En as m,S as n,He as o,qe as p,ie as q,ze as r,Hn as s,sr as t,p as u,_ as v,w,et as x,v as y,ar as z,y as A,E as B,or as C,ir as D,Xt as E,nt as F,dr as G,lr as H,cr as I,ur as J,mr as K,sn as L,hr as M,fr as N,pr as O,gr as P,kr as Q,yr as R,br as S,wr as T,vr as U,$r as V,Q as W,jr as X,at as Y,ot as Z,Or as _,Pr as $,Br as aa,Ir as ba,Lr as ca,Fr as da,Mr as ea,pn as fa,Ur as ga,Yr as ha,Gr as ia};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{$ as F,B as f,E as d,G as m,I as S,L as x,M as E,O as y,Q as B,T as A,Z as N,_ as R,aa as P,b as g,c as b,d as T,ea as U,fa as j,ga as O,u as h,w as k,x as C,y as p}from"./chunk-J7QEHKDW.js";import"./chunk-CR5YON2J.js";async function q(t,e){let i=h(t,e);if(!k(i))return i;if(process.stdout.isTTY!==!0)return{ok:!1,exitCode:1,message:`The interactive board needs a terminal.
2
+ import{$ as N,A as p,D as f,G as d,I as m,K as S,N as x,O as E,Q as y,S as B,V as A,aa as R,b as g,ba as F,c as b,ca as P,d as T,ga as U,ha as j,ia as O,w as h,y as k,z as C}from"./chunk-MOXGQLKI.js";import"./chunk-CR5YON2J.js";async function q(t,e){let i=h(t,e);if(!k(i))return i;if(process.stdout.isTTY!==!0)return{ok:!1,exitCode:1,message:`The interactive board needs a terminal.
3
3
  For pipes and scripts use:
4
4
  kadence board --json`};let c;try{c=await import("./board-5SQP5RX6.js")}catch(r){return{ok:!1,exitCode:1,message:`The interactive board could not start: ${r.message}
5
5
  The plain board always works: