@nathapp/nax 0.54.3 → 0.54.5
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 +795 -715
- package/package.json +1 -1
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 {
|
|
@@ -22348,7 +22348,7 @@ var package_default;
|
|
|
22348
22348
|
var init_package = __esm(() => {
|
|
22349
22349
|
package_default = {
|
|
22350
22350
|
name: "@nathapp/nax",
|
|
22351
|
-
version: "0.54.
|
|
22351
|
+
version: "0.54.5",
|
|
22352
22352
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22353
22353
|
type: "module",
|
|
22354
22354
|
bin: {
|
|
@@ -22425,8 +22425,8 @@ var init_version = __esm(() => {
|
|
|
22425
22425
|
NAX_VERSION = package_default.version;
|
|
22426
22426
|
NAX_COMMIT = (() => {
|
|
22427
22427
|
try {
|
|
22428
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22429
|
-
return "
|
|
22428
|
+
if (/^[0-9a-f]{6,10}$/.test("e98d5b1"))
|
|
22429
|
+
return "e98d5b1";
|
|
22430
22430
|
} catch {}
|
|
22431
22431
|
try {
|
|
22432
22432
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -22444,316 +22444,37 @@ var init_version = __esm(() => {
|
|
|
22444
22444
|
NAX_BUILD_INFO = NAX_COMMIT === "dev" ? `v${NAX_VERSION}` : `v${NAX_VERSION} (${NAX_COMMIT})`;
|
|
22445
22445
|
});
|
|
22446
22446
|
|
|
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;
|
|
22447
|
+
// src/interaction/bridge-builder.ts
|
|
22448
|
+
function buildInteractionBridge(chain, context, timeoutMs = DEFAULT_INTERACTION_TIMEOUT_MS) {
|
|
22449
|
+
const plugin = chain?.getPrimary();
|
|
22450
|
+
if (!plugin)
|
|
22451
|
+
return;
|
|
22527
22452
|
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;
|
|
22453
|
+
detectQuestion: async (text) => QUESTION_PATTERNS.some((p) => p.test(text)),
|
|
22454
|
+
onQuestionDetected: async (text) => {
|
|
22455
|
+
const requestId = `ix-${context.stage}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
22456
|
+
await plugin.send({
|
|
22457
|
+
id: requestId,
|
|
22458
|
+
type: "input",
|
|
22459
|
+
featureName: context.featureName ?? "unknown",
|
|
22460
|
+
storyId: context.storyId,
|
|
22461
|
+
stage: context.stage,
|
|
22462
|
+
summary: text,
|
|
22463
|
+
fallback: "continue",
|
|
22464
|
+
createdAt: Date.now()
|
|
22465
|
+
});
|
|
22466
|
+
try {
|
|
22467
|
+
const response = await plugin.receive(requestId, timeoutMs);
|
|
22468
|
+
return response.value ?? "continue";
|
|
22469
|
+
} catch {
|
|
22470
|
+
return "continue";
|
|
22669
22471
|
}
|
|
22670
22472
|
}
|
|
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
22473
|
};
|
|
22689
22474
|
}
|
|
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
|
-
};
|
|
22475
|
+
var QUESTION_PATTERNS, DEFAULT_INTERACTION_TIMEOUT_MS = 120000;
|
|
22476
|
+
var init_bridge_builder = __esm(() => {
|
|
22477
|
+
QUESTION_PATTERNS = [/\?/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
|
|
22757
22478
|
});
|
|
22758
22479
|
|
|
22759
22480
|
// src/interaction/chain.ts
|
|
@@ -22844,47 +22565,166 @@ class InteractionChain {
|
|
|
22844
22565
|
}
|
|
22845
22566
|
}
|
|
22846
22567
|
|
|
22847
|
-
// src/interaction/
|
|
22848
|
-
|
|
22849
|
-
|
|
22850
|
-
|
|
22851
|
-
|
|
22852
|
-
|
|
22853
|
-
|
|
22854
|
-
|
|
22855
|
-
|
|
22856
|
-
|
|
22857
|
-
|
|
22568
|
+
// src/interaction/plugins/auto.ts
|
|
22569
|
+
class AutoInteractionPlugin {
|
|
22570
|
+
name = "auto";
|
|
22571
|
+
config = {};
|
|
22572
|
+
async init(config2) {
|
|
22573
|
+
const cfg = AutoConfigSchema.parse(config2);
|
|
22574
|
+
this.config = {
|
|
22575
|
+
model: cfg.model ?? "fast",
|
|
22576
|
+
confidenceThreshold: cfg.confidenceThreshold ?? 0.7,
|
|
22577
|
+
maxCostPerDecision: cfg.maxCostPerDecision ?? 0.01,
|
|
22578
|
+
naxConfig: cfg.naxConfig
|
|
22579
|
+
};
|
|
22580
|
+
}
|
|
22581
|
+
async destroy() {}
|
|
22582
|
+
async send(request) {}
|
|
22583
|
+
async receive(_requestId, _timeout = 60000) {
|
|
22584
|
+
throw new Error("Auto plugin requires full request context (not just requestId)");
|
|
22585
|
+
}
|
|
22586
|
+
async decide(request) {
|
|
22587
|
+
if (request.metadata?.trigger === "security-review") {
|
|
22588
|
+
return;
|
|
22589
|
+
}
|
|
22590
|
+
try {
|
|
22591
|
+
if (_autoPluginDeps.callLlm) {
|
|
22592
|
+
const decision2 = await _autoPluginDeps.callLlm(request);
|
|
22593
|
+
if (decision2.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
22594
|
+
return;
|
|
22595
|
+
}
|
|
22596
|
+
return {
|
|
22597
|
+
requestId: request.id,
|
|
22598
|
+
action: decision2.action,
|
|
22599
|
+
value: decision2.value,
|
|
22600
|
+
respondedBy: "auto-ai",
|
|
22601
|
+
respondedAt: Date.now()
|
|
22602
|
+
};
|
|
22603
|
+
}
|
|
22604
|
+
const decision = await this.callLlm(request);
|
|
22605
|
+
if (decision.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
22606
|
+
return;
|
|
22607
|
+
}
|
|
22608
|
+
return {
|
|
22609
|
+
requestId: request.id,
|
|
22610
|
+
action: decision.action,
|
|
22611
|
+
value: decision.value,
|
|
22612
|
+
respondedBy: "auto-ai",
|
|
22613
|
+
respondedAt: Date.now()
|
|
22614
|
+
};
|
|
22615
|
+
} catch (err) {
|
|
22616
|
+
return;
|
|
22858
22617
|
}
|
|
22859
|
-
const json2 = await file2.text();
|
|
22860
|
-
const request = JSON.parse(json2);
|
|
22861
|
-
return request;
|
|
22862
|
-
} catch {
|
|
22863
|
-
return null;
|
|
22864
22618
|
}
|
|
22865
|
-
|
|
22866
|
-
|
|
22867
|
-
|
|
22868
|
-
|
|
22869
|
-
|
|
22870
|
-
const exists = await dir.exists();
|
|
22871
|
-
if (!exists) {
|
|
22872
|
-
return [];
|
|
22619
|
+
async callLlm(request) {
|
|
22620
|
+
const prompt = this.buildPrompt(request);
|
|
22621
|
+
const adapter = _autoPluginDeps.adapter;
|
|
22622
|
+
if (!adapter) {
|
|
22623
|
+
throw new Error("Auto plugin requires adapter to be injected via _autoPluginDeps.adapter");
|
|
22873
22624
|
}
|
|
22874
|
-
|
|
22875
|
-
|
|
22876
|
-
|
|
22625
|
+
let modelArg;
|
|
22626
|
+
if (this.config.naxConfig) {
|
|
22627
|
+
const modelTier = this.config.model ?? "fast";
|
|
22628
|
+
const modelEntry = this.config.naxConfig.models[modelTier];
|
|
22629
|
+
if (!modelEntry) {
|
|
22630
|
+
throw new Error(`Model tier "${modelTier}" not found in config.models`);
|
|
22631
|
+
}
|
|
22632
|
+
const modelDef = resolveModel(modelEntry);
|
|
22633
|
+
modelArg = modelDef.model;
|
|
22634
|
+
}
|
|
22635
|
+
const output = await adapter.complete(prompt, {
|
|
22636
|
+
...modelArg && { model: modelArg },
|
|
22637
|
+
jsonMode: true,
|
|
22638
|
+
...this.config.naxConfig && { config: this.config.naxConfig },
|
|
22639
|
+
featureName: request.featureName,
|
|
22640
|
+
storyId: request.storyId,
|
|
22641
|
+
sessionRole: "auto"
|
|
22877
22642
|
});
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
`
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
|
|
22643
|
+
return this.parseResponse(output);
|
|
22644
|
+
}
|
|
22645
|
+
buildPrompt(request) {
|
|
22646
|
+
let prompt = `You are an AI decision assistant for a code orchestration system. Given an interaction request, decide the best action.
|
|
22647
|
+
|
|
22648
|
+
## Interaction Request
|
|
22649
|
+
Type: ${request.type}
|
|
22650
|
+
Stage: ${request.stage}
|
|
22651
|
+
Feature: ${request.featureName}
|
|
22652
|
+
${request.storyId ? `Story: ${request.storyId}` : ""}
|
|
22653
|
+
|
|
22654
|
+
Summary: ${request.summary.replace(/`/g, "\\`").replace(/\$/g, "\\$")}
|
|
22655
|
+
${request.detail ? `
|
|
22656
|
+
Detail: ${request.detail.replace(/`/g, "\\`").replace(/\$/g, "\\$")}` : ""}
|
|
22657
|
+
`;
|
|
22658
|
+
if (request.options && request.options.length > 0) {
|
|
22659
|
+
prompt += `
|
|
22660
|
+
Options:
|
|
22661
|
+
`;
|
|
22662
|
+
for (const opt of request.options) {
|
|
22663
|
+
const desc = opt.description ? ` \u2014 ${opt.description}` : "";
|
|
22664
|
+
prompt += ` [${opt.key}] ${opt.label}${desc}
|
|
22665
|
+
`;
|
|
22666
|
+
}
|
|
22667
|
+
}
|
|
22668
|
+
prompt += `
|
|
22669
|
+
Fallback behavior on timeout: ${request.fallback}
|
|
22670
|
+
Safety tier: ${request.metadata?.safety ?? "unknown"}
|
|
22671
|
+
|
|
22672
|
+
## Available Actions
|
|
22673
|
+
- approve: Proceed with the operation
|
|
22674
|
+
- reject: Deny the operation
|
|
22675
|
+
- choose: Select an option (requires value field)
|
|
22676
|
+
- input: Provide text input (requires value field)
|
|
22677
|
+
- skip: Skip this interaction
|
|
22678
|
+
- abort: Abort execution
|
|
22679
|
+
|
|
22680
|
+
## Rules
|
|
22681
|
+
1. For "red" safety tier (security-review, cost-exceeded, merge-conflict): ALWAYS return confidence 0 to escalate to human
|
|
22682
|
+
2. For "yellow" safety tier (cost-warning, max-retries, pre-merge): High confidence (0.8+) ONLY if clearly safe
|
|
22683
|
+
3. For "green" safety tier (story-ambiguity, review-gate): Can approve with moderate confidence (0.6+)
|
|
22684
|
+
4. Default to the fallback behavior if unsure
|
|
22685
|
+
5. Never auto-approve security issues
|
|
22686
|
+
6. If the summary mentions "critical" or "security", confidence MUST be < 0.5
|
|
22687
|
+
|
|
22688
|
+
Respond with ONLY this JSON (no markdown, no explanation):
|
|
22689
|
+
{"action":"approve|reject|choose|input|skip|abort","value":"<optional>","confidence":0.0-1.0,"reasoning":"<one line>"}`;
|
|
22690
|
+
return prompt;
|
|
22691
|
+
}
|
|
22692
|
+
parseResponse(output) {
|
|
22693
|
+
let jsonText = output.trim();
|
|
22694
|
+
if (jsonText.startsWith("```")) {
|
|
22695
|
+
const lines = jsonText.split(`
|
|
22696
|
+
`);
|
|
22697
|
+
jsonText = lines.slice(1, -1).join(`
|
|
22698
|
+
`).trim();
|
|
22699
|
+
}
|
|
22700
|
+
if (jsonText.startsWith("json")) {
|
|
22701
|
+
jsonText = jsonText.slice(4).trim();
|
|
22702
|
+
}
|
|
22703
|
+
const parsed = JSON.parse(jsonText);
|
|
22704
|
+
if (!parsed.action || parsed.confidence === undefined || !parsed.reasoning) {
|
|
22705
|
+
throw new Error(`Invalid LLM response: ${jsonText}`);
|
|
22706
|
+
}
|
|
22707
|
+
if (parsed.confidence < 0 || parsed.confidence > 1) {
|
|
22708
|
+
throw new Error(`Invalid confidence: ${parsed.confidence} (must be 0-1)`);
|
|
22709
|
+
}
|
|
22710
|
+
return parsed;
|
|
22885
22711
|
}
|
|
22886
22712
|
}
|
|
22887
|
-
var
|
|
22713
|
+
var AutoConfigSchema, _autoPluginDeps;
|
|
22714
|
+
var init_auto = __esm(() => {
|
|
22715
|
+
init_zod();
|
|
22716
|
+
init_config();
|
|
22717
|
+
AutoConfigSchema = exports_external.object({
|
|
22718
|
+
model: exports_external.string().optional(),
|
|
22719
|
+
confidenceThreshold: exports_external.number().min(0).max(1).optional(),
|
|
22720
|
+
maxCostPerDecision: exports_external.number().positive().optional(),
|
|
22721
|
+
naxConfig: exports_external.any().optional()
|
|
22722
|
+
});
|
|
22723
|
+
_autoPluginDeps = {
|
|
22724
|
+
adapter: null,
|
|
22725
|
+
callLlm: null
|
|
22726
|
+
};
|
|
22727
|
+
});
|
|
22888
22728
|
|
|
22889
22729
|
// src/interaction/plugins/cli.ts
|
|
22890
22730
|
import * as readline from "readline";
|
|
@@ -22953,9 +22793,9 @@ ${request.summary}
|
|
|
22953
22793
|
if (!this.rl) {
|
|
22954
22794
|
throw new Error("CLI plugin not initialized");
|
|
22955
22795
|
}
|
|
22956
|
-
const timeoutPromise = new Promise((
|
|
22796
|
+
const timeoutPromise = new Promise((resolve4) => {
|
|
22957
22797
|
setTimeout(() => {
|
|
22958
|
-
|
|
22798
|
+
resolve4({
|
|
22959
22799
|
requestId: request.id,
|
|
22960
22800
|
action: "skip",
|
|
22961
22801
|
respondedBy: "timeout",
|
|
@@ -23107,9 +22947,9 @@ ${request.summary}
|
|
|
23107
22947
|
if (!this.rl) {
|
|
23108
22948
|
throw new Error("CLI plugin not initialized");
|
|
23109
22949
|
}
|
|
23110
|
-
return new Promise((
|
|
22950
|
+
return new Promise((resolve4) => {
|
|
23111
22951
|
this.rl?.question(prompt, (answer) => {
|
|
23112
|
-
|
|
22952
|
+
resolve4(answer);
|
|
23113
22953
|
});
|
|
23114
22954
|
});
|
|
23115
22955
|
}
|
|
@@ -23144,28 +22984,38 @@ class TelegramInteractionPlugin {
|
|
|
23144
22984
|
if (!this.botToken || !this.chatId) {
|
|
23145
22985
|
throw new Error("Telegram plugin not initialized");
|
|
23146
22986
|
}
|
|
23147
|
-
const
|
|
22987
|
+
const header = this.buildHeader(request);
|
|
23148
22988
|
const keyboard = this.buildKeyboard(request);
|
|
22989
|
+
const body = this.buildBody(request);
|
|
22990
|
+
const chunks = this.splitText(body, MAX_MESSAGE_CHARS - header.length - 10);
|
|
23149
22991
|
try {
|
|
23150
|
-
const
|
|
23151
|
-
|
|
23152
|
-
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
22992
|
+
const sentIds = [];
|
|
22993
|
+
for (let i = 0;i < chunks.length; i++) {
|
|
22994
|
+
const isLast = i === chunks.length - 1;
|
|
22995
|
+
const partLabel = chunks.length > 1 ? `[${i + 1}/${chunks.length}] ` : "";
|
|
22996
|
+
const text = `${header}
|
|
22997
|
+
${partLabel}${chunks[i]}`;
|
|
22998
|
+
const response = await fetch(`https://api.telegram.org/bot${this.botToken}/sendMessage`, {
|
|
22999
|
+
method: "POST",
|
|
23000
|
+
headers: { "Content-Type": "application/json" },
|
|
23001
|
+
body: JSON.stringify({
|
|
23002
|
+
chat_id: this.chatId,
|
|
23003
|
+
text,
|
|
23004
|
+
reply_markup: isLast && keyboard ? { inline_keyboard: keyboard } : undefined,
|
|
23005
|
+
parse_mode: "Markdown"
|
|
23006
|
+
})
|
|
23007
|
+
});
|
|
23008
|
+
if (!response.ok) {
|
|
23009
|
+
const errorBody = await response.text().catch(() => "");
|
|
23010
|
+
throw new Error(`Telegram API error (${response.status}): ${errorBody || response.statusText}`);
|
|
23011
|
+
}
|
|
23012
|
+
const data = await response.json();
|
|
23013
|
+
if (!data.ok) {
|
|
23014
|
+
throw new Error(`Telegram API returned ok=false: ${JSON.stringify(data)}`);
|
|
23015
|
+
}
|
|
23016
|
+
sentIds.push(data.result.message_id);
|
|
23167
23017
|
}
|
|
23168
|
-
this.pendingMessages.set(request.id,
|
|
23018
|
+
this.pendingMessages.set(request.id, sentIds);
|
|
23169
23019
|
} catch (err) {
|
|
23170
23020
|
const msg = err instanceof Error ? err.message : String(err);
|
|
23171
23021
|
throw new Error(`Failed to send Telegram message: ${msg}`);
|
|
@@ -23202,10 +23052,9 @@ class TelegramInteractionPlugin {
|
|
|
23202
23052
|
await this.sendTimeoutMessage(requestId);
|
|
23203
23053
|
this.pendingMessages.delete(requestId);
|
|
23204
23054
|
}
|
|
23205
|
-
|
|
23055
|
+
buildHeader(request) {
|
|
23206
23056
|
const emoji3 = this.getStageEmoji(request.stage);
|
|
23207
23057
|
let text = `${emoji3} *${request.stage.toUpperCase()}*
|
|
23208
|
-
|
|
23209
23058
|
`;
|
|
23210
23059
|
text += `*Feature:* ${request.featureName}
|
|
23211
23060
|
`;
|
|
@@ -23214,11 +23063,15 @@ class TelegramInteractionPlugin {
|
|
|
23214
23063
|
`;
|
|
23215
23064
|
}
|
|
23216
23065
|
text += `
|
|
23217
|
-
|
|
23066
|
+
`;
|
|
23067
|
+
return text;
|
|
23068
|
+
}
|
|
23069
|
+
buildBody(request) {
|
|
23070
|
+
let text = `${this.sanitizeMarkdown(request.summary)}
|
|
23218
23071
|
`;
|
|
23219
23072
|
if (request.detail) {
|
|
23220
23073
|
text += `
|
|
23221
|
-
${request.detail}
|
|
23074
|
+
${this.sanitizeMarkdown(request.detail)}
|
|
23222
23075
|
`;
|
|
23223
23076
|
}
|
|
23224
23077
|
if (request.options && request.options.length > 0) {
|
|
@@ -23226,8 +23079,8 @@ ${request.detail}
|
|
|
23226
23079
|
*Options:*
|
|
23227
23080
|
`;
|
|
23228
23081
|
for (const opt of request.options) {
|
|
23229
|
-
const desc = opt.description ? `
|
|
23230
|
-
text += `
|
|
23082
|
+
const desc = opt.description ? ` - ${this.sanitizeMarkdown(opt.description)}` : "";
|
|
23083
|
+
text += ` - ${opt.label}${desc}
|
|
23231
23084
|
`;
|
|
23232
23085
|
}
|
|
23233
23086
|
}
|
|
@@ -23238,6 +23091,30 @@ ${request.detail}
|
|
|
23238
23091
|
}
|
|
23239
23092
|
return text;
|
|
23240
23093
|
}
|
|
23094
|
+
sanitizeMarkdown(text) {
|
|
23095
|
+
return text.replace(/\\(?=[_*`\[])/g, "\\\\").replace(/_/g, "\\_").replace(/`/g, "\\`").replace(/\*/g, "\\*").replace(/\[/g, "\\[");
|
|
23096
|
+
}
|
|
23097
|
+
splitText(text, maxChars) {
|
|
23098
|
+
if (text.length <= maxChars)
|
|
23099
|
+
return [text];
|
|
23100
|
+
const chunks = [];
|
|
23101
|
+
let remaining = text;
|
|
23102
|
+
while (remaining.length > maxChars) {
|
|
23103
|
+
const slice = remaining.slice(0, maxChars);
|
|
23104
|
+
const lastNewline = slice.lastIndexOf(`
|
|
23105
|
+
`);
|
|
23106
|
+
if (lastNewline > maxChars * 0.5) {
|
|
23107
|
+
chunks.push(remaining.slice(0, lastNewline));
|
|
23108
|
+
remaining = remaining.slice(lastNewline + 1);
|
|
23109
|
+
} else {
|
|
23110
|
+
chunks.push(slice);
|
|
23111
|
+
remaining = remaining.slice(maxChars);
|
|
23112
|
+
}
|
|
23113
|
+
}
|
|
23114
|
+
if (remaining.length > 0)
|
|
23115
|
+
chunks.push(remaining);
|
|
23116
|
+
return chunks;
|
|
23117
|
+
}
|
|
23241
23118
|
buildKeyboard(request) {
|
|
23242
23119
|
switch (request.type) {
|
|
23243
23120
|
case "confirm":
|
|
@@ -23345,8 +23222,11 @@ ${request.detail}
|
|
|
23345
23222
|
};
|
|
23346
23223
|
}
|
|
23347
23224
|
if (update.message?.text) {
|
|
23348
|
-
const
|
|
23349
|
-
if (!
|
|
23225
|
+
const messageIds = this.pendingMessages.get(requestId);
|
|
23226
|
+
if (!messageIds)
|
|
23227
|
+
return null;
|
|
23228
|
+
const replyToId = update.message.reply_to_message?.message_id;
|
|
23229
|
+
if (replyToId !== undefined && !messageIds.includes(replyToId))
|
|
23350
23230
|
return null;
|
|
23351
23231
|
return {
|
|
23352
23232
|
requestId,
|
|
@@ -23372,20 +23252,20 @@ ${request.detail}
|
|
|
23372
23252
|
} catch {}
|
|
23373
23253
|
}
|
|
23374
23254
|
async sendTimeoutMessage(requestId) {
|
|
23375
|
-
const
|
|
23376
|
-
if (!
|
|
23255
|
+
const messageIds = this.pendingMessages.get(requestId);
|
|
23256
|
+
if (!messageIds || !this.botToken || !this.chatId) {
|
|
23377
23257
|
this.pendingMessages.delete(requestId);
|
|
23378
23258
|
return;
|
|
23379
23259
|
}
|
|
23260
|
+
const lastId = messageIds[messageIds.length - 1];
|
|
23380
23261
|
try {
|
|
23381
23262
|
await fetch(`https://api.telegram.org/bot${this.botToken}/editMessageText`, {
|
|
23382
23263
|
method: "POST",
|
|
23383
23264
|
headers: { "Content-Type": "application/json" },
|
|
23384
23265
|
body: JSON.stringify({
|
|
23385
23266
|
chat_id: this.chatId,
|
|
23386
|
-
message_id:
|
|
23387
|
-
text: "\u23F1
|
|
23388
|
-
parse_mode: "Markdown"
|
|
23267
|
+
message_id: lastId,
|
|
23268
|
+
text: "\u23F1 EXPIRED \u2014 Interaction timed out"
|
|
23389
23269
|
})
|
|
23390
23270
|
});
|
|
23391
23271
|
} catch {} finally {
|
|
@@ -23393,7 +23273,7 @@ ${request.detail}
|
|
|
23393
23273
|
}
|
|
23394
23274
|
}
|
|
23395
23275
|
}
|
|
23396
|
-
var TelegramConfigSchema;
|
|
23276
|
+
var MAX_MESSAGE_CHARS = 4000, TelegramConfigSchema;
|
|
23397
23277
|
var init_telegram = __esm(() => {
|
|
23398
23278
|
init_zod();
|
|
23399
23279
|
TelegramConfigSchema = exports_external.object({
|
|
@@ -23466,10 +23346,10 @@ class WebhookInteractionPlugin {
|
|
|
23466
23346
|
this.pendingResponses.delete(requestId);
|
|
23467
23347
|
return early;
|
|
23468
23348
|
}
|
|
23469
|
-
return new Promise((
|
|
23349
|
+
return new Promise((resolve4) => {
|
|
23470
23350
|
const timer = setTimeout(() => {
|
|
23471
23351
|
this.receiveCallbacks.delete(requestId);
|
|
23472
|
-
|
|
23352
|
+
resolve4({
|
|
23473
23353
|
requestId,
|
|
23474
23354
|
action: "skip",
|
|
23475
23355
|
respondedBy: "timeout",
|
|
@@ -23479,7 +23359,7 @@ class WebhookInteractionPlugin {
|
|
|
23479
23359
|
this.receiveCallbacks.set(requestId, (response) => {
|
|
23480
23360
|
clearTimeout(timer);
|
|
23481
23361
|
this.receiveCallbacks.delete(requestId);
|
|
23482
|
-
|
|
23362
|
+
resolve4(response);
|
|
23483
23363
|
});
|
|
23484
23364
|
});
|
|
23485
23365
|
}
|
|
@@ -23600,290 +23480,6 @@ var init_webhook = __esm(() => {
|
|
|
23600
23480
|
});
|
|
23601
23481
|
});
|
|
23602
23482
|
|
|
23603
|
-
// src/interaction/plugins/auto.ts
|
|
23604
|
-
class AutoInteractionPlugin {
|
|
23605
|
-
name = "auto";
|
|
23606
|
-
config = {};
|
|
23607
|
-
async init(config2) {
|
|
23608
|
-
const cfg = AutoConfigSchema.parse(config2);
|
|
23609
|
-
this.config = {
|
|
23610
|
-
model: cfg.model ?? "fast",
|
|
23611
|
-
confidenceThreshold: cfg.confidenceThreshold ?? 0.7,
|
|
23612
|
-
maxCostPerDecision: cfg.maxCostPerDecision ?? 0.01,
|
|
23613
|
-
naxConfig: cfg.naxConfig
|
|
23614
|
-
};
|
|
23615
|
-
}
|
|
23616
|
-
async destroy() {}
|
|
23617
|
-
async send(request) {}
|
|
23618
|
-
async receive(_requestId, _timeout = 60000) {
|
|
23619
|
-
throw new Error("Auto plugin requires full request context (not just requestId)");
|
|
23620
|
-
}
|
|
23621
|
-
async decide(request) {
|
|
23622
|
-
if (request.metadata?.trigger === "security-review") {
|
|
23623
|
-
return;
|
|
23624
|
-
}
|
|
23625
|
-
try {
|
|
23626
|
-
if (_autoPluginDeps.callLlm) {
|
|
23627
|
-
const decision2 = await _autoPluginDeps.callLlm(request);
|
|
23628
|
-
if (decision2.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
23629
|
-
return;
|
|
23630
|
-
}
|
|
23631
|
-
return {
|
|
23632
|
-
requestId: request.id,
|
|
23633
|
-
action: decision2.action,
|
|
23634
|
-
value: decision2.value,
|
|
23635
|
-
respondedBy: "auto-ai",
|
|
23636
|
-
respondedAt: Date.now()
|
|
23637
|
-
};
|
|
23638
|
-
}
|
|
23639
|
-
const decision = await this.callLlm(request);
|
|
23640
|
-
if (decision.confidence < (this.config.confidenceThreshold ?? 0.7)) {
|
|
23641
|
-
return;
|
|
23642
|
-
}
|
|
23643
|
-
return {
|
|
23644
|
-
requestId: request.id,
|
|
23645
|
-
action: decision.action,
|
|
23646
|
-
value: decision.value,
|
|
23647
|
-
respondedBy: "auto-ai",
|
|
23648
|
-
respondedAt: Date.now()
|
|
23649
|
-
};
|
|
23650
|
-
} catch (err) {
|
|
23651
|
-
return;
|
|
23652
|
-
}
|
|
23653
|
-
}
|
|
23654
|
-
async callLlm(request) {
|
|
23655
|
-
const prompt = this.buildPrompt(request);
|
|
23656
|
-
const adapter = _autoPluginDeps.adapter;
|
|
23657
|
-
if (!adapter) {
|
|
23658
|
-
throw new Error("Auto plugin requires adapter to be injected via _autoPluginDeps.adapter");
|
|
23659
|
-
}
|
|
23660
|
-
let modelArg;
|
|
23661
|
-
if (this.config.naxConfig) {
|
|
23662
|
-
const modelTier = this.config.model ?? "fast";
|
|
23663
|
-
const modelEntry = this.config.naxConfig.models[modelTier];
|
|
23664
|
-
if (!modelEntry) {
|
|
23665
|
-
throw new Error(`Model tier "${modelTier}" not found in config.models`);
|
|
23666
|
-
}
|
|
23667
|
-
const modelDef = resolveModel(modelEntry);
|
|
23668
|
-
modelArg = modelDef.model;
|
|
23669
|
-
}
|
|
23670
|
-
const output = await adapter.complete(prompt, {
|
|
23671
|
-
...modelArg && { model: modelArg },
|
|
23672
|
-
jsonMode: true,
|
|
23673
|
-
...this.config.naxConfig && { config: this.config.naxConfig },
|
|
23674
|
-
featureName: request.featureName,
|
|
23675
|
-
storyId: request.storyId,
|
|
23676
|
-
sessionRole: "auto"
|
|
23677
|
-
});
|
|
23678
|
-
return this.parseResponse(output);
|
|
23679
|
-
}
|
|
23680
|
-
buildPrompt(request) {
|
|
23681
|
-
let prompt = `You are an AI decision assistant for a code orchestration system. Given an interaction request, decide the best action.
|
|
23682
|
-
|
|
23683
|
-
## Interaction Request
|
|
23684
|
-
Type: ${request.type}
|
|
23685
|
-
Stage: ${request.stage}
|
|
23686
|
-
Feature: ${request.featureName}
|
|
23687
|
-
${request.storyId ? `Story: ${request.storyId}` : ""}
|
|
23688
|
-
|
|
23689
|
-
Summary: ${request.summary.replace(/`/g, "\\`").replace(/\$/g, "\\$")}
|
|
23690
|
-
${request.detail ? `
|
|
23691
|
-
Detail: ${request.detail.replace(/`/g, "\\`").replace(/\$/g, "\\$")}` : ""}
|
|
23692
|
-
`;
|
|
23693
|
-
if (request.options && request.options.length > 0) {
|
|
23694
|
-
prompt += `
|
|
23695
|
-
Options:
|
|
23696
|
-
`;
|
|
23697
|
-
for (const opt of request.options) {
|
|
23698
|
-
const desc = opt.description ? ` \u2014 ${opt.description}` : "";
|
|
23699
|
-
prompt += ` [${opt.key}] ${opt.label}${desc}
|
|
23700
|
-
`;
|
|
23701
|
-
}
|
|
23702
|
-
}
|
|
23703
|
-
prompt += `
|
|
23704
|
-
Fallback behavior on timeout: ${request.fallback}
|
|
23705
|
-
Safety tier: ${request.metadata?.safety ?? "unknown"}
|
|
23706
|
-
|
|
23707
|
-
## Available Actions
|
|
23708
|
-
- approve: Proceed with the operation
|
|
23709
|
-
- reject: Deny the operation
|
|
23710
|
-
- choose: Select an option (requires value field)
|
|
23711
|
-
- input: Provide text input (requires value field)
|
|
23712
|
-
- skip: Skip this interaction
|
|
23713
|
-
- abort: Abort execution
|
|
23714
|
-
|
|
23715
|
-
## Rules
|
|
23716
|
-
1. For "red" safety tier (security-review, cost-exceeded, merge-conflict): ALWAYS return confidence 0 to escalate to human
|
|
23717
|
-
2. For "yellow" safety tier (cost-warning, max-retries, pre-merge): High confidence (0.8+) ONLY if clearly safe
|
|
23718
|
-
3. For "green" safety tier (story-ambiguity, review-gate): Can approve with moderate confidence (0.6+)
|
|
23719
|
-
4. Default to the fallback behavior if unsure
|
|
23720
|
-
5. Never auto-approve security issues
|
|
23721
|
-
6. If the summary mentions "critical" or "security", confidence MUST be < 0.5
|
|
23722
|
-
|
|
23723
|
-
Respond with ONLY this JSON (no markdown, no explanation):
|
|
23724
|
-
{"action":"approve|reject|choose|input|skip|abort","value":"<optional>","confidence":0.0-1.0,"reasoning":"<one line>"}`;
|
|
23725
|
-
return prompt;
|
|
23726
|
-
}
|
|
23727
|
-
parseResponse(output) {
|
|
23728
|
-
let jsonText = output.trim();
|
|
23729
|
-
if (jsonText.startsWith("```")) {
|
|
23730
|
-
const lines = jsonText.split(`
|
|
23731
|
-
`);
|
|
23732
|
-
jsonText = lines.slice(1, -1).join(`
|
|
23733
|
-
`).trim();
|
|
23734
|
-
}
|
|
23735
|
-
if (jsonText.startsWith("json")) {
|
|
23736
|
-
jsonText = jsonText.slice(4).trim();
|
|
23737
|
-
}
|
|
23738
|
-
const parsed = JSON.parse(jsonText);
|
|
23739
|
-
if (!parsed.action || parsed.confidence === undefined || !parsed.reasoning) {
|
|
23740
|
-
throw new Error(`Invalid LLM response: ${jsonText}`);
|
|
23741
|
-
}
|
|
23742
|
-
if (parsed.confidence < 0 || parsed.confidence > 1) {
|
|
23743
|
-
throw new Error(`Invalid confidence: ${parsed.confidence} (must be 0-1)`);
|
|
23744
|
-
}
|
|
23745
|
-
return parsed;
|
|
23746
|
-
}
|
|
23747
|
-
}
|
|
23748
|
-
var AutoConfigSchema, _autoPluginDeps;
|
|
23749
|
-
var init_auto = __esm(() => {
|
|
23750
|
-
init_zod();
|
|
23751
|
-
init_config();
|
|
23752
|
-
AutoConfigSchema = exports_external.object({
|
|
23753
|
-
model: exports_external.string().optional(),
|
|
23754
|
-
confidenceThreshold: exports_external.number().min(0).max(1).optional(),
|
|
23755
|
-
maxCostPerDecision: exports_external.number().positive().optional(),
|
|
23756
|
-
naxConfig: exports_external.any().optional()
|
|
23757
|
-
});
|
|
23758
|
-
_autoPluginDeps = {
|
|
23759
|
-
adapter: null,
|
|
23760
|
-
callLlm: null
|
|
23761
|
-
};
|
|
23762
|
-
});
|
|
23763
|
-
|
|
23764
|
-
// src/interaction/triggers.ts
|
|
23765
|
-
function isTriggerEnabled(trigger, config2) {
|
|
23766
|
-
const triggerConfig = config2.interaction?.triggers?.[trigger];
|
|
23767
|
-
if (triggerConfig === undefined)
|
|
23768
|
-
return false;
|
|
23769
|
-
if (typeof triggerConfig === "boolean")
|
|
23770
|
-
return triggerConfig;
|
|
23771
|
-
return triggerConfig.enabled;
|
|
23772
|
-
}
|
|
23773
|
-
function getTriggerConfig(trigger, config2) {
|
|
23774
|
-
const metadata = TRIGGER_METADATA[trigger];
|
|
23775
|
-
const triggerConfig = config2.interaction?.triggers?.[trigger];
|
|
23776
|
-
const defaults = config2.interaction?.defaults ?? {
|
|
23777
|
-
timeout: 600000,
|
|
23778
|
-
fallback: "escalate"
|
|
23779
|
-
};
|
|
23780
|
-
let fallback = metadata.defaultFallback;
|
|
23781
|
-
let timeout = defaults.timeout;
|
|
23782
|
-
if (typeof triggerConfig === "object") {
|
|
23783
|
-
if (triggerConfig.fallback) {
|
|
23784
|
-
fallback = triggerConfig.fallback;
|
|
23785
|
-
}
|
|
23786
|
-
if (triggerConfig.timeout) {
|
|
23787
|
-
timeout = triggerConfig.timeout;
|
|
23788
|
-
}
|
|
23789
|
-
}
|
|
23790
|
-
return { fallback, timeout };
|
|
23791
|
-
}
|
|
23792
|
-
function substituteTemplate(template, context) {
|
|
23793
|
-
let result = template;
|
|
23794
|
-
for (const [key, value] of Object.entries(context)) {
|
|
23795
|
-
if (value !== undefined) {
|
|
23796
|
-
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(value));
|
|
23797
|
-
}
|
|
23798
|
-
}
|
|
23799
|
-
return result;
|
|
23800
|
-
}
|
|
23801
|
-
function createTriggerRequest(trigger, context, config2) {
|
|
23802
|
-
const metadata = TRIGGER_METADATA[trigger];
|
|
23803
|
-
const { fallback, timeout } = getTriggerConfig(trigger, config2);
|
|
23804
|
-
const summary = substituteTemplate(metadata.defaultSummary, context);
|
|
23805
|
-
const id = `trigger-${trigger}-${Date.now()}`;
|
|
23806
|
-
return {
|
|
23807
|
-
id,
|
|
23808
|
-
type: "confirm",
|
|
23809
|
-
featureName: context.featureName,
|
|
23810
|
-
storyId: context.storyId,
|
|
23811
|
-
stage: "custom",
|
|
23812
|
-
summary,
|
|
23813
|
-
fallback,
|
|
23814
|
-
timeout,
|
|
23815
|
-
createdAt: Date.now(),
|
|
23816
|
-
metadata: {
|
|
23817
|
-
trigger,
|
|
23818
|
-
safety: metadata.safety
|
|
23819
|
-
}
|
|
23820
|
-
};
|
|
23821
|
-
}
|
|
23822
|
-
async function executeTrigger(trigger, context, config2, chain) {
|
|
23823
|
-
const request = createTriggerRequest(trigger, context, config2);
|
|
23824
|
-
const response = await chain.prompt(request);
|
|
23825
|
-
return response;
|
|
23826
|
-
}
|
|
23827
|
-
async function checkSecurityReview(context, config2, chain) {
|
|
23828
|
-
if (!isTriggerEnabled("security-review", config2))
|
|
23829
|
-
return true;
|
|
23830
|
-
const response = await executeTrigger("security-review", context, config2, chain);
|
|
23831
|
-
return response.action !== "abort";
|
|
23832
|
-
}
|
|
23833
|
-
async function checkCostExceeded(context, config2, chain) {
|
|
23834
|
-
if (!isTriggerEnabled("cost-exceeded", config2))
|
|
23835
|
-
return true;
|
|
23836
|
-
const response = await executeTrigger("cost-exceeded", context, config2, chain);
|
|
23837
|
-
return response.action !== "abort";
|
|
23838
|
-
}
|
|
23839
|
-
async function checkMergeConflict(context, config2, chain) {
|
|
23840
|
-
if (!isTriggerEnabled("merge-conflict", config2))
|
|
23841
|
-
return true;
|
|
23842
|
-
const response = await executeTrigger("merge-conflict", context, config2, chain);
|
|
23843
|
-
return response.action !== "abort";
|
|
23844
|
-
}
|
|
23845
|
-
async function checkCostWarning(context, config2, chain) {
|
|
23846
|
-
if (!isTriggerEnabled("cost-warning", config2))
|
|
23847
|
-
return "continue";
|
|
23848
|
-
const response = await executeTrigger("cost-warning", context, config2, chain);
|
|
23849
|
-
return response.action === "approve" ? "escalate" : "continue";
|
|
23850
|
-
}
|
|
23851
|
-
async function checkPreMerge(context, config2, chain) {
|
|
23852
|
-
if (!isTriggerEnabled("pre-merge", config2))
|
|
23853
|
-
return true;
|
|
23854
|
-
const response = await executeTrigger("pre-merge", context, config2, chain);
|
|
23855
|
-
return response.action === "approve";
|
|
23856
|
-
}
|
|
23857
|
-
async function checkStoryAmbiguity(context, config2, chain) {
|
|
23858
|
-
if (!isTriggerEnabled("story-ambiguity", config2))
|
|
23859
|
-
return true;
|
|
23860
|
-
const response = await executeTrigger("story-ambiguity", context, config2, chain);
|
|
23861
|
-
return response.action === "approve";
|
|
23862
|
-
}
|
|
23863
|
-
async function checkReviewGate(context, config2, chain) {
|
|
23864
|
-
if (!isTriggerEnabled("review-gate", config2))
|
|
23865
|
-
return true;
|
|
23866
|
-
const response = await executeTrigger("review-gate", context, config2, chain);
|
|
23867
|
-
return response.action === "approve";
|
|
23868
|
-
}
|
|
23869
|
-
async function checkStoryOversized(context, config2, chain) {
|
|
23870
|
-
if (!isTriggerEnabled("story-oversized", config2))
|
|
23871
|
-
return "continue";
|
|
23872
|
-
try {
|
|
23873
|
-
const response = await executeTrigger("story-oversized", context, config2, chain);
|
|
23874
|
-
if (response.action === "approve")
|
|
23875
|
-
return "decompose";
|
|
23876
|
-
if (response.action === "skip")
|
|
23877
|
-
return "skip";
|
|
23878
|
-
return "continue";
|
|
23879
|
-
} catch {
|
|
23880
|
-
return "continue";
|
|
23881
|
-
}
|
|
23882
|
-
}
|
|
23883
|
-
var init_triggers = __esm(() => {
|
|
23884
|
-
init_types4();
|
|
23885
|
-
});
|
|
23886
|
-
|
|
23887
23483
|
// src/interaction/init.ts
|
|
23888
23484
|
function createInteractionPlugin(pluginName) {
|
|
23889
23485
|
switch (pluginName) {
|
|
@@ -23938,6 +23534,483 @@ var init_init = __esm(() => {
|
|
|
23938
23534
|
init_webhook();
|
|
23939
23535
|
});
|
|
23940
23536
|
|
|
23537
|
+
// src/prd/validate.ts
|
|
23538
|
+
function validateStoryId(id) {
|
|
23539
|
+
if (!id || id.length === 0) {
|
|
23540
|
+
throw new Error("Story ID cannot be empty");
|
|
23541
|
+
}
|
|
23542
|
+
if (id.includes("..")) {
|
|
23543
|
+
throw new Error("Story ID cannot contain path traversal (..)");
|
|
23544
|
+
}
|
|
23545
|
+
if (id.startsWith("--")) {
|
|
23546
|
+
throw new Error("Story ID cannot start with git flags (--)");
|
|
23547
|
+
}
|
|
23548
|
+
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/;
|
|
23549
|
+
if (!validPattern.test(id)) {
|
|
23550
|
+
throw new Error(`Story ID must match pattern [a-zA-Z0-9][a-zA-Z0-9._-]{0,63}. Got: ${id}`);
|
|
23551
|
+
}
|
|
23552
|
+
}
|
|
23553
|
+
|
|
23554
|
+
// src/errors.ts
|
|
23555
|
+
var NaxError, AgentNotFoundError, AgentNotInstalledError, StoryLimitExceededError, LockAcquisitionError;
|
|
23556
|
+
var init_errors3 = __esm(() => {
|
|
23557
|
+
NaxError = class NaxError extends Error {
|
|
23558
|
+
code;
|
|
23559
|
+
context;
|
|
23560
|
+
constructor(message, code, context) {
|
|
23561
|
+
super(message);
|
|
23562
|
+
this.code = code;
|
|
23563
|
+
this.context = context;
|
|
23564
|
+
this.name = "NaxError";
|
|
23565
|
+
Error.captureStackTrace(this, this.constructor);
|
|
23566
|
+
}
|
|
23567
|
+
};
|
|
23568
|
+
AgentNotFoundError = class AgentNotFoundError extends NaxError {
|
|
23569
|
+
constructor(agentName, binary) {
|
|
23570
|
+
super(`Agent "${agentName}" not found or not installed`, "AGENT_NOT_FOUND", { agentName, binary });
|
|
23571
|
+
this.name = "AgentNotFoundError";
|
|
23572
|
+
}
|
|
23573
|
+
};
|
|
23574
|
+
AgentNotInstalledError = class AgentNotInstalledError extends NaxError {
|
|
23575
|
+
constructor(agentName, binary) {
|
|
23576
|
+
super(`Agent "${agentName}" is not installed or not in PATH: ${binary}`, "AGENT_NOT_INSTALLED", {
|
|
23577
|
+
agentName,
|
|
23578
|
+
binary
|
|
23579
|
+
});
|
|
23580
|
+
this.name = "AgentNotInstalledError";
|
|
23581
|
+
}
|
|
23582
|
+
};
|
|
23583
|
+
StoryLimitExceededError = class StoryLimitExceededError extends NaxError {
|
|
23584
|
+
constructor(totalStories, limit) {
|
|
23585
|
+
super(`Feature exceeds story limit: ${totalStories} stories (max: ${limit})`, "STORY_LIMIT_EXCEEDED", {
|
|
23586
|
+
totalStories,
|
|
23587
|
+
limit
|
|
23588
|
+
});
|
|
23589
|
+
this.name = "StoryLimitExceededError";
|
|
23590
|
+
}
|
|
23591
|
+
};
|
|
23592
|
+
LockAcquisitionError = class LockAcquisitionError extends NaxError {
|
|
23593
|
+
constructor(workdir) {
|
|
23594
|
+
super("Another nax process is already running in this directory", "LOCK_ACQUISITION_FAILED", { workdir });
|
|
23595
|
+
this.name = "LockAcquisitionError";
|
|
23596
|
+
}
|
|
23597
|
+
};
|
|
23598
|
+
});
|
|
23599
|
+
|
|
23600
|
+
// src/metrics/tracker.ts
|
|
23601
|
+
import path2 from "path";
|
|
23602
|
+
function collectStoryMetrics(ctx, storyStartTime) {
|
|
23603
|
+
const story = ctx.story;
|
|
23604
|
+
const routing = ctx.routing;
|
|
23605
|
+
const agentResult = ctx.agentResult;
|
|
23606
|
+
const escalationCount = story.escalations?.length || 0;
|
|
23607
|
+
const priorFailureCount = story.priorFailures?.length || 0;
|
|
23608
|
+
const attempts = priorFailureCount + Math.max(1, story.attempts || 1);
|
|
23609
|
+
const finalTier = escalationCount > 0 ? story.escalations[escalationCount - 1].toTier : routing.modelTier;
|
|
23610
|
+
const firstPassSuccess = agentResult?.success === true && escalationCount === 0 && priorFailureCount === 0;
|
|
23611
|
+
const modelEntry = ctx.config.models[routing.modelTier];
|
|
23612
|
+
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
23613
|
+
const modelUsed = modelDef?.model || routing.modelTier;
|
|
23614
|
+
const initialComplexity = story.routing?.initialComplexity ?? routing.complexity;
|
|
23615
|
+
const isTddStrategy = routing.testStrategy === "three-session-tdd" || routing.testStrategy === "three-session-tdd-lite";
|
|
23616
|
+
const fullSuiteGatePassed = isTddStrategy ? ctx.fullSuiteGatePassed ?? false : false;
|
|
23617
|
+
return {
|
|
23618
|
+
storyId: story.id,
|
|
23619
|
+
complexity: routing.complexity,
|
|
23620
|
+
initialComplexity,
|
|
23621
|
+
modelTier: routing.modelTier,
|
|
23622
|
+
modelUsed,
|
|
23623
|
+
attempts,
|
|
23624
|
+
finalTier,
|
|
23625
|
+
success: agentResult?.success || false,
|
|
23626
|
+
cost: (ctx.accumulatedAttemptCost ?? 0) + (agentResult?.estimatedCost || 0),
|
|
23627
|
+
durationMs: agentResult?.durationMs || 0,
|
|
23628
|
+
firstPassSuccess,
|
|
23629
|
+
startedAt: storyStartTime,
|
|
23630
|
+
completedAt: new Date().toISOString(),
|
|
23631
|
+
fullSuiteGatePassed,
|
|
23632
|
+
runtimeCrashes: ctx.storyRuntimeCrashes ?? 0
|
|
23633
|
+
};
|
|
23634
|
+
}
|
|
23635
|
+
function collectBatchMetrics(ctx, storyStartTime) {
|
|
23636
|
+
const stories = ctx.stories;
|
|
23637
|
+
const routing = ctx.routing;
|
|
23638
|
+
const agentResult = ctx.agentResult;
|
|
23639
|
+
const totalCost = agentResult?.estimatedCost || 0;
|
|
23640
|
+
const totalDuration = agentResult?.durationMs || 0;
|
|
23641
|
+
const costPerStory = totalCost / stories.length;
|
|
23642
|
+
const durationPerStory = totalDuration / stories.length;
|
|
23643
|
+
const modelEntry = ctx.config.models[routing.modelTier];
|
|
23644
|
+
const modelDef = modelEntry ? resolveModel(modelEntry) : null;
|
|
23645
|
+
const modelUsed = modelDef?.model || routing.modelTier;
|
|
23646
|
+
return stories.map((story) => {
|
|
23647
|
+
const initialComplexity = story.routing?.initialComplexity ?? routing.complexity;
|
|
23648
|
+
return {
|
|
23649
|
+
storyId: story.id,
|
|
23650
|
+
complexity: routing.complexity,
|
|
23651
|
+
initialComplexity,
|
|
23652
|
+
modelTier: routing.modelTier,
|
|
23653
|
+
modelUsed,
|
|
23654
|
+
attempts: 1,
|
|
23655
|
+
finalTier: routing.modelTier,
|
|
23656
|
+
success: true,
|
|
23657
|
+
cost: costPerStory,
|
|
23658
|
+
durationMs: durationPerStory,
|
|
23659
|
+
firstPassSuccess: true,
|
|
23660
|
+
startedAt: storyStartTime,
|
|
23661
|
+
completedAt: new Date().toISOString(),
|
|
23662
|
+
fullSuiteGatePassed: false,
|
|
23663
|
+
runtimeCrashes: 0
|
|
23664
|
+
};
|
|
23665
|
+
});
|
|
23666
|
+
}
|
|
23667
|
+
async function saveRunMetrics(workdir, runMetrics) {
|
|
23668
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
23669
|
+
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
23670
|
+
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
23671
|
+
allMetrics.push(runMetrics);
|
|
23672
|
+
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
23673
|
+
}
|
|
23674
|
+
async function loadRunMetrics(workdir) {
|
|
23675
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
23676
|
+
const content = await loadJsonFile(metricsPath, "metrics");
|
|
23677
|
+
return Array.isArray(content) ? content : [];
|
|
23678
|
+
}
|
|
23679
|
+
var init_tracker = __esm(() => {
|
|
23680
|
+
init_schema();
|
|
23681
|
+
init_json_file();
|
|
23682
|
+
});
|
|
23683
|
+
|
|
23684
|
+
// src/metrics/aggregator.ts
|
|
23685
|
+
function calculateAggregateMetrics(runs) {
|
|
23686
|
+
if (runs.length === 0) {
|
|
23687
|
+
return {
|
|
23688
|
+
totalRuns: 0,
|
|
23689
|
+
totalCost: 0,
|
|
23690
|
+
totalStories: 0,
|
|
23691
|
+
firstPassRate: 0,
|
|
23692
|
+
escalationRate: 0,
|
|
23693
|
+
avgCostPerStory: 0,
|
|
23694
|
+
avgCostPerFeature: 0,
|
|
23695
|
+
modelEfficiency: {},
|
|
23696
|
+
complexityAccuracy: {}
|
|
23697
|
+
};
|
|
23698
|
+
}
|
|
23699
|
+
const allStories = runs.flatMap((run) => run.stories);
|
|
23700
|
+
const totalRuns = runs.length;
|
|
23701
|
+
const totalCost = runs.reduce((sum, run) => sum + run.totalCost, 0);
|
|
23702
|
+
const totalStories = allStories.length;
|
|
23703
|
+
const firstPassSuccesses = allStories.filter((s) => s.firstPassSuccess).length;
|
|
23704
|
+
const firstPassRate = totalStories > 0 ? firstPassSuccesses / totalStories : 0;
|
|
23705
|
+
const escalatedStories = allStories.filter((s) => s.attempts > 1).length;
|
|
23706
|
+
const escalationRate = totalStories > 0 ? escalatedStories / totalStories : 0;
|
|
23707
|
+
const avgCostPerStory = totalStories > 0 ? totalCost / totalStories : 0;
|
|
23708
|
+
const avgCostPerFeature = totalRuns > 0 ? totalCost / totalRuns : 0;
|
|
23709
|
+
const modelStats = new Map;
|
|
23710
|
+
for (const story of allStories) {
|
|
23711
|
+
const modelKey = story.modelUsed;
|
|
23712
|
+
const existing = modelStats.get(modelKey) || {
|
|
23713
|
+
attempts: 0,
|
|
23714
|
+
successes: 0,
|
|
23715
|
+
totalCost: 0
|
|
23716
|
+
};
|
|
23717
|
+
modelStats.set(modelKey, {
|
|
23718
|
+
attempts: existing.attempts + story.attempts,
|
|
23719
|
+
successes: existing.successes + (story.success ? 1 : 0),
|
|
23720
|
+
totalCost: existing.totalCost + story.cost
|
|
23721
|
+
});
|
|
23722
|
+
}
|
|
23723
|
+
const modelEfficiency = {};
|
|
23724
|
+
for (const [modelKey, stats] of modelStats) {
|
|
23725
|
+
const passRate = stats.attempts > 0 ? stats.successes / stats.attempts : 0;
|
|
23726
|
+
const avgCost = stats.successes > 0 ? stats.totalCost / stats.successes : 0;
|
|
23727
|
+
modelEfficiency[modelKey] = {
|
|
23728
|
+
attempts: stats.attempts,
|
|
23729
|
+
successes: stats.successes,
|
|
23730
|
+
passRate,
|
|
23731
|
+
avgCost,
|
|
23732
|
+
totalCost: stats.totalCost
|
|
23733
|
+
};
|
|
23734
|
+
}
|
|
23735
|
+
const complexityStats = new Map;
|
|
23736
|
+
for (const story of allStories) {
|
|
23737
|
+
const complexity = story.initialComplexity ?? story.complexity;
|
|
23738
|
+
const existing = complexityStats.get(complexity) || {
|
|
23739
|
+
predicted: 0,
|
|
23740
|
+
tierCounts: new Map,
|
|
23741
|
+
mismatches: 0
|
|
23742
|
+
};
|
|
23743
|
+
existing.predicted += 1;
|
|
23744
|
+
const finalTier = story.finalTier;
|
|
23745
|
+
existing.tierCounts.set(finalTier, (existing.tierCounts.get(finalTier) || 0) + 1);
|
|
23746
|
+
if (story.modelTier !== story.finalTier) {
|
|
23747
|
+
existing.mismatches += 1;
|
|
23748
|
+
}
|
|
23749
|
+
complexityStats.set(complexity, existing);
|
|
23750
|
+
}
|
|
23751
|
+
const complexityAccuracy = {};
|
|
23752
|
+
for (const [complexity, stats] of complexityStats) {
|
|
23753
|
+
let maxCount = 0;
|
|
23754
|
+
let mostCommonTier = "unknown";
|
|
23755
|
+
for (const [tier, count] of stats.tierCounts) {
|
|
23756
|
+
if (count > maxCount) {
|
|
23757
|
+
maxCount = count;
|
|
23758
|
+
mostCommonTier = tier;
|
|
23759
|
+
}
|
|
23760
|
+
}
|
|
23761
|
+
const mismatchRate = stats.predicted > 0 ? stats.mismatches / stats.predicted : 0;
|
|
23762
|
+
complexityAccuracy[complexity] = {
|
|
23763
|
+
predicted: stats.predicted,
|
|
23764
|
+
actualTierUsed: mostCommonTier,
|
|
23765
|
+
mismatchRate
|
|
23766
|
+
};
|
|
23767
|
+
}
|
|
23768
|
+
return {
|
|
23769
|
+
totalRuns,
|
|
23770
|
+
totalCost,
|
|
23771
|
+
totalStories,
|
|
23772
|
+
firstPassRate,
|
|
23773
|
+
escalationRate,
|
|
23774
|
+
avgCostPerStory,
|
|
23775
|
+
avgCostPerFeature,
|
|
23776
|
+
modelEfficiency,
|
|
23777
|
+
complexityAccuracy
|
|
23778
|
+
};
|
|
23779
|
+
}
|
|
23780
|
+
function getLastRun(runs) {
|
|
23781
|
+
if (runs.length === 0) {
|
|
23782
|
+
return null;
|
|
23783
|
+
}
|
|
23784
|
+
return runs[runs.length - 1];
|
|
23785
|
+
}
|
|
23786
|
+
|
|
23787
|
+
// src/metrics/index.ts
|
|
23788
|
+
var init_metrics = __esm(() => {
|
|
23789
|
+
init_tracker();
|
|
23790
|
+
});
|
|
23791
|
+
|
|
23792
|
+
// src/interaction/types.ts
|
|
23793
|
+
var TRIGGER_METADATA;
|
|
23794
|
+
var init_types4 = __esm(() => {
|
|
23795
|
+
TRIGGER_METADATA = {
|
|
23796
|
+
"security-review": {
|
|
23797
|
+
defaultFallback: "abort",
|
|
23798
|
+
safety: "red",
|
|
23799
|
+
defaultSummary: "Security review failed \u2014 abort execution?"
|
|
23800
|
+
},
|
|
23801
|
+
"cost-exceeded": {
|
|
23802
|
+
defaultFallback: "abort",
|
|
23803
|
+
safety: "red",
|
|
23804
|
+
defaultSummary: "Cost limit exceeded ({{cost}} USD) \u2014 abort execution?"
|
|
23805
|
+
},
|
|
23806
|
+
"merge-conflict": {
|
|
23807
|
+
defaultFallback: "abort",
|
|
23808
|
+
safety: "red",
|
|
23809
|
+
defaultSummary: "Merge conflict detected in {{storyId}} \u2014 abort execution?"
|
|
23810
|
+
},
|
|
23811
|
+
"cost-warning": {
|
|
23812
|
+
defaultFallback: "escalate",
|
|
23813
|
+
safety: "yellow",
|
|
23814
|
+
defaultSummary: "Cost warning: {{cost}} USD / {{limit}} USD \u2014 escalate to higher tier?"
|
|
23815
|
+
},
|
|
23816
|
+
"max-retries": {
|
|
23817
|
+
defaultFallback: "skip",
|
|
23818
|
+
safety: "yellow",
|
|
23819
|
+
defaultSummary: "Max retries reached for {{storyId}} \u2014 skip story?"
|
|
23820
|
+
},
|
|
23821
|
+
"pre-merge": {
|
|
23822
|
+
defaultFallback: "escalate",
|
|
23823
|
+
safety: "yellow",
|
|
23824
|
+
defaultSummary: "Pre-merge checkpoint for {{storyId}} \u2014 proceed with merge?"
|
|
23825
|
+
},
|
|
23826
|
+
"human-review": {
|
|
23827
|
+
defaultFallback: "skip",
|
|
23828
|
+
safety: "yellow",
|
|
23829
|
+
defaultSummary: "Human review required for story {{storyId}} \u2014 skip and continue?"
|
|
23830
|
+
},
|
|
23831
|
+
"story-oversized": {
|
|
23832
|
+
defaultFallback: "continue",
|
|
23833
|
+
safety: "yellow",
|
|
23834
|
+
defaultSummary: "Story {{storyId}} is oversized ({{criteriaCount}} acceptance criteria) \u2014 decompose into smaller stories?"
|
|
23835
|
+
},
|
|
23836
|
+
"story-ambiguity": {
|
|
23837
|
+
defaultFallback: "continue",
|
|
23838
|
+
safety: "green",
|
|
23839
|
+
defaultSummary: "Story {{storyId}} requirements unclear \u2014 continue with best effort?"
|
|
23840
|
+
},
|
|
23841
|
+
"review-gate": {
|
|
23842
|
+
defaultFallback: "continue",
|
|
23843
|
+
safety: "green",
|
|
23844
|
+
defaultSummary: "Code review checkpoint for {{storyId}} \u2014 proceed?"
|
|
23845
|
+
}
|
|
23846
|
+
};
|
|
23847
|
+
});
|
|
23848
|
+
|
|
23849
|
+
// src/interaction/state.ts
|
|
23850
|
+
import * as path3 from "path";
|
|
23851
|
+
async function loadPendingInteraction(requestId, featureDir) {
|
|
23852
|
+
const interactionsDir = path3.join(featureDir, "interactions");
|
|
23853
|
+
const filename = `${requestId}.json`;
|
|
23854
|
+
const filePath = path3.join(interactionsDir, filename);
|
|
23855
|
+
try {
|
|
23856
|
+
const file2 = Bun.file(filePath);
|
|
23857
|
+
const exists = await file2.exists();
|
|
23858
|
+
if (!exists) {
|
|
23859
|
+
return null;
|
|
23860
|
+
}
|
|
23861
|
+
const json2 = await file2.text();
|
|
23862
|
+
const request = JSON.parse(json2);
|
|
23863
|
+
return request;
|
|
23864
|
+
} catch {
|
|
23865
|
+
return null;
|
|
23866
|
+
}
|
|
23867
|
+
}
|
|
23868
|
+
async function listPendingInteractions(featureDir) {
|
|
23869
|
+
const interactionsDir = path3.join(featureDir, "interactions");
|
|
23870
|
+
try {
|
|
23871
|
+
const dir = Bun.file(interactionsDir);
|
|
23872
|
+
const exists = await dir.exists();
|
|
23873
|
+
if (!exists) {
|
|
23874
|
+
return [];
|
|
23875
|
+
}
|
|
23876
|
+
const proc = Bun.spawn(["ls", interactionsDir], {
|
|
23877
|
+
stdout: "pipe",
|
|
23878
|
+
stderr: "pipe"
|
|
23879
|
+
});
|
|
23880
|
+
const output = await new Response(proc.stdout).text();
|
|
23881
|
+
await proc.exited;
|
|
23882
|
+
const files = output.split(`
|
|
23883
|
+
`).filter((f) => f.endsWith(".json") && f !== ".gitkeep").map((f) => f.replace(".json", ""));
|
|
23884
|
+
return files;
|
|
23885
|
+
} catch {
|
|
23886
|
+
return [];
|
|
23887
|
+
}
|
|
23888
|
+
}
|
|
23889
|
+
var init_state = () => {};
|
|
23890
|
+
|
|
23891
|
+
// src/interaction/triggers.ts
|
|
23892
|
+
function isTriggerEnabled(trigger, config2) {
|
|
23893
|
+
const triggerConfig = config2.interaction?.triggers?.[trigger];
|
|
23894
|
+
if (triggerConfig === undefined)
|
|
23895
|
+
return false;
|
|
23896
|
+
if (typeof triggerConfig === "boolean")
|
|
23897
|
+
return triggerConfig;
|
|
23898
|
+
return triggerConfig.enabled;
|
|
23899
|
+
}
|
|
23900
|
+
function getTriggerConfig(trigger, config2) {
|
|
23901
|
+
const metadata = TRIGGER_METADATA[trigger];
|
|
23902
|
+
const triggerConfig = config2.interaction?.triggers?.[trigger];
|
|
23903
|
+
const defaults = config2.interaction?.defaults ?? {
|
|
23904
|
+
timeout: 600000,
|
|
23905
|
+
fallback: "escalate"
|
|
23906
|
+
};
|
|
23907
|
+
let fallback = metadata.defaultFallback;
|
|
23908
|
+
let timeout = defaults.timeout;
|
|
23909
|
+
if (typeof triggerConfig === "object") {
|
|
23910
|
+
if (triggerConfig.fallback) {
|
|
23911
|
+
fallback = triggerConfig.fallback;
|
|
23912
|
+
}
|
|
23913
|
+
if (triggerConfig.timeout) {
|
|
23914
|
+
timeout = triggerConfig.timeout;
|
|
23915
|
+
}
|
|
23916
|
+
}
|
|
23917
|
+
return { fallback, timeout };
|
|
23918
|
+
}
|
|
23919
|
+
function substituteTemplate(template, context) {
|
|
23920
|
+
let result = template;
|
|
23921
|
+
for (const [key, value] of Object.entries(context)) {
|
|
23922
|
+
if (value !== undefined) {
|
|
23923
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(value));
|
|
23924
|
+
}
|
|
23925
|
+
}
|
|
23926
|
+
return result;
|
|
23927
|
+
}
|
|
23928
|
+
function createTriggerRequest(trigger, context, config2) {
|
|
23929
|
+
const metadata = TRIGGER_METADATA[trigger];
|
|
23930
|
+
const { fallback, timeout } = getTriggerConfig(trigger, config2);
|
|
23931
|
+
const summary = substituteTemplate(metadata.defaultSummary, context);
|
|
23932
|
+
const id = `trigger-${trigger}-${Date.now()}`;
|
|
23933
|
+
return {
|
|
23934
|
+
id,
|
|
23935
|
+
type: "confirm",
|
|
23936
|
+
featureName: context.featureName,
|
|
23937
|
+
storyId: context.storyId,
|
|
23938
|
+
stage: "custom",
|
|
23939
|
+
summary,
|
|
23940
|
+
fallback,
|
|
23941
|
+
timeout,
|
|
23942
|
+
createdAt: Date.now(),
|
|
23943
|
+
metadata: {
|
|
23944
|
+
trigger,
|
|
23945
|
+
safety: metadata.safety
|
|
23946
|
+
}
|
|
23947
|
+
};
|
|
23948
|
+
}
|
|
23949
|
+
async function executeTrigger(trigger, context, config2, chain) {
|
|
23950
|
+
const request = createTriggerRequest(trigger, context, config2);
|
|
23951
|
+
const response = await chain.prompt(request);
|
|
23952
|
+
return response;
|
|
23953
|
+
}
|
|
23954
|
+
async function checkSecurityReview(context, config2, chain) {
|
|
23955
|
+
if (!isTriggerEnabled("security-review", config2))
|
|
23956
|
+
return true;
|
|
23957
|
+
const response = await executeTrigger("security-review", context, config2, chain);
|
|
23958
|
+
return response.action !== "abort";
|
|
23959
|
+
}
|
|
23960
|
+
async function checkCostExceeded(context, config2, chain) {
|
|
23961
|
+
if (!isTriggerEnabled("cost-exceeded", config2))
|
|
23962
|
+
return true;
|
|
23963
|
+
const response = await executeTrigger("cost-exceeded", context, config2, chain);
|
|
23964
|
+
return response.action !== "abort";
|
|
23965
|
+
}
|
|
23966
|
+
async function checkMergeConflict(context, config2, chain) {
|
|
23967
|
+
if (!isTriggerEnabled("merge-conflict", config2))
|
|
23968
|
+
return true;
|
|
23969
|
+
const response = await executeTrigger("merge-conflict", context, config2, chain);
|
|
23970
|
+
return response.action !== "abort";
|
|
23971
|
+
}
|
|
23972
|
+
async function checkCostWarning(context, config2, chain) {
|
|
23973
|
+
if (!isTriggerEnabled("cost-warning", config2))
|
|
23974
|
+
return "continue";
|
|
23975
|
+
const response = await executeTrigger("cost-warning", context, config2, chain);
|
|
23976
|
+
return response.action === "approve" ? "escalate" : "continue";
|
|
23977
|
+
}
|
|
23978
|
+
async function checkPreMerge(context, config2, chain) {
|
|
23979
|
+
if (!isTriggerEnabled("pre-merge", config2))
|
|
23980
|
+
return true;
|
|
23981
|
+
const response = await executeTrigger("pre-merge", context, config2, chain);
|
|
23982
|
+
return response.action === "approve";
|
|
23983
|
+
}
|
|
23984
|
+
async function checkStoryAmbiguity(context, config2, chain) {
|
|
23985
|
+
if (!isTriggerEnabled("story-ambiguity", config2))
|
|
23986
|
+
return true;
|
|
23987
|
+
const response = await executeTrigger("story-ambiguity", context, config2, chain);
|
|
23988
|
+
return response.action === "approve";
|
|
23989
|
+
}
|
|
23990
|
+
async function checkReviewGate(context, config2, chain) {
|
|
23991
|
+
if (!isTriggerEnabled("review-gate", config2))
|
|
23992
|
+
return true;
|
|
23993
|
+
const response = await executeTrigger("review-gate", context, config2, chain);
|
|
23994
|
+
return response.action === "approve";
|
|
23995
|
+
}
|
|
23996
|
+
async function checkStoryOversized(context, config2, chain) {
|
|
23997
|
+
if (!isTriggerEnabled("story-oversized", config2))
|
|
23998
|
+
return "continue";
|
|
23999
|
+
try {
|
|
24000
|
+
const response = await executeTrigger("story-oversized", context, config2, chain);
|
|
24001
|
+
if (response.action === "approve")
|
|
24002
|
+
return "decompose";
|
|
24003
|
+
if (response.action === "skip")
|
|
24004
|
+
return "skip";
|
|
24005
|
+
return "continue";
|
|
24006
|
+
} catch {
|
|
24007
|
+
return "continue";
|
|
24008
|
+
}
|
|
24009
|
+
}
|
|
24010
|
+
var init_triggers = __esm(() => {
|
|
24011
|
+
init_types4();
|
|
24012
|
+
});
|
|
24013
|
+
|
|
23941
24014
|
// src/interaction/index.ts
|
|
23942
24015
|
var init_interaction = __esm(() => {
|
|
23943
24016
|
init_types4();
|
|
@@ -23948,6 +24021,7 @@ var init_interaction = __esm(() => {
|
|
|
23948
24021
|
init_auto();
|
|
23949
24022
|
init_triggers();
|
|
23950
24023
|
init_init();
|
|
24024
|
+
init_bridge_builder();
|
|
23951
24025
|
});
|
|
23952
24026
|
|
|
23953
24027
|
// src/pipeline/runner.ts
|
|
@@ -24406,10 +24480,11 @@ ${stderr}` };
|
|
|
24406
24480
|
if (workdirGroups.size === 0) {
|
|
24407
24481
|
workdirGroups.set("", { stories: [], criteria: [] });
|
|
24408
24482
|
}
|
|
24483
|
+
const featureName = ctx.prd.feature;
|
|
24409
24484
|
const testPaths = [];
|
|
24410
24485
|
for (const [workdir] of workdirGroups) {
|
|
24411
24486
|
const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
|
|
24412
|
-
const testPath = path5.join(packageDir, acceptanceTestFilename(language));
|
|
24487
|
+
const testPath = path5.join(packageDir, ".nax", "features", featureName, acceptanceTestFilename(language));
|
|
24413
24488
|
testPaths.push({ testPath, packageDir });
|
|
24414
24489
|
}
|
|
24415
24490
|
let totalCriteria = 0;
|
|
@@ -28357,7 +28432,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
28357
28432
|
}
|
|
28358
28433
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
28359
28434
|
}
|
|
28360
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName) {
|
|
28435
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge) {
|
|
28361
28436
|
const startTime = Date.now();
|
|
28362
28437
|
let prompt;
|
|
28363
28438
|
if (_sessionRunnerDeps.buildPrompt) {
|
|
@@ -28391,7 +28466,8 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
28391
28466
|
featureName,
|
|
28392
28467
|
storyId: story.id,
|
|
28393
28468
|
sessionRole: role,
|
|
28394
|
-
keepSessionOpen
|
|
28469
|
+
keepSessionOpen,
|
|
28470
|
+
interactionBridge
|
|
28395
28471
|
});
|
|
28396
28472
|
if (!result.success && result.pid) {
|
|
28397
28473
|
await _sessionRunnerDeps.cleanupProcessTree(result.pid);
|
|
@@ -28756,7 +28832,8 @@ async function runThreeSessionTdd(options) {
|
|
|
28756
28832
|
constitution,
|
|
28757
28833
|
dryRun = false,
|
|
28758
28834
|
lite = false,
|
|
28759
|
-
_recursionDepth = 0
|
|
28835
|
+
_recursionDepth = 0,
|
|
28836
|
+
interactionChain
|
|
28760
28837
|
} = options;
|
|
28761
28838
|
const logger = getLogger();
|
|
28762
28839
|
const MAX_RECURSION_DEPTH = 2;
|
|
@@ -28815,7 +28892,7 @@ async function runThreeSessionTdd(options) {
|
|
|
28815
28892
|
let session1;
|
|
28816
28893
|
if (!isRetry) {
|
|
28817
28894
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
28818
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
28895
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
|
|
28819
28896
|
sessions.push(session1);
|
|
28820
28897
|
}
|
|
28821
28898
|
if (session1 && !session1.success) {
|
|
@@ -28877,7 +28954,7 @@ async function runThreeSessionTdd(options) {
|
|
|
28877
28954
|
});
|
|
28878
28955
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
28879
28956
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
28880
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName);
|
|
28957
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
|
|
28881
28958
|
sessions.push(session2);
|
|
28882
28959
|
if (!session2.success) {
|
|
28883
28960
|
needsHumanReview = true;
|
|
@@ -28995,6 +29072,7 @@ async function runThreeSessionTdd(options) {
|
|
|
28995
29072
|
var init_orchestrator2 = __esm(() => {
|
|
28996
29073
|
init_config();
|
|
28997
29074
|
init_greenfield();
|
|
29075
|
+
init_bridge_builder();
|
|
28998
29076
|
init_logger2();
|
|
28999
29077
|
init_git();
|
|
29000
29078
|
init_verification();
|
|
@@ -29060,6 +29138,7 @@ var executionStage, _executionDeps;
|
|
|
29060
29138
|
var init_execution2 = __esm(() => {
|
|
29061
29139
|
init_agents();
|
|
29062
29140
|
init_config();
|
|
29141
|
+
init_bridge_builder();
|
|
29063
29142
|
init_triggers();
|
|
29064
29143
|
init_logger2();
|
|
29065
29144
|
init_tdd();
|
|
@@ -29094,7 +29173,8 @@ var init_execution2 = __esm(() => {
|
|
|
29094
29173
|
contextMarkdown: ctx.contextMarkdown,
|
|
29095
29174
|
constitution: ctx.constitution?.content,
|
|
29096
29175
|
dryRun: false,
|
|
29097
|
-
lite: isLiteMode
|
|
29176
|
+
lite: isLiteMode,
|
|
29177
|
+
interactionChain: ctx.interaction
|
|
29098
29178
|
});
|
|
29099
29179
|
ctx.agentResult = {
|
|
29100
29180
|
success: tddResult.success,
|
|
@@ -29168,34 +29248,11 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
29168
29248
|
pidRegistry: ctx.pidRegistry,
|
|
29169
29249
|
featureName: ctx.prd.feature,
|
|
29170
29250
|
storyId: ctx.story.id,
|
|
29171
|
-
interactionBridge: (
|
|
29172
|
-
|
|
29173
|
-
|
|
29174
|
-
|
|
29175
|
-
|
|
29176
|
-
return {
|
|
29177
|
-
detectQuestion: async (text) => QUESTION_PATTERNS.some((p) => p.test(text)),
|
|
29178
|
-
onQuestionDetected: async (text) => {
|
|
29179
|
-
const requestId = `ix-acp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
29180
|
-
await plugin.send({
|
|
29181
|
-
id: requestId,
|
|
29182
|
-
type: "input",
|
|
29183
|
-
featureName: ctx.prd.feature,
|
|
29184
|
-
storyId: ctx.story.id,
|
|
29185
|
-
stage: "execution",
|
|
29186
|
-
summary: text,
|
|
29187
|
-
fallback: "continue",
|
|
29188
|
-
createdAt: Date.now()
|
|
29189
|
-
});
|
|
29190
|
-
try {
|
|
29191
|
-
const response = await plugin.receive(requestId, 120000);
|
|
29192
|
-
return response.value ?? "continue";
|
|
29193
|
-
} catch {
|
|
29194
|
-
return "continue";
|
|
29195
|
-
}
|
|
29196
|
-
}
|
|
29197
|
-
};
|
|
29198
|
-
})()
|
|
29251
|
+
interactionBridge: buildInteractionBridge(ctx.interaction, {
|
|
29252
|
+
featureName: ctx.prd.feature,
|
|
29253
|
+
storyId: ctx.story.id,
|
|
29254
|
+
stage: "execution"
|
|
29255
|
+
})
|
|
29199
29256
|
});
|
|
29200
29257
|
ctx.agentResult = result;
|
|
29201
29258
|
await autoCommitIfDirty(storyWorkdir, "execution", "single-session", ctx.story.id);
|
|
@@ -32198,7 +32255,8 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
32198
32255
|
".nax/features/*/status.json",
|
|
32199
32256
|
".nax-pids",
|
|
32200
32257
|
".nax-wt/",
|
|
32201
|
-
"**/.nax-acceptance*"
|
|
32258
|
+
"**/.nax-acceptance*",
|
|
32259
|
+
"**/.nax/features/*/"
|
|
32202
32260
|
];
|
|
32203
32261
|
const missing = patterns.filter((pattern) => !content.includes(pattern));
|
|
32204
32262
|
const passed = missing.length === 0;
|
|
@@ -34481,7 +34539,7 @@ __export(exports_parallel_executor_rectify, {
|
|
|
34481
34539
|
});
|
|
34482
34540
|
import path15 from "path";
|
|
34483
34541
|
async function rectifyConflictedStory(options) {
|
|
34484
|
-
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter } = options;
|
|
34542
|
+
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter, agentGetFn } = options;
|
|
34485
34543
|
const logger = getSafeLogger();
|
|
34486
34544
|
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
34487
34545
|
try {
|
|
@@ -34513,7 +34571,8 @@ async function rectifyConflictedStory(options) {
|
|
|
34513
34571
|
hooks,
|
|
34514
34572
|
plugins: pluginRegistry,
|
|
34515
34573
|
storyStartTime: new Date().toISOString(),
|
|
34516
|
-
routing
|
|
34574
|
+
routing,
|
|
34575
|
+
agentGetFn
|
|
34517
34576
|
};
|
|
34518
34577
|
const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
|
|
34519
34578
|
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
@@ -34549,7 +34608,7 @@ var init_parallel_executor_rectify = __esm(() => {
|
|
|
34549
34608
|
// src/execution/parallel-executor-rectification-pass.ts
|
|
34550
34609
|
async function runRectificationPass(conflictedStories, options, prd, rectifyConflictedStory2) {
|
|
34551
34610
|
const logger = getSafeLogger();
|
|
34552
|
-
const { workdir, config: config2, hooks, pluginRegistry, eventEmitter } = options;
|
|
34611
|
+
const { workdir, config: config2, hooks, pluginRegistry, eventEmitter, agentGetFn } = options;
|
|
34553
34612
|
const rectify = rectifyConflictedStory2 || (async (opts) => {
|
|
34554
34613
|
const { rectifyConflictedStory: importedRectify } = await Promise.resolve().then(() => (init_parallel_executor_rectify(), exports_parallel_executor_rectify));
|
|
34555
34614
|
return importedRectify(opts);
|
|
@@ -34570,7 +34629,8 @@ async function runRectificationPass(conflictedStories, options, prd, rectifyConf
|
|
|
34570
34629
|
hooks,
|
|
34571
34630
|
pluginRegistry,
|
|
34572
34631
|
prd,
|
|
34573
|
-
eventEmitter
|
|
34632
|
+
eventEmitter,
|
|
34633
|
+
agentGetFn
|
|
34574
34634
|
});
|
|
34575
34635
|
additionalCost += result.cost;
|
|
34576
34636
|
if (result.success) {
|
|
@@ -68241,7 +68301,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
68241
68301
|
init_registry();
|
|
68242
68302
|
import { existsSync as existsSync11 } from "fs";
|
|
68243
68303
|
import { join as join11 } from "path";
|
|
68244
|
-
import { createInterface } from "readline";
|
|
68304
|
+
import { createInterface as createInterface2 } from "readline";
|
|
68245
68305
|
init_test_strategy();
|
|
68246
68306
|
|
|
68247
68307
|
// src/context/generator.ts
|
|
@@ -68779,6 +68839,8 @@ async function generateForPackage(packageDir, config2, dryRun = false, repoRoot)
|
|
|
68779
68839
|
|
|
68780
68840
|
// src/cli/plan.ts
|
|
68781
68841
|
init_pid_registry();
|
|
68842
|
+
init_bridge_builder();
|
|
68843
|
+
init_init();
|
|
68782
68844
|
init_logger2();
|
|
68783
68845
|
|
|
68784
68846
|
// src/prd/schema.ts
|
|
@@ -68907,11 +68969,19 @@ function validateStory(raw, index, allIds) {
|
|
|
68907
68969
|
...contextFiles.length > 0 ? { contextFiles } : {}
|
|
68908
68970
|
};
|
|
68909
68971
|
}
|
|
68972
|
+
function sanitizeInvalidEscapes(text) {
|
|
68973
|
+
let result = text.replace(/\\x([0-9a-fA-F]{1,2})/g, (_, hex3) => `\\u00${hex3.padStart(2, "0")}`);
|
|
68974
|
+
result = result.replace(/\\u([0-9a-fA-F]{1,3})(?![0-9a-fA-F])/g, (_, digits) => `\\u${digits.padStart(4, "0")}`);
|
|
68975
|
+
result = result.replace(/\\u(?![0-9a-fA-F])/g, "\\");
|
|
68976
|
+
result = result.replace(/\\([^"\\\/bfnrtu])/g, "$1");
|
|
68977
|
+
return result;
|
|
68978
|
+
}
|
|
68910
68979
|
function parseRawString(text) {
|
|
68911
68980
|
const extracted = extractJsonFromMarkdown(text);
|
|
68912
68981
|
const cleaned = stripTrailingCommas(extracted);
|
|
68982
|
+
const sanitized = sanitizeInvalidEscapes(cleaned);
|
|
68913
68983
|
try {
|
|
68914
|
-
return JSON.parse(
|
|
68984
|
+
return JSON.parse(sanitized);
|
|
68915
68985
|
} catch (err) {
|
|
68916
68986
|
const parseErr = err;
|
|
68917
68987
|
throw new Error(`[schema] Failed to parse JSON: ${parseErr.message}`, { cause: parseErr });
|
|
@@ -68965,7 +69035,8 @@ var _planDeps = {
|
|
|
68965
69035
|
existsSync: (path) => existsSync11(path),
|
|
68966
69036
|
discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages(repoRoot),
|
|
68967
69037
|
readPackageJsonAt: (path) => Bun.file(path).json().catch(() => null),
|
|
68968
|
-
createInteractionBridge: () => createCliInteractionBridge()
|
|
69038
|
+
createInteractionBridge: () => createCliInteractionBridge(),
|
|
69039
|
+
initInteractionChain: (cfg, headless) => initInteractionChain(cfg, headless)
|
|
68969
69040
|
};
|
|
68970
69041
|
async function planCommand(workdir, config2, options) {
|
|
68971
69042
|
const naxDir = join11(workdir, ".nax");
|
|
@@ -69028,7 +69099,13 @@ async function planCommand(workdir, config2, options) {
|
|
|
69028
69099
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
69029
69100
|
if (!adapter)
|
|
69030
69101
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
69031
|
-
const
|
|
69102
|
+
const headless = !process.stdin.isTTY;
|
|
69103
|
+
const interactionChain = config2 ? await _planDeps.initInteractionChain(config2, headless) : null;
|
|
69104
|
+
const configuredBridge = interactionChain ? buildInteractionBridge(interactionChain, {
|
|
69105
|
+
featureName: options.feature,
|
|
69106
|
+
stage: "pre-flight"
|
|
69107
|
+
}) : undefined;
|
|
69108
|
+
const interactionBridge = configuredBridge ?? _planDeps.createInteractionBridge();
|
|
69032
69109
|
const pidRegistry = new PidRegistry(workdir);
|
|
69033
69110
|
const resolvedPerm = resolvePermissions(config2, "plan");
|
|
69034
69111
|
const resolvedModel = config2?.plan?.model ?? "balanced";
|
|
@@ -69057,6 +69134,8 @@ async function planCommand(workdir, config2, options) {
|
|
|
69057
69134
|
});
|
|
69058
69135
|
} finally {
|
|
69059
69136
|
await pidRegistry.killAll().catch(() => {});
|
|
69137
|
+
if (interactionChain)
|
|
69138
|
+
await interactionChain.destroy().catch(() => {});
|
|
69060
69139
|
logger?.info("plan", "Interactive session ended", { durationMs: Date.now() - planStartTime });
|
|
69061
69140
|
}
|
|
69062
69141
|
if (!_planDeps.existsSync(outputPath)) {
|
|
@@ -69083,7 +69162,7 @@ function createCliInteractionBridge() {
|
|
|
69083
69162
|
\uD83E\uDD16 Agent: ${text}
|
|
69084
69163
|
You: `);
|
|
69085
69164
|
return new Promise((resolve4) => {
|
|
69086
|
-
const rl =
|
|
69165
|
+
const rl = createInterface2({ input: process.stdin, terminal: false });
|
|
69087
69166
|
rl.once("line", (line) => {
|
|
69088
69167
|
rl.close();
|
|
69089
69168
|
resolve4(line.trim());
|
|
@@ -72036,7 +72115,8 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
72036
72115
|
allStoryMetrics,
|
|
72037
72116
|
pluginRegistry,
|
|
72038
72117
|
formatterMode: options.formatterMode,
|
|
72039
|
-
headless: options.headless
|
|
72118
|
+
headless: options.headless,
|
|
72119
|
+
agentGetFn: options.agentGetFn
|
|
72040
72120
|
}, prd);
|
|
72041
72121
|
prd = parallelResult.prd;
|
|
72042
72122
|
totalCost = parallelResult.totalCost;
|