@nathapp/nax 0.40.1 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +1072 -268
- package/package.json +2 -2
- package/src/acceptance/fix-generator.ts +4 -35
- package/src/acceptance/generator.ts +4 -27
- package/src/agents/acp/adapter.ts +644 -0
- package/src/agents/acp/cost.ts +79 -0
- package/src/agents/acp/index.ts +9 -0
- package/src/agents/acp/interaction-bridge.ts +126 -0
- package/src/agents/acp/parser.ts +166 -0
- package/src/agents/acp/spawn-client.ts +309 -0
- package/src/agents/acp/types.ts +22 -0
- package/src/agents/claude-complete.ts +3 -3
- package/src/agents/registry.ts +83 -0
- package/src/agents/types-extended.ts +23 -0
- package/src/agents/types.ts +17 -0
- package/src/cli/analyze.ts +6 -2
- package/src/cli/plan.ts +23 -0
- 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/pipeline/stages/execution.ts +33 -1
- package/src/pipeline/stages/routing.ts +18 -7
- package/src/pipeline/types.ts +10 -0
- package/src/tdd/orchestrator.ts +7 -0
- package/src/tdd/rectification-gate.ts +6 -0
- package/src/tdd/session-runner.ts +4 -0
package/dist/nax.js
CHANGED
|
@@ -3241,9 +3241,6 @@ async function executeComplete(binary, prompt, options) {
|
|
|
3241
3241
|
if (options?.model) {
|
|
3242
3242
|
cmd.push("--model", options.model);
|
|
3243
3243
|
}
|
|
3244
|
-
if (options?.maxTokens !== undefined) {
|
|
3245
|
-
cmd.push("--max-tokens", String(options.maxTokens));
|
|
3246
|
-
}
|
|
3247
3244
|
if (options?.jsonMode) {
|
|
3248
3245
|
cmd.push("--output-format", "json");
|
|
3249
3246
|
}
|
|
@@ -3469,6 +3466,17 @@ function estimateCostByDuration(modelTier, durationMs) {
|
|
|
3469
3466
|
confidence: "fallback"
|
|
3470
3467
|
};
|
|
3471
3468
|
}
|
|
3469
|
+
function formatCostWithConfidence(estimate) {
|
|
3470
|
+
const formattedCost = `$${estimate.cost.toFixed(2)}`;
|
|
3471
|
+
switch (estimate.confidence) {
|
|
3472
|
+
case "exact":
|
|
3473
|
+
return formattedCost;
|
|
3474
|
+
case "estimated":
|
|
3475
|
+
return `~${formattedCost}`;
|
|
3476
|
+
case "fallback":
|
|
3477
|
+
return `~${formattedCost} (duration-based)`;
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3472
3480
|
var COST_RATES;
|
|
3473
3481
|
var init_cost = __esm(() => {
|
|
3474
3482
|
COST_RATES = {
|
|
@@ -17525,7 +17533,7 @@ var init_zod = __esm(() => {
|
|
|
17525
17533
|
});
|
|
17526
17534
|
|
|
17527
17535
|
// src/config/schemas.ts
|
|
17528
|
-
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, AdaptiveRoutingConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, NaxConfigSchema;
|
|
17536
|
+
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, AdaptiveRoutingConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, NaxConfigSchema;
|
|
17529
17537
|
var init_schemas3 = __esm(() => {
|
|
17530
17538
|
init_zod();
|
|
17531
17539
|
TokenPricingSchema = exports_external.object({
|
|
@@ -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()
|
|
@@ -18676,29 +18689,10 @@ async function generateAcceptanceTests(adapter, options) {
|
|
|
18676
18689
|
logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
|
|
18677
18690
|
const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
|
|
18678
18691
|
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
|
-
}
|
|
18692
|
+
const output = await adapter.complete(prompt, {
|
|
18693
|
+
model: options.modelDef.model
|
|
18690
18694
|
});
|
|
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);
|
|
18695
|
+
const testCode = extractTestCode(output);
|
|
18702
18696
|
return {
|
|
18703
18697
|
testCode,
|
|
18704
18698
|
criteria
|
|
@@ -18801,7 +18795,7 @@ Requirements:
|
|
|
18801
18795
|
Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
|
|
18802
18796
|
}
|
|
18803
18797
|
async function generateFixStories(adapter, options) {
|
|
18804
|
-
const { failedACs, testOutput, prd, specContent,
|
|
18798
|
+
const { failedACs, testOutput, prd, specContent, modelDef } = options;
|
|
18805
18799
|
const fixStories = [];
|
|
18806
18800
|
const acTextMap = parseACTextFromSpec(specContent);
|
|
18807
18801
|
const logger = getLogger();
|
|
@@ -18816,34 +18810,9 @@ async function generateFixStories(adapter, options) {
|
|
|
18816
18810
|
}
|
|
18817
18811
|
const prompt = buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd);
|
|
18818
18812
|
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
|
-
}
|
|
18813
|
+
const fixDescription = await adapter.complete(prompt, {
|
|
18814
|
+
model: modelDef.model
|
|
18830
18815
|
});
|
|
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
18816
|
fixStories.push({
|
|
18848
18817
|
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
18849
18818
|
title: `Fix: ${failedAC} \u2014 ${acText.slice(0, 50)}`,
|
|
@@ -18910,6 +18879,679 @@ var init_acceptance = __esm(() => {
|
|
|
18910
18879
|
init_fix_generator();
|
|
18911
18880
|
});
|
|
18912
18881
|
|
|
18882
|
+
// src/agents/acp/parser.ts
|
|
18883
|
+
function parseAcpxJsonOutput(rawOutput) {
|
|
18884
|
+
const lines = rawOutput.split(`
|
|
18885
|
+
`).filter((l) => l.trim());
|
|
18886
|
+
let text = "";
|
|
18887
|
+
let tokenUsage;
|
|
18888
|
+
let stopReason;
|
|
18889
|
+
let error48;
|
|
18890
|
+
for (const line of lines) {
|
|
18891
|
+
try {
|
|
18892
|
+
const event = JSON.parse(line);
|
|
18893
|
+
if (event.content && typeof event.content === "string")
|
|
18894
|
+
text += event.content;
|
|
18895
|
+
if (event.text && typeof event.text === "string")
|
|
18896
|
+
text += event.text;
|
|
18897
|
+
if (event.result && typeof event.result === "string")
|
|
18898
|
+
text = event.result;
|
|
18899
|
+
if (event.cumulative_token_usage)
|
|
18900
|
+
tokenUsage = event.cumulative_token_usage;
|
|
18901
|
+
if (event.usage) {
|
|
18902
|
+
tokenUsage = {
|
|
18903
|
+
input_tokens: event.usage.input_tokens ?? event.usage.prompt_tokens ?? 0,
|
|
18904
|
+
output_tokens: event.usage.output_tokens ?? event.usage.completion_tokens ?? 0
|
|
18905
|
+
};
|
|
18906
|
+
}
|
|
18907
|
+
if (event.stopReason)
|
|
18908
|
+
stopReason = event.stopReason;
|
|
18909
|
+
if (event.stop_reason)
|
|
18910
|
+
stopReason = event.stop_reason;
|
|
18911
|
+
if (event.error) {
|
|
18912
|
+
error48 = typeof event.error === "string" ? event.error : event.error.message ?? JSON.stringify(event.error);
|
|
18913
|
+
}
|
|
18914
|
+
} catch {
|
|
18915
|
+
if (!text)
|
|
18916
|
+
text = line;
|
|
18917
|
+
}
|
|
18918
|
+
}
|
|
18919
|
+
return { text: text.trim(), tokenUsage, stopReason, error: error48 };
|
|
18920
|
+
}
|
|
18921
|
+
|
|
18922
|
+
// src/agents/acp/spawn-client.ts
|
|
18923
|
+
function buildAllowedEnv2(extraEnv) {
|
|
18924
|
+
const allowed = {};
|
|
18925
|
+
const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
|
|
18926
|
+
for (const varName of essentialVars) {
|
|
18927
|
+
if (process.env[varName])
|
|
18928
|
+
allowed[varName] = process.env[varName];
|
|
18929
|
+
}
|
|
18930
|
+
const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
|
|
18931
|
+
for (const varName of apiKeyVars) {
|
|
18932
|
+
if (process.env[varName])
|
|
18933
|
+
allowed[varName] = process.env[varName];
|
|
18934
|
+
}
|
|
18935
|
+
const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_"];
|
|
18936
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
18937
|
+
if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
18938
|
+
allowed[key] = value;
|
|
18939
|
+
}
|
|
18940
|
+
}
|
|
18941
|
+
if (extraEnv)
|
|
18942
|
+
Object.assign(allowed, extraEnv);
|
|
18943
|
+
return allowed;
|
|
18944
|
+
}
|
|
18945
|
+
|
|
18946
|
+
class SpawnAcpSession {
|
|
18947
|
+
agentName;
|
|
18948
|
+
sessionName;
|
|
18949
|
+
cwd;
|
|
18950
|
+
model;
|
|
18951
|
+
timeoutSeconds;
|
|
18952
|
+
permissionMode;
|
|
18953
|
+
env;
|
|
18954
|
+
constructor(opts) {
|
|
18955
|
+
this.agentName = opts.agentName;
|
|
18956
|
+
this.sessionName = opts.sessionName;
|
|
18957
|
+
this.cwd = opts.cwd;
|
|
18958
|
+
this.model = opts.model;
|
|
18959
|
+
this.timeoutSeconds = opts.timeoutSeconds;
|
|
18960
|
+
this.permissionMode = opts.permissionMode;
|
|
18961
|
+
this.env = opts.env;
|
|
18962
|
+
}
|
|
18963
|
+
async prompt(text) {
|
|
18964
|
+
const cmd = [
|
|
18965
|
+
"acpx",
|
|
18966
|
+
"--cwd",
|
|
18967
|
+
this.cwd,
|
|
18968
|
+
...this.permissionMode === "approve-all" ? ["--approve-all"] : [],
|
|
18969
|
+
"--format",
|
|
18970
|
+
"json",
|
|
18971
|
+
"--model",
|
|
18972
|
+
this.model,
|
|
18973
|
+
"--timeout",
|
|
18974
|
+
String(this.timeoutSeconds),
|
|
18975
|
+
this.agentName,
|
|
18976
|
+
"prompt",
|
|
18977
|
+
"-s",
|
|
18978
|
+
this.sessionName,
|
|
18979
|
+
"--file",
|
|
18980
|
+
"-"
|
|
18981
|
+
];
|
|
18982
|
+
getSafeLogger()?.debug("acp-adapter", `Sending prompt to session: ${this.sessionName}`);
|
|
18983
|
+
const proc = _spawnClientDeps.spawn(cmd, {
|
|
18984
|
+
cwd: this.cwd,
|
|
18985
|
+
stdin: "pipe",
|
|
18986
|
+
stdout: "pipe",
|
|
18987
|
+
stderr: "pipe",
|
|
18988
|
+
env: this.env
|
|
18989
|
+
});
|
|
18990
|
+
proc.stdin.write(text);
|
|
18991
|
+
proc.stdin.end();
|
|
18992
|
+
const exitCode = await proc.exited;
|
|
18993
|
+
const stdout = await new Response(proc.stdout).text();
|
|
18994
|
+
const stderr = await new Response(proc.stderr).text();
|
|
18995
|
+
if (exitCode !== 0) {
|
|
18996
|
+
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
18997
|
+
stderr: stderr.slice(0, 200)
|
|
18998
|
+
});
|
|
18999
|
+
return {
|
|
19000
|
+
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
19001
|
+
stopReason: "error"
|
|
19002
|
+
};
|
|
19003
|
+
}
|
|
19004
|
+
try {
|
|
19005
|
+
const parsed = parseAcpxJsonOutput(stdout);
|
|
19006
|
+
return {
|
|
19007
|
+
messages: [{ role: "assistant", content: parsed.text || "" }],
|
|
19008
|
+
stopReason: "end_turn",
|
|
19009
|
+
cumulative_token_usage: parsed.tokenUsage
|
|
19010
|
+
};
|
|
19011
|
+
} catch (err) {
|
|
19012
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
|
|
19013
|
+
stderr: stderr.slice(0, 200)
|
|
19014
|
+
});
|
|
19015
|
+
throw err;
|
|
19016
|
+
}
|
|
19017
|
+
}
|
|
19018
|
+
async close() {
|
|
19019
|
+
const cmd = ["acpx", this.agentName, "sessions", "close", this.sessionName];
|
|
19020
|
+
getSafeLogger()?.debug("acp-adapter", `Closing session: ${this.sessionName}`);
|
|
19021
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19022
|
+
const exitCode = await proc.exited;
|
|
19023
|
+
if (exitCode !== 0) {
|
|
19024
|
+
const stderr = await new Response(proc.stderr).text();
|
|
19025
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to close session", {
|
|
19026
|
+
sessionName: this.sessionName,
|
|
19027
|
+
stderr: stderr.slice(0, 200)
|
|
19028
|
+
});
|
|
19029
|
+
}
|
|
19030
|
+
}
|
|
19031
|
+
async cancelActivePrompt() {
|
|
19032
|
+
const cmd = ["acpx", this.agentName, "cancel"];
|
|
19033
|
+
getSafeLogger()?.debug("acp-adapter", `Cancelling active prompt: ${this.sessionName}`);
|
|
19034
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19035
|
+
await proc.exited;
|
|
19036
|
+
}
|
|
19037
|
+
}
|
|
19038
|
+
|
|
19039
|
+
class SpawnAcpClient {
|
|
19040
|
+
agentName;
|
|
19041
|
+
model;
|
|
19042
|
+
cwd;
|
|
19043
|
+
timeoutSeconds;
|
|
19044
|
+
env;
|
|
19045
|
+
constructor(cmdStr, cwd, timeoutSeconds) {
|
|
19046
|
+
const parts = cmdStr.split(/\s+/);
|
|
19047
|
+
const modelIdx = parts.indexOf("--model");
|
|
19048
|
+
this.model = modelIdx >= 0 && parts[modelIdx + 1] ? parts[modelIdx + 1] : "default";
|
|
19049
|
+
this.agentName = parts[parts.length - 1] || "claude";
|
|
19050
|
+
this.cwd = cwd || process.cwd();
|
|
19051
|
+
this.timeoutSeconds = timeoutSeconds || 1800;
|
|
19052
|
+
this.env = buildAllowedEnv2();
|
|
19053
|
+
}
|
|
19054
|
+
async start() {}
|
|
19055
|
+
async createSession(opts) {
|
|
19056
|
+
const sessionName = opts.sessionName || `nax-${Date.now()}`;
|
|
19057
|
+
const cmd = ["acpx", opts.agentName, "sessions", "ensure", "--name", sessionName];
|
|
19058
|
+
getSafeLogger()?.debug("acp-adapter", `Ensuring session: ${sessionName}`);
|
|
19059
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19060
|
+
const exitCode = await proc.exited;
|
|
19061
|
+
if (exitCode !== 0) {
|
|
19062
|
+
const stderr = await new Response(proc.stderr).text();
|
|
19063
|
+
throw new Error(`[acp-adapter] Failed to create session: ${stderr || `exit code ${exitCode}`}`);
|
|
19064
|
+
}
|
|
19065
|
+
return new SpawnAcpSession({
|
|
19066
|
+
agentName: opts.agentName,
|
|
19067
|
+
sessionName,
|
|
19068
|
+
cwd: this.cwd,
|
|
19069
|
+
model: this.model,
|
|
19070
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
19071
|
+
permissionMode: opts.permissionMode,
|
|
19072
|
+
env: this.env
|
|
19073
|
+
});
|
|
19074
|
+
}
|
|
19075
|
+
async loadSession(sessionName) {
|
|
19076
|
+
const cmd = ["acpx", this.agentName, "sessions", "ensure", "--name", sessionName];
|
|
19077
|
+
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
19078
|
+
const exitCode = await proc.exited;
|
|
19079
|
+
if (exitCode !== 0) {
|
|
19080
|
+
return null;
|
|
19081
|
+
}
|
|
19082
|
+
return new SpawnAcpSession({
|
|
19083
|
+
agentName: this.agentName,
|
|
19084
|
+
sessionName,
|
|
19085
|
+
cwd: this.cwd,
|
|
19086
|
+
model: this.model,
|
|
19087
|
+
timeoutSeconds: this.timeoutSeconds,
|
|
19088
|
+
permissionMode: "approve-all",
|
|
19089
|
+
env: this.env
|
|
19090
|
+
});
|
|
19091
|
+
}
|
|
19092
|
+
async close() {}
|
|
19093
|
+
}
|
|
19094
|
+
function createSpawnAcpClient(cmdStr, cwd, timeoutSeconds) {
|
|
19095
|
+
return new SpawnAcpClient(cmdStr, cwd, timeoutSeconds);
|
|
19096
|
+
}
|
|
19097
|
+
var _spawnClientDeps;
|
|
19098
|
+
var init_spawn_client = __esm(() => {
|
|
19099
|
+
init_logger2();
|
|
19100
|
+
_spawnClientDeps = {
|
|
19101
|
+
spawn(cmd, opts) {
|
|
19102
|
+
return Bun.spawn(cmd, opts);
|
|
19103
|
+
}
|
|
19104
|
+
};
|
|
19105
|
+
});
|
|
19106
|
+
|
|
19107
|
+
// src/agents/acp/cost.ts
|
|
19108
|
+
function estimateCostFromTokenUsage(usage, model) {
|
|
19109
|
+
const pricing = MODEL_PRICING[model];
|
|
19110
|
+
if (!pricing) {
|
|
19111
|
+
const fallbackInputRate = 3 / 1e6;
|
|
19112
|
+
const fallbackOutputRate = 15 / 1e6;
|
|
19113
|
+
const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
|
|
19114
|
+
const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
|
|
19115
|
+
const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
|
|
19116
|
+
const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
|
|
19117
|
+
return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
|
|
19118
|
+
}
|
|
19119
|
+
const inputRate = pricing.input / 1e6;
|
|
19120
|
+
const outputRate = pricing.output / 1e6;
|
|
19121
|
+
const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
|
|
19122
|
+
const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
|
|
19123
|
+
const inputCost = (usage.input_tokens ?? 0) * inputRate;
|
|
19124
|
+
const outputCost = (usage.output_tokens ?? 0) * outputRate;
|
|
19125
|
+
const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
|
|
19126
|
+
const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
|
|
19127
|
+
return inputCost + outputCost + cacheReadCost + cacheCreationCost;
|
|
19128
|
+
}
|
|
19129
|
+
var MODEL_PRICING;
|
|
19130
|
+
var init_cost2 = __esm(() => {
|
|
19131
|
+
MODEL_PRICING = {
|
|
19132
|
+
"claude-sonnet-4": { input: 3, output: 15 },
|
|
19133
|
+
"claude-sonnet-4-5": { input: 3, output: 15 },
|
|
19134
|
+
"claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
|
|
19135
|
+
"claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
|
|
19136
|
+
"claude-opus": { input: 15, output: 75 },
|
|
19137
|
+
"claude-opus-4": { input: 15, output: 75 },
|
|
19138
|
+
"gpt-4.1": { input: 10, output: 30 },
|
|
19139
|
+
"gpt-4": { input: 30, output: 60 },
|
|
19140
|
+
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
19141
|
+
"gemini-2.5-pro": { input: 0.075, output: 0.3 },
|
|
19142
|
+
"gemini-2-pro": { input: 0.075, output: 0.3 },
|
|
19143
|
+
codex: { input: 0.02, output: 0.06 },
|
|
19144
|
+
"code-davinci-002": { input: 0.02, output: 0.06 }
|
|
19145
|
+
};
|
|
19146
|
+
});
|
|
19147
|
+
|
|
19148
|
+
// src/agents/acp/adapter.ts
|
|
19149
|
+
import { createHash } from "crypto";
|
|
19150
|
+
import { join as join3 } from "path";
|
|
19151
|
+
function resolveRegistryEntry(agentName) {
|
|
19152
|
+
return AGENT_REGISTRY[agentName] ?? DEFAULT_ENTRY;
|
|
19153
|
+
}
|
|
19154
|
+
function isRateLimitError(err) {
|
|
19155
|
+
if (!(err instanceof Error))
|
|
19156
|
+
return false;
|
|
19157
|
+
const msg = err.message.toLowerCase();
|
|
19158
|
+
return msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429");
|
|
19159
|
+
}
|
|
19160
|
+
function buildSessionName(workdir, featureName, storyId, sessionRole) {
|
|
19161
|
+
const hash2 = createHash("sha256").update(workdir).digest("hex").slice(0, 8);
|
|
19162
|
+
const sanitize = (s) => s.replace(/[^a-z0-9]+/gi, "-").toLowerCase().replace(/^-+|-+$/g, "");
|
|
19163
|
+
const parts = ["nax", hash2];
|
|
19164
|
+
if (featureName)
|
|
19165
|
+
parts.push(sanitize(featureName));
|
|
19166
|
+
if (storyId)
|
|
19167
|
+
parts.push(sanitize(storyId));
|
|
19168
|
+
if (sessionRole)
|
|
19169
|
+
parts.push(sanitize(sessionRole));
|
|
19170
|
+
return parts.join("-");
|
|
19171
|
+
}
|
|
19172
|
+
async function ensureAcpSession(client, sessionName, agentName, permissionMode) {
|
|
19173
|
+
if (client.loadSession) {
|
|
19174
|
+
try {
|
|
19175
|
+
const existing = await client.loadSession(sessionName);
|
|
19176
|
+
if (existing) {
|
|
19177
|
+
getSafeLogger()?.debug("acp-adapter", `Resumed existing session: ${sessionName}`);
|
|
19178
|
+
return existing;
|
|
19179
|
+
}
|
|
19180
|
+
} catch {}
|
|
19181
|
+
}
|
|
19182
|
+
getSafeLogger()?.debug("acp-adapter", `Creating new session: ${sessionName}`);
|
|
19183
|
+
return client.createSession({ agentName, permissionMode, sessionName });
|
|
19184
|
+
}
|
|
19185
|
+
async function runSessionPrompt(session, prompt, timeoutMs) {
|
|
19186
|
+
const promptPromise = session.prompt(prompt);
|
|
19187
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve("timeout"), timeoutMs));
|
|
19188
|
+
const winner = await Promise.race([promptPromise, timeoutPromise]);
|
|
19189
|
+
if (winner === "timeout") {
|
|
19190
|
+
try {
|
|
19191
|
+
await session.cancelActivePrompt();
|
|
19192
|
+
} catch {
|
|
19193
|
+
await session.close().catch(() => {});
|
|
19194
|
+
}
|
|
19195
|
+
return { response: null, timedOut: true };
|
|
19196
|
+
}
|
|
19197
|
+
return { response: winner, timedOut: false };
|
|
19198
|
+
}
|
|
19199
|
+
async function closeAcpSession(session) {
|
|
19200
|
+
try {
|
|
19201
|
+
await session.close();
|
|
19202
|
+
} catch (err) {
|
|
19203
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to close session", { error: String(err) });
|
|
19204
|
+
}
|
|
19205
|
+
}
|
|
19206
|
+
function acpSessionsPath(workdir, featureName) {
|
|
19207
|
+
return join3(workdir, "nax", "features", featureName, "acp-sessions.json");
|
|
19208
|
+
}
|
|
19209
|
+
async function saveAcpSession(workdir, featureName, storyId, sessionName) {
|
|
19210
|
+
try {
|
|
19211
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19212
|
+
let data = {};
|
|
19213
|
+
try {
|
|
19214
|
+
const existing = await Bun.file(path).text();
|
|
19215
|
+
data = JSON.parse(existing);
|
|
19216
|
+
} catch {}
|
|
19217
|
+
data[storyId] = sessionName;
|
|
19218
|
+
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19219
|
+
} catch (err) {
|
|
19220
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to save session to sidecar", { error: String(err) });
|
|
19221
|
+
}
|
|
19222
|
+
}
|
|
19223
|
+
async function clearAcpSession(workdir, featureName, storyId) {
|
|
19224
|
+
try {
|
|
19225
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19226
|
+
let data = {};
|
|
19227
|
+
try {
|
|
19228
|
+
const existing = await Bun.file(path).text();
|
|
19229
|
+
data = JSON.parse(existing);
|
|
19230
|
+
} catch {
|
|
19231
|
+
return;
|
|
19232
|
+
}
|
|
19233
|
+
delete data[storyId];
|
|
19234
|
+
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19235
|
+
} catch (err) {
|
|
19236
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to clear session from sidecar", { error: String(err) });
|
|
19237
|
+
}
|
|
19238
|
+
}
|
|
19239
|
+
async function readAcpSession(workdir, featureName, storyId) {
|
|
19240
|
+
try {
|
|
19241
|
+
const path = acpSessionsPath(workdir, featureName);
|
|
19242
|
+
const existing = await Bun.file(path).text();
|
|
19243
|
+
const data = JSON.parse(existing);
|
|
19244
|
+
return data[storyId] ?? null;
|
|
19245
|
+
} catch {
|
|
19246
|
+
return null;
|
|
19247
|
+
}
|
|
19248
|
+
}
|
|
19249
|
+
function extractOutput(response) {
|
|
19250
|
+
if (!response)
|
|
19251
|
+
return "";
|
|
19252
|
+
return response.messages.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
19253
|
+
`).trim();
|
|
19254
|
+
}
|
|
19255
|
+
function extractQuestion(output) {
|
|
19256
|
+
const text = output.trim();
|
|
19257
|
+
if (!text)
|
|
19258
|
+
return null;
|
|
19259
|
+
const sentences = text.split(/(?<=[.!?])\s+/);
|
|
19260
|
+
const questionSentences = sentences.filter((s) => s.trim().endsWith("?"));
|
|
19261
|
+
if (questionSentences.length > 0) {
|
|
19262
|
+
const q = questionSentences[questionSentences.length - 1].trim();
|
|
19263
|
+
if (q.length > 10)
|
|
19264
|
+
return q;
|
|
19265
|
+
}
|
|
19266
|
+
const lower = text.toLowerCase();
|
|
19267
|
+
const markers = [
|
|
19268
|
+
"please confirm",
|
|
19269
|
+
"please specify",
|
|
19270
|
+
"please provide",
|
|
19271
|
+
"which would you",
|
|
19272
|
+
"should i ",
|
|
19273
|
+
"do you want",
|
|
19274
|
+
"can you clarify"
|
|
19275
|
+
];
|
|
19276
|
+
for (const marker of markers) {
|
|
19277
|
+
if (lower.includes(marker)) {
|
|
19278
|
+
return text.slice(-200).trim();
|
|
19279
|
+
}
|
|
19280
|
+
}
|
|
19281
|
+
return null;
|
|
19282
|
+
}
|
|
19283
|
+
|
|
19284
|
+
class AcpAgentAdapter {
|
|
19285
|
+
name;
|
|
19286
|
+
displayName;
|
|
19287
|
+
binary;
|
|
19288
|
+
capabilities;
|
|
19289
|
+
constructor(agentName) {
|
|
19290
|
+
const entry = resolveRegistryEntry(agentName);
|
|
19291
|
+
this.name = agentName;
|
|
19292
|
+
this.displayName = entry.displayName;
|
|
19293
|
+
this.binary = entry.binary;
|
|
19294
|
+
this.capabilities = {
|
|
19295
|
+
supportedTiers: entry.supportedTiers,
|
|
19296
|
+
maxContextTokens: entry.maxContextTokens,
|
|
19297
|
+
features: new Set(["tdd", "review", "refactor"])
|
|
19298
|
+
};
|
|
19299
|
+
}
|
|
19300
|
+
async isInstalled() {
|
|
19301
|
+
const path = _acpAdapterDeps.which(this.binary);
|
|
19302
|
+
return path !== null;
|
|
19303
|
+
}
|
|
19304
|
+
buildCommand(_options) {
|
|
19305
|
+
return ["acpx", this.name, "session"];
|
|
19306
|
+
}
|
|
19307
|
+
buildAllowedEnv(_options) {
|
|
19308
|
+
return {};
|
|
19309
|
+
}
|
|
19310
|
+
async run(options) {
|
|
19311
|
+
const startTime = Date.now();
|
|
19312
|
+
let lastError;
|
|
19313
|
+
getSafeLogger()?.debug("acp-adapter", `Starting run for ${this.name}`, {
|
|
19314
|
+
model: options.modelDef.model,
|
|
19315
|
+
workdir: options.workdir,
|
|
19316
|
+
featureName: options.featureName,
|
|
19317
|
+
storyId: options.storyId,
|
|
19318
|
+
sessionRole: options.sessionRole
|
|
19319
|
+
});
|
|
19320
|
+
for (let attempt = 0;attempt < MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
19321
|
+
try {
|
|
19322
|
+
const result = await this._runWithClient(options, startTime);
|
|
19323
|
+
if (!result.success) {
|
|
19324
|
+
getSafeLogger()?.warn("acp-adapter", `Run failed for ${this.name}`, { exitCode: result.exitCode });
|
|
19325
|
+
}
|
|
19326
|
+
return result;
|
|
19327
|
+
} catch (err) {
|
|
19328
|
+
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
19329
|
+
lastError = error48;
|
|
19330
|
+
const shouldRetry = isRateLimitError(error48) && attempt < MAX_RATE_LIMIT_RETRIES - 1;
|
|
19331
|
+
if (!shouldRetry)
|
|
19332
|
+
break;
|
|
19333
|
+
const backoffMs = 2 ** (attempt + 1) * 1000;
|
|
19334
|
+
getSafeLogger()?.warn("acp-adapter", "Retrying after rate limit", {
|
|
19335
|
+
backoffSeconds: backoffMs / 1000,
|
|
19336
|
+
attempt: attempt + 1
|
|
19337
|
+
});
|
|
19338
|
+
await _acpAdapterDeps.sleep(backoffMs);
|
|
19339
|
+
}
|
|
19340
|
+
}
|
|
19341
|
+
const durationMs = Date.now() - startTime;
|
|
19342
|
+
return {
|
|
19343
|
+
success: false,
|
|
19344
|
+
exitCode: 1,
|
|
19345
|
+
output: lastError?.message ?? "Run failed",
|
|
19346
|
+
rateLimited: isRateLimitError(lastError),
|
|
19347
|
+
durationMs,
|
|
19348
|
+
estimatedCost: 0
|
|
19349
|
+
};
|
|
19350
|
+
}
|
|
19351
|
+
async _runWithClient(options, startTime) {
|
|
19352
|
+
const cmdStr = `acpx --model ${options.modelDef.model} ${this.name}`;
|
|
19353
|
+
const client = _acpAdapterDeps.createClient(cmdStr, options.workdir, options.timeoutSeconds);
|
|
19354
|
+
await client.start();
|
|
19355
|
+
let sessionName = options.acpSessionName;
|
|
19356
|
+
if (!sessionName && options.featureName && options.storyId) {
|
|
19357
|
+
sessionName = await readAcpSession(options.workdir, options.featureName, options.storyId) ?? undefined;
|
|
19358
|
+
}
|
|
19359
|
+
sessionName ??= buildSessionName(options.workdir, options.featureName, options.storyId, options.sessionRole);
|
|
19360
|
+
const permissionMode = options.dangerouslySkipPermissions ? "approve-all" : "default";
|
|
19361
|
+
const session = await ensureAcpSession(client, sessionName, this.name, permissionMode);
|
|
19362
|
+
if (options.featureName && options.storyId) {
|
|
19363
|
+
await saveAcpSession(options.workdir, options.featureName, options.storyId, sessionName);
|
|
19364
|
+
}
|
|
19365
|
+
let lastResponse = null;
|
|
19366
|
+
let timedOut = false;
|
|
19367
|
+
const totalTokenUsage = { input_tokens: 0, output_tokens: 0 };
|
|
19368
|
+
try {
|
|
19369
|
+
let currentPrompt = options.prompt;
|
|
19370
|
+
let turnCount = 0;
|
|
19371
|
+
const MAX_TURNS = options.interactionBridge ? 10 : 1;
|
|
19372
|
+
while (turnCount < MAX_TURNS) {
|
|
19373
|
+
turnCount++;
|
|
19374
|
+
getSafeLogger()?.debug("acp-adapter", `Session turn ${turnCount}/${MAX_TURNS}`, { sessionName });
|
|
19375
|
+
const turnResult = await runSessionPrompt(session, currentPrompt, options.timeoutSeconds * 1000);
|
|
19376
|
+
if (turnResult.timedOut) {
|
|
19377
|
+
timedOut = true;
|
|
19378
|
+
break;
|
|
19379
|
+
}
|
|
19380
|
+
lastResponse = turnResult.response;
|
|
19381
|
+
if (!lastResponse)
|
|
19382
|
+
break;
|
|
19383
|
+
if (lastResponse.cumulative_token_usage) {
|
|
19384
|
+
totalTokenUsage.input_tokens += lastResponse.cumulative_token_usage.input_tokens ?? 0;
|
|
19385
|
+
totalTokenUsage.output_tokens += lastResponse.cumulative_token_usage.output_tokens ?? 0;
|
|
19386
|
+
}
|
|
19387
|
+
const outputText = extractOutput(lastResponse);
|
|
19388
|
+
const question = extractQuestion(outputText);
|
|
19389
|
+
if (!question || !options.interactionBridge)
|
|
19390
|
+
break;
|
|
19391
|
+
getSafeLogger()?.debug("acp-adapter", "Agent asked question, routing to interactionBridge", { question });
|
|
19392
|
+
try {
|
|
19393
|
+
const answer = await Promise.race([
|
|
19394
|
+
options.interactionBridge.onQuestionDetected(question),
|
|
19395
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("interaction timeout")), INTERACTION_TIMEOUT_MS))
|
|
19396
|
+
]);
|
|
19397
|
+
currentPrompt = answer;
|
|
19398
|
+
} catch (err) {
|
|
19399
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19400
|
+
getSafeLogger()?.warn("acp-adapter", `InteractionBridge failed: ${msg}`);
|
|
19401
|
+
break;
|
|
19402
|
+
}
|
|
19403
|
+
}
|
|
19404
|
+
if (turnCount >= MAX_TURNS) {
|
|
19405
|
+
getSafeLogger()?.warn("acp-adapter", "Reached max turns limit", { sessionName, maxTurns: MAX_TURNS });
|
|
19406
|
+
}
|
|
19407
|
+
} finally {
|
|
19408
|
+
await closeAcpSession(session);
|
|
19409
|
+
await client.close().catch(() => {});
|
|
19410
|
+
if (options.featureName && options.storyId) {
|
|
19411
|
+
await clearAcpSession(options.workdir, options.featureName, options.storyId);
|
|
19412
|
+
}
|
|
19413
|
+
}
|
|
19414
|
+
const durationMs = Date.now() - startTime;
|
|
19415
|
+
if (timedOut) {
|
|
19416
|
+
return {
|
|
19417
|
+
success: false,
|
|
19418
|
+
exitCode: 124,
|
|
19419
|
+
output: `Session timed out after ${options.timeoutSeconds}s`,
|
|
19420
|
+
rateLimited: false,
|
|
19421
|
+
durationMs,
|
|
19422
|
+
estimatedCost: 0
|
|
19423
|
+
};
|
|
19424
|
+
}
|
|
19425
|
+
const success2 = lastResponse?.stopReason === "end_turn";
|
|
19426
|
+
const output = extractOutput(lastResponse);
|
|
19427
|
+
const estimatedCost = totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0;
|
|
19428
|
+
return {
|
|
19429
|
+
success: success2,
|
|
19430
|
+
exitCode: success2 ? 0 : 1,
|
|
19431
|
+
output: output.slice(-MAX_AGENT_OUTPUT_CHARS2),
|
|
19432
|
+
rateLimited: false,
|
|
19433
|
+
durationMs,
|
|
19434
|
+
estimatedCost
|
|
19435
|
+
};
|
|
19436
|
+
}
|
|
19437
|
+
async complete(prompt, _options) {
|
|
19438
|
+
const model = _options?.model ?? "default";
|
|
19439
|
+
const cmdStr = `acpx --model ${model} ${this.name}`;
|
|
19440
|
+
const client = _acpAdapterDeps.createClient(cmdStr);
|
|
19441
|
+
await client.start();
|
|
19442
|
+
const permissionMode = _options?.dangerouslySkipPermissions ? "approve-all" : "default";
|
|
19443
|
+
let session = null;
|
|
19444
|
+
try {
|
|
19445
|
+
session = await client.createSession({ agentName: this.name, permissionMode });
|
|
19446
|
+
const response = await session.prompt(prompt);
|
|
19447
|
+
if (response.stopReason === "error") {
|
|
19448
|
+
throw new CompleteError("complete() failed: stop reason is error");
|
|
19449
|
+
}
|
|
19450
|
+
const text = response.messages.filter((m) => m.role === "assistant").map((m) => m.content).join(`
|
|
19451
|
+
`).trim();
|
|
19452
|
+
if (!text) {
|
|
19453
|
+
throw new CompleteError("complete() returned empty output");
|
|
19454
|
+
}
|
|
19455
|
+
return text;
|
|
19456
|
+
} finally {
|
|
19457
|
+
if (session) {
|
|
19458
|
+
await session.close().catch(() => {});
|
|
19459
|
+
}
|
|
19460
|
+
await client.close().catch(() => {});
|
|
19461
|
+
}
|
|
19462
|
+
}
|
|
19463
|
+
async plan(options) {
|
|
19464
|
+
if (options.interactive) {
|
|
19465
|
+
throw new Error("[acp-adapter] plan() interactive mode is not yet supported via ACP");
|
|
19466
|
+
}
|
|
19467
|
+
const modelDef = options.modelDef ?? { provider: "anthropic", model: "default" };
|
|
19468
|
+
const timeoutSeconds = options.timeoutSeconds ?? options.config?.execution?.sessionTimeoutSeconds ?? 600;
|
|
19469
|
+
const result = await this.run({
|
|
19470
|
+
prompt: options.prompt,
|
|
19471
|
+
workdir: options.workdir,
|
|
19472
|
+
modelTier: options.modelTier ?? "balanced",
|
|
19473
|
+
modelDef,
|
|
19474
|
+
timeoutSeconds,
|
|
19475
|
+
dangerouslySkipPermissions: options.dangerouslySkipPermissions ?? false,
|
|
19476
|
+
interactionBridge: options.interactionBridge,
|
|
19477
|
+
featureName: options.featureName,
|
|
19478
|
+
storyId: options.storyId,
|
|
19479
|
+
sessionRole: options.sessionRole
|
|
19480
|
+
});
|
|
19481
|
+
if (!result.success) {
|
|
19482
|
+
throw new Error(`[acp-adapter] plan() failed: ${result.output}`);
|
|
19483
|
+
}
|
|
19484
|
+
const specContent = result.output.trim();
|
|
19485
|
+
if (!specContent) {
|
|
19486
|
+
throw new Error("[acp-adapter] plan() returned empty spec content");
|
|
19487
|
+
}
|
|
19488
|
+
return { specContent };
|
|
19489
|
+
}
|
|
19490
|
+
async decompose(options) {
|
|
19491
|
+
const model = options.modelDef?.model;
|
|
19492
|
+
const prompt = buildDecomposePrompt(options);
|
|
19493
|
+
let output;
|
|
19494
|
+
try {
|
|
19495
|
+
output = await this.complete(prompt, { model, jsonMode: true });
|
|
19496
|
+
} catch (err) {
|
|
19497
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
19498
|
+
throw new Error(`[acp-adapter] decompose() failed: ${msg}`, { cause: err });
|
|
19499
|
+
}
|
|
19500
|
+
let stories;
|
|
19501
|
+
try {
|
|
19502
|
+
stories = parseDecomposeOutput(output);
|
|
19503
|
+
} catch (err) {
|
|
19504
|
+
throw new Error(`[acp-adapter] decompose() failed to parse stories: ${err.message}`, { cause: err });
|
|
19505
|
+
}
|
|
19506
|
+
return { stories };
|
|
19507
|
+
}
|
|
19508
|
+
}
|
|
19509
|
+
var MAX_AGENT_OUTPUT_CHARS2 = 5000, MAX_RATE_LIMIT_RETRIES = 3, INTERACTION_TIMEOUT_MS, AGENT_REGISTRY, DEFAULT_ENTRY, _acpAdapterDeps;
|
|
19510
|
+
var init_adapter = __esm(() => {
|
|
19511
|
+
init_logger2();
|
|
19512
|
+
init_spawn_client();
|
|
19513
|
+
init_types2();
|
|
19514
|
+
init_cost2();
|
|
19515
|
+
INTERACTION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
19516
|
+
AGENT_REGISTRY = {
|
|
19517
|
+
claude: {
|
|
19518
|
+
binary: "claude",
|
|
19519
|
+
displayName: "Claude Code (ACP)",
|
|
19520
|
+
supportedTiers: ["fast", "balanced", "powerful"],
|
|
19521
|
+
maxContextTokens: 200000
|
|
19522
|
+
},
|
|
19523
|
+
codex: {
|
|
19524
|
+
binary: "codex",
|
|
19525
|
+
displayName: "OpenAI Codex (ACP)",
|
|
19526
|
+
supportedTiers: ["fast", "balanced"],
|
|
19527
|
+
maxContextTokens: 128000
|
|
19528
|
+
},
|
|
19529
|
+
gemini: {
|
|
19530
|
+
binary: "gemini",
|
|
19531
|
+
displayName: "Gemini CLI (ACP)",
|
|
19532
|
+
supportedTiers: ["fast", "balanced", "powerful"],
|
|
19533
|
+
maxContextTokens: 1e6
|
|
19534
|
+
}
|
|
19535
|
+
};
|
|
19536
|
+
DEFAULT_ENTRY = {
|
|
19537
|
+
binary: "claude",
|
|
19538
|
+
displayName: "ACP Agent",
|
|
19539
|
+
supportedTiers: ["balanced"],
|
|
19540
|
+
maxContextTokens: 128000
|
|
19541
|
+
};
|
|
19542
|
+
_acpAdapterDeps = {
|
|
19543
|
+
which(name) {
|
|
19544
|
+
return Bun.which(name);
|
|
19545
|
+
},
|
|
19546
|
+
async sleep(ms) {
|
|
19547
|
+
await Bun.sleep(ms);
|
|
19548
|
+
},
|
|
19549
|
+
createClient(cmdStr, cwd, timeoutSeconds) {
|
|
19550
|
+
return createSpawnAcpClient(cmdStr, cwd, timeoutSeconds);
|
|
19551
|
+
}
|
|
19552
|
+
};
|
|
19553
|
+
});
|
|
19554
|
+
|
|
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.41.0",
|
|
21119
21811
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
21120
21812
|
type: "module",
|
|
21121
21813
|
bin: {
|
|
@@ -21179,8 +21871,8 @@ var init_version = __esm(() => {
|
|
|
21179
21871
|
NAX_VERSION = package_default.version;
|
|
21180
21872
|
NAX_COMMIT = (() => {
|
|
21181
21873
|
try {
|
|
21182
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
21183
|
-
return "
|
|
21874
|
+
if (/^[0-9a-f]{6,10}$/.test("0db1c5f"))
|
|
21875
|
+
return "0db1c5f";
|
|
21184
21876
|
} catch {}
|
|
21185
21877
|
try {
|
|
21186
21878
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -23460,10 +24152,10 @@ var init_autofix = __esm(() => {
|
|
|
23460
24152
|
|
|
23461
24153
|
// src/execution/progress.ts
|
|
23462
24154
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
23463
|
-
import { join as
|
|
24155
|
+
import { join as join15 } from "path";
|
|
23464
24156
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
23465
24157
|
mkdirSync2(featureDir, { recursive: true });
|
|
23466
|
-
const progressPath =
|
|
24158
|
+
const progressPath = join15(featureDir, "progress.txt");
|
|
23467
24159
|
const timestamp = new Date().toISOString();
|
|
23468
24160
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
23469
24161
|
`;
|
|
@@ -23547,7 +24239,7 @@ function estimateTokens(text) {
|
|
|
23547
24239
|
|
|
23548
24240
|
// src/constitution/loader.ts
|
|
23549
24241
|
import { existsSync as existsSync13 } from "fs";
|
|
23550
|
-
import { join as
|
|
24242
|
+
import { join as join16 } from "path";
|
|
23551
24243
|
function truncateToTokens(text, maxTokens) {
|
|
23552
24244
|
const maxChars = maxTokens * 3;
|
|
23553
24245
|
if (text.length <= maxChars) {
|
|
@@ -23569,7 +24261,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
23569
24261
|
}
|
|
23570
24262
|
let combinedContent = "";
|
|
23571
24263
|
if (!config2.skipGlobal) {
|
|
23572
|
-
const globalPath =
|
|
24264
|
+
const globalPath = join16(globalConfigDir(), config2.path);
|
|
23573
24265
|
if (existsSync13(globalPath)) {
|
|
23574
24266
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
23575
24267
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -23579,7 +24271,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
23579
24271
|
}
|
|
23580
24272
|
}
|
|
23581
24273
|
}
|
|
23582
|
-
const projectPath =
|
|
24274
|
+
const projectPath = join16(projectDir, config2.path);
|
|
23583
24275
|
if (existsSync13(projectPath)) {
|
|
23584
24276
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
23585
24277
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -24605,6 +25297,15 @@ ${pluginMarkdown}` : pluginMarkdown;
|
|
|
24605
25297
|
function validateAgentForTier(agent, tier) {
|
|
24606
25298
|
return agent.capabilities.supportedTiers.includes(tier);
|
|
24607
25299
|
}
|
|
25300
|
+
function validateAgentFeature(agent, feature) {
|
|
25301
|
+
return agent.capabilities.features.has(feature);
|
|
25302
|
+
}
|
|
25303
|
+
function describeAgentCapabilities(agent) {
|
|
25304
|
+
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
25305
|
+
const features = Array.from(agent.capabilities.features).join(",");
|
|
25306
|
+
const maxTokens = agent.capabilities.maxContextTokens;
|
|
25307
|
+
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
25308
|
+
}
|
|
24608
25309
|
|
|
24609
25310
|
// src/agents/version-detection.ts
|
|
24610
25311
|
async function getAgentVersion(binaryName) {
|
|
@@ -24655,6 +25356,26 @@ var init_version_detection = __esm(() => {
|
|
|
24655
25356
|
});
|
|
24656
25357
|
|
|
24657
25358
|
// src/agents/index.ts
|
|
25359
|
+
var exports_agents = {};
|
|
25360
|
+
__export(exports_agents, {
|
|
25361
|
+
validateAgentForTier: () => validateAgentForTier,
|
|
25362
|
+
validateAgentFeature: () => validateAgentFeature,
|
|
25363
|
+
parseTokenUsage: () => parseTokenUsage,
|
|
25364
|
+
getInstalledAgents: () => getInstalledAgents,
|
|
25365
|
+
getAllAgentNames: () => getAllAgentNames,
|
|
25366
|
+
getAgentVersions: () => getAgentVersions,
|
|
25367
|
+
getAgentVersion: () => getAgentVersion,
|
|
25368
|
+
getAgent: () => getAgent,
|
|
25369
|
+
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
25370
|
+
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
25371
|
+
estimateCostByDuration: () => estimateCostByDuration,
|
|
25372
|
+
estimateCost: () => estimateCost,
|
|
25373
|
+
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
25374
|
+
checkAgentHealth: () => checkAgentHealth,
|
|
25375
|
+
CompleteError: () => CompleteError,
|
|
25376
|
+
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
25377
|
+
COST_RATES: () => COST_RATES
|
|
25378
|
+
});
|
|
24658
25379
|
var init_agents = __esm(() => {
|
|
24659
25380
|
init_types2();
|
|
24660
25381
|
init_claude();
|
|
@@ -24732,14 +25453,14 @@ var init_isolation = __esm(() => {
|
|
|
24732
25453
|
|
|
24733
25454
|
// src/context/greenfield.ts
|
|
24734
25455
|
import { readdir } from "fs/promises";
|
|
24735
|
-
import { join as
|
|
25456
|
+
import { join as join17 } from "path";
|
|
24736
25457
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
24737
25458
|
const results = [];
|
|
24738
25459
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
24739
25460
|
try {
|
|
24740
25461
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
24741
25462
|
for (const entry of entries) {
|
|
24742
|
-
const fullPath =
|
|
25463
|
+
const fullPath = join17(dir, entry.name);
|
|
24743
25464
|
if (entry.isDirectory()) {
|
|
24744
25465
|
if (ignoreDirs.has(entry.name))
|
|
24745
25466
|
continue;
|
|
@@ -25127,13 +25848,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
25127
25848
|
|
|
25128
25849
|
// src/verification/runners.ts
|
|
25129
25850
|
import { existsSync as existsSync14 } from "fs";
|
|
25130
|
-
import { join as
|
|
25851
|
+
import { join as join18 } from "path";
|
|
25131
25852
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
25132
25853
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
25133
25854
|
return { success: true, missingFiles: [] };
|
|
25134
25855
|
const missingFiles = [];
|
|
25135
25856
|
for (const file2 of expectedFiles) {
|
|
25136
|
-
if (!existsSync14(
|
|
25857
|
+
if (!existsSync14(join18(workingDirectory, file2)))
|
|
25137
25858
|
missingFiles.push(file2);
|
|
25138
25859
|
}
|
|
25139
25860
|
if (missingFiles.length > 0) {
|
|
@@ -25345,7 +26066,7 @@ var init_prompts = __esm(() => {
|
|
|
25345
26066
|
});
|
|
25346
26067
|
|
|
25347
26068
|
// src/tdd/rectification-gate.ts
|
|
25348
|
-
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger) {
|
|
26069
|
+
async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName) {
|
|
25349
26070
|
const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
|
|
25350
26071
|
if (!rectificationEnabled)
|
|
25351
26072
|
return false;
|
|
@@ -25361,7 +26082,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
25361
26082
|
if (!fullSuitePassed && fullSuiteResult.output) {
|
|
25362
26083
|
const testSummary = parseBunTestOutput(fullSuiteResult.output);
|
|
25363
26084
|
if (testSummary.failed > 0) {
|
|
25364
|
-
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout);
|
|
26085
|
+
return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName);
|
|
25365
26086
|
}
|
|
25366
26087
|
if (testSummary.passed > 0) {
|
|
25367
26088
|
logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
|
|
@@ -25389,7 +26110,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
|
|
|
25389
26110
|
});
|
|
25390
26111
|
return false;
|
|
25391
26112
|
}
|
|
25392
|
-
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout) {
|
|
26113
|
+
async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName) {
|
|
25393
26114
|
const rectificationState = {
|
|
25394
26115
|
attempt: 0,
|
|
25395
26116
|
initialFailures: testSummary.failed,
|
|
@@ -25411,7 +26132,10 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
25411
26132
|
modelTier: implementerTier,
|
|
25412
26133
|
modelDef: resolveModel(config2.models[implementerTier]),
|
|
25413
26134
|
timeoutSeconds: config2.execution.sessionTimeoutSeconds,
|
|
25414
|
-
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions
|
|
26135
|
+
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions,
|
|
26136
|
+
featureName,
|
|
26137
|
+
storyId: story.id,
|
|
26138
|
+
sessionRole: "implementer"
|
|
25415
26139
|
});
|
|
25416
26140
|
if (!rectifyResult.success && rectifyResult.pid) {
|
|
25417
26141
|
await cleanupProcessTree(rectifyResult.pid);
|
|
@@ -25808,13 +26532,13 @@ var exports_loader = {};
|
|
|
25808
26532
|
__export(exports_loader, {
|
|
25809
26533
|
loadOverride: () => loadOverride
|
|
25810
26534
|
});
|
|
25811
|
-
import { join as
|
|
26535
|
+
import { join as join19 } from "path";
|
|
25812
26536
|
async function loadOverride(role, workdir, config2) {
|
|
25813
26537
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
25814
26538
|
if (!overridePath) {
|
|
25815
26539
|
return null;
|
|
25816
26540
|
}
|
|
25817
|
-
const absolutePath =
|
|
26541
|
+
const absolutePath = join19(workdir, overridePath);
|
|
25818
26542
|
const file2 = Bun.file(absolutePath);
|
|
25819
26543
|
if (!await file2.exists()) {
|
|
25820
26544
|
return null;
|
|
@@ -25993,7 +26717,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
25993
26717
|
}
|
|
25994
26718
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
25995
26719
|
}
|
|
25996
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution) {
|
|
26720
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName) {
|
|
25997
26721
|
const startTime = Date.now();
|
|
25998
26722
|
let prompt;
|
|
25999
26723
|
switch (role) {
|
|
@@ -26015,7 +26739,10 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
26015
26739
|
modelTier,
|
|
26016
26740
|
modelDef: resolveModel(config2.models[modelTier]),
|
|
26017
26741
|
timeoutSeconds: config2.execution.sessionTimeoutSeconds,
|
|
26018
|
-
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions
|
|
26742
|
+
dangerouslySkipPermissions: config2.execution.dangerouslySkipPermissions,
|
|
26743
|
+
featureName,
|
|
26744
|
+
storyId: story.id,
|
|
26745
|
+
sessionRole: role
|
|
26019
26746
|
});
|
|
26020
26747
|
if (!result.success && result.pid) {
|
|
26021
26748
|
await cleanupProcessTree(result.pid);
|
|
@@ -26363,6 +27090,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26363
27090
|
config: config2,
|
|
26364
27091
|
workdir,
|
|
26365
27092
|
modelTier,
|
|
27093
|
+
featureName,
|
|
26366
27094
|
contextMarkdown,
|
|
26367
27095
|
constitution,
|
|
26368
27096
|
dryRun = false,
|
|
@@ -26426,7 +27154,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26426
27154
|
let session1;
|
|
26427
27155
|
if (!isRetry) {
|
|
26428
27156
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
26429
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution);
|
|
27157
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
26430
27158
|
sessions.push(session1);
|
|
26431
27159
|
}
|
|
26432
27160
|
if (session1 && !session1.success) {
|
|
@@ -26488,7 +27216,7 @@ async function runThreeSessionTdd(options) {
|
|
|
26488
27216
|
});
|
|
26489
27217
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
26490
27218
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
26491
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution);
|
|
27219
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
26492
27220
|
sessions.push(session2);
|
|
26493
27221
|
if (!session2.success) {
|
|
26494
27222
|
needsHumanReview = true;
|
|
@@ -26504,10 +27232,10 @@ async function runThreeSessionTdd(options) {
|
|
|
26504
27232
|
lite
|
|
26505
27233
|
};
|
|
26506
27234
|
}
|
|
26507
|
-
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger);
|
|
27235
|
+
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName);
|
|
26508
27236
|
const session3Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
26509
27237
|
const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
|
|
26510
|
-
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution);
|
|
27238
|
+
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName);
|
|
26511
27239
|
sessions.push(session3);
|
|
26512
27240
|
const verdict = await readVerdict(workdir);
|
|
26513
27241
|
await cleanupVerdict(workdir);
|
|
@@ -26666,7 +27394,7 @@ var init_execution = __esm(() => {
|
|
|
26666
27394
|
enabled: () => true,
|
|
26667
27395
|
async execute(ctx) {
|
|
26668
27396
|
const logger = getLogger();
|
|
26669
|
-
const agent = _executionDeps.getAgent(ctx.config.autoMode.defaultAgent);
|
|
27397
|
+
const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.config.autoMode.defaultAgent);
|
|
26670
27398
|
if (!agent) {
|
|
26671
27399
|
return {
|
|
26672
27400
|
action: "fail",
|
|
@@ -26686,6 +27414,7 @@ var init_execution = __esm(() => {
|
|
|
26686
27414
|
config: ctx.config,
|
|
26687
27415
|
workdir: ctx.workdir,
|
|
26688
27416
|
modelTier: ctx.routing.modelTier,
|
|
27417
|
+
featureName: ctx.prd.feature,
|
|
26689
27418
|
contextMarkdown: ctx.contextMarkdown,
|
|
26690
27419
|
constitution: ctx.constitution?.content,
|
|
26691
27420
|
dryRun: false,
|
|
@@ -26755,7 +27484,38 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
26755
27484
|
modelTier: ctx.routing.modelTier,
|
|
26756
27485
|
modelDef: resolveModel(ctx.config.models[ctx.routing.modelTier]),
|
|
26757
27486
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
26758
|
-
dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions
|
|
27487
|
+
dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions,
|
|
27488
|
+
pidRegistry: ctx.pidRegistry,
|
|
27489
|
+
featureName: ctx.prd.feature,
|
|
27490
|
+
storyId: ctx.story.id,
|
|
27491
|
+
interactionBridge: (() => {
|
|
27492
|
+
const plugin = ctx.interaction?.getPrimary();
|
|
27493
|
+
if (!plugin)
|
|
27494
|
+
return;
|
|
27495
|
+
const QUESTION_PATTERNS2 = [/\?/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
|
|
27496
|
+
return {
|
|
27497
|
+
detectQuestion: async (text) => QUESTION_PATTERNS2.some((p) => p.test(text)),
|
|
27498
|
+
onQuestionDetected: async (text) => {
|
|
27499
|
+
const requestId = `ix-acp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
27500
|
+
await plugin.send({
|
|
27501
|
+
id: requestId,
|
|
27502
|
+
type: "input",
|
|
27503
|
+
featureName: ctx.prd.feature,
|
|
27504
|
+
storyId: ctx.story.id,
|
|
27505
|
+
stage: "execution",
|
|
27506
|
+
summary: text,
|
|
27507
|
+
fallback: "continue",
|
|
27508
|
+
createdAt: Date.now()
|
|
27509
|
+
});
|
|
27510
|
+
try {
|
|
27511
|
+
const response = await plugin.receive(requestId, 120000);
|
|
27512
|
+
return response.value ?? "continue";
|
|
27513
|
+
} catch {
|
|
27514
|
+
return "continue";
|
|
27515
|
+
}
|
|
27516
|
+
}
|
|
27517
|
+
};
|
|
27518
|
+
})()
|
|
26759
27519
|
});
|
|
26760
27520
|
ctx.agentResult = result;
|
|
26761
27521
|
await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
|
|
@@ -27971,16 +28731,20 @@ var init_regression2 = __esm(() => {
|
|
|
27971
28731
|
});
|
|
27972
28732
|
|
|
27973
28733
|
// src/pipeline/stages/routing.ts
|
|
27974
|
-
async function runDecompose(story, prd, config2, _workdir) {
|
|
28734
|
+
async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
|
|
27975
28735
|
const naxDecompose = config2.decompose;
|
|
27976
28736
|
const builderConfig = {
|
|
27977
28737
|
maxSubStories: naxDecompose?.maxSubstories ?? 5,
|
|
27978
28738
|
maxComplexity: naxDecompose?.maxSubstoryComplexity ?? "medium",
|
|
27979
28739
|
maxRetries: naxDecompose?.maxRetries ?? 2
|
|
27980
28740
|
};
|
|
28741
|
+
const agent = (agentGetFn ?? getAgent)(config2.autoMode.defaultAgent);
|
|
28742
|
+
if (!agent) {
|
|
28743
|
+
throw new Error(`[decompose] Agent "${config2.autoMode.defaultAgent}" not found \u2014 cannot decompose`);
|
|
28744
|
+
}
|
|
27981
28745
|
const adapter = {
|
|
27982
|
-
async decompose(
|
|
27983
|
-
|
|
28746
|
+
async decompose(prompt) {
|
|
28747
|
+
return agent.complete(prompt, { jsonMode: true });
|
|
27984
28748
|
}
|
|
27985
28749
|
};
|
|
27986
28750
|
return DecomposeBuilder.for(story).prd(prd).config(builderConfig).decompose(adapter);
|
|
@@ -28001,7 +28765,7 @@ var init_routing2 = __esm(() => {
|
|
|
28001
28765
|
async execute(ctx) {
|
|
28002
28766
|
const logger = getLogger();
|
|
28003
28767
|
const agentName = ctx.config.execution?.agent ?? "claude";
|
|
28004
|
-
const adapter = _routingDeps.getAgent(agentName);
|
|
28768
|
+
const adapter = (ctx.agentGetFn ?? _routingDeps.getAgent)(agentName);
|
|
28005
28769
|
const hasExistingRouting = ctx.story.routing !== undefined;
|
|
28006
28770
|
const hasContentHash = ctx.story.routing?.contentHash !== undefined;
|
|
28007
28771
|
let currentHash;
|
|
@@ -28070,7 +28834,7 @@ var init_routing2 = __esm(() => {
|
|
|
28070
28834
|
if (decomposeConfig.trigger === "disabled") {
|
|
28071
28835
|
logger.warn("routing", `Story ${ctx.story.id} is oversized (${acCount} ACs) but decompose is disabled \u2014 continuing with original`);
|
|
28072
28836
|
} else if (decomposeConfig.trigger === "auto") {
|
|
28073
|
-
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
|
|
28837
|
+
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
|
|
28074
28838
|
if (result.validation.valid) {
|
|
28075
28839
|
_routingDeps.applyDecomposition(ctx.prd, result);
|
|
28076
28840
|
if (ctx.prdPath) {
|
|
@@ -28085,7 +28849,7 @@ var init_routing2 = __esm(() => {
|
|
|
28085
28849
|
} else if (decomposeConfig.trigger === "confirm") {
|
|
28086
28850
|
const action = await _routingDeps.checkStoryOversized({ featureName: ctx.prd.feature, storyId: ctx.story.id, criteriaCount: acCount }, ctx.config, ctx.interaction);
|
|
28087
28851
|
if (action === "decompose") {
|
|
28088
|
-
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
|
|
28852
|
+
const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
|
|
28089
28853
|
if (result.validation.valid) {
|
|
28090
28854
|
_routingDeps.applyDecomposition(ctx.prd, result);
|
|
28091
28855
|
if (ctx.prdPath) {
|
|
@@ -29573,19 +30337,19 @@ var init_precheck = __esm(() => {
|
|
|
29573
30337
|
});
|
|
29574
30338
|
|
|
29575
30339
|
// src/hooks/runner.ts
|
|
29576
|
-
import { join as
|
|
30340
|
+
import { join as join37 } from "path";
|
|
29577
30341
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
29578
30342
|
let globalHooks = { hooks: {} };
|
|
29579
30343
|
let projectHooks = { hooks: {} };
|
|
29580
30344
|
let skipGlobal = false;
|
|
29581
|
-
const projectPath =
|
|
30345
|
+
const projectPath = join37(projectDir, "hooks.json");
|
|
29582
30346
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
29583
30347
|
if (projectData) {
|
|
29584
30348
|
projectHooks = projectData;
|
|
29585
30349
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
29586
30350
|
}
|
|
29587
30351
|
if (!skipGlobal && globalDir) {
|
|
29588
|
-
const globalPath =
|
|
30352
|
+
const globalPath = join37(globalDir, "hooks.json");
|
|
29589
30353
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
29590
30354
|
if (globalData) {
|
|
29591
30355
|
globalHooks = globalData;
|
|
@@ -30026,7 +30790,8 @@ function buildResult(success2, prd, totalCost, iterations, storiesCompleted, prd
|
|
|
30026
30790
|
}
|
|
30027
30791
|
async function generateAndAddFixStories(ctx, failures, prd) {
|
|
30028
30792
|
const logger = getSafeLogger();
|
|
30029
|
-
const
|
|
30793
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
30794
|
+
const agent = (ctx.agentGetFn ?? getAgent2)(ctx.config.autoMode.defaultAgent);
|
|
30030
30795
|
if (!agent) {
|
|
30031
30796
|
logger?.error("acceptance", "Agent not found, cannot generate fix stories");
|
|
30032
30797
|
return null;
|
|
@@ -30172,7 +30937,6 @@ async function runAcceptanceLoop(ctx) {
|
|
|
30172
30937
|
}
|
|
30173
30938
|
var init_acceptance_loop = __esm(() => {
|
|
30174
30939
|
init_acceptance();
|
|
30175
|
-
init_agents();
|
|
30176
30940
|
init_schema();
|
|
30177
30941
|
init_hooks();
|
|
30178
30942
|
init_logger2();
|
|
@@ -30622,12 +31386,12 @@ __export(exports_manager, {
|
|
|
30622
31386
|
WorktreeManager: () => WorktreeManager
|
|
30623
31387
|
});
|
|
30624
31388
|
import { existsSync as existsSync30, symlinkSync } from "fs";
|
|
30625
|
-
import { join as
|
|
31389
|
+
import { join as join38 } from "path";
|
|
30626
31390
|
|
|
30627
31391
|
class WorktreeManager {
|
|
30628
31392
|
async create(projectRoot, storyId) {
|
|
30629
31393
|
validateStoryId(storyId);
|
|
30630
|
-
const worktreePath =
|
|
31394
|
+
const worktreePath = join38(projectRoot, ".nax-wt", storyId);
|
|
30631
31395
|
const branchName = `nax/${storyId}`;
|
|
30632
31396
|
try {
|
|
30633
31397
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -30652,9 +31416,9 @@ class WorktreeManager {
|
|
|
30652
31416
|
}
|
|
30653
31417
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
30654
31418
|
}
|
|
30655
|
-
const nodeModulesSource =
|
|
31419
|
+
const nodeModulesSource = join38(projectRoot, "node_modules");
|
|
30656
31420
|
if (existsSync30(nodeModulesSource)) {
|
|
30657
|
-
const nodeModulesTarget =
|
|
31421
|
+
const nodeModulesTarget = join38(worktreePath, "node_modules");
|
|
30658
31422
|
try {
|
|
30659
31423
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
30660
31424
|
} catch (error48) {
|
|
@@ -30662,9 +31426,9 @@ class WorktreeManager {
|
|
|
30662
31426
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
30663
31427
|
}
|
|
30664
31428
|
}
|
|
30665
|
-
const envSource =
|
|
31429
|
+
const envSource = join38(projectRoot, ".env");
|
|
30666
31430
|
if (existsSync30(envSource)) {
|
|
30667
|
-
const envTarget =
|
|
31431
|
+
const envTarget = join38(worktreePath, ".env");
|
|
30668
31432
|
try {
|
|
30669
31433
|
symlinkSync(envSource, envTarget, "file");
|
|
30670
31434
|
} catch (error48) {
|
|
@@ -30675,7 +31439,7 @@ class WorktreeManager {
|
|
|
30675
31439
|
}
|
|
30676
31440
|
async remove(projectRoot, storyId) {
|
|
30677
31441
|
validateStoryId(storyId);
|
|
30678
|
-
const worktreePath =
|
|
31442
|
+
const worktreePath = join38(projectRoot, ".nax-wt", storyId);
|
|
30679
31443
|
const branchName = `nax/${storyId}`;
|
|
30680
31444
|
try {
|
|
30681
31445
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -31065,7 +31829,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
31065
31829
|
|
|
31066
31830
|
// src/execution/parallel-coordinator.ts
|
|
31067
31831
|
import os3 from "os";
|
|
31068
|
-
import { join as
|
|
31832
|
+
import { join as join39 } from "path";
|
|
31069
31833
|
function groupStoriesByDependencies(stories) {
|
|
31070
31834
|
const batches = [];
|
|
31071
31835
|
const processed = new Set;
|
|
@@ -31142,7 +31906,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
31142
31906
|
};
|
|
31143
31907
|
const worktreePaths = new Map;
|
|
31144
31908
|
for (const story of batch) {
|
|
31145
|
-
const worktreePath =
|
|
31909
|
+
const worktreePath = join39(projectRoot, ".nax-wt", story.id);
|
|
31146
31910
|
try {
|
|
31147
31911
|
await worktreeManager.create(projectRoot, story.id);
|
|
31148
31912
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -31191,7 +31955,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
31191
31955
|
});
|
|
31192
31956
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
31193
31957
|
storyId: mergeResult.storyId,
|
|
31194
|
-
worktreePath:
|
|
31958
|
+
worktreePath: join39(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
31195
31959
|
});
|
|
31196
31960
|
}
|
|
31197
31961
|
}
|
|
@@ -31650,12 +32414,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
31650
32414
|
// src/pipeline/subscribers/events-writer.ts
|
|
31651
32415
|
import { appendFile as appendFile2, mkdir } from "fs/promises";
|
|
31652
32416
|
import { homedir as homedir5 } from "os";
|
|
31653
|
-
import { basename as basename3, join as
|
|
32417
|
+
import { basename as basename3, join as join40 } from "path";
|
|
31654
32418
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
31655
32419
|
const logger = getSafeLogger();
|
|
31656
32420
|
const project = basename3(workdir);
|
|
31657
|
-
const eventsDir =
|
|
31658
|
-
const eventsFile =
|
|
32421
|
+
const eventsDir = join40(homedir5(), ".nax", "events", project);
|
|
32422
|
+
const eventsFile = join40(eventsDir, "events.jsonl");
|
|
31659
32423
|
let dirReady = false;
|
|
31660
32424
|
const write = (line) => {
|
|
31661
32425
|
(async () => {
|
|
@@ -31815,12 +32579,12 @@ var init_interaction2 = __esm(() => {
|
|
|
31815
32579
|
// src/pipeline/subscribers/registry.ts
|
|
31816
32580
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
31817
32581
|
import { homedir as homedir6 } from "os";
|
|
31818
|
-
import { basename as basename4, join as
|
|
32582
|
+
import { basename as basename4, join as join41 } from "path";
|
|
31819
32583
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
31820
32584
|
const logger = getSafeLogger();
|
|
31821
32585
|
const project = basename4(workdir);
|
|
31822
|
-
const runDir =
|
|
31823
|
-
const metaFile =
|
|
32586
|
+
const runDir = join41(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
32587
|
+
const metaFile = join41(runDir, "meta.json");
|
|
31824
32588
|
const unsub = bus.on("run:started", (_ev) => {
|
|
31825
32589
|
(async () => {
|
|
31826
32590
|
try {
|
|
@@ -31830,8 +32594,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
31830
32594
|
project,
|
|
31831
32595
|
feature,
|
|
31832
32596
|
workdir,
|
|
31833
|
-
statusPath:
|
|
31834
|
-
eventsDir:
|
|
32597
|
+
statusPath: join41(workdir, "nax", "features", feature, "status.json"),
|
|
32598
|
+
eventsDir: join41(workdir, "nax", "features", feature, "runs"),
|
|
31835
32599
|
registeredAt: new Date().toISOString()
|
|
31836
32600
|
};
|
|
31837
32601
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -32499,6 +33263,8 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
32499
33263
|
storyStartTime: new Date().toISOString(),
|
|
32500
33264
|
storyGitRef: storyGitRef ?? undefined,
|
|
32501
33265
|
interaction: ctx.interactionChain ?? undefined,
|
|
33266
|
+
agentGetFn: ctx.agentGetFn,
|
|
33267
|
+
pidRegistry: ctx.pidRegistry,
|
|
32502
33268
|
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
32503
33269
|
};
|
|
32504
33270
|
ctx.statusWriter.setPrd(prd);
|
|
@@ -32825,7 +33591,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
32825
33591
|
var init_status_file = () => {};
|
|
32826
33592
|
|
|
32827
33593
|
// src/execution/status-writer.ts
|
|
32828
|
-
import { join as
|
|
33594
|
+
import { join as join42 } from "path";
|
|
32829
33595
|
|
|
32830
33596
|
class StatusWriter {
|
|
32831
33597
|
statusFile;
|
|
@@ -32893,7 +33659,7 @@ class StatusWriter {
|
|
|
32893
33659
|
if (!this._prd)
|
|
32894
33660
|
return;
|
|
32895
33661
|
const safeLogger = getSafeLogger();
|
|
32896
|
-
const featureStatusPath =
|
|
33662
|
+
const featureStatusPath = join42(featureDir, "status.json");
|
|
32897
33663
|
try {
|
|
32898
33664
|
const base = this.getSnapshot(totalCost, iterations);
|
|
32899
33665
|
if (!base) {
|
|
@@ -33097,6 +33863,7 @@ var init_precheck_runner = __esm(() => {
|
|
|
33097
33863
|
// src/execution/lifecycle/run-initialization.ts
|
|
33098
33864
|
var exports_run_initialization = {};
|
|
33099
33865
|
__export(exports_run_initialization, {
|
|
33866
|
+
logActiveProtocol: () => logActiveProtocol,
|
|
33100
33867
|
initializeRun: () => initializeRun
|
|
33101
33868
|
});
|
|
33102
33869
|
async function reconcileState(prd, prdPath, workdir) {
|
|
@@ -33123,11 +33890,12 @@ async function reconcileState(prd, prdPath, workdir) {
|
|
|
33123
33890
|
}
|
|
33124
33891
|
return prd;
|
|
33125
33892
|
}
|
|
33126
|
-
async function checkAgentInstalled(config2, dryRun) {
|
|
33893
|
+
async function checkAgentInstalled(config2, dryRun, agentGetFn) {
|
|
33127
33894
|
if (dryRun)
|
|
33128
33895
|
return;
|
|
33129
33896
|
const logger = getSafeLogger();
|
|
33130
|
-
const
|
|
33897
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
33898
|
+
const agent = (agentGetFn ?? getAgent2)(config2.autoMode.defaultAgent);
|
|
33131
33899
|
if (!agent) {
|
|
33132
33900
|
logger?.error("execution", "Agent not found", {
|
|
33133
33901
|
agent: config2.autoMode.defaultAgent
|
|
@@ -33155,9 +33923,14 @@ function validateStoryCount(counts, config2) {
|
|
|
33155
33923
|
throw new StoryLimitExceededError(counts.total, config2.execution.maxStoriesPerFeature);
|
|
33156
33924
|
}
|
|
33157
33925
|
}
|
|
33926
|
+
function logActiveProtocol(config2) {
|
|
33927
|
+
const logger = getSafeLogger();
|
|
33928
|
+
const protocol = config2.agent?.protocol ?? "cli";
|
|
33929
|
+
logger?.info("run-initialization", `Agent protocol: ${protocol}`, { protocol });
|
|
33930
|
+
}
|
|
33158
33931
|
async function initializeRun(ctx) {
|
|
33159
33932
|
const logger = getSafeLogger();
|
|
33160
|
-
await checkAgentInstalled(ctx.config, ctx.dryRun);
|
|
33933
|
+
await checkAgentInstalled(ctx.config, ctx.dryRun, ctx.agentGetFn);
|
|
33161
33934
|
let prd = await loadPRD(ctx.prdPath);
|
|
33162
33935
|
prd = await reconcileState(prd, ctx.prdPath, ctx.workdir);
|
|
33163
33936
|
const counts = countStories(prd);
|
|
@@ -33170,7 +33943,6 @@ async function initializeRun(ctx) {
|
|
|
33170
33943
|
return { prd, storyCounts: counts };
|
|
33171
33944
|
}
|
|
33172
33945
|
var init_run_initialization = __esm(() => {
|
|
33173
|
-
init_agents();
|
|
33174
33946
|
init_errors3();
|
|
33175
33947
|
init_logger2();
|
|
33176
33948
|
init_prd();
|
|
@@ -33279,7 +34051,8 @@ async function setupRun(options) {
|
|
|
33279
34051
|
config: config2,
|
|
33280
34052
|
prdPath,
|
|
33281
34053
|
workdir,
|
|
33282
|
-
dryRun
|
|
34054
|
+
dryRun,
|
|
34055
|
+
agentGetFn: options.agentGetFn
|
|
33283
34056
|
});
|
|
33284
34057
|
prd = initResult.prd;
|
|
33285
34058
|
const counts = initResult.storyCounts;
|
|
@@ -64209,7 +64982,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
64209
64982
|
init_source();
|
|
64210
64983
|
import { existsSync as existsSync32, mkdirSync as mkdirSync6 } from "fs";
|
|
64211
64984
|
import { homedir as homedir8 } from "os";
|
|
64212
|
-
import { join as
|
|
64985
|
+
import { join as join43 } from "path";
|
|
64213
64986
|
|
|
64214
64987
|
// node_modules/commander/esm.mjs
|
|
64215
64988
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -64231,14 +65004,14 @@ var {
|
|
|
64231
65004
|
init_acceptance();
|
|
64232
65005
|
init_registry();
|
|
64233
65006
|
import { existsSync as existsSync8 } from "fs";
|
|
64234
|
-
import { join as
|
|
65007
|
+
import { join as join9 } from "path";
|
|
64235
65008
|
|
|
64236
65009
|
// src/analyze/scanner.ts
|
|
64237
65010
|
import { existsSync as existsSync2 } from "fs";
|
|
64238
|
-
import { join as
|
|
65011
|
+
import { join as join4 } from "path";
|
|
64239
65012
|
async function scanCodebase(workdir) {
|
|
64240
|
-
const srcPath =
|
|
64241
|
-
const packageJsonPath =
|
|
65013
|
+
const srcPath = join4(workdir, "src");
|
|
65014
|
+
const packageJsonPath = join4(workdir, "package.json");
|
|
64242
65015
|
const fileTree = existsSync2(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
|
|
64243
65016
|
let dependencies = {};
|
|
64244
65017
|
let devDependencies = {};
|
|
@@ -64278,7 +65051,7 @@ async function generateFileTree(dir, maxDepth, currentDepth = 0, prefix = "") {
|
|
|
64278
65051
|
});
|
|
64279
65052
|
for (let i = 0;i < dirEntries.length; i++) {
|
|
64280
65053
|
const entry = dirEntries[i];
|
|
64281
|
-
const fullPath =
|
|
65054
|
+
const fullPath = join4(dir, entry);
|
|
64282
65055
|
const isLast = i === dirEntries.length - 1;
|
|
64283
65056
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
64284
65057
|
const childPrefix = isLast ? " " : "\u2502 ";
|
|
@@ -64310,16 +65083,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
|
|
|
64310
65083
|
} else {
|
|
64311
65084
|
patterns.push("Test framework: likely bun:test (no framework dependency)");
|
|
64312
65085
|
}
|
|
64313
|
-
if (existsSync2(
|
|
65086
|
+
if (existsSync2(join4(workdir, "test"))) {
|
|
64314
65087
|
patterns.push("Test directory: test/");
|
|
64315
65088
|
}
|
|
64316
|
-
if (existsSync2(
|
|
65089
|
+
if (existsSync2(join4(workdir, "__tests__"))) {
|
|
64317
65090
|
patterns.push("Test directory: __tests__/");
|
|
64318
65091
|
}
|
|
64319
|
-
if (existsSync2(
|
|
65092
|
+
if (existsSync2(join4(workdir, "tests"))) {
|
|
64320
65093
|
patterns.push("Test directory: tests/");
|
|
64321
65094
|
}
|
|
64322
|
-
const hasTestFiles = existsSync2(
|
|
65095
|
+
const hasTestFiles = existsSync2(join4(workdir, "test")) || existsSync2(join4(workdir, "src"));
|
|
64323
65096
|
if (hasTestFiles) {
|
|
64324
65097
|
patterns.push("Test files: *.test.ts, *.spec.ts");
|
|
64325
65098
|
}
|
|
@@ -64337,7 +65110,7 @@ init_version();
|
|
|
64337
65110
|
// src/cli/analyze-parser.ts
|
|
64338
65111
|
init_registry();
|
|
64339
65112
|
import { existsSync as existsSync7 } from "fs";
|
|
64340
|
-
import { join as
|
|
65113
|
+
import { join as join8 } from "path";
|
|
64341
65114
|
init_schema();
|
|
64342
65115
|
init_logger2();
|
|
64343
65116
|
init_prd();
|
|
@@ -64467,7 +65240,7 @@ function estimateLOCFromComplexity(complexity) {
|
|
|
64467
65240
|
}
|
|
64468
65241
|
}
|
|
64469
65242
|
async function reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2) {
|
|
64470
|
-
const prdPath =
|
|
65243
|
+
const prdPath = join8(featureDir, "prd.json");
|
|
64471
65244
|
if (!existsSync7(prdPath)) {
|
|
64472
65245
|
throw new Error(`prd.json not found at ${prdPath}. Run analyze without --reclassify first.`);
|
|
64473
65246
|
}
|
|
@@ -64568,11 +65341,11 @@ function reclassifyWithKeywords(story, config2) {
|
|
|
64568
65341
|
// src/cli/analyze.ts
|
|
64569
65342
|
async function analyzeFeature(options) {
|
|
64570
65343
|
const { featureDir, featureName, branchName, config: config2, specPath: explicitSpecPath, reclassify = false } = options;
|
|
64571
|
-
const workdir =
|
|
65344
|
+
const workdir = join9(featureDir, "../..");
|
|
64572
65345
|
if (reclassify) {
|
|
64573
65346
|
return await reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2);
|
|
64574
65347
|
}
|
|
64575
|
-
const specPath = explicitSpecPath ||
|
|
65348
|
+
const specPath = explicitSpecPath || join9(featureDir, "spec.md");
|
|
64576
65349
|
if (!existsSync8(specPath))
|
|
64577
65350
|
throw new Error(`spec.md not found at ${specPath}`);
|
|
64578
65351
|
const specContent = await Bun.file(specPath).text();
|
|
@@ -64689,7 +65462,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
64689
65462
|
modelDef,
|
|
64690
65463
|
config: config2
|
|
64691
65464
|
});
|
|
64692
|
-
const acceptanceTestPath =
|
|
65465
|
+
const acceptanceTestPath = join9(featureDir, config2.acceptance.testPath);
|
|
64693
65466
|
await Bun.write(acceptanceTestPath, result.testCode);
|
|
64694
65467
|
logger.info("cli", "[OK] Acceptance tests generated", {
|
|
64695
65468
|
criteriaCount: result.criteria.length,
|
|
@@ -64702,9 +65475,25 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
64702
65475
|
// src/cli/plan.ts
|
|
64703
65476
|
init_claude();
|
|
64704
65477
|
import { existsSync as existsSync9 } from "fs";
|
|
64705
|
-
import { join as
|
|
65478
|
+
import { join as join10 } from "path";
|
|
65479
|
+
import { createInterface } from "readline";
|
|
64706
65480
|
init_schema();
|
|
64707
65481
|
init_logger2();
|
|
65482
|
+
var QUESTION_PATTERNS = [/\?[\s]*$/, /\bwhich\b/i, /\bshould i\b/i, /\bdo you want\b/i, /\bwould you like\b/i];
|
|
65483
|
+
async function detectQuestion(text) {
|
|
65484
|
+
return QUESTION_PATTERNS.some((p) => p.test(text.trim()));
|
|
65485
|
+
}
|
|
65486
|
+
async function askHuman(question) {
|
|
65487
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
65488
|
+
return new Promise((resolve6) => {
|
|
65489
|
+
rl.question(`
|
|
65490
|
+
[Agent asks]: ${question}
|
|
65491
|
+
Your answer: `, (answer) => {
|
|
65492
|
+
rl.close();
|
|
65493
|
+
resolve6(answer.trim());
|
|
65494
|
+
});
|
|
65495
|
+
});
|
|
65496
|
+
}
|
|
64708
65497
|
var SPEC_TEMPLATE = `# Feature: [title]
|
|
64709
65498
|
|
|
64710
65499
|
## Problem
|
|
@@ -64725,8 +65514,8 @@ What this does NOT include.
|
|
|
64725
65514
|
`;
|
|
64726
65515
|
async function planCommand(prompt, workdir, config2, options = {}) {
|
|
64727
65516
|
const interactive = options.interactive !== false;
|
|
64728
|
-
const ngentDir =
|
|
64729
|
-
const outputPath =
|
|
65517
|
+
const ngentDir = join10(workdir, "nax");
|
|
65518
|
+
const outputPath = join10(ngentDir, config2.plan.outputPath);
|
|
64730
65519
|
if (!existsSync9(ngentDir)) {
|
|
64731
65520
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
64732
65521
|
}
|
|
@@ -64746,7 +65535,8 @@ async function planCommand(prompt, workdir, config2, options = {}) {
|
|
|
64746
65535
|
inputFile: options.from,
|
|
64747
65536
|
modelTier,
|
|
64748
65537
|
modelDef,
|
|
64749
|
-
config: config2
|
|
65538
|
+
config: config2,
|
|
65539
|
+
interactionBridge: interactive ? { detectQuestion, onQuestionDetected: askHuman } : undefined
|
|
64750
65540
|
};
|
|
64751
65541
|
const adapter = new ClaudeCodeAdapter;
|
|
64752
65542
|
logger.info("cli", interactive ? "Starting interactive planning session..." : `Reading from ${options.from}...`, {
|
|
@@ -64964,13 +65754,13 @@ async function displayModelEfficiency(workdir) {
|
|
|
64964
65754
|
// src/cli/status-features.ts
|
|
64965
65755
|
init_source();
|
|
64966
65756
|
import { existsSync as existsSync11, readdirSync as readdirSync2 } from "fs";
|
|
64967
|
-
import { join as
|
|
65757
|
+
import { join as join13 } from "path";
|
|
64968
65758
|
|
|
64969
65759
|
// src/commands/common.ts
|
|
64970
65760
|
init_path_security2();
|
|
64971
65761
|
init_errors3();
|
|
64972
65762
|
import { existsSync as existsSync10, readdirSync, realpathSync as realpathSync2 } from "fs";
|
|
64973
|
-
import { join as
|
|
65763
|
+
import { join as join11, resolve as resolve6 } from "path";
|
|
64974
65764
|
function resolveProject(options = {}) {
|
|
64975
65765
|
const { dir, feature } = options;
|
|
64976
65766
|
let projectRoot;
|
|
@@ -64978,12 +65768,12 @@ function resolveProject(options = {}) {
|
|
|
64978
65768
|
let configPath;
|
|
64979
65769
|
if (dir) {
|
|
64980
65770
|
projectRoot = realpathSync2(resolve6(dir));
|
|
64981
|
-
naxDir =
|
|
65771
|
+
naxDir = join11(projectRoot, "nax");
|
|
64982
65772
|
if (!existsSync10(naxDir)) {
|
|
64983
65773
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
64984
65774
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
64985
65775
|
}
|
|
64986
|
-
configPath =
|
|
65776
|
+
configPath = join11(naxDir, "config.json");
|
|
64987
65777
|
if (!existsSync10(configPath)) {
|
|
64988
65778
|
throw new NaxError(`nax directory found but config.json is missing: ${naxDir}
|
|
64989
65779
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
@@ -64991,22 +65781,22 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
|
64991
65781
|
} else {
|
|
64992
65782
|
const found = findProjectRoot(process.cwd());
|
|
64993
65783
|
if (!found) {
|
|
64994
|
-
const cwdNaxDir =
|
|
65784
|
+
const cwdNaxDir = join11(process.cwd(), "nax");
|
|
64995
65785
|
if (existsSync10(cwdNaxDir)) {
|
|
64996
|
-
const cwdConfigPath =
|
|
65786
|
+
const cwdConfigPath = join11(cwdNaxDir, "config.json");
|
|
64997
65787
|
throw new NaxError(`nax directory found but config.json is missing: ${cwdNaxDir}
|
|
64998
65788
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
64999
65789
|
}
|
|
65000
65790
|
throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
|
|
65001
65791
|
}
|
|
65002
65792
|
projectRoot = found;
|
|
65003
|
-
naxDir =
|
|
65004
|
-
configPath =
|
|
65793
|
+
naxDir = join11(projectRoot, "nax");
|
|
65794
|
+
configPath = join11(naxDir, "config.json");
|
|
65005
65795
|
}
|
|
65006
65796
|
let featureDir;
|
|
65007
65797
|
if (feature) {
|
|
65008
|
-
const featuresDir =
|
|
65009
|
-
featureDir =
|
|
65798
|
+
const featuresDir = join11(naxDir, "features");
|
|
65799
|
+
featureDir = join11(featuresDir, feature);
|
|
65010
65800
|
if (!existsSync10(featureDir)) {
|
|
65011
65801
|
const availableFeatures = existsSync10(featuresDir) ? readdirSync(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
65012
65802
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
@@ -65033,12 +65823,12 @@ function findProjectRoot(startDir) {
|
|
|
65033
65823
|
let current = resolve6(startDir);
|
|
65034
65824
|
let depth = 0;
|
|
65035
65825
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
65036
|
-
const naxDir =
|
|
65037
|
-
const configPath =
|
|
65826
|
+
const naxDir = join11(current, "nax");
|
|
65827
|
+
const configPath = join11(naxDir, "config.json");
|
|
65038
65828
|
if (existsSync10(configPath)) {
|
|
65039
65829
|
return realpathSync2(current);
|
|
65040
65830
|
}
|
|
65041
|
-
const parent =
|
|
65831
|
+
const parent = join11(current, "..");
|
|
65042
65832
|
if (parent === current) {
|
|
65043
65833
|
break;
|
|
65044
65834
|
}
|
|
@@ -65060,7 +65850,7 @@ function isPidAlive(pid) {
|
|
|
65060
65850
|
}
|
|
65061
65851
|
}
|
|
65062
65852
|
async function loadStatusFile(featureDir) {
|
|
65063
|
-
const statusPath =
|
|
65853
|
+
const statusPath = join13(featureDir, "status.json");
|
|
65064
65854
|
if (!existsSync11(statusPath)) {
|
|
65065
65855
|
return null;
|
|
65066
65856
|
}
|
|
@@ -65072,7 +65862,7 @@ async function loadStatusFile(featureDir) {
|
|
|
65072
65862
|
}
|
|
65073
65863
|
}
|
|
65074
65864
|
async function loadProjectStatusFile(projectDir) {
|
|
65075
|
-
const statusPath =
|
|
65865
|
+
const statusPath = join13(projectDir, "nax", "status.json");
|
|
65076
65866
|
if (!existsSync11(statusPath)) {
|
|
65077
65867
|
return null;
|
|
65078
65868
|
}
|
|
@@ -65084,7 +65874,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
65084
65874
|
}
|
|
65085
65875
|
}
|
|
65086
65876
|
async function getFeatureSummary(featureName, featureDir) {
|
|
65087
|
-
const prdPath =
|
|
65877
|
+
const prdPath = join13(featureDir, "prd.json");
|
|
65088
65878
|
const prd = await loadPRD(prdPath);
|
|
65089
65879
|
const counts = countStories(prd);
|
|
65090
65880
|
const summary = {
|
|
@@ -65118,7 +65908,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
65118
65908
|
};
|
|
65119
65909
|
}
|
|
65120
65910
|
}
|
|
65121
|
-
const runsDir =
|
|
65911
|
+
const runsDir = join13(featureDir, "runs");
|
|
65122
65912
|
if (existsSync11(runsDir)) {
|
|
65123
65913
|
const runs = readdirSync2(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
|
|
65124
65914
|
if (runs.length > 0) {
|
|
@@ -65129,7 +65919,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
65129
65919
|
return summary;
|
|
65130
65920
|
}
|
|
65131
65921
|
async function displayAllFeatures(projectDir) {
|
|
65132
|
-
const featuresDir =
|
|
65922
|
+
const featuresDir = join13(projectDir, "nax", "features");
|
|
65133
65923
|
if (!existsSync11(featuresDir)) {
|
|
65134
65924
|
console.log(source_default.dim("No features found."));
|
|
65135
65925
|
return;
|
|
@@ -65170,7 +65960,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
65170
65960
|
console.log();
|
|
65171
65961
|
}
|
|
65172
65962
|
}
|
|
65173
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
65963
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join13(featuresDir, name))));
|
|
65174
65964
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
65175
65965
|
`));
|
|
65176
65966
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -65196,7 +65986,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
65196
65986
|
console.log();
|
|
65197
65987
|
}
|
|
65198
65988
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
65199
|
-
const prdPath =
|
|
65989
|
+
const prdPath = join13(featureDir, "prd.json");
|
|
65200
65990
|
const prd = await loadPRD(prdPath);
|
|
65201
65991
|
const counts = countStories(prd);
|
|
65202
65992
|
const status = await loadStatusFile(featureDir);
|
|
@@ -65311,7 +66101,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
65311
66101
|
init_errors3();
|
|
65312
66102
|
init_logger2();
|
|
65313
66103
|
import { existsSync as existsSync12, readdirSync as readdirSync3 } from "fs";
|
|
65314
|
-
import { join as
|
|
66104
|
+
import { join as join14 } from "path";
|
|
65315
66105
|
async function parseRunLog(logPath) {
|
|
65316
66106
|
const logger = getLogger();
|
|
65317
66107
|
try {
|
|
@@ -65327,7 +66117,7 @@ async function parseRunLog(logPath) {
|
|
|
65327
66117
|
async function runsListCommand(options) {
|
|
65328
66118
|
const logger = getLogger();
|
|
65329
66119
|
const { feature, workdir } = options;
|
|
65330
|
-
const runsDir =
|
|
66120
|
+
const runsDir = join14(workdir, "nax", "features", feature, "runs");
|
|
65331
66121
|
if (!existsSync12(runsDir)) {
|
|
65332
66122
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
65333
66123
|
return;
|
|
@@ -65339,7 +66129,7 @@ async function runsListCommand(options) {
|
|
|
65339
66129
|
}
|
|
65340
66130
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
65341
66131
|
for (const file2 of files.sort().reverse()) {
|
|
65342
|
-
const logPath =
|
|
66132
|
+
const logPath = join14(runsDir, file2);
|
|
65343
66133
|
const entries = await parseRunLog(logPath);
|
|
65344
66134
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
65345
66135
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -65365,7 +66155,7 @@ async function runsListCommand(options) {
|
|
|
65365
66155
|
async function runsShowCommand(options) {
|
|
65366
66156
|
const logger = getLogger();
|
|
65367
66157
|
const { runId, feature, workdir } = options;
|
|
65368
|
-
const logPath =
|
|
66158
|
+
const logPath = join14(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
65369
66159
|
if (!existsSync12(logPath)) {
|
|
65370
66160
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
65371
66161
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -65404,7 +66194,7 @@ async function runsShowCommand(options) {
|
|
|
65404
66194
|
// src/cli/prompts-main.ts
|
|
65405
66195
|
init_logger2();
|
|
65406
66196
|
import { existsSync as existsSync15, mkdirSync as mkdirSync3 } from "fs";
|
|
65407
|
-
import { join as
|
|
66197
|
+
import { join as join21 } from "path";
|
|
65408
66198
|
|
|
65409
66199
|
// src/pipeline/index.ts
|
|
65410
66200
|
init_runner();
|
|
@@ -65440,7 +66230,7 @@ init_prd();
|
|
|
65440
66230
|
|
|
65441
66231
|
// src/cli/prompts-tdd.ts
|
|
65442
66232
|
init_prompts2();
|
|
65443
|
-
import { join as
|
|
66233
|
+
import { join as join20 } from "path";
|
|
65444
66234
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
65445
66235
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
65446
66236
|
PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
@@ -65459,7 +66249,7 @@ ${frontmatter}---
|
|
|
65459
66249
|
|
|
65460
66250
|
${session.prompt}`;
|
|
65461
66251
|
if (outputDir) {
|
|
65462
|
-
const promptFile =
|
|
66252
|
+
const promptFile = join20(outputDir, `${story.id}.${session.role}.md`);
|
|
65463
66253
|
await Bun.write(promptFile, fullOutput);
|
|
65464
66254
|
logger.info("cli", "Written TDD prompt file", {
|
|
65465
66255
|
storyId: story.id,
|
|
@@ -65475,7 +66265,7 @@ ${"=".repeat(80)}`);
|
|
|
65475
66265
|
}
|
|
65476
66266
|
}
|
|
65477
66267
|
if (outputDir && ctx.contextMarkdown) {
|
|
65478
|
-
const contextFile =
|
|
66268
|
+
const contextFile = join20(outputDir, `${story.id}.context.md`);
|
|
65479
66269
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
65480
66270
|
const contextOutput = `---
|
|
65481
66271
|
${frontmatter}---
|
|
@@ -65489,12 +66279,12 @@ ${ctx.contextMarkdown}`;
|
|
|
65489
66279
|
async function promptsCommand(options) {
|
|
65490
66280
|
const logger = getLogger();
|
|
65491
66281
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
65492
|
-
const naxDir =
|
|
66282
|
+
const naxDir = join21(workdir, "nax");
|
|
65493
66283
|
if (!existsSync15(naxDir)) {
|
|
65494
66284
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
65495
66285
|
}
|
|
65496
|
-
const featureDir =
|
|
65497
|
-
const prdPath =
|
|
66286
|
+
const featureDir = join21(naxDir, "features", feature);
|
|
66287
|
+
const prdPath = join21(featureDir, "prd.json");
|
|
65498
66288
|
if (!existsSync15(prdPath)) {
|
|
65499
66289
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
65500
66290
|
}
|
|
@@ -65554,10 +66344,10 @@ ${frontmatter}---
|
|
|
65554
66344
|
|
|
65555
66345
|
${ctx.prompt}`;
|
|
65556
66346
|
if (outputDir) {
|
|
65557
|
-
const promptFile =
|
|
66347
|
+
const promptFile = join21(outputDir, `${story.id}.prompt.md`);
|
|
65558
66348
|
await Bun.write(promptFile, fullOutput);
|
|
65559
66349
|
if (ctx.contextMarkdown) {
|
|
65560
|
-
const contextFile =
|
|
66350
|
+
const contextFile = join21(outputDir, `${story.id}.context.md`);
|
|
65561
66351
|
const contextOutput = `---
|
|
65562
66352
|
${frontmatter}---
|
|
65563
66353
|
|
|
@@ -65621,7 +66411,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
65621
66411
|
}
|
|
65622
66412
|
// src/cli/prompts-init.ts
|
|
65623
66413
|
import { existsSync as existsSync16, mkdirSync as mkdirSync4 } from "fs";
|
|
65624
|
-
import { join as
|
|
66414
|
+
import { join as join22 } from "path";
|
|
65625
66415
|
var TEMPLATE_ROLES = [
|
|
65626
66416
|
{ file: "test-writer.md", role: "test-writer" },
|
|
65627
66417
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -65645,9 +66435,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
65645
66435
|
`;
|
|
65646
66436
|
async function promptsInitCommand(options) {
|
|
65647
66437
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
65648
|
-
const templatesDir =
|
|
66438
|
+
const templatesDir = join22(workdir, "nax", "templates");
|
|
65649
66439
|
mkdirSync4(templatesDir, { recursive: true });
|
|
65650
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(
|
|
66440
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(join22(templatesDir, f)));
|
|
65651
66441
|
if (existingFiles.length > 0 && !force) {
|
|
65652
66442
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
65653
66443
|
Pass --force to overwrite existing templates.`);
|
|
@@ -65655,7 +66445,7 @@ async function promptsInitCommand(options) {
|
|
|
65655
66445
|
}
|
|
65656
66446
|
const written = [];
|
|
65657
66447
|
for (const template of TEMPLATE_ROLES) {
|
|
65658
|
-
const filePath =
|
|
66448
|
+
const filePath = join22(templatesDir, template.file);
|
|
65659
66449
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
65660
66450
|
const content = TEMPLATE_HEADER + roleBody;
|
|
65661
66451
|
await Bun.write(filePath, content);
|
|
@@ -65671,7 +66461,7 @@ async function promptsInitCommand(options) {
|
|
|
65671
66461
|
return written;
|
|
65672
66462
|
}
|
|
65673
66463
|
async function autoWirePromptsConfig(workdir) {
|
|
65674
|
-
const configPath =
|
|
66464
|
+
const configPath = join22(workdir, "nax.config.json");
|
|
65675
66465
|
if (!existsSync16(configPath)) {
|
|
65676
66466
|
const exampleConfig = JSON.stringify({
|
|
65677
66467
|
prompts: {
|
|
@@ -65838,7 +66628,7 @@ init_config();
|
|
|
65838
66628
|
init_logger2();
|
|
65839
66629
|
init_prd();
|
|
65840
66630
|
import { existsSync as existsSync17, readdirSync as readdirSync4 } from "fs";
|
|
65841
|
-
import { join as
|
|
66631
|
+
import { join as join25 } from "path";
|
|
65842
66632
|
|
|
65843
66633
|
// src/cli/diagnose-analysis.ts
|
|
65844
66634
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -66037,7 +66827,7 @@ function isProcessAlive2(pid) {
|
|
|
66037
66827
|
}
|
|
66038
66828
|
}
|
|
66039
66829
|
async function loadStatusFile2(workdir) {
|
|
66040
|
-
const statusPath =
|
|
66830
|
+
const statusPath = join25(workdir, "nax", "status.json");
|
|
66041
66831
|
if (!existsSync17(statusPath))
|
|
66042
66832
|
return null;
|
|
66043
66833
|
try {
|
|
@@ -66065,7 +66855,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
66065
66855
|
}
|
|
66066
66856
|
}
|
|
66067
66857
|
async function checkLock(workdir) {
|
|
66068
|
-
const lockFile = Bun.file(
|
|
66858
|
+
const lockFile = Bun.file(join25(workdir, "nax.lock"));
|
|
66069
66859
|
if (!await lockFile.exists())
|
|
66070
66860
|
return { lockPresent: false };
|
|
66071
66861
|
try {
|
|
@@ -66083,8 +66873,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
66083
66873
|
const logger = getLogger();
|
|
66084
66874
|
const workdir = options.workdir ?? process.cwd();
|
|
66085
66875
|
const naxSubdir = findProjectDir(workdir);
|
|
66086
|
-
let projectDir = naxSubdir ?
|
|
66087
|
-
if (!projectDir && existsSync17(
|
|
66876
|
+
let projectDir = naxSubdir ? join25(naxSubdir, "..") : null;
|
|
66877
|
+
if (!projectDir && existsSync17(join25(workdir, "nax"))) {
|
|
66088
66878
|
projectDir = workdir;
|
|
66089
66879
|
}
|
|
66090
66880
|
if (!projectDir)
|
|
@@ -66095,7 +66885,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
66095
66885
|
if (status2) {
|
|
66096
66886
|
feature = status2.run.feature;
|
|
66097
66887
|
} else {
|
|
66098
|
-
const featuresDir =
|
|
66888
|
+
const featuresDir = join25(projectDir, "nax", "features");
|
|
66099
66889
|
if (!existsSync17(featuresDir))
|
|
66100
66890
|
throw new Error("No features found in project");
|
|
66101
66891
|
const features = readdirSync4(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -66105,8 +66895,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
66105
66895
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
66106
66896
|
}
|
|
66107
66897
|
}
|
|
66108
|
-
const featureDir =
|
|
66109
|
-
const prdPath =
|
|
66898
|
+
const featureDir = join25(projectDir, "nax", "features", feature);
|
|
66899
|
+
const prdPath = join25(featureDir, "prd.json");
|
|
66110
66900
|
if (!existsSync17(prdPath))
|
|
66111
66901
|
throw new Error(`Feature not found: ${feature}`);
|
|
66112
66902
|
const prd = await loadPRD(prdPath);
|
|
@@ -66149,16 +66939,16 @@ init_interaction();
|
|
|
66149
66939
|
init_source();
|
|
66150
66940
|
init_loader2();
|
|
66151
66941
|
import { existsSync as existsSync20 } from "fs";
|
|
66152
|
-
import { join as
|
|
66942
|
+
import { join as join28 } from "path";
|
|
66153
66943
|
|
|
66154
66944
|
// src/context/generator.ts
|
|
66155
66945
|
init_path_security2();
|
|
66156
66946
|
import { existsSync as existsSync19 } from "fs";
|
|
66157
|
-
import { join as
|
|
66947
|
+
import { join as join27 } from "path";
|
|
66158
66948
|
|
|
66159
66949
|
// src/context/injector.ts
|
|
66160
66950
|
import { existsSync as existsSync18 } from "fs";
|
|
66161
|
-
import { join as
|
|
66951
|
+
import { join as join26 } from "path";
|
|
66162
66952
|
var NOTABLE_NODE_DEPS = [
|
|
66163
66953
|
"@nestjs",
|
|
66164
66954
|
"express",
|
|
@@ -66188,7 +66978,7 @@ var NOTABLE_NODE_DEPS = [
|
|
|
66188
66978
|
"ioredis"
|
|
66189
66979
|
];
|
|
66190
66980
|
async function detectNode(workdir) {
|
|
66191
|
-
const pkgPath =
|
|
66981
|
+
const pkgPath = join26(workdir, "package.json");
|
|
66192
66982
|
if (!existsSync18(pkgPath))
|
|
66193
66983
|
return null;
|
|
66194
66984
|
try {
|
|
@@ -66205,7 +66995,7 @@ async function detectNode(workdir) {
|
|
|
66205
66995
|
}
|
|
66206
66996
|
}
|
|
66207
66997
|
async function detectGo(workdir) {
|
|
66208
|
-
const goMod =
|
|
66998
|
+
const goMod = join26(workdir, "go.mod");
|
|
66209
66999
|
if (!existsSync18(goMod))
|
|
66210
67000
|
return null;
|
|
66211
67001
|
try {
|
|
@@ -66229,7 +67019,7 @@ async function detectGo(workdir) {
|
|
|
66229
67019
|
}
|
|
66230
67020
|
}
|
|
66231
67021
|
async function detectRust(workdir) {
|
|
66232
|
-
const cargoPath =
|
|
67022
|
+
const cargoPath = join26(workdir, "Cargo.toml");
|
|
66233
67023
|
if (!existsSync18(cargoPath))
|
|
66234
67024
|
return null;
|
|
66235
67025
|
try {
|
|
@@ -66245,8 +67035,8 @@ async function detectRust(workdir) {
|
|
|
66245
67035
|
}
|
|
66246
67036
|
}
|
|
66247
67037
|
async function detectPython(workdir) {
|
|
66248
|
-
const pyproject =
|
|
66249
|
-
const requirements =
|
|
67038
|
+
const pyproject = join26(workdir, "pyproject.toml");
|
|
67039
|
+
const requirements = join26(workdir, "requirements.txt");
|
|
66250
67040
|
if (!existsSync18(pyproject) && !existsSync18(requirements))
|
|
66251
67041
|
return null;
|
|
66252
67042
|
try {
|
|
@@ -66265,7 +67055,7 @@ async function detectPython(workdir) {
|
|
|
66265
67055
|
}
|
|
66266
67056
|
}
|
|
66267
67057
|
async function detectPhp(workdir) {
|
|
66268
|
-
const composerPath =
|
|
67058
|
+
const composerPath = join26(workdir, "composer.json");
|
|
66269
67059
|
if (!existsSync18(composerPath))
|
|
66270
67060
|
return null;
|
|
66271
67061
|
try {
|
|
@@ -66278,7 +67068,7 @@ async function detectPhp(workdir) {
|
|
|
66278
67068
|
}
|
|
66279
67069
|
}
|
|
66280
67070
|
async function detectRuby(workdir) {
|
|
66281
|
-
const gemfile =
|
|
67071
|
+
const gemfile = join26(workdir, "Gemfile");
|
|
66282
67072
|
if (!existsSync18(gemfile))
|
|
66283
67073
|
return null;
|
|
66284
67074
|
try {
|
|
@@ -66290,9 +67080,9 @@ async function detectRuby(workdir) {
|
|
|
66290
67080
|
}
|
|
66291
67081
|
}
|
|
66292
67082
|
async function detectJvm(workdir) {
|
|
66293
|
-
const pom =
|
|
66294
|
-
const gradle =
|
|
66295
|
-
const gradleKts =
|
|
67083
|
+
const pom = join26(workdir, "pom.xml");
|
|
67084
|
+
const gradle = join26(workdir, "build.gradle");
|
|
67085
|
+
const gradleKts = join26(workdir, "build.gradle.kts");
|
|
66296
67086
|
if (!existsSync18(pom) && !existsSync18(gradle) && !existsSync18(gradleKts))
|
|
66297
67087
|
return null;
|
|
66298
67088
|
try {
|
|
@@ -66300,7 +67090,7 @@ async function detectJvm(workdir) {
|
|
|
66300
67090
|
const content2 = await Bun.file(pom).text();
|
|
66301
67091
|
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
66302
67092
|
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
66303
|
-
const lang2 = existsSync18(
|
|
67093
|
+
const lang2 = existsSync18(join26(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
66304
67094
|
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
66305
67095
|
}
|
|
66306
67096
|
const gradleFile = existsSync18(gradleKts) ? gradleKts : gradle;
|
|
@@ -66521,7 +67311,7 @@ async function generateFor(agent, options, config2) {
|
|
|
66521
67311
|
try {
|
|
66522
67312
|
const context = await loadContextContent(options, config2);
|
|
66523
67313
|
const content = generator.generate(context);
|
|
66524
|
-
const outputPath =
|
|
67314
|
+
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
66525
67315
|
validateFilePath(outputPath, options.outputDir);
|
|
66526
67316
|
if (!options.dryRun) {
|
|
66527
67317
|
await Bun.write(outputPath, content);
|
|
@@ -66538,7 +67328,7 @@ async function generateAll(options, config2) {
|
|
|
66538
67328
|
for (const [agentKey, generator] of Object.entries(GENERATORS)) {
|
|
66539
67329
|
try {
|
|
66540
67330
|
const content = generator.generate(context);
|
|
66541
|
-
const outputPath =
|
|
67331
|
+
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
66542
67332
|
validateFilePath(outputPath, options.outputDir);
|
|
66543
67333
|
if (!options.dryRun) {
|
|
66544
67334
|
await Bun.write(outputPath, content);
|
|
@@ -66556,8 +67346,8 @@ async function generateAll(options, config2) {
|
|
|
66556
67346
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
66557
67347
|
async function generateCommand(options) {
|
|
66558
67348
|
const workdir = process.cwd();
|
|
66559
|
-
const contextPath = options.context ?
|
|
66560
|
-
const outputDir = options.output ?
|
|
67349
|
+
const contextPath = options.context ? join28(workdir, options.context) : join28(workdir, "nax/context.md");
|
|
67350
|
+
const outputDir = options.output ? join28(workdir, options.output) : workdir;
|
|
66561
67351
|
const autoInject = !options.noAutoInject;
|
|
66562
67352
|
const dryRun = options.dryRun ?? false;
|
|
66563
67353
|
if (!existsSync20(contextPath)) {
|
|
@@ -66633,7 +67423,7 @@ async function generateCommand(options) {
|
|
|
66633
67423
|
// src/cli/config-display.ts
|
|
66634
67424
|
init_loader2();
|
|
66635
67425
|
import { existsSync as existsSync22 } from "fs";
|
|
66636
|
-
import { join as
|
|
67426
|
+
import { join as join30 } from "path";
|
|
66637
67427
|
|
|
66638
67428
|
// src/cli/config-descriptions.ts
|
|
66639
67429
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -66839,7 +67629,7 @@ function deepEqual(a, b) {
|
|
|
66839
67629
|
init_defaults();
|
|
66840
67630
|
init_loader2();
|
|
66841
67631
|
import { existsSync as existsSync21 } from "fs";
|
|
66842
|
-
import { join as
|
|
67632
|
+
import { join as join29 } from "path";
|
|
66843
67633
|
async function loadConfigFile(path14) {
|
|
66844
67634
|
if (!existsSync21(path14))
|
|
66845
67635
|
return null;
|
|
@@ -66861,7 +67651,7 @@ async function loadProjectConfig() {
|
|
|
66861
67651
|
const projectDir = findProjectDir();
|
|
66862
67652
|
if (!projectDir)
|
|
66863
67653
|
return null;
|
|
66864
|
-
const projectPath =
|
|
67654
|
+
const projectPath = join29(projectDir, "config.json");
|
|
66865
67655
|
return await loadConfigFile(projectPath);
|
|
66866
67656
|
}
|
|
66867
67657
|
|
|
@@ -66921,7 +67711,7 @@ async function configCommand(config2, options = {}) {
|
|
|
66921
67711
|
function determineConfigSources() {
|
|
66922
67712
|
const globalPath = globalConfigPath();
|
|
66923
67713
|
const projectDir = findProjectDir();
|
|
66924
|
-
const projectPath = projectDir ?
|
|
67714
|
+
const projectPath = projectDir ? join30(projectDir, "config.json") : null;
|
|
66925
67715
|
return {
|
|
66926
67716
|
global: fileExists(globalPath) ? globalPath : null,
|
|
66927
67717
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -67101,21 +67891,21 @@ async function diagnose(options) {
|
|
|
67101
67891
|
|
|
67102
67892
|
// src/commands/logs.ts
|
|
67103
67893
|
import { existsSync as existsSync24 } from "fs";
|
|
67104
|
-
import { join as
|
|
67894
|
+
import { join as join33 } from "path";
|
|
67105
67895
|
|
|
67106
67896
|
// src/commands/logs-formatter.ts
|
|
67107
67897
|
init_source();
|
|
67108
67898
|
init_formatter();
|
|
67109
67899
|
import { readdirSync as readdirSync6 } from "fs";
|
|
67110
|
-
import { join as
|
|
67900
|
+
import { join as join32 } from "path";
|
|
67111
67901
|
|
|
67112
67902
|
// src/commands/logs-reader.ts
|
|
67113
67903
|
import { existsSync as existsSync23, readdirSync as readdirSync5 } from "fs";
|
|
67114
67904
|
import { readdir as readdir3 } from "fs/promises";
|
|
67115
67905
|
import { homedir as homedir3 } from "os";
|
|
67116
|
-
import { join as
|
|
67906
|
+
import { join as join31 } from "path";
|
|
67117
67907
|
var _deps5 = {
|
|
67118
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
67908
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir3(), ".nax", "runs")
|
|
67119
67909
|
};
|
|
67120
67910
|
async function resolveRunFileFromRegistry(runId) {
|
|
67121
67911
|
const runsDir = _deps5.getRunsDir();
|
|
@@ -67127,7 +67917,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
67127
67917
|
}
|
|
67128
67918
|
let matched = null;
|
|
67129
67919
|
for (const entry of entries) {
|
|
67130
|
-
const metaPath =
|
|
67920
|
+
const metaPath = join31(runsDir, entry, "meta.json");
|
|
67131
67921
|
try {
|
|
67132
67922
|
const meta3 = await Bun.file(metaPath).json();
|
|
67133
67923
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -67149,14 +67939,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
67149
67939
|
return null;
|
|
67150
67940
|
}
|
|
67151
67941
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
67152
|
-
return
|
|
67942
|
+
return join31(matched.eventsDir, specificFile ?? files[0]);
|
|
67153
67943
|
}
|
|
67154
67944
|
async function selectRunFile(runsDir) {
|
|
67155
67945
|
const files = readdirSync5(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
67156
67946
|
if (files.length === 0) {
|
|
67157
67947
|
return null;
|
|
67158
67948
|
}
|
|
67159
|
-
return
|
|
67949
|
+
return join31(runsDir, files[0]);
|
|
67160
67950
|
}
|
|
67161
67951
|
async function extractRunSummary(filePath) {
|
|
67162
67952
|
const file2 = Bun.file(filePath);
|
|
@@ -67241,7 +68031,7 @@ Runs:
|
|
|
67241
68031
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
67242
68032
|
console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
67243
68033
|
for (const file2 of files) {
|
|
67244
|
-
const filePath =
|
|
68034
|
+
const filePath = join32(runsDir, file2);
|
|
67245
68035
|
const summary = await extractRunSummary(filePath);
|
|
67246
68036
|
const timestamp = file2.replace(".jsonl", "");
|
|
67247
68037
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -67366,7 +68156,7 @@ async function logsCommand(options) {
|
|
|
67366
68156
|
return;
|
|
67367
68157
|
}
|
|
67368
68158
|
const resolved = resolveProject({ dir: options.dir });
|
|
67369
|
-
const naxDir =
|
|
68159
|
+
const naxDir = join33(resolved.projectDir, "nax");
|
|
67370
68160
|
const configPath = resolved.configPath;
|
|
67371
68161
|
const configFile = Bun.file(configPath);
|
|
67372
68162
|
const config2 = await configFile.json();
|
|
@@ -67374,8 +68164,8 @@ async function logsCommand(options) {
|
|
|
67374
68164
|
if (!featureName) {
|
|
67375
68165
|
throw new Error("No feature specified in config.json");
|
|
67376
68166
|
}
|
|
67377
|
-
const featureDir =
|
|
67378
|
-
const runsDir =
|
|
68167
|
+
const featureDir = join33(naxDir, "features", featureName);
|
|
68168
|
+
const runsDir = join33(featureDir, "runs");
|
|
67379
68169
|
if (!existsSync24(runsDir)) {
|
|
67380
68170
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
67381
68171
|
}
|
|
@@ -67400,7 +68190,7 @@ init_config();
|
|
|
67400
68190
|
init_prd();
|
|
67401
68191
|
init_precheck();
|
|
67402
68192
|
import { existsSync as existsSync29 } from "fs";
|
|
67403
|
-
import { join as
|
|
68193
|
+
import { join as join34 } from "path";
|
|
67404
68194
|
async function precheckCommand(options) {
|
|
67405
68195
|
const resolved = resolveProject({
|
|
67406
68196
|
dir: options.dir,
|
|
@@ -67416,9 +68206,9 @@ async function precheckCommand(options) {
|
|
|
67416
68206
|
process.exit(1);
|
|
67417
68207
|
}
|
|
67418
68208
|
}
|
|
67419
|
-
const naxDir =
|
|
67420
|
-
const featureDir =
|
|
67421
|
-
const prdPath =
|
|
68209
|
+
const naxDir = join34(resolved.projectDir, "nax");
|
|
68210
|
+
const featureDir = join34(naxDir, "features", featureName);
|
|
68211
|
+
const prdPath = join34(featureDir, "prd.json");
|
|
67422
68212
|
if (!existsSync29(featureDir)) {
|
|
67423
68213
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
67424
68214
|
process.exit(1);
|
|
@@ -67442,10 +68232,10 @@ async function precheckCommand(options) {
|
|
|
67442
68232
|
init_source();
|
|
67443
68233
|
import { readdir as readdir4 } from "fs/promises";
|
|
67444
68234
|
import { homedir as homedir4 } from "os";
|
|
67445
|
-
import { join as
|
|
68235
|
+
import { join as join35 } from "path";
|
|
67446
68236
|
var DEFAULT_LIMIT = 20;
|
|
67447
68237
|
var _deps7 = {
|
|
67448
|
-
getRunsDir: () =>
|
|
68238
|
+
getRunsDir: () => join35(homedir4(), ".nax", "runs")
|
|
67449
68239
|
};
|
|
67450
68240
|
function formatDuration3(ms) {
|
|
67451
68241
|
if (ms <= 0)
|
|
@@ -67497,7 +68287,7 @@ async function runsCommand(options = {}) {
|
|
|
67497
68287
|
}
|
|
67498
68288
|
const rows = [];
|
|
67499
68289
|
for (const entry of entries) {
|
|
67500
|
-
const metaPath =
|
|
68290
|
+
const metaPath = join35(runsDir, entry, "meta.json");
|
|
67501
68291
|
let meta3;
|
|
67502
68292
|
try {
|
|
67503
68293
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -67574,7 +68364,7 @@ async function runsCommand(options = {}) {
|
|
|
67574
68364
|
|
|
67575
68365
|
// src/commands/unlock.ts
|
|
67576
68366
|
init_source();
|
|
67577
|
-
import { join as
|
|
68367
|
+
import { join as join36 } from "path";
|
|
67578
68368
|
function isProcessAlive3(pid) {
|
|
67579
68369
|
try {
|
|
67580
68370
|
process.kill(pid, 0);
|
|
@@ -67589,7 +68379,7 @@ function formatLockAge(ageMs) {
|
|
|
67589
68379
|
}
|
|
67590
68380
|
async function unlockCommand(options) {
|
|
67591
68381
|
const workdir = options.dir ?? process.cwd();
|
|
67592
|
-
const lockPath =
|
|
68382
|
+
const lockPath = join36(workdir, "nax.lock");
|
|
67593
68383
|
const lockFile = Bun.file(lockPath);
|
|
67594
68384
|
const exists = await lockFile.exists();
|
|
67595
68385
|
if (!exists) {
|
|
@@ -67628,6 +68418,7 @@ async function unlockCommand(options) {
|
|
|
67628
68418
|
init_config();
|
|
67629
68419
|
|
|
67630
68420
|
// src/execution/runner.ts
|
|
68421
|
+
init_registry();
|
|
67631
68422
|
init_hooks();
|
|
67632
68423
|
init_logger2();
|
|
67633
68424
|
init_prd();
|
|
@@ -67637,6 +68428,7 @@ init_crash_recovery();
|
|
|
67637
68428
|
init_hooks();
|
|
67638
68429
|
init_logger2();
|
|
67639
68430
|
init_prd();
|
|
68431
|
+
init_git();
|
|
67640
68432
|
init_crash_recovery();
|
|
67641
68433
|
init_story_context();
|
|
67642
68434
|
async function runCompletionPhase(options) {
|
|
@@ -67646,7 +68438,7 @@ async function runCompletionPhase(options) {
|
|
|
67646
68438
|
const acceptanceResult = await runAcceptanceLoop2({
|
|
67647
68439
|
config: options.config,
|
|
67648
68440
|
prd: options.prd,
|
|
67649
|
-
prdPath:
|
|
68441
|
+
prdPath: options.prdPath,
|
|
67650
68442
|
workdir: options.workdir,
|
|
67651
68443
|
featureDir: options.featureDir,
|
|
67652
68444
|
hooks: options.hooks,
|
|
@@ -67657,7 +68449,8 @@ async function runCompletionPhase(options) {
|
|
|
67657
68449
|
allStoryMetrics: options.allStoryMetrics,
|
|
67658
68450
|
pluginRegistry: options.pluginRegistry,
|
|
67659
68451
|
eventEmitter: options.eventEmitter,
|
|
67660
|
-
statusWriter: options.statusWriter
|
|
68452
|
+
statusWriter: options.statusWriter,
|
|
68453
|
+
agentGetFn: options.agentGetFn
|
|
67661
68454
|
});
|
|
67662
68455
|
Object.assign(options, {
|
|
67663
68456
|
prd: acceptanceResult.prd,
|
|
@@ -67708,6 +68501,7 @@ async function runCompletionPhase(options) {
|
|
|
67708
68501
|
}
|
|
67709
68502
|
stopHeartbeat();
|
|
67710
68503
|
await writeExitSummary(options.logFilePath, options.totalCost, options.iterations, options.storiesCompleted, durationMs);
|
|
68504
|
+
await autoCommitIfDirty(options.workdir, "run.complete", "run-summary", options.feature);
|
|
67711
68505
|
return {
|
|
67712
68506
|
durationMs,
|
|
67713
68507
|
runCompletedAt
|
|
@@ -67858,7 +68652,9 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
67858
68652
|
logFilePath: options.logFilePath,
|
|
67859
68653
|
runId: options.runId,
|
|
67860
68654
|
startTime: options.startTime,
|
|
67861
|
-
batchPlan
|
|
68655
|
+
batchPlan,
|
|
68656
|
+
agentGetFn: options.agentGetFn,
|
|
68657
|
+
pidRegistry: options.pidRegistry
|
|
67862
68658
|
}, prd);
|
|
67863
68659
|
prd = sequentialResult.prd;
|
|
67864
68660
|
iterations = sequentialResult.iterations;
|
|
@@ -67896,7 +68692,8 @@ async function runSetupPhase(options) {
|
|
|
67896
68692
|
getTotalCost: options.getTotalCost,
|
|
67897
68693
|
getIterations: options.getIterations,
|
|
67898
68694
|
getStoriesCompleted: options.getStoriesCompleted,
|
|
67899
|
-
getTotalStories: options.getTotalStories
|
|
68695
|
+
getTotalStories: options.getTotalStories,
|
|
68696
|
+
agentGetFn: options.agentGetFn
|
|
67900
68697
|
});
|
|
67901
68698
|
return setupResult;
|
|
67902
68699
|
}
|
|
@@ -67934,6 +68731,8 @@ async function run(options) {
|
|
|
67934
68731
|
let totalCost = 0;
|
|
67935
68732
|
const allStoryMetrics = [];
|
|
67936
68733
|
const logger = getSafeLogger();
|
|
68734
|
+
const registry2 = createAgentRegistry(config2);
|
|
68735
|
+
const agentGetFn = registry2.getAgent.bind(registry2);
|
|
67937
68736
|
let prd;
|
|
67938
68737
|
const setupResult = await runSetupPhase({
|
|
67939
68738
|
prdPath,
|
|
@@ -67951,6 +68750,7 @@ async function run(options) {
|
|
|
67951
68750
|
skipPrecheck,
|
|
67952
68751
|
headless,
|
|
67953
68752
|
formatterMode,
|
|
68753
|
+
agentGetFn,
|
|
67954
68754
|
getTotalCost: () => totalCost,
|
|
67955
68755
|
getIterations: () => iterations,
|
|
67956
68756
|
getStoriesCompleted: () => storiesCompleted,
|
|
@@ -67978,7 +68778,9 @@ async function run(options) {
|
|
|
67978
68778
|
formatterMode,
|
|
67979
68779
|
headless,
|
|
67980
68780
|
parallel,
|
|
67981
|
-
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined
|
|
68781
|
+
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined,
|
|
68782
|
+
agentGetFn,
|
|
68783
|
+
pidRegistry
|
|
67982
68784
|
}, prd, pluginRegistry);
|
|
67983
68785
|
prd = executionResult.prd;
|
|
67984
68786
|
iterations = executionResult.iterations;
|
|
@@ -67999,6 +68801,7 @@ async function run(options) {
|
|
|
67999
68801
|
hooks,
|
|
68000
68802
|
feature,
|
|
68001
68803
|
workdir,
|
|
68804
|
+
prdPath,
|
|
68002
68805
|
statusFile,
|
|
68003
68806
|
logFilePath,
|
|
68004
68807
|
runId,
|
|
@@ -68014,7 +68817,8 @@ async function run(options) {
|
|
|
68014
68817
|
iterations,
|
|
68015
68818
|
statusWriter,
|
|
68016
68819
|
pluginRegistry,
|
|
68017
|
-
eventEmitter
|
|
68820
|
+
eventEmitter,
|
|
68821
|
+
agentGetFn
|
|
68018
68822
|
});
|
|
68019
68823
|
const { durationMs } = completionResult;
|
|
68020
68824
|
return {
|
|
@@ -75357,15 +76161,15 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
75357
76161
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
75358
76162
|
process.exit(1);
|
|
75359
76163
|
}
|
|
75360
|
-
const naxDir =
|
|
76164
|
+
const naxDir = join43(workdir, "nax");
|
|
75361
76165
|
if (existsSync32(naxDir) && !options.force) {
|
|
75362
76166
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
75363
76167
|
return;
|
|
75364
76168
|
}
|
|
75365
|
-
mkdirSync6(
|
|
75366
|
-
mkdirSync6(
|
|
75367
|
-
await Bun.write(
|
|
75368
|
-
await Bun.write(
|
|
76169
|
+
mkdirSync6(join43(naxDir, "features"), { recursive: true });
|
|
76170
|
+
mkdirSync6(join43(naxDir, "hooks"), { recursive: true });
|
|
76171
|
+
await Bun.write(join43(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
76172
|
+
await Bun.write(join43(naxDir, "hooks.json"), JSON.stringify({
|
|
75369
76173
|
hooks: {
|
|
75370
76174
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
75371
76175
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -75373,12 +76177,12 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
75373
76177
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
75374
76178
|
}
|
|
75375
76179
|
}, null, 2));
|
|
75376
|
-
await Bun.write(
|
|
76180
|
+
await Bun.write(join43(naxDir, ".gitignore"), `# nax temp files
|
|
75377
76181
|
*.tmp
|
|
75378
76182
|
.paused.json
|
|
75379
76183
|
.nax-verifier-verdict.json
|
|
75380
76184
|
`);
|
|
75381
|
-
await Bun.write(
|
|
76185
|
+
await Bun.write(join43(naxDir, "context.md"), `# Project Context
|
|
75382
76186
|
|
|
75383
76187
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
75384
76188
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -75496,16 +76300,16 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75496
76300
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75497
76301
|
process.exit(1);
|
|
75498
76302
|
}
|
|
75499
|
-
const featureDir =
|
|
75500
|
-
const prdPath =
|
|
76303
|
+
const featureDir = join43(naxDir, "features", options.feature);
|
|
76304
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
75501
76305
|
if (!existsSync32(prdPath)) {
|
|
75502
76306
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
75503
76307
|
process.exit(1);
|
|
75504
76308
|
}
|
|
75505
|
-
const runsDir =
|
|
76309
|
+
const runsDir = join43(featureDir, "runs");
|
|
75506
76310
|
mkdirSync6(runsDir, { recursive: true });
|
|
75507
76311
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
75508
|
-
const logFilePath =
|
|
76312
|
+
const logFilePath = join43(runsDir, `${runId}.jsonl`);
|
|
75509
76313
|
const isTTY = process.stdout.isTTY ?? false;
|
|
75510
76314
|
const headlessFlag = options.headless ?? false;
|
|
75511
76315
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -75521,7 +76325,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75521
76325
|
config2.autoMode.defaultAgent = options.agent;
|
|
75522
76326
|
}
|
|
75523
76327
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
75524
|
-
const globalNaxDir =
|
|
76328
|
+
const globalNaxDir = join43(homedir8(), ".nax");
|
|
75525
76329
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
75526
76330
|
const eventEmitter = new PipelineEventEmitter;
|
|
75527
76331
|
let tuiInstance;
|
|
@@ -75544,7 +76348,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75544
76348
|
} else {
|
|
75545
76349
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
75546
76350
|
}
|
|
75547
|
-
const statusFilePath =
|
|
76351
|
+
const statusFilePath = join43(workdir, "nax", "status.json");
|
|
75548
76352
|
let parallel;
|
|
75549
76353
|
if (options.parallel !== undefined) {
|
|
75550
76354
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -75570,7 +76374,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
75570
76374
|
headless: useHeadless,
|
|
75571
76375
|
skipPrecheck: options.skipPrecheck ?? false
|
|
75572
76376
|
});
|
|
75573
|
-
const latestSymlink =
|
|
76377
|
+
const latestSymlink = join43(runsDir, "latest.jsonl");
|
|
75574
76378
|
try {
|
|
75575
76379
|
if (existsSync32(latestSymlink)) {
|
|
75576
76380
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -75608,9 +76412,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75608
76412
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75609
76413
|
process.exit(1);
|
|
75610
76414
|
}
|
|
75611
|
-
const featureDir =
|
|
76415
|
+
const featureDir = join43(naxDir, "features", name);
|
|
75612
76416
|
mkdirSync6(featureDir, { recursive: true });
|
|
75613
|
-
await Bun.write(
|
|
76417
|
+
await Bun.write(join43(featureDir, "spec.md"), `# Feature: ${name}
|
|
75614
76418
|
|
|
75615
76419
|
## Overview
|
|
75616
76420
|
|
|
@@ -75618,7 +76422,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75618
76422
|
|
|
75619
76423
|
## Acceptance Criteria
|
|
75620
76424
|
`);
|
|
75621
|
-
await Bun.write(
|
|
76425
|
+
await Bun.write(join43(featureDir, "plan.md"), `# Plan: ${name}
|
|
75622
76426
|
|
|
75623
76427
|
## Architecture
|
|
75624
76428
|
|
|
@@ -75626,7 +76430,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75626
76430
|
|
|
75627
76431
|
## Dependencies
|
|
75628
76432
|
`);
|
|
75629
|
-
await Bun.write(
|
|
76433
|
+
await Bun.write(join43(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
75630
76434
|
|
|
75631
76435
|
## US-001: [Title]
|
|
75632
76436
|
|
|
@@ -75635,7 +76439,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
75635
76439
|
### Acceptance Criteria
|
|
75636
76440
|
- [ ] Criterion 1
|
|
75637
76441
|
`);
|
|
75638
|
-
await Bun.write(
|
|
76442
|
+
await Bun.write(join43(featureDir, "progress.txt"), `# Progress: ${name}
|
|
75639
76443
|
|
|
75640
76444
|
Created: ${new Date().toISOString()}
|
|
75641
76445
|
|
|
@@ -75663,7 +76467,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
75663
76467
|
console.error(source_default.red("nax not initialized."));
|
|
75664
76468
|
process.exit(1);
|
|
75665
76469
|
}
|
|
75666
|
-
const featuresDir =
|
|
76470
|
+
const featuresDir = join43(naxDir, "features");
|
|
75667
76471
|
if (!existsSync32(featuresDir)) {
|
|
75668
76472
|
console.log(source_default.dim("No features yet."));
|
|
75669
76473
|
return;
|
|
@@ -75678,7 +76482,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
75678
76482
|
Features:
|
|
75679
76483
|
`));
|
|
75680
76484
|
for (const name of entries) {
|
|
75681
|
-
const prdPath =
|
|
76485
|
+
const prdPath = join43(featuresDir, name, "prd.json");
|
|
75682
76486
|
if (existsSync32(prdPath)) {
|
|
75683
76487
|
const prd = await loadPRD(prdPath);
|
|
75684
76488
|
const c = countStories(prd);
|
|
@@ -75731,7 +76535,7 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
|
|
|
75731
76535
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
75732
76536
|
process.exit(1);
|
|
75733
76537
|
}
|
|
75734
|
-
const featureDir =
|
|
76538
|
+
const featureDir = join43(naxDir, "features", options.feature);
|
|
75735
76539
|
if (!existsSync32(featureDir)) {
|
|
75736
76540
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
75737
76541
|
process.exit(1);
|
|
@@ -75747,7 +76551,7 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
|
|
|
75747
76551
|
specPath: options.from,
|
|
75748
76552
|
reclassify: options.reclassify
|
|
75749
76553
|
});
|
|
75750
|
-
const prdPath =
|
|
76554
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
75751
76555
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
75752
76556
|
const c = countStories(prd);
|
|
75753
76557
|
console.log(source_default.green(`
|