aiwcli 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/lib/template-installer.js +3 -3
  2. package/dist/lib/version.js +2 -2
  3. package/dist/templates/_shared/hooks-ts/session_end.ts +75 -4
  4. package/dist/templates/_shared/hooks-ts/session_start.ts +14 -11
  5. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +8 -8
  6. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +45 -29
  7. package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
  8. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +1 -1
  9. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
  10. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
  11. package/dist/templates/_shared/scripts/resume_handoff.ts +4 -4
  12. package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
  13. package/dist/templates/_shared/scripts/status_line.ts +103 -70
  14. package/dist/templates/cc-native/.claude/settings.json +11 -12
  15. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +62 -65
  16. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +24 -8
  17. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +3 -2
  18. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +5 -5
  19. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +4 -4
  20. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -5
  21. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +19 -18
  22. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +67 -18
  23. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +6 -6
  24. package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +3 -2
  25. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +14 -11
  26. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +108 -108
  27. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +2 -2
  28. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +18 -18
  29. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +75 -74
  30. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +8 -8
  31. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +33 -33
  32. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +4 -2
  33. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +17 -16
  34. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +10 -2
  35. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
  36. package/oclif.manifest.json +1 -1
  37. package/package.json +1 -1
@@ -9,10 +9,10 @@
9
9
  *
10
10
  * Usage: echo '{"session_id":"...","model":{"display_name":"Opus"},...}' | bun status_line.ts
11
11
  */
12
- import * as fs from "node:fs";
13
- import * as path from "node:path";
14
12
  import { execFileSync } from "node:child_process";
13
+ import * as fs from "node:fs";
15
14
  import { homedir } from "node:os";
15
+ import * as path from "node:path";
16
16
 
17
17
  import { CONTEXT_BASELINE_TOKENS } from "../lib-ts/base/hook-utils.js";
18
18
  import { getContextBySessionId, getContext, loadState, saveState } from "../lib-ts/context/context-store.js";
@@ -31,37 +31,37 @@ const STATUSLINE_CACHE = path.join(CACHE_DIR, ".statusline-cache.json");
31
31
  // ---------------------------------------------------------------------------
32
32
  const NO_COLOR = Boolean(process.env.NO_COLOR);
33
33
 
34
- const RESET = NO_COLOR ? "" : "\x1b[0m";
34
+ const RESET = NO_COLOR ? "" : "\u001B[0m";
35
35
 
36
36
  // Structural
37
- const SLATE_300 = NO_COLOR ? "" : "\x1b[38;2;203;213;225m";
38
- const SLATE_400 = NO_COLOR ? "" : "\x1b[38;2;148;163;184m";
39
- const SLATE_500 = NO_COLOR ? "" : "\x1b[38;2;100;116;139m";
40
- const SLATE_600 = NO_COLOR ? "" : "\x1b[38;2;71;85;105m";
37
+ const SLATE_300 = NO_COLOR ? "" : "\u001B[38;2;203;213;225m";
38
+ const SLATE_400 = NO_COLOR ? "" : "\u001B[38;2;148;163;184m";
39
+ const SLATE_500 = NO_COLOR ? "" : "\u001B[38;2;100;116;139m";
40
+ const SLATE_600 = NO_COLOR ? "" : "\u001B[38;2;71;85;105m";
41
41
 
42
42
  // Semantic
43
- const EMERALD = NO_COLOR ? "" : "\x1b[38;2;74;222;128m";
44
- const ROSE = NO_COLOR ? "" : "\x1b[38;2;251;113;133m";
45
- const AMBER = NO_COLOR ? "" : "\x1b[38;2;251;191;36m";
43
+ const EMERALD = NO_COLOR ? "" : "\u001B[38;2;74;222;128m";
44
+ const ROSE = NO_COLOR ? "" : "\u001B[38;2;251;113;133m";
45
+ const AMBER = NO_COLOR ? "" : "\u001B[38;2;251;191;36m";
46
46
 
47
47
  // Context colors
48
- const CTX_PRIMARY = NO_COLOR ? "" : "\x1b[38;2;129;140;248m";
49
- const CTX_SECONDARY = NO_COLOR ? "" : "\x1b[38;2;165;180;252m";
50
- const CTX_ACCENT = NO_COLOR ? "" : "\x1b[38;2;139;92;246m";
51
- const CTX_BUCKET_EMPTY = NO_COLOR ? "" : "\x1b[38;2;75;82;95m";
48
+ const CTX_PRIMARY = NO_COLOR ? "" : "\u001B[38;2;129;140;248m";
49
+ const CTX_SECONDARY = NO_COLOR ? "" : "\u001B[38;2;165;180;252m";
50
+ const CTX_ACCENT = NO_COLOR ? "" : "\u001B[38;2;139;92;246m";
51
+ const CTX_BUCKET_EMPTY = NO_COLOR ? "" : "\u001B[38;2;75;82;95m";
52
52
 
