aiwcli 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/clear.d.ts +8 -0
- package/dist/commands/clear.js +86 -0
- package/dist/lib/bmad-installer.d.ts +2 -27
- package/dist/lib/bmad-installer.js +3 -43
- package/dist/lib/claude-settings-types.d.ts +2 -1
- package/dist/lib/env-compat.d.ts +0 -8
- package/dist/lib/env-compat.js +0 -12
- package/dist/lib/git/index.d.ts +0 -1
- package/dist/lib/gitignore-manager.d.ts +0 -2
- package/dist/lib/gitignore-manager.js +1 -1
- package/dist/lib/hooks-merger.d.ts +1 -15
- package/dist/lib/hooks-merger.js +1 -1
- package/dist/lib/index.d.ts +3 -7
- package/dist/lib/index.js +3 -11
- package/dist/lib/output.d.ts +2 -1
- package/dist/lib/settings-hierarchy.d.ts +1 -13
- package/dist/lib/settings-hierarchy.js +1 -1
- package/dist/lib/template-installer.d.ts +5 -9
- package/dist/lib/template-installer.js +3 -13
- package/dist/lib/template-linter.d.ts +3 -10
- package/dist/lib/template-linter.js +2 -2
- package/dist/lib/template-resolver.d.ts +6 -0
- package/dist/lib/template-resolver.js +10 -0
- package/dist/lib/template-settings-reconstructor.d.ts +1 -1
- package/dist/lib/template-settings-reconstructor.js +17 -24
- package/dist/lib/terminal.d.ts +3 -14
- package/dist/lib/terminal.js +0 -4
- package/dist/lib/version.d.ts +2 -11
- package/dist/lib/version.js +3 -3
- package/dist/lib/windsurf-hooks-merger.d.ts +1 -15
- package/dist/lib/windsurf-hooks-merger.js +1 -1
- package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
- package/dist/templates/_shared/hooks-ts/session_end.ts +75 -4
- package/dist/templates/_shared/hooks-ts/session_start.ts +11 -13
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +6 -8
- package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +176 -29
- package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -165
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
- package/dist/templates/_shared/lib-ts/package.json +1 -2
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
- package/dist/templates/_shared/lib-ts/types.ts +17 -2
- package/dist/templates/_shared/scripts/resume_handoff.ts +4 -4
- package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
- package/dist/templates/_shared/scripts/status_line.ts +104 -71
- package/dist/templates/_shared/workflows/handoff.md +1 -1
- package/dist/templates/cc-native/.claude/settings.json +182 -175
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
- package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +142 -111
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -0
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +52 -0
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +19 -19
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -5
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +114 -83
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +107 -10
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +6 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +0 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -219
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +102 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +26 -227
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +4 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +185 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +2 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +17 -16
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +13 -108
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +2 -14
- package/oclif.manifest.json +1 -1
- package/package.json +1 -2
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -107
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-STRENGTH.md → plan-review/VERIFY-STRENGTH.md} +0 -0
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
* - reviewer-output/{reviewer}.json (individual reviewer results)
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import * as crypto from "node:crypto";
|
|
22
23
|
import * as fs from "node:fs";
|
|
23
|
-
import * as path from "node:path";
|
|
24
24
|
import * as os from "node:os";
|
|
25
|
-
import * as
|
|
25
|
+
import * as path from "node:path";
|
|
26
26
|
|
|
27
|
+
import { getProjectRoot, getAiwcliDir, getContextReviewsDir, getContextDir, getReviewFolderPath } from "../../_shared/lib-ts/base/constants.js";
|
|
27
28
|
import {
|
|
28
29
|
loadHookInput,
|
|
29
30
|
runHookAsync,
|
|
@@ -36,11 +37,40 @@ import {
|
|
|
36
37
|
emitContextAndBlock,
|
|
37
38
|
} from "../../_shared/lib-ts/base/hook-utils.js";
|
|
38
39
|
import { isInternalCall, findExecutable } from "../../_shared/lib-ts/base/subprocess-utils.js";
|
|
39
|
-
import { getProjectRoot, getAiwcliDir, getContextReviewsDir, getContextDir, getReviewFolderPath } from "../../_shared/lib-ts/base/constants.js";
|
|
40
40
|
import { eprint } from "../../_shared/lib-ts/base/utils.js";
|
|
41
41
|
import { getContextBySessionId, getAllContexts } from "../../_shared/lib-ts/context/context-store.js";
|
|
42
42
|
import { findPlanPathInTranscript } from "../../_shared/lib-ts/context/plan-manager.js";
|
|
43
|
-
|
|
43
|
+
import type { ContextState } from "../../_shared/lib-ts/types.js";
|
|
44
|
+
import { aggregateAgents } from "../lib-ts/aggregate-agents.js";
|
|
45
|
+
import {
|
|
46
|
+
writeCombinedArtifacts,
|
|
47
|
+
buildInlineReviewSummary,
|
|
48
|
+
extractTopIssuesText,
|
|
49
|
+
buildHighIssuesDocument,
|
|
50
|
+
buildCorroborationReport,
|
|
51
|
+
writeReviewTracker,
|
|
52
|
+
} from "../lib-ts/artifacts.js";
|
|
53
|
+
import type { ReviewTrackerEntry } from "../lib-ts/artifacts.js";
|
|
54
|
+
import {
|
|
55
|
+
isPlanAlreadyReviewed,
|
|
56
|
+
wasPlanPreviouslyDenied,
|
|
57
|
+
getLastPlanReview,
|
|
58
|
+
markPlanReviewed,
|
|
59
|
+
wasPlanQuestionsAgentAsked,
|
|
60
|
+
markQuestionsAsked,
|
|
61
|
+
} from "../lib-ts/cc-native-state.js";
|
|
62
|
+
import { loadConfig, getDisplaySettings } from "../lib-ts/config.js";
|
|
63
|
+
import { computeCorroboratedDecision } from "../lib-ts/corroboration.js";
|
|
64
|
+
import { debugLog } from "../lib-ts/debug.js";
|
|
65
|
+
import { runOrchestrator } from "../lib-ts/orchestrator.js";
|
|
66
|
+
import { runPlanQuestions } from "../lib-ts/plan-questions.js";
|
|
67
|
+
import { runAgentReview } from "../lib-ts/reviewers/index.js";
|
|
68
|
+
import { DEFAULT_REVIEW_ITERATIONS } from "../lib-ts/state.js";
|
|
69
|
+
import {
|
|
70
|
+
REVIEW_SCHEMA,
|
|
71
|
+
DEFAULT_DISPLAY,
|
|
72
|
+
DEFAULT_SANITIZATION,
|
|
73
|
+
} from "../lib-ts/types.js";
|
|
44
74
|
import type {
|
|
45
75
|
AgentConfig,
|
|
46
76
|
OrchestratorConfig,
|
|
@@ -52,35 +82,7 @@ import type {
|
|
|
52
82
|
Verdict,
|
|
53
83
|
IterationState,
|
|
54
84
|
} from "../lib-ts/types.js";
|
|
55
|
-
import type { ContextState } from "../../_shared/lib-ts/types.js";
|
|
56
|
-
import {
|
|
57
|
-
REVIEW_SCHEMA,
|
|
58
|
-
DEFAULT_DISPLAY,
|
|
59
|
-
DEFAULT_SANITIZATION,
|
|
60
|
-
} from "../lib-ts/types.js";
|
|
61
|
-
|
|
62
|
-
import {
|
|
63
|
-
isPlanAlreadyReviewed,
|
|
64
|
-
wasPlanPreviouslyDenied,
|
|
65
|
-
markPlanReviewed,
|
|
66
|
-
} from "../lib-ts/cc-native-state.js";
|
|
67
|
-
|
|
68
85
|
import { worstVerdict } from "../lib-ts/verdict.js";
|
|
69
|
-
import { computeCorroboratedDecision } from "../lib-ts/corroboration.js";
|
|
70
|
-
import { loadConfig, getDisplaySettings } from "../lib-ts/config.js";
|
|
71
|
-
import { runOrchestrator } from "../lib-ts/orchestrator.js";
|
|
72
|
-
import { aggregateAgents } from "../lib-ts/aggregate-agents.js";
|
|
73
|
-
import { debugLog } from "../lib-ts/debug.js";
|
|
74
|
-
import {
|
|
75
|
-
writeCombinedArtifacts,
|
|
76
|
-
buildInlineReviewSummary,
|
|
77
|
-
extractTopIssuesText,
|
|
78
|
-
buildHighIssuesDocument,
|
|
79
|
-
writeReviewTracker,
|
|
80
|
-
} from "../lib-ts/artifacts.js";
|
|
81
|
-
import type { ReviewTrackerEntry } from "../lib-ts/artifacts.js";
|
|
82
|
-
import { runAgentReview, runCodexReview, runGeminiReview } from "../lib-ts/reviewers/index.js";
|
|
83
|
-
import { DEFAULT_REVIEW_ITERATIONS } from "../lib-ts/state.js";
|
|
84
86
|
|
|
85
87
|
// ---------------------------------------------------------------------------
|
|
86
88
|
// Hook Name
|
|
@@ -118,10 +120,7 @@ function extractTopIssuesForTracker(
|
|
|
118
120
|
combined: CombinedReviewResult,
|
|
119
121
|
maxCount = 5,
|
|
120
122
|
): string[] {
|
|
121
|
-
const allReviewers =
|
|
122
|
-
...Object.values(combined.cli_reviewers),
|
|
123
|
-
...Object.values(combined.agents),
|
|
124
|
-
];
|
|
123
|
+
const allReviewers = Object.values(combined.agents);
|
|
125
124
|
const issues: string[] = [];
|
|
126
125
|
for (const r of allReviewers) {
|
|
127
126
|
if (!r.data) continue;
|
|
@@ -224,7 +223,7 @@ function resolveMandatoryAgents(
|
|
|
224
223
|
return new Set(configValue as string[]);
|
|
225
224
|
}
|
|
226
225
|
if (!configValue || typeof configValue !== "object") {
|
|
227
|
-
return new Set(["
|
|
226
|
+
return new Set(["clarity-auditor", "handoff-readiness", "skeptic"]);
|
|
228
227
|
}
|
|
229
228
|
const cfg = configValue as Record<string, string[]>;
|
|
230
229
|
const names = new Set(cfg.always ?? []);
|
|
@@ -274,8 +273,8 @@ function loadIterationState(reviewsDir: string): IterationState | null {
|
|
|
274
273
|
if (!fs.existsSync(iterationFile)) return null;
|
|
275
274
|
try {
|
|
276
275
|
return JSON.parse(fs.readFileSync(iterationFile, "utf-8")) as IterationState;
|
|
277
|
-
} catch (
|
|
278
|
-
logError(HOOK, `Failed to load iteration state: ${
|
|
276
|
+
} catch (error) {
|
|
277
|
+
logError(HOOK, `Failed to load iteration state: ${error}`);
|
|
279
278
|
return null;
|
|
280
279
|
}
|
|
281
280
|
}
|
|
@@ -287,8 +286,8 @@ function saveIterationState(reviewsDir: string, state: IterationState & { schema
|
|
|
287
286
|
state.schema_version = "1.0.0";
|
|
288
287
|
fs.writeFileSync(iterationFile, JSON.stringify(state, null, 2), "utf-8");
|
|
289
288
|
return true;
|
|
290
|
-
} catch (
|
|
291
|
-
logError(HOOK, `Failed to save iteration state: ${
|
|
289
|
+
} catch (error) {
|
|
290
|
+
logError(HOOK, `Failed to save iteration state: ${error}`);
|
|
292
291
|
return false;
|
|
293
292
|
}
|
|
294
293
|
}
|
|
@@ -333,7 +332,7 @@ function assignModelsToAgents(
|
|
|
333
332
|
if (!found) {
|
|
334
333
|
logWarn(HOOK, `Provider '${name}' enabled but CLI '${cliName}' not found on PATH — skipping`);
|
|
335
334
|
}
|
|
336
|
-
return
|
|
335
|
+
return Boolean(found);
|
|
337
336
|
});
|
|
338
337
|
|
|
339
338
|
if (enabledProviders.length === 0) {
|
|
@@ -401,11 +400,11 @@ function loadSettings(projDir: string): Record<string, unknown> {
|
|
|
401
400
|
}
|
|
402
401
|
mergedAgent.display = getDisplaySettings(config, "agentReview");
|
|
403
402
|
const configRecord = config as Record<string, unknown>;
|
|
404
|
-
mergedAgent.agentSelection = { ...DEFAULT_AGENT_SELECTION, ...(
|
|
405
|
-
mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...(
|
|
403
|
+
mergedAgent.agentSelection = { ...DEFAULT_AGENT_SELECTION, ...(configRecord.agentSelection as Record<string, unknown>) };
|
|
404
|
+
mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...(configRecord.agentDefaults as Record<string, unknown>) };
|
|
406
405
|
mergedAgent.complexityCategories = (configRecord.complexityCategories as string[]) ?? [...DEFAULT_COMPLEXITY_CATEGORIES];
|
|
407
|
-
mergedAgent.sanitization = { ...DEFAULT_SANITIZATION, ...(
|
|
408
|
-
mergedAgent.reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS, ...agentReview.reviewIterations
|
|
406
|
+
mergedAgent.sanitization = { ...DEFAULT_SANITIZATION, ...(configRecord.sanitization as Record<string, unknown>) };
|
|
407
|
+
mergedAgent.reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS, ...agentReview.reviewIterations };
|
|
409
408
|
|
|
410
409
|
const modelsRaw = (config as Record<string, unknown>).models ?? {};
|
|
411
410
|
return { planReview: mergedPlan, agentReview: mergedAgent, models: modelsRaw };
|
|
@@ -415,7 +414,7 @@ function loadAgentLibrary(
|
|
|
415
414
|
projDir: string,
|
|
416
415
|
settings?: Record<string, unknown>,
|
|
417
416
|
): AgentConfig[] {
|
|
418
|
-
const agentsData = aggregateAgents(path.join(projDir, "_cc-native", "agents"));
|
|
417
|
+
const agentsData = aggregateAgents(path.join(projDir, "_cc-native", "agents", "plan-review"));
|
|
419
418
|
const defaultModel = settings?.agentDefaults?.model ?? DEFAULT_AGENT_MODEL;
|
|
420
419
|
|
|
421
420
|
if (!agentsData || agentsData.length === 0) {
|
|
@@ -502,8 +501,8 @@ async function main(): Promise<void> {
|
|
|
502
501
|
let plan: string;
|
|
503
502
|
try {
|
|
504
503
|
plan = fs.readFileSync(planPath, "utf-8").trim();
|
|
505
|
-
} catch (
|
|
506
|
-
skipWithInfo(`Failed to read plan file: ${
|
|
504
|
+
} catch (error) {
|
|
505
|
+
skipWithInfo(`Failed to read plan file: ${error}`);
|
|
507
506
|
return;
|
|
508
507
|
}
|
|
509
508
|
|
|
@@ -515,6 +514,43 @@ async function main(): Promise<void> {
|
|
|
515
514
|
logInfo(HOOK, `Found plan at: ${planPath}`);
|
|
516
515
|
logDebug(HOOK, `Plan length: ${plan.length} chars`);
|
|
517
516
|
|
|
517
|
+
// ============================================
|
|
518
|
+
// Questions Gate: ask user questions before review
|
|
519
|
+
// ============================================
|
|
520
|
+
if (!wasPlanQuestionsAgentAsked(sessionId, base)) {
|
|
521
|
+
logInfo(HOOK, "Questions gate (Phase B): plan-questions agent has not run yet, running now");
|
|
522
|
+
const timeout = typeof (settings.agentReview ?? {}).timeout === "number"
|
|
523
|
+
? (settings.agentReview as Record<string, unknown>).timeout as number : 120;
|
|
524
|
+
const questionsResult = await runPlanQuestions(plan, aiwcliDir, timeout, undefined, sessionId);
|
|
525
|
+
|
|
526
|
+
// Mark agent questions as asked NOW — prevents infinite gate loop if Claude
|
|
527
|
+
// doesn't use AskUserQuestion after denial. Gate fires at most once.
|
|
528
|
+
markQuestionsAsked(sessionId, base, "agent");
|
|
529
|
+
|
|
530
|
+
const hasQuestions = questionsResult && (
|
|
531
|
+
questionsResult.questions.length > 0 ||
|
|
532
|
+
questionsResult.assumptions.length > 0 ||
|
|
533
|
+
questionsResult.ambiguities.length > 0
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
if (hasQuestions) {
|
|
537
|
+
const questionsList = questionsResult.questions.map((q: string, i: number) => `${i + 1}. ${q}`).join("\n");
|
|
538
|
+
const assumptionsList = questionsResult.assumptions.length > 0
|
|
539
|
+
? `\n\nAssumptions detected:\n${questionsResult.assumptions.map((a: string) => `- ${a}`).join("\n")}`
|
|
540
|
+
: "";
|
|
541
|
+
const ambiguitiesList = questionsResult.ambiguities.length > 0
|
|
542
|
+
? `\n\nAmbiguities detected:\n${questionsResult.ambiguities.map((a: string) => `- ${a}`).join("\n")}`
|
|
543
|
+
: "";
|
|
544
|
+
const contextMsg = `## Plan Questions (from independent review)\n\nAn agent reviewed your plan in a fresh context — without access to your session history or codebase exploration. It identified these questions:\n\n${questionsList}${assumptionsList}${ambiguitiesList}\n\nAsk the user these questions using AskUserQuestion before submitting the plan.`;
|
|
545
|
+
emitContextAndBlock(contextMsg, "Ask the user clarifying questions before submitting the plan. Use AskUserQuestion with the questions above.");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
logInfo(HOOK, "Questions gate (Phase B): no questions generated, proceeding to review");
|
|
550
|
+
} else {
|
|
551
|
+
logInfo(HOOK, "Questions gate (Phase B): agent already ran, skipping");
|
|
552
|
+
}
|
|
553
|
+
|
|
518
554
|
const planHash = computePlanHash(plan);
|
|
519
555
|
logDiagnostic(HOOK, "receive", `plan_size=${plan.length}, session=${sessionId.slice(0, 8)}`, {
|
|
520
556
|
inputs: { plan_hash: planHash, plan_size: plan.length, session_id: sessionId.slice(0, 12) },
|
|
@@ -537,31 +573,38 @@ async function main(): Promise<void> {
|
|
|
537
573
|
// Plan-hash deduplication
|
|
538
574
|
logDebug(HOOK, `Plan hash: ${planHash}`);
|
|
539
575
|
if (isPlanAlreadyReviewed(sessionId, planHash, base)) {
|
|
576
|
+
const lastReview = getLastPlanReview(sessionId, planHash, base);
|
|
577
|
+
|
|
540
578
|
if (wasPlanPreviouslyDenied(sessionId, planHash, base)) {
|
|
579
|
+
// Plan unchanged since last FAIL verdict
|
|
541
580
|
emitContextAndBlock(
|
|
542
581
|
"[Plan Review] Plan content unchanged since last review which found issues.",
|
|
543
582
|
"Plan unchanged since denial. Modify the plan to address review findings, then attempt ExitPlanMode again.",
|
|
544
583
|
);
|
|
545
584
|
return;
|
|
546
|
-
}
|
|
547
|
-
|
|
585
|
+
}
|
|
586
|
+
// Plan already reviewed with PASS or WARN verdict - skip review
|
|
587
|
+
const verdict = lastReview?.iteration?.latest_verdict || "pass";
|
|
588
|
+
const skipMsg = `[Plan Review] Plan already reviewed (verdict: ${verdict}). Skipping re-review.`;
|
|
589
|
+
emitContext(skipMsg);
|
|
590
|
+
logInfo(HOOK, skipMsg);
|
|
548
591
|
return;
|
|
549
|
-
|
|
592
|
+
|
|
550
593
|
}
|
|
551
594
|
|
|
552
595
|
// Single load of iteration state — reused throughout, saved once at end.
|
|
553
596
|
// Default max=1 is safe: first iteration 1>1=false (runs), Edit E updates max from config before save.
|
|
554
|
-
|
|
597
|
+
const iterationState: IterationState = loadIterationState(reviewsDir) ?? {
|
|
555
598
|
current: 1, max: 1, complexity: "medium",
|
|
556
599
|
history: [], graduated: [], passStreaks: {}, lastPlanHash: "",
|
|
557
600
|
};
|
|
558
601
|
|
|
559
|
-
//
|
|
560
|
-
//
|
|
602
|
+
// Log plan hash changes for diagnostics (iteration counter no longer resets —
|
|
603
|
+
// plans change every iteration as Claude addresses feedback, so resetting
|
|
604
|
+
// would keep iteration perpetually at 1).
|
|
561
605
|
const lastHash = iterationState.lastPlanHash ?? "";
|
|
562
606
|
if (lastHash && lastHash !== planHash) {
|
|
563
|
-
logInfo(HOOK, `Plan hash changed (${lastHash.slice(0, 8)}→${planHash.slice(0, 8)}),
|
|
564
|
-
iterationState.current = 1;
|
|
607
|
+
logInfo(HOOK, `Plan hash changed (${lastHash.slice(0, 8)}→${planHash.slice(0, 8)}), iteration continues at ${iterationState.current}`);
|
|
565
608
|
}
|
|
566
609
|
|
|
567
610
|
// Early iteration check: if we've exhausted max iterations, allow plan through
|
|
@@ -571,19 +614,13 @@ async function main(): Promise<void> {
|
|
|
571
614
|
}
|
|
572
615
|
|
|
573
616
|
// Initialize result containers
|
|
574
|
-
const cliResults: Record<string, ReviewerResult> = {};
|
|
575
617
|
let orchResult: OrchestratorResult | null = null;
|
|
576
618
|
const agentResults: Record<string, ReviewerResult> = {};
|
|
577
|
-
let allVerdicts: Verdict[] = [];
|
|
578
619
|
let detectedComplexity = "medium";
|
|
579
620
|
|
|
580
621
|
// ============================================
|
|
581
|
-
// PHASE 1
|
|
622
|
+
// PHASE 1: Orchestrator (Complexity Analysis)
|
|
582
623
|
// ============================================
|
|
583
|
-
const reviewersConfig = planReviewEnabled ? (planSettings.reviewers ?? {}) : {};
|
|
584
|
-
// Deprecated: agents now support Codex provider via models.providers.codex
|
|
585
|
-
const codexEnabled = planReviewEnabled && (reviewersConfig.codex?.enabled ?? false);
|
|
586
|
-
const geminiEnabled = planReviewEnabled && (reviewersConfig.gemini?.enabled ?? false);
|
|
587
624
|
|
|
588
625
|
// Graduated agents from previous iterations (empty after hash reset or on iteration 1)
|
|
589
626
|
const graduatedSet = new Set(iterationState.graduated);
|
|
@@ -608,7 +645,6 @@ async function main(): Promise<void> {
|
|
|
608
645
|
const alwaysMandatory = resolveMandatoryAgents(mandatoryConfig, "simple");
|
|
609
646
|
let mandatoryNames = alwaysMandatory;
|
|
610
647
|
|
|
611
|
-
logDebug(HOOK, `Codex enabled: ${codexEnabled}, Gemini enabled: ${geminiEnabled}`);
|
|
612
648
|
logDebug(HOOK, `Agent library: ${agentLibrary.map(a => a.name)}`);
|
|
613
649
|
logDebug(HOOK, `Mandatory agents: ${[...mandatoryNames].sort()}`);
|
|
614
650
|
logDebug(HOOK, `Orchestrator enabled: ${orchestratorConfig.enabled}`);
|
|
@@ -616,18 +652,6 @@ async function main(): Promise<void> {
|
|
|
616
652
|
// Build phase 1 tasks as promises
|
|
617
653
|
const phase1Promises: Array<{ name: string; promise: Promise<ReviewerResult | OrchestratorResult> }> = [];
|
|
618
654
|
|
|
619
|
-
if (codexEnabled) {
|
|
620
|
-
phase1Promises.push({
|
|
621
|
-
name: "codex",
|
|
622
|
-
promise: runCodexReview(plan, REVIEW_SCHEMA, planSettings),
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
if (geminiEnabled) {
|
|
626
|
-
phase1Promises.push({
|
|
627
|
-
name: "gemini",
|
|
628
|
-
promise: runGeminiReview(plan, REVIEW_SCHEMA, planSettings),
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
655
|
if (orchestratorConfig.enabled && enabledAgents.length > 0 && !legacyMode) {
|
|
632
656
|
phase1Promises.push({
|
|
633
657
|
name: "orchestrator",
|
|
@@ -656,9 +680,7 @@ async function main(): Promise<void> {
|
|
|
656
680
|
}
|
|
657
681
|
}
|
|
658
682
|
|
|
659
|
-
// Collect
|
|
660
|
-
if (phase1Results.codex) cliResults.codex = phase1Results.codex as ReviewerResult;
|
|
661
|
-
if (phase1Results.gemini) cliResults.gemini = phase1Results.gemini as ReviewerResult;
|
|
683
|
+
// Collect orchestrator result
|
|
662
684
|
if (phase1Results.orchestrator) orchResult = phase1Results.orchestrator as OrchestratorResult;
|
|
663
685
|
|
|
664
686
|
// ============================================
|
|
@@ -736,7 +758,7 @@ async function main(): Promise<void> {
|
|
|
736
758
|
// Update complexity/max on the already-loaded iteration state (no second disk read)
|
|
737
759
|
const reviewIterations: Record<string, number> = {
|
|
738
760
|
...DEFAULT_REVIEW_ITERATIONS,
|
|
739
|
-
...
|
|
761
|
+
...agentSettings.reviewIterations,
|
|
740
762
|
};
|
|
741
763
|
iterationState.complexity = detectedComplexity;
|
|
742
764
|
iterationState.max = reviewIterations[detectedComplexity] ?? iterationState.max;
|
|
@@ -791,7 +813,7 @@ async function main(): Promise<void> {
|
|
|
791
813
|
const maxIssuesPerAgent = typeof agentSettings.maxIssuesPerAgent === "number"
|
|
792
814
|
? agentSettings.maxIssuesPerAgent : 3;
|
|
793
815
|
|
|
794
|
-
for (const r of
|
|
816
|
+
for (const r of Object.values(agentResults)) {
|
|
795
817
|
if (!Array.isArray(r.data?.issues)) continue;
|
|
796
818
|
const issues = r.data.issues as Array<{ severity?: string }>;
|
|
797
819
|
if (issues.length <= maxIssuesPerAgent) continue;
|
|
@@ -814,19 +836,17 @@ async function main(): Promise<void> {
|
|
|
814
836
|
// Per-agent high-severity threshold: override verdict to "fail"
|
|
815
837
|
// ============================================
|
|
816
838
|
const highIssueThreshold = typeof agentSettings.highIssueThreshold === "number" ? agentSettings.highIssueThreshold : 3;
|
|
817
|
-
allVerdicts = [];
|
|
818
839
|
|
|
819
|
-
for (const r of
|
|
840
|
+
for (const r of Object.values(agentResults)) {
|
|
820
841
|
if (!r.verdict || r.verdict === "skip" || r.verdict === "error") continue;
|
|
821
842
|
const issues = Array.isArray(r.data?.issues) ? r.data.issues as Array<{ severity?: string }> : [];
|
|
822
843
|
const agentHigh = issues.filter(i => i.severity === "high").length;
|
|
823
|
-
let verdict = r
|
|
844
|
+
let {verdict} = r;
|
|
824
845
|
if (agentHigh >= highIssueThreshold) {
|
|
825
846
|
logInfo(HOOK, `${r.name}: verdict overridden to 'fail' (${agentHigh} high issues >= ${highIssueThreshold})`);
|
|
826
847
|
verdict = "fail";
|
|
827
848
|
r.verdict = verdict;
|
|
828
849
|
}
|
|
829
|
-
allVerdicts.push(verdict);
|
|
830
850
|
}
|
|
831
851
|
|
|
832
852
|
// ============================================
|
|
@@ -834,7 +854,7 @@ async function main(): Promise<void> {
|
|
|
834
854
|
// ============================================
|
|
835
855
|
logInfo(HOOK, "=== PHASE 4: Generate Output ===");
|
|
836
856
|
|
|
837
|
-
if (Object.keys(
|
|
857
|
+
if (Object.keys(agentResults).length === 0) {
|
|
838
858
|
if (graduatedSet.size > 0 && originalAgentCount > 0) {
|
|
839
859
|
skipWithInfo("All agent reviewers graduated from previous iterations — no review needed.");
|
|
840
860
|
} else {
|
|
@@ -843,20 +863,25 @@ async function main(): Promise<void> {
|
|
|
843
863
|
return;
|
|
844
864
|
}
|
|
845
865
|
|
|
846
|
-
|
|
866
|
+
// Review decision — corroboration-based (proportional threshold per dimension)
|
|
867
|
+
// Must be computed before writeCombinedArtifacts and buildInlineReviewSummary which consume it.
|
|
868
|
+
const allReviewerResults: Record<string, ReviewerResult> = agentResults;
|
|
869
|
+
const corroborationResult = computeCorroboratedDecision(allReviewerResults);
|
|
870
|
+
|
|
871
|
+
// Use corroboration verdict as single source of truth (not worstVerdict from individual agents)
|
|
872
|
+
const overall = corroborationResult.verdict;
|
|
847
873
|
|
|
848
874
|
const combinedResult: CombinedReviewResult = {
|
|
849
875
|
plan_hash: planHash,
|
|
850
876
|
overall_verdict: overall,
|
|
851
|
-
cli_reviewers: cliResults,
|
|
852
877
|
orchestration: orchResult,
|
|
853
878
|
agents: agentResults,
|
|
854
879
|
timestamp: new Date().toISOString(),
|
|
855
880
|
};
|
|
856
881
|
|
|
857
882
|
const displaySettings = {
|
|
858
|
-
...
|
|
859
|
-
...
|
|
883
|
+
...planSettings.display,
|
|
884
|
+
...agentSettings.display,
|
|
860
885
|
};
|
|
861
886
|
const combinedSettings = { display: displaySettings };
|
|
862
887
|
|
|
@@ -868,11 +893,6 @@ async function main(): Promise<void> {
|
|
|
868
893
|
fs.mkdirSync(reviewFolder, { recursive: true });
|
|
869
894
|
logInfo(HOOK, `Created review folder: ${reviewFolder}`);
|
|
870
895
|
|
|
871
|
-
// Review decision — corroboration-based (proportional threshold per dimension)
|
|
872
|
-
// Must be computed before writeCombinedArtifacts and buildInlineReviewSummary which consume it.
|
|
873
|
-
const allReviewerResults: Record<string, ReviewerResult> = { ...cliResults, ...agentResults };
|
|
874
|
-
const corroborationResult = computeCorroboratedDecision(allReviewerResults);
|
|
875
|
-
|
|
876
896
|
const reviewFile = writeCombinedArtifacts(
|
|
877
897
|
base,
|
|
878
898
|
plan,
|
|
@@ -886,12 +906,18 @@ async function main(): Promise<void> {
|
|
|
886
906
|
);
|
|
887
907
|
logInfo(HOOK, `Saved review: ${reviewFile}`);
|
|
888
908
|
|
|
909
|
+
// Write corroboration analysis report
|
|
910
|
+
const corroborationReport = buildCorroborationReport(corroborationResult);
|
|
911
|
+
const corroborationPath = path.join(reviewFolder, "corroboration.md");
|
|
912
|
+
fs.writeFileSync(corroborationPath, corroborationReport, "utf-8");
|
|
913
|
+
logInfo(HOOK, `Saved corroboration report: ${corroborationPath}`);
|
|
914
|
+
|
|
889
915
|
// Save plan snapshot for diffing between iterations
|
|
890
916
|
try {
|
|
891
917
|
fs.writeFileSync(path.join(reviewFolder, "plan.md"), plan, "utf-8");
|
|
892
918
|
logDebug(HOOK, `Saved plan snapshot: ${path.join(reviewFolder, "plan.md")}`);
|
|
893
|
-
} catch (
|
|
894
|
-
logWarn(HOOK, `Failed to save plan snapshot: ${
|
|
919
|
+
} catch (error) {
|
|
920
|
+
logWarn(HOOK, `Failed to save plan snapshot: ${error}`);
|
|
895
921
|
}
|
|
896
922
|
|
|
897
923
|
// Build inline summary with top issues (always emitted, even on pass)
|
|
@@ -904,7 +930,7 @@ async function main(): Promise<void> {
|
|
|
904
930
|
contextParts.push(`\nFull review: \`${reviewFile}\`\n`);
|
|
905
931
|
const shouldDeny = corroborationResult.blocking.length > 0;
|
|
906
932
|
const denyReason = shouldDeny ? "corroborated_issues" : "no_corroboration";
|
|
907
|
-
const reviewScore = shouldDeny ? 1
|
|
933
|
+
const reviewScore = shouldDeny ? 1 : 0;
|
|
908
934
|
|
|
909
935
|
logInfo(HOOK, `REVIEW_DECISION: verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}, score=${reviewScore.toFixed(2)}`);
|
|
910
936
|
logDiagnostic(HOOK, "result", `verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}`, {
|
|
@@ -913,7 +939,6 @@ async function main(): Promise<void> {
|
|
|
913
939
|
inputs: {
|
|
914
940
|
overall_verdict: combinedResult.overall_verdict,
|
|
915
941
|
review_score: Math.round(reviewScore * 100) / 100,
|
|
916
|
-
cli_count: Object.keys(cliResults).length,
|
|
917
942
|
agent_count: Object.keys(agentResults).length,
|
|
918
943
|
},
|
|
919
944
|
});
|
|
@@ -944,7 +969,7 @@ async function main(): Promise<void> {
|
|
|
944
969
|
}
|
|
945
970
|
|
|
946
971
|
// Update pass streaks — only for agents that actually ran this iteration
|
|
947
|
-
const passStreaks = { ...
|
|
972
|
+
const passStreaks = { ...iterationState.passStreaks };
|
|
948
973
|
const passEligibleSet = new Set(passEligible);
|
|
949
974
|
const graduatedSetCurrent = new Set(iterationState.graduated);
|
|
950
975
|
|
|
@@ -990,7 +1015,8 @@ async function main(): Promise<void> {
|
|
|
990
1015
|
writeReviewTracker(ccNativeReviewsDir, trackerEntry);
|
|
991
1016
|
logInfo(HOOK, `Updated review tracker: ${path.join(ccNativeReviewsDir, "review-tracker.md")}`);
|
|
992
1017
|
|
|
993
|
-
//
|
|
1018
|
+
// ALL first-time reviews block ExitPlanMode and inject feedback
|
|
1019
|
+
// Verdict controls iteration logic and next-run skip decision only
|
|
994
1020
|
const contextText = contextParts.join("");
|
|
995
1021
|
|
|
996
1022
|
logDebug(HOOK, `REVIEW_CONTEXT_INJECTED: chars=${contextText.length}, inline_chars=${inlineSummary.length}`);
|
|
@@ -998,7 +1024,10 @@ async function main(): Promise<void> {
|
|
|
998
1024
|
const REVIEWER_CAVEAT = "Reviewers have limited context compared to your full session — use your judgment to adopt valid points and dismiss genuine false positives. However, treat false positives as a clarity signal: if a reviewer misunderstood your plan, an agent executing it will likely hit the same confusion. Revise those sections to be unambiguous so no future reader — human or AI — makes the same mistake.";
|
|
999
1025
|
const RESUBMIT_INSTRUCTION = "IMPORTANT: After revising the plan file, you MUST call ExitPlanMode again to trigger re-review. Do not end your turn or ask the user without calling ExitPlanMode.";
|
|
1000
1026
|
|
|
1027
|
+
const iterInfo = ` (iteration ${iterationState.current - 1}/${iterationState.max}, score=${reviewScore.toFixed(2)})`;
|
|
1028
|
+
|
|
1001
1029
|
if (shouldDeny) {
|
|
1030
|
+
// FAIL verdict - critical issues found
|
|
1002
1031
|
const disposition = `hook_deny_iter_${iterationState.current - 1}`;
|
|
1003
1032
|
markPlanReviewed(sessionId, planHash, base, HOOK, iterationState, disposition);
|
|
1004
1033
|
const topIssuesText = extractTopIssuesText(combinedResult, 3, "high");
|
|
@@ -1006,21 +1035,23 @@ async function main(): Promise<void> {
|
|
|
1006
1035
|
const highIssuesPath = path.join(reviewFolder, "high-issues.md");
|
|
1007
1036
|
fs.writeFileSync(highIssuesPath, highIssuesDoc, "utf-8");
|
|
1008
1037
|
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
emitContextAndBlock(
|
|
1012
|
-
contextText,
|
|
1013
|
-
`Plan review FAILED${iterInfo}. ` +
|
|
1038
|
+
const blockReason = `Plan review FAILED${iterInfo}. ` +
|
|
1014
1039
|
`Critical issues: ${topIssuesText}. ` +
|
|
1015
1040
|
`IMPORTANT: Read \`${highIssuesPath}\` for ALL high-severity issues — ` +
|
|
1016
1041
|
`this file contains only the most critical findings, no noise. ` +
|
|
1017
1042
|
`${REVIEWER_CAVEAT} ` +
|
|
1018
1043
|
`Revise the plan to address these issues, then call ExitPlanMode again. ` +
|
|
1019
|
-
RESUBMIT_INSTRUCTION
|
|
1020
|
-
|
|
1044
|
+
RESUBMIT_INSTRUCTION;
|
|
1045
|
+
|
|
1046
|
+
emitContextAndBlock(contextText, blockReason);
|
|
1021
1047
|
} else {
|
|
1022
|
-
|
|
1023
|
-
|
|
1048
|
+
// PASS or WARN verdict - block to inject feedback, but mark as allowed
|
|
1049
|
+
const disposition = `hook_allow_iter_${iterationState.current - 1}`;
|
|
1050
|
+
markPlanReviewed(sessionId, planHash, base, HOOK, iterationState, disposition);
|
|
1051
|
+
|
|
1052
|
+
const blockReason = `Plan review ${overall.toUpperCase()}${iterInfo}. Review complete. ${REVIEWER_CAVEAT}`;
|
|
1053
|
+
|
|
1054
|
+
emitContextAndBlock(contextText, blockReason);
|
|
1024
1055
|
}
|
|
1025
1056
|
}
|
|
1026
1057
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse:Task Hook: Plan Quality Review Context
|
|
4
|
+
*
|
|
5
|
+
* Fires after Task tool completes with a Plan subagent. Emits plan quality review guidance
|
|
6
|
+
* as context for the main agent to review the plan before ExitPlanMode.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Never blocks (all errors exit 0)
|
|
10
|
+
* - Emits context via emitContext() — no file mutation
|
|
11
|
+
* - Only fires for Task tool with subagent_type="Plan"
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
loadHookInput,
|
|
16
|
+
runHook,
|
|
17
|
+
logInfo,
|
|
18
|
+
logDebug,
|
|
19
|
+
emitContext,
|
|
20
|
+
getToolInput,
|
|
21
|
+
} from "../../_shared/lib-ts/base/hook-utils.js";
|
|
22
|
+
import { isInternalCall } from "../../_shared/lib-ts/base/subprocess-utils.js";
|
|
23
|
+
import { getPlanQualityReviewContext } from "../lib-ts/plan-enhancement.js";
|
|
24
|
+
|
|
25
|
+
function main(): void {
|
|
26
|
+
if (isInternalCall()) return;
|
|
27
|
+
|
|
28
|
+
const payload = loadHookInput();
|
|
29
|
+
if (!payload) {
|
|
30
|
+
logDebug("enhance_plan_post_subagent", "No payload received");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if this is a Task tool call
|
|
35
|
+
if (payload.tool_name !== "Task") {
|
|
36
|
+
logDebug("enhance_plan_post_subagent", `Skipping: tool_name is "${payload.tool_name}", not "Task"`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if the Task is spawning a Plan subagent
|
|
41
|
+
const toolInput = getToolInput(payload);
|
|
42
|
+
const subagentType = toolInput?.subagent_type;
|
|
43
|
+
logDebug("enhance_plan_post_subagent", `subagent_type: ${subagentType ?? "undefined"}`);
|
|
44
|
+
|
|
45
|
+
if (subagentType !== "Plan") {
|
|
46
|
+
logDebug("enhance_plan_post_subagent", `Skipping: subagent_type is "${subagentType}", not "Plan"`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
logInfo("enhance_plan_post_subagent", "Emitting plan quality review context");
|
|
51
|
+
emitContext(getPlanQualityReviewContext());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
runHook(main, "enhance_plan_post_subagent");
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse:Write Hook: Plan Quality Review Context
|
|
4
|
+
*
|
|
5
|
+
* Fires after Write tool completes. Detects writes to ~/.claude/plans/*.md files
|
|
6
|
+
* and emits plan quality review guidance as context.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Never blocks (all paths exit 0)
|
|
10
|
+
* - Uses normalized path comparison (cross-platform)
|
|
11
|
+
* - Shares prompt logic with SubagentStop hook via plan-enhancement.ts
|
|
12
|
+
* - Emits context via emitContext() — no file mutation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
loadHookInput,
|
|
20
|
+
runHook,
|
|
21
|
+
logInfo,
|
|
22
|
+
emitContext,
|
|
23
|
+
} from "../../_shared/lib-ts/base/hook-utils.js";
|
|
24
|
+
import { isInternalCall } from "../../_shared/lib-ts/base/subprocess-utils.js";
|
|
25
|
+
import { getPlanQualityReviewContext } from "../lib-ts/plan-enhancement.js";
|
|
26
|
+
|
|
27
|
+
function main(): void {
|
|
28
|
+
if (isInternalCall()) return;
|
|
29
|
+
|
|
30
|
+
const payload = loadHookInput();
|
|
31
|
+
if (!payload) return;
|
|
32
|
+
|
|
33
|
+
const toolInput = payload.tool_input;
|
|
34
|
+
if (!toolInput || typeof toolInput !== "object") return;
|
|
35
|
+
|
|
36
|
+
const filePath = toolInput.file_path as string | undefined;
|
|
37
|
+
if (!filePath) return;
|
|
38
|
+
|
|
39
|
+
// Normalize paths for cross-platform comparison
|
|
40
|
+
const normalizedPath = path.normalize(path.resolve(filePath));
|
|
41
|
+
const plansDir = path.normalize(path.join(os.homedir(), ".claude", "plans"));
|
|
42
|
+
|
|
43
|
+
// Check if file is a markdown file in the plans directory
|
|
44
|
+
if (!normalizedPath.startsWith(plansDir) || !normalizedPath.endsWith(".md")) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
logInfo("enhance_plan_write", `Detected plan file write: ${filePath}`);
|
|
49
|
+
emitContext(getPlanQualityReviewContext());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
runHook(main, "enhance_plan_post_write");
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Mark Questions Asked Hook
|
|
4
|
+
*
|
|
5
|
+
* Tracks when AskUserQuestion tool is used during a session.
|
|
6
|
+
* Used by other hooks to determine if user clarification was gathered.
|
|
7
|
+
*
|
|
8
|
+
* Registered for:
|
|
9
|
+
* - PostToolUse: AskUserQuestion — marks questions asked state for this session
|
|
10
|
+
*
|
|
11
|
+
* Fail-safe: Any error exits 0 (non-blocking).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { getProjectRoot } from "../../_shared/lib-ts/base/constants.js";
|
|
15
|
+
import {
|
|
16
|
+
loadHookInput,
|
|
17
|
+
runHook,
|
|
18
|
+
logInfo,
|
|
19
|
+
logDiagnostic,
|
|
20
|
+
} from "../../_shared/lib-ts/base/hook-utils.js";
|
|
21
|
+
import { isInternalCall } from "../../_shared/lib-ts/base/subprocess-utils.js";
|
|
22
|
+
import { markQuestionsAsked } from "../lib-ts/cc-native-state.js";
|
|
23
|
+
|
|
24
|
+
function main(): void {
|
|
25
|
+
// Guard: skip for internal subprocess calls (prevents recursive hook execution)
|
|
26
|
+
if (isInternalCall()) return;
|
|
27
|
+
|
|
28
|
+
const payload = loadHookInput();
|
|
29
|
+
if (!payload) return;
|
|
30
|
+
|
|
31
|
+
const toolName = payload.tool_name;
|
|
32
|
+
const hookEvent = payload.hook_event_name ?? "unknown";
|
|
33
|
+
logDiagnostic(
|
|
34
|
+
"add_plan_context",
|
|
35
|
+
"receive",
|
|
36
|
+
`tool=${toolName}, event=${hookEvent}`,
|
|
37
|
+
{ inputs: { tool_name: toolName, hook_event: hookEvent } },
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const projectRoot = getProjectRoot(payload.cwd);
|
|
41
|
+
|
|
42
|
+
// PostToolUse: AskUserQuestion — mark that early questions (Phase A) were asked
|
|
43
|
+
if (toolName === "AskUserQuestion") {
|
|
44
|
+
const sessionId = String(payload.session_id ?? "");
|
|
45
|
+
if (sessionId) {
|
|
46
|
+
markQuestionsAsked(sessionId, projectRoot, "early");
|
|
47
|
+
logInfo("add_plan_context", `Marked early questions asked for session ${sessionId.slice(0, 8)}...`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
runHook(main, "mark_questions_asked");
|