clud-bug 0.7.0-rc.15 → 0.7.0-rc.18

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.
@@ -1,18 +1,26 @@
1
- /** Stable marker embedded in our hook's prompt so re-runs replace it in place. */
1
+ /** Stable marker embedded in our hook so re-runs and upgrades from the old,
2
+ * broken `type: agent` hook — replace it in place. */
2
3
  export declare const CLUD_BUG_HOOK_MARKER = "clud-bug-local-review";
3
4
  /**
4
- * Build the `prompt` of the Claude Code `type: agent` commit-review hook,
5
- * pinned to the clud-bug VERSION that scaffolded it. Pinning matters: a bare
6
- * `npx clud-bug` resolves to the `latest` dist-tag, which can predate the
7
- * `review-prompt` verb (e.g. while v0.7 is prerelease on `next`); `@${version}`
8
- * guarantees the verb exists. `clud-bug update` refreshes the pin in place.
5
+ * Build the shell `command` of the commit-review hook, pinned to the clud-bug
6
+ * VERSION that scaffolded it (a bare `npx clud-bug` resolves to the `latest`
7
+ * dist-tag, which can predate the `review-prompt` verb; `@${version}` guarantees
8
+ * it). `clud-bug update` refreshes the pin in place.
9
9
  *
10
- * Rather than baking a static recipe (which would drift from the repo's
11
- * skills/config), it tells the subagent to run `clud-bug review-prompt` the
12
- * engine-driven verb that emits a recipe tailored to THIS repo's resolved plan
13
- * — and follow it. The recipe is generated fresh at commit time, always current.
10
+ * The command, in order:
11
+ * 1. Idempotency skip if this exact HEAD was already surfaced (avoids a
12
+ * re-review on an amend-with-no-change or a double-fire). The reviewed SHA
13
+ * is recorded under `.git/` (untracked), reusing the "last-reviewed-sha"
14
+ * idea the hosted bot uses on PR comments.
15
+ * 2. Fetch a fresh recipe tailored to THIS repo: `review-prompt --trigger
16
+ * commit` (an instruction recipe — `git show HEAD` + the skills + the
17
+ * report format — NOT raw data; it is meant to be FOLLOWED by an agent).
18
+ * 3. Surface it to the session by printing it and `exit 2`, so `asyncRewake`
19
+ * shows it to the main agent as a system reminder. The agent then reviews
20
+ * the commit on the session subscription.
21
+ * 4. Any failure or empty output → `exit 0` (quiet; the commit is never blocked).
14
22
  */
15
- export declare function buildCommitReviewPrompt(version: string): string;
23
+ export declare function buildCommitReviewCommand(version: string): string;
16
24
  /** One Claude Code hook entry: a tool matcher plus its hook list. */
17
25
  export interface HookMatcherEntry {
18
26
  matcher?: string;
@@ -24,17 +32,17 @@ export interface ClaudeSettings {
24
32
  [key: string]: unknown;
25
33
  }
26
34
  /**
27
- * Builds the PostToolUse entry that spawns the commit-review subagent: a native
28
- * `type: agent` hook that is backgrounded (`async`), surfaces findings
29
- * (`asyncRewake`), and fires only on `git commit` (`if`).
35
+ * Builds the PostToolUse entry that runs the commit-review command: a native
36
+ * `type: command` hook, backgrounded (`async`), surfacing the recipe back to the
37
+ * session (`asyncRewake` + exit 2), firing only on `git commit` (`if`).
30
38
  */
31
- export declare function buildLocalReviewHook(recipe: string): HookMatcherEntry;
39
+ export declare function buildLocalReviewHook(command: string): HookMatcherEntry;
32
40
  /**
33
41
  * Merges the clud-bug commit-review hook into an existing `.claude/settings.json`
34
- * object. **Idempotent** (replaces any prior clud-bug entry rather than
35
- * duplicating) and **non-clobbering** (preserves every other top-level key,
36
- * every other event, and every other hook). Tolerates a missing/malformed
42
+ * object. **Idempotent** (replaces any prior clud-bug entry including the old
43
+ * `type: agent` one — rather than duplicating) and **non-clobbering** (preserves
44
+ * every other top-level key, event, and hook). Tolerates a missing/malformed
37
45
  * `existing` value.
38
46
  */
39
- export declare function mergeLocalReviewHook(existing: unknown, recipe: string): ClaudeSettings;
47
+ export declare function mergeLocalReviewHook(existing: unknown, command: string): ClaudeSettings;
40
48
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"AAcA,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkB/D;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvC;AAED,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC3C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAcrE;AAUD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAwBtF"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"AAkBA;sDACsD;AACtD,eAAO,MAAM,oBAAoB,0BAA0B,CAAC;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAuBhE;AAED,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvC;AAED,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC3C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CActE;AAcD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAwBvF"}
package/dist/cli/hooks.js CHANGED
@@ -1,90 +1,111 @@
1
- // Wave 6b — Claude Code `type: agent` commit-review hook scaffolding.
1
+ // Wave 6b (rc.17 fix) — Claude Code `type: command` commit-review hook scaffolding.
2
2
  //
