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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isHintOn } from '../shared/config'
|
|
2
|
+
import { isWithinDirectory } from '../shared/files'
|
|
3
|
+
import { firstPromptOf, isLanePrompt } from '../shared/transcript'
|
|
4
|
+
import { decideTaskHint, hasTaskInProgress, TASK_HINT_SUBAGENT_TEXT, TASK_HINT_TEXT } from './decision'
|
|
5
|
+
|
|
6
|
+
/** An Edit, Write or Agent call as the hook receives it: the session, its directory, its transcript and the tool. */
|
|
7
|
+
export type TaskHintCall = { session: string; cwd: string; transcriptPath: string; tool: string }
|
|
8
|
+
|
|
9
|
+
/** The tool that starts a subagent. */
|
|
10
|
+
const SUBAGENT_TOOL = 'Agent'
|
|
11
|
+
|
|
12
|
+
/** Everything with a side effect, passed in so a test reaches the flow without git, files or a home directory. */
|
|
13
|
+
export type TaskHintDependencies = {
|
|
14
|
+
mainCheckoutOf: (cwd: string) => string
|
|
15
|
+
repositoryRootOf: (cwd: string) => string
|
|
16
|
+
/** The parsed configs, the main checkout's first, then the home directory's. */
|
|
17
|
+
configs: (root: string) => Record<string, unknown>[]
|
|
18
|
+
/** The first lines of the transcript, which hold the session's first prompt. */
|
|
19
|
+
readHead: (path: string) => string[]
|
|
20
|
+
/** The last lines of the transcript, which hold the task list's latest state. */
|
|
21
|
+
readTail: (path: string) => string[]
|
|
22
|
+
/** The directories of the open `jh task` records in the main checkout. */
|
|
23
|
+
openTaskDirectories: (main: string) => string[]
|
|
24
|
+
/** Whether this session was hinted for its current task already. */
|
|
25
|
+
readMark: (main: string, session: string) => boolean
|
|
26
|
+
writeMark: (main: string, session: string, hinted: boolean) => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const isSignalled = (call: TaskHintCall, main: string, worktree: string, dependencies: TaskHintDependencies): boolean =>
|
|
30
|
+
dependencies.openTaskDirectories(main).some((directory) => isWithinDirectory(directory, worktree)) ||
|
|
31
|
+
hasTaskInProgress(dependencies.readTail(call.transcriptPath))
|
|
32
|
+
|
|
33
|
+
const hintFor = (call: TaskHintCall, dependencies: TaskHintDependencies): string | null => {
|
|
34
|
+
const main = dependencies.mainCheckoutOf(call.cwd)
|
|
35
|
+
const worktree = dependencies.repositoryRootOf(call.cwd)
|
|
36
|
+
const hinted = dependencies.readMark(main, call.session)
|
|
37
|
+
const subagent = call.tool === SUBAGENT_TOOL
|
|
38
|
+
const decision = decideTaskHint({
|
|
39
|
+
enabled: isHintOn(dependencies.configs(main), 'taskSignal'),
|
|
40
|
+
lane: isLanePrompt(firstPromptOf(dependencies.readHead(call.transcriptPath).join('\n'))),
|
|
41
|
+
signalled: isSignalled(call, main, worktree, dependencies),
|
|
42
|
+
hinted,
|
|
43
|
+
subagent,
|
|
44
|
+
})
|
|
45
|
+
if (decision.hinted !== hinted) dependencies.writeMark(main, call.session, decision.hinted)
|
|
46
|
+
if (!decision.hint) return null
|
|
47
|
+
return subagent ? TASK_HINT_SUBAGENT_TEXT : TASK_HINT_TEXT
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The hint after an Edit, Write or Agent call in a lane without an open task signal (D112): a `jh task` record whose
|
|
52
|
+
* directory lies in the session's worktree, or a task of the session's own list in progress. Once per task for an edit:
|
|
53
|
+
* the mark set by the hint is cleared by the next signal; at every subagent started without one. Only a session whose first prompt opens a lane is hinted; any failure answers
|
|
54
|
+
* nothing, so the edit is never held up.
|
|
55
|
+
*/
|
|
56
|
+
export const runTaskHint = (call: TaskHintCall, dependencies: TaskHintDependencies): string | null => {
|
|
57
|
+
try {
|
|
58
|
+
return hintFor(call, dependencies)
|
|
59
|
+
} catch {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { isRecord } from '../shared/json'
|
|
4
|
+
import { writePrivateRecord } from '../shared/private-directory'
|
|
5
|
+
|
|
6
|
+
const TASK_RECORD_FILE = /^[0-9a-f]{12}\.json$/
|
|
7
|
+
const SESSION_ID = /^[A-Za-z0-9-]{1,64}$/
|
|
8
|
+
const MAX_RECORD_BYTES = 16 * 1024
|
|
9
|
+
|
|
10
|
+
/** Where `jh task start` keeps its records, in the main checkout. */
|
|
11
|
+
const tasksDirectory = (main: string): string => join(main, '.jackherd', 'tasks')
|
|
12
|
+
|
|
13
|
+
/** Where the hook marks the sessions it hinted, beside the task records. Ignored by git. */
|
|
14
|
+
const hintsDirectory = (main: string): string => join(main, '.jackherd', 'task-hints')
|
|
15
|
+
|
|
16
|
+
const parsedFile = (path: string): unknown => {
|
|
17
|
+
try {
|
|
18
|
+
const entry = lstatSync(path)
|
|
19
|
+
return entry.isFile() && entry.size <= MAX_RECORD_BYTES ? JSON.parse(readFileSync(path, 'utf8')) : null
|
|
20
|
+
} catch {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The directories of the open `jh task` records; a link, a file over 16 KiB and a malformed record are skipped. */
|
|
26
|
+
export const openTaskDirectories = (main: string): string[] => {
|
|
27
|
+
const directory = tasksDirectory(main)
|
|
28
|
+
try {
|
|
29
|
+
return readdirSync(directory)
|
|
30
|
+
.filter((name) => TASK_RECORD_FILE.test(name))
|
|
31
|
+
.flatMap((name) => {
|
|
32
|
+
const record = parsedFile(join(directory, name))
|
|
33
|
+
return isRecord(record) && record.closedAt === null && typeof record.cwd === 'string' ? [record.cwd] : []
|
|
34
|
+
})
|
|
35
|
+
} catch {
|
|
36
|
+
return []
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Whether the session was hinted for its current task; false without a mark or for an id that is no plain name. */
|
|
41
|
+
export const readHintMark = (main: string, session: string): boolean => {
|
|
42
|
+
if (!SESSION_ID.test(session)) return false
|
|
43
|
+
const mark = parsedFile(join(hintsDirectory(main), `${session}.json`))
|
|
44
|
+
return isRecord(mark) && mark.hinted === true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Writes the session's mark; throws for an id that is no plain name, so nothing is written outside the directory. */
|
|
48
|
+
export const writeHintMark = (main: string, session: string, hinted: boolean): void => {
|
|
49
|
+
if (!SESSION_ID.test(session)) throw new Error('session id is no plain name')
|
|
50
|
+
writePrivateRecord(hintsDirectory(main), { id: session, hinted })
|
|
51
|
+
}
|
package/kit/hooks/hooks.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"matcher": "Bash",
|
|
40
40
|
"handler": {
|
|
41
41
|
"type": "command",
|
|
42
|
-
"command": "cd \"$CLAUDE_PROJECT_DIR\" && bun .claude/hooks/merge-guard/index.ts '{{kit.guard.selfMergeLabel}}' '{{kit.guard.
|
|
42
|
+
"command": "cd \"$CLAUDE_PROJECT_DIR\" && bun .claude/hooks/merge-guard/index.ts '{{kit.guard.selfMergeLabel}}' '{{kit.guard.standingApproval.approve}}' '{{kit.guard.standingApproval.until}}' || exit 2",
|
|
43
43
|
"timeout": 10,
|
|
44
44
|
"onFailure": "block"
|
|
45
45
|
}
|
|
@@ -61,12 +61,11 @@
|
|
|
61
61
|
"timeout": 10
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
-
"
|
|
65
|
-
"event": "
|
|
66
|
-
"matcher": "AskUserQuestion",
|
|
64
|
+
"open-question": {
|
|
65
|
+
"event": "Stop",
|
|
67
66
|
"handler": {
|
|
68
67
|
"type": "command",
|
|
69
|
-
"command": "cd \"$CLAUDE_PROJECT_DIR\" && bun .claude/hooks/
|
|
68
|
+
"command": "cd \"$CLAUDE_PROJECT_DIR\" && bun .claude/hooks/open-question/index.ts",
|
|
70
69
|
"timeout": 10
|
|
71
70
|
}
|
|
72
71
|
},
|
|
@@ -79,6 +78,15 @@
|
|
|
79
78
|
"timeout": 10
|
|
80
79
|
}
|
|
81
80
|
},
|
|
81
|
+
"task-hint": {
|
|
82
|
+
"event": "PostToolUse",
|
|
83
|
+
"matcher": "Edit|Write|Agent",
|
|
84
|
+
"handler": {
|
|
85
|
+
"type": "command",
|
|
86
|
+
"command": "cd \"$CLAUDE_PROJECT_DIR\" && bun .claude/hooks/task-hint/index.ts",
|
|
87
|
+
"timeout": 10
|
|
88
|
+
}
|
|
89
|
+
},
|
|
82
90
|
"watch-guard": {
|
|
83
91
|
"event": "PreToolUse",
|
|
84
92
|
"matcher": "Bash",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-review
|
|
3
|
+
description: 'Agent Text Review: does a change to a skill, agent, rule, project supplement or CLAUDE.md still steer the agent that reads it? Use when /pre-commit routes a change to agent texts.'
|
|
4
|
+
model: {{kit.model.review}}
|
|
5
|
+
tools: Read, Grep, Glob
|
|
6
|
+
user-invocable: false
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Read `.claude/rules/kit/writing-for-agents.md` first, then the project's `.claude/rules/writing-for-agents.md` if it exists, then `.claude/project/skill-review.md` if it exists. They carry the levers this review applies; the project files win where they disagree with the kit.
|
|
10
|
+
|
|
11
|
+
You receive the `git diff` of agent texts: skills, agents, rules, project supplements, `CLAUDE.md`, `AGENTS.md`. Judge only the lines the diff adds, changes or removes, a moved passage where its trigger or rung changed; the rest of a file is context. A budget, a broken link, a required rule and a trigger in the first lines are measured by `jh check`, so leave them to it.
|
|
12
|
+
|
|
13
|
+
## What is a finding
|
|
14
|
+
|
|
15
|
+
Check every changed passage against each lens; a passage that passes all six needs no row.
|
|
16
|
+
|
|
17
|
+
1. **No-op:** the sentence changes nothing against what the model does by default. Delete the sentence, never trim it.
|
|
18
|
+
2. **Same meaning twice:** the passage restates, in other words, what the skill, its project supplement, a rule, `CLAUDE.md` or a memory file the caller names already carries. Grep for the key terms before you call it new; name the other place. A passage removed in favour of a pointer is lost when the target lacks part of it.
|
|
19
|
+
3. **Wrong rung:** a step every run takes sits in a disclosed reference, or reference only some branches reach sits in the core, or a lasting reason that explains the code belongs in `ARCHITECTURE.md` rather than in a text loaded on every run.
|
|
20
|
+
4. **Late trigger:** a pointer to a reference or another document names its condition after the content, or fires at a moment the agent has already passed.
|
|
21
|
+
5. **Negation:** the passage steers by prohibition where a target could be stated; a hard guardrail stays, paired with its target.
|
|
22
|
+
6. **Open step:** a step ends without a condition the agent can check, so done and not done look alike.
|
|
23
|
+
|
|
24
|
+
## Output
|
|
25
|
+
|
|
26
|
+
```markdown
|
|
27
|
+
## Agent Text Review — PASS | PASS WITH NOTES | FIX FIRST
|
|
28
|
+
|
|
29
|
+
| File:line | Lens | Passage | Proposal |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| skills/x/SKILL.md:14 | same meaning twice (rules/y.md:8) | "…" | delete; the rule carries it |
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`FIX FIRST` for a duplicate that already disagrees with its other copy, or a step that lost its completion criterion; `PASS WITH NOTES` for the other findings; `PASS` with no row. Quote the passage so the author finds it. Your output is the report; applying a proposal is the caller's decision.
|
|
@@ -25,7 +25,7 @@ Run `/review` for what the command cannot decide: dead exports, naming, function
|
|
|
25
25
|
|
|
26
26
|
## Step 3: Specialized Reviewers
|
|
27
27
|
|
|
28
|
-
**Step 1 printed the reviewer list** (`Reviewers: …`, from `check.reviewerRoutes`) — dispatch exactly those, each as a subagent via the `Agent` tool with its `subagent_type`:
|
|
28
|
+
**Step 1 printed the reviewer list** (`Reviewers: …`, from `check.reviewerRoutes`, plus `check.publicContent` and `skill-review` for changed agent texts) — dispatch exactly those, each as a subagent via the `Agent` tool with its `subagent_type`:
|
|
29
29
|
|
|
30
30
|
- Pass in the `prompt`: the `git diff` of the relevant files and the file paths.
|
|
31
31
|
- Start all of them **in parallel**, in a single message, and wait for every result before step 4.
|
|
@@ -42,6 +42,8 @@ Add a fenced ` ```mermaid ` block **when it removes reader effort**:
|
|
|
42
42
|
|
|
43
43
|
Keep each diagram to one idea; two small diagrams beat one dense one. Small or single-file PRs need none.
|
|
44
44
|
|
|
45
|
+
Write edge labels as `-->|"label"|` (`-- text -->` breaks on a `--` in it); `jh mermaid <body file>` must exit 0 before `gh pr create` or `gh pr edit`.
|
|
46
|
+
|
|
45
47
|
## Review order (large PRs)
|
|
46
48
|
|
|
47
49
|
When a PR spans many files, add a short **suggested review order** — **pure/domain logic first, adapters next, UI and wiring last**. A `| # | Area | Start here | Why first |` table works well.
|
|
@@ -15,8 +15,8 @@ You add one entry to `watch.sources`, so that `jh watch` reports a new or change
|
|
|
15
15
|
- **Service:** the services the recipe index lists, and "another command that prints JSON".
|
|
16
16
|
- **What to follow:** the item the argument names (its comments, recommended) or a query over several items.
|
|
17
17
|
- **Lane:** the lane the reports name, from the board's lanes table, or none.
|
|
18
|
-
- **Ticket:** whether the entries are tickets (`ticket: true`, recommended for an issue tracker or a project board), which the herd shows as a badge beside the plan of their lane: the lane above, or the lane whose name, board row or plan names the ticket's id. A source of comments or messages is no ticket. The path of the status you find yourself in step 3.
|
|
19
|
-
- **What reports:** a change of what the user reads (title, body, the latest comment; recommended), or a change of named fields such as a status (`reportOn` with those fields, mapped in `map`).
|
|
18
|
+
- **Ticket:** whether the entries are tickets (`ticket: true`, recommended for an issue tracker or a project board), which the herd shows as a badge beside the plan of their lane: the lane above, or the lane whose name, board row or plan names the ticket's id. A source of comments or messages is no ticket; it shows in the herd's `Sources` block instead (a click opens the entry at `map.url`). The path of the status you find yourself in step 3.
|
|
19
|
+
- **Tone:** for a non-ticket source, the colour of its `Sources` mark while its report is new (`tone`: amber, teal, grey, red, green, blue, magenta); recommended: no `tone`.- **What reports:** a change of what the user reads (title, body, the latest comment; recommended), or a change of named fields such as a status (`reportOn` with those fields, mapped in `map`).
|
|
20
20
|
- **Own changes:** ignore the user's own account on that service (`ignoreAuthors`, needs an author in the map), or report everything.
|
|
21
21
|
|
|
22
22
|
## 2. Check, read only
|
|
@@ -30,14 +30,7 @@ Anything missing goes to the user as the command to run themselves (`! <command>
|
|
|
30
30
|
|
|
31
31
|
Before filling, read the help of the installed CLI version for every command the recipe calls (`acli jira workitem search --help` and `workitem view --help`, `gh issue list --help`) and confirm that the fields and options the entry needs exist there. Read the provider's web documentation only when the help leaves it open or no recipe fits. A recipe is a starting point; the installed version decides.
|
|
32
32
|
|
|
33
|
-
Fill the recipe with the item or query and the answers from step 1: a `name` of letters, digits, `.`, `_` and `-` that says what is followed, `lane`, `ignoreAuthors` and `
|
|
34
|
-
|
|
35
|
-
- bounds every request and every call of a tool with `AbortSignal.timeout(20_000)`, where a shell recipe would use `--max-time`;
|
|
36
|
-
- reads a token only from `process.env`, and never prints it, passes it as an argument or writes it into an output;
|
|
37
|
-
- ends with `process.exit(1)` when the token is missing, the answer is not 2xx (or carries an `errors` list, as GraphQL does with a 200) or a tool it calls fails, without printing the answer's body, so a failure is an exit code and never an empty list the watcher would take for a source with nothing new;
|
|
38
|
-
- prints one JSON value on stdout and nothing else.
|
|
39
|
-
|
|
40
|
-
A recipe that needs no script (`gh …`, `curl` on a public page, `curl --netrc`) stays a direct command. Write `bun` as the program, never an absolute path to it: the watcher starts a source without a shell and resolves `bun` to the Bun that runs the watcher, and a path in the config would differ from machine to machine.
|
|
33
|
+
Fill the recipe with the item or query and the answers from step 1: a `name` of letters, digits, `.`, `_` and `-` that says what is followed, `lane`, `ignoreAuthors`, `ticket` and `tone` where chosen, and a short `label` (e.g. `Error log · Checkout`; it prefixes the source's `Waiting for you` rows). For a ticket source, find the path of the status in the output (from the CLI's help, or the dry run with `--raw`) and set it as `map.status`; the entry you show for approval in step 4 carries it, and without a status in the output the entry has none and the badge shows only the key. A recipe that needs a script (a token from the environment, or one call per item) gets TypeScript run by Bun: write it, from the recipe's script under `recipes/sources/`, to the scratchpad first as `<name>.ts`, never into the repository before step 4, and let the entry's `command` be `["bun", "scripts/sources/<name>.ts"]`. Script and direct command follow the index's **Rules every recipe follows**.
|
|
41
34
|
|
|
42
35
|
Write the entry as one JSON object to a draft file in the scratchpad and run, from the main checkout:
|
|
43
36
|
|
|
@@ -19,16 +19,15 @@ You are the **Implementer**. Your job is to carry tasks through completely: expl
|
|
|
19
19
|
1. Read the model recommended for the phase from the table.
|
|
20
20
|
2. Compare it with the model your current run is on:
|
|
21
21
|
- **Same model** → continue with step 1.
|
|
22
|
-
- **Any other model, stronger or weaker** → delegate the phase to a subagent with the recommended model: `Agent(subagent_type: 'implement', model: <recommended>, prompt: …)
|
|
23
|
-
3. **The recommendation is a choice, not a floor.** The table records the tier the plan's author judged right for the error cost; a model cannot switch itself, so spawning the subagent is the way to apply it.
|
|
22
|
+
- **Any other model, stronger or weaker** (the recommendation is a choice, not a floor) → delegate the phase to a subagent with the recommended model: `Agent(subagent_type: 'implement', model: <recommended>, description: …, prompt: …)`, the `description` as in Step 0b item 3. Hand it what you already know (files located, decisions made, the phase's review gate) so it does not repeat the exploration. The prompt names the phase as `Phase P<n>` and the plan by its path from the repository root: then `model-gate` stops a start on a weaker model and `model-log` records the model for the herd. It ticks its phase's tasks itself as it finishes them (see Plan Hygiene). You stay the orchestrator: the review gate, gate verification, the arc42 and architecture checks and `/pre-commit` stay with you.
|
|
24
23
|
|
|
25
24
|
## Step 0b: Parallel Gate (plans with a `{{kit.heading.dependencies}}` table)
|
|
26
25
|
|
|
27
26
|
When the user asks for more than one phase:
|
|
28
27
|
|
|
29
|
-
1. **Read the computed waves:** run `jh check --parallel <plan>`. It
|
|
28
|
+
1. **Read the computed waves:** run `jh check --parallel <plan>`. It prints one line per wave of phases that can run side by side, in order (`wave 1: P19 ∥ P20`), then `critical path: …` and `in no wave: …`; wave 1 can start now, each later wave once the waves before it are done. A phase is ready once every `{{kit.heading.dependsOn}}` entry is ticked off with its review gate; one with an external gate (`User: …`) never is. Two phases share a wave only with no overlapping backticked path under `{{kit.heading.writes}}` (a `like N` entry adds that phase's set), no shared `{{kit.heading.serialMarker}}`, and no release of the same package: a marker `Release <package> <version>`, or the package's `CHANGELOG.md` or `package.json` under `{{kit.heading.writes}}`. `no dependency table`: the phases run one after the other. `no phase is ready`: every open phase waits, is done, or is in no wave.
|
|
30
29
|
2. **Batch:** the requested phases in wave 1. A requested phase in a later wave waits, naming the phase it collides with. One in no wave is not ready for one of these reasons: it has an external gate (ask whether the gate holds), its `like N` entry names an unknown phase or runs in a cycle, its `{{kit.heading.writes}}` cell has no backticked path, or it depends on such a phase; say which. The command checks paths, markers and releases only: two phases that collide in content stay your judgement, so serialise them yourself.
|
|
31
|
-
3. **Dispatch the batch in one message**, one `Agent(subagent_type: 'implement', model: <from the model table>, prompt: …)` per phase, in the same checkout: the disjoint `{{kit.heading.writes}}` sets are the isolation. They may run in the background; a hand-over (Step 7) waits until all are back. Each prompt names its phase (`Phase P<n>` and the plan's path, as in Step 0) and its `{{kit.heading.writes}}` set as the **only** paths it may edit, besides ticking its own phase's task lines in the plan, and states that it ticks the task checkboxes of its own phase as it finishes each one (Plan Hygiene), and leaves the review gate, commits and `/pre-commit` to you — it reports changed files, test results and the gate items it ran.
|
|
30
|
+
3. **Dispatch the batch in one message**, one `Agent(subagent_type: 'implement', model: <from the model table>, description: …, prompt: …)` per phase, in the same checkout: the disjoint `{{kit.heading.writes}}` sets are the isolation. The `description` starts with the short name of the task the subagent works on, the first open one of its phase, then the phase: `<Name> · P<n>`; the herd puts its `▶` on that task. Without a short name, the phase alone. They may run in the background; a hand-over (Step 7) waits until all are back. Each prompt names its phase (`Phase P<n>` and the plan's path, as in Step 0) and its `{{kit.heading.writes}}` set as the **only** paths it may edit, besides ticking its own phase's task lines in the plan, and states that it ticks the task checkboxes of its own phase as it finishes each one (Plan Hygiene), and leaves the review gate, commits and `/pre-commit` to you — it reports changed files, test results and the gate items it ran.
|
|
32
31
|
4. **When the batch returns:** check each report against its set (`git diff --stat`; an edit outside the set is a finding; a ticked line outside its own phase is one too), tick what it left open, walk each review gate. Gate items that write outside a phase's set (documentation, a changelog entry, plan synchronisation) are yours, at the wave's end. Then recompute the ready set and dispatch the next batch.
|
|
33
32
|
5. **Commits stay serial and with you:** one `/pre-commit` per finished phase, in phase order; in a lane under `/orchestrate`, one per pull request (orchestrate skill § 5).
|
|
34
33
|
|
|
@@ -36,6 +35,7 @@ When the user asks for more than one phase:
|
|
|
36
35
|
|
|
37
36
|
- **Tick off tasks as you go.** As soon as a task is done, set `- [ ]` → `- [x]` in the plan document, right then, so the plan reflects real progress and the herd shows it while the phase runs.
|
|
38
37
|
- **A subagent ticks only its own phase,** and only with `Edit` on the exact task line (enough of it to be unique in the file). Never `Write` the plan or rewrite it with a script: parallel subagents share the file, and `Edit` applies its replacement to the file as it is on disk, so ticks in other phases survive, while a whole-file write from an older read erases them. A line that is not unique, and every review-gate item, stays with the caller.
|
|
38
|
+
- **A task you add starts with its short name:** `- [ ] **Name**: …`, the name 40 characters at most, naming the subject and the result; how, conditions, origin and evidence follow the colon (`/plan`, step 2). A task the user adds later ends with `(User <DD.MM.> <HH:MM>)`.
|
|
39
39
|
- **The review gate is binding.** A phase is finished once its review gate has been walked through completely and ticked off; only then comes the next phase or `/pre-commit`. Gate items that name a skill (`/uncle-bob`, `/security-audit`, …) are ticked after the skill has actually run.
|
|
40
40
|
|
|
41
41
|
## Step 1: Explore
|
|
@@ -54,6 +54,8 @@ Before you write a single line:
|
|
|
54
54
|
|
|
55
55
|
Follow `.claude/rules/kit/code-style.md`: types → pure functions → higher-order functions, names that reveal intent, no section-label comments.
|
|
56
56
|
|
|
57
|
+
Write code and tests with Write and Edit, never through a here-document or a script in Bash: such a call shows the reviewer no diff, and an unquoted or unreadable one can stop at a guard.
|
|
58
|
+
|
|
57
59
|
## Step 3: Verify
|
|
58
60
|
|
|
59
61
|
After each changed file:
|
|
@@ -91,7 +93,7 @@ The report at the end is short: one line of diff stat, one line per check with i
|
|
|
91
93
|
|
|
92
94
|
`git add`, `git commit`, `git fetch`, `git rebase` and `git push` each run as a Bash command of their own, with no check or other step joined to them by `&&`, `||`, `;`, a pipe or a line break. The checks run as separate commands before and after. The order is: checks, commit, fetch, rebase, checks again, push. Push only once the checks after the rebase are green. The checks again run only where the rebase brought in changes to your package roots or plans, all of them when in doubt (orchestrate skill § 6).
|
|
93
95
|
|
|
94
|
-
Why:
|
|
96
|
+
Why: a chained command cannot be approved as one (Claude Code docs, "Configure permissions", section "Compound commands").
|
|
95
97
|
|
|
96
98
|
## Step 7: Phase Boundary
|
|
97
99
|
|
|
@@ -103,6 +105,6 @@ Runs once the phase's review gate is ticked off, committed or not (a lane under
|
|
|
103
105
|
bunx jackherd context
|
|
104
106
|
```
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
`null` or `level` `ok` → continue with the next phase in this chat, and say nothing about it. A line on stderr that the auto-compact window is not above `plans.context.alertTokens` means Claude Code compacts before this hand-over: name it in one line of your report.
|
|
107
109
|
2. `warn` or `alert`, and a next phase exists → the next phase runs in a new chat, without a question: go on with step 3, once every subagent you started is back, no dialog of yours is open and no question of yours waits for an answer. The same holds when Jack, the coordinator, asked this lane for its hand-over.
|
|
108
110
|
3. Write the hand-over to `.claude/handover/<lane>.md` (`<lane>` is the name of the worktree directory; the directory is ignored by git and stays out of every commit) and end your turn with its path. The file holds, in this order: the line `{{kit.word.openSubagents}}: 0`, which the coordinator requires, the state (branch, commits or uncommitted changes, what is merged), the open points, the next step (after a wave, the state of each of its phases), the traps you ran into, and links to the plan and the files that matter. Jack ends this agent and starts a fresh one in the same worktree with the hand-over as its first task; without a coordinator, the last line names the fill and tells the user to start a new chat with that file.
|