jackherd 1.9.0 → 1.11.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.
- package/CHANGELOG.md +49 -0
- package/INSTALL.md +1 -1
- package/README.md +65 -32
- package/dist/{chunk-MCP4XJQH.js → chunk-RMMXD4G5.js} +18 -6
- package/dist/config.d.ts +99 -4
- package/dist/config.js +105 -29
- package/dist/plan-format.d.ts +51 -6
- package/dist/plan-format.js +49 -7
- package/docs/workflow-dark.svg +67 -63
- package/docs/workflow.svg +67 -63
- package/herdr-plugin/herdr-plugin.toml +1 -1
- package/install.sh +1 -1
- package/kit/hooks/ask-rules.json +15 -0
- package/kit/hooks/hooks/ask-hint/decision.ts +82 -14
- package/kit/hooks/hooks/ask-hint/index.ts +15 -19
- package/kit/hooks/hooks/ask-hint/settings.ts +3 -18
- package/kit/hooks/hooks/git-guard/decision.ts +50 -102
- package/kit/hooks/hooks/merge-guard/always-ask.ts +63 -0
- package/kit/hooks/hooks/merge-guard/{plan-approval-settings.ts → board-settings.ts} +21 -67
- package/kit/hooks/hooks/merge-guard/check-free.ts +20 -0
- package/kit/hooks/hooks/merge-guard/decision.ts +127 -187
- package/kit/hooks/hooks/merge-guard/index.ts +6 -3
- package/kit/hooks/hooks/merge-guard/lane-plan.ts +79 -0
- package/kit/hooks/hooks/merge-guard/run.ts +59 -54
- package/kit/hooks/hooks/merge-guard/settings.ts +13 -1
- package/kit/hooks/hooks/merge-guard/standing-approval.ts +142 -0
- package/kit/hooks/hooks/open-question/index.ts +26 -0
- package/kit/hooks/hooks/open-question/record.ts +135 -0
- package/kit/hooks/hooks/open-question/run.ts +57 -0
- package/kit/hooks/hooks/open-question/store.ts +39 -0
- package/kit/hooks/hooks/sent-file/store.ts +3 -12
- package/kit/hooks/hooks/shared/config.ts +7 -0
- package/kit/hooks/hooks/shared/dialog-answers.ts +9 -9
- package/kit/hooks/hooks/shared/files.ts +7 -0
- package/kit/hooks/hooks/shared/lane.ts +1 -1
- package/kit/hooks/hooks/shared/newest-verdict.ts +10 -0
- package/kit/hooks/hooks/shared/private-directory.ts +56 -9
- package/kit/hooks/hooks/shared/program-position.ts +2 -75
- package/kit/hooks/hooks/shared/shell-aliases.ts +1 -71
- package/kit/hooks/hooks/{ask-hint → shared}/transcript.ts +37 -6
- package/kit/hooks/hooks/{merge-guard → shared}/xargs-program.ts +10 -8
- package/kit/hooks/hooks/task-hint/decision.ts +49 -0
- package/kit/hooks/hooks/task-hint/index.ts +79 -0
- package/kit/hooks/hooks/task-hint/run.ts +62 -0
- package/kit/hooks/hooks/task-hint/store.ts +51 -0
- package/kit/hooks/hooks.json +13 -5
- package/kit/review/agents/skill-review.md +34 -0
- package/kit/review/skills/pre-commit/SKILL.md +1 -1
- package/kit/rules/rules/kit/pull-requests.md +2 -0
- package/kit/workflow/skills/follow/SKILL.md +3 -10
- package/kit/workflow/skills/implement/SKILL.md +8 -6
- package/kit/workflow/skills/orchestrate/SKILL.md +218 -433
- package/kit/workflow/skills/orchestrate/handover/decision.ts +43 -8
- package/kit/workflow/skills/orchestrate/handover/index.ts +32 -4
- package/kit/workflow/skills/orchestrate/handover/standing-approval.ts +84 -0
- package/kit/workflow/skills/orchestrate/references/acceptance.md +15 -0
- package/kit/workflow/skills/orchestrate/references/close-lane.md +17 -0
- package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +43 -17
- package/kit/workflow/skills/orchestrate/references/draft-review.md +19 -0
- package/kit/workflow/skills/orchestrate/references/events.md +86 -0
- package/kit/workflow/skills/orchestrate/references/herd.md +6 -6
- package/kit/workflow/skills/orchestrate/references/lane-handover.md +18 -0
- package/kit/workflow/skills/orchestrate/references/lane-session.md +25 -0
- package/kit/workflow/skills/orchestrate/references/lane-setup.md +37 -0
- package/kit/workflow/skills/orchestrate/references/plan-commit.md +22 -10
- package/kit/workflow/skills/orchestrate/references/sensitive-input.md +5 -7
- package/kit/workflow/skills/plan/SKILL.md +6 -5
- package/kit/workflow/skills/plan/references/template-English.md +4 -4
- package/kit/workflow/skills/plan/references/template-German.md +4 -4
- package/package.json +3 -1
- package/recipes/README.md +2 -1
- package/recipes/sources.md +5 -1
- package/schemas/jackherd.schema.json +139 -5
- package/src/check/agent-text-reviewers.ts +23 -0
- package/src/check/command.ts +2 -0
- package/src/check/plan-structure-check.ts +49 -2
- package/src/check/plan.ts +8 -1
- package/src/check/schema.ts +9 -0
- package/src/check/skill-budget-defaults.ts +55 -0
- package/src/check/skill-budgets.ts +185 -0
- package/src/cli.ts +2 -0
- package/src/config/auto-window.ts +8 -0
- package/src/config/link-kinds.ts +7 -1
- package/src/config/schema.ts +82 -25
- package/src/config/tones.ts +27 -0
- package/src/lane/close-board.ts +106 -0
- package/src/lane/close-check.ts +183 -0
- package/src/lane/close-command-line.ts +11 -0
- package/src/lane/close-command.ts +217 -0
- package/src/lane/close-options.ts +43 -0
- package/src/lane/close-survey.ts +83 -0
- package/src/lane/close.ts +141 -0
- package/src/lane/command.ts +13 -0
- package/src/lane-prompt/additions.ts +14 -0
- package/src/lane-prompt/build.ts +74 -0
- package/src/lane-prompt/command.ts +93 -0
- package/src/lane-prompt/input.ts +157 -0
- package/src/lane-prompt/options.ts +70 -0
- package/src/lane-prompt/texts.ts +101 -0
- package/src/mermaid/command.ts +36 -0
- package/src/mermaid/problems.ts +48 -0
- package/src/plan-format/clock.ts +8 -0
- package/src/plan-format/dependencies.ts +9 -6
- package/src/plan-format/index.ts +1 -0
- package/src/plan-format/short-names.ts +71 -0
- package/src/plan-format/waiting.ts +22 -4
- package/src/plans/appointment-rows.ts +83 -0
- package/src/plans/ask-coordinator.ts +111 -0
- package/src/plans/ask-target.ts +36 -0
- package/src/plans/board-close.ts +85 -0
- package/src/plans/child-stop.ts +51 -0
- package/src/plans/closable-records.ts +34 -0
- package/src/plans/command.ts +3 -2
- package/src/plans/fetch.ts +26 -3
- package/src/plans/frame.ts +5 -12
- package/src/plans/git.ts +54 -5
- package/src/plans/groomed.ts +27 -0
- package/src/plans/herd-claim.ts +173 -0
- package/src/plans/herd-memory.ts +36 -25
- package/src/plans/lane-plans.ts +44 -13
- package/src/plans/lane-summary.ts +253 -45
- package/src/plans/layout-phase.ts +36 -11
- package/src/plans/layout-summary.ts +89 -18
- package/src/plans/layout.ts +502 -98
- package/src/plans/link-chips.ts +23 -4
- package/src/plans/live.ts +178 -23
- package/src/plans/next-rows.ts +44 -0
- package/src/plans/phase-ids.ts +8 -0
- package/src/plans/phase-source.ts +15 -0
- package/src/plans/plan-cache.ts +28 -0
- package/src/plans/plan-heads.ts +54 -0
- package/src/plans/planned.ts +22 -0
- package/src/plans/pull-requests.ts +17 -2
- package/src/plans/question-records.ts +48 -0
- package/src/plans/questions-read.ts +13 -0
- package/src/plans/read-cache.ts +8 -0
- package/src/plans/record-id.ts +4 -0
- package/src/plans/restart-decision.ts +13 -4
- package/src/plans/row-actions.ts +52 -0
- package/src/plans/row-focus.ts +148 -0
- package/src/plans/row-targets.ts +7 -0
- package/src/plans/selection.ts +111 -18
- package/src/plans/sent-files-read.ts +6 -15
- package/src/plans/sent-files.ts +7 -14
- package/src/plans/settings.ts +12 -1
- package/src/plans/signalled-phase.ts +42 -0
- package/src/plans/signals-read.ts +12 -4
- package/src/plans/snapshot.ts +162 -81
- package/src/plans/source-asked.ts +19 -0
- package/src/plans/source-prefix.ts +17 -0
- package/src/plans/source-rows.ts +188 -0
- package/src/plans/sprint-export.ts +11 -11
- package/src/plans/sprint-page.ts +5 -5
- package/src/plans/subagent-phases.ts +95 -0
- package/src/plans/subagent-tasks.ts +51 -0
- package/src/plans/subagents-read.ts +66 -17
- package/src/plans/subagents.ts +64 -6
- package/src/plans/supervisor.ts +14 -4
- package/src/plans/symbols.ts +3 -0
- package/src/plans/task-name.ts +34 -0
- package/src/plans/texts.ts +48 -6
- package/src/plans/tones.ts +30 -0
- package/src/plans/view.ts +25 -11
- package/src/plans/waiting-click.ts +295 -35
- package/src/plans/waiting-records.ts +39 -0
- package/src/plans/waiting-window.ts +8 -0
- package/src/preview/command.ts +25 -3
- package/src/shared/private-location.ts +89 -0
- package/src/shared/stat-cache.ts +48 -0
- package/src/sync/kit-source.ts +29 -10
- package/src/sync/kit.ts +2 -0
- package/src/sync/manifest.ts +2 -0
- package/src/sync/plan-ask-rules.ts +94 -0
- package/src/sync/plan-hooks.ts +2 -0
- package/src/sync/render.ts +3 -3
- package/src/sync/settings-hooks.ts +4 -1
- package/src/sync/sync-plan.ts +1 -0
- package/src/sync/sync.ts +24 -3
- package/src/task-signals/claude-read.ts +27 -10
- package/src/terminal/text.ts +16 -3
- package/src/terminal/types.ts +4 -0
- package/src/version/skill-diff.ts +46 -0
- package/src/vocabulary/index.ts +26 -6
- package/src/waiting/command.ts +22 -7
- package/src/watch/appointments.ts +1 -2
- package/src/watch/asking-lanes.ts +40 -0
- package/src/watch/base-tip.ts +11 -0
- package/src/watch/command.ts +330 -105
- package/src/watch/heartbeat-progress.ts +6 -18
- package/src/watch/kit-update.ts +28 -9
- package/src/watch/lane-name.ts +6 -0
- package/src/watch/lane-progress.ts +40 -8
- package/src/watch/lane-report-file.ts +20 -5
- package/src/watch/lane-report.ts +32 -4
- package/src/watch/lane-sources.ts +31 -3
- package/src/watch/main-checkout.ts +34 -0
- package/src/watch/plan-badges-read.ts +22 -9
- package/src/watch/plan-badges.ts +10 -2
- package/src/watch/pull-requests.ts +6 -2
- package/src/watch/reference-paths.ts +87 -0
- package/src/watch/report-questions.ts +4 -3
- package/src/watch/running-subagents.ts +43 -0
- package/src/watch/skill-change.ts +124 -32
- package/src/watch/source-change.ts +48 -0
- package/src/watch/source-entries.ts +55 -0
- package/src/watch/source-fields.ts +1 -0
- package/src/watch/source-statuses.ts +60 -0
- package/src/watch/source-tickets.ts +21 -1
- package/src/watch/sources.ts +47 -4
- package/src/watch/stream-output.ts +4 -3
- package/src/watch/task-parallel-auto.ts +208 -0
- package/src/watch/task-parallel-data-paths.ts +46 -0
- package/src/watch/task-parallel-input.ts +5 -15
- package/src/watch/task-parallel-read.ts +3 -1
- package/src/watch/task-parallel.ts +36 -7
- package/src/watch/task-signal-missing.ts +69 -0
- package/src/watch/task-signal-read.ts +29 -0
- package/src/watch/texts.ts +10 -0
- package/src/watch/update-available.ts +6 -3
- package/src/watch/watch-events.ts +23 -0
- package/src/watch/watcher-lock.ts +25 -29
- package/kit/hooks/hooks/merge-guard/plan-approval-record.ts +0 -23
- package/kit/hooks/hooks/merge-guard/plan-approval-sources.ts +0 -196
- package/kit/hooks/hooks/merge-guard/plan-approval.ts +0 -168
- package/kit/hooks/hooks/merge-guard/plan-growth.ts +0 -40
- package/kit/hooks/hooks/merge-guard/plan-pointers.ts +0 -194
- package/kit/hooks/hooks/shared/command-text-runners.ts +0 -71
- package/kit/hooks/hooks/shared/plan-open-phases.ts +0 -114
|
@@ -35,7 +35,9 @@ Skip this when the request already carries its decisions (a plan for a bug fix w
|
|
|
35
35
|
|
|
36
36
|
The closing documentation section reads `## N — …` with rows `N.1` to `N.5`: replace `N` with the number of that phase (`## 10 — …`, `10.1`). A literal `N` is no phase to the herd, which shows it as "not in the plan".
|
|
37
37
|
|
|
38
|
-
A checkbox moved on purpose to a later phase stays where it is, unticked, and ends in the target: `- [ ] Update the documentation → P9`. The herd counts it neither as open nor as done in its own phase and lists it as open in phase 9
|
|
38
|
+
A checkbox moved on purpose to a later phase stays where it is, unticked, and ends in the target: `- [ ] Update the documentation → P9`. The herd counts it neither as open nor as done in its own phase and lists it as open in phase 9. The arrow stands only at the end of the line. Once a phase's tasks are ticked on the base branch, every box still open in it, gate boxes included, is ticked or moved on this way; `jh check` checks both.
|
|
39
|
+
|
|
40
|
+
A task starts with its short name: `- [ ] **Name**: …` (a review-gate item keeps its own form). The name has 40 characters at most and names the subject and the result, what is different afterwards (`Heartbeat shows progress without ticks`, `` `ask-hint` finds questions anywhere ``); the how, the conditions, the origin and the evidence follow the colon. A phase's title is its name, its context goes into the origin line. A task the user adds later: `- [ ] **Name**: … (User <DD.MM.> <HH:MM>)`.
|
|
39
41
|
|
|
40
42
|
What the template's tables mean:
|
|
41
43
|
|
|
@@ -67,7 +69,7 @@ Every phase gets the review gate block from the template at its end. Add the rev
|
|
|
67
69
|
- **Shared code checked:** For every planned component it is documented why it is built new instead of reused or extended.
|
|
68
70
|
- **Tests defined:** The tests section is filled in or carries an explicit rationale why none are needed (decision tree in `.claude/rules/kit/testing.md`).
|
|
69
71
|
- **Model recommendations defined:** `## {{kit.heading.implementation}}` starts with the model table, one row per phase, justified by error cost × complexity.
|
|
70
|
-
- **Parallelism defined (3+ phases):** the dependencies table follows it
|
|
72
|
+
- **Parallelism defined (3+ phases):** the dependencies table follows it, a row for every phase, one added later too. Every migration, schema bump and new event of a phase is a serial marker, every shared file stands under {{kit.heading.writes}}; otherwise `/implement` starts two phases that collide.
|
|
71
73
|
- **arc42 check (after the plan exists):** apply the filter in `.claude/rules/kit/arc42.md` to the finished plan and **ask the user concretely** which sections should be extended. On "yes", add a concrete task to `## {{kit.heading.documentation}}`.
|
|
72
74
|
- **Architecture check (after the plan exists):** apply the filter in `.claude/rules/kit/architecture-files.md` — new ownership, a new invariant or an accepted risk each trigger a concrete question about the unit's `ARCHITECTURE.md`.
|
|
73
75
|
- **arc42 closing phase (3+ phases or a new service/package):** end the plan with the architecture documentation phase from the template. Add arc42 sections beyond it only when they deliver real value.
|
|
@@ -94,9 +96,8 @@ Derive the rows from the dependencies table and the model table, nothing else:
|
|
|
94
96
|
- **Plan:** a Markdown link to the plan, its path relative to the board's directory (`../<file>.md` for a plan
|
|
95
97
|
directly under `{{kit.planDirectory}}`).
|
|
96
98
|
Plans stay where they are; the row only links them.
|
|
97
|
-
- **Lane name:** short kebab case, unique on the board
|
|
98
|
-
|
|
99
|
-
watcher reporting the lane as `no agent`.
|
|
99
|
+
- **Lane name:** short kebab case, unique on the board; the worktree directory and the herdr agent carry the same name
|
|
100
|
+
(`/orchestrate` § 2).
|
|
100
101
|
- **Port:** `–`; the coordinator sets it when the lane starts, unless the project supplement assigns ports here.
|
|
101
102
|
|
|
102
103
|
Show the rows as the table they become: `## {{kit.heading.lanes}}` with the columns lane, port, plan, phases, starts
|
|
@@ -42,8 +42,8 @@ What changes concretely? Files, modules, flows.
|
|
|
42
42
|
|
|
43
43
|
### {{kit.heading.phase}} 1: ...
|
|
44
44
|
|
|
45
|
-
- [ ]
|
|
46
|
-
- [ ]
|
|
45
|
+
- [ ] **Name of step 1**: how, conditions, origin, evidence
|
|
46
|
+
- [ ] **Name of step 2**: how, conditions, origin, evidence
|
|
47
47
|
|
|
48
48
|
**Review gate {{kit.heading.phase}} 1:**
|
|
49
49
|
|
|
@@ -67,13 +67,13 @@ Which tests are needed? Decide with the decision tree in `.claude/rules/kit/test
|
|
|
67
67
|
|
|
68
68
|
What gets documented after completion?
|
|
69
69
|
|
|
70
|
-
- [ ] ...
|
|
70
|
+
- [ ] **Name**: ...
|
|
71
71
|
|
|
72
72
|
## Follow-ups
|
|
73
73
|
|
|
74
74
|
> Ideas that come up during the work: noted here, not built in this plan, presented together once the release PR is merged.
|
|
75
75
|
|
|
76
|
-
- [ ]
|
|
76
|
+
- [ ] **Name**: the idea in one sentence (User <DD.MM.> <HH:MM>, where it came up)
|
|
77
77
|
|
|
78
78
|
## Rejected Alternatives
|
|
79
79
|
|
|
@@ -42,8 +42,8 @@ Was ändert sich konkret? Dateien, Module, Flows.
|
|
|
42
42
|
|
|
43
43
|
### {{kit.heading.phase}} 1: ...
|
|
44
44
|
|
|
45
|
-
- [ ] Schritt 1
|
|
46
|
-
- [ ] Schritt 2
|
|
45
|
+
- [ ] **Name von Schritt 1**: Wie, Bedingungen, Herkunft, Beleg
|
|
46
|
+
- [ ] **Name von Schritt 2**: Wie, Bedingungen, Herkunft, Beleg
|
|
47
47
|
|
|
48
48
|
**Review-Gate {{kit.heading.phase}} 1:**
|
|
49
49
|
|
|
@@ -67,13 +67,13 @@ Welche Tests sind nötig? Entscheidung nach dem Entscheidungsbaum in `.claude/ru
|
|
|
67
67
|
|
|
68
68
|
Was wird nach Abschluss dokumentiert?
|
|
69
69
|
|
|
70
|
-
- [ ] ...
|
|
70
|
+
- [ ] **Name**: ...
|
|
71
71
|
|
|
72
72
|
## Follow-ups
|
|
73
73
|
|
|
74
74
|
> Ideen, die während der Umsetzung aufkommen: hier vorgemerkt, nicht in diesem Plan gebaut, gesammelt vorgelegt, sobald der Release-PR gemergt ist.
|
|
75
75
|
|
|
76
|
-
- [ ] Idee in einem Satz (User <TT.MM.> <HH:MM>, wo sie aufkam)
|
|
76
|
+
- [ ] **Name**: Idee in einem Satz (User <TT.MM.> <HH:MM>, wo sie aufkam)
|
|
77
77
|
|
|
78
78
|
## Verworfene Alternativen
|
|
79
79
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jackherd",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"claude-code",
|
|
6
6
|
"coding-agents",
|
|
7
7
|
"ai-agents",
|
|
8
8
|
"agent-skills",
|
|
9
9
|
"subagents",
|
|
10
|
+
"multi-agent",
|
|
10
11
|
"orchestration",
|
|
12
|
+
"plan-driven",
|
|
11
13
|
"git-worktree",
|
|
12
14
|
"herdr",
|
|
13
15
|
"planning",
|
package/recipes/README.md
CHANGED
|
@@ -15,7 +15,8 @@ Each recipe is one `watch.sources` entry for the jackherd watcher: put in your k
|
|
|
15
15
|
## Rules every recipe follows
|
|
16
16
|
|
|
17
17
|
- **A token never goes into an argument:** an argument shows in the process list. Where a tool keeps its own login, it is used; where a token sits in an environment variable, a small TypeScript script run by Bun (which runs wherever `jh` does) puts it from `process.env` straight into the request header.
|
|
18
|
-
- **A script
|
|
18
|
+
- **A script prints one JSON value on stdout and nothing else.**
|
|
19
|
+
- **A script fails with an exit code**, never with an empty answer the watcher would take for a source with nothing new, and it prints neither the token nor the body of a failed answer. A 200 that carries an `errors` list, as GraphQL answers, is a failure too. Every request and every tool call is bounded with `AbortSignal.timeout(20_000)`, well under the 30 seconds a source may run.
|
|
19
20
|
- **The scripts are files of their own** under [`sources/`](sources/). Copy one to `scripts/sources/<name>.ts` in your repository, the path the entry's `command` names. The closing `export {}` makes the file a module, so the type check accepts top-level `await` and the names do not clash between scripts.
|
|
20
21
|
- **A recipe that needs no script** (`gh …`, `curl` on a public page, `curl --netrc`) stays a direct command.
|
|
21
22
|
- **`bun` as the first word, never a path to it.** The watcher starts a source without a shell and reads `bun` to mean the Bun that runs the watcher itself, so the entry needs no absolute path, which would differ from machine to machine, even when Bun is not on the `PATH` (installed in `~/.bun/bin` only, say).
|
package/recipes/sources.md
CHANGED
|
@@ -36,7 +36,7 @@ Source reports are off unless the config lists `watch.sources`. The watcher cann
|
|
|
36
36
|
|
|
37
37
|
`items` is the path of the list in the output (without it the output is the list), `map` picks each entry's fields: `id` is required, `updatedAt` too unless a content field (`title`, `author`, `body`, `url`) or `reportOn` can tell a change, and `title`, `url`, `body`, `author` and `status` (a ticket's status, below) are optional; any further field you name (`priority`, `assignee`; a letter, then letters, digits and `_`) is read for `reportOn`. A path is `.` for the whole value or a chain of `.key`, `[n]` and `[-n]` (counted from the end), nothing more; for anything else, put a script in between.
|
|
38
38
|
|
|
39
|
-
What counts as news: an unknown `id` reports `new`; `updated` only when what you read changed, a short hash over `title`, `author`, `body` and `url`. A change of the time alone (a label, a sprint, a status) wakes nobody; where none of the four is mapped, `updatedAt` is the content, and then it is required. An entry whose `author` is in `ignoreAuthors` (case ignored) is recorded but wakes nobody, so your own comment does not. The first successful run of a source is a baseline, and the saved state holds per source only each id with its hash (with `reportOn` also the values of those fields, which may name people, such as an assignee), the 1000 seen most recently, so a restart repeats nothing and an entry that falls out of a time window and comes back is not new. Everything one GitHub read finds goes into one report, at most `reportLimit` entries per source (`5`), the rest as one line `=== source tickets +3 more`. The body shows its first line with text that is no Markdown heading (a bot's `## Code review` is skipped; a body of headings only shows the first), at most 160 characters, and the URL only as an `https:` URL without user name or password. `every` is in minutes, at least 1, `5` by default; the sources run with the GitHub read, so at most once a minute,
|
|
39
|
+
What counts as news: an unknown `id` reports `new`; `updated` only when what you read changed, a short hash over `title`, `author`, `body` and `url`. A change of the time alone (a label, a sprint, a status) wakes nobody; where none of the four is mapped, `updatedAt` is the content, and then it is required. An entry whose `author` is in `ignoreAuthors` (case ignored) is recorded but wakes nobody, so your own comment does not. The first successful run of a source is a baseline, and the saved state holds per source only each id with its hash (with `reportOn` also the values of those fields, which may name people, such as an assignee), the 1000 seen most recently, so a restart repeats nothing and an entry that falls out of a time window and comes back is not new. Everything one GitHub read finds goes into one report, at most `reportLimit` entries per source (`5`), the rest as one line `=== source tickets +3 more`. The body shows its first line with text that is no Markdown heading (a bot's `## Code review` is skipped; a body of headings only shows the first), at most 160 characters, and the URL only as an `https:` URL without user name or password. `every` is in minutes, at least 1, `5` by default; the sources run with the GitHub read, so at most once a minute. The saved state keeps when each source last ran, failed or not, so a restart runs a source only once its `every` has passed; a source the state does not know yet, a new one or one from a state written before that time was kept, runs at once.
|
|
40
40
|
|
|
41
41
|
**Report a status change.** `reportOn` names the mapped fields whose change reports, in place of the content hash: with `"reportOn": ["status"]` a ticket that moves from one column to the next wakes the coordinator, and a new comment alone does not. The report names each changed field with its old and new value, cut to 60 characters, `(empty)` for none, in one line; the body follows only when `body` is one of the changed fields. An unknown `id` still reports `new`, and `ignoreAuthors`, `reportLimit` and the one report per read stay as they are. `status` is just a field name: any mapped field but `id` may be listed, including `title` or `body`.
|
|
42
42
|
|
|
@@ -60,6 +60,10 @@ A run that ends with an exit code other than 0, runs past 30 seconds (then its w
|
|
|
60
60
|
|
|
61
61
|
**Show a ticket beside its plan.** `"ticket": true` marks a source's entries as tickets. The herd then shows each one as a badge in the 📎 line of its lane's plan, right after `📄 Plan`: `📡 T-123 · In Progress`, linked to the ticket. A ticket belongs to the source's `lane`, and also to any lane whose board row, name or plan names its id as a whole word (`T-12` is not found in `T-123`); so one source listing all your open tickets works without `lane`. `map.status` is the status the badge shows; without it the badge shows only the key. A ticket whose last reported change is less than a day old is amber, and a folded lane then shows `📡 <n>` in amber before its bar, linked to the first new ticket. A lane shows at most three tickets, new ones first, then `+<n>`; a link chip of the plan with the same URL gives way to the badge. For this the saved state keeps, of a marked source only, each entry of its last successful run with its URL, its status (stored like a `reportOn` value) and the time its last change was reported. The badges come from the watcher's state like the pull request badges: without a running watcher or with a state older than three minutes there are none, and the header says `watcher off` or `state 4 min old`, also without a pull request. The [GitHub Projects recipe](#user-content-recipes) is a ticket source with a status.
|
|
62
62
|
|
|
63
|
+
**Show a source in the herd.** A source without `ticket` appears in the herd's `Sources` block, one row each: its name, when it last ran (`ran 12 min ago`), since when it fails (`failing since 3 h ago`, mark red) and its newest report (mark amber while that report is under a day old). A source with several entries is one folded row; a click opens its entries (up to five, then `+<n> more`), each with its title, `map.count` as `113×` and the time of its last change. A click on an entry opens its `url` (the watcher keeps it only as a checked `https:` URL of at most 200 characters, so map a short link), `[ask]` asks the coordinator to look at that entry. An entry that an open row of "Waiting for you" already asks about (the row's `Origin` names the source and its text the title) is dim and says `↑ asked`; the row is amber only for a new entry nobody asked about. The watcher keeps the last 20 entries of such a source. `tone` (amber, teal, grey, red, green, blue or magenta) sets the mark's colour while the report is new, instead of the tone of the role `new`; the roles `waiting`, `new`, `running` and `resting` take their tones from `plans.tones` in the config (defaults: amber, amber, teal, grey).
|
|
64
|
+
|
|
65
|
+
**Name a source in "Waiting for you".** A row of the board's waiting section whose `Origin` cell is the name of a source (case ignored) shows `<label>: <row>` in the herd; `label` (one line, at most 40 characters) is optional, without it the source's name stands before the row. `/jh-follow` proposes a short label when it writes the entry.
|
|
66
|
+
|
|
63
67
|
**How you hear about it.** The report goes to the coordinator, as a `=== source …` event of its watcher. It passes you one line with the entry, its gist and the link. A decision inside it, such as a product owner's answer, it takes over only after you confirmed it, since the text comes from outside. You hear of it only while the coordinator's session and its watcher run; what happened in between is reported at the next start. There is no desktop notification of its own: you get the one herdr shows for any lane that needs you. What a source reports is a change in the mapped content fields (`title`, `author`, `body`, `url`), or with `reportOn` a change of the fields it names, such as a status.
|
|
64
68
|
|
|
65
69
|
**Try a source before the watcher runs it.** `jh watch source <name> --dry-run` runs a configured source once; a draft not yet in the config comes as a file (`jh watch source draft.json --dry-run`, one source object) or on stdin (`-`). It lists the entries (`<id> · <author> · <title>`), says that the watcher's first run of the source is a baseline and reports nothing, and shows one entry as a later report would print it; it neither reads nor changes the watcher's state. A field of `map` that is empty in every entry is named with its path (`map.updatedAt: in 23 of 23 entries empty (.fields.updated)`), read from the raw output, so a run that failed on a missing `id` or `updatedAt` names it too; the dry run then ends with exit code 1, as for a failed run, and `/jh-follow` does not write the source. Without entries it says `0 entries: map not checked`. `--raw` adds the command's output, for finding the paths of `items` and `map`:
|
|
@@ -297,6 +297,65 @@
|
|
|
297
297
|
}
|
|
298
298
|
},
|
|
299
299
|
"additionalProperties": false
|
|
300
|
+
},
|
|
301
|
+
"tones": {
|
|
302
|
+
"default": {},
|
|
303
|
+
"type": "object",
|
|
304
|
+
"properties": {
|
|
305
|
+
"waiting": {
|
|
306
|
+
"default": "amber",
|
|
307
|
+
"type": "string",
|
|
308
|
+
"enum": [
|
|
309
|
+
"amber",
|
|
310
|
+
"teal",
|
|
311
|
+
"grey",
|
|
312
|
+
"red",
|
|
313
|
+
"green",
|
|
314
|
+
"blue",
|
|
315
|
+
"magenta"
|
|
316
|
+
]
|
|
317
|
+
},
|
|
318
|
+
"new": {
|
|
319
|
+
"default": "amber",
|
|
320
|
+
"type": "string",
|
|
321
|
+
"enum": [
|
|
322
|
+
"amber",
|
|
323
|
+
"teal",
|
|
324
|
+
"grey",
|
|
325
|
+
"red",
|
|
326
|
+
"green",
|
|
327
|
+
"blue",
|
|
328
|
+
"magenta"
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
"running": {
|
|
332
|
+
"default": "teal",
|
|
333
|
+
"type": "string",
|
|
334
|
+
"enum": [
|
|
335
|
+
"amber",
|
|
336
|
+
"teal",
|
|
337
|
+
"grey",
|
|
338
|
+
"red",
|
|
339
|
+
"green",
|
|
340
|
+
"blue",
|
|
341
|
+
"magenta"
|
|
342
|
+
]
|
|
343
|
+
},
|
|
344
|
+
"resting": {
|
|
345
|
+
"default": "grey",
|
|
346
|
+
"type": "string",
|
|
347
|
+
"enum": [
|
|
348
|
+
"amber",
|
|
349
|
+
"teal",
|
|
350
|
+
"grey",
|
|
351
|
+
"red",
|
|
352
|
+
"green",
|
|
353
|
+
"blue",
|
|
354
|
+
"magenta"
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
"additionalProperties": false
|
|
300
359
|
}
|
|
301
360
|
},
|
|
302
361
|
"additionalProperties": false
|
|
@@ -381,10 +440,36 @@
|
|
|
381
440
|
"type": "integer",
|
|
382
441
|
"exclusiveMinimum": 0,
|
|
383
442
|
"maximum": 9007199254740991
|
|
443
|
+
},
|
|
444
|
+
"auto": {
|
|
445
|
+
"type": "object",
|
|
446
|
+
"properties": {
|
|
447
|
+
"since": {
|
|
448
|
+
"type": "string",
|
|
449
|
+
"format": "date",
|
|
450
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
|
|
451
|
+
},
|
|
452
|
+
"until": {
|
|
453
|
+
"type": "string",
|
|
454
|
+
"format": "date",
|
|
455
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
"required": [
|
|
459
|
+
"since",
|
|
460
|
+
"until"
|
|
461
|
+
],
|
|
462
|
+
"additionalProperties": false
|
|
384
463
|
}
|
|
385
464
|
},
|
|
386
465
|
"additionalProperties": false
|
|
387
466
|
},
|
|
467
|
+
"taskSignalMinutes": {
|
|
468
|
+
"default": 10,
|
|
469
|
+
"type": "integer",
|
|
470
|
+
"exclusiveMinimum": 0,
|
|
471
|
+
"maximum": 9007199254740991
|
|
472
|
+
},
|
|
388
473
|
"previews": {
|
|
389
474
|
"type": "object",
|
|
390
475
|
"properties": {
|
|
@@ -457,6 +542,9 @@
|
|
|
457
542
|
},
|
|
458
543
|
"status": {
|
|
459
544
|
"type": "string"
|
|
545
|
+
},
|
|
546
|
+
"count": {
|
|
547
|
+
"type": "string"
|
|
460
548
|
}
|
|
461
549
|
},
|
|
462
550
|
"required": [
|
|
@@ -480,6 +568,12 @@
|
|
|
480
568
|
"minimum": 1,
|
|
481
569
|
"maximum": 9007199254740991
|
|
482
570
|
},
|
|
571
|
+
"label": {
|
|
572
|
+
"type": "string",
|
|
573
|
+
"minLength": 1,
|
|
574
|
+
"maxLength": 40,
|
|
575
|
+
"pattern": "^[^\\r\\n]+$"
|
|
576
|
+
},
|
|
483
577
|
"lane": {
|
|
484
578
|
"type": "string",
|
|
485
579
|
"minLength": 1
|
|
@@ -487,6 +581,18 @@
|
|
|
487
581
|
"ticket": {
|
|
488
582
|
"type": "boolean"
|
|
489
583
|
},
|
|
584
|
+
"tone": {
|
|
585
|
+
"type": "string",
|
|
586
|
+
"enum": [
|
|
587
|
+
"amber",
|
|
588
|
+
"teal",
|
|
589
|
+
"grey",
|
|
590
|
+
"red",
|
|
591
|
+
"green",
|
|
592
|
+
"blue",
|
|
593
|
+
"magenta"
|
|
594
|
+
]
|
|
595
|
+
},
|
|
490
596
|
"reportOn": {
|
|
491
597
|
"minItems": 1,
|
|
492
598
|
"maxItems": 10,
|
|
@@ -515,9 +621,7 @@
|
|
|
515
621
|
"commands": {
|
|
516
622
|
"default": [
|
|
517
623
|
"gh pr merge",
|
|
518
|
-
"gh workflow run"
|
|
519
|
-
"gh run rerun",
|
|
520
|
-
"gh api"
|
|
624
|
+
"gh workflow run"
|
|
521
625
|
],
|
|
522
626
|
"type": "array",
|
|
523
627
|
"items": {
|
|
@@ -555,12 +659,26 @@
|
|
|
555
659
|
},
|
|
556
660
|
"additionalProperties": false
|
|
557
661
|
},
|
|
662
|
+
"alwaysAsk": {
|
|
663
|
+
"default": [],
|
|
664
|
+
"type": "array",
|
|
665
|
+
"items": {
|
|
666
|
+
"type": "string",
|
|
667
|
+
"pattern": "^[A-Za-z0-9_.*/-]+$"
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
"requiredChecks": {
|
|
671
|
+
"type": "array",
|
|
672
|
+
"items": {
|
|
673
|
+
"type": "string",
|
|
674
|
+
"maxLength": 200
|
|
675
|
+
}
|
|
676
|
+
},
|
|
558
677
|
"planApproval": {
|
|
559
|
-
"
|
|
678
|
+
"deprecated": true,
|
|
560
679
|
"type": "object",
|
|
561
680
|
"properties": {
|
|
562
681
|
"exceptPaths": {
|
|
563
|
-
"default": [],
|
|
564
682
|
"type": "array",
|
|
565
683
|
"items": {
|
|
566
684
|
"type": "string",
|
|
@@ -602,6 +720,10 @@
|
|
|
602
720
|
"askUserQuestion": {
|
|
603
721
|
"default": true,
|
|
604
722
|
"type": "boolean"
|
|
723
|
+
},
|
|
724
|
+
"taskSignal": {
|
|
725
|
+
"default": true,
|
|
726
|
+
"type": "boolean"
|
|
605
727
|
}
|
|
606
728
|
},
|
|
607
729
|
"additionalProperties": false
|
|
@@ -741,6 +863,18 @@
|
|
|
741
863
|
],
|
|
742
864
|
"additionalProperties": false
|
|
743
865
|
},
|
|
866
|
+
"skillBudgets": {
|
|
867
|
+
"type": "object",
|
|
868
|
+
"propertyNames": {
|
|
869
|
+
"type": "string",
|
|
870
|
+
"minLength": 1
|
|
871
|
+
},
|
|
872
|
+
"additionalProperties": {
|
|
873
|
+
"type": "integer",
|
|
874
|
+
"exclusiveMinimum": 0,
|
|
875
|
+
"maximum": 9007199254740991
|
|
876
|
+
}
|
|
877
|
+
},
|
|
744
878
|
"looseTests": {
|
|
745
879
|
"default": false,
|
|
746
880
|
"type": "boolean"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { matchesAnyGlob } from './glob'
|
|
2
|
+
|
|
3
|
+
/** The kit agent that judges how a changed agent text steers its reader. */
|
|
4
|
+
export const AGENT_TEXT_REVIEWER = 'skill-review'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Texts an agent reads as instructions, wherever they sit: the project's `.claude/` and a kit source tree alike.
|
|
8
|
+
* Only Markdown counts, so a script beside a skill or a source directory named `agents` stays out.
|
|
9
|
+
*/
|
|
10
|
+
const AGENT_TEXT_GLOBS = [
|
|
11
|
+
'**/skills/**/*.md',
|
|
12
|
+
'**/agents/**/*.md',
|
|
13
|
+
'**/rules/**/*.md',
|
|
14
|
+
'.claude/project/**/*.md',
|
|
15
|
+
'**/CLAUDE.md',
|
|
16
|
+
'**/AGENTS.md',
|
|
17
|
+
] as const
|
|
18
|
+
|
|
19
|
+
export const isAgentTextPath = (path: string): boolean => matchesAnyGlob(path, AGENT_TEXT_GLOBS)
|
|
20
|
+
|
|
21
|
+
/** The agent-text reviewer when a change touches an agent text. */
|
|
22
|
+
export const agentTextReviewers = (paths: readonly string[]): string[] =>
|
|
23
|
+
paths.some(isAgentTextPath) ? [AGENT_TEXT_REVIEWER] : []
|
package/src/check/command.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { publicContentCheck } from './public-content-check'
|
|
|
15
15
|
import { artifactResult, formatPlanHeader, formatResults, skippedSlowResult, summarize } from './report'
|
|
16
16
|
import { type CheckResult, SKIP_SLOW_FLAG } from './result'
|
|
17
17
|
import { type CheckSettings, CONFIG_PATH, parseCheckSettings } from './settings'
|
|
18
|
+
import { skillBudgetCheck } from './skill-budgets'
|
|
18
19
|
|
|
19
20
|
type CheckTarget = { root: string; configPath: string }
|
|
20
21
|
|
|
@@ -154,6 +155,7 @@ export const run = async (args: string[], home: string = homedir()): Promise<num
|
|
|
154
155
|
const results = await executePlan(plan, root, args.includes(SKIP_SLOW_FLAG), [
|
|
155
156
|
publicContent,
|
|
156
157
|
deferralArrows,
|
|
158
|
+
skillBudgetCheck(root, settings.check.skillBudgets),
|
|
157
159
|
...planStructure,
|
|
158
160
|
])
|
|
159
161
|
print(formatResults(results))
|
|
@@ -4,10 +4,13 @@ import {
|
|
|
4
4
|
boardRowsWithoutPhase,
|
|
5
5
|
boardRowsWithProse,
|
|
6
6
|
hasLanesTable,
|
|
7
|
+
type LongPhaseTitle,
|
|
8
|
+
longPhaseTitles,
|
|
7
9
|
type MergedPhaseWithOpenBoxes,
|
|
8
10
|
mergedPhasesWithOpenBoxes,
|
|
9
11
|
newPhasesWithoutReviewGate,
|
|
10
12
|
PHASE_CELL_LIMIT,
|
|
13
|
+
PHASE_TITLE_LIMIT,
|
|
11
14
|
type PhaseWithoutGate,
|
|
12
15
|
type PhaseWithoutRow,
|
|
13
16
|
PLAN_VOCABULARY,
|
|
@@ -17,6 +20,10 @@ import {
|
|
|
17
20
|
phasesWithoutDependencyRow,
|
|
18
21
|
type RowWithoutPhase,
|
|
19
22
|
type RowWithProse,
|
|
23
|
+
SHORT_NAME_LIMIT,
|
|
24
|
+
SHORT_NAME_PROBLEM,
|
|
25
|
+
type ShortNameFinding,
|
|
26
|
+
shortNameFindings,
|
|
20
27
|
} from '../plan-format'
|
|
21
28
|
import { changedPlansOf } from './deferral-arrows'
|
|
22
29
|
import { CHECK_STATUS, type CheckResult } from './result'
|
|
@@ -129,6 +136,30 @@ const unlistedReport: Report<PhaseWithoutRow> = {
|
|
|
129
136
|
clean: 'every phase has a row in the dependency table',
|
|
130
137
|
}
|
|
131
138
|
|
|
139
|
+
const shortNameText = (found: Found<ShortNameFinding>): string =>
|
|
140
|
+
found.problem === SHORT_NAME_PROBLEM.long
|
|
141
|
+
? `${found.path}:${found.line} Phase ${found.phase}: the short name has ${found.length} characters (limit ${SHORT_NAME_LIMIT})`
|
|
142
|
+
: `${found.path}:${found.line} Phase ${found.phase}: open task has no short name; start it with - [ ] **Name**: (${SHORT_NAME_LIMIT} characters at most)`
|
|
143
|
+
|
|
144
|
+
const shortNameReport: Report<ShortNameFinding> = {
|
|
145
|
+
name: 'Task short names',
|
|
146
|
+
noun: 'plan',
|
|
147
|
+
rule: 'open tasks the change wrote start with a short name',
|
|
148
|
+
unmet: CHECK_STATUS.warn,
|
|
149
|
+
text: shortNameText,
|
|
150
|
+
clean: 'every open task the change wrote has a short name',
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const titleReport: Report<LongPhaseTitle> = {
|
|
154
|
+
name: 'Phase title length',
|
|
155
|
+
noun: 'plan',
|
|
156
|
+
rule: `phase titles the change wrote have ${PHASE_TITLE_LIMIT} characters at most`,
|
|
157
|
+
unmet: CHECK_STATUS.warn,
|
|
158
|
+
text: ({ path, line, id, title, length }) =>
|
|
159
|
+
`${path}:${line} Phase ${id} ${title} has ${length} characters (limit ${PHASE_TITLE_LIMIT}); the title is the phase's name, its context goes into the origin line`,
|
|
160
|
+
clean: `every phase title the change wrote has ${PHASE_TITLE_LIMIT} characters at most`,
|
|
161
|
+
}
|
|
162
|
+
|
|
132
163
|
const withPath =
|
|
133
164
|
<T>(path: string) =>
|
|
134
165
|
(found: T): Found<T> => ({ ...found, path })
|
|
@@ -150,6 +181,12 @@ const mergedOf = ({ path, current, base }: Changed, language: PlanLanguage): Fou
|
|
|
150
181
|
const unlistedOf = ({ path, current }: Changed, language: PlanLanguage): Found<PhaseWithoutRow>[] | null =>
|
|
151
182
|
phasesWithoutDependencyRow(current, language)?.map(withPath(path)) ?? null
|
|
152
183
|
|
|
184
|
+
const shortNamesOf = ({ path, current, previousLines }: Changed, language: PlanLanguage): Found<ShortNameFinding>[] =>
|
|
185
|
+
shortNameFindings(current, language, previousLines).map(withPath(path))
|
|
186
|
+
|
|
187
|
+
const titlesOf = ({ path, current, previousLines }: Changed, language: PlanLanguage): Found<LongPhaseTitle>[] =>
|
|
188
|
+
longPhaseTitles(current, language, previousLines).map(withPath(path))
|
|
189
|
+
|
|
153
190
|
const changedContents = ({ root, previousOf, baseOf, language }: PlanCheckInput, plans: readonly string[]): Changed[] =>
|
|
154
191
|
plans
|
|
155
192
|
.filter((path) => existsSync(join(root, path)))
|
|
@@ -166,10 +203,12 @@ const changedContents = ({ root, previousOf, baseOf, language }: PlanCheckInput,
|
|
|
166
203
|
* - a lane row the change added or edited that names no phase warns, and so does one whose phases cell is longer
|
|
167
204
|
* than `PHASE_CELL_LIMIT` or records a side order of the user;
|
|
168
205
|
* - a phase whose tasks are all ticked on the base branch and that still has open boxes without `→ P<n>` warns;
|
|
169
|
-
* - a phase of a plan with a dependency table that has no row in it warns
|
|
206
|
+
* - a phase of a plan with a dependency table that has no row in it warns;
|
|
207
|
+
* - an open task the change added or edited without a short name, or with one over `SHORT_NAME_LIMIT`, warns, and so
|
|
208
|
+
* does a phase title it wrote over `PHASE_TITLE_LIMIT`.
|
|
170
209
|
*
|
|
171
210
|
* Empty when no plan changed. The row and prose results are left out when no changed file has a lanes table, the
|
|
172
|
-
* merged
|
|
211
|
+
* merged, short-name and phase-title results when every one has, the dependency result when no changed plan has the table.
|
|
173
212
|
*/
|
|
174
213
|
export const planStructureChecks = (input: PlanCheckInput): CheckResult[] => {
|
|
175
214
|
const files = changedPlansOf(input.paths, input.planDirectory)
|
|
@@ -185,6 +224,8 @@ export const planStructureChecks = (input: PlanCheckInput): CheckResult[] => {
|
|
|
185
224
|
const prose = boards.flatMap((file) => proseOf(file, language))
|
|
186
225
|
const merged = plans.flatMap((file) => mergedOf(file, language))
|
|
187
226
|
const unlisted = unlistedPerPlan.flat()
|
|
227
|
+
const shortNames = plans.flatMap((file) => shortNamesOf(file, language))
|
|
228
|
+
const titles = plans.flatMap((file) => titlesOf(file, language))
|
|
188
229
|
const durationMs = Date.now() - started
|
|
189
230
|
return [
|
|
190
231
|
findingsResult(gateReport(language), gates, files.length, durationMs),
|
|
@@ -198,5 +239,11 @@ export const planStructureChecks = (input: PlanCheckInput): CheckResult[] => {
|
|
|
198
239
|
...(unlistedPerPlan.length > 0
|
|
199
240
|
? [findingsResult(unlistedReport, unlisted, unlistedPerPlan.length, durationMs)]
|
|
200
241
|
: []),
|
|
242
|
+
...(plans.length > 0
|
|
243
|
+
? [
|
|
244
|
+
findingsResult(shortNameReport, shortNames, plans.length, durationMs),
|
|
245
|
+
findingsResult(titleReport, titles, plans.length, durationMs),
|
|
246
|
+
]
|
|
247
|
+
: []),
|
|
201
248
|
]
|
|
202
249
|
}
|
package/src/check/plan.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { JackherdConfig } from '../config'
|
|
2
|
+
import { agentTextReviewers } from './agent-text-reviewers'
|
|
2
3
|
import { type ChangedFile, generatorOwnedFiles, stagedBuildArtifacts } from './changes'
|
|
3
4
|
import { tokenize } from './command-line'
|
|
4
5
|
import { looseDirectories, packageRootsFor } from './roots'
|
|
@@ -90,7 +91,13 @@ export const planChecks = (
|
|
|
90
91
|
return {
|
|
91
92
|
paths,
|
|
92
93
|
roots,
|
|
93
|
-
reviewers: [
|
|
94
|
+
reviewers: [
|
|
95
|
+
...new Set([
|
|
96
|
+
...reviewersFor(paths, config.reviewerRoutes),
|
|
97
|
+
...publicContentReviewers(paths, config.publicContent),
|
|
98
|
+
...agentTextReviewers(paths),
|
|
99
|
+
]),
|
|
100
|
+
],
|
|
94
101
|
generated: generatorOwnedFiles(paths, config.generatorOwned),
|
|
95
102
|
artifacts: stagedBuildArtifacts(files, config.buildArtifacts),
|
|
96
103
|
checks:
|
package/src/check/schema.ts
CHANGED
|
@@ -57,6 +57,9 @@ const publicContent = z.strictObject({
|
|
|
57
57
|
reviewer: nonEmpty.default('public-content'),
|
|
58
58
|
})
|
|
59
59
|
|
|
60
|
+
/** A budget in words per path or glob from the project root; a key joining two paths with ` + ` budgets them together. */
|
|
61
|
+
const skillBudgets = z.record(glob, z.number().int().positive())
|
|
62
|
+
|
|
60
63
|
/**
|
|
61
64
|
* The `check` block of `.jackherd/config.json`. Test and type commands come from the top-level
|
|
62
65
|
* `commands` block; this block holds what only the check runner reads.
|
|
@@ -73,6 +76,11 @@ export const checkConfigSchema = z.strictObject({
|
|
|
73
76
|
buildArtifacts: z.array(glob).default([...DEFAULT_BUILD_ARTIFACT_GLOBS]),
|
|
74
77
|
reviewerRoutes: z.array(reviewerRoute).default([]),
|
|
75
78
|
publicContent: publicContent.optional(),
|
|
79
|
+
/**
|
|
80
|
+
* Word budgets for the files an agent loads again and again. An entry replaces the kit's budget for the same
|
|
81
|
+
* path and sets one for the project's own skills; a file with none is measured against the kit's default.
|
|
82
|
+
*/
|
|
83
|
+
skillBudgets: skillBudgets.optional(),
|
|
76
84
|
/** Also run tests in directories that hold test files but belong to no package. */
|
|
77
85
|
looseTests: z.boolean().default(false),
|
|
78
86
|
})
|
|
@@ -81,3 +89,4 @@ export type CheckConfig = z.infer<typeof checkConfigSchema>
|
|
|
81
89
|
export type ReviewerRoute = CheckConfig['reviewerRoutes'][number]
|
|
82
90
|
export type ProjectCheck = CheckConfig['checks'][number]
|
|
83
91
|
export type PublicContentConfig = NonNullable<CheckConfig['publicContent']>
|
|
92
|
+
export type SkillBudgets = NonNullable<CheckConfig['skillBudgets']>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Words allowed to a skill the kit did not install and the project gave no budget. */
|
|
2
|
+
export const OWN_SKILL_BUDGET = 2000
|
|
3
|
+
|
|
4
|
+
/** Words allowed to a file the kit installed when neither the project nor `KIT_SKILL_BUDGETS` names it. */
|
|
5
|
+
export const KIT_FILE_FALLBACK_BUDGET = 2000
|
|
6
|
+
|
|
7
|
+
/** Words allowed to a project supplement, `.claude/project/<skill>.md`. */
|
|
8
|
+
export const SUPPLEMENT_BUDGET = 800
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The kit's word budgets, by path from the project root. A key joining two paths with ` + ` budgets what is
|
|
12
|
+
* loaded together. A budget is the measured size plus at most 10 %, so a file has room for a sentence, not for a
|
|
13
|
+
* section; a file that needs more justifies the growth where it raises the number.
|
|
14
|
+
*/
|
|
15
|
+
export const KIT_SKILL_BUDGETS: Readonly<Record<string, number>> = {
|
|
16
|
+
'.claude/skills/devops/SKILL.md': 321,
|
|
17
|
+
'.claude/skills/diagnose/SKILL.md': 1521,
|
|
18
|
+
'.claude/skills/diagnose/references/feedback-loops.md': 636,
|
|
19
|
+
'.claude/skills/follow/SKILL.md': 1100,
|
|
20
|
+
'.claude/skills/grill/SKILL.md': 685,
|
|
21
|
+
'.claude/skills/implement/SKILL.md': 2190,
|
|
22
|
+
'.claude/skills/orchestrate/SKILL.md': 7820,
|
|
23
|
+
'.claude/skills/orchestrate/references/acceptance.md': 200,
|
|
24
|
+
'.claude/skills/orchestrate/references/close-lane.md': 250,
|
|
25
|
+
'.claude/skills/orchestrate/references/coordinator-handover.md': 551,
|
|
26
|
+
'.claude/skills/orchestrate/references/draft-review.md': 300,
|
|
27
|
+
'.claude/skills/orchestrate/references/events.md': 1070,
|
|
28
|
+
'.claude/skills/orchestrate/references/herd.md': 299,
|
|
29
|
+
'.claude/skills/orchestrate/references/lane-handover.md': 280,
|
|
30
|
+
'.claude/skills/orchestrate/references/lane-session.md': 280,
|
|
31
|
+
'.claude/skills/orchestrate/references/lane-setup.md': 380,
|
|
32
|
+
'.claude/skills/orchestrate/references/plan-commit.md': 330,
|
|
33
|
+
'.claude/skills/orchestrate/references/sensitive-input.md': 269,
|
|
34
|
+
'.claude/skills/plan/SKILL.md': 1920,
|
|
35
|
+
'.claude/skills/plan/references/template-English.md': 536,
|
|
36
|
+
'.claude/skills/plan/references/template-German.md': 501,
|
|
37
|
+
'.claude/skills/pre-commit/SKILL.md': 1003,
|
|
38
|
+
'.claude/skills/review/SKILL.md': 319,
|
|
39
|
+
'.claude/skills/security-audit/SKILL.md': 519,
|
|
40
|
+
'.claude/skills/start/SKILL.md': 607,
|
|
41
|
+
'.claude/skills/uncle-bob/SKILL.md': 466,
|
|
42
|
+
'.claude/skills/orchestrate/SKILL.md + .claude/project/orchestrate.md': 8620,
|
|
43
|
+
'.claude/agents/devops.md': 104,
|
|
44
|
+
'.claude/agents/explore.md': 132,
|
|
45
|
+
'.claude/agents/implement.md': 303,
|
|
46
|
+
'.claude/agents/plan.md': 209,
|
|
47
|
+
'.claude/agents/public-content.md': 449,
|
|
48
|
+
'.claude/agents/security-audit.md': 128,
|
|
49
|
+
'.claude/agents/skill-review.md': 468,
|
|
50
|
+
'.claude/agents/spec-review.md': 1137,
|
|
51
|
+
'.claude/agents/uncle-bob.md': 111,
|
|
52
|
+
'.claude/rules/kit/code-style.md': 1664,
|
|
53
|
+
'.claude/rules/kit/pull-requests.md': 601,
|
|
54
|
+
'.claude/rules/kit/testing.md': 741,
|
|
55
|
+
}
|