rafcode 3.2.1 → 3.8.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.
- package/.claude/settings.local.json +3 -1
- package/CLAUDE.md +0 -1
- package/RAF/41-echo-chamber/decisions.md +13 -0
- package/RAF/41-echo-chamber/input.md +4 -0
- package/RAF/41-echo-chamber/outcomes/1-update-codex-model-defaults.md +24 -0
- package/RAF/41-echo-chamber/outcomes/2-e2e-test-codex-provider.md +74 -0
- package/RAF/41-echo-chamber/plans/1-update-codex-model-defaults.md +28 -0
- package/RAF/41-echo-chamber/plans/2-e2e-test-codex-provider.md +103 -0
- package/RAF/42-patch-parade/decisions.md +29 -0
- package/RAF/42-patch-parade/input.md +9 -0
- package/RAF/42-patch-parade/outcomes/1-fix-codex-model-resolution.md +36 -0
- package/RAF/42-patch-parade/outcomes/2-fix-provider-aware-name-generation.md +31 -0
- package/RAF/42-patch-parade/outcomes/3-fix-codex-error-event-rendering.md +32 -0
- package/RAF/42-patch-parade/outcomes/4-update-cli-help-docs.md +28 -0
- package/RAF/42-patch-parade/outcomes/5-update-default-codex-models-to-gpt-5-4.md +33 -0
- package/RAF/42-patch-parade/outcomes/6-unify-model-config-schema.md +89 -0
- package/RAF/42-patch-parade/plans/1-fix-codex-model-resolution.md +35 -0
- package/RAF/42-patch-parade/plans/2-fix-provider-aware-name-generation.md +38 -0
- package/RAF/42-patch-parade/plans/3-fix-codex-error-event-rendering.md +32 -0
- package/RAF/42-patch-parade/plans/4-update-cli-help-docs.md +31 -0
- package/RAF/42-patch-parade/plans/5-update-default-codex-models-to-gpt-5-4.md +35 -0
- package/RAF/42-patch-parade/plans/6-unify-model-config-schema.md +46 -0
- package/RAF/43-swiss-army/decisions.md +34 -0
- package/RAF/43-swiss-army/input.md +7 -0
- package/RAF/43-swiss-army/outcomes/1-fix-model-validation.md +21 -0
- package/RAF/43-swiss-army/outcomes/2-update-commit-format.md +31 -0
- package/RAF/43-swiss-army/outcomes/3-wire-reasoning-effort.md +28 -0
- package/RAF/43-swiss-army/outcomes/4-remove-provider-flag.md +27 -0
- package/RAF/43-swiss-army/outcomes/5-config-wizard-validation.md +23 -0
- package/RAF/43-swiss-army/outcomes/6-add-fast-mode.md +32 -0
- package/RAF/43-swiss-army/outcomes/7-config-preset.md +31 -0
- package/RAF/43-swiss-army/plans/1-fix-model-validation.md +38 -0
- package/RAF/43-swiss-army/plans/2-update-commit-format.md +46 -0
- package/RAF/43-swiss-army/plans/3-wire-reasoning-effort.md +39 -0
- package/RAF/43-swiss-army/plans/4-remove-provider-flag.md +43 -0
- package/RAF/43-swiss-army/plans/5-config-wizard-validation.md +42 -0
- package/RAF/43-swiss-army/plans/6-add-fast-mode.md +46 -0
- package/RAF/43-swiss-army/plans/7-config-preset.md +51 -0
- package/RAF/44-config-api-change/decisions.md +22 -0
- package/RAF/44-config-api-change/input.md +5 -0
- package/RAF/44-config-api-change/outcomes/1-restructure-config-subcommands.md +19 -0
- package/RAF/44-config-api-change/outcomes/2-move-preset-under-config.md +17 -0
- package/RAF/44-config-api-change/outcomes/3-update-existing-tests-for-config-api.md +14 -0
- package/RAF/44-config-api-change/outcomes/4-update-config-command-docs.md +11 -0
- package/RAF/44-config-api-change/outcomes/5-fix-codex-name-generation.md +18 -0
- package/RAF/44-config-api-change/plans/1-restructure-config-subcommands.md +37 -0
- package/RAF/44-config-api-change/plans/2-move-preset-under-config.md +38 -0
- package/RAF/44-config-api-change/plans/3-update-existing-tests-for-config-api.md +38 -0
- package/RAF/44-config-api-change/plans/4-update-config-command-docs.md +36 -0
- package/RAF/44-config-api-change/plans/5-fix-codex-name-generation.md +49 -0
- package/RAF/45-signal-cairn/decisions.md +7 -0
- package/RAF/45-signal-cairn/input.md +2 -0
- package/RAF/45-signal-cairn/outcomes/1-rename-provider-to-harness.md +19 -0
- package/RAF/45-signal-cairn/outcomes/2-normalize-model-display-names.md +18 -0
- package/RAF/45-signal-cairn/plans/1-rename-provider-to-harness.md +40 -0
- package/RAF/45-signal-cairn/plans/2-normalize-model-display-names.md +41 -0
- package/RAF/45-signal-lantern/decisions.md +10 -0
- package/RAF/45-signal-lantern/input.md +2 -0
- package/RAF/45-signal-lantern/outcomes/1-add-effort-and-fast-to-do-model-display.md +15 -0
- package/RAF/45-signal-lantern/outcomes/2-capture-codex-post-run-token-usage.md +15 -0
- package/RAF/45-signal-lantern/outcomes/3-show-codex-token-summaries-without-fake-cost.md +14 -0
- package/RAF/45-signal-lantern/plans/1-add-effort-and-fast-to-do-model-display.md +38 -0
- package/RAF/45-signal-lantern/plans/2-capture-codex-post-run-token-usage.md +37 -0
- package/RAF/45-signal-lantern/plans/3-show-codex-token-summaries-without-fake-cost.md +40 -0
- package/RAF/46-lantern-arc/decisions.md +19 -0
- package/RAF/46-lantern-arc/input.md +6 -0
- package/RAF/46-lantern-arc/outcomes/1-remove-spark-alias.md +16 -0
- package/RAF/46-lantern-arc/outcomes/2-clean-up-worktree-plan-command.md +30 -0
- package/RAF/46-lantern-arc/outcomes/3-fix-token-usage-accumulation.md +32 -0
- package/RAF/46-lantern-arc/outcomes/4-display-effort-in-compact-mode.md +22 -0
- package/RAF/46-lantern-arc/outcomes/5-codex-fast-mode-research.md +38 -0
- package/RAF/46-lantern-arc/outcomes/6-optimize-llm-prompts.md +39 -0
- package/RAF/46-lantern-arc/plans/1-remove-spark-alias.md +38 -0
- package/RAF/46-lantern-arc/plans/2-clean-up-worktree-plan-command.md +33 -0
- package/RAF/46-lantern-arc/plans/3-fix-token-usage-accumulation.md +33 -0
- package/RAF/46-lantern-arc/plans/4-display-effort-in-compact-mode.md +28 -0
- package/RAF/46-lantern-arc/plans/5-codex-fast-mode-research.md +34 -0
- package/RAF/46-lantern-arc/plans/6-optimize-llm-prompts.md +48 -0
- package/RAF/47-signal-trim/decisions.md +13 -0
- package/RAF/47-signal-trim/input.md +2 -0
- package/RAF/47-signal-trim/plans/1-remove-cache-from-status.md +73 -0
- package/README.md +47 -57
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +47 -49
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/do.d.ts +2 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +57 -44
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +36 -153
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/preset.d.ts +3 -0
- package/dist/commands/preset.d.ts.map +1 -0
- package/dist/commands/preset.js +158 -0
- package/dist/commands/preset.js.map +1 -0
- package/dist/core/claude-runner.d.ts +2 -0
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +36 -12
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/codex-runner.d.ts +1 -0
- package/dist/core/codex-runner.d.ts.map +1 -1
- package/dist/core/codex-runner.js +26 -7
- package/dist/core/codex-runner.js.map +1 -1
- package/dist/core/failure-analyzer.js +2 -1
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/git.d.ts +2 -2
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +53 -3
- package/dist/core/git.js.map +1 -1
- package/dist/core/pull-request.js +3 -3
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/runner-factory.d.ts +4 -4
- package/dist/core/runner-factory.d.ts.map +1 -1
- package/dist/core/runner-factory.js +8 -8
- package/dist/core/runner-factory.js.map +1 -1
- package/dist/core/runner-interface.d.ts +1 -1
- package/dist/core/runner-types.d.ts +17 -4
- package/dist/core/runner-types.d.ts.map +1 -1
- package/dist/parsers/codex-stream-renderer.d.ts +7 -0
- package/dist/parsers/codex-stream-renderer.d.ts.map +1 -1
- package/dist/parsers/codex-stream-renderer.js +37 -4
- package/dist/parsers/codex-stream-renderer.js.map +1 -1
- package/dist/prompts/amend.d.ts.map +1 -1
- package/dist/prompts/amend.js +29 -101
- package/dist/prompts/amend.js.map +1 -1
- package/dist/prompts/execution.d.ts.map +1 -1
- package/dist/prompts/execution.js +17 -34
- package/dist/prompts/execution.js.map +1 -1
- package/dist/prompts/planning.d.ts.map +1 -1
- package/dist/prompts/planning.js +21 -120
- package/dist/prompts/planning.js.map +1 -1
- package/dist/types/config.d.ts +33 -31
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +14 -28
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +36 -16
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +209 -104
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +25 -12
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +15 -2
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +36 -4
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +6 -1
- package/dist/utils/token-tracker.d.ts.map +1 -1
- package/dist/utils/token-tracker.js +84 -51
- package/dist/utils/token-tracker.js.map +1 -1
- package/dist/utils/validation.d.ts +1 -2
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +4 -25
- package/dist/utils/validation.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +60 -63
- package/src/commands/do.ts +63 -51
- package/src/commands/plan.ts +34 -165
- package/src/commands/preset.ts +186 -0
- package/src/core/claude-runner.ts +45 -5
- package/src/core/codex-runner.ts +32 -7
- package/src/core/failure-analyzer.ts +2 -1
- package/src/core/git.ts +57 -3
- package/src/core/pull-request.ts +3 -3
- package/src/core/runner-factory.ts +9 -9
- package/src/core/runner-interface.ts +1 -1
- package/src/core/runner-types.ts +17 -4
- package/src/parsers/codex-stream-renderer.ts +47 -4
- package/src/prompts/amend.ts +29 -101
- package/src/prompts/config-docs.md +206 -62
- package/src/prompts/execution.ts +17 -34
- package/src/prompts/planning.ts +21 -120
- package/src/types/config.ts +47 -58
- package/src/utils/config.ts +248 -115
- package/src/utils/name-generator.ts +29 -13
- package/src/utils/terminal-symbols.ts +46 -6
- package/src/utils/token-tracker.ts +96 -57
- package/src/utils/validation.ts +5 -30
- package/tests/unit/amend-prompt.test.ts +3 -2
- package/tests/unit/claude-runner-interactive.test.ts +21 -3
- package/tests/unit/claude-runner.test.ts +39 -0
- package/tests/unit/codex-runner.test.ts +163 -0
- package/tests/unit/codex-stream-renderer.test.ts +127 -0
- package/tests/unit/command-output.test.ts +57 -0
- package/tests/unit/commit-planning-artifacts-worktree.test.ts +24 -7
- package/tests/unit/commit-planning-artifacts.test.ts +26 -4
- package/tests/unit/config-command.test.ts +215 -303
- package/tests/unit/config.test.ts +319 -235
- package/tests/unit/dependency-integration.test.ts +27 -1
- package/tests/unit/do-model-display.test.ts +35 -0
- package/tests/unit/execution-prompt.test.ts +49 -19
- package/tests/unit/name-generator.test.ts +82 -12
- package/tests/unit/plan-command-auto-flag.test.ts +7 -10
- package/tests/unit/plan-command.test.ts +14 -17
- package/tests/unit/planning-prompt.test.ts +9 -8
- package/tests/unit/terminal-symbols.test.ts +94 -3
- package/tests/unit/token-tracker.test.ts +180 -1
- package/tests/unit/validation.test.ts +9 -41
- package/tests/unit/worktree-flag-override.test.ts +0 -186
package/src/utils/config.ts
CHANGED
|
@@ -8,16 +8,17 @@ import {
|
|
|
8
8
|
UserConfig,
|
|
9
9
|
VALID_MODEL_ALIASES,
|
|
10
10
|
VALID_CODEX_MODEL_ALIASES,
|
|
11
|
-
|
|
11
|
+
VALID_HARNESSES,
|
|
12
12
|
FULL_MODEL_ID_PATTERN,
|
|
13
|
-
ClaudeModelName,
|
|
14
13
|
TaskEffortLevel,
|
|
15
14
|
ModelScenario,
|
|
15
|
+
ModelEntry,
|
|
16
16
|
CommitFormatType,
|
|
17
17
|
DisplayConfig,
|
|
18
18
|
EffortMappingConfig,
|
|
19
|
-
|
|
19
|
+
HarnessName,
|
|
20
20
|
} from '../types/config.js';
|
|
21
|
+
import { logger } from './logger.js';
|
|
21
22
|
|
|
22
23
|
const CONFIG_DIR = path.join(os.homedir(), '.raf');
|
|
23
24
|
const CONFIG_FILENAME = 'raf.config.json';
|
|
@@ -36,11 +37,18 @@ export function getClaudeSettingsPath(): string {
|
|
|
36
37
|
// ---- Validation ----
|
|
37
38
|
|
|
38
39
|
const VALID_TOP_LEVEL_KEYS = new Set<string>([
|
|
39
|
-
'
|
|
40
|
+
'models', 'effortMapping',
|
|
40
41
|
'timeout', 'maxRetries', 'autoCommit',
|
|
41
42
|
'worktree', 'syncMainBranch', 'commitFormat', 'display',
|
|
42
43
|
]);
|
|
43
44
|
|
|
45
|
+
/** Keys that were removed in the schema migration. Rejected with a helpful error. */
|
|
46
|
+
const REMOVED_KEYS: Record<string, string> = {
|
|
47
|
+
provider: 'Top-level "provider" has been removed. Use "harness" inside each "models" and "effortMapping" entry instead.',
|
|
48
|
+
codexModels: '"codexModels" has been removed. Use "models" with harness-aware entries (e.g. { "model": "gpt-5.4", "harness": "codex" }) instead.',
|
|
49
|
+
codexEffortMapping: '"codexEffortMapping" has been removed. Use "effortMapping" with harness-aware entries instead.',
|
|
50
|
+
};
|
|
51
|
+
|
|
44
52
|
const VALID_MODEL_KEYS = new Set<string>([
|
|
45
53
|
'plan', 'execute', 'nameGeneration', 'failureAnalysis', 'prGeneration', 'config',
|
|
46
54
|
]);
|
|
@@ -51,6 +59,10 @@ const VALID_COMMIT_FORMAT_KEYS = new Set<string>(['task', 'plan', 'amend', 'pref
|
|
|
51
59
|
|
|
52
60
|
const VALID_DISPLAY_KEYS = new Set<string>(['showCacheTokens']);
|
|
53
61
|
|
|
62
|
+
const VALID_MODEL_ENTRY_KEYS = new Set<string>(['model', 'harness', 'reasoningEffort', 'fast']);
|
|
63
|
+
|
|
64
|
+
const VALID_REASONING_EFFORTS = new Set<string>(['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
65
|
+
|
|
54
66
|
export class ConfigValidationError extends Error {
|
|
55
67
|
constructor(message: string) {
|
|
56
68
|
super(message);
|
|
@@ -66,7 +78,7 @@ function checkUnknownKeys(obj: Record<string, unknown>, validKeys: Set<string>,
|
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
/** Regex for raw Codex model IDs (e.g., `gpt-5.4`, `gpt-5.3-codex
|
|
81
|
+
/** Regex for raw Codex model IDs (e.g., `gpt-5.4`, `gpt-5.3-codex`). Requires dot-separated version. */
|
|
70
82
|
const CODEX_MODEL_ID_PATTERN = /^gpt-\d+\.\d+(-.+)*$/;
|
|
71
83
|
|
|
72
84
|
/**
|
|
@@ -74,19 +86,19 @@ const CODEX_MODEL_ID_PATTERN = /^gpt-\d+\.\d+(-.+)*$/;
|
|
|
74
86
|
* Accepts:
|
|
75
87
|
* - Claude short aliases: sonnet, haiku, opus
|
|
76
88
|
* - Claude full IDs: claude-opus-4-6
|
|
77
|
-
* - Codex short aliases:
|
|
78
|
-
* - Raw Codex model IDs: gpt-5.4, gpt-5.3-codex
|
|
89
|
+
* - Codex short aliases: codex, gpt54
|
|
90
|
+
* - Raw Codex model IDs: gpt-5.4, gpt-5.3-codex
|
|
79
91
|
* - Harness-prefixed format: claude/opus, codex/gpt-5.4
|
|
80
92
|
*/
|
|
81
93
|
export function isValidModelName(value: string): boolean {
|
|
82
|
-
// Harness-prefixed format:
|
|
94
|
+
// Harness-prefixed format: harness/model
|
|
83
95
|
const prefixMatch = value.match(/^(claude|codex)\/(.+)$/);
|
|
84
96
|
if (prefixMatch) {
|
|
85
|
-
const [,
|
|
86
|
-
if (
|
|
97
|
+
const [, harness, model] = prefixMatch;
|
|
98
|
+
if (harness === 'claude') {
|
|
87
99
|
return (VALID_MODEL_ALIASES as readonly string[]).includes(model!) || FULL_MODEL_ID_PATTERN.test(model!);
|
|
88
100
|
}
|
|
89
|
-
if (
|
|
101
|
+
if (harness === 'codex') {
|
|
90
102
|
return (VALID_CODEX_MODEL_ALIASES as readonly string[]).includes(model!) || CODEX_MODEL_ID_PATTERN.test(model!);
|
|
91
103
|
}
|
|
92
104
|
}
|
|
@@ -105,25 +117,97 @@ export function isValidModelName(value: string): boolean {
|
|
|
105
117
|
}
|
|
106
118
|
|
|
107
119
|
/**
|
|
108
|
-
* Parse a model spec string into its
|
|
109
|
-
* - `codex/gpt-5.4` -> {
|
|
110
|
-
* - `claude/opus` -> {
|
|
111
|
-
* - `opus` -> {
|
|
112
|
-
* - `gpt-5.4` -> {
|
|
120
|
+
* Parse a model spec string into its harness and model components.
|
|
121
|
+
* - `codex/gpt-5.4` -> { harness: 'codex', model: 'gpt-5.4' }
|
|
122
|
+
* - `claude/opus` -> { harness: 'claude', model: 'opus' }
|
|
123
|
+
* - `opus` -> { harness: 'claude', model: 'opus' } (defaults to claude)
|
|
124
|
+
* - `gpt-5.4` -> { harness: 'codex', model: 'gpt-5.4' } (inferred from model format)
|
|
113
125
|
*/
|
|
114
|
-
export function parseModelSpec(modelSpec: string): {
|
|
126
|
+
export function parseModelSpec(modelSpec: string): { harness: HarnessName; model: string } {
|
|
115
127
|
const prefixMatch = modelSpec.match(/^(claude|codex)\/(.+)$/);
|
|
116
128
|
if (prefixMatch) {
|
|
117
|
-
return {
|
|
129
|
+
return { harness: prefixMatch[1] as HarnessName, model: prefixMatch[2]! };
|
|
118
130
|
}
|
|
119
131
|
|
|
120
|
-
// Infer
|
|
132
|
+
// Infer harness from model format
|
|
121
133
|
if ((VALID_CODEX_MODEL_ALIASES as readonly string[]).includes(modelSpec) || CODEX_MODEL_ID_PATTERN.test(modelSpec)) {
|
|
122
|
-
return {
|
|
134
|
+
return { harness: 'codex', model: modelSpec };
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
// Default to claude
|
|
126
|
-
return {
|
|
138
|
+
return { harness: 'claude', model: modelSpec };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Validate a model entry object: { model, harness, reasoningEffort? }
|
|
143
|
+
*/
|
|
144
|
+
function validateModelEntry(obj: unknown, prefix: string): void {
|
|
145
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
146
|
+
throw new ConfigValidationError(`${prefix} must be a model entry object (e.g. { "model": "opus", "harness": "claude" })`);
|
|
147
|
+
}
|
|
148
|
+
const entry = obj as Record<string, unknown>;
|
|
149
|
+
checkUnknownKeys(entry, VALID_MODEL_ENTRY_KEYS, prefix);
|
|
150
|
+
|
|
151
|
+
if (entry.model === undefined) {
|
|
152
|
+
throw new ConfigValidationError(`${prefix}.model is required`);
|
|
153
|
+
}
|
|
154
|
+
if (typeof entry.model !== 'string' || !isValidModelName(entry.model)) {
|
|
155
|
+
throw new ConfigValidationError(
|
|
156
|
+
`${prefix}.model must be a valid model name (e.g. opus, sonnet, gpt-5.4, claude-opus-4-6)`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (entry.harness === undefined) {
|
|
161
|
+
throw new ConfigValidationError(`${prefix}.harness is required`);
|
|
162
|
+
}
|
|
163
|
+
if (typeof entry.harness !== 'string' || !(VALID_HARNESSES as readonly string[]).includes(entry.harness)) {
|
|
164
|
+
throw new ConfigValidationError(`${prefix}.harness must be one of: ${VALID_HARNESSES.join(', ')}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (entry.reasoningEffort !== undefined) {
|
|
168
|
+
if (typeof entry.reasoningEffort !== 'string' || !VALID_REASONING_EFFORTS.has(entry.reasoningEffort)) {
|
|
169
|
+
throw new ConfigValidationError(`${prefix}.reasoningEffort must be one of: none, minimal, low, medium, high, xhigh, max`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (entry.fast !== undefined) {
|
|
174
|
+
if (typeof entry.fast !== 'boolean') {
|
|
175
|
+
throw new ConfigValidationError(`${prefix}.fast must be a boolean`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getCodexFastWarning(prefix: string): string {
|
|
181
|
+
return `${prefix}.fast is enabled but ignored because Codex does not support fast mode`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function collectModelEntryWarnings(obj: unknown, prefix: string, warnings: string[]): void {
|
|
185
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const entry = obj as Record<string, unknown>;
|
|
190
|
+
if (entry.harness === 'codex' && entry.fast === true) {
|
|
191
|
+
warnings.push(getCodexFastWarning(prefix));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function collectConfigValidationWarnings(config: UserConfig): string[] {
|
|
196
|
+
const warnings: string[] = [];
|
|
197
|
+
|
|
198
|
+
if (config.models && typeof config.models === 'object') {
|
|
199
|
+
for (const [key, value] of Object.entries(config.models)) {
|
|
200
|
+
collectModelEntryWarnings(value, `models.${key}`, warnings);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (config.effortMapping && typeof config.effortMapping === 'object') {
|
|
205
|
+
for (const [key, value] of Object.entries(config.effortMapping)) {
|
|
206
|
+
collectModelEntryWarnings(value, `effortMapping.${key}`, warnings);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return warnings;
|
|
127
211
|
}
|
|
128
212
|
|
|
129
213
|
export function validateConfig(config: unknown): UserConfig {
|
|
@@ -132,15 +216,16 @@ export function validateConfig(config: unknown): UserConfig {
|
|
|
132
216
|
}
|
|
133
217
|
|
|
134
218
|
const obj = config as Record<string, unknown>;
|
|
135
|
-
checkUnknownKeys(obj, VALID_TOP_LEVEL_KEYS, '');
|
|
136
219
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
throw new ConfigValidationError(
|
|
220
|
+
// Check for removed keys first (helpful error messages)
|
|
221
|
+
for (const [key, message] of Object.entries(REMOVED_KEYS)) {
|
|
222
|
+
if (key in obj) {
|
|
223
|
+
throw new ConfigValidationError(message);
|
|
141
224
|
}
|
|
142
225
|
}
|
|
143
226
|
|
|
227
|
+
checkUnknownKeys(obj, VALID_TOP_LEVEL_KEYS, '');
|
|
228
|
+
|
|
144
229
|
// models
|
|
145
230
|
if (obj.models !== undefined) {
|
|
146
231
|
if (typeof obj.models !== 'object' || obj.models === null || Array.isArray(obj.models)) {
|
|
@@ -149,11 +234,7 @@ export function validateConfig(config: unknown): UserConfig {
|
|
|
149
234
|
const models = obj.models as Record<string, unknown>;
|
|
150
235
|
checkUnknownKeys(models, VALID_MODEL_KEYS, 'models');
|
|
151
236
|
for (const [key, val] of Object.entries(models)) {
|
|
152
|
-
|
|
153
|
-
throw new ConfigValidationError(
|
|
154
|
-
`models.${key} must be a short alias (${VALID_MODEL_ALIASES.join(', ')}) or a full model ID (e.g., claude-sonnet-4-5-20250929)`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
237
|
+
validateModelEntry(val, `models.${key}`);
|
|
157
238
|
}
|
|
158
239
|
}
|
|
159
240
|
|
|
@@ -165,43 +246,7 @@ export function validateConfig(config: unknown): UserConfig {
|
|
|
165
246
|
const effortMapping = obj.effortMapping as Record<string, unknown>;
|
|
166
247
|
checkUnknownKeys(effortMapping, VALID_EFFORT_MAPPING_KEYS, 'effortMapping');
|
|
167
248
|
for (const [key, val] of Object.entries(effortMapping)) {
|
|
168
|
-
|
|
169
|
-
throw new ConfigValidationError(
|
|
170
|
-
`effortMapping.${key} must be a short alias (${VALID_MODEL_ALIASES.join(', ')}) or a full model ID (e.g., claude-sonnet-4-5-20250929)`
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// codexModels
|
|
177
|
-
if (obj.codexModels !== undefined) {
|
|
178
|
-
if (typeof obj.codexModels !== 'object' || obj.codexModels === null || Array.isArray(obj.codexModels)) {
|
|
179
|
-
throw new ConfigValidationError('codexModels must be an object');
|
|
180
|
-
}
|
|
181
|
-
const codexModels = obj.codexModels as Record<string, unknown>;
|
|
182
|
-
checkUnknownKeys(codexModels, VALID_MODEL_KEYS, 'codexModels');
|
|
183
|
-
for (const [key, val] of Object.entries(codexModels)) {
|
|
184
|
-
if (typeof val !== 'string' || !isValidModelName(val)) {
|
|
185
|
-
throw new ConfigValidationError(
|
|
186
|
-
`codexModels.${key} must be a valid model name (e.g., gpt-5.4, gpt-5.3-codex-spark)`
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// codexEffortMapping
|
|
193
|
-
if (obj.codexEffortMapping !== undefined) {
|
|
194
|
-
if (typeof obj.codexEffortMapping !== 'object' || obj.codexEffortMapping === null || Array.isArray(obj.codexEffortMapping)) {
|
|
195
|
-
throw new ConfigValidationError('codexEffortMapping must be an object');
|
|
196
|
-
}
|
|
197
|
-
const codexEffortMapping = obj.codexEffortMapping as Record<string, unknown>;
|
|
198
|
-
checkUnknownKeys(codexEffortMapping, VALID_EFFORT_MAPPING_KEYS, 'codexEffortMapping');
|
|
199
|
-
for (const [key, val] of Object.entries(codexEffortMapping)) {
|
|
200
|
-
if (typeof val !== 'string' || !isValidModelName(val)) {
|
|
201
|
-
throw new ConfigValidationError(
|
|
202
|
-
`codexEffortMapping.${key} must be a valid model name (e.g., gpt-5.4, gpt-5.3-codex-spark)`
|
|
203
|
-
);
|
|
204
|
-
}
|
|
249
|
+
validateModelEntry(val, `effortMapping.${key}`);
|
|
205
250
|
}
|
|
206
251
|
}
|
|
207
252
|
|
|
@@ -273,21 +318,57 @@ export function validateConfig(config: unknown): UserConfig {
|
|
|
273
318
|
|
|
274
319
|
// ---- Deep merge ----
|
|
275
320
|
|
|
321
|
+
/** Deep-merge a single model entry: user override replaces the default entirely. */
|
|
322
|
+
function mergeModelEntry(defaultEntry: ModelEntry, override: unknown): ModelEntry {
|
|
323
|
+
const normalizedEntry = (entry: ModelEntry): ModelEntry => {
|
|
324
|
+
if (entry.harness !== 'codex') {
|
|
325
|
+
return entry;
|
|
326
|
+
}
|
|
327
|
+
const { fast: _ignored, ...rest } = entry;
|
|
328
|
+
return rest;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
if (override && typeof override === 'object' && !Array.isArray(override)) {
|
|
332
|
+
const o = override as Record<string, unknown>;
|
|
333
|
+
return normalizedEntry({
|
|
334
|
+
model: typeof o.model === 'string' ? o.model : defaultEntry.model,
|
|
335
|
+
harness: typeof o.harness === 'string' ? (o.harness as HarnessName) : defaultEntry.harness,
|
|
336
|
+
...(o.reasoningEffort !== undefined
|
|
337
|
+
? { reasoningEffort: o.reasoningEffort as ModelEntry['reasoningEffort'] }
|
|
338
|
+
: defaultEntry.reasoningEffort !== undefined
|
|
339
|
+
? { reasoningEffort: defaultEntry.reasoningEffort }
|
|
340
|
+
: {}),
|
|
341
|
+
...(o.fast !== undefined
|
|
342
|
+
? { fast: o.fast as boolean }
|
|
343
|
+
: defaultEntry.fast !== undefined
|
|
344
|
+
? { fast: defaultEntry.fast }
|
|
345
|
+
: {}),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return normalizedEntry(defaultEntry);
|
|
349
|
+
}
|
|
350
|
+
|
|
276
351
|
function deepMerge(defaults: RafConfig, overrides: UserConfig): RafConfig {
|
|
277
352
|
const result = { ...defaults };
|
|
278
353
|
|
|
279
|
-
if (overrides.provider !== undefined) result.provider = overrides.provider;
|
|
280
354
|
if (overrides.models) {
|
|
281
|
-
|
|
355
|
+
const m = overrides.models as Record<string, unknown>;
|
|
356
|
+
result.models = {
|
|
357
|
+
plan: m.plan ? mergeModelEntry(defaults.models.plan, m.plan) : { ...defaults.models.plan },
|
|
358
|
+
execute: m.execute ? mergeModelEntry(defaults.models.execute, m.execute) : { ...defaults.models.execute },
|
|
359
|
+
nameGeneration: m.nameGeneration ? mergeModelEntry(defaults.models.nameGeneration, m.nameGeneration) : { ...defaults.models.nameGeneration },
|
|
360
|
+
failureAnalysis: m.failureAnalysis ? mergeModelEntry(defaults.models.failureAnalysis, m.failureAnalysis) : { ...defaults.models.failureAnalysis },
|
|
361
|
+
prGeneration: m.prGeneration ? mergeModelEntry(defaults.models.prGeneration, m.prGeneration) : { ...defaults.models.prGeneration },
|
|
362
|
+
config: m.config ? mergeModelEntry(defaults.models.config, m.config) : { ...defaults.models.config },
|
|
363
|
+
};
|
|
282
364
|
}
|
|
283
365
|
if (overrides.effortMapping) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
result.codexEffortMapping = { ...defaults.codexEffortMapping, ...overrides.codexEffortMapping };
|
|
366
|
+
const e = overrides.effortMapping as Record<string, unknown>;
|
|
367
|
+
result.effortMapping = {
|
|
368
|
+
low: e.low ? mergeModelEntry(defaults.effortMapping.low, e.low) : { ...defaults.effortMapping.low },
|
|
369
|
+
medium: e.medium ? mergeModelEntry(defaults.effortMapping.medium, e.medium) : { ...defaults.effortMapping.medium },
|
|
370
|
+
high: e.high ? mergeModelEntry(defaults.effortMapping.high, e.high) : { ...defaults.effortMapping.high },
|
|
371
|
+
};
|
|
291
372
|
}
|
|
292
373
|
if (overrides.commitFormat) {
|
|
293
374
|
result.commitFormat = { ...defaults.commitFormat, ...overrides.commitFormat };
|
|
@@ -316,10 +397,19 @@ export function resolveConfig(configPath?: string): RafConfig {
|
|
|
316
397
|
if (!fs.existsSync(filePath)) {
|
|
317
398
|
return {
|
|
318
399
|
...DEFAULT_CONFIG,
|
|
319
|
-
models: {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
400
|
+
models: {
|
|
401
|
+
plan: { ...DEFAULT_CONFIG.models.plan },
|
|
402
|
+
execute: { ...DEFAULT_CONFIG.models.execute },
|
|
403
|
+
nameGeneration: { ...DEFAULT_CONFIG.models.nameGeneration },
|
|
404
|
+
failureAnalysis: { ...DEFAULT_CONFIG.models.failureAnalysis },
|
|
405
|
+
prGeneration: { ...DEFAULT_CONFIG.models.prGeneration },
|
|
406
|
+
config: { ...DEFAULT_CONFIG.models.config },
|
|
407
|
+
},
|
|
408
|
+
effortMapping: {
|
|
409
|
+
low: { ...DEFAULT_CONFIG.effortMapping.low },
|
|
410
|
+
medium: { ...DEFAULT_CONFIG.effortMapping.medium },
|
|
411
|
+
high: { ...DEFAULT_CONFIG.effortMapping.high },
|
|
412
|
+
},
|
|
323
413
|
commitFormat: { ...DEFAULT_CONFIG.commitFormat },
|
|
324
414
|
display: { ...DEFAULT_CONFIG.display },
|
|
325
415
|
};
|
|
@@ -328,6 +418,9 @@ export function resolveConfig(configPath?: string): RafConfig {
|
|
|
328
418
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
329
419
|
const parsed: unknown = JSON.parse(content);
|
|
330
420
|
const validated = validateConfig(parsed);
|
|
421
|
+
for (const warning of collectConfigValidationWarnings(validated)) {
|
|
422
|
+
logger.warn(`Config validation warning: ${warning}`);
|
|
423
|
+
}
|
|
331
424
|
return deepMerge(DEFAULT_CONFIG, validated);
|
|
332
425
|
}
|
|
333
426
|
|
|
@@ -365,36 +458,27 @@ export function resetConfigCache(): void {
|
|
|
365
458
|
_cachedConfig = null;
|
|
366
459
|
}
|
|
367
460
|
|
|
368
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Get the model entry for a scenario.
|
|
463
|
+
*/
|
|
464
|
+
export function getModel(scenario: ModelScenario): ModelEntry {
|
|
369
465
|
const config = getResolvedConfig();
|
|
370
|
-
const effectiveProvider = provider ?? config.provider;
|
|
371
|
-
if (effectiveProvider === 'codex') {
|
|
372
|
-
return config.codexModels[scenario];
|
|
373
|
-
}
|
|
374
466
|
return config.models[scenario];
|
|
375
467
|
}
|
|
376
468
|
|
|
377
469
|
/**
|
|
378
470
|
* Get the full effort mapping config.
|
|
379
471
|
*/
|
|
380
|
-
export function getEffortMapping(
|
|
472
|
+
export function getEffortMapping(): EffortMappingConfig {
|
|
381
473
|
const config = getResolvedConfig();
|
|
382
|
-
const effectiveProvider = provider ?? config.provider;
|
|
383
|
-
if (effectiveProvider === 'codex') {
|
|
384
|
-
return config.codexEffortMapping;
|
|
385
|
-
}
|
|
386
474
|
return config.effortMapping;
|
|
387
475
|
}
|
|
388
476
|
|
|
389
477
|
/**
|
|
390
|
-
* Resolve a task effort level to a model
|
|
478
|
+
* Resolve a task effort level to a model entry using the effort mapping config.
|
|
391
479
|
*/
|
|
392
|
-
export function resolveEffortToModel(effort: TaskEffortLevel
|
|
480
|
+
export function resolveEffortToModel(effort: TaskEffortLevel): ModelEntry {
|
|
393
481
|
const config = getResolvedConfig();
|
|
394
|
-
const effectiveProvider = provider ?? config.provider;
|
|
395
|
-
if (effectiveProvider === 'codex') {
|
|
396
|
-
return config.codexEffortMapping[effort];
|
|
397
|
-
}
|
|
398
482
|
return config.effortMapping[effort];
|
|
399
483
|
}
|
|
400
484
|
|
|
@@ -409,14 +493,12 @@ const MODEL_TIER_ORDER: Record<string, number> = {
|
|
|
409
493
|
opus: 3,
|
|
410
494
|
};
|
|
411
495
|
|
|
412
|
-
/** Codex model tier ordering:
|
|
496
|
+
/** Codex model tier ordering: codex (1) < gpt54 (2) */
|
|
413
497
|
const CODEX_MODEL_TIER_ORDER: Record<string, number> = {
|
|
414
|
-
|
|
415
|
-
'gpt-5.3-codex
|
|
416
|
-
|
|
417
|
-
'gpt-5.
|
|
418
|
-
gpt54: 3,
|
|
419
|
-
'gpt-5.4': 3,
|
|
498
|
+
codex: 1,
|
|
499
|
+
'gpt-5.3-codex': 1,
|
|
500
|
+
gpt54: 2,
|
|
501
|
+
'gpt-5.4': 2,
|
|
420
502
|
};
|
|
421
503
|
|
|
422
504
|
/**
|
|
@@ -451,19 +533,20 @@ export function getModelTier(modelName: string): number {
|
|
|
451
533
|
}
|
|
452
534
|
|
|
453
535
|
/**
|
|
454
|
-
* Apply ceiling to a model based on the configured models.execute ceiling.
|
|
455
|
-
* Returns the lower-tier
|
|
536
|
+
* Apply ceiling to a model entry based on the configured models.execute ceiling.
|
|
537
|
+
* Returns the lower-tier entry between the input and the ceiling.
|
|
538
|
+
* When the input exceeds the ceiling, the ceiling entry is returned (including its harness).
|
|
456
539
|
*/
|
|
457
|
-
export function applyModelCeiling(
|
|
458
|
-
const
|
|
459
|
-
const resolvedTier = getModelTier(
|
|
460
|
-
const ceilingTier = getModelTier(
|
|
540
|
+
export function applyModelCeiling(resolved: ModelEntry, ceiling?: ModelEntry): ModelEntry {
|
|
541
|
+
const ceilingEntry = ceiling ?? getModel('execute');
|
|
542
|
+
const resolvedTier = getModelTier(resolved.model);
|
|
543
|
+
const ceilingTier = getModelTier(ceilingEntry.model);
|
|
461
544
|
|
|
462
545
|
// If resolved model is above ceiling, use ceiling instead
|
|
463
546
|
if (resolvedTier > ceilingTier) {
|
|
464
|
-
return
|
|
547
|
+
return ceilingEntry;
|
|
465
548
|
}
|
|
466
|
-
return
|
|
549
|
+
return resolved;
|
|
467
550
|
}
|
|
468
551
|
|
|
469
552
|
export function getCommitFormat(type: CommitFormatType): string {
|
|
@@ -505,11 +588,10 @@ export function getModelShortName(modelId: string): string {
|
|
|
505
588
|
return modelId;
|
|
506
589
|
}
|
|
507
590
|
// Codex short aliases
|
|
508
|
-
if (modelId === '
|
|
591
|
+
if (modelId === 'codex' || modelId === 'gpt54') {
|
|
509
592
|
return modelId;
|
|
510
593
|
}
|
|
511
594
|
// Codex model IDs -> short names
|
|
512
|
-
if (modelId === 'gpt-5.3-codex-spark') return 'spark';
|
|
513
595
|
if (modelId === 'gpt-5.3-codex') return 'codex';
|
|
514
596
|
if (modelId === 'gpt-5.4') return 'gpt54';
|
|
515
597
|
// Extract family from full Claude model ID: claude-{family}-{version}
|
|
@@ -524,6 +606,10 @@ export function getModelShortName(modelId: string): string {
|
|
|
524
606
|
return modelId;
|
|
525
607
|
}
|
|
526
608
|
|
|
609
|
+
function normalizeModelAlias(value: string): string {
|
|
610
|
+
return value.toLowerCase().replace(/^gpt-/, 'gpt').replace(/[^a-z0-9]/g, '');
|
|
611
|
+
}
|
|
612
|
+
|
|
527
613
|
/**
|
|
528
614
|
* Mapping of short model aliases to their current full model IDs.
|
|
529
615
|
* These should match the latest Claude model versions.
|
|
@@ -532,11 +618,58 @@ const MODEL_ALIAS_TO_FULL_ID: Record<string, string> = {
|
|
|
532
618
|
opus: 'claude-opus-4-6',
|
|
533
619
|
sonnet: 'claude-sonnet-4-5-20250929',
|
|
534
620
|
haiku: 'claude-haiku-4-5-20251001',
|
|
535
|
-
spark: 'gpt-5.3-codex-spark',
|
|
536
621
|
codex: 'gpt-5.3-codex',
|
|
537
622
|
gpt54: 'gpt-5.4',
|
|
538
623
|
};
|
|
539
624
|
|
|
625
|
+
function getPreferredModelDisplayLabel(alias: string): string {
|
|
626
|
+
const fullId = MODEL_ALIAS_TO_FULL_ID[alias];
|
|
627
|
+
if (!fullId) {
|
|
628
|
+
return alias;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return normalizeModelAlias(alias) === normalizeModelAlias(fullId) ? fullId : alias;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Get the centralized user-facing display label for a model.
|
|
636
|
+
* Keeps concise Claude labels, while normalizing compact aliases like gpt54 -> gpt-5.4.
|
|
637
|
+
*/
|
|
638
|
+
export function getModelDisplayName(modelId: string): string {
|
|
639
|
+
const aliasedDisplay = getPreferredModelDisplayLabel(modelId);
|
|
640
|
+
if (aliasedDisplay !== modelId) {
|
|
641
|
+
return aliasedDisplay;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const matchingAliases = Object.entries(MODEL_ALIAS_TO_FULL_ID)
|
|
645
|
+
.filter(([, fullId]) => fullId === modelId)
|
|
646
|
+
.map(([alias]) => alias);
|
|
647
|
+
if (matchingAliases.length > 0) {
|
|
648
|
+
const preferredAlias = matchingAliases[matchingAliases.length - 1]!;
|
|
649
|
+
return getPreferredModelDisplayLabel(preferredAlias);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return getModelShortName(modelId);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
interface ModelDisplayOptions {
|
|
656
|
+
includeHarness?: boolean;
|
|
657
|
+
fullId?: boolean;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Format a model label for user-facing logs.
|
|
662
|
+
* Defaults to the centralized display policy, or can opt into explicit full-ID display.
|
|
663
|
+
*/
|
|
664
|
+
export function formatModelDisplay(
|
|
665
|
+
model: string,
|
|
666
|
+
harness?: HarnessName,
|
|
667
|
+
options: ModelDisplayOptions = {},
|
|
668
|
+
): string {
|
|
669
|
+
const label = options.fullId ? resolveFullModelId(model) : getModelDisplayName(model);
|
|
670
|
+
return options.includeHarness && harness ? `${label} (${harness})` : label;
|
|
671
|
+
}
|
|
672
|
+
|
|
540
673
|
/**
|
|
541
674
|
* Resolve a model name to its full model ID.
|
|
542
675
|
* If already a full model ID, returns as-is.
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { logger } from './logger.js';
|
|
3
3
|
import { sanitizeProjectName } from './validation.js';
|
|
4
4
|
import { getModel } from './config.js';
|
|
5
|
+
import type { HarnessName } from '../types/config.js';
|
|
5
6
|
|
|
6
7
|
const NAME_GENERATION_PROMPT = `Output ONLY the kebab-case name. No introduction, no explanation, no quotes.
|
|
7
8
|
|
|
@@ -29,20 +30,35 @@ Rules:
|
|
|
29
30
|
|
|
30
31
|
Project description:`;
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*/
|
|
36
|
-
function runClaudePrint(prompt: string): Promise<string | null> {
|
|
37
|
-
return new Promise((resolve) => {
|
|
38
|
-
const model = getModel('nameGeneration');
|
|
39
|
-
|
|
40
|
-
const proc = spawn('claude', [
|
|
33
|
+
function buildNameGenerationArgs(harness: HarnessName, model: string, prompt: string): string[] {
|
|
34
|
+
if (harness === 'claude') {
|
|
35
|
+
return [
|
|
41
36
|
'--model', model,
|
|
42
37
|
'--no-session-persistence',
|
|
43
38
|
'-p',
|
|
44
39
|
prompt,
|
|
45
|
-
]
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
'exec',
|
|
45
|
+
'--skip-git-repo-check',
|
|
46
|
+
'--ephemeral',
|
|
47
|
+
'--color', 'never',
|
|
48
|
+
'-m', model,
|
|
49
|
+
prompt,
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run the configured name-generation CLI with the given prompt and return stdout.
|
|
55
|
+
*/
|
|
56
|
+
function runNameGenerationPrint(prompt: string): Promise<string | null> {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const entry = getModel('nameGeneration');
|
|
59
|
+
const args = buildNameGenerationArgs(entry.harness, entry.model, prompt);
|
|
60
|
+
|
|
61
|
+
const proc = spawn(entry.harness, args, {
|
|
46
62
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
47
63
|
});
|
|
48
64
|
|
|
@@ -110,7 +126,7 @@ export async function generateProjectName(description: string): Promise<string>
|
|
|
110
126
|
export async function generateProjectNames(description: string): Promise<string[]> {
|
|
111
127
|
try {
|
|
112
128
|
const names = await callSonnetForMultipleNames(description);
|
|
113
|
-
if (names.length
|
|
129
|
+
if (names.length > 0) {
|
|
114
130
|
logger.debug(`Generated ${names.length} project names`);
|
|
115
131
|
return names;
|
|
116
132
|
}
|
|
@@ -129,7 +145,7 @@ export async function generateProjectNames(description: string): Promise<string[
|
|
|
129
145
|
*/
|
|
130
146
|
async function callSonnetForName(description: string): Promise<string | null> {
|
|
131
147
|
const fullPrompt = `${NAME_GENERATION_PROMPT}\n${description}`;
|
|
132
|
-
return
|
|
148
|
+
return runNameGenerationPrint(fullPrompt);
|
|
133
149
|
}
|
|
134
150
|
|
|
135
151
|
/**
|
|
@@ -137,7 +153,7 @@ async function callSonnetForName(description: string): Promise<string | null> {
|
|
|
137
153
|
*/
|
|
138
154
|
async function callSonnetForMultipleNames(description: string): Promise<string[]> {
|
|
139
155
|
const fullPrompt = `${MULTI_NAME_GENERATION_PROMPT}\n${description}`;
|
|
140
|
-
const result = await
|
|
156
|
+
const result = await runNameGenerationPrint(fullPrompt);
|
|
141
157
|
|
|
142
158
|
if (!result) {
|
|
143
159
|
return [];
|