aiwcli 0.13.8 → 0.15.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 (47) hide show
  1. package/README.md +11 -1
  2. package/dist/commands/launch.d.ts +8 -0
  3. package/dist/commands/launch.js +96 -5
  4. package/dist/templates/_shared/.claude/skills/codex/SKILL.md +42 -0
  5. package/dist/templates/_shared/.claude/skills/codex/prompt.md +30 -0
  6. package/dist/templates/_shared/lib-ts/agent-exec/backends/headless.ts +33 -0
  7. package/dist/templates/_shared/lib-ts/agent-exec/backends/index.ts +6 -0
  8. package/dist/templates/_shared/lib-ts/agent-exec/backends/tmux.ts +145 -0
  9. package/dist/templates/_shared/lib-ts/agent-exec/base-agent.ts +229 -0
  10. package/dist/templates/_shared/lib-ts/agent-exec/execution-backend.ts +50 -0
  11. package/dist/templates/_shared/lib-ts/agent-exec/index.ts +6 -0
  12. package/dist/templates/_shared/lib-ts/agent-exec/structured-output.ts +166 -0
  13. package/dist/templates/_shared/lib-ts/base/cli-args.ts +287 -0
  14. package/dist/templates/_shared/lib-ts/base/inference.ts +53 -47
  15. package/dist/templates/_shared/lib-ts/base/models.ts +16 -0
  16. package/dist/templates/_shared/lib-ts/base/preflight.ts +98 -0
  17. package/dist/templates/_shared/lib-ts/base/state-io.ts +1 -1
  18. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +4 -3
  19. package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +381 -0
  20. package/dist/templates/_shared/lib-ts/base/utils.ts +8 -0
  21. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +35 -11
  22. package/dist/templates/_shared/lib-ts/context/context-store.ts +3 -0
  23. package/dist/templates/_shared/lib-ts/types.ts +17 -0
  24. package/dist/templates/_shared/scripts/status_line.ts +93 -47
  25. package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +71 -0
  26. package/dist/templates/_shared/skills/prompt-codex/scripts/launch-codex.ts +387 -0
  27. package/dist/templates/_shared/skills/prompt-codex/scripts/watch-codex.ts +257 -0
  28. package/dist/templates/cc-native/.claude/settings.json +121 -1
  29. package/dist/templates/cc-native/_cc-native/CLAUDE.md +73 -0
  30. package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +70 -0
  31. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +9 -133
  32. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +120 -43
  33. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +1 -0
  34. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +66 -12
  35. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +5 -4
  36. package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +4 -4
  37. package/dist/templates/cc-native/_cc-native/plan-review/lib/preflight.ts +14 -80
  38. package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +16 -13
  39. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/agent.ts +19 -7
  40. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +4 -215
  41. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/index.ts +1 -1
  42. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/claude-agent.ts +9 -39
  43. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/codex-agent.ts +19 -22
  44. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/gemini-agent.ts +2 -1
  45. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +65 -36
  46. package/oclif.manifest.json +21 -3
  47. package/package.json +1 -1
