@nathapp/nax 0.40.0 → 0.41.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/dist/nax.js +1166 -277
- package/package.json +2 -2
- package/src/acceptance/fix-generator.ts +4 -35
- package/src/acceptance/generator.ts +27 -28
- package/src/acceptance/refinement.ts +72 -5
- package/src/acceptance/templates/cli.ts +47 -0
- package/src/acceptance/templates/component.ts +78 -0
- package/src/acceptance/templates/e2e.ts +43 -0
- package/src/acceptance/templates/index.ts +21 -0
- package/src/acceptance/templates/snapshot.ts +50 -0
- package/src/acceptance/templates/unit.ts +48 -0
- package/src/acceptance/types.ts +9 -1
- package/src/agents/acp/adapter.ts +644 -0
- package/src/agents/acp/cost.ts +79 -0
- package/src/agents/acp/index.ts +9 -0
- package/src/agents/acp/interaction-bridge.ts +126 -0
- package/src/agents/acp/parser.ts +166 -0
- package/src/agents/acp/spawn-client.ts +309 -0
- package/src/agents/acp/types.ts +22 -0
- package/src/agents/claude-complete.ts +3 -3
- package/src/agents/registry.ts +83 -0
- package/src/agents/types-extended.ts +23 -0
- package/src/agents/types.ts +17 -0
- package/src/cli/analyze.ts +6 -2
- package/src/cli/init-detect.ts +94 -8
- package/src/cli/init.ts +2 -2
- package/src/cli/plan.ts +23 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +1 -1
- package/src/config/runtime-types.ts +17 -0
- package/src/config/schema.ts +3 -1
- package/src/config/schemas.ts +9 -1
- package/src/config/types.ts +2 -0
- package/src/execution/executor-types.ts +6 -0
- package/src/execution/iteration-runner.ts +2 -0
- package/src/execution/lifecycle/acceptance-loop.ts +5 -2
- package/src/execution/lifecycle/run-initialization.ts +16 -4
- package/src/execution/lifecycle/run-setup.ts +4 -0
- package/src/execution/runner-completion.ts +11 -1
- package/src/execution/runner-execution.ts +8 -0
- package/src/execution/runner-setup.ts +4 -0
- package/src/execution/runner.ts +10 -0
- package/src/pipeline/stages/acceptance-setup.ts +4 -0
- package/src/pipeline/stages/execution.ts +33 -1
- package/src/pipeline/stages/routing.ts +18 -7
- package/src/pipeline/types.ts +10 -0
- package/src/tdd/orchestrator.ts +7 -0
- package/src/tdd/rectification-gate.ts +6 -0
- package/src/tdd/session-runner.ts +4 -0
package/dist/nax.js
CHANGED
|
@@ -3241,9 +3241,6 @@ async function executeComplete(binary, prompt, options) {
|
|
|
3241
3241
|
if (options?.model) {
|
|
3242
3242
|
cmd.push("--model", options.model);
|
|
3243
3243
|
}
|
|
3244
|
-
if (options?.maxTokens !== undefined) {
|
|
3245
|
-
cmd.push("--max-tokens", String(options.maxTokens));
|
|
3246
|
-
}
|
|
3247
3244
|
if (options?.jsonMode) {
|
|
3248
3245
|
cmd.push("--output-format", "json");
|
|
3249
3246
|
}
|
|
@@ -3469,6 +3466,17 @@ function estimateCostByDuration(modelTier, durationMs) {
|
|
|
3469
3466
|
confidence: "fallback"
|
|
3470
3467
|
};
|
|
3471
3468
|
}
|
|
3469
|
+
function formatCostWithConfidence(estimate) {
|
|
3470
|
+
const formattedCost = `$${estimate.cost.toFixed(2)}`;
|
|
3471
|
+
switch (estimate.confidence) {
|
|
3472
|
+
case "exact":
|
|
3473
|
+
return formattedCost;
|
|
3474
|
+
case "estimated":
|
|
3475
|
+
return `~${formattedCost}`;
|
|
3476
|
+
case "fallback":
|
|
3477
|
+
return `~${formattedCost} (duration-based)`;
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3472
3480
|
var COST_RATES;
|
|
3473
3481
|
var init_cost = __esm(() => {
|
|
3474
3482
|
COST_RATES = {
|
|
@@ -17525,7 +17533,7 @@ var init_zod = __esm(() => {
|
|
|
17525
17533
|
});
|
|
17526
17534
|
|
|
17527
17535
|
// src/config/schemas.ts
|
|
17528
|
-
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, AdaptiveRoutingConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, NaxConfigSchema;
|
|
17536
|
+
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, AdaptiveRoutingConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, NaxConfigSchema;
|
|
17529
17537
|
var init_schemas3 = __esm(() => {
|
|
17530
17538
|
init_zod();
|
|
17531
17539
|
TokenPricingSchema = exports_external.object({
|
|
@@ -17708,7 +17716,9 @@ var init_schemas3 = __esm(() => {
|
|
|
17708
17716
|
testPath: exports_external.string().min(1, "acceptance.testPath must be non-empty"),
|
|
17709
17717
|
model: exports_external.enum(["fast", "balanced", "powerful"]).default("fast"),
|
|
17710
17718
|
refinement: exports_external.boolean().default(true),
|
|
17711
|
-
redGate: exports_external.boolean().default(true)
|
|
17719
|
+
redGate: exports_external.boolean().default(true),
|
|
17720
|
+
testStrategy: exports_external.enum(["unit", "component", "cli", "e2e", "snapshot"]).optional(),
|
|
17721
|
+
testFramework: exports_external.string().min(1, "acceptance.testFramework must be non-empty").optional()
|
|
17712
17722
|
});
|
|
17713
17723
|
TestCoverageConfigSchema = exports_external.object({
|
|
17714
17724
|
enabled: exports_external.boolean().default(true),
|
|
@@ -17792,6 +17802,10 @@ var init_schemas3 = __esm(() => {
|
|
|
17792
17802
|
maxDescriptionLength: exports_external.number().int().min(100).max(1e4).default(2000),
|
|
17793
17803
|
maxBulletPoints: exports_external.number().int().min(1).max(100).default(8)
|
|
17794
17804
|
});
|
|
17805
|
+
AgentConfigSchema = exports_external.object({
|
|
17806
|
+
protocol: exports_external.enum(["acp", "cli"]).default("acp"),
|
|
17807
|
+
acpPermissionMode: exports_external.string().optional()
|
|
17808
|
+
});
|
|
17795
17809
|
PrecheckConfigSchema = exports_external.object({
|
|
17796
17810
|
storySizeGate: StorySizeGateConfigSchema
|
|
17797
17811
|
});
|
|
@@ -17827,6 +17841,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17827
17841
|
disabledPlugins: exports_external.array(exports_external.string()).optional(),
|
|
17828
17842
|
hooks: HooksConfigSchema.optional(),
|
|
17829
17843
|
interaction: InteractionConfigSchema.optional(),
|
|
17844
|
+
agent: AgentConfigSchema.optional(),
|
|
17830
17845
|
precheck: PrecheckConfigSchema.optional(),
|
|
17831
17846
|
prompts: PromptsConfigSchema.optional(),
|
|
17832
17847
|
decompose: DecomposeConfigSchema.optional()
|
|
@@ -18362,14 +18377,16 @@ __export(exports_refinement, {
|
|
|
18362
18377
|
buildRefinementPrompt: () => buildRefinementPrompt,
|
|
18363
18378
|
_refineDeps: () => _refineDeps
|
|
18364
18379
|
});
|
|
18365
|
-
function buildRefinementPrompt(criteria, codebaseContext) {
|
|
18380
|
+
function buildRefinementPrompt(criteria, codebaseContext, options) {
|
|
18366
18381
|
const criteriaList = criteria.map((c, i) => `${i + 1}. ${c}`).join(`
|
|
18367
18382
|
`);
|
|
18383
|
+
const strategySection = buildStrategySection(options);
|
|
18384
|
+
const refinedExample = buildRefinedExample(options?.testStrategy);
|
|
18368
18385
|
return `You are an acceptance criteria refinement assistant. Your task is to convert raw acceptance criteria into concrete, machine-verifiable assertions.
|
|
18369
18386
|
|
|
18370
18387
|
CODEBASE CONTEXT:
|
|
18371
18388
|
${codebaseContext}
|
|
18372
|
-
|
|
18389
|
+
${strategySection}
|
|
18373
18390
|
ACCEPTANCE CRITERIA TO REFINE:
|
|
18374
18391
|
${criteriaList}
|
|
18375
18392
|
|
|
@@ -18384,11 +18401,53 @@ Respond with ONLY a JSON array (no markdown code fences):
|
|
|
18384
18401
|
|
|
18385
18402
|
Rules:
|
|
18386
18403
|
- "original" must match the input criterion text exactly
|
|
18387
|
-
- "refined" must be a concrete assertion (e.g.,
|
|
18404
|
+
- "refined" must be a concrete assertion (e.g., ${refinedExample})
|
|
18388
18405
|
- "testable" is false only if the criterion cannot be automatically verified (e.g., "UX feels responsive", "design looks good")
|
|
18389
18406
|
- "storyId" leave as empty string \u2014 it will be assigned by the caller
|
|
18390
18407
|
- Respond with ONLY the JSON array`;
|
|
18391
18408
|
}
|
|
18409
|
+
function buildStrategySection(options) {
|
|
18410
|
+
if (!options?.testStrategy) {
|
|
18411
|
+
return "";
|
|
18412
|
+
}
|
|
18413
|
+
const framework = options.testFramework ? ` Use ${options.testFramework} testing library syntax.` : "";
|
|
18414
|
+
switch (options.testStrategy) {
|
|
18415
|
+
case "component":
|
|
18416
|
+
return `
|
|
18417
|
+
TEST STRATEGY: component
|
|
18418
|
+
Focus assertions on rendered output visible on screen \u2014 text content, visible elements, and screen state.
|
|
18419
|
+
Assert what the user sees rendered in the component, not what internal functions produce.${framework}
|
|
18420
|
+
`;
|
|
18421
|
+
case "cli":
|
|
18422
|
+
return `
|
|
18423
|
+
TEST STRATEGY: cli
|
|
18424
|
+
Focus assertions on stdout and stderr text output from the CLI command.
|
|
18425
|
+
Assert about terminal output content, exit codes, and standard output/standard error streams.${framework}
|
|
18426
|
+
`;
|
|
18427
|
+
case "e2e":
|
|
18428
|
+
return `
|
|
18429
|
+
TEST STRATEGY: e2e
|
|
18430
|
+
Focus assertions on HTTP response content \u2014 status codes, response bodies, and endpoint behavior.
|
|
18431
|
+
Assert about HTTP responses, status codes, and API endpoint output.${framework}
|
|
18432
|
+
`;
|
|
18433
|
+
default:
|
|
18434
|
+
return framework ? `
|
|
18435
|
+
TEST FRAMEWORK: ${options.testFramework}
|
|
18436
|
+
` : "";
|
|
18437
|
+
}
|
|
18438
|
+
}
|
|
18439
|
+
function buildRefinedExample(testStrategy) {
|
|
18440
|
+
switch (testStrategy) {
|
|
18441
|
+
case "component":
|
|
18442
|
+
return '"Text content visible on screen matches expected", "Rendered output contains expected element"';
|
|
18443
|
+
case "cli":
|
|
18444
|
+
return '"stdout contains expected text", "stderr is empty on success", "exit code is 0"';
|
|
18445
|
+
case "e2e":
|
|
18446
|
+
return '"HTTP status 200 returned", "Response body contains expected field", "Endpoint returns JSON"';
|
|
18447
|
+
default:
|
|
18448
|
+
return '"Array of length N returned", "HTTP status 200 returned"';
|
|
18449
|
+
}
|
|
18450
|
+
}
|
|
18392
18451
|
function parseRefinementResponse(response, criteria) {
|
|
18393
18452
|
if (!response || !response.trim()) {
|
|
18394
18453
|
return fallbackCriteria(criteria);
|
|
@@ -18412,7 +18471,7 @@ async function refineAcceptanceCriteria(criteria, context) {
|
|
|
18412
18471
|
if (criteria.length === 0) {
|
|
18413
18472
|
return [];
|
|
18414
18473
|
}
|
|
18415
|
-
const { storyId, codebaseContext, config: config2 } = context;
|
|
18474
|
+
const { storyId, codebaseContext, config: config2, testStrategy, testFramework } = context;
|
|
18416
18475
|
const logger = getLogger();
|
|
18417
18476
|
const modelTier = config2.acceptance?.model ?? "fast";
|
|
18418
18477
|
const modelEntry = config2.models[modelTier] ?? config2.models.fast;
|
|
@@ -18420,7 +18479,7 @@ async function refineAcceptanceCriteria(criteria, context) {
|
|
|
18420
18479
|
throw new Error(`[refinement] config.models.${modelTier} not configured`);
|
|
18421
18480
|
}
|
|
18422
18481
|
const modelDef = resolveModel(modelEntry);
|
|
18423
|
-
const prompt = buildRefinementPrompt(criteria, codebaseContext);
|
|
18482
|
+
const prompt = buildRefinementPrompt(criteria, codebaseContext, { testStrategy, testFramework });
|
|
18424
18483
|
let response;
|
|
18425
18484
|
try {
|
|
18426
18485
|
response = await _refineDeps.adapter.complete(prompt, {
|
|
@@ -18483,6 +18542,7 @@ async function generateFromPRD(_stories, refinedCriteria, options) {
|
|
|
18483
18542
|
}
|
|
18484
18543
|
const criteriaList = refinedCriteria.map((c, i) => `AC-${i + 1}: ${c.refined}`).join(`
|
|
18485
18544
|
`);
|
|
18545
|
+
const strategyInstructions = buildStrategyInstructions(options.testStrategy, options.testFramework);
|
|
18486
18546
|
const prompt = `You are a test engineer. Generate acceptance tests for the "${options.featureName}" feature based on the refined acceptance criteria below.
|
|
18487
18547
|
|
|
18488
18548
|
CODEBASE CONTEXT:
|
|
@@ -18491,7 +18551,7 @@ ${options.codebaseContext}
|
|
|
18491
18551
|
ACCEPTANCE CRITERIA (refined):
|
|
18492
18552
|
${criteriaList}
|
|
18493
18553
|
|
|
18494
|
-
Generate a complete acceptance.test.ts file using bun:test framework. Each AC maps to exactly one test named "AC-N: <description>".
|
|
18554
|
+
${strategyInstructions}Generate a complete acceptance.test.ts file using bun:test framework. Each AC maps to exactly one test named "AC-N: <description>".
|
|
18495
18555
|
|
|
18496
18556
|
Use this structure:
|
|
18497
18557
|
|
|
@@ -18518,6 +18578,40 @@ Respond with ONLY the TypeScript test code (no markdown code fences, no explanat
|
|
|
18518
18578
|
await _generatorPRDDeps.writeFile(join2(options.workdir, "acceptance-refined.json"), refinedJsonContent);
|
|
18519
18579
|
return { testCode, criteria };
|
|
18520
18580
|
}
|
|
18581
|
+
function buildStrategyInstructions(strategy, framework) {
|
|
18582
|
+
switch (strategy) {
|
|
18583
|
+
case "component": {
|
|
18584
|
+
const fw = framework ?? "ink-testing-library";
|
|
18585
|
+
if (fw === "react") {
|
|
18586
|
+
return `TEST STRATEGY: component (react)
|
|
18587
|
+
Import render and screen from @testing-library/react. Render the component and use screen.getByText to assert on output.
|
|
18588
|
+
|
|
18589
|
+
`;
|
|
18590
|
+
}
|
|
18591
|
+
return `TEST STRATEGY: component (ink-testing-library)
|
|
18592
|
+
Import render from ink-testing-library. Render the component and use lastFrame() to assert on output.
|
|
18593
|
+
|
|
18594
|
+
`;
|
|
18595
|
+
}
|
|
18596
|
+
case "cli":
|
|
18597
|
+
return `TEST STRATEGY: cli
|
|
18598
|
+
Use Bun.spawn to run the binary. Read stdout and assert on the text output.
|
|
18599
|
+
|
|
18600
|
+
`;
|
|
18601
|
+
case "e2e":
|
|
18602
|
+
return `TEST STRATEGY: e2e
|
|
18603
|
+
Use fetch() against http://localhost to call the running service. Assert on response body using response.text() or response.json().
|
|
18604
|
+
|
|
18605
|
+
`;
|
|
18606
|
+
case "snapshot":
|
|
18607
|
+
return `TEST STRATEGY: snapshot
|
|
18608
|
+
Render the component and use toMatchSnapshot() to capture and compare snapshots.
|
|
18609
|
+
|
|
18610
|
+
`;
|
|
18611
|
+
default:
|
|
18612
|
+
return "";
|
|
18613
|
+
}
|
|
18614
|
+
}
|
|
18521
18615
|
function parseAcceptanceCriteria(specContent) {
|
|
18522
18616
|
const criteria = [];
|
|
18523
18617
|
const lines = specContent.split(`
|
|
@@ -18595,29 +18689,10 @@ async function generateAcceptanceTests(adapter, options) {
|
|
|
18595
18689
|
logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
|
|
18596
18690
|
const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
|
|
18597
18691
|
try {
|
|
18598
|
-
const
|
|
18599
|
-
|
|
18600
|
-
const cmd = [adapter.binary, "--model", options.modelDef.model, ...permArgs, "-p", prompt];
|
|
18601
|
-
const proc = Bun.spawn(cmd, {
|
|
18602
|
-
cwd: options.workdir,
|
|
18603
|
-
stdout: "pipe",
|
|
18604
|
-
stderr: "pipe",
|
|
18605
|
-
env: {
|
|
18606
|
-
...process.env,
|
|
18607
|
-
...options.modelDef.env || {}
|
|
18608
|
-
}
|
|
18692
|
+
const output = await adapter.complete(prompt, {
|
|
18693
|
+
model: options.modelDef.model
|
|
18609
18694
|
});
|
|
18610
|
-
const
|
|
18611
|
-
const stdout = await new Response(proc.stdout).text();
|
|
18612
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18613
|
-
if (exitCode !== 0) {
|
|
18614
|
-
logger.warn("acceptance", "\u26A0 Agent test generation failed", { stderr });
|
|
18615
|
-
return {
|
|
18616
|
-
testCode: generateSkeletonTests(options.featureName, criteria),
|
|
18617
|
-
criteria
|
|
18618
|
-
};
|
|
18619
|
-
}
|
|
18620
|
-
const testCode = extractTestCode(stdout);
|
|
18695
|
+
const testCode = extractTestCode(output);
|
|
18621
18696
|
return {
|
|
18622
18697
|
testCode,
|
|
18623
18698
|
criteria
|
|
@@ -18720,7 +18795,7 @@ Requirements:
|
|
|
18720
18795
|
Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
|
|
18721
18796
|
}
|
|
18722
18797
|
async function generateFixStories(adapter, options) {
|
|
18723
|
-
const { failedACs, testOutput, prd, specContent,
|
|
18798
|
+
const { failedACs, testOutput, prd, specContent, modelDef } = options;
|
|
18724
18799
|
const fixStories = [];
|
|
18725
18800
|
const acTextMap = parseACTextFromSpec(specContent);
|
|
18726
18801
|
const logger = getLogger();
|
|
@@ -18735,34 +18810,9 @@ async function generateFixStories(adapter, options) {
|
|
|
18735
18810
|
}
|
|
18736
18811
|
const prompt = buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd);
|
|
18737
18812
|
try {
|
|
18738
|
-
const
|
|
18739
|
-
|
|
18740
|
-
const cmd = [adapter.binary, "--model", modelDef.model, ...permArgs, "-p", prompt];
|
|
18741
|
-
const proc = Bun.spawn(cmd, {
|
|
18742
|
-
cwd: workdir,
|
|
18743
|
-
stdout: "pipe",
|
|
18744
|
-
stderr: "pipe",
|
|
18745
|
-
env: {
|
|
18746
|
-
...process.env,
|
|
18747
|
-
...modelDef.env || {}
|
|
18748
|
-
}
|
|
18813
|
+
const fixDescription = await adapter.complete(prompt, {
|
|
18814
|
+
model: modelDef.model
|
|
18749
18815
|
});
|
|
18750
|
-
const exitCode = await proc.exited;
|
|
18751
|
-
const stdout = await new Response(proc.stdout).text();
|
|
18752
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18753
|
-
if (exitCode !== 0) {
|
|
18754
|
-
logger.warn("acceptance", "\u26A0 Agent fix generation failed", { failedAC, stderr });
|
|
18755
|
-
fixStories.push({
|
|
18756
|
-
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
18757
|
-
title: `Fix: ${failedAC}`,
|
|
18758
|
-
failedAC,
|
|
18759
|
-
testOutput,
|
|
18760
|
-
relatedStories,
|
|
18761
|
-
description: `Fix the implementation to make ${failedAC} pass. Related stories: ${relatedStories.join(", ")}.`
|
|
18762
|
-
});
|
|
18763
|
-
continue;
|
|
18764
|
-
}
|
|
18765
|
-
const fixDescription = stdout.trim();
|
|
18766
18816
|
fixStories.push({
|
|
18767
18817
|
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
18768
18818
|
title: `Fix: ${failedAC} \u2014 ${acText.slice(0, 50)}`,
|
|
@@ -18829,6 +18879,679 @@ var init_acceptance = __esm(() => {
|
|
|
18829
18879
|
init_fix_generator();
|
|
18830
18880
|
});
|
|
18831
18881
|
|
|
18882
|
+
// src/agents/acp/parser.ts
|
|
18883
|
+
function parseAcpxJsonOutput(rawOutput) {
|
|
18884
|
+
const lines = rawOutput.split(`
|
|
18885
|
+
`).filter((l) => l.trim());
|
|
18886
|
+
let text = "";
|
|
18887
|
+
let tokenUsage;
|
|
18888
|
+
let stopReason;
|
|
18889
|
+
let error48;
|
|
18890
|
+
for (const line of lines) {
|
|
18891
|
+
try {
|
|
18892
|
+
const event = JSON.parse(line);
|
|
18893
|
+
if (event.content && typeof event.content === "string")
|
|
18894
|
+
text += event.content;
|
|
18895
|
+
if (event.text && typeof event.text === "string")
|
|
18896
|
+
text += event.text;
|
|
18897
|
+
if (event.result && typeof event.result === "string")
|
|
18898
|
+
text = event.result;
|
|
18899
|
+
if (event.cumulative_token_usage)
|
|
18900
|
+
tokenUsage = event.cumulative_token_usage;
|
|
18901
|
+
if (event.usage) {
|
|
18902
|
+
tokenUsage = {
|
|
18903
|
+
input_tokens: event.usage.input_tokens ?? event.usage.prompt_tokens ?? 0,
|
|
18904
|
+
output_tokens: event.usage.output_tokens ?? event.usage.completion_tokens ?? 0
|
|
18905
|
+
};
|
|
18906
|
+
}
|
|
18907
|
+
if (event.stopReason)
|
|
18908
|
+
stopReason = event.stopReason;
|
|
18909
|
+
if (event.stop_reason)
|
|
18910
|
+
stopReason = event.stop_reason;
|
|
18911
|
+
if (event.error) {
|
|
18912
|
+
error48 = typeof event.error === "string" ? event.error : event.error.message ?? JSON.stringify(event.error);
|
|
18913
|
+
}
|
|
18914
|
+
} catch {
|
|
18915
|
+
if (!text)
|
|
18916
|
+
text = line;
|
|
18917
|
+
}
|
|
18918
|
+
}
|
|
18919
|
+
return { text: text.trim(), tokenUsage, stopReason, error: error48 };
|
|
18920
|
+
}
|
|
18921
|
+
|
|
18922
|
+
// src/agents/acp/spawn-client.ts
|
|
18923
|
+
function buildAllowedEnv2(extraEnv) {
|
|
18924
|
+
const allowed = {};
|
|
18925
|
+
const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
|
|
18926
|
+
for (const varName of essentialVars) {
|
|
18927
|
+
if (process.env[varName])
|
|
18928
|
+
allowed[varName] = process.env[varName];
|
|
18929
|
+
}
|
|
18930
|
+
const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
|
|
18931
|
+
for (const varName of apiKeyVars) {
|
|
18932
|
+
if (process.env[varName])
|
|
18933
|
+
allowed[varName] = process.env[varName];
|
|
18934
|
+
}
|
|
18935
|
+
const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_"];
|
|
18936
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
18937
|
+
if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
18938
|
+
allowed[key] = value;
|
|
18939
|
+
}
|
|
18940
|
+
}
|
|
18941
|
+
if (extraEnv)
|
|
18942
|
+
Object.assign(allowed, extraEnv);
|
|
18943
|
+
return allowed;
|
|
18944
|
+
}
|
|
18945
|
+
|
|
18946
|
+
class SpawnAcpSession {
|
|
18947
|
+
agentName;
|
|
18948
|
+
sessionName;
|
|
18949
|
+
cwd;
|
|
18950
|
+
model;
|
|
18951
|
+
timeoutSeconds;
|
|
18952
|
+
permissionMode;
|
|
18953
|
+
env;
|
|
18954
|
+
constructor(opts) {
|
|
18955
|
+
this.agentName = opts.agentName;
|
|
18956
|
+
this.sessionName = opts.sessionName;
|
|
18957
|
+
this.cwd = opts.cwd;
|
|
18958
|
+
this.model = opts.model;
|
|
18959
|
+
this.timeoutSeconds = opts.timeoutSeconds;
|
|
18960
|
+
this.permissionMode = opts.permissionMode;
|
|
18961
|
+
this.env = opts.env;
|
|
18962
|
+
}
|
|
18963
|
+
async prompt(text) {
|
|
18964
|
+
const cmd = [
|
|
18965
|
+
"acpx",
|
|
18966
|
+
"--cwd",
|
|
18967
|
+
this.cwd,
|
|
18968
|
+
...this.permissionMode === "approve-all" ? ["--approve-all"] : [],
|
|
18969
|
+
"--format",
|
|
18970
|
+
"json",
|
|
18971
|
+
"--model",
|
|
18972
|
+
this.model,
|
|
18973
|
+
"--timeout",
|
|
18974
|
+
String(this.timeoutSeconds),
|
|
18975
|
+
this.agentName,
|
|
18976
|
+
"prompt",
|
|
18977
|
+
"-s",
|
|
18978
|
+
this.sessionName,
|
|
18979
|
+
"--file",
|
|
18980
|
+
"-"
|
|
18981
|
+
];
|
|
18982
|
+
getSafeLogger()?.debug("acp-adapter", `Sending prompt to session: ${this.sessionName}`);
|
|
18983
|
+
const proc = _spawnClientDeps.spawn(cmd, {
|
|
18984
|
+
cwd: this.cwd,
|
|
18985
|
+
stdin: "pipe",
|
|
18986
|
+
stdout: "pipe",
|
|
18987
|
+
stderr: "pipe",
|
|
18988
|
+
env: this.env
|
|
18989
|
+
});
|
|
18990
|
+
proc.stdin.write(text);
|
|
18991
|
+
proc.stdin.end();
|
|
18992
|
+
const exitCode = await proc.exited;
|
|
18993
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18994
|
+
const stderr = await new Response(proc.stderr).text();
|
|
18995
|
+
if (exitCode !== 0) {
|
|
18996
|
+
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
18997
|
+
stderr: stderr.slice(0, 200)
|
|
18998
|
+
});
|
|
18999
|
+
return {
|
|
19000
|
+
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
19001
|
+
stopReason: "error"
|
|
19002
|
+
};
|
|
19003
|
+
}
|
|
19004
|
+
try {
|
|
19005
|
+
const parsed = parseAcpxJsonOutput(stdout);
|
|
19006
|
+
return {
|
|
19007
|
+
messages: [{ role: "assistant", content: parsed.text || "" }],
|
|
19008
|
+
stopReason: "end_turn",
|
|
19009
|
+
cumulative_token_usage: parsed.tokenUsage
|
|
19010
|
+
};
|
|
19011
|
+
} catch (err) {
|
|
19012
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
|
|
19013
|
+
stderr: stderr.slice(0, 200)
|
|
19014
|
+
});
|
|
19015
|
+
throw err;
|
|
19016
|
+
}
|
|
19017
|
+
}
|
|
19018
|
+
async close() {
|
|
19019
|
+
const cmd = ["acpx", this.agentName, "sessions", "close", this.sessionName];
|
|
19020
|
+
getSafeLogger()?.debug("acp-adapter", `Closing session: ${this.sessionName}`);
|
|
19021
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19022
|
+
const exitCode = await proc.exited;
|
|
19023
|
+
if (exitCode !== 0) {
|
|
19024
|
+
const stderr = await new Response(proc.stderr).text();
|
|
19025
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to close session", {
|
|
19026
|
+
sessionName: this.sessionName,
|
|
19027
|
+
stderr: stderr.slice(0, 200)
|
|
19028
|
+
});
|
|
19029
|
+
}
|
|
19030
|
+
}
|
|
19031
|
+
async cancelActivePrompt() {
|
|
19032
|
+
const cmd = ["acpx", this.agentName, "cancel"];
|
|
19033
|
+
getSafeLogger()?.debug("acp-adapter", `Cancelling active prompt: ${this.sessionName}`);
|
|
19034
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19035
|
+
await proc.exited;
|
|
19036
|
+
}
|
|
19037
|
+
}
|
|
19038
|
+
|
|
19039
|
+
class SpawnAcpClient {
|
|
19040
|
+
agentName;
|
|
19041
|
+
model;
|
|
19042
|
+
cwd;
|
|
19043
|
+
timeoutSeconds;
|
|
19044
|
+
env;
|
|
19045
|
+
constructor(cmdStr, cwd, timeoutSeconds) {
|
|
19046
|
+
const parts = cmdStr.split(/\s+/);
|
|
19047
|
+
const modelIdx = parts.indexOf("--model");
|
|
19048
|
+
this.model = modelIdx >= 0 && parts[modelIdx + 1] ? parts[modelIdx + 1] : "default";
|
|
19049
|
+
this.agentName = parts[parts.length - 1] || "claude";
|
|
19050
|
+
this.cwd = cwd || process.cwd();
|
|
19051
|
+
this.timeoutSeconds = timeoutSeconds || 1800;
|
|
19052
|
+
this.env = buildAllowedEnv2();
|
|
19053
|
+
}
|
|
19054
|
+
async start() {}
|
|
19055
|
+
async createSession(opts) {
|
|
19056
|
+
const sessionName = opts.sessionName || `nax-${Date.now()}`;
|
|
19057
|
+
const cmd = ["acpx", opts.agentName, "sessions", "ensure", "--name", sessionName];
|
|
19058
|
+
getSafeLogger()?.debug("acp-adapter", `Ensuring session: ${sessionName}`);
|
|
19059
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19060
|
+
const exitCode = await proc.exited;
|
|
19061
|
+
if (exitCode !== 0) {
|
|
19062
|
+
const stderr = await new Response(proc.stderr).text();
|
|
19063
|
+
throw new Error(`[acp-adapter] Failed to create session: ${stderr || `exit code ${exitCode}`}`);
|
|
19064
|
+
}
|
|
19065
|
+
return new SpawnAcpSession({
|
|
19066
|
+
agentName: opts.agentName,
|
|
19067
|
+
sessionName,
|
|
19068
|
+
cwd: this.cwd,
|
|
19069
|
+
model: this.model,
|
|
19070
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
19071
|
+
permissionMode: opts.permissionMode,
|
|
19072
|
+
env: this.env
|
|
19073
|
+
});
|
|
19074
|
+
}
|
|
19075
|
+
async loadSession(sessionName) {
|
|
19076
|
+
const cmd = ["acpx", this.agentName, "sessions", "ensure", "--name", sessionName];
|
|
19077
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19078
|
+
const exitCode = await proc.exited;
|
|
19079
|
+
if (exitCode !== 0) {
|
|
19080
|
+
return null;
|
|
19081
|
+
}
|
|
19082
|
+
return new SpawnAcpSession({
|
|
19083
|
+
agentName: this.agentName,
|
|
19084
|
+
sessionName,
|
|
19085
|
+
cwd: this.cwd,
|
|
19086
|
+
model: this.model,
|
|
19087
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
19088
|
+
permissionMode: "approve-all",
|
|
19089
|
+
env: this.env
|
|
19090
|
+
});
|
|
19091
|
+
}
|
|
19092
|
+
async close() {}
|
|
19093
|
+
}
|
|
19094
|
+
function createSpawnAcpClient(cmdStr, cwd, timeoutSeconds) {
|
|
19095
|
+
return new SpawnAcpClient(cmdStr, cwd, timeoutSeconds);
|
|
19096
|
+
}
|
|
19097
|
+
var _spawnClientDeps;
|
|
19098
|
+
var init_spawn_client = __esm(() => {
|
|
19099
|
+
init_logger2();
|
|
19100
|
+
_spawnClientDeps = {
|
|
19101
|
+
spawn(cmd, opts) {
|
|
19102
|
+
return Bun.spawn(cmd, opts);
|
|
19103
|
+
}
|
|
19104
|
+
};
|
|
19105
|
+
});
|
|
19106
|
+
|
|
19107
|
+
// src/agents/acp/cost.ts
|
|
19108
|
+
function estimateCostFromTokenUsage(usage, model) {
|
|
19109
|
+
const pricing = MODEL_PRICING[model];
|
|
19110
|
+
if (!pricing) {
|
|
19111
|
+
const fallbackInputRate = 3 / 1e6;
|
|
19112
|
+
const fallbackOutputRate = 15 / 1e6;
|
|
19113
|
+
const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
|
|
19114
|
+
const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
|
|
19115
|
+
const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
|
|
19116
|
+
const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
|
|
19117
|
+
return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
|
|
19118
|
+
}
|
|
19119
|
+
const inputRate = pricing.input / 1e6;
|
|
19120
|
+
const outputRate = pricing.output / 1e6;
|
|
19121
|
+
const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
|
|
19122
|
+
const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
|
|
19123
|
+
const inputCost = (usage.input_tokens ?? 0) * inputRate;
|
|
19124
|
+
const outputCost = (usage.output_tokens ?? 0) * outputRate;
|
|
19125
|
+
const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
|
|
19126
|
+
const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
|
|
19127
|
+
return inputCost + outputCost + cacheReadCost + cacheCreationCost;
|
|
19128
|
+
}
|
|
19129
|
+
var MODEL_PRICING;
|
|
19130
|
+
var init_cost2 = __esm(() => {
|
|
19131
|
+
MODEL_PRICING = {
|
|
19132
|
+
"claude-sonnet-4": { input: 3, output: 15 },
|
|
19133
|
+
"claude-sonnet-4-5": { input: 3, output: 15 },
|
|
19134
|
+
"claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
|
|
19135
|
+
"claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
|
|
19136
|
+
"claude-opus": { input: 15, output: 75 },
|
|
19137
|
+
"claude-opus-4": { input: 15, output: 75 },
|
|
19138
|
+
"gpt-4.1": { input: 10, output: 30 },
|
|
19139
|
+
"gpt-4": { input: 30, output: 60 },
|
|
19140
|
+
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
19141
|
+
"gemini-2.5-pro": { input: 0.075, output: 0.3 },
|
|
19142
|
+
"gemini-2-pro": { input: 0.075, output: 0.3 },
|
|
19143
|
+
codex: { input: 0.02, output: 0.06 },
|
|
19144
|
+
"code-davinci-002": { input: 0.02, output: 0.06 }
|
|
19145
|
+
};
|
|
19146
|
+
});
|
|
19147
|
+
|
|
19148
|
+
// src/agents/acp/adapter.ts
|
|
19149
|
+
import { createHash } from "crypto";
|
|
19150
|
+
import { join as join3 } from "path";
|
|
19151
|
+
function resolveRegistryEntry(agentName) {
|
|
19152
|
+
return AGENT_REGISTRY[agentName] ?? DEFAULT_ENTRY;
|
|
19153
|
+
}
|
|
19154
|
+
function isRateLimitError(err) {
|
|
19155
|
+
if (!(err instanceof Error))
|
|
19156
|
+
return false;
|
|
19157
|
+
const msg = err.message.toLowerCase();
|
|
19158
|
+
return msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429");
|
|
19159
|
+
}
|
|
19160
|
+
function buildSessionName(workdir, featureName, storyId, sessionRole) {
|
|
19161
|
+
const hash2 = createHash("sha256").update(workdir).digest("hex").slice(0, 8);
|
|
19162
|
+
const sanitize = (s) => s.replace(/[^a-z0-9]+/gi, "-").toLowerCase().replace(/^-+|-+$/g, "");
|
|
19163
|
+
const parts = ["nax", hash2];
|
|
19164
|
+
if (featureName)
|
|
19165
|
+
parts.push(sanitize(featureName));
|
|
19166
|
+
if (storyId)
|
|
19167
|
+
parts.push(sanitize(storyId));
|
|
19168
|
+
if (sessionRole)
|
|
19169
|
+
parts.push(sanitize(sessionRole));
|
|
19170
|
+
return parts.join("-");
|
|
19171
|
+
}
|
|
19172
|
+
async function ensureAcpSession(client, sessionName, agentName, permissionMode) {
|
|
19173
|
+
if (client.loadSession) {
|
|
19174
|
+
try {
|
|
19175
|
+
const existing = await client.loadSession(sessionName);
|
|
19176
|
+
if (existing) {
|
|
19177
|
+
getSafeLogger()?.debug("acp-adapter", `Resumed existing session: ${sessionName}`);
|
|
19178
|
+
return existing;
|
|
19179
|
+
}
|
|
19180
|
+
} catch {}
|
|
19181
|
+
}
|
|
19182
|
+
getSafeLogger()?.debug("acp-adapter", `Creating new session: ${sessionName}`);
|
|
19183
|
+
return client.createSession({ agentName, permissionMode, sessionName });
|
|
19184
|
+
}
|
|
19185
|
+
async function runSessionPrompt(session, prompt, timeoutMs) {
|
|
19186
|
+
const promptPromise = session.prompt(prompt);
|
|
19187
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve("timeout"), timeoutMs));
|
|
19188
|
+
const winner = await Promise.race([promptPromise, timeoutPromise]);
|
|
19189
|
+
if (winner === "timeout") {
|
|
19190
|
+
try {
|
|
19191
|
+
await session.cancelActivePrompt();
|
|
19192
|
+
} catch {
|
|
19193
|
+
await session.close().catch(() => {});
|
|
19194
|
+
}
|
|
19195
|
+
return { response: null, timedOut: true };
|
|
19196
|
+
}
|
|
19197
|
+
return { response: winner, timedOut: false };
|
|
19198
|
+
}
|
|
19199
|
+
async function closeAcpSession(session) {
|
|
19200
|
+
try {
|
|
19201
|
+
await session.close();
|
|
19202
|
+
} catch (err) {
|
|
19203
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to close session", { error: String(err) });
|
|
19204
|
+
}
|
|
19205
|
+
}
|
|
19206
|
+
function acpSessionsPath(workdir, featureName) {
|
|
19207
|
+
return join3(workdir, "nax", "features", featureName, "acp-sessions.json");
|
|
19208
|
+
}
|
|
19209
|
+
async function saveAcpSession(workdir, featureName, storyId, sessionName) {
|
|
19210
|
+
try {
|
|
19211
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19212
|
+
let data = {};
|
|
19213
|
+
try {
|
|
19214
|
+
const existing = await Bun.file(path).text();
|
|
19215
|
+
data = JSON.parse(existing);
|
|
19216
|
+
} catch {}
|
|
19217
|
+
data[storyId] = sessionName;
|
|
19218
|
+
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19219
|
+
} catch (err) {
|
|
19220
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to save session to sidecar", { error: String(err) });
|
|
19221
|
+
}
|
|
19222
|
+
}
|
|
19223
|
+
async function clearAcpSession(workdir, featureName, storyId) {
|
|
19224
|
+
try {
|
|
19225
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19226
|
+
let data = {};
|
|
19227
|
+
try {
|
|
19228
|
+
const existing = await Bun.file(path).text();
|
|
19229
|
+
data = JSON.parse(existing);
|
|
19230
|
+
} catch {
|
|
19231
|
+
return;
|
|
19232
|
+
}
|
|
19233
|
+
delete data[storyId];
|
|
19234
|
+
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19235
|
+
} catch (err) {
|
|
19236
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to clear session from sidecar", { error: String(err) });
|
|
19237
|
+
}
|
|
19238
|
+
}
|
|
19239
|
+
async function readAcpSession(workdir, featureName, storyId) {
|
|
19240
|
+
try {
|
|
19241
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19242
|
+
const existing = await Bun.file(path).text();
|
|
19243
|
+
const data = JSON.parse(existing);
|
|
19244
|
+
return data[storyId] ?? null;
|
|
19245
|
+
} catch {
|
|
19246
|
+
return null;
|
|
19247
|
+
}
|
|
19248
|
+
}
|
|
19249
|
+
function extractOutput(response) {
|
|
19250
|
+
if (!response)
|
|
19251
|
+
return "";
|
|
19252
|
+
return response.messages.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
19253
|
+
`).trim();
|
|
19254
|
+
}
|
|
19255
|
+
function extractQuestion(output) {
|
|
19256
|
+
const text = output.trim();
|
|
19257
|
+
if (!text)
|
|
19258
|
+
return null;
|
|
19259
|
+
const sentences = text.split(/(?<=[.!?])\s+/);
|
|
19260
|
+
const questionSentences = sentences.filter((s) => s.trim().endsWith("?"));
|
|
19261
|
+
if (questionSentences.length > 0) {
|
|
19262
|
+
const q = questionSentences[questionSentences.length - 1].trim();
|
|
19263
|
+
if (q.length > 10)
|
|
19264
|
+
return q;
|
|
19265
|
+
}
|
|
19266
|
+
const lower = text.toLowerCase();
|
|
19267
|
+
const markers = [
|
|
19268
|
+
"please confirm",
|
|
19269
|
+
"please specify",
|
|
19270
|
+
"please provide",
|
|
19271
|
+
"which would you",
|
|
19272
|
+
"should i ",
|
|
19273
|
+
"do you want",
|
|
19274
|
+
"can you clarify"
|
|
19275
|
+
];
|
|
19276
|
+
for (const marker of markers) {
|
|
19277
|
+
if (lower.includes(marker)) {
|
|
19278
|
+
return text.slice(-200).trim();
|
|
19279
|
+
}
|
|
19280
|
+
}
|
|
19281
|
+
return null;
|
|
19282
|
+
}
|
|
19283
|
+
|
|
19284
|
+
class AcpAgentAdapter {
|
|
19285
|
+
name;
|
|
19286
|
+
displayName;
|
|
19287
|
+
binary;
|
|
19288
|
+
capabilities;
|
|
19289
|
+
constructor(agentName) {
|
|
19290
|
+
const entry = resolveRegistryEntry(agentName);
|
|
19291
|
+
this.name = agentName;
|
|
19292
|
+
this.displayName = entry.displayName;
|
|
19293
|
+
this.binary = entry.binary;
|
|
19294
|
+
this.capabilities = {
|
|
19295
|
+
supportedTiers: entry.supportedTiers,
|
|
19296
|
+
maxContextTokens: entry.maxContextTokens,
|
|
19297
|
+
features: new Set(["tdd", "review", "refactor"])
|
|
19298
|
+
};
|
|
19299
|
+
}
|
|
19300
|
+
async isInstalled() {
|
|
19301
|
+
const path = _acpAdapterDeps.which(this.binary);
|
|
19302
|
+
return path !== null;
|
|
19303
|
+
}
|
|
19304
|
+
buildCommand(_options) {
|
|
19305
|
+
return ["acpx", this.name, "session"];
|
|
19306
|
+
}
|
|
19307
|
+
buildAllowedEnv(_options) {
|
|
19308
|
+
return {};
|
|
19309
|
+
}
|
|
19310
|
+
async run(options) {
|
|
19311
|
+
const startTime = Date.now();
|
|
19312
|
+
let lastError;
|
|
19313
|
+
getSafeLogger()?.debug("acp-adapter", `Starting run for ${this.name}`, {
|
|
19314
|
+
model: options.modelDef.model,
|
|
19315
|
+
workdir: options.workdir,
|
|
19316
|
+
featureName: options.featureName,
|
|
19317
|
+
storyId: options.storyId,
|
|
19318
|
+
sessionRole: options.sessionRole
|
|
19319
|
+
});
|
|
19320
|
+
for (let attempt = 0;attempt < MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
19321
|
+
try {
|
|
19322
|
+
const result = await this._runWithClient(options, startTime);
|
|
19323
|
+
if (!result.success) {
|
|
19324
|
+
getSafeLogger()?.warn("acp-adapter", `Run failed for ${this.name}`, { exitCode: result.exitCode });
|
|
19325
|
+
}
|
|
19326
|
+
return result;
|
|
19327
|
+
} catch (err) {
|
|
19328
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
19329
|
+
lastError = error48;
|
|
19330
|
+
const shouldRetry = isRateLimitError(error48) && attempt < MAX_RATE_LIMIT_RETRIES - 1;
|
|
19331
|
+
if (!shouldRetry)
|
|
19332
|
+
break;
|
|
19333
|
+
const backoffMs = 2 ** (attempt + 1) * 1000;
|
|
19334
|
+
getSafeLogger()?.warn("acp-adapter", "Retrying after rate limit", {
|
|
19335
|
+
backoffSeconds: backoffMs / 1000,
|
|
19336
|
+
attempt: attempt + 1
|
|
19337
|
+
});
|
|
19338
|
+
await _acpAdapterDeps.sleep(backoffMs);
|
|
19339
|
+
}
|
|
19340
|
+
}
|
|
19341
|
+
const durationMs = Date.now() - startTime;
|
|
19342
|
+
return {
|
|
19343
|
+
success: false,
|
|
19344
|
+
exitCode: 1,
|
|
19345
|
+
output: lastError?.message ?? "Run failed",
|
|
19346
|
+
rateLimited: isRateLimitError(lastError),
|
|
19347
|
+
durationMs,
|
|
19348
|
+
estimatedCost: 0
|
|
19349
|
+
};
|
|
19350
|
+
}
|
|
19351
|
+
async _runWithClient(options, startTime) {
|
|
19352
|
+
const cmdStr = `acpx --model ${options.modelDef.model} ${this.name}`;
|
|
19353
|
+
const client = _acpAdapterDeps.createClient(cmdStr, options.workdir, options.timeoutSeconds);
|
|
19354
|
+
await client.start();
|
|
19355
|
+
let sessionName = options.acpSessionName;
|
|
19356
|
+
if (!sessionName && options.featureName && options.storyId) {
|
|
19357
|
+
sessionName = await readAcpSession(options.workdir, options.featureName, options.storyId) ?? undefined;
|
|
19358
|
+
}
|
|
19359
|
+
sessionName ??= buildSessionName(options.workdir, options.featureName, options.storyId, options.sessionRole);
|
|
19360
|
+
const permissionMode = options.dangerouslySkipPermissions ? "approve-all" : "default";
|
|
19361
|
+
const session = await ensureAcpSession(client, sessionName, this.name, permissionMode);
|
|
19362
|
+
if (options.featureName && options.storyId) {
|
|
19363
|
+
await saveAcpSession(options.workdir, options.featureName, options.storyId, sessionName);
|
|
19364
|
+
}
|
|
19365
|
+
let lastResponse = null;
|
|
19366
|
+
let timedOut = false;
|
|
19367
|
+
const totalTokenUsage = { input_tokens: 0, output_tokens: 0 };
|
|
19368
|
+
try {
|
|
19369
|
+
let currentPrompt = options.prompt;
|
|
19370
|
+
let turnCount = 0;
|
|
19371
|
+
const MAX_TURNS = options.interactionBridge ? 10 : 1;
|
|
19372
|
+
while (turnCount < MAX_TURNS) {
|
|
19373
|
+
turnCount++;
|
|
19374
|
+
getSafeLogger()?.debug("acp-adapter", `Session turn ${turnCount}/${MAX_TURNS}`, { sessionName });
|
|
19375
|
+
const turnResult = await runSessionPrompt(session, currentPrompt, options.timeoutSeconds * 1000);
|
|
19376
|
+
if (turnResult.timedOut) {
|
|
19377
|
+
timedOut = true;
|
|
19378
|
+
break;
|
|
19379
|
+
}
|
|
19380
|
+
lastResponse = turnResult.response;
|
|
19381
|
+
if (!lastResponse)
|
|
19382
|
+
break;
|
|
19383
|
+
if (lastResponse.cumulative_token_usage) {
|
|
19384
|
+
totalTokenUsage.input_tokens += lastResponse.cumulative_token_usage.input_tokens ?? 0;
|
|
19385
|
+
totalTokenUsage.output_tokens += lastResponse.cumulative_token_usage.output_tokens ?? 0;
|
|
19386
|
+
}
|
|
19387
|
+
const outputText = extractOutput(lastResponse);
|
|
19388
|
+
const question = extractQuestion(outputText);
|
|
19389
|
+
if (!question || !options.interactionBridge)
|
|
19390
|
+
break;
|
|
19391
|
+
getSafeLogger()?.debug("acp-adapter", "Agent asked question, routing to interactionBridge", { question });
|
|
19392
|
+
try {
|
|
19393
|
+
const answer = await Promise.race([
|
|
19394
|
+
options.interactionBridge.onQuestionDetected(question),
|
|
19395
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("interaction timeout")), INTERACTION_TIMEOUT_MS))
|
|
19396
|
+
]);
|
|
19397
|
+
currentPrompt = answer;
|
|
19398
|
+
} catch (err) {
|
|
19399
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19400
|
+
getSafeLogger()?.warn("acp-adapter", `InteractionBridge failed: ${msg}`);
|
|
19401
|
+
break;
|
|
19402
|
+
}
|
|
19403
|
+
}
|
|
19404
|
+
if (turnCount >= MAX_TURNS) {
|
|
19405
|
+
getSafeLogger()?.warn("acp-adapter", "Reached max turns limit", { sessionName, maxTurns: MAX_TURNS });
|
|
19406
|
+
}
|
|
19407
|
+
} finally {
|
|
19408
|
+
await closeAcpSession(session);
|
|
19409
|
+
await client.close().catch(() => {});
|
|
19410
|
+
if (options.featureName && options.storyId) {
|
|
19411
|
+
await clearAcpSession(options.workdir, options.featureName, options.storyId);
|
|
19412
|
+
}
|
|
19413
|
+
}
|
|
19414
|
+
const durationMs = Date.now() - startTime;
|
|
19415
|
+
if (timedOut) {
|
|
19416
|
+
return {
|
|
19417
|
+
success: false,
|
|
19418
|
+
exitCode: 124,
|
|
19419
|
+
output: `Session timed out after ${options.timeoutSeconds}s`,
|
|
19420
|
+
rateLimited: false,
|
|
19421
|
+
durationMs,
|
|
19422
|
+
estimatedCost: 0
|
|
19423
|
+
};
|
|
19424
|
+
}
|
|
19425
|
+
const success2 = lastResponse?.stopReason === "end_turn";
|
|
19426
|
+
const output = extractOutput(lastResponse);
|
|
19427
|
+
const estimatedCost = totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0;
|
|
19428
|
+
return {
|
|
19429
|
+
success: success2,
|
|
19430
|
+
exitCode: success2 ? 0 : 1,
|
|
19431
|
+
output: output.slice(-MAX_AGENT_OUTPUT_CHARS2),
|
|
19432
|
+
rateLimited: false,
|
|
19433
|
+
durationMs,
|
|
19434
|
+
estimatedCost
|
|
19435
|
+
};
|
|
19436
|
+
}
|
|
19437
|
+
async complete(prompt, _options) {
|
|
19438
|
+
const model = _options?.model ?? "default";
|
|
19439
|
+
const cmdStr = `acpx --model ${model} ${this.name}`;
|
|
19440
|
+
const client = _acpAdapterDeps.createClient(cmdStr);
|
|
19441
|
+
await client.start();
|
|
19442
|
+
const permissionMode = _options?.dangerouslySkipPermissions ? "approve-all" : "default";
|
|
19443
|
+
let session = null;
|
|
19444
|
+
try {
|
|
19445
|
+
session = await client.createSession({ agentName: this.name, permissionMode });
|
|
19446
|
+
const response = await session.prompt(prompt);
|
|
19447
|
+
if (response.stopReason === "error") {
|
|
19448
|
+
throw new CompleteError("complete() failed: stop reason is error");
|
|
19449
|
+
}
|
|
19450
|
+
const text = response.messages.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
19451
|
+
`).trim();
|
|
19452
|
+
if (!text) {
|
|
19453
|
+
throw new CompleteError("complete() returned empty output");
|
|
19454
|
+
}
|
|
19455
|
+
return text;
|
|
19456
|
+
} finally {
|
|
19457
|
+
if (session) {
|
|
19458
|
+
await session.close().catch(() => {});
|
|
19459
|
+
}
|
|
19460
|
+
await client.close().catch(() => {});
|
|
19461
|
+
}
|
|
19462
|
+
}
|
|
19463
|
+
async plan(options) {
|
|
19464
|
+
if (options.interactive) {
|
|
19465
|
+
throw new Error("[acp-adapter] plan() interactive mode is not yet supported via ACP");
|
|
19466
|
+
}
|
|
19467
|
+
const modelDef = options.modelDef ?? { provider: "anthropic", model: "default" };
|
|
19468
|
+
const timeoutSeconds = options.timeoutSeconds ?? options.config?.execution?.sessionTimeoutSeconds ?? 600;
|
|
19469
|
+
const result = await this.run({
|
|
19470
|
+
prompt: options.prompt,
|
|
19471
|
+
workdir: options.workdir,
|
|
19472
|
+
modelTier: options.modelTier ?? "balanced",
|
|
19473
|
+
modelDef,
|
|
19474
|
+
timeoutSeconds,
|
|
19475
|
+
dangerouslySkipPermissions: options.dangerouslySkipPermissions ?? false,
|
|
19476
|
+
interactionBridge: options.interactionBridge,
|
|
19477
|
+
featureName: options.featureName,
|
|
19478
|
+
storyId: options.storyId,
|
|
19479
|
+
sessionRole: options.sessionRole
|
|
19480
|
+
});
|
|
19481
|
+
if (!result.success) {
|
|
19482
|
+
throw new Error(`[acp-adapter] plan() failed: ${result.output}`);
|
|
19483
|
+
}
|
|
19484
|
+
const specContent = result.output.trim();
|
|
19485
|
+
if (!specContent) {
|
|
19486
|
+
throw new Error("[acp-adapter] plan() returned empty spec content");
|
|
19487
|
+
}
|
|
19488
|
+
return { specContent };
|
|
19489
|
+
}
|
|
19490
|
+
async decompose(options) {
|
|
19491
|
+
const model = options.modelDef?.model;
|
|
19492
|
+
const prompt = buildDecomposePrompt(options);
|
|
19493
|
+
let output;
|
|
19494
|
+
try {
|
|
19495
|
+
output = await this.complete(prompt, { model, jsonMode: true });
|
|
19496
|
+
} catch (err) {
|
|
19497
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19498
|
+
throw new Error(`[acp-adapter] decompose() failed: ${msg}`, { cause: err });
|
|
19499
|
+
}
|
|
19500
|
+
let stories;
|
|
19501
|
+
try {
|
|
19502
|
+
stories = parseDecomposeOutput(output);
|
|
19503
|
+
} catch (err) {
|
|
19504
|
+
throw new Error(`[acp-adapter] decompose() failed to parse stories: ${err.message}`, { cause: err });
|
|
19505
|
+
}
|
|
19506
|
+
return { stories };
|
|
19507
|
+
}
|
|
19508
|
+
}
|
|
19509
|
+
var MAX_AGENT_OUTPUT_CHARS2 = 5000, MAX_RATE_LIMIT_RETRIES = 3, INTERACTION_TIMEOUT_MS, AGENT_REGISTRY, DEFAULT_ENTRY, _acpAdapterDeps;
|
|
19510
|
+
var init_adapter = __esm(() => {
|
|
19511
|
+
init_logger2();
|
|
19512
|
+
init_spawn_client();
|
|
19513
|
+
init_types2();
|
|
19514
|
+
init_cost2();
|
|
19515
|
+
INTERACTION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
19516
|
+
AGENT_REGISTRY = {
|
|
19517
|
+
claude: {
|
|
19518
|
+
binary: "claude",
|
|
19519
|
+
displayName: "Claude Code (ACP)",
|
|
19520
|
+
supportedTiers: ["fast", "balanced", "powerful"],
|
|
19521
|
+
maxContextTokens: 200000
|
|
19522
|
+
},
|
|
19523
|
+
codex: {
|
|
19524
|
+
binary: "codex",
|
|
19525
|
+
displayName: "OpenAI Codex (ACP)",
|
|
19526
|
+
supportedTiers: ["fast", "balanced"],
|
|
19527
|
+
maxContextTokens: 128000
|
|
19528
|
+
},
|
|
19529
|
+
gemini: {
|
|
19530
|
+
binary: "gemini",
|
|
19531
|
+
displayName: "Gemini CLI (ACP)",
|
|
19532
|
+
supportedTiers: ["fast", "balanced", "powerful"],
|
|
19533
|
+
maxContextTokens: 1e6
|
|
19534
|
+
}
|
|
19535
|
+
};
|
|
19536
|
+
DEFAULT_ENTRY = {
|
|
19537
|
+
binary: "claude",
|
|
19538
|
+
displayName: "ACP Agent",
|
|
19539
|
+
supportedTiers: ["balanced"],
|
|
19540
|
+
maxContextTokens: 128000
|
|
19541
|
+
};
|
|
19542
|
+
_acpAdapterDeps = {
|
|
19543
|
+
which(name) {
|
|
19544
|
+
return Bun.which(name);
|
|
19545
|
+
},
|
|
19546
|
+
async sleep(ms) {
|
|
19547
|
+
await Bun.sleep(ms);
|
|
19548
|
+
},
|
|
19549
|
+
createClient(cmdStr, cwd, timeoutSeconds) {
|
|
19550
|
+
return createSpawnAcpClient(cmdStr, cwd, timeoutSeconds);
|
|
19551
|
+
}
|
|
19552
|
+
};
|
|
19553
|
+
});
|
|
19554
|
+
|
|
18832
19555
|
// src/agents/adapters/aider.ts
|
|
18833
19556
|
class AiderAdapter {
|
|
18834
19557
|
name = "aider";
|
|
@@ -18859,7 +19582,7 @@ class AiderAdapter {
|
|
|
18859
19582
|
return {
|
|
18860
19583
|
success: exitCode === 0,
|
|
18861
19584
|
exitCode,
|
|
18862
|
-
output: stdout.slice(-
|
|
19585
|
+
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS3),
|
|
18863
19586
|
rateLimited: false,
|
|
18864
19587
|
durationMs,
|
|
18865
19588
|
estimatedCost: 0,
|
|
@@ -18893,7 +19616,7 @@ class AiderAdapter {
|
|
|
18893
19616
|
throw new Error("AiderAdapter.decompose() not implemented");
|
|
18894
19617
|
}
|
|
18895
19618
|
}
|
|
18896
|
-
var _aiderCompleteDeps,
|
|
19619
|
+
var _aiderCompleteDeps, MAX_AGENT_OUTPUT_CHARS3 = 5000;
|
|
18897
19620
|
var init_aider = __esm(() => {
|
|
18898
19621
|
init_types2();
|
|
18899
19622
|
_aiderCompleteDeps = {
|
|
@@ -18937,7 +19660,7 @@ class CodexAdapter {
|
|
|
18937
19660
|
return {
|
|
18938
19661
|
success: exitCode === 0,
|
|
18939
19662
|
exitCode,
|
|
18940
|
-
output: stdout.slice(-
|
|
19663
|
+
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS4),
|
|
18941
19664
|
rateLimited: false,
|
|
18942
19665
|
durationMs,
|
|
18943
19666
|
estimatedCost: 0,
|
|
@@ -18968,7 +19691,7 @@ class CodexAdapter {
|
|
|
18968
19691
|
throw new Error("CodexAdapter.decompose() not implemented");
|
|
18969
19692
|
}
|
|
18970
19693
|
}
|
|
18971
|
-
var _codexRunDeps, _codexCompleteDeps,
|
|
19694
|
+
var _codexRunDeps, _codexCompleteDeps, MAX_AGENT_OUTPUT_CHARS4 = 5000;
|
|
18972
19695
|
var init_codex = __esm(() => {
|
|
18973
19696
|
init_types2();
|
|
18974
19697
|
_codexRunDeps = {
|
|
@@ -19037,7 +19760,7 @@ class GeminiAdapter {
|
|
|
19037
19760
|
return {
|
|
19038
19761
|
success: exitCode === 0,
|
|
19039
19762
|
exitCode,
|
|
19040
|
-
output: stdout.slice(-
|
|
19763
|
+
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS5),
|
|
19041
19764
|
rateLimited: false,
|
|
19042
19765
|
durationMs,
|
|
19043
19766
|
estimatedCost: 0,
|
|
@@ -19068,7 +19791,7 @@ class GeminiAdapter {
|
|
|
19068
19791
|
throw new Error("GeminiAdapter.decompose() not implemented");
|
|
19069
19792
|
}
|
|
19070
19793
|
}
|
|
19071
|
-
var _geminiRunDeps, _geminiCompleteDeps,
|
|
19794
|
+
var _geminiRunDeps, _geminiCompleteDeps, MAX_AGENT_OUTPUT_CHARS5 = 5000;
|
|
19072
19795
|
var init_gemini = __esm(() => {
|
|
19073
19796
|
init_types2();
|
|
19074
19797
|
_geminiRunDeps = {
|
|
@@ -19149,6 +19872,7 @@ __export(exports_registry, {
|
|
|
19149
19872
|
getInstalledAgents: () => getInstalledAgents,
|
|
19150
19873
|
getAllAgentNames: () => getAllAgentNames,
|
|
19151
19874
|
getAgent: () => getAgent,
|
|
19875
|
+
createAgentRegistry: () => createAgentRegistry,
|
|
19152
19876
|
checkAgentHealth: () => checkAgentHealth,
|
|
19153
19877
|
ALL_AGENTS: () => ALL_AGENTS
|
|
19154
19878
|
});
|
|
@@ -19172,8 +19896,57 @@ async function checkAgentHealth() {
|
|
|
19172
19896
|
installed: await agent.isInstalled()
|
|
19173
19897
|
})));
|
|
19174
19898
|
}
|
|
19899
|
+
function createAgentRegistry(config2) {
|
|
19900
|
+
const protocol = config2.agent?.protocol ?? "cli";
|
|
19901
|
+
const logger = getLogger();
|
|
19902
|
+
const acpCache = new Map;
|
|
19903
|
+
logger?.info("agents", `Agent protocol: ${protocol}`, { protocol, hasConfig: !!config2.agent });
|
|
19904
|
+
function getAgent2(name) {
|
|
19905
|
+
if (protocol === "acp") {
|
|
19906
|
+
const known = ALL_AGENTS.find((a) => a.name === name);
|
|
19907
|
+
if (!known)
|
|
19908
|
+
return;
|
|
19909
|
+
if (!acpCache.has(name)) {
|
|
19910
|
+
acpCache.set(name, new AcpAgentAdapter(name));
|
|
19911
|
+
logger?.debug("agents", `Created AcpAgentAdapter for ${name}`, { name, protocol });
|
|
19912
|
+
}
|
|
19913
|
+
return acpCache.get(name);
|
|
19914
|
+
}
|
|
19915
|
+
const adapter = ALL_AGENTS.find((a) => a.name === name);
|
|
19916
|
+
if (adapter) {
|
|
19917
|
+
logger?.debug("agents", `Using CLI adapter for ${name}: ${adapter.constructor.name}`, { name });
|
|
19918
|
+
}
|
|
19919
|
+
return adapter;
|
|
19920
|
+
}
|
|
19921
|
+
async function getInstalledAgents2() {
|
|
19922
|
+
const agents = protocol === "acp" ? ALL_AGENTS.map((a) => {
|
|
19923
|
+
if (!acpCache.has(a.name)) {
|
|
19924
|
+
acpCache.set(a.name, new AcpAgentAdapter(a.name));
|
|
19925
|
+
}
|
|
19926
|
+
return acpCache.get(a.name);
|
|
19927
|
+
}) : ALL_AGENTS;
|
|
19928
|
+
const results = await Promise.all(agents.map(async (agent) => ({ agent, installed: await agent.isInstalled() })));
|
|
19929
|
+
return results.filter((r) => r.installed).map((r) => r.agent);
|
|
19930
|
+
}
|
|
19931
|
+
async function checkAgentHealth2() {
|
|
19932
|
+
const agents = protocol === "acp" ? ALL_AGENTS.map((a) => {
|
|
19933
|
+
if (!acpCache.has(a.name)) {
|
|
19934
|
+
acpCache.set(a.name, new AcpAgentAdapter(a.name));
|
|
19935
|
+
}
|
|
19936
|
+
return acpCache.get(a.name);
|
|
19937
|
+
}) : ALL_AGENTS;
|
|
19938
|
+
return Promise.all(agents.map(async (agent) => ({
|
|
19939
|
+
name: agent.name,
|
|
19940
|
+
displayName: agent.displayName,
|
|
19941
|
+
installed: await agent.isInstalled()
|
|
19942
|
+
})));
|
|
19943
|
+
}
|
|
19944
|
+
return { getAgent: getAgent2, getInstalledAgents: getInstalledAgents2, checkAgentHealth: checkAgentHealth2, protocol };
|
|
19945
|
+
}
|
|
19175
19946
|
var ALL_AGENTS;
|
|
19176
19947
|
var init_registry = __esm(() => {
|
|
19948
|
+
init_logger2();
|
|
19949
|
+
init_adapter();
|
|
19177
19950
|
init_aider();
|
|
19178
19951
|
init_codex();
|
|
19179
19952
|
init_gemini();
|
|
@@ -19335,7 +20108,7 @@ var init_chain = __esm(() => {
|
|
|
19335
20108
|
});
|
|
19336
20109
|
|
|
19337
20110
|
// src/utils/path-security.ts
|
|
19338
|
-
import { isAbsolute, join as
|
|
20111
|
+
import { isAbsolute, join as join5, normalize, resolve } from "path";
|
|
19339
20112
|
function validateModulePath(modulePath, allowedRoots) {
|
|
19340
20113
|
if (!modulePath) {
|
|
19341
20114
|
return { valid: false, error: "Module path is empty" };
|
|
@@ -19351,7 +20124,7 @@ function validateModulePath(modulePath, allowedRoots) {
|
|
|
19351
20124
|
}
|
|
19352
20125
|
} else {
|
|
19353
20126
|
for (const root of normalizedRoots) {
|
|
19354
|
-
const absoluteTarget = resolve(
|
|
20127
|
+
const absoluteTarget = resolve(join5(root, modulePath));
|
|
19355
20128
|
if (absoluteTarget.startsWith(`${root}/`) || absoluteTarget === root) {
|
|
19356
20129
|
return { valid: true, absolutePath: absoluteTarget };
|
|
19357
20130
|
}
|
|
@@ -19701,27 +20474,27 @@ var init_path_security2 = () => {};
|
|
|
19701
20474
|
|
|
19702
20475
|
// src/config/paths.ts
|
|
19703
20476
|
import { homedir } from "os";
|
|
19704
|
-
import { join as
|
|
20477
|
+
import { join as join6, resolve as resolve4 } from "path";
|
|
19705
20478
|
function globalConfigDir() {
|
|
19706
|
-
return
|
|
20479
|
+
return join6(homedir(), ".nax");
|
|
19707
20480
|
}
|
|
19708
20481
|
var init_paths = () => {};
|
|
19709
20482
|
|
|
19710
20483
|
// src/config/loader.ts
|
|
19711
20484
|
import { existsSync as existsSync5 } from "fs";
|
|
19712
|
-
import { join as
|
|
20485
|
+
import { join as join7, resolve as resolve5 } from "path";
|
|
19713
20486
|
function globalConfigPath() {
|
|
19714
|
-
return
|
|
20487
|
+
return join7(globalConfigDir(), "config.json");
|
|
19715
20488
|
}
|
|
19716
20489
|
function findProjectDir(startDir = process.cwd()) {
|
|
19717
20490
|
let dir = resolve5(startDir);
|
|
19718
20491
|
let depth = 0;
|
|
19719
20492
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
19720
|
-
const candidate =
|
|
19721
|
-
if (existsSync5(
|
|
20493
|
+
const candidate = join7(dir, "nax");
|
|
20494
|
+
if (existsSync5(join7(candidate, "config.json"))) {
|
|
19722
20495
|
return candidate;
|
|
19723
20496
|
}
|
|
19724
|
-
const parent =
|
|
20497
|
+
const parent = join7(dir, "..");
|
|
19725
20498
|
if (parent === dir)
|
|
19726
20499
|
break;
|
|
19727
20500
|
dir = parent;
|
|
@@ -19759,7 +20532,7 @@ async function loadConfig(projectDir, cliOverrides) {
|
|
|
19759
20532
|
}
|
|
19760
20533
|
const projDir = projectDir ?? findProjectDir();
|
|
19761
20534
|
if (projDir) {
|
|
19762
|
-
const projConf = await loadJsonFile(
|
|
20535
|
+
const projConf = await loadJsonFile(join7(projDir, "config.json"), "config");
|
|
19763
20536
|
if (projConf) {
|
|
19764
20537
|
const resolvedProjConf = applyBatchModeCompat(projConf);
|
|
19765
20538
|
rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
|
|
@@ -21034,7 +21807,7 @@ var package_default;
|
|
|
21034
21807
|
var init_package = __esm(() => {
|
|
21035
21808
|
package_default = {
|
|
21036
21809
|
name: "@nathapp/nax",
|
|
21037
|
-
version: "0.
|
|
21810
|
+
version: "0.41.0",
|
|
21038
21811
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
21039
21812
|
type: "module",
|
|
21040
21813
|
bin: {
|
|
@@ -21098,8 +21871,8 @@ var init_version = __esm(() => {
|
|
|
21098
21871
|
NAX_VERSION = package_default.version;
|
|
21099
21872
|
NAX_COMMIT = (() => {
|
|
21100
21873
|
try {
|
|
21101
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
21102
|
-
return "
|
|
21874
|
+
if (/^[0-9a-f]{6,10}$/.test("0db1c5f"))
|
|
21875
|
+
return "0db1c5f";
|
|
21103
21876
|
} catch {}
|
|
21104
21877
|
try {
|
|
21105
21878
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -22846,7 +23619,9 @@ ${stderr}` };
|
|
|
22846
23619
|
refinedCriteria = await _acceptanceSetupDeps.refine(allCriteria, {
|
|
22847
23620
|
storyId: ctx.prd.userStories[0]?.id ?? "US-001",
|
|
22848
23621
|
codebaseContext: "",
|
|
22849
|
-
config: ctx.config
|
|
23622
|
+
config: ctx.config,
|
|
23623
|
+
testStrategy: ctx.config.acceptance.testStrategy,
|
|
23624
|
+
testFramework: ctx.config.acceptance.testFramework
|
|
22850
23625
|
});
|
|
22851
23626
|
} else {
|
|
22852
23627
|
refinedCriteria = allCriteria.map((c) => ({
|
|
@@ -22863,7 +23638,9 @@ ${stderr}` };
|
|
|
22863
23638
|
codebaseContext: "",
|
|
22864
23639
|
modelTier: ctx.config.acceptance.model ?? "fast",
|
|
22865
23640
|
modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
|
|
22866
|
-
config: ctx.config
|
|
23641
|
+
config: ctx.config,
|
|
23642
|
+
testStrategy: ctx.config.acceptance.testStrategy,
|
|
23643
|
+
testFramework: ctx.config.acceptance.testFramework
|
|
22867
23644
|
});
|
|
22868
23645
|
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|
|
22869
23646
|
}
|
|
@@ -23375,10 +24152,10 @@ var init_autofix = __esm(() => {
|
|
|
23375
24152
|
|
|
23376
24153
|
// src/execution/progress.ts
|
|
23377
24154
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
23378
|
-
import { join as
|
|
24155
|
+
import { join as join15 } from "path";
|
|
23379
24156
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
23380
24157
|
mkdirSync2(featureDir, { recursive: true });
|
|
23381
|
-
const progressPath =
|
|
24158
|
+
const progressPath = join15(featureDir, "progress.txt");
|
|
23382
24159
|
const timestamp = new Date().toISOString();
|
|
23383
24160
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
23384
24161
|
`;
|
|
@@ -23462,7 +24239,7 @@ function estimateTokens(text) {
|
|
|
23462
24239
|
|
|
23463
24240
|
// src/constitution/loader.ts
|
|
23464
24241
|
import { existsSync as existsSync13 } from "fs";
|
|
23465
|
-
import { join as
|
|
24242
|
+
import { join as join16 } from "path";
|
|
23466
24243
|
function truncateToTokens(text, maxTokens) {
|
|
23467
24244
|
const maxChars = maxTokens * 3;
|
|
23468
24245
|
if (text.length <= maxChars) {
|
|
@@ -23484,7 +24261,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
23484
24261
|
}
|
|
23485
24262
|
let combinedContent = "";
|
|
23486
24263
|
if (!config2.skipGlobal) {
|
|
23487
|
-
const globalPath =
|
|
24264
|
+
const globalPath = join16(globalConfigDir(), config2.path);
|
|
23488
24265
|
if (existsSync13(globalPath)) {
|
|
23489
24266
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
23490
24267
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -23494,7 +24271,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
23494
24271
|
}
|
|
23495
24272
|
}
|
|
23496
24273
|
}
|
|
23497
|
-
const projectPath =
|
|
24274
|
+
const projectPath = join16(projectDir, config2.path);
|
|
23498
24275
|
if (existsSync13(projectPath)) {
|
|
23499
24276
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
23500
24277
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -24520,6 +25297,15 @@ ${pluginMarkdown}` : pluginMarkdown;
|
|
|
24520
25297
|
function validateAgentForTier(agent, tier) {
|
|
24521
25298
|
return agent.capabilities.supportedTiers.includes(tier);
|
|
24522
25299
|
}
|
|
25300
|
+
function validateAgentFeature(agent, feature) {
|
|
25301
|
+
return agent.capabilities.features.has(feature);
|
|
25302
|
+
}
|
|
25303
|
+
function describeAgentCapabilities(agent) {
|
|
25304
|
+
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
25305
|
+
const features = Array.from(agent.capabilities.features).join(",");
|
|
25306
|
+
const maxTokens = agent.capabilities.maxContextTokens;
|
|
25307
|
+
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
25308
|
+
}
|
|
24523
25309
|
|
|
24524
25310
|
// src/agents/version-detection.ts
|
|
24525
25311
|
async function getAgentVersion(binaryName) {
|
|
@@ -24570,6 +25356,26 @@ var init_version_detection = __esm(() => {
|
|
|
24570
25356
|
});
|
|
24571
25357
|
|
|
24572
25358
|
// src/agents/index.ts
|
|
25359
|
+
var exports_agents = {};
|
|
25360
|
+
__export(exports_agents, {
|
|
25361
|
+
validateAgentForTier: () => validateAgentForTier,
|
|
25362
|
+
validateAgentFeature: () => validateAgentFeature,
|
|
25363
|
+
parseTokenUsage: () => parseTokenUsage,
|
|
25364
|
+
getInstalledAgents: () => getInstalledAgents,
|
|
25365
|
+
getAllAgentNames: () => getAllAgentNames,
|
|
25366
|
+
getAgentVersions: () => getAgentVersions,
|
|
25367
|
+
getAgentVersion: () => getAgentVersion,
|
|
25368
|
+
getAgent: () => getAgent,
|
|
25369
|
+
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
25370
|
+
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
25371
|
+
estimateCostByDuration: () => estimateCostByDuration,
|
|
25372
|
+
estimateCost: () => estimateCost,
|
|
25373
|
+
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
25374
|
+
checkAgentHealth: () => checkAgentHealth,
|
|
25375
|
+
CompleteError: () => CompleteError,
|
|
25376
|
+
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
25377
|
+
COST_RATES: () => COST_RATES
|
|
25378
|
+
});
|
|
24573
25379
|
var init_agents = __esm(() => {
|
|
24574
25380
|
init_types2();
|
|
24575
25381
|
init_claude();
|
|
@@ -24647,14 +25453,14 @@ var init_isolation = __esm(() => {
|
|
|
24647
25453
|
|
|
24648
25454
|
// src/context/greenfield.ts
|
|
24649
25455
|
import { readdir } from "fs/promises";
|
|
24650
|
-
import { join as
|
|
25456
|
+
import { join as join17 } from "path";
|
|
24651
25457
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
24652
25458
|
const results = [];
|
|
24653
25459
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
24654
25460
|
try {
|
|
24655
25461
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
24656
25462
|
for (const entry of entries) {
|
|
24657
|
-
const fullPath =
|
|
25463
|
+
const fullPath = join17(dir, entry.name);
|
|
24658
25464
|
if (entry.isDirectory()) {
|
|
24659
25465
|
if (ignoreDirs.has(entry.name))
|
|
24660
25466
|
continue;
|
|
@@ -25042,13 +25848,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
25042
25848
|
|
|
25043
25849
|
// src/verification/runners.ts
|
|
25044
25850
|
import { existsSync as existsSync14 } from "fs";
|
|
25045
|
-
import { join as
|
|
25851
|
+
import { join as join18 } from "path";
|
|
25046
25852
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
25047
25853
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
25048
25854
|
return { success: true, missingFiles: [] };
|
|
25049
25855
|
const missingFiles = [];
|
|
25050
25856
|
for (const file2 of expectedFiles) {
|
|
25051
|
-
if (!existsSync14(
|
|
25857
|
+
if (!existsSync14(join18(workingDirectory, file2)))
|
|
25052
25858
|
missingFiles.push(file2);
|
|
25053
25859
|
}
|
|
25054
25860
|
if (missingFiles.length > 0) {
|
|
@@ -25260,7 +26066,7 @@ var init_prompts = __esm(() => {
|
|
|
25260
26066
|
});
|
|
25261
26067
|
|
|
25262
26068
|
// src/tdd/rectification-gate.ts
|
|
25263
|
-
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger) {
|
|
26069
|
+
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName) {
|
|
25264
26070
|
const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
|
|
25265
26071
|
if (!rectificationEnabled)
|
|
25266
26072
|
return false;
|
|
@@ -25276,7 +26082,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
25276
26082
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
25277
26083
|
const testSummary = parseBunTestOutput(fullSuiteResult.output);
|
|
25278
26084
|
if (testSummary.failed > 0) {
|
|
25279
|
-
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout);
|
|
26085
|
+
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName);
|
|
25280
26086
|
}
|
|
25281
26087
|
if (testSummary.passed > 0) {
|
|
25282
26088
|
logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
|
|
@@ -25304,7 +26110,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
25304
26110
|
});
|
|
25305
26111
|
return false;
|
|
25306
26112
|
}
|
|
25307
|
-
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout) {
|
|
26113
|
+
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName) {
|
|
25308
26114
|
const rectificationState = {
|
|
25309
26115
|
attempt: 0,
|
|
25310
26116
|
initialFailures: testSummary.failed,
|
|
@@ -25326,7 +26132,10 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
25326
26132
|
modelTier: implementerTier,
|
|
25327
26133
|
modelDef: resolveModel(config2.models[implementerTier]),
|
|
25328
26134
|
timeoutSeconds: config2.execution.sessionTimeoutSeconds,
|
|
25329
|
-
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions
|
|
26135
|
+
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions,
|
|
26136
|
+
featureName,
|
|
26137
|
+
storyId: story.id,
|
|
26138
|
+
sessionRole: "implementer"
|
|
25330
26139
|
});
|
|
25331
26140
|
if (!rectifyResult.success && rectifyResult.pid) {
|
|
25332
26141
|
await cleanupProcessTree(rectifyResult.pid);
|
|
@@ -25723,13 +26532,13 @@ var exports_loader = {};
|
|
|
25723
26532
|
__export(exports_loader, {
|
|
25724
26533
|
loadOverride: () => loadOverride
|
|
25725
26534
|
});
|
|
25726
|
-
import { join as
|
|
26535
|
+
import { join as join19 } from "path";
|
|
25727
26536
|
async function loadOverride(role, workdir, config2) {
|
|
25728
26537
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
25729
26538
|
if (!overridePath) {
|
|
25730
26539
|
return null;
|
|
25731
26540
|
}
|
|
25732
|
-
const absolutePath =
|
|
26541
|
+
const absolutePath = join19(workdir, overridePath);
|
|
25733
26542
|
const file2 = Bun.file(absolutePath);
|
|
25734
26543
|
if (!await file2.exists()) {
|
|
25735
26544
|
return null;
|
|
@@ -25908,7 +26717,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
25908
26717
|
}
|
|
25909
26718
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
25910
26719
|
}
|
|
25911
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution) {
|
|
26720
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName) {
|
|
25912
26721
|
const startTime = Date.now();
|
|
25913
26722
|
let prompt;
|
|
25914
26723
|
switch (role) {
|
|
@@ -25930,7 +26739,10 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
25930
26739
|
modelTier,
|
|
25931
26740
|
modelDef: resolveModel(config2.models[modelTier]),
|
|
25932
26741
|
timeoutSeconds: config2.execution.sessionTimeoutSeconds,
|
|
25933
|
-
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions
|
|
26742
|
+
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions,
|
|
26743
|
+
featureName,
|
|
26744
|
+
storyId: story.id,
|
|
26745
|
+
sessionRole: role
|
|
25934
26746
|
});
|
|
25935
26747
|
if (!result.success && result.pid) {
|
|
25936
26748
|
await cleanupProcessTree(result.pid);
|
|
@@ -26278,6 +27090,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26278
27090
|
config: config2,
|
|
26279
27091
|
workdir,
|
|
26280
27092
|
modelTier,
|
|
27093
|
+
featureName,
|
|
26281
27094
|
contextMarkdown,
|
|
26282
27095
|
constitution,
|
|
26283
27096
|
dryRun = false,
|
|
@@ -26341,7 +27154,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26341
27154
|
let session1;
|
|
26342
27155
|
if (!isRetry) {
|
|
26343
27156
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
26344
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution);
|
|
27157
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
26345
27158
|
sessions.push(session1);
|
|
26346
27159
|
}
|
|
26347
27160
|
if (session1 && !session1.success) {
|
|
@@ -26403,7 +27216,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26403
27216
|
});
|
|
26404
27217
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
26405
27218
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
26406
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution);
|
|
27219
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
26407
27220
|
sessions.push(session2);
|
|
26408
27221
|
if (!session2.success) {
|
|
26409
27222
|
needsHumanReview = true;
|
|
@@ -26419,10 +27232,10 @@ async function runThreeSessionTdd(options) {
|
|
|
26419
27232
|
lite
|
|
26420
27233
|
};
|
|
26421
27234
|
}
|
|
26422
|
-
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger);
|
|
27235
|
+
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName);
|
|
26423
27236
|
const session3Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
26424
27237
|
const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
|
|
26425
|
-
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution);
|
|
27238
|
+
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName);
|
|
26426
27239
|
sessions.push(session3);
|
|
26427
27240
|
const verdict = await readVerdict(workdir);
|
|
26428
27241
|
await cleanupVerdict(workdir);
|
|
@@ -26581,7 +27394,7 @@ var init_execution = __esm(() => {
|
|
|
26581
27394
|
enabled: () => true,
|
|
26582
27395
|
async execute(ctx) {
|
|
26583
27396
|
const logger = getLogger();
|
|
26584
|
-
const agent = _executionDeps.getAgent(ctx.config.autoMode.defaultAgent);
|
|
27397
|
+
const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.config.autoMode.defaultAgent);
|
|
26585
27398
|
if (!agent) {
|
|
26586
27399
|
return {
|
|
26587
27400
|
action: "fail",
|
|
@@ -26601,6 +27414,7 @@ var init_execution = __esm(() => {
|
|
|
26601
27414
|
config: ctx.config,
|
|
26602
27415
|
workdir: ctx.workdir,
|
|
26603
27416
|
modelTier: ctx.routing.modelTier,
|
|
27417
|
+
featureName: ctx.prd.feature,
|
|
26604
27418
|
contextMarkdown: ctx.contextMarkdown,
|
|
26605
27419
|
constitution: ctx.constitution?.content,
|
|
26606
27420
|
dryRun: false,
|
|
@@ -26670,7 +27484,38 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
26670
27484
|
modelTier: ctx.routing.modelTier,
|
|
26671
27485
|
modelDef: resolveModel(ctx.config.models[ctx.routing.modelTier]),
|
|
26672
27486
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
26673
|
-
dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions
|
|
27487
|
+
dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions,
|
|
27488
|
+
pidRegistry: ctx.pidRegistry,
|
|
27489
|
+
featureName: ctx.prd.feature,
|
|
27490
|
+
storyId: ctx.story.id,
|
|
27491
|
+
interactionBridge: (() => {
|
|
27492
|
+
const plugin = ctx.interaction?.getPrimary();
|
|
27493
|
+
if (!plugin)
|
|
27494
|
+
return;
|
|
27495
|
+
const QUESTION_PATTERNS2 = [/\?/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
|
|
27496
|
+
return {
|
|
27497
|
+
detectQuestion: async (text) => QUESTION_PATTERNS2.some((p) => p.test(text)),
|
|
27498
|
+
onQuestionDetected: async (text) => {
|
|
27499
|
+
const requestId = `ix-acp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
27500
|
+
await plugin.send({
|
|
27501
|
+
id: requestId,
|
|
27502
|
+
type: "input",
|
|
27503
|
+
featureName: ctx.prd.feature,
|
|
27504
|
+
storyId: ctx.story.id,
|
|
27505
|
+
stage: "execution",
|
|
27506
|
+
summary: text,
|
|
27507
|
+
fallback: "continue",
|
|
27508
|
+
createdAt: Date.now()
|
|
27509
|
+
});
|
|
27510
|
+
try {
|
|
27511
|
+
const response = await plugin.receive(requestId, 120000);
|
|
27512
|
+
return response.value ?? "continue";
|
|
27513
|
+
} catch {
|
|
27514
|
+
return "continue";
|
|
27515
|
+
}
|
|
27516
|
+
}
|
|
27517
|
+
};
|
|
27518
|
+
})()
|
|
26674
27519
|
});
|
|
26675
27520
|
ctx.agentResult = result;
|
|
26676
27521
|
await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
|
|
@@ -27886,16 +28731,20 @@ var init_regression2 = __esm(() => {
|
|
|
27886
28731
|
});
|
|
27887
28732
|
|
|
27888
28733
|
// src/pipeline/stages/routing.ts
|
|
27889
|
-
async function runDecompose(story, prd, config2, _workdir) {
|
|
28734
|
+
async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
|
|
27890
28735
|
const naxDecompose = config2.decompose;
|
|
27891
28736
|
const builderConfig = {
|
|
27892
28737
|
maxSubStories: naxDecompose?.maxSubstories ?? 5,
|
|
27893
28738
|
maxComplexity: naxDecompose?.maxSubstoryComplexity ?? "medium",
|
|
27894
28739
|
maxRetries: naxDecompose?.maxRetries ?? 2
|
|
27895
28740
|
};
|
|
28741
|
+
const agent = (agentGetFn ?? getAgent)(config2.autoMode.defaultAgent);
|
|
28742
|
+
if (!agent) {
|
|
28743
|
+
throw new Error(`[decompose] Agent "${config2.autoMode.defaultAgent}" not found \u2014 cannot decompose`);
|
|
28744
|
+
}
|
|
27896
28745
|
const adapter = {
|
|
27897
|
-
async decompose(
|
|
27898
|
-
|
|
28746
|
+
async decompose(prompt) {
|
|
28747
|
+
return agent.complete(prompt, { jsonMode: true });
|
|
27899
28748
|
}
|
|
27900
28749
|
};
|
|
27901
28750
|
return DecomposeBuilder.for(story).prd(prd).config(builderConfig).decompose(adapter);
|
|
@@ -27916,7 +28765,7 @@ var init_routing2 = __esm(() => {
|
|
|
27916
28765
|
async execute(ctx) {
|
|
27917
28766
|
const logger = getLogger();
|
|
27918
28767
|
const agentName = ctx.config.execution?.agent ?? "claude";
|
|
27919
|
-
const adapter = _routingDeps.getAgent(agentName);
|
|
28768
|
+
const adapter = (ctx.agentGetFn ?? _routingDeps.getAgent)(agentName);
|
|
27920
28769
|
const hasExistingRouting = ctx.story.routing !== undefined;
|
|
27921
28770
|
const hasContentHash = ctx.story.routing?.contentHash !== undefined;
|
|
27922
28771
|
let currentHash;
|
|
@@ -27985,7 +28834,7 @@ var init_routing2 = __esm(() => {
|
|
|
27985
28834
|
if (decomposeConfig.trigger === "disabled") {
|
|
27986
28835
|
logger.warn("routing", `Story ${ctx.story.id} is oversized (${acCount} ACs) but decompose is disabled \u2014 continuing with original`);
|
|
27987
28836
|
} else if (decomposeConfig.trigger === "auto") {
|
|
27988
|
-
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
|
|
28837
|
+
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
|
|
27989
28838
|
if (result.validation.valid) {
|
|
27990
28839
|
_routingDeps.applyDecomposition(ctx.prd, result);
|
|
27991
28840
|
if (ctx.prdPath) {
|
|
@@ -28000,7 +28849,7 @@ var init_routing2 = __esm(() => {
|
|
|
28000
28849
|
} else if (decomposeConfig.trigger === "confirm") {
|
|
28001
28850
|
const action = await _routingDeps.checkStoryOversized({ featureName: ctx.prd.feature, storyId: ctx.story.id, criteriaCount: acCount }, ctx.config, ctx.interaction);
|
|
28002
28851
|
if (action === "decompose") {
|
|
28003
|
-
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
|
|
28852
|
+
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
|
|
28004
28853
|
if (result.validation.valid) {
|
|
28005
28854
|
_routingDeps.applyDecomposition(ctx.prd, result);
|
|
28006
28855
|
if (ctx.prdPath) {
|
|
@@ -29488,19 +30337,19 @@ var init_precheck = __esm(() => {
|
|
|
29488
30337
|
});
|
|
29489
30338
|
|
|
29490
30339
|
// src/hooks/runner.ts
|
|
29491
|
-
import { join as
|
|
30340
|
+
import { join as join37 } from "path";
|
|
29492
30341
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
29493
30342
|
let globalHooks = { hooks: {} };
|
|
29494
30343
|
let projectHooks = { hooks: {} };
|
|
29495
30344
|
let skipGlobal = false;
|
|
29496
|
-
const projectPath =
|
|
30345
|
+
const projectPath = join37(projectDir, "hooks.json");
|
|
29497
30346
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
29498
30347
|
if (projectData) {
|
|
29499
30348
|
projectHooks = projectData;
|
|
29500
30349
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
29501
30350
|
}
|
|
29502
30351
|
if (!skipGlobal && globalDir) {
|
|
29503
|
-
const globalPath =
|
|
30352
|
+
const globalPath = join37(globalDir, "hooks.json");
|
|
29504
30353
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
29505
30354
|
if (globalData) {
|
|
29506
30355
|
globalHooks = globalData;
|
|
@@ -29941,7 +30790,8 @@ function buildResult(success2, prd, totalCost, iterations, storiesCompleted, prd
|
|
|
29941
30790
|
}
|
|
29942
30791
|
async function generateAndAddFixStories(ctx, failures, prd) {
|
|
29943
30792
|
const logger = getSafeLogger();
|
|
29944
|
-
const
|
|
30793
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
30794
|
+
const agent = (ctx.agentGetFn ?? getAgent2)(ctx.config.autoMode.defaultAgent);
|
|
29945
30795
|
if (!agent) {
|
|
29946
30796
|
logger?.error("acceptance", "Agent not found, cannot generate fix stories");
|
|
29947
30797
|
return null;
|
|
@@ -30087,7 +30937,6 @@ async function runAcceptanceLoop(ctx) {
|
|
|
30087
30937
|
}
|
|
30088
30938
|
var init_acceptance_loop = __esm(() => {
|
|
30089
30939
|
init_acceptance();
|
|
30090
|
-
init_agents();
|
|
30091
30940
|
init_schema();
|
|
30092
30941
|
init_hooks();
|
|
30093
30942
|
init_logger2();
|
|
@@ -30537,12 +31386,12 @@ __export(exports_manager, {
|
|
|
30537
31386
|
WorktreeManager: () => WorktreeManager
|
|
30538
31387
|
});
|
|
30539
31388
|
import { existsSync as existsSync30, symlinkSync } from "fs";
|
|
30540
|
-
import { join as
|
|
31389
|
+
import { join as join38 } from "path";
|
|
30541
31390
|
|
|
30542
31391
|
class WorktreeManager {
|
|
30543
31392
|
async create(projectRoot, storyId) {
|
|
30544
31393
|
validateStoryId(storyId);
|
|
30545
|
-
const worktreePath =
|
|
31394
|
+
const worktreePath = join38(projectRoot, ".nax-wt", storyId);
|
|
30546
31395
|
const branchName = `nax/${storyId}`;
|
|
30547
31396
|
try {
|
|
30548
31397
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -30567,9 +31416,9 @@ class WorktreeManager {
|
|
|
30567
31416
|
}
|
|
30568
31417
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
30569
31418
|
}
|
|
30570
|
-
const nodeModulesSource =
|
|
31419
|
+
const nodeModulesSource = join38(projectRoot, "node_modules");
|
|
30571
31420
|
if (existsSync30(nodeModulesSource)) {
|
|
30572
|
-
const nodeModulesTarget =
|
|
31421
|
+
const nodeModulesTarget = join38(worktreePath, "node_modules");
|
|
30573
31422
|
try {
|
|
30574
31423
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
30575
31424
|
} catch (error48) {
|
|
@@ -30577,9 +31426,9 @@ class WorktreeManager {
|
|
|
30577
31426
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
30578
31427
|
}
|
|
30579
31428
|
}
|
|
30580
|
-
const envSource =
|
|
31429
|
+
const envSource = join38(projectRoot, ".env");
|
|
30581
31430
|
if (existsSync30(envSource)) {
|
|
30582
|
-
const envTarget =
|
|
31431
|
+
const envTarget = join38(worktreePath, ".env");
|
|
30583
31432
|
try {
|
|
30584
31433
|
symlinkSync(envSource, envTarget, "file");
|
|
30585
31434
|
} catch (error48) {
|
|
@@ -30590,7 +31439,7 @@ class WorktreeManager {
|
|
|
30590
31439
|
}
|
|
30591
31440
|
async remove(projectRoot, storyId) {
|
|
30592
31441
|
validateStoryId(storyId);
|
|
30593
|
-
const worktreePath =
|
|
31442
|
+
const worktreePath = join38(projectRoot, ".nax-wt", storyId);
|
|
30594
31443
|
const branchName = `nax/${storyId}`;
|
|
30595
31444
|
try {
|
|
30596
31445
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -30980,7 +31829,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
30980
31829
|
|
|
30981
31830
|
// src/execution/parallel-coordinator.ts
|
|
30982
31831
|
import os3 from "os";
|
|
30983
|
-
import { join as
|
|
31832
|
+
import { join as join39 } from "path";
|
|
30984
31833
|
function groupStoriesByDependencies(stories) {
|
|
30985
31834
|
const batches = [];
|
|
30986
31835
|
const processed = new Set;
|
|
@@ -31057,7 +31906,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
31057
31906
|
};
|
|
31058
31907
|
const worktreePaths = new Map;
|
|
31059
31908
|
for (const story of batch) {
|
|
31060
|
-
const worktreePath =
|
|
31909
|
+
const worktreePath = join39(projectRoot, ".nax-wt", story.id);
|
|
31061
31910
|
try {
|
|
31062
31911
|
await worktreeManager.create(projectRoot, story.id);
|
|
31063
31912
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -31106,7 +31955,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
31106
31955
|
});
|
|
31107
31956
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
31108
31957
|
storyId: mergeResult.storyId,
|
|
31109
|
-
worktreePath:
|
|
31958
|
+
worktreePath: join39(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
31110
31959
|
});
|
|
31111
31960
|
}
|
|
31112
31961
|
}
|
|
@@ -31565,12 +32414,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
31565
32414
|
// src/pipeline/subscribers/events-writer.ts
|
|
31566
32415
|
import { appendFile as appendFile2, mkdir } from "fs/promises";
|
|
31567
32416
|
import { homedir as homedir5 } from "os";
|
|
31568
|
-
import { basename as basename3, join as
|
|
32417
|
+
import { basename as basename3, join as join40 } from "path";
|
|
31569
32418
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
31570
32419
|
const logger = getSafeLogger();
|
|
31571
32420
|
const project = basename3(workdir);
|
|
31572
|
-
const eventsDir =
|
|
31573
|
-
const eventsFile =
|
|
32421
|
+
const eventsDir = join40(homedir5(), ".nax", "events", project);
|
|
32422
|
+
const eventsFile = join40(eventsDir, "events.jsonl");
|
|
31574
32423
|
let dirReady = false;
|
|
31575
32424
|
const write = (line) => {
|
|
31576
32425
|
(async () => {
|
|
@@ -31730,12 +32579,12 @@ var init_interaction2 = __esm(() => {
|
|
|
31730
32579
|
// src/pipeline/subscribers/registry.ts
|
|
31731
32580
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
31732
32581
|
import { homedir as homedir6 } from "os";
|
|
31733
|
-
import { basename as basename4, join as
|
|
32582
|
+
import { basename as basename4, join as join41 } from "path";
|
|
31734
32583
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
31735
32584
|
const logger = getSafeLogger();
|
|
31736
32585
|
const project = basename4(workdir);
|
|
31737
|
-
const runDir =
|
|
31738
|
-
const metaFile =
|
|
32586
|
+
const runDir = join41(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
32587
|
+
const metaFile = join41(runDir, "meta.json");
|
|
31739
32588
|
const unsub = bus.on("run:started", (_ev) => {
|
|
31740
32589
|
(async () => {
|
|
31741
32590
|
try {
|
|
@@ -31745,8 +32594,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
31745
32594
|
project,
|
|
31746
32595
|
feature,
|
|
31747
32596
|
workdir,
|
|
31748
|
-
statusPath:
|
|
31749
|
-
eventsDir:
|
|
32597
|
+
statusPath: join41(workdir, "nax", "features", feature, "status.json"),
|
|
32598
|
+
eventsDir: join41(workdir, "nax", "features", feature, "runs"),
|
|
31750
32599
|
registeredAt: new Date().toISOString()
|
|
31751
32600
|
};
|
|
31752
32601
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -32414,6 +33263,8 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
32414
33263
|
storyStartTime: new Date().toISOString(),
|
|
32415
33264
|
storyGitRef: storyGitRef ?? undefined,
|
|
32416
33265
|
interaction: ctx.interactionChain ?? undefined,
|
|
33266
|
+
agentGetFn: ctx.agentGetFn,
|
|
33267
|
+
pidRegistry: ctx.pidRegistry,
|
|
32417
33268
|
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
32418
33269
|
};
|
|
32419
33270
|
ctx.statusWriter.setPrd(prd);
|
|
@@ -32740,7 +33591,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
32740
33591
|
var init_status_file = () => {};
|
|
32741
33592
|
|
|
32742
33593
|
// src/execution/status-writer.ts
|
|
32743
|
-
import { join as
|
|
33594
|
+
import { join as join42 } from "path";
|
|
32744
33595
|
|
|
32745
33596
|
class StatusWriter {
|
|
32746
33597
|
statusFile;
|
|
@@ -32808,7 +33659,7 @@ class StatusWriter {
|
|
|
32808
33659
|
if (!this._prd)
|
|
32809
33660
|
return;
|
|
32810
33661
|
const safeLogger = getSafeLogger();
|
|
32811
|
-
const featureStatusPath =
|
|
33662
|
+
const featureStatusPath = join42(featureDir, "status.json");
|
|
32812
33663
|
try {
|
|
32813
33664
|
const base = this.getSnapshot(totalCost, iterations);
|
|
32814
33665
|
if (!base) {
|
|
@@ -33012,6 +33863,7 @@ var init_precheck_runner = __esm(() => {
|
|
|
33012
33863
|
// src/execution/lifecycle/run-initialization.ts
|
|
33013
33864
|
var exports_run_initialization = {};
|
|
33014
33865
|
__export(exports_run_initialization, {
|
|
33866
|
+
logActiveProtocol: () => logActiveProtocol,
|
|
33015
33867
|
initializeRun: () => initializeRun
|
|
33016
33868
|
});
|
|
33017
33869
|
async function reconcileState(prd, prdPath, workdir) {
|
|
@@ -33038,11 +33890,12 @@ async function reconcileState(prd, prdPath, workdir) {
|
|
|
33038
33890
|
}
|
|
33039
33891
|
return prd;
|
|
33040
33892
|
}
|
|
33041
|
-
async function checkAgentInstalled(config2, dryRun) {
|
|
33893
|
+
async function checkAgentInstalled(config2, dryRun, agentGetFn) {
|
|
33042
33894
|
if (dryRun)
|
|
33043
33895
|
return;
|
|
33044
33896
|
const logger = getSafeLogger();
|
|
33045
|
-
const
|
|
33897
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
33898
|
+
const agent = (agentGetFn ?? getAgent2)(config2.autoMode.defaultAgent);
|
|
33046
33899
|
if (!agent) {
|
|
33047
33900
|
logger?.error("execution", "Agent not found", {
|
|
33048
33901
|
agent: config2.autoMode.defaultAgent
|
|
@@ -33070,9 +33923,14 @@ function validateStoryCount(counts, config2) {
|
|
|
33070
33923
|
throw new StoryLimitExceededError(counts.total, config2.execution.maxStoriesPerFeature);
|
|
33071
33924
|
}
|
|
33072
33925
|
}
|
|
33926
|
+
function logActiveProtocol(config2) {
|
|
33927
|
+
const logger = getSafeLogger();
|
|
33928
|
+
const protocol = config2.agent?.protocol ?? "cli";
|
|
33929
|
+
logger?.info("run-initialization", `Agent protocol: ${protocol}`, { protocol });
|
|
33930
|
+
}
|
|
33073
33931
|
async function initializeRun(ctx) {
|
|
33074
33932
|
const logger = getSafeLogger();
|
|
33075
|
-
await checkAgentInstalled(ctx.config, ctx.dryRun);
|
|
33933
|
+
await checkAgentInstalled(ctx.config, ctx.dryRun, ctx.agentGetFn);
|
|
33076
33934
|
let prd = await loadPRD(ctx.prdPath);
|
|
33077
33935
|
prd = await reconcileState(prd, ctx.prdPath, ctx.workdir);
|
|
33078
33936
|
const counts = countStories(prd);
|
|
@@ -33085,7 +33943,6 @@ async function initializeRun(ctx) {
|
|
|
33085
33943
|
return { prd, storyCounts: counts };
|
|
33086
33944
|
}
|
|
33087
33945
|
var init_run_initialization = __esm(() => {
|
|
33088
|
-
init_agents();
|
|
33089
33946
|
init_errors3();
|
|
33090
33947
|
init_logger2();
|
|
33091
33948
|
init_prd();
|
|
@@ -33194,7 +34051,8 @@ async function setupRun(options) {
|
|
|
33194
34051
|
config: config2,
|
|
33195
34052
|
prdPath,
|
|
33196
34053
|
workdir,
|
|
33197
|
-
dryRun
|
|
34054
|
+
dryRun,
|
|
34055
|
+
agentGetFn: options.agentGetFn
|
|
33198
34056
|
});
|
|
33199
34057
|
prd = initResult.prd;
|
|
33200
34058
|
const counts = initResult.storyCounts;
|
|
@@ -64124,7 +64982,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
64124
64982
|
init_source();
|
|
64125
64983
|
import { existsSync as existsSync32, mkdirSync as mkdirSync6 } from "fs";
|
|
64126
64984
|
import { homedir as homedir8 } from "os";
|
|
64127
|
-
import { join as
|
|
64985
|
+
import { join as join43 } from "path";
|
|
64128
64986
|
|
|
64129
64987
|
// node_modules/commander/esm.mjs
|
|
64130
64988
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -64146,14 +65004,14 @@ var {
|
|
|
64146
65004
|
init_acceptance();
|
|
64147
65005
|
init_registry();
|
|
64148
65006
|
import { existsSync as existsSync8 } from "fs";
|
|
64149
|
-
import { join as
|
|
65007
|
+
import { join as join9 } from "path";
|
|
64150
65008
|
|
|
64151
65009
|
// src/analyze/scanner.ts
|
|
64152
65010
|
import { existsSync as existsSync2 } from "fs";
|
|
64153
|
-
import { join as
|
|
65011
|
+
import { join as join4 } from "path";
|
|
64154
65012
|
async function scanCodebase(workdir) {
|
|
64155
|
-
const srcPath =
|
|
64156
|
-
const packageJsonPath =
|
|
65013
|
+
const srcPath = join4(workdir, "src");
|
|
65014
|
+
const packageJsonPath = join4(workdir, "package.json");
|
|
64157
65015
|
const fileTree = existsSync2(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
|
|
64158
65016
|
let dependencies = {};
|
|
64159
65017
|
let devDependencies = {};
|
|
@@ -64193,7 +65051,7 @@ async function generateFileTree(dir, maxDepth, currentDepth = 0, prefix = "") {
|
|
|
64193
65051
|
});
|
|
64194
65052
|
for (let i = 0;i < dirEntries.length; i++) {
|
|
64195
65053
|
const entry = dirEntries[i];
|
|
64196
|
-
const fullPath =
|
|
65054
|
+
const fullPath = join4(dir, entry);
|
|
64197
65055
|
const isLast = i === dirEntries.length - 1;
|
|
64198
65056
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
64199
65057
|
const childPrefix = isLast ? " " : "\u2502 ";
|
|
@@ -64225,16 +65083,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
|
|
|
64225
65083
|
} else {
|
|
64226
65084
|
patterns.push("Test framework: likely bun:test (no framework dependency)");
|
|
64227
65085
|
}
|
|
64228
|
-
if (existsSync2(
|
|
65086
|
+
if (existsSync2(join4(workdir, "test"))) {
|
|
64229
65087
|
patterns.push("Test directory: test/");
|
|
64230
65088
|
}
|
|
64231
|
-
if (existsSync2(
|
|
65089
|
+
if (existsSync2(join4(workdir, "__tests__"))) {
|
|
64232
65090
|
patterns.push("Test directory: __tests__/");
|
|
64233
65091
|
}
|
|
64234
|
-
if (existsSync2(
|
|
65092
|
+
if (existsSync2(join4(workdir, "tests"))) {
|
|
64235
65093
|
patterns.push("Test directory: tests/");
|
|
64236
65094
|
}
|
|
64237
|
-
const hasTestFiles = existsSync2(
|
|
65095
|
+
const hasTestFiles = existsSync2(join4(workdir, "test")) || existsSync2(join4(workdir, "src"));
|
|
64238
65096
|
if (hasTestFiles) {
|
|
64239
65097
|
patterns.push("Test files: *.test.ts, *.spec.ts");
|
|
64240
65098
|
}
|
|
@@ -64252,7 +65110,7 @@ init_version();
|
|
|
64252
65110
|
// src/cli/analyze-parser.ts
|
|
64253
65111
|
init_registry();
|
|
64254
65112
|
import { existsSync as existsSync7 } from "fs";
|
|
64255
|
-
import { join as
|
|
65113
|
+
import { join as join8 } from "path";
|
|
64256
65114
|
init_schema();
|
|
64257
65115
|
init_logger2();
|
|
64258
65116
|
init_prd();
|
|
@@ -64382,7 +65240,7 @@ function estimateLOCFromComplexity(complexity) {
|
|
|
64382
65240
|
}
|
|
64383
65241
|
}
|
|
64384
65242
|
async function reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2) {
|
|
64385
|
-
const prdPath =
|
|
65243
|
+
const prdPath = join8(featureDir, "prd.json");
|
|
64386
65244
|
if (!existsSync7(prdPath)) {
|
|
64387
65245
|
throw new Error(`prd.json not found at ${prdPath}. Run analyze without --reclassify first.`);
|
|
64388
65246
|
}
|
|
@@ -64483,11 +65341,11 @@ function reclassifyWithKeywords(story, config2) {
|
|
|
64483
65341
|
// src/cli/analyze.ts
|
|
64484
65342
|
async function analyzeFeature(options) {
|
|
64485
65343
|
const { featureDir, featureName, branchName, config: config2, specPath: explicitSpecPath, reclassify = false } = options;
|
|
64486
|
-
const workdir =
|
|
65344
|
+
const workdir = join9(featureDir, "../..");
|
|
64487
65345
|
if (reclassify) {
|
|
64488
65346
|
return await reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2);
|
|
64489
65347
|
}
|
|
64490
|
-
const specPath = explicitSpecPath ||
|
|
65348
|
+
const specPath = explicitSpecPath || join9(featureDir, "spec.md");
|
|
64491
65349
|
if (!existsSync8(specPath))
|
|
64492
65350
|
throw new Error(`spec.md not found at ${specPath}`);
|
|
64493
65351
|
const specContent = await Bun.file(specPath).text();
|
|
@@ -64604,7 +65462,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
64604
65462
|
modelDef,
|
|
64605
65463
|
config: config2
|
|
64606
65464
|
});
|
|
64607
|
-
const acceptanceTestPath =
|
|
65465
|
+
const acceptanceTestPath = join9(featureDir, config2.acceptance.testPath);
|
|
64608
65466
|
await Bun.write(acceptanceTestPath, result.testCode);
|
|
64609
65467
|
logger.info("cli", "[OK] Acceptance tests generated", {
|
|
64610
65468
|
criteriaCount: result.criteria.length,
|
|
@@ -64617,9 +65475,25 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
64617
65475
|
// src/cli/plan.ts
|
|
64618
65476
|
init_claude();
|
|
64619
65477
|
import { existsSync as existsSync9 } from "fs";
|
|
64620
|
-
import { join as
|
|
65478
|
+
import { join as join10 } from "path";
|
|
65479
|
+
import { createInterface } from "readline";
|
|
64621
65480
|
init_schema();
|
|
64622
65481
|
init_logger2();
|
|
65482
|
+
var QUESTION_PATTERNS = [/\?[\s]*$/, /\bwhich\b/i, /\bshould i\b/i, /\bdo you want\b/i, /\bwould you like\b/i];
|
|
65483
|
+
async function detectQuestion(text) {
|
|
65484
|
+
return QUESTION_PATTERNS.some((p) => p.test(text.trim()));
|
|
65485
|
+
}
|
|
65486
|
+
async function askHuman(question) {
|
|
65487
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
65488
|
+
return new Promise((resolve6) => {
|
|
65489
|
+
rl.question(`
|
|
65490
|
+
[Agent asks]: ${question}
|
|
65491
|
+
Your answer: `, (answer) => {
|
|
65492
|
+
rl.close();
|
|
65493
|
+
resolve6(answer.trim());
|
|
65494
|
+
});
|
|
65495
|
+
});
|
|
65496
|
+
}
|
|
64623
65497
|
var SPEC_TEMPLATE = `# Feature: [title]
|
|
64624
65498
|
|
|
64625
65499
|
## Problem
|
|
@@ -64640,8 +65514,8 @@ What this does NOT include.
|
|
|
64640
65514
|
`;
|
|
64641
65515
|
async function planCommand(prompt, workdir, config2, options = {}) {
|
|
64642
65516
|
const interactive = options.interactive !== false;
|
|
64643
|
-
const ngentDir =
|
|
64644
|
-
const outputPath =
|
|
65517
|
+
const ngentDir = join10(workdir, "nax");
|
|
65518
|
+
const outputPath = join10(ngentDir, config2.plan.outputPath);
|
|
64645
65519
|
if (!existsSync9(ngentDir)) {
|
|
64646
65520
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
64647
65521
|
}
|
|
@@ -64661,7 +65535,8 @@ async function planCommand(prompt, workdir, config2, options = {}) {
|
|
|
64661
65535
|
inputFile: options.from,
|
|
64662
65536
|
modelTier,
|
|
64663
65537
|
modelDef,
|
|
64664
|
-
config: config2
|
|
65538
|
+
config: config2,
|
|
65539
|
+
interactionBridge: interactive ? { detectQuestion, onQuestionDetected: askHuman } : undefined
|
|
64665
65540
|
};
|
|
64666
65541
|
const adapter = new ClaudeCodeAdapter;
|
|
64667
65542
|
logger.info("cli", interactive ? "Starting interactive planning session..." : `Reading from ${options.from}...`, {
|
|
@@ -64879,13 +65754,13 @@ async function displayModelEfficiency(workdir) {
|
|
|
64879
65754
|
// src/cli/status-features.ts
|
|
64880
65755
|
init_source();
|
|
64881
65756
|
import { existsSync as existsSync11, readdirSync as readdirSync2 } from "fs";
|
|
64882
|
-
import { join as
|
|
65757
|
+
import { join as join13 } from "path";
|
|
64883
65758
|
|
|
64884
65759
|
// src/commands/common.ts
|
|
64885
65760
|
init_path_security2();
|
|
64886
65761
|
init_errors3();
|
|
64887
65762
|
import { existsSync as existsSync10, readdirSync, realpathSync as realpathSync2 } from "fs";
|
|
64888
|
-
import { join as
|
|
65763
|
+
import { join as join11, resolve as resolve6 } from "path";
|
|
64889
65764
|
function resolveProject(options = {}) {
|
|
64890
65765
|
const { dir, feature } = options;
|
|
64891
65766
|
let projectRoot;
|
|
@@ -64893,12 +65768,12 @@ function resolveProject(options = {}) {
|
|
|
64893
65768
|
let configPath;
|
|
64894
65769
|
if (dir) {
|
|
64895
65770
|
projectRoot = realpathSync2(resolve6(dir));
|
|
64896
|
-
naxDir =
|
|
65771
|
+
naxDir = join11(projectRoot, "nax");
|
|
64897
65772
|
if (!existsSync10(naxDir)) {
|
|
64898
65773
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
64899
65774
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
64900
65775
|
}
|
|
64901
|
-
configPath =
|
|
65776
|
+
configPath = join11(naxDir, "config.json");
|
|
64902
65777
|
if (!existsSync10(configPath)) {
|
|
64903
65778
|
throw new NaxError(`nax directory found but config.json is missing: ${naxDir}
|
|
64904
65779
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
@@ -64906,22 +65781,22 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
|
64906
65781
|
} else {
|
|
64907
65782
|
const found = findProjectRoot(process.cwd());
|
|
64908
65783
|
if (!found) {
|
|
64909
|
-
const cwdNaxDir =
|
|
65784
|
+
const cwdNaxDir = join11(process.cwd(), "nax");
|
|
64910
65785
|
if (existsSync10(cwdNaxDir)) {
|
|
64911
|
-
const cwdConfigPath =
|
|
65786
|
+
const cwdConfigPath = join11(cwdNaxDir, "config.json");
|
|
64912
65787
|
throw new NaxError(`nax directory found but config.json is missing: ${cwdNaxDir}
|
|
64913
65788
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
64914
65789
|
}
|
|
64915
65790
|
throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
|
|
64916
65791
|
}
|
|
64917
65792
|
projectRoot = found;
|
|
64918
|
-
naxDir =
|
|
64919
|
-
configPath =
|
|
65793
|
+
naxDir = join11(projectRoot, "nax");
|
|
65794
|
+
configPath = join11(naxDir, "config.json");
|
|
64920
65795
|
}
|
|
64921
65796
|
let featureDir;
|
|
64922
65797
|
if (feature) {
|
|
64923
|
-
const featuresDir =
|
|
64924
|
-
featureDir =
|
|
65798
|
+
const featuresDir = join11(naxDir, "features");
|
|
65799
|
+
featureDir = join11(featuresDir, feature);
|
|
64925
65800
|
if (!existsSync10(featureDir)) {
|
|
64926
65801
|
const availableFeatures = existsSync10(featuresDir) ? readdirSync(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
64927
65802
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
@@ -64948,12 +65823,12 @@ function findProjectRoot(startDir) {
|
|
|
64948
65823
|
let current = resolve6(startDir);
|
|
64949
65824
|
let depth = 0;
|
|
64950
65825
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
64951
|
-
const naxDir =
|
|
64952
|
-
const configPath =
|
|
65826
|
+
const naxDir = join11(current, "nax");
|
|
65827
|
+
const configPath = join11(naxDir, "config.json");
|
|
64953
65828
|
if (existsSync10(configPath)) {
|
|
64954
65829
|
return realpathSync2(current);
|
|
64955
65830
|
}
|
|
64956
|
-
const parent =
|
|
65831
|
+
const parent = join11(current, "..");
|
|
64957
65832
|
if (parent === current) {
|
|
64958
65833
|
break;
|
|
64959
65834
|
}
|
|
@@ -64975,7 +65850,7 @@ function isPidAlive(pid) {
|
|
|
64975
65850
|
}
|
|
64976
65851
|
}
|
|
64977
65852
|
async function loadStatusFile(featureDir) {
|
|
64978
|
-
const statusPath =
|
|
65853
|
+
const statusPath = join13(featureDir, "status.json");
|
|
64979
65854
|
if (!existsSync11(statusPath)) {
|
|
64980
65855
|
return null;
|
|
64981
65856
|
}
|
|
@@ -64987,7 +65862,7 @@ async function loadStatusFile(featureDir) {
|
|
|
64987
65862
|
}
|
|
64988
65863
|
}
|
|
64989
65864
|
async function loadProjectStatusFile(projectDir) {
|
|
64990
|
-
const statusPath =
|
|
65865
|
+
const statusPath = join13(projectDir, "nax", "status.json");
|
|
64991
65866
|
if (!existsSync11(statusPath)) {
|
|
64992
65867
|
return null;
|
|
64993
65868
|
}
|
|
@@ -64999,7 +65874,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
64999
65874
|
}
|
|
65000
65875
|
}
|
|
65001
65876
|
async function getFeatureSummary(featureName, featureDir) {
|
|
65002
|
-
const prdPath =
|
|
65877
|
+
const prdPath = join13(featureDir, "prd.json");
|
|
65003
65878
|
const prd = await loadPRD(prdPath);
|
|
65004
65879
|
const counts = countStories(prd);
|
|
65005
65880
|
const summary = {
|
|
@@ -65033,7 +65908,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
65033
65908
|
};
|
|
65034
65909
|
}
|
|
65035
65910
|
}
|
|
65036
|
-
const runsDir =
|
|
65911
|
+
const runsDir = join13(featureDir, "runs");
|
|
65037
65912
|
if (existsSync11(runsDir)) {
|
|
65038
65913
|
const runs = readdirSync2(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
|
|
65039
65914
|
if (runs.length > 0) {
|
|
@@ -65044,7 +65919,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
65044
65919
|
return summary;
|
|
65045
65920
|
}
|
|
65046
65921
|
async function displayAllFeatures(projectDir) {
|
|
65047
|
-
const featuresDir =
|
|
65922
|
+
const featuresDir = join13(projectDir, "nax", "features");
|
|
65048
65923
|
if (!existsSync11(featuresDir)) {
|
|
65049
65924
|
console.log(source_default.dim("No features found."));
|
|
65050
65925
|
return;
|
|
@@ -65085,7 +65960,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
65085
65960
|
console.log();
|
|
65086
65961
|
}
|
|
65087
65962
|
}
|
|
65088
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
65963
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join13(featuresDir, name))));
|
|
65089
65964
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
65090
65965
|
`));
|
|
65091
65966
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -65111,7 +65986,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
65111
65986
|
console.log();
|
|
65112
65987
|
}
|
|
65113
65988
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
65114
|
-
const prdPath =
|
|
65989
|
+
const prdPath = join13(featureDir, "prd.json");
|
|
65115
65990
|
const prd = await loadPRD(prdPath);
|
|
65116
65991
|
const counts = countStories(prd);
|
|
65117
65992
|
const status = await loadStatusFile(featureDir);
|
|
@@ -65226,7 +66101,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
65226
66101
|
init_errors3();
|
|
65227
66102
|
init_logger2();
|
|
65228
66103
|
import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
|
|
65229
|
-
import { join as
|
|
66104
|
+
import { join as join14 } from "path";
|
|
65230
66105
|
async function parseRunLog(logPath) {
|
|
65231
66106
|
const logger = getLogger();
|
|
65232
66107
|
try {
|
|
@@ -65242,7 +66117,7 @@ async function parseRunLog(logPath) {
|
|
|
65242
66117
|
async function runsListCommand(options) {
|
|
65243
66118
|
const logger = getLogger();
|
|
65244
66119
|
const { feature, workdir } = options;
|
|
65245
|
-
const runsDir =
|
|
66120
|
+
const runsDir = join14(workdir, "nax", "features", feature, "runs");
|
|
65246
66121
|
if (!existsSync12(runsDir)) {
|
|
65247
66122
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
65248
66123
|
return;
|
|
@@ -65254,7 +66129,7 @@ async function runsListCommand(options) {
|
|
|
65254
66129
|
}
|
|
65255
66130
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
65256
66131
|
for (const file2 of files.sort().reverse()) {
|
|
65257
|
-
const logPath =
|
|
66132
|
+
const logPath = join14(runsDir, file2);
|
|
65258
66133
|
const entries = await parseRunLog(logPath);
|
|
65259
66134
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
65260
66135
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -65280,7 +66155,7 @@ async function runsListCommand(options) {
|
|
|
65280
66155
|
async function runsShowCommand(options) {
|
|
65281
66156
|
const logger = getLogger();
|
|
65282
66157
|
const { runId, feature, workdir } = options;
|
|
65283
|
-
const logPath =
|
|
66158
|
+
const logPath = join14(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
65284
66159
|
if (!existsSync12(logPath)) {
|
|
65285
66160
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
65286
66161
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -65319,7 +66194,7 @@ async function runsShowCommand(options) {
|
|
|
65319
66194
|
// src/cli/prompts-main.ts
|
|
65320
66195
|
init_logger2();
|
|
65321
66196
|
import { existsSync as existsSync15, mkdirSync as mkdirSync3 } from "fs";
|
|
65322
|
-
import { join as
|
|
66197
|
+
import { join as join21 } from "path";
|
|
65323
66198
|
|
|
65324
66199
|
// src/pipeline/index.ts
|
|
65325
66200
|
init_runner();
|
|
@@ -65355,7 +66230,7 @@ init_prd();
|
|
|
65355
66230
|
|
|
65356
66231
|
// src/cli/prompts-tdd.ts
|
|
65357
66232
|
init_prompts2();
|
|
65358
|
-
import { join as
|
|
66233
|
+
import { join as join20 } from "path";
|
|
65359
66234
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
65360
66235
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
65361
66236
|
PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
@@ -65374,7 +66249,7 @@ ${frontmatter}---
|
|
|
65374
66249
|
|
|
65375
66250
|
${session.prompt}`;
|
|
65376
66251
|
if (outputDir) {
|
|
65377
|
-
const promptFile =
|
|
66252
|
+
const promptFile = join20(outputDir, `${story.id}.${session.role}.md`);
|
|
65378
66253
|
await Bun.write(promptFile, fullOutput);
|
|
65379
66254
|
logger.info("cli", "Written TDD prompt file", {
|
|
65380
66255
|
storyId: story.id,
|
|
@@ -65390,7 +66265,7 @@ ${"=".repeat(80)}`);
|
|
|
65390
66265
|
}
|
|
65391
66266
|
}
|
|
65392
66267
|
if (outputDir && ctx.contextMarkdown) {
|
|
65393
|
-
const contextFile =
|
|
66268
|
+
const contextFile = join20(outputDir, `${story.id}.context.md`);
|
|
65394
66269
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
65395
66270
|
const contextOutput = `---
|
|
65396
66271
|
${frontmatter}---
|
|
@@ -65404,12 +66279,12 @@ ${ctx.contextMarkdown}`;
|
|
|
65404
66279
|
async function promptsCommand(options) {
|
|
65405
66280
|
const logger = getLogger();
|
|
65406
66281
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
65407
|
-
const naxDir =
|
|
66282
|
+
const naxDir = join21(workdir, "nax");
|
|
65408
66283
|
if (!existsSync15(naxDir)) {
|
|
65409
66284
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
65410
66285
|
}
|
|
65411
|
-
const featureDir =
|
|
65412
|
-
const prdPath =
|
|
66286
|
+
const featureDir = join21(naxDir, "features", feature);
|
|
66287
|
+
const prdPath = join21(featureDir, "prd.json");
|
|
65413
66288
|
if (!existsSync15(prdPath)) {
|
|
65414
66289
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
65415
66290
|
}
|
|
@@ -65469,10 +66344,10 @@ ${frontmatter}---
|
|
|
65469
66344
|
|
|
65470
66345
|
${ctx.prompt}`;
|
|
65471
66346
|
if (outputDir) {
|
|
65472
|
-
const promptFile =
|
|
66347
|
+
const promptFile = join21(outputDir, `${story.id}.prompt.md`);
|
|
65473
66348
|
await Bun.write(promptFile, fullOutput);
|
|
65474
66349
|
if (ctx.contextMarkdown) {
|
|
65475
|
-
const contextFile =
|
|
66350
|
+
const contextFile = join21(outputDir, `${story.id}.context.md`);
|
|
65476
66351
|
const contextOutput = `---
|
|
65477
66352
|
${frontmatter}---
|
|
65478
66353
|
|
|
@@ -65536,7 +66411,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
65536
66411
|
}
|
|
65537
66412
|
// src/cli/prompts-init.ts
|
|
65538
66413
|
import { existsSync as existsSync16, mkdirSync as mkdirSync4 } from "fs";
|
|
65539
|
-
import { join as
|
|
66414
|
+
import { join as join22 } from "path";
|
|
65540
66415
|
var TEMPLATE_ROLES = [
|
|
65541
66416
|
{ file: "test-writer.md", role: "test-writer" },
|
|
65542
66417
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -65560,9 +66435,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
65560
66435
|
`;
|
|
65561
66436
|
async function promptsInitCommand(options) {
|
|
65562
66437
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
65563
|
-
const templatesDir =
|
|
66438
|
+
const templatesDir = join22(workdir, "nax", "templates");
|
|
65564
66439
|
mkdirSync4(templatesDir, { recursive: true });
|
|
65565
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(
|
|
66440
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(join22(templatesDir, f)));
|
|
65566
66441
|
if (existingFiles.length > 0 && !force) {
|
|
65567
66442
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
65568
66443
|
Pass --force to overwrite existing templates.`);
|
|
@@ -65570,7 +66445,7 @@ async function promptsInitCommand(options) {
|
|
|
65570
66445
|
}
|
|
65571
66446
|
const written = [];
|
|
65572
66447
|
for (const template of TEMPLATE_ROLES) {
|
|
65573
|
-
const filePath =
|
|
66448
|
+
const filePath = join22(templatesDir, template.file);
|
|
65574
66449
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
65575
66450
|
const content = TEMPLATE_HEADER + roleBody;
|
|
65576
66451
|
await Bun.write(filePath, content);
|
|
@@ -65586,7 +66461,7 @@ async function promptsInitCommand(options) {
|
|
|
65586
66461
|
return written;
|
|
65587
66462
|
}
|
|
65588
66463
|
async function autoWirePromptsConfig(workdir) {
|
|
65589
|
-
const configPath =
|
|
66464
|
+
const configPath = join22(workdir, "nax.config.json");
|
|
65590
66465
|
if (!existsSync16(configPath)) {
|
|
65591
66466
|
const exampleConfig = JSON.stringify({
|
|
65592
66467
|
prompts: {
|
|
@@ -65753,7 +66628,7 @@ init_config();
|
|
|
65753
66628
|
init_logger2();
|
|
65754
66629
|
init_prd();
|
|
65755
66630
|
import { existsSync as existsSync17, readdirSync as readdirSync4 } from "fs";
|
|
65756
|
-
import { join as
|
|
66631
|
+
import { join as join25 } from "path";
|
|
65757
66632
|
|
|
65758
66633
|
// src/cli/diagnose-analysis.ts
|
|
65759
66634
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -65952,7 +66827,7 @@ function isProcessAlive2(pid) {
|
|
|
65952
66827
|
}
|
|
65953
66828
|
}
|
|
65954
66829
|
async function loadStatusFile2(workdir) {
|
|
65955
|
-
const statusPath =
|
|
66830
|
+
const statusPath = join25(workdir, "nax", "status.json");
|
|
65956
66831
|
if (!existsSync17(statusPath))
|
|
65957
66832
|
return null;
|
|
65958
66833
|
try {
|
|
@@ -65980,7 +66855,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
65980
66855
|
}
|
|
65981
66856
|
}
|
|
65982
66857
|
async function checkLock(workdir) {
|
|
65983
|
-
const lockFile = Bun.file(
|
|
66858
|
+
const lockFile = Bun.file(join25(workdir, "nax.lock"));
|
|
65984
66859
|
if (!await lockFile.exists())
|
|
65985
66860
|
return { lockPresent: false };
|
|
65986
66861
|
try {
|
|
@@ -65998,8 +66873,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
65998
66873
|
const logger = getLogger();
|
|
65999
66874
|
const workdir = options.workdir ?? process.cwd();
|
|
66000
66875
|
const naxSubdir = findProjectDir(workdir);
|
|
66001
|
-
let projectDir = naxSubdir ?
|
|
66002
|
-
if (!projectDir && existsSync17(
|
|
66876
|
+
let projectDir = naxSubdir ? join25(naxSubdir, "..") : null;
|
|
66877
|
+
if (!projectDir && existsSync17(join25(workdir, "nax"))) {
|
|
66003
66878
|
projectDir = workdir;
|
|
66004
66879
|
}
|
|
66005
66880
|
if (!projectDir)
|
|
@@ -66010,7 +66885,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
66010
66885
|
if (status2) {
|
|
66011
66886
|
feature = status2.run.feature;
|
|
66012
66887
|
} else {
|
|
66013
|
-
const featuresDir =
|
|
66888
|
+
const featuresDir = join25(projectDir, "nax", "features");
|
|
66014
66889
|
if (!existsSync17(featuresDir))
|
|
66015
66890
|
throw new Error("No features found in project");
|
|
66016
66891
|
const features = readdirSync4(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -66020,8 +66895,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
66020
66895
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
66021
66896
|
}
|
|
66022
66897
|
}
|
|
66023
|
-
const featureDir =
|
|
66024
|
-
const prdPath =
|
|
66898
|
+
const featureDir = join25(projectDir, "nax", "features", feature);
|
|
66899
|
+
const prdPath = join25(featureDir, "prd.json");
|
|
66025
66900
|
if (!existsSync17(prdPath))
|
|
66026
66901
|
throw new Error(`Feature not found: ${feature}`);
|
|
66027
66902
|
const prd = await loadPRD(prdPath);
|
|
@@ -66064,16 +66939,16 @@ init_interaction();
|
|
|
66064
66939
|
init_source();
|
|
66065
66940
|
init_loader2();
|
|
66066
66941
|
import { existsSync as existsSync20 } from "fs";
|
|
66067
|
-
import { join as
|
|
66942
|
+
import { join as join28 } from "path";
|
|
66068
66943
|
|
|
66069
66944
|
// src/context/generator.ts
|
|
66070
66945
|
init_path_security2();
|
|
66071
66946
|
import { existsSync as existsSync19 } from "fs";
|
|
66072
|
-
import { join as
|
|
66947
|
+
import { join as join27 } from "path";
|
|
66073
66948
|
|
|
66074
66949
|
// src/context/injector.ts
|
|
66075
66950
|
import { existsSync as existsSync18 } from "fs";
|
|
66076
|
-
import { join as
|
|
66951
|
+
import { join as join26 } from "path";
|
|
66077
66952
|
var NOTABLE_NODE_DEPS = [
|
|
66078
66953
|
"@nestjs",
|
|
66079
66954
|
"express",
|
|
@@ -66103,7 +66978,7 @@ var NOTABLE_NODE_DEPS = [
|
|
|
66103
66978
|
"ioredis"
|
|
66104
66979
|
];
|
|
66105
66980
|
async function detectNode(workdir) {
|
|
66106
|
-
const pkgPath =
|
|
66981
|
+
const pkgPath = join26(workdir, "package.json");
|
|
66107
66982
|
if (!existsSync18(pkgPath))
|
|
66108
66983
|
return null;
|
|
66109
66984
|
try {
|
|
@@ -66120,7 +66995,7 @@ async function detectNode(workdir) {
|
|
|
66120
66995
|
}
|
|
66121
66996
|
}
|
|
66122
66997
|
async function detectGo(workdir) {
|
|
66123
|
-
const goMod =
|
|
66998
|
+
const goMod = join26(workdir, "go.mod");
|
|
66124
66999
|
if (!existsSync18(goMod))
|
|
66125
67000
|
return null;
|
|
66126
67001
|
try {
|
|
@@ -66144,7 +67019,7 @@ async function detectGo(workdir) {
|
|
|
66144
67019
|
}
|
|
66145
67020
|
}
|
|
66146
67021
|
async function detectRust(workdir) {
|
|
66147
|
-
const cargoPath =
|
|
67022
|
+
const cargoPath = join26(workdir, "Cargo.toml");
|
|
66148
67023
|
if (!existsSync18(cargoPath))
|
|
66149
67024
|
return null;
|
|
66150
67025
|
try {
|
|
@@ -66160,8 +67035,8 @@ async function detectRust(workdir) {
|
|
|
66160
67035
|
}
|
|
66161
67036
|
}
|
|
66162
67037
|
async function detectPython(workdir) {
|
|
66163
|
-
const pyproject =
|
|
66164
|
-
const requirements =
|
|
67038
|
+
const pyproject = join26(workdir, "pyproject.toml");
|
|
67039
|
+
const requirements = join26(workdir, "requirements.txt");
|
|
66165
67040
|
if (!existsSync18(pyproject) && !existsSync18(requirements))
|
|
66166
67041
|
return null;
|
|
66167
67042
|
try {
|
|
@@ -66180,7 +67055,7 @@ async function detectPython(workdir) {
|
|
|
66180
67055
|
}
|
|
66181
67056
|
}
|
|
66182
67057
|
async function detectPhp(workdir) {
|
|
66183
|
-
const composerPath =
|
|
67058
|
+
const composerPath = join26(workdir, "composer.json");
|
|
66184
67059
|
if (!existsSync18(composerPath))
|
|
66185
67060
|
return null;
|
|
66186
67061
|
try {
|
|
@@ -66193,7 +67068,7 @@ async function detectPhp(workdir) {
|
|
|
66193
67068
|
}
|
|
66194
67069
|
}
|
|
66195
67070
|
async function detectRuby(workdir) {
|
|
66196
|
-
const gemfile =
|
|
67071
|
+
const gemfile = join26(workdir, "Gemfile");
|
|
66197
67072
|
if (!existsSync18(gemfile))
|
|
66198
67073
|
return null;
|
|
66199
67074
|
try {
|
|
@@ -66205,9 +67080,9 @@ async function detectRuby(workdir) {
|
|
|
66205
67080
|
}
|
|
66206
67081
|
}
|
|
66207
67082
|
async function detectJvm(workdir) {
|
|
66208
|
-
const pom =
|
|
66209
|
-
const gradle =
|
|
66210
|
-
const gradleKts =
|
|
67083
|
+
const pom = join26(workdir, "pom.xml");
|
|
67084
|
+
const gradle = join26(workdir, "build.gradle");
|
|
67085
|
+
const gradleKts = join26(workdir, "build.gradle.kts");
|
|
66211
67086
|
if (!existsSync18(pom) && !existsSync18(gradle) && !existsSync18(gradleKts))
|
|
66212
67087
|
return null;
|
|
66213
67088
|
try {
|
|
@@ -66215,7 +67090,7 @@ async function detectJvm(workdir) {
|
|
|
66215
67090
|
const content2 = await Bun.file(pom).text();
|
|
66216
67091
|
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
66217
67092
|
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
66218
|
-
const lang2 = existsSync18(
|
|
67093
|
+
const lang2 = existsSync18(join26(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
66219
67094
|
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
66220
67095
|
}
|
|
66221
67096
|
const gradleFile = existsSync18(gradleKts) ? gradleKts : gradle;
|
|
@@ -66436,7 +67311,7 @@ async function generateFor(agent, options, config2) {
|
|
|
66436
67311
|
try {
|
|
66437
67312
|
const context = await loadContextContent(options, config2);
|
|
66438
67313
|
const content = generator.generate(context);
|
|
66439
|
-
const outputPath =
|
|
67314
|
+
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
66440
67315
|
validateFilePath(outputPath, options.outputDir);
|
|
66441
67316
|
if (!options.dryRun) {
|
|
66442
67317
|
await Bun.write(outputPath, content);
|
|
@@ -66453,7 +67328,7 @@ async function generateAll(options, config2) {
|
|
|
66453
67328
|
for (const [agentKey, generator] of Object.entries(GENERATORS)) {
|
|
66454
67329
|
try {
|
|
66455
67330
|
const content = generator.generate(context);
|
|
66456
|
-
const outputPath =
|
|
67331
|
+
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
66457
67332
|
validateFilePath(outputPath, options.outputDir);
|
|
66458
67333
|
if (!options.dryRun) {
|
|
66459
67334
|
await Bun.write(outputPath, content);
|
|
@@ -66471,8 +67346,8 @@ async function generateAll(options, config2) {
|
|
|
66471
67346
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
66472
67347
|
async function generateCommand(options) {
|
|
66473
67348
|
const workdir = process.cwd();
|
|
66474
|
-
const contextPath = options.context ?
|
|
66475
|
-
const outputDir = options.output ?
|
|
67349
|
+
const contextPath = options.context ? join28(workdir, options.context) : join28(workdir, "nax/context.md");
|
|
67350
|
+
const outputDir = options.output ? join28(workdir, options.output) : workdir;
|
|
66476
67351
|
const autoInject = !options.noAutoInject;
|
|
66477
67352
|
const dryRun = options.dryRun ?? false;
|
|
66478
67353
|
if (!existsSync20(contextPath)) {
|
|
@@ -66548,7 +67423,7 @@ async function generateCommand(options) {
|
|
|
66548
67423
|
// src/cli/config-display.ts
|
|
66549
67424
|
init_loader2();
|
|
66550
67425
|
import { existsSync as existsSync22 } from "fs";
|
|
66551
|
-
import { join as
|
|
67426
|
+
import { join as join30 } from "path";
|
|
66552
67427
|
|
|
66553
67428
|
// src/cli/config-descriptions.ts
|
|
66554
67429
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -66754,7 +67629,7 @@ function deepEqual(a, b) {
|
|
|
66754
67629
|
init_defaults();
|
|
66755
67630
|
init_loader2();
|
|
66756
67631
|
import { existsSync as existsSync21 } from "fs";
|
|
66757
|
-
import { join as
|
|
67632
|
+
import { join as join29 } from "path";
|
|
66758
67633
|
async function loadConfigFile(path14) {
|
|
66759
67634
|
if (!existsSync21(path14))
|
|
66760
67635
|
return null;
|
|
@@ -66776,7 +67651,7 @@ async function loadProjectConfig() {
|
|
|
66776
67651
|
const projectDir = findProjectDir();
|
|
66777
67652
|
if (!projectDir)
|
|
66778
67653
|
return null;
|
|
66779
|
-
const projectPath =
|
|
67654
|
+
const projectPath = join29(projectDir, "config.json");
|
|
66780
67655
|
return await loadConfigFile(projectPath);
|
|
66781
67656
|
}
|
|
66782
67657
|
|
|
@@ -66836,7 +67711,7 @@ async function configCommand(config2, options = {}) {
|
|
|
66836
67711
|
function determineConfigSources() {
|
|
66837
67712
|
const globalPath = globalConfigPath();
|
|
66838
67713
|
const projectDir = findProjectDir();
|
|
66839
|
-
const projectPath = projectDir ?
|
|
67714
|
+
const projectPath = projectDir ? join30(projectDir, "config.json") : null;
|
|
66840
67715
|
return {
|
|
66841
67716
|
global: fileExists(globalPath) ? globalPath : null,
|
|
66842
67717
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -67016,21 +67891,21 @@ async function diagnose(options) {
|
|
|
67016
67891
|
|
|
67017
67892
|
// src/commands/logs.ts
|
|
67018
67893
|
import { existsSync as existsSync24 } from "fs";
|
|
67019
|
-
import { join as
|
|
67894
|
+
import { join as join33 } from "path";
|
|
67020
67895
|
|
|
67021
67896
|
// src/commands/logs-formatter.ts
|
|
67022
67897
|
init_source();
|
|
67023
67898
|
init_formatter();
|
|
67024
67899
|
import { readdirSync as readdirSync6 } from "fs";
|
|
67025
|
-
import { join as
|
|
67900
|
+
import { join as join32 } from "path";
|
|
67026
67901
|
|
|
67027
67902
|
// src/commands/logs-reader.ts
|
|
67028
67903
|
import { existsSync as existsSync23, readdirSync as readdirSync5 } from "fs";
|
|
67029
67904
|
import { readdir as readdir3 } from "fs/promises";
|
|
67030
67905
|
import { homedir as homedir3 } from "os";
|
|
67031
|
-
import { join as
|
|
67906
|
+
import { join as join31 } from "path";
|
|
67032
67907
|
var _deps5 = {
|
|
67033
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
67908
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir3(), ".nax", "runs")
|
|
67034
67909
|
};
|
|
67035
67910
|
async function resolveRunFileFromRegistry(runId) {
|
|
67036
67911
|
const runsDir = _deps5.getRunsDir();
|
|
@@ -67042,7 +67917,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
67042
67917
|
}
|
|
67043
67918
|
let matched = null;
|
|
67044
67919
|
for (const entry of entries) {
|
|
67045
|
-
const metaPath =
|
|
67920
|
+
const metaPath = join31(runsDir, entry, "meta.json");
|
|
67046
67921
|
try {
|
|
67047
67922
|
const meta3 = await Bun.file(metaPath).json();
|
|
67048
67923
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -67064,14 +67939,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
67064
67939
|
return null;
|
|
67065
67940
|
}
|
|
67066
67941
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
67067
|
-
return
|
|
67942
|
+
return join31(matched.eventsDir, specificFile ?? files[0]);
|
|
67068
67943
|
}
|
|
67069
67944
|
async function selectRunFile(runsDir) {
|
|
67070
67945
|
const files = readdirSync5(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
67071
67946
|
if (files.length === 0) {
|
|
67072
67947
|
return null;
|
|
67073
67948
|
}
|
|
67074
|
-
return
|
|
67949
|
+
return join31(runsDir, files[0]);
|
|
67075
67950
|
}
|
|
67076
67951
|
async function extractRunSummary(filePath) {
|
|
67077
67952
|
const file2 = Bun.file(filePath);
|
|
@@ -67156,7 +68031,7 @@ Runs:
|
|
|
67156
68031
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
67157
68032
|
console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
67158
68033
|
for (const file2 of files) {
|
|
67159
|
-
const filePath =
|
|
68034
|
+
const filePath = join32(runsDir, file2);
|
|
67160
68035
|
const summary = await extractRunSummary(filePath);
|
|
67161
68036
|
const timestamp = file2.replace(".jsonl", "");
|
|
67162
68037
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -67281,7 +68156,7 @@ async function logsCommand(options) {
|
|
|
67281
68156
|
return;
|
|
67282
68157
|
}
|
|
67283
68158
|
const resolved = resolveProject({ dir: options.dir });
|
|
67284
|
-
const naxDir =
|
|
68159
|
+
const naxDir = join33(resolved.projectDir, "nax");
|
|
67285
68160
|
const configPath = resolved.configPath;
|
|
67286
68161
|
const configFile = Bun.file(configPath);
|
|
67287
68162
|
const config2 = await configFile.json();
|
|
@@ -67289,8 +68164,8 @@ async function logsCommand(options) {
|
|
|
67289
68164
|
if (!featureName) {
|
|
67290
68165
|
throw new Error("No feature specified in config.json");
|
|
67291
68166
|
}
|
|
67292
|
-
const featureDir =
|
|
67293
|
-
const runsDir =
|
|
68167
|
+
const featureDir = join33(naxDir, "features", featureName);
|
|
68168
|
+
const runsDir = join33(featureDir, "runs");
|
|
67294
68169
|
if (!existsSync24(runsDir)) {
|
|
67295
68170
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
67296
68171
|
}
|
|
@@ -67315,7 +68190,7 @@ init_config();
|
|
|
67315
68190
|
init_prd();
|
|
67316
68191
|
init_precheck();
|
|
67317
68192
|
import { existsSync as existsSync29 } from "fs";
|
|
67318
|
-
import { join as
|
|
68193
|
+
import { join as join34 } from "path";
|
|
67319
68194
|
async function precheckCommand(options) {
|
|
67320
68195
|
const resolved = resolveProject({
|
|
67321
68196
|
dir: options.dir,
|
|
@@ -67331,9 +68206,9 @@ async function precheckCommand(options) {
|
|
|
67331
68206
|
process.exit(1);
|
|
67332
68207
|
}
|
|
67333
68208
|
}
|
|
67334
|
-
const naxDir =
|
|
67335
|
-
const featureDir =
|
|
67336
|
-
const prdPath =
|
|
68209
|
+
const naxDir = join34(resolved.projectDir, "nax");
|
|
68210
|
+
const featureDir = join34(naxDir, "features", featureName);
|
|
68211
|
+
const prdPath = join34(featureDir, "prd.json");
|
|
67337
68212
|
if (!existsSync29(featureDir)) {
|
|
67338
68213
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
67339
68214
|
process.exit(1);
|
|
@@ -67357,10 +68232,10 @@ async function precheckCommand(options) {
|
|
|
67357
68232
|
init_source();
|
|
67358
68233
|
import { readdir as readdir4 } from "fs/promises";
|
|
67359
68234
|
import { homedir as homedir4 } from "os";
|
|
67360
|
-
import { join as
|
|
68235
|
+
import { join as join35 } from "path";
|
|
67361
68236
|
var DEFAULT_LIMIT = 20;
|
|
67362
68237
|
var _deps7 = {
|
|
67363
|
-
getRunsDir: () =>
|
|
68238
|
+
getRunsDir: () => join35(homedir4(), ".nax", "runs")
|
|
67364
68239
|
};
|
|
67365
68240
|
function formatDuration3(ms) {
|
|
67366
68241
|
if (ms <= 0)
|
|
@@ -67412,7 +68287,7 @@ async function runsCommand(options = {}) {
|
|
|
67412
68287
|
}
|
|
67413
68288
|
const rows = [];
|
|
67414
68289
|
for (const entry of entries) {
|
|
67415
|
-
const metaPath =
|
|
68290
|
+
const metaPath = join35(runsDir, entry, "meta.json");
|
|
67416
68291
|
let meta3;
|
|
67417
68292
|
try {
|
|
67418
68293
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -67489,7 +68364,7 @@ async function runsCommand(options = {}) {
|
|
|
67489
68364
|
|
|
67490
68365
|
// src/commands/unlock.ts
|
|
67491
68366
|
init_source();
|
|
67492
|
-
import { join as
|
|
68367
|
+
import { join as join36 } from "path";
|
|
67493
68368
|
function isProcessAlive3(pid) {
|
|
67494
68369
|
try {
|
|
67495
68370
|
process.kill(pid, 0);
|
|
@@ -67504,7 +68379,7 @@ function formatLockAge(ageMs) {
|
|
|
67504
68379
|
}
|
|
67505
68380
|
async function unlockCommand(options) {
|
|
67506
68381
|
const workdir = options.dir ?? process.cwd();
|
|
67507
|
-
const lockPath =
|
|
68382
|
+
const lockPath = join36(workdir, "nax.lock");
|
|
67508
68383
|
const lockFile = Bun.file(lockPath);
|
|
67509
68384
|
const exists = await lockFile.exists();
|
|
67510
68385
|
if (!exists) {
|
|
@@ -67543,6 +68418,7 @@ async function unlockCommand(options) {
|
|
|
67543
68418
|
init_config();
|
|
67544
68419
|
|
|
67545
68420
|
// src/execution/runner.ts
|
|
68421
|
+
init_registry();
|
|
67546
68422
|
init_hooks();
|
|
67547
68423
|
init_logger2();
|
|
67548
68424
|
init_prd();
|
|
@@ -67552,6 +68428,7 @@ init_crash_recovery();
|
|
|
67552
68428
|
init_hooks();
|
|
67553
68429
|
init_logger2();
|
|
67554
68430
|
init_prd();
|
|
68431
|
+
init_git();
|
|
67555
68432
|
init_crash_recovery();
|
|
67556
68433
|
init_story_context();
|
|
67557
68434
|
async function runCompletionPhase(options) {
|
|
@@ -67561,7 +68438,7 @@ async function runCompletionPhase(options) {
|
|
|
67561
68438
|
const acceptanceResult = await runAcceptanceLoop2({
|
|
67562
68439
|
config: options.config,
|
|
67563
68440
|
prd: options.prd,
|
|
67564
|
-
prdPath:
|
|
68441
|
+
prdPath: options.prdPath,
|
|
67565
68442
|
workdir: options.workdir,
|
|
67566
68443
|
featureDir: options.featureDir,
|
|
67567
68444
|
hooks: options.hooks,
|
|
@@ -67572,7 +68449,8 @@ async function runCompletionPhase(options) {
|
|
|
67572
68449
|
allStoryMetrics: options.allStoryMetrics,
|
|
67573
68450
|
pluginRegistry: options.pluginRegistry,
|
|
67574
68451
|
eventEmitter: options.eventEmitter,
|
|
67575
|
-
statusWriter: options.statusWriter
|
|
68452
|
+
statusWriter: options.statusWriter,
|
|
68453
|
+
agentGetFn: options.agentGetFn
|
|
67576
68454
|
});
|
|
67577
68455
|
Object.assign(options, {
|
|
67578
68456
|
prd: acceptanceResult.prd,
|
|
@@ -67623,6 +68501,7 @@ async function runCompletionPhase(options) {
|
|
|
67623
68501
|
}
|
|
67624
68502
|
stopHeartbeat();
|
|
67625
68503
|
await writeExitSummary(options.logFilePath, options.totalCost, options.iterations, options.storiesCompleted, durationMs);
|
|
68504
|
+
await autoCommitIfDirty(options.workdir, "run.complete", "run-summary", options.feature);
|
|
67626
68505
|
return {
|
|
67627
68506
|
durationMs,
|
|
67628
68507
|
runCompletedAt
|
|
@@ -67773,7 +68652,9 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
67773
68652
|
logFilePath: options.logFilePath,
|
|
67774
68653
|
runId: options.runId,
|
|
67775
68654
|
startTime: options.startTime,
|
|
67776
|
-
batchPlan
|
|
68655
|
+
batchPlan,
|
|
68656
|
+
agentGetFn: options.agentGetFn,
|
|
68657
|
+
pidRegistry: options.pidRegistry
|
|
67777
68658
|
}, prd);
|
|
67778
68659
|
prd = sequentialResult.prd;
|
|
67779
68660
|
iterations = sequentialResult.iterations;
|
|
@@ -67811,7 +68692,8 @@ async function runSetupPhase(options) {
|
|
|
67811
68692
|
getTotalCost: options.getTotalCost,
|
|
67812
68693
|
getIterations: options.getIterations,
|
|
67813
68694
|
getStoriesCompleted: options.getStoriesCompleted,
|
|
67814
|
-
getTotalStories: options.getTotalStories
|
|
68695
|
+
getTotalStories: options.getTotalStories,
|
|
68696
|
+
agentGetFn: options.agentGetFn
|
|
67815
68697
|
});
|
|
67816
68698
|
return setupResult;
|
|
67817
68699
|
}
|
|
@@ -67849,6 +68731,8 @@ async function run(options) {
|
|
|
67849
68731
|
let totalCost = 0;
|
|
67850
68732
|
const allStoryMetrics = [];
|
|
67851
68733
|
const logger = getSafeLogger();
|
|
68734
|
+
const registry2 = createAgentRegistry(config2);
|
|
68735
|
+
const agentGetFn = registry2.getAgent.bind(registry2);
|
|
67852
68736
|
let prd;
|
|
67853
68737
|
const setupResult = await runSetupPhase({
|
|
67854
68738
|
prdPath,
|
|
@@ -67866,6 +68750,7 @@ async function run(options) {
|
|
|
67866
68750
|
skipPrecheck,
|
|
67867
68751
|
headless,
|
|
67868
68752
|
formatterMode,
|
|
68753
|
+
agentGetFn,
|
|
67869
68754
|
getTotalCost: () => totalCost,
|
|
67870
68755
|
getIterations: () => iterations,
|
|
67871
68756
|
getStoriesCompleted: () => storiesCompleted,
|
|
@@ -67893,7 +68778,9 @@ async function run(options) {
|
|
|
67893
68778
|
formatterMode,
|
|
67894
68779
|
headless,
|
|
67895
68780
|
parallel,
|
|
67896
|
-
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined
|
|
68781
|
+
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined,
|
|
68782
|
+
agentGetFn,
|
|
68783
|
+
pidRegistry
|
|
67897
68784
|
}, prd, pluginRegistry);
|
|
67898
68785
|
prd = executionResult.prd;
|
|
67899
68786
|
iterations = executionResult.iterations;
|
|
@@ -67914,6 +68801,7 @@ async function run(options) {
|
|
|
67914
68801
|
hooks,
|
|
67915
68802
|
feature,
|
|
67916
68803
|
workdir,
|
|
68804
|
+
prdPath,
|
|
67917
68805
|
statusFile,
|
|
67918
68806
|
logFilePath,
|
|
67919
68807
|
runId,
|
|
@@ -67929,7 +68817,8 @@ async function run(options) {
|
|
|
67929
68817
|
iterations,
|
|
67930
68818
|
statusWriter,
|
|
67931
68819
|
pluginRegistry,
|
|
67932
|
-
eventEmitter
|
|
68820
|
+
eventEmitter,
|
|
68821
|
+
agentGetFn
|
|
67933
68822
|
});
|
|
67934
68823
|
const { durationMs } = completionResult;
|
|
67935
68824
|
return {
|
|
@@ -75272,15 +76161,15 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
75272
76161
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
75273
76162
|
process.exit(1);
|
|
75274
76163
|
}
|
|
75275
|
-
const naxDir =
|
|
76164
|
+
const naxDir = join43(workdir, "nax");
|
|
75276
76165
|
if (existsSync32(naxDir) && !options.force) {
|
|
75277
76166
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
75278
76167
|
return;
|
|
75279
76168
|
}
|
|
75280
|
-
mkdirSync6(
|
|
75281
|
-
mkdirSync6(
|
|
75282
|
-
await Bun.write(
|
|
75283
|
-
await Bun.write(
|
|
76169
|
+
mkdirSync6(join43(naxDir, "features"), { recursive: true });
|
|
76170
|
+
mkdirSync6(join43(naxDir, "hooks"), { recursive: true });
|
|
76171
|
+
await Bun.write(join43(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
76172
|
+
await Bun.write(join43(naxDir, "hooks.json"), JSON.stringify({
|
|
75284
76173
|
hooks: {
|
|
75285
76174
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
75286
76175
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -75288,12 +76177,12 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
75288
76177
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
75289
76178
|
}
|
|
75290
76179
|
}, null, 2));
|
|
75291
|
-
await Bun.write(
|
|
76180
|
+
await Bun.write(join43(naxDir, ".gitignore"), `# nax temp files
|
|
75292
76181
|
*.tmp
|
|
75293
76182
|
.paused.json
|
|
75294
76183
|
.nax-verifier-verdict.json
|
|
75295
76184
|
`);
|
|
75296
|
-
await Bun.write(
|
|
76185
|
+
await Bun.write(join43(naxDir, "context.md"), `# Project Context
|
|
75297
76186
|
|
|
75298
76187
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
75299
76188
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -75411,16 +76300,16 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75411
76300
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75412
76301
|
process.exit(1);
|
|
75413
76302
|
}
|
|
75414
|
-
const featureDir =
|
|
75415
|
-
const prdPath =
|
|
76303
|
+
const featureDir = join43(naxDir, "features", options.feature);
|
|
76304
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
75416
76305
|
if (!existsSync32(prdPath)) {
|
|
75417
76306
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
75418
76307
|
process.exit(1);
|
|
75419
76308
|
}
|
|
75420
|
-
const runsDir =
|
|
76309
|
+
const runsDir = join43(featureDir, "runs");
|
|
75421
76310
|
mkdirSync6(runsDir, { recursive: true });
|
|
75422
76311
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
75423
|
-
const logFilePath =
|
|
76312
|
+
const logFilePath = join43(runsDir, `${runId}.jsonl`);
|
|
75424
76313
|
const isTTY = process.stdout.isTTY ?? false;
|
|
75425
76314
|
const headlessFlag = options.headless ?? false;
|
|
75426
76315
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -75436,7 +76325,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75436
76325
|
config2.autoMode.defaultAgent = options.agent;
|
|
75437
76326
|
}
|
|
75438
76327
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
75439
|
-
const globalNaxDir =
|
|
76328
|
+
const globalNaxDir = join43(homedir8(), ".nax");
|
|
75440
76329
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
75441
76330
|
const eventEmitter = new PipelineEventEmitter;
|
|
75442
76331
|
let tuiInstance;
|
|
@@ -75459,7 +76348,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75459
76348
|
} else {
|
|
75460
76349
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
75461
76350
|
}
|
|
75462
|
-
const statusFilePath =
|
|
76351
|
+
const statusFilePath = join43(workdir, "nax", "status.json");
|
|
75463
76352
|
let parallel;
|
|
75464
76353
|
if (options.parallel !== undefined) {
|
|
75465
76354
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -75485,7 +76374,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75485
76374
|
headless: useHeadless,
|
|
75486
76375
|
skipPrecheck: options.skipPrecheck ?? false
|
|
75487
76376
|
});
|
|
75488
|
-
const latestSymlink =
|
|
76377
|
+
const latestSymlink = join43(runsDir, "latest.jsonl");
|
|
75489
76378
|
try {
|
|
75490
76379
|
if (existsSync32(latestSymlink)) {
|
|
75491
76380
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -75523,9 +76412,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75523
76412
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75524
76413
|
process.exit(1);
|
|
75525
76414
|
}
|
|
75526
|
-
const featureDir =
|
|
76415
|
+
const featureDir = join43(naxDir, "features", name);
|
|
75527
76416
|
mkdirSync6(featureDir, { recursive: true });
|
|
75528
|
-
await Bun.write(
|
|
76417
|
+
await Bun.write(join43(featureDir, "spec.md"), `# Feature: ${name}
|
|
75529
76418
|
|
|
75530
76419
|
## Overview
|
|
75531
76420
|
|
|
@@ -75533,7 +76422,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75533
76422
|
|
|
75534
76423
|
## Acceptance Criteria
|
|
75535
76424
|
`);
|
|
75536
|
-
await Bun.write(
|
|
76425
|
+
await Bun.write(join43(featureDir, "plan.md"), `# Plan: ${name}
|
|
75537
76426
|
|
|
75538
76427
|
## Architecture
|
|
75539
76428
|
|
|
@@ -75541,7 +76430,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75541
76430
|
|
|
75542
76431
|
## Dependencies
|
|
75543
76432
|
`);
|
|
75544
|
-
await Bun.write(
|
|
76433
|
+
await Bun.write(join43(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
75545
76434
|
|
|
75546
76435
|
## US-001: [Title]
|
|
75547
76436
|
|
|
@@ -75550,7 +76439,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75550
76439
|
### Acceptance Criteria
|
|
75551
76440
|
- [ ] Criterion 1
|
|
75552
76441
|
`);
|
|
75553
|
-
await Bun.write(
|
|
76442
|
+
await Bun.write(join43(featureDir, "progress.txt"), `# Progress: ${name}
|
|
75554
76443
|
|
|
75555
76444
|
Created: ${new Date().toISOString()}
|
|
75556
76445
|
|
|
@@ -75578,7 +76467,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
75578
76467
|
console.error(source_default.red("nax not initialized."));
|
|
75579
76468
|
process.exit(1);
|
|
75580
76469
|
}
|
|
75581
|
-
const featuresDir =
|
|
76470
|
+
const featuresDir = join43(naxDir, "features");
|
|
75582
76471
|
if (!existsSync32(featuresDir)) {
|
|
75583
76472
|
console.log(source_default.dim("No features yet."));
|
|
75584
76473
|
return;
|
|
@@ -75593,7 +76482,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
75593
76482
|
Features:
|
|
75594
76483
|
`));
|
|
75595
76484
|
for (const name of entries) {
|
|
75596
|
-
const prdPath =
|
|
76485
|
+
const prdPath = join43(featuresDir, name, "prd.json");
|
|
75597
76486
|
if (existsSync32(prdPath)) {
|
|
75598
76487
|
const prd = await loadPRD(prdPath);
|
|
75599
76488
|
const c = countStories(prd);
|
|
@@ -75646,7 +76535,7 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
|
|
|
75646
76535
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75647
76536
|
process.exit(1);
|
|
75648
76537
|
}
|
|
75649
|
-
const featureDir =
|
|
76538
|
+
const featureDir = join43(naxDir, "features", options.feature);
|
|
75650
76539
|
if (!existsSync32(featureDir)) {
|
|
75651
76540
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
75652
76541
|
process.exit(1);
|
|
@@ -75662,7 +76551,7 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
|
|
|
75662
76551
|
specPath: options.from,
|
|
75663
76552
|
reclassify: options.reclassify
|
|
75664
76553
|
});
|
|
75665
|
-
const prdPath =
|
|
76554
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
75666
76555
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
75667
76556
|
const c = countStories(prd);
|
|
75668
76557
|
console.log(source_default.green(`
|