@nathapp/nax 0.54.4 → 0.54.6
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 +733 -630
- package/package.json +2 -2
package/dist/nax.js
CHANGED
|
@@ -18945,7 +18945,7 @@ Rules:
|
|
|
18945
18945
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18946
18946
|
- **Prefer behavioral tests** \u2014 import functions and call them rather than reading source files. For example, to verify "getPostRunActions() returns empty array", import PluginRegistry and call getPostRunActions(), don't grep the source file for the method name.
|
|
18947
18947
|
- **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
|
|
18948
|
-
- **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${options.
|
|
18948
|
+
- **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join2(options.workdir, ".nax", "features", options.featureName, acceptanceTestFilename(options.language))}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root).`;
|
|
18949
18949
|
const prompt = basePrompt;
|
|
18950
18950
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
18951
18951
|
const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
|
|
@@ -18963,7 +18963,7 @@ Rules:
|
|
|
18963
18963
|
outputPreview: rawOutput.slice(0, 300)
|
|
18964
18964
|
});
|
|
18965
18965
|
if (!testCode) {
|
|
18966
|
-
const targetPath = join2(options.
|
|
18966
|
+
const targetPath = join2(options.workdir, ".nax", "features", options.featureName, acceptanceTestFilename(options.language));
|
|
18967
18967
|
let recoveryFailed = false;
|
|
18968
18968
|
logger.debug("acceptance", "BUG-076 recovery: checking for agent-written file", { targetPath });
|
|
18969
18969
|
try {
|
|
@@ -19826,7 +19826,7 @@ async function saveAcpSession(workdir, featureName, storyId, sessionName, agentN
|
|
|
19826
19826
|
getSafeLogger()?.warn("acp-adapter", "Failed to save session to sidecar", { error: String(err) });
|
|
19827
19827
|
}
|
|
19828
19828
|
}
|
|
19829
|
-
async function clearAcpSession(workdir, featureName, storyId) {
|
|
19829
|
+
async function clearAcpSession(workdir, featureName, storyId, sessionRole) {
|
|
19830
19830
|
try {
|
|
19831
19831
|
const path = acpSessionsPath(workdir, featureName);
|
|
19832
19832
|
let data = {};
|
|
@@ -19836,7 +19836,8 @@ async function clearAcpSession(workdir, featureName, storyId) {
|
|
|
19836
19836
|
} catch {
|
|
19837
19837
|
return;
|
|
19838
19838
|
}
|
|
19839
|
-
|
|
19839
|
+
const sidecarKey = sessionRole ? `${storyId}:${sessionRole}` : storyId;
|
|
19840
|
+
delete data[sidecarKey];
|
|
19840
19841
|
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19841
19842
|
} catch (err) {
|
|
19842
19843
|
getSafeLogger()?.warn("acp-adapter", "Failed to clear session from sidecar", { error: String(err) });
|
|
@@ -20024,7 +20025,8 @@ class AcpAgentAdapter {
|
|
|
20024
20025
|
await client.start();
|
|
20025
20026
|
let sessionName = options.acpSessionName;
|
|
20026
20027
|
if (!sessionName && options.featureName && options.storyId) {
|
|
20027
|
-
|
|
20028
|
+
const sidecarKey = options.sessionRole ? `${options.storyId}:${options.sessionRole}` : options.storyId;
|
|
20029
|
+
sessionName = await readAcpSession(options.workdir, options.featureName, sidecarKey) ?? undefined;
|
|
20028
20030
|
}
|
|
20029
20031
|
sessionName ??= buildSessionName(options.workdir, options.featureName, options.storyId, options.sessionRole);
|
|
20030
20032
|
const resolvedPerm = resolvePermissions(options.config, options.pipelineStage ?? "run");
|
|
@@ -20035,7 +20037,8 @@ class AcpAgentAdapter {
|
|
|
20035
20037
|
});
|
|
20036
20038
|
const session = await ensureAcpSession(client, sessionName, this.name, permissionMode);
|
|
20037
20039
|
if (options.featureName && options.storyId) {
|
|
20038
|
-
|
|
20040
|
+
const sidecarKey = options.sessionRole ? `${options.storyId}:${options.sessionRole}` : options.storyId;
|
|
20041
|
+
await saveAcpSession(options.workdir, options.featureName, sidecarKey, sessionName, this.name);
|
|
20039
20042
|
}
|
|
20040
20043
|
let lastResponse = null;
|
|
20041
20044
|
let timedOut = false;
|
|
@@ -20072,7 +20075,8 @@ class AcpAgentAdapter {
|
|
|
20072
20075
|
totalExactCostUsd = (totalExactCostUsd ?? 0) + lastResponse.exactCostUsd;
|
|
20073
20076
|
}
|
|
20074
20077
|
const outputText = extractOutput(lastResponse);
|
|
20075
|
-
const
|
|
20078
|
+
const isEndTurn = lastResponse.stopReason === "end_turn";
|
|
20079
|
+
const question = isEndTurn ? extractQuestion(outputText) : null;
|
|
20076
20080
|
if (!question || !options.interactionBridge)
|
|
20077
20081
|
break;
|
|
20078
20082
|
getSafeLogger()?.debug("acp-adapter", "Agent asked question, routing to interactionBridge", { question });
|
|
@@ -22348,7 +22352,7 @@ var package_default;
|
|
|
22348
22352
|
var init_package = __esm(() => {
|
|
22349
22353
|
package_default = {
|
|
22350
22354
|
name: "@nathapp/nax",
|
|
22351
|
-
version: "0.54.
|
|
22355
|
+
version: "0.54.6",
|
|
22352
22356
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22353
22357
|
type: "module",
|
|
22354
22358
|
bin: {
|
|
@@ -22361,7 +22365,7 @@ var init_package = __esm(() => {
|
|
|
22361
22365
|
typecheck: "bun x tsc --noEmit",
|
|
22362
22366
|
lint: "bun x biome check src/ bin/",
|
|
22363
22367
|
release: "bun scripts/release.ts",
|
|
22364
|
-
test: "bun test test/ --timeout=60000",
|
|
22368
|
+
test: "bun test test/unit/ --timeout=60000 && bun test test/integration/ --timeout=60000 && bun test test/ui/ --timeout=60000",
|
|
22365
22369
|
"test:watch": "bun test --watch",
|
|
22366
22370
|
"test:unit": "bun test ./test/unit/ --timeout=60000",
|
|
22367
22371
|
"test:integration": "bun test ./test/integration/ --timeout=60000",
|
|
@@ -22425,8 +22429,8 @@ var init_version = __esm(() => {
|
|
|
22425
22429
|
NAX_VERSION = package_default.version;
|
|
22426
22430
|
NAX_COMMIT = (() => {
|
|
22427
22431
|
try {
|
|
22428
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22429
|
-
return "
|
|
22432
|
+
if (/^[0-9a-f]{6,10}$/.test("955ab31"))
|
|
22433
|
+
return "955ab31";
|
|
22430
22434
|
} catch {}
|
|
22431
22435
|
try {
|
|
22432
22436
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -22444,316 +22448,37 @@ var init_version = __esm(() => {
|
|
|
22444
22448
|
NAX_BUILD_INFO = NAX_COMMIT === "dev" ? `v${NAX_VERSION}` : `v${NAX_VERSION} (${NAX_COMMIT})`;
|
|
22445
22449
|
});
|
|
22446
22450
|
|
|
22447
|
-
// src/
|
|
22448
|
-
function
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
|
|
22452
|
-
if (id.includes("..")) {
|
|
22453
|
-
throw new Error("Story ID cannot contain path traversal (..)");
|
|
22454
|
-
}
|
|
22455
|
-
if (id.startsWith("--")) {
|
|
22456
|
-
throw new Error("Story ID cannot start with git flags (--)");
|
|
22457
|
-
}
|
|
22458
|
-
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
22459
|
-
if (!validPattern.test(id)) {
|
|
22460
|
-
throw new Error(`Story ID must match pattern [a-zA-Z0-9][a-zA-Z0-9._-]{0,63}. Got: ${id}`);
|
|
22461
|
-
}
|
|
22462
|
-
}
|
|
22463
|
-
|
|
22464
|
-
// src/errors.ts
|
|
22465
|
-
var NaxError, AgentNotFoundError, AgentNotInstalledError, StoryLimitExceededError, LockAcquisitionError;
|
|
22466
|
-
var init_errors3 = __esm(() => {
|
|
22467
|
-
NaxError = class NaxError extends Error {
|
|
22468
|
-
code;
|
|
22469
|
-
context;
|
|
22470
|
-
constructor(message, code, context) {
|
|
22471
|
-
super(message);
|
|
22472
|
-
this.code = code;
|
|
22473
|
-
this.context = context;
|
|
22474
|
-
this.name = "NaxError";
|
|
22475
|
-
Error.captureStackTrace(this, this.constructor);
|
|
22476
|
-
}
|
|
22477
|
-
};
|
|
22478
|
-
AgentNotFoundError = class AgentNotFoundError extends NaxError {
|
|
22479
|
-
constructor(agentName, binary) {
|
|
22480
|
-
super(`Agent "${agentName}" not found or not installed`, "AGENT_NOT_FOUND", { agentName, binary });
|
|
22481
|
-
this.name = "AgentNotFoundError";
|
|
22482
|
-
}
|
|
22483
|
-
};
|
|
22484
|
-
AgentNotInstalledError = class AgentNotInstalledError extends NaxError {
|
|
22485
|
-
constructor(agentName, binary) {
|
|
22486
|
-
super(`Agent "${agentName}" is not installed or not in PATH: ${binary}`, "AGENT_NOT_INSTALLED", {
|
|
22487
|
-
agentName,
|
|
22488
|
-
binary
|
|
22489
|
-
});
|
|
22490
|
-
this.name = "AgentNotInstalledError";
|
|
22491
|
-
}
|
|
22492
|
-
};
|
|
22493
|
-
StoryLimitExceededError = class StoryLimitExceededError extends NaxError {
|
|
22494
|
-
constructor(totalStories, limit) {
|
|
22495
|
-
super(`Feature exceeds story limit: ${totalStories} stories (max: ${limit})`, "STORY_LIMIT_EXCEEDED", {
|
|
22496
|
-
totalStories,
|
|
22497
|
-
limit
|
|
22498
|
-
});
|
|
22499
|
-
this.name = "StoryLimitExceededError";
|
|
22500
|
-
}
|
|
22501
|
-
};
|
|
22502
|
-
LockAcquisitionError = class LockAcquisitionError extends NaxError {
|
|
22503
|
-
constructor(workdir) {
|
|
22504
|
-
super("Another nax process is already running in this directory", "LOCK_ACQUISITION_FAILED", { workdir });
|
|
22505
|
-
this.name = "LockAcquisitionError";
|
|
22506
|
-
}
|
|
22507
|
-
};
|
|
22508
|
-
});
|
|
22509
|
-
|
|
22510
|
-
// src/metrics/tracker.ts
|
|
22511
|
-
import path2 from "path";
|
|
22512
|
-
function collectStoryMetrics(ctx, storyStartTime) {
|
|
22513
|
-
const story = ctx.story;
|
|
22514
|
-
const routing = ctx.routing;
|
|
22515
|
-
const agentResult = ctx.agentResult;
|
|
22516
|
-
const escalationCount = story.escalations?.length || 0;
|
|
22517
|
-
const priorFailureCount = story.priorFailures?.length || 0;
|
|
22518
|
-
const attempts = priorFailureCount + Math.max(1, story.attempts || 1);
|
|
22519
|
-
const finalTier = escalationCount > 0 ? story.escalations[escalationCount - 1].toTier : routing.modelTier;
|
|
22520
|
-
const firstPassSuccess = agentResult?.success === true && escalationCount === 0 && priorFailureCount === 0;
|
|
22521
|
-
const modelEntry = ctx.config.models[routing.modelTier];
|
|
22522
|
-
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
22523
|
-
const modelUsed = modelDef?.model || routing.modelTier;
|
|
22524
|
-
const initialComplexity = story.routing?.initialComplexity ?? routing.complexity;
|
|
22525
|
-
const isTddStrategy = routing.testStrategy === "three-session-tdd" || routing.testStrategy === "three-session-tdd-lite";
|
|
22526
|
-
const fullSuiteGatePassed = isTddStrategy ? ctx.fullSuiteGatePassed ?? false : false;
|
|
22451
|
+
// src/interaction/bridge-builder.ts
|
|
22452
|
+
function buildInteractionBridge(chain, context, timeoutMs = DEFAULT_INTERACTION_TIMEOUT_MS) {
|
|
22453
|
+
const plugin = chain?.getPrimary();
|
|
22454
|
+
if (!plugin)
|
|
22455
|
+
return;
|
|
22527
22456
|
return {
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
22532
|
-
|
|
22533
|
-
|
|
22534
|
-
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
|
|
22540
|
-
|
|
22541
|
-
|
|
22542
|
-
|
|
22543
|
-
|
|
22544
|
-
}
|
|
22545
|
-
|
|
22546
|
-
const stories = ctx.stories;
|
|
22547
|
-
const routing = ctx.routing;
|
|
22548
|
-
const agentResult = ctx.agentResult;
|
|
22549
|
-
const totalCost = agentResult?.estimatedCost || 0;
|
|
22550
|
-
const totalDuration = agentResult?.durationMs || 0;
|
|
22551
|
-
const costPerStory = totalCost / stories.length;
|
|
22552
|
-
const durationPerStory = totalDuration / stories.length;
|
|
22553
|
-
const modelEntry = ctx.config.models[routing.modelTier];
|
|
22554
|
-
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
22555
|
-
const modelUsed = modelDef?.model || routing.modelTier;
|
|
22556
|
-
return stories.map((story) => {
|
|
22557
|
-
const initialComplexity = story.routing?.initialComplexity ?? routing.complexity;
|
|
22558
|
-
return {
|
|
22559
|
-
storyId: story.id,
|
|
22560
|
-
complexity: routing.complexity,
|
|
22561
|
-
initialComplexity,
|
|
22562
|
-
modelTier: routing.modelTier,
|
|
22563
|
-
modelUsed,
|
|
22564
|
-
attempts: 1,
|
|
22565
|
-
finalTier: routing.modelTier,
|
|
22566
|
-
success: true,
|
|
22567
|
-
cost: costPerStory,
|
|
22568
|
-
durationMs: durationPerStory,
|
|
22569
|
-
firstPassSuccess: true,
|
|
22570
|
-
startedAt: storyStartTime,
|
|
22571
|
-
completedAt: new Date().toISOString(),
|
|
22572
|
-
fullSuiteGatePassed: false,
|
|
22573
|
-
runtimeCrashes: 0
|
|
22574
|
-
};
|
|
22575
|
-
});
|
|
22576
|
-
}
|
|
22577
|
-
async function saveRunMetrics(workdir, runMetrics) {
|
|
22578
|
-
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
22579
|
-
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
22580
|
-
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
22581
|
-
allMetrics.push(runMetrics);
|
|
22582
|
-
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
22583
|
-
}
|
|
22584
|
-
async function loadRunMetrics(workdir) {
|
|
22585
|
-
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
22586
|
-
const content = await loadJsonFile(metricsPath, "metrics");
|
|
22587
|
-
return Array.isArray(content) ? content : [];
|
|
22588
|
-
}
|
|
22589
|
-
var init_tracker = __esm(() => {
|
|
22590
|
-
init_schema();
|
|
22591
|
-
init_json_file();
|
|
22592
|
-
});
|
|
22593
|
-
|
|
22594
|
-
// src/metrics/aggregator.ts
|
|
22595
|
-
function calculateAggregateMetrics(runs) {
|
|
22596
|
-
if (runs.length === 0) {
|
|
22597
|
-
return {
|
|
22598
|
-
totalRuns: 0,
|
|
22599
|
-
totalCost: 0,
|
|
22600
|
-
totalStories: 0,
|
|
22601
|
-
firstPassRate: 0,
|
|
22602
|
-
escalationRate: 0,
|
|
22603
|
-
avgCostPerStory: 0,
|
|
22604
|
-
avgCostPerFeature: 0,
|
|
22605
|
-
modelEfficiency: {},
|
|
22606
|
-
complexityAccuracy: {}
|
|
22607
|
-
};
|
|
22608
|
-
}
|
|
22609
|
-
const allStories = runs.flatMap((run) => run.stories);
|
|
22610
|
-
const totalRuns = runs.length;
|
|
22611
|
-
const totalCost = runs.reduce((sum, run) => sum + run.totalCost, 0);
|
|
22612
|
-
const totalStories = allStories.length;
|
|
22613
|
-
const firstPassSuccesses = allStories.filter((s) => s.firstPassSuccess).length;
|
|
22614
|
-
const firstPassRate = totalStories > 0 ? firstPassSuccesses / totalStories : 0;
|
|
22615
|
-
const escalatedStories = allStories.filter((s) => s.attempts > 1).length;
|
|
22616
|
-
const escalationRate = totalStories > 0 ? escalatedStories / totalStories : 0;
|
|
22617
|
-
const avgCostPerStory = totalStories > 0 ? totalCost / totalStories : 0;
|
|
22618
|
-
const avgCostPerFeature = totalRuns > 0 ? totalCost / totalRuns : 0;
|
|
22619
|
-
const modelStats = new Map;
|
|
22620
|
-
for (const story of allStories) {
|
|
22621
|
-
const modelKey = story.modelUsed;
|
|
22622
|
-
const existing = modelStats.get(modelKey) || {
|
|
22623
|
-
attempts: 0,
|
|
22624
|
-
successes: 0,
|
|
22625
|
-
totalCost: 0
|
|
22626
|
-
};
|
|
22627
|
-
modelStats.set(modelKey, {
|
|
22628
|
-
attempts: existing.attempts + story.attempts,
|
|
22629
|
-
successes: existing.successes + (story.success ? 1 : 0),
|
|
22630
|
-
totalCost: existing.totalCost + story.cost
|
|
22631
|
-
});
|
|
22632
|
-
}
|
|
22633
|
-
const modelEfficiency = {};
|
|
22634
|
-
for (const [modelKey, stats] of modelStats) {
|
|
22635
|
-
const passRate = stats.attempts > 0 ? stats.successes / stats.attempts : 0;
|
|
22636
|
-
const avgCost = stats.successes > 0 ? stats.totalCost / stats.successes : 0;
|
|
22637
|
-
modelEfficiency[modelKey] = {
|
|
22638
|
-
attempts: stats.attempts,
|
|
22639
|
-
successes: stats.successes,
|
|
22640
|
-
passRate,
|
|
22641
|
-
avgCost,
|
|
22642
|
-
totalCost: stats.totalCost
|
|
22643
|
-
};
|
|
22644
|
-
}
|
|
22645
|
-
const complexityStats = new Map;
|
|
22646
|
-
for (const story of allStories) {
|
|
22647
|
-
const complexity = story.initialComplexity ?? story.complexity;
|
|
22648
|
-
const existing = complexityStats.get(complexity) || {
|
|
22649
|
-
predicted: 0,
|
|
22650
|
-
tierCounts: new Map,
|
|
22651
|
-
mismatches: 0
|
|
22652
|
-
};
|
|
22653
|
-
existing.predicted += 1;
|
|
22654
|
-
const finalTier = story.finalTier;
|
|
22655
|
-
existing.tierCounts.set(finalTier, (existing.tierCounts.get(finalTier) || 0) + 1);
|
|
22656
|
-
if (story.modelTier !== story.finalTier) {
|
|
22657
|
-
existing.mismatches += 1;
|
|
22658
|
-
}
|
|
22659
|
-
complexityStats.set(complexity, existing);
|
|
22660
|
-
}
|
|
22661
|
-
const complexityAccuracy = {};
|
|
22662
|
-
for (const [complexity, stats] of complexityStats) {
|
|
22663
|
-
let maxCount = 0;
|
|
22664
|
-
let mostCommonTier = "unknown";
|
|
22665
|
-
for (const [tier, count] of stats.tierCounts) {
|
|
22666
|
-
if (count > maxCount) {
|
|
22667
|
-
maxCount = count;
|
|
22668
|
-
mostCommonTier = tier;
|
|
22457
|
+
detectQuestion: async (text) => QUESTION_PATTERNS.some((p) => p.test(text)),
|
|
22458
|
+
onQuestionDetected: async (text) => {
|
|
22459
|
+
const requestId = `ix-${context.stage}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
22460
|
+
await plugin.send({
|
|
22461
|
+
id: requestId,
|
|
22462
|
+
type: "input",
|
|
22463
|
+
featureName: context.featureName ?? "unknown",
|
|
22464
|
+
storyId: context.storyId,
|
|
22465
|
+
stage: context.stage,
|
|
22466
|
+
summary: text,
|
|
22467
|
+
fallback: "continue",
|
|
22468
|
+
createdAt: Date.now()
|
|
22469
|
+
});
|
|
22470
|
+
try {
|
|
22471
|
+
const response = await plugin.receive(requestId, timeoutMs);
|
|
22472
|
+
return response.value ?? "continue";
|
|
22473
|
+
} catch {
|
|
22474
|
+
return "continue";
|
|
22669
22475
|
}
|
|
22670
22476
|
}
|
|
22671
|
-
const mismatchRate = stats.predicted > 0 ? stats.mismatches / stats.predicted : 0;
|
|
22672
|
-
complexityAccuracy[complexity] = {
|
|
22673
|
-
predicted: stats.predicted,
|
|
22674
|
-
actualTierUsed: mostCommonTier,
|
|
22675
|
-
mismatchRate
|
|
22676
|
-
};
|
|
22677
|
-
}
|
|
22678
|
-
return {
|
|
22679
|
-
totalRuns,
|
|
22680
|
-
totalCost,
|
|
22681
|
-
totalStories,
|
|
22682
|
-
firstPassRate,
|
|
22683
|
-
escalationRate,
|
|
22684
|
-
avgCostPerStory,
|
|
22685
|
-
avgCostPerFeature,
|
|
22686
|
-
modelEfficiency,
|
|
22687
|
-
complexityAccuracy
|
|
22688
22477
|
};
|
|
22689
22478
|
}
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
}
|
|
22694
|
-
return runs[runs.length - 1];
|
|
22695
|
-
}
|
|
22696
|
-
|
|
22697
|
-
// src/metrics/index.ts
|
|
22698
|
-
var init_metrics = __esm(() => {
|
|
22699
|
-
init_tracker();
|
|
22700
|
-
});
|
|
22701
|
-
|
|
22702
|
-
// src/interaction/types.ts
|
|
22703
|
-
var TRIGGER_METADATA;
|
|
22704
|
-
var init_types4 = __esm(() => {
|
|
22705
|
-
TRIGGER_METADATA = {
|
|
22706
|
-
"security-review": {
|
|
22707
|
-
defaultFallback: "abort",
|
|
22708
|
-
safety: "red",
|
|
22709
|
-
defaultSummary: "Security review failed \u2014 abort execution?"
|
|
22710
|
-
},
|
|
22711
|
-
"cost-exceeded": {
|
|
22712
|
-
defaultFallback: "abort",
|
|
22713
|
-
safety: "red",
|
|
22714
|
-
defaultSummary: "Cost limit exceeded ({{cost}} USD) \u2014 abort execution?"
|
|
22715
|
-
},
|
|
22716
|
-
"merge-conflict": {
|
|
22717
|
-
defaultFallback: "abort",
|
|
22718
|
-
safety: "red",
|
|
22719
|
-
defaultSummary: "Merge conflict detected in {{storyId}} \u2014 abort execution?"
|
|
22720
|
-
},
|
|
22721
|
-
"cost-warning": {
|
|
22722
|
-
defaultFallback: "escalate",
|
|
22723
|
-
safety: "yellow",
|
|
22724
|
-
defaultSummary: "Cost warning: {{cost}} USD / {{limit}} USD \u2014 escalate to higher tier?"
|
|
22725
|
-
},
|
|
22726
|
-
"max-retries": {
|
|
22727
|
-
defaultFallback: "skip",
|
|
22728
|
-
safety: "yellow",
|
|
22729
|
-
defaultSummary: "Max retries reached for {{storyId}} \u2014 skip story?"
|
|
22730
|
-
},
|
|
22731
|
-
"pre-merge": {
|
|
22732
|
-
defaultFallback: "escalate",
|
|
22733
|
-
safety: "yellow",
|
|
22734
|
-
defaultSummary: "Pre-merge checkpoint for {{storyId}} \u2014 proceed with merge?"
|
|
22735
|
-
},
|
|
22736
|
-
"human-review": {
|
|
22737
|
-
defaultFallback: "skip",
|
|
22738
|
-
safety: "yellow",
|
|
22739
|
-
defaultSummary: "Human review required for story {{storyId}} \u2014 skip and continue?"
|
|
22740
|
-
},
|
|
22741
|
-
"story-oversized": {
|
|
22742
|
-
defaultFallback: "continue",
|
|
22743
|
-
safety: "yellow",
|
|
22744
|
-
defaultSummary: "Story {{storyId}} is oversized ({{criteriaCount}} acceptance criteria) \u2014 decompose into smaller stories?"
|
|
22745
|
-
},
|
|
22746
|
-
"story-ambiguity": {
|
|
22747
|
-
defaultFallback: "continue",
|
|
22748
|
-
safety: "green",
|
|
22749
|
-
defaultSummary: "Story {{storyId}} requirements unclear \u2014 continue with best effort?"
|
|
22750
|
-
},
|
|
22751
|
-
"review-gate": {
|
|
22752
|
-
defaultFallback: "continue",
|
|
22753
|
-
safety: "green",
|
|
22754
|
-
defaultSummary: "Code review checkpoint for {{storyId}} \u2014 proceed?"
|
|
22755
|
-
}
|
|
22756
|
-
};
|
|
22479
|
+
var QUESTION_PATTERNS, DEFAULT_INTERACTION_TIMEOUT_MS = 120000;
|
|
22480
|
+
var init_bridge_builder = __esm(() => {
|
|
22481
|
+
QUESTION_PATTERNS = [/\?/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
|
|
22757
22482
|
});
|
|
22758
22483
|
|
|
22759
22484
|
// src/interaction/chain.ts
|
|
@@ -22844,47 +22569,166 @@ class InteractionChain {
|
|
|
22844
22569
|
}
|
|
22845
22570
|
}
|
|
22846
22571
|
|
|
22847
|
-
// src/interaction/
|
|
22848
|
-
|
|
22849
|
-
|
|
22850
|
-
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
|
|
22856
|
-
|
|
22857
|
-
|
|
22572
|
+
// src/interaction/plugins/auto.ts
|
|
22573
|
+
class AutoInteractionPlugin {
|
|
22574
|
+
name = "auto";
|
|
22575
|
+
config = {};
|
|
22576
|
+
async init(config2) {
|
|
22577
|
+
const cfg = AutoConfigSchema.parse(config2);
|
|
22578
|
+
this.config = {
|
|
22579
|
+
model: cfg.model ?? "fast",
|
|
22580
|
+
confidenceThreshold: cfg.confidenceThreshold ?? 0.7,
|
|
22581
|
+
maxCostPerDecision: cfg.maxCostPerDecision ?? 0.01,
|
|
22582
|
+
naxConfig: cfg.naxConfig
|
|
22583
|
+
};
|
|
22584
|
+
}
|
|
22585
|
+
async destroy() {}
|
|
22586
|
+
async send(request) {}
|
|
22587
|
+
async receive(_requestId, _timeout = 60000) {
|
|
22588
|
+
throw new Error("Auto plugin requires full request context (not just requestId)");
|
|
22589
|
+
}
|
|
22590
|
+
async decide(request) {
|
|
22591
|
+
if (request.metadata?.trigger === "security-review") {
|
|
22592
|
+
return;
|
|
22593
|
+
}
|
|
22594
|
+
try {
|
|
22595
|
+
if (_autoPluginDeps.callLlm) {
|
|
22596
|
+
const decision2 = await _autoPluginDeps.callLlm(request);
|
|
22597
|
+
if (decision2.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
22598
|
+
return;
|
|
22599
|
+
}
|
|
22600
|
+
return {
|
|
22601
|
+
requestId: request.id,
|
|
22602
|
+
action: decision2.action,
|
|
22603
|
+
value: decision2.value,
|
|
22604
|
+
respondedBy: "auto-ai",
|
|
22605
|
+
respondedAt: Date.now()
|
|
22606
|
+
};
|
|
22607
|
+
}
|
|
22608
|
+
const decision = await this.callLlm(request);
|
|
22609
|
+
if (decision.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
22610
|
+
return;
|
|
22611
|
+
}
|
|
22612
|
+
return {
|
|
22613
|
+
requestId: request.id,
|
|
22614
|
+
action: decision.action,
|
|
22615
|
+
value: decision.value,
|
|
22616
|
+
respondedBy: "auto-ai",
|
|
22617
|
+
respondedAt: Date.now()
|
|
22618
|
+
};
|
|
22619
|
+
} catch (err) {
|
|
22620
|
+
return;
|
|
22858
22621
|
}
|
|
22859
|
-
const json2 = await file2.text();
|
|
22860
|
-
const request = JSON.parse(json2);
|
|
22861
|
-
return request;
|
|
22862
|
-
} catch {
|
|
22863
|
-
return null;
|
|
22864
22622
|
}
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
22870
|
-
const exists = await dir.exists();
|
|
22871
|
-
if (!exists) {
|
|
22872
|
-
return [];
|
|
22623
|
+
async callLlm(request) {
|
|
22624
|
+
const prompt = this.buildPrompt(request);
|
|
22625
|
+
const adapter = _autoPluginDeps.adapter;
|
|
22626
|
+
if (!adapter) {
|
|
22627
|
+
throw new Error("Auto plugin requires adapter to be injected via _autoPluginDeps.adapter");
|
|
22873
22628
|
}
|
|
22874
|
-
|
|
22875
|
-
|
|
22876
|
-
|
|
22629
|
+
let modelArg;
|
|
22630
|
+
if (this.config.naxConfig) {
|
|
22631
|
+
const modelTier = this.config.model ?? "fast";
|
|
22632
|
+
const modelEntry = this.config.naxConfig.models[modelTier];
|
|
22633
|
+
if (!modelEntry) {
|
|
22634
|
+
throw new Error(`Model tier "${modelTier}" not found in config.models`);
|
|
22635
|
+
}
|
|
22636
|
+
const modelDef = resolveModel(modelEntry);
|
|
22637
|
+
modelArg = modelDef.model;
|
|
22638
|
+
}
|
|
22639
|
+
const output = await adapter.complete(prompt, {
|
|
22640
|
+
...modelArg && { model: modelArg },
|
|
22641
|
+
jsonMode: true,
|
|
22642
|
+
...this.config.naxConfig && { config: this.config.naxConfig },
|
|
22643
|
+
featureName: request.featureName,
|
|
22644
|
+
storyId: request.storyId,
|
|
22645
|
+
sessionRole: "auto"
|
|
22877
22646
|
});
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
`
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22647
|
+
return this.parseResponse(output);
|
|
22648
|
+
}
|
|
22649
|
+
buildPrompt(request) {
|
|
22650
|
+
let prompt = `You are an AI decision assistant for a code orchestration system. Given an interaction request, decide the best action.
|
|
22651
|
+
|
|
22652
|
+
## Interaction Request
|
|
22653
|
+
Type: ${request.type}
|
|
22654
|
+
Stage: ${request.stage}
|
|
22655
|
+
Feature: ${request.featureName}
|
|
22656
|
+
${request.storyId ? `Story: ${request.storyId}` : ""}
|
|
22657
|
+
|
|
22658
|
+
Summary: ${request.summary.replace(/`/g, "\\`").replace(/\$/g, "\\$")}
|
|
22659
|
+
${request.detail ? `
|
|
22660
|
+
Detail: ${request.detail.replace(/`/g, "\\`").replace(/\$/g, "\\$")}` : ""}
|
|
22661
|
+
`;
|
|
22662
|
+
if (request.options && request.options.length > 0) {
|
|
22663
|
+
prompt += `
|
|
22664
|
+
Options:
|
|
22665
|
+
`;
|
|
22666
|
+
for (const opt of request.options) {
|
|
22667
|
+
const desc = opt.description ? ` \u2014 ${opt.description}` : "";
|
|
22668
|
+
prompt += ` [${opt.key}] ${opt.label}${desc}
|
|
22669
|
+
`;
|
|
22670
|
+
}
|
|
22671
|
+
}
|
|
22672
|
+
prompt += `
|
|
22673
|
+
Fallback behavior on timeout: ${request.fallback}
|
|
22674
|
+
Safety tier: ${request.metadata?.safety ?? "unknown"}
|
|
22675
|
+
|
|
22676
|
+
## Available Actions
|
|
22677
|
+
- approve: Proceed with the operation
|
|
22678
|
+
- reject: Deny the operation
|
|
22679
|
+
- choose: Select an option (requires value field)
|
|
22680
|
+
- input: Provide text input (requires value field)
|
|
22681
|
+
- skip: Skip this interaction
|
|
22682
|
+
- abort: Abort execution
|
|
22683
|
+
|
|
22684
|
+
## Rules
|
|
22685
|
+
1. For "red" safety tier (security-review, cost-exceeded, merge-conflict): ALWAYS return confidence 0 to escalate to human
|
|
22686
|
+
2. For "yellow" safety tier (cost-warning, max-retries, pre-merge): High confidence (0.8+) ONLY if clearly safe
|
|
22687
|
+
3. For "green" safety tier (story-ambiguity, review-gate): Can approve with moderate confidence (0.6+)
|
|
22688
|
+
4. Default to the fallback behavior if unsure
|
|
22689
|
+
5. Never auto-approve security issues
|
|
22690
|
+
6. If the summary mentions "critical" or "security", confidence MUST be < 0.5
|
|
22691
|
+
|
|
22692
|
+
Respond with ONLY this JSON (no markdown, no explanation):
|
|
22693
|
+
{"action":"approve|reject|choose|input|skip|abort","value":"<optional>","confidence":0.0-1.0,"reasoning":"<one line>"}`;
|
|
22694
|
+
return prompt;
|
|
22695
|
+
}
|
|
22696
|
+
parseResponse(output) {
|
|
22697
|
+
let jsonText = output.trim();
|
|
22698
|
+
if (jsonText.startsWith("```")) {
|
|
22699
|
+
const lines = jsonText.split(`
|
|
22700
|
+
`);
|
|
22701
|
+
jsonText = lines.slice(1, -1).join(`
|
|
22702
|
+
`).trim();
|
|
22703
|
+
}
|
|
22704
|
+
if (jsonText.startsWith("json")) {
|
|
22705
|
+
jsonText = jsonText.slice(4).trim();
|
|
22706
|
+
}
|
|
22707
|
+
const parsed = JSON.parse(jsonText);
|
|
22708
|
+
if (!parsed.action || parsed.confidence === undefined || !parsed.reasoning) {
|
|
22709
|
+
throw new Error(`Invalid LLM response: ${jsonText}`);
|
|
22710
|
+
}
|
|
22711
|
+
if (parsed.confidence < 0 || parsed.confidence > 1) {
|
|
22712
|
+
throw new Error(`Invalid confidence: ${parsed.confidence} (must be 0-1)`);
|
|
22713
|
+
}
|
|
22714
|
+
return parsed;
|
|
22885
22715
|
}
|
|
22886
22716
|
}
|
|
22887
|
-
var
|
|
22717
|
+
var AutoConfigSchema, _autoPluginDeps;
|
|
22718
|
+
var init_auto = __esm(() => {
|
|
22719
|
+
init_zod();
|
|
22720
|
+
init_config();
|
|
22721
|
+
AutoConfigSchema = exports_external.object({
|
|
22722
|
+
model: exports_external.string().optional(),
|
|
22723
|
+
confidenceThreshold: exports_external.number().min(0).max(1).optional(),
|
|
22724
|
+
maxCostPerDecision: exports_external.number().positive().optional(),
|
|
22725
|
+
naxConfig: exports_external.any().optional()
|
|
22726
|
+
});
|
|
22727
|
+
_autoPluginDeps = {
|
|
22728
|
+
adapter: null,
|
|
22729
|
+
callLlm: null
|
|
22730
|
+
};
|
|
22731
|
+
});
|
|
22888
22732
|
|
|
22889
22733
|
// src/interaction/plugins/cli.ts
|
|
22890
22734
|
import * as readline from "readline";
|
|
@@ -22953,9 +22797,9 @@ ${request.summary}
|
|
|
22953
22797
|
if (!this.rl) {
|
|
22954
22798
|
throw new Error("CLI plugin not initialized");
|
|
22955
22799
|
}
|
|
22956
|
-
const timeoutPromise = new Promise((
|
|
22800
|
+
const timeoutPromise = new Promise((resolve4) => {
|
|
22957
22801
|
setTimeout(() => {
|
|
22958
|
-
|
|
22802
|
+
resolve4({
|
|
22959
22803
|
requestId: request.id,
|
|
22960
22804
|
action: "skip",
|
|
22961
22805
|
respondedBy: "timeout",
|
|
@@ -23107,9 +22951,9 @@ ${request.summary}
|
|
|
23107
22951
|
if (!this.rl) {
|
|
23108
22952
|
throw new Error("CLI plugin not initialized");
|
|
23109
22953
|
}
|
|
23110
|
-
return new Promise((
|
|
22954
|
+
return new Promise((resolve4) => {
|
|
23111
22955
|
this.rl?.question(prompt, (answer) => {
|
|
23112
|
-
|
|
22956
|
+
resolve4(answer);
|
|
23113
22957
|
});
|
|
23114
22958
|
});
|
|
23115
22959
|
}
|
|
@@ -23506,10 +23350,10 @@ class WebhookInteractionPlugin {
|
|
|
23506
23350
|
this.pendingResponses.delete(requestId);
|
|
23507
23351
|
return early;
|
|
23508
23352
|
}
|
|
23509
|
-
return new Promise((
|
|
23353
|
+
return new Promise((resolve4) => {
|
|
23510
23354
|
const timer = setTimeout(() => {
|
|
23511
23355
|
this.receiveCallbacks.delete(requestId);
|
|
23512
|
-
|
|
23356
|
+
resolve4({
|
|
23513
23357
|
requestId,
|
|
23514
23358
|
action: "skip",
|
|
23515
23359
|
respondedBy: "timeout",
|
|
@@ -23519,7 +23363,7 @@ class WebhookInteractionPlugin {
|
|
|
23519
23363
|
this.receiveCallbacks.set(requestId, (response) => {
|
|
23520
23364
|
clearTimeout(timer);
|
|
23521
23365
|
this.receiveCallbacks.delete(requestId);
|
|
23522
|
-
|
|
23366
|
+
resolve4(response);
|
|
23523
23367
|
});
|
|
23524
23368
|
});
|
|
23525
23369
|
}
|
|
@@ -23640,167 +23484,414 @@ var init_webhook = __esm(() => {
|
|
|
23640
23484
|
});
|
|
23641
23485
|
});
|
|
23642
23486
|
|
|
23643
|
-
// src/interaction/
|
|
23644
|
-
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
|
|
23648
|
-
|
|
23649
|
-
|
|
23650
|
-
|
|
23651
|
-
|
|
23652
|
-
|
|
23653
|
-
|
|
23654
|
-
|
|
23487
|
+
// src/interaction/init.ts
|
|
23488
|
+
function createInteractionPlugin(pluginName) {
|
|
23489
|
+
switch (pluginName) {
|
|
23490
|
+
case "cli":
|
|
23491
|
+
return new CLIInteractionPlugin;
|
|
23492
|
+
case "telegram":
|
|
23493
|
+
return new TelegramInteractionPlugin;
|
|
23494
|
+
case "webhook":
|
|
23495
|
+
return new WebhookInteractionPlugin;
|
|
23496
|
+
case "auto":
|
|
23497
|
+
return new AutoInteractionPlugin;
|
|
23498
|
+
default:
|
|
23499
|
+
throw new Error(`Unknown interaction plugin: ${pluginName}`);
|
|
23655
23500
|
}
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
|
|
23659
|
-
|
|
23501
|
+
}
|
|
23502
|
+
async function initInteractionChain(config2, headless) {
|
|
23503
|
+
const logger = getSafeLogger();
|
|
23504
|
+
if (!config2.interaction) {
|
|
23505
|
+
logger?.debug("interaction", "No interaction config - skipping interaction system");
|
|
23506
|
+
return null;
|
|
23660
23507
|
}
|
|
23661
|
-
|
|
23662
|
-
|
|
23663
|
-
|
|
23664
|
-
|
|
23665
|
-
try {
|
|
23666
|
-
if (_autoPluginDeps.callLlm) {
|
|
23667
|
-
const decision2 = await _autoPluginDeps.callLlm(request);
|
|
23668
|
-
if (decision2.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
23669
|
-
return;
|
|
23670
|
-
}
|
|
23671
|
-
return {
|
|
23672
|
-
requestId: request.id,
|
|
23673
|
-
action: decision2.action,
|
|
23674
|
-
value: decision2.value,
|
|
23675
|
-
respondedBy: "auto-ai",
|
|
23676
|
-
respondedAt: Date.now()
|
|
23677
|
-
};
|
|
23678
|
-
}
|
|
23679
|
-
const decision = await this.callLlm(request);
|
|
23680
|
-
if (decision.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
23681
|
-
return;
|
|
23682
|
-
}
|
|
23683
|
-
return {
|
|
23684
|
-
requestId: request.id,
|
|
23685
|
-
action: decision.action,
|
|
23686
|
-
value: decision.value,
|
|
23687
|
-
respondedBy: "auto-ai",
|
|
23688
|
-
respondedAt: Date.now()
|
|
23689
|
-
};
|
|
23690
|
-
} catch (err) {
|
|
23691
|
-
return;
|
|
23692
|
-
}
|
|
23508
|
+
const pluginName = config2.interaction.plugin;
|
|
23509
|
+
if (headless && pluginName === "cli") {
|
|
23510
|
+
logger?.debug("interaction", "Headless mode with CLI plugin - skipping interaction system (stdin unavailable)");
|
|
23511
|
+
return null;
|
|
23693
23512
|
}
|
|
23694
|
-
|
|
23695
|
-
|
|
23696
|
-
|
|
23697
|
-
|
|
23698
|
-
|
|
23699
|
-
|
|
23700
|
-
|
|
23701
|
-
|
|
23702
|
-
|
|
23703
|
-
|
|
23704
|
-
|
|
23705
|
-
|
|
23706
|
-
}
|
|
23707
|
-
const modelDef = resolveModel(modelEntry);
|
|
23708
|
-
modelArg = modelDef.model;
|
|
23709
|
-
}
|
|
23710
|
-
const output = await adapter.complete(prompt, {
|
|
23711
|
-
...modelArg && { model: modelArg },
|
|
23712
|
-
jsonMode: true,
|
|
23713
|
-
...this.config.naxConfig && { config: this.config.naxConfig },
|
|
23714
|
-
featureName: request.featureName,
|
|
23715
|
-
storyId: request.storyId,
|
|
23716
|
-
sessionRole: "auto"
|
|
23513
|
+
const chain = new InteractionChain({
|
|
23514
|
+
defaultTimeout: config2.interaction.defaults.timeout,
|
|
23515
|
+
defaultFallback: config2.interaction.defaults.fallback
|
|
23516
|
+
});
|
|
23517
|
+
try {
|
|
23518
|
+
const plugin = createInteractionPlugin(pluginName);
|
|
23519
|
+
chain.register(plugin, 100);
|
|
23520
|
+
const pluginConfig = config2.interaction.config ?? {};
|
|
23521
|
+
await chain.init({ [pluginName]: pluginConfig });
|
|
23522
|
+
logger?.info("interaction", `Initialized ${pluginName} interaction plugin`, {
|
|
23523
|
+
timeout: config2.interaction.defaults.timeout,
|
|
23524
|
+
fallback: config2.interaction.defaults.fallback
|
|
23717
23525
|
});
|
|
23718
|
-
return
|
|
23526
|
+
return chain;
|
|
23527
|
+
} catch (err) {
|
|
23528
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
23529
|
+
logger?.error("interaction", `Failed to initialize interaction plugin: ${error48}`);
|
|
23530
|
+
throw err;
|
|
23719
23531
|
}
|
|
23720
|
-
|
|
23721
|
-
|
|
23532
|
+
}
|
|
23533
|
+
var init_init = __esm(() => {
|
|
23534
|
+
init_logger2();
|
|
23535
|
+
init_auto();
|
|
23536
|
+
init_cli();
|
|
23537
|
+
init_telegram();
|
|
23538
|
+
init_webhook();
|
|
23539
|
+
});
|
|
23722
23540
|
|
|
23723
|
-
|
|
23724
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
23727
|
-
|
|
23541
|
+
// src/prd/validate.ts
|
|
23542
|
+
function validateStoryId(id) {
|
|
23543
|
+
if (!id || id.length === 0) {
|
|
23544
|
+
throw new Error("Story ID cannot be empty");
|
|
23545
|
+
}
|
|
23546
|
+
if (id.includes("..")) {
|
|
23547
|
+
throw new Error("Story ID cannot contain path traversal (..)");
|
|
23548
|
+
}
|
|
23549
|
+
if (id.startsWith("--")) {
|
|
23550
|
+
throw new Error("Story ID cannot start with git flags (--)");
|
|
23551
|
+
}
|
|
23552
|
+
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
23553
|
+
if (!validPattern.test(id)) {
|
|
23554
|
+
throw new Error(`Story ID must match pattern [a-zA-Z0-9][a-zA-Z0-9._-]{0,63}. Got: ${id}`);
|
|
23555
|
+
}
|
|
23556
|
+
}
|
|
23728
23557
|
|
|
23729
|
-
|
|
23730
|
-
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
}
|
|
23558
|
+
// src/errors.ts
|
|
23559
|
+
var NaxError, AgentNotFoundError, AgentNotInstalledError, StoryLimitExceededError, LockAcquisitionError;
|
|
23560
|
+
var init_errors3 = __esm(() => {
|
|
23561
|
+
NaxError = class NaxError extends Error {
|
|
23562
|
+
code;
|
|
23563
|
+
context;
|
|
23564
|
+
constructor(message, code, context) {
|
|
23565
|
+
super(message);
|
|
23566
|
+
this.code = code;
|
|
23567
|
+
this.context = context;
|
|
23568
|
+
this.name = "NaxError";
|
|
23569
|
+
Error.captureStackTrace(this, this.constructor);
|
|
23742
23570
|
}
|
|
23743
|
-
|
|
23744
|
-
|
|
23745
|
-
|
|
23746
|
-
|
|
23747
|
-
|
|
23748
|
-
|
|
23749
|
-
|
|
23750
|
-
|
|
23751
|
-
|
|
23752
|
-
|
|
23753
|
-
|
|
23571
|
+
};
|
|
23572
|
+
AgentNotFoundError = class AgentNotFoundError extends NaxError {
|
|
23573
|
+
constructor(agentName, binary) {
|
|
23574
|
+
super(`Agent "${agentName}" not found or not installed`, "AGENT_NOT_FOUND", { agentName, binary });
|
|
23575
|
+
this.name = "AgentNotFoundError";
|
|
23576
|
+
}
|
|
23577
|
+
};
|
|
23578
|
+
AgentNotInstalledError = class AgentNotInstalledError extends NaxError {
|
|
23579
|
+
constructor(agentName, binary) {
|
|
23580
|
+
super(`Agent "${agentName}" is not installed or not in PATH: ${binary}`, "AGENT_NOT_INSTALLED", {
|
|
23581
|
+
agentName,
|
|
23582
|
+
binary
|
|
23583
|
+
});
|
|
23584
|
+
this.name = "AgentNotInstalledError";
|
|
23585
|
+
}
|
|
23586
|
+
};
|
|
23587
|
+
StoryLimitExceededError = class StoryLimitExceededError extends NaxError {
|
|
23588
|
+
constructor(totalStories, limit) {
|
|
23589
|
+
super(`Feature exceeds story limit: ${totalStories} stories (max: ${limit})`, "STORY_LIMIT_EXCEEDED", {
|
|
23590
|
+
totalStories,
|
|
23591
|
+
limit
|
|
23592
|
+
});
|
|
23593
|
+
this.name = "StoryLimitExceededError";
|
|
23594
|
+
}
|
|
23595
|
+
};
|
|
23596
|
+
LockAcquisitionError = class LockAcquisitionError extends NaxError {
|
|
23597
|
+
constructor(workdir) {
|
|
23598
|
+
super("Another nax process is already running in this directory", "LOCK_ACQUISITION_FAILED", { workdir });
|
|
23599
|
+
this.name = "LockAcquisitionError";
|
|
23600
|
+
}
|
|
23601
|
+
};
|
|
23602
|
+
});
|
|
23754
23603
|
|
|
23755
|
-
|
|
23756
|
-
|
|
23757
|
-
|
|
23758
|
-
|
|
23759
|
-
|
|
23760
|
-
|
|
23761
|
-
|
|
23604
|
+
// src/metrics/tracker.ts
|
|
23605
|
+
import path2 from "path";
|
|
23606
|
+
function collectStoryMetrics(ctx, storyStartTime) {
|
|
23607
|
+
const story = ctx.story;
|
|
23608
|
+
const routing = ctx.routing;
|
|
23609
|
+
const agentResult = ctx.agentResult;
|
|
23610
|
+
const escalationCount = story.escalations?.length || 0;
|
|
23611
|
+
const priorFailureCount = story.priorFailures?.length || 0;
|
|
23612
|
+
const attempts = priorFailureCount + Math.max(1, story.attempts || 1);
|
|
23613
|
+
const finalTier = escalationCount > 0 ? story.escalations[escalationCount - 1].toTier : routing.modelTier;
|
|
23614
|
+
const firstPassSuccess = agentResult?.success === true && escalationCount === 0 && priorFailureCount === 0;
|
|
23615
|
+
const modelEntry = ctx.config.models[routing.modelTier];
|
|
23616
|
+
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
23617
|
+
const modelUsed = modelDef?.model || routing.modelTier;
|
|
23618
|
+
const initialComplexity = story.routing?.initialComplexity ?? routing.complexity;
|
|
23619
|
+
const isTddStrategy = routing.testStrategy === "three-session-tdd" || routing.testStrategy === "three-session-tdd-lite";
|
|
23620
|
+
const fullSuiteGatePassed = isTddStrategy ? ctx.fullSuiteGatePassed ?? false : false;
|
|
23621
|
+
return {
|
|
23622
|
+
storyId: story.id,
|
|
23623
|
+
complexity: routing.complexity,
|
|
23624
|
+
initialComplexity,
|
|
23625
|
+
modelTier: routing.modelTier,
|
|
23626
|
+
modelUsed,
|
|
23627
|
+
attempts,
|
|
23628
|
+
finalTier,
|
|
23629
|
+
success: agentResult?.success || false,
|
|
23630
|
+
cost: (ctx.accumulatedAttemptCost ?? 0) + (agentResult?.estimatedCost || 0),
|
|
23631
|
+
durationMs: agentResult?.durationMs || 0,
|
|
23632
|
+
firstPassSuccess,
|
|
23633
|
+
startedAt: storyStartTime,
|
|
23634
|
+
completedAt: new Date().toISOString(),
|
|
23635
|
+
fullSuiteGatePassed,
|
|
23636
|
+
runtimeCrashes: ctx.storyRuntimeCrashes ?? 0
|
|
23637
|
+
};
|
|
23638
|
+
}
|
|
23639
|
+
function collectBatchMetrics(ctx, storyStartTime) {
|
|
23640
|
+
const stories = ctx.stories;
|
|
23641
|
+
const routing = ctx.routing;
|
|
23642
|
+
const agentResult = ctx.agentResult;
|
|
23643
|
+
const totalCost = agentResult?.estimatedCost || 0;
|
|
23644
|
+
const totalDuration = agentResult?.durationMs || 0;
|
|
23645
|
+
const costPerStory = totalCost / stories.length;
|
|
23646
|
+
const durationPerStory = totalDuration / stories.length;
|
|
23647
|
+
const modelEntry = ctx.config.models[routing.modelTier];
|
|
23648
|
+
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
23649
|
+
const modelUsed = modelDef?.model || routing.modelTier;
|
|
23650
|
+
return stories.map((story) => {
|
|
23651
|
+
const initialComplexity = story.routing?.initialComplexity ?? routing.complexity;
|
|
23652
|
+
return {
|
|
23653
|
+
storyId: story.id,
|
|
23654
|
+
complexity: routing.complexity,
|
|
23655
|
+
initialComplexity,
|
|
23656
|
+
modelTier: routing.modelTier,
|
|
23657
|
+
modelUsed,
|
|
23658
|
+
attempts: 1,
|
|
23659
|
+
finalTier: routing.modelTier,
|
|
23660
|
+
success: true,
|
|
23661
|
+
cost: costPerStory,
|
|
23662
|
+
durationMs: durationPerStory,
|
|
23663
|
+
firstPassSuccess: true,
|
|
23664
|
+
startedAt: storyStartTime,
|
|
23665
|
+
completedAt: new Date().toISOString(),
|
|
23666
|
+
fullSuiteGatePassed: false,
|
|
23667
|
+
runtimeCrashes: 0
|
|
23668
|
+
};
|
|
23669
|
+
});
|
|
23670
|
+
}
|
|
23671
|
+
async function saveRunMetrics(workdir, runMetrics) {
|
|
23672
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
23673
|
+
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
23674
|
+
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
23675
|
+
allMetrics.push(runMetrics);
|
|
23676
|
+
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
23677
|
+
}
|
|
23678
|
+
async function loadRunMetrics(workdir) {
|
|
23679
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
23680
|
+
const content = await loadJsonFile(metricsPath, "metrics");
|
|
23681
|
+
return Array.isArray(content) ? content : [];
|
|
23682
|
+
}
|
|
23683
|
+
var init_tracker = __esm(() => {
|
|
23684
|
+
init_schema();
|
|
23685
|
+
init_json_file();
|
|
23686
|
+
});
|
|
23762
23687
|
|
|
23763
|
-
|
|
23764
|
-
{
|
|
23765
|
-
|
|
23688
|
+
// src/metrics/aggregator.ts
|
|
23689
|
+
function calculateAggregateMetrics(runs) {
|
|
23690
|
+
if (runs.length === 0) {
|
|
23691
|
+
return {
|
|
23692
|
+
totalRuns: 0,
|
|
23693
|
+
totalCost: 0,
|
|
23694
|
+
totalStories: 0,
|
|
23695
|
+
firstPassRate: 0,
|
|
23696
|
+
escalationRate: 0,
|
|
23697
|
+
avgCostPerStory: 0,
|
|
23698
|
+
avgCostPerFeature: 0,
|
|
23699
|
+
modelEfficiency: {},
|
|
23700
|
+
complexityAccuracy: {}
|
|
23701
|
+
};
|
|
23766
23702
|
}
|
|
23767
|
-
|
|
23768
|
-
|
|
23769
|
-
|
|
23770
|
-
|
|
23771
|
-
|
|
23772
|
-
|
|
23773
|
-
|
|
23774
|
-
|
|
23775
|
-
|
|
23776
|
-
|
|
23777
|
-
|
|
23778
|
-
|
|
23779
|
-
|
|
23780
|
-
|
|
23703
|
+
const allStories = runs.flatMap((run) => run.stories);
|
|
23704
|
+
const totalRuns = runs.length;
|
|
23705
|
+
const totalCost = runs.reduce((sum, run) => sum + run.totalCost, 0);
|
|
23706
|
+
const totalStories = allStories.length;
|
|
23707
|
+
const firstPassSuccesses = allStories.filter((s) => s.firstPassSuccess).length;
|
|
23708
|
+
const firstPassRate = totalStories > 0 ? firstPassSuccesses / totalStories : 0;
|
|
23709
|
+
const escalatedStories = allStories.filter((s) => s.attempts > 1).length;
|
|
23710
|
+
const escalationRate = totalStories > 0 ? escalatedStories / totalStories : 0;
|
|
23711
|
+
const avgCostPerStory = totalStories > 0 ? totalCost / totalStories : 0;
|
|
23712
|
+
const avgCostPerFeature = totalRuns > 0 ? totalCost / totalRuns : 0;
|
|
23713
|
+
const modelStats = new Map;
|
|
23714
|
+
for (const story of allStories) {
|
|
23715
|
+
const modelKey = story.modelUsed;
|
|
23716
|
+
const existing = modelStats.get(modelKey) || {
|
|
23717
|
+
attempts: 0,
|
|
23718
|
+
successes: 0,
|
|
23719
|
+
totalCost: 0
|
|
23720
|
+
};
|
|
23721
|
+
modelStats.set(modelKey, {
|
|
23722
|
+
attempts: existing.attempts + story.attempts,
|
|
23723
|
+
successes: existing.successes + (story.success ? 1 : 0),
|
|
23724
|
+
totalCost: existing.totalCost + story.cost
|
|
23725
|
+
});
|
|
23726
|
+
}
|
|
23727
|
+
const modelEfficiency = {};
|
|
23728
|
+
for (const [modelKey, stats] of modelStats) {
|
|
23729
|
+
const passRate = stats.attempts > 0 ? stats.successes / stats.attempts : 0;
|
|
23730
|
+
const avgCost = stats.successes > 0 ? stats.totalCost / stats.successes : 0;
|
|
23731
|
+
modelEfficiency[modelKey] = {
|
|
23732
|
+
attempts: stats.attempts,
|
|
23733
|
+
successes: stats.successes,
|
|
23734
|
+
passRate,
|
|
23735
|
+
avgCost,
|
|
23736
|
+
totalCost: stats.totalCost
|
|
23737
|
+
};
|
|
23738
|
+
}
|
|
23739
|
+
const complexityStats = new Map;
|
|
23740
|
+
for (const story of allStories) {
|
|
23741
|
+
const complexity = story.initialComplexity ?? story.complexity;
|
|
23742
|
+
const existing = complexityStats.get(complexity) || {
|
|
23743
|
+
predicted: 0,
|
|
23744
|
+
tierCounts: new Map,
|
|
23745
|
+
mismatches: 0
|
|
23746
|
+
};
|
|
23747
|
+
existing.predicted += 1;
|
|
23748
|
+
const finalTier = story.finalTier;
|
|
23749
|
+
existing.tierCounts.set(finalTier, (existing.tierCounts.get(finalTier) || 0) + 1);
|
|
23750
|
+
if (story.modelTier !== story.finalTier) {
|
|
23751
|
+
existing.mismatches += 1;
|
|
23781
23752
|
}
|
|
23782
|
-
|
|
23783
|
-
|
|
23753
|
+
complexityStats.set(complexity, existing);
|
|
23754
|
+
}
|
|
23755
|
+
const complexityAccuracy = {};
|
|
23756
|
+
for (const [complexity, stats] of complexityStats) {
|
|
23757
|
+
let maxCount = 0;
|
|
23758
|
+
let mostCommonTier = "unknown";
|
|
23759
|
+
for (const [tier, count] of stats.tierCounts) {
|
|
23760
|
+
if (count > maxCount) {
|
|
23761
|
+
maxCount = count;
|
|
23762
|
+
mostCommonTier = tier;
|
|
23763
|
+
}
|
|
23784
23764
|
}
|
|
23785
|
-
|
|
23765
|
+
const mismatchRate = stats.predicted > 0 ? stats.mismatches / stats.predicted : 0;
|
|
23766
|
+
complexityAccuracy[complexity] = {
|
|
23767
|
+
predicted: stats.predicted,
|
|
23768
|
+
actualTierUsed: mostCommonTier,
|
|
23769
|
+
mismatchRate
|
|
23770
|
+
};
|
|
23786
23771
|
}
|
|
23772
|
+
return {
|
|
23773
|
+
totalRuns,
|
|
23774
|
+
totalCost,
|
|
23775
|
+
totalStories,
|
|
23776
|
+
firstPassRate,
|
|
23777
|
+
escalationRate,
|
|
23778
|
+
avgCostPerStory,
|
|
23779
|
+
avgCostPerFeature,
|
|
23780
|
+
modelEfficiency,
|
|
23781
|
+
complexityAccuracy
|
|
23782
|
+
};
|
|
23787
23783
|
}
|
|
23788
|
-
|
|
23789
|
-
|
|
23790
|
-
|
|
23791
|
-
|
|
23792
|
-
|
|
23793
|
-
|
|
23794
|
-
|
|
23795
|
-
|
|
23796
|
-
|
|
23797
|
-
|
|
23798
|
-
|
|
23799
|
-
|
|
23800
|
-
|
|
23784
|
+
function getLastRun(runs) {
|
|
23785
|
+
if (runs.length === 0) {
|
|
23786
|
+
return null;
|
|
23787
|
+
}
|
|
23788
|
+
return runs[runs.length - 1];
|
|
23789
|
+
}
|
|
23790
|
+
|
|
23791
|
+
// src/metrics/index.ts
|
|
23792
|
+
var init_metrics = __esm(() => {
|
|
23793
|
+
init_tracker();
|
|
23794
|
+
});
|
|
23795
|
+
|
|
23796
|
+
// src/interaction/types.ts
|
|
23797
|
+
var TRIGGER_METADATA;
|
|
23798
|
+
var init_types4 = __esm(() => {
|
|
23799
|
+
TRIGGER_METADATA = {
|
|
23800
|
+
"security-review": {
|
|
23801
|
+
defaultFallback: "abort",
|
|
23802
|
+
safety: "red",
|
|
23803
|
+
defaultSummary: "Security review failed \u2014 abort execution?"
|
|
23804
|
+
},
|
|
23805
|
+
"cost-exceeded": {
|
|
23806
|
+
defaultFallback: "abort",
|
|
23807
|
+
safety: "red",
|
|
23808
|
+
defaultSummary: "Cost limit exceeded ({{cost}} USD) \u2014 abort execution?"
|
|
23809
|
+
},
|
|
23810
|
+
"merge-conflict": {
|
|
23811
|
+
defaultFallback: "abort",
|
|
23812
|
+
safety: "red",
|
|
23813
|
+
defaultSummary: "Merge conflict detected in {{storyId}} \u2014 abort execution?"
|
|
23814
|
+
},
|
|
23815
|
+
"cost-warning": {
|
|
23816
|
+
defaultFallback: "escalate",
|
|
23817
|
+
safety: "yellow",
|
|
23818
|
+
defaultSummary: "Cost warning: {{cost}} USD / {{limit}} USD \u2014 escalate to higher tier?"
|
|
23819
|
+
},
|
|
23820
|
+
"max-retries": {
|
|
23821
|
+
defaultFallback: "skip",
|
|
23822
|
+
safety: "yellow",
|
|
23823
|
+
defaultSummary: "Max retries reached for {{storyId}} \u2014 skip story?"
|
|
23824
|
+
},
|
|
23825
|
+
"pre-merge": {
|
|
23826
|
+
defaultFallback: "escalate",
|
|
23827
|
+
safety: "yellow",
|
|
23828
|
+
defaultSummary: "Pre-merge checkpoint for {{storyId}} \u2014 proceed with merge?"
|
|
23829
|
+
},
|
|
23830
|
+
"human-review": {
|
|
23831
|
+
defaultFallback: "skip",
|
|
23832
|
+
safety: "yellow",
|
|
23833
|
+
defaultSummary: "Human review required for story {{storyId}} \u2014 skip and continue?"
|
|
23834
|
+
},
|
|
23835
|
+
"story-oversized": {
|
|
23836
|
+
defaultFallback: "continue",
|
|
23837
|
+
safety: "yellow",
|
|
23838
|
+
defaultSummary: "Story {{storyId}} is oversized ({{criteriaCount}} acceptance criteria) \u2014 decompose into smaller stories?"
|
|
23839
|
+
},
|
|
23840
|
+
"story-ambiguity": {
|
|
23841
|
+
defaultFallback: "continue",
|
|
23842
|
+
safety: "green",
|
|
23843
|
+
defaultSummary: "Story {{storyId}} requirements unclear \u2014 continue with best effort?"
|
|
23844
|
+
},
|
|
23845
|
+
"review-gate": {
|
|
23846
|
+
defaultFallback: "continue",
|
|
23847
|
+
safety: "green",
|
|
23848
|
+
defaultSummary: "Code review checkpoint for {{storyId}} \u2014 proceed?"
|
|
23849
|
+
}
|
|
23801
23850
|
};
|
|
23802
23851
|
});
|
|
23803
23852
|
|
|
23853
|
+
// src/interaction/state.ts
|
|
23854
|
+
import * as path3 from "path";
|
|
23855
|
+
async function loadPendingInteraction(requestId, featureDir) {
|
|
23856
|
+
const interactionsDir = path3.join(featureDir, "interactions");
|
|
23857
|
+
const filename = `${requestId}.json`;
|
|
23858
|
+
const filePath = path3.join(interactionsDir, filename);
|
|
23859
|
+
try {
|
|
23860
|
+
const file2 = Bun.file(filePath);
|
|
23861
|
+
const exists = await file2.exists();
|
|
23862
|
+
if (!exists) {
|
|
23863
|
+
return null;
|
|
23864
|
+
}
|
|
23865
|
+
const json2 = await file2.text();
|
|
23866
|
+
const request = JSON.parse(json2);
|
|
23867
|
+
return request;
|
|
23868
|
+
} catch {
|
|
23869
|
+
return null;
|
|
23870
|
+
}
|
|
23871
|
+
}
|
|
23872
|
+
async function listPendingInteractions(featureDir) {
|
|
23873
|
+
const interactionsDir = path3.join(featureDir, "interactions");
|
|
23874
|
+
try {
|
|
23875
|
+
const dir = Bun.file(interactionsDir);
|
|
23876
|
+
const exists = await dir.exists();
|
|
23877
|
+
if (!exists) {
|
|
23878
|
+
return [];
|
|
23879
|
+
}
|
|
23880
|
+
const proc = Bun.spawn(["ls", interactionsDir], {
|
|
23881
|
+
stdout: "pipe",
|
|
23882
|
+
stderr: "pipe"
|
|
23883
|
+
});
|
|
23884
|
+
const output = await new Response(proc.stdout).text();
|
|
23885
|
+
await proc.exited;
|
|
23886
|
+
const files = output.split(`
|
|
23887
|
+
`).filter((f) => f.endsWith(".json") && f !== ".gitkeep").map((f) => f.replace(".json", ""));
|
|
23888
|
+
return files;
|
|
23889
|
+
} catch {
|
|
23890
|
+
return [];
|
|
23891
|
+
}
|
|
23892
|
+
}
|
|
23893
|
+
var init_state = () => {};
|
|
23894
|
+
|
|
23804
23895
|
// src/interaction/triggers.ts
|
|
23805
23896
|
function isTriggerEnabled(trigger, config2) {
|
|
23806
23897
|
const triggerConfig = config2.interaction?.triggers?.[trigger];
|
|
@@ -23924,60 +24015,6 @@ var init_triggers = __esm(() => {
|
|
|
23924
24015
|
init_types4();
|
|
23925
24016
|
});
|
|
23926
24017
|
|
|
23927
|
-
// src/interaction/init.ts
|
|
23928
|
-
function createInteractionPlugin(pluginName) {
|
|
23929
|
-
switch (pluginName) {
|
|
23930
|
-
case "cli":
|
|
23931
|
-
return new CLIInteractionPlugin;
|
|
23932
|
-
case "telegram":
|
|
23933
|
-
return new TelegramInteractionPlugin;
|
|
23934
|
-
case "webhook":
|
|
23935
|
-
return new WebhookInteractionPlugin;
|
|
23936
|
-
case "auto":
|
|
23937
|
-
return new AutoInteractionPlugin;
|
|
23938
|
-
default:
|
|
23939
|
-
throw new Error(`Unknown interaction plugin: ${pluginName}`);
|
|
23940
|
-
}
|
|
23941
|
-
}
|
|
23942
|
-
async function initInteractionChain(config2, headless) {
|
|
23943
|
-
const logger = getSafeLogger();
|
|
23944
|
-
if (!config2.interaction) {
|
|
23945
|
-
logger?.debug("interaction", "No interaction config - skipping interaction system");
|
|
23946
|
-
return null;
|
|
23947
|
-
}
|
|
23948
|
-
const pluginName = config2.interaction.plugin;
|
|
23949
|
-
if (headless && pluginName === "cli") {
|
|
23950
|
-
logger?.debug("interaction", "Headless mode with CLI plugin - skipping interaction system (stdin unavailable)");
|
|
23951
|
-
return null;
|
|
23952
|
-
}
|
|
23953
|
-
const chain = new InteractionChain({
|
|
23954
|
-
defaultTimeout: config2.interaction.defaults.timeout,
|
|
23955
|
-
defaultFallback: config2.interaction.defaults.fallback
|
|
23956
|
-
});
|
|
23957
|
-
try {
|
|
23958
|
-
const plugin = createInteractionPlugin(pluginName);
|
|
23959
|
-
chain.register(plugin, 100);
|
|
23960
|
-
const pluginConfig = config2.interaction.config ?? {};
|
|
23961
|
-
await chain.init({ [pluginName]: pluginConfig });
|
|
23962
|
-
logger?.info("interaction", `Initialized ${pluginName} interaction plugin`, {
|
|
23963
|
-
timeout: config2.interaction.defaults.timeout,
|
|
23964
|
-
fallback: config2.interaction.defaults.fallback
|
|
23965
|
-
});
|
|
23966
|
-
return chain;
|
|
23967
|
-
} catch (err) {
|
|
23968
|
-
const error48 = err instanceof Error ? err.message : String(err);
|
|
23969
|
-
logger?.error("interaction", `Failed to initialize interaction plugin: ${error48}`);
|
|
23970
|
-
throw err;
|
|
23971
|
-
}
|
|
23972
|
-
}
|
|
23973
|
-
var init_init = __esm(() => {
|
|
23974
|
-
init_logger2();
|
|
23975
|
-
init_auto();
|
|
23976
|
-
init_cli();
|
|
23977
|
-
init_telegram();
|
|
23978
|
-
init_webhook();
|
|
23979
|
-
});
|
|
23980
|
-
|
|
23981
24018
|
// src/interaction/index.ts
|
|
23982
24019
|
var init_interaction = __esm(() => {
|
|
23983
24020
|
init_types4();
|
|
@@ -23988,6 +24025,7 @@ var init_interaction = __esm(() => {
|
|
|
23988
24025
|
init_auto();
|
|
23989
24026
|
init_triggers();
|
|
23990
24027
|
init_init();
|
|
24028
|
+
init_bridge_builder();
|
|
23991
24029
|
});
|
|
23992
24030
|
|
|
23993
24031
|
// src/pipeline/runner.ts
|
|
@@ -24446,10 +24484,11 @@ ${stderr}` };
|
|
|
24446
24484
|
if (workdirGroups.size === 0) {
|
|
24447
24485
|
workdirGroups.set("", { stories: [], criteria: [] });
|
|
24448
24486
|
}
|
|
24487
|
+
const featureName = ctx.prd.feature;
|
|
24449
24488
|
const testPaths = [];
|
|
24450
24489
|
for (const [workdir] of workdirGroups) {
|
|
24451
24490
|
const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
|
|
24452
|
-
const testPath = path5.join(packageDir, acceptanceTestFilename(language));
|
|
24491
|
+
const testPath = path5.join(packageDir, ".nax", "features", featureName, acceptanceTestFilename(language));
|
|
24453
24492
|
testPaths.push({ testPath, packageDir });
|
|
24454
24493
|
}
|
|
24455
24494
|
let totalCriteria = 0;
|
|
@@ -27429,6 +27468,9 @@ function shouldRetryRectification(state, config2) {
|
|
|
27429
27468
|
if (state.attempt >= config2.maxRetries) {
|
|
27430
27469
|
return false;
|
|
27431
27470
|
}
|
|
27471
|
+
if (state.lastExitCode !== undefined && state.lastExitCode !== 0 && state.currentFailures === 0) {
|
|
27472
|
+
return true;
|
|
27473
|
+
}
|
|
27432
27474
|
if (state.currentFailures === 0) {
|
|
27433
27475
|
return false;
|
|
27434
27476
|
}
|
|
@@ -28397,7 +28439,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
28397
28439
|
}
|
|
28398
28440
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
28399
28441
|
}
|
|
28400
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName) {
|
|
28442
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge) {
|
|
28401
28443
|
const startTime = Date.now();
|
|
28402
28444
|
let prompt;
|
|
28403
28445
|
if (_sessionRunnerDeps.buildPrompt) {
|
|
@@ -28431,7 +28473,8 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
28431
28473
|
featureName,
|
|
28432
28474
|
storyId: story.id,
|
|
28433
28475
|
sessionRole: role,
|
|
28434
|
-
keepSessionOpen
|
|
28476
|
+
keepSessionOpen,
|
|
28477
|
+
interactionBridge
|
|
28435
28478
|
});
|
|
28436
28479
|
if (!result.success && result.pid) {
|
|
28437
28480
|
await _sessionRunnerDeps.cleanupProcessTree(result.pid);
|
|
@@ -28796,7 +28839,8 @@ async function runThreeSessionTdd(options) {
|
|
|
28796
28839
|
constitution,
|
|
28797
28840
|
dryRun = false,
|
|
28798
28841
|
lite = false,
|
|
28799
|
-
_recursionDepth = 0
|
|
28842
|
+
_recursionDepth = 0,
|
|
28843
|
+
interactionChain
|
|
28800
28844
|
} = options;
|
|
28801
28845
|
const logger = getLogger();
|
|
28802
28846
|
const MAX_RECURSION_DEPTH = 2;
|
|
@@ -28855,7 +28899,7 @@ async function runThreeSessionTdd(options) {
|
|
|
28855
28899
|
let session1;
|
|
28856
28900
|
if (!isRetry) {
|
|
28857
28901
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
28858
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
28902
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
|
|
28859
28903
|
sessions.push(session1);
|
|
28860
28904
|
}
|
|
28861
28905
|
if (session1 && !session1.success) {
|
|
@@ -28917,7 +28961,7 @@ async function runThreeSessionTdd(options) {
|
|
|
28917
28961
|
});
|
|
28918
28962
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
28919
28963
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
28920
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
28964
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
|
|
28921
28965
|
sessions.push(session2);
|
|
28922
28966
|
if (!session2.success) {
|
|
28923
28967
|
needsHumanReview = true;
|
|
@@ -29035,6 +29079,7 @@ async function runThreeSessionTdd(options) {
|
|
|
29035
29079
|
var init_orchestrator2 = __esm(() => {
|
|
29036
29080
|
init_config();
|
|
29037
29081
|
init_greenfield();
|
|
29082
|
+
init_bridge_builder();
|
|
29038
29083
|
init_logger2();
|
|
29039
29084
|
init_git();
|
|
29040
29085
|
init_verification();
|
|
@@ -29100,6 +29145,7 @@ var executionStage, _executionDeps;
|
|
|
29100
29145
|
var init_execution2 = __esm(() => {
|
|
29101
29146
|
init_agents();
|
|
29102
29147
|
init_config();
|
|
29148
|
+
init_bridge_builder();
|
|
29103
29149
|
init_triggers();
|
|
29104
29150
|
init_logger2();
|
|
29105
29151
|
init_tdd();
|
|
@@ -29134,7 +29180,8 @@ var init_execution2 = __esm(() => {
|
|
|
29134
29180
|
contextMarkdown: ctx.contextMarkdown,
|
|
29135
29181
|
constitution: ctx.constitution?.content,
|
|
29136
29182
|
dryRun: false,
|
|
29137
|
-
lite: isLiteMode
|
|
29183
|
+
lite: isLiteMode,
|
|
29184
|
+
interactionChain: ctx.interaction
|
|
29138
29185
|
});
|
|
29139
29186
|
ctx.agentResult = {
|
|
29140
29187
|
success: tddResult.success,
|
|
@@ -29208,34 +29255,11 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
29208
29255
|
pidRegistry: ctx.pidRegistry,
|
|
29209
29256
|
featureName: ctx.prd.feature,
|
|
29210
29257
|
storyId: ctx.story.id,
|
|
29211
|
-
interactionBridge: (
|
|
29212
|
-
|
|
29213
|
-
|
|
29214
|
-
|
|
29215
|
-
|
|
29216
|
-
return {
|
|
29217
|
-
detectQuestion: async (text) => QUESTION_PATTERNS.some((p) => p.test(text)),
|
|
29218
|
-
onQuestionDetected: async (text) => {
|
|
29219
|
-
const requestId = `ix-acp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29220
|
-
await plugin.send({
|
|
29221
|
-
id: requestId,
|
|
29222
|
-
type: "input",
|
|
29223
|
-
featureName: ctx.prd.feature,
|
|
29224
|
-
storyId: ctx.story.id,
|
|
29225
|
-
stage: "execution",
|
|
29226
|
-
summary: text,
|
|
29227
|
-
fallback: "continue",
|
|
29228
|
-
createdAt: Date.now()
|
|
29229
|
-
});
|
|
29230
|
-
try {
|
|
29231
|
-
const response = await plugin.receive(requestId, 120000);
|
|
29232
|
-
return response.value ?? "continue";
|
|
29233
|
-
} catch {
|
|
29234
|
-
return "continue";
|
|
29235
|
-
}
|
|
29236
|
-
}
|
|
29237
|
-
};
|
|
29238
|
-
})()
|
|
29258
|
+
interactionBridge: buildInteractionBridge(ctx.interaction, {
|
|
29259
|
+
featureName: ctx.prd.feature,
|
|
29260
|
+
storyId: ctx.story.id,
|
|
29261
|
+
stage: "execution"
|
|
29262
|
+
})
|
|
29239
29263
|
});
|
|
29240
29264
|
ctx.agentResult = result;
|
|
29241
29265
|
await autoCommitIfDirty(storyWorkdir, "execution", "single-session", ctx.story.id);
|
|
@@ -29737,7 +29761,8 @@ async function runRectificationLoop2(opts) {
|
|
|
29737
29761
|
const rectificationState = {
|
|
29738
29762
|
attempt: 0,
|
|
29739
29763
|
initialFailures: testSummary.failed,
|
|
29740
|
-
currentFailures: testSummary.failed
|
|
29764
|
+
currentFailures: testSummary.failed,
|
|
29765
|
+
lastExitCode: 1
|
|
29741
29766
|
};
|
|
29742
29767
|
logger?.info("rectification", `Starting ${label} loop`, {
|
|
29743
29768
|
storyId: story.id,
|
|
@@ -29815,6 +29840,7 @@ ${rectificationPrompt}`;
|
|
|
29815
29840
|
if (retryVerification.output) {
|
|
29816
29841
|
const newTestSummary = parseBunTestOutput(retryVerification.output);
|
|
29817
29842
|
rectificationState.currentFailures = newTestSummary.failed;
|
|
29843
|
+
rectificationState.lastExitCode = retryVerification.status === "SUCCESS" ? 0 : 1;
|
|
29818
29844
|
testSummary.failures = newTestSummary.failures;
|
|
29819
29845
|
testSummary.failed = newTestSummary.failed;
|
|
29820
29846
|
testSummary.passed = newTestSummary.passed;
|
|
@@ -30023,7 +30049,8 @@ function makeFailResult(storyId, strategy, status, opts = {}) {
|
|
|
30023
30049
|
failures: opts.failures ?? [],
|
|
30024
30050
|
rawOutput: opts.rawOutput,
|
|
30025
30051
|
durationMs: opts.durationMs ?? 0,
|
|
30026
|
-
countsTowardEscalation: opts.countsTowardEscalation ?? true
|
|
30052
|
+
countsTowardEscalation: opts.countsTowardEscalation ?? true,
|
|
30053
|
+
exitCode: opts.exitCode
|
|
30027
30054
|
};
|
|
30028
30055
|
}
|
|
30029
30056
|
function makePassResult(storyId, strategy, opts = {}) {
|
|
@@ -30432,7 +30459,8 @@ class ScopedStrategy {
|
|
|
30432
30459
|
passCount: parsed.passed,
|
|
30433
30460
|
failCount: parsed.failed,
|
|
30434
30461
|
failures: parsed.failures,
|
|
30435
|
-
durationMs
|
|
30462
|
+
durationMs,
|
|
30463
|
+
exitCode: result.status === "TEST_FAILURE" ? 1 : undefined
|
|
30436
30464
|
});
|
|
30437
30465
|
}
|
|
30438
30466
|
}
|
|
@@ -32238,7 +32266,8 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
32238
32266
|
".nax/features/*/status.json",
|
|
32239
32267
|
".nax-pids",
|
|
32240
32268
|
".nax-wt/",
|
|
32241
|
-
"**/.nax-acceptance*"
|
|
32269
|
+
"**/.nax-acceptance*",
|
|
32270
|
+
"**/.nax/features/*/"
|
|
32242
32271
|
];
|
|
32243
32272
|
const missing = patterns.filter((pattern) => !content.includes(pattern));
|
|
32244
32273
|
const passed = missing.length === 0;
|
|
@@ -34280,7 +34309,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
34280
34309
|
};
|
|
34281
34310
|
}
|
|
34282
34311
|
}
|
|
34283
|
-
async function executeParallelBatch(stories, projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter) {
|
|
34312
|
+
async function executeParallelBatch(stories, projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs) {
|
|
34284
34313
|
const logger = getSafeLogger();
|
|
34285
34314
|
const results = {
|
|
34286
34315
|
pipelinePassed: [],
|
|
@@ -34301,7 +34330,9 @@ async function executeParallelBatch(stories, projectRoot, config2, context, work
|
|
|
34301
34330
|
continue;
|
|
34302
34331
|
}
|
|
34303
34332
|
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
34304
|
-
const
|
|
34333
|
+
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
34334
|
+
const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
|
|
34335
|
+
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
34305
34336
|
results.totalCost += result.cost;
|
|
34306
34337
|
results.storyCosts.set(story.id, result.cost);
|
|
34307
34338
|
if (result.success) {
|
|
@@ -34336,6 +34367,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
34336
34367
|
});
|
|
34337
34368
|
|
|
34338
34369
|
// src/execution/parallel-coordinator.ts
|
|
34370
|
+
import { existsSync as existsSync33, symlinkSync as symlinkSync2 } from "fs";
|
|
34339
34371
|
import os3 from "os";
|
|
34340
34372
|
import { join as join48 } from "path";
|
|
34341
34373
|
function groupStoriesByDependencies(stories) {
|
|
@@ -34380,7 +34412,7 @@ function resolveMaxConcurrency(parallel) {
|
|
|
34380
34412
|
}
|
|
34381
34413
|
return Math.max(1, parallel);
|
|
34382
34414
|
}
|
|
34383
|
-
async function executeParallel(stories, prdPath, projectRoot, config2, hooks, plugins, prd, featureDir, parallel, eventEmitter, agentGetFn) {
|
|
34415
|
+
async function executeParallel(stories, prdPath, projectRoot, config2, hooks, plugins, prd, featureDir, parallel, eventEmitter, agentGetFn, pidRegistry, interactionChain) {
|
|
34384
34416
|
const logger = getSafeLogger();
|
|
34385
34417
|
const maxConcurrency = resolveMaxConcurrency(parallel);
|
|
34386
34418
|
const worktreeManager = new WorktreeManager;
|
|
@@ -34412,9 +34444,12 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34412
34444
|
hooks,
|
|
34413
34445
|
plugins,
|
|
34414
34446
|
storyStartTime: new Date().toISOString(),
|
|
34415
|
-
agentGetFn
|
|
34447
|
+
agentGetFn,
|
|
34448
|
+
pidRegistry,
|
|
34449
|
+
interaction: interactionChain ?? undefined
|
|
34416
34450
|
};
|
|
34417
34451
|
const worktreePaths = new Map;
|
|
34452
|
+
const storyEffectiveConfigs = new Map;
|
|
34418
34453
|
for (const story of batch) {
|
|
34419
34454
|
const worktreePath = join48(projectRoot, ".nax-wt", story.id);
|
|
34420
34455
|
try {
|
|
@@ -34424,6 +34459,27 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34424
34459
|
storyId: story.id,
|
|
34425
34460
|
worktreePath
|
|
34426
34461
|
});
|
|
34462
|
+
if (story.workdir) {
|
|
34463
|
+
const pkgNodeModulesSrc = join48(projectRoot, story.workdir, "node_modules");
|
|
34464
|
+
const pkgNodeModulesDst = join48(worktreePath, story.workdir, "node_modules");
|
|
34465
|
+
if (existsSync33(pkgNodeModulesSrc) && !existsSync33(pkgNodeModulesDst)) {
|
|
34466
|
+
try {
|
|
34467
|
+
symlinkSync2(pkgNodeModulesSrc, pkgNodeModulesDst, "dir");
|
|
34468
|
+
logger?.debug("parallel", "Symlinked package node_modules", {
|
|
34469
|
+
storyId: story.id,
|
|
34470
|
+
src: pkgNodeModulesSrc
|
|
34471
|
+
});
|
|
34472
|
+
} catch (symlinkError) {
|
|
34473
|
+
logger?.warn("parallel", "Failed to symlink package node_modules \u2014 test runner may not find deps", {
|
|
34474
|
+
storyId: story.id,
|
|
34475
|
+
error: errorMessage(symlinkError)
|
|
34476
|
+
});
|
|
34477
|
+
}
|
|
34478
|
+
}
|
|
34479
|
+
}
|
|
34480
|
+
const rootConfigPath = join48(projectRoot, ".nax", "config.json");
|
|
34481
|
+
const effectiveConfig = story.workdir ? await loadConfigForWorkdir(rootConfigPath, story.workdir) : config2;
|
|
34482
|
+
storyEffectiveConfigs.set(story.id, effectiveConfig);
|
|
34427
34483
|
} catch (error48) {
|
|
34428
34484
|
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
34429
34485
|
logger?.error("parallel", "Failed to create worktree", {
|
|
@@ -34432,7 +34488,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34432
34488
|
});
|
|
34433
34489
|
}
|
|
34434
34490
|
}
|
|
34435
|
-
const batchResult = await executeParallelBatch(batch, projectRoot, config2, baseContext, worktreePaths, maxConcurrency, eventEmitter);
|
|
34491
|
+
const batchResult = await executeParallelBatch(batch, projectRoot, config2, baseContext, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs);
|
|
34436
34492
|
totalCost += batchResult.totalCost;
|
|
34437
34493
|
if (batchResult.pipelinePassed.length > 0) {
|
|
34438
34494
|
const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
|
|
@@ -34502,6 +34558,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34502
34558
|
return { storiesCompleted, totalCost, updatedPrd: currentPrd, mergeConflicts: allMergeConflicts };
|
|
34503
34559
|
}
|
|
34504
34560
|
var init_parallel_coordinator = __esm(() => {
|
|
34561
|
+
init_loader();
|
|
34505
34562
|
init_logger2();
|
|
34506
34563
|
init_prd();
|
|
34507
34564
|
init_manager();
|
|
@@ -34789,7 +34846,7 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
34789
34846
|
const batchStoryMetrics = [];
|
|
34790
34847
|
let conflictedStories = [];
|
|
34791
34848
|
try {
|
|
34792
|
-
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter, options.agentGetFn);
|
|
34849
|
+
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter, options.agentGetFn, options.pidRegistry, options.interactionChain);
|
|
34793
34850
|
const batchDurationMs = Date.now() - batchStartMs;
|
|
34794
34851
|
const batchCompletedAt = new Date().toISOString();
|
|
34795
34852
|
prd = parallelResult.updatedPrd;
|
|
@@ -67794,7 +67851,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
67794
67851
|
|
|
67795
67852
|
// bin/nax.ts
|
|
67796
67853
|
init_source();
|
|
67797
|
-
import { existsSync as
|
|
67854
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync6 } from "fs";
|
|
67798
67855
|
import { homedir as homedir8 } from "os";
|
|
67799
67856
|
import { join as join56 } from "path";
|
|
67800
67857
|
|
|
@@ -68283,7 +68340,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
68283
68340
|
init_registry();
|
|
68284
68341
|
import { existsSync as existsSync11 } from "fs";
|
|
68285
68342
|
import { join as join11 } from "path";
|
|
68286
|
-
import { createInterface } from "readline";
|
|
68343
|
+
import { createInterface as createInterface2 } from "readline";
|
|
68287
68344
|
init_test_strategy();
|
|
68288
68345
|
|
|
68289
68346
|
// src/context/generator.ts
|
|
@@ -68821,6 +68878,8 @@ async function generateForPackage(packageDir, config2, dryRun = false, repoRoot)
|
|
|
68821
68878
|
|
|
68822
68879
|
// src/cli/plan.ts
|
|
68823
68880
|
init_pid_registry();
|
|
68881
|
+
init_bridge_builder();
|
|
68882
|
+
init_init();
|
|
68824
68883
|
init_logger2();
|
|
68825
68884
|
|
|
68826
68885
|
// src/prd/schema.ts
|
|
@@ -69015,7 +69074,8 @@ var _planDeps = {
|
|
|
69015
69074
|
existsSync: (path) => existsSync11(path),
|
|
69016
69075
|
discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages(repoRoot),
|
|
69017
69076
|
readPackageJsonAt: (path) => Bun.file(path).json().catch(() => null),
|
|
69018
|
-
createInteractionBridge: () => createCliInteractionBridge()
|
|
69077
|
+
createInteractionBridge: () => createCliInteractionBridge(),
|
|
69078
|
+
initInteractionChain: (cfg, headless) => initInteractionChain(cfg, headless)
|
|
69019
69079
|
};
|
|
69020
69080
|
async function planCommand(workdir, config2, options) {
|
|
69021
69081
|
const naxDir = join11(workdir, ".nax");
|
|
@@ -69046,9 +69106,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
69046
69106
|
const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
|
|
69047
69107
|
let rawResponse;
|
|
69048
69108
|
if (options.auto) {
|
|
69049
|
-
const
|
|
69050
|
-
const
|
|
69051
|
-
|
|
69109
|
+
const isAcp = config2?.agent?.protocol === "acp";
|
|
69110
|
+
const prompt = buildPlanningPrompt(specContent, codebaseContext, isAcp ? outputPath : undefined, relativePackages, packageDetails, config2?.project);
|
|
69111
|
+
const adapter = _planDeps.getAgent(agentName, config2);
|
|
69112
|
+
if (!adapter)
|
|
69052
69113
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
69053
69114
|
let autoModel;
|
|
69054
69115
|
try {
|
|
@@ -69059,26 +69120,64 @@ async function planCommand(workdir, config2, options) {
|
|
|
69059
69120
|
if (entry)
|
|
69060
69121
|
autoModel = resolveModel2(entry).model;
|
|
69061
69122
|
} catch {}
|
|
69062
|
-
|
|
69063
|
-
|
|
69064
|
-
|
|
69065
|
-
|
|
69066
|
-
|
|
69067
|
-
|
|
69068
|
-
|
|
69069
|
-
|
|
69070
|
-
|
|
69071
|
-
|
|
69072
|
-
|
|
69073
|
-
|
|
69123
|
+
if (isAcp) {
|
|
69124
|
+
logger?.info("plan", "Starting ACP auto planning session", {
|
|
69125
|
+
agent: agentName,
|
|
69126
|
+
model: autoModel ?? config2?.plan?.model ?? "balanced",
|
|
69127
|
+
workdir,
|
|
69128
|
+
feature: options.feature,
|
|
69129
|
+
timeoutSeconds
|
|
69130
|
+
});
|
|
69131
|
+
const pidRegistry = new PidRegistry(workdir);
|
|
69132
|
+
try {
|
|
69133
|
+
await adapter.plan({
|
|
69134
|
+
prompt,
|
|
69135
|
+
workdir,
|
|
69136
|
+
interactive: false,
|
|
69137
|
+
timeoutSeconds,
|
|
69138
|
+
config: config2,
|
|
69139
|
+
modelTier: config2?.plan?.model ?? "balanced",
|
|
69140
|
+
dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
|
|
69141
|
+
maxInteractionTurns: config2?.agent?.maxInteractionTurns,
|
|
69142
|
+
featureName: options.feature,
|
|
69143
|
+
pidRegistry,
|
|
69144
|
+
sessionRole: "plan"
|
|
69145
|
+
});
|
|
69146
|
+
} finally {
|
|
69147
|
+
await pidRegistry.killAll().catch(() => {});
|
|
69074
69148
|
}
|
|
69075
|
-
|
|
69149
|
+
if (!_planDeps.existsSync(outputPath)) {
|
|
69150
|
+
throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
|
|
69151
|
+
}
|
|
69152
|
+
rawResponse = await _planDeps.readFile(outputPath);
|
|
69153
|
+
} else {
|
|
69154
|
+
rawResponse = await adapter.complete(prompt, {
|
|
69155
|
+
model: autoModel,
|
|
69156
|
+
jsonMode: true,
|
|
69157
|
+
workdir,
|
|
69158
|
+
config: config2,
|
|
69159
|
+
featureName: options.feature,
|
|
69160
|
+
sessionRole: "plan"
|
|
69161
|
+
});
|
|
69162
|
+
try {
|
|
69163
|
+
const envelope = JSON.parse(rawResponse);
|
|
69164
|
+
if (envelope?.type === "result" && typeof envelope?.result === "string") {
|
|
69165
|
+
rawResponse = envelope.result;
|
|
69166
|
+
}
|
|
69167
|
+
} catch {}
|
|
69168
|
+
}
|
|
69076
69169
|
} else {
|
|
69077
69170
|
const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
|
|
69078
69171
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
69079
69172
|
if (!adapter)
|
|
69080
69173
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
69081
|
-
const
|
|
69174
|
+
const headless = !process.stdin.isTTY;
|
|
69175
|
+
const interactionChain = config2 ? await _planDeps.initInteractionChain(config2, headless) : null;
|
|
69176
|
+
const configuredBridge = interactionChain ? buildInteractionBridge(interactionChain, {
|
|
69177
|
+
featureName: options.feature,
|
|
69178
|
+
stage: "pre-flight"
|
|
69179
|
+
}) : undefined;
|
|
69180
|
+
const interactionBridge = configuredBridge ?? _planDeps.createInteractionBridge();
|
|
69082
69181
|
const pidRegistry = new PidRegistry(workdir);
|
|
69083
69182
|
const resolvedPerm = resolvePermissions(config2, "plan");
|
|
69084
69183
|
const resolvedModel = config2?.plan?.model ?? "balanced";
|
|
@@ -69107,6 +69206,8 @@ async function planCommand(workdir, config2, options) {
|
|
|
69107
69206
|
});
|
|
69108
69207
|
} finally {
|
|
69109
69208
|
await pidRegistry.killAll().catch(() => {});
|
|
69209
|
+
if (interactionChain)
|
|
69210
|
+
await interactionChain.destroy().catch(() => {});
|
|
69110
69211
|
logger?.info("plan", "Interactive session ended", { durationMs: Date.now() - planStartTime });
|
|
69111
69212
|
}
|
|
69112
69213
|
if (!_planDeps.existsSync(outputPath)) {
|
|
@@ -69133,7 +69234,7 @@ function createCliInteractionBridge() {
|
|
|
69133
69234
|
\uD83E\uDD16 Agent: ${text}
|
|
69134
69235
|
You: `);
|
|
69135
69236
|
return new Promise((resolve4) => {
|
|
69136
|
-
const rl =
|
|
69237
|
+
const rl = createInterface2({ input: process.stdin, terminal: false });
|
|
69137
69238
|
rl.once("line", (line) => {
|
|
69138
69239
|
rl.close();
|
|
69139
69240
|
resolve4(line.trim());
|
|
@@ -72087,7 +72188,9 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
72087
72188
|
pluginRegistry,
|
|
72088
72189
|
formatterMode: options.formatterMode,
|
|
72089
72190
|
headless: options.headless,
|
|
72090
|
-
agentGetFn: options.agentGetFn
|
|
72191
|
+
agentGetFn: options.agentGetFn,
|
|
72192
|
+
pidRegistry: options.pidRegistry,
|
|
72193
|
+
interactionChain: options.interactionChain
|
|
72091
72194
|
}, prd);
|
|
72092
72195
|
prd = parallelResult.prd;
|
|
72093
72196
|
totalCost = parallelResult.totalCost;
|
|
@@ -79683,7 +79786,7 @@ Next: nax generate --package ${options.package}`));
|
|
|
79683
79786
|
return;
|
|
79684
79787
|
}
|
|
79685
79788
|
const naxDir = join56(workdir, ".nax");
|
|
79686
|
-
if (
|
|
79789
|
+
if (existsSync35(naxDir) && !options.force) {
|
|
79687
79790
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
79688
79791
|
return;
|
|
79689
79792
|
}
|
|
@@ -79800,7 +79903,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79800
79903
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
79801
79904
|
process.exit(1);
|
|
79802
79905
|
}
|
|
79803
|
-
if (options.from && !
|
|
79906
|
+
if (options.from && !existsSync35(options.from)) {
|
|
79804
79907
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
79805
79908
|
process.exit(1);
|
|
79806
79909
|
}
|
|
@@ -79832,7 +79935,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79832
79935
|
const featureDir = join56(naxDir, "features", options.feature);
|
|
79833
79936
|
const prdPath = join56(featureDir, "prd.json");
|
|
79834
79937
|
if (options.plan && options.from) {
|
|
79835
|
-
if (
|
|
79938
|
+
if (existsSync35(prdPath) && !options.force) {
|
|
79836
79939
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
79837
79940
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
79838
79941
|
process.exit(1);
|
|
@@ -79888,7 +79991,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79888
79991
|
process.exit(1);
|
|
79889
79992
|
}
|
|
79890
79993
|
}
|
|
79891
|
-
if (!
|
|
79994
|
+
if (!existsSync35(prdPath)) {
|
|
79892
79995
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
79893
79996
|
process.exit(1);
|
|
79894
79997
|
}
|
|
@@ -79963,7 +80066,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79963
80066
|
});
|
|
79964
80067
|
const latestSymlink = join56(runsDir, "latest.jsonl");
|
|
79965
80068
|
try {
|
|
79966
|
-
if (
|
|
80069
|
+
if (existsSync35(latestSymlink)) {
|
|
79967
80070
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
79968
80071
|
}
|
|
79969
80072
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -80061,7 +80164,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
80061
80164
|
process.exit(1);
|
|
80062
80165
|
}
|
|
80063
80166
|
const featuresDir = join56(naxDir, "features");
|
|
80064
|
-
if (!
|
|
80167
|
+
if (!existsSync35(featuresDir)) {
|
|
80065
80168
|
console.log(source_default.dim("No features yet."));
|
|
80066
80169
|
return;
|
|
80067
80170
|
}
|
|
@@ -80076,7 +80179,7 @@ Features:
|
|
|
80076
80179
|
`));
|
|
80077
80180
|
for (const name of entries) {
|
|
80078
80181
|
const prdPath = join56(featuresDir, name, "prd.json");
|
|
80079
|
-
if (
|
|
80182
|
+
if (existsSync35(prdPath)) {
|
|
80080
80183
|
const prd = await loadPRD(prdPath);
|
|
80081
80184
|
const c = countStories(prd);
|
|
80082
80185
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -80147,7 +80250,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
80147
80250
|
process.exit(1);
|
|
80148
80251
|
}
|
|
80149
80252
|
const featureDir = join56(naxDir, "features", options.feature);
|
|
80150
|
-
if (!
|
|
80253
|
+
if (!existsSync35(featureDir)) {
|
|
80151
80254
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
80152
80255
|
process.exit(1);
|
|
80153
80256
|
}
|