@nathapp/nax 0.54.4 → 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 +705 -676
- 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
|
}
|
|
@@ -23506,10 +23346,10 @@ class WebhookInteractionPlugin {
|
|
|
23506
23346
|
this.pendingResponses.delete(requestId);
|
|
23507
23347
|
return early;
|
|
23508
23348
|
}
|
|
23509
|
-
return new Promise((
|
|
23349
|
+
return new Promise((resolve4) => {
|
|
23510
23350
|
const timer = setTimeout(() => {
|
|
23511
23351
|
this.receiveCallbacks.delete(requestId);
|
|
23512
|
-
|
|
23352
|
+
resolve4({
|
|
23513
23353
|
requestId,
|
|
23514
23354
|
action: "skip",
|
|
23515
23355
|
respondedBy: "timeout",
|
|
@@ -23519,7 +23359,7 @@ class WebhookInteractionPlugin {
|
|
|
23519
23359
|
this.receiveCallbacks.set(requestId, (response) => {
|
|
23520
23360
|
clearTimeout(timer);
|
|
23521
23361
|
this.receiveCallbacks.delete(requestId);
|
|
23522
|
-
|
|
23362
|
+
resolve4(response);
|
|
23523
23363
|
});
|
|
23524
23364
|
});
|
|
23525
23365
|
}
|
|
@@ -23640,290 +23480,6 @@ var init_webhook = __esm(() => {
|
|
|
23640
23480
|
});
|
|
23641
23481
|
});
|
|
23642
23482
|
|
|
23643
|
-
// src/interaction/plugins/auto.ts
|
|
23644
|
-
class AutoInteractionPlugin {
|
|
23645
|
-
name = "auto";
|
|
23646
|
-
config = {};
|
|
23647
|
-
async init(config2) {
|
|
23648
|
-
const cfg = AutoConfigSchema.parse(config2);
|
|
23649
|
-
this.config = {
|
|
23650
|
-
model: cfg.model ?? "fast",
|
|
23651
|
-
confidenceThreshold: cfg.confidenceThreshold ?? 0.7,
|
|
23652
|
-
maxCostPerDecision: cfg.maxCostPerDecision ?? 0.01,
|
|
23653
|
-
naxConfig: cfg.naxConfig
|
|
23654
|
-
};
|
|
23655
|
-
}
|
|
23656
|
-
async destroy() {}
|
|
23657
|
-
async send(request) {}
|
|
23658
|
-
async receive(_requestId, _timeout = 60000) {
|
|
23659
|
-
throw new Error("Auto plugin requires full request context (not just requestId)");
|
|
23660
|
-
}
|
|
23661
|
-
async decide(request) {
|
|
23662
|
-
if (request.metadata?.trigger === "security-review") {
|
|
23663
|
-
return;
|
|
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
|
-
}
|
|
23693
|
-
}
|
|
23694
|
-
async callLlm(request) {
|
|
23695
|
-
const prompt = this.buildPrompt(request);
|
|
23696
|
-
const adapter = _autoPluginDeps.adapter;
|
|
23697
|
-
if (!adapter) {
|
|
23698
|
-
throw new Error("Auto plugin requires adapter to be injected via _autoPluginDeps.adapter");
|
|
23699
|
-
}
|
|
23700
|
-
let modelArg;
|
|
23701
|
-
if (this.config.naxConfig) {
|
|
23702
|
-
const modelTier = this.config.model ?? "fast";
|
|
23703
|
-
const modelEntry = this.config.naxConfig.models[modelTier];
|
|
23704
|
-
if (!modelEntry) {
|
|
23705
|
-
throw new Error(`Model tier "${modelTier}" not found in config.models`);
|
|
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"
|
|
23717
|
-
});
|
|
23718
|
-
return this.parseResponse(output);
|
|
23719
|
-
}
|
|
23720
|
-
buildPrompt(request) {
|
|
23721
|
-
let prompt = `You are an AI decision assistant for a code orchestration system. Given an interaction request, decide the best action.
|
|
23722
|
-
|
|
23723
|
-
## Interaction Request
|
|
23724
|
-
Type: ${request.type}
|
|
23725
|
-
Stage: ${request.stage}
|
|
23726
|
-
Feature: ${request.featureName}
|
|
23727
|
-
${request.storyId ? `Story: ${request.storyId}` : ""}
|
|
23728
|
-
|
|
23729
|
-
Summary: ${request.summary.replace(/`/g, "\\`").replace(/\$/g, "\\$")}
|
|
23730
|
-
${request.detail ? `
|
|
23731
|
-
Detail: ${request.detail.replace(/`/g, "\\`").replace(/\$/g, "\\$")}` : ""}
|
|
23732
|
-
`;
|
|
23733
|
-
if (request.options && request.options.length > 0) {
|
|
23734
|
-
prompt += `
|
|
23735
|
-
Options:
|
|
23736
|
-
`;
|
|
23737
|
-
for (const opt of request.options) {
|
|
23738
|
-
const desc = opt.description ? ` \u2014 ${opt.description}` : "";
|
|
23739
|
-
prompt += ` [${opt.key}] ${opt.label}${desc}
|
|
23740
|
-
`;
|
|
23741
|
-
}
|
|
23742
|
-
}
|
|
23743
|
-
prompt += `
|
|
23744
|
-
Fallback behavior on timeout: ${request.fallback}
|
|
23745
|
-
Safety tier: ${request.metadata?.safety ?? "unknown"}
|
|
23746
|
-
|
|
23747
|
-
## Available Actions
|
|
23748
|
-
- approve: Proceed with the operation
|
|
23749
|
-
- reject: Deny the operation
|
|
23750
|
-
- choose: Select an option (requires value field)
|
|
23751
|
-
- input: Provide text input (requires value field)
|
|
23752
|
-
- skip: Skip this interaction
|
|
23753
|
-
- abort: Abort execution
|
|
23754
|
-
|
|
23755
|
-
## Rules
|
|
23756
|
-
1. For "red" safety tier (security-review, cost-exceeded, merge-conflict): ALWAYS return confidence 0 to escalate to human
|
|
23757
|
-
2. For "yellow" safety tier (cost-warning, max-retries, pre-merge): High confidence (0.8+) ONLY if clearly safe
|
|
23758
|
-
3. For "green" safety tier (story-ambiguity, review-gate): Can approve with moderate confidence (0.6+)
|
|
23759
|
-
4. Default to the fallback behavior if unsure
|
|
23760
|
-
5. Never auto-approve security issues
|
|
23761
|
-
6. If the summary mentions "critical" or "security", confidence MUST be < 0.5
|
|
23762
|
-
|
|
23763
|
-
Respond with ONLY this JSON (no markdown, no explanation):
|
|
23764
|
-
{"action":"approve|reject|choose|input|skip|abort","value":"<optional>","confidence":0.0-1.0,"reasoning":"<one line>"}`;
|
|
23765
|
-
return prompt;
|
|
23766
|
-
}
|
|
23767
|
-
parseResponse(output) {
|
|
23768
|
-
let jsonText = output.trim();
|
|
23769
|
-
if (jsonText.startsWith("```")) {
|
|
23770
|
-
const lines = jsonText.split(`
|
|
23771
|
-
`);
|
|
23772
|
-
jsonText = lines.slice(1, -1).join(`
|
|
23773
|
-
`).trim();
|
|
23774
|
-
}
|
|
23775
|
-
if (jsonText.startsWith("json")) {
|
|
23776
|
-
jsonText = jsonText.slice(4).trim();
|
|
23777
|
-
}
|
|
23778
|
-
const parsed = JSON.parse(jsonText);
|
|
23779
|
-
if (!parsed.action || parsed.confidence === undefined || !parsed.reasoning) {
|
|
23780
|
-
throw new Error(`Invalid LLM response: ${jsonText}`);
|
|
23781
|
-
}
|
|
23782
|
-
if (parsed.confidence < 0 || parsed.confidence > 1) {
|
|
23783
|
-
throw new Error(`Invalid confidence: ${parsed.confidence} (must be 0-1)`);
|
|
23784
|
-
}
|
|
23785
|
-
return parsed;
|
|
23786
|
-
}
|
|
23787
|
-
}
|
|
23788
|
-
var AutoConfigSchema, _autoPluginDeps;
|
|
23789
|
-
var init_auto = __esm(() => {
|
|
23790
|
-
init_zod();
|
|
23791
|
-
init_config();
|
|
23792
|
-
AutoConfigSchema = exports_external.object({
|
|
23793
|
-
model: exports_external.string().optional(),
|
|
23794
|
-
confidenceThreshold: exports_external.number().min(0).max(1).optional(),
|
|
23795
|
-
maxCostPerDecision: exports_external.number().positive().optional(),
|
|
23796
|
-
naxConfig: exports_external.any().optional()
|
|
23797
|
-
});
|
|
23798
|
-
_autoPluginDeps = {
|
|
23799
|
-
adapter: null,
|
|
23800
|
-
callLlm: null
|
|
23801
|
-
};
|
|
23802
|
-
});
|
|
23803
|
-
|
|
23804
|
-
// src/interaction/triggers.ts
|
|
23805
|
-
function isTriggerEnabled(trigger, config2) {
|
|
23806
|
-
const triggerConfig = config2.interaction?.triggers?.[trigger];
|
|
23807
|
-
if (triggerConfig === undefined)
|
|
23808
|
-
return false;
|
|
23809
|
-
if (typeof triggerConfig === "boolean")
|
|
23810
|
-
return triggerConfig;
|
|
23811
|
-
return triggerConfig.enabled;
|
|
23812
|
-
}
|
|
23813
|
-
function getTriggerConfig(trigger, config2) {
|
|
23814
|
-
const metadata = TRIGGER_METADATA[trigger];
|
|
23815
|
-
const triggerConfig = config2.interaction?.triggers?.[trigger];
|
|
23816
|
-
const defaults = config2.interaction?.defaults ?? {
|
|
23817
|
-
timeout: 600000,
|
|
23818
|
-
fallback: "escalate"
|
|
23819
|
-
};
|
|
23820
|
-
let fallback = metadata.defaultFallback;
|
|
23821
|
-
let timeout = defaults.timeout;
|
|
23822
|
-
if (typeof triggerConfig === "object") {
|
|
23823
|
-
if (triggerConfig.fallback) {
|
|
23824
|
-
fallback = triggerConfig.fallback;
|
|
23825
|
-
}
|
|
23826
|
-
if (triggerConfig.timeout) {
|
|
23827
|
-
timeout = triggerConfig.timeout;
|
|
23828
|
-
}
|
|
23829
|
-
}
|
|
23830
|
-
return { fallback, timeout };
|
|
23831
|
-
}
|
|
23832
|
-
function substituteTemplate(template, context) {
|
|
23833
|
-
let result = template;
|
|
23834
|
-
for (const [key, value] of Object.entries(context)) {
|
|
23835
|
-
if (value !== undefined) {
|
|
23836
|
-
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(value));
|
|
23837
|
-
}
|
|
23838
|
-
}
|
|
23839
|
-
return result;
|
|
23840
|
-
}
|
|
23841
|
-
function createTriggerRequest(trigger, context, config2) {
|
|
23842
|
-
const metadata = TRIGGER_METADATA[trigger];
|
|
23843
|
-
const { fallback, timeout } = getTriggerConfig(trigger, config2);
|
|
23844
|
-
const summary = substituteTemplate(metadata.defaultSummary, context);
|
|
23845
|
-
const id = `trigger-${trigger}-${Date.now()}`;
|
|
23846
|
-
return {
|
|
23847
|
-
id,
|
|
23848
|
-
type: "confirm",
|
|
23849
|
-
featureName: context.featureName,
|
|
23850
|
-
storyId: context.storyId,
|
|
23851
|
-
stage: "custom",
|
|
23852
|
-
summary,
|
|
23853
|
-
fallback,
|
|
23854
|
-
timeout,
|
|
23855
|
-
createdAt: Date.now(),
|
|
23856
|
-
metadata: {
|
|
23857
|
-
trigger,
|
|
23858
|
-
safety: metadata.safety
|
|
23859
|
-
}
|
|
23860
|
-
};
|
|
23861
|
-
}
|
|
23862
|
-
async function executeTrigger(trigger, context, config2, chain) {
|
|
23863
|
-
const request = createTriggerRequest(trigger, context, config2);
|
|
23864
|
-
const response = await chain.prompt(request);
|
|
23865
|
-
return response;
|
|
23866
|
-
}
|
|
23867
|
-
async function checkSecurityReview(context, config2, chain) {
|
|
23868
|
-
if (!isTriggerEnabled("security-review", config2))
|
|
23869
|
-
return true;
|
|
23870
|
-
const response = await executeTrigger("security-review", context, config2, chain);
|
|
23871
|
-
return response.action !== "abort";
|
|
23872
|
-
}
|
|
23873
|
-
async function checkCostExceeded(context, config2, chain) {
|
|
23874
|
-
if (!isTriggerEnabled("cost-exceeded", config2))
|
|
23875
|
-
return true;
|
|
23876
|
-
const response = await executeTrigger("cost-exceeded", context, config2, chain);
|
|
23877
|
-
return response.action !== "abort";
|
|
23878
|
-
}
|
|
23879
|
-
async function checkMergeConflict(context, config2, chain) {
|
|
23880
|
-
if (!isTriggerEnabled("merge-conflict", config2))
|
|
23881
|
-
return true;
|
|
23882
|
-
const response = await executeTrigger("merge-conflict", context, config2, chain);
|
|
23883
|
-
return response.action !== "abort";
|
|
23884
|
-
}
|
|
23885
|
-
async function checkCostWarning(context, config2, chain) {
|
|
23886
|
-
if (!isTriggerEnabled("cost-warning", config2))
|
|
23887
|
-
return "continue";
|
|
23888
|
-
const response = await executeTrigger("cost-warning", context, config2, chain);
|
|
23889
|
-
return response.action === "approve" ? "escalate" : "continue";
|
|
23890
|
-
}
|
|
23891
|
-
async function checkPreMerge(context, config2, chain) {
|
|
23892
|
-
if (!isTriggerEnabled("pre-merge", config2))
|
|
23893
|
-
return true;
|
|
23894
|
-
const response = await executeTrigger("pre-merge", context, config2, chain);
|
|
23895
|
-
return response.action === "approve";
|
|
23896
|
-
}
|
|
23897
|
-
async function checkStoryAmbiguity(context, config2, chain) {
|
|
23898
|
-
if (!isTriggerEnabled("story-ambiguity", config2))
|
|
23899
|
-
return true;
|
|
23900
|
-
const response = await executeTrigger("story-ambiguity", context, config2, chain);
|
|
23901
|
-
return response.action === "approve";
|
|
23902
|
-
}
|
|
23903
|
-
async function checkReviewGate(context, config2, chain) {
|
|
23904
|
-
if (!isTriggerEnabled("review-gate", config2))
|
|
23905
|
-
return true;
|
|
23906
|
-
const response = await executeTrigger("review-gate", context, config2, chain);
|
|
23907
|
-
return response.action === "approve";
|
|
23908
|
-
}
|
|
23909
|
-
async function checkStoryOversized(context, config2, chain) {
|
|
23910
|
-
if (!isTriggerEnabled("story-oversized", config2))
|
|
23911
|
-
return "continue";
|
|
23912
|
-
try {
|
|
23913
|
-
const response = await executeTrigger("story-oversized", context, config2, chain);
|
|
23914
|
-
if (response.action === "approve")
|
|
23915
|
-
return "decompose";
|
|
23916
|
-
if (response.action === "skip")
|
|
23917
|
-
return "skip";
|
|
23918
|
-
return "continue";
|
|
23919
|
-
} catch {
|
|
23920
|
-
return "continue";
|
|
23921
|
-
}
|
|
23922
|
-
}
|
|
23923
|
-
var init_triggers = __esm(() => {
|
|
23924
|
-
init_types4();
|
|
23925
|
-
});
|
|
23926
|
-
|
|
23927
23483
|
// src/interaction/init.ts
|
|
23928
23484
|
function createInteractionPlugin(pluginName) {
|
|
23929
23485
|
switch (pluginName) {
|
|
@@ -23978,6 +23534,483 @@ var init_init = __esm(() => {
|
|
|
23978
23534
|
init_webhook();
|
|
23979
23535
|
});
|
|
23980
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
|
+
|
|
23981
24014
|
// src/interaction/index.ts
|
|
23982
24015
|
var init_interaction = __esm(() => {
|
|
23983
24016
|
init_types4();
|
|
@@ -23988,6 +24021,7 @@ var init_interaction = __esm(() => {
|
|
|
23988
24021
|
init_auto();
|
|
23989
24022
|
init_triggers();
|
|
23990
24023
|
init_init();
|
|
24024
|
+
init_bridge_builder();
|
|
23991
24025
|
});
|
|
23992
24026
|
|
|
23993
24027
|
// src/pipeline/runner.ts
|
|
@@ -24446,10 +24480,11 @@ ${stderr}` };
|
|
|
24446
24480
|
if (workdirGroups.size === 0) {
|
|
24447
24481
|
workdirGroups.set("", { stories: [], criteria: [] });
|
|
24448
24482
|
}
|
|
24483
|
+
const featureName = ctx.prd.feature;
|
|
24449
24484
|
const testPaths = [];
|
|
24450
24485
|
for (const [workdir] of workdirGroups) {
|
|
24451
24486
|
const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
|
|
24452
|
-
const testPath = path5.join(packageDir, acceptanceTestFilename(language));
|
|
24487
|
+
const testPath = path5.join(packageDir, ".nax", "features", featureName, acceptanceTestFilename(language));
|
|
24453
24488
|
testPaths.push({ testPath, packageDir });
|
|
24454
24489
|
}
|
|
24455
24490
|
let totalCriteria = 0;
|
|
@@ -28397,7 +28432,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
28397
28432
|
}
|
|
28398
28433
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
28399
28434
|
}
|
|
28400
|
-
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) {
|
|
28401
28436
|
const startTime = Date.now();
|
|
28402
28437
|
let prompt;
|
|
28403
28438
|
if (_sessionRunnerDeps.buildPrompt) {
|
|
@@ -28431,7 +28466,8 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
28431
28466
|
featureName,
|
|
28432
28467
|
storyId: story.id,
|
|
28433
28468
|
sessionRole: role,
|
|
28434
|
-
keepSessionOpen
|
|
28469
|
+
keepSessionOpen,
|
|
28470
|
+
interactionBridge
|
|
28435
28471
|
});
|
|
28436
28472
|
if (!result.success && result.pid) {
|
|
28437
28473
|
await _sessionRunnerDeps.cleanupProcessTree(result.pid);
|
|
@@ -28796,7 +28832,8 @@ async function runThreeSessionTdd(options) {
|
|
|
28796
28832
|
constitution,
|
|
28797
28833
|
dryRun = false,
|
|
28798
28834
|
lite = false,
|
|
28799
|
-
_recursionDepth = 0
|
|
28835
|
+
_recursionDepth = 0,
|
|
28836
|
+
interactionChain
|
|
28800
28837
|
} = options;
|
|
28801
28838
|
const logger = getLogger();
|
|
28802
28839
|
const MAX_RECURSION_DEPTH = 2;
|
|
@@ -28855,7 +28892,7 @@ async function runThreeSessionTdd(options) {
|
|
|
28855
28892
|
let session1;
|
|
28856
28893
|
if (!isRetry) {
|
|
28857
28894
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
28858
|
-
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" }));
|
|
28859
28896
|
sessions.push(session1);
|
|
28860
28897
|
}
|
|
28861
28898
|
if (session1 && !session1.success) {
|
|
@@ -28917,7 +28954,7 @@ async function runThreeSessionTdd(options) {
|
|
|
28917
28954
|
});
|
|
28918
28955
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
28919
28956
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
28920
|
-
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" }));
|
|
28921
28958
|
sessions.push(session2);
|
|
28922
28959
|
if (!session2.success) {
|
|
28923
28960
|
needsHumanReview = true;
|
|
@@ -29035,6 +29072,7 @@ async function runThreeSessionTdd(options) {
|
|
|
29035
29072
|
var init_orchestrator2 = __esm(() => {
|
|
29036
29073
|
init_config();
|
|
29037
29074
|
init_greenfield();
|
|
29075
|
+
init_bridge_builder();
|
|
29038
29076
|
init_logger2();
|
|
29039
29077
|
init_git();
|
|
29040
29078
|
init_verification();
|
|
@@ -29100,6 +29138,7 @@ var executionStage, _executionDeps;
|
|
|
29100
29138
|
var init_execution2 = __esm(() => {
|
|
29101
29139
|
init_agents();
|
|
29102
29140
|
init_config();
|
|
29141
|
+
init_bridge_builder();
|
|
29103
29142
|
init_triggers();
|
|
29104
29143
|
init_logger2();
|
|
29105
29144
|
init_tdd();
|
|
@@ -29134,7 +29173,8 @@ var init_execution2 = __esm(() => {
|
|
|
29134
29173
|
contextMarkdown: ctx.contextMarkdown,
|
|
29135
29174
|
constitution: ctx.constitution?.content,
|
|
29136
29175
|
dryRun: false,
|
|
29137
|
-
lite: isLiteMode
|
|
29176
|
+
lite: isLiteMode,
|
|
29177
|
+
interactionChain: ctx.interaction
|
|
29138
29178
|
});
|
|
29139
29179
|
ctx.agentResult = {
|
|
29140
29180
|
success: tddResult.success,
|
|
@@ -29208,34 +29248,11 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
|
29208
29248
|
pidRegistry: ctx.pidRegistry,
|
|
29209
29249
|
featureName: ctx.prd.feature,
|
|
29210
29250
|
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
|
-
})()
|
|
29251
|
+
interactionBridge: buildInteractionBridge(ctx.interaction, {
|
|
29252
|
+
featureName: ctx.prd.feature,
|
|
29253
|
+
storyId: ctx.story.id,
|
|
29254
|
+
stage: "execution"
|
|
29255
|
+
})
|
|
29239
29256
|
});
|
|
29240
29257
|
ctx.agentResult = result;
|
|
29241
29258
|
await autoCommitIfDirty(storyWorkdir, "execution", "single-session", ctx.story.id);
|
|
@@ -32238,7 +32255,8 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
32238
32255
|
".nax/features/*/status.json",
|
|
32239
32256
|
".nax-pids",
|
|
32240
32257
|
".nax-wt/",
|
|
32241
|
-
"**/.nax-acceptance*"
|
|
32258
|
+
"**/.nax-acceptance*",
|
|
32259
|
+
"**/.nax/features/*/"
|
|
32242
32260
|
];
|
|
32243
32261
|
const missing = patterns.filter((pattern) => !content.includes(pattern));
|
|
32244
32262
|
const passed = missing.length === 0;
|
|
@@ -68283,7 +68301,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
68283
68301
|
init_registry();
|
|
68284
68302
|
import { existsSync as existsSync11 } from "fs";
|
|
68285
68303
|
import { join as join11 } from "path";
|
|
68286
|
-
import { createInterface } from "readline";
|
|
68304
|
+
import { createInterface as createInterface2 } from "readline";
|
|
68287
68305
|
init_test_strategy();
|
|
68288
68306
|
|
|
68289
68307
|
// src/context/generator.ts
|
|
@@ -68821,6 +68839,8 @@ async function generateForPackage(packageDir, config2, dryRun = false, repoRoot)
|
|
|
68821
68839
|
|
|
68822
68840
|
// src/cli/plan.ts
|
|
68823
68841
|
init_pid_registry();
|
|
68842
|
+
init_bridge_builder();
|
|
68843
|
+
init_init();
|
|
68824
68844
|
init_logger2();
|
|
68825
68845
|
|
|
68826
68846
|
// src/prd/schema.ts
|
|
@@ -69015,7 +69035,8 @@ var _planDeps = {
|
|
|
69015
69035
|
existsSync: (path) => existsSync11(path),
|
|
69016
69036
|
discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages(repoRoot),
|
|
69017
69037
|
readPackageJsonAt: (path) => Bun.file(path).json().catch(() => null),
|
|
69018
|
-
createInteractionBridge: () => createCliInteractionBridge()
|
|
69038
|
+
createInteractionBridge: () => createCliInteractionBridge(),
|
|
69039
|
+
initInteractionChain: (cfg, headless) => initInteractionChain(cfg, headless)
|
|
69019
69040
|
};
|
|
69020
69041
|
async function planCommand(workdir, config2, options) {
|
|
69021
69042
|
const naxDir = join11(workdir, ".nax");
|
|
@@ -69078,7 +69099,13 @@ async function planCommand(workdir, config2, options) {
|
|
|
69078
69099
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
69079
69100
|
if (!adapter)
|
|
69080
69101
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
69081
|
-
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();
|
|
69082
69109
|
const pidRegistry = new PidRegistry(workdir);
|
|
69083
69110
|
const resolvedPerm = resolvePermissions(config2, "plan");
|
|
69084
69111
|
const resolvedModel = config2?.plan?.model ?? "balanced";
|
|
@@ -69107,6 +69134,8 @@ async function planCommand(workdir, config2, options) {
|
|
|
69107
69134
|
});
|
|
69108
69135
|
} finally {
|
|
69109
69136
|
await pidRegistry.killAll().catch(() => {});
|
|
69137
|
+
if (interactionChain)
|
|
69138
|
+
await interactionChain.destroy().catch(() => {});
|
|
69110
69139
|
logger?.info("plan", "Interactive session ended", { durationMs: Date.now() - planStartTime });
|
|
69111
69140
|
}
|
|
69112
69141
|
if (!_planDeps.existsSync(outputPath)) {
|
|
@@ -69133,7 +69162,7 @@ function createCliInteractionBridge() {
|
|
|
69133
69162
|
\uD83E\uDD16 Agent: ${text}
|
|
69134
69163
|
You: `);
|
|
69135
69164
|
return new Promise((resolve4) => {
|
|
69136
|
-
const rl =
|
|
69165
|
+
const rl = createInterface2({ input: process.stdin, terminal: false });
|
|
69137
69166
|
rl.once("line", (line) => {
|
|
69138
69167
|
rl.close();
|
|
69139
69168
|
resolve4(line.trim());
|