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
@@ -1,37 +1,37 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * UserPromptSubmit hook — injects Phase A clarification prompt in plan mode.
3
+ * UserPromptSubmit hook — injects post-exploration clarification prompt in plan mode.
4
4
  *
5
- * On the first prompt in plan mode (before any code exploration), injects
6
- * a system-reminder telling Claude to ask clarification questions via
7
- * AskUserQuestion before exploring the codebase.
5
+ * After explore agents finish examining the codebase, injects a system-reminder
6
+ * telling Claude to ask clarification questions via AskUserQuestion to narrow
7
+ * the approach before drafting the plan.
8
8
  *
9
9
  * Skips if questions were already asked this session.
10
10
  */
11
11
 
12
12
  import { getProjectRoot } from "../../_shared/lib-ts/base/constants.js";
13
- import { emitContext, loadHookInput, logDebug, logInfo, runHook } from "../../_shared/lib-ts/base/hook-utils.js";
14
- import { wasQuestionsAsked } from "../lib-ts/cc-native-state.js";
13
+ import { loadHookInput, runHook, logDebug, logInfo, emitContext } from "../../_shared/lib-ts/base/hook-utils.js";
14
+ import { wasEarlyQuestionsAsked } from "../lib-ts/cc-native-state.js";
15
15
 
16
- const PHASE_A_PROMPT = `## Plan Mode: Clarify Before Exploring
16
+ const PHASE_A_PROMPT = `## Plan Mode: Narrow the Approach After Exploration
17
17
 
18
- Use AskUserQuestion now — one call, 3-4 questions — before reading any code.
18
+ After exploring the codebase, use AskUserQuestion — one call, 3-4 questions — before drafting the plan.
19
19
 
20
20
  ### Why This Matters
21
- Once you explore the codebase, you anchor on what you find. Questions asked after exploration confirm your assumptions instead of challenging them. Ask now, while your interpretation is still flexible.
21
+ Once you've explored the codebase, you'll understand what exists — but not which direction the user prefers. That's a branch point: multiple viable approaches, and the user's priorities determine which is best. Questions asked after exploration have maximum steering value: they narrow your path before you commit to an implementation direction.
22
22
 
23
23
  ### What to Ask About
24
- Only ask about things you cannot discover from codethe user's intent, constraints, history, and priorities:
24
+ Only ask about decisions that exploration will surface but can't resolve where human judgment is needed to choose between viable options:
25
25
 
26
- - **Ambiguity:** If you can read this request two different ways, ask which interpretation is correct. Provide your top 2-3 readings as options.
27
- - **Invisible context:** What does the user assume "everyone knows" about this system that isn't documented? What's obvious to them but hidden to you?
28
- - **Success criteria:** What does "done well" look like beyond the literal request? What would make them rate this a 10?
29
- - **Constraints and history:** Has this been attempted before? Are there parts of the system that are off-limits or sensitive?
26
+ - **Approach selection:** If exploration reveals 2-3 viable implementation paths, ask which the user prefers. Present each option with its trade-offs as concrete choices.
27
+ - **Scope boundaries:** What's in scope vs. out of scope for this change? Which areas of the codebase should be left untouched? How far should the change ripple?
28
+ - **Trade-off preferences:** Where exploration reveals tensions (simplicity vs. flexibility, speed vs. thoroughness, minimal change vs. full refactor), ask which side the user leans toward.
29
+ - **Success criteria beyond the literal ask:** What would make this a 10? What non-obvious quality matters most performance, readability, extensibility, consistency with existing patterns?
30
30
 
31
31
  ### How to Select Questions