53
53
  // Git colors
54
- const GIT_PRIMARY = NO_COLOR ? "" : "\x1b[38;2;56;189;248m";
55
- const GIT_VALUE = NO_COLOR ? "" : "\x1b[38;2;186;230;253m";
56
- const GIT_DIR = NO_COLOR ? "" : "\x1b[38;2;147;197;253m";
57
- const GIT_CLEAN = NO_COLOR ? "" : "\x1b[38;2;125;211;252m";
58
- const GIT_MODIFIED = NO_COLOR ? "" : "\x1b[38;2;96;165;250m";
59
- const GIT_ADDED = NO_COLOR ? "" : "\x1b[38;2;59;130;246m";
60
- const GIT_STASH = NO_COLOR ? "" : "\x1b[38;2;165;180;252m";
61
- const GIT_AGE_FRESH = NO_COLOR ? "" : "\x1b[38;2;125;211;252m";
62
- const GIT_AGE_RECENT = NO_COLOR ? "" : "\x1b[38;2;96;165;250m";
63
- const GIT_AGE_STALE = NO_COLOR ? "" : "\x1b[38;2;59;130;246m";
64
- const GIT_AGE_OLD = NO_COLOR ? "" : "\x1b[38;2;99;102;241m";
54
+ const GIT_PRIMARY = NO_COLOR ? "" : "\u001B[38;2;56;189;248m";
55
+ const GIT_VALUE = NO_COLOR ? "" : "\u001B[38;2;186;230;253m";
56
+ const GIT_DIR = NO_COLOR ? "" : "\u001B[38;2;147;197;253m";
57
+ const GIT_CLEAN = NO_COLOR ? "" : "\u001B[38;2;125;211;252m";
58
+ const GIT_MODIFIED = NO_COLOR ? "" : "\u001B[38;2;96;165;250m";
59
+ const GIT_ADDED = NO_COLOR ? "" : "\u001B[38;2;59;130;246m";
60
+ const GIT_STASH = NO_COLOR ? "" : "\u001B[38;2;165;180;252m";
61
+ const GIT_AGE_FRESH = NO_COLOR ? "" : "\u001B[38;2;125;211;252m";
62
+ const GIT_AGE_RECENT = NO_COLOR ? "" : "\u001B[38;2;96;165;250m";
63
+ const GIT_AGE_STALE = NO_COLOR ? "" : "\u001B[38;2;59;130;246m";
64
+ const GIT_AGE_OLD = NO_COLOR ? "" : "\u001B[38;2;99;102;241m";
65
65
 
66
66
  // ---------------------------------------------------------------------------
67
67
  // Display modes
