@nathapp/nax 0.50.3 → 0.51.2
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/CHANGELOG.md +30 -0
- package/README.md +177 -104
- package/dist/nax.js +417 -213
- package/package.json +1 -3
- package/bin/nax.ts +0 -1195
- package/src/acceptance/fix-generator.ts +0 -322
- package/src/acceptance/generator.ts +0 -415
- package/src/acceptance/index.ts +0 -42
- package/src/acceptance/refinement.ts +0 -224
- package/src/acceptance/templates/cli.ts +0 -47
- package/src/acceptance/templates/component.ts +0 -78
- package/src/acceptance/templates/e2e.ts +0 -43
- package/src/acceptance/templates/index.ts +0 -21
- package/src/acceptance/templates/snapshot.ts +0 -50
- package/src/acceptance/templates/unit.ts +0 -48
- package/src/acceptance/types.ts +0 -138
- package/src/agents/acp/adapter.ts +0 -888
- package/src/agents/acp/cost.ts +0 -9
- package/src/agents/acp/index.ts +0 -7
- package/src/agents/acp/interaction-bridge.ts +0 -126
- package/src/agents/acp/parser.ts +0 -119
- package/src/agents/acp/spawn-client.ts +0 -373
- package/src/agents/acp/types.ts +0 -22
- package/src/agents/aider/adapter.ts +0 -135
- package/src/agents/claude/adapter.ts +0 -258
- package/src/agents/claude/complete.ts +0 -80
- package/src/agents/claude/cost.ts +0 -16
- package/src/agents/claude/execution.ts +0 -215
- package/src/agents/claude/index.ts +0 -3
- package/src/agents/claude/interactive.ts +0 -77
- package/src/agents/claude/plan.ts +0 -179
- package/src/agents/codex/adapter.ts +0 -153
- package/src/agents/cost/calculate.ts +0 -154
- package/src/agents/cost/index.ts +0 -10
- package/src/agents/cost/parse.ts +0 -97
- package/src/agents/cost/pricing.ts +0 -59
- package/src/agents/cost/types.ts +0 -45
- package/src/agents/gemini/adapter.ts +0 -177
- package/src/agents/index.ts +0 -18
- package/src/agents/opencode/adapter.ts +0 -106
- package/src/agents/registry.ts +0 -136
- package/src/agents/shared/decompose.ts +0 -154
- package/src/agents/shared/model-resolution.ts +0 -43
- package/src/agents/shared/types-extended.ts +0 -164
- package/src/agents/shared/validation.ts +0 -69
- package/src/agents/shared/version-detection.ts +0 -109
- package/src/agents/types.ts +0 -205
- package/src/analyze/classifier.ts +0 -282
- package/src/analyze/index.ts +0 -16
- package/src/analyze/scanner.ts +0 -171
- package/src/analyze/types.ts +0 -51
- package/src/cli/accept.ts +0 -108
- package/src/cli/agents.ts +0 -87
- package/src/cli/analyze-parser.ts +0 -291
- package/src/cli/analyze.ts +0 -352
- package/src/cli/config-descriptions.ts +0 -219
- package/src/cli/config-diff.ts +0 -103
- package/src/cli/config-display.ts +0 -285
- package/src/cli/config-get.ts +0 -55
- package/src/cli/config.ts +0 -14
- package/src/cli/constitution.ts +0 -17
- package/src/cli/diagnose-analysis.ts +0 -159
- package/src/cli/diagnose-formatter.ts +0 -87
- package/src/cli/diagnose.ts +0 -203
- package/src/cli/generate.ts +0 -250
- package/src/cli/index.ts +0 -42
- package/src/cli/init-context.ts +0 -405
- package/src/cli/init-detect.ts +0 -303
- package/src/cli/init.ts +0 -296
- package/src/cli/interact.ts +0 -295
- package/src/cli/plan.ts +0 -509
- package/src/cli/plugins.ts +0 -122
- package/src/cli/prompts-export.ts +0 -58
- package/src/cli/prompts-init.ts +0 -200
- package/src/cli/prompts-main.ts +0 -183
- package/src/cli/prompts-shared.ts +0 -70
- package/src/cli/prompts-tdd.ts +0 -88
- package/src/cli/prompts.ts +0 -17
- package/src/cli/runs.ts +0 -174
- package/src/cli/status-cost.ts +0 -151
- package/src/cli/status-features.ts +0 -405
- package/src/cli/status.ts +0 -13
- package/src/commands/common.ts +0 -171
- package/src/commands/diagnose.ts +0 -17
- package/src/commands/index.ts +0 -9
- package/src/commands/logs-formatter.ts +0 -201
- package/src/commands/logs-reader.ts +0 -171
- package/src/commands/logs.ts +0 -103
- package/src/commands/precheck.ts +0 -86
- package/src/commands/runs.ts +0 -220
- package/src/commands/unlock.ts +0 -96
- package/src/config/defaults.ts +0 -218
- package/src/config/index.ts +0 -22
- package/src/config/loader.ts +0 -143
- package/src/config/merge.ts +0 -106
- package/src/config/merger.ts +0 -147
- package/src/config/path-security.ts +0 -121
- package/src/config/paths.ts +0 -27
- package/src/config/permissions.ts +0 -63
- package/src/config/runtime-types.ts +0 -522
- package/src/config/schema-types.ts +0 -53
- package/src/config/schema.ts +0 -60
- package/src/config/schemas.ts +0 -426
- package/src/config/test-strategy.ts +0 -71
- package/src/config/types.ts +0 -57
- package/src/config/validate.ts +0 -103
- package/src/constitution/generator.ts +0 -158
- package/src/constitution/generators/aider.ts +0 -41
- package/src/constitution/generators/claude.ts +0 -35
- package/src/constitution/generators/cursor.ts +0 -36
- package/src/constitution/generators/opencode.ts +0 -38
- package/src/constitution/generators/types.ts +0 -33
- package/src/constitution/generators/windsurf.ts +0 -36
- package/src/constitution/index.ts +0 -11
- package/src/constitution/loader.ts +0 -121
- package/src/constitution/types.ts +0 -31
- package/src/context/auto-detect.ts +0 -228
- package/src/context/builder.ts +0 -299
- package/src/context/elements.ts +0 -122
- package/src/context/formatter.ts +0 -107
- package/src/context/generator.ts +0 -343
- package/src/context/generators/aider.ts +0 -34
- package/src/context/generators/claude.ts +0 -28
- package/src/context/generators/codex.ts +0 -28
- package/src/context/generators/cursor.ts +0 -28
- package/src/context/generators/gemini.ts +0 -28
- package/src/context/generators/opencode.ts +0 -30
- package/src/context/generators/windsurf.ts +0 -28
- package/src/context/greenfield.ts +0 -114
- package/src/context/index.ts +0 -34
- package/src/context/injector.ts +0 -279
- package/src/context/parent-context.ts +0 -39
- package/src/context/test-scanner.ts +0 -370
- package/src/context/types.ts +0 -98
- package/src/decompose/apply.ts +0 -50
- package/src/decompose/builder.ts +0 -181
- package/src/decompose/index.ts +0 -8
- package/src/decompose/sections/codebase.ts +0 -26
- package/src/decompose/sections/constraints.ts +0 -32
- package/src/decompose/sections/index.ts +0 -4
- package/src/decompose/sections/sibling-stories.ts +0 -25
- package/src/decompose/sections/target-story.ts +0 -31
- package/src/decompose/types.ts +0 -55
- package/src/decompose/validators/complexity.ts +0 -45
- package/src/decompose/validators/coverage.ts +0 -134
- package/src/decompose/validators/dependency.ts +0 -91
- package/src/decompose/validators/index.ts +0 -35
- package/src/decompose/validators/overlap.ts +0 -128
- package/src/errors.ts +0 -67
- package/src/execution/batching.ts +0 -157
- package/src/execution/crash-heartbeat.ts +0 -77
- package/src/execution/crash-recovery.ts +0 -79
- package/src/execution/crash-signals.ts +0 -165
- package/src/execution/crash-writer.ts +0 -154
- package/src/execution/deferred-review.ts +0 -105
- package/src/execution/dry-run.ts +0 -81
- package/src/execution/escalation/escalation.ts +0 -46
- package/src/execution/escalation/index.ts +0 -13
- package/src/execution/escalation/tier-escalation.ts +0 -346
- package/src/execution/escalation/tier-outcome.ts +0 -143
- package/src/execution/executor-types.ts +0 -73
- package/src/execution/helpers.ts +0 -38
- package/src/execution/index.ts +0 -27
- package/src/execution/iteration-runner.ts +0 -160
- package/src/execution/lifecycle/acceptance-loop.ts +0 -309
- package/src/execution/lifecycle/headless-formatter.ts +0 -83
- package/src/execution/lifecycle/index.ts +0 -11
- package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
- package/src/execution/lifecycle/precheck-runner.ts +0 -140
- package/src/execution/lifecycle/run-cleanup.ts +0 -81
- package/src/execution/lifecycle/run-completion.ts +0 -247
- package/src/execution/lifecycle/run-initialization.ts +0 -187
- package/src/execution/lifecycle/run-regression.ts +0 -305
- package/src/execution/lifecycle/run-setup.ts +0 -240
- package/src/execution/lifecycle/story-size-prompts.ts +0 -123
- package/src/execution/lock.ts +0 -129
- package/src/execution/parallel-coordinator.ts +0 -281
- package/src/execution/parallel-executor-rectification-pass.ts +0 -117
- package/src/execution/parallel-executor-rectify.ts +0 -136
- package/src/execution/parallel-executor.ts +0 -330
- package/src/execution/parallel-worker.ts +0 -149
- package/src/execution/parallel.ts +0 -13
- package/src/execution/pid-registry.ts +0 -275
- package/src/execution/pipeline-result-handler.ts +0 -221
- package/src/execution/progress.ts +0 -27
- package/src/execution/queue-handler.ts +0 -109
- package/src/execution/runner-completion.ts +0 -171
- package/src/execution/runner-execution.ts +0 -243
- package/src/execution/runner-setup.ts +0 -86
- package/src/execution/runner.ts +0 -265
- package/src/execution/sequential-executor.ts +0 -219
- package/src/execution/status-file.ts +0 -264
- package/src/execution/status-writer.ts +0 -181
- package/src/execution/story-context.ts +0 -266
- package/src/execution/story-selector.ts +0 -76
- package/src/execution/test-output-parser.ts +0 -14
- package/src/execution/timeout-handler.ts +0 -100
- package/src/hooks/index.ts +0 -2
- package/src/hooks/runner.ts +0 -280
- package/src/hooks/types.ts +0 -79
- package/src/interaction/chain.ts +0 -170
- package/src/interaction/index.ts +0 -61
- package/src/interaction/init.ts +0 -84
- package/src/interaction/plugins/auto.ts +0 -243
- package/src/interaction/plugins/cli.ts +0 -300
- package/src/interaction/plugins/telegram.ts +0 -384
- package/src/interaction/plugins/webhook.ts +0 -286
- package/src/interaction/state.ts +0 -171
- package/src/interaction/triggers.ts +0 -250
- package/src/interaction/types.ts +0 -170
- package/src/logger/formatters.ts +0 -84
- package/src/logger/index.ts +0 -16
- package/src/logger/logger.ts +0 -296
- package/src/logger/types.ts +0 -48
- package/src/logging/formatter.ts +0 -355
- package/src/logging/index.ts +0 -22
- package/src/logging/types.ts +0 -93
- package/src/metrics/aggregator.ts +0 -191
- package/src/metrics/index.ts +0 -14
- package/src/metrics/tracker.ts +0 -200
- package/src/metrics/types.ts +0 -115
- package/src/optimizer/index.ts +0 -63
- package/src/optimizer/noop.optimizer.ts +0 -24
- package/src/optimizer/rule-based.optimizer.ts +0 -248
- package/src/optimizer/types.ts +0 -53
- package/src/pipeline/event-bus.ts +0 -297
- package/src/pipeline/events.ts +0 -130
- package/src/pipeline/index.ts +0 -19
- package/src/pipeline/runner.ts +0 -149
- package/src/pipeline/stages/acceptance-setup.ts +0 -144
- package/src/pipeline/stages/acceptance.ts +0 -215
- package/src/pipeline/stages/autofix.ts +0 -262
- package/src/pipeline/stages/completion.ts +0 -110
- package/src/pipeline/stages/constitution.ts +0 -63
- package/src/pipeline/stages/context.ts +0 -122
- package/src/pipeline/stages/execution.ts +0 -359
- package/src/pipeline/stages/index.ts +0 -86
- package/src/pipeline/stages/optimizer.ts +0 -74
- package/src/pipeline/stages/prompt.ts +0 -79
- package/src/pipeline/stages/queue-check.ts +0 -103
- package/src/pipeline/stages/rectify.ts +0 -101
- package/src/pipeline/stages/regression.ts +0 -99
- package/src/pipeline/stages/review.ts +0 -94
- package/src/pipeline/stages/routing.ts +0 -276
- package/src/pipeline/stages/verify.ts +0 -286
- package/src/pipeline/subscribers/events-writer.ts +0 -135
- package/src/pipeline/subscribers/hooks.ts +0 -179
- package/src/pipeline/subscribers/interaction.ts +0 -103
- package/src/pipeline/subscribers/registry.ts +0 -73
- package/src/pipeline/subscribers/reporters.ts +0 -174
- package/src/pipeline/types.ts +0 -220
- package/src/plugins/extensions.ts +0 -225
- package/src/plugins/index.ts +0 -33
- package/src/plugins/loader.ts +0 -352
- package/src/plugins/plugin-logger.ts +0 -41
- package/src/plugins/registry.ts +0 -168
- package/src/plugins/types.ts +0 -206
- package/src/plugins/validator.ts +0 -352
- package/src/prd/index.ts +0 -220
- package/src/prd/schema.ts +0 -268
- package/src/prd/types.ts +0 -273
- package/src/prd/validate.ts +0 -41
- package/src/precheck/checks-agents.ts +0 -63
- package/src/precheck/checks-blockers.ts +0 -23
- package/src/precheck/checks-cli.ts +0 -68
- package/src/precheck/checks-config.ts +0 -102
- package/src/precheck/checks-git.ts +0 -117
- package/src/precheck/checks-system.ts +0 -101
- package/src/precheck/checks-warnings.ts +0 -221
- package/src/precheck/checks.ts +0 -36
- package/src/precheck/index.ts +0 -374
- package/src/precheck/story-size-gate.ts +0 -144
- package/src/precheck/types.ts +0 -31
- package/src/prompts/builder.ts +0 -166
- package/src/prompts/index.ts +0 -2
- package/src/prompts/loader.ts +0 -43
- package/src/prompts/sections/conventions.ts +0 -19
- package/src/prompts/sections/hermetic.ts +0 -41
- package/src/prompts/sections/index.ts +0 -12
- package/src/prompts/sections/isolation.ts +0 -70
- package/src/prompts/sections/role-task.ts +0 -182
- package/src/prompts/sections/story.ts +0 -55
- package/src/prompts/sections/verdict.ts +0 -70
- package/src/prompts/types.ts +0 -21
- package/src/queue/index.ts +0 -2
- package/src/queue/manager.ts +0 -254
- package/src/queue/types.ts +0 -54
- package/src/review/index.ts +0 -8
- package/src/review/orchestrator.ts +0 -154
- package/src/review/runner.ts +0 -303
- package/src/review/types.ts +0 -70
- package/src/routing/batch-route.ts +0 -35
- package/src/routing/builder.ts +0 -81
- package/src/routing/chain.ts +0 -75
- package/src/routing/content-hash.ts +0 -25
- package/src/routing/index.ts +0 -20
- package/src/routing/loader.ts +0 -62
- package/src/routing/router.ts +0 -305
- package/src/routing/strategies/adaptive.ts +0 -215
- package/src/routing/strategies/index.ts +0 -8
- package/src/routing/strategies/keyword.ts +0 -180
- package/src/routing/strategies/llm-prompts.ts +0 -224
- package/src/routing/strategies/llm.ts +0 -320
- package/src/routing/strategies/manual.ts +0 -50
- package/src/routing/strategy.ts +0 -102
- package/src/tdd/cleanup.ts +0 -120
- package/src/tdd/index.ts +0 -22
- package/src/tdd/isolation.ts +0 -117
- package/src/tdd/orchestrator.ts +0 -406
- package/src/tdd/prompts.ts +0 -40
- package/src/tdd/rectification-gate.ts +0 -274
- package/src/tdd/session-runner.ts +0 -263
- package/src/tdd/types.ts +0 -84
- package/src/tdd/verdict-reader.ts +0 -266
- package/src/tdd/verdict.ts +0 -152
- package/src/tui/App.tsx +0 -265
- package/src/tui/components/AgentPanel.tsx +0 -75
- package/src/tui/components/CostOverlay.tsx +0 -118
- package/src/tui/components/HelpOverlay.tsx +0 -107
- package/src/tui/components/StatusBar.tsx +0 -63
- package/src/tui/components/StoriesPanel.tsx +0 -177
- package/src/tui/hooks/useKeyboard.ts +0 -142
- package/src/tui/hooks/useLayout.ts +0 -137
- package/src/tui/hooks/usePipelineEvents.ts +0 -183
- package/src/tui/hooks/usePty.ts +0 -189
- package/src/tui/index.tsx +0 -38
- package/src/tui/types.ts +0 -76
- package/src/utils/errors.ts +0 -12
- package/src/utils/git.ts +0 -245
- package/src/utils/json-file.ts +0 -72
- package/src/utils/log-test-output.ts +0 -25
- package/src/utils/path-security.ts +0 -73
- package/src/utils/queue-writer.ts +0 -54
- package/src/verification/crash-detector.ts +0 -34
- package/src/verification/executor.ts +0 -250
- package/src/verification/index.ts +0 -12
- package/src/verification/orchestrator-types.ts +0 -154
- package/src/verification/orchestrator.ts +0 -76
- package/src/verification/parser.ts +0 -220
- package/src/verification/rectification-loop.ts +0 -172
- package/src/verification/rectification.ts +0 -108
- package/src/verification/runners.ts +0 -129
- package/src/verification/smart-runner.ts +0 -307
- package/src/verification/strategies/acceptance.ts +0 -136
- package/src/verification/strategies/regression.ts +0 -90
- package/src/verification/strategies/scoped.ts +0 -154
- package/src/verification/types.ts +0 -117
- package/src/version.ts +0 -40
- package/src/worktree/dispatcher.ts +0 -6
- package/src/worktree/index.ts +0 -2
- package/src/worktree/manager.ts +0 -193
- package/src/worktree/merge.ts +0 -302
- package/src/worktree/types.ts +0 -4
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cost rate tables for all supported model tiers and specific models.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ModelTier } from "../../config/schema";
|
|
6
|
-
import type { ModelCostRates } from "./types";
|
|
7
|
-
|
|
8
|
-
/** Model tier cost rates (as of 2025-01) */
|
|
9
|
-
export const COST_RATES: Record<ModelTier, ModelCostRates> = {
|
|
10
|
-
fast: {
|
|
11
|
-
// Haiku 4.5
|
|
12
|
-
inputPer1M: 0.8,
|
|
13
|
-
outputPer1M: 4.0,
|
|
14
|
-
},
|
|
15
|
-
balanced: {
|
|
16
|
-
// Sonnet 4.5
|
|
17
|
-
inputPer1M: 3.0,
|
|
18
|
-
outputPer1M: 15.0,
|
|
19
|
-
},
|
|
20
|
-
powerful: {
|
|
21
|
-
// Opus 4
|
|
22
|
-
inputPer1M: 15.0,
|
|
23
|
-
outputPer1M: 75.0,
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/** Per-model pricing in $/1M tokens: { input, output } */
|
|
28
|
-
export const MODEL_PRICING: Record<
|
|
29
|
-
string,
|
|
30
|
-
{ input: number; output: number; cacheRead?: number; cacheCreation?: number }
|
|
31
|
-
> = {
|
|
32
|
-
// Anthropic Claude models (short aliases)
|
|
33
|
-
sonnet: { input: 3, output: 15 },
|
|
34
|
-
haiku: { input: 0.8, output: 4.0, cacheRead: 0.1, cacheCreation: 1.0 },
|
|
35
|
-
opus: { input: 15, output: 75 },
|
|
36
|
-
|
|
37
|
-
// Anthropic Claude models (full names)
|
|
38
|
-
"claude-sonnet-4": { input: 3, output: 15 },
|
|
39
|
-
"claude-sonnet-4-5": { input: 3, output: 15 },
|
|
40
|
-
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
41
|
-
"claude-haiku": { input: 0.8, output: 4.0, cacheRead: 0.1, cacheCreation: 1.0 },
|
|
42
|
-
"claude-haiku-4-5": { input: 0.8, output: 4.0, cacheRead: 0.1, cacheCreation: 1.0 },
|
|
43
|
-
"claude-opus": { input: 15, output: 75 },
|
|
44
|
-
"claude-opus-4": { input: 15, output: 75 },
|
|
45
|
-
"claude-opus-4-6": { input: 15, output: 75 },
|
|
46
|
-
|
|
47
|
-
// OpenAI models
|
|
48
|
-
"gpt-4.1": { input: 10, output: 30 },
|
|
49
|
-
"gpt-4": { input: 30, output: 60 },
|
|
50
|
-
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
51
|
-
|
|
52
|
-
// Google Gemini
|
|
53
|
-
"gemini-2.5-pro": { input: 0.075, output: 0.3 },
|
|
54
|
-
"gemini-2-pro": { input: 0.075, output: 0.3 },
|
|
55
|
-
|
|
56
|
-
// OpenAI Codex
|
|
57
|
-
codex: { input: 0.02, output: 0.06 },
|
|
58
|
-
"code-davinci-002": { input: 0.02, output: 0.06 },
|
|
59
|
-
};
|
package/src/agents/cost/types.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cost tracking types — shared across all agent adapters.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ModelTier } from "../../config/schema";
|
|
6
|
-
|
|
7
|
-
export type { ModelTier };
|
|
8
|
-
|
|
9
|
-
/** Cost rates per 1M tokens (USD) */
|
|
10
|
-
export interface ModelCostRates {
|
|
11
|
-
inputPer1M: number;
|
|
12
|
-
outputPer1M: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Token usage data (camelCase — nax-internal representation) */
|
|
16
|
-
export interface TokenUsage {
|
|
17
|
-
inputTokens: number;
|
|
18
|
-
outputTokens: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Cost estimate with confidence indicator */
|
|
22
|
-
export interface CostEstimate {
|
|
23
|
-
cost: number;
|
|
24
|
-
confidence: "exact" | "estimated" | "fallback";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Token usage with confidence indicator */
|
|
28
|
-
export interface TokenUsageWithConfidence {
|
|
29
|
-
inputTokens: number;
|
|
30
|
-
outputTokens: number;
|
|
31
|
-
confidence: "exact" | "estimated";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Token usage from an ACP session's cumulative_token_usage field.
|
|
36
|
-
* Uses snake_case to match the ACP wire format.
|
|
37
|
-
*/
|
|
38
|
-
export interface SessionTokenUsage {
|
|
39
|
-
input_tokens: number;
|
|
40
|
-
output_tokens: number;
|
|
41
|
-
/** Cache read tokens — billed at a reduced rate */
|
|
42
|
-
cache_read_input_tokens?: number;
|
|
43
|
-
/** Cache creation tokens — billed at a higher creation rate */
|
|
44
|
-
cache_creation_input_tokens?: number;
|
|
45
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gemini CLI Agent Adapter — implements AgentAdapter interface
|
|
3
|
-
*
|
|
4
|
-
* Provides uniform interface for spawning Gemini CLI processes,
|
|
5
|
-
* supporting one-shot completions via 'gemini -p' and Google auth detection.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
AgentAdapter,
|
|
10
|
-
AgentCapabilities,
|
|
11
|
-
AgentResult,
|
|
12
|
-
AgentRunOptions,
|
|
13
|
-
CompleteOptions,
|
|
14
|
-
DecomposeOptions,
|
|
15
|
-
DecomposeResult,
|
|
16
|
-
PlanOptions,
|
|
17
|
-
PlanResult,
|
|
18
|
-
} from "../types";
|
|
19
|
-
import { CompleteError } from "../types";
|
|
20
|
-
|
|
21
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
// Injectable dependencies — follows the _deps pattern
|
|
23
|
-
// Replaced in unit tests to intercept Bun.spawn/Bun.which calls.
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
export const _geminiRunDeps = {
|
|
27
|
-
which(name: string): string | null {
|
|
28
|
-
return Bun.which(name);
|
|
29
|
-
},
|
|
30
|
-
spawn(
|
|
31
|
-
cmd: string[],
|
|
32
|
-
opts: { cwd?: string; stdout: "pipe"; stderr: "pipe" | "inherit"; env?: Record<string, string | undefined> },
|
|
33
|
-
): {
|
|
34
|
-
stdout: ReadableStream<Uint8Array>;
|
|
35
|
-
stderr: ReadableStream<Uint8Array>;
|
|
36
|
-
exited: Promise<number>;
|
|
37
|
-
pid: number;
|
|
38
|
-
kill(signal?: number | NodeJS.Signals): void;
|
|
39
|
-
} {
|
|
40
|
-
return Bun.spawn(cmd, opts) as unknown as {
|
|
41
|
-
stdout: ReadableStream<Uint8Array>;
|
|
42
|
-
stderr: ReadableStream<Uint8Array>;
|
|
43
|
-
exited: Promise<number>;
|
|
44
|
-
pid: number;
|
|
45
|
-
kill(signal?: number | NodeJS.Signals): void;
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const _geminiCompleteDeps = {
|
|
51
|
-
spawn(
|
|
52
|
-
cmd: string[],
|
|
53
|
-
opts: { stdout: "pipe"; stderr: "pipe" | "inherit" },
|
|
54
|
-
): {
|
|
55
|
-
stdout: ReadableStream<Uint8Array>;
|
|
56
|
-
stderr: ReadableStream<Uint8Array>;
|
|
57
|
-
exited: Promise<number>;
|
|
58
|
-
pid: number;
|
|
59
|
-
} {
|
|
60
|
-
return Bun.spawn(cmd, opts) as unknown as {
|
|
61
|
-
stdout: ReadableStream<Uint8Array>;
|
|
62
|
-
stderr: ReadableStream<Uint8Array>;
|
|
63
|
-
exited: Promise<number>;
|
|
64
|
-
pid: number;
|
|
65
|
-
};
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
-
// GeminiAdapter implementation
|
|
71
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
const MAX_AGENT_OUTPUT_CHARS = 5000;
|
|
74
|
-
|
|
75
|
-
export class GeminiAdapter implements AgentAdapter {
|
|
76
|
-
readonly name = "gemini";
|
|
77
|
-
readonly displayName = "Gemini CLI";
|
|
78
|
-
readonly binary = "gemini";
|
|
79
|
-
|
|
80
|
-
readonly capabilities: AgentCapabilities = {
|
|
81
|
-
supportedTiers: ["fast", "balanced", "powerful"],
|
|
82
|
-
maxContextTokens: 1_000_000,
|
|
83
|
-
features: new Set<"tdd" | "review" | "refactor" | "batch">(["tdd", "review", "refactor"]),
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
async isInstalled(): Promise<boolean> {
|
|
87
|
-
const path = _geminiRunDeps.which("gemini");
|
|
88
|
-
if (path === null) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Check Google auth — run 'gemini' with a flag that shows auth status
|
|
93
|
-
try {
|
|
94
|
-
const proc = _geminiRunDeps.spawn(["gemini", "--version"], {
|
|
95
|
-
stdout: "pipe",
|
|
96
|
-
stderr: "pipe",
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const exitCode = await proc.exited;
|
|
100
|
-
if (exitCode !== 0) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const stdout = await new Response(proc.stdout).text();
|
|
105
|
-
const lowerOut = stdout.toLowerCase();
|
|
106
|
-
|
|
107
|
-
// If output explicitly says "not logged in", auth has failed
|
|
108
|
-
if (lowerOut.includes("not logged in")) {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return true;
|
|
113
|
-
} catch {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
buildCommand(options: AgentRunOptions): string[] {
|
|
119
|
-
return ["gemini", "-p", options.prompt];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async run(options: AgentRunOptions): Promise<AgentResult> {
|
|
123
|
-
const cmd = this.buildCommand(options);
|
|
124
|
-
const startTime = Date.now();
|
|
125
|
-
|
|
126
|
-
const proc = _geminiRunDeps.spawn(cmd, {
|
|
127
|
-
cwd: options.workdir,
|
|
128
|
-
stdout: "pipe",
|
|
129
|
-
stderr: "inherit",
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const exitCode = await proc.exited;
|
|
133
|
-
const stdout = await new Response(proc.stdout).text();
|
|
134
|
-
const durationMs = Date.now() - startTime;
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
success: exitCode === 0,
|
|
138
|
-
exitCode,
|
|
139
|
-
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS),
|
|
140
|
-
rateLimited: false,
|
|
141
|
-
durationMs,
|
|
142
|
-
estimatedCost: 0,
|
|
143
|
-
pid: proc.pid,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async complete(prompt: string, _options?: CompleteOptions): Promise<string> {
|
|
148
|
-
const cmd = ["gemini", "-p", prompt];
|
|
149
|
-
|
|
150
|
-
const proc = _geminiCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
151
|
-
const exitCode = await proc.exited;
|
|
152
|
-
|
|
153
|
-
const stdout = await new Response(proc.stdout).text();
|
|
154
|
-
const stderr = await new Response(proc.stderr).text();
|
|
155
|
-
const trimmed = stdout.trim();
|
|
156
|
-
|
|
157
|
-
if (exitCode !== 0) {
|
|
158
|
-
const errorDetails = stderr.trim() || trimmed;
|
|
159
|
-
const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
|
|
160
|
-
throw new CompleteError(errorMessage, exitCode);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (!trimmed) {
|
|
164
|
-
throw new CompleteError("complete() returned empty output");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return trimmed;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async plan(_options: PlanOptions): Promise<PlanResult> {
|
|
171
|
-
throw new Error("GeminiAdapter.plan() not implemented");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async decompose(_options: DecomposeOptions): Promise<DecomposeResult> {
|
|
175
|
-
throw new Error("GeminiAdapter.decompose() not implemented");
|
|
176
|
-
}
|
|
177
|
-
}
|
package/src/agents/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export type { AgentAdapter, AgentCapabilities, AgentResult, AgentRunOptions, CompleteOptions } from "./types";
|
|
2
|
-
export { CompleteError } from "./types";
|
|
3
|
-
export { ClaudeCodeAdapter } from "./claude";
|
|
4
|
-
export { getAllAgentNames, getAgent, getInstalledAgents, checkAgentHealth } from "./registry";
|
|
5
|
-
export type { ModelCostRates, TokenUsage, CostEstimate, TokenUsageWithConfidence, SessionTokenUsage } from "./cost";
|
|
6
|
-
export {
|
|
7
|
-
COST_RATES,
|
|
8
|
-
MODEL_PRICING,
|
|
9
|
-
parseTokenUsage,
|
|
10
|
-
estimateCost,
|
|
11
|
-
estimateCostFromOutput,
|
|
12
|
-
estimateCostByDuration,
|
|
13
|
-
formatCostWithConfidence,
|
|
14
|
-
estimateCostFromTokenUsage,
|
|
15
|
-
} from "./cost";
|
|
16
|
-
export { validateAgentForTier, validateAgentFeature, describeAgentCapabilities } from "./shared/validation";
|
|
17
|
-
export type { AgentVersionInfo } from "./shared/version-detection";
|
|
18
|
-
export { getAgentVersion, getAgentVersions } from "./shared/version-detection";
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenCode Agent Adapter — implements AgentAdapter interface
|
|
3
|
-
*
|
|
4
|
-
* Provides uniform interface for spawning OpenCode agent processes,
|
|
5
|
-
* supporting one-shot completions.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
AgentAdapter,
|
|
10
|
-
AgentCapabilities,
|
|
11
|
-
AgentResult,
|
|
12
|
-
AgentRunOptions,
|
|
13
|
-
CompleteOptions,
|
|
14
|
-
DecomposeOptions,
|
|
15
|
-
DecomposeResult,
|
|
16
|
-
PlanOptions,
|
|
17
|
-
PlanResult,
|
|
18
|
-
} from "../types";
|
|
19
|
-
import { CompleteError } from "../types";
|
|
20
|
-
|
|
21
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
// Injectable dependencies — matches the _deps pattern used in claude.ts
|
|
23
|
-
// These are replaced in unit tests to intercept Bun.spawn calls.
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
export const _opencodeCompleteDeps = {
|
|
27
|
-
which(name: string): string | null {
|
|
28
|
-
return Bun.which(name);
|
|
29
|
-
},
|
|
30
|
-
spawn(
|
|
31
|
-
cmd: string[],
|
|
32
|
-
opts: { stdout: "pipe"; stderr: "pipe" | "inherit" },
|
|
33
|
-
): {
|
|
34
|
-
stdout: ReadableStream<Uint8Array>;
|
|
35
|
-
stderr: ReadableStream<Uint8Array>;
|
|
36
|
-
exited: Promise<number>;
|
|
37
|
-
pid: number;
|
|
38
|
-
} {
|
|
39
|
-
return Bun.spawn(cmd, opts) as unknown as {
|
|
40
|
-
stdout: ReadableStream<Uint8Array>;
|
|
41
|
-
stderr: ReadableStream<Uint8Array>;
|
|
42
|
-
exited: Promise<number>;
|
|
43
|
-
pid: number;
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
-
// OpenCodeAdapter implementation
|
|
50
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
export class OpenCodeAdapter implements AgentAdapter {
|
|
53
|
-
readonly name = "opencode";
|
|
54
|
-
readonly displayName = "OpenCode";
|
|
55
|
-
readonly binary = "opencode";
|
|
56
|
-
|
|
57
|
-
readonly capabilities: AgentCapabilities = {
|
|
58
|
-
supportedTiers: ["fast", "balanced"],
|
|
59
|
-
maxContextTokens: 8_000,
|
|
60
|
-
features: new Set<"tdd" | "review" | "refactor" | "batch">(["tdd", "refactor"]),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
async isInstalled(): Promise<boolean> {
|
|
64
|
-
const path = _opencodeCompleteDeps.which("opencode");
|
|
65
|
-
return path !== null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
buildCommand(_options: AgentRunOptions): string[] {
|
|
69
|
-
throw new Error("OpenCodeAdapter.buildCommand() not implemented");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async run(_options: AgentRunOptions): Promise<AgentResult> {
|
|
73
|
-
throw new Error("OpenCodeAdapter.run() not implemented");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async complete(prompt: string, _options?: CompleteOptions): Promise<string> {
|
|
77
|
-
const cmd = ["opencode", "--prompt", prompt];
|
|
78
|
-
|
|
79
|
-
const proc = _opencodeCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
80
|
-
const exitCode = await proc.exited;
|
|
81
|
-
|
|
82
|
-
const stdout = await new Response(proc.stdout).text();
|
|
83
|
-
const stderr = await new Response(proc.stderr).text();
|
|
84
|
-
const trimmed = stdout.trim();
|
|
85
|
-
|
|
86
|
-
if (exitCode !== 0) {
|
|
87
|
-
const errorDetails = stderr.trim() || trimmed;
|
|
88
|
-
const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
|
|
89
|
-
throw new CompleteError(errorMessage, exitCode);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (!trimmed) {
|
|
93
|
-
throw new CompleteError("complete() returned empty output");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return trimmed;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async plan(_options: PlanOptions): Promise<PlanResult> {
|
|
100
|
-
throw new Error("OpenCodeAdapter.plan() not implemented");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async decompose(_options: DecomposeOptions): Promise<DecomposeResult> {
|
|
104
|
-
throw new Error("OpenCodeAdapter.decompose() not implemented");
|
|
105
|
-
}
|
|
106
|
-
}
|
package/src/agents/registry.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Registry
|
|
3
|
-
*
|
|
4
|
-
* Discovers and manages available coding agents.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { NaxConfig } from "../config/schema";
|
|
8
|
-
import { getLogger } from "../logger";
|
|
9
|
-
import { AcpAgentAdapter } from "./acp/adapter";
|
|
10
|
-
import { AiderAdapter } from "./aider/adapter";
|
|
11
|
-
import { ClaudeCodeAdapter } from "./claude/adapter";
|
|
12
|
-
import { CodexAdapter } from "./codex/adapter";
|
|
13
|
-
import { GeminiAdapter } from "./gemini/adapter";
|
|
14
|
-
import { OpenCodeAdapter } from "./opencode/adapter";
|
|
15
|
-
import type { AgentAdapter } from "./types";
|
|
16
|
-
|
|
17
|
-
/** All known agent adapters */
|
|
18
|
-
export const ALL_AGENTS: AgentAdapter[] = [
|
|
19
|
-
new ClaudeCodeAdapter(),
|
|
20
|
-
new CodexAdapter(),
|
|
21
|
-
new OpenCodeAdapter(),
|
|
22
|
-
new GeminiAdapter(),
|
|
23
|
-
new AiderAdapter(),
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
/** Get all registered agent names */
|
|
27
|
-
export function getAllAgentNames(): string[] {
|
|
28
|
-
return ALL_AGENTS.map((a) => a.name);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Get a specific agent by name */
|
|
32
|
-
export function getAgent(name: string): AgentAdapter | undefined {
|
|
33
|
-
return ALL_AGENTS.find((a) => a.name === name);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Get all installed agents on this machine */
|
|
37
|
-
export async function getInstalledAgents(): Promise<AgentAdapter[]> {
|
|
38
|
-
const results = await Promise.all(
|
|
39
|
-
ALL_AGENTS.map(async (agent) => ({
|
|
40
|
-
agent,
|
|
41
|
-
installed: await agent.isInstalled(),
|
|
42
|
-
})),
|
|
43
|
-
);
|
|
44
|
-
return results.filter((r) => r.installed).map((r) => r.agent);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Check health of all agents */
|
|
48
|
-
export async function checkAgentHealth(): Promise<Array<{ name: string; displayName: string; installed: boolean }>> {
|
|
49
|
-
return Promise.all(
|
|
50
|
-
ALL_AGENTS.map(async (agent) => ({
|
|
51
|
-
name: agent.name,
|
|
52
|
-
displayName: agent.displayName,
|
|
53
|
-
installed: await agent.isInstalled(),
|
|
54
|
-
})),
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Protocol-aware agent registry returned by createAgentRegistry() */
|
|
59
|
-
export interface AgentRegistry {
|
|
60
|
-
/** Get a specific agent, respecting the configured protocol */
|
|
61
|
-
getAgent(name: string): AgentAdapter | undefined;
|
|
62
|
-
/** Get all installed agents */
|
|
63
|
-
getInstalledAgents(): Promise<AgentAdapter[]>;
|
|
64
|
-
/** Check health of all agents */
|
|
65
|
-
checkAgentHealth(): Promise<Array<{ name: string; displayName: string; installed: boolean }>>;
|
|
66
|
-
/** Active protocol ('acp' | 'cli') */
|
|
67
|
-
protocol: "acp" | "cli";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Create a protocol-aware agent registry.
|
|
72
|
-
*
|
|
73
|
-
* When config.agent.protocol is 'acp', returns AcpAgentAdapter instances.
|
|
74
|
-
* When 'cli' (or unset), returns legacy CLI adapters.
|
|
75
|
-
* AcpAgentAdapter instances are cached per agent name for the lifetime of the registry.
|
|
76
|
-
*/
|
|
77
|
-
export function createAgentRegistry(config: NaxConfig): AgentRegistry {
|
|
78
|
-
const protocol: "acp" | "cli" = config.agent?.protocol ?? "cli";
|
|
79
|
-
const logger = getLogger();
|
|
80
|
-
const acpCache = new Map<string, AcpAgentAdapter>();
|
|
81
|
-
|
|
82
|
-
// Log which protocol is being used at startup
|
|
83
|
-
logger?.info("agents", `Agent protocol: ${protocol}`, { protocol, hasConfig: !!config.agent });
|
|
84
|
-
|
|
85
|
-
function getAgent(name: string): AgentAdapter | undefined {
|
|
86
|
-
if (protocol === "acp") {
|
|
87
|
-
const known = ALL_AGENTS.find((a) => a.name === name);
|
|
88
|
-
if (!known) return undefined;
|
|
89
|
-
if (!acpCache.has(name)) {
|
|
90
|
-
acpCache.set(name, new AcpAgentAdapter(name));
|
|
91
|
-
logger?.debug("agents", `Created AcpAgentAdapter for ${name}`, { name, protocol });
|
|
92
|
-
}
|
|
93
|
-
return acpCache.get(name);
|
|
94
|
-
}
|
|
95
|
-
const adapter = ALL_AGENTS.find((a) => a.name === name);
|
|
96
|
-
if (adapter) {
|
|
97
|
-
logger?.debug("agents", `Using CLI adapter for ${name}: ${adapter.constructor.name}`, { name });
|
|
98
|
-
}
|
|
99
|
-
return adapter;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async function getInstalledAgents(): Promise<AgentAdapter[]> {
|
|
103
|
-
const agents =
|
|
104
|
-
protocol === "acp"
|
|
105
|
-
? ALL_AGENTS.map((a) => {
|
|
106
|
-
if (!acpCache.has(a.name)) {
|
|
107
|
-
acpCache.set(a.name, new AcpAgentAdapter(a.name));
|
|
108
|
-
}
|
|
109
|
-
return acpCache.get(a.name) as AcpAgentAdapter;
|
|
110
|
-
})
|
|
111
|
-
: ALL_AGENTS;
|
|
112
|
-
const results = await Promise.all(agents.map(async (agent) => ({ agent, installed: await agent.isInstalled() })));
|
|
113
|
-
return results.filter((r) => r.installed).map((r) => r.agent);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function checkAgentHealth(): Promise<Array<{ name: string; displayName: string; installed: boolean }>> {
|
|
117
|
-
const agents =
|
|
118
|
-
protocol === "acp"
|
|
119
|
-
? ALL_AGENTS.map((a) => {
|
|
120
|
-
if (!acpCache.has(a.name)) {
|
|
121
|
-
acpCache.set(a.name, new AcpAgentAdapter(a.name));
|
|
122
|
-
}
|
|
123
|
-
return acpCache.get(a.name) as AcpAgentAdapter;
|
|
124
|
-
})
|
|
125
|
-
: ALL_AGENTS;
|
|
126
|
-
return Promise.all(
|
|
127
|
-
agents.map(async (agent) => ({
|
|
128
|
-
name: agent.name,
|
|
129
|
-
displayName: agent.displayName,
|
|
130
|
-
installed: await agent.isInstalled(),
|
|
131
|
-
})),
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return { getAgent, getInstalledAgents, checkAgentHealth, protocol };
|
|
136
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code Decompose Logic
|
|
3
|
-
*
|
|
4
|
-
* Extracted from claude.ts: decompose(), buildDecomposePrompt(),
|
|
5
|
-
* parseDecomposeOutput(), validateComplexity()
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { COMPLEXITY_GUIDE, GROUPING_RULES, TEST_STRATEGY_GUIDE, resolveTestStrategy } from "../../config/test-strategy";
|
|
9
|
-
import type { DecomposeOptions, DecomposeResult, DecomposedStory } from "../types";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Build the decompose prompt combining spec content and codebase context.
|
|
13
|
-
*/
|
|
14
|
-
export function buildDecomposePrompt(options: DecomposeOptions): string {
|
|
15
|
-
return `You are a requirements analyst. Break down the following feature specification into user stories and classify each story's complexity.
|
|
16
|
-
|
|
17
|
-
CODEBASE CONTEXT:
|
|
18
|
-
${options.codebaseContext}
|
|
19
|
-
|
|
20
|
-
FEATURE SPECIFICATION:
|
|
21
|
-
${options.specContent}
|
|
22
|
-
|
|
23
|
-
Decompose this spec into user stories. For each story, provide:
|
|
24
|
-
1. id: Story ID (e.g., "US-001")
|
|
25
|
-
2. title: Concise story title
|
|
26
|
-
3. description: What needs to be implemented
|
|
27
|
-
4. acceptanceCriteria: Array of testable criteria
|
|
28
|
-
5. tags: Array of routing tags (e.g., ["security", "api"])
|
|
29
|
-
6. dependencies: Array of story IDs this depends on (e.g., ["US-001"])
|
|
30
|
-
7. complexity: "simple" | "medium" | "complex" | "expert"
|
|
31
|
-
8. contextFiles: Array of file paths to inject into agent prompt before execution
|
|
32
|
-
9. reasoning: Why this complexity level
|
|
33
|
-
10. estimatedLOC: Estimated lines of code to change
|
|
34
|
-
11. risks: Array of implementation risks
|
|
35
|
-
12. testStrategy: "test-after" | "tdd-simple" | "three-session-tdd" | "three-session-tdd-lite"
|
|
36
|
-
|
|
37
|
-
${COMPLEXITY_GUIDE}
|
|
38
|
-
|
|
39
|
-
${TEST_STRATEGY_GUIDE}
|
|
40
|
-
|
|
41
|
-
${GROUPING_RULES}
|
|
42
|
-
|
|
43
|
-
Consider:
|
|
44
|
-
1. Does infrastructure exist? (e.g., "add caching" when no cache layer exists = complex)
|
|
45
|
-
2. How many files will be touched?
|
|
46
|
-
3. Are there cross-cutting concerns (auth, validation, error handling)?
|
|
47
|
-
4. Does it require new dependencies or architectural decisions?
|
|
48
|
-
|
|
49
|
-
Respond with ONLY a JSON array (no markdown code fences):
|
|
50
|
-
[{
|
|
51
|
-
"id": "US-001",
|
|
52
|
-
"title": "Story title",
|
|
53
|
-
"description": "Story description",
|
|
54
|
-
"acceptanceCriteria": ["Criterion 1", "Criterion 2"],
|
|
55
|
-
"tags": ["tag1"],
|
|
56
|
-
"dependencies": [],
|
|
57
|
-
"complexity": "medium",
|
|
58
|
-
"contextFiles": ["src/path/to/file.ts"],
|
|
59
|
-
"reasoning": "Why this complexity level",
|
|
60
|
-
"estimatedLOC": 150,
|
|
61
|
-
"risks": ["Risk 1"],
|
|
62
|
-
"testStrategy": "test-after"
|
|
63
|
-
}]`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Parse decompose output from agent stdout.
|
|
68
|
-
*
|
|
69
|
-
* Extracts JSON array from output, handles markdown code fences,
|
|
70
|
-
* and validates structure.
|
|
71
|
-
*/
|
|
72
|
-
export function parseDecomposeOutput(output: string): DecomposedStory[] {
|
|
73
|
-
// Extract JSON from output (handles markdown code fences)
|
|
74
|
-
const jsonMatch = output.match(/```(?:json)?\s*(\[[\s\S]*?\])\s*```/);
|
|
75
|
-
let jsonText = jsonMatch ? jsonMatch[1] : output;
|
|
76
|
-
|
|
77
|
-
// Try to find JSON array directly if no code fence
|
|
78
|
-
if (!jsonMatch) {
|
|
79
|
-
const arrayMatch = output.match(/\[[\s\S]*\]/);
|
|
80
|
-
if (arrayMatch) {
|
|
81
|
-
jsonText = arrayMatch[0];
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Parse JSON
|
|
86
|
-
let parsed: unknown;
|
|
87
|
-
try {
|
|
88
|
-
parsed = JSON.parse(jsonText.trim());
|
|
89
|
-
} catch (error) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`Failed to parse decompose output as JSON: ${(error as Error).message}\n\nOutput:\n${output.slice(0, 500)}`,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Validate structure
|
|
96
|
-
if (!Array.isArray(parsed)) {
|
|
97
|
-
throw new Error("Decompose output is not an array");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Map to DecomposedStory[] with validation
|
|
101
|
-
const stories: DecomposedStory[] = parsed.map((item: unknown, index: number) => {
|
|
102
|
-
// Type guard: ensure item is an object
|
|
103
|
-
if (typeof item !== "object" || item === null) {
|
|
104
|
-
throw new Error(`Story at index ${index} is not an object`);
|
|
105
|
-
}
|
|
106
|
-
const record = item as Record<string, unknown>;
|
|
107
|
-
if (!record.id || typeof record.id !== "string") {
|
|
108
|
-
throw new Error(`Story at index ${index} missing valid 'id' field`);
|
|
109
|
-
}
|
|
110
|
-
if (!record.title || typeof record.title !== "string") {
|
|
111
|
-
throw new Error(`Story ${record.id} missing valid 'title' field`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
id: record.id,
|
|
116
|
-
title: record.title,
|
|
117
|
-
description: String(record.description || record.title),
|
|
118
|
-
acceptanceCriteria: Array.isArray(record.acceptanceCriteria)
|
|
119
|
-
? record.acceptanceCriteria
|
|
120
|
-
: ["Implementation complete"],
|
|
121
|
-
tags: Array.isArray(record.tags) ? record.tags : [],
|
|
122
|
-
dependencies: Array.isArray(record.dependencies) ? record.dependencies : [],
|
|
123
|
-
complexity: coerceComplexity(record.complexity),
|
|
124
|
-
// contextFiles: prefer the new field; fall back to legacy relevantFiles from older LLM responses
|
|
125
|
-
contextFiles: Array.isArray(record.contextFiles)
|
|
126
|
-
? record.contextFiles
|
|
127
|
-
: Array.isArray(record.relevantFiles)
|
|
128
|
-
? record.relevantFiles
|
|
129
|
-
: [],
|
|
130
|
-
relevantFiles: Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
|
|
131
|
-
reasoning: String(record.reasoning || "No reasoning provided"),
|
|
132
|
-
estimatedLOC: Number(record.estimatedLOC) || 0,
|
|
133
|
-
risks: Array.isArray(record.risks) ? record.risks : [],
|
|
134
|
-
testStrategy: resolveTestStrategy(typeof record.testStrategy === "string" ? record.testStrategy : undefined),
|
|
135
|
-
};
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (stories.length === 0) {
|
|
139
|
-
throw new Error("Decompose returned empty story array");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return stories;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Coerce complexity value from decompose output.
|
|
147
|
-
*/
|
|
148
|
-
export function coerceComplexity(value: unknown): "simple" | "medium" | "complex" | "expert" {
|
|
149
|
-
if (value === "simple" || value === "medium" || value === "complex" || value === "expert") {
|
|
150
|
-
return value;
|
|
151
|
-
}
|
|
152
|
-
// Default to medium if invalid
|
|
153
|
-
return "medium";
|
|
154
|
-
}
|