@basementuniverse/kanbn 2.0.0 → 2.5.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.
Files changed (100) hide show
  1. package/README.md +3 -1
  2. package/coverage/tmp/coverage-916017-1788028598821-0.json +1 -0
  3. package/coverage/tmp/{coverage-214293-1787777184569-0.json → coverage-916018-1788028597179-0.json} +1 -1
  4. package/coverage/tmp/coverage-916036-1788028598796-0.json +1 -0
  5. package/docs/actions.md +337 -0
  6. package/docs/advanced-configuration.md +55 -0
  7. package/docs/commands/add.txt +17 -1
  8. package/docs/commands/archive.txt +11 -0
  9. package/docs/commands/board.txt +9 -0
  10. package/docs/commands/boards.txt +34 -0
  11. package/docs/commands/burndown.txt +7 -0
  12. package/docs/commands/comment.txt +13 -1
  13. package/docs/commands/contributors.txt +58 -0
  14. package/docs/commands/edit.txt +18 -1
  15. package/docs/commands/find.txt +39 -1
  16. package/docs/commands/gantt.txt +6 -0
  17. package/docs/commands/help.txt +2 -0
  18. package/docs/commands/history.txt +6 -0
  19. package/docs/commands/init.txt +13 -0
  20. package/docs/commands/move.txt +26 -0
  21. package/docs/commands/remove.txt +22 -1
  22. package/docs/commands/rename.txt +5 -0
  23. package/docs/commands/restore.txt +12 -0
  24. package/docs/commands/sort.txt +23 -0
  25. package/docs/commands/sprint.txt +9 -0
  26. package/docs/commands/status.txt +10 -1
  27. package/docs/commands/task.txt +9 -0
  28. package/docs/commands/validate.txt +29 -1
  29. package/docs/contributors.md +145 -0
  30. package/docs/filtering-and-sorting.md +60 -3
  31. package/docs/index-structure.md +145 -11
  32. package/docs/index.md +6 -2
  33. package/docs/multiple-boards.md +259 -0
  34. package/docs/quick-start.md +21 -1
  35. package/docs/task-structure.md +31 -3
  36. package/example/README.md +23 -0
  37. package/example/advanced/kanbn.yml +65 -0
  38. package/example/boards/.kanbn/design.md +31 -0
  39. package/example/boards/.kanbn/index.md +44 -0
  40. package/example/boards/.kanbn/tasks/add-usage-alert-emails.md +19 -0
  41. package/example/boards/.kanbn/tasks/build-tenant-settings-page.md +43 -0
  42. package/example/boards/.kanbn/tasks/create-organization-switcher.md +44 -0
  43. package/example/boards/.kanbn/tasks/design-onboarding-checklist.md +22 -0
  44. package/example/boards/.kanbn/tasks/refresh-marketing-site.md +28 -0
  45. package/example/boards/.kanbn/tasks/ship-billing-portal.md +29 -0
  46. package/package.json +9 -7
  47. package/routes/add.json +40 -12
  48. package/routes/archive.json +14 -2
  49. package/routes/board.json +11 -3
  50. package/routes/boards.json +30 -0
  51. package/routes/burndown.json +23 -7
  52. package/routes/comment.json +19 -5
  53. package/routes/contributors.json +18 -0
  54. package/routes/edit.json +37 -11
  55. package/routes/find.json +37 -12
  56. package/routes/gantt.json +20 -6
  57. package/routes/history.json +39 -25
  58. package/routes/init.json +3 -1
  59. package/routes/move.json +24 -6
  60. package/routes/remove.json +18 -3
  61. package/routes/rename.json +11 -3
  62. package/routes/restore.json +14 -2
  63. package/routes/sort.json +40 -11
  64. package/routes/sprint.json +14 -4
  65. package/routes/status.json +23 -7
  66. package/routes/task.json +10 -2
  67. package/routes/validate.json +18 -5
  68. package/skills/kanbn-plan/SKILL.md +10 -1
  69. package/skills/kanbn-replan/SKILL.md +6 -1
  70. package/src/actions.js +904 -0
  71. package/src/board.js +25 -2
  72. package/src/controller/add.js +72 -55
  73. package/src/controller/archive.js +9 -4
  74. package/src/controller/board.js +21 -13
  75. package/src/controller/boards.js +140 -0
  76. package/src/controller/burndown.js +14 -7
  77. package/src/controller/comment.js +13 -6
  78. package/src/controller/contributors.js +166 -0
  79. package/src/controller/edit.js +48 -18
  80. package/src/controller/find.js +87 -13
  81. package/src/controller/gantt.js +13 -6
  82. package/src/controller/history.js +13 -6
  83. package/src/controller/init.js +39 -4
  84. package/src/controller/move.js +153 -25
  85. package/src/controller/remove.js +73 -13
  86. package/src/controller/rename.js +8 -4
  87. package/src/controller/restore.js +24 -8
  88. package/src/controller/sort.js +59 -3
  89. package/src/controller/sprint.js +31 -7
  90. package/src/controller/status.js +8 -4
  91. package/src/controller/task.js +34 -10
  92. package/src/controller/validate.js +197 -7
  93. package/src/git-user-name.js +9 -0
  94. package/src/git-user.js +55 -0
  95. package/src/main.d.ts +387 -9
  96. package/src/main.js +2757 -113
  97. package/src/parse-index.js +219 -17
  98. package/src/parse-task.js +16 -0
  99. package/src/utility.js +274 -0
  100. package/coverage/tmp/coverage-214292-1787777191526-0.json +0 -1
