aiwcli 0.10.3 → 0.11.0
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/bin/run.js +1 -1
- package/dist/commands/clear.js +28 -131
- package/dist/commands/init/index.js +3 -3
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
- package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
- package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
- package/dist/templates/_shared/lib-ts/base/logger.ts +31 -15
- package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +139 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
- package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +61 -37
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
- package/dist/templates/_shared/lib-ts/types.ts +68 -55
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +21 -21
- package/dist/templates/_shared/scripts/status_line.ts +733 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -9
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -177
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
- package/dist/templates/_shared/lib/base/inference.py +0 -307
- package/dist/templates/_shared/lib/base/logger.py +0 -305
- package/dist/templates/_shared/lib/base/stop_words.py +0 -221
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -263
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/formatters.py +0 -146
- package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -716
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI plan reviewer.
|
|
3
|
+
* Runs Gemini CLI in YOLO mode (auto-approve).
|
|
4
|
+
* See cc-native-plan-review-spec.md §4.12
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { logDebug, logError, logInfo as _logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.js";
|
|
8
|
+
import { execFileAsync, findExecutable } from "../../../_shared/lib-ts/base/subprocess-utils.js";
|
|
9
|
+
import { coerceToReview, parseJsonMaybe } from "../json-parser.js";
|
|
10
|
+
import type { ReviewerResult, ReviewOptions } from "../types.js";
|
|
11
|
+
import { makeResult } from "./types.js";
|
|
12
|
+
import type { Reviewer } from "./types.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gemini reviewer — runs gemini -y -p <instruction>.
|
|
16
|
+
*/
|
|
17
|
+
export class GeminiReviewer implements Reviewer {
|
|
18
|
+
private settings: Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
constructor(settings: Record<string, unknown>) {
|
|
21
|
+
this.settings = settings;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async review(
|
|
25
|
+
plan: string,
|
|
26
|
+
schema: Record<string, unknown>,
|
|
27
|
+
_options: ReviewOptions,
|
|
28
|
+
): Promise<ReviewerResult> {
|
|
29
|
+
return runGeminiReview(plan, schema, this.settings);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Run Gemini CLI to review the plan.
|
|
35
|
+
* Never throws — returns error ReviewerResult on failure.
|
|
36
|
+
*/
|
|
37
|
+
export async function runGeminiReview(
|
|
38
|
+
plan: string,
|
|
39
|
+
schema: Record<string, unknown>,
|
|
40
|
+
settings: Record<string, unknown>,
|
|
41
|
+
): Promise<ReviewerResult> {
|
|
42
|
+
const geminiSettings =
|
|
43
|
+
((settings.reviewers as Record<string, unknown> | undefined)?.gemini as
|
|
44
|
+
| Record<string, unknown>
|
|
45
|
+
| undefined) ?? {};
|
|
46
|
+
const timeout = (geminiSettings.timeout as number) ?? 120;
|
|
47
|
+
const model = (geminiSettings.model as string) ?? "";
|
|
48
|
+
|
|
49
|
+
const geminiPath = findExecutable("gemini");
|
|
50
|
+
if (!geminiPath) {
|
|
51
|
+
logWarn("gemini", "CLI not found on PATH");
|
|
52
|
+
return makeResult("gemini", false, "skip", {}, "", "gemini CLI not found on PATH");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
logDebug("gemini", `Found CLI at: ${geminiPath}`);
|
|
56
|
+
|
|
57
|
+
const instruction = `
|
|
58
|
+
|
|
59
|
+
Review the PLAN above as a senior staff software engineer. Focus on:
|
|
60
|
+
- missing steps, unclear assumptions, edge cases
|
|
61
|
+
- security/privacy concerns
|
|
62
|
+
- testing/rollout/rollback completeness
|
|
63
|
+
- operational concerns (observability, failure modes)
|
|
64
|
+
|
|
65
|
+
Return ONLY a JSON object that matches this JSON Schema (no markdown, no code fences):
|
|
66
|
+
${JSON.stringify(schema)}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const cmdArgs = ["-y", "-p", instruction];
|
|
70
|
+
|
|
71
|
+
if (model) {
|
|
72
|
+
cmdArgs.push("--model", model);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
logDebug("gemini", "Running command: gemini -y -p <instruction>");
|
|
76
|
+
|
|
77
|
+
const result = await execFileAsync(geminiPath, cmdArgs, {
|
|
78
|
+
input: plan,
|
|
79
|
+
timeout: timeout * 1000,
|
|
80
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (result.killed || result.signal === "SIGTERM") {
|
|
84
|
+
logWarn("gemini", `TIMEOUT after ${timeout}s`);
|
|
85
|
+
return makeResult("gemini", false, "error", {}, "", `gemini timed out after ${timeout}s`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!result.stdout && !result.stderr && result.exitCode !== 0) {
|
|
89
|
+
logError("gemini", `Process exited with code ${result.exitCode}`);
|
|
90
|
+
return makeResult("gemini", false, "error", {}, "", `gemini failed to run (exit ${result.exitCode})`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
logDebug("gemini", `Exit code: ${result.exitCode}`);
|
|
94
|
+
|
|
95
|
+
const raw = result.stdout.trim();
|
|
96
|
+
const err = result.stderr.trim();
|
|
97
|
+
|
|
98
|
+
const obj = parseJsonMaybe(raw);
|
|
99
|
+
const [ok, verdict, norm] = coerceToReview(
|
|
100
|
+
obj,
|
|
101
|
+
"Retry or check CLI auth/config.",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return makeResult("gemini", ok, verdict, norm, raw, err);
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewers package — re-exports all reviewer implementations.
|
|
3
|
+
* See cc-native-plan-review-spec.md §4.9
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { AgentReviewer, runAgentReview } from "./agent.js";
|
|
7
|
+
export { CodexReviewer, runCodexReview } from "./codex.js";
|
|
8
|
+
export { GeminiReviewer, runGeminiReview } from "./gemini.js";
|
|
9
|
+
export type { Reviewer, ReviewerResult, ReviewOptions } from "./types.js";
|
|
10
|
+
export { makeResult } from "./types.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reviewer interface and options for plan review implementations.
|
|
3
|
+
* See cc-native-plan-review-spec.md §4.9
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ReviewData, ReviewerResult, Verdict } from "../types.js";
|
|
7
|
+
|
|
8
|
+
// Re-export for convenience
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/** Create a standard ReviewerResult. Shared by all reviewer implementations. */
|
|
12
|
+
export function makeResult(
|
|
13
|
+
name: string,
|
|
14
|
+
ok: boolean,
|
|
15
|
+
verdict: Verdict,
|
|
16
|
+
data: Record<string, unknown> | ReviewData,
|
|
17
|
+
raw: string,
|
|
18
|
+
err: string,
|
|
19
|
+
): ReviewerResult {
|
|
20
|
+
return { name, ok, verdict, data: data as Record<string, unknown>, raw, err };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {type Reviewer, type ReviewerResult, type ReviewOptions} from "../types.js";
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iteration state management for plan review cycles.
|
|
3
|
+
* State files are stored adjacent to plan files (e.g., foo.md → foo.state.json).
|
|
4
|
+
* See cc-native-plan-review-spec.md §4.7
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
10
|
+
import { validatePlanPath } from "./constants.js";
|
|
11
|
+
import type { IterationEntry, IterationState } from "./types.js";
|
|
12
|
+
import { atomicWrite } from "../../_shared/lib-ts/base/atomic-write.js";
|
|
13
|
+
import { logError, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
|
|
14
|
+
import { nowIso } from "../../_shared/lib-ts/base/utils.js";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Constants
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
const STATE_SCHEMA_VERSION = "1.0.0";
|
|
21
|
+
|
|
22
|
+
const DEFAULT_REVIEW_ITERATIONS: Record<string, number> = {
|
|
23
|
+
simple: 1,
|
|
24
|
+
medium: 1,
|
|
25
|
+
high: 2,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// State File Management
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Derive state file path from plan file path with security validation.
|
|
34
|
+
* Example: ~/.claude/plans/foo.md → ~/.claude/plans/foo.state.json
|
|
35
|
+
*
|
|
36
|
+
* @throws Error if planPath is invalid or insecure
|
|
37
|
+
*/
|
|
38
|
+
export function getStateFilePath(planPath: string): string {
|
|
39
|
+
const validated = validatePlanPath(planPath);
|
|
40
|
+
const parsed = path.parse(validated);
|
|
41
|
+
return path.join(parsed.dir, `${parsed.name}.state.json`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load state file with schema validation and migration.
|
|
46
|
+
*/
|
|
47
|
+
export function loadState(planPath: string): null | Record<string, unknown> {
|
|
48
|
+
try {
|
|
49
|
+
const stateFile = getStateFilePath(planPath);
|
|
50
|
+
|
|
51
|
+
if (!fs.existsSync(stateFile)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const state = JSON.parse(
|
|
56
|
+
fs.readFileSync(stateFile, "utf8"),
|
|
57
|
+
) as Record<string, unknown>;
|
|
58
|
+
|
|
59
|
+
// Handle schema version (backward compatible)
|
|
60
|
+
const schemaVersion = state.schema_version as string | undefined;
|
|
61
|
+
|
|
62
|
+
if (schemaVersion === undefined) {
|
|
63
|
+
state.schema_version = STATE_SCHEMA_VERSION;
|
|
64
|
+
logInfo(
|
|
65
|
+
"state",
|
|
66
|
+
`Migrated state file to schema v${STATE_SCHEMA_VERSION}`,
|
|
67
|
+
);
|
|
68
|
+
} else if (schemaVersion !== STATE_SCHEMA_VERSION) {
|
|
69
|
+
logWarn(
|
|
70
|
+
"state",
|
|
71
|
+
`Schema mismatch (expected ${STATE_SCHEMA_VERSION}, got ${schemaVersion})`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return state;
|
|
76
|
+
} catch (error: unknown) {
|
|
77
|
+
if (error instanceof Error && error.message.includes("Invalid plan path")) {
|
|
78
|
+
logError("state", `SECURITY: Invalid plan path: ${error}`);
|
|
79
|
+
} else {
|
|
80
|
+
logError("state", `Failed to load state: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Save state file with schema version and validation.
|
|
89
|
+
* Returns true on success, false on failure.
|
|
90
|
+
*/
|
|
91
|
+
export function saveStateToPlan(
|
|
92
|
+
planPath: string,
|
|
93
|
+
state: Record<string, unknown>,
|
|
94
|
+
): boolean {
|
|
95
|
+
try {
|
|
96
|
+
const stateFile = getStateFilePath(planPath);
|
|
97
|
+
|
|
98
|
+
const stateWithVersion = {
|
|
99
|
+
schema_version: STATE_SCHEMA_VERSION,
|
|
100
|
+
...state,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const [success, error] = atomicWrite(
|
|
104
|
+
stateFile,
|
|
105
|
+
JSON.stringify(stateWithVersion, null, 2),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!success) {
|
|
109
|
+
logError("state", `Failed to save state: ${error}`);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return true;
|
|
114
|
+
} catch (error: unknown) {
|
|
115
|
+
if (error instanceof Error && error.message.includes("Invalid plan path")) {
|
|
116
|
+
logError("state", `SECURITY: Invalid plan path: ${error}`);
|
|
117
|
+
} else {
|
|
118
|
+
logError("state", String(error));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Delete state file after successful archive.
|
|
127
|
+
* Returns true if deleted or didn't exist, false on error.
|
|
128
|
+
*/
|
|
129
|
+
export function deleteState(planPath: string): boolean {
|
|
130
|
+
try {
|
|
131
|
+
const stateFile = getStateFilePath(planPath);
|
|
132
|
+
if (fs.existsSync(stateFile)) {
|
|
133
|
+
fs.unlinkSync(stateFile);
|
|
134
|
+
logInfo("state", `Deleted state file: ${stateFile}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
} catch (error: unknown) {
|
|
139
|
+
if (error instanceof Error && error.message.includes("Invalid plan path")) {
|
|
140
|
+
logError("state", `SECURITY: Invalid plan path in delete: ${error}`);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
logWarn("state", `Failed to delete state file: ${error}`);
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Iteration State Management
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get or initialize iteration state based on complexity.
|
|
155
|
+
*/
|
|
156
|
+
export function getIterationState(
|
|
157
|
+
state: Record<string, unknown>,
|
|
158
|
+
complexity: string,
|
|
159
|
+
config?: Record<string, unknown>,
|
|
160
|
+
): IterationState {
|
|
161
|
+
if (state.iteration) {
|
|
162
|
+
return state.iteration as IterationState;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Initialize new iteration state
|
|
166
|
+
const reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS };
|
|
167
|
+
if (config) {
|
|
168
|
+
const overrides = config.reviewIterations as
|
|
169
|
+
| Record<string, number>
|
|
170
|
+
| undefined;
|
|
171
|
+
if (overrides) {
|
|
172
|
+
Object.assign(reviewIterations, overrides);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
current: 1,
|
|
178
|
+
max: reviewIterations[complexity] ?? 1,
|
|
179
|
+
complexity,
|
|
180
|
+
history: [],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Record review result in iteration history and update state.
|
|
186
|
+
*/
|
|
187
|
+
export function updateIterationState(
|
|
188
|
+
state: Record<string, unknown>,
|
|
189
|
+
iteration: IterationState,
|
|
190
|
+
planHash: string,
|
|
191
|
+
verdict: string,
|
|
192
|
+
): Record<string, unknown> {
|
|
193
|
+
const entry: IterationEntry = {
|
|
194
|
+
hash: planHash,
|
|
195
|
+
verdict,
|
|
196
|
+
timestamp: nowIso(),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
iteration.history.push(entry);
|
|
200
|
+
state.iteration = iteration;
|
|
201
|
+
return state;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Determine if more review iterations are needed.
|
|
206
|
+
*/
|
|
207
|
+
export function shouldContinueIterating(
|
|
208
|
+
iteration: IterationState,
|
|
209
|
+
verdict: string,
|
|
210
|
+
config?: Record<string, unknown>,
|
|
211
|
+
): boolean {
|
|
212
|
+
const {current} = iteration;
|
|
213
|
+
const maxIter = iteration.max;
|
|
214
|
+
|
|
215
|
+
// At or past max iterations
|
|
216
|
+
if (current >= maxIter) {
|
|
217
|
+
logInfo(
|
|
218
|
+
"state",
|
|
219
|
+
`At max iterations (${current}/${maxIter}), no more iterations`,
|
|
220
|
+
);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check early exit on all pass
|
|
225
|
+
let earlyExit = true;
|
|
226
|
+
if (config) {
|
|
227
|
+
earlyExit = (config.earlyExitOnAllPass as boolean) ?? true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (earlyExit && verdict === "pass") {
|
|
231
|
+
logInfo(
|
|
232
|
+
"state",
|
|
233
|
+
"All reviewers passed and earlyExitOnAllPass=true, exiting early",
|
|
234
|
+
);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
logInfo(
|
|
239
|
+
"state",
|
|
240
|
+
`Continuing to next iteration (${current + 1}/${maxIter}), verdict=${verdict}`,
|
|
241
|
+
);
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"noFallthroughCasesInSwitch": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "../..",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"types": ["bun-types"],
|
|
14
|
+
"skipLibCheck": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["./**/*.ts", "../../_shared/lib-ts/**/*.ts"],
|
|
17
|
+
"exclude": ["../../_shared/lib-ts/__tests__/**"]
|
|
18
|
+
}
|