32
- 1. Generate 5+ candidate questions across the lenses above
33
- 2. For each, evaluate: "If they answered A vs B, would I explore different files or take a different approach?" If no — discard it.
34
- 3. Keep the 3-4 where different answers lead to meaningfully different exploration strategies
32
+ 1. Generate 5+ candidate questions across the categories above
33
+ 2. For each, evaluate: "If they answered A vs B, would I take a different approach or write different code?" If no — discard it.
34
+ 3. Keep the 3-4 where different answers lead to meaningfully different implementation strategies
35
35
  4. Frame each with 2-3 concrete options so the user can react rather than generate from scratch`;
36
36
 
37
37
  function main(): void {
@@ -49,8 +49,8 @@ function main(): void {
49
49
 
50
50
  const projectRoot = getProjectRoot(payload.cwd);
51
51
 
52
- if (wasQuestionsAsked(sessionId, projectRoot)) {
53
- logDebug("plan_questions_early", "Questions already asked, skipping");
52
+ if (wasEarlyQuestionsAsked(sessionId, projectRoot)) {
53
+ logDebug("plan_questions_early", "Early questions already asked, skipping Phase A prompt");
54
54
  return;
55
55
  }
56
56
 
@@ -4,9 +4,10 @@
4
4
  */
5
5
 
6
6
  import * as fs from "node:fs";
7
- import * as path from "node:path";
8
- import { logDebug, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
7
+ import * as path from "node:path";
8
+
9
9
  import type { AgentConfig } from "./types.js";
10
+ import { logDebug, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
10
11
 
11
12
  /**
12
13
  * Extract simple YAML frontmatter from markdown content.
@@ -47,7 +48,7 @@ export function extractFrontmatter(
47
48
  value = value
48
49
  .slice(1, -1)
49
50
  .split(",")
50
- .map((s) => s.trim().replace(/^["']|["']$/g, ""))
51
+ .map((s) => s.trim().replaceAll(/^["']|["']$/g, ""))
51
52
  .filter(Boolean);
52
53
  }
53
54
  // Handle booleans
@@ -114,8 +115,8 @@ export function aggregateAgents(agentsDir?: string): AgentConfig[] {
114
115
  let content: string;
115
116
  try {
116
117
  content = fs.readFileSync(filePath, "utf-8");
117
- } catch (e: unknown) {
118
- logWarn("aggregate", `Failed to read ${file}: ${e}`);
118
+ } catch (error: unknown) {
119
+ logWarn("aggregate", `Failed to read ${file}: ${error}`);
119
120
  continue;
120
121
  }
121
122
 
@@ -4,11 +4,8 @@
4
4
  */
5
5
 
6
6
  import * as fs from "node:fs";
7
- import * as path from "node:path";
8
- import { atomicWrite } from "../../_shared/lib-ts/base/atomic-write.js";
9
- import { logDebug, logWarn, logError } from "../../_shared/lib-ts/base/logger.js";
10
- import { nowIso } from "../../_shared/lib-ts/base/utils.js";
11
- import { sanitizeFilename } from "../../_shared/lib-ts/base/constants.js";
7
+ import * as path from "node:path";
8
+
12
9
  import { ENABLE_ROBUST_PLAN_WRITES } from "./constants.js";
13
10
  import type {
14
11
  CombinedReviewResult,
@@ -17,6 +14,10 @@ import type {
17
14
  CorroborationResult,
18
15
  } from "./types.js";
19
16
  import { DEFAULT_DISPLAY } from "./types.js";
17
+ import { atomicWrite } from "../../_shared/lib-ts/base/atomic-write.js";
18
+ import { sanitizeFilename } from "../../_shared/lib-ts/base/constants.js";
19
+ import { logDebug, logWarn, logError } from "../../_shared/lib-ts/base/logger.js";
20
+ import { nowIso } from "../../_shared/lib-ts/base/utils.js";
20
21
 
21
22
  // ---------------------------------------------------------------------------
22
23
  // Markdown Formatting
@@ -81,7 +82,7 @@ export function formatCombinedMarkdown(
81
82
  if (corroboration.blocking.length > 0) {
82
83
  lines.push("### Blocking Dimensions\n");
83
84
  for (const group of corroboration.blocking) {
84
- lines.push(`- **${group.dimension}**: ${group.issues.length} issues from ${group.agentCount} agents (threshold: >${group.threshold})`);
85
+ lines.push(`- **${group.dimension}**: ${group.issues.length} issues from ${group.agentCount} agents (threshold: ≥${group.threshold})`);
85
86
  }
86
87
  lines.push("");
87
88
  }
@@ -99,22 +100,6 @@ export function formatCombinedMarkdown(
99
100
 
100
101
  lines.push("---\n");
101
102
 
102
- // CLI Reviewers section
103
- if (Object.keys(result.cli_reviewers).length > 0) {
104
- lines.push("## CLI Reviewers\n");
105
- for (const [name, r] of Object.entries(result.cli_reviewers)) {
106
- lines.push(`### ${titleCase(name)}\n`);
107
- lines.push(`- verdict: \`${r.verdict}\``);
108
- if (r.data && Object.keys(r.data).length > 0) {
109
- appendSummaryLine(lines, r.data);
110
- appendReviewDetails(lines, r.data, display);
111
- } else if (r.err) {
112
- lines.push(`- error: ${r.err}`);
113
- }
114
- lines.push("");
115
- }
116
- }
117
-
118
103
  // Orchestration section
