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.
- package/dist/templates/_shared/hooks-ts/session_start.ts +21 -15
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +20 -8
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +151 -29
- package/dist/templates/_shared/scripts/resume_handoff.ts +25 -0
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -7
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-EVOLUTION.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-PATTERNS.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ARCH-STRUCTURE.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/ASSUMPTION-TRACER.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CLARITY-AUDITOR.md +53 -54
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-GAPS.md +70 -71
- package/dist/templates/cc-native/_cc-native/agents/plan-review/COMPLETENESS-ORDERING.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -73
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -65
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DEVILS-ADVOCATE.md +56 -57
- package/dist/templates/cc-native/_cc-native/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -87
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HANDOFF-READINESS.md +59 -60
- package/dist/templates/cc-native/_cc-native/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -59
- package/dist/templates/cc-native/_cc-native/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-DEPENDENCY.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-FMEA.md +66 -67
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-PREMORTEM.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/RISK-REVERSIBILITY.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SCOPE-BOUNDARY.md +77 -78
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -63
- package/dist/templates/cc-native/_cc-native/agents/plan-review/SKEPTIC.md +68 -69
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -72
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -62
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-COSTS.md +67 -68
- package/dist/templates/cc-native/_cc-native/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -66
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-COVERAGE.md +74 -75
- package/dist/templates/cc-native/_cc-native/agents/plan-review/VERIFY-STRENGTH.md +69 -70
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +19 -2
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +28 -1010
- package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +1 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/format.ts +597 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/index.ts +26 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/tracker.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts/write.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +19 -821
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +36 -13
- package/dist/templates/cc-native/_cc-native/lib-ts/graduation.ts +132 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +1 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/output-builder.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/review-pipeline.ts +489 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +184 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +51 -17
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +40 -2
- package/oclif.manifest.json +1 -1
- 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
|
-
|
|
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 "
|
|
128
|
-
await handleClearRestore(sessionId, projectRoot);
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case "compact": {
|
|
136
|
+
case "compact":
|
|
132
137
|
handleCompactRestore(sessionId, projectRoot);
|
|
133
|
-
break;
|
|
134
|
-
|
|
135
|
-
|
|
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 {
|
|
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 (
|
|
42
|
-
hookLog("warn", "user_prompt_submit", `maybeActivate failed (non-critical): ${
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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
|
|
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:
|
|
45
|
+
export function formatRelativeTime(isoTimestamp: string | null): string {
|
|
46
46
|
if (!isoTimestamp) return "unknown";
|
|
47
47
|
|
|
48
|
-
|
|
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
|
|
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): [
|
|
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, "
|
|
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.
|
|
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.
|
|
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:**"
|
|
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, "
|
|
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 (
|
|
231
|
-
lines.push(`*Handoff document at \`${handoffPath}\` could not be read: ${
|
|
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 (
|
|
275
|
-
const ctx =
|
|
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}
|
|
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 (
|
|
359
|
-
const ctx =
|
|
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" ||
|
|
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}
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
| arch-
|
|
44
|
-
| arch-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- **
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
62
|
-
- **
|
|
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
|