@@ -0,0 +1,337 @@
1
+ # Actions
2
+
3
+ An action is a rule that says "when _this_ happens to a task, do _that_". Kanbn has had a single-purpose version of this since the beginning — [`startedColumns` and `completedColumns`](index-structure.md#startedcolumns) stamp a date when a task enters a column, and [custom date fields](index-structure.md#customfields) let you declare more of the same. Actions are that idea generalised: more triggers, and more than one thing you can do.
4
+
5
+ ```yaml
6
+ actions:
7
+ - name: start-work
8
+ on: task.moved
9
+ when:
10
+ toColumn: In Progress
11
+ then:
12
+ - assign: '@me'
13
+ - addTag: active
14
+ - removeTag: ready
15
+ ```
16
+
17
+ Two properties hold everywhere in this document, and most of the rest follows from them:
18
+
19
+ - **Actions only fire during commands that change something.** `board`, `status`, `find`, `burndown` and `gantt` never fire an action, so a report is always a pure function of the files on disk. There are no time-based triggers, because Kanbn isn't running when time passes.
20
+ - **Actions never fire actions.** Anything a rule writes is written with the rule engine switched off, so a rule can never trigger another rule — or itself. Rules do not chain. If you want two things to happen, write two verbs in one rule.
21
+
22
+ ## Where rules live
23
+
24
+ `actions` is an ordinary [index option](index-structure.md#project-options), so it layers the way every other option does:
25
+
26
+ - In `kanbn.yml` / `kanbn.json` / the `kanbn` key in `package.json`, it applies to **every board** in the workspace.
27
+ - In a board's front matter — including the main board's — it applies to **that board only**.
28
+
29
+ A board that declares its own `actions` **replaces** the inherited list rather than adding to it, which is how every other option layers. A board that wants the workspace rules and one of its own has to restate them; [`actionsFile`](#actionsfile) is what makes that bearable.
30
+
31
+ An operation is scoped to exactly one board, so only that board's rules fire for it — with one deliberate exception, [`anyBoard`](#listening-from-another-board).
32
+
33
+ ## Events
34
+
35
+ | Event | Fires when | Also carries |
36
+ | --- | --- | --- |
37
+ | `task.created` | `kanbn add` creates a task | `column` |
38
+ | `task.moved` | a task changes column | `fromColumn`, `toColumn` |
39
+ | `task.updated` | a task is edited | `changedFields`, `unsetFields` |
40
+ | `task.commented` | a comment is added | `comment` |
41
+ | `task.addedToBoard` | a task joins this board | `column`, `board` |
42
+ | `task.archived` | a task is archived | `fromColumn` |
43
+ | `task.restored` | a task is restored from the archive | `toColumn` |
44
+ | `task.deleted` | a task is removed from a board | `fromColumn`, `removeFile`, `allBoards` |
45
+ | `task.started` | a task becomes started | (whatever the operation carried) |
46
+ | `task.completed` | a task becomes completed | (whatever the operation carried) |
47
+
48
+ Every event also carries `board`, `boardSlug` and `isMainBoard`.
49
+
50
+ `task.moved` fires only when the column actually changes, matching the rule Kanbn already uses for history events.
51
+
52
+ ### Derived events
53
+
54
+ `task.started` and `task.completed` aren't operations — they're **transitions**. They fire when the task was not started (or completed) before the operation and is afterwards, whatever caused it: a move into a started column, a `completed` date typed in by hand, a restore into a done column. They read the board's own `startedColumns`, `completedColumns`, `startedField` and `completedField`, so a rule meaning "when this is done" doesn't have to repeat the column list config already holds:
55
+
56
+ ```yaml
57
+ # Correct on a board with one completed column, and still correct after you add a second
58
+ - on: task.completed
59
+ then:
60
+ - removeTag: active
61
+ ```
62
+
63
+ One operation can fire two events — `task.moved` and then `task.completed`. Both sets of rules run, the concrete event first. The transition is evaluated against the state Kanbn is about to write, and before any rule runs, so a rule that writes a `completed` date does **not** fire `task.completed`.
64
+
65
+ ### What can't be an event
66
+
67
+ There is no daemon, so nothing happens in a Kanbn workspace on its own: due dates passing, sprint boundaries, "no activity for three days" and inbound webhooks are all triggers that need something to be running when time passes, and nothing is. Reporting commands never fire actions either, which is what keeps `kanbn burndown` giving the same answer twice for the same files.
68
+
69
+ ## Conditions
70
+
71
+ `when` filters the event. It uses the ordinary [filter vocabulary](filtering-and-sorting.md#filter-fields) — every field `kanbn find` accepts, including `tag`, `assigned`, `overdue`, `is-completed` and your own custom fields — plus the event's own keys:
72
+
73
+ ```yaml
74
+ - on: task.moved
75
+ when:
76
+ toColumn: [In Progress, Reviewing] # an event key: matched exactly, a list matches any of them
77
+ tag: epic # a task filter: the usual find vocabulary
78
+ overdue: true
79
+ then:
80
+ - addTag: late-start
81
+ ```
82
+
83
+ Event keys are matched **exactly**, so a rule written for `Done` doesn't match a column called `Done Later`. Task filters behave exactly as they do in `kanbn find`. A rule with no `when` matches every occurrence of its event.
84
+
85
+ The task is filtered as it will be *after* the operation, so `when: { toColumn: Done }` and `when: { 'in-completed-column': true }` agree with each other.
86
+
87
+ ## Verbs
88
+
89
+ `then` is a list of verbs, applied in order. This list is closed, on purpose.
90
+
91
+ | Verb | Argument | Notes |
92
+ | --- | --- | --- |
93
+ | `set` | `{ field: value }` | Metadata only. `created` and `updated` are Kanbn's and can't be written |
94
+ | `unset` | a field name, or a list of them | |
95
+ | `assign` | a name | Shorthand for `set: { assigned: … }` |
96
+ | `addTag` / `removeTag` | a tag, or a list of them | `addTag` is idempotent |
97
+ | `setProgress` | a number between 0 and 1 | Records a `progress` history event, as a manual change does |
98
+ | `comment` | some text | Authored by the rule — see [attribution](#attribution) |
99
+ | `move` | a column name, or `{ column, position }` | The only verb that touches the index |
100
+ | `addToBoard` | `{ board, column }` | Adds the task to another board |
101
+
102
+ ```yaml
103
+ - name: close-out
104
+ on: task.completed
105
+ then:
106
+ - removeTag: active
107
+ - set: { closedBy: '@me', closedIn: '@event.toColumn' }
108
+ - unset: [blocked-by, escalated]
109
+ - setProgress: 1
110
+ - comment: 'Completed by @me'
111
+ ```
112
+
113
+ There are no expressions, no arithmetic and no conditionals. `when` is where conditions go, and a rule that needs two conditions is two rules. If you need something this list can't express, Kanbn is a command-line application: write a script that calls it.
114
+
115
+ ### `move`
116
+
117
+ `move` doesn't perform a second move — it changes the destination of the write that was already about to happen, so "when it enters Review, move it to QA" leaves the task in QA with QA's dates stamped on it:
118
+
119
+ ```yaml
120
+ - on: task.moved
121
+ when: { toColumn: Review }
122
+ then:
123
+ - move: QA
124
+ ```
125
+
126
+ - A move to the column the task is already in (or already moving to) is a no-op.
127
+ - A move to a column that doesn't exist is a configuration error, reported by `kanbn validate`.
128
+ - `move` is not available on `task.archived` or `task.deleted`, where the task is leaving the board.
129
+
130
+ ## Substitutions
131
+
132
+ | Token | Value |
133
+ | --- | --- |
134
+ | `@me` | The [current user](contributors.md#the-current-user) |
135
+ | `@now` | The operation's timestamp |
136
+ | `@task.id`, `@task.name` | The task being written |
137
+ | `@event.<key>` | Any key the event carries: `@event.toColumn`, `@event.fromColumn`, `@event.column`, `@event.board`, `@event.comment` |
138
+ | `@board` | The board the operation ran on |
139
+ | `@event.task.id`, `@event.task.name` | The task the event fired for — only different from `@task.*` in a [cross-task rule](#acting-on-related-tasks) |
140
+
141
+ A value that is nothing but a token keeps that token's type, so `set: { reviewedAt: '@now' }` writes a date rather than a string. A token inside a longer string is interpolated: `comment: 'Moved to @event.toColumn by @me'`.
142
+
143
+ `@me` can resolve to nothing — a CI container with no `KANBN_USER`, no matching contributor and no git identity has no current user. A verb whose value can't be resolved is **skipped with a warning** rather than writing an empty value, and `kanbn validate` points out rules that use `@me` where no user resolves.
144
+
145
+ ## Acting on related tasks
146
+
147
+ By default a rule's verbs apply to the task the event fired for. A `for` clause points them at the tasks it is [related](task-structure.md#relations) to instead:
148
+
149
+ ```yaml
150
+ - name: epic-children-follow
151
+ on: task.moved
152
+ when: { tag: epic, toColumn: In Progress }
153
+ for:
154
+ related: child-of # the relation type; omit it to match any relation
155
+ direction: incoming # incoming: tasks that point at this one. outgoing: tasks it points at
156
+ where: { is-completed: false } # optional, narrows the set - the usual filter vocabulary
157
+ then:
158
+ - addToBoard: { board: sprint, column: Todo }
159
+ ```
160
+
161
+ The division of labour is worth learning once: **`when` decides whether the rule fires, `for` decides who it fires on, and `where` narrows that set.**
162
+
163
+ Direction matters because Kanbn doesn't maintain inverse relations, and neither will you:
164
+
165
+ - **`outgoing`** — the tasks named in this task's own `relations` list. This is the child-to-parent direction, given that a child is the natural place to record `child-of`.
166
+ - **`incoming`** — the tasks whose `relations` name this task. This is the parent-to-children direction, and it is what makes an epic workable: adding a child doesn't mean editing the epic.
167
+
168
+ Relation types are matched loosely, so `child-of`, `Child Of` and `child of` are the same type.
169
+
170
+ Three things to know about the cost and the limits:
171
+
172
+ - An `incoming` selector has to read every task on the board, which is the same work `kanbn find` does. It happens only when such a rule actually matches, and once per operation however many rules need it. `outgoing` reads only the tasks the relations name.
173
+ - A relation pointing at a task that doesn't exist is skipped with a warning, not an error.
174
+ - Targets are selected **one hop** away, and their writes fire nothing. So "when a child is done, close the epic if all its siblings are done" is not expressible: that needs a condition over a whole set of tasks, which actions deliberately don't have.
175
+
176
+ `task.deleted` rules **must** have a `for` clause. Verbs applied to a task that is about to stop
177
+ existing are writes to a file about to be removed; acting on the tasks that related to it is the
178
+ useful thing to do:
179
+
180
+ ```yaml
181
+ - name: orphan-children
182
+ on: task.deleted
183
+ for: { related: child-of, direction: incoming }
184
+ then:
185
+ - removeTag: in-epic
186
+ - comment: 'Epic @event.task.id was removed'
187
+ ```
188
+
189
+ ## Listening from another board
190
+
191
+ Archiving, restoring and deleting affect **every** board a task is on, so the board you happened to run the command from is an arbitrary choice of whose rules should fire. By default the acting board's rules are the ones that run — the same way `kanbn restore` already lets the acting board's options drive the dates while every board gets its membership back.
192
+
193
+ A rule on another board can opt in to hearing about those three events whoever triggered them:
194
+
195
+ ```yaml
196
+ # in the design board's front matter
197
+ - name: log-archives
198
+ on: task.archived
199
+ anyBoard: true
200
+ then:
201
+ - comment: 'Archived from @event.board'
202
+ ```
203
+
204
+ - `anyBoard` is only meaningful on `task.archived`, `task.restored` and `task.deleted`. Setting it on any other event is a configuration error.
205
+ - It applies only when the operation really did affect every board. Removing a task from one board (`kanbn remove` without `--all-boards`) is not workspace-wide, so no other board hears about it.
206
+ - Rules run in board order, with the acting board's first.
207
+ - An `anyBoard` rule can't use `move`, because the columns it names belong to a different board.
208
+ - If another board's rules are broken, they're skipped with a warning rather than failing a command that didn't ask for that board. `kanbn validate --all-boards` reports them.
209
+
210
+ ## `actionsFile`
211
+
212
+ A long rule set makes front matter hard to read, and a board that wants the workspace rules plus one of its own has to restate all of them. `actionsFile` points at a file holding the same list:
213
+
214
+ ```yaml
215
+ actionsFile: actions/design.yml # relative to the .kanbn folder
216
+ ```
217
+
218
+ ```yaml
219
+ # .kanbn/actions/design.yml
220
+ - name: tag-on-review
221
+ on: task.moved
222
+ when: { toColumn: Review }
223
+ then:
224
+ - addTag: needs-review
225
+ ```
226
+
227
+ - The file contains exactly what the `actions` option would have contained: a list of rules. A file with a top-level `actions:` key is accepted too.
228
+ - `actions` and `actionsFile` on the same board is an error — pick one.
229
+ - A missing or unparseable `actionsFile` is an error rather than "no rules". A rule set that quietly stops existing is worse than one that fails loudly.
230
+
231
+ ## When something goes wrong
232
+
233
+ Two kinds of problem, treated differently on purpose:
234
+
235
+ **Configuration errors** are wrong in the file: an unknown event or verb, a `move` to a column that doesn't exist, a `set` on `created`. `kanbn validate` reports them, and any command that would have run the rules **fails before writing anything**. The whole rule set is checked, not just the rules that would have fired, so a typo shows up the first time you run any command rather than the first time that one rule matches.
236
+
237
+ **Runtime skips** are things that can only be discovered when the rule fires: `@me` resolving to nothing, a relation pointing at a missing task, `addToBoard` naming a column that has since gone. These print a warning to stderr and the rest of the operation completes.
238
+
239
+ Nothing fails silently. A rule that quietly stopped working would be worse than no rule at all.
240
+
241
+ ```bash
242
+ kanbn validate
243
+ kanbn validate --all-boards
244
+ ```
245
+
246
+ `kanbn validate` also warns about rule sets that work but probably don't do what they look like — two rules writing the same field on the same event, where the last one silently wins.
247
+
248
+ ## Turning actions off
249
+
250
+ Every command that can fire an action takes `--no-actions`, and `KANBN_NO_ACTIONS=1` does the same thing for a whole shell:
251
+
252
+ ```bash
253
+ kanbn move my-task -c Done --no-actions
254
+ KANBN_NO_ACTIONS=1 kanbn add -n "Imported task" -c Backlog
255
+ ```
256
+
257
+ This is a way around a misbehaving rule, not a safety control — actions are declarative, so there is nothing being executed to be protected from.
258
+
259
+ ## Attribution
260
+
261
+ Anything a rule writes is attributed to the rule rather than to a person. A comment written by a rule called `close-out` has the author `@kanbn/close-out`, and so do the history events for a move, a progress change or a board membership that a rule caused:
262
+
263
+ ```yaml
264
+ comments:
265
+ - author: '@kanbn/close-out'
266
+ date: 2026-08-29T14:22:05.000Z
267
+ text: Completed by gordon
268
+ ```
269
+
270
+ `@me` inside a rule means the human who ran the command, not the rule — both are available at once, which is what lets a rule-written comment say who did it without pretending to be them. [Contributor warnings](contributors.md) ignore `@kanbn/*` authors: a rule is not a missing contributor.
271
+
272
+ ## Interaction with column-linked dates
273
+
274
+ [Column-linked date fields](index-structure.md#customfields) still work exactly as they did, and they are **not** implemented in terms of actions. Built-in stamping happens first, then rules, so a rule can observe and override a stamped value:
275
+
276
+ ```yaml
277
+ customFields:
278
+ - name: testedAt
279
+ type: date
280
+ testedAtColumns: [Testing]
281
+
282
+ actions:
283
+ - on: task.moved
284
+ when: { toColumn: Testing }
285
+ then:
286
+ - assign: '@me' # runs after testedAt has been stamped
287
+ ```
288
+
289
+ A rule that redirects a move with `move` re-stamps for the column the task actually ends up in.
290
+
291
+ ## Full example
292
+
293
+ ```yaml
294
+ actions:
295
+ # Tag and assign work as it starts
296
+ - name: start-work
297
+ on: task.started
298
+ then:
299
+ - assign: '@me'
300
+ - addTag: active
301
+ - removeTag: ready
302
+
303
+ # Tidy up when it finishes, whatever caused it to finish
304
+ - name: close-out
305
+ on: task.completed
306
+ then:
307
+ - removeTag: active
308
+ - setProgress: 1
309
+ - set: { closedBy: '@me' }
310
+
311
+ # Anything entering Review is stamped and flagged
312
+ - name: review-stamp
313
+ on: task.moved
314
+ when: { toColumn: Review }
315
+ then:
316
+ - set: { reviewRequestedAt: '@now' }
317
+ - addTag: needs-review
318
+
319
+ # An epic starting pulls its unfinished children onto the sprint board with it
320
+ - name: epic-children-follow
321
+ on: task.moved
322
+ when: { tag: epic, toColumn: In Progress }
323
+ for:
324
+ related: child-of
325
+ direction: incoming
326
+ where: { is-completed: false }
327
+ then:
328
+ - addToBoard: { board: sprint, column: Todo }
329
+
330
+ # Removing an epic leaves its children findable
331
+ - name: orphan-children
332
+ on: task.deleted
333
+ for: { related: child-of, direction: incoming }
334
+ then:
335
+ - removeTag: in-epic
336
+ - comment: 'Epic @event.task.id was removed'
337
+ ```
@@ -8,6 +8,8 @@ If any project options are added to the index file, then the next time Kanbn wri
8
8
 
9
9
  _Note: in future, it might be possible to split project options between the index and configuration files, but for now configuration must be saved inside only one of these files._
10
10
 
11
+ This applies to the **main board**. In a workspace with [several boards](multiple-boards.md), a secondary board's own options always live in that board file's front matter, and a board-local operation never writes to the configuration file.
12
+
11
13
  _Note: Kanbn rewrites whichever file holds the project options every time it saves them (`kanbn add`, `kanbn move`, `kanbn sort --save`, `kanbn validate --save`, and so on). The YAML is regenerated, so formatting and comments in that file are not preserved._
12
14
 
13
15
  ## Configuring default locations
@@ -48,3 +50,56 @@ This will rename the folder where tasks are stored. By default this is `tasks`.
48
50
  ### `archiveFolder`
49
51
 
50
52
  This will rename the archive folder where archived tasks are stored. By default this is `archive`. It is relative to the main folder.
53
+
54
+ ## Multiple boards
55
+
56
+ Two more workspace-scoped options control [multiple boards](multiple-boards.md): `defaultBoard` names the board that commands target when `-b` isn't given, and `boards` controls discovery, display order and per-board options.
57
+
58
+ ```yaml
59
+ defaultBoard: design
60
+ boards:
61
+ exclude:
62
+ - notes.md
63
+ order:
64
+ - index
65
+ - design
66
+ design:
67
+ hiddenColumns:
68
+ - Ideas
69
+ ```
70
+
71
+ ## Contributors
72
+
73
+ `contributors` is workspace-scoped too: an optional list of the people who work on this workspace, used to canonicalise `assigned` and comment `author` values and to answer "who is the current user?".
74
+
75
+ ```yaml
76
+ contributors:
77
+ - gordon
78
+ - name: dave
79
+ email: dave@example.com
80
+ aliases:
81
+ - Dave Smith
82
+ ```
83
+
84
+ It is advisory — nothing is ever validated against it. See [Contributors](contributors.md).
85
+
86
+ ## Actions
87
+
88
+ `actions` is a list of rules that fire when a task changes — "when this enters In Progress, assign it to me and tag it active". Unlike `contributors` it is **board-scoped**: declared in a configuration file it applies to every board, and declared in a board's front matter it applies to that board alone.
89
+
90
+ ```yaml
91
+ actions:
92
+ - name: start-work
93
+ on: task.moved
94
+ when:
95
+ toColumn: In Progress
96
+ then:
97
+ - assign: '@me'
98
+ - addTag: active
99
+ ```
100
+
101
+ Rules are declarative — Kanbn never executes anything a workspace supplies — and nothing a rule writes can fire another rule. `--no-actions` on any mutating command, or `KANBN_NO_ACTIONS=1`, steps around a rule that is misbehaving. `actionsFile` moves a long rule set out of front matter and into a file of its own. See [Actions](actions.md).
102
+
103
+ Note that `indexFile` also sets the **main board's slug**: with `indexFile: board.md`, the main board's slug is `board`, and `main` / `default` still resolve to it.
104
+
105
+ Where a configuration file exists, everything in it is inherited by every board — it is workspace-level by construction, so a board-scoped option declared there is a deliberate statement about all boards. Where there is no configuration file, the workspace options live in the main board's front matter, and only the workspace-scoped ones propagate; `startedColumns`, `views` and the rest stay with the main board.
@@ -35,7 +35,8 @@ Options:
35
35
 
36
36
  {b}kanbn add --assigned "name"{b}
37
37
  {b}kanbn add -a "name"{b}
38
- Create a new task and set the assigned user name. If this option is left blank, the current git username will be used.
38
+ Create a new task and set the assigned user name. If this option is left blank, the task is assigned to the current user.
39
+ The current user comes from KANBN_USER or your git identity, canonicalised against the {b}contributors{b} list if the workspace has one - see {b}kanbn contributors --help{b}.
39
40
 
40
41
  {b}kanbn add --sub-task "sub-task"{b}
41
42
  {b}kanbn add -s "sub-task"{b}
@@ -70,3 +71,18 @@ Examples:
70
71
 
71
72
  {b}kanbn a -ui -f "my-task-3" -f "my-task-4" -c "Done"{b}
72
73
  Interactively adds untracked tasks "my-task-3.md" and "my-task-4.md" to the "Done" column.
74
+
75
+ {b}kanbn add --board "board-slug"{b}
76
+ {b}kanbn add -b "board-slug"{b}
77
+ Add the task to a board other than the main one. This option can be repeated to put one task on
78
+ several boards at once - the task file is created once and every board named references it:
79
+ {b}kanbn add -n "Task" -b index -b design -c Todo -c Designing{b}
80
+ Each {b}-b{b} pairs with the {b}-c{b} in the same position; boards with no {b}-c{b} of their own
81
+ use the first one given, and a board that hasn't got that column falls back to its first column
82
+ with a notice. With a single board an unknown column is still an error.
83
+
84
+ Boards can declare {b}actions{b} - rules that fire when a task changes, e.g. "when this enters In
85
+ Progress, assign it to me and tag it active". See {b}docs/actions.md{b}.
86
+
87
+ {b}kanbn add --no-actions{b}
88
+ Run without firing any action rules. {b}KANBN_NO_ACTIONS=1{b} does the same for a whole shell.
@@ -10,3 +10,14 @@ Options:
10
10
  {b}kanbn archive --list{b}
11
11
  {b}kanbn archive -l{b}
12
12
  Show a list of archived task filenames.
13
+
14
+ {b}kanbn archive "task-id" --board "board-slug"{b}
15
+ {b}kanbn archive "task-id" -b "board-slug"{b}
16
+ Target a board other than the main one. Falls back to the KANBN_BOARD environment variable and
17
+ then to the defaultBoard option. See {b}kanbn boards{b} for the list of boards.
18
+
19
+ Boards can declare {b}actions{b} - rules that fire when a task changes, e.g. "when this enters In
20
+ Progress, assign it to me and tag it active". See {b}docs/actions.md{b}.
21
+
22
+ {b}kanbn archive --no-actions{b}
23
+ Run without firing any action rules. {b}KANBN_NO_ACTIONS=1{b} does the same for a whole shell.
@@ -13,3 +13,12 @@ Options:
13
13
  {b}kanbn board -j{b}
14
14
  Output raw data instead of rendering the board. The data will be returned in JSON format.
15
15
  This is the quickest way to check what a view's filters actually match.
16
+
17
+ {b}kanbn board --board "board-slug"{b}
18
+ {b}kanbn board -b "board-slug"{b}
19
+ Target a board other than the main one. Falls back to the KANBN_BOARD environment variable and
20
+ then to the defaultBoard option. See {b}kanbn boards{b} for the list of boards.
21
+
22
+ Simple tasks - lines in a column that aren't task links - are shown dimmed at the end of their
23
+ column. They only appear in the default layout: a view filters and sorts tasks, and a simple task
24
+ has no fields to filter or sort on. They are left out of {b}--json{b} for the same reason.
@@ -0,0 +1,34 @@
1
+ {b}kanbn boards{b}
2
+
3
+ List the boards in this workspace.
4
+
5
+ A board is a markdown file directly inside the kanbn folder, in the same format as the index. The
6
+ index file is the main board; every other markdown file beside it is a secondary board. All boards
7
+ share one task folder, so a task can appear on several boards, in a different column on each.
8
+
9
+ Create a board with {b}kanbn init -b "board-slug"{b}, and target one with {b}-b "board-slug"{b} on
10
+ any board-scoped command.
11
+
12
+ Options:
13
+ {b}kanbn boards --tasks{b}
14
+ {b}kanbn boards -t{b}
15
+ Show every task that appears on more than one board, with the column it occupies on each.
16
+
17
+ {b}kanbn boards --tasks --all{b}
18
+ {b}kanbn boards -t -a{b}
19
+ Show every tracked task, not only the ones on more than one board.
20
+
21
+ {b}kanbn boards --rename "board-slug" --name "Board Name"{b}
22
+ {b}kanbn boards -r "board-slug" -n "Board Name"{b}
23
+ Rename a board. The positional argument is the new slug, and {b}--name{b} sets the display name:
24
+ {b}kanbn boards --rename design ux --name "UX Pipeline"{b}
25
+ Either the new slug or {b}--name{b} may be omitted, but not both. The main board can't be renamed.
26
+
27
+ {b}kanbn boards --delete "board-slug"{b}
28
+ {b}kanbn boards -d "board-slug"{b}
29
+ Delete a board file. Task files are never deleted, but tasks referenced only by this board become
30
+ untracked - if that would happen, the tasks are listed and {b}-f{b} is required to go ahead.
31
+
32
+ {b}kanbn boards --json{b}
33
+ {b}kanbn boards -j{b}
34
+ Output the board list, or the cross-board task overview, in JSON format.
@@ -30,6 +30,7 @@ Options:
30
30
  Filter for tasks in a particular column. This option can be repeated to include multiple columns.
31
31
 
32
32
  {b}kanbn burndown --assigned "user"{b}
33
+ {b}kanbn burndown --assigned @me{b}
33
34
  {b}kanbn burndown -a "user"{b}
34
35
  Filter for tasks that are assigned to a particular user.
35
36
 
@@ -37,3 +38,9 @@ Options:
37
38
  {b}kanbn burndown --n "days"|"hours"|"minutes"|"seconds"{b}
38
39
  Normalise dates. Task event times (history events or legacy created/started/completed dates) will be rounded down to the nearest day, hour, minute or second. This may cause task events to be grouped together.
39
40
  If this option is set to a blank or unsupported value, the normalisation mode will be automatically selected based on the date range.
41
+
42
+ {b}kanbn burndown --board "board-slug"{b}
43
+ {b}kanbn burndown -b "board-slug"{b}
44
+ Chart a board other than the main one. Burndown measures work in flight, so a board that
45
+ declares no {b}startedColumns{b} has nothing to chart and says so rather than drawing an empty
46
+ chart.
@@ -10,8 +10,20 @@ Options:
10
10
 
11
11
  {b}kanbn comment "task-id" --author "name"{b}
12
12
  {b}kanbn comment "task-id" -a "name"{b}
13
- Set the comment author. If this option is left blank or omitted, the current git username will be used.
13
+ Set the comment author. If this option is left blank or omitted, the current user is used, and the comment has no author at all if there isn't one.
14
+ The current user comes from KANBN_USER or your git identity, canonicalised against the {b}contributors{b} list if the workspace has one - see {b}kanbn contributors --help{b}.
14
15
 
15
16
  {b}kanbn comment "task-id" --text "text"{b}
16
17
  {b}kanbn comment "task-id" -t "text"{b}
17
18
  Set the comment text.
19
+
20
+ {b}kanbn comment "task-id" --board "board-slug"{b}
21
+ {b}kanbn comment "task-id" -b "board-slug"{b}
22
+ Target a board other than the main one. Falls back to the KANBN_BOARD environment variable and
23
+ then to the defaultBoard option. See {b}kanbn boards{b} for the list of boards.
24
+
25
+ Boards can declare {b}actions{b} - rules that fire when a task changes, e.g. "when this enters In
26
+ Progress, assign it to me and tag it active". See {b}docs/actions.md{b}.
27
+
28
+ {b}kanbn comment --no-actions{b}
29
+ Run without firing any action rules. {b}KANBN_NO_ACTIONS=1{b} does the same for a whole shell.
@@ -0,0 +1,58 @@
1
+ {b}kanbn contributors{b}
2
+
3
+ List the people who work on this workspace, and say which of them Kanbn thinks you are.
4
+
5
+ Contributors are an optional, workspace-scoped list declared in {b}kanbn.yml{b} / {b}kanbn.json{b}, or
6
+ in the main board's front matter:
7
+
8
+ contributors:
9
+ - gordon
10
+ - dave
11
+ - name: sam
12
+ displayName: Sam Vimes
13
+ email: sam@example.com
14
+ aliases:
15
+ - Samuel Vimes
16
+ - samv
17
+ colour: '#7c5cff'
18
+
19
+ A contributor can be a bare name or an object; only {b}name{b} is required, and it is the value
20
+ written into a task's {b}assigned{b} field or a comment's {b}author{b}.
21
+
22
+ Contributors are {b}advisory{b}. {b}assigned{b} and {b}author{b} stay free text and are never
23
+ validated against the list, never rejected and never rewritten - the list makes the common case one
24
+ keystroke instead of a retyped name, and makes the names a workspace uses discoverable.
25
+
26
+ {b}The current user{b}
27
+
28
+ Kanbn resolves "you" in this order, first match wins:
29
+
30
+ 1. the {b}KANBN_USER{b} environment variable, used exactly as given
31
+ 2. {b}git config user.email{b} matched against a contributor's {b}email{b}
32
+ 3. {b}git config user.name{b} matched against a contributor's {b}name{b}, {b}displayName{b} or
33
+ {b}aliases{b}, ignoring case
34
+ 4. {b}git config user.name{b} as-is
35
+ 5. nobody
36
+
37
+ That value is what {b}kanbn add --assigned{b} and {b}kanbn edit --assigned{b} write when given no
38
+ value, what a comment's author defaults to, and what {b}@me{b} expands to in
39
+ {b}kanbn find --assigned{b}, {b}kanbn burndown --assigned{b}, {b}kanbn gantt --assigned{b} and
40
+ {b}kanbn history --assigned{b}.
41
+
42
+ Steps 2 and 3 are what canonicalise: on a machine whose git username is "Gordon Larrigan", a
43
+ workspace listing {b}gordon{b} with that name as an alias writes {b}gordon{b} into the task file.
44
+ With no contributors declared, this is exactly the git username Kanbn has always used.
45
+
46
+ Options:
47
+ {b}kanbn contributors --usage{b}
48
+ {b}kanbn contributors -u{b}
49
+ Show how many tasks and comments each contributor appears in, and every name used in a task that
50
+ isn't a known contributor. This is how you adopt contributors in an existing workspace: it tells
51
+ you what to put in the list, and which spellings to add as aliases.
52
+
53
+ It is read-only. Nothing is rewritten - renaming "Gordon" to "gordon" across every task file is a
54
+ bulk mutation and isn't done here.
55
+
56
+ {b}kanbn contributors --json{b}
57
+ {b}kanbn contributors -j{b}
58
+ Output the contributor list, or the usage report, in JSON format.
@@ -34,7 +34,8 @@ Options:
34
34
  Modify a task's progress. This should be a number between 0 (not started) and 1 (complete).
35
35
 
36
36
  {b}kanbn edit "task-id" --assigned "name"{b}
37
- Modify a task assigned user name. If this option is left blank, the current git username will be used.
37
+ Modify a task assigned user name. If this option is left blank, the task is assigned to the current user.
38
+ The current user comes from KANBN_USER or your git identity, canonicalised against the {b}contributors{b} list if the workspace has one - see {b}kanbn contributors --help{b}.
38
39
 
39
40
  {b}kanbn edit "task-id" --remove-sub-task "sub-task"{b}
40
41
  Remove a sub-task.
@@ -78,3 +79,19 @@ Options:
78
79
 
79
80
  {b}kanbn edit --remove-<custom field name>{b}
80
81
  Remove a custom metadata field.
82
+
83
+ {b}kanbn edit "task-id" --board "board-slug"{b}
84
+ {b}kanbn edit "task-id" -b "board-slug"{b}
85
+ Target a board other than the main one. Falls back to the KANBN_BOARD environment variable and
86
+ then to the defaultBoard option. See {b}kanbn boards{b} for the list of boards.
87
+
88
+ {b}kanbn edit "Buy milk"{b}
89
+ Editing a simple task - a line in a column that isn't a task link - promotes it to a real task
90
+ file first, and then applies the edit. The new task is created in the column the line was in,
91
+ with a created date as of the promotion. There is no way back.
92
+
93
+ Boards can declare {b}actions{b} - rules that fire when a task changes, e.g. "when this enters In
94
+ Progress, assign it to me and tag it active". See {b}docs/actions.md{b}.
95
+
96
+ {b}kanbn edit --no-actions{b}
97
+ Run without firing any action rules. {b}KANBN_NO_ACTIONS=1{b} does the same for a whole shell.