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.
- package/dist/lib/template-installer.js +3 -3
- package/dist/lib/version.js +2 -2
- package/dist/templates/_shared/hooks-ts/session_end.ts +75 -4
- package/dist/templates/_shared/hooks-ts/session_start.ts +14 -11
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +8 -8
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +45 -29
- package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +1 -1
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
- package/dist/templates/_shared/scripts/resume_handoff.ts +4 -4
- package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
- package/dist/templates/_shared/scripts/status_line.ts +103 -70
- package/dist/templates/cc-native/.claude/settings.json +11 -12
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +62 -65
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +24 -8
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +3 -2
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +5 -5
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +4 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -5
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +19 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +67 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +6 -6
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +3 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +14 -11
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +108 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +2 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +18 -18
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +75 -74
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +8 -8
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +33 -33
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +4 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +17 -16
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +10 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
- package/oclif.manifest.json +1 -1
- 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 ? "" : "\
|
|
34
|
+
const RESET = NO_COLOR ? "" : "\u001B[0m";
|
|
35
35
|
|
|
36
36
|
// Structural
|
|
37
|
-
const SLATE_300 = NO_COLOR ? "" : "\
|
|
38
|
-
const SLATE_400 = NO_COLOR ? "" : "\
|
|
39
|
-
const SLATE_500 = NO_COLOR ? "" : "\
|
|
40
|
-
const SLATE_600 = NO_COLOR ? "" : "\
|
|
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 ? "" : "\
|
|
44
|
-
const ROSE = NO_COLOR ? "" : "\
|
|
45
|
-
const AMBER = NO_COLOR ? "" : "\
|
|
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 ? "" : "\
|
|
49
|
-
const CTX_SECONDARY = NO_COLOR ? "" : "\
|
|
50
|
-
const CTX_ACCENT = NO_COLOR ? "" : "\
|
|
51
|
-
const CTX_BUCKET_EMPTY = NO_COLOR ? "" : "\
|
|
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 ? "" : "\
|
|
55
|
-
const GIT_VALUE = NO_COLOR ? "" : "\
|
|
56
|
-
const GIT_DIR = NO_COLOR ? "" : "\
|
|
57
|
-
const GIT_CLEAN = NO_COLOR ? "" : "\
|
|
58
|
-
const GIT_MODIFIED = NO_COLOR ? "" : "\
|
|
59
|
-
const GIT_ADDED = NO_COLOR ? "" : "\
|
|
60
|
-
const GIT_STASH = NO_COLOR ? "" : "\
|
|
61
|
-
const GIT_AGE_FRESH = NO_COLOR ? "" : "\
|
|
62
|
-
const GIT_AGE_RECENT = NO_COLOR ? "" : "\
|
|
63
|
-
const GIT_AGE_STALE = NO_COLOR ? "" : "\
|
|
64
|
-
const GIT_AGE_OLD = NO_COLOR ? "" : "\
|
|
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
|
|
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 `\
|
|
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}\
|
|
134
|
+
parts.push(`${color}\u26C1${RESET}`);
|
|
135
135
|
} else {
|
|
136
|
-
parts.push(`${CTX_BUCKET_EMPTY}\
|
|
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
|
-
|
|
188
|
-
|
|
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}\
|
|
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}\
|
|
194
|
+
`${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
|
|
201
195
|
);
|
|
202
|
-
|
|
196
|
+
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "mini": {
|
|
203
200
|
const [bar] = renderContextBar(8, contextPct);
|
|
204
201
|
console.log(
|
|
205
|
-
`${CTX_PRIMARY}\
|
|
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}\
|
|
206
|
+
`${CTX_ACCENT}\u23F1${RESET} ${SLATE_300}${timeDisplay}${RESET}`,
|
|
210
207
|
);
|
|
211
|
-
|
|
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}\
|
|
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}\
|
|
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 /
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
365
|
+
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case "mini": {
|
|
362
369
|
let line =
|
|
363
|
-
`${GIT_PRIMARY}\
|
|
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
|
-
|
|
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}\
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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}\
|
|
526
|
+
`${CTX_ACCENT}\u25C6${RESET} ${SLATE_400}${truncatedId}${RESET}` +
|
|
503
527
|
`${modeBadge}${planPart}`,
|
|
504
528
|
);
|
|
505
|
-
|
|
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}\
|
|
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}\
|
|
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 ??
|
|
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
|
|
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 {
|
|
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(["
|
|
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 (
|
|
280
|
-
logError(HOOK, `Failed to load iteration state: ${
|
|
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 (
|
|
293
|
-
logError(HOOK, `Failed to save iteration state: ${
|
|
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
|
|
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, ...(
|
|
407
|
-
mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...(
|
|
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, ...(
|
|
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 (
|
|
508
|
-
skipWithInfo(`Failed to read plan file: ${
|
|
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 (!
|
|
524
|
-
logInfo(HOOK, "Questions gate:
|
|
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:
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
-
...
|
|
887
|
-
...
|
|
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 (
|
|
923
|
-
logWarn(HOOK, `Failed to save plan snapshot: ${
|
|
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
|
|
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 = { ...
|
|
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
|
-
*
|
|
3
|
+
* PostToolUse:Task Hook: Plan Quality Review Context
|
|
4
4
|
*
|
|
5
|
-
* Fires after Plan subagent
|
|
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
|
|
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)
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
return;
|
|
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("
|
|
50
|
+
logInfo("enhance_plan_post_subagent", "Emitting plan quality review context");
|
|
35
51
|
emitContext(getPlanQualityReviewContext());
|
|
36
52
|
}
|
|
37
53
|
|