aiwcli 0.12.1 → 0.12.2

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 (57) hide show
  1. package/dist/templates/_shared/hooks-ts/session_start.ts +21 -15
  2. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +20 -8
  3. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +151 -29
  4. package/dist/templates/_shared/scripts/resume_handoff.ts +25 -0
  5. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -7
  6. package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-EVOLUTION.md +62 -63
  7. package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-PATTERNS.md +61 -62
  8. package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-STRUCTURE.md +62 -63
  9. package/dist/templates/cc-native/_cc-native/agents/plan-review/ASSUMPTION-TRACER.md +56 -57
  10. package/dist/templates/cc-native/_cc-native/agents/plan-review/CLARITY-AUDITOR.md +53 -54
  11. package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -67
  12. package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-GAPS.md +70 -71
  13. package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-ORDERING.md +62 -63
  14. package/dist/templates/cc-native/_cc-native/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -73
  15. package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -62
  16. package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -65
  17. package/dist/templates/cc-native/_cc-native/agents/plan-review/DEVILS-ADVOCATE.md +56 -57
  18. package/dist/templates/cc-native/_cc-native/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -87
  19. package/dist/templates/cc-native/_cc-native/agents/plan-review/HANDOFF-READINESS.md +59 -60
  20. package/dist/templates/cc-native/_cc-native/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -59
  21. package/dist/templates/cc-native/_cc-native/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -67
  22. package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-DEPENDENCY.md +62 -63
  23. package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-FMEA.md +66 -67
  24. package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-PREMORTEM.md +71 -72
  25. package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-REVERSIBILITY.md +74 -75
  26. package/dist/templates/cc-native/_cc-native/agents/plan-review/SCOPE-BOUNDARY.md +77 -78
  27. package/dist/templates/cc-native/_cc-native/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -63
  28. package/dist/templates/cc-native/_cc-native/agents/plan-review/SKEPTIC.md +68 -69
  29. package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -62
  30. package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -72
  31. package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -62
  32. package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -62
  33. package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-COSTS.md +67 -68
  34. package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -66
  35. package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-COVERAGE.md +74 -75
  36. package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-STRENGTH.md +69 -70
  37. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +19 -2
  38. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +28 -1010
  39. package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
  40. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +1 -2
  41. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -0
  42. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -0
  43. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -0
  44. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -0
  45. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +19 -821
  46. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +36 -13
  47. package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -0
  48. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +1 -2
  49. package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -0
  50. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +489 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +1 -1
  53. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +51 -17
  55. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +40 -2
  56. package/oclif.manifest.json +1 -1
  57. package/package.json +1 -1
@@ -3,17 +3,18 @@
3
3
  * SessionStart hook: Restore context after /clear (plan/handoff) or compaction.
4
4
  * Routes by source field to appropriate handler.
5
5
  */
6
- import { getProjectRoot } from "../lib-ts/base/constants.js";
7
6
  import {
8
7
  loadHookInput, emitContext, runHook, runHookAsync,
9
8
  logDebug, logInfo, logError, logDiagnostic,
10
9
  } from "../lib-ts/base/hook-utils.js";
11
- import {
12
- buildRestoreSections, formatHandoffContinuation, getModeDisplay,
13
- } from "../lib-ts/context/context-formatter.js";
10
+ import { getProjectRoot } from "../lib-ts/base/constants.js";
14
11
  import {
15
12
  getContextBySessionId, getAllContexts, bindSession, updateMode,
16
13
  } from "../lib-ts/context/context-store.js";
14
+ import {
15
+ buildRestoreSections, formatHandoffContinuation, getModeDisplay,
16
+ buildContextInventory,
17
+ } from "../lib-ts/context/context-formatter.js";
17
18
  import type { ContextState } from "../lib-ts/types.js";
18
19
 
