aiwcli 0.12.7 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/commands/clean.d.ts +7 -0
  2. package/dist/commands/clean.js +17 -8
  3. package/dist/commands/clear.d.ts +85 -0
  4. package/dist/commands/clear.js +455 -347
  5. package/dist/commands/init/index.d.ts +15 -0
  6. package/dist/commands/init/index.js +79 -38
  7. package/dist/lib/gitignore-manager.js +12 -13
  8. package/dist/lib/settings-hierarchy.d.ts +13 -1
  9. package/dist/lib/settings-hierarchy.js +1 -1
  10. package/dist/lib/template-linter.d.ts +4 -0
  11. package/dist/lib/template-linter.js +1 -1
  12. package/dist/lib/tty-detection.d.ts +1 -0
  13. package/dist/lib/tty-detection.js +1 -0
  14. package/dist/templates/CLAUDE.md +27 -0
  15. package/dist/templates/_shared/.claude/settings.json +7 -7
  16. package/dist/templates/_shared/.claude/{commands/handoff.md → skills/handoff/SKILL.md} +4 -3
  17. package/dist/templates/_shared/.claude/{commands/handoff-resume.md → skills/handoff-resume/SKILL.md} +3 -2
  18. package/dist/templates/_shared/.claude/skills/meta-plan/SKILL.md +43 -0
  19. package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
  20. package/dist/templates/_shared/.codex/workflows/meta-plan.md +347 -0
  21. package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
  22. package/dist/templates/_shared/.windsurf/workflows/meta-plan.md +347 -0
  23. package/dist/templates/_shared/hooks-ts/lint_after_edit.ts +59 -0
  24. package/dist/templates/_shared/hooks-ts/session_end.ts +11 -10
  25. package/dist/templates/_shared/hooks-ts/session_start.ts +15 -12
  26. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -12
  27. package/dist/templates/_shared/lib-ts/CLAUDE.md +3 -3
  28. package/dist/templates/_shared/lib-ts/base/constants.ts +324 -306
  29. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +26 -7
  30. package/dist/templates/_shared/lib-ts/base/inference.ts +19 -19
  31. package/dist/templates/_shared/lib-ts/base/lint-dispatch.ts +287 -0
  32. package/dist/templates/_shared/lib-ts/base/state-io.ts +4 -3
  33. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +3 -3
  34. package/dist/templates/_shared/lib-ts/context/CLAUDE.md +134 -0
  35. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +16 -15
  36. package/dist/templates/_shared/lib-ts/context/context-selector.ts +16 -16
  37. package/dist/templates/_shared/lib-ts/context/context-store.ts +15 -14
  38. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +2 -2
  39. package/dist/templates/_shared/scripts/resolve-run.ts +61 -0
  40. package/dist/templates/_shared/scripts/resolve_context.ts +1 -1
  41. package/dist/templates/_shared/scripts/status_line.ts +100 -94
  42. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/CLAUDE.md +433 -421
  43. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/document-generator.ts +5 -4
  44. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/lib/handoff-reader.ts +2 -1
  45. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/resume_handoff.ts +6 -6
  46. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/scripts/save_handoff.ts +16 -17
  47. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff-resume.md +2 -2
  48. package/dist/templates/_shared/{handoff-system → skills/handoff-system}/workflows/handoff.md +3 -3
  49. package/dist/templates/_shared/skills/meta-plan/CLAUDE.md +44 -0
  50. package/dist/templates/_shared/skills/meta-plan/workflows/meta-plan.md +347 -0
  51. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +1 -1
  52. package/dist/templates/cc-native/.claude/settings.json +86 -57
  53. package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +64 -0
  54. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/format.ts +599 -597
  55. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/index.ts +26 -26
  56. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/tracker.ts +107 -106
  57. package/dist/templates/cc-native/_cc-native/{lib-ts/artifacts → artifacts/lib}/write.ts +119 -118
  58. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +237 -247
  59. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +76 -74
  60. package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +76 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +163 -156
  62. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +15 -16
  63. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +116 -116
  64. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +3 -3
  65. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +16 -12
  66. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +2 -3
  67. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +31 -31
  68. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +7 -6
  69. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +9 -7
  70. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +17 -14
  71. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +41 -37
  72. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +43 -33
  73. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +20 -20
  74. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +9 -8
  75. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +4 -3
  76. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +9 -10
  77. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +20 -19
  78. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +329 -329
  79. package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +149 -0
  80. package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +143 -0
  81. package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +213 -0
  82. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  83. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +62 -0
  84. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +61 -0
  85. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +62 -0
  86. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +56 -0
  87. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +53 -0
  88. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +66 -0
  89. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +70 -0
  90. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +62 -0
  91. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +72 -0
  92. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +61 -0
  93. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +64 -0
  94. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +56 -0
  95. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +86 -0
  96. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +59 -0
  97. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +58 -0
  98. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +66 -0
  99. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +62 -0
  100. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +66 -0
  101. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +71 -0
  102. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +74 -0
  103. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +77 -0
  104. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +62 -0
  105. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +68 -0
  106. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +61 -0
  107. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +71 -0
  108. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +61 -0
  109. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +61 -0
  110. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +67 -0
  111. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +65 -0
  112. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +74 -0
  113. package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +69 -0
  114. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/agent-selection.ts +162 -163
  115. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/corroboration.ts +119 -119
  116. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/graduation.ts +132 -132
  117. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/orchestrator.ts +70 -70
  118. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/output-builder.ts +121 -130
  119. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/plan-questions.ts +101 -102
  120. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/review-pipeline.ts +507 -511
  121. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/agent.ts +73 -74
  122. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/base/base-agent.ts +217 -217
  123. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/index.ts +12 -12
  124. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/claude-agent.ts +66 -66
  125. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/codex-agent.ts +185 -185
  126. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/gemini-agent.ts +39 -39
  127. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/providers/orchestrator-claude-agent.ts +196 -196
  128. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/schemas.ts +201 -201
  129. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/reviewers/types.ts +23 -23
  130. package/dist/templates/cc-native/_cc-native/{lib-ts → plan-review/lib}/verdict.ts +72 -72
  131. package/dist/templates/cc-native/_cc-native/{workflows → plan-review/workflows}/specdev.md +9 -9
  132. package/oclif.manifest.json +1 -1
  133. package/package.json +6 -5
  134. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +0 -21
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * PreToolUse:Task Hook: Prompt Validation Gate
4
+ *
5
+ * Validates that Task tool calls have self-contained, goal-oriented prompts.
6
+ * Short-circuits immediately for resume calls (no prompt to validate).
7
+ *
8
+ * Design:
9
+ * - Resume calls (tool_input.resume present) → allow through, no inference
10
+ * - Missing/empty prompt → allow through (fail open)
11
+ * - Non-empty prompt → run AI validation via inference(), block if ok:false
12
+ */
13
+
14
+ import {
15
+ loadHookInput,
16
+ runHook,
17
+ logInfo,
18
+ logDebug,
19
+ logWarn,
20
+ emitContextAndBlock,
21
+ getToolInput,
22
+ } from "../../_shared/lib-ts/base/hook-utils.js";
23
+ import { inference } from "../../_shared/lib-ts/base/inference.js";
24
+ import { isInternalCall } from "../../_shared/lib-ts/base/subprocess-utils.js";
25
+
26
+ const VALIDATION_SYSTEM_PROMPT = `The sub-agent receives ONLY the prompt text — no conversation history, no prior context.
27
+
28
+ Check 1 — Dangling References: Does the prompt use pronouns or demonstratives that ONLY make sense with prior conversation? Violations: 'the file we looked at', 'as discussed above', 'that approach we chose', 'the error from earlier', 'fix the issue mentioned above'. NOT violations: relative paths ('_output/', 'src/lib/'), search terms ('context-manager', 'auth module'), directory exploration ('find files matching X'), tool names, or any concrete noun — even if imprecise. Only flag references that are truly UNRESOLVABLE without conversation history.
29
+
30
+ Check 2 — Implicit Contract: Does the prompt have ANY discernible goal? 'Explore the _output directory and find context files' IS a clear goal. 'Search for hooks that handle Task events' IS a clear goal. 'Read and summarize all files in X' IS a clear goal. Only flag if the prompt is truly goalless — e.g., a sentence fragment with no verb, or pure context with no request.
31
+
32
+ If both checks pass, return ok:true. When in doubt, pass — false negatives (letting a vague prompt through) are far less costly than false positives (blocking legitimate work).
33
+
34
+ When returning ok:false, end your response with: 'Retry: Re-invoke the Task tool with a revised prompt that resolves the issues above.'`;
35
+
36
+ function main(): void {
37
+ if (isInternalCall()) return;
38
+
39
+ const payload = loadHookInput();
40
+ if (!payload) return;
41
+
42
+ const toolInput = getToolInput(payload);
43
+
44
+ // Resume calls: tool_input contains `resume: "<agent-id>"` (may also have prompt/description).
45
+ // The resume field being present is the authoritative discriminator — skip all validation.
46
+ if (toolInput?.resume) {
47
+ logInfo("validate_task_prompt", `Resume call detected (agent: ${toolInput.resume}) — skipping validation`);
48
+ return;
49
+ }
50
+
51
+ const prompt: string = toolInput?.prompt ?? "";
52
+ if (!prompt.trim()) {
53
+ logDebug("validate_task_prompt", "No prompt field — allowing through (fail open)");
54
+ return;
55
+ }
56
+
57
+ logInfo("validate_task_prompt", `Validating Task prompt (${prompt.length} chars)`);
58
+ const result = inference(VALIDATION_SYSTEM_PROMPT, prompt, "fast");
59
+
60
+ if (!result.success) {
61
+ logWarn("validate_task_prompt", `inference() failed (${result.error}) — failing open`);
62
+ return;
63
+ }
64
+
65
+ const {output} = result;
66
+ if (output.includes("ok:false")) {
67
+ // Extract the Retry line if present; fall back to full output
68
+ const retryMatch = output.match(/Retry:.*$/m);
69
+ const retryMessage = retryMatch ? retryMatch[0] : "Retry: Re-invoke the Task tool with a revised, self-contained prompt.";
70
+ logInfo("validate_task_prompt", "Blocking Task — prompt failed validation");
71
+ emitContextAndBlock(output, retryMessage);
72
+ }
73
+ // ok:true or anything else → allow through (no output = no block)
74
+ }
75
+
76
+ runHook(main, "validate_task_prompt");
@@ -1,156 +1,163 @@
1
- /**
2
- * Agent frontmatter parser — discovers and loads agent configs from markdown files.
3
- * See cc-native-plan-review-spec.md §4.14
4
- */
5
-
6
- import * as fs from "node:fs";
7
- import * as path from "node:path";
8
-
9
- import type { AgentConfig } from "./types.js";
10
- import { logDebug, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
11
-
12
- /**
13
- * Extract simple YAML frontmatter from markdown content.
14
- * Only handles flat key: value pairs (no nested YAML).
15
- */
16
- export function extractFrontmatter(
17
- content: string,
18
- ): Record<string, unknown> | null {
19
- const lines = content.split(/\r?\n/);
20
- if (lines[0]?.trim() !== "---") return null;
21
-
22
- const frontmatterLines: string[] = [];
23
- let endIndex = -1;
24
-
25
- for (let i = 1; i < lines.length; i++) {
26
- if (lines[i]?.trim() === "---") {
27
- endIndex = i;
28
- break;
29
- }
30
- const line = lines[i];
31
- if (line !== undefined) {
32
- frontmatterLines.push(line);
33
- }
34
- }
35
-
36
- if (endIndex === -1) return null;
37
-
38
- const result: Record<string, unknown> = {};
39
- for (const line of frontmatterLines) {
40
- const colonIdx = line.indexOf(":");
41
- if (colonIdx === -1) continue;
42
-
43
- const key = line.slice(0, colonIdx).trim();
44
- let value: unknown = line.slice(colonIdx + 1).trim();
45
-
46
- // Handle arrays: [item1, item2]
47
- if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
48
- value = value
49
- .slice(1, -1)
50
- .split(",")
51
- .map((s) => s.trim().replaceAll(/^["']|["']$/g, ""))
52
- .filter(Boolean);
53
- }
54
- // Handle booleans
55
- else if (value === "true") value = true;
56
- else if (value === "false") value = false;
57
- // Handle quoted strings
58
- else if (
59
- typeof value === "string" &&
60
- ((value.startsWith('"') && value.endsWith('"')) ||
61
- (value.startsWith("'") && value.endsWith("'")))
62
- ) {
63
- value = value.slice(1, -1);
64
- }
65
-
66
- if (key) result[key] = value;
67
- }
68
-
69
- return result;
70
- }
71
-
72
- /**
73
- * Extract markdown body after frontmatter.
74
- */
75
- export function extractBody(content: string): string {
76
- const lines = content.split(/\r?\n/);
77
- if (lines[0]?.trim() !== "---") return content;
78
-
79
- for (let i = 1; i < lines.length; i++) {
80
- if (lines[i]?.trim() === "---") {
81
- return lines
82
- .slice(i + 1)
83
- .join("\n")
84
- .trim();
85
- }
86
- }
87
-
88
- return content;
89
- }
90
-
91
- /**
92
- * Discover and load all agent configs from a directory of markdown files.
93
- * Skips the plan-orchestrator agent. Defaults categories to ["code"].
94
- *
95
- * @param agentsDir - Path to agents directory (default: _cc-native/agents/)
96
- * @returns Array of AgentConfig objects
97
- */
98
- export function aggregateAgents(agentsDir?: string): AgentConfig[] {
99
- const dir = agentsDir ?? path.join("_cc-native", "agents");
100
-
101
- if (!fs.existsSync(dir)) {
102
- logWarn("aggregate", `Agents directory not found: ${dir}`);
103
- return [];
104
- }
105
-
106
- const files = fs
107
- .readdirSync(dir)
108
- .filter((f) => f.endsWith(".md"))
109
- .sort();
110
-
111
- const agents: AgentConfig[] = [];
112
-
113
- for (const file of files) {
114
- const filePath = path.join(dir, file);
115
- let content: string;
116
- try {
117
- content = fs.readFileSync(filePath, "utf-8");
118
- } catch (error: unknown) {
119
- logWarn("aggregate", `Failed to read ${file}: ${error}`);
120
- continue;
121
- }
122
-
123
- const fm = extractFrontmatter(content);
124
- if (!fm) {
125
- logDebug("aggregate", `No frontmatter in ${file}, skipping`);
126
- continue;
127
- }
128
-
129
- const name = (fm.name as string) ?? path.basename(file, ".md");
130
-
131
- // Skip the plan orchestrator — it's not a reviewer
132
- if (name === "plan-orchestrator") {
133
- logDebug("aggregate", `Skipping plan-orchestrator agent`);
134
- continue;
135
- }
136
-
137
- const agent: AgentConfig = {
138
- name,
139
- model: (fm.model as string) ?? "sonnet",
140
- provider: "claude", // Default; overwritten by assignModelsToAgents() at runtime
141
- focus: (fm.focus as string) ?? "",
142
- categories: Array.isArray(fm.categories)
143
- ? (fm.categories as string[])
144
- : ["code"],
145
- description: (fm.description as string) ?? "",
146
- system_prompt: extractBody(content),
147
- };
148
-
149
- agents.push(agent);
150
- logDebug("aggregate", `Loaded agent: ${agent.name} [${agent.categories.join(", ")}]`);
151
- }
152
-
153
- logInfo("aggregate", `Loaded ${agents.length} agents from ${dir}`);
154
- return agents;
155
- }
156
-
1
+ /**
2
+ * Agent frontmatter parser — discovers and loads agent configs from markdown files.
3
+ * See cc-native-plan-review-spec.md §4.14
4
+ *
5
+ * NOTE: This file intentionally stays in lib-ts/ rather than plan-review/lib/.
6
+ * Both settings.ts (shared cc-native infra) and plan-questions.ts (plan-review) import it.
7
+ * Moving it to plan-review/ would create a forbidden backward dependency: lib-ts → plan-review.
8
+ * Do not move this file without first moving settings.ts out of lib-ts/.
9
+ */
10
+
11
+ import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
+
14
+ import type { AgentConfig } from "./types.js";
15
+ import { logDebug, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
16
+
17
+ /**
18
+ * Extract simple YAML frontmatter from markdown content.
19
+ * Only handles flat key: value pairs (no nested YAML).
20
+ */
21
+ export function extractFrontmatter(
22
+ content: string,
23
+ ): Record<string, unknown> | null {
24
+ const lines = content.split(/\r?\n/);
25
+ if (lines[0]?.trim() !== "---") return null;
26
+
27
+ const frontmatterLines: string[] = [];
28
+ let endIndex = -1;
29
+
30
+ for (let i = 1; i < lines.length; i++) {
31
+ if (lines[i]?.trim() === "---") {
32
+ endIndex = i;
33
+ break;
34
+ }
35
+ const line = lines[i];
36
+ if (line !== undefined) {
37
+ frontmatterLines.push(line);
38
+ }
39
+ }
40
+
41
+ if (endIndex === -1) return null;
42
+
43
+ const result: Record<string, unknown> = {};
44
+ for (const line of frontmatterLines) {
45
+ const colonIdx = line.indexOf(":");
46
+ if (colonIdx === -1) continue;
47
+
48
+ const key = line.slice(0, colonIdx).trim();
49
+ let value: unknown = line.slice(colonIdx + 1).trim();
50
+
51
+ // Handle arrays: [item1, item2]
52
+ if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
53
+ value = value
54
+ .slice(1, -1)
55
+ .split(",")
56
+ .map((s) => s.trim().replaceAll(/^["']|["']$/g, ""))
57
+ .filter(Boolean);
58
+ }
59
+ // Handle booleans
60
+ else if (value === "true") value = true;
61
+ else if (value === "false") value = false;
62
+ // Handle quoted strings
63
+ else if (
64
+ typeof value === "string" &&
65
+ ((value.startsWith('"') && value.endsWith('"')) ||
66
+ (value.startsWith("'") && value.endsWith("'")))
67
+ ) {
68
+ value = value.slice(1, -1);
69
+ }
70
+
71
+ if (key) result[key] = value;
72
+ }
73
+
74
+ return result;
75
+ }
76
+
77
+ /**
78
+ * Extract markdown body after frontmatter.
79
+ */
80
+ export function extractBody(content: string): string {
81
+ const lines = content.split(/\r?\n/);
82
+ if (lines[0]?.trim() !== "---") return content;
83
+
84
+ for (let i = 1; i < lines.length; i++) {
85
+ if (lines[i]?.trim() === "---") {
86
+ return lines
87
+ .slice(i + 1)
88
+ .join("\n")
89
+ .trim();
90
+ }
91
+ }
92
+
93
+ return content;
94
+ }
95
+
96
+ /**
97
+ * Discover and load all agent configs from a directory of markdown files.
98
+ * Skips the plan-orchestrator agent. Defaults categories to ["code"].
99
+ *
100
+ * @param agentsDir - Path to agents directory. Callers must pass an explicit path.
101
+ * Known call sites: settings.ts → plan-review/agents/plan-review,
102
+ * plan-questions.ts plan-review/agents/plan-questions
103
+ * @returns Array of AgentConfig objects
104
+ */
105
+ export function aggregateAgents(agentsDir?: string): AgentConfig[] {
106
+ const dir = agentsDir ?? path.join("_cc-native", "plan-review", "agents");
107
+
108
+ if (!fs.existsSync(dir)) {
109
+ logWarn("aggregate", `Agents directory not found: ${dir}`);
110
+ return [];
111
+ }
112
+
113
+ const files = fs
114
+ .readdirSync(dir)
115
+ .filter((f) => f.endsWith(".md"))
116
+ .sort();
117
+
118
+ const agents: AgentConfig[] = [];
119
+
120
+ for (const file of files) {
121
+ const filePath = path.join(dir, file);
122
+ let content: string;
123
+ try {
124
+ content = fs.readFileSync(filePath, "utf-8");
125
+ } catch (error: unknown) {
126
+ logWarn("aggregate", `Failed to read ${file}: ${error}`);
127
+ continue;
128
+ }
129
+
130
+ const fm = extractFrontmatter(content);
131
+ if (!fm) {
132
+ logDebug("aggregate", `No frontmatter in ${file}, skipping`);
133
+ continue;
134
+ }
135
+
136
+ const name = (fm.name as string) ?? path.basename(file, ".md");
137
+
138
+ // Skip the plan orchestrator — it's not a reviewer
139
+ if (name === "plan-orchestrator") {
140
+ logDebug("aggregate", `Skipping plan-orchestrator agent`);
141
+ continue;
142
+ }
143
+
144
+ const agent: AgentConfig = {
145
+ name,
146
+ model: (fm.model as string) ?? "sonnet",
147
+ provider: "claude", // Default; overwritten by assignModelsToAgents() at runtime
148
+ focus: (fm.focus as string) ?? "",
149
+ categories: Array.isArray(fm.categories)
150
+ ? (fm.categories as string[])
151
+ : ["code"],
152
+ description: (fm.description as string) ?? "",
153
+ system_prompt: extractBody(content),
154
+ };
155
+
156
+ agents.push(agent);
157
+ logDebug("aggregate", `Loaded agent: ${agent.name} [${agent.categories.join(", ")}]`);
158
+ }
159
+
160
+ logInfo("aggregate", `Loaded ${agents.length} agents from ${dir}`);
161
+ return agents;
162
+ }
163
+
@@ -4,16 +4,15 @@
4
4
  * See cc-native-plan-review-spec.md §4.5
5
5
  */
6
6
 
7
- import { getContextBySessionId, saveState } from "../../_shared/lib-ts/context/context-store.js";
8
- import { logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
9
- import { nowIso } from "../../_shared/lib-ts/base/utils.js";
10
7
  import type {
11
8
  CcNativeState,
12
9
  PlanReviewState,
13
- QuestionsAskedState,
14
10
  IterationState,
15
11
  StuckDetectionState,
16
12
  } from "./types.js";
13
+ import { logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
14
+ import { nowIso } from "../../_shared/lib-ts/base/utils.js";
15
+ import { getContextBySessionId, saveState } from "../../_shared/lib-ts/context/context-store.js";
17
16
  import type { ContextState } from "../../_shared/lib-ts/types.js";
18
17
 
19
18
  /**
@@ -62,8 +61,8 @@ export function saveCcNativeState(
62
61
  saveState(state.id, state, projectRoot);
63
62
  return true;
64
63
  }
65
- } catch (e: unknown) {
66
- logWarn("utils", `Failed to save cc_native state: ${e}`);
64
+ } catch (error: unknown) {
65
+ logWarn("utils", `Failed to save cc_native state: ${error}`);
67
66
  }
68
67
  return false;
69
68
  }
@@ -145,9 +144,9 @@ export function markPlanReviewed(
145
144
  max: iterationState.max ?? 1,
146
145
  complexity: iterationState.complexity ?? "unknown",
147
146
  };
148
- const history = iterationState.history;
147
+ const {history} = iterationState;
149
148
  if (history && history.length > 0) {
150
- const lastEntry = history[history.length - 1];
149
+ const lastEntry = history.at(-1);
151
150
  if (lastEntry) {
152
151
  reviewData.iteration.latest_verdict = lastEntry.verdict ?? "unknown";
153
152
  }
@@ -167,8 +166,8 @@ export function markPlanReviewed(
167
166
  `Failed to save plan review state for session ${sessionId}`,
168
167
  );
169
168
  }
170
- } catch (e: unknown) {
171
- logWarn(hookName, `Failed to mark plan reviewed: ${e}`);
169
+ } catch (error: unknown) {
170
+ logWarn(hookName, `Failed to mark plan reviewed: ${error}`);
172
171
  }
173
172
  }
174
173
 
@@ -255,8 +254,8 @@ export function markQuestionsAsked(
255
254
  ccNative.questions_asked.asked_at = timestamp;
256
255
 
257
256
  return saveCcNativeState(sessionId, projectRoot, ccNative);
258
- } catch (e: unknown) {
259
- logWarn("utils", `Failed to mark questions asked: ${e}`);
257
+ } catch (error: unknown) {
258
+ logWarn("utils", `Failed to mark questions asked: ${error}`);
260
259
  return false;
261
260
  }
262
261
  }
@@ -278,8 +277,8 @@ export function resetPlanQuestionsAsked(
278
277
  };
279
278
  }
280
279
  return saveCcNativeState(sessionId, projectRoot, ccNative);
281
- } catch (e: unknown) {
282
- logWarn("utils", `Failed to reset plan questions asked: ${e}`);
280
+ } catch (error: unknown) {
281
+ logWarn("utils", `Failed to reset plan questions asked: ${error}`);
283
282
  return false;
284
283
  }
285
284
  }
@@ -311,8 +310,8 @@ export function updateStuckDetectionState(
311
310
  const ccNative = getCcNativeState(sessionId, projectRoot) ?? {};
312
311
  ccNative.stuck_detection = stuckState;
313
312
  return saveCcNativeState(sessionId, projectRoot, ccNative);
314
- } catch (e: unknown) {
315
- logWarn("utils", `Failed to update stuck detection state: ${e}`);
313
+ } catch (error: unknown) {
314
+ logWarn("utils", `Failed to update stuck detection state: ${error}`);
316
315
  return false;
317
316
  }
318
317
  }