3
3
  // `clud-bug init --with-hooks` writes a native Claude Code hook that, on every
4
- // `git commit` the agent makes, spawns a clud-bug review SUBAGENT in the same
5
- // session — so it runs on the session's subscription (no API key), in the
6
- // background (the commit never blocks), and surfaces findings back to the agent.
4
+ // `git commit`, runs clud-bug's review recipe ON THIS SESSION'S SUBSCRIPTION
5
+ // (no API key), in the background (the commit never blocks).
7
6
  //
8
- // The model (see the Wave 6b plan): clud-bug supplies the *recipe* (the
9
- // agent-hook `prompt`); Claude Code runs it as a subagent. This is the dynamic,
10
- // always-on counterpart to the `/clud-bug-review` slash command.
11
- //
12
- // `type: agent` hooks are documented as experimental; the same outcome is
13
- // reachable via a `type: command` hook + `additionalContext` if the API shifts.
14
- /** Stable marker embedded in our hook's prompt so re-runs replace it in place. */
7
+ // WHY `type: command`, NOT `type: agent`: a Claude Code `type: agent` hook
8
+ // spawns a subagent restricted to Read/Grep/Glob — it has NO Bash and the tool
9
+ // set is not configurable (see code.claude.com/docs/en/hooks: "spawn a subagent
10
+ // that can use tools like Read, Grep, and Glob"). The original Wave 6b hook used
11
+ // `type: agent` and told the subagent to run `npx clud-bug review-prompt` — a
12
+ // Bash CLI call an agent hook can never make — so the review NEVER ran for
13
+ // anyone. A `type: command` hook CAN run the CLI: it fetches the engine's recipe
14
+ // and surfaces it to the session via exit-2 (`asyncRewake`), so the MAIN agent —
15
+ // which has Bash, git, gh, and the subscription — performs the review. The hook
16
+ // exits 0 on any failure, so a review that can't run is a quiet no-op and the
17
+ // commit is NEVER blocked.
18
+ /** Stable marker embedded in our hook so re-runs — and upgrades from the old,
19
+ * broken `type: agent` hook — replace it in place. */
15
20
  export const CLUD_BUG_HOOK_MARKER = 'clud-bug-local-review';
16
21
  /**
17
- * Build the `prompt` of the Claude Code `type: agent` commit-review hook,
18
- * pinned to the clud-bug VERSION that scaffolded it. Pinning matters: a bare
19
- * `npx clud-bug` resolves to the `latest` dist-tag, which can predate the
20
- * `review-prompt` verb (e.g. while v0.7 is prerelease on `next`); `@${version}`
21
- * guarantees the verb exists. `clud-bug update` refreshes the pin in place.
22
+ * Build the shell `command` of the commit-review hook, pinned to the clud-bug
23
+ * VERSION that scaffolded it (a bare `npx clud-bug` resolves to the `latest`
24
+ * dist-tag, which can predate the `review-prompt` verb; `@${version}` guarantees
25
+ * it). `clud-bug update` refreshes the pin in place.
22
26
  *
23
- * Rather than baking a static recipe (which would drift from the repo's
24
- * skills/config), it tells the subagent to run `clud-bug review-prompt` the
25
- * engine-driven verb that emits a recipe tailored to THIS repo's resolved plan
26
- * — and follow it. The recipe is generated fresh at commit time, always current.
27
+ * The command, in order:
28
+ * 1. Idempotency skip if this exact HEAD was already surfaced (avoids a
29
+ * re-review on an amend-with-no-change or a double-fire). The reviewed SHA
30
+ * is recorded under `.git/` (untracked), reusing the "last-reviewed-sha"
31
+ * idea the hosted bot uses on PR comments.
32
+ * 2. Fetch a fresh recipe tailored to THIS repo: `review-prompt --trigger
33
+ * commit` (an instruction recipe — `git show HEAD` + the skills + the
34
+ * report format — NOT raw data; it is meant to be FOLLOWED by an agent).
35
+ * 3. Surface it to the session by printing it and `exit 2`, so `asyncRewake`
36
+ * shows it to the main agent as a system reminder. The agent then reviews
37
+ * the commit on the session subscription.
38
+ * 4. Any failure or empty output → `exit 0` (quiet; the commit is never blocked).
27
39
  */