19
20
  /**
@@ -39,6 +40,9 @@ function handleCompactRestore(sessionId: string, projectRoot: string): void {
39
40
  const restore = buildRestoreSections(state, projectRoot, true);
40
41
  if (restore) sections.push(restore);
41
42
 
43
+ const inventory = buildContextInventory(state, projectRoot);
44
+ if (inventory) sections.push("", inventory);
45
+
42
46
  sections.push(
43
47
  "",
44
48
  "---",
@@ -79,6 +83,9 @@ async function handleClearRestore(sessionId: string, projectRoot: string): Promi
79
83
  const restore = buildRestoreSections(ctx, projectRoot, false);
80
84
  if (restore) sections.push(restore);
81
85
 
86
+ const inventory = buildContextInventory(ctx, projectRoot);
87
+ if (inventory) sections.push("", inventory);
88
+
82
89
  sections.push(
83
90
  "",
84
91
  "---",
@@ -100,7 +107,9 @@ async function handleClearRestore(sessionId: string, projectRoot: string): Promi
100
107
  logInfo("session_start", `Clear restore: ${ctx.id} has_handoff → active (handoff_consumed=true)`);
101
108
 
102
109
  const handoffContent = formatHandoffContinuation(ctx, projectRoot);
103
- emitContext(handoffContent);
110
+ const handoffInventory = buildContextInventory(ctx, projectRoot);
111
+ const combined = handoffInventory ? handoffContent + "\n\n" + handoffInventory : handoffContent;
112
+ emitContext(combined);
104
113
  return;
105
114
  }
106
115
 
@@ -124,18 +133,15 @@ async function main(): Promise<void> {
124
133
  logDiagnostic("session_start", "entry", `source=${source}, session=${sessionId}`);
125
134
 
126
135
  switch (source) {
127
- case "clear": {
128
- await handleClearRestore(sessionId, projectRoot);
129
- break;
130
- }
131
- case "compact": {
136
+ case "compact":
132
137
  handleCompactRestore(sessionId, projectRoot);
133
- break;
134
- }
135
- default: {
138
+ break;
139
+ case "clear":
140
+ await handleClearRestore(sessionId, projectRoot);
141
+ break;
142
+ default:
136
143
  logDebug("session_start", `Unhandled source: ${source}`);
137
- break;
138
- }
144
+ break;
139
145
  }
140
146
  }
141
147
 
@@ -6,14 +6,15 @@
6
6
  * Uses emitContext() for output — context text is passed via hookSpecificOutput JSON.
7
7
  * Catches BlockRequest and uses emitBlock() to block the prompt.
8
8
  */
9
- import { getProjectRoot } from "../lib-ts/base/constants.js";
10
9
  import {
11
10
  loadHookInput, runHookAsync, logDebug, logInfo, logWarn, logBlocking, logDiagnostic, hookLog, emitContext, emitBlock,
12
11
  } from "../lib-ts/base/hook-utils.js";
13
- import { determineContext, BlockRequest } from "../lib-ts/context/context-selector.js";
12
+ import { getProjectRoot } from "../lib-ts/base/constants.js";
14
13
  import {
15
14
  getContextBySessionId, bindSession, maybeActivate, saveState,
16
15
  } from "../lib-ts/context/context-store.js";
16
+ import { determineContext, BlockRequest } from "../lib-ts/context/context-selector.js";
17
+ import { buildContextInventory } from "../lib-ts/context/context-formatter.js";
17
18
 
