@nathapp/nax 0.40.1 → 0.42.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/README.md +1 -0
- package/bin/nax.ts +130 -11
- package/dist/nax.js +1520 -424
- package/package.json +8 -7
- package/src/acceptance/fix-generator.ts +4 -35
- package/src/acceptance/generator.ts +4 -27
- package/src/agents/acp/adapter.ts +642 -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/claude.ts +12 -2
- package/src/agents/registry.ts +83 -0
- package/src/agents/types-extended.ts +23 -0
- package/src/agents/types.ts +17 -0
- package/src/analyze/scanner.ts +16 -20
- package/src/cli/analyze.ts +6 -2
- package/src/cli/plan.ts +218 -129
- package/src/commands/precheck.ts +1 -1
- package/src/config/defaults.ts +1 -0
- package/src/config/runtime-types.ts +10 -0
- package/src/config/schema.ts +1 -0
- package/src/config/schemas.ts +6 -0
- package/src/config/types.ts +1 -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/interaction/plugins/webhook.ts +10 -1
- 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/prd/schema.ts +249 -0
- package/src/tdd/orchestrator.ts +7 -0
- package/src/tdd/rectification-gate.ts +6 -0
- package/src/tdd/session-runner.ts +15 -2
- package/src/utils/git.ts +30 -0
- package/src/verification/runners.ts +10 -1
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({
|
|
@@ -17794,6 +17802,10 @@ var init_schemas3 = __esm(() => {
|
|
|
17794
17802
|
maxDescriptionLength: exports_external.number().int().min(100).max(1e4).default(2000),
|
|
17795
17803
|
maxBulletPoints: exports_external.number().int().min(1).max(100).default(8)
|
|
17796
17804
|
});
|
|
17805
|
+
AgentConfigSchema = exports_external.object({
|
|
17806
|
+
protocol: exports_external.enum(["acp", "cli"]).default("acp"),
|
|
17807
|
+
acpPermissionMode: exports_external.string().optional()
|
|
17808
|
+
});
|
|
17797
17809
|
PrecheckConfigSchema = exports_external.object({
|
|
17798
17810
|
storySizeGate: StorySizeGateConfigSchema
|
|
17799
17811
|
});
|
|
@@ -17829,6 +17841,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17829
17841
|
disabledPlugins: exports_external.array(exports_external.string()).optional(),
|
|
17830
17842
|
hooks: HooksConfigSchema.optional(),
|
|
17831
17843
|
interaction: InteractionConfigSchema.optional(),
|
|
17844
|
+
agent: AgentConfigSchema.optional(),
|
|
17832
17845
|
precheck: PrecheckConfigSchema.optional(),
|
|
17833
17846
|
prompts: PromptsConfigSchema.optional(),
|
|
17834
17847
|
decompose: DecomposeConfigSchema.optional()
|
|
@@ -18240,7 +18253,7 @@ class ClaudeCodeAdapter {
|
|
|
18240
18253
|
const backoffMs = 2 ** attempt * 1000;
|
|
18241
18254
|
const logger = getLogger();
|
|
18242
18255
|
logger.warn("agent", "Rate limited, retrying", { backoffSeconds: backoffMs / 1000, attempt, maxRetries });
|
|
18243
|
-
await
|
|
18256
|
+
await _claudeAdapterDeps.sleep(backoffMs);
|
|
18244
18257
|
continue;
|
|
18245
18258
|
}
|
|
18246
18259
|
return result;
|
|
@@ -18256,7 +18269,7 @@ class ClaudeCodeAdapter {
|
|
|
18256
18269
|
attempt,
|
|
18257
18270
|
maxRetries
|
|
18258
18271
|
});
|
|
18259
|
-
await
|
|
18272
|
+
await _claudeAdapterDeps.sleep(backoffMs);
|
|
18260
18273
|
continue;
|
|
18261
18274
|
}
|
|
18262
18275
|
throw lastError;
|
|
@@ -18336,7 +18349,7 @@ class ClaudeCodeAdapter {
|
|
|
18336
18349
|
return runInteractiveMode(this.binary, options, pidRegistry);
|
|
18337
18350
|
}
|
|
18338
18351
|
}
|
|
18339
|
-
var _decomposeDeps;
|
|
18352
|
+
var _decomposeDeps, _claudeAdapterDeps;
|
|
18340
18353
|
var init_claude = __esm(() => {
|
|
18341
18354
|
init_pid_registry();
|
|
18342
18355
|
init_logger2();
|
|
@@ -18349,6 +18362,9 @@ var init_claude = __esm(() => {
|
|
|
18349
18362
|
return Bun.spawn(cmd, opts);
|
|
18350
18363
|
}
|
|
18351
18364
|
};
|
|
18365
|
+
_claudeAdapterDeps = {
|
|
18366
|
+
sleep: (ms) => Bun.sleep(ms)
|
|
18367
|
+
};
|
|
18352
18368
|
});
|
|
18353
18369
|
|
|
18354
18370
|
// src/utils/errors.ts
|
|
@@ -18676,29 +18692,10 @@ async function generateAcceptanceTests(adapter, options) {
|
|
|
18676
18692
|
logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
|
|
18677
18693
|
const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
|
|
18678
18694
|
try {
|
|
18679
|
-
const
|
|
18680
|
-
|
|
18681
|
-
const cmd = [adapter.binary, "--model", options.modelDef.model, ...permArgs, "-p", prompt];
|
|
18682
|
-
const proc = Bun.spawn(cmd, {
|
|
18683
|
-
cwd: options.workdir,
|
|
18684
|
-
stdout: "pipe",
|
|
18685
|
-
stderr: "pipe",
|
|
18686
|
-
env: {
|
|
18687
|
-
...process.env,
|
|
18688
|
-
...options.modelDef.env || {}
|
|
18689
|
-
}
|
|
18695
|
+
const output = await adapter.complete(prompt, {
|
|
18696
|
+
model: options.modelDef.model
|
|
18690
18697
|
});
|
|
18691
|
-
const
|
|
18692
|
-
const stdout = await new Response(proc.stdout).text();
|
|
18693
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18694
|
-
if (exitCode !== 0) {
|
|
18695
|
-
logger.warn("acceptance", "\u26A0 Agent test generation failed", { stderr });
|
|
18696
|
-
return {
|
|
18697
|
-
testCode: generateSkeletonTests(options.featureName, criteria),
|
|
18698
|
-
criteria
|
|
18699
|
-
};
|
|
18700
|
-
}
|
|
18701
|
-
const testCode = extractTestCode(stdout);
|
|
18698
|
+
const testCode = extractTestCode(output);
|
|
18702
18699
|
return {
|
|
18703
18700
|
testCode,
|
|
18704
18701
|
criteria
|
|
@@ -18801,7 +18798,7 @@ Requirements:
|
|
|
18801
18798
|
Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
|
|
18802
18799
|
}
|
|
18803
18800
|
async function generateFixStories(adapter, options) {
|
|
18804
|
-
const { failedACs, testOutput, prd, specContent,
|
|
18801
|
+
const { failedACs, testOutput, prd, specContent, modelDef } = options;
|
|
18805
18802
|
const fixStories = [];
|
|
18806
18803
|
const acTextMap = parseACTextFromSpec(specContent);
|
|
18807
18804
|
const logger = getLogger();
|
|
@@ -18816,34 +18813,9 @@ async function generateFixStories(adapter, options) {
|
|
|
18816
18813
|
}
|
|
18817
18814
|
const prompt = buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd);
|
|
18818
18815
|
try {
|
|
18819
|
-
const
|
|
18820
|
-
|
|
18821
|
-
const cmd = [adapter.binary, "--model", modelDef.model, ...permArgs, "-p", prompt];
|
|
18822
|
-
const proc = Bun.spawn(cmd, {
|
|
18823
|
-
cwd: workdir,
|
|
18824
|
-
stdout: "pipe",
|
|
18825
|
-
stderr: "pipe",
|
|
18826
|
-
env: {
|
|
18827
|
-
...process.env,
|
|
18828
|
-
...modelDef.env || {}
|
|
18829
|
-
}
|
|
18816
|
+
const fixDescription = await adapter.complete(prompt, {
|
|
18817
|
+
model: modelDef.model
|
|
18830
18818
|
});
|
|
18831
|
-
const exitCode = await proc.exited;
|
|
18832
|
-
const stdout = await new Response(proc.stdout).text();
|
|
18833
|
-
const stderr = await new Response(proc.stderr).text();
|
|
18834
|
-
if (exitCode !== 0) {
|
|
18835
|
-
logger.warn("acceptance", "\u26A0 Agent fix generation failed", { failedAC, stderr });
|
|
18836
|
-
fixStories.push({
|
|
18837
|
-
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
18838
|
-
title: `Fix: ${failedAC}`,
|
|
18839
|
-
failedAC,
|
|
18840
|
-
testOutput,
|
|
18841
|
-
relatedStories,
|
|
18842
|
-
description: `Fix the implementation to make ${failedAC} pass. Related stories: ${relatedStories.join(", ")}.`
|
|
18843
|
-
});
|
|
18844
|
-
continue;
|
|
18845
|
-
}
|
|
18846
|
-
const fixDescription = stdout.trim();
|
|
18847
18819
|
fixStories.push({
|
|
18848
18820
|
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
18849
18821
|
title: `Fix: ${failedAC} \u2014 ${acText.slice(0, 50)}`,
|
|
@@ -18910,6 +18882,676 @@ var init_acceptance = __esm(() => {
|
|
|
18910
18882
|
init_fix_generator();
|
|
18911
18883
|
});
|
|
18912
18884
|
|
|
18885
|
+
// src/agents/acp/parser.ts
|
|
18886
|
+
function parseAcpxJsonOutput(rawOutput) {
|
|
18887
|
+
const lines = rawOutput.split(`
|
|
18888
|
+
`).filter((l) => l.trim());
|
|
18889
|
+
let text = "";
|
|
18890
|
+
let tokenUsage;
|
|
18891
|
+
let stopReason;
|
|
18892
|
+
let error48;
|
|
18893
|
+
for (const line of lines) {
|
|
18894
|
+
try {
|
|
18895
|
+
const event = JSON.parse(line);
|
|
18896
|
+
if (event.content && typeof event.content === "string")
|
|
18897
|
+
text += event.content;
|
|
18898
|
+
if (event.text && typeof event.text === "string")
|
|
18899
|
+
text += event.text;
|
|
18900
|
+
if (event.result && typeof event.result === "string")
|
|
18901
|
+
text = event.result;
|
|
18902
|
+
if (event.cumulative_token_usage)
|
|
18903
|
+
tokenUsage = event.cumulative_token_usage;
|
|
18904
|
+
if (event.usage) {
|
|
18905
|
+
tokenUsage = {
|
|
18906
|
+
input_tokens: event.usage.input_tokens ?? event.usage.prompt_tokens ?? 0,
|
|
18907
|
+
output_tokens: event.usage.output_tokens ?? event.usage.completion_tokens ?? 0
|
|
18908
|
+
};
|
|
18909
|
+
}
|
|
18910
|
+
if (event.stopReason)
|
|
18911
|
+
stopReason = event.stopReason;
|
|
18912
|
+
if (event.stop_reason)
|
|
18913
|
+
stopReason = event.stop_reason;
|
|
18914
|
+
if (event.error) {
|
|
18915
|
+
error48 = typeof event.error === "string" ? event.error : event.error.message ?? JSON.stringify(event.error);
|
|
18916
|
+
}
|
|
18917
|
+
} catch {
|
|
18918
|
+
if (!text)
|
|
18919
|
+
text = line;
|
|
18920
|
+
}
|
|
18921
|
+
}
|
|
18922
|
+
return { text: text.trim(), tokenUsage, stopReason, error: error48 };
|
|
18923
|
+
}
|
|
18924
|
+
|
|
18925
|
+
// src/agents/acp/spawn-client.ts
|
|
18926
|
+
function buildAllowedEnv2(extraEnv) {
|
|
18927
|
+
const allowed = {};
|
|
18928
|
+
const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
|
|
18929
|
+
for (const varName of essentialVars) {
|
|
18930
|
+
if (process.env[varName])
|
|
18931
|
+
allowed[varName] = process.env[varName];
|
|
18932
|
+
}
|
|
18933
|
+
const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
|
|
18934
|
+
for (const varName of apiKeyVars) {
|
|
18935
|
+
if (process.env[varName])
|
|
18936
|
+
allowed[varName] = process.env[varName];
|
|
18937
|
+
}
|
|
18938
|
+
const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_"];
|
|
18939
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
18940
|
+
if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
18941
|
+
allowed[key] = value;
|
|
18942
|
+
}
|
|
18943
|
+
}
|
|
18944
|
+
if (extraEnv)
|
|
18945
|
+
Object.assign(allowed, extraEnv);
|
|
18946
|
+
return allowed;
|
|
18947
|
+
}
|
|
18948
|
+
|
|
18949
|
+
class SpawnAcpSession {
|
|
18950
|
+
agentName;
|
|
18951
|
+
sessionName;
|
|
18952
|
+
cwd;
|
|
18953
|
+
model;
|
|
18954
|
+
timeoutSeconds;
|
|
18955
|
+
permissionMode;
|
|
18956
|
+
env;
|
|
18957
|
+
constructor(opts) {
|
|
18958
|
+
this.agentName = opts.agentName;
|
|
18959
|
+
this.sessionName = opts.sessionName;
|
|
18960
|
+
this.cwd = opts.cwd;
|
|
18961
|
+
this.model = opts.model;
|
|
18962
|
+
this.timeoutSeconds = opts.timeoutSeconds;
|
|
18963
|
+
this.permissionMode = opts.permissionMode;
|
|
18964
|
+
this.env = opts.env;
|
|
18965
|
+
}
|
|
18966
|
+
async prompt(text) {
|
|
18967
|
+
const cmd = [
|
|
18968
|
+
"acpx",
|
|
18969
|
+
"--cwd",
|
|
18970
|
+
this.cwd,
|
|
18971
|
+
...this.permissionMode === "approve-all" ? ["--approve-all"] : [],
|
|
18972
|
+
"--format",
|
|
18973
|
+
"json",
|
|
18974
|
+
"--model",
|
|
18975
|
+
this.model,
|
|
18976
|
+
"--timeout",
|
|
18977
|
+
String(this.timeoutSeconds),
|
|
18978
|
+
this.agentName,
|
|
18979
|
+
"prompt",
|
|
18980
|
+
"-s",
|
|
18981
|
+
this.sessionName,
|
|
18982
|
+
"--file",
|
|
18983
|
+
"-"
|
|
18984
|
+
];
|
|
18985
|
+
getSafeLogger()?.debug("acp-adapter", `Sending prompt to session: ${this.sessionName}`);
|
|
18986
|
+
const proc = _spawnClientDeps.spawn(cmd, {
|
|
18987
|
+
cwd: this.cwd,
|
|
18988
|
+
stdin: "pipe",
|
|
18989
|
+
stdout: "pipe",
|
|
18990
|
+
stderr: "pipe",
|
|
18991
|
+
env: this.env
|
|
18992
|
+
});
|
|
18993
|
+
proc.stdin.write(text);
|
|
18994
|
+
proc.stdin.end();
|
|
18995
|
+
const exitCode = await proc.exited;
|
|
18996
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18997
|
+
const stderr = await new Response(proc.stderr).text();
|
|
18998
|
+
if (exitCode !== 0) {
|
|
18999
|
+
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
19000
|
+
stderr: stderr.slice(0, 200)
|
|
19001
|
+
});
|
|
19002
|
+
return {
|
|
19003
|
+
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
19004
|
+
stopReason: "error"
|
|
19005
|
+
};
|
|
19006
|
+
}
|
|
19007
|
+
try {
|
|
19008
|
+
const parsed = parseAcpxJsonOutput(stdout);
|
|
19009
|
+
return {
|
|
19010
|
+
messages: [{ role: "assistant", content: parsed.text || "" }],
|
|
19011
|
+
stopReason: "end_turn",
|
|
19012
|
+
cumulative_token_usage: parsed.tokenUsage
|
|
19013
|
+
};
|
|
19014
|
+
} catch (err) {
|
|
19015
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
|
|
19016
|
+
stderr: stderr.slice(0, 200)
|
|
19017
|
+
});
|
|
19018
|
+
throw err;
|
|
19019
|
+
}
|
|
19020
|
+
}
|
|
19021
|
+
async close() {
|
|
19022
|
+
const cmd = ["acpx", this.agentName, "sessions", "close", this.sessionName];
|
|
19023
|
+
getSafeLogger()?.debug("acp-adapter", `Closing session: ${this.sessionName}`);
|
|
19024
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19025
|
+
const exitCode = await proc.exited;
|
|
19026
|
+
if (exitCode !== 0) {
|
|
19027
|
+
const stderr = await new Response(proc.stderr).text();
|
|
19028
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to close session", {
|
|
19029
|
+
sessionName: this.sessionName,
|
|
19030
|
+
stderr: stderr.slice(0, 200)
|
|
19031
|
+
});
|
|
19032
|
+
}
|
|
19033
|
+
}
|
|
19034
|
+
async cancelActivePrompt() {
|
|
19035
|
+
const cmd = ["acpx", this.agentName, "cancel"];
|
|
19036
|
+
getSafeLogger()?.debug("acp-adapter", `Cancelling active prompt: ${this.sessionName}`);
|
|
19037
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19038
|
+
await proc.exited;
|
|
19039
|
+
}
|
|
19040
|
+
}
|
|
19041
|
+
|
|
19042
|
+
class SpawnAcpClient {
|
|
19043
|
+
agentName;
|
|
19044
|
+
model;
|
|
19045
|
+
cwd;
|
|
19046
|
+
timeoutSeconds;
|
|
19047
|
+
env;
|
|
19048
|
+
constructor(cmdStr, cwd, timeoutSeconds) {
|
|
19049
|
+
const parts = cmdStr.split(/\s+/);
|
|
19050
|
+
const modelIdx = parts.indexOf("--model");
|
|
19051
|
+
this.model = modelIdx >= 0 && parts[modelIdx + 1] ? parts[modelIdx + 1] : "default";
|
|
19052
|
+
this.agentName = parts[parts.length - 1] || "claude";
|
|
19053
|
+
this.cwd = cwd || process.cwd();
|
|
19054
|
+
this.timeoutSeconds = timeoutSeconds || 1800;
|
|
19055
|
+
this.env = buildAllowedEnv2();
|
|
19056
|
+
}
|
|
19057
|
+
async start() {}
|
|
19058
|
+
async createSession(opts) {
|
|
19059
|
+
const sessionName = opts.sessionName || `nax-${Date.now()}`;
|
|
19060
|
+
const cmd = ["acpx", opts.agentName, "sessions", "ensure", "--name", sessionName];
|
|
19061
|
+
getSafeLogger()?.debug("acp-adapter", `Ensuring session: ${sessionName}`);
|
|
19062
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19063
|
+
const exitCode = await proc.exited;
|
|
19064
|
+
if (exitCode !== 0) {
|
|
19065
|
+
const stderr = await new Response(proc.stderr).text();
|
|
19066
|
+
throw new Error(`[acp-adapter] Failed to create session: ${stderr || `exit code ${exitCode}`}`);
|
|
19067
|
+
}
|
|
19068
|
+
return new SpawnAcpSession({
|
|
19069
|
+
agentName: opts.agentName,
|
|
19070
|
+
sessionName,
|
|
19071
|
+
cwd: this.cwd,
|
|
19072
|
+
model: this.model,
|
|
19073
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
19074
|
+
permissionMode: opts.permissionMode,
|
|
19075
|
+
env: this.env
|
|
19076
|
+
});
|
|
19077
|
+
}
|
|
19078
|
+
async loadSession(sessionName) {
|
|
19079
|
+
const cmd = ["acpx", this.agentName, "sessions", "ensure", "--name", sessionName];
|
|
19080
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19081
|
+
const exitCode = await proc.exited;
|
|
19082
|
+
if (exitCode !== 0) {
|
|
19083
|
+
return null;
|
|
19084
|
+
}
|
|
19085
|
+
return new SpawnAcpSession({
|
|
19086
|
+
agentName: this.agentName,
|
|
19087
|
+
sessionName,
|
|
19088
|
+
cwd: this.cwd,
|
|
19089
|
+
model: this.model,
|
|
19090
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
19091
|
+
permissionMode: "approve-all",
|
|
19092
|
+
env: this.env
|
|
19093
|
+
});
|
|
19094
|
+
}
|
|
19095
|
+
async close() {}
|
|
19096
|
+
}
|
|
19097
|
+
function createSpawnAcpClient(cmdStr, cwd, timeoutSeconds) {
|
|
19098
|
+
return new SpawnAcpClient(cmdStr, cwd, timeoutSeconds);
|
|
19099
|
+
}
|
|
19100
|
+
var _spawnClientDeps;
|
|
19101
|
+
var init_spawn_client = __esm(() => {
|
|
19102
|
+
init_logger2();
|
|
19103
|
+
_spawnClientDeps = {
|
|
19104
|
+
spawn(cmd, opts) {
|
|
19105
|
+
return Bun.spawn(cmd, opts);
|
|
19106
|
+
}
|
|
19107
|
+
};
|
|
19108
|
+
});
|
|
19109
|
+
|
|
19110
|
+
// src/agents/acp/cost.ts
|
|
19111
|
+
function estimateCostFromTokenUsage(usage, model) {
|
|
19112
|
+
const pricing = MODEL_PRICING[model];
|
|
19113
|
+
if (!pricing) {
|
|
19114
|
+
const fallbackInputRate = 3 / 1e6;
|
|
19115
|
+
const fallbackOutputRate = 15 / 1e6;
|
|
19116
|
+
const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
|
|
19117
|
+
const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
|
|
19118
|
+
const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
|
|
19119
|
+
const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
|
|
19120
|
+
return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
|
|
19121
|
+
}
|
|
19122
|
+
const inputRate = pricing.input / 1e6;
|
|
19123
|
+
const outputRate = pricing.output / 1e6;
|
|
19124
|
+
const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
|
|
19125
|
+
const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
|
|
19126
|
+
const inputCost = (usage.input_tokens ?? 0) * inputRate;
|
|
19127
|
+
const outputCost = (usage.output_tokens ?? 0) * outputRate;
|
|
19128
|
+
const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
|
|
19129
|
+
const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
|
|
19130
|
+
return inputCost + outputCost + cacheReadCost + cacheCreationCost;
|
|
19131
|
+
}
|
|
19132
|
+
var MODEL_PRICING;
|
|
19133
|
+
var init_cost2 = __esm(() => {
|
|
19134
|
+
MODEL_PRICING = {
|
|
19135
|
+
"claude-sonnet-4": { input: 3, output: 15 },
|
|
19136
|
+
"claude-sonnet-4-5": { input: 3, output: 15 },
|
|
19137
|
+
"claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
|
|
19138
|
+
"claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
|
|
19139
|
+
"claude-opus": { input: 15, output: 75 },
|
|
19140
|
+
"claude-opus-4": { input: 15, output: 75 },
|
|
19141
|
+
"gpt-4.1": { input: 10, output: 30 },
|
|
19142
|
+
"gpt-4": { input: 30, output: 60 },
|
|
19143
|
+
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
19144
|
+
"gemini-2.5-pro": { input: 0.075, output: 0.3 },
|
|
19145
|
+
"gemini-2-pro": { input: 0.075, output: 0.3 },
|
|
19146
|
+
codex: { input: 0.02, output: 0.06 },
|
|
19147
|
+
"code-davinci-002": { input: 0.02, output: 0.06 }
|
|
19148
|
+
};
|
|
19149
|
+
});
|
|
19150
|
+
|
|
19151
|
+
// src/agents/acp/adapter.ts
|
|
19152
|
+
import { createHash } from "crypto";
|
|
19153
|
+
import { join as join3 } from "path";
|
|
19154
|
+
function resolveRegistryEntry(agentName) {
|
|
19155
|
+
return AGENT_REGISTRY[agentName] ?? DEFAULT_ENTRY;
|
|
19156
|
+
}
|
|
19157
|
+
function isRateLimitError(err) {
|
|
19158
|
+
if (!(err instanceof Error))
|
|
19159
|
+
return false;
|
|
19160
|
+
const msg = err.message.toLowerCase();
|
|
19161
|
+
return msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429");
|
|
19162
|
+
}
|
|
19163
|
+
function buildSessionName(workdir, featureName, storyId, sessionRole) {
|
|
19164
|
+
const hash2 = createHash("sha256").update(workdir).digest("hex").slice(0, 8);
|
|
19165
|
+
const sanitize = (s) => s.replace(/[^a-z0-9]+/gi, "-").toLowerCase().replace(/^-+|-+$/g, "");
|
|
19166
|
+
const parts = ["nax", hash2];
|
|
19167
|
+
if (featureName)
|
|
19168
|
+
parts.push(sanitize(featureName));
|
|
19169
|
+
if (storyId)
|
|
19170
|
+
parts.push(sanitize(storyId));
|
|
19171
|
+
if (sessionRole)
|
|
19172
|
+
parts.push(sanitize(sessionRole));
|
|
19173
|
+
return parts.join("-");
|
|
19174
|
+
}
|
|
19175
|
+
async function ensureAcpSession(client, sessionName, agentName, permissionMode) {
|
|
19176
|
+
if (client.loadSession) {
|
|
19177
|
+
try {
|
|
19178
|
+
const existing = await client.loadSession(sessionName);
|
|
19179
|
+
if (existing) {
|
|
19180
|
+
getSafeLogger()?.debug("acp-adapter", `Resumed existing session: ${sessionName}`);
|
|
19181
|
+
return existing;
|
|
19182
|
+
}
|
|
19183
|
+
} catch {}
|
|
19184
|
+
}
|
|
19185
|
+
getSafeLogger()?.debug("acp-adapter", `Creating new session: ${sessionName}`);
|
|
19186
|
+
return client.createSession({ agentName, permissionMode, sessionName });
|
|
19187
|
+
}
|
|
19188
|
+
async function runSessionPrompt(session, prompt, timeoutMs) {
|
|
19189
|
+
const promptPromise = session.prompt(prompt);
|
|
19190
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve("timeout"), timeoutMs));
|
|
19191
|
+
const winner = await Promise.race([promptPromise, timeoutPromise]);
|
|
19192
|
+
if (winner === "timeout") {
|
|
19193
|
+
try {
|
|
19194
|
+
await session.cancelActivePrompt();
|
|
19195
|
+
} catch {
|
|
19196
|
+
await session.close().catch(() => {});
|
|
19197
|
+
}
|
|
19198
|
+
return { response: null, timedOut: true };
|
|
19199
|
+
}
|
|
19200
|
+
return { response: winner, timedOut: false };
|
|
19201
|
+
}
|
|
19202
|
+
async function closeAcpSession(session) {
|
|
19203
|
+
try {
|
|
19204
|
+
await session.close();
|
|
19205
|
+
} catch (err) {
|
|
19206
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to close session", { error: String(err) });
|
|
19207
|
+
}
|
|
19208
|
+
}
|
|
19209
|
+
function acpSessionsPath(workdir, featureName) {
|
|
19210
|
+
return join3(workdir, "nax", "features", featureName, "acp-sessions.json");
|
|
19211
|
+
}
|
|
19212
|
+
async function saveAcpSession(workdir, featureName, storyId, sessionName) {
|
|
19213
|
+
try {
|
|
19214
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19215
|
+
let data = {};
|
|
19216
|
+
try {
|
|
19217
|
+
const existing = await Bun.file(path).text();
|
|
19218
|
+
data = JSON.parse(existing);
|
|
19219
|
+
} catch {}
|
|
19220
|
+
data[storyId] = sessionName;
|
|
19221
|
+
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19222
|
+
} catch (err) {
|
|
19223
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to save session to sidecar", { error: String(err) });
|
|
19224
|
+
}
|
|
19225
|
+
}
|
|
19226
|
+
async function clearAcpSession(workdir, featureName, storyId) {
|
|
19227
|
+
try {
|
|
19228
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19229
|
+
let data = {};
|
|
19230
|
+
try {
|
|
19231
|
+
const existing = await Bun.file(path).text();
|
|
19232
|
+
data = JSON.parse(existing);
|
|
19233
|
+
} catch {
|
|
19234
|
+
return;
|
|
19235
|
+
}
|
|
19236
|
+
delete data[storyId];
|
|
19237
|
+
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19238
|
+
} catch (err) {
|
|
19239
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to clear session from sidecar", { error: String(err) });
|
|
19240
|
+
}
|
|
19241
|
+
}
|
|
19242
|
+
async function readAcpSession(workdir, featureName, storyId) {
|
|
19243
|
+
try {
|
|
19244
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19245
|
+
const existing = await Bun.file(path).text();
|
|
19246
|
+
const data = JSON.parse(existing);
|
|
19247
|
+
return data[storyId] ?? null;
|
|
19248
|
+
} catch {
|
|
19249
|
+
return null;
|
|
19250
|
+
}
|
|
19251
|
+
}
|
|
19252
|
+
function extractOutput(response) {
|
|
19253
|
+
if (!response)
|
|
19254
|
+
return "";
|
|
19255
|
+
return response.messages.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
19256
|
+
`).trim();
|
|
19257
|
+
}
|
|
19258
|
+
function extractQuestion(output) {
|
|
19259
|
+
const text = output.trim();
|
|
19260
|
+
if (!text)
|
|
19261
|
+
return null;
|
|
19262
|
+
const sentences = text.split(/(?<=[.!?])\s+/);
|
|
19263
|
+
const questionSentences = sentences.filter((s) => s.trim().endsWith("?"));
|
|
19264
|
+
if (questionSentences.length > 0) {
|
|
19265
|
+
const q = questionSentences[questionSentences.length - 1].trim();
|
|
19266
|
+
if (q.length > 10)
|
|
19267
|
+
return q;
|
|
19268
|
+
}
|
|
19269
|
+
const lower = text.toLowerCase();
|
|
19270
|
+
const markers = [
|
|
19271
|
+
"please confirm",
|
|
19272
|
+
"please specify",
|
|
19273
|
+
"please provide",
|
|
19274
|
+
"which would you",
|
|
19275
|
+
"should i ",
|
|
19276
|
+
"do you want",
|
|
19277
|
+
"can you clarify"
|
|
19278
|
+
];
|
|
19279
|
+
for (const marker of markers) {
|
|
19280
|
+
if (lower.includes(marker)) {
|
|
19281
|
+
return text.slice(-200).trim();
|
|
19282
|
+
}
|
|
19283
|
+
}
|
|
19284
|
+
return null;
|
|
19285
|
+
}
|
|
19286
|
+
|
|
19287
|
+
class AcpAgentAdapter {
|
|
19288
|
+
name;
|
|
19289
|
+
displayName;
|
|
19290
|
+
binary;
|
|
19291
|
+
capabilities;
|
|
19292
|
+
constructor(agentName) {
|
|
19293
|
+
const entry = resolveRegistryEntry(agentName);
|
|
19294
|
+
this.name = agentName;
|
|
19295
|
+
this.displayName = entry.displayName;
|
|
19296
|
+
this.binary = entry.binary;
|
|
19297
|
+
this.capabilities = {
|
|
19298
|
+
supportedTiers: entry.supportedTiers,
|
|
19299
|
+
maxContextTokens: entry.maxContextTokens,
|
|
19300
|
+
features: new Set(["tdd", "review", "refactor"])
|
|
19301
|
+
};
|
|
19302
|
+
}
|
|
19303
|
+
async isInstalled() {
|
|
19304
|
+
const path = _acpAdapterDeps.which(this.binary);
|
|
19305
|
+
return path !== null;
|
|
19306
|
+
}
|
|
19307
|
+
buildCommand(_options) {
|
|
19308
|
+
return ["acpx", this.name, "session"];
|
|
19309
|
+
}
|
|
19310
|
+
buildAllowedEnv(_options) {
|
|
19311
|
+
return {};
|
|
19312
|
+
}
|
|
19313
|
+
async run(options) {
|
|
19314
|
+
const startTime = Date.now();
|
|
19315
|
+
let lastError;
|
|
19316
|
+
getSafeLogger()?.debug("acp-adapter", `Starting run for ${this.name}`, {
|
|
19317
|
+
model: options.modelDef.model,
|
|
19318
|
+
workdir: options.workdir,
|
|
19319
|
+
featureName: options.featureName,
|
|
19320
|
+
storyId: options.storyId,
|
|
19321
|
+
sessionRole: options.sessionRole
|
|
19322
|
+
});
|
|
19323
|
+
for (let attempt = 0;attempt < MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
19324
|
+
try {
|
|
19325
|
+
const result = await this._runWithClient(options, startTime);
|
|
19326
|
+
if (!result.success) {
|
|
19327
|
+
getSafeLogger()?.warn("acp-adapter", `Run failed for ${this.name}`, { exitCode: result.exitCode });
|
|
19328
|
+
}
|
|
19329
|
+
return result;
|
|
19330
|
+
} catch (err) {
|
|
19331
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
19332
|
+
lastError = error48;
|
|
19333
|
+
const shouldRetry = isRateLimitError(error48) && attempt < MAX_RATE_LIMIT_RETRIES - 1;
|
|
19334
|
+
if (!shouldRetry)
|
|
19335
|
+
break;
|
|
19336
|
+
const backoffMs = 2 ** (attempt + 1) * 1000;
|
|
19337
|
+
getSafeLogger()?.warn("acp-adapter", "Retrying after rate limit", {
|
|
19338
|
+
backoffSeconds: backoffMs / 1000,
|
|
19339
|
+
attempt: attempt + 1
|
|
19340
|
+
});
|
|
19341
|
+
await _acpAdapterDeps.sleep(backoffMs);
|
|
19342
|
+
}
|
|
19343
|
+
}
|
|
19344
|
+
const durationMs = Date.now() - startTime;
|
|
19345
|
+
return {
|
|
19346
|
+
success: false,
|
|
19347
|
+
exitCode: 1,
|
|
19348
|
+
output: lastError?.message ?? "Run failed",
|
|
19349
|
+
rateLimited: isRateLimitError(lastError),
|
|
19350
|
+
durationMs,
|
|
19351
|
+
estimatedCost: 0
|
|
19352
|
+
};
|
|
19353
|
+
}
|
|
19354
|
+
async _runWithClient(options, startTime) {
|
|
19355
|
+
const cmdStr = `acpx --model ${options.modelDef.model} ${this.name}`;
|
|
19356
|
+
const client = _acpAdapterDeps.createClient(cmdStr, options.workdir, options.timeoutSeconds);
|
|
19357
|
+
await client.start();
|
|
19358
|
+
let sessionName = options.acpSessionName;
|
|
19359
|
+
if (!sessionName && options.featureName && options.storyId) {
|
|
19360
|
+
sessionName = await readAcpSession(options.workdir, options.featureName, options.storyId) ?? undefined;
|
|
19361
|
+
}
|
|
19362
|
+
sessionName ??= buildSessionName(options.workdir, options.featureName, options.storyId, options.sessionRole);
|
|
19363
|
+
const permissionMode = options.dangerouslySkipPermissions ? "approve-all" : "default";
|
|
19364
|
+
const session = await ensureAcpSession(client, sessionName, this.name, permissionMode);
|
|
19365
|
+
if (options.featureName && options.storyId) {
|
|
19366
|
+
await saveAcpSession(options.workdir, options.featureName, options.storyId, sessionName);
|
|
19367
|
+
}
|
|
19368
|
+
let lastResponse = null;
|
|
19369
|
+
let timedOut = false;
|
|
19370
|
+
const totalTokenUsage = { input_tokens: 0, output_tokens: 0 };
|
|
19371
|
+
try {
|
|
19372
|
+
let currentPrompt = options.prompt;
|
|
19373
|
+
let turnCount = 0;
|
|
19374
|
+
const MAX_TURNS = options.interactionBridge ? 10 : 1;
|
|
19375
|
+
while (turnCount < MAX_TURNS) {
|
|
19376
|
+
turnCount++;
|
|
19377
|
+
getSafeLogger()?.debug("acp-adapter", `Session turn ${turnCount}/${MAX_TURNS}`, { sessionName });
|
|
19378
|
+
const turnResult = await runSessionPrompt(session, currentPrompt, options.timeoutSeconds * 1000);
|
|
19379
|
+
if (turnResult.timedOut) {
|
|
19380
|
+
timedOut = true;
|
|
19381
|
+
break;
|
|
19382
|
+
}
|
|
19383
|
+
lastResponse = turnResult.response;
|
|
19384
|
+
if (!lastResponse)
|
|
19385
|
+
break;
|
|
19386
|
+
if (lastResponse.cumulative_token_usage) {
|
|
19387
|
+
totalTokenUsage.input_tokens += lastResponse.cumulative_token_usage.input_tokens ?? 0;
|
|
19388
|
+
totalTokenUsage.output_tokens += lastResponse.cumulative_token_usage.output_tokens ?? 0;
|
|
19389
|
+
}
|
|
19390
|
+
const outputText = extractOutput(lastResponse);
|
|
19391
|
+
const question = extractQuestion(outputText);
|
|
19392
|
+
if (!question || !options.interactionBridge)
|
|
19393
|
+
break;
|
|
19394
|
+
getSafeLogger()?.debug("acp-adapter", "Agent asked question, routing to interactionBridge", { question });
|
|
19395
|
+
try {
|
|
19396
|
+
const answer = await Promise.race([
|
|
19397
|
+
options.interactionBridge.onQuestionDetected(question),
|
|
19398
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("interaction timeout")), INTERACTION_TIMEOUT_MS))
|
|
19399
|
+
]);
|
|
19400
|
+
currentPrompt = answer;
|
|
19401
|
+
} catch (err) {
|
|
19402
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19403
|
+
getSafeLogger()?.warn("acp-adapter", `InteractionBridge failed: ${msg}`);
|
|
19404
|
+
break;
|
|
19405
|
+
}
|
|
19406
|
+
}
|
|
19407
|
+
if (turnCount >= MAX_TURNS && options.interactionBridge) {
|
|
19408
|
+
getSafeLogger()?.warn("acp-adapter", "Reached max turns limit", { sessionName, maxTurns: MAX_TURNS });
|
|
19409
|
+
}
|
|
19410
|
+
} finally {
|
|
19411
|
+
await closeAcpSession(session);
|
|
19412
|
+
await client.close().catch(() => {});
|
|
19413
|
+
if (options.featureName && options.storyId) {
|
|
19414
|
+
await clearAcpSession(options.workdir, options.featureName, options.storyId);
|
|
19415
|
+
}
|
|
19416
|
+
}
|
|
19417
|
+
const durationMs = Date.now() - startTime;
|
|
19418
|
+
if (timedOut) {
|
|
19419
|
+
return {
|
|
19420
|
+
success: false,
|
|
19421
|
+
exitCode: 124,
|
|
19422
|
+
output: `Session timed out after ${options.timeoutSeconds}s`,
|
|
19423
|
+
rateLimited: false,
|
|
19424
|
+
durationMs,
|
|
19425
|
+
estimatedCost: 0
|
|
19426
|
+
};
|
|
19427
|
+
}
|
|
19428
|
+
const success2 = lastResponse?.stopReason === "end_turn";
|
|
19429
|
+
const output = extractOutput(lastResponse);
|
|
19430
|
+
const estimatedCost = totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0;
|
|
19431
|
+
return {
|
|
19432
|
+
success: success2,
|
|
19433
|
+
exitCode: success2 ? 0 : 1,
|
|
19434
|
+
output: output.slice(-MAX_AGENT_OUTPUT_CHARS2),
|
|
19435
|
+
rateLimited: false,
|
|
19436
|
+
durationMs,
|
|
19437
|
+
estimatedCost
|
|
19438
|
+
};
|
|
19439
|
+
}
|
|
19440
|
+
async complete(prompt, _options) {
|
|
19441
|
+
const model = _options?.model ?? "default";
|
|
19442
|
+
const cmdStr = `acpx --model ${model} ${this.name}`;
|
|
19443
|
+
const client = _acpAdapterDeps.createClient(cmdStr);
|
|
19444
|
+
await client.start();
|
|
19445
|
+
const permissionMode = _options?.dangerouslySkipPermissions ? "approve-all" : "default";
|
|
19446
|
+
let session = null;
|
|
19447
|
+
try {
|
|
19448
|
+
session = await client.createSession({ agentName: this.name, permissionMode });
|
|
19449
|
+
const response = await session.prompt(prompt);
|
|
19450
|
+
if (response.stopReason === "error") {
|
|
19451
|
+
throw new CompleteError("complete() failed: stop reason is error");
|
|
19452
|
+
}
|
|
19453
|
+
const text = response.messages.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
19454
|
+
`).trim();
|
|
19455
|
+
if (!text) {
|
|
19456
|
+
throw new CompleteError("complete() returned empty output");
|
|
19457
|
+
}
|
|
19458
|
+
return text;
|
|
19459
|
+
} finally {
|
|
19460
|
+
if (session) {
|
|
19461
|
+
await session.close().catch(() => {});
|
|
19462
|
+
}
|
|
19463
|
+
await client.close().catch(() => {});
|
|
19464
|
+
}
|
|
19465
|
+
}
|
|
19466
|
+
async plan(options) {
|
|
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
|
+
|
|
18913
19555
|
// src/agents/adapters/aider.ts
|
|
18914
19556
|
class AiderAdapter {
|
|
18915
19557
|
name = "aider";
|
|
@@ -18940,7 +19582,7 @@ class AiderAdapter {
|
|
|
18940
19582
|
return {
|
|
18941
19583
|
success: exitCode === 0,
|
|
18942
19584
|
exitCode,
|
|
18943
|
-
output: stdout.slice(-
|
|
19585
|
+
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS3),
|
|
18944
19586
|
rateLimited: false,
|
|
18945
19587
|
durationMs,
|
|
18946
19588
|
estimatedCost: 0,
|
|
@@ -18974,7 +19616,7 @@ class AiderAdapter {
|
|
|
18974
19616
|
throw new Error("AiderAdapter.decompose() not implemented");
|
|
18975
19617
|
}
|
|
18976
19618
|
}
|
|
18977
|
-
var _aiderCompleteDeps,
|
|
19619
|
+
var _aiderCompleteDeps, MAX_AGENT_OUTPUT_CHARS3 = 5000;
|
|
18978
19620
|
var init_aider = __esm(() => {
|
|
18979
19621
|
init_types2();
|
|
18980
19622
|
_aiderCompleteDeps = {
|
|
@@ -19018,7 +19660,7 @@ class CodexAdapter {
|
|
|
19018
19660
|
return {
|
|
19019
19661
|
success: exitCode === 0,
|
|
19020
19662
|
exitCode,
|
|
19021
|
-
output: stdout.slice(-
|
|
19663
|
+
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS4),
|
|
19022
19664
|
rateLimited: false,
|
|
19023
19665
|
durationMs,
|
|
19024
19666
|
estimatedCost: 0,
|
|
@@ -19049,7 +19691,7 @@ class CodexAdapter {
|
|
|
19049
19691
|
throw new Error("CodexAdapter.decompose() not implemented");
|
|
19050
19692
|
}
|
|
19051
19693
|
}
|
|
19052
|
-
var _codexRunDeps, _codexCompleteDeps,
|
|
19694
|
+
var _codexRunDeps, _codexCompleteDeps, MAX_AGENT_OUTPUT_CHARS4 = 5000;
|
|
19053
19695
|
var init_codex = __esm(() => {
|
|
19054
19696
|
init_types2();
|
|
19055
19697
|
_codexRunDeps = {
|
|
@@ -19118,7 +19760,7 @@ class GeminiAdapter {
|
|
|
19118
19760
|
return {
|
|
19119
19761
|
success: exitCode === 0,
|
|
19120
19762
|
exitCode,
|
|
19121
|
-
output: stdout.slice(-
|
|
19763
|
+
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS5),
|
|
19122
19764
|
rateLimited: false,
|
|
19123
19765
|
durationMs,
|
|
19124
19766
|
estimatedCost: 0,
|
|
@@ -19149,7 +19791,7 @@ class GeminiAdapter {
|
|
|
19149
19791
|
throw new Error("GeminiAdapter.decompose() not implemented");
|
|
19150
19792
|
}
|
|
19151
19793
|
}
|
|
19152
|
-
var _geminiRunDeps, _geminiCompleteDeps,
|
|
19794
|
+
var _geminiRunDeps, _geminiCompleteDeps, MAX_AGENT_OUTPUT_CHARS5 = 5000;
|
|
19153
19795
|
var init_gemini = __esm(() => {
|
|
19154
19796
|
init_types2();
|
|
19155
19797
|
_geminiRunDeps = {
|
|
@@ -19230,6 +19872,7 @@ __export(exports_registry, {
|
|
|
19230
19872
|
getInstalledAgents: () => getInstalledAgents,
|
|
19231
19873
|
getAllAgentNames: () => getAllAgentNames,
|
|
19232
19874
|
getAgent: () => getAgent,
|
|
19875
|
+
createAgentRegistry: () => createAgentRegistry,
|
|
19233
19876
|
checkAgentHealth: () => checkAgentHealth,
|
|
19234
19877
|
ALL_AGENTS: () => ALL_AGENTS
|
|
19235
19878
|
});
|
|
@@ -19253,8 +19896,57 @@ async function checkAgentHealth() {
|
|
|
19253
19896
|
installed: await agent.isInstalled()
|
|
19254
19897
|
})));
|
|
19255
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
|
+
}
|
|
19256
19946
|
var ALL_AGENTS;
|
|
19257
19947
|
var init_registry = __esm(() => {
|
|
19948
|
+
init_logger2();
|
|
19949
|
+
init_adapter();
|
|
19258
19950
|
init_aider();
|
|
19259
19951
|
init_codex();
|
|
19260
19952
|
init_gemini();
|
|
@@ -19416,7 +20108,7 @@ var init_chain = __esm(() => {
|
|
|
19416
20108
|
});
|
|
19417
20109
|
|
|
19418
20110
|
// src/utils/path-security.ts
|
|
19419
|
-
import { isAbsolute, join as
|
|
20111
|
+
import { isAbsolute, join as join5, normalize, resolve } from "path";
|
|
19420
20112
|
function validateModulePath(modulePath, allowedRoots) {
|
|
19421
20113
|
if (!modulePath) {
|
|
19422
20114
|
return { valid: false, error: "Module path is empty" };
|
|
@@ -19432,7 +20124,7 @@ function validateModulePath(modulePath, allowedRoots) {
|
|
|
19432
20124
|
}
|
|
19433
20125
|
} else {
|
|
19434
20126
|
for (const root of normalizedRoots) {
|
|
19435
|
-
const absoluteTarget = resolve(
|
|
20127
|
+
const absoluteTarget = resolve(join5(root, modulePath));
|
|
19436
20128
|
if (absoluteTarget.startsWith(`${root}/`) || absoluteTarget === root) {
|
|
19437
20129
|
return { valid: true, absolutePath: absoluteTarget };
|
|
19438
20130
|
}
|
|
@@ -19782,27 +20474,27 @@ var init_path_security2 = () => {};
|
|
|
19782
20474
|
|
|
19783
20475
|
// src/config/paths.ts
|
|
19784
20476
|
import { homedir } from "os";
|
|
19785
|
-
import { join as
|
|
20477
|
+
import { join as join6, resolve as resolve4 } from "path";
|
|
19786
20478
|
function globalConfigDir() {
|
|
19787
|
-
return
|
|
20479
|
+
return join6(homedir(), ".nax");
|
|
19788
20480
|
}
|
|
19789
20481
|
var init_paths = () => {};
|
|
19790
20482
|
|
|
19791
20483
|
// src/config/loader.ts
|
|
19792
20484
|
import { existsSync as existsSync5 } from "fs";
|
|
19793
|
-
import { join as
|
|
20485
|
+
import { join as join7, resolve as resolve5 } from "path";
|
|
19794
20486
|
function globalConfigPath() {
|
|
19795
|
-
return
|
|
20487
|
+
return join7(globalConfigDir(), "config.json");
|
|
19796
20488
|
}
|
|
19797
20489
|
function findProjectDir(startDir = process.cwd()) {
|
|
19798
20490
|
let dir = resolve5(startDir);
|
|
19799
20491
|
let depth = 0;
|
|
19800
20492
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
19801
|
-
const candidate =
|
|
19802
|
-
if (existsSync5(
|
|
20493
|
+
const candidate = join7(dir, "nax");
|
|
20494
|
+
if (existsSync5(join7(candidate, "config.json"))) {
|
|
19803
20495
|
return candidate;
|
|
19804
20496
|
}
|
|
19805
|
-
const parent =
|
|
20497
|
+
const parent = join7(dir, "..");
|
|
19806
20498
|
if (parent === dir)
|
|
19807
20499
|
break;
|
|
19808
20500
|
dir = parent;
|
|
@@ -19840,7 +20532,7 @@ async function loadConfig(projectDir, cliOverrides) {
|
|
|
19840
20532
|
}
|
|
19841
20533
|
const projDir = projectDir ?? findProjectDir();
|
|
19842
20534
|
if (projDir) {
|
|
19843
|
-
const projConf = await loadJsonFile(
|
|
20535
|
+
const projConf = await loadJsonFile(join7(projDir, "config.json"), "config");
|
|
19844
20536
|
if (projConf) {
|
|
19845
20537
|
const resolvedProjConf = applyBatchModeCompat(projConf);
|
|
19846
20538
|
rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
|
|
@@ -21115,7 +21807,7 @@ var package_default;
|
|
|
21115
21807
|
var init_package = __esm(() => {
|
|
21116
21808
|
package_default = {
|
|
21117
21809
|
name: "@nathapp/nax",
|
|
21118
|
-
version: "0.
|
|
21810
|
+
version: "0.42.0",
|
|
21119
21811
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
21120
21812
|
type: "module",
|
|
21121
21813
|
bin: {
|
|
@@ -21127,11 +21819,12 @@ var init_package = __esm(() => {
|
|
|
21127
21819
|
build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
|
|
21128
21820
|
typecheck: "bun x tsc --noEmit",
|
|
21129
21821
|
lint: "bun x biome check src/ bin/",
|
|
21130
|
-
test: "NAX_SKIP_PRECHECK=1 bun test test/ --timeout=60000",
|
|
21131
|
-
"test:watch": "bun test --watch",
|
|
21132
|
-
"test:unit": "bun test ./test/unit/ --timeout=60000",
|
|
21133
|
-
"test:integration": "bun test ./test/integration/ --timeout=60000",
|
|
21134
|
-
"test:ui": "bun test ./test/ui/ --timeout=60000",
|
|
21822
|
+
test: "CI=1 NAX_SKIP_PRECHECK=1 bun test test/ --timeout=60000",
|
|
21823
|
+
"test:watch": "CI=1 bun test --watch",
|
|
21824
|
+
"test:unit": "CI=1 NAX_SKIP_PRECHECK=1 bun test ./test/unit/ --timeout=60000",
|
|
21825
|
+
"test:integration": "CI=1 NAX_SKIP_PRECHECK=1 bun test ./test/integration/ --timeout=60000",
|
|
21826
|
+
"test:ui": "CI=1 bun test ./test/ui/ --timeout=60000",
|
|
21827
|
+
"test:real": "NAX_SKIP_PRECHECK=1 bun test test/ --timeout=60000",
|
|
21135
21828
|
"check-test-overlap": "bun run scripts/check-test-overlap.ts",
|
|
21136
21829
|
"check-dead-tests": "bun run scripts/check-dead-tests.ts",
|
|
21137
21830
|
"check:test-sizes": "bun run scripts/check-test-sizes.ts",
|
|
@@ -21179,8 +21872,8 @@ var init_version = __esm(() => {
|
|
|
21179
21872
|
NAX_VERSION = package_default.version;
|
|
21180
21873
|
NAX_COMMIT = (() => {
|
|
21181
21874
|
try {
|
|
21182
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
21183
|
-
return "
|
|
21875
|
+
if (/^[0-9a-f]{6,10}$/.test("a59af3a"))
|
|
21876
|
+
return "a59af3a";
|
|
21184
21877
|
} catch {}
|
|
21185
21878
|
try {
|
|
21186
21879
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -21198,6 +21891,23 @@ var init_version = __esm(() => {
|
|
|
21198
21891
|
NAX_BUILD_INFO = NAX_COMMIT === "dev" ? `v${NAX_VERSION}` : `v${NAX_VERSION} (${NAX_COMMIT})`;
|
|
21199
21892
|
});
|
|
21200
21893
|
|
|
21894
|
+
// src/prd/validate.ts
|
|
21895
|
+
function validateStoryId(id) {
|
|
21896
|
+
if (!id || id.length === 0) {
|
|
21897
|
+
throw new Error("Story ID cannot be empty");
|
|
21898
|
+
}
|
|
21899
|
+
if (id.includes("..")) {
|
|
21900
|
+
throw new Error("Story ID cannot contain path traversal (..)");
|
|
21901
|
+
}
|
|
21902
|
+
if (id.startsWith("--")) {
|
|
21903
|
+
throw new Error("Story ID cannot start with git flags (--)");
|
|
21904
|
+
}
|
|
21905
|
+
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
21906
|
+
if (!validPattern.test(id)) {
|
|
21907
|
+
throw new Error(`Story ID must match pattern [a-zA-Z0-9][a-zA-Z0-9._-]{0,63}. Got: ${id}`);
|
|
21908
|
+
}
|
|
21909
|
+
}
|
|
21910
|
+
|
|
21201
21911
|
// src/errors.ts
|
|
21202
21912
|
var NaxError, AgentNotFoundError, AgentNotInstalledError, StoryLimitExceededError, LockAcquisitionError;
|
|
21203
21913
|
var init_errors3 = __esm(() => {
|
|
@@ -22206,7 +22916,7 @@ class WebhookInteractionPlugin {
|
|
|
22206
22916
|
this.pendingResponses.delete(requestId);
|
|
22207
22917
|
return response;
|
|
22208
22918
|
}
|
|
22209
|
-
await
|
|
22919
|
+
await _webhookPluginDeps.sleep(backoffMs);
|
|
22210
22920
|
backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
|
|
22211
22921
|
}
|
|
22212
22922
|
return {
|
|
@@ -22305,9 +23015,12 @@ class WebhookInteractionPlugin {
|
|
|
22305
23015
|
}
|
|
22306
23016
|
}
|
|
22307
23017
|
}
|
|
22308
|
-
var WebhookConfigSchema, InteractionResponseSchema;
|
|
23018
|
+
var _webhookPluginDeps, WebhookConfigSchema, InteractionResponseSchema;
|
|
22309
23019
|
var init_webhook = __esm(() => {
|
|
22310
23020
|
init_zod();
|
|
23021
|
+
_webhookPluginDeps = {
|
|
23022
|
+
sleep: (ms) => Bun.sleep(ms)
|
|
23023
|
+
};
|
|
22311
23024
|
WebhookConfigSchema = exports_external.object({
|
|
22312
23025
|
url: exports_external.string().url().optional(),
|
|
22313
23026
|
callbackPort: exports_external.number().int().min(1024).max(65535).optional(),
|
|
@@ -22346,8 +23059,8 @@ class AutoInteractionPlugin {
|
|
|
22346
23059
|
return;
|
|
22347
23060
|
}
|
|
22348
23061
|
try {
|
|
22349
|
-
if (
|
|
22350
|
-
const decision2 = await
|
|
23062
|
+
if (_deps3.callLlm) {
|
|
23063
|
+
const decision2 = await _deps3.callLlm(request);
|
|
22351
23064
|
if (decision2.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
22352
23065
|
return;
|
|
22353
23066
|
}
|
|
@@ -22376,7 +23089,7 @@ class AutoInteractionPlugin {
|
|
|
22376
23089
|
}
|
|
22377
23090
|
async callLlm(request) {
|
|
22378
23091
|
const prompt = this.buildPrompt(request);
|
|
22379
|
-
const adapter =
|
|
23092
|
+
const adapter = _deps3.adapter;
|
|
22380
23093
|
if (!adapter) {
|
|
22381
23094
|
throw new Error("Auto plugin requires adapter to be injected via _deps.adapter");
|
|
22382
23095
|
}
|
|
@@ -22464,7 +23177,7 @@ Respond with ONLY this JSON (no markdown, no explanation):
|
|
|
22464
23177
|
return parsed;
|
|
22465
23178
|
}
|
|
22466
23179
|
}
|
|
22467
|
-
var AutoConfigSchema,
|
|
23180
|
+
var AutoConfigSchema, _deps3;
|
|
22468
23181
|
var init_auto = __esm(() => {
|
|
22469
23182
|
init_zod();
|
|
22470
23183
|
init_config();
|
|
@@ -22474,7 +23187,7 @@ var init_auto = __esm(() => {
|
|
|
22474
23187
|
maxCostPerDecision: exports_external.number().positive().optional(),
|
|
22475
23188
|
naxConfig: exports_external.any().optional()
|
|
22476
23189
|
});
|
|
22477
|
-
|
|
23190
|
+
_deps3 = {
|
|
22478
23191
|
adapter: null,
|
|
22479
23192
|
callLlm: null
|
|
22480
23193
|
};
|
|
@@ -23155,7 +23868,7 @@ async function runReview(config2, workdir, executionConfig) {
|
|
|
23155
23868
|
const logger = getSafeLogger();
|
|
23156
23869
|
const checks3 = [];
|
|
23157
23870
|
let firstFailure;
|
|
23158
|
-
const allUncommittedFiles = await
|
|
23871
|
+
const allUncommittedFiles = await _deps4.getUncommittedFiles(workdir);
|
|
23159
23872
|
const NAX_RUNTIME_FILES = new Set(["nax/status.json", ".nax-verifier-verdict.json"]);
|
|
23160
23873
|
const uncommittedFiles = allUncommittedFiles.filter((f) => !NAX_RUNTIME_FILES.has(f) && !f.match(/^nax\/features\/.+\/prd\.json$/));
|
|
23161
23874
|
if (uncommittedFiles.length > 0) {
|
|
@@ -23195,11 +23908,11 @@ Stage and commit these files before running review.`
|
|
|
23195
23908
|
failureReason: firstFailure
|
|
23196
23909
|
};
|
|
23197
23910
|
}
|
|
23198
|
-
var _reviewRunnerDeps, REVIEW_CHECK_TIMEOUT_MS = 120000, SIGKILL_GRACE_PERIOD_MS2 = 5000,
|
|
23911
|
+
var _reviewRunnerDeps, REVIEW_CHECK_TIMEOUT_MS = 120000, SIGKILL_GRACE_PERIOD_MS2 = 5000, _deps4;
|
|
23199
23912
|
var init_runner2 = __esm(() => {
|
|
23200
23913
|
init_logger2();
|
|
23201
23914
|
_reviewRunnerDeps = { spawn, file: Bun.file };
|
|
23202
|
-
|
|
23915
|
+
_deps4 = {
|
|
23203
23916
|
getUncommittedFiles: getUncommittedFilesImpl
|
|
23204
23917
|
};
|
|
23205
23918
|
});
|
|
@@ -23460,10 +24173,10 @@ var init_autofix = __esm(() => {
|
|
|
23460
24173
|
|
|
23461
24174
|
// src/execution/progress.ts
|
|
23462
24175
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
23463
|
-
import { join as
|
|
24176
|
+
import { join as join15 } from "path";
|
|
23464
24177
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
23465
24178
|
mkdirSync2(featureDir, { recursive: true });
|
|
23466
|
-
const progressPath =
|
|
24179
|
+
const progressPath = join15(featureDir, "progress.txt");
|
|
23467
24180
|
const timestamp = new Date().toISOString();
|
|
23468
24181
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
23469
24182
|
`;
|
|
@@ -23547,7 +24260,7 @@ function estimateTokens(text) {
|
|
|
23547
24260
|
|
|
23548
24261
|
// src/constitution/loader.ts
|
|
23549
24262
|
import { existsSync as existsSync13 } from "fs";
|
|
23550
|
-
import { join as
|
|
24263
|
+
import { join as join16 } from "path";
|
|
23551
24264
|
function truncateToTokens(text, maxTokens) {
|
|
23552
24265
|
const maxChars = maxTokens * 3;
|
|
23553
24266
|
if (text.length <= maxChars) {
|
|
@@ -23569,7 +24282,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
23569
24282
|
}
|
|
23570
24283
|
let combinedContent = "";
|
|
23571
24284
|
if (!config2.skipGlobal) {
|
|
23572
|
-
const globalPath =
|
|
24285
|
+
const globalPath = join16(globalConfigDir(), config2.path);
|
|
23573
24286
|
if (existsSync13(globalPath)) {
|
|
23574
24287
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
23575
24288
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -23579,7 +24292,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
23579
24292
|
}
|
|
23580
24293
|
}
|
|
23581
24294
|
}
|
|
23582
|
-
const projectPath =
|
|
24295
|
+
const projectPath = join16(projectDir, config2.path);
|
|
23583
24296
|
if (existsSync13(projectPath)) {
|
|
23584
24297
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
23585
24298
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -24304,7 +25017,7 @@ async function addFileElements(elements, storyContext, story) {
|
|
|
24304
25017
|
if (contextFiles.length === 0 && storyContext.config?.context?.autoDetect?.enabled !== false && storyContext.workdir) {
|
|
24305
25018
|
const autoDetectConfig = storyContext.config?.context?.autoDetect;
|
|
24306
25019
|
try {
|
|
24307
|
-
const detected = await
|
|
25020
|
+
const detected = await _deps5.autoDetectContextFiles({
|
|
24308
25021
|
workdir: storyContext.workdir,
|
|
24309
25022
|
storyTitle: story.title,
|
|
24310
25023
|
maxFiles: autoDetectConfig?.maxFiles ?? 5,
|
|
@@ -24362,7 +25075,7 @@ ${content}
|
|
|
24362
25075
|
}
|
|
24363
25076
|
}
|
|
24364
25077
|
}
|
|
24365
|
-
var
|
|
25078
|
+
var _deps5;
|
|
24366
25079
|
var init_builder3 = __esm(() => {
|
|
24367
25080
|
init_logger2();
|
|
24368
25081
|
init_prd();
|
|
@@ -24370,7 +25083,7 @@ var init_builder3 = __esm(() => {
|
|
|
24370
25083
|
init_elements();
|
|
24371
25084
|
init_test_scanner();
|
|
24372
25085
|
init_elements();
|
|
24373
|
-
|
|
25086
|
+
_deps5 = {
|
|
24374
25087
|
autoDetectContextFiles
|
|
24375
25088
|
};
|
|
24376
25089
|
});
|
|
@@ -24605,6 +25318,15 @@ ${pluginMarkdown}` : pluginMarkdown;
|
|
|
24605
25318
|
function validateAgentForTier(agent, tier) {
|
|
24606
25319
|
return agent.capabilities.supportedTiers.includes(tier);
|
|
24607
25320
|
}
|
|
25321
|
+
function validateAgentFeature(agent, feature) {
|
|
25322
|
+
return agent.capabilities.features.has(feature);
|
|
25323
|
+
}
|
|
25324
|
+
function describeAgentCapabilities(agent) {
|
|
25325
|
+
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
25326
|
+
const features = Array.from(agent.capabilities.features).join(",");
|
|
25327
|
+
const maxTokens = agent.capabilities.maxContextTokens;
|
|
25328
|
+
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
25329
|
+
}
|
|
24608
25330
|
|
|
24609
25331
|
// src/agents/version-detection.ts
|
|
24610
25332
|
async function getAgentVersion(binaryName) {
|
|
@@ -24655,6 +25377,26 @@ var init_version_detection = __esm(() => {
|
|
|
24655
25377
|
});
|
|
24656
25378
|
|
|
24657
25379
|
// src/agents/index.ts
|
|
25380
|
+
var exports_agents = {};
|
|
25381
|
+
__export(exports_agents, {
|
|
25382
|
+
validateAgentForTier: () => validateAgentForTier,
|
|
25383
|
+
validateAgentFeature: () => validateAgentFeature,
|
|
25384
|
+
parseTokenUsage: () => parseTokenUsage,
|
|
25385
|
+
getInstalledAgents: () => getInstalledAgents,
|
|
25386
|
+
getAllAgentNames: () => getAllAgentNames,
|
|
25387
|
+
getAgentVersions: () => getAgentVersions,
|
|
25388
|
+
getAgentVersion: () => getAgentVersion,
|
|
25389
|
+
getAgent: () => getAgent,
|
|
25390
|
+
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
25391
|
+
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
25392
|
+
estimateCostByDuration: () => estimateCostByDuration,
|
|
25393
|
+
estimateCost: () => estimateCost,
|
|
25394
|
+
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
25395
|
+
checkAgentHealth: () => checkAgentHealth,
|
|
25396
|
+
CompleteError: () => CompleteError,
|
|
25397
|
+
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
25398
|
+
COST_RATES: () => COST_RATES
|
|
25399
|
+
});
|
|
24658
25400
|
var init_agents = __esm(() => {
|
|
24659
25401
|
init_types2();
|
|
24660
25402
|
init_claude();
|
|
@@ -24732,14 +25474,14 @@ var init_isolation = __esm(() => {
|
|
|
24732
25474
|
|
|
24733
25475
|
// src/context/greenfield.ts
|
|
24734
25476
|
import { readdir } from "fs/promises";
|
|
24735
|
-
import { join as
|
|
25477
|
+
import { join as join17 } from "path";
|
|
24736
25478
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
24737
25479
|
const results = [];
|
|
24738
25480
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
24739
25481
|
try {
|
|
24740
25482
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
24741
25483
|
for (const entry of entries) {
|
|
24742
|
-
const fullPath =
|
|
25484
|
+
const fullPath = join17(dir, entry.name);
|
|
24743
25485
|
if (entry.isDirectory()) {
|
|
24744
25486
|
if (ignoreDirs.has(entry.name))
|
|
24745
25487
|
continue;
|
|
@@ -24823,6 +25565,30 @@ function detectMergeConflict(output) {
|
|
|
24823
25565
|
async function autoCommitIfDirty(workdir, stage, role, storyId) {
|
|
24824
25566
|
const logger = getSafeLogger();
|
|
24825
25567
|
try {
|
|
25568
|
+
const topLevelProc = _gitDeps.spawn(["git", "rev-parse", "--show-toplevel"], {
|
|
25569
|
+
cwd: workdir,
|
|
25570
|
+
stdout: "pipe",
|
|
25571
|
+
stderr: "pipe"
|
|
25572
|
+
});
|
|
25573
|
+
const gitRoot = (await new Response(topLevelProc.stdout).text()).trim();
|
|
25574
|
+
await topLevelProc.exited;
|
|
25575
|
+
const { realpathSync: realpathSync3 } = await import("fs");
|
|
25576
|
+
const realWorkdir = (() => {
|
|
25577
|
+
try {
|
|
25578
|
+
return realpathSync3(workdir);
|
|
25579
|
+
} catch {
|
|
25580
|
+
return workdir;
|
|
25581
|
+
}
|
|
25582
|
+
})();
|
|
25583
|
+
const realGitRoot = (() => {
|
|
25584
|
+
try {
|
|
25585
|
+
return realpathSync3(gitRoot);
|
|
25586
|
+
} catch {
|
|
25587
|
+
return gitRoot;
|
|
25588
|
+
}
|
|
25589
|
+
})();
|
|
25590
|
+
if (realWorkdir !== realGitRoot)
|
|
25591
|
+
return;
|
|
24826
25592
|
const statusProc = _gitDeps.spawn(["git", "status", "--porcelain"], {
|
|
24827
25593
|
cwd: workdir,
|
|
24828
25594
|
stdout: "pipe",
|
|
@@ -25127,13 +25893,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
25127
25893
|
|
|
25128
25894
|
// src/verification/runners.ts
|
|
25129
25895
|
import { existsSync as existsSync14 } from "fs";
|
|
25130
|
-
import { join as
|
|
25896
|
+
import { join as join18 } from "path";
|
|
25131
25897
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
25132
25898
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
25133
25899
|
return { success: true, missingFiles: [] };
|
|
25134
25900
|
const missingFiles = [];
|
|
25135
25901
|
for (const file2 of expectedFiles) {
|
|
25136
|
-
if (!existsSync14(
|
|
25902
|
+
if (!existsSync14(join18(workingDirectory, file2)))
|
|
25137
25903
|
missingFiles.push(file2);
|
|
25138
25904
|
}
|
|
25139
25905
|
if (missingFiles.length > 0) {
|
|
@@ -25208,11 +25974,15 @@ async function fullSuite(options) {
|
|
|
25208
25974
|
return runVerificationCore(options);
|
|
25209
25975
|
}
|
|
25210
25976
|
async function regression(options) {
|
|
25211
|
-
await
|
|
25977
|
+
await _regressionRunnerDeps.sleep(2000);
|
|
25212
25978
|
return runVerificationCore({ ...options, expectedFiles: undefined });
|
|
25213
25979
|
}
|
|
25980
|
+
var _regressionRunnerDeps;
|
|
25214
25981
|
var init_runners = __esm(() => {
|
|
25215
25982
|
init_executor();
|
|
25983
|
+
_regressionRunnerDeps = {
|
|
25984
|
+
sleep: (ms) => Bun.sleep(ms)
|
|
25985
|
+
};
|
|
25216
25986
|
});
|
|
25217
25987
|
|
|
25218
25988
|
// src/verification/rectification.ts
|
|
@@ -25345,7 +26115,7 @@ var init_prompts = __esm(() => {
|
|
|
25345
26115
|
});
|
|
25346
26116
|
|
|
25347
26117
|
// src/tdd/rectification-gate.ts
|
|
25348
|
-
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger) {
|
|
26118
|
+
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName) {
|
|
25349
26119
|
const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
|
|
25350
26120
|
if (!rectificationEnabled)
|
|
25351
26121
|
return false;
|
|
@@ -25361,7 +26131,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
25361
26131
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
25362
26132
|
const testSummary = parseBunTestOutput(fullSuiteResult.output);
|
|
25363
26133
|
if (testSummary.failed > 0) {
|
|
25364
|
-
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout);
|
|
26134
|
+
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName);
|
|
25365
26135
|
}
|
|
25366
26136
|
if (testSummary.passed > 0) {
|
|
25367
26137
|
logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
|
|
@@ -25389,7 +26159,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
25389
26159
|
});
|
|
25390
26160
|
return false;
|
|
25391
26161
|
}
|
|
25392
|
-
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout) {
|
|
26162
|
+
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName) {
|
|
25393
26163
|
const rectificationState = {
|
|
25394
26164
|
attempt: 0,
|
|
25395
26165
|
initialFailures: testSummary.failed,
|
|
@@ -25411,7 +26181,10 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
25411
26181
|
modelTier: implementerTier,
|
|
25412
26182
|
modelDef: resolveModel(config2.models[implementerTier]),
|
|
25413
26183
|
timeoutSeconds: config2.execution.sessionTimeoutSeconds,
|
|
25414
|
-
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions
|
|
26184
|
+
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions,
|
|
26185
|
+
featureName,
|
|
26186
|
+
storyId: story.id,
|
|
26187
|
+
sessionRole: "implementer"
|
|
25415
26188
|
});
|
|
25416
26189
|
if (!rectifyResult.success && rectifyResult.pid) {
|
|
25417
26190
|
await cleanupProcessTree(rectifyResult.pid);
|
|
@@ -25808,13 +26581,13 @@ var exports_loader = {};
|
|
|
25808
26581
|
__export(exports_loader, {
|
|
25809
26582
|
loadOverride: () => loadOverride
|
|
25810
26583
|
});
|
|
25811
|
-
import { join as
|
|
26584
|
+
import { join as join19 } from "path";
|
|
25812
26585
|
async function loadOverride(role, workdir, config2) {
|
|
25813
26586
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
25814
26587
|
if (!overridePath) {
|
|
25815
26588
|
return null;
|
|
25816
26589
|
}
|
|
25817
|
-
const absolutePath =
|
|
26590
|
+
const absolutePath = join19(workdir, overridePath);
|
|
25818
26591
|
const file2 = Bun.file(absolutePath);
|
|
25819
26592
|
if (!await file2.exists()) {
|
|
25820
26593
|
return null;
|
|
@@ -25993,7 +26766,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
25993
26766
|
}
|
|
25994
26767
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
25995
26768
|
}
|
|
25996
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution) {
|
|
26769
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName) {
|
|
25997
26770
|
const startTime = Date.now();
|
|
25998
26771
|
let prompt;
|
|
25999
26772
|
switch (role) {
|
|
@@ -26015,7 +26788,10 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
26015
26788
|
modelTier,
|
|
26016
26789
|
modelDef: resolveModel(config2.models[modelTier]),
|
|
26017
26790
|
timeoutSeconds: config2.execution.sessionTimeoutSeconds,
|
|
26018
|
-
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions
|
|
26791
|
+
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions,
|
|
26792
|
+
featureName,
|
|
26793
|
+
storyId: story.id,
|
|
26794
|
+
sessionRole: role
|
|
26019
26795
|
});
|
|
26020
26796
|
if (!result.success && result.pid) {
|
|
26021
26797
|
await cleanupProcessTree(result.pid);
|
|
@@ -26035,7 +26811,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
26035
26811
|
exitCode: result.exitCode
|
|
26036
26812
|
});
|
|
26037
26813
|
}
|
|
26038
|
-
await autoCommitIfDirty(workdir, "tdd", role, story.id);
|
|
26814
|
+
await _sessionRunnerDeps.autoCommitIfDirty(workdir, "tdd", role, story.id);
|
|
26039
26815
|
let isolation;
|
|
26040
26816
|
if (!skipIsolation) {
|
|
26041
26817
|
if (role === "test-writer") {
|
|
@@ -26082,6 +26858,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
26082
26858
|
estimatedCost: result.estimatedCost
|
|
26083
26859
|
};
|
|
26084
26860
|
}
|
|
26861
|
+
var _sessionRunnerDeps;
|
|
26085
26862
|
var init_session_runner = __esm(() => {
|
|
26086
26863
|
init_config();
|
|
26087
26864
|
init_logger2();
|
|
@@ -26089,6 +26866,9 @@ var init_session_runner = __esm(() => {
|
|
|
26089
26866
|
init_git();
|
|
26090
26867
|
init_cleanup();
|
|
26091
26868
|
init_isolation();
|
|
26869
|
+
_sessionRunnerDeps = {
|
|
26870
|
+
autoCommitIfDirty
|
|
26871
|
+
};
|
|
26092
26872
|
});
|
|
26093
26873
|
|
|
26094
26874
|
// src/tdd/verdict-reader.ts
|
|
@@ -26363,6 +27143,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26363
27143
|
config: config2,
|
|
26364
27144
|
workdir,
|
|
26365
27145
|
modelTier,
|
|
27146
|
+
featureName,
|
|
26366
27147
|
contextMarkdown,
|
|
26367
27148
|
constitution,
|
|
26368
27149
|
dryRun = false,
|
|
@@ -26426,7 +27207,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26426
27207
|
let session1;
|
|
26427
27208
|
if (!isRetry) {
|
|
26428
27209
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
26429
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution);
|
|
27210
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
26430
27211
|
sessions.push(session1);
|
|
26431
27212
|
}
|
|
26432
27213
|
if (session1 && !session1.success) {
|
|
@@ -26488,7 +27269,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26488
27269
|
});
|
|
26489
27270
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
26490
27271
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
26491
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution);
|
|
27272
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
26492
27273
|
sessions.push(session2);
|
|
26493
27274
|
if (!session2.success) {
|
|
26494
27275
|
needsHumanReview = true;
|
|
@@ -26504,10 +27285,10 @@ async function runThreeSessionTdd(options) {
|
|
|
26504
27285
|
lite
|
|
26505
27286
|
};
|
|
26506
27287
|
}
|
|
26507
|
-
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger);
|
|
27288
|
+
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName);
|
|
26508
27289
|
const session3Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
26509
27290
|
const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
|
|
26510
|
-
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution);
|
|
27291
|
+
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName);
|
|
26511
27292
|
sessions.push(session3);
|
|
26512
27293
|
const verdict = await readVerdict(workdir);
|
|
26513
27294
|
await cleanupVerdict(workdir);
|
|
@@ -26666,7 +27447,7 @@ var init_execution = __esm(() => {
|
|
|
26666
27447
|
enabled: () => true,
|
|
26667
27448
|
async execute(ctx) {
|
|
26668
27449
|
const logger = getLogger();
|
|
26669
|
-
const agent = _executionDeps.getAgent(ctx.config.autoMode.defaultAgent);
|
|
27450
|
+
const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.config.autoMode.defaultAgent);
|
|
26670
27451
|
if (!agent) {
|
|
26671
27452
|
return {
|
|
26672
27453
|
action: "fail",
|
|
@@ -26686,6 +27467,7 @@ var init_execution = __esm(() => {
|
|
|
26686
27467
|
config: ctx.config,
|
|
26687
27468
|
workdir: ctx.workdir,
|
|
26688
27469
|
modelTier: ctx.routing.modelTier,
|
|
27470
|
+
featureName: ctx.prd.feature,
|
|
26689
27471
|
contextMarkdown: ctx.contextMarkdown,
|
|
26690
27472
|
constitution: ctx.constitution?.content,
|
|
26691
27473
|
dryRun: false,
|
|
@@ -26755,7 +27537,38 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
26755
27537
|
modelTier: ctx.routing.modelTier,
|
|
26756
27538
|
modelDef: resolveModel(ctx.config.models[ctx.routing.modelTier]),
|
|
26757
27539
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
26758
|
-
dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions
|
|
27540
|
+
dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions,
|
|
27541
|
+
pidRegistry: ctx.pidRegistry,
|
|
27542
|
+
featureName: ctx.prd.feature,
|
|
27543
|
+
storyId: ctx.story.id,
|
|
27544
|
+
interactionBridge: (() => {
|
|
27545
|
+
const plugin = ctx.interaction?.getPrimary();
|
|
27546
|
+
if (!plugin)
|
|
27547
|
+
return;
|
|
27548
|
+
const QUESTION_PATTERNS = [/\?/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
|
|
27549
|
+
return {
|
|
27550
|
+
detectQuestion: async (text) => QUESTION_PATTERNS.some((p) => p.test(text)),
|
|
27551
|
+
onQuestionDetected: async (text) => {
|
|
27552
|
+
const requestId = `ix-acp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
27553
|
+
await plugin.send({
|
|
27554
|
+
id: requestId,
|
|
27555
|
+
type: "input",
|
|
27556
|
+
featureName: ctx.prd.feature,
|
|
27557
|
+
storyId: ctx.story.id,
|
|
27558
|
+
stage: "execution",
|
|
27559
|
+
summary: text,
|
|
27560
|
+
fallback: "continue",
|
|
27561
|
+
createdAt: Date.now()
|
|
27562
|
+
});
|
|
27563
|
+
try {
|
|
27564
|
+
const response = await plugin.receive(requestId, 120000);
|
|
27565
|
+
return response.value ?? "continue";
|
|
27566
|
+
} catch {
|
|
27567
|
+
return "continue";
|
|
27568
|
+
}
|
|
27569
|
+
}
|
|
27570
|
+
};
|
|
27571
|
+
})()
|
|
26759
27572
|
});
|
|
26760
27573
|
ctx.agentResult = result;
|
|
26761
27574
|
await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
|
|
@@ -27971,16 +28784,20 @@ var init_regression2 = __esm(() => {
|
|
|
27971
28784
|
});
|
|
27972
28785
|
|
|
27973
28786
|
// src/pipeline/stages/routing.ts
|
|
27974
|
-
async function runDecompose(story, prd, config2, _workdir) {
|
|
28787
|
+
async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
|
|
27975
28788
|
const naxDecompose = config2.decompose;
|
|
27976
28789
|
const builderConfig = {
|
|
27977
28790
|
maxSubStories: naxDecompose?.maxSubstories ?? 5,
|
|
27978
28791
|
maxComplexity: naxDecompose?.maxSubstoryComplexity ?? "medium",
|
|
27979
28792
|
maxRetries: naxDecompose?.maxRetries ?? 2
|
|
27980
28793
|
};
|
|
28794
|
+
const agent = (agentGetFn ?? getAgent)(config2.autoMode.defaultAgent);
|
|
28795
|
+
if (!agent) {
|
|
28796
|
+
throw new Error(`[decompose] Agent "${config2.autoMode.defaultAgent}" not found \u2014 cannot decompose`);
|
|
28797
|
+
}
|
|
27981
28798
|
const adapter = {
|
|
27982
|
-
async decompose(
|
|
27983
|
-
|
|
28799
|
+
async decompose(prompt) {
|
|
28800
|
+
return agent.complete(prompt, { jsonMode: true });
|
|
27984
28801
|
}
|
|
27985
28802
|
};
|
|
27986
28803
|
return DecomposeBuilder.for(story).prd(prd).config(builderConfig).decompose(adapter);
|
|
@@ -28001,7 +28818,7 @@ var init_routing2 = __esm(() => {
|
|
|
28001
28818
|
async execute(ctx) {
|
|
28002
28819
|
const logger = getLogger();
|
|
28003
28820
|
const agentName = ctx.config.execution?.agent ?? "claude";
|
|
28004
|
-
const adapter = _routingDeps.getAgent(agentName);
|
|
28821
|
+
const adapter = (ctx.agentGetFn ?? _routingDeps.getAgent)(agentName);
|
|
28005
28822
|
const hasExistingRouting = ctx.story.routing !== undefined;
|
|
28006
28823
|
const hasContentHash = ctx.story.routing?.contentHash !== undefined;
|
|
28007
28824
|
let currentHash;
|
|
@@ -28070,7 +28887,7 @@ var init_routing2 = __esm(() => {
|
|
|
28070
28887
|
if (decomposeConfig.trigger === "disabled") {
|
|
28071
28888
|
logger.warn("routing", `Story ${ctx.story.id} is oversized (${acCount} ACs) but decompose is disabled \u2014 continuing with original`);
|
|
28072
28889
|
} else if (decomposeConfig.trigger === "auto") {
|
|
28073
|
-
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
|
|
28890
|
+
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
|
|
28074
28891
|
if (result.validation.valid) {
|
|
28075
28892
|
_routingDeps.applyDecomposition(ctx.prd, result);
|
|
28076
28893
|
if (ctx.prdPath) {
|
|
@@ -28085,7 +28902,7 @@ var init_routing2 = __esm(() => {
|
|
|
28085
28902
|
} else if (decomposeConfig.trigger === "confirm") {
|
|
28086
28903
|
const action = await _routingDeps.checkStoryOversized({ featureName: ctx.prd.feature, storyId: ctx.story.id, criteriaCount: acCount }, ctx.config, ctx.interaction);
|
|
28087
28904
|
if (action === "decompose") {
|
|
28088
|
-
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
|
|
28905
|
+
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
|
|
28089
28906
|
if (result.validation.valid) {
|
|
28090
28907
|
_routingDeps.applyDecomposition(ctx.prd, result);
|
|
28091
28908
|
if (ctx.prdPath) {
|
|
@@ -28987,7 +29804,7 @@ var init_checks_config = () => {};
|
|
|
28987
29804
|
async function checkAgentCLI(config2) {
|
|
28988
29805
|
const agent = config2.execution?.agent || "claude";
|
|
28989
29806
|
try {
|
|
28990
|
-
const proc =
|
|
29807
|
+
const proc = _deps7.spawn([agent, "--version"], {
|
|
28991
29808
|
stdout: "pipe",
|
|
28992
29809
|
stderr: "pipe"
|
|
28993
29810
|
});
|
|
@@ -29008,9 +29825,9 @@ async function checkAgentCLI(config2) {
|
|
|
29008
29825
|
};
|
|
29009
29826
|
}
|
|
29010
29827
|
}
|
|
29011
|
-
var
|
|
29828
|
+
var _deps7;
|
|
29012
29829
|
var init_checks_cli = __esm(() => {
|
|
29013
|
-
|
|
29830
|
+
_deps7 = {
|
|
29014
29831
|
spawn: Bun.spawn
|
|
29015
29832
|
};
|
|
29016
29833
|
});
|
|
@@ -29573,19 +30390,19 @@ var init_precheck = __esm(() => {
|
|
|
29573
30390
|
});
|
|
29574
30391
|
|
|
29575
30392
|
// src/hooks/runner.ts
|
|
29576
|
-
import { join as
|
|
30393
|
+
import { join as join37 } from "path";
|
|
29577
30394
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
29578
30395
|
let globalHooks = { hooks: {} };
|
|
29579
30396
|
let projectHooks = { hooks: {} };
|
|
29580
30397
|
let skipGlobal = false;
|
|
29581
|
-
const projectPath =
|
|
30398
|
+
const projectPath = join37(projectDir, "hooks.json");
|
|
29582
30399
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
29583
30400
|
if (projectData) {
|
|
29584
30401
|
projectHooks = projectData;
|
|
29585
30402
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
29586
30403
|
}
|
|
29587
30404
|
if (!skipGlobal && globalDir) {
|
|
29588
|
-
const globalPath =
|
|
30405
|
+
const globalPath = join37(globalDir, "hooks.json");
|
|
29589
30406
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
29590
30407
|
if (globalData) {
|
|
29591
30408
|
globalHooks = globalData;
|
|
@@ -30026,7 +30843,8 @@ function buildResult(success2, prd, totalCost, iterations, storiesCompleted, prd
|
|
|
30026
30843
|
}
|
|
30027
30844
|
async function generateAndAddFixStories(ctx, failures, prd) {
|
|
30028
30845
|
const logger = getSafeLogger();
|
|
30029
|
-
const
|
|
30846
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
30847
|
+
const agent = (ctx.agentGetFn ?? getAgent2)(ctx.config.autoMode.defaultAgent);
|
|
30030
30848
|
if (!agent) {
|
|
30031
30849
|
logger?.error("acceptance", "Agent not found, cannot generate fix stories");
|
|
30032
30850
|
return null;
|
|
@@ -30172,7 +30990,6 @@ async function runAcceptanceLoop(ctx) {
|
|
|
30172
30990
|
}
|
|
30173
30991
|
var init_acceptance_loop = __esm(() => {
|
|
30174
30992
|
init_acceptance();
|
|
30175
|
-
init_agents();
|
|
30176
30993
|
init_schema();
|
|
30177
30994
|
init_hooks();
|
|
30178
30995
|
init_logger2();
|
|
@@ -30599,35 +31416,18 @@ var init_headless_formatter = __esm(() => {
|
|
|
30599
31416
|
init_version();
|
|
30600
31417
|
});
|
|
30601
31418
|
|
|
30602
|
-
// src/prd/validate.ts
|
|
30603
|
-
function validateStoryId(id) {
|
|
30604
|
-
if (!id || id.length === 0) {
|
|
30605
|
-
throw new Error("Story ID cannot be empty");
|
|
30606
|
-
}
|
|
30607
|
-
if (id.includes("..")) {
|
|
30608
|
-
throw new Error("Story ID cannot contain path traversal (..)");
|
|
30609
|
-
}
|
|
30610
|
-
if (id.startsWith("--")) {
|
|
30611
|
-
throw new Error("Story ID cannot start with git flags (--)");
|
|
30612
|
-
}
|
|
30613
|
-
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
30614
|
-
if (!validPattern.test(id)) {
|
|
30615
|
-
throw new Error(`Story ID must match pattern [a-zA-Z0-9][a-zA-Z0-9._-]{0,63}. Got: ${id}`);
|
|
30616
|
-
}
|
|
30617
|
-
}
|
|
30618
|
-
|
|
30619
31419
|
// src/worktree/manager.ts
|
|
30620
31420
|
var exports_manager = {};
|
|
30621
31421
|
__export(exports_manager, {
|
|
30622
31422
|
WorktreeManager: () => WorktreeManager
|
|
30623
31423
|
});
|
|
30624
31424
|
import { existsSync as existsSync30, symlinkSync } from "fs";
|
|
30625
|
-
import { join as
|
|
31425
|
+
import { join as join38 } from "path";
|
|
30626
31426
|
|
|
30627
31427
|
class WorktreeManager {
|
|
30628
31428
|
async create(projectRoot, storyId) {
|
|
30629
31429
|
validateStoryId(storyId);
|
|
30630
|
-
const worktreePath =
|
|
31430
|
+
const worktreePath = join38(projectRoot, ".nax-wt", storyId);
|
|
30631
31431
|
const branchName = `nax/${storyId}`;
|
|
30632
31432
|
try {
|
|
30633
31433
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -30652,9 +31452,9 @@ class WorktreeManager {
|
|
|
30652
31452
|
}
|
|
30653
31453
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
30654
31454
|
}
|
|
30655
|
-
const nodeModulesSource =
|
|
31455
|
+
const nodeModulesSource = join38(projectRoot, "node_modules");
|
|
30656
31456
|
if (existsSync30(nodeModulesSource)) {
|
|
30657
|
-
const nodeModulesTarget =
|
|
31457
|
+
const nodeModulesTarget = join38(worktreePath, "node_modules");
|
|
30658
31458
|
try {
|
|
30659
31459
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
30660
31460
|
} catch (error48) {
|
|
@@ -30662,9 +31462,9 @@ class WorktreeManager {
|
|
|
30662
31462
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
30663
31463
|
}
|
|
30664
31464
|
}
|
|
30665
|
-
const envSource =
|
|
31465
|
+
const envSource = join38(projectRoot, ".env");
|
|
30666
31466
|
if (existsSync30(envSource)) {
|
|
30667
|
-
const envTarget =
|
|
31467
|
+
const envTarget = join38(worktreePath, ".env");
|
|
30668
31468
|
try {
|
|
30669
31469
|
symlinkSync(envSource, envTarget, "file");
|
|
30670
31470
|
} catch (error48) {
|
|
@@ -30675,7 +31475,7 @@ class WorktreeManager {
|
|
|
30675
31475
|
}
|
|
30676
31476
|
async remove(projectRoot, storyId) {
|
|
30677
31477
|
validateStoryId(storyId);
|
|
30678
|
-
const worktreePath =
|
|
31478
|
+
const worktreePath = join38(projectRoot, ".nax-wt", storyId);
|
|
30679
31479
|
const branchName = `nax/${storyId}`;
|
|
30680
31480
|
try {
|
|
30681
31481
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -31065,7 +31865,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
31065
31865
|
|
|
31066
31866
|
// src/execution/parallel-coordinator.ts
|
|
31067
31867
|
import os3 from "os";
|
|
31068
|
-
import { join as
|
|
31868
|
+
import { join as join39 } from "path";
|
|
31069
31869
|
function groupStoriesByDependencies(stories) {
|
|
31070
31870
|
const batches = [];
|
|
31071
31871
|
const processed = new Set;
|
|
@@ -31142,7 +31942,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
31142
31942
|
};
|
|
31143
31943
|
const worktreePaths = new Map;
|
|
31144
31944
|
for (const story of batch) {
|
|
31145
|
-
const worktreePath =
|
|
31945
|
+
const worktreePath = join39(projectRoot, ".nax-wt", story.id);
|
|
31146
31946
|
try {
|
|
31147
31947
|
await worktreeManager.create(projectRoot, story.id);
|
|
31148
31948
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -31191,7 +31991,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
31191
31991
|
});
|
|
31192
31992
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
31193
31993
|
storyId: mergeResult.storyId,
|
|
31194
|
-
worktreePath:
|
|
31994
|
+
worktreePath: join39(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
31195
31995
|
});
|
|
31196
31996
|
}
|
|
31197
31997
|
}
|
|
@@ -31650,12 +32450,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
31650
32450
|
// src/pipeline/subscribers/events-writer.ts
|
|
31651
32451
|
import { appendFile as appendFile2, mkdir } from "fs/promises";
|
|
31652
32452
|
import { homedir as homedir5 } from "os";
|
|
31653
|
-
import { basename as basename3, join as
|
|
32453
|
+
import { basename as basename3, join as join40 } from "path";
|
|
31654
32454
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
31655
32455
|
const logger = getSafeLogger();
|
|
31656
32456
|
const project = basename3(workdir);
|
|
31657
|
-
const eventsDir =
|
|
31658
|
-
const eventsFile =
|
|
32457
|
+
const eventsDir = join40(homedir5(), ".nax", "events", project);
|
|
32458
|
+
const eventsFile = join40(eventsDir, "events.jsonl");
|
|
31659
32459
|
let dirReady = false;
|
|
31660
32460
|
const write = (line) => {
|
|
31661
32461
|
(async () => {
|
|
@@ -31815,12 +32615,12 @@ var init_interaction2 = __esm(() => {
|
|
|
31815
32615
|
// src/pipeline/subscribers/registry.ts
|
|
31816
32616
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
31817
32617
|
import { homedir as homedir6 } from "os";
|
|
31818
|
-
import { basename as basename4, join as
|
|
32618
|
+
import { basename as basename4, join as join41 } from "path";
|
|
31819
32619
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
31820
32620
|
const logger = getSafeLogger();
|
|
31821
32621
|
const project = basename4(workdir);
|
|
31822
|
-
const runDir =
|
|
31823
|
-
const metaFile =
|
|
32622
|
+
const runDir = join41(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
32623
|
+
const metaFile = join41(runDir, "meta.json");
|
|
31824
32624
|
const unsub = bus.on("run:started", (_ev) => {
|
|
31825
32625
|
(async () => {
|
|
31826
32626
|
try {
|
|
@@ -31830,8 +32630,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
31830
32630
|
project,
|
|
31831
32631
|
feature,
|
|
31832
32632
|
workdir,
|
|
31833
|
-
statusPath:
|
|
31834
|
-
eventsDir:
|
|
32633
|
+
statusPath: join41(workdir, "nax", "features", feature, "status.json"),
|
|
32634
|
+
eventsDir: join41(workdir, "nax", "features", feature, "runs"),
|
|
31835
32635
|
registeredAt: new Date().toISOString()
|
|
31836
32636
|
};
|
|
31837
32637
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -32499,6 +33299,8 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
32499
33299
|
storyStartTime: new Date().toISOString(),
|
|
32500
33300
|
storyGitRef: storyGitRef ?? undefined,
|
|
32501
33301
|
interaction: ctx.interactionChain ?? undefined,
|
|
33302
|
+
agentGetFn: ctx.agentGetFn,
|
|
33303
|
+
pidRegistry: ctx.pidRegistry,
|
|
32502
33304
|
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
32503
33305
|
};
|
|
32504
33306
|
ctx.statusWriter.setPrd(prd);
|
|
@@ -32825,7 +33627,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
32825
33627
|
var init_status_file = () => {};
|
|
32826
33628
|
|
|
32827
33629
|
// src/execution/status-writer.ts
|
|
32828
|
-
import { join as
|
|
33630
|
+
import { join as join42 } from "path";
|
|
32829
33631
|
|
|
32830
33632
|
class StatusWriter {
|
|
32831
33633
|
statusFile;
|
|
@@ -32893,7 +33695,7 @@ class StatusWriter {
|
|
|
32893
33695
|
if (!this._prd)
|
|
32894
33696
|
return;
|
|
32895
33697
|
const safeLogger = getSafeLogger();
|
|
32896
|
-
const featureStatusPath =
|
|
33698
|
+
const featureStatusPath = join42(featureDir, "status.json");
|
|
32897
33699
|
try {
|
|
32898
33700
|
const base = this.getSnapshot(totalCost, iterations);
|
|
32899
33701
|
if (!base) {
|
|
@@ -33097,6 +33899,7 @@ var init_precheck_runner = __esm(() => {
|
|
|
33097
33899
|
// src/execution/lifecycle/run-initialization.ts
|
|
33098
33900
|
var exports_run_initialization = {};
|
|
33099
33901
|
__export(exports_run_initialization, {
|
|
33902
|
+
logActiveProtocol: () => logActiveProtocol,
|
|
33100
33903
|
initializeRun: () => initializeRun
|
|
33101
33904
|
});
|
|
33102
33905
|
async function reconcileState(prd, prdPath, workdir) {
|
|
@@ -33123,11 +33926,12 @@ async function reconcileState(prd, prdPath, workdir) {
|
|
|
33123
33926
|
}
|
|
33124
33927
|
return prd;
|
|
33125
33928
|
}
|
|
33126
|
-
async function checkAgentInstalled(config2, dryRun) {
|
|
33929
|
+
async function checkAgentInstalled(config2, dryRun, agentGetFn) {
|
|
33127
33930
|
if (dryRun)
|
|
33128
33931
|
return;
|
|
33129
33932
|
const logger = getSafeLogger();
|
|
33130
|
-
const
|
|
33933
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
33934
|
+
const agent = (agentGetFn ?? getAgent2)(config2.autoMode.defaultAgent);
|
|
33131
33935
|
if (!agent) {
|
|
33132
33936
|
logger?.error("execution", "Agent not found", {
|
|
33133
33937
|
agent: config2.autoMode.defaultAgent
|
|
@@ -33155,9 +33959,14 @@ function validateStoryCount(counts, config2) {
|
|
|
33155
33959
|
throw new StoryLimitExceededError(counts.total, config2.execution.maxStoriesPerFeature);
|
|
33156
33960
|
}
|
|
33157
33961
|
}
|
|
33962
|
+
function logActiveProtocol(config2) {
|
|
33963
|
+
const logger = getSafeLogger();
|
|
33964
|
+
const protocol = config2.agent?.protocol ?? "cli";
|
|
33965
|
+
logger?.info("run-initialization", `Agent protocol: ${protocol}`, { protocol });
|
|
33966
|
+
}
|
|
33158
33967
|
async function initializeRun(ctx) {
|
|
33159
33968
|
const logger = getSafeLogger();
|
|
33160
|
-
await checkAgentInstalled(ctx.config, ctx.dryRun);
|
|
33969
|
+
await checkAgentInstalled(ctx.config, ctx.dryRun, ctx.agentGetFn);
|
|
33161
33970
|
let prd = await loadPRD(ctx.prdPath);
|
|
33162
33971
|
prd = await reconcileState(prd, ctx.prdPath, ctx.workdir);
|
|
33163
33972
|
const counts = countStories(prd);
|
|
@@ -33170,7 +33979,6 @@ async function initializeRun(ctx) {
|
|
|
33170
33979
|
return { prd, storyCounts: counts };
|
|
33171
33980
|
}
|
|
33172
33981
|
var init_run_initialization = __esm(() => {
|
|
33173
|
-
init_agents();
|
|
33174
33982
|
init_errors3();
|
|
33175
33983
|
init_logger2();
|
|
33176
33984
|
init_prd();
|
|
@@ -33279,7 +34087,8 @@ async function setupRun(options) {
|
|
|
33279
34087
|
config: config2,
|
|
33280
34088
|
prdPath,
|
|
33281
34089
|
workdir,
|
|
33282
|
-
dryRun
|
|
34090
|
+
dryRun,
|
|
34091
|
+
agentGetFn: options.agentGetFn
|
|
33283
34092
|
});
|
|
33284
34093
|
prd = initResult.prd;
|
|
33285
34094
|
const counts = initResult.storyCounts;
|
|
@@ -64209,7 +65018,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
64209
65018
|
init_source();
|
|
64210
65019
|
import { existsSync as existsSync32, mkdirSync as mkdirSync6 } from "fs";
|
|
64211
65020
|
import { homedir as homedir8 } from "os";
|
|
64212
|
-
import { join as
|
|
65021
|
+
import { join as join43 } from "path";
|
|
64213
65022
|
|
|
64214
65023
|
// node_modules/commander/esm.mjs
|
|
64215
65024
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -64231,14 +65040,14 @@ var {
|
|
|
64231
65040
|
init_acceptance();
|
|
64232
65041
|
init_registry();
|
|
64233
65042
|
import { existsSync as existsSync8 } from "fs";
|
|
64234
|
-
import { join as
|
|
65043
|
+
import { join as join9 } from "path";
|
|
64235
65044
|
|
|
64236
65045
|
// src/analyze/scanner.ts
|
|
64237
|
-
import { existsSync as existsSync2 } from "fs";
|
|
64238
|
-
import { join as
|
|
65046
|
+
import { existsSync as existsSync2, readdirSync } from "fs";
|
|
65047
|
+
import { join as join4 } from "path";
|
|
64239
65048
|
async function scanCodebase(workdir) {
|
|
64240
|
-
const srcPath =
|
|
64241
|
-
const packageJsonPath =
|
|
65049
|
+
const srcPath = join4(workdir, "src");
|
|
65050
|
+
const packageJsonPath = join4(workdir, "package.json");
|
|
64242
65051
|
const fileTree = existsSync2(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
|
|
64243
65052
|
let dependencies = {};
|
|
64244
65053
|
let devDependencies = {};
|
|
@@ -64263,30 +65072,23 @@ async function generateFileTree(dir, maxDepth, currentDepth = 0, prefix = "") {
|
|
|
64263
65072
|
}
|
|
64264
65073
|
const entries = [];
|
|
64265
65074
|
try {
|
|
64266
|
-
const dirEntries =
|
|
64267
|
-
cwd: dir,
|
|
64268
|
-
onlyFiles: false
|
|
64269
|
-
}));
|
|
65075
|
+
const dirEntries = readdirSync(dir, { withFileTypes: true });
|
|
64270
65076
|
dirEntries.sort((a, b) => {
|
|
64271
|
-
|
|
64272
|
-
const bIsDir = !b.includes(".");
|
|
64273
|
-
if (aIsDir && !bIsDir)
|
|
65077
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
64274
65078
|
return -1;
|
|
64275
|
-
if (!
|
|
65079
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
64276
65080
|
return 1;
|
|
64277
|
-
return a.localeCompare(b);
|
|
65081
|
+
return a.name.localeCompare(b.name);
|
|
64278
65082
|
});
|
|
64279
65083
|
for (let i = 0;i < dirEntries.length; i++) {
|
|
64280
|
-
const
|
|
64281
|
-
const fullPath = join3(dir, entry);
|
|
65084
|
+
const dirent = dirEntries[i];
|
|
64282
65085
|
const isLast = i === dirEntries.length - 1;
|
|
64283
65086
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
64284
65087
|
const childPrefix = isLast ? " " : "\u2502 ";
|
|
64285
|
-
const
|
|
64286
|
-
|
|
64287
|
-
entries.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
|
|
65088
|
+
const isDir = dirent.isDirectory();
|
|
65089
|
+
entries.push(`${prefix}${connector}${dirent.name}${isDir ? "/" : ""}`);
|
|
64288
65090
|
if (isDir) {
|
|
64289
|
-
const subtree = await generateFileTree(
|
|
65091
|
+
const subtree = await generateFileTree(join4(dir, dirent.name), maxDepth, currentDepth + 1, prefix + childPrefix);
|
|
64290
65092
|
if (subtree) {
|
|
64291
65093
|
entries.push(subtree);
|
|
64292
65094
|
}
|
|
@@ -64310,16 +65112,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
|
|
|
64310
65112
|
} else {
|
|
64311
65113
|
patterns.push("Test framework: likely bun:test (no framework dependency)");
|
|
64312
65114
|
}
|
|
64313
|
-
if (existsSync2(
|
|
65115
|
+
if (existsSync2(join4(workdir, "test"))) {
|
|
64314
65116
|
patterns.push("Test directory: test/");
|
|
64315
65117
|
}
|
|
64316
|
-
if (existsSync2(
|
|
65118
|
+
if (existsSync2(join4(workdir, "__tests__"))) {
|
|
64317
65119
|
patterns.push("Test directory: __tests__/");
|
|
64318
65120
|
}
|
|
64319
|
-
if (existsSync2(
|
|
65121
|
+
if (existsSync2(join4(workdir, "tests"))) {
|
|
64320
65122
|
patterns.push("Test directory: tests/");
|
|
64321
65123
|
}
|
|
64322
|
-
const hasTestFiles = existsSync2(
|
|
65124
|
+
const hasTestFiles = existsSync2(join4(workdir, "test")) || existsSync2(join4(workdir, "src"));
|
|
64323
65125
|
if (hasTestFiles) {
|
|
64324
65126
|
patterns.push("Test files: *.test.ts, *.spec.ts");
|
|
64325
65127
|
}
|
|
@@ -64337,7 +65139,7 @@ init_version();
|
|
|
64337
65139
|
// src/cli/analyze-parser.ts
|
|
64338
65140
|
init_registry();
|
|
64339
65141
|
import { existsSync as existsSync7 } from "fs";
|
|
64340
|
-
import { join as
|
|
65142
|
+
import { join as join8 } from "path";
|
|
64341
65143
|
init_schema();
|
|
64342
65144
|
init_logger2();
|
|
64343
65145
|
init_prd();
|
|
@@ -64467,7 +65269,7 @@ function estimateLOCFromComplexity(complexity) {
|
|
|
64467
65269
|
}
|
|
64468
65270
|
}
|
|
64469
65271
|
async function reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2) {
|
|
64470
|
-
const prdPath =
|
|
65272
|
+
const prdPath = join8(featureDir, "prd.json");
|
|
64471
65273
|
if (!existsSync7(prdPath)) {
|
|
64472
65274
|
throw new Error(`prd.json not found at ${prdPath}. Run analyze without --reclassify first.`);
|
|
64473
65275
|
}
|
|
@@ -64568,11 +65370,11 @@ function reclassifyWithKeywords(story, config2) {
|
|
|
64568
65370
|
// src/cli/analyze.ts
|
|
64569
65371
|
async function analyzeFeature(options) {
|
|
64570
65372
|
const { featureDir, featureName, branchName, config: config2, specPath: explicitSpecPath, reclassify = false } = options;
|
|
64571
|
-
const workdir =
|
|
65373
|
+
const workdir = join9(featureDir, "../..");
|
|
64572
65374
|
if (reclassify) {
|
|
64573
65375
|
return await reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2);
|
|
64574
65376
|
}
|
|
64575
|
-
const specPath = explicitSpecPath ||
|
|
65377
|
+
const specPath = explicitSpecPath || join9(featureDir, "spec.md");
|
|
64576
65378
|
if (!existsSync8(specPath))
|
|
64577
65379
|
throw new Error(`spec.md not found at ${specPath}`);
|
|
64578
65380
|
const specContent = await Bun.file(specPath).text();
|
|
@@ -64689,7 +65491,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
64689
65491
|
modelDef,
|
|
64690
65492
|
config: config2
|
|
64691
65493
|
});
|
|
64692
|
-
const acceptanceTestPath =
|
|
65494
|
+
const acceptanceTestPath = join9(featureDir, config2.acceptance.testPath);
|
|
64693
65495
|
await Bun.write(acceptanceTestPath, result.testCode);
|
|
64694
65496
|
logger.info("cli", "[OK] Acceptance tests generated", {
|
|
64695
65497
|
criteriaCount: result.criteria.length,
|
|
@@ -64700,77 +65502,243 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
64700
65502
|
}
|
|
64701
65503
|
}
|
|
64702
65504
|
// src/cli/plan.ts
|
|
64703
|
-
|
|
65505
|
+
init_registry();
|
|
64704
65506
|
import { existsSync as existsSync9 } from "fs";
|
|
64705
|
-
import { join as
|
|
64706
|
-
init_schema();
|
|
65507
|
+
import { join as join10 } from "path";
|
|
64707
65508
|
init_logger2();
|
|
64708
|
-
var SPEC_TEMPLATE = `# Feature: [title]
|
|
64709
|
-
|
|
64710
|
-
## Problem
|
|
64711
|
-
Why this is needed.
|
|
64712
|
-
|
|
64713
|
-
## Requirements
|
|
64714
|
-
- REQ-1: ...
|
|
64715
|
-
- REQ-2: ...
|
|
64716
|
-
|
|
64717
|
-
## Acceptance Criteria
|
|
64718
|
-
- AC-1: ...
|
|
64719
65509
|
|
|
64720
|
-
|
|
64721
|
-
|
|
65510
|
+
// src/prd/schema.ts
|
|
65511
|
+
var VALID_COMPLEXITY = ["simple", "medium", "complex", "expert"];
|
|
65512
|
+
var VALID_TEST_STRATEGIES = [
|
|
65513
|
+
"test-after",
|
|
65514
|
+
"tdd-simple",
|
|
65515
|
+
"three-session-tdd",
|
|
65516
|
+
"three-session-tdd-lite"
|
|
65517
|
+
];
|
|
65518
|
+
var STORY_ID_NO_SEPARATOR = /^([A-Za-z]+)(\d+)$/;
|
|
65519
|
+
function extractJsonFromMarkdown(text) {
|
|
65520
|
+
const match = text.match(/```(?:json)?\s*\n([\s\S]*?)\n?\s*```/);
|
|
65521
|
+
if (match) {
|
|
65522
|
+
return match[1] ?? text;
|
|
65523
|
+
}
|
|
65524
|
+
return text;
|
|
65525
|
+
}
|
|
65526
|
+
function stripTrailingCommas(text) {
|
|
65527
|
+
return text.replace(/,\s*([}\]])/g, "$1");
|
|
65528
|
+
}
|
|
65529
|
+
function normalizeStoryId(id) {
|
|
65530
|
+
const match = id.match(STORY_ID_NO_SEPARATOR);
|
|
65531
|
+
if (match) {
|
|
65532
|
+
return `${match[1]}-${match[2]}`;
|
|
65533
|
+
}
|
|
65534
|
+
return id;
|
|
65535
|
+
}
|
|
65536
|
+
function normalizeComplexity2(raw) {
|
|
65537
|
+
const lower = raw.toLowerCase();
|
|
65538
|
+
if (VALID_COMPLEXITY.includes(lower)) {
|
|
65539
|
+
return lower;
|
|
65540
|
+
}
|
|
65541
|
+
return null;
|
|
65542
|
+
}
|
|
65543
|
+
function validateStory(raw, index, allIds) {
|
|
65544
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
65545
|
+
throw new Error(`[schema] story[${index}] must be an object`);
|
|
65546
|
+
}
|
|
65547
|
+
const s = raw;
|
|
65548
|
+
const rawId = s.id;
|
|
65549
|
+
if (rawId === undefined || rawId === null || rawId === "") {
|
|
65550
|
+
throw new Error(`[schema] story[${index}].id is required and must be non-empty`);
|
|
65551
|
+
}
|
|
65552
|
+
if (typeof rawId !== "string") {
|
|
65553
|
+
throw new Error(`[schema] story[${index}].id must be a string`);
|
|
65554
|
+
}
|
|
65555
|
+
const id = normalizeStoryId(rawId);
|
|
65556
|
+
validateStoryId(id);
|
|
65557
|
+
const title = s.title;
|
|
65558
|
+
if (!title || typeof title !== "string" || title.trim() === "") {
|
|
65559
|
+
throw new Error(`[schema] story[${index}].title is required and must be non-empty`);
|
|
65560
|
+
}
|
|
65561
|
+
const description = s.description;
|
|
65562
|
+
if (!description || typeof description !== "string" || description.trim() === "") {
|
|
65563
|
+
throw new Error(`[schema] story[${index}].description is required and must be non-empty`);
|
|
65564
|
+
}
|
|
65565
|
+
const ac = s.acceptanceCriteria;
|
|
65566
|
+
if (!Array.isArray(ac) || ac.length === 0) {
|
|
65567
|
+
throw new Error(`[schema] story[${index}].acceptanceCriteria is required and must be a non-empty array`);
|
|
65568
|
+
}
|
|
65569
|
+
for (let i = 0;i < ac.length; i++) {
|
|
65570
|
+
if (typeof ac[i] !== "string") {
|
|
65571
|
+
throw new Error(`[schema] story[${index}].acceptanceCriteria[${i}] must be a string`);
|
|
65572
|
+
}
|
|
65573
|
+
}
|
|
65574
|
+
const routing = typeof s.routing === "object" && s.routing !== null ? s.routing : {};
|
|
65575
|
+
const rawComplexity = routing.complexity ?? s.complexity;
|
|
65576
|
+
if (rawComplexity === undefined || rawComplexity === null) {
|
|
65577
|
+
throw new Error(`[schema] story[${index}] missing complexity. Set routing.complexity to one of: ${VALID_COMPLEXITY.join(", ")}`);
|
|
65578
|
+
}
|
|
65579
|
+
if (typeof rawComplexity !== "string") {
|
|
65580
|
+
throw new Error(`[schema] story[${index}].routing.complexity must be a string`);
|
|
65581
|
+
}
|
|
65582
|
+
const complexity = normalizeComplexity2(rawComplexity);
|
|
65583
|
+
if (complexity === null) {
|
|
65584
|
+
throw new Error(`[schema] story[${index}].routing.complexity "${rawComplexity}" is invalid. Valid values: ${VALID_COMPLEXITY.join(", ")}`);
|
|
65585
|
+
}
|
|
65586
|
+
const rawTestStrategy = routing.testStrategy ?? s.testStrategy;
|
|
65587
|
+
const testStrategy = rawTestStrategy !== undefined && VALID_TEST_STRATEGIES.includes(rawTestStrategy) ? rawTestStrategy : "tdd-simple";
|
|
65588
|
+
const rawDeps = s.dependencies;
|
|
65589
|
+
const dependencies = Array.isArray(rawDeps) ? rawDeps : [];
|
|
65590
|
+
for (const dep of dependencies) {
|
|
65591
|
+
if (!allIds.has(dep)) {
|
|
65592
|
+
throw new Error(`[schema] story[${index}].dependencies references unknown story ID "${dep}"`);
|
|
65593
|
+
}
|
|
65594
|
+
}
|
|
65595
|
+
const rawTags = s.tags;
|
|
65596
|
+
const tags = Array.isArray(rawTags) ? rawTags : [];
|
|
65597
|
+
return {
|
|
65598
|
+
id,
|
|
65599
|
+
title: title.trim(),
|
|
65600
|
+
description: description.trim(),
|
|
65601
|
+
acceptanceCriteria: ac,
|
|
65602
|
+
tags,
|
|
65603
|
+
dependencies,
|
|
65604
|
+
status: "pending",
|
|
65605
|
+
passes: false,
|
|
65606
|
+
attempts: 0,
|
|
65607
|
+
escalations: [],
|
|
65608
|
+
routing: {
|
|
65609
|
+
complexity,
|
|
65610
|
+
testStrategy,
|
|
65611
|
+
reasoning: "validated from LLM output"
|
|
65612
|
+
}
|
|
65613
|
+
};
|
|
65614
|
+
}
|
|
65615
|
+
function parseRawString(text) {
|
|
65616
|
+
const extracted = extractJsonFromMarkdown(text);
|
|
65617
|
+
const cleaned = stripTrailingCommas(extracted);
|
|
65618
|
+
try {
|
|
65619
|
+
return JSON.parse(cleaned);
|
|
65620
|
+
} catch (err) {
|
|
65621
|
+
const parseErr = err;
|
|
65622
|
+
throw new Error(`[schema] Failed to parse JSON: ${parseErr.message}`, { cause: parseErr });
|
|
65623
|
+
}
|
|
65624
|
+
}
|
|
65625
|
+
function validatePlanOutput(raw, feature, branch) {
|
|
65626
|
+
const parsed = typeof raw === "string" ? parseRawString(raw) : raw;
|
|
65627
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
65628
|
+
throw new Error("[schema] PRD output must be a JSON object");
|
|
65629
|
+
}
|
|
65630
|
+
const obj = parsed;
|
|
65631
|
+
const rawStories = obj.userStories;
|
|
65632
|
+
if (!Array.isArray(rawStories) || rawStories.length === 0) {
|
|
65633
|
+
throw new Error("[schema] userStories is required and must be a non-empty array");
|
|
65634
|
+
}
|
|
65635
|
+
const allIds = new Set;
|
|
65636
|
+
for (const story of rawStories) {
|
|
65637
|
+
if (typeof story === "object" && story !== null && !Array.isArray(story)) {
|
|
65638
|
+
const s = story;
|
|
65639
|
+
const rawId = s.id;
|
|
65640
|
+
if (typeof rawId === "string" && rawId !== "") {
|
|
65641
|
+
allIds.add(normalizeStoryId(rawId));
|
|
65642
|
+
}
|
|
65643
|
+
}
|
|
65644
|
+
}
|
|
65645
|
+
const userStories = rawStories.map((story, index) => validateStory(story, index, allIds));
|
|
65646
|
+
const now = new Date().toISOString();
|
|
65647
|
+
return {
|
|
65648
|
+
project: typeof obj.project === "string" && obj.project !== "" ? obj.project : feature,
|
|
65649
|
+
feature,
|
|
65650
|
+
branchName: branch,
|
|
65651
|
+
createdAt: typeof obj.createdAt === "string" ? obj.createdAt : now,
|
|
65652
|
+
updatedAt: now,
|
|
65653
|
+
userStories
|
|
65654
|
+
};
|
|
65655
|
+
}
|
|
64722
65656
|
|
|
64723
|
-
|
|
64724
|
-
|
|
64725
|
-
|
|
64726
|
-
|
|
64727
|
-
|
|
64728
|
-
|
|
64729
|
-
|
|
64730
|
-
|
|
65657
|
+
// src/cli/plan.ts
|
|
65658
|
+
var _deps2 = {
|
|
65659
|
+
readFile: (path) => Bun.file(path).text(),
|
|
65660
|
+
writeFile: (path, content) => Bun.write(path, content).then(() => {}),
|
|
65661
|
+
scanCodebase: (workdir) => scanCodebase(workdir),
|
|
65662
|
+
getAgent: (name) => getAgent(name),
|
|
65663
|
+
readPackageJson: (workdir) => Bun.file(join10(workdir, "package.json")).json().catch(() => null),
|
|
65664
|
+
spawnSync: (cmd, opts) => {
|
|
65665
|
+
const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
|
|
65666
|
+
return { stdout: result.stdout, exitCode: result.exitCode };
|
|
65667
|
+
},
|
|
65668
|
+
mkdirp: (path) => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {})
|
|
65669
|
+
};
|
|
65670
|
+
async function planCommand(workdir, config2, options) {
|
|
65671
|
+
const naxDir = join10(workdir, "nax");
|
|
65672
|
+
if (!existsSync9(naxDir)) {
|
|
64731
65673
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
64732
65674
|
}
|
|
64733
65675
|
const logger = getLogger();
|
|
64734
|
-
logger
|
|
64735
|
-
const
|
|
65676
|
+
logger?.info("plan", "Reading spec", { from: options.from });
|
|
65677
|
+
const specContent = await _deps2.readFile(options.from);
|
|
65678
|
+
logger?.info("plan", "Scanning codebase...");
|
|
65679
|
+
const scan = await _deps2.scanCodebase(workdir);
|
|
64736
65680
|
const codebaseContext = buildCodebaseContext2(scan);
|
|
64737
|
-
const
|
|
64738
|
-
const
|
|
64739
|
-
const
|
|
64740
|
-
const
|
|
64741
|
-
const
|
|
64742
|
-
|
|
64743
|
-
|
|
64744
|
-
|
|
64745
|
-
|
|
64746
|
-
|
|
64747
|
-
|
|
64748
|
-
|
|
64749
|
-
|
|
64750
|
-
};
|
|
64751
|
-
const adapter = new ClaudeCodeAdapter;
|
|
64752
|
-
logger.info("cli", interactive ? "Starting interactive planning session..." : `Reading from ${options.from}...`, {
|
|
64753
|
-
interactive,
|
|
64754
|
-
from: options.from
|
|
64755
|
-
});
|
|
64756
|
-
const result = await adapter.plan(planOptions);
|
|
64757
|
-
if (interactive) {
|
|
64758
|
-
if (result.specContent) {
|
|
64759
|
-
await Bun.write(outputPath, result.specContent);
|
|
64760
|
-
} else {
|
|
64761
|
-
if (!existsSync9(outputPath)) {
|
|
64762
|
-
throw new Error(`Interactive planning completed but spec not found at ${outputPath}`);
|
|
64763
|
-
}
|
|
64764
|
-
}
|
|
65681
|
+
const pkg = await _deps2.readPackageJson(workdir);
|
|
65682
|
+
const projectName = detectProjectName(workdir, pkg);
|
|
65683
|
+
const branchName = options.branch ?? `feat/${options.feature}`;
|
|
65684
|
+
const prompt = buildPlanningPrompt(specContent, codebaseContext);
|
|
65685
|
+
const agentName = config2?.autoMode?.defaultAgent ?? "claude";
|
|
65686
|
+
const adapter = _deps2.getAgent(agentName);
|
|
65687
|
+
if (!adapter) {
|
|
65688
|
+
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
65689
|
+
}
|
|
65690
|
+
const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
|
|
65691
|
+
let rawResponse;
|
|
65692
|
+
if (options.auto) {
|
|
65693
|
+
rawResponse = await adapter.complete(prompt, { jsonMode: true });
|
|
64765
65694
|
} else {
|
|
64766
|
-
|
|
64767
|
-
|
|
65695
|
+
const interactionBridge = createCliInteractionBridge();
|
|
65696
|
+
logger?.info("plan", "Starting interactive planning session...", { agent: agentName });
|
|
65697
|
+
try {
|
|
65698
|
+
const result = await adapter.plan({
|
|
65699
|
+
prompt,
|
|
65700
|
+
workdir,
|
|
65701
|
+
interactive: true,
|
|
65702
|
+
timeoutSeconds,
|
|
65703
|
+
interactionBridge
|
|
65704
|
+
});
|
|
65705
|
+
rawResponse = result.specContent;
|
|
65706
|
+
} finally {
|
|
65707
|
+
logger?.info("plan", "Interactive session ended");
|
|
64768
65708
|
}
|
|
64769
|
-
await Bun.write(outputPath, result.specContent);
|
|
64770
65709
|
}
|
|
64771
|
-
|
|
65710
|
+
const finalPrd = validatePlanOutput(rawResponse, options.feature, branchName);
|
|
65711
|
+
finalPrd.project = projectName;
|
|
65712
|
+
const outputDir = join10(naxDir, "features", options.feature);
|
|
65713
|
+
const outputPath = join10(outputDir, "prd.json");
|
|
65714
|
+
await _deps2.mkdirp(outputDir);
|
|
65715
|
+
await _deps2.writeFile(outputPath, JSON.stringify(finalPrd, null, 2));
|
|
65716
|
+
logger?.info("plan", "[OK] PRD written", { outputPath });
|
|
64772
65717
|
return outputPath;
|
|
64773
65718
|
}
|
|
65719
|
+
function createCliInteractionBridge() {
|
|
65720
|
+
return {
|
|
65721
|
+
async detectQuestion(text) {
|
|
65722
|
+
return text.includes("?");
|
|
65723
|
+
},
|
|
65724
|
+
async onQuestionDetected(text) {
|
|
65725
|
+
return text;
|
|
65726
|
+
}
|
|
65727
|
+
};
|
|
65728
|
+
}
|
|
65729
|
+
function detectProjectName(workdir, pkg) {
|
|
65730
|
+
if (pkg?.name && typeof pkg.name === "string") {
|
|
65731
|
+
return pkg.name;
|
|
65732
|
+
}
|
|
65733
|
+
const result = _deps2.spawnSync(["git", "remote", "get-url", "origin"], { cwd: workdir });
|
|
65734
|
+
if (result.exitCode === 0) {
|
|
65735
|
+
const url2 = result.stdout.toString().trim();
|
|
65736
|
+
const match = url2.match(/\/([^/]+?)(?:\.git)?$/);
|
|
65737
|
+
if (match?.[1])
|
|
65738
|
+
return match[1];
|
|
65739
|
+
}
|
|
65740
|
+
return "unknown";
|
|
65741
|
+
}
|
|
64774
65742
|
function buildCodebaseContext2(scan) {
|
|
64775
65743
|
const sections = [];
|
|
64776
65744
|
sections.push(`## Codebase Structure
|
|
@@ -64797,24 +65765,62 @@ function buildCodebaseContext2(scan) {
|
|
|
64797
65765
|
return sections.join(`
|
|
64798
65766
|
`);
|
|
64799
65767
|
}
|
|
64800
|
-
function
|
|
64801
|
-
return `You are
|
|
65768
|
+
function buildPlanningPrompt(specContent, codebaseContext) {
|
|
65769
|
+
return `You are a senior software architect generating a product requirements document (PRD) as JSON.
|
|
65770
|
+
|
|
65771
|
+
## Spec
|
|
65772
|
+
|
|
65773
|
+
${specContent}
|
|
65774
|
+
|
|
65775
|
+
## Codebase Context
|
|
65776
|
+
|
|
65777
|
+
${codebaseContext}
|
|
65778
|
+
|
|
65779
|
+
## Output Schema
|
|
65780
|
+
|
|
65781
|
+
Generate a JSON object with this exact structure (no markdown, no explanation \u2014 JSON only):
|
|
65782
|
+
|
|
65783
|
+
{
|
|
65784
|
+
"project": "string \u2014 project name",
|
|
65785
|
+
"feature": "string \u2014 feature name",
|
|
65786
|
+
"branchName": "string \u2014 git branch (e.g. feat/my-feature)",
|
|
65787
|
+
"createdAt": "ISO 8601 timestamp",
|
|
65788
|
+
"updatedAt": "ISO 8601 timestamp",
|
|
65789
|
+
"userStories": [
|
|
65790
|
+
{
|
|
65791
|
+
"id": "string \u2014 e.g. US-001",
|
|
65792
|
+
"title": "string \u2014 concise story title",
|
|
65793
|
+
"description": "string \u2014 detailed description of the story",
|
|
65794
|
+
"acceptanceCriteria": ["string \u2014 each AC line"],
|
|
65795
|
+
"tags": ["string \u2014 routing tags, e.g. feature, security, api"],
|
|
65796
|
+
"dependencies": ["string \u2014 story IDs this story depends on"],
|
|
65797
|
+
"status": "pending",
|
|
65798
|
+
"passes": false,
|
|
65799
|
+
"routing": {
|
|
65800
|
+
"complexity": "simple | medium | complex | expert",
|
|
65801
|
+
"testStrategy": "test-after | tdd-lite | three-session-tdd",
|
|
65802
|
+
"reasoning": "string \u2014 brief classification rationale"
|
|
65803
|
+
},
|
|
65804
|
+
"escalations": [],
|
|
65805
|
+
"attempts": 0
|
|
65806
|
+
}
|
|
65807
|
+
]
|
|
65808
|
+
}
|
|
64802
65809
|
|
|
64803
|
-
|
|
65810
|
+
## Complexity Classification Guide
|
|
64804
65811
|
|
|
64805
|
-
|
|
65812
|
+
- simple: \u226450 LOC, single-file change, purely additive, no new dependencies \u2192 test-after
|
|
65813
|
+
- medium: 50\u2013200 LOC, 2\u20135 files, standard patterns, clear requirements \u2192 tdd-lite
|
|
65814
|
+
- complex: 200\u2013500 LOC, multiple modules, new abstractions or integrations \u2192 three-session-tdd
|
|
65815
|
+
- expert: 500+ LOC, architectural changes, cross-cutting concerns, high risk \u2192 three-session-tdd
|
|
64806
65816
|
|
|
64807
|
-
|
|
65817
|
+
## Test Strategy Guide
|
|
64808
65818
|
|
|
64809
|
-
|
|
64810
|
-
|
|
64811
|
-
-
|
|
64812
|
-
- Specific requirements and constraints
|
|
64813
|
-
- Acceptance criteria for success
|
|
64814
|
-
- Technical approach and architecture
|
|
64815
|
-
- What is explicitly out of scope
|
|
65819
|
+
- test-after: Simple changes with well-understood behavior. Write tests after implementation.
|
|
65820
|
+
- tdd-lite: Medium complexity. Write key tests first, implement, then fill coverage.
|
|
65821
|
+
- three-session-tdd: Complex/expert. Full TDD cycle with separate sessions for tests and implementation.
|
|
64816
65822
|
|
|
64817
|
-
|
|
65823
|
+
Output ONLY the JSON object. Do not wrap in markdown code blocks.`;
|
|
64818
65824
|
}
|
|
64819
65825
|
// src/cli/accept.ts
|
|
64820
65826
|
init_config();
|
|
@@ -64963,14 +65969,14 @@ async function displayModelEfficiency(workdir) {
|
|
|
64963
65969
|
}
|
|
64964
65970
|
// src/cli/status-features.ts
|
|
64965
65971
|
init_source();
|
|
64966
|
-
import { existsSync as existsSync11, readdirSync as
|
|
64967
|
-
import { join as
|
|
65972
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
|
|
65973
|
+
import { join as join13 } from "path";
|
|
64968
65974
|
|
|
64969
65975
|
// src/commands/common.ts
|
|
64970
65976
|
init_path_security2();
|
|
64971
65977
|
init_errors3();
|
|
64972
|
-
import { existsSync as existsSync10, readdirSync, realpathSync as realpathSync2 } from "fs";
|
|
64973
|
-
import { join as
|
|
65978
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
|
|
65979
|
+
import { join as join11, resolve as resolve6 } from "path";
|
|
64974
65980
|
function resolveProject(options = {}) {
|
|
64975
65981
|
const { dir, feature } = options;
|
|
64976
65982
|
let projectRoot;
|
|
@@ -64978,12 +65984,12 @@ function resolveProject(options = {}) {
|
|
|
64978
65984
|
let configPath;
|
|
64979
65985
|
if (dir) {
|
|
64980
65986
|
projectRoot = realpathSync2(resolve6(dir));
|
|
64981
|
-
naxDir =
|
|
65987
|
+
naxDir = join11(projectRoot, "nax");
|
|
64982
65988
|
if (!existsSync10(naxDir)) {
|
|
64983
65989
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
64984
65990
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
64985
65991
|
}
|
|
64986
|
-
configPath =
|
|
65992
|
+
configPath = join11(naxDir, "config.json");
|
|
64987
65993
|
if (!existsSync10(configPath)) {
|
|
64988
65994
|
throw new NaxError(`nax directory found but config.json is missing: ${naxDir}
|
|
64989
65995
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
@@ -64991,24 +65997,24 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
|
64991
65997
|
} else {
|
|
64992
65998
|
const found = findProjectRoot(process.cwd());
|
|
64993
65999
|
if (!found) {
|
|
64994
|
-
const cwdNaxDir =
|
|
66000
|
+
const cwdNaxDir = join11(process.cwd(), "nax");
|
|
64995
66001
|
if (existsSync10(cwdNaxDir)) {
|
|
64996
|
-
const cwdConfigPath =
|
|
66002
|
+
const cwdConfigPath = join11(cwdNaxDir, "config.json");
|
|
64997
66003
|
throw new NaxError(`nax directory found but config.json is missing: ${cwdNaxDir}
|
|
64998
66004
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
64999
66005
|
}
|
|
65000
66006
|
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() });
|
|
65001
66007
|
}
|
|
65002
66008
|
projectRoot = found;
|
|
65003
|
-
naxDir =
|
|
65004
|
-
configPath =
|
|
66009
|
+
naxDir = join11(projectRoot, "nax");
|
|
66010
|
+
configPath = join11(naxDir, "config.json");
|
|
65005
66011
|
}
|
|
65006
66012
|
let featureDir;
|
|
65007
66013
|
if (feature) {
|
|
65008
|
-
const featuresDir =
|
|
65009
|
-
featureDir =
|
|
66014
|
+
const featuresDir = join11(naxDir, "features");
|
|
66015
|
+
featureDir = join11(featuresDir, feature);
|
|
65010
66016
|
if (!existsSync10(featureDir)) {
|
|
65011
|
-
const availableFeatures = existsSync10(featuresDir) ?
|
|
66017
|
+
const availableFeatures = existsSync10(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
65012
66018
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
65013
66019
|
|
|
65014
66020
|
Available features:
|
|
@@ -65033,12 +66039,12 @@ function findProjectRoot(startDir) {
|
|
|
65033
66039
|
let current = resolve6(startDir);
|
|
65034
66040
|
let depth = 0;
|
|
65035
66041
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
65036
|
-
const naxDir =
|
|
65037
|
-
const configPath =
|
|
66042
|
+
const naxDir = join11(current, "nax");
|
|
66043
|
+
const configPath = join11(naxDir, "config.json");
|
|
65038
66044
|
if (existsSync10(configPath)) {
|
|
65039
66045
|
return realpathSync2(current);
|
|
65040
66046
|
}
|
|
65041
|
-
const parent =
|
|
66047
|
+
const parent = join11(current, "..");
|
|
65042
66048
|
if (parent === current) {
|
|
65043
66049
|
break;
|
|
65044
66050
|
}
|
|
@@ -65060,7 +66066,7 @@ function isPidAlive(pid) {
|
|
|
65060
66066
|
}
|
|
65061
66067
|
}
|
|
65062
66068
|
async function loadStatusFile(featureDir) {
|
|
65063
|
-
const statusPath =
|
|
66069
|
+
const statusPath = join13(featureDir, "status.json");
|
|
65064
66070
|
if (!existsSync11(statusPath)) {
|
|
65065
66071
|
return null;
|
|
65066
66072
|
}
|
|
@@ -65072,7 +66078,7 @@ async function loadStatusFile(featureDir) {
|
|
|
65072
66078
|
}
|
|
65073
66079
|
}
|
|
65074
66080
|
async function loadProjectStatusFile(projectDir) {
|
|
65075
|
-
const statusPath =
|
|
66081
|
+
const statusPath = join13(projectDir, "nax", "status.json");
|
|
65076
66082
|
if (!existsSync11(statusPath)) {
|
|
65077
66083
|
return null;
|
|
65078
66084
|
}
|
|
@@ -65084,7 +66090,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
65084
66090
|
}
|
|
65085
66091
|
}
|
|
65086
66092
|
async function getFeatureSummary(featureName, featureDir) {
|
|
65087
|
-
const prdPath =
|
|
66093
|
+
const prdPath = join13(featureDir, "prd.json");
|
|
65088
66094
|
const prd = await loadPRD(prdPath);
|
|
65089
66095
|
const counts = countStories(prd);
|
|
65090
66096
|
const summary = {
|
|
@@ -65118,9 +66124,9 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
65118
66124
|
};
|
|
65119
66125
|
}
|
|
65120
66126
|
}
|
|
65121
|
-
const runsDir =
|
|
66127
|
+
const runsDir = join13(featureDir, "runs");
|
|
65122
66128
|
if (existsSync11(runsDir)) {
|
|
65123
|
-
const runs =
|
|
66129
|
+
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
|
|
65124
66130
|
if (runs.length > 0) {
|
|
65125
66131
|
const latestRun = runs[0].replace(".jsonl", "");
|
|
65126
66132
|
summary.lastRun = latestRun;
|
|
@@ -65129,12 +66135,12 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
65129
66135
|
return summary;
|
|
65130
66136
|
}
|
|
65131
66137
|
async function displayAllFeatures(projectDir) {
|
|
65132
|
-
const featuresDir =
|
|
66138
|
+
const featuresDir = join13(projectDir, "nax", "features");
|
|
65133
66139
|
if (!existsSync11(featuresDir)) {
|
|
65134
66140
|
console.log(source_default.dim("No features found."));
|
|
65135
66141
|
return;
|
|
65136
66142
|
}
|
|
65137
|
-
const features =
|
|
66143
|
+
const features = readdirSync3(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
65138
66144
|
if (features.length === 0) {
|
|
65139
66145
|
console.log(source_default.dim("No features found."));
|
|
65140
66146
|
return;
|
|
@@ -65170,7 +66176,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
65170
66176
|
console.log();
|
|
65171
66177
|
}
|
|
65172
66178
|
}
|
|
65173
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
66179
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join13(featuresDir, name))));
|
|
65174
66180
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
65175
66181
|
`));
|
|
65176
66182
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -65196,7 +66202,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
65196
66202
|
console.log();
|
|
65197
66203
|
}
|
|
65198
66204
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
65199
|
-
const prdPath =
|
|
66205
|
+
const prdPath = join13(featureDir, "prd.json");
|
|
65200
66206
|
const prd = await loadPRD(prdPath);
|
|
65201
66207
|
const counts = countStories(prd);
|
|
65202
66208
|
const status = await loadStatusFile(featureDir);
|
|
@@ -65310,8 +66316,8 @@ async function displayFeatureStatus(options = {}) {
|
|
|
65310
66316
|
// src/cli/runs.ts
|
|
65311
66317
|
init_errors3();
|
|
65312
66318
|
init_logger2();
|
|
65313
|
-
import { existsSync as existsSync12, readdirSync as
|
|
65314
|
-
import { join as
|
|
66319
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4 } from "fs";
|
|
66320
|
+
import { join as join14 } from "path";
|
|
65315
66321
|
async function parseRunLog(logPath) {
|
|
65316
66322
|
const logger = getLogger();
|
|
65317
66323
|
try {
|
|
@@ -65327,19 +66333,19 @@ async function parseRunLog(logPath) {
|
|
|
65327
66333
|
async function runsListCommand(options) {
|
|
65328
66334
|
const logger = getLogger();
|
|
65329
66335
|
const { feature, workdir } = options;
|
|
65330
|
-
const runsDir =
|
|
66336
|
+
const runsDir = join14(workdir, "nax", "features", feature, "runs");
|
|
65331
66337
|
if (!existsSync12(runsDir)) {
|
|
65332
66338
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
65333
66339
|
return;
|
|
65334
66340
|
}
|
|
65335
|
-
const files =
|
|
66341
|
+
const files = readdirSync4(runsDir).filter((f) => f.endsWith(".jsonl"));
|
|
65336
66342
|
if (files.length === 0) {
|
|
65337
66343
|
logger.info("cli", "No runs found for feature", { feature });
|
|
65338
66344
|
return;
|
|
65339
66345
|
}
|
|
65340
66346
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
65341
66347
|
for (const file2 of files.sort().reverse()) {
|
|
65342
|
-
const logPath =
|
|
66348
|
+
const logPath = join14(runsDir, file2);
|
|
65343
66349
|
const entries = await parseRunLog(logPath);
|
|
65344
66350
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
65345
66351
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -65365,7 +66371,7 @@ async function runsListCommand(options) {
|
|
|
65365
66371
|
async function runsShowCommand(options) {
|
|
65366
66372
|
const logger = getLogger();
|
|
65367
66373
|
const { runId, feature, workdir } = options;
|
|
65368
|
-
const logPath =
|
|
66374
|
+
const logPath = join14(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
65369
66375
|
if (!existsSync12(logPath)) {
|
|
65370
66376
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
65371
66377
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -65404,7 +66410,7 @@ async function runsShowCommand(options) {
|
|
|
65404
66410
|
// src/cli/prompts-main.ts
|
|
65405
66411
|
init_logger2();
|
|
65406
66412
|
import { existsSync as existsSync15, mkdirSync as mkdirSync3 } from "fs";
|
|
65407
|
-
import { join as
|
|
66413
|
+
import { join as join21 } from "path";
|
|
65408
66414
|
|
|
65409
66415
|
// src/pipeline/index.ts
|
|
65410
66416
|
init_runner();
|
|
@@ -65440,7 +66446,7 @@ init_prd();
|
|
|
65440
66446
|
|
|
65441
66447
|
// src/cli/prompts-tdd.ts
|
|
65442
66448
|
init_prompts2();
|
|
65443
|
-
import { join as
|
|
66449
|
+
import { join as join20 } from "path";
|
|
65444
66450
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
65445
66451
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
65446
66452
|
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(),
|
|
@@ -65459,7 +66465,7 @@ ${frontmatter}---
|
|
|
65459
66465
|
|
|
65460
66466
|
${session.prompt}`;
|
|
65461
66467
|
if (outputDir) {
|
|
65462
|
-
const promptFile =
|
|
66468
|
+
const promptFile = join20(outputDir, `${story.id}.${session.role}.md`);
|
|
65463
66469
|
await Bun.write(promptFile, fullOutput);
|
|
65464
66470
|
logger.info("cli", "Written TDD prompt file", {
|
|
65465
66471
|
storyId: story.id,
|
|
@@ -65475,7 +66481,7 @@ ${"=".repeat(80)}`);
|
|
|
65475
66481
|
}
|
|
65476
66482
|
}
|
|
65477
66483
|
if (outputDir && ctx.contextMarkdown) {
|
|
65478
|
-
const contextFile =
|
|
66484
|
+
const contextFile = join20(outputDir, `${story.id}.context.md`);
|
|
65479
66485
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
65480
66486
|
const contextOutput = `---
|
|
65481
66487
|
${frontmatter}---
|
|
@@ -65489,12 +66495,12 @@ ${ctx.contextMarkdown}`;
|
|
|
65489
66495
|
async function promptsCommand(options) {
|
|
65490
66496
|
const logger = getLogger();
|
|
65491
66497
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
65492
|
-
const naxDir =
|
|
66498
|
+
const naxDir = join21(workdir, "nax");
|
|
65493
66499
|
if (!existsSync15(naxDir)) {
|
|
65494
66500
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
65495
66501
|
}
|
|
65496
|
-
const featureDir =
|
|
65497
|
-
const prdPath =
|
|
66502
|
+
const featureDir = join21(naxDir, "features", feature);
|
|
66503
|
+
const prdPath = join21(featureDir, "prd.json");
|
|
65498
66504
|
if (!existsSync15(prdPath)) {
|
|
65499
66505
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
65500
66506
|
}
|
|
@@ -65554,10 +66560,10 @@ ${frontmatter}---
|
|
|
65554
66560
|
|
|
65555
66561
|
${ctx.prompt}`;
|
|
65556
66562
|
if (outputDir) {
|
|
65557
|
-
const promptFile =
|
|
66563
|
+
const promptFile = join21(outputDir, `${story.id}.prompt.md`);
|
|
65558
66564
|
await Bun.write(promptFile, fullOutput);
|
|
65559
66565
|
if (ctx.contextMarkdown) {
|
|
65560
|
-
const contextFile =
|
|
66566
|
+
const contextFile = join21(outputDir, `${story.id}.context.md`);
|
|
65561
66567
|
const contextOutput = `---
|
|
65562
66568
|
${frontmatter}---
|
|
65563
66569
|
|
|
@@ -65621,7 +66627,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
65621
66627
|
}
|
|
65622
66628
|
// src/cli/prompts-init.ts
|
|
65623
66629
|
import { existsSync as existsSync16, mkdirSync as mkdirSync4 } from "fs";
|
|
65624
|
-
import { join as
|
|
66630
|
+
import { join as join22 } from "path";
|
|
65625
66631
|
var TEMPLATE_ROLES = [
|
|
65626
66632
|
{ file: "test-writer.md", role: "test-writer" },
|
|
65627
66633
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -65645,9 +66651,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
65645
66651
|
`;
|
|
65646
66652
|
async function promptsInitCommand(options) {
|
|
65647
66653
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
65648
|
-
const templatesDir =
|
|
66654
|
+
const templatesDir = join22(workdir, "nax", "templates");
|
|
65649
66655
|
mkdirSync4(templatesDir, { recursive: true });
|
|
65650
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(
|
|
66656
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(join22(templatesDir, f)));
|
|
65651
66657
|
if (existingFiles.length > 0 && !force) {
|
|
65652
66658
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
65653
66659
|
Pass --force to overwrite existing templates.`);
|
|
@@ -65655,7 +66661,7 @@ async function promptsInitCommand(options) {
|
|
|
65655
66661
|
}
|
|
65656
66662
|
const written = [];
|
|
65657
66663
|
for (const template of TEMPLATE_ROLES) {
|
|
65658
|
-
const filePath =
|
|
66664
|
+
const filePath = join22(templatesDir, template.file);
|
|
65659
66665
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
65660
66666
|
const content = TEMPLATE_HEADER + roleBody;
|
|
65661
66667
|
await Bun.write(filePath, content);
|
|
@@ -65671,7 +66677,7 @@ async function promptsInitCommand(options) {
|
|
|
65671
66677
|
return written;
|
|
65672
66678
|
}
|
|
65673
66679
|
async function autoWirePromptsConfig(workdir) {
|
|
65674
|
-
const configPath =
|
|
66680
|
+
const configPath = join22(workdir, "nax.config.json");
|
|
65675
66681
|
if (!existsSync16(configPath)) {
|
|
65676
66682
|
const exampleConfig = JSON.stringify({
|
|
65677
66683
|
prompts: {
|
|
@@ -65837,8 +66843,8 @@ function pad(str, width) {
|
|
|
65837
66843
|
init_config();
|
|
65838
66844
|
init_logger2();
|
|
65839
66845
|
init_prd();
|
|
65840
|
-
import { existsSync as existsSync17, readdirSync as
|
|
65841
|
-
import { join as
|
|
66846
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5 } from "fs";
|
|
66847
|
+
import { join as join25 } from "path";
|
|
65842
66848
|
|
|
65843
66849
|
// src/cli/diagnose-analysis.ts
|
|
65844
66850
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -66037,7 +67043,7 @@ function isProcessAlive2(pid) {
|
|
|
66037
67043
|
}
|
|
66038
67044
|
}
|
|
66039
67045
|
async function loadStatusFile2(workdir) {
|
|
66040
|
-
const statusPath =
|
|
67046
|
+
const statusPath = join25(workdir, "nax", "status.json");
|
|
66041
67047
|
if (!existsSync17(statusPath))
|
|
66042
67048
|
return null;
|
|
66043
67049
|
try {
|
|
@@ -66065,7 +67071,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
66065
67071
|
}
|
|
66066
67072
|
}
|
|
66067
67073
|
async function checkLock(workdir) {
|
|
66068
|
-
const lockFile = Bun.file(
|
|
67074
|
+
const lockFile = Bun.file(join25(workdir, "nax.lock"));
|
|
66069
67075
|
if (!await lockFile.exists())
|
|
66070
67076
|
return { lockPresent: false };
|
|
66071
67077
|
try {
|
|
@@ -66083,8 +67089,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
66083
67089
|
const logger = getLogger();
|
|
66084
67090
|
const workdir = options.workdir ?? process.cwd();
|
|
66085
67091
|
const naxSubdir = findProjectDir(workdir);
|
|
66086
|
-
let projectDir = naxSubdir ?
|
|
66087
|
-
if (!projectDir && existsSync17(
|
|
67092
|
+
let projectDir = naxSubdir ? join25(naxSubdir, "..") : null;
|
|
67093
|
+
if (!projectDir && existsSync17(join25(workdir, "nax"))) {
|
|
66088
67094
|
projectDir = workdir;
|
|
66089
67095
|
}
|
|
66090
67096
|
if (!projectDir)
|
|
@@ -66095,18 +67101,18 @@ async function diagnoseCommand(options = {}) {
|
|
|
66095
67101
|
if (status2) {
|
|
66096
67102
|
feature = status2.run.feature;
|
|
66097
67103
|
} else {
|
|
66098
|
-
const featuresDir =
|
|
67104
|
+
const featuresDir = join25(projectDir, "nax", "features");
|
|
66099
67105
|
if (!existsSync17(featuresDir))
|
|
66100
67106
|
throw new Error("No features found in project");
|
|
66101
|
-
const features =
|
|
67107
|
+
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
66102
67108
|
if (features.length === 0)
|
|
66103
67109
|
throw new Error("No features found");
|
|
66104
67110
|
feature = features[0];
|
|
66105
67111
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
66106
67112
|
}
|
|
66107
67113
|
}
|
|
66108
|
-
const featureDir =
|
|
66109
|
-
const prdPath =
|
|
67114
|
+
const featureDir = join25(projectDir, "nax", "features", feature);
|
|
67115
|
+
const prdPath = join25(featureDir, "prd.json");
|
|
66110
67116
|
if (!existsSync17(prdPath))
|
|
66111
67117
|
throw new Error(`Feature not found: ${feature}`);
|
|
66112
67118
|
const prd = await loadPRD(prdPath);
|
|
@@ -66149,16 +67155,16 @@ init_interaction();
|
|
|
66149
67155
|
init_source();
|
|
66150
67156
|
init_loader2();
|
|
66151
67157
|
import { existsSync as existsSync20 } from "fs";
|
|
66152
|
-
import { join as
|
|
67158
|
+
import { join as join28 } from "path";
|
|
66153
67159
|
|
|
66154
67160
|
// src/context/generator.ts
|
|
66155
67161
|
init_path_security2();
|
|
66156
67162
|
import { existsSync as existsSync19 } from "fs";
|
|
66157
|
-
import { join as
|
|
67163
|
+
import { join as join27 } from "path";
|
|
66158
67164
|
|
|
66159
67165
|
// src/context/injector.ts
|
|
66160
67166
|
import { existsSync as existsSync18 } from "fs";
|
|
66161
|
-
import { join as
|
|
67167
|
+
import { join as join26 } from "path";
|
|
66162
67168
|
var NOTABLE_NODE_DEPS = [
|
|
66163
67169
|
"@nestjs",
|
|
66164
67170
|
"express",
|
|
@@ -66188,7 +67194,7 @@ var NOTABLE_NODE_DEPS = [
|
|
|
66188
67194
|
"ioredis"
|
|
66189
67195
|
];
|
|
66190
67196
|
async function detectNode(workdir) {
|
|
66191
|
-
const pkgPath =
|
|
67197
|
+
const pkgPath = join26(workdir, "package.json");
|
|
66192
67198
|
if (!existsSync18(pkgPath))
|
|
66193
67199
|
return null;
|
|
66194
67200
|
try {
|
|
@@ -66205,7 +67211,7 @@ async function detectNode(workdir) {
|
|
|
66205
67211
|
}
|
|
66206
67212
|
}
|
|
66207
67213
|
async function detectGo(workdir) {
|
|
66208
|
-
const goMod =
|
|
67214
|
+
const goMod = join26(workdir, "go.mod");
|
|
66209
67215
|
if (!existsSync18(goMod))
|
|
66210
67216
|
return null;
|
|
66211
67217
|
try {
|
|
@@ -66229,7 +67235,7 @@ async function detectGo(workdir) {
|
|
|
66229
67235
|
}
|
|
66230
67236
|
}
|
|
66231
67237
|
async function detectRust(workdir) {
|
|
66232
|
-
const cargoPath =
|
|
67238
|
+
const cargoPath = join26(workdir, "Cargo.toml");
|
|
66233
67239
|
if (!existsSync18(cargoPath))
|
|
66234
67240
|
return null;
|
|
66235
67241
|
try {
|
|
@@ -66245,8 +67251,8 @@ async function detectRust(workdir) {
|
|
|
66245
67251
|
}
|
|
66246
67252
|
}
|
|
66247
67253
|
async function detectPython(workdir) {
|
|
66248
|
-
const pyproject =
|
|
66249
|
-
const requirements =
|
|
67254
|
+
const pyproject = join26(workdir, "pyproject.toml");
|
|
67255
|
+
const requirements = join26(workdir, "requirements.txt");
|
|
66250
67256
|
if (!existsSync18(pyproject) && !existsSync18(requirements))
|
|
66251
67257
|
return null;
|
|
66252
67258
|
try {
|
|
@@ -66265,7 +67271,7 @@ async function detectPython(workdir) {
|
|
|
66265
67271
|
}
|
|
66266
67272
|
}
|
|
66267
67273
|
async function detectPhp(workdir) {
|
|
66268
|
-
const composerPath =
|
|
67274
|
+
const composerPath = join26(workdir, "composer.json");
|
|
66269
67275
|
if (!existsSync18(composerPath))
|
|
66270
67276
|
return null;
|
|
66271
67277
|
try {
|
|
@@ -66278,7 +67284,7 @@ async function detectPhp(workdir) {
|
|
|
66278
67284
|
}
|
|
66279
67285
|
}
|
|
66280
67286
|
async function detectRuby(workdir) {
|
|
66281
|
-
const gemfile =
|
|
67287
|
+
const gemfile = join26(workdir, "Gemfile");
|
|
66282
67288
|
if (!existsSync18(gemfile))
|
|
66283
67289
|
return null;
|
|
66284
67290
|
try {
|
|
@@ -66290,9 +67296,9 @@ async function detectRuby(workdir) {
|
|
|
66290
67296
|
}
|
|
66291
67297
|
}
|
|
66292
67298
|
async function detectJvm(workdir) {
|
|
66293
|
-
const pom =
|
|
66294
|
-
const gradle =
|
|
66295
|
-
const gradleKts =
|
|
67299
|
+
const pom = join26(workdir, "pom.xml");
|
|
67300
|
+
const gradle = join26(workdir, "build.gradle");
|
|
67301
|
+
const gradleKts = join26(workdir, "build.gradle.kts");
|
|
66296
67302
|
if (!existsSync18(pom) && !existsSync18(gradle) && !existsSync18(gradleKts))
|
|
66297
67303
|
return null;
|
|
66298
67304
|
try {
|
|
@@ -66300,7 +67306,7 @@ async function detectJvm(workdir) {
|
|
|
66300
67306
|
const content2 = await Bun.file(pom).text();
|
|
66301
67307
|
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
66302
67308
|
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
66303
|
-
const lang2 = existsSync18(
|
|
67309
|
+
const lang2 = existsSync18(join26(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
66304
67310
|
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
66305
67311
|
}
|
|
66306
67312
|
const gradleFile = existsSync18(gradleKts) ? gradleKts : gradle;
|
|
@@ -66521,7 +67527,7 @@ async function generateFor(agent, options, config2) {
|
|
|
66521
67527
|
try {
|
|
66522
67528
|
const context = await loadContextContent(options, config2);
|
|
66523
67529
|
const content = generator.generate(context);
|
|
66524
|
-
const outputPath =
|
|
67530
|
+
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
66525
67531
|
validateFilePath(outputPath, options.outputDir);
|
|
66526
67532
|
if (!options.dryRun) {
|
|
66527
67533
|
await Bun.write(outputPath, content);
|
|
@@ -66538,7 +67544,7 @@ async function generateAll(options, config2) {
|
|
|
66538
67544
|
for (const [agentKey, generator] of Object.entries(GENERATORS)) {
|
|
66539
67545
|
try {
|
|
66540
67546
|
const content = generator.generate(context);
|
|
66541
|
-
const outputPath =
|
|
67547
|
+
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
66542
67548
|
validateFilePath(outputPath, options.outputDir);
|
|
66543
67549
|
if (!options.dryRun) {
|
|
66544
67550
|
await Bun.write(outputPath, content);
|
|
@@ -66556,8 +67562,8 @@ async function generateAll(options, config2) {
|
|
|
66556
67562
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
66557
67563
|
async function generateCommand(options) {
|
|
66558
67564
|
const workdir = process.cwd();
|
|
66559
|
-
const contextPath = options.context ?
|
|
66560
|
-
const outputDir = options.output ?
|
|
67565
|
+
const contextPath = options.context ? join28(workdir, options.context) : join28(workdir, "nax/context.md");
|
|
67566
|
+
const outputDir = options.output ? join28(workdir, options.output) : workdir;
|
|
66561
67567
|
const autoInject = !options.noAutoInject;
|
|
66562
67568
|
const dryRun = options.dryRun ?? false;
|
|
66563
67569
|
if (!existsSync20(contextPath)) {
|
|
@@ -66633,7 +67639,7 @@ async function generateCommand(options) {
|
|
|
66633
67639
|
// src/cli/config-display.ts
|
|
66634
67640
|
init_loader2();
|
|
66635
67641
|
import { existsSync as existsSync22 } from "fs";
|
|
66636
|
-
import { join as
|
|
67642
|
+
import { join as join30 } from "path";
|
|
66637
67643
|
|
|
66638
67644
|
// src/cli/config-descriptions.ts
|
|
66639
67645
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -66839,7 +67845,7 @@ function deepEqual(a, b) {
|
|
|
66839
67845
|
init_defaults();
|
|
66840
67846
|
init_loader2();
|
|
66841
67847
|
import { existsSync as existsSync21 } from "fs";
|
|
66842
|
-
import { join as
|
|
67848
|
+
import { join as join29 } from "path";
|
|
66843
67849
|
async function loadConfigFile(path14) {
|
|
66844
67850
|
if (!existsSync21(path14))
|
|
66845
67851
|
return null;
|
|
@@ -66861,7 +67867,7 @@ async function loadProjectConfig() {
|
|
|
66861
67867
|
const projectDir = findProjectDir();
|
|
66862
67868
|
if (!projectDir)
|
|
66863
67869
|
return null;
|
|
66864
|
-
const projectPath =
|
|
67870
|
+
const projectPath = join29(projectDir, "config.json");
|
|
66865
67871
|
return await loadConfigFile(projectPath);
|
|
66866
67872
|
}
|
|
66867
67873
|
|
|
@@ -66921,7 +67927,7 @@ async function configCommand(config2, options = {}) {
|
|
|
66921
67927
|
function determineConfigSources() {
|
|
66922
67928
|
const globalPath = globalConfigPath();
|
|
66923
67929
|
const projectDir = findProjectDir();
|
|
66924
|
-
const projectPath = projectDir ?
|
|
67930
|
+
const projectPath = projectDir ? join30(projectDir, "config.json") : null;
|
|
66925
67931
|
return {
|
|
66926
67932
|
global: fileExists(globalPath) ? globalPath : null,
|
|
66927
67933
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -67101,24 +68107,24 @@ async function diagnose(options) {
|
|
|
67101
68107
|
|
|
67102
68108
|
// src/commands/logs.ts
|
|
67103
68109
|
import { existsSync as existsSync24 } from "fs";
|
|
67104
|
-
import { join as
|
|
68110
|
+
import { join as join33 } from "path";
|
|
67105
68111
|
|
|
67106
68112
|
// src/commands/logs-formatter.ts
|
|
67107
68113
|
init_source();
|
|
67108
68114
|
init_formatter();
|
|
67109
|
-
import { readdirSync as
|
|
67110
|
-
import { join as
|
|
68115
|
+
import { readdirSync as readdirSync7 } from "fs";
|
|
68116
|
+
import { join as join32 } from "path";
|
|
67111
68117
|
|
|
67112
68118
|
// src/commands/logs-reader.ts
|
|
67113
|
-
import { existsSync as existsSync23, readdirSync as
|
|
68119
|
+
import { existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
|
|
67114
68120
|
import { readdir as readdir3 } from "fs/promises";
|
|
67115
68121
|
import { homedir as homedir3 } from "os";
|
|
67116
|
-
import { join as
|
|
67117
|
-
var
|
|
67118
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
68122
|
+
import { join as join31 } from "path";
|
|
68123
|
+
var _deps6 = {
|
|
68124
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir3(), ".nax", "runs")
|
|
67119
68125
|
};
|
|
67120
68126
|
async function resolveRunFileFromRegistry(runId) {
|
|
67121
|
-
const runsDir =
|
|
68127
|
+
const runsDir = _deps6.getRunsDir();
|
|
67122
68128
|
let entries;
|
|
67123
68129
|
try {
|
|
67124
68130
|
entries = await readdir3(runsDir);
|
|
@@ -67127,7 +68133,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
67127
68133
|
}
|
|
67128
68134
|
let matched = null;
|
|
67129
68135
|
for (const entry of entries) {
|
|
67130
|
-
const metaPath =
|
|
68136
|
+
const metaPath = join31(runsDir, entry, "meta.json");
|
|
67131
68137
|
try {
|
|
67132
68138
|
const meta3 = await Bun.file(metaPath).json();
|
|
67133
68139
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -67143,20 +68149,20 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
67143
68149
|
console.log(`Log directory unavailable for run: ${runId}`);
|
|
67144
68150
|
return null;
|
|
67145
68151
|
}
|
|
67146
|
-
const files =
|
|
68152
|
+
const files = readdirSync6(matched.eventsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
67147
68153
|
if (files.length === 0) {
|
|
67148
68154
|
console.log(`No log files found for run: ${runId}`);
|
|
67149
68155
|
return null;
|
|
67150
68156
|
}
|
|
67151
68157
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
67152
|
-
return
|
|
68158
|
+
return join31(matched.eventsDir, specificFile ?? files[0]);
|
|
67153
68159
|
}
|
|
67154
68160
|
async function selectRunFile(runsDir) {
|
|
67155
|
-
const files =
|
|
68161
|
+
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
67156
68162
|
if (files.length === 0) {
|
|
67157
68163
|
return null;
|
|
67158
68164
|
}
|
|
67159
|
-
return
|
|
68165
|
+
return join31(runsDir, files[0]);
|
|
67160
68166
|
}
|
|
67161
68167
|
async function extractRunSummary(filePath) {
|
|
67162
68168
|
const file2 = Bun.file(filePath);
|
|
@@ -67230,7 +68236,7 @@ var LOG_LEVEL_PRIORITY2 = {
|
|
|
67230
68236
|
error: 3
|
|
67231
68237
|
};
|
|
67232
68238
|
async function displayRunsList(runsDir) {
|
|
67233
|
-
const files =
|
|
68239
|
+
const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
67234
68240
|
if (files.length === 0) {
|
|
67235
68241
|
console.log(source_default.dim("No runs found"));
|
|
67236
68242
|
return;
|
|
@@ -67241,7 +68247,7 @@ Runs:
|
|
|
67241
68247
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
67242
68248
|
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"));
|
|
67243
68249
|
for (const file2 of files) {
|
|
67244
|
-
const filePath =
|
|
68250
|
+
const filePath = join32(runsDir, file2);
|
|
67245
68251
|
const summary = await extractRunSummary(filePath);
|
|
67246
68252
|
const timestamp = file2.replace(".jsonl", "");
|
|
67247
68253
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -67366,7 +68372,7 @@ async function logsCommand(options) {
|
|
|
67366
68372
|
return;
|
|
67367
68373
|
}
|
|
67368
68374
|
const resolved = resolveProject({ dir: options.dir });
|
|
67369
|
-
const naxDir =
|
|
68375
|
+
const naxDir = join33(resolved.projectDir, "nax");
|
|
67370
68376
|
const configPath = resolved.configPath;
|
|
67371
68377
|
const configFile = Bun.file(configPath);
|
|
67372
68378
|
const config2 = await configFile.json();
|
|
@@ -67374,8 +68380,8 @@ async function logsCommand(options) {
|
|
|
67374
68380
|
if (!featureName) {
|
|
67375
68381
|
throw new Error("No feature specified in config.json");
|
|
67376
68382
|
}
|
|
67377
|
-
const featureDir =
|
|
67378
|
-
const runsDir =
|
|
68383
|
+
const featureDir = join33(naxDir, "features", featureName);
|
|
68384
|
+
const runsDir = join33(featureDir, "runs");
|
|
67379
68385
|
if (!existsSync24(runsDir)) {
|
|
67380
68386
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
67381
68387
|
}
|
|
@@ -67400,7 +68406,7 @@ init_config();
|
|
|
67400
68406
|
init_prd();
|
|
67401
68407
|
init_precheck();
|
|
67402
68408
|
import { existsSync as existsSync29 } from "fs";
|
|
67403
|
-
import { join as
|
|
68409
|
+
import { join as join34 } from "path";
|
|
67404
68410
|
async function precheckCommand(options) {
|
|
67405
68411
|
const resolved = resolveProject({
|
|
67406
68412
|
dir: options.dir,
|
|
@@ -67416,16 +68422,16 @@ async function precheckCommand(options) {
|
|
|
67416
68422
|
process.exit(1);
|
|
67417
68423
|
}
|
|
67418
68424
|
}
|
|
67419
|
-
const naxDir =
|
|
67420
|
-
const featureDir =
|
|
67421
|
-
const prdPath =
|
|
68425
|
+
const naxDir = join34(resolved.projectDir, "nax");
|
|
68426
|
+
const featureDir = join34(naxDir, "features", featureName);
|
|
68427
|
+
const prdPath = join34(featureDir, "prd.json");
|
|
67422
68428
|
if (!existsSync29(featureDir)) {
|
|
67423
68429
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
67424
68430
|
process.exit(1);
|
|
67425
68431
|
}
|
|
67426
68432
|
if (!existsSync29(prdPath)) {
|
|
67427
68433
|
console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
|
|
67428
|
-
console.error(source_default.dim(`Run: nax
|
|
68434
|
+
console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
|
|
67429
68435
|
process.exit(EXIT_CODES.INVALID_PRD);
|
|
67430
68436
|
}
|
|
67431
68437
|
const config2 = await loadConfig(resolved.projectDir);
|
|
@@ -67442,10 +68448,10 @@ async function precheckCommand(options) {
|
|
|
67442
68448
|
init_source();
|
|
67443
68449
|
import { readdir as readdir4 } from "fs/promises";
|
|
67444
68450
|
import { homedir as homedir4 } from "os";
|
|
67445
|
-
import { join as
|
|
68451
|
+
import { join as join35 } from "path";
|
|
67446
68452
|
var DEFAULT_LIMIT = 20;
|
|
67447
|
-
var
|
|
67448
|
-
getRunsDir: () =>
|
|
68453
|
+
var _deps8 = {
|
|
68454
|
+
getRunsDir: () => join35(homedir4(), ".nax", "runs")
|
|
67449
68455
|
};
|
|
67450
68456
|
function formatDuration3(ms) {
|
|
67451
68457
|
if (ms <= 0)
|
|
@@ -67487,7 +68493,7 @@ function pad3(str, width) {
|
|
|
67487
68493
|
return str + " ".repeat(padding);
|
|
67488
68494
|
}
|
|
67489
68495
|
async function runsCommand(options = {}) {
|
|
67490
|
-
const runsDir =
|
|
68496
|
+
const runsDir = _deps8.getRunsDir();
|
|
67491
68497
|
let entries;
|
|
67492
68498
|
try {
|
|
67493
68499
|
entries = await readdir4(runsDir);
|
|
@@ -67497,7 +68503,7 @@ async function runsCommand(options = {}) {
|
|
|
67497
68503
|
}
|
|
67498
68504
|
const rows = [];
|
|
67499
68505
|
for (const entry of entries) {
|
|
67500
|
-
const metaPath =
|
|
68506
|
+
const metaPath = join35(runsDir, entry, "meta.json");
|
|
67501
68507
|
let meta3;
|
|
67502
68508
|
try {
|
|
67503
68509
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -67574,7 +68580,7 @@ async function runsCommand(options = {}) {
|
|
|
67574
68580
|
|
|
67575
68581
|
// src/commands/unlock.ts
|
|
67576
68582
|
init_source();
|
|
67577
|
-
import { join as
|
|
68583
|
+
import { join as join36 } from "path";
|
|
67578
68584
|
function isProcessAlive3(pid) {
|
|
67579
68585
|
try {
|
|
67580
68586
|
process.kill(pid, 0);
|
|
@@ -67589,7 +68595,7 @@ function formatLockAge(ageMs) {
|
|
|
67589
68595
|
}
|
|
67590
68596
|
async function unlockCommand(options) {
|
|
67591
68597
|
const workdir = options.dir ?? process.cwd();
|
|
67592
|
-
const lockPath =
|
|
68598
|
+
const lockPath = join36(workdir, "nax.lock");
|
|
67593
68599
|
const lockFile = Bun.file(lockPath);
|
|
67594
68600
|
const exists = await lockFile.exists();
|
|
67595
68601
|
if (!exists) {
|
|
@@ -67628,6 +68634,7 @@ async function unlockCommand(options) {
|
|
|
67628
68634
|
init_config();
|
|
67629
68635
|
|
|
67630
68636
|
// src/execution/runner.ts
|
|
68637
|
+
init_registry();
|
|
67631
68638
|
init_hooks();
|
|
67632
68639
|
init_logger2();
|
|
67633
68640
|
init_prd();
|
|
@@ -67637,6 +68644,7 @@ init_crash_recovery();
|
|
|
67637
68644
|
init_hooks();
|
|
67638
68645
|
init_logger2();
|
|
67639
68646
|
init_prd();
|
|
68647
|
+
init_git();
|
|
67640
68648
|
init_crash_recovery();
|
|
67641
68649
|
init_story_context();
|
|
67642
68650
|
async function runCompletionPhase(options) {
|
|
@@ -67646,7 +68654,7 @@ async function runCompletionPhase(options) {
|
|
|
67646
68654
|
const acceptanceResult = await runAcceptanceLoop2({
|
|
67647
68655
|
config: options.config,
|
|
67648
68656
|
prd: options.prd,
|
|
67649
|
-
prdPath:
|
|
68657
|
+
prdPath: options.prdPath,
|
|
67650
68658
|
workdir: options.workdir,
|
|
67651
68659
|
featureDir: options.featureDir,
|
|
67652
68660
|
hooks: options.hooks,
|
|
@@ -67657,7 +68665,8 @@ async function runCompletionPhase(options) {
|
|
|
67657
68665
|
allStoryMetrics: options.allStoryMetrics,
|
|
67658
68666
|
pluginRegistry: options.pluginRegistry,
|
|
67659
68667
|
eventEmitter: options.eventEmitter,
|
|
67660
|
-
statusWriter: options.statusWriter
|
|
68668
|
+
statusWriter: options.statusWriter,
|
|
68669
|
+
agentGetFn: options.agentGetFn
|
|
67661
68670
|
});
|
|
67662
68671
|
Object.assign(options, {
|
|
67663
68672
|
prd: acceptanceResult.prd,
|
|
@@ -67708,6 +68717,7 @@ async function runCompletionPhase(options) {
|
|
|
67708
68717
|
}
|
|
67709
68718
|
stopHeartbeat();
|
|
67710
68719
|
await writeExitSummary(options.logFilePath, options.totalCost, options.iterations, options.storiesCompleted, durationMs);
|
|
68720
|
+
await autoCommitIfDirty(options.workdir, "run.complete", "run-summary", options.feature);
|
|
67711
68721
|
return {
|
|
67712
68722
|
durationMs,
|
|
67713
68723
|
runCompletedAt
|
|
@@ -67858,7 +68868,9 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
67858
68868
|
logFilePath: options.logFilePath,
|
|
67859
68869
|
runId: options.runId,
|
|
67860
68870
|
startTime: options.startTime,
|
|
67861
|
-
batchPlan
|
|
68871
|
+
batchPlan,
|
|
68872
|
+
agentGetFn: options.agentGetFn,
|
|
68873
|
+
pidRegistry: options.pidRegistry
|
|
67862
68874
|
}, prd);
|
|
67863
68875
|
prd = sequentialResult.prd;
|
|
67864
68876
|
iterations = sequentialResult.iterations;
|
|
@@ -67896,7 +68908,8 @@ async function runSetupPhase(options) {
|
|
|
67896
68908
|
getTotalCost: options.getTotalCost,
|
|
67897
68909
|
getIterations: options.getIterations,
|
|
67898
68910
|
getStoriesCompleted: options.getStoriesCompleted,
|
|
67899
|
-
getTotalStories: options.getTotalStories
|
|
68911
|
+
getTotalStories: options.getTotalStories,
|
|
68912
|
+
agentGetFn: options.agentGetFn
|
|
67900
68913
|
});
|
|
67901
68914
|
return setupResult;
|
|
67902
68915
|
}
|
|
@@ -67934,6 +68947,8 @@ async function run(options) {
|
|
|
67934
68947
|
let totalCost = 0;
|
|
67935
68948
|
const allStoryMetrics = [];
|
|
67936
68949
|
const logger = getSafeLogger();
|
|
68950
|
+
const registry2 = createAgentRegistry(config2);
|
|
68951
|
+
const agentGetFn = registry2.getAgent.bind(registry2);
|
|
67937
68952
|
let prd;
|
|
67938
68953
|
const setupResult = await runSetupPhase({
|
|
67939
68954
|
prdPath,
|
|
@@ -67951,6 +68966,7 @@ async function run(options) {
|
|
|
67951
68966
|
skipPrecheck,
|
|
67952
68967
|
headless,
|
|
67953
68968
|
formatterMode,
|
|
68969
|
+
agentGetFn,
|
|
67954
68970
|
getTotalCost: () => totalCost,
|
|
67955
68971
|
getIterations: () => iterations,
|
|
67956
68972
|
getStoriesCompleted: () => storiesCompleted,
|
|
@@ -67978,7 +68994,9 @@ async function run(options) {
|
|
|
67978
68994
|
formatterMode,
|
|
67979
68995
|
headless,
|
|
67980
68996
|
parallel,
|
|
67981
|
-
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined
|
|
68997
|
+
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined,
|
|
68998
|
+
agentGetFn,
|
|
68999
|
+
pidRegistry
|
|
67982
69000
|
}, prd, pluginRegistry);
|
|
67983
69001
|
prd = executionResult.prd;
|
|
67984
69002
|
iterations = executionResult.iterations;
|
|
@@ -67999,6 +69017,7 @@ async function run(options) {
|
|
|
67999
69017
|
hooks,
|
|
68000
69018
|
feature,
|
|
68001
69019
|
workdir,
|
|
69020
|
+
prdPath,
|
|
68002
69021
|
statusFile,
|
|
68003
69022
|
logFilePath,
|
|
68004
69023
|
runId,
|
|
@@ -68014,7 +69033,8 @@ async function run(options) {
|
|
|
68014
69033
|
iterations,
|
|
68015
69034
|
statusWriter,
|
|
68016
69035
|
pluginRegistry,
|
|
68017
|
-
eventEmitter
|
|
69036
|
+
eventEmitter,
|
|
69037
|
+
agentGetFn
|
|
68018
69038
|
});
|
|
68019
69039
|
const { durationMs } = completionResult;
|
|
68020
69040
|
return {
|
|
@@ -75349,6 +76369,31 @@ function renderTui(props) {
|
|
|
75349
76369
|
init_version();
|
|
75350
76370
|
var program2 = new Command;
|
|
75351
76371
|
program2.name("nax").description("AI Coding Agent Orchestrator \u2014 loops until done").version(NAX_VERSION);
|
|
76372
|
+
async function promptForConfirmation(question) {
|
|
76373
|
+
if (!process.stdin.isTTY) {
|
|
76374
|
+
return true;
|
|
76375
|
+
}
|
|
76376
|
+
return new Promise((resolve9) => {
|
|
76377
|
+
process.stdout.write(source_default.bold(`${question} [Y/n] `));
|
|
76378
|
+
process.stdin.setRawMode(true);
|
|
76379
|
+
process.stdin.resume();
|
|
76380
|
+
process.stdin.setEncoding("utf8");
|
|
76381
|
+
const handler = (char) => {
|
|
76382
|
+
process.stdin.setRawMode(false);
|
|
76383
|
+
process.stdin.pause();
|
|
76384
|
+
process.stdin.removeListener("data", handler);
|
|
76385
|
+
const answer = char.toLowerCase();
|
|
76386
|
+
process.stdout.write(`
|
|
76387
|
+
`);
|
|
76388
|
+
if (answer === "n") {
|
|
76389
|
+
resolve9(false);
|
|
76390
|
+
} else {
|
|
76391
|
+
resolve9(true);
|
|
76392
|
+
}
|
|
76393
|
+
};
|
|
76394
|
+
process.stdin.on("data", handler);
|
|
76395
|
+
});
|
|
76396
|
+
}
|
|
75352
76397
|
program2.command("init").description("Initialize nax in the current project").option("-d, --dir <path>", "Project directory", process.cwd()).option("-f, --force", "Force overwrite existing files", false).action(async (options) => {
|
|
75353
76398
|
let workdir;
|
|
75354
76399
|
try {
|
|
@@ -75357,15 +76402,15 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
75357
76402
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
75358
76403
|
process.exit(1);
|
|
75359
76404
|
}
|
|
75360
|
-
const naxDir =
|
|
76405
|
+
const naxDir = join43(workdir, "nax");
|
|
75361
76406
|
if (existsSync32(naxDir) && !options.force) {
|
|
75362
76407
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
75363
76408
|
return;
|
|
75364
76409
|
}
|
|
75365
|
-
mkdirSync6(
|
|
75366
|
-
mkdirSync6(
|
|
75367
|
-
await Bun.write(
|
|
75368
|
-
await Bun.write(
|
|
76410
|
+
mkdirSync6(join43(naxDir, "features"), { recursive: true });
|
|
76411
|
+
mkdirSync6(join43(naxDir, "hooks"), { recursive: true });
|
|
76412
|
+
await Bun.write(join43(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
76413
|
+
await Bun.write(join43(naxDir, "hooks.json"), JSON.stringify({
|
|
75369
76414
|
hooks: {
|
|
75370
76415
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
75371
76416
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -75373,12 +76418,12 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
75373
76418
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
75374
76419
|
}
|
|
75375
76420
|
}, null, 2));
|
|
75376
|
-
await Bun.write(
|
|
76421
|
+
await Bun.write(join43(naxDir, ".gitignore"), `# nax temp files
|
|
75377
76422
|
*.tmp
|
|
75378
76423
|
.paused.json
|
|
75379
76424
|
.nax-verifier-verdict.json
|
|
75380
76425
|
`);
|
|
75381
|
-
await Bun.write(
|
|
76426
|
+
await Bun.write(join43(naxDir, "context.md"), `# Project Context
|
|
75382
76427
|
|
|
75383
76428
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
75384
76429
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -75463,7 +76508,7 @@ Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cu
|
|
|
75463
76508
|
console.log(source_default.dim(`
|
|
75464
76509
|
Next: nax features create <name>`));
|
|
75465
76510
|
});
|
|
75466
|
-
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations", "20").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)").option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).action(async (options) => {
|
|
76511
|
+
program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations", "20").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).action(async (options) => {
|
|
75467
76512
|
let workdir;
|
|
75468
76513
|
try {
|
|
75469
76514
|
workdir = validateDirectory(options.dir);
|
|
@@ -75471,6 +76516,14 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75471
76516
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
75472
76517
|
process.exit(1);
|
|
75473
76518
|
}
|
|
76519
|
+
if (options.plan && !options.from) {
|
|
76520
|
+
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
76521
|
+
process.exit(1);
|
|
76522
|
+
}
|
|
76523
|
+
if (options.from && !existsSync32(options.from)) {
|
|
76524
|
+
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
76525
|
+
process.exit(1);
|
|
76526
|
+
}
|
|
75474
76527
|
let logLevel = "info";
|
|
75475
76528
|
const envLevel = process.env.NAX_LOG_LEVEL?.toLowerCase();
|
|
75476
76529
|
if (envLevel && ["error", "warn", "info", "debug"].includes(envLevel)) {
|
|
@@ -75496,16 +76549,48 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75496
76549
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75497
76550
|
process.exit(1);
|
|
75498
76551
|
}
|
|
75499
|
-
const featureDir =
|
|
75500
|
-
const prdPath =
|
|
76552
|
+
const featureDir = join43(naxDir, "features", options.feature);
|
|
76553
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
76554
|
+
if (options.plan && options.from) {
|
|
76555
|
+
try {
|
|
76556
|
+
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
76557
|
+
const generatedPrdPath = await planCommand(workdir, config2, {
|
|
76558
|
+
from: options.from,
|
|
76559
|
+
feature: options.feature,
|
|
76560
|
+
auto: true,
|
|
76561
|
+
branch: undefined
|
|
76562
|
+
});
|
|
76563
|
+
const generatedPrd = await loadPRD(generatedPrdPath);
|
|
76564
|
+
console.log(source_default.bold(`
|
|
76565
|
+
\u2500\u2500 Planning Summary \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`));
|
|
76566
|
+
console.log(source_default.dim(`Feature: ${generatedPrd.feature}`));
|
|
76567
|
+
console.log(source_default.dim(`Stories: ${generatedPrd.userStories.length}`));
|
|
76568
|
+
console.log();
|
|
76569
|
+
for (const story of generatedPrd.userStories) {
|
|
76570
|
+
const complexity = story.routing?.complexity || "unknown";
|
|
76571
|
+
console.log(source_default.dim(` ${story.id}: ${story.title} [${complexity}]`));
|
|
76572
|
+
}
|
|
76573
|
+
console.log();
|
|
76574
|
+
if (!options.headless) {
|
|
76575
|
+
const confirmationResult = await promptForConfirmation("Proceed with execution?");
|
|
76576
|
+
if (!confirmationResult) {
|
|
76577
|
+
console.log(source_default.yellow("Execution cancelled."));
|
|
76578
|
+
process.exit(0);
|
|
76579
|
+
}
|
|
76580
|
+
}
|
|
76581
|
+
} catch (err) {
|
|
76582
|
+
console.error(source_default.red(`Error during planning: ${err.message}`));
|
|
76583
|
+
process.exit(1);
|
|
76584
|
+
}
|
|
76585
|
+
}
|
|
75501
76586
|
if (!existsSync32(prdPath)) {
|
|
75502
76587
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
75503
76588
|
process.exit(1);
|
|
75504
76589
|
}
|
|
75505
|
-
const runsDir =
|
|
76590
|
+
const runsDir = join43(featureDir, "runs");
|
|
75506
76591
|
mkdirSync6(runsDir, { recursive: true });
|
|
75507
76592
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
75508
|
-
const logFilePath =
|
|
76593
|
+
const logFilePath = join43(runsDir, `${runId}.jsonl`);
|
|
75509
76594
|
const isTTY = process.stdout.isTTY ?? false;
|
|
75510
76595
|
const headlessFlag = options.headless ?? false;
|
|
75511
76596
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -75521,7 +76606,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75521
76606
|
config2.autoMode.defaultAgent = options.agent;
|
|
75522
76607
|
}
|
|
75523
76608
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
75524
|
-
const globalNaxDir =
|
|
76609
|
+
const globalNaxDir = join43(homedir8(), ".nax");
|
|
75525
76610
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
75526
76611
|
const eventEmitter = new PipelineEventEmitter;
|
|
75527
76612
|
let tuiInstance;
|
|
@@ -75544,7 +76629,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75544
76629
|
} else {
|
|
75545
76630
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
75546
76631
|
}
|
|
75547
|
-
const statusFilePath =
|
|
76632
|
+
const statusFilePath = join43(workdir, "nax", "status.json");
|
|
75548
76633
|
let parallel;
|
|
75549
76634
|
if (options.parallel !== undefined) {
|
|
75550
76635
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -75570,7 +76655,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75570
76655
|
headless: useHeadless,
|
|
75571
76656
|
skipPrecheck: options.skipPrecheck ?? false
|
|
75572
76657
|
});
|
|
75573
|
-
const latestSymlink =
|
|
76658
|
+
const latestSymlink = join43(runsDir, "latest.jsonl");
|
|
75574
76659
|
try {
|
|
75575
76660
|
if (existsSync32(latestSymlink)) {
|
|
75576
76661
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -75608,9 +76693,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75608
76693
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75609
76694
|
process.exit(1);
|
|
75610
76695
|
}
|
|
75611
|
-
const featureDir =
|
|
76696
|
+
const featureDir = join43(naxDir, "features", name);
|
|
75612
76697
|
mkdirSync6(featureDir, { recursive: true });
|
|
75613
|
-
await Bun.write(
|
|
76698
|
+
await Bun.write(join43(featureDir, "spec.md"), `# Feature: ${name}
|
|
75614
76699
|
|
|
75615
76700
|
## Overview
|
|
75616
76701
|
|
|
@@ -75618,7 +76703,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75618
76703
|
|
|
75619
76704
|
## Acceptance Criteria
|
|
75620
76705
|
`);
|
|
75621
|
-
await Bun.write(
|
|
76706
|
+
await Bun.write(join43(featureDir, "plan.md"), `# Plan: ${name}
|
|
75622
76707
|
|
|
75623
76708
|
## Architecture
|
|
75624
76709
|
|
|
@@ -75626,7 +76711,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75626
76711
|
|
|
75627
76712
|
## Dependencies
|
|
75628
76713
|
`);
|
|
75629
|
-
await Bun.write(
|
|
76714
|
+
await Bun.write(join43(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
75630
76715
|
|
|
75631
76716
|
## US-001: [Title]
|
|
75632
76717
|
|
|
@@ -75635,7 +76720,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75635
76720
|
### Acceptance Criteria
|
|
75636
76721
|
- [ ] Criterion 1
|
|
75637
76722
|
`);
|
|
75638
|
-
await Bun.write(
|
|
76723
|
+
await Bun.write(join43(featureDir, "progress.txt"), `# Progress: ${name}
|
|
75639
76724
|
|
|
75640
76725
|
Created: ${new Date().toISOString()}
|
|
75641
76726
|
|
|
@@ -75648,7 +76733,7 @@ Created: ${new Date().toISOString()}
|
|
|
75648
76733
|
console.log(source_default.dim(" \u251C\u2500\u2500 tasks.md"));
|
|
75649
76734
|
console.log(source_default.dim(" \u2514\u2500\u2500 progress.txt"));
|
|
75650
76735
|
console.log(source_default.dim(`
|
|
75651
|
-
Next: Edit spec.md and tasks.md, then: nax
|
|
76736
|
+
Next: Edit spec.md and tasks.md, then: nax plan -f ${name} --from spec.md --auto`));
|
|
75652
76737
|
});
|
|
75653
76738
|
features.command("list").description("List all features").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
75654
76739
|
let workdir;
|
|
@@ -75663,13 +76748,13 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
75663
76748
|
console.error(source_default.red("nax not initialized."));
|
|
75664
76749
|
process.exit(1);
|
|
75665
76750
|
}
|
|
75666
|
-
const featuresDir =
|
|
76751
|
+
const featuresDir = join43(naxDir, "features");
|
|
75667
76752
|
if (!existsSync32(featuresDir)) {
|
|
75668
76753
|
console.log(source_default.dim("No features yet."));
|
|
75669
76754
|
return;
|
|
75670
76755
|
}
|
|
75671
|
-
const { readdirSync:
|
|
75672
|
-
const entries =
|
|
76756
|
+
const { readdirSync: readdirSync8 } = await import("fs");
|
|
76757
|
+
const entries = readdirSync8(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
75673
76758
|
if (entries.length === 0) {
|
|
75674
76759
|
console.log(source_default.dim("No features yet."));
|
|
75675
76760
|
return;
|
|
@@ -75678,7 +76763,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
75678
76763
|
Features:
|
|
75679
76764
|
`));
|
|
75680
76765
|
for (const name of entries) {
|
|
75681
|
-
const prdPath =
|
|
76766
|
+
const prdPath = join43(featuresDir, name, "prd.json");
|
|
75682
76767
|
if (existsSync32(prdPath)) {
|
|
75683
76768
|
const prd = await loadPRD(prdPath);
|
|
75684
76769
|
const c = countStories(prd);
|
|
@@ -75689,7 +76774,13 @@ Features:
|
|
|
75689
76774
|
}
|
|
75690
76775
|
console.log();
|
|
75691
76776
|
});
|
|
75692
|
-
program2.command("plan
|
|
76777
|
+
program2.command("plan [description]").description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')").requiredOption("--from <spec-path>", "Path to spec file (required)").requiredOption("-f, --feature <name>", "Feature name (required)").option("--auto", "Run in auto (one-shot LLM) mode", false).option("-b, --branch <branch>", "Override default branch name").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (description, options) => {
|
|
76778
|
+
if (description) {
|
|
76779
|
+
console.error(source_default.red(`Error: Positional args removed in plan v2.
|
|
76780
|
+
|
|
76781
|
+
Use: nax plan -f <feature> --from <spec>`));
|
|
76782
|
+
process.exit(1);
|
|
76783
|
+
}
|
|
75693
76784
|
let workdir;
|
|
75694
76785
|
try {
|
|
75695
76786
|
workdir = validateDirectory(options.dir);
|
|
@@ -75704,21 +76795,26 @@ program2.command("plan <description>").description("Interactive planning via age
|
|
|
75704
76795
|
}
|
|
75705
76796
|
const config2 = await loadConfig(workdir);
|
|
75706
76797
|
try {
|
|
75707
|
-
const
|
|
75708
|
-
|
|
75709
|
-
|
|
76798
|
+
const prdPath = await planCommand(workdir, config2, {
|
|
76799
|
+
from: options.from,
|
|
76800
|
+
feature: options.feature,
|
|
76801
|
+
auto: options.auto,
|
|
76802
|
+
branch: options.branch
|
|
75710
76803
|
});
|
|
75711
76804
|
console.log(source_default.green(`
|
|
75712
|
-
|
|
75713
|
-
console.log(source_default.dim(`
|
|
76805
|
+
[OK] PRD generated`));
|
|
76806
|
+
console.log(source_default.dim(` PRD: ${prdPath}`));
|
|
75714
76807
|
console.log(source_default.dim(`
|
|
75715
|
-
Next: nax
|
|
76808
|
+
Next: nax run -f ${options.feature}`));
|
|
75716
76809
|
} catch (err) {
|
|
75717
76810
|
console.error(source_default.red(`Error: ${err.message}`));
|
|
75718
76811
|
process.exit(1);
|
|
75719
76812
|
}
|
|
75720
76813
|
});
|
|
75721
|
-
program2.command("analyze").description("Parse spec.md into prd.json via agent decompose").requiredOption("-f, --feature <name>", "Feature name").option("-b, --branch <name>", "Branch name", "feat/<feature>").option("--from <path>", "Explicit spec path (overrides default spec.md)").option("--reclassify", "Re-classify existing prd.json without decompose", false).option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
76814
|
+
program2.command("analyze").description("(deprecated) Parse spec.md into prd.json via agent decompose \u2014 use 'nax plan' instead").requiredOption("-f, --feature <name>", "Feature name").option("-b, --branch <name>", "Branch name", "feat/<feature>").option("--from <path>", "Explicit spec path (overrides default spec.md)").option("--reclassify", "Re-classify existing prd.json without decompose", false).option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
76815
|
+
const deprecationMsg = "\u26A0\uFE0F 'nax analyze' is deprecated. Use 'nax plan -f <feature> --from <spec> --auto' instead.";
|
|
76816
|
+
process.stderr.write(`${source_default.yellow(deprecationMsg)}
|
|
76817
|
+
`);
|
|
75722
76818
|
let workdir;
|
|
75723
76819
|
try {
|
|
75724
76820
|
workdir = validateDirectory(options.dir);
|
|
@@ -75731,7 +76827,7 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
|
|
|
75731
76827
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75732
76828
|
process.exit(1);
|
|
75733
76829
|
}
|
|
75734
|
-
const featureDir =
|
|
76830
|
+
const featureDir = join43(naxDir, "features", options.feature);
|
|
75735
76831
|
if (!existsSync32(featureDir)) {
|
|
75736
76832
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
75737
76833
|
process.exit(1);
|
|
@@ -75747,7 +76843,7 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
|
|
|
75747
76843
|
specPath: options.from,
|
|
75748
76844
|
reclassify: options.reclassify
|
|
75749
76845
|
});
|
|
75750
|
-
const prdPath =
|
|
76846
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
75751
76847
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
75752
76848
|
const c = countStories(prd);
|
|
75753
76849
|
console.log(source_default.green(`
|