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.
Files changed (117) hide show
  1. package/dist/commands/clear.d.ts +8 -0
  2. package/dist/commands/clear.js +86 -0
  3. package/dist/lib/bmad-installer.d.ts +2 -27
  4. package/dist/lib/bmad-installer.js +3 -43
  5. package/dist/lib/claude-settings-types.d.ts +2 -1
  6. package/dist/lib/env-compat.d.ts +0 -8
  7. package/dist/lib/env-compat.js +0 -12
  8. package/dist/lib/git/index.d.ts +0 -1
  9. package/dist/lib/gitignore-manager.d.ts +0 -2
  10. package/dist/lib/gitignore-manager.js +1 -1
  11. package/dist/lib/hooks-merger.d.ts +1 -15
  12. package/dist/lib/hooks-merger.js +1 -1
  13. package/dist/lib/index.d.ts +3 -7
  14. package/dist/lib/index.js +3 -11
  15. package/dist/lib/output.d.ts +2 -1
  16. package/dist/lib/settings-hierarchy.d.ts +1 -13
  17. package/dist/lib/settings-hierarchy.js +1 -1
  18. package/dist/lib/template-installer.d.ts +5 -9
  19. package/dist/lib/template-installer.js +3 -13
  20. package/dist/lib/template-linter.d.ts +3 -10
  21. package/dist/lib/template-linter.js +2 -2
  22. package/dist/lib/template-resolver.d.ts +6 -0
  23. package/dist/lib/template-resolver.js +10 -0
  24. package/dist/lib/template-settings-reconstructor.d.ts +1 -1
  25. package/dist/lib/template-settings-reconstructor.js +17 -24
  26. package/dist/lib/terminal.d.ts +3 -14
  27. package/dist/lib/terminal.js +0 -4
  28. package/dist/lib/version.d.ts +2 -11
  29. package/dist/lib/version.js +3 -3
  30. package/dist/lib/windsurf-hooks-merger.d.ts +1 -15
  31. package/dist/lib/windsurf-hooks-merger.js +1 -1
  32. package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
  33. package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
  34. package/dist/templates/_shared/hooks-ts/session_end.ts +75 -4
  35. package/dist/templates/_shared/hooks-ts/session_start.ts +11 -13
  36. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +6 -8
  37. package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
  38. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +176 -29
  39. package/dist/templates/_shared/lib-ts/base/logger.ts +1 -1
  40. package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
  41. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -165
  42. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +14 -13
  43. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +3 -2
  44. package/dist/templates/_shared/lib-ts/package.json +1 -2
  45. package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
  46. package/dist/templates/_shared/lib-ts/types.ts +17 -2
  47. package/dist/templates/_shared/scripts/resume_handoff.ts +4 -4
  48. package/dist/templates/_shared/scripts/save_handoff.ts +7 -7
  49. package/dist/templates/_shared/scripts/status_line.ts +104 -71
  50. package/dist/templates/_shared/workflows/handoff.md +1 -1
  51. package/dist/templates/cc-native/.claude/settings.json +182 -175
  52. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
  53. package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  54. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
  55. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +142 -111
  56. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +54 -0
  57. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +52 -0
  58. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
  59. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +19 -19
  60. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -5
  61. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +114 -83
  62. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +107 -10
  63. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +1 -1
  64. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +6 -2
  65. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +0 -4
  66. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -219
  67. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +102 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +26 -227
  70. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +4 -2
  72. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
  73. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +185 -0
  74. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
  75. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
  76. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
  77. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +2 -2
  78. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +17 -16
  79. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +13 -108
  80. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +3 -3
  81. package/dist/templates/cc-native/_cc-native/plan-review.config.json +2 -14
  82. package/oclif.manifest.json +1 -1
  83. package/package.json +1 -2
  84. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
  85. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
  86. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -107
  87. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
  88. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
  89. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
  90. /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
  91. /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
  92. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
  93. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
  94. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
  95. /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
  96. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
  97. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
  98. /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
  99. /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
  100. /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
  101. /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
  102. /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
  103. /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
  104. /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
  105. /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
  106. /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
  107. /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
  108. /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
  109. /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
  110. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
  111. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
  112. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
  113. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
  114. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
  115. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
  116. /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
  117. /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 crypto from "node:crypto";
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(["handoff-readiness", "clarity-auditor", "skeptic"]);
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 (e) {
278
- logError(HOOK, `Failed to load iteration state: ${e}`);
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 (e) {
291
- logError(HOOK, `Failed to save iteration state: ${e}`);
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 !!found;
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, ...((configRecord.agentSelection as Record<string, unknown>) ?? {}) };
405
- mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...((configRecord.agentDefaults as Record<string, unknown>) ?? {}) };
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, ...((configRecord.sanitization as Record<string, unknown>) ?? {}) };
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 (e) {
506
- skipWithInfo(`Failed to read plan file: ${e}`);
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
- } else {
547
- skipWithInfo("Plan already reviewed and approved (same hash).");
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
- let iterationState: IterationState = loadIterationState(reviewsDir) ?? {
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
- // Reset iteration counter when plan content changes (BEFORE early exit check)
560
- // Graduation state (graduated[], passStreaks{}) persists across plan changes.
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)}), resetting iteration counter`);
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 & 2: CLI Reviewers + Orchestrator (PARALLEL)
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 CLI results
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
- ...(agentSettings.reviewIterations ?? {}),
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 [...Object.values(cliResults), ...Object.values(agentResults)]) {
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 [...Object.values(cliResults), ...Object.values(agentResults)]) {
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.verdict;
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(cliResults).length === 0 && Object.keys(agentResults).length === 0) {
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
- const overall = allVerdicts.length > 0 ? worstVerdict(allVerdicts) : "pass";
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
- ...(planSettings.display ?? {}),
859
- ...(agentSettings.display ?? {}),
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 (e) {
894
- logWarn(HOOK, `Failed to save plan snapshot: ${e}`);
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.0 : 0.0;
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 = { ...(iterationState.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
- // Emit output always emit context with top issues + link; block only on fail
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 iterInfo = ` (iteration ${iterationState.current - 1}/${iterationState.max}, score=${reviewScore.toFixed(2)})`;
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
- markPlanReviewed(sessionId, planHash, base, HOOK, iterationState, "allow");
1023
- emitContext(contextText);
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");