18
19
  async function asyncMain(): Promise<void> {
19
20
  const payload = loadHookInput();
@@ -38,8 +39,8 @@ async function asyncMain(): Promise<void> {
38
39
  // Returning user — context already bound (stderr: false to avoid "hook error" display)
39
40
  try {
40
41
  maybeActivate(existingCtx.id, permissionMode, projectRoot, "user_prompt_submit");
41
- } catch (error) {
42
- hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${error}`, { stderr: false });
42
+ } catch (e) {
43
+ hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${e}`, { stderr: false });
43
44
  }
44
45
  hookLog("debug", "user_prompt_submit", `Session bound to ${existingCtx.id}`, { stderr: false });
45
46
  } else if (prompt) {
@@ -64,12 +65,23 @@ async function asyncMain(): Promise<void> {
64
65
  if (outputText) {
65
66
  outputs.push(outputText);
66
67
  }
67
- } catch (error) {
68
- if (error instanceof BlockRequest) {
69
- emitBlock((error as Error).message);
68
+
69
+ // Append context folder inventory
70
+ try {
71
+ const boundState = getContextBySessionId(sessionId, projectRoot);
72
+ if (boundState) {
73
+ const inventory = buildContextInventory(boundState, projectRoot);
74
+ if (inventory) outputs.push(inventory);
75
+ }
76
+ } catch (e) {
77
+ logWarn("user_prompt_submit", `Inventory failed (non-critical): ${e}`);
78
+ }
79
+ } catch (e) {
80
+ if (e instanceof BlockRequest) {
81
+ emitBlock((e as Error).message);
70
82
  return;
71
83
  }
72
- throw error; // Re-throw unexpected errors
84
+ throw e; // Re-throw unexpected errors
73
85
  }
74
86
  }
75
87
 
@@ -8,9 +8,9 @@
8
8
  */
9
9
 
10
10
  import * as fs from "node:fs";
11
- import * as _path from "node:path";
12
-
11
+ import * as path from "node:path";
13
12
  import { parseIsoTimestamp } from "../base/utils.js";
13
+ import { getContextDir } from "../base/constants.js";
14
14
  import type { ContextState, Task } from "../types.js";
15
15
 
16
16
  const MAX_PLAN_INLINE_CHARS = 30_000;
@@ -42,10 +42,10 @@ export function getModeDisplay(mode: string): string {
42
42
  * Format ISO timestamp as '2 hours ago', 'yesterday', etc.
43
43
  * See SPEC.md §11.3
44
44
  */
45
- export function formatRelativeTime(isoTimestamp: null | string): string {
45
+ export function formatRelativeTime(isoTimestamp: string | null): string {
46
46
  if (!isoTimestamp) return "unknown";
47
47
 
48
- const dt = parseIsoTimestamp(isoTimestamp);
48
+ let dt = parseIsoTimestamp(isoTimestamp);
49
49
  if (!dt) return isoTimestamp.slice(0, 16);
50
50
 
51
51
  const now = new Date();
@@ -62,10 +62,8 @@ export function formatRelativeTime(isoTimestamp: null | string): string {
62
62
  if (diffMin === 0) return "just now";
63
63
  return diffMin === 1 ? "1 minute ago" : `${diffMin} minutes ago`;
64
64
  }
65
-
66
65
  return diffHours === 1 ? "1 hour ago" : `${diffHours} hours ago`;
67
66
  }
68
-
69
67
  if (diffDays === 1) return "yesterday";
70
68
  if (diffDays < 7) return `${diffDays} days ago`;
71
69
 
@@ -80,23 +78,21 @@ export function formatRelativeTime(isoTimestamp: null | string): string {
80
78
  // Internal helpers
81
79
  // ---------------------------------------------------------------------------
82
80
 
83
- function taskAttr(task: Record<string, any> | Task, key: string, defaultVal = ""): string {
81
+ function taskAttr(task: Task | Record<string, any>, key: string, defaultVal = ""): string {
84
82
  if (typeof task === "object" && task !== null) {
85
83
  return (task as any)[key] ?? defaultVal;
86
84
  }
87
-
88
85
  return defaultVal;
89
86
  }
90
87
 
91
- function readPlanContent(planPath: string): [null | string, boolean, number] {
88
+ function readPlanContent(planPath: string): [string | null, boolean, number] {
92
89
  try {
93
90
  if (!fs.existsSync(planPath)) return [null, false, 0];
94
- const content = fs.readFileSync(planPath, "utf8");
91
+ const content = fs.readFileSync(planPath, "utf-8");
95
92
  const total = content.length;
96
93
  if (total > MAX_PLAN_INLINE_CHARS) {
97
94
  return [content.slice(0, MAX_PLAN_INLINE_CHARS), true, total];
98
95
  }
99
-
100
96
  return [content, false, total];
101
97
  } catch {
102
98
  return [null, false, 0];
@@ -105,7 +101,7 @@ function readPlanContent(planPath: string): [null | string, boolean, number] {
105
101
 
106
102
  function modeLabel(ctx: ContextState): string {
107
103
  const d = getModeDisplay(ctx.mode ?? "idle");
108
- return d ? d.replaceAll(/^\[|\]$/g, "") : "Active";
104
+ return d ? d.replace(/^\[|\]$/g, "") : "Active";
109
105
  }
110
106
 
111
107
  /**
@@ -124,7 +120,7 @@ export function buildRestoreSections(
124
120
  const savedAt = lastSession.saved_at ?? "";
125
121
  if (savedAt) {
126
122
  const reason = lastSession.save_reason ?? "";
127
- const reasonDisplay = reason ? reason.replaceAll('_', " ") : "unknown";
123
+ const reasonDisplay = reason ? reason.replace(/_/g, " ") : "unknown";
128
124
  sections.push(`**Last session ended:** ${formatRelativeTime(savedAt)} (${reasonDisplay})`);
129
125
  }
130
126
  }
@@ -143,7 +139,6 @@ export function buildRestoreSections(
143
139
  buckets[s]!.push(taskAttr(t, "subject"));
144
140
  }
145
141
  }
146
-
147
142
  if (Object.values(buckets).some(b => b.length > 0)) {
148
143
  sections.push("", `### Previous Work (${tasks.length} tasks)`, "");
149
144
  const marks: Record<string, string> = {
@@ -201,7 +196,8 @@ function resumeBlock(ctx: ContextState, projectRoot: string | undefined, modeTex
201
196
  ];
202
197
  const restore = buildRestoreSections(ctx, projectRoot, true);
203
198
  if (restore) lines.push(restore);
204
- lines.push("", "---", "", "**Instructions:**", ...instructions);
199
+ lines.push("", "---", "", "**Instructions:**");
200
+ lines.push(...instructions);
205
201
  return lines.join("\n");
206
202
  }
207
203
 
@@ -223,12 +219,12 @@ export function formatHandoffContinuation(ctx: ContextState, projectRoot?: strin
223
219
 
224
220
  try {
225
221
  if (handoffPath && fs.existsSync(handoffPath)) {
226
- lines.push("### Previous Session Handoff", "", fs.readFileSync(handoffPath, "utf8"), "");
222
+ lines.push("### Previous Session Handoff", "", fs.readFileSync(handoffPath, "utf-8"), "");
227
223
  } else {
228
224
  lines.push(`*Handoff document not found at \`${handoffPath}\`*`, "");
229
225
  }
230
- } catch (error: any) {
231
- lines.push(`*Handoff document at \`${handoffPath}\` could not be read: ${error}*`, "");
226
+ } catch (e: any) {
227
+ lines.push(`*Handoff document at \`${handoffPath}\` could not be read: ${e}*`, "");
232
228
  }
233
229
 
234
230
  const restore = buildRestoreSections(ctx, projectRoot, true);
@@ -271,21 +267,20 @@ export function formatContextList(contexts: ContextState[]): string {
271
267
  if (contexts.length === 0) return "No active contexts found.";
272
268
 
273
269
  const lines = ["## Active Contexts\n"];
274
- for (const [i, context_] of contexts.entries()) {
275
- const ctx = context_!;
270
+ for (let i = 0; i < contexts.length; i++) {
271
+ const ctx = contexts[i]!;
276
272
  const timeStr = formatRelativeTime(ctx.last_active);
277
273
  const md = getModeDisplay(ctx.mode ?? "idle");
278
274
  const si = md ? ` ${md}` : "";
279
- lines.push(`**${i + 1}. ${ctx.id}**${si}`, ` ${ctx.summary}`);
275
+ lines.push(`**${i + 1}. ${ctx.id}**${si}`);
276
+ lines.push(` ${ctx.summary}`);
280
277
  if (ctx.method) {
281
278
  lines.push(` Method: ${ctx.method} | Last active: ${timeStr}`);
282
279
  } else {
283
280
  lines.push(` Last active: ${timeStr}`);
284
281
  }
285
-
286
282
  lines.push("");
287
283
  }
288
-
289
284
  return lines.join("\n");
290
285
  }
291
286
 
@@ -355,11 +350,11 @@ export function formatContextPickerStderr(contexts: ContextState[]): string {
355
350
  ];
356
351
 
357
352
  let selectableCount = 0;
358
- for (const [i, context_] of contexts.entries()) {
359
- const ctx = context_!;
353
+ for (let i = 0; i < contexts.length; i++) {
354
+ const ctx = contexts[i]!;
360
355
  const timeStr = formatRelativeTime(ctx.last_active);
361
356
  const mode = ctx.mode ?? "idle";
362
- const isSelectable = mode === "active" || Boolean(ctx.handoff_path);
357
+ const isSelectable = mode === "active" || !!ctx.handoff_path;
363
358
  if (isSelectable) selectableCount++;
364
359
 
365
360
  let status = "";
@@ -372,7 +367,10 @@ export function formatContextPickerStderr(contexts: ContextState[]): string {
372
367
  const summary = ctx.summary.length > 48 ? ctx.summary.slice(0, 45) + "..." : ctx.summary;
373
368
  const selTag = isSelectable ? " [selectable]" : " [end only]";
374
369
 
375
- lines.push(`| ^${i + 1} ${ctx.id}${status}${selTag}`, `| ${summary}`, `| [${timeStr}]`, "|");
370
+ lines.push(`| ^${i + 1} ${ctx.id}${status}${selTag}`);
371
+ lines.push(`| ${summary}`);
372
+ lines.push(`| [${timeStr}]`);
373
+ lines.push("|");
376
374
  }
377
375
 
378
376
  lines.push(
@@ -397,7 +395,6 @@ export function formatContextPickerStderr(contexts: ContextState[]): string {
397
395
  "+----------------------------------------------------------------+",
398
396
  );
399
397
  }
400
-
401
398
  lines.push("");
402
399
  return lines.join("\n");
403
400
  }
@@ -417,7 +414,6 @@ export function formatCommandFeedback(
417
414
  const s = ctx.summary.length > 50 ? ctx.summary.slice(0, 50) + "..." : ctx.summary;
418
415
  lines.push(`- **${ctx.id}**: ${s}`);
419
416
  }
420
-
421
417
  lines.push("");
422
418
  }
423
419
 
@@ -433,6 +429,132 @@ export function formatCommandFeedback(
433
429
  "Tasks created with TaskCreate will be persisted to this context.",
434
430
  );
435
431
  }
432
+ return lines.join("\n");
433
+ }
434
+
435
+ // ---------------------------------------------------------------------------
436
+ // Context Inventory
437
+ // ---------------------------------------------------------------------------
438
+
439
+ /** Collector function: scans one aspect of the context folder, returns markdown or null. */
440
+ type InventoryCollector = (
441
+ contextId: string,
442
+ contextDir: string,
443
+ state: ContextState,
444
+ ) => string | null;
445
+
446
+ /** Descriptions for known context subfolders. */
447
+ const KNOWN_FOLDERS: Record<string, string> = {
448
+ "plans": "Archived implementation plans from plan mode",
449
+ "session-transcripts": "JSONL records of previous agent sessions — read these to understand prior work",
450
+ "handoffs": "Structured briefing documents for session continuity",
451
+ "reviews": "Plan review artifacts (reviewer verdicts, corroboration reports)",
452
+ };
453
+
454
+ function collectFolderPath(contextId: string, contextDir: string, state: ContextState): string | null {
455
+ if (!fs.existsSync(contextDir)) return null;
456
+ return `**Context folder:** \`${contextDir}\`\n**State file:** \`${path.join(contextDir, "state.json")}\` — contains session history, task records, plan/handoff metadata`;
457
+ }
436
458
 
459
+ function collectStatePointers(contextId: string, contextDir: string, state: ContextState): string | null {
460
+ const pointers: string[] = [];
461
+ if (state.plan_path) {
462
+ const exists = fs.existsSync(state.plan_path);
463
+ pointers.push(`- **Active plan:** \`${state.plan_path}\`${exists ? "" : " (not found)"}`);
464
+ }
465
+ if (state.handoff_path) {
466
+ const exists = fs.existsSync(state.handoff_path);
467
+ pointers.push(`- **Active handoff:** \`${state.handoff_path}\`${exists ? "" : " (not found)"}`);
468
+ }
469
+ if (pointers.length === 0) return null;
470
+ return "**Key artifacts:**\n" + pointers.join("\n");
471
+ }
472
+
473
+ function countFilesRecursive(dirPath: string): number {
474
+ let count = 0;
475
+ try {
476
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
477
+ for (const entry of entries) {
478
+ if (entry.isFile()) {
479
+ count++;
480
+ } else if (entry.isDirectory()) {
481
+ count += countFilesRecursive(path.join(dirPath, entry.name));
482
+ }
483
+ }
484
+ } catch { /* permission errors, etc. */ }
485
+ return count;
486
+ }
487
+
488
+ function collectFolderInventory(contextId: string, contextDir: string, state: ContextState): string | null {
489
+ if (!fs.existsSync(contextDir)) return null;
490
+ let entries: fs.Dirent[];
491
+ try {
492
+ entries = fs.readdirSync(contextDir, { withFileTypes: true });
493
+ } catch { return null; }
494
+
495
+ const dirs = entries.filter(e => e.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
496
+ if (dirs.length === 0) return null;
497
+
498
+ const lines: string[] = ["**Available folders:**"];
499
+ for (const dir of dirs) {
500
+ const dirPath = path.join(contextDir, dir.name);
501
+ const desc = KNOWN_FOLDERS[dir.name] ?? "Project-specific artifacts";
502
+ const fileCount = countFilesRecursive(dirPath);
503
+ lines.push(`- \`${dir.name}/\` — ${desc} (${fileCount} file${fileCount !== 1 ? "s" : ""})`);
504
+ }
437
505
  return lines.join("\n");
438
506
  }
507
+
508
+ function collectSessionStats(contextId: string, contextDir: string, state: ContextState): string | null {
509
+ const sessionCount = (state.session_ids ?? []).length;
510
+ if (sessionCount === 0) return null;
511
+
512
+ const transcriptsDir = path.join(contextDir, "session-transcripts");
513
+ let transcriptCount = 0;
514
+ let timeRange = "";
515
+
516
+ if (fs.existsSync(transcriptsDir)) {
517
+ try {
518
+ const files = fs.readdirSync(transcriptsDir).filter(f => f.endsWith(".jsonl")).sort();
519
+ transcriptCount = files.length;
520
+ if (files.length > 1) {
521
+ const oldest = files[0]!.slice(0, 10);
522
+ const newest = files[files.length - 1]!.slice(0, 10);
523
+ if (oldest !== newest) timeRange = ` (${oldest} to ${newest})`;
524
+ }
525
+ } catch { /* ignore */ }
526
+ }
527
+
528
+ let line = `**Sessions:** ${sessionCount} total`;
529
+ if (transcriptCount > 0) {
530
+ line += `, ${transcriptCount} transcript${transcriptCount !== 1 ? "s" : ""} archived${timeRange}`;
531
+ }
532
+ return line;
533
+ }
534
+
535
+ /** Ordered list of inventory collectors. Append new collectors here. */
536
+ const INVENTORY_COLLECTORS: InventoryCollector[] = [
537
+ collectFolderPath,
538
+ collectStatePointers,
539
+ collectFolderInventory,
540
+ collectSessionStats,
541
+ ];
542
+
543
+ /**
544
+ * Build a markdown inventory of resources available in the context folder.
545
+ * Returns null if the context folder doesn't exist yet (brand new context).
546
+ */
547
+ export function buildContextInventory(
548
+ state: ContextState,
549
+ projectRoot: string,
550
+ ): string | null {
551
+ const contextDir = getContextDir(state.id, projectRoot);
552
+ if (!fs.existsSync(contextDir)) return null;
553
+
554
+ const sections = INVENTORY_COLLECTORS
555
+ .map(c => c(state.id, contextDir, state))
556
+ .filter((s): s is string => s !== null);
557
+
558
+ if (sections.length === 0) return null;
559
+ return "### Context Resources\n\n" + sections.join("\n\n");
560
+ }
@@ -315,6 +315,31 @@ function main(): void {
315
315
  out.push("---");
316
316
  out.push("");
317
317
  out.push("**Create ISC tasks** from the pending items and remaining plan items above using TaskCreate. Each task should be ~8 words, state a desired end-state (not an action), and be binary testable.");
318
+ out.push("");
319
+
320
+ // Appendix: Full Plan
321
+ let fullPlanContent: string | null = sections.plan ?? null;
322
+ if (!fullPlanContent && resolvedContextId) {
323
+ const planRef = getHandoffPlanReference(handoffFolder, resolvedContextId, projectRoot);
324
+ if (planRef) {
325
+ try {
326
+ fullPlanContent = fs.readFileSync(planRef, "utf-8");
327
+ } catch {
328
+ // ignore — no plan to append
329
+ }
330
+ }
331
+ }
332
+ if (fullPlanContent) {
333
+ const planBody = stripTitle(fullPlanContent);
334
+ if (planBody && planBody !== "(No content for this section)") {
335
+ out.push("---");
336
+ out.push("");
337
+ out.push("### Appendix: Full Plan");
338
+ out.push("");
339
+ out.push(planBody);
340
+ out.push("");
341
+ }
342
+ }
318
343
 
319
344
  console.log(out.join("\n"));
320
345
  }
@@ -113,7 +113,7 @@ Each family covers the same topic area but through different analytical lenses.
113
113
  ## File Structure
114
114
 
115
115
  Each agent file has:
116
- - **Frontmatter (YAML):** name, model, focus, categories, enabled
116
+ - **Frontmatter (YAML):** name, model, focus, categories
117
117
  - **Body (Markdown):** Full persona content → becomes `system_prompt` for `--system-prompt` flag
118
118
 
119
119
  ## --setting-sources "" Requirement
@@ -140,10 +140,4 @@ Each agent file has:
140
140
 
141
141
  **Constraint:** The agent markdown files MUST contain clear instructions to "call StructuredOutput IMMEDIATELY" and "do NOT use any other tools". Without these instructions, the model will try to use its turns for file operations instead of outputting the review.
142
142
 
143
- ## enabled: false Convention
144
143
 
145
- **Decision:** Set `enabled: false` in frontmatter for all plan review agents
146
-
147
- **Rationale:** The `enabled` field controls Claude Code's auto-suggestion feature (showing agents in command palette). For plan review agents, we don't want them appearing as general-purpose agents - they're invoked programmatically by the hook. Setting `enabled: false` hides them from auto-suggestion while still allowing the hook to use them.
148
-
149
- **Constraint:** Don't set `enabled: true` unless you want the agent to appear in Claude Code's agent picker for general use.
@@ -1,63 +1,62 @@
1
- ---
2
- name: arch-evolution
3
- description: Evolutionary architecture analyst who evaluates how well planned architecture accommodates future change. Performs change-amplification analysis to find designs that break or require large changes from small requirement shifts.
4
- model: sonnet
5
- focus: evolutionary architecture and change amplification
6
- enabled: false
7
- categories:
8
- - code
9
- - infrastructure
10
- - design
11
- ---
12
-
13
- # Architecture Evolution - Plan Review Agent
14
-
15
- You evaluate how well planned architecture handles future change. Your question: "When requirements change — and they will — does this architecture bend or break?"
16
-
17
- ## Your Core Principle
18
-
19
- Evolutionary architecture (Ford, Parsons & Kua 2017) designs for guided, incremental change across multiple dimensions. The key metric is change amplification — when a small requirement change forces a large architectural change, the design is brittle. Good architecture minimizes change amplification by placing extension points where change is most likely and isolating volatile decisions behind stable interfaces.
20
-
21
- ## Your Expertise
22
-
23
- - **Change amplification analysis**: Would a small requirement change force large structural changes?
24
- - **Extension point evaluation**: Are extension points placed where change is most likely to occur?
25
- - **Volatility isolation**: Are the most likely-to-change decisions isolated behind stable interfaces?
26
- - **Future adaptability**: Does this architecture support the probable evolution paths?
27
- - **Fitness function identification**: What measurable properties should guide this architecture's evolution?
28
-
29
- ## Review Approach
30
-
31
- Evaluate the plan's evolutionary fitness:
32
-
33
- 1. **Identify likely change vectors**: Based on the plan's domain, what changes are most probable? (New features, scaling needs, integration requirements, technology updates)
34
- 2. **Assess change amplification**: For each likely change, how much of the planned architecture would need to change?
35
- 3. **Evaluate extension points**: Does the plan provide extension points aligned with likely change vectors?
36
- 4. **Check volatility isolation**: Are volatile decisions (technology choices, external APIs, business rules) behind stable interfaces?
37
- 5. **Consider fitness functions**: What properties should be measured to ensure the architecture evolves correctly?
38
-
39
- ## Key Distinction
40
-
41
- | Agent | Asks |
42
- |-------|------|
43
- | arch-structure | "Are boundaries at natural seams?" |
44
- | arch-patterns | "Is the chosen pattern appropriate?" |
45
- | **arch-evolution** | **"When requirements change, does this bend or break?"** |
46
-
47
- ## CRITICAL: Single-Turn Review
48
-
49
- When reviewing a plan:
50
- 1. Analyze the plan content provided directly (do not use Read, Glob, Grep, or any file tools)
51
- 2. Call StructuredOutput immediately with your assessment
52
- 3. Complete your entire review in one response
53
-
54
- Avoid querying external systems, reading codebase files, requesting additional information, or asking follow-up questions.
55
-
56
- ## Required Output
57
-
58
- Call StructuredOutput with exactly these fields:
59
- - **verdict**: "pass" (architecture supports evolution), "warn" (some rigidity concerns), or "fail" (brittle architecture that resists change)
60
- - **summary**: 2-3 sentences explaining evolutionary fitness assessment (minimum 20 characters)
61
- - **issues**: Array of evolution concerns, each with: severity (high/medium/low), category (e.g., "change-amplification", "missing-extension-point", "volatility-exposure", "brittle-coupling", "fitness-gap"), issue description, suggested_fix (add extension point, isolate volatile decision, reduce change amplification)
62
- - **missing_sections**: Evolution considerations the plan should address (likely change vectors, extension points, volatility isolation)
63
- - **questions**: Evolution aspects that need investigation
1
+ ---
2
+ name: arch-evolution
3
+ description: Evolutionary architecture analyst who evaluates how well planned architecture accommodates future change. Performs change-amplification analysis to find designs that break or require large changes from small requirement shifts.
4
+ model: sonnet
5
+ focus: evolutionary architecture and change amplification
6
+ categories:
7
+ - code
8
+ - infrastructure
9
+ - design
10
+ ---
11
+
12
+ # Architecture Evolution - Plan Review Agent
13
+
14
+ You evaluate how well planned architecture handles future change. Your question: "When requirements change — and they will — does this architecture bend or break?"
15
+
16
+ ## Your Core Principle
17
+
18
+ Evolutionary architecture (Ford, Parsons & Kua 2017) designs for guided, incremental change across multiple dimensions. The key metric is change amplification — when a small requirement change forces a large architectural change, the design is brittle. Good architecture minimizes change amplification by placing extension points where change is most likely and isolating volatile decisions behind stable interfaces.
19
+
20
+ ## Your Expertise
21
+
22
+ - **Change amplification analysis**: Would a small requirement change force large structural changes?
23
+ - **Extension point evaluation**: Are extension points placed where change is most likely to occur?
24
+ - **Volatility isolation**: Are the most likely-to-change decisions isolated behind stable interfaces?
25
+ - **Future adaptability**: Does this architecture support the probable evolution paths?
26
+ - **Fitness function identification**: What measurable properties should guide this architecture's evolution?
27
+
28
+ ## Review Approach
29
+
30
+ Evaluate the plan's evolutionary fitness:
31
+
32
+ 1. **Identify likely change vectors**: Based on the plan's domain, what changes are most probable? (New features, scaling needs, integration requirements, technology updates)
33
+ 2. **Assess change amplification**: For each likely change, how much of the planned architecture would need to change?
34
+ 3. **Evaluate extension points**: Does the plan provide extension points aligned with likely change vectors?
35
+ 4. **Check volatility isolation**: Are volatile decisions (technology choices, external APIs, business rules) behind stable interfaces?
36
+ 5. **Consider fitness functions**: What properties should be measured to ensure the architecture evolves correctly?
37
+
38
+ ## Key Distinction
39
+
40
+ | Agent | Asks |
41
+ |-------|------|
42
+ | arch-structure | "Are boundaries at natural seams?" |
43
+ | arch-patterns | "Is the chosen pattern appropriate?" |
44
+ | **arch-evolution** | **"When requirements change, does this bend or break?"** |
45
+
46
+ ## CRITICAL: Single-Turn Review
47
+
48
+ When reviewing a plan:
49
+ 1. Analyze the plan content provided directly (do not use Read, Glob, Grep, or any file tools)
50
+ 2. Call StructuredOutput immediately with your assessment
51
+ 3. Complete your entire review in one response
52
+
53
+ Avoid querying external systems, reading codebase files, requesting additional information, or asking follow-up questions.
54
+
55
+ ## Required Output
56
+
57
+ Call StructuredOutput with exactly these fields:
58
+ - **verdict**: "pass" (architecture supports evolution), "warn" (some rigidity concerns), or "fail" (brittle architecture that resists change)
59
+ - **summary**: 2-3 sentences explaining evolutionary fitness assessment (minimum 20 characters)
60
+ - **issues**: Array of evolution concerns, each with: severity (high/medium/low), category (e.g., "change-amplification", "missing-extension-point", "volatility-exposure", "brittle-coupling", "fitness-gap"), issue description, suggested_fix (add extension point, isolate volatile decision, reduce change amplification)
61
+ - **missing_sections**: Evolution considerations the plan should address (likely change vectors, extension points, volatility isolation)
62
+ - **questions**: Evolution aspects that need investigation