28
- export function buildCommitReviewPrompt(version) {
29
- return `<!-- ${CLUD_BUG_HOOK_MARKER} v1 -->
30
- You are clud-bug's local commit review, running as a background subagent on this
31
- session's own subscription (no extra auth you have git, gh, and file access).
32
-
33
- Step 1get your review recipe from clud-bug's engine:
34
-
35
- npx clud-bug@${version} review-prompt --trigger commit
36
-
37
- That prints a structured review recipe tailored to THIS repo's skills + config (a
38
- fast single pass for a commit). If that command isn't available, run the repo's
39
- installed clud-bug CLI's \`review-prompt --trigger commit\` instead.
40
-
41
- Step 2 follow that recipe exactly: review the commit that was just made against
42
- the skills it names, and surface any findings back into the session so they can be
43
- fixed. A clean commit needs only a one-line note.
44
-
45
- Keep it tight this is the commit-time safety net; the deeper review runs at PR time.`;
40
+ export function buildCommitReviewCommand(version) {
41
+ return [
42
+ // Marker as a `#` comment (NOT a `:` no-op) so its free text can never break
43
+ // `sh` a paren / quote / `$` in the marker line would be a syntax error
44
+ // under `: ...`. The dogfood caught this. `isOurHook` finds the marker here.
45
+ `# ${CLUD_BUG_HOOK_MARKER} v2 clud-bug commit review on the session subscription`,
46
+ // Belt-and-suspenders gate. The `if: Bash(git commit *)` / `Bash(logmind log *)`
47
+ // field filters at the platform on Claude Code >= 2.1.85; OLDER CC ignores
48
+ // `if` and would fire this on EVERY Bash call (a review recipe after every
49
+ // command). Command hooks get the event JSON on stdin re-check it here. If
50
+ // stdin is empty (a CC that doesn't pipe it), fall through and trust `if`.
51
+ `ev=$(cat 2>/dev/null)`,
52
+ `if [ -n "$ev" ]; then case "$ev" in *'git commit'*|*'logmind log'*) ;; *) exit 0 ;; esac; fi`,
53
+ `sha=$(git rev-parse HEAD 2>/dev/null) || exit 0`,
54
+ `gitdir=$(git rev-parse --git-dir 2>/dev/null) || exit 0`,
55
+ `marker="$gitdir/clud-bug-last-commit-review"`,
56
+ `[ "$(cat "$marker" 2>/dev/null)" = "$sha" ] && exit 0`,
57
+ `recipe=$(npx clud-bug@${version} review-prompt --trigger commit 2>/dev/null) || exit 0`,
58
+ `[ -n "$recipe" ] || exit 0`,
59
+ `printf '%s' "$sha" > "$marker" 2>/dev/null || true`,
60
+ `printf '%s\\n\\n%s\\n' "clud-bug commit review (max mode — on this session's subscription): a commit was just made. Follow this recipe now — review that commit against the skills it names and surface any findings." "$recipe"`,
61
+ `exit 2`,
62
+ ].join('\n');
46
63
  }
47
64
  /**
48
- * Builds the PostToolUse entry that spawns the commit-review subagent: a native
49
- * `type: agent` hook that is backgrounded (`async`), surfaces findings
50
- * (`asyncRewake`), and fires only on `git commit` (`if`).
65
+ * Builds the PostToolUse entry that runs the commit-review command: a native
66
+ * `type: command` hook, backgrounded (`async`), surfacing the recipe back to the
67
+ * session (`asyncRewake` + exit 2), firing only on `git commit` (`if`).
51
68
  */
52
- export function buildLocalReviewHook(recipe) {
69
+ export function buildLocalReviewHook(command) {
70
+ const base = { type: 'command', async: true, asyncRewake: true, timeout: 180, command };
53
71
  return {
54
72
  matcher: 'Bash',
55
73
  hooks: [
56
- {
57
- type: 'agent',
58
- if: 'Bash(git commit *)',
59
- async: true,
60
- asyncRewake: true,
61
- timeout: 180,
62
- prompt: recipe,
63
- },
74
+ { ...base, if: 'Bash(git commit *)' },
75
+ // thrillmade repos (and any logmind user) commit via `logmind log`, which
76
+ // wraps `git commit` inside its own binary — so the Bash tool call is
77
+ // `logmind log ...`, which `Bash(git commit *)` never matches. Fire on it
78
+ // too, or max mode never triggers in a logmind repo. The idempotency
79
+ // SHA-marker means whichever path runs, a commit is reviewed exactly once.
80
+ { ...base, if: 'Bash(logmind log *)' },
64
81
  ],
65
82
  };
66
83
  }
67
84
  function isOurHook(h) {
68
- return typeof h?.['prompt'] === 'string' && h['prompt'].includes(CLUD_BUG_HOOK_MARKER);
85
+ // Match our marker in `command` (current `type: command` hook) OR `prompt`
86
+ // (the old, broken `type: agent` hook) so a re-install / `clud-bug update`
87
+ // replaces either in place.
88
+ const field = h?.['command'] ?? h?.['prompt'];
89
+ return typeof field === 'string' && field.includes(CLUD_BUG_HOOK_MARKER);
69
90
  }
70
91
  function isCludBugReviewEntry(entry) {
71
92
  return !!entry && Array.isArray(entry.hooks) && entry.hooks.some(isOurHook);
72
93
  }
73
94
  /**
74
95
  * Merges the clud-bug commit-review hook into an existing `.claude/settings.json`
75
- * object. **Idempotent** (replaces any prior clud-bug entry rather than
76
- * duplicating) and **non-clobbering** (preserves every other top-level key,
77
- * every other event, and every other hook). Tolerates a missing/malformed
96
+ * object. **Idempotent** (replaces any prior clud-bug entry including the old
97
+ * `type: agent` one — rather than duplicating) and **non-clobbering** (preserves
98
+ * every other top-level key, event, and hook). Tolerates a missing/malformed
78
99
  * `existing` value.
79
100
  */
80
- export function mergeLocalReviewHook(existing, recipe) {
101
+ export function mergeLocalReviewHook(existing, command) {
81
102
  const base = existing && typeof existing === 'object' ? { ...existing } : {};
82
103
  const hooks = { ...(base.hooks ?? {}) };
83
104
  const priorPost = Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : [];
84
105
  // Preserve every non-clud-bug hook — including any the user co-located INSIDE
85
106
  // our own matcher entry: drop only the hook(s) carrying our marker, never the
86
107
  // whole entry.
87
- const ours = buildLocalReviewHook(recipe);
108
+ const ours = buildLocalReviewHook(command);
88
109
  const otherEntries = [];
89
110
  const coLocatedUserHooks = [];
90
111
  for (const entry of priorPost) {
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,0EAA0E;AAC1E,iFAAiF;AACjF,EAAE;AACF,wEAAwE;AACxE,gFAAgF;AAChF,iEAAiE;AACjE,EAAE;AACF,0EAA0E;AAC1E,gFAAgF;AAEhF,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,OAAO,QAAQ,oBAAoB;;;;;;mBAMlB,OAAO;;;;;;;;;;uFAU6D,CAAC;AACxF,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,OAAO;QACL,OAAO,EAAE,MAAM;QACf,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,OAAO;gBACb,EAAE,EAAE,oBAAoB;gBACxB,KAAK,EAAE,IAAI;gBACX,WAAW,EAAE,IAAI;gBACjB,OAAO,EAAE,GAAG;gBACZ,MAAM,EAAE,MAAM;aACf;SACF;KACF,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,CAAsC;IACvD,OAAO,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,QAAQ,IAAK,CAAC,CAAC,QAAQ,CAAY,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;AACrG,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAmC;IAC/D,OAAO,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAiB,EAAE,MAAc;IACpE,MAAM,IAAI,GACR,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAI,QAA2B,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtF,MAAM,KAAK,GAAuC,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;IAC5E,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5E,8EAA8E;IAC9E,8EAA8E;IAC9E,eAAe;IACf,MAAM,IAAI,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAuB,EAAE,CAAC;IAC5C,MAAM,kBAAkB,GAAmC,EAAE,CAAC;IAC9D,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK;gBAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;oBAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7E,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GACZ,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpG,KAAK,CAAC,WAAW,GAAG,CAAC,GAAG,YAAY,EAAE,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACnB,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,6DAA6D;AAC7D,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,gFAAgF;AAChF,iFAAiF;AACjF,8EAA8E;AAC9E,2EAA2E;AAC3E,iFAAiF;AACjF,iFAAiF;AACjF,gFAAgF;AAChF,8EAA8E;AAC9E,2BAA2B;AAE3B;sDACsD;AACtD,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,OAAO;QACL,6EAA6E;QAC7E,0EAA0E;QAC1E,6EAA6E;QAC7E,KAAK,oBAAoB,0DAA0D;QACnF,iFAAiF;QACjF,2EAA2E;QAC3E,2EAA2E;QAC3E,6EAA6E;QAC7E,2EAA2E;QAC3E,uBAAuB;QACvB,8FAA8F;QAC9F,iDAAiD;QACjD,yDAAyD;QACzD,8CAA8C;QAC9C,uDAAuD;QACvD,yBAAyB,OAAO,wDAAwD;QACxF,4BAA4B;QAC5B,oDAAoD;QACpD,kOAAkO;QAClO,QAAQ;KACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAW,CAAC;IACjG,OAAO;QACL,OAAO,EAAE,MAAM;QACf,KAAK,EAAE;YACL,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,oBAAoB,EAAE;YACrC,0EAA0E;YAC1E,sEAAsE;YACtE,0EAA0E;YAC1E,qEAAqE;YACrE,2EAA2E;YAC3E,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,qBAAqB,EAAE;SACvC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,CAAsC;IACvD,2EAA2E;IAC3E,2EAA2E;IAC3E,4BAA4B;IAC5B,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC9C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAmC;IAC/D,OAAO,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAiB,EAAE,OAAe;IACrE,MAAM,IAAI,GACR,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAI,QAA2B,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtF,MAAM,KAAK,GAAuC,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;IAC5E,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5E,8EAA8E;IAC9E,8EAA8E;IAC9E,eAAe;IACf,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAuB,EAAE,CAAC;IAC5C,MAAM,kBAAkB,GAAmC,EAAE,CAAC;IAC9D,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK;gBAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;oBAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7E,CAAC;aAAM,CAAC;YACN,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GACZ,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACpG,KAAK,CAAC,WAAW,GAAG,CAAC,GAAG,YAAY,EAAE,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACnB,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AAmPA,iBAAe,IAAI,kBA6BlB;AAm8DD,OAAO,EAAE,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AAuQA,iBAAe,IAAI,kBA6BlB;AAigED,OAAO,EAAE,IAAI,EAAE,CAAC"}
package/dist/cli/main.js CHANGED
@@ -22,7 +22,7 @@ import { detect, buildDescriptionLine } from '../core/detect.js';
22
22
  import { renderFile, pickTemplate, templateLanguage } from '../core/render.js';
23
23
  import { reviewPrompt } from '../core/prompts.js';
24
24
  import { SkillsClient, rankAndCap } from '../core/skills.js';
25
- import { writeSkills, writeSkill, loadBaseline, readManifest, writeManifest, removeSkill, listInstalled, diffManifest, } from './skills.js';
25
+ import { writeSkills, writeSkill, loadBaseline, loadDesignKit, readManifest, writeManifest, removeSkill, listInstalled, diffManifest, } from './skills.js';
26
26
  import { computeAuditFileSet } from './audit.js';
27
27
  import { renderAuditHeader } from '../core/audit.js';
28
28
  import { runUpdate } from './update.js';
@@ -38,6 +38,7 @@ import { computeReviewCost, costPerLOC, cacheHitRate, extractTokensFromLog, roll
38
38
  const PKG_ROOT = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
39
39
  const TEMPLATES = join(PKG_ROOT, 'templates');
40
40
  const BASELINE_DIR = join(TEMPLATES, 'skills', 'baseline');
41
+ const DESIGN_DIR = join(TEMPLATES, 'skills', 'design');
41
42
  function parseArgs(argv) {
42
43
  const args = {
43
44
  _: [], offline: false, acceptAll: false, commit: false, help: false, version: false,
@@ -61,6 +62,16 @@ function parseArgs(argv) {
61
62
  // Claude Code session (reviews the current PR with the session's tokens).
62
63
  withLocalReview: false,
63
64
  withHooks: false,
65
+ // rc.16: `clud-bug init --with-design` installs the design-critic kit
66
+ // (3 `kind: design` skills) and flips the off-by-default `design` block to
67
+ // enabled so the visual review lens runs (local recipe + hosted bot).
68
+ withDesign: false,
69
+ // rc.18: `clud-bug init --local-only` installs MAX MODE (skills + slash
70
+ // command + commit hook), but SKIPS the GitHub Action workflows (which run
71
+ // claude-code-action with ANTHROPIC_API_KEY). For repos that review on a
72
+ // Claude subscription via the in-session hook, not a billed CI Action.
73
+ // Implies --with-local-review + --with-hooks.
74
+ localOnly: false,
64
75
  // v0.7.0-rc.4: `clud-bug configure-github` flags.
65
76
  // --dry-run prints the diff but skips PATCH; --branch overrides "main".
66
77
  dryRun: false,
@@ -110,6 +121,10 @@ function parseArgs(argv) {
110
121
  args.withLocalReview = true;
111
122
  else if (a === '--with-hooks')
112
123
  args.withHooks = true;
124
+ else if (a === '--with-design')
125
+ args.withDesign = true;
126
+ else if (a === '--local-only')
127
+ args.localOnly = true;
113
128
  else if (a === '--dry-run')
114
129
  args.dryRun = true;
115
130
  else if (a === '--branch')
@@ -222,11 +237,18 @@ Options:
222
237
  \`/clud-bug-review\` works in a Claude Code session —
223
238
  reviews the current branch's PR using that session's
224
239
  own tokens (no hosted App, no extra auth).
225
- --with-hooks (init) Also scaffold a native Claude Code \`type: agent\`
240
+ --with-hooks (init) Scaffold a native Claude Code \`type: command\`
226
241
  commit-review hook into .claude/settings.json — on every
227
- \`git commit\` the agent makes, a backgrounded clud-bug
228
- review subagent runs on the session's subscription.
229
- Implies --with-local-review. Off by default.
242
+ \`git commit\` / \`logmind log\`, it fetches a review recipe and
243
+ surfaces it to the agent (on this session's subscription)
244
+ via asyncRewake. Implies --with-local-review. Off by default.
245
+ --with-design (init) Install the design-critic kit (3 \`kind: design\`
246
+ skills) and enable the off-by-default visual review
247
+ lens — renders changed UI and critiques it. Off by default.
248
+ --local-only (init) MAX MODE: install the slash command + commit hook
249
+ (implies --with-local-review + --with-hooks) but SKIP the
250
+ GitHub Action workflows. Reviews run in your Claude Code
251
+ session on your subscription — no API key, no per-review bill.
230
252
  --quiet,-q Token-frugal mode for agent invocations. Suppresses
231
253
  progress chatter; emits exactly one final
232
254
  \`ok <key-value>\` summary line per command. Errors
@@ -1141,27 +1163,37 @@ async function runInit(args) {
1141
1163
  language: templateLanguage(tmplName),
1142
1164
  }),
1143
1165
  });
1144
- const workflowPath = join(cwd, '.github', 'workflows', 'clud-bug-review.yml');
1145
- await mkdir(dirname(workflowPath), { recursive: true });
1146
- await writeFile(workflowPath, workflow);
1147
- log(` wrote ${rel(cwd, workflowPath)}`);
1148
- // Install the audit workflow alongside the per-PR review one.
1149
- // Manual-trigger by default; users opt into the cron by uncommenting.
1150
- // Routed through renderFile so {{CCA_VERSION}} substitution pins
1151
- // claude-code-action consistently with the review workflow.
1152
- const auditTmpl = await renderFile(join(TEMPLATES, 'audit.yml.tmpl'), {});
1153
- const auditPath = join(cwd, '.github', 'workflows', 'clud-bug-audit.yml');
1154
- await writeFile(auditPath, auditTmpl);
1155
- log(` wrote ${rel(cwd, auditPath)}`);
1156
- // Install the self-update workflow. Cron weekly Mondays 12:00 UTC; opens
1157
- // a PR if a newer clud-bug version is published. Disable by deleting the
1158
- // file or pinning via .claude/skills/.clud-bug.json.
1159
- // Routed through renderFile for parity (no CCA ref today but future
1160
- // tokens should propagate uniformly).
1161
- const selfUpdateTmpl = await renderFile(join(TEMPLATES, 'self-update.yml.tmpl'), {});
1162
- const selfUpdatePath = join(cwd, '.github', 'workflows', 'clud-bug-self-update.yml');
1163
- await writeFile(selfUpdatePath, selfUpdateTmpl);
1164
- log(` wrote ${rel(cwd, selfUpdatePath)}`);
1166
+ // --local-only (max mode): SKIP the GitHub Action workflows entirely. The
1167
+ // review + audit workflows run `claude-code-action` with ANTHROPIC_API_KEY
1168
+ // (per-token cost); max mode reviews on the session's own subscription, so a
1169
+ // local-only install must never add a token-billing Action. (self-update is
1170
+ // benign but skipped too local-only means no clud-bug CI workflows at all.)
1171
+ if (args.localOnly) {
1172
+ log(' skipped GitHub Action workflows (--local-only: max mode runs on the session subscription, no API key)');
1173
+ }
1174
+ else {
1175
+ const workflowPath = join(cwd, '.github', 'workflows', 'clud-bug-review.yml');
1176
+ await mkdir(dirname(workflowPath), { recursive: true });
1177
+ await writeFile(workflowPath, workflow);
1178
+ log(` wrote ${rel(cwd, workflowPath)}`);
1179
+ // Install the audit workflow alongside the per-PR review one.
1180
+ // Manual-trigger by default; users opt into the cron by uncommenting.
1181
+ // Routed through renderFile so {{CCA_VERSION}} substitution pins
1182
+ // claude-code-action consistently with the review workflow.
1183
+ const auditTmpl = await renderFile(join(TEMPLATES, 'audit.yml.tmpl'), {});
1184
+ const auditPath = join(cwd, '.github', 'workflows', 'clud-bug-audit.yml');
1185
+ await writeFile(auditPath, auditTmpl);
1186
+ log(` wrote ${rel(cwd, auditPath)}`);
1187
+ // Install the self-update workflow. Cron weekly Mondays 12:00 UTC; opens
1188
+ // a PR if a newer clud-bug version is published. Disable by deleting the
1189
+ // file or pinning via .claude/skills/.clud-bug.json.
1190
+ // Routed through renderFile for parity (no CCA ref today but future
1191
+ // tokens should propagate uniformly).
1192
+ const selfUpdateTmpl = await renderFile(join(TEMPLATES, 'self-update.yml.tmpl'), {});
1193
+ const selfUpdatePath = join(cwd, '.github', 'workflows', 'clud-bug-self-update.yml');
1194
+ await writeFile(selfUpdatePath, selfUpdateTmpl);
1195
+ log(` wrote ${rel(cwd, selfUpdatePath)}`);
1196
+ }
1165
1197
  // v0.7.0 (Wave 6b): optional local-review slash command. Scaffolds
1166
1198
  // `.claude/commands/clud-bug-review.md` so `/clud-bug-review` works inside a
1167
1199
  // Claude Code session — the agent reviews the current branch's open PR using
@@ -1170,6 +1202,12 @@ async function runInit(args) {
1170
1202
  // `--with-hooks` implies `--with-local-review` — the auto hook and the manual
1171
1203
  // `/clud-bug-review` command are complementary (the hook auto-runs the engine
1172
1204
  // recipe on each commit; the command runs it on demand against the PR).
1205
+ // --local-only IS the max-mode install: the slash command + the commit hook,
1206
+ // minus the GitHub Action workflows (gated above).
1207
+ if (args.localOnly) {
1208
+ args.withLocalReview = true;
1209
+ args.withHooks = true;
1210
+ }
1173
1211
  if (args.withHooks)
1174
1212
  args.withLocalReview = true;
1175
1213
  if (args.withLocalReview) {
@@ -1179,14 +1217,16 @@ async function runInit(args) {
1179
1217
  await writeFile(commandPath, commandContent);
1180
1218
  log(` wrote ${rel(cwd, commandPath)}`);
1181
1219
  }
1182
- // v0.7.0 (Wave 6b): optional native commit-review hook. Merges a Claude Code
1183
- // `type: agent` PostToolUse hook into `.claude/settings.json` (preserving any
1184
- // existing settings + hooks) that, on every `git commit` the agent makes,
1185
- // spawns a clud-bug review subagent on the session's subscription, in the
1186
- // background the hook's prompt runs `clud-bug review-prompt` and follows it.
1220
+ // v0.7.0 (Wave 6b; rc.17 fix): optional native commit-review hook. Merges a
1221
+ // Claude Code `type: command` PostToolUse hook into `.claude/settings.json`
1222
+ // (preserving any existing settings + hooks) that, on every `git commit`, runs
1223
+ // `clud-bug review-prompt` and surfaces the recipe back to the session so the
1224
+ // agent reviews the commit on the session's subscription, in the background.
1225
+ // (`type: command`, not `type: agent` — agent hooks get no Bash, so they could
1226
+ // never run the CLI; see hooks.ts.)
1187
1227
  if (args.withHooks) {
1188
- const { mergeLocalReviewHook, buildCommitReviewPrompt } = await import('./hooks.js');
1189
- const hookPrompt = buildCommitReviewPrompt(await readPkgVersion());
1228
+ const { mergeLocalReviewHook, buildCommitReviewCommand } = await import('./hooks.js');
1229
+ const hookCommand = buildCommitReviewCommand(await readPkgVersion());
1190
1230
  const settingsPath = join(cwd, '.claude', 'settings.json');
1191
1231
  await mkdir(dirname(settingsPath), { recursive: true });
1192
1232
  // Read-then-parse so we can tell "no file yet" (fresh merge) from "file
@@ -1210,11 +1250,26 @@ async function runInit(args) {
1210
1250
  }
1211
1251
  }
1212
1252
  if (proceed) {
1213
- const merged = mergeLocalReviewHook(existing, hookPrompt);
1253
+ const merged = mergeLocalReviewHook(existing, hookCommand);
1214
1254
  await writeFile(settingsPath, JSON.stringify(merged, null, 2) + '\n');
1215
1255
  log(` wrote ${rel(cwd, settingsPath)} (commit-review hook)`);
1216
1256
  }
1217
1257
  }
1258
+ // rc.16: --with-design installs the bundled design-critic kit (3 `kind:
1259
+ // design` skills) and enables the off-by-default design lens. writeSkills
1260
+ // writes the SKILL.md files + merges the manifest entries; the `design` block
1261
+ // is flipped on the manifest read below so the local recipe + hosted bot both
1262
+ // run the visual pass. Idempotent — re-runs replace the skills in place.
1263
+ if (args.withDesign) {
1264
+ const designKit = await loadDesignKit(DESIGN_DIR);
1265
+ if (designKit.length > 0) {
1266
+ await writeSkills(join(cwd, '.claude', 'skills'), designKit, client);
1267
+ log(` pinned ${designKit.length} design specimens (visual review lens enabled)`);
1268
+ }
1269
+ else {
1270
+ warn('No design-kit skills found to install (templates/skills/design/ empty?).');
1271
+ }
1272
+ }
1218
1273
  // Stamp the manifest. Sets strictMode: true ONLY on fresh installs —
1219
1274
  // a manifest that's never been touched by clud-bug init/update has no
1220
1275
  // lastUpdate field. Existing v0.3.x advisory installs (where strictMode
@@ -1223,6 +1278,12 @@ async function runInit(args) {
1223
1278
  // fresh inits. Users opt out by setting strictMode: false.
1224
1279
  const skillsDirPath = join(cwd, '.claude', 'skills');
1225
1280
  const manifest = await readManifest(skillsDirPath);
1281
+ if (args.withDesign) {
1282
+ // Flip the off-by-default design lens on; preserve any existing knobs
1283
+ // (gate / themes / viewports) the user already set.
1284
+ const prior = manifest.design ?? {};
1285
+ manifest.design = { ...prior, enabled: true };
1286
+ }
1226
1287
  const isFreshInstall = manifest.lastUpdate === undefined;
1227
1288
  manifest.lastUpdateVersion = await readPkgVersion();
1228
1289
  manifest.lastUpdate = new Date().toISOString();
@@ -1252,9 +1313,14 @@ async function runInit(args) {
1252
1313
  log(' committing...');
1253
1314
  const toAdd = [
1254
1315
  '.claude',
1255
- '.github/workflows/clud-bug-review.yml',
1256
- '.github/workflows/clud-bug-audit.yml',
1257
- '.github/workflows/clud-bug-self-update.yml',
1316
+ // No GitHub Action workflows under --local-only (none were written).
1317
+ ...(args.localOnly
1318
+ ? []
1319
+ : [
1320
+ '.github/workflows/clud-bug-review.yml',
1321
+ '.github/workflows/clud-bug-audit.yml',
1322
+ '.github/workflows/clud-bug-self-update.yml',
1323
+ ]),
1258
1324
  ...agentDocs.created,
1259
1325
  ...agentDocs.touched,
1260
1326
  ];
@@ -1269,22 +1335,44 @@ async function runInit(args) {
1269
1335
  await runInitBranchProtection(args);
1270
1336
  log('');
1271
1337
  log('Field kit assembled. Next:');
1272
- log(' 1. Set ANTHROPIC_API_KEY in your repo secrets:');
1273
- log(' Settings Secrets and variables Actions New repository secret');
1274
- if (!args.commit) {
1275
- log(' 2. git add .claude .github/workflows/clud-bug-*.yml && git commit && git push');
1276
- log(' 3. Open a PR — the naturalist arrives within ~2 minutes.');
1338
+ if (args.localOnly) {
1339
+ log(' Max mode reviews run inside your Claude Code session on your own');
1340
+ log(' subscription. No API key, no GitHub Action, no per-review bill. On every');
1341
+ log(' commit the hook surfaces a review recipe; or run /clud-bug-review on demand.');
1342
+ if (!args.commit) {
1343
+ log(' → git add .claude && git commit && git push');
1344
+ }
1345
+ else {
1346
+ log(' → git push.');
1347
+ }
1277
1348
  }
1278
1349
  else {
1279
- log(' 2. git push, then open a PR — the naturalist arrives within ~2 minutes.');
1350
+ log(' 1. Set ANTHROPIC_API_KEY in your repo secrets:');
1351
+ log(' Settings → Secrets and variables → Actions → New repository secret');
1352
+ if (!args.commit) {
1353
+ log(' 2. git add .claude .github/workflows/clud-bug-*.yml && git commit && git push');
1354
+ log(' 3. Open a PR — the naturalist arrives within ~2 minutes.');
1355
+ }
1356
+ else {
1357
+ log(' 2. git push, then open a PR — the naturalist arrives within ~2 minutes.');
1358
+ }
1280
1359
  }
1281
1360
  log('');
1282
1361
  log('Drop your own .claude/skills/<name>/SKILL.md files anytime — they get pinned automatically.');
1283
- log('For a whole-repo walk: Actions tab Clud Bug 🐛 Audit → Run workflow.');
1284
- log('Self-update is on (weekly Mondays 12:00 UTC). Pin via "pinVersion" in .claude/skills/.clud-bug.json.');
1362
+ // These reference the GitHub Action workflows, which --local-only does not write.
1363
+ if (!args.localOnly) {
1364
+ log('For a whole-repo walk: Actions tab → Clud Bug 🐛 Audit → Run workflow.');
1365
+ log('Self-update is on (weekly Mondays 12:00 UTC). Pin via "pinVersion" in .claude/skills/.clud-bug.json.');
1366
+ }
1285
1367
  log('');
1286
- log('Strict mode is ON by default (clud-bug-review fails the check on critical findings).');
1287
- log(' Add `clud-bug-review` to your branch protection required checks for full enforcement.');
1368
+ if (args.localOnly) {
1369
+ log('Strict mode + branch-protection enforcement apply to the GitHub Action path; max mode is');
1370
+ log('advisory by design — findings surface in your session for you to act on.');
1371
+ }
1372
+ else {
1373
+ log('Strict mode is ON by default (clud-bug-review fails the check on critical findings).');
1374
+ log(' • Add `clud-bug-review` to your branch protection required checks for full enforcement.');
1375
+ }
1288
1376
  log(' • Opt out by setting "strictMode": false in .claude/skills/.clud-bug.json.');
1289
1377
  // v0.6.33 — opt-in unified install (mirror of logmind v0.6.8). When
1290
1378
  // --with-skdd is passed, subprocess to `pip install logmind` + `logmind init`