@@ -96,7 +96,7 @@ function getBucketColor(pos: number, maxPos: number): string {
96
96
  if (NO_COLOR) return "";
97
97
  const pct = Math.floor((pos * 100) / maxPos);
98
98
 
99
- let r: number, g: number, b: number;
99
+ let b: number; let g: number; let r: number;
100
100
 
101
101
  if (pct <= 33) {
102
102
  r = 74 + Math.floor(((250 - 74) * pct) / 33);
@@ -114,7 +114,7 @@ function getBucketColor(pos: number, maxPos: number): string {
114
114
  b = 60 + Math.floor(((68 - 60) * t) / 34);
115
115
  }
116
116
 
117
- return `\x1b[38;2;${r};${g};${b}m`;
117
+ return `\u001B[38;2;${r};${g};${b}m`;
118
118
  }
119
119
 
120
120
  // ---------------------------------------------------------------------------
@@ -131,9 +131,9 @@ function renderContextBar(width: number, pct: number): [string, string] {
131
131
  if (i <= filled) {
132
132
  const color = getBucketColor(i, width);
133
133
  lastColor = color;
134
- parts.push(`${color}\u26c1${RESET}`);
134
+ parts.push(`${color}\u26C1${RESET}`);
135
135
  } else {
136
- parts.push(`${CTX_BUCKET_EMPTY}\u26c1${RESET}`);
136
+ parts.push(`${CTX_BUCKET_EMPTY}\u26C1${RESET}`);
137
137
  }
138
138
  if (width > 8) {
139
139
  parts.push(" ");
@@ -184,40 +184,51 @@ function renderContext(
184
184
 
185
185
  const shortModel = shortenModel(modelName);
186
186
 
187
- if (mode === "nano") {
188
- const [bar] = renderContextBar(5, contextPct);
189
- console.log(
190
- `${CTX_PRIMARY}\u25c9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
191
- `${bar} ${pctColor}${contextPct}%${RESET} ` +
192
- `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
193
- );
194
- } else if (mode === "micro") {
187
+ switch (mode) {
188
+ case "micro": {
195
189
  const [bar] = renderContextBar(6, contextPct);
196
190
  console.log(
197
- `${CTX_PRIMARY}\u25c9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
191
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
198
192
  `${SLATE_600}\u2502${RESET} ` +
199
193
  `${bar} ${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k)${RESET} ` +
200
- `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
194
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
201
195
  );
202
- } else if (mode === "mini") {
196
+
197
+ break;
198
+ }
199
+ case "mini": {
203
200
  const [bar] = renderContextBar(8, contextPct);
204
201
  console.log(
205
- `${CTX_PRIMARY}\u25c9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
202
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
206
203
  `${SLATE_600}\u2502${RESET} ` +
207
204
  `${CTX_SECONDARY}CTX:${RESET} ${bar} ` +
208
205
  `${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k/${maxK}k)${RESET} ` +
209
- `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
206
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
210
207
  );
211
- } else {
208
+
209
+ break;
210
+ }
211
+ case "nano": {
212
+ const [bar] = renderContextBar(5, contextPct);
213
+ console.log(
214
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
215
+ `${bar} ${pctColor}${contextPct}%${RESET} ` +
216
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
217
+ );
218
+
219
+ break;
220
+ }
221
+ default: {
212
222
  const [bar, lastColor] = renderContextBar(16, contextPct);
213
223
  console.log(
214
- `${CTX_PRIMARY}\u25c9${RESET} ${CTX_SECONDARY}Model:${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
224
+ `${CTX_PRIMARY}\u25C9${RESET} ${CTX_SECONDARY}Model:${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
215
225
  `${SLATE_600}\u2502${RESET} ` +
216
226
  `${CTX_SECONDARY}Context:${RESET} ${bar} ` +
217
227
  `${lastColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k/${maxK}k)${RESET} ` +
218
228
  `${SLATE_600}\u2502${RESET} ` +
219
- `${CTX_ACCENT}\u23f1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
229
+ `${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
220
230
  );
231
+ }
221
232
  }
222
233
 
223
234
  console.log(SEPARATOR);
@@ -310,7 +321,7 @@ function getGitStatus(cwd: string): GitStatus | null {
310
321
  const ageSec = nowEpoch - lastEpoch;
311
322
  const ageMin = Math.floor(ageSec / 60);
312
323
  const ageHrs = Math.floor(ageSec / 3600);
313
- const ageDays = Math.floor(ageSec / 86400);
324
+ const ageDays = Math.floor(ageSec / 86_400);
314
325
 
315
326
  if (ageMin < 1) {
316
327
  status.age_display = "now";
@@ -338,16 +349,9 @@ function renderGit(mode: string, git: GitStatus, dirName: string): void {
338
349
  const totalChanged = git.modified + git.staged;
339
350
  const statusIcon = (totalChanged > 0 || git.untracked > 0) ? "*" : "\u2713";
340
351
 
341
- if (mode === "nano") {
342
- let line = `${GIT_PRIMARY}\u25c8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET} `;
343
- if (statusIcon === "\u2713") {
344
- line += `${GIT_CLEAN}\u2713${RESET}`;
345
- } else {
346
- line += `${GIT_MODIFIED}*${totalChanged}${RESET}`;
347
- }
348
- console.log(line);
349
- } else if (mode === "micro") {
350
- let line = `${GIT_PRIMARY}\u25c8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
352
+ switch (mode) {
353
+ case "micro": {
354
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
351
355
  if (git.age_display) {
352
356
  line += ` ${git.age_color}${git.age_display}${RESET}`;
353
357
  }
@@ -358,9 +362,12 @@ function renderGit(mode: string, git: GitStatus, dirName: string): void {
358
362
  line += `${GIT_MODIFIED}${statusIcon}${totalChanged}${RESET}`;
359
363
  }
360
364
  console.log(line);
361
- } else if (mode === "mini") {
365
+
366
+ break;
367
+ }
368
+ case "mini": {
362
369
  let line =
363
- `${GIT_PRIMARY}\u25c8${RESET} ${GIT_DIR}${dirName}${RESET} ` +
370
+ `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ` +
364
371
  `${SLATE_600}\u2502${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
365
372
  if (git.age_display) {
366
373
  line += ` ${SLATE_600}\u2502${RESET} ${git.age_color}${git.age_display}${RESET}`;
@@ -375,9 +382,23 @@ function renderGit(mode: string, git: GitStatus, dirName: string): void {
375
382
  }
376
383
  }
377
384
  console.log(line);
378
- } else {
385
+
386
+ break;
387
+ }
388
+ case "nano": {
389
+ let line = `${GIT_PRIMARY}\u25C8${RESET} ${GIT_DIR}${dirName}${RESET} ${GIT_VALUE}${git.branch}${RESET} `;
390
+ if (statusIcon === "\u2713") {
391
+ line += `${GIT_CLEAN}\u2713${RESET}`;
392
+ } else {
393
+ line += `${GIT_MODIFIED}*${totalChanged}${RESET}`;
394
+ }
395
+ console.log(line);
396
+
397
+ break;
398
+ }
399
+ default: {
379
400
  let line =
380
- `${GIT_PRIMARY}\u25c8${RESET} ${GIT_PRIMARY}PWD:${RESET} ${GIT_DIR}${dirName}${RESET} ` +
401
+ `${GIT_PRIMARY}\u25C8${RESET} ${GIT_PRIMARY}PWD:${RESET} ${GIT_DIR}${dirName}${RESET} ` +
381
402
  `${SLATE_600}\u2502${RESET} ` +
382
403
  `${GIT_PRIMARY}Branch:${RESET} ${GIT_VALUE}${git.branch}${RESET}`;
383
404
  if (git.age_display) {
@@ -410,6 +431,7 @@ function renderGit(mode: string, git: GitStatus, dirName: string): void {
410
431
  }
411
432
  }
412
433
  console.log(line);
434
+ }
413
435
  }
414
436
  }
415
437
 
@@ -493,25 +515,36 @@ function renderContextManager(
493
515
  planPart = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Plan:${RESET} ${SLATE_300}${truncatedPlan}${RESET}`;
494
516
  }
495
517
 
496
- if (mode === "nano") {
497
- console.log(`${CTX_ACCENT}\u25c6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
498
- } else if (mode === "micro") {
499
- console.log(`${CTX_ACCENT}\u25c6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
500
- } else if (mode === "mini") {
518
+ switch (mode) {
519
+ case "micro": {
520
+ console.log(`${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
521
+
522
+ break;
523
+ }
524
+ case "mini": {
501
525
  console.log(
502
- `${CTX_ACCENT}\u25c6${RESET} ${SLATE_400}${truncatedId}${RESET}` +
526
+ `${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}` +
503
527
  `${modeBadge}${planPart}`,
504
528
  );
505
- } else {
529
+
530
+ break;
531
+ }
532
+ case "nano": {
533
+ console.log(`${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}${modeBadge}`);
534
+
535
+ break;
536
+ }
537
+ default: {
506
538
  console.log(
507
- `${CTX_ACCENT}\u25c6${RESET} ${CTX_SECONDARY}Context:${RESET} ${SLATE_300}${truncatedId}${RESET}` +
539
+ `${CTX_ACCENT}\u25C6${RESET} ${CTX_SECONDARY}Context:${RESET} ${SLATE_300}${truncatedId}${RESET}` +
508
540
  `${modeBadge}${planPart}`,
509
541
  );
542
+ }
510
543
  }
511
544
  }
512
545
 
513
546
  function renderNoContext(mode: string): void {
514
- const warn = `${ROSE}\u26a0 ${RESET}`;
547
+ const warn = `${ROSE}\u26A0 ${RESET}`;
515
548
  if (mode === "normal") {
516
549
  console.log(`${warn} ${ROSE}NO CONTEXT${RESET} ${SLATE_500}\u2014 type ^ for context manager${RESET}`);
517
550
  } else {
@@ -620,7 +653,7 @@ function main(): void {
620
653
  const inputTokens: number = usage.input_tokens ?? 0;
621
654
  const cacheCreation: number = usage.cache_creation_input_tokens ?? 0;
622
655
  const outputTokens: number = usage.output_tokens ?? 0;
623
- const contextMax: number = ctxWin.context_window_size ?? 200000;
656
+ const contextMax: number = ctxWin.context_window_size ?? 200_000;
624
657
 
625
658
  // Calculate context percentage
626
659
  const usedPct = ctxWin.used_percentage;
@@ -123,6 +123,17 @@
123
123
  "timeout": 5000
124
124
  }
125
125
  ]
126
+ },
127
+ {
128
+ "matcher": "^Task$",
129
+ "hooks": [
130
+ {
131
+ "type": "command",
132
+ "command": "bun .aiwcli/_cc-native/hooks/enhance_plan_post_subagent.ts",
133
+ "timeout": 10000,
134
+ "blockOnFail": false
135
+ }
136
+ ]
126
137
  }
127
138
  ],
128
139
  "PermissionRequest": [
@@ -166,18 +177,6 @@
166
177
  }
167
178
  ]
168
179
  }
169
- ],
170
- "SubagentStop": [
171
- {
172
- "matcher": "Plan",
173
- "hooks": [
174
- {
175
- "type": "command",
176
- "command": "bun .aiwcli/_cc-native/hooks/enhance_plan_post_subagent.ts",
177
- "timeout": 10000
178
- }
179
- ]
180
- }
181
180
  ]
182
181
  }
183
182
  }
@@ -19,11 +19,12 @@
19
19
  * - reviewer-output/{reviewer}.json (individual reviewer results)
20
20
  */
21
21
 
22
+ import * as crypto from "node:crypto";
22
23
  import * as fs from "node:fs";
23
- import * as path from "node:path";
24
24
  import * as os from "node:os";
25
- import * as crypto from "node:crypto";
25
+ import * as path from "node:path";
26
26
 
27
+ import { getProjectRoot, getAiwcliDir, getContextReviewsDir, getContextDir, getReviewFolderPath } from "../../_shared/lib-ts/base/constants.js";
27
28
  import {
28
29
  loadHookInput,
29
30
  runHookAsync,
@@ -36,44 +37,11 @@ import {
36
37
  emitContextAndBlock,
37
38
  } from "../../_shared/lib-ts/base/hook-utils.js";
38
39
  import { isInternalCall, findExecutable } from "../../_shared/lib-ts/base/subprocess-utils.js";
39
- import { getProjectRoot, getAiwcliDir, getContextReviewsDir, getContextDir, getReviewFolderPath } from "../../_shared/lib-ts/base/constants.js";
40
40
  import { eprint } from "../../_shared/lib-ts/base/utils.js";
41
41
  import { getContextBySessionId, getAllContexts } from "../../_shared/lib-ts/context/context-store.js";
42
42
  import { findPlanPathInTranscript } from "../../_shared/lib-ts/context/plan-manager.js";
43
-
44
- import type {
45
- AgentConfig,
46
- OrchestratorConfig,
47
- ProviderConfig,
48
- ModelsConfig,
49
- ReviewerResult,
50
- CombinedReviewResult,
51
- OrchestratorResult,
52
- Verdict,
53
- IterationState,
54
- } from "../lib-ts/types.js";
55
43
  import type { ContextState } from "../../_shared/lib-ts/types.js";
56
- import {
57
- REVIEW_SCHEMA,
58
- DEFAULT_DISPLAY,
59
- DEFAULT_SANITIZATION,
60
- } from "../lib-ts/types.js";
61
-
62
- import {
63
- isPlanAlreadyReviewed,
64
- wasPlanPreviouslyDenied,
65
- getLastPlanReview,
66
- markPlanReviewed,
67
- wasQuestionsAsked,
68
- markQuestionsAsked,
69
- } from "../lib-ts/cc-native-state.js";
70
-
71
- import { worstVerdict } from "../lib-ts/verdict.js";
72
- import { computeCorroboratedDecision } from "../lib-ts/corroboration.js";
73
- import { loadConfig, getDisplaySettings } from "../lib-ts/config.js";
74
- import { runOrchestrator } from "../lib-ts/orchestrator.js";
75
44
  import { aggregateAgents } from "../lib-ts/aggregate-agents.js";
76
- import { debugLog } from "../lib-ts/debug.js";
77
45
  import {
78
46
  writeCombinedArtifacts,
79
47
  buildInlineReviewSummary,
@@ -83,9 +51,38 @@ import {
83
51
  writeReviewTracker,
84
52
  } from "../lib-ts/artifacts.js";
85
53
  import type { ReviewTrackerEntry } from "../lib-ts/artifacts.js";
54
+ import {
55
+ isPlanAlreadyReviewed,
56
+ wasPlanPreviouslyDenied,
57
+ getLastPlanReview,
58
+ markPlanReviewed,
59
+ wasPlanQuestionsAgentAsked,
60
+ markQuestionsAsked,
61
+ } from "../lib-ts/cc-native-state.js";
62
+ import { loadConfig, getDisplaySettings } from "../lib-ts/config.js";
63
+ import { computeCorroboratedDecision } from "../lib-ts/corroboration.js";
64
+ import { debugLog } from "../lib-ts/debug.js";
65
+ import { runOrchestrator } from "../lib-ts/orchestrator.js";
66
+ import { runPlanQuestions } from "../lib-ts/plan-questions.js";
86
67
  import { runAgentReview } from "../lib-ts/reviewers/index.js";
87
68
  import { DEFAULT_REVIEW_ITERATIONS } from "../lib-ts/state.js";
88
- import { runPlanQuestions } from "../lib-ts/plan-questions.js";
69
+ import {
70
+ REVIEW_SCHEMA,
71
+ DEFAULT_DISPLAY,
72
+ DEFAULT_SANITIZATION,
73
+ } from "../lib-ts/types.js";
74
+ import type {
75
+ AgentConfig,
76
+ OrchestratorConfig,
77
+ ProviderConfig,
78
+ ModelsConfig,
79
+ ReviewerResult,
80
+ CombinedReviewResult,
81
+ OrchestratorResult,
82
+ Verdict,
83
+ IterationState,
84
+ } from "../lib-ts/types.js";
85
+ import { worstVerdict } from "../lib-ts/verdict.js";
89
86
 
90
87
  // ---------------------------------------------------------------------------
91
88
  // Hook Name
@@ -226,7 +223,7 @@ function resolveMandatoryAgents(
226
223
  return new Set(configValue as string[]);
227
224
  }
228
225
  if (!configValue || typeof configValue !== "object") {
229
- return new Set(["handoff-readiness", "clarity-auditor", "skeptic"]);
226
+ return new Set(["clarity-auditor", "handoff-readiness", "skeptic"]);
230
227
  }
231
228
  const cfg = configValue as Record<string, string[]>;
232
229
  const names = new Set(cfg.always ?? []);
@@ -276,8 +273,8 @@ function loadIterationState(reviewsDir: string): IterationState | null {
276
273
  if (!fs.existsSync(iterationFile)) return null;
277
274
  try {
278
275
  return JSON.parse(fs.readFileSync(iterationFile, "utf-8")) as IterationState;
279
- } catch (e) {
280
- logError(HOOK, `Failed to load iteration state: ${e}`);
276
+ } catch (error) {
277
+ logError(HOOK, `Failed to load iteration state: ${error}`);
281
278
  return null;
282
279
  }
283
280
  }
@@ -289,8 +286,8 @@ function saveIterationState(reviewsDir: string, state: IterationState & { schema
289
286
  state.schema_version = "1.0.0";
290
287
  fs.writeFileSync(iterationFile, JSON.stringify(state, null, 2), "utf-8");
291
288
  return true;
292
- } catch (e) {
293
- logError(HOOK, `Failed to save iteration state: ${e}`);
289
+ } catch (error) {
290
+ logError(HOOK, `Failed to save iteration state: ${error}`);
294
291
  return false;
295
292
  }
296
293
  }
@@ -335,7 +332,7 @@ function assignModelsToAgents(
335
332
  if (!found) {
336
333
  logWarn(HOOK, `Provider '${name}' enabled but CLI '${cliName}' not found on PATH — skipping`);
337
334
  }
338
- return !!found;
335
+ return Boolean(found);
339
336
  });
340
337
 
341
338
  if (enabledProviders.length === 0) {
@@ -403,11 +400,11 @@ function loadSettings(projDir: string): Record<string, unknown> {
403
400
  }
404
401
  mergedAgent.display = getDisplaySettings(config, "agentReview");
405
402
  const configRecord = config as Record<string, unknown>;
406
- mergedAgent.agentSelection = { ...DEFAULT_AGENT_SELECTION, ...((configRecord.agentSelection as Record<string, unknown>) ?? {}) };
407
- mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...((configRecord.agentDefaults as Record<string, unknown>) ?? {}) };
403
+ mergedAgent.agentSelection = { ...DEFAULT_AGENT_SELECTION, ...(configRecord.agentSelection as Record<string, unknown>) };
404
+ mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...(configRecord.agentDefaults as Record<string, unknown>) };
408
405
  mergedAgent.complexityCategories = (configRecord.complexityCategories as string[]) ?? [...DEFAULT_COMPLEXITY_CATEGORIES];
409
- mergedAgent.sanitization = { ...DEFAULT_SANITIZATION, ...((configRecord.sanitization as Record<string, unknown>) ?? {}) };
410
- mergedAgent.reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS, ...agentReview.reviewIterations ?? {} };
406
+ mergedAgent.sanitization = { ...DEFAULT_SANITIZATION, ...(configRecord.sanitization as Record<string, unknown>) };
407
+ mergedAgent.reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS, ...agentReview.reviewIterations };
411
408
 
412
409
  const modelsRaw = (config as Record<string, unknown>).models ?? {};
413
410
  return { planReview: mergedPlan, agentReview: mergedAgent, models: modelsRaw };
@@ -504,8 +501,8 @@ async function main(): Promise<void> {
504
501
  let plan: string;
505
502
  try {
506
503
  plan = fs.readFileSync(planPath, "utf-8").trim();
507
- } catch (e) {
508
- skipWithInfo(`Failed to read plan file: ${e}`);
504
+ } catch (error) {
505
+ skipWithInfo(`Failed to read plan file: ${error}`);
509
506
  return;
510
507
  }
511
508
 
@@ -520,15 +517,15 @@ async function main(): Promise<void> {
520
517
  // ============================================
521
518
  // Questions Gate: ask user questions before review
522
519
  // ============================================
523
- if (!wasQuestionsAsked(sessionId, base)) {
524
- logInfo(HOOK, "Questions gate: user has not been asked questions yet, running plan-questions agent");
520
+ if (!wasPlanQuestionsAgentAsked(sessionId, base)) {
521
+ logInfo(HOOK, "Questions gate (Phase B): plan-questions agent has not run yet, running now");
525
522
  const timeout = typeof (settings.agentReview ?? {}).timeout === "number"
526
523
  ? (settings.agentReview as Record<string, unknown>).timeout as number : 120;
527
524
  const questionsResult = await runPlanQuestions(plan, aiwcliDir, timeout, undefined, sessionId);
528
525
 
529
- // Mark questions as asked NOW — prevents infinite gate loop if Claude
526
+ // Mark agent questions as asked NOW — prevents infinite gate loop if Claude
530
527
  // doesn't use AskUserQuestion after denial. Gate fires at most once.
531
- markQuestionsAsked(sessionId, base);
528
+ markQuestionsAsked(sessionId, base, "agent");
532
529
 
533
530
  const hasQuestions = questionsResult && (
534
531
  questionsResult.questions.length > 0 ||
@@ -549,9 +546,9 @@ async function main(): Promise<void> {
549
546
  return;
550
547
  }
551
548
 
552
- logInfo(HOOK, "Questions gate: no questions generated, proceeding to review");
549
+ logInfo(HOOK, "Questions gate (Phase B): no questions generated, proceeding to review");
553
550
  } else {
554
- logInfo(HOOK, "Questions gate: questions already asked, skipping");
551
+ logInfo(HOOK, "Questions gate (Phase B): agent already ran, skipping");
555
552
  }
556
553
 
557
554
  const planHash = computePlanHash(plan);
@@ -585,19 +582,19 @@ async function main(): Promise<void> {
585
582
  "Plan unchanged since denial. Modify the plan to address review findings, then attempt ExitPlanMode again.",
586
583
  );
587
584
  return;
588
- } else {
585
+ }
589
586
  // Plan already reviewed with PASS or WARN verdict - skip review
590
587
  const verdict = lastReview?.iteration?.latest_verdict || "pass";
591
588
  const skipMsg = `[Plan Review] Plan already reviewed (verdict: ${verdict}). Skipping re-review.`;
592
589
  emitContext(skipMsg);
593
590
  logInfo(HOOK, skipMsg);
594
591
  return;
595
- }
592
+
596
593
  }
597
594
 
598
595
  // Single load of iteration state — reused throughout, saved once at end.
599
596
  // Default max=1 is safe: first iteration 1>1=false (runs), Edit E updates max from config before save.
600
- let iterationState: IterationState = loadIterationState(reviewsDir) ?? {
597
+ const iterationState: IterationState = loadIterationState(reviewsDir) ?? {
601
598
  current: 1, max: 1, complexity: "medium",
602
599
  history: [], graduated: [], passStreaks: {}, lastPlanHash: "",
603
600
  };
@@ -761,7 +758,7 @@ async function main(): Promise<void> {
761
758
  // Update complexity/max on the already-loaded iteration state (no second disk read)
762
759
  const reviewIterations: Record<string, number> = {
763
760
  ...DEFAULT_REVIEW_ITERATIONS,
764
- ...(agentSettings.reviewIterations ?? {}),
761
+ ...agentSettings.reviewIterations,
765
762
  };
766
763
  iterationState.complexity = detectedComplexity;
767
764
  iterationState.max = reviewIterations[detectedComplexity] ?? iterationState.max;
@@ -844,7 +841,7 @@ async function main(): Promise<void> {
844
841
  if (!r.verdict || r.verdict === "skip" || r.verdict === "error") continue;
845
842
  const issues = Array.isArray(r.data?.issues) ? r.data.issues as Array<{ severity?: string }> : [];
846
843
  const agentHigh = issues.filter(i => i.severity === "high").length;
847
- let verdict = r.verdict;
844
+ let {verdict} = r;
848
845
  if (agentHigh >= highIssueThreshold) {
849
846
  logInfo(HOOK, `${r.name}: verdict overridden to 'fail' (${agentHigh} high issues >= ${highIssueThreshold})`);
850
847
  verdict = "fail";
@@ -883,8 +880,8 @@ async function main(): Promise<void> {
883
880
  };
884
881
 
885
882
  const displaySettings = {
886
- ...(planSettings.display ?? {}),
887
- ...(agentSettings.display ?? {}),
883
+ ...planSettings.display,
884
+ ...agentSettings.display,
888
885
  };
889
886
  const combinedSettings = { display: displaySettings };
890
887
 
@@ -919,8 +916,8 @@ async function main(): Promise<void> {
919
916
  try {
920
917
  fs.writeFileSync(path.join(reviewFolder, "plan.md"), plan, "utf-8");
921
918
  logDebug(HOOK, `Saved plan snapshot: ${path.join(reviewFolder, "plan.md")}`);
922
- } catch (e) {
923
- logWarn(HOOK, `Failed to save plan snapshot: ${e}`);
919
+ } catch (error) {
920
+ logWarn(HOOK, `Failed to save plan snapshot: ${error}`);
924
921
  }
925
922
 
926
923
  // Build inline summary with top issues (always emitted, even on pass)
@@ -933,7 +930,7 @@ async function main(): Promise<void> {
933
930
  contextParts.push(`\nFull review: \`${reviewFile}\`\n`);
934
931
  const shouldDeny = corroborationResult.blocking.length > 0;
935
932
  const denyReason = shouldDeny ? "corroborated_issues" : "no_corroboration";
936
- const reviewScore = shouldDeny ? 1.0 : 0.0;
933
+ const reviewScore = shouldDeny ? 1 : 0;
937
934
 
938
935
  logInfo(HOOK, `REVIEW_DECISION: verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}, score=${reviewScore.toFixed(2)}`);
939
936
  logDiagnostic(HOOK, "result", `verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}`, {
@@ -972,7 +969,7 @@ async function main(): Promise<void> {
972
969
  }
973
970
 
974
971
  // Update pass streaks — only for agents that actually ran this iteration
975
- const passStreaks = { ...(iterationState.passStreaks ?? {}) };
972
+ const passStreaks = { ...iterationState.passStreaks };
976
973
  const passEligibleSet = new Set(passEligible);
977
974
  const graduatedSetCurrent = new Set(iterationState.graduated);
978
975
 
@@ -1,21 +1,23 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * SubagentStop Hook: Plan Quality Review Context
3
+ * PostToolUse:Task Hook: Plan Quality Review Context
4
4
  *
5
- * Fires after Plan subagent completes. Emits plan quality review guidance
5
+ * Fires after Task tool completes with a Plan subagent. Emits plan quality review guidance
6
6
  * as context for the main agent to review the plan before ExitPlanMode.
7
7
  *
8
8
  * Design:
9
9
  * - Never blocks (all errors exit 0)
10
10
  * - Emits context via emitContext() — no file mutation
11
- * - Only fires for Plan subagents
11
+ * - Only fires for Task tool with subagent_type="Plan"
12
12
  */
13
13
 
14
14
  import {
15
15
  loadHookInput,
16
16
  runHook,
17
17
  logInfo,
18
+ logDebug,
18
19
  emitContext,
20
+ getToolInput,
19
21
  } from "../../_shared/lib-ts/base/hook-utils.js";
20
22
  import { isInternalCall } from "../../_shared/lib-ts/base/subprocess-utils.js";
21
23
  import { getPlanQualityReviewContext } from "../lib-ts/plan-enhancement.js";
@@ -24,14 +26,28 @@ function main(): void {
24
26
  if (isInternalCall()) return;
25
27
 
26
28
  const payload = loadHookInput();
27
- if (!payload) return;
29
+ if (!payload) {
30
+ logDebug("enhance_plan_post_subagent", "No payload received");
31
+ return;
32
+ }
33
+
34
+ // Check if this is a Task tool call
35
+ if (payload.tool_name !== "Task") {
36
+ logDebug("enhance_plan_post_subagent", `Skipping: tool_name is "${payload.tool_name}", not "Task"`);
37
+ return;
38
+ }
39
+
40
+ // Check if the Task is spawning a Plan subagent
41
+ const toolInput = getToolInput(payload);
42
+ const subagentType = toolInput?.subagent_type;
43
+ logDebug("enhance_plan_post_subagent", `subagent_type: ${subagentType ?? "undefined"}`);
28
44
 
29
- const agentType = payload.agent_type;
30
- if (agentType !== "Plan") {
31
- return; // Only emit for Plan agents
45
+ if (subagentType !== "Plan") {
46
+ logDebug("enhance_plan_post_subagent", `Skipping: subagent_type is "${subagentType}", not "Plan"`);
47
+ return;
32
48
  }
33
49
 
34
- logInfo("enhance_plan", "Emitting plan quality review context");
50
+ logInfo("enhance_plan_post_subagent", "Emitting plan quality review context");
35
51
  emitContext(getPlanQualityReviewContext());
36
52
  }
37
53