@@ -1,6 +1,23 @@
1
1
  {
2
+ "statusLine": {
3
+ "type": "command",
4
+ "command": "NO_COLOR= FORCE_COLOR=2 bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/scripts/status_line.ts"
5
+ },
6
+ "fileSuggestion": {
7
+ "type": "command",
8
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/file-suggestion.ts"
9
+ },
2
10
  "hooks": {
3
11
  "UserPromptSubmit": [
12
+ {
13
+ "hooks": [
14
+ {
15
+ "type": "command",
16
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/user_prompt_submit.ts",
17
+ "timeout": 10000
18
+ }
19
+ ]
20
+ },
4
21
  {
5
22
  "hooks": [
6
23
  {
@@ -12,6 +29,56 @@
12
29
  }
13
30
  ],
14
31
  "PostToolUse": [
32
+ {
33
+ "matcher": "*",
34
+ "hooks": [
35
+ {
36
+ "type": "command",
37
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/context_monitor.ts",
38
+ "timeout": 5000
39
+ }
40
+ ]
41
+ },
42
+ {
43
+ "matcher": "TaskCreate",
44
+ "hooks": [
45
+ {
46
+ "type": "command",
47
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/task_create_capture.ts",
48
+ "timeout": 3000
49
+ }
50
+ ]
51
+ },
52
+ {
53
+ "matcher": "TaskUpdate",
54
+ "hooks": [
55
+ {
56
+ "type": "command",
57
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/task_update_capture.ts",
58
+ "timeout": 3000
59
+ }
60
+ ]
61
+ },
62
+ {
63
+ "matcher": "ExitPlanMode",
64
+ "hooks": [
65
+ {
66
+ "type": "command",
67
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/archive_plan.ts",
68
+ "timeout": 5000
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ "matcher": "Write|Edit",
74
+ "hooks": [
75
+ {
76
+ "type": "command",
77
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/lint_after_edit.ts",
78
+ "timeout": 10000
79
+ }
80
+ ]
81
+ },
15
82
  {
16
83
  "matcher": "AskUserQuestion",
17
84
  "hooks": [
@@ -44,6 +111,51 @@
44
111
  ]
45
112
  }
46
113
  ],
114
+ "SessionStart": [
115
+ {
116
+ "hooks": [
117
+ {
118
+ "type": "command",
119
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/session_start.ts",
120
+ "timeout": 5000
121
+ }
122
+ ]
123
+ }
124
+ ],
125
+ "SessionEnd": [
126
+ {
127
+ "hooks": [
128
+ {
129
+ "type": "command",
130
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/session_end.ts",
131
+ "timeout": 5000
132
+ }
133
+ ]
134
+ }
135
+ ],
136
+ "PreCompact": [
137
+ {
138
+ "hooks": [
139
+ {
140
+ "type": "command",
141
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/pre_compact.ts",
142
+ "timeout": 5000
143
+ }
144
+ ]
145
+ }
146
+ ],
147
+ "PermissionRequest": [
148
+ {
149
+ "matcher": "ExitPlanMode",
150
+ "hooks": [
151
+ {
152
+ "type": "command",
153
+ "command": "bun ~/.aiwcli/bin/resolve-run.ts .aiwcli/_shared/hooks-ts/archive_plan.ts",
154
+ "timeout": 5000
155
+ }
156
+ ]
157
+ }
158
+ ],
47
159
  "PreToolUse": [
48
160
  {
49
161
  "matcher": "^TaskCreate$",
@@ -83,5 +195,13 @@
83
195
  "env": {
84
196
  "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
85
197
  },
86
- "enabledPlugins": {}
198
+ "enabledPlugins": {},
199
+ "methods": {
200
+ "cc-native": {
201
+ "ides": [
202
+ "claude"
203
+ ],
204
+ "installedAt": "2026-02-21T16:59:33.520Z"
205
+ }
206
+ }
87
207
  }
@@ -0,0 +1,73 @@
1
+ # CC-Native Method
2
+
3
+ **Location:** `.aiwcli/_cc-native/` — Claude Code-specific plan review, artifacts, and agent orchestration.
4
+
5
+ ---
6
+
7
+ ## Subsystems
8
+
9
+ | Directory | Purpose | CLAUDE.md |
10
+ |-----------|---------|-----------|
11
+ | `agents/` | Plan review agent roster and specs | `agents/CLAUDE.md` |
12
+ | `artifacts/` | Review artifact generation and tracking | `artifacts/CLAUDE.md` |
13
+ | `hooks/` | CC-native hook entry points (plan review triggers) | `hooks/CLAUDE.md` |
14
+ | `lib-ts/` | Shared TypeScript library for cc-native subsystems | `lib-ts/CLAUDE.md` |
15
+ | `lib-ts/rlm/` | Retrieval-augmented learning memory | `lib-ts/rlm/CLAUDE.md` |
16
+ | `plan-review/` | Multi-agent plan review pipeline | `plan-review/CLAUDE.md` |
17
+
18
+ ---
19
+
20
+ ## Shared Infrastructure (`_shared/lib-ts/`)
21
+
22
+ CC-native code depends heavily on shared infrastructure. Full API details: `_shared/lib-ts/CLAUDE.md`.
23
+
24
+ | Module | Capability | Use When |
25
+ |--------|-----------|----------|
26
+ | `base/hook-utils` | Hook lifecycle (load input, run, emit context) | Writing hooks |
27
+ | `base/logger` | Structured logging (debug/info/warn/error) | Any hook or lib module |
28
+ | `base/constants` | Project paths, context dirs, sanitization | Resolving file locations |
29
+ | `base/subprocess-utils` | Find executables, exec with env, shell quoting | Spawning agent CLIs |
30
+ | `base/cli-args` | CLI invocation builder, review spec construction | Launching review agents |
31
+ | `base/atomic-write` | Crash-safe file writes | Writing state or artifacts |
32
+ | `base/state-io` | State read/write helpers | Context state persistence |
33
+ | `base/inference` | Claude CLI subprocess calls | AI inference from hooks |
34
+ | `context/context-store` | Context CRUD (get by session, list all) | Session/context binding |
35
+ | `context/plan-manager` | Plan lifecycle (archive, hash, sign) | Plan discovery and hashing |
36
+ | `types` | Shared type definitions (`ContextState`, etc.) | Type imports |
37
+
38
+ ---
39
+
40
+ ## Import Patterns
41
+
42
+ **Import direction:** `hooks/` → `lib-ts/` → `_shared/lib-ts/`. Never the reverse.
43
+
44
+ ```typescript
45
+ // From hooks/ (2 levels up to _shared):
46
+ import { runHook, logInfo } from "../../_shared/lib-ts/base/hook-utils.js";
47
+ import { loadConfig } from "../lib-ts/config.js";
48
+
49
+ // From lib-ts/ (2 levels up to _shared):
50
+ import { logDebug } from "../../_shared/lib-ts/base/logger.js";
51
+ import { atomicWrite } from "../../_shared/lib-ts/base/atomic-write.js";
52
+
53
+ // From plan-review/lib/ (3 levels up to _shared):
54
+ import { logInfo } from "../../../_shared/lib-ts/base/logger.js";
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Context Maintenance
60
+
61
+ **After modifying files in this directory:** scan the entries above — if any claim is now
62
+ false or incomplete, update this file before ending the task. Do not defer.
63
+
64
+ **Add** an entry only if an agent would fail without knowing it, it is not obvious from
65
+ the code, and it belongs at this scope.
66
+
67
+ **Remove** any entry that fails the falsifiability test: if removing it would not change
68
+ how an agent acts here, remove it.
69
+
70
+ **Staleness anchor:** This file assumes `lib-ts/index.ts` exists. If it doesn't, this file
71
+ is stale — update or regenerate before relying on it.
72
+
73
+ <!-- context-layer: generated=2026-03-01 | last-audited=2026-03-01 | version=1 | dir-commits-at-audit=15 -->
@@ -0,0 +1,70 @@
1
+ # CC-Native Library
2
+
3
+ **Location:** `_cc-native/lib-ts/` — TypeScript modules for plan review, state, and agent orchestration.
4
+
5
+ **Import direction:** `hooks/` → `lib-ts/` → `_shared/lib-ts/`. Never the reverse.
6
+
7
+ ---
8
+
9
+ ## Module Reference
10
+
11
+ The barrel `index.ts` re-exports most modules. Three files are **not** re-exported and must be imported directly: `plan-discovery.ts`, `plan-enhancement.ts`, `settings.ts`.
12
+
13
+ | File | Purpose | Key Exports |
14
+ |------|---------|-------------|
15
+ | `aggregate-agents.ts` | Agent frontmatter parser — loads agent configs from markdown | `aggregateAgents`, `extractBody`, `extractFrontmatter` |
16
+ | `cc-native-state.ts` | CC-native state accessor for context `state.json` | `getCcNativeState`, `saveCcNativeState`, `isPlanAlreadyReviewed`, `markPlanReviewed`, `markQuestionsAsked` |
17
+ | `cli-output-parser.ts` | Unified Claude CLI JSON output parser | `parseCliOutput` |
18
+ | `config.ts` | Configuration loading from `cc-native.config.json` | `loadConfig`, `getDisplaySettings` |
19
+ | `constants.ts` | Feature flags, security limits, path validation | `ENABLE_ROBUST_PLAN_WRITES`, `PLANS_DIR`, `validatePlanPath`, `MAX_RETRY_ATTEMPTS` |
20
+ | `debug.ts` | Per-context debug logging (thin layer over shared logger) | `debugLog`, `debugRaw`, `getDebugDir`, `cleanupDebugFolder` |
21
+ | `index.ts` | Barrel — re-exports public API from all modules | (see individual modules) |
22
+ | `json-parser.ts` | JSON parsing with recovery for LLM responses | `parseJsonMaybe`, `coerceToReview` |
23
+ | `plan-discovery.ts` | Plan file discovery, reading, and hashing | *(not re-exported)* — import directly |
24
+ | `plan-enhancement.ts` | Plan quality guidance prompt for context emission | *(not re-exported)* — import directly |
25
+ | `settings.ts` | Settings loading, defaults, agent library management | *(not re-exported)* — import directly |
26
+ | `state.ts` | Iteration state management for plan review cycles | `loadState`, `saveStateToPlan`, `getIterationState`, `shouldContinueIterating` |
27
+ | `types.ts` | All cc-native type definitions and prompt constants | `Verdict`, `ReviewData`, `AgentConfig`, `PlanReviewConfig`, `REVIEW_SCHEMA` |
28
+
29
+ **Subfolder:** `rlm/` — retrieval-augmented learning memory. Has its own `rlm/CLAUDE.md`.
30
+
31
+ ---
32
+
33
+ ## Shared Dependencies
34
+
35
+ These `_shared/lib-ts` modules are used across cc-native lib-ts:
36
+
37
+ | Shared Module | Used By |
38
+ |--------------|---------|
39
+ | `base/logger` | All modules (logging) |
40
+ | `base/atomic-write` | `state.ts` (crash-safe writes) |
41
+ | `base/utils` | `cc-native-state.ts` (`nowIso`) |
42
+ | `context/context-store` | `cc-native-state.ts` (state access) |
43
+ | `context/plan-manager` | `plan-discovery.ts` (plan path lookup) |
44
+ | `types` | `types.ts` (re-exports `ContextState`, `HookInput`, `HookOutput`) |
45
+
46
+ ---
47
+
48
+ ## Import Direction
49
+
50
+ ```
51
+ hooks/ (entry points — import from lib-ts/ and _shared/)
52
+
53
+ lib-ts/ (this directory — import from _shared/ only)
54
+
55
+ _shared/lib-ts/ (cross-method infrastructure — no reverse imports)
56
+ ```
57
+
58
+ Never import from `hooks/` or `plan-review/` into `lib-ts/`. The one exception noted in `aggregate-agents.ts`: it stays in `lib-ts/` because both `settings.ts` and `plan-review/` depend on it.
59
+
60
+ ---
61
+
62
+ ## Context Maintenance
63
+
64
+ **After modifying files in this directory:** scan the entries above — if any claim is now
65
+ false or incomplete, update this file before ending the task. Do not defer.
66
+
67
+ **Staleness anchor:** This file assumes `index.ts` exists with 13 sibling `.ts` files. If the
68
+ count changes, update the Module Reference table.
69
+
70
+ <!-- context-layer: generated=2026-03-01 | last-audited=2026-03-01 | version=1 | dir-commits-at-audit=15 -->
@@ -1,144 +1,20 @@
1
1
  /**
2
- * Unified Claude CLI output parser.
3
- * Deduplicates identical logic from orchestrator.ts and reviewers/agent.ts.
4
- * See cc-native-plan-review-spec.md §4.6
2
+ * CC-native wrapper around shared structured-output parsing.
3
+ * Keeps existing import surface stable for provider implementations.
5
4
  */
6
5
 
7
- import { parseJsonMaybe } from "./json-parser.js";
8
- import { logDebug, logError, logWarn } from "../../_shared/lib-ts/base/logger.js";
6
+ import { parseStructuredOutput } from "../../_shared/lib-ts/agent-exec/structured-output.js";
9
7
 
10
8
  /**
11
- * Parse Claude CLI JSON output, handling various formats.
12
- *
13
- * Claude CLI can output in several formats:
14
- * - Direct structured_output dict
15
- * - Assistant message with StructuredOutput tool use
16
- * - List of events with assistant messages
17
- * - Raw text with embedded JSON (heuristic fallback)
18
- *
19
- * @param raw - Raw stdout from Claude CLI
20
- * @param requireFields - Optional fields to validate in heuristic fallback
21
- * @returns Parsed JSON dict or null if parsing failed
9
+ * Parse CLI JSON output into a structured object.
10
+ * Delegates to shared parser with cc-native logging tag.
22
11
  */
23
12
  export function parseCliOutput(
24
13
  raw: string,
25
14
  requireFields?: string[],
26
15
  ): null | Record<string, unknown> {
27
- try {
28
- const result: unknown = JSON.parse(raw);
29
-
30
- if (result !== null && typeof result === "object" && !Array.isArray(result)) {
31
- const dict = result as Record<string, unknown>;
32
-
33
- // Strategy 1: Direct structured_output key
34
- if ("structured_output" in dict) {
35
- logDebug("cli_parser", "Found structured_output in root dict");
36
- return dict.structured_output as Record<string, unknown>;
37
- }
38
-
39
- // Strategy 1.5: Session result envelope (type: "result")
40
- // When the model fails to call StructuredOutput, the CLI returns a
41
- // session metadata object with type/subtype/duration_ms/usage/etc.
42
- // but no structured_output key. Check for the `result` text field
43
- // (model may have written review as text) and for error states.
44
- if (dict.type === "result" || ("duration_ms" in dict && "session_id" in dict)) {
45
- if (dict.is_error === true || (Array.isArray(dict.errors) && (dict.errors as unknown[]).length > 0)) {
46
- logWarn("cli_parser", `CLI returned error result: ${JSON.stringify(dict.errors ?? "is_error=true")}`);
47
- return null;
48
- }
49
- if (typeof dict.result === "string" && dict.result) {
50
- logDebug("cli_parser", "Found result text in session envelope, attempting JSON extraction");
51
- const extracted = parseJsonMaybe(dict.result as string, requireFields);
52
- if (extracted) return extracted;
53
- logWarn("cli_parser", "Session envelope result text contained no extractable JSON");
54
- }
55
- logDebug("cli_parser", "Session result envelope with no structured_output or extractable result");
56
- return null;
57
- }
58
-
59
- // Strategy 2: Assistant message with StructuredOutput tool use
60
- if (dict.type === "assistant") {
61
- const message = dict.message as Record<string, unknown> | undefined;
62
- const content = message?.content;
63
- if (Array.isArray(content)) {
64
- for (const item of content) {
65
- if (
66
- item !== null &&
67
- typeof item === "object" &&
68
- (item as Record<string, unknown>).name === "StructuredOutput"
69
- ) {
70
- logDebug(
71
- "cli_parser",
72
- "Found StructuredOutput in assistant message content",
73
- );
74
- return (item as Record<string, unknown>).input as Record<
75
- string,
76
- unknown
77
- >;
78
- }
79
- }
80
- }
81
-
82
- logDebug(
83
- "cli_parser",
84
- "Assistant message found but no StructuredOutput tool use in content",
85
- );
86
- }
87
- } else if (Array.isArray(result)) {
88
- // Strategy 3: List of events with assistant messages
89
- logDebug(
90
- "cli_parser",
91
- `Received list of ${(result as unknown[]).length} events, searching for assistant message`,
92
- );
93
- for (let i = 0; i < (result as unknown[]).length; i++) {
94
- const event = (result as unknown[])[i];
95
- if (event === null || typeof event !== "object") continue;
96
-
97
- const dict = event as Record<string, unknown>;
98
- if (dict.type === "assistant") {
99
- const message = dict.message as Record<string, unknown> | undefined;
100
- const content = message?.content;
101
- if (Array.isArray(content)) {
102
- for (const item of content) {
103
- if (
104
- item !== null &&
105
- typeof item === "object" &&
106
- (item as Record<string, unknown>).name === "StructuredOutput"
107
- ) {
108
- logDebug(
109
- "cli_parser",
110
- `Found StructuredOutput in event[${i}] assistant message`,
111
- );
112
- return (item as Record<string, unknown>).input as Record<
113
- string,
114
- unknown
115
- >;
116
- }
117
- }
118
- }
119
- }
120
- }
121
-
122
- logDebug(
123
- "cli_parser",
124
- "No StructuredOutput found in any assistant message in event list",
125
- );
126
- }
127
- } catch (error: unknown) {
128
- if (error instanceof SyntaxError) {
129
- logWarn("cli_parser", `JSON decode error: ${error.message}`);
130
- } else {
131
- logError(
132
- "cli_parser",
133
- `Unexpected error during structured parsing: ${error}`,
134
- );
135
- }
136
- }
137
-
138
- // Strategy 4: Heuristic {…} extraction fallback
139
- logDebug(
140
- "cli_parser",
141
- "No structured output found, falling back to heuristic JSON extraction",
142
- );
143
- return parseJsonMaybe(raw, requireFields);
16
+ return parseStructuredOutput(raw, {
17
+ requireFields,
18
+ loggerTag: "cli_parser",
19
+ });
144
20
  }
@@ -11,11 +11,16 @@ import { loadConfig, getDisplaySettings } from "./config.js";
11
11
  import { DEFAULT_REVIEW_ITERATIONS } from "./state.js";
12
12
  import type {
13
13
  AgentConfig,
14
- ProviderConfig,
14
+ AgentReviewSettings,
15
+ AgentSelectionConfig,
16
+ LoadedSettings,
15
17
  ModelsConfig,
18
+ PlanReviewSettings,
19
+ ProviderConfig,
16
20
  } from "./types.js";
17
21
  import { DEFAULT_DISPLAY, DEFAULT_SANITIZATION } from "./types.js";
18
22
  import { logInfo } from "../../_shared/lib-ts/base/logger.js";
23
+ import { CODEX_MODELS } from "../../_shared/lib-ts/base/models.js";
19
24
 
20
25
  const HOOK = "settings";
21
26
 
@@ -56,10 +61,10 @@ export const DEFAULT_AGENTS: Array<{ name: string; model: string; provider: stri
56
61
  { ...AGENT_DEFAULTS, name: "constraint-validator", focus: "constraint identification and satisfaction", categories: ALL_CATEGORIES },
57
62
  ];
58
63
 
59
- export const DEFAULT_ORCHESTRATOR: { enabled: boolean; model: string; timeout: number } = { enabled: true, model: "opus", timeout: 60 };
64
+ export const DEFAULT_ORCHESTRATOR = { enabled: true, model: CODEX_MODELS.codex, provider: "codex", timeout: 60 } as const;
60
65
  export const DEFAULT_AGENT_MODEL = "sonnet";
61
66
 
62
- export const DEFAULT_AGENT_SELECTION: Record<string, unknown> = {
67
+ export const DEFAULT_AGENT_SELECTION: AgentSelectionConfig = {
63
68
  simple: { min: 3, max: 3 },
64
69
  medium: { min: 5, max: 5 },
65
70
  high: { min: 7, max: 7 },
@@ -71,7 +76,7 @@ export const DEFAULT_COMPLEXITY_CATEGORIES = ["code", "infrastructure", "documen
71
76
  export const DEFAULT_MODELS_CONFIG: ModelsConfig = {
72
77
  providers: {
73
78
  claude: { enabled: false, models: ["sonnet"] },
74
- codex: { enabled: true, models: ["gpt-5.2"] },
79
+ codex: { enabled: true, models: [CODEX_MODELS.codex] },
75
80
  },
76
81
  };
77
82
 
@@ -79,58 +84,131 @@ export const DEFAULT_MODELS_CONFIG: ModelsConfig = {
79
84
  // Settings Loading
80
85
  // ---------------------------------------------------------------------------
81
86
 
82
- export function loadSettings(projDir: string): Record<string, unknown> {
83
- const defaults: Record<string, unknown> = {
84
- planReview: {
85
- enabled: true,
86
- reviewers: {
87
- codex: { enabled: true, model: "", timeout: 120 },
88
- gemini: { enabled: false, model: "", timeout: 120 },
89
- },
90
- display: { ...DEFAULT_DISPLAY },
91
- },
92
- agentReview: {
93
- enabled: true,
94
- orchestrator: { ...DEFAULT_ORCHESTRATOR },
95
- timeout: 180,
96
- highIssueThreshold: 3,
97
- legacyMode: false,
98
- display: { ...DEFAULT_DISPLAY },
99
- agentSelection: { ...DEFAULT_AGENT_SELECTION },
100
- agentDefaults: { model: DEFAULT_AGENT_MODEL },
101
- complexityCategories: [...DEFAULT_COMPLEXITY_CATEGORIES],
102
- sanitization: { ...DEFAULT_SANITIZATION },
87
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
88
+ return value && typeof value === "object" && !Array.isArray(value)
89
+ ? value as Record<string, unknown>
90
+ : undefined;
91
+ }
92
+
93
+ function asStringArray(value: unknown): string[] | undefined {
94
+ if (!Array.isArray(value)) return undefined;
95
+ const filtered = value.filter((item): item is string => typeof item === "string" && item.trim().length > 0);
96
+ return filtered.length > 0 ? filtered : undefined;
97
+ }
98
+
99
+ export function loadSettings(projDir: string): LoadedSettings {
100
+ const defaultPlan: PlanReviewSettings = {
101
+ enabled: true,
102
+ reviewers: {
103
+ codex: { enabled: true, model: "", timeout: 120 },
104
+ gemini: { enabled: false, model: "", timeout: 120 },
103
105
  },
106
+ display: { ...DEFAULT_DISPLAY },
107
+ };
108
+
109
+ const defaultAgent: AgentReviewSettings = {
110
+ enabled: true,
111
+ orchestrator: { ...DEFAULT_ORCHESTRATOR },
112
+ timeout: 180,
113
+ highIssueThreshold: 3,
114
+ legacyMode: false,
115
+ display: { ...DEFAULT_DISPLAY },
116
+ agentSelection: { ...DEFAULT_AGENT_SELECTION },
117
+ agentDefaults: { model: DEFAULT_AGENT_MODEL },
118
+ complexityCategories: [...DEFAULT_COMPLEXITY_CATEGORIES],
119
+ sanitization: { ...DEFAULT_SANITIZATION },
104
120
  };
105
121
 
106
122
  const config = loadConfig(projDir);
107
- if (!config || Object.keys(config).length === 0) return { ...defaults, models: {} };
123
+ if (!config || Object.keys(config).length === 0) {
124
+ return { planReview: defaultPlan, agentReview: defaultAgent, models: {} };
125
+ }
126
+
127
+ // Cast raw config to access arbitrary keys from JSON
128
+ const raw = config as Record<string, unknown>;
108
129
 
109
130
  // Merge planReview
110
- const planReview = config.planReview ?? {};
111
- const mergedPlan = { ...defaults.planReview, ...planReview };
112
- if (planReview.reviewers) {
113
- mergedPlan.reviewers = { ...defaults.planReview.reviewers, ...planReview.reviewers };
131
+ const planReviewRaw = (asRecord(raw.planReview) ?? {}) as Partial<PlanReviewSettings>;
132
+ const mergedPlan: PlanReviewSettings = { ...defaultPlan, ...planReviewRaw };
133
+ if (planReviewRaw.reviewers) {
134
+ mergedPlan.reviewers = { ...defaultPlan.reviewers, ...planReviewRaw.reviewers };
114
135
  }
115
136
  mergedPlan.display = getDisplaySettings(config, "planReview");
116
137
 
117
138
  // Merge agentReview
118
- const agentReview = (config as Record<string, unknown>).agentReview ?? {};
119
- const mergedAgent = { ...defaults.agentReview, ...agentReview };
139
+ const agentReviewRawRecord = asRecord(raw.agentReview);
140
+ const agentReviewRaw = (agentReviewRawRecord ?? {}) as Partial<AgentReviewSettings>;
141
+ const mergedAgent: AgentReviewSettings = { ...defaultAgent, ...agentReviewRaw };
120
142
  if (!mergedAgent.orchestrator || typeof mergedAgent.orchestrator !== "object") {
121
143
  mergedAgent.orchestrator = { ...DEFAULT_ORCHESTRATOR };
122
144
  } else {
123
145
  mergedAgent.orchestrator = { ...DEFAULT_ORCHESTRATOR, ...mergedAgent.orchestrator };
124
146
  }
125
147
  mergedAgent.display = getDisplaySettings(config, "agentReview");
126
- const configRecord = config as Record<string, unknown>;
127
- mergedAgent.agentSelection = { ...DEFAULT_AGENT_SELECTION, ...(configRecord.agentSelection as Record<string, unknown>) };
128
- mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...(configRecord.agentDefaults as Record<string, unknown>) };
129
- mergedAgent.complexityCategories = (configRecord.complexityCategories as string[]) ?? [...DEFAULT_COMPLEXITY_CATEGORIES];
130
- mergedAgent.sanitization = { ...DEFAULT_SANITIZATION, ...(configRecord.sanitization as Record<string, unknown>) };
131
- mergedAgent.reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS, ...agentReview.reviewIterations };
132
-
133
- const modelsRaw = (config as Record<string, unknown>).models ?? {};
148
+
149
+ const nestedAgentSelection = asRecord(agentReviewRawRecord?.agentSelection) as AgentSelectionConfig | undefined;
150
+ const topLevelAgentSelection = asRecord(raw.agentSelection) as AgentSelectionConfig | undefined;
151
+ mergedAgent.agentSelection = {
152
+ ...DEFAULT_AGENT_SELECTION,
153
+ ...nestedAgentSelection,
154
+ ...topLevelAgentSelection,
155
+ };
156
+
157
+ const nestedAgentDefaults = asRecord(agentReviewRawRecord?.agentDefaults) as { model?: string } | undefined;
158
+ const topLevelAgentDefaults = asRecord(raw.agentDefaults) as { model?: string } | undefined;
159
+ mergedAgent.agentDefaults = {
160
+ model: DEFAULT_AGENT_MODEL,
161
+ ...nestedAgentDefaults,
162
+ ...topLevelAgentDefaults,
163
+ };
164
+
165
+ const nestedComplexityCategories = asStringArray(agentReviewRawRecord?.complexityCategories);
166
+ const topLevelComplexityCategories = asStringArray(raw.complexityCategories);
167
+ mergedAgent.complexityCategories = topLevelComplexityCategories
168
+ ?? nestedComplexityCategories
169
+ ?? [...DEFAULT_COMPLEXITY_CATEGORIES];
170
+
171
+ const nestedSanitization = asRecord(agentReviewRawRecord?.sanitization);
172
+ const topLevelSanitization = asRecord(raw.sanitization);
173
+ mergedAgent.sanitization = {
174
+ ...DEFAULT_SANITIZATION,
175
+ ...nestedSanitization,
176
+ ...topLevelSanitization,
177
+ };
178
+
179
+ const nestedFallbackByComplexity = asRecord(agentReviewRawRecord?.fallbackByComplexity) as Record<string, number> | undefined;
180
+ const topLevelFallbackByComplexity = asRecord(raw.fallbackByComplexity) as Record<string, number> | undefined;
181
+ if (nestedFallbackByComplexity || topLevelFallbackByComplexity) {
182
+ mergedAgent.fallbackByComplexity = {
183
+ ...(nestedFallbackByComplexity ?? {}),
184
+ ...(topLevelFallbackByComplexity ?? {}),
185
+ };
186
+ }
187
+
188
+ const nestedMandatoryAgents = agentReviewRawRecord?.mandatoryAgents as AgentReviewSettings["mandatoryAgents"] | undefined;
189
+ const topLevelMandatoryAgents = raw.mandatoryAgents as AgentReviewSettings["mandatoryAgents"] | undefined;
190
+ if (nestedMandatoryAgents !== undefined || topLevelMandatoryAgents !== undefined) {
191
+ mergedAgent.mandatoryAgents = topLevelMandatoryAgents ?? nestedMandatoryAgents;
192
+ }
193
+
194
+ const nestedPreflight = asRecord(agentReviewRawRecord?.preflight);
195
+ const topLevelPreflight = asRecord(raw.preflight);
196
+ if (nestedPreflight || topLevelPreflight) {
197
+ mergedAgent.preflight = {
198
+ ...(nestedPreflight ?? {}),
199
+ ...(topLevelPreflight ?? {}),
200
+ };
201
+ }
202
+
203
+ const nestedReviewIterations = asRecord(agentReviewRawRecord?.reviewIterations) as Record<string, number> | undefined;
204
+ const topLevelReviewIterations = asRecord(raw.reviewIterations) as Record<string, number> | undefined;
205
+ mergedAgent.reviewIterations = {
206
+ ...DEFAULT_REVIEW_ITERATIONS,
207
+ ...(nestedReviewIterations ?? {}),
208
+ ...(topLevelReviewIterations ?? {}),
209
+ };
210
+
211
+ const modelsRaw = (raw.models ?? {}) as Record<string, unknown>;
134
212
  return { planReview: mergedPlan, agentReview: mergedAgent, models: modelsRaw };
135
213
  }
136
214
 
@@ -138,7 +216,7 @@ export function loadSettings(projDir: string): Record<string, unknown> {
138
216
  // Models Config
139
217
  // ---------------------------------------------------------------------------
140
218
 
141
- export function loadModelsConfig(settings: Record<string, unknown>): ModelsConfig {
219
+ export function loadModelsConfig(settings: LoadedSettings): ModelsConfig {
142
220
  const raw = settings.models as Record<string, unknown> | undefined;
143
221
  if (!raw?.providers || typeof raw.providers !== "object") {
144
222
  return DEFAULT_MODELS_CONFIG;
@@ -149,7 +227,6 @@ export function loadModelsConfig(settings: Record<string, unknown>): ModelsConfi
149
227
  providers[name] = {
150
228
  enabled: c.enabled !== false,
151
229
  models: Array.isArray(c.models) ? (c.models as string[]).filter(Boolean) : [],
152
- ...(typeof c.reasoningEffort === "string" && { reasoningEffort: c.reasoningEffort }),
153
230
  };
154
231
  }
155
232
  return { providers };
@@ -161,7 +238,7 @@ export function loadModelsConfig(settings: Record<string, unknown>): ModelsConfi
161
238
 
162
239
  export function loadAgentLibrary(
163
240
  projDir: string,
164
- settings?: Record<string, unknown>,
241
+ settings?: AgentReviewSettings,
165
242
  ): AgentConfig[] {
166
243
  const agentsData = aggregateAgents(path.join(projDir, "_cc-native", "plan-review", "agents", "plan-review"));
167
244
  const defaultModel = settings?.agentDefaults?.model ?? DEFAULT_AGENT_MODEL;