119
104
  if (result.orchestration) {
120
105
  lines.push("---\n");
@@ -170,10 +155,7 @@ export function buildInlineReviewSummary(
170
155
  maxChars = 800,
171
156
  corroboration?: CorroborationResult,
172
157
  ): string {
173
- const allReviewers = [
174
- ...Object.values(combined.cli_reviewers),
175
- ...Object.values(combined.agents),
176
- ];
158
+ const allReviewers = Object.values(combined.agents);
177
159
 
178
160
  // Build set of blocking dimensions for annotation
179
161
  const blockingDims = new Set(
@@ -257,10 +239,7 @@ export function extractTopIssuesText(
257
239
  maxCount = 3,
258
240
  severity = "high",
259
241
  ): string {
260
- const allReviewers = [
261
- ...Object.values(combined.cli_reviewers),
262
- ...Object.values(combined.agents),
263
- ];
242
+ const allReviewers = Object.values(combined.agents);
264
243
 
265
244
  const issues: string[] = [];
266
245
  for (const r of allReviewers) {
@@ -318,10 +297,7 @@ export function buildHighIssuesDocument(
318
297
 
319
298
  // Fallback: no corroboration data — show all high-severity issues
320
299
  const lines = ["# High-Severity Issues\n"];
321
- const allReviewers = [
322
- ...Object.values(combined.cli_reviewers),
323
- ...Object.values(combined.agents),
324
- ];
300
+ const allReviewers = Object.values(combined.agents);
325
301
 
326
302
  let foundAny = false;
327
303
  for (const r of allReviewers) {
@@ -406,11 +382,6 @@ export function generateReviewIndex(
406
382
  "| [plan.md](./plan.md) | Plan snapshot at review time |",
407
383
  );
408
384
 
409
- for (const name of Object.keys(result.cli_reviewers)) {
410
- lines.push(
411
- `| [${name}.json](./reviewer-output/${name}.json) | ${titleCase(name)} reviewer output |`,
412
- );
413
- }
414
385
  for (const name of Object.keys(result.agents)) {
415
386
  const safeName = sanitizeFilename(name);
416
387
  lines.push(
@@ -426,9 +397,6 @@ export function generateReviewIndex(
426
397
  "|----------|---------|",
427
398
  );
428
399
 
429
- for (const [name, r] of Object.entries(result.cli_reviewers)) {
430
- lines.push(`| ${titleCase(name)} | \`${r.verdict}\` |`);
431
- }
432
400
  for (const [name, r] of Object.entries(result.agents)) {
433
401
  lines.push(`| ${name} | \`${r.verdict}\` |`);
434
402
  }
@@ -457,26 +425,6 @@ export function buildCombinedJson(
457
425
  },
458
426
  };
459
427
 
460
- // CLI reviewers
461
- if (Object.keys(result.cli_reviewers).length > 0) {
462
- const cliReviewers: Record<string, unknown> = {};
463
- output.cliReviewers = cliReviewers;
464
- for (const [name, r] of Object.entries(result.cli_reviewers)) {
465
- cliReviewers[name] = {
466
- verdict: r.verdict,
467
- summary: r.data?.summary ?? null,
468
- summarySource: r.data?.summary_source ?? null,
469
- issues: r.data
470
- ? ((r.data.issues as Array<Record<string, unknown>>) ?? []).filter(
471
- (i) => i.severity !== "low",
472
- )
473
- : [],
474
- ok: r.ok,
475
- error: r.err || null,
476
- };
477
- }
478
- }
479
-
480
428
  // Orchestration
481
429
  if (result.orchestration) {
482
430
  output.orchestration = {
@@ -543,9 +491,9 @@ export function writeCombinedArtifacts(
543
491
  // Create directory
544
492
  try {
545
493
  fs.mkdirSync(outDir, { recursive: true });
546
- } catch (e: unknown) {
547
- logError("utils", `Cannot create directory ${outDir}: ${e}`);
548
- throw e;
494
+ } catch (error: unknown) {
495
+ logError("utils", `Cannot create directory ${outDir}: ${error}`);
496
+ throw error;
549
497
  }
550
498
 
551
499
  // JSON write
@@ -565,14 +513,6 @@ export function writeCombinedArtifacts(
565
513
  } catch {
566
514
  // Best-effort — non-critical
567
515
  }
568
- for (const [name, r] of Object.entries(result.cli_reviewers)) {
569
- if (r.data) {
570
- writeFileNonCritical(
571
- path.join(reviewerOutputDir, `${name}.json`),
572
- JSON.stringify(r.data, null, 2),
573
- );
574
- }
575
- }
576
516
  for (const [name, r] of Object.entries(result.agents)) {
577
517
  if (r.data) {
578
518
  writeFileNonCritical(
@@ -658,9 +598,9 @@ function writeFile(filePath: string, content: string): void {
658
598
  } else {
659
599
  fs.writeFileSync(filePath, content, "utf-8");
660
600
  }
661
- } catch (e: unknown) {
662
- logError("utils", `Failed to write ${path.basename(filePath)}: ${e}`);
663
- throw e;
601
+ } catch (error: unknown) {
602
+ logError("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
603
+ throw error;
664
604
  }
665
605
  }
666
606
 
@@ -674,8 +614,8 @@ function writeFileNonCritical(filePath: string, content: string): void {
674
614
  } else {
675
615
  fs.writeFileSync(filePath, content, "utf-8");
676
616
  }
677
- } catch (e: unknown) {
678
- logWarn("utils", `Failed to write ${path.basename(filePath)}: ${e}`);
617
+ } catch (error: unknown) {
618
+ logWarn("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
679
619
  }
680
620
  }
681
621
 
@@ -731,13 +671,13 @@ export function writeReviewTracker(
731
671
  // Parse existing entries to detect plan changes
732
672
  const previousHashes = extractPreviousHashes(existingContent);
733
673
  const hashChanged = previousHashes.length > 0 &&
734
- previousHashes[previousHashes.length - 1] !== entry.planHash;
674
+ previousHashes.at(-1) !== entry.planHash;
735
675
 
736
676
  // Build the new entry section
737
677
  const lines: string[] = [];
738
- const verdictEmoji = entry.decision === "allow" ? "\u2705" : "\u274c";
678
+ const verdictEmoji = entry.decision === "allow" ? "\u2705" : "\u274C";
739
679
  const changeNote = previousHashes.length > 0
740
- ? (hashChanged ? "\u2705 Plan was revised (hash changed)" : "\u26a0\ufe0f Plan unchanged since last review")
680
+ ? (hashChanged ? "\u2705 Plan was revised (hash changed)" : "\u26A0\uFE0F Plan unchanged since last review")
741
681
  : "Initial review";
742
682
 
743
683
  lines.push(`## Iteration ${entry.iteration} \u2014 ${entry.timestamp} \u2014 ${verdictEmoji} ${entry.verdict.toUpperCase()}`);
@@ -776,8 +716,8 @@ export function writeReviewTracker(
776
716
 
777
717
  try {
778
718
  fs.writeFileSync(trackerPath, output, "utf-8");
779
- } catch (e) {
780
- logWarn("artifacts", `Failed to write review tracker: ${e}`);
719
+ } catch (error) {
720
+ logWarn("artifacts", `Failed to write review tracker: ${error}`);
781
721
  }
782
722
  }
783
723
 
@@ -790,3 +730,94 @@ function extractPreviousHashes(content: string): string[] {
790
730
  }
791
731
  return hashes;
792
732
  }
733
+
734
+ // ---------------------------------------------------------------------------
735
+ // Corroboration Report
736
+ // ---------------------------------------------------------------------------
737
+
738
+ /**
739
+ * Build a detailed markdown report of the corroboration analysis.
740
+ * Shows blocking vs solo findings with threshold comparison.
741
+ */
742
+ export function buildCorroborationReport(
743
+ corroborationResult: CorroborationResult,
744
+ ): string {
745
+ const lines: string[] = [
746
+ "# Corroboration Analysis",
747
+ "",
748
+ "## Verdict: " + corroborationResult.verdict.toUpperCase(),
749
+ "",
750
+ ];
751
+
752
+ // Blocking groups table
753
+ if (corroborationResult.blocking.length > 0) {
754
+ lines.push("## Blocking Issues (Corroborated)");
755
+ lines.push("");
756
+ lines.push("| Dimension | Issues | Agents | Threshold | Status |");
757
+ lines.push("|-----------|--------|--------|-----------|--------|");
758
+
759
+ for (const group of corroborationResult.blocking) {
760
+ lines.push(
761
+ `| ${group.dimension} | ${group.issues.length} | ${group.agentCount} | ${group.threshold} | ⛔ EXCEEDED |`
762
+ );
763
+ }
764
+ lines.push("");
765
+
766
+ // Issue details
767
+ for (const group of corroborationResult.blocking) {
768
+ lines.push(`### ${group.dimension} (${group.issues.length} issues)`);
769
+ lines.push("");
770
+ for (const {agent, issue} of group.issues) {
771
+ lines.push(`- **[${agent}]** ${issue.description || issue.issue || "No description"}`);
772
+ }
773
+ lines.push("");
774
+ }
775
+ }
776
+
777
+ // Solo findings table
778
+ if (corroborationResult.solo.length > 0) {
779
+ lines.push("## Solo Findings (Below Threshold)");
780
+ lines.push("");
781
+ lines.push("| Dimension | Issues | Agents | Threshold | Status |");
782
+ lines.push("|-----------|--------|--------|-----------|--------|");
783
+
784
+ for (const group of corroborationResult.solo) {
785
+ lines.push(
786
+ `| ${group.dimension} | ${group.issues.length} | ${group.agentCount} | ${group.threshold} | ℹ️ SOLO |`
787
+ );
788
+ }
789
+ lines.push("");
790
+
791
+ // Issue details
792
+ for (const group of corroborationResult.solo) {
793
+ lines.push(`### ${group.dimension} (${group.issues.length} issues)`);
794
+ lines.push("");
795
+ for (const {agent, issue} of group.issues) {
796
+ lines.push(`- **[${agent}]** ${issue.description || issue.issue || "No description"}`);
797
+ }
798
+ lines.push("");
799
+ }
800
+ }
801
+
802
+ // Unclassified issues
803
+ if (corroborationResult.unclassified.length > 0) {
804
+ lines.push("## Unclassified Issues (No Dimension)");
805
+ lines.push("");
806
+ for (const {agent, issue} of corroborationResult.unclassified) {
807
+ lines.push(`- **[${agent}]** ${issue.description || issue.issue || "No description"}`);
808
+ }
809
+ lines.push("");
810
+ }
811
+
812
+ // Summary
813
+ lines.push("## Summary");
814
+ lines.push("");
815
+ lines.push(`- **Blocking groups**: ${corroborationResult.blocking.length}`);
816
+ lines.push(`- **Solo findings**: ${corroborationResult.solo.length}`);
817
+ lines.push(`- **Unclassified**: ${corroborationResult.unclassified.length}`);
818
+ lines.push(`- **Final verdict**: ${corroborationResult.verdict}`);
819
+ lines.push("");
820
+ lines.push("**Threshold rule**: Issues in a dimension block when count ≥ 2× distinct agents in that dimension.");
821
+
822
+ return lines.join("\n");
823
+ }
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * CC-native state accessor for context state.json.
3
- * Deduplicates state access patterns for cc-native hooks.
3
+ * Deduplicates state access patterns from utils.py and suggest-fresh-perspective.py.
4
4
  * See cc-native-plan-review-spec.md §4.5
5
5
  */
6
6
 
7
7
  import type {
8
8
  CcNativeState,
9
- IterationState,
10
9
  PlanReviewState,
11
- QuestionsAskedState as _QuestionsAskedState,
10
+ QuestionsAskedState,
11
+ IterationState,
12
+ StuckDetectionState,
12
13
  } from "./types.js";
13
14
  import { logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
14
15
  import { nowIso } from "../../_shared/lib-ts/base/utils.js";
@@ -42,7 +43,6 @@ export function getCcNativeState(
42
43
  } catch {
43
44
  // Fail-safe: return null
44
45
  }
45
-
46
46
  return null;
47
47
  }
48
48
 
@@ -65,7 +65,6 @@ export function saveCcNativeState(
65
65
  } catch (error: unknown) {
66
66
  logWarn("utils", `Failed to save cc_native state: ${error}`);
67
67
  }
68
-
69
68
  return false;
70
69
  }
71
70
 
@@ -104,6 +103,22 @@ export function wasPlanPreviouslyDenied(
104
103
  return decision === "deny" || decision.startsWith("hook_deny");
105
104
  }
106
105
 
106
+ /**
107
+ * Get the last plan review state for this session.
108
+ * Returns null if no review state exists or plan hash doesn't match.
109
+ */
110
+ export function getLastPlanReview(
111
+ sessionId: string,
112
+ planHash: string,
113
+ projectRoot: string,
114
+ ): PlanReviewState | null {
115
+ const ccNative = getCcNativeState(sessionId, projectRoot);
116
+ if (!ccNative) return null;
117
+ const reviewState = ccNative.plan_review;
118
+ if (reviewState?.plan_hash !== planHash) return null;
119
+ return reviewState;
120
+ }
121
+
107
122
  /**
108
123
  * Mark this plan as reviewed (stores hash and decision in state.json).
109
124
  */
@@ -175,20 +190,69 @@ export function wasQuestionsAsked(
175
190
  }
176
191
 
177
192
  /**
178
- * Mark that AskUserQuestion was called. Returns true on success.
193
+ * Check if early questions (Phase A: AskUserQuestion) were asked this session.
194
+ * Returns false on any error (fail-safe: allow feature to work).
195
+ */
196
+ export function wasEarlyQuestionsAsked(
197
+ sessionId: string,
198
+ projectRoot: string,
199
+ ): boolean {
200
+ const ccNative = getCcNativeState(sessionId, projectRoot);
201
+ if (!ccNative) return false;
202
+ return ccNative.questions_asked?.early_questions_asked?.asked === true;
203
+ }
204
+
205
+ /**
206
+ * Check if plan questions agent (Phase B: independent question agent) ran this session.
207
+ * Returns false on any error (fail-safe: allow feature to work).
208
+ */
209
+ export function wasPlanQuestionsAgentAsked(
210
+ sessionId: string,
211
+ projectRoot: string,
212
+ ): boolean {
213
+ const ccNative = getCcNativeState(sessionId, projectRoot);
214
+ if (!ccNative) return false;
215
+ return ccNative.questions_asked?.plan_questions_agent_asked?.asked === true;
216
+ }
217
+
218
+ /**
219
+ * Mark that questions were asked in a specific phase. Returns true on success.
179
220
  * Only stores timestamp, no user data.
221
+ * @param phase - 'early' for Phase A (AskUserQuestion), 'agent' for Phase B (independent question agent)
180
222
  */
181
223
  export function markQuestionsAsked(
182
224
  sessionId: string,
183
225
  projectRoot: string,
226
+ phase: "early" | "agent",
184
227
  ): boolean {
185
228
  try {
186
229
  const ccNative = getCcNativeState(sessionId, projectRoot) ?? {};
230
+ const timestamp = nowIso();
187
231
 
188
- ccNative.questions_asked = {
189
- asked: true,
190
- asked_at: nowIso(),
191
- };
232
+ // Initialize questions_asked if it doesn't exist
233
+ if (!ccNative.questions_asked) {
234
+ ccNative.questions_asked = {
235
+ asked: false,
236
+ asked_at: "",
237
+ };
238
+ }
239
+
240
+ // Mark phase-specific gate
241
+ if (phase === "early") {
242
+ ccNative.questions_asked.early_questions_asked = {
243
+ asked: true,
244
+ asked_at: timestamp,
245
+ };
246
+ } else {
247
+ ccNative.questions_asked.plan_questions_agent_asked = {
248
+ asked: true,
249
+ asked_at: timestamp,
250
+ };
251
+ }
252
+
253
+ // Update backward-compatible fields
254
+ ccNative.questions_asked.asked = true;
255
+ ccNative.questions_asked.asked_at = timestamp;
192
256
 
193
257
  return saveCcNativeState(sessionId, projectRoot, ccNative);
194
258
  } catch (error: unknown) {
@@ -197,3 +261,36 @@ export function markQuestionsAsked(
197
261
  }
198
262
  }
199
263
 
264
+ // ---------------------------------------------------------------------------
265
+ // Stuck Detection State
266
+ // ---------------------------------------------------------------------------
267
+
268
+ /**
269
+ * Get stuck detection state from cc_native.
270
+ */
271
+ export function getStuckDetectionState(
272
+ sessionId: string,
273
+ projectRoot: string,
274
+ ): StuckDetectionState | null {
275
+ const ccNative = getCcNativeState(sessionId, projectRoot);
276
+ return ccNative?.stuck_detection ?? null;
277
+ }
278
+
279
+ /**
280
+ * Update stuck detection state.
281
+ */
282
+ export function updateStuckDetectionState(
283
+ sessionId: string,
284
+ projectRoot: string,
285
+ stuckState: StuckDetectionState,
286
+ ): boolean {
287
+ try {
288
+ const ccNative = getCcNativeState(sessionId, projectRoot) ?? {};
289
+ ccNative.stuck_detection = stuckState;
290
+ return saveCcNativeState(sessionId, projectRoot, ccNative);
291
+ } catch (error: unknown) {
292
+ logWarn("utils", `Failed to update stuck detection state: ${error}`);
293
+ return false;
294
+ }
295
+ }
296
+
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Unified Claude CLI output parser.
3
- * Deduplicates identical logic from orchestrator.py and reviewers/agent.py.
3
+ * Deduplicates identical logic from orchestrator.ts and reviewers/agent.ts.
4
4
  * See cc-native-plan-review-spec.md §4.6
5
5
  */
6
6
 
@@ -89,7 +89,7 @@ export function computeCorroboratedDecision(
89
89
  const agentCount = group.agentNames.size;
90
90
  const threshold = 2 * agentCount;
91
91
 
92
- if (group.issues.length > threshold) {
92
+ if (group.issues.length >= threshold) {
93
93
  blocking.push({
94
94
  dimension,
95
95
  issues: group.issues,
@@ -110,6 +110,10 @@ export function computeCorroboratedDecision(
110
110
  blocking,
111
111
  solo,
112
112
  unclassified,
113
- verdict: blocking.length > 0 ? "fail" : "pass",
113
+ verdict: blocking.length > 0
114
+ ? "fail"
115
+ : solo.length > 0
116
+ ? "warn"
117
+ : "pass",
114
118
  };
115
119
  }
@@ -64,11 +64,7 @@ export { buildOrchestratorSchema, runOrchestrator } from "./orchestrator.js";
64
64
  // Reviewers
65
65
  export {
66
66
  AgentReviewer,
67
- CodexReviewer,
68
- GeminiReviewer,
69
67
  runAgentReview,
70
- runCodexReview,
71
- runGeminiReview,
72
68
  } from "./reviewers/index.js";
73
69
 
74
70
  // Iteration state