@nathapp/nax 0.54.7 → 0.54.9
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 +1994 -2159
- package/package.json +3 -1
package/dist/nax.js
CHANGED
|
@@ -3004,7 +3004,7 @@ ${JSON.stringify(entry.data, null, 2)}`;
|
|
|
3004
3004
|
}
|
|
3005
3005
|
close() {}
|
|
3006
3006
|
}
|
|
3007
|
-
function initLogger(options) {
|
|
3007
|
+
function initLogger(options = { level: "warn" }) {
|
|
3008
3008
|
if (instance) {
|
|
3009
3009
|
throw new Error("Logger already initialized. Call getLogger() to access existing instance.");
|
|
3010
3010
|
}
|
|
@@ -17805,7 +17805,7 @@ var init_zod = __esm(() => {
|
|
|
17805
17805
|
});
|
|
17806
17806
|
|
|
17807
17807
|
// src/config/schemas.ts
|
|
17808
|
-
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, ProjectProfileSchema, NaxConfigSchema;
|
|
17808
|
+
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, DecomposeConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, NaxConfigSchema;
|
|
17809
17809
|
var init_schemas3 = __esm(() => {
|
|
17810
17810
|
init_zod();
|
|
17811
17811
|
TokenPricingSchema = exports_external.object({
|
|
@@ -17982,7 +17982,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17982
17982
|
SemanticReviewConfigSchema = exports_external.object({
|
|
17983
17983
|
modelTier: ModelTierSchema.default("balanced"),
|
|
17984
17984
|
rules: exports_external.array(exports_external.string()).default([]),
|
|
17985
|
-
timeoutMs: exports_external.number().int().positive().default(600000)
|
|
17985
|
+
timeoutMs: exports_external.number().int().positive().default(600000),
|
|
17986
|
+
excludePatterns: exports_external.array(exports_external.string()).default([":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"])
|
|
17986
17987
|
});
|
|
17987
17988
|
ReviewConfigSchema = exports_external.object({
|
|
17988
17989
|
enabled: exports_external.boolean(),
|
|
@@ -18106,6 +18107,10 @@ var init_schemas3 = __esm(() => {
|
|
|
18106
18107
|
testFramework: exports_external.string().optional(),
|
|
18107
18108
|
lintTool: exports_external.string().optional()
|
|
18108
18109
|
});
|
|
18110
|
+
VALID_AGENT_TYPES = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
18111
|
+
GenerateConfigSchema = exports_external.object({
|
|
18112
|
+
agents: exports_external.array(exports_external.enum(VALID_AGENT_TYPES)).optional()
|
|
18113
|
+
});
|
|
18109
18114
|
NaxConfigSchema = exports_external.object({
|
|
18110
18115
|
version: exports_external.number(),
|
|
18111
18116
|
models: ModelMapSchema,
|
|
@@ -18129,6 +18134,7 @@ var init_schemas3 = __esm(() => {
|
|
|
18129
18134
|
precheck: PrecheckConfigSchema.optional(),
|
|
18130
18135
|
prompts: PromptsConfigSchema.optional(),
|
|
18131
18136
|
decompose: DecomposeConfigSchema.optional(),
|
|
18137
|
+
generate: GenerateConfigSchema.optional(),
|
|
18132
18138
|
project: ProjectProfileSchema.optional()
|
|
18133
18139
|
}).refine((data) => data.version === 1, {
|
|
18134
18140
|
message: "Invalid version: expected 1",
|
|
@@ -18278,7 +18284,8 @@ var init_defaults = __esm(() => {
|
|
|
18278
18284
|
semantic: {
|
|
18279
18285
|
modelTier: "balanced",
|
|
18280
18286
|
rules: [],
|
|
18281
|
-
timeoutMs: 600000
|
|
18287
|
+
timeoutMs: 600000,
|
|
18288
|
+
excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
|
|
18282
18289
|
}
|
|
18283
18290
|
},
|
|
18284
18291
|
plan: {
|
|
@@ -19927,14 +19934,13 @@ function extractQuestion(output) {
|
|
|
19927
19934
|
const text = output.trim();
|
|
19928
19935
|
if (!text)
|
|
19929
19936
|
return null;
|
|
19930
|
-
const
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
|
|
19935
|
-
|
|
19936
|
-
|
|
19937
|
-
const lower = text.toLowerCase();
|
|
19937
|
+
const lines = text.split(`
|
|
19938
|
+
`).filter((l) => l.trim().length > 0);
|
|
19939
|
+
const lastLine = lines.at(-1)?.trim() ?? "";
|
|
19940
|
+
if (lastLine.endsWith("?") && lastLine.length > 10) {
|
|
19941
|
+
return lastLine;
|
|
19942
|
+
}
|
|
19943
|
+
const lower = lastLine.toLowerCase();
|
|
19938
19944
|
const markers = [
|
|
19939
19945
|
"please confirm",
|
|
19940
19946
|
"please specify",
|
|
@@ -19946,7 +19952,7 @@ function extractQuestion(output) {
|
|
|
19946
19952
|
];
|
|
19947
19953
|
for (const marker of markers) {
|
|
19948
19954
|
if (lower.includes(marker)) {
|
|
19949
|
-
return
|
|
19955
|
+
return lastLine;
|
|
19950
19956
|
}
|
|
19951
19957
|
}
|
|
19952
19958
|
return null;
|
|
@@ -20133,7 +20139,19 @@ class AcpAgentAdapter {
|
|
|
20133
20139
|
};
|
|
20134
20140
|
}
|
|
20135
20141
|
async complete(prompt, _options) {
|
|
20136
|
-
|
|
20142
|
+
let model = _options?.model;
|
|
20143
|
+
if (!model && _options?.modelTier && _options?.config?.models) {
|
|
20144
|
+
const tier = _options.modelTier;
|
|
20145
|
+
const { resolveModel: resolveModel2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
20146
|
+
const models = _options.config.models;
|
|
20147
|
+
const entry = models[tier] ?? models.balanced;
|
|
20148
|
+
if (entry) {
|
|
20149
|
+
try {
|
|
20150
|
+
model = resolveModel2(entry).model;
|
|
20151
|
+
} catch {}
|
|
20152
|
+
}
|
|
20153
|
+
}
|
|
20154
|
+
model ??= "default";
|
|
20137
20155
|
const timeoutMs = _options?.timeoutMs ?? 120000;
|
|
20138
20156
|
const permissionMode = resolvePermissions(_options?.config, "complete").mode;
|
|
20139
20157
|
const workdir = _options?.workdir;
|
|
@@ -21088,7 +21106,7 @@ var init_paths = () => {};
|
|
|
21088
21106
|
|
|
21089
21107
|
// src/config/loader.ts
|
|
21090
21108
|
import { existsSync as existsSync5 } from "fs";
|
|
21091
|
-
import { dirname, join as join6, resolve as resolve3 } from "path";
|
|
21109
|
+
import { basename, dirname, join as join6, resolve as resolve3 } from "path";
|
|
21092
21110
|
function globalConfigPath() {
|
|
21093
21111
|
return join6(globalConfigDir(), "config.json");
|
|
21094
21112
|
}
|
|
@@ -21141,14 +21159,14 @@ function applyBatchModeCompat(conf) {
|
|
|
21141
21159
|
}
|
|
21142
21160
|
return conf;
|
|
21143
21161
|
}
|
|
21144
|
-
async function loadConfig(
|
|
21162
|
+
async function loadConfig(startDir, cliOverrides) {
|
|
21145
21163
|
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
21146
21164
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
21147
21165
|
if (globalConfRaw) {
|
|
21148
21166
|
const globalConf = applyBatchModeCompat(applyRemovedStrategyCompat(globalConfRaw));
|
|
21149
21167
|
rawConfig = deepMergeConfig(rawConfig, globalConf);
|
|
21150
21168
|
}
|
|
21151
|
-
const projDir =
|
|
21169
|
+
const projDir = startDir ? basename(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
21152
21170
|
if (projDir) {
|
|
21153
21171
|
const projConf = await loadJsonFile(join6(projDir, "config.json"), "config");
|
|
21154
21172
|
if (projConf) {
|
|
@@ -22352,7 +22370,7 @@ var package_default;
|
|
|
22352
22370
|
var init_package = __esm(() => {
|
|
22353
22371
|
package_default = {
|
|
22354
22372
|
name: "@nathapp/nax",
|
|
22355
|
-
version: "0.54.
|
|
22373
|
+
version: "0.54.9",
|
|
22356
22374
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22357
22375
|
type: "module",
|
|
22358
22376
|
bin: {
|
|
@@ -22364,8 +22382,10 @@ var init_package = __esm(() => {
|
|
|
22364
22382
|
build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
|
|
22365
22383
|
typecheck: "bun x tsc --noEmit",
|
|
22366
22384
|
lint: "bun x biome check src/ bin/",
|
|
22385
|
+
"lint:fix": "bun x biome lint --write src/ bin/",
|
|
22367
22386
|
release: "bun scripts/release.ts",
|
|
22368
22387
|
test: "bun test test/unit/ --timeout=60000 && bun test test/integration/ --timeout=60000 && bun test test/ui/ --timeout=60000",
|
|
22388
|
+
"test:bail": "bun test test/unit/ --timeout=60000 --bail && bun test test/integration/ --timeout=60000 --bail && bun test test/ui/ --timeout=60000 --bail",
|
|
22369
22389
|
"test:watch": "bun test --watch",
|
|
22370
22390
|
"test:unit": "bun test ./test/unit/ --timeout=60000",
|
|
22371
22391
|
"test:integration": "bun test ./test/integration/ --timeout=60000",
|
|
@@ -22429,8 +22449,8 @@ var init_version = __esm(() => {
|
|
|
22429
22449
|
NAX_VERSION = package_default.version;
|
|
22430
22450
|
NAX_COMMIT = (() => {
|
|
22431
22451
|
try {
|
|
22432
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22433
|
-
return "
|
|
22452
|
+
if (/^[0-9a-f]{6,10}$/.test("3852fac"))
|
|
22453
|
+
return "3852fac";
|
|
22434
22454
|
} catch {}
|
|
22435
22455
|
try {
|
|
22436
22456
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -22478,7 +22498,7 @@ function buildInteractionBridge(chain, context, timeoutMs = DEFAULT_INTERACTION_
|
|
|
22478
22498
|
}
|
|
22479
22499
|
var QUESTION_PATTERNS, DEFAULT_INTERACTION_TIMEOUT_MS = 120000;
|
|
22480
22500
|
var init_bridge_builder = __esm(() => {
|
|
22481
|
-
QUESTION_PATTERNS = [
|
|
22501
|
+
QUESTION_PATTERNS = [/\?\s*$/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
|
|
22482
22502
|
});
|
|
22483
22503
|
|
|
22484
22504
|
// src/interaction/chain.ts
|
|
@@ -24705,6 +24725,36 @@ async function captureGitRef(workdir) {
|
|
|
24705
24725
|
return;
|
|
24706
24726
|
}
|
|
24707
24727
|
}
|
|
24728
|
+
async function isGitRefValid(workdir, ref) {
|
|
24729
|
+
try {
|
|
24730
|
+
const { exitCode } = await gitWithTimeout(["cat-file", "-e", `${ref}^{commit}`], workdir);
|
|
24731
|
+
return exitCode === 0;
|
|
24732
|
+
} catch {
|
|
24733
|
+
return false;
|
|
24734
|
+
}
|
|
24735
|
+
}
|
|
24736
|
+
async function getMergeBase(workdir) {
|
|
24737
|
+
for (const branch of ["origin/main", "origin/master"]) {
|
|
24738
|
+
try {
|
|
24739
|
+
const { stdout, exitCode } = await gitWithTimeout(["merge-base", "HEAD", branch], workdir);
|
|
24740
|
+
if (exitCode === 0) {
|
|
24741
|
+
const sha = stdout.trim();
|
|
24742
|
+
if (sha)
|
|
24743
|
+
return sha;
|
|
24744
|
+
}
|
|
24745
|
+
} catch {}
|
|
24746
|
+
}
|
|
24747
|
+
try {
|
|
24748
|
+
const { stdout, exitCode } = await gitWithTimeout(["rev-list", "--max-parents=0", "HEAD"], workdir);
|
|
24749
|
+
if (exitCode === 0) {
|
|
24750
|
+
const sha = stdout.trim().split(`
|
|
24751
|
+
`)[0];
|
|
24752
|
+
if (sha)
|
|
24753
|
+
return sha;
|
|
24754
|
+
}
|
|
24755
|
+
} catch {}
|
|
24756
|
+
return;
|
|
24757
|
+
}
|
|
24708
24758
|
async function hasCommitsForStory(workdir, storyId, maxCommits = 20) {
|
|
24709
24759
|
try {
|
|
24710
24760
|
const { stdout, exitCode } = await gitWithTimeout(["log", `-${maxCommits}`, "--oneline", "--grep", storyId], workdir);
|
|
@@ -24853,9 +24903,13 @@ var init_language_commands = __esm(() => {
|
|
|
24853
24903
|
|
|
24854
24904
|
// src/review/semantic.ts
|
|
24855
24905
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
24856
|
-
async function collectDiff(workdir, storyGitRef) {
|
|
24906
|
+
async function collectDiff(workdir, storyGitRef, excludePatterns) {
|
|
24907
|
+
const cmd = ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`];
|
|
24908
|
+
if (excludePatterns.length > 0) {
|
|
24909
|
+
cmd.push("--", ".", ...excludePatterns);
|
|
24910
|
+
}
|
|
24857
24911
|
const proc = _semanticDeps.spawn({
|
|
24858
|
-
cmd
|
|
24912
|
+
cmd,
|
|
24859
24913
|
cwd: workdir,
|
|
24860
24914
|
stdout: "pipe",
|
|
24861
24915
|
stderr: "pipe"
|
|
@@ -24901,15 +24955,13 @@ ${stat}
|
|
|
24901
24955
|
}
|
|
24902
24956
|
function buildPrompt(story, semanticConfig, diff) {
|
|
24903
24957
|
const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
24904
|
-
`);
|
|
24905
|
-
const defaultRulesText = DEFAULT_RULES.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
24906
24958
|
`);
|
|
24907
24959
|
const customRulesSection = semanticConfig.rules.length > 0 ? `
|
|
24908
|
-
##
|
|
24960
|
+
## Additional Review Rules
|
|
24909
24961
|
${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
24910
24962
|
`)}
|
|
24911
24963
|
` : "";
|
|
24912
|
-
return `You are a code reviewer.
|
|
24964
|
+
return `You are a semantic code reviewer. Your job is to verify that the implementation satisfies the story's acceptance criteria (ACs). You are NOT a linter or style checker \u2014 lint, typecheck, and convention checks are handled separately.
|
|
24913
24965
|
|
|
24914
24966
|
## Story: ${story.title}
|
|
24915
24967
|
|
|
@@ -24918,27 +24970,29 @@ ${story.description}
|
|
|
24918
24970
|
|
|
24919
24971
|
### Acceptance Criteria
|
|
24920
24972
|
${acList}
|
|
24921
|
-
|
|
24922
|
-
## Review Rules
|
|
24923
|
-
|
|
24924
|
-
### Default Rules
|
|
24925
|
-
${defaultRulesText}
|
|
24926
24973
|
${customRulesSection}
|
|
24927
|
-
## Git Diff
|
|
24974
|
+
## Git Diff (production code only \u2014 test files excluded)
|
|
24928
24975
|
|
|
24929
24976
|
\`\`\`diff
|
|
24930
24977
|
${diff}\`\`\`
|
|
24931
24978
|
|
|
24932
24979
|
## Instructions
|
|
24933
24980
|
|
|
24934
|
-
|
|
24935
|
-
|
|
24981
|
+
For each acceptance criterion, verify the diff implements it correctly. Flag issues only when:
|
|
24982
|
+
1. An AC is not implemented or partially implemented
|
|
24983
|
+
2. The implementation contradicts what the AC specifies
|
|
24984
|
+
3. New code has dead paths that will never execute (stubs, noops, unreachable branches)
|
|
24985
|
+
4. New code is not wired into callers/exports (written but never used)
|
|
24986
|
+
|
|
24987
|
+
Do NOT flag: style issues, naming conventions, import ordering, file length, or anything lint handles.
|
|
24988
|
+
|
|
24989
|
+
Respond in JSON format:
|
|
24936
24990
|
{
|
|
24937
24991
|
"passed": boolean,
|
|
24938
24992
|
"findings": [
|
|
24939
24993
|
{
|
|
24940
24994
|
"severity": "error" | "warn" | "info",
|
|
24941
|
-
"file": "path/to/file
|
|
24995
|
+
"file": "path/to/file",
|
|
24942
24996
|
"line": 42,
|
|
24943
24997
|
"issue": "description of the issue",
|
|
24944
24998
|
"suggestion": "how to fix it"
|
|
@@ -24946,7 +25000,7 @@ Format:
|
|
|
24946
25000
|
]
|
|
24947
25001
|
}
|
|
24948
25002
|
|
|
24949
|
-
If
|
|
25003
|
+
If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }.`;
|
|
24950
25004
|
}
|
|
24951
25005
|
function parseLLMResponse(raw) {
|
|
24952
25006
|
try {
|
|
@@ -24989,10 +25043,24 @@ function toReviewFindings(findings) {
|
|
|
24989
25043
|
source: "semantic-review"
|
|
24990
25044
|
}));
|
|
24991
25045
|
}
|
|
24992
|
-
async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver) {
|
|
25046
|
+
async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig) {
|
|
24993
25047
|
const startTime = Date.now();
|
|
24994
25048
|
const logger = getSafeLogger();
|
|
24995
|
-
|
|
25049
|
+
let effectiveRef;
|
|
25050
|
+
if (storyGitRef && await _semanticDeps.isGitRefValid(workdir, storyGitRef)) {
|
|
25051
|
+
effectiveRef = storyGitRef;
|
|
25052
|
+
} else {
|
|
25053
|
+
const fallback = await _semanticDeps.getMergeBase(workdir);
|
|
25054
|
+
if (fallback) {
|
|
25055
|
+
logger?.info("review", "storyGitRef missing or invalid \u2014 using merge-base fallback", {
|
|
25056
|
+
storyId: story.id,
|
|
25057
|
+
storyGitRef,
|
|
25058
|
+
fallback
|
|
25059
|
+
});
|
|
25060
|
+
effectiveRef = fallback;
|
|
25061
|
+
}
|
|
25062
|
+
}
|
|
25063
|
+
if (!effectiveRef) {
|
|
24996
25064
|
return {
|
|
24997
25065
|
check: "semantic",
|
|
24998
25066
|
success: true,
|
|
@@ -25002,10 +25070,14 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
25002
25070
|
durationMs: Date.now() - startTime
|
|
25003
25071
|
};
|
|
25004
25072
|
}
|
|
25005
|
-
logger?.info("review", "Running semantic check", {
|
|
25006
|
-
|
|
25073
|
+
logger?.info("review", "Running semantic check", {
|
|
25074
|
+
storyId: story.id,
|
|
25075
|
+
modelTier: semanticConfig.modelTier,
|
|
25076
|
+
configProvided: !!naxConfig
|
|
25077
|
+
});
|
|
25078
|
+
const rawDiff = await collectDiff(workdir, effectiveRef, semanticConfig.excludePatterns);
|
|
25007
25079
|
const needsTruncation = rawDiff.length > DIFF_CAP_BYTES;
|
|
25008
|
-
const stat = needsTruncation ? await collectDiffStat(workdir,
|
|
25080
|
+
const stat = needsTruncation ? await collectDiffStat(workdir, effectiveRef) : undefined;
|
|
25009
25081
|
const diff = truncateDiff(rawDiff, stat);
|
|
25010
25082
|
const agent = modelResolver(semanticConfig.modelTier);
|
|
25011
25083
|
if (!agent) {
|
|
@@ -25027,7 +25099,9 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
25027
25099
|
rawResponse = await agent.complete(prompt, {
|
|
25028
25100
|
sessionName: `nax-semantic-${story.id}`,
|
|
25029
25101
|
workdir,
|
|
25030
|
-
timeoutMs: semanticConfig.timeoutMs
|
|
25102
|
+
timeoutMs: semanticConfig.timeoutMs,
|
|
25103
|
+
modelTier: semanticConfig.modelTier,
|
|
25104
|
+
config: naxConfig
|
|
25031
25105
|
});
|
|
25032
25106
|
} catch (err) {
|
|
25033
25107
|
logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
|
|
@@ -25108,19 +25182,15 @@ ${formatFindings(parsed.findings)}`;
|
|
|
25108
25182
|
durationMs
|
|
25109
25183
|
};
|
|
25110
25184
|
}
|
|
25111
|
-
var _semanticDeps, DIFF_CAP_BYTES =
|
|
25185
|
+
var _semanticDeps, DIFF_CAP_BYTES = 51200;
|
|
25112
25186
|
var init_semantic = __esm(() => {
|
|
25113
25187
|
init_logger2();
|
|
25188
|
+
init_git();
|
|
25114
25189
|
_semanticDeps = {
|
|
25115
|
-
spawn: spawn2
|
|
25116
|
-
|
|
25117
|
-
|
|
25118
|
-
|
|
25119
|
-
"No placeholder values (TODO, FIXME, hardcoded dummy data)",
|
|
25120
|
-
"No unrelated changes outside the story scope",
|
|
25121
|
-
"All new code is properly wired into callers and exports",
|
|
25122
|
-
"No silent error swallowing (catch blocks that discard errors without logging)"
|
|
25123
|
-
];
|
|
25190
|
+
spawn: spawn2,
|
|
25191
|
+
isGitRefValid,
|
|
25192
|
+
getMergeBase
|
|
25193
|
+
};
|
|
25124
25194
|
});
|
|
25125
25195
|
|
|
25126
25196
|
// src/review/runner.ts
|
|
@@ -25267,7 +25337,7 @@ async function getUncommittedFilesImpl(workdir) {
|
|
|
25267
25337
|
return [];
|
|
25268
25338
|
}
|
|
25269
25339
|
}
|
|
25270
|
-
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver) {
|
|
25340
|
+
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig) {
|
|
25271
25341
|
const startTime = Date.now();
|
|
25272
25342
|
const logger = getSafeLogger();
|
|
25273
25343
|
const checks3 = [];
|
|
@@ -25317,9 +25387,10 @@ Stage and commit these files before running review.`
|
|
|
25317
25387
|
const semanticCfg = config2.semantic ?? {
|
|
25318
25388
|
modelTier: "balanced",
|
|
25319
25389
|
rules: [],
|
|
25320
|
-
timeoutMs: 600000
|
|
25390
|
+
timeoutMs: 600000,
|
|
25391
|
+
excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
|
|
25321
25392
|
};
|
|
25322
|
-
const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null));
|
|
25393
|
+
const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null), naxConfig);
|
|
25323
25394
|
checks3.push(result2);
|
|
25324
25395
|
if (!result2.success && !firstFailure) {
|
|
25325
25396
|
firstFailure = `${checkName} failed`;
|
|
@@ -25401,9 +25472,9 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
25401
25472
|
}
|
|
25402
25473
|
|
|
25403
25474
|
class ReviewOrchestrator {
|
|
25404
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver) {
|
|
25475
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig) {
|
|
25405
25476
|
const logger = getSafeLogger();
|
|
25406
|
-
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver);
|
|
25477
|
+
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig);
|
|
25407
25478
|
if (!builtIn.success) {
|
|
25408
25479
|
return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
|
|
25409
25480
|
}
|
|
@@ -25502,7 +25573,7 @@ var init_review = __esm(() => {
|
|
|
25502
25573
|
title: ctx.story.title,
|
|
25503
25574
|
description: ctx.story.description,
|
|
25504
25575
|
acceptanceCriteria: ctx.story.acceptanceCriteria
|
|
25505
|
-
}, modelResolver);
|
|
25576
|
+
}, modelResolver, ctx.config);
|
|
25506
25577
|
ctx.reviewResult = result.builtIn;
|
|
25507
25578
|
if (!result.success) {
|
|
25508
25579
|
const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
@@ -26242,8 +26313,8 @@ function extractTestStructure(source) {
|
|
|
26242
26313
|
function deriveTestPatterns(contextFiles) {
|
|
26243
26314
|
const patterns = new Set;
|
|
26244
26315
|
for (const filePath of contextFiles) {
|
|
26245
|
-
const
|
|
26246
|
-
const basenameNoExt =
|
|
26316
|
+
const basename2 = path6.basename(filePath);
|
|
26317
|
+
const basenameNoExt = basename2.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
26247
26318
|
patterns.add(`${basenameNoExt}.test.ts`);
|
|
26248
26319
|
patterns.add(`${basenameNoExt}.test.js`);
|
|
26249
26320
|
patterns.add(`${basenameNoExt}.test.tsx`);
|
|
@@ -26296,8 +26367,8 @@ async function scanTestFiles(options) {
|
|
|
26296
26367
|
const files = [];
|
|
26297
26368
|
for await (const filePath of glob.scan({ cwd: scanDir, absolute: false })) {
|
|
26298
26369
|
if (allowedBasenames !== null) {
|
|
26299
|
-
const
|
|
26300
|
-
if (!allowedBasenames.has(
|
|
26370
|
+
const basename2 = path6.basename(filePath);
|
|
26371
|
+
if (!allowedBasenames.has(basename2)) {
|
|
26301
26372
|
continue;
|
|
26302
26373
|
}
|
|
26303
26374
|
}
|
|
@@ -30286,8 +30357,8 @@ function extractSearchTerms(sourceFile) {
|
|
|
30286
30357
|
const withoutSrc = sourceFile.replace(/^src\//, "");
|
|
30287
30358
|
const withoutExt = withoutSrc.replace(/\.ts$/, "");
|
|
30288
30359
|
const parts = withoutExt.split("/");
|
|
30289
|
-
const
|
|
30290
|
-
return [`/${
|
|
30360
|
+
const basename2 = parts[parts.length - 1];
|
|
30361
|
+
return [`/${basename2}`, withoutExt];
|
|
30291
30362
|
}
|
|
30292
30363
|
async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
30293
30364
|
if (sourceFiles.length === 0 || testFilePatterns.length === 0)
|
|
@@ -31060,7 +31131,7 @@ __export(exports_init_context, {
|
|
|
31060
31131
|
});
|
|
31061
31132
|
import { existsSync as existsSync20 } from "fs";
|
|
31062
31133
|
import { mkdir } from "fs/promises";
|
|
31063
|
-
import { basename as
|
|
31134
|
+
import { basename as basename3, join as join30 } from "path";
|
|
31064
31135
|
async function findFiles(dir, maxFiles = 200) {
|
|
31065
31136
|
try {
|
|
31066
31137
|
const proc = Bun.spawnSync([
|
|
@@ -31148,7 +31219,7 @@ async function scanProject(projectRoot) {
|
|
|
31148
31219
|
const readmeSnippet = await readReadmeSnippet(projectRoot);
|
|
31149
31220
|
const entryPoints = await detectEntryPoints(projectRoot);
|
|
31150
31221
|
const configFiles = await detectConfigFiles(projectRoot);
|
|
31151
|
-
const projectName = packageManifest?.name ||
|
|
31222
|
+
const projectName = packageManifest?.name || basename3(projectRoot);
|
|
31152
31223
|
return {
|
|
31153
31224
|
projectName,
|
|
31154
31225
|
fileTree,
|
|
@@ -31730,11 +31801,11 @@ function getSafeLogger6() {
|
|
|
31730
31801
|
return getSafeLogger();
|
|
31731
31802
|
}
|
|
31732
31803
|
function extractPluginName(pluginPath) {
|
|
31733
|
-
const
|
|
31734
|
-
if (
|
|
31804
|
+
const basename5 = path12.basename(pluginPath);
|
|
31805
|
+
if (basename5 === "index.ts" || basename5 === "index.js" || basename5 === "index.mjs") {
|
|
31735
31806
|
return path12.basename(path12.dirname(pluginPath));
|
|
31736
31807
|
}
|
|
31737
|
-
return
|
|
31808
|
+
return basename5.replace(/\.(ts|js|mjs)$/, "");
|
|
31738
31809
|
}
|
|
31739
31810
|
async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
|
|
31740
31811
|
const loadedPlugins = [];
|
|
@@ -33939,2119 +34010,1712 @@ var init_headless_formatter = __esm(() => {
|
|
|
33939
34010
|
init_version();
|
|
33940
34011
|
});
|
|
33941
34012
|
|
|
33942
|
-
// src/
|
|
33943
|
-
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
|
|
33947
|
-
|
|
33948
|
-
|
|
33949
|
-
|
|
33950
|
-
|
|
33951
|
-
|
|
33952
|
-
|
|
33953
|
-
|
|
33954
|
-
const worktreePath = join47(projectRoot, ".nax-wt", storyId);
|
|
33955
|
-
const branchName = `nax/${storyId}`;
|
|
33956
|
-
try {
|
|
33957
|
-
const proc = _managerDeps.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
33958
|
-
cwd: projectRoot,
|
|
33959
|
-
stdout: "pipe",
|
|
33960
|
-
stderr: "pipe"
|
|
33961
|
-
});
|
|
33962
|
-
const exitCode = await proc.exited;
|
|
33963
|
-
if (exitCode !== 0) {
|
|
33964
|
-
const stderr = await new Response(proc.stderr).text();
|
|
33965
|
-
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
33966
|
-
}
|
|
33967
|
-
} catch (error48) {
|
|
33968
|
-
if (error48 instanceof Error) {
|
|
33969
|
-
if (error48.message.includes("not a git repository")) {
|
|
33970
|
-
throw new Error(`Not a git repository: ${projectRoot}`);
|
|
33971
|
-
}
|
|
33972
|
-
if (error48.message.includes("already exists")) {
|
|
33973
|
-
throw new Error(`Worktree for story ${storyId} already exists at ${worktreePath}`);
|
|
33974
|
-
}
|
|
33975
|
-
throw error48;
|
|
33976
|
-
}
|
|
33977
|
-
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
33978
|
-
}
|
|
33979
|
-
const nodeModulesSource = join47(projectRoot, "node_modules");
|
|
33980
|
-
if (existsSync32(nodeModulesSource)) {
|
|
33981
|
-
const nodeModulesTarget = join47(worktreePath, "node_modules");
|
|
33982
|
-
try {
|
|
33983
|
-
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
33984
|
-
} catch (error48) {
|
|
33985
|
-
await this.remove(projectRoot, storyId);
|
|
33986
|
-
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
33987
|
-
}
|
|
33988
|
-
}
|
|
33989
|
-
const envSource = join47(projectRoot, ".env");
|
|
33990
|
-
if (existsSync32(envSource)) {
|
|
33991
|
-
const envTarget = join47(worktreePath, ".env");
|
|
34013
|
+
// src/pipeline/subscribers/events-writer.ts
|
|
34014
|
+
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
34015
|
+
import { homedir as homedir5 } from "os";
|
|
34016
|
+
import { basename as basename6, join as join47 } from "path";
|
|
34017
|
+
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
34018
|
+
const logger = getSafeLogger();
|
|
34019
|
+
const project = basename6(workdir);
|
|
34020
|
+
const eventsDir = join47(homedir5(), ".nax", "events", project);
|
|
34021
|
+
const eventsFile = join47(eventsDir, "events.jsonl");
|
|
34022
|
+
let dirReady = false;
|
|
34023
|
+
const write = (line) => {
|
|
34024
|
+
return (async () => {
|
|
33992
34025
|
try {
|
|
33993
|
-
|
|
33994
|
-
|
|
33995
|
-
|
|
33996
|
-
throw new Error(`Failed to symlink .env: ${errorMessage(error48)}`);
|
|
33997
|
-
}
|
|
33998
|
-
}
|
|
33999
|
-
}
|
|
34000
|
-
async remove(projectRoot, storyId) {
|
|
34001
|
-
validateStoryId(storyId);
|
|
34002
|
-
const worktreePath = join47(projectRoot, ".nax-wt", storyId);
|
|
34003
|
-
const branchName = `nax/${storyId}`;
|
|
34004
|
-
try {
|
|
34005
|
-
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
34006
|
-
cwd: projectRoot,
|
|
34007
|
-
stdout: "pipe",
|
|
34008
|
-
stderr: "pipe"
|
|
34009
|
-
});
|
|
34010
|
-
const exitCode = await proc.exited;
|
|
34011
|
-
if (exitCode !== 0) {
|
|
34012
|
-
const stderr = await new Response(proc.stderr).text();
|
|
34013
|
-
if (stderr.includes("not found") || stderr.includes("does not exist") || stderr.includes("no such worktree") || stderr.includes("is not a working tree")) {
|
|
34014
|
-
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
34015
|
-
}
|
|
34016
|
-
throw new Error(`Failed to remove worktree: ${stderr || "unknown error"}`);
|
|
34017
|
-
}
|
|
34018
|
-
} catch (error48) {
|
|
34019
|
-
if (error48 instanceof Error) {
|
|
34020
|
-
throw error48;
|
|
34021
|
-
}
|
|
34022
|
-
throw new Error(`Failed to remove worktree: ${String(error48)}`);
|
|
34023
|
-
}
|
|
34024
|
-
try {
|
|
34025
|
-
const proc = _managerDeps.spawn(["git", "branch", "-D", branchName], {
|
|
34026
|
-
cwd: projectRoot,
|
|
34027
|
-
stdout: "pipe",
|
|
34028
|
-
stderr: "pipe"
|
|
34029
|
-
});
|
|
34030
|
-
const exitCode = await proc.exited;
|
|
34031
|
-
if (exitCode !== 0) {
|
|
34032
|
-
const stderr = await new Response(proc.stderr).text();
|
|
34033
|
-
if (!stderr.includes("not found")) {
|
|
34034
|
-
const logger = getSafeLogger();
|
|
34035
|
-
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
34026
|
+
if (!dirReady) {
|
|
34027
|
+
await mkdir2(eventsDir, { recursive: true });
|
|
34028
|
+
dirReady = true;
|
|
34036
34029
|
}
|
|
34037
|
-
|
|
34038
|
-
} catch (error48) {
|
|
34039
|
-
const logger = getSafeLogger();
|
|
34040
|
-
logger?.warn("worktree", `Failed to delete branch ${branchName}`, {
|
|
34041
|
-
error: errorMessage(error48)
|
|
34042
|
-
});
|
|
34043
|
-
}
|
|
34044
|
-
}
|
|
34045
|
-
async list(projectRoot) {
|
|
34046
|
-
try {
|
|
34047
|
-
const proc = _managerDeps.spawn(["git", "worktree", "list", "--porcelain"], {
|
|
34048
|
-
cwd: projectRoot,
|
|
34049
|
-
stdout: "pipe",
|
|
34050
|
-
stderr: "pipe"
|
|
34051
|
-
});
|
|
34052
|
-
const exitCode = await proc.exited;
|
|
34053
|
-
if (exitCode !== 0) {
|
|
34054
|
-
const stderr = await new Response(proc.stderr).text();
|
|
34055
|
-
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
34056
|
-
}
|
|
34057
|
-
const stdout = await new Response(proc.stdout).text();
|
|
34058
|
-
return this.parseWorktreeList(stdout);
|
|
34059
|
-
} catch (error48) {
|
|
34060
|
-
if (error48 instanceof Error) {
|
|
34061
|
-
throw error48;
|
|
34062
|
-
}
|
|
34063
|
-
throw new Error(`Failed to list worktrees: ${String(error48)}`);
|
|
34064
|
-
}
|
|
34065
|
-
}
|
|
34066
|
-
parseWorktreeList(output) {
|
|
34067
|
-
const worktrees = [];
|
|
34068
|
-
const lines = output.trim().split(`
|
|
34030
|
+
await appendFile2(eventsFile, `${JSON.stringify(line)}
|
|
34069
34031
|
`);
|
|
34070
|
-
|
|
34071
|
-
|
|
34072
|
-
|
|
34073
|
-
|
|
34074
|
-
|
|
34075
|
-
currentWorktree.branch = line.substring("branch ".length).replace("refs/heads/", "");
|
|
34076
|
-
} else if (line === "") {
|
|
34077
|
-
if (currentWorktree.path && currentWorktree.branch) {
|
|
34078
|
-
worktrees.push(currentWorktree);
|
|
34079
|
-
}
|
|
34080
|
-
currentWorktree = {};
|
|
34032
|
+
} catch (err) {
|
|
34033
|
+
logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
|
|
34034
|
+
event: line.event,
|
|
34035
|
+
error: String(err)
|
|
34036
|
+
});
|
|
34081
34037
|
}
|
|
34082
|
-
}
|
|
34083
|
-
|
|
34084
|
-
|
|
34085
|
-
|
|
34086
|
-
return
|
|
34087
|
-
}
|
|
34038
|
+
})();
|
|
34039
|
+
};
|
|
34040
|
+
const unsubs = [];
|
|
34041
|
+
unsubs.push(bus.on("run:started", (_ev) => {
|
|
34042
|
+
return write({ ts: new Date().toISOString(), event: "run:started", runId, feature, project });
|
|
34043
|
+
}));
|
|
34044
|
+
unsubs.push(bus.on("story:started", (ev) => {
|
|
34045
|
+
return write({
|
|
34046
|
+
ts: new Date().toISOString(),
|
|
34047
|
+
event: "story:started",
|
|
34048
|
+
runId,
|
|
34049
|
+
feature,
|
|
34050
|
+
project,
|
|
34051
|
+
storyId: ev.storyId
|
|
34052
|
+
});
|
|
34053
|
+
}));
|
|
34054
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34055
|
+
return write({
|
|
34056
|
+
ts: new Date().toISOString(),
|
|
34057
|
+
event: "story:completed",
|
|
34058
|
+
runId,
|
|
34059
|
+
feature,
|
|
34060
|
+
project,
|
|
34061
|
+
storyId: ev.storyId
|
|
34062
|
+
});
|
|
34063
|
+
}));
|
|
34064
|
+
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
34065
|
+
return write({
|
|
34066
|
+
ts: new Date().toISOString(),
|
|
34067
|
+
event: "story:decomposed",
|
|
34068
|
+
runId,
|
|
34069
|
+
feature,
|
|
34070
|
+
project,
|
|
34071
|
+
storyId: ev.storyId,
|
|
34072
|
+
data: { subStoryCount: ev.subStoryCount }
|
|
34073
|
+
});
|
|
34074
|
+
}));
|
|
34075
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34076
|
+
return write({
|
|
34077
|
+
ts: new Date().toISOString(),
|
|
34078
|
+
event: "story:failed",
|
|
34079
|
+
runId,
|
|
34080
|
+
feature,
|
|
34081
|
+
project,
|
|
34082
|
+
storyId: ev.storyId
|
|
34083
|
+
});
|
|
34084
|
+
}));
|
|
34085
|
+
unsubs.push(bus.on("run:completed", (_ev) => {
|
|
34086
|
+
return write({ ts: new Date().toISOString(), event: "on-complete", runId, feature, project });
|
|
34087
|
+
}));
|
|
34088
|
+
unsubs.push(bus.on("run:paused", (ev) => {
|
|
34089
|
+
return write({
|
|
34090
|
+
ts: new Date().toISOString(),
|
|
34091
|
+
event: "run:paused",
|
|
34092
|
+
runId,
|
|
34093
|
+
feature,
|
|
34094
|
+
project,
|
|
34095
|
+
...ev.storyId !== undefined && { storyId: ev.storyId }
|
|
34096
|
+
});
|
|
34097
|
+
}));
|
|
34098
|
+
return () => {
|
|
34099
|
+
for (const u of unsubs)
|
|
34100
|
+
u();
|
|
34101
|
+
};
|
|
34088
34102
|
}
|
|
34089
|
-
var
|
|
34090
|
-
var init_manager = __esm(() => {
|
|
34103
|
+
var init_events_writer = __esm(() => {
|
|
34091
34104
|
init_logger2();
|
|
34092
|
-
init_bun_deps();
|
|
34093
|
-
_managerDeps = {
|
|
34094
|
-
spawn
|
|
34095
|
-
};
|
|
34096
34105
|
});
|
|
34097
34106
|
|
|
34098
|
-
// src/
|
|
34099
|
-
|
|
34100
|
-
|
|
34101
|
-
|
|
34102
|
-
|
|
34107
|
+
// src/pipeline/subscribers/hooks.ts
|
|
34108
|
+
function wireHooks(bus, hooks, workdir, feature) {
|
|
34109
|
+
const logger = getSafeLogger();
|
|
34110
|
+
const safe = (name, fn) => {
|
|
34111
|
+
return fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) })).catch(() => {});
|
|
34112
|
+
};
|
|
34113
|
+
const unsubs = [];
|
|
34114
|
+
unsubs.push(bus.on("run:started", (ev) => {
|
|
34115
|
+
return safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
|
|
34116
|
+
}));
|
|
34117
|
+
unsubs.push(bus.on("story:started", (ev) => {
|
|
34118
|
+
return safe("on-story-start", () => fireHook(hooks, "on-story-start", hookCtx(feature, { storyId: ev.storyId, model: ev.modelTier, agent: ev.agent }), workdir));
|
|
34119
|
+
}));
|
|
34120
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34121
|
+
return safe("on-story-complete", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "passed", cost: ev.cost }), workdir));
|
|
34122
|
+
}));
|
|
34123
|
+
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
34124
|
+
return safe("on-story-complete (decomposed)", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "decomposed", subStoryCount: ev.subStoryCount }), workdir));
|
|
34125
|
+
}));
|
|
34126
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34127
|
+
return safe("on-story-fail", () => fireHook(hooks, "on-story-fail", hookCtx(feature, { storyId: ev.storyId, status: "failed", reason: ev.reason }), workdir));
|
|
34128
|
+
}));
|
|
34129
|
+
unsubs.push(bus.on("story:paused", (ev) => {
|
|
34130
|
+
return safe("on-pause (story)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
34131
|
+
}));
|
|
34132
|
+
unsubs.push(bus.on("run:paused", (ev) => {
|
|
34133
|
+
return safe("on-pause (run)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
34134
|
+
}));
|
|
34135
|
+
unsubs.push(bus.on("run:completed", (ev) => {
|
|
34136
|
+
return safe("on-complete", () => fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir));
|
|
34137
|
+
}));
|
|
34138
|
+
unsubs.push(bus.on("run:resumed", (ev) => {
|
|
34139
|
+
return safe("on-resume", () => fireHook(hooks, "on-resume", hookCtx(feature, { status: "running" }), workdir));
|
|
34140
|
+
}));
|
|
34141
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34142
|
+
return safe("on-session-end (completed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "passed" }), workdir));
|
|
34143
|
+
}));
|
|
34144
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34145
|
+
return safe("on-session-end (failed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "failed" }), workdir));
|
|
34146
|
+
}));
|
|
34147
|
+
unsubs.push(bus.on("run:errored", (ev) => {
|
|
34148
|
+
return safe("on-error", () => fireHook(hooks, "on-error", hookCtx(feature, { reason: ev.reason }), workdir));
|
|
34149
|
+
}));
|
|
34150
|
+
return () => {
|
|
34151
|
+
for (const u of unsubs)
|
|
34152
|
+
u();
|
|
34153
|
+
};
|
|
34154
|
+
}
|
|
34155
|
+
var init_hooks2 = __esm(() => {
|
|
34156
|
+
init_story_context();
|
|
34157
|
+
init_hooks();
|
|
34158
|
+
init_logger2();
|
|
34103
34159
|
});
|
|
34104
34160
|
|
|
34105
|
-
|
|
34106
|
-
|
|
34107
|
-
|
|
34108
|
-
|
|
34109
|
-
|
|
34110
|
-
|
|
34111
|
-
|
|
34112
|
-
|
|
34113
|
-
|
|
34114
|
-
|
|
34115
|
-
|
|
34116
|
-
|
|
34161
|
+
// src/pipeline/subscribers/interaction.ts
|
|
34162
|
+
function wireInteraction(bus, interactionChain, config2) {
|
|
34163
|
+
const logger = getSafeLogger();
|
|
34164
|
+
const unsubs = [];
|
|
34165
|
+
if (interactionChain && isTriggerEnabled("human-review", config2)) {
|
|
34166
|
+
unsubs.push(bus.on("human-review:requested", (ev) => {
|
|
34167
|
+
executeTrigger("human-review", {
|
|
34168
|
+
featureName: ev.feature ?? "",
|
|
34169
|
+
storyId: ev.storyId,
|
|
34170
|
+
iteration: ev.attempts ?? 0,
|
|
34171
|
+
reason: ev.reason
|
|
34172
|
+
}, config2, interactionChain).catch((err) => {
|
|
34173
|
+
logger?.warn("interaction-subscriber", "human-review trigger failed", {
|
|
34174
|
+
storyId: ev.storyId,
|
|
34175
|
+
error: String(err)
|
|
34176
|
+
});
|
|
34117
34177
|
});
|
|
34118
|
-
|
|
34119
|
-
|
|
34120
|
-
|
|
34121
|
-
|
|
34122
|
-
|
|
34123
|
-
|
|
34124
|
-
|
|
34125
|
-
|
|
34126
|
-
|
|
34127
|
-
|
|
34178
|
+
}));
|
|
34179
|
+
}
|
|
34180
|
+
if (interactionChain && isTriggerEnabled("max-retries", config2)) {
|
|
34181
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34182
|
+
if (!ev.countsTowardEscalation) {
|
|
34183
|
+
return;
|
|
34184
|
+
}
|
|
34185
|
+
executeTrigger("max-retries", {
|
|
34186
|
+
featureName: ev.feature ?? "",
|
|
34187
|
+
storyId: ev.storyId,
|
|
34188
|
+
iteration: ev.attempts ?? 0
|
|
34189
|
+
}, config2, interactionChain).then((response) => {
|
|
34190
|
+
if (response.action === "abort") {
|
|
34191
|
+
logger?.warn("interaction-subscriber", "max-retries abort requested", {
|
|
34192
|
+
storyId: ev.storyId
|
|
34128
34193
|
});
|
|
34129
34194
|
}
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
34133
|
-
|
|
34134
|
-
|
|
34135
|
-
|
|
34136
|
-
|
|
34137
|
-
return {
|
|
34138
|
-
success: false,
|
|
34139
|
-
conflictFiles
|
|
34140
|
-
};
|
|
34141
|
-
}
|
|
34142
|
-
throw new Error(`Merge failed: ${stderr || stdout || "unknown error"}`);
|
|
34143
|
-
} catch (error48) {
|
|
34144
|
-
if (error48 instanceof Error) {
|
|
34145
|
-
throw error48;
|
|
34146
|
-
}
|
|
34147
|
-
throw new Error(`Failed to merge branch ${branchName}: ${String(error48)}`);
|
|
34148
|
-
}
|
|
34195
|
+
}).catch((err) => {
|
|
34196
|
+
logger?.warn("interaction-subscriber", "max-retries trigger failed", {
|
|
34197
|
+
storyId: ev.storyId,
|
|
34198
|
+
error: String(err)
|
|
34199
|
+
});
|
|
34200
|
+
});
|
|
34201
|
+
}));
|
|
34149
34202
|
}
|
|
34150
|
-
|
|
34151
|
-
const
|
|
34152
|
-
|
|
34153
|
-
|
|
34154
|
-
|
|
34155
|
-
|
|
34156
|
-
|
|
34157
|
-
|
|
34158
|
-
|
|
34159
|
-
|
|
34160
|
-
|
|
34161
|
-
|
|
34203
|
+
return () => {
|
|
34204
|
+
for (const u of unsubs)
|
|
34205
|
+
u();
|
|
34206
|
+
};
|
|
34207
|
+
}
|
|
34208
|
+
var init_interaction2 = __esm(() => {
|
|
34209
|
+
init_triggers();
|
|
34210
|
+
init_logger2();
|
|
34211
|
+
});
|
|
34212
|
+
|
|
34213
|
+
// src/pipeline/subscribers/registry.ts
|
|
34214
|
+
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
34215
|
+
import { homedir as homedir6 } from "os";
|
|
34216
|
+
import { basename as basename7, join as join48 } from "path";
|
|
34217
|
+
function wireRegistry(bus, feature, runId, workdir) {
|
|
34218
|
+
const logger = getSafeLogger();
|
|
34219
|
+
const project = basename7(workdir);
|
|
34220
|
+
const runDir = join48(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
34221
|
+
const metaFile = join48(runDir, "meta.json");
|
|
34222
|
+
const unsub = bus.on("run:started", (_ev) => {
|
|
34223
|
+
return (async () => {
|
|
34224
|
+
try {
|
|
34225
|
+
await mkdir3(runDir, { recursive: true });
|
|
34226
|
+
const meta3 = {
|
|
34227
|
+
runId,
|
|
34228
|
+
project,
|
|
34229
|
+
feature,
|
|
34230
|
+
workdir,
|
|
34231
|
+
statusPath: join48(workdir, ".nax", "features", feature, "status.json"),
|
|
34232
|
+
eventsDir: join48(workdir, ".nax", "features", feature, "runs"),
|
|
34233
|
+
registeredAt: new Date().toISOString()
|
|
34234
|
+
};
|
|
34235
|
+
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
34236
|
+
} catch (err) {
|
|
34237
|
+
logger?.warn("registry-writer", "Failed to write meta.json (non-fatal)", {
|
|
34238
|
+
path: metaFile,
|
|
34239
|
+
error: String(err)
|
|
34162
34240
|
});
|
|
34163
|
-
failedStories.add(storyId);
|
|
34164
|
-
continue;
|
|
34165
34241
|
}
|
|
34166
|
-
|
|
34167
|
-
|
|
34168
|
-
|
|
34169
|
-
|
|
34170
|
-
|
|
34171
|
-
|
|
34172
|
-
|
|
34173
|
-
|
|
34174
|
-
|
|
34175
|
-
|
|
34176
|
-
|
|
34242
|
+
})();
|
|
34243
|
+
});
|
|
34244
|
+
return unsub;
|
|
34245
|
+
}
|
|
34246
|
+
var init_registry3 = __esm(() => {
|
|
34247
|
+
init_logger2();
|
|
34248
|
+
});
|
|
34249
|
+
|
|
34250
|
+
// src/pipeline/subscribers/reporters.ts
|
|
34251
|
+
function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
34252
|
+
const logger = getSafeLogger();
|
|
34253
|
+
const safe = (name, fn) => {
|
|
34254
|
+
return fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) })).catch(() => {});
|
|
34255
|
+
};
|
|
34256
|
+
const unsubs = [];
|
|
34257
|
+
unsubs.push(bus.on("run:started", (ev) => {
|
|
34258
|
+
return safe("onRunStart", async () => {
|
|
34259
|
+
const reporters = pluginRegistry.getReporters();
|
|
34260
|
+
for (const r of reporters) {
|
|
34261
|
+
if (r.onRunStart) {
|
|
34262
|
+
try {
|
|
34263
|
+
await r.onRunStart({
|
|
34264
|
+
runId,
|
|
34265
|
+
feature: ev.feature,
|
|
34266
|
+
totalStories: ev.totalStories,
|
|
34267
|
+
startTime: new Date(startTime).toISOString()
|
|
34177
34268
|
});
|
|
34178
|
-
|
|
34179
|
-
|
|
34269
|
+
} catch (err) {
|
|
34270
|
+
logger?.warn("plugins", `Reporter '${r.name}' onRunStart failed`, { error: err });
|
|
34180
34271
|
}
|
|
34181
|
-
results.push({
|
|
34182
|
-
success: true,
|
|
34183
|
-
storyId,
|
|
34184
|
-
retryCount: 1
|
|
34185
|
-
});
|
|
34186
|
-
} catch (error48) {
|
|
34187
|
-
results.push({
|
|
34188
|
-
success: false,
|
|
34189
|
-
storyId,
|
|
34190
|
-
conflictFiles: result.conflictFiles,
|
|
34191
|
-
retryCount: 1
|
|
34192
|
-
});
|
|
34193
|
-
failedStories.add(storyId);
|
|
34194
34272
|
}
|
|
34195
|
-
} else if (result.success) {
|
|
34196
|
-
results.push({
|
|
34197
|
-
success: true,
|
|
34198
|
-
storyId,
|
|
34199
|
-
retryCount: 0
|
|
34200
|
-
});
|
|
34201
|
-
} else {
|
|
34202
|
-
results.push({
|
|
34203
|
-
success: false,
|
|
34204
|
-
storyId,
|
|
34205
|
-
retryCount: 0
|
|
34206
|
-
});
|
|
34207
|
-
failedStories.add(storyId);
|
|
34208
|
-
}
|
|
34209
|
-
}
|
|
34210
|
-
return results;
|
|
34211
|
-
}
|
|
34212
|
-
topologicalSort(storyIds, dependencies) {
|
|
34213
|
-
const visited = new Set;
|
|
34214
|
-
const sorted = [];
|
|
34215
|
-
const visiting = new Set;
|
|
34216
|
-
const visit = (storyId) => {
|
|
34217
|
-
if (visited.has(storyId)) {
|
|
34218
|
-
return;
|
|
34219
|
-
}
|
|
34220
|
-
if (visiting.has(storyId)) {
|
|
34221
|
-
throw new Error(`Circular dependency detected involving ${storyId}`);
|
|
34222
34273
|
}
|
|
34223
|
-
|
|
34224
|
-
|
|
34225
|
-
|
|
34226
|
-
|
|
34227
|
-
|
|
34274
|
+
});
|
|
34275
|
+
}));
|
|
34276
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
34277
|
+
return safe("onStoryComplete(completed)", async () => {
|
|
34278
|
+
const reporters = pluginRegistry.getReporters();
|
|
34279
|
+
for (const r of reporters) {
|
|
34280
|
+
if (r.onStoryComplete) {
|
|
34281
|
+
try {
|
|
34282
|
+
await r.onStoryComplete({
|
|
34283
|
+
runId,
|
|
34284
|
+
storyId: ev.storyId,
|
|
34285
|
+
status: "completed",
|
|
34286
|
+
runElapsedMs: ev.runElapsedMs,
|
|
34287
|
+
cost: ev.cost ?? 0,
|
|
34288
|
+
tier: ev.modelTier ?? "balanced",
|
|
34289
|
+
testStrategy: ev.testStrategy ?? "test-after"
|
|
34290
|
+
});
|
|
34291
|
+
} catch (err) {
|
|
34292
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
34293
|
+
}
|
|
34228
34294
|
}
|
|
34229
34295
|
}
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
|
|
34233
|
-
|
|
34234
|
-
|
|
34235
|
-
|
|
34236
|
-
|
|
34237
|
-
|
|
34238
|
-
|
|
34239
|
-
|
|
34240
|
-
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
|
|
34245
|
-
|
|
34246
|
-
|
|
34247
|
-
|
|
34248
|
-
|
|
34249
|
-
|
|
34296
|
+
});
|
|
34297
|
+
}));
|
|
34298
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
34299
|
+
return safe("onStoryComplete(failed)", async () => {
|
|
34300
|
+
const reporters = pluginRegistry.getReporters();
|
|
34301
|
+
for (const r of reporters) {
|
|
34302
|
+
if (r.onStoryComplete) {
|
|
34303
|
+
try {
|
|
34304
|
+
await r.onStoryComplete({
|
|
34305
|
+
runId,
|
|
34306
|
+
storyId: ev.storyId,
|
|
34307
|
+
status: "failed",
|
|
34308
|
+
runElapsedMs: Date.now() - startTime,
|
|
34309
|
+
cost: 0,
|
|
34310
|
+
tier: "balanced",
|
|
34311
|
+
testStrategy: "test-after"
|
|
34312
|
+
});
|
|
34313
|
+
} catch (err) {
|
|
34314
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
34315
|
+
}
|
|
34316
|
+
}
|
|
34250
34317
|
}
|
|
34251
|
-
|
|
34252
|
-
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
34257
|
-
|
|
34258
|
-
|
|
34259
|
-
|
|
34260
|
-
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
34264
|
-
|
|
34265
|
-
|
|
34266
|
-
|
|
34318
|
+
});
|
|
34319
|
+
}));
|
|
34320
|
+
unsubs.push(bus.on("story:paused", (ev) => {
|
|
34321
|
+
return safe("onStoryComplete(paused)", async () => {
|
|
34322
|
+
const reporters = pluginRegistry.getReporters();
|
|
34323
|
+
for (const r of reporters) {
|
|
34324
|
+
if (r.onStoryComplete) {
|
|
34325
|
+
try {
|
|
34326
|
+
await r.onStoryComplete({
|
|
34327
|
+
runId,
|
|
34328
|
+
storyId: ev.storyId,
|
|
34329
|
+
status: "paused",
|
|
34330
|
+
runElapsedMs: Date.now() - startTime,
|
|
34331
|
+
cost: 0,
|
|
34332
|
+
tier: "balanced",
|
|
34333
|
+
testStrategy: "test-after"
|
|
34334
|
+
});
|
|
34335
|
+
} catch (err) {
|
|
34336
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
34337
|
+
}
|
|
34338
|
+
}
|
|
34267
34339
|
}
|
|
34268
|
-
}
|
|
34269
|
-
|
|
34270
|
-
|
|
34340
|
+
});
|
|
34341
|
+
}));
|
|
34342
|
+
unsubs.push(bus.on("run:completed", (ev) => {
|
|
34343
|
+
return safe("onRunEnd", async () => {
|
|
34344
|
+
const reporters = pluginRegistry.getReporters();
|
|
34345
|
+
for (const r of reporters) {
|
|
34346
|
+
if (r.onRunEnd) {
|
|
34347
|
+
try {
|
|
34348
|
+
await r.onRunEnd({
|
|
34349
|
+
runId,
|
|
34350
|
+
totalDurationMs: Date.now() - startTime,
|
|
34351
|
+
totalCost: ev.totalCost ?? 0,
|
|
34352
|
+
storySummary: {
|
|
34353
|
+
completed: ev.passedStories,
|
|
34354
|
+
failed: ev.failedStories,
|
|
34355
|
+
skipped: 0,
|
|
34356
|
+
paused: 0
|
|
34357
|
+
}
|
|
34358
|
+
});
|
|
34359
|
+
} catch (err) {
|
|
34360
|
+
logger?.warn("plugins", `Reporter '${r.name}' onRunEnd failed`, { error: err });
|
|
34361
|
+
}
|
|
34362
|
+
}
|
|
34271
34363
|
}
|
|
34272
|
-
|
|
34273
|
-
|
|
34364
|
+
});
|
|
34365
|
+
}));
|
|
34366
|
+
return () => {
|
|
34367
|
+
for (const u of unsubs)
|
|
34368
|
+
u();
|
|
34369
|
+
};
|
|
34370
|
+
}
|
|
34371
|
+
var init_reporters = __esm(() => {
|
|
34372
|
+
init_logger2();
|
|
34373
|
+
});
|
|
34374
|
+
|
|
34375
|
+
// src/execution/deferred-review.ts
|
|
34376
|
+
var {spawn: spawn5 } = globalThis.Bun;
|
|
34377
|
+
async function captureRunStartRef(workdir) {
|
|
34378
|
+
try {
|
|
34379
|
+
const proc = _deferredReviewDeps.spawn({
|
|
34380
|
+
cmd: ["git", "rev-parse", "HEAD"],
|
|
34381
|
+
cwd: workdir,
|
|
34382
|
+
stdout: "pipe",
|
|
34383
|
+
stderr: "pipe"
|
|
34384
|
+
});
|
|
34385
|
+
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
34386
|
+
return stdout.trim();
|
|
34387
|
+
} catch {
|
|
34388
|
+
return "";
|
|
34274
34389
|
}
|
|
34275
|
-
|
|
34276
|
-
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
34283
|
-
|
|
34284
|
-
|
|
34285
|
-
|
|
34286
|
-
|
|
34287
|
-
|
|
34288
|
-
|
|
34289
|
-
} catch {
|
|
34290
|
-
return [];
|
|
34291
|
-
}
|
|
34390
|
+
}
|
|
34391
|
+
async function getChangedFilesForDeferred(workdir, baseRef) {
|
|
34392
|
+
try {
|
|
34393
|
+
const proc = _deferredReviewDeps.spawn({
|
|
34394
|
+
cmd: ["git", "diff", "--name-only", `${baseRef}...HEAD`],
|
|
34395
|
+
cwd: workdir,
|
|
34396
|
+
stdout: "pipe",
|
|
34397
|
+
stderr: "pipe"
|
|
34398
|
+
});
|
|
34399
|
+
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
34400
|
+
return stdout.trim().split(`
|
|
34401
|
+
`).filter(Boolean);
|
|
34402
|
+
} catch {
|
|
34403
|
+
return [];
|
|
34292
34404
|
}
|
|
34293
|
-
|
|
34405
|
+
}
|
|
34406
|
+
async function runDeferredReview(workdir, reviewConfig, plugins, runStartRef) {
|
|
34407
|
+
if (!reviewConfig || reviewConfig.pluginMode !== "deferred") {
|
|
34408
|
+
return;
|
|
34409
|
+
}
|
|
34410
|
+
const reviewers = plugins.getReviewers();
|
|
34411
|
+
if (reviewers.length === 0) {
|
|
34412
|
+
return;
|
|
34413
|
+
}
|
|
34414
|
+
const changedFiles = await getChangedFilesForDeferred(workdir, runStartRef);
|
|
34415
|
+
const reviewerResults = [];
|
|
34416
|
+
let anyFailed = false;
|
|
34417
|
+
for (const reviewer of reviewers) {
|
|
34294
34418
|
try {
|
|
34295
|
-
const
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34419
|
+
const result = await reviewer.check(workdir, changedFiles);
|
|
34420
|
+
reviewerResults.push({
|
|
34421
|
+
name: reviewer.name,
|
|
34422
|
+
passed: result.passed,
|
|
34423
|
+
output: result.output,
|
|
34424
|
+
exitCode: result.exitCode
|
|
34299
34425
|
});
|
|
34300
|
-
|
|
34426
|
+
if (!result.passed) {
|
|
34427
|
+
anyFailed = true;
|
|
34428
|
+
}
|
|
34301
34429
|
} catch (error48) {
|
|
34302
|
-
const
|
|
34303
|
-
|
|
34304
|
-
|
|
34430
|
+
const errorMsg = error48 instanceof Error ? error48.message : String(error48);
|
|
34431
|
+
reviewerResults.push({
|
|
34432
|
+
name: reviewer.name,
|
|
34433
|
+
passed: false,
|
|
34434
|
+
output: "",
|
|
34435
|
+
error: errorMsg
|
|
34305
34436
|
});
|
|
34437
|
+
anyFailed = true;
|
|
34306
34438
|
}
|
|
34307
34439
|
}
|
|
34440
|
+
return { runStartRef, changedFiles, reviewerResults, anyFailed };
|
|
34308
34441
|
}
|
|
34309
|
-
var
|
|
34310
|
-
var
|
|
34311
|
-
|
|
34312
|
-
init_bun_deps();
|
|
34313
|
-
_mergeDeps = {
|
|
34314
|
-
spawn
|
|
34315
|
-
};
|
|
34442
|
+
var _deferredReviewDeps;
|
|
34443
|
+
var init_deferred_review = __esm(() => {
|
|
34444
|
+
_deferredReviewDeps = { spawn: spawn5 };
|
|
34316
34445
|
});
|
|
34317
34446
|
|
|
34318
|
-
// src/execution/
|
|
34319
|
-
|
|
34447
|
+
// src/execution/executor-types.ts
|
|
34448
|
+
function buildPreviewRouting(story, config2) {
|
|
34449
|
+
const cached2 = story.routing;
|
|
34450
|
+
const defaultComplexity = "medium";
|
|
34451
|
+
const defaultTier = "balanced";
|
|
34452
|
+
const defaultStrategy = "test-after";
|
|
34453
|
+
return {
|
|
34454
|
+
complexity: cached2?.complexity ?? defaultComplexity,
|
|
34455
|
+
modelTier: cached2?.modelTier ?? config2.autoMode.complexityRouting?.[defaultComplexity] ?? defaultTier,
|
|
34456
|
+
testStrategy: cached2?.testStrategy ?? defaultStrategy,
|
|
34457
|
+
reasoning: cached2 ? "cached from story.routing" : "preview (pending pipeline routing stage)"
|
|
34458
|
+
};
|
|
34459
|
+
}
|
|
34460
|
+
|
|
34461
|
+
// src/execution/dry-run.ts
|
|
34462
|
+
async function handleDryRun(ctx) {
|
|
34320
34463
|
const logger = getSafeLogger();
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34324
|
-
|
|
34325
|
-
|
|
34326
|
-
|
|
34327
|
-
|
|
34328
|
-
|
|
34329
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
34332
|
-
|
|
34464
|
+
ctx.statusWriter.setPrd(ctx.prd);
|
|
34465
|
+
ctx.statusWriter.setCurrentStory({
|
|
34466
|
+
storyId: ctx.storiesToExecute[0].id,
|
|
34467
|
+
title: ctx.storiesToExecute[0].title,
|
|
34468
|
+
complexity: ctx.routing.complexity,
|
|
34469
|
+
tddStrategy: ctx.routing.testStrategy,
|
|
34470
|
+
model: ctx.routing.modelTier,
|
|
34471
|
+
attempt: (ctx.storiesToExecute[0].attempts ?? 0) + 1,
|
|
34472
|
+
phase: "routing"
|
|
34473
|
+
});
|
|
34474
|
+
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
34475
|
+
for (const s of ctx.storiesToExecute) {
|
|
34476
|
+
logger?.info("execution", "[DRY RUN] Would execute agent here", {
|
|
34477
|
+
storyId: s.id,
|
|
34478
|
+
storyTitle: s.title,
|
|
34479
|
+
modelTier: ctx.routing.modelTier,
|
|
34480
|
+
complexity: ctx.routing.complexity,
|
|
34481
|
+
testStrategy: ctx.routing.testStrategy
|
|
34333
34482
|
});
|
|
34334
|
-
const result = await runPipeline(defaultPipeline, pipelineContext, eventEmitter);
|
|
34335
|
-
return {
|
|
34336
|
-
success: result.success,
|
|
34337
|
-
cost: result.context.agentResult?.estimatedCost || 0,
|
|
34338
|
-
error: result.success ? undefined : result.reason
|
|
34339
|
-
};
|
|
34340
|
-
} catch (error48) {
|
|
34341
|
-
return {
|
|
34342
|
-
success: false,
|
|
34343
|
-
cost: 0,
|
|
34344
|
-
error: errorMessage(error48)
|
|
34345
|
-
};
|
|
34346
34483
|
}
|
|
34347
|
-
|
|
34348
|
-
|
|
34349
|
-
|
|
34350
|
-
|
|
34351
|
-
|
|
34352
|
-
|
|
34353
|
-
|
|
34354
|
-
|
|
34355
|
-
|
|
34356
|
-
|
|
34357
|
-
|
|
34358
|
-
|
|
34359
|
-
|
|
34360
|
-
|
|
34361
|
-
if (!worktreePath) {
|
|
34362
|
-
results.failed.push({
|
|
34363
|
-
story,
|
|
34364
|
-
error: "Worktree not created"
|
|
34365
|
-
});
|
|
34366
|
-
continue;
|
|
34367
|
-
}
|
|
34368
|
-
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
34369
|
-
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
34370
|
-
const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
|
|
34371
|
-
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
34372
|
-
results.totalCost += result.cost;
|
|
34373
|
-
results.storyCosts.set(story.id, result.cost);
|
|
34374
|
-
if (result.success) {
|
|
34375
|
-
results.pipelinePassed.push(story);
|
|
34376
|
-
logger?.info("parallel", "Story execution succeeded", {
|
|
34377
|
-
storyId: story.id,
|
|
34378
|
-
cost: result.cost
|
|
34379
|
-
});
|
|
34380
|
-
} else {
|
|
34381
|
-
results.failed.push({ story, error: result.error || "Unknown error" });
|
|
34382
|
-
logger?.error("parallel", "Story execution failed", {
|
|
34383
|
-
storyId: story.id,
|
|
34384
|
-
error: result.error
|
|
34385
|
-
});
|
|
34386
|
-
}
|
|
34387
|
-
}).finally(() => {
|
|
34388
|
-
executing.delete(executePromise);
|
|
34484
|
+
for (const s of ctx.storiesToExecute) {
|
|
34485
|
+
markStoryPassed(ctx.prd, s.id);
|
|
34486
|
+
}
|
|
34487
|
+
await savePRD(ctx.prd, ctx.prdPath);
|
|
34488
|
+
for (const s of ctx.storiesToExecute) {
|
|
34489
|
+
pipelineEventBus.emit({
|
|
34490
|
+
type: "story:completed",
|
|
34491
|
+
storyId: s.id,
|
|
34492
|
+
story: s,
|
|
34493
|
+
passed: true,
|
|
34494
|
+
runElapsedMs: 0,
|
|
34495
|
+
cost: 0,
|
|
34496
|
+
modelTier: ctx.routing.modelTier,
|
|
34497
|
+
testStrategy: ctx.routing.testStrategy
|
|
34389
34498
|
});
|
|
34390
|
-
executing.add(executePromise);
|
|
34391
|
-
if (executing.size >= maxConcurrency) {
|
|
34392
|
-
await Promise.race(executing);
|
|
34393
|
-
}
|
|
34394
34499
|
}
|
|
34395
|
-
|
|
34396
|
-
|
|
34500
|
+
ctx.statusWriter.setPrd(ctx.prd);
|
|
34501
|
+
ctx.statusWriter.setCurrentStory(null);
|
|
34502
|
+
await ctx.statusWriter.update(ctx.totalCost, ctx.iterations);
|
|
34503
|
+
return { storiesCompletedDelta: ctx.storiesToExecute.length, prdDirty: true };
|
|
34397
34504
|
}
|
|
34398
|
-
var
|
|
34505
|
+
var init_dry_run = __esm(() => {
|
|
34399
34506
|
init_logger2();
|
|
34400
|
-
|
|
34401
|
-
|
|
34402
|
-
init_routing();
|
|
34507
|
+
init_event_bus();
|
|
34508
|
+
init_prd();
|
|
34403
34509
|
});
|
|
34404
34510
|
|
|
34405
|
-
// src/execution/
|
|
34406
|
-
|
|
34407
|
-
|
|
34408
|
-
|
|
34409
|
-
|
|
34410
|
-
|
|
34411
|
-
|
|
34412
|
-
|
|
34413
|
-
|
|
34414
|
-
|
|
34415
|
-
|
|
34416
|
-
|
|
34417
|
-
|
|
34418
|
-
|
|
34419
|
-
if (depsCompleted) {
|
|
34420
|
-
batch.push(story);
|
|
34421
|
-
}
|
|
34422
|
-
}
|
|
34423
|
-
if (batch.length === 0) {
|
|
34424
|
-
const remaining = stories.filter((s) => !processed.has(s.id));
|
|
34425
|
-
const logger = getSafeLogger();
|
|
34426
|
-
logger?.error("parallel", "Cannot resolve story dependencies", {
|
|
34427
|
-
remainingStories: remaining.map((s) => s.id)
|
|
34428
|
-
});
|
|
34429
|
-
throw new Error("Circular dependency or missing dependency detected");
|
|
34430
|
-
}
|
|
34431
|
-
for (const story of batch) {
|
|
34432
|
-
processed.add(story.id);
|
|
34511
|
+
// src/execution/escalation/tier-outcome.ts
|
|
34512
|
+
async function handleNoTierAvailable(ctx, failureCategory) {
|
|
34513
|
+
const logger = getSafeLogger();
|
|
34514
|
+
const outcome = resolveMaxAttemptsOutcome(failureCategory);
|
|
34515
|
+
if (outcome === "pause") {
|
|
34516
|
+
const pausedPrd = { ...ctx.prd };
|
|
34517
|
+
markStoryPaused(pausedPrd, ctx.story.id);
|
|
34518
|
+
await savePRD(pausedPrd, ctx.prdPath);
|
|
34519
|
+
logger?.warn("execution", "Story paused - no tier available (needs human review)", {
|
|
34520
|
+
storyId: ctx.story.id,
|
|
34521
|
+
failureCategory
|
|
34522
|
+
});
|
|
34523
|
+
if (ctx.featureDir) {
|
|
34524
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "paused", `${ctx.story.title} \u2014 Execution stopped (needs human review)`);
|
|
34433
34525
|
}
|
|
34434
|
-
|
|
34435
|
-
|
|
34436
|
-
|
|
34437
|
-
}
|
|
34438
|
-
|
|
34439
|
-
|
|
34440
|
-
|
|
34441
|
-
deps[story.id] = story.dependencies;
|
|
34526
|
+
pipelineEventBus.emit({
|
|
34527
|
+
type: "story:paused",
|
|
34528
|
+
storyId: ctx.story.id,
|
|
34529
|
+
reason: `Execution stopped (${failureCategory ?? "unknown"} requires human review)`,
|
|
34530
|
+
cost: ctx.totalCost
|
|
34531
|
+
});
|
|
34532
|
+
return { outcome: "paused", prdDirty: true, prd: pausedPrd };
|
|
34442
34533
|
}
|
|
34443
|
-
|
|
34444
|
-
|
|
34445
|
-
|
|
34446
|
-
|
|
34447
|
-
|
|
34534
|
+
const failedPrd = { ...ctx.prd };
|
|
34535
|
+
markStoryFailed(failedPrd, ctx.story.id, failureCategory, undefined);
|
|
34536
|
+
await savePRD(failedPrd, ctx.prdPath);
|
|
34537
|
+
logger?.error("execution", "Story failed - execution failed", {
|
|
34538
|
+
storyId: ctx.story.id
|
|
34539
|
+
});
|
|
34540
|
+
if (ctx.featureDir) {
|
|
34541
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Execution failed`);
|
|
34448
34542
|
}
|
|
34449
|
-
|
|
34543
|
+
pipelineEventBus.emit({
|
|
34544
|
+
type: "story:failed",
|
|
34545
|
+
storyId: ctx.story.id,
|
|
34546
|
+
story: ctx.story,
|
|
34547
|
+
reason: "Execution failed",
|
|
34548
|
+
countsTowardEscalation: true
|
|
34549
|
+
});
|
|
34550
|
+
return { outcome: "failed", prdDirty: true, prd: failedPrd };
|
|
34450
34551
|
}
|
|
34451
|
-
async function
|
|
34552
|
+
async function handleMaxAttemptsReached(ctx, failureCategory) {
|
|
34452
34553
|
const logger = getSafeLogger();
|
|
34453
|
-
const
|
|
34454
|
-
|
|
34455
|
-
|
|
34456
|
-
|
|
34457
|
-
|
|
34458
|
-
|
|
34459
|
-
|
|
34460
|
-
|
|
34461
|
-
logger?.info("parallel", "Grouped stories into batches", {
|
|
34462
|
-
batchCount: batches.length,
|
|
34463
|
-
batches: batches.map((b, i) => ({ index: i, storyCount: b.length, storyIds: b.map((s) => s.id) }))
|
|
34464
|
-
});
|
|
34465
|
-
let storiesCompleted = 0;
|
|
34466
|
-
let totalCost = 0;
|
|
34467
|
-
const currentPrd = prd;
|
|
34468
|
-
const allMergeConflicts = [];
|
|
34469
|
-
for (let batchIndex = 0;batchIndex < batches.length; batchIndex++) {
|
|
34470
|
-
const batch = batches[batchIndex];
|
|
34471
|
-
logger?.info("parallel", `Executing batch ${batchIndex + 1}/${batches.length}`, {
|
|
34472
|
-
storyCount: batch.length,
|
|
34473
|
-
storyIds: batch.map((s) => s.id)
|
|
34554
|
+
const outcome = resolveMaxAttemptsOutcome(failureCategory);
|
|
34555
|
+
if (outcome === "pause") {
|
|
34556
|
+
const pausedPrd = { ...ctx.prd };
|
|
34557
|
+
markStoryPaused(pausedPrd, ctx.story.id);
|
|
34558
|
+
await savePRD(pausedPrd, ctx.prdPath);
|
|
34559
|
+
logger?.warn("execution", "Story paused - max attempts reached (needs human review)", {
|
|
34560
|
+
storyId: ctx.story.id,
|
|
34561
|
+
failureCategory
|
|
34474
34562
|
});
|
|
34475
|
-
|
|
34476
|
-
|
|
34477
|
-
effectiveConfig: config2,
|
|
34478
|
-
prd: currentPrd,
|
|
34479
|
-
featureDir,
|
|
34480
|
-
hooks,
|
|
34481
|
-
plugins,
|
|
34482
|
-
storyStartTime: new Date().toISOString(),
|
|
34483
|
-
agentGetFn,
|
|
34484
|
-
pidRegistry,
|
|
34485
|
-
interaction: interactionChain ?? undefined
|
|
34486
|
-
};
|
|
34487
|
-
const worktreePaths = new Map;
|
|
34488
|
-
const storyEffectiveConfigs = new Map;
|
|
34489
|
-
for (const story of batch) {
|
|
34490
|
-
const worktreePath = join48(projectRoot, ".nax-wt", story.id);
|
|
34491
|
-
try {
|
|
34492
|
-
await worktreeManager.create(projectRoot, story.id);
|
|
34493
|
-
worktreePaths.set(story.id, worktreePath);
|
|
34494
|
-
logger?.info("parallel", "Created worktree for story", {
|
|
34495
|
-
storyId: story.id,
|
|
34496
|
-
worktreePath
|
|
34497
|
-
});
|
|
34498
|
-
if (story.workdir) {
|
|
34499
|
-
const pkgNodeModulesSrc = join48(projectRoot, story.workdir, "node_modules");
|
|
34500
|
-
const pkgNodeModulesDst = join48(worktreePath, story.workdir, "node_modules");
|
|
34501
|
-
if (existsSync33(pkgNodeModulesSrc) && !existsSync33(pkgNodeModulesDst)) {
|
|
34502
|
-
try {
|
|
34503
|
-
symlinkSync2(pkgNodeModulesSrc, pkgNodeModulesDst, "dir");
|
|
34504
|
-
logger?.debug("parallel", "Symlinked package node_modules", {
|
|
34505
|
-
storyId: story.id,
|
|
34506
|
-
src: pkgNodeModulesSrc
|
|
34507
|
-
});
|
|
34508
|
-
} catch (symlinkError) {
|
|
34509
|
-
logger?.warn("parallel", "Failed to symlink package node_modules \u2014 test runner may not find deps", {
|
|
34510
|
-
storyId: story.id,
|
|
34511
|
-
error: errorMessage(symlinkError)
|
|
34512
|
-
});
|
|
34513
|
-
}
|
|
34514
|
-
}
|
|
34515
|
-
}
|
|
34516
|
-
const rootConfigPath = join48(projectRoot, ".nax", "config.json");
|
|
34517
|
-
const effectiveConfig = story.workdir ? await loadConfigForWorkdir(rootConfigPath, story.workdir) : config2;
|
|
34518
|
-
storyEffectiveConfigs.set(story.id, effectiveConfig);
|
|
34519
|
-
} catch (error48) {
|
|
34520
|
-
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
34521
|
-
logger?.error("parallel", "Failed to create worktree", {
|
|
34522
|
-
storyId: story.id,
|
|
34523
|
-
error: errorMessage(error48)
|
|
34524
|
-
});
|
|
34525
|
-
}
|
|
34526
|
-
}
|
|
34527
|
-
const batchResult = await executeParallelBatch(batch, projectRoot, config2, baseContext, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs);
|
|
34528
|
-
totalCost += batchResult.totalCost;
|
|
34529
|
-
if (batchResult.pipelinePassed.length > 0) {
|
|
34530
|
-
const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
|
|
34531
|
-
const deps = buildDependencyMap(batch);
|
|
34532
|
-
logger?.info("parallel", "Merging successful stories", {
|
|
34533
|
-
storyIds: successfulIds
|
|
34534
|
-
});
|
|
34535
|
-
const mergeResults = await mergeEngine.mergeAll(projectRoot, successfulIds, deps);
|
|
34536
|
-
for (const mergeResult of mergeResults) {
|
|
34537
|
-
if (mergeResult.success) {
|
|
34538
|
-
markStoryPassed(currentPrd, mergeResult.storyId);
|
|
34539
|
-
storiesCompleted++;
|
|
34540
|
-
const mergedStory = batchResult.pipelinePassed.find((s) => s.id === mergeResult.storyId);
|
|
34541
|
-
if (mergedStory)
|
|
34542
|
-
batchResult.merged.push(mergedStory);
|
|
34543
|
-
logger?.info("parallel", "Story merged successfully", {
|
|
34544
|
-
storyId: mergeResult.storyId,
|
|
34545
|
-
retryCount: mergeResult.retryCount
|
|
34546
|
-
});
|
|
34547
|
-
} else {
|
|
34548
|
-
markStoryFailed(currentPrd, mergeResult.storyId, undefined, undefined);
|
|
34549
|
-
batchResult.mergeConflicts.push({
|
|
34550
|
-
storyId: mergeResult.storyId,
|
|
34551
|
-
conflictFiles: mergeResult.conflictFiles || [],
|
|
34552
|
-
originalCost: batchResult.storyCosts.get(mergeResult.storyId) ?? 0
|
|
34553
|
-
});
|
|
34554
|
-
logger?.error("parallel", "Merge conflict", {
|
|
34555
|
-
storyId: mergeResult.storyId,
|
|
34556
|
-
conflictFiles: mergeResult.conflictFiles
|
|
34557
|
-
});
|
|
34558
|
-
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
34559
|
-
storyId: mergeResult.storyId,
|
|
34560
|
-
worktreePath: join48(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
34561
|
-
});
|
|
34562
|
-
}
|
|
34563
|
-
}
|
|
34564
|
-
}
|
|
34565
|
-
for (const { story, error: error48 } of batchResult.failed) {
|
|
34566
|
-
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
34567
|
-
logger?.error("parallel", "Cleaning up failed story worktree", {
|
|
34568
|
-
storyId: story.id,
|
|
34569
|
-
error: error48
|
|
34570
|
-
});
|
|
34571
|
-
try {
|
|
34572
|
-
await worktreeManager.remove(projectRoot, story.id);
|
|
34573
|
-
} catch (cleanupError) {
|
|
34574
|
-
logger?.warn("parallel", "Failed to clean up worktree", {
|
|
34575
|
-
storyId: story.id,
|
|
34576
|
-
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
34577
|
-
});
|
|
34578
|
-
}
|
|
34563
|
+
if (ctx.featureDir) {
|
|
34564
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "paused", `${ctx.story.title} \u2014 Max attempts reached (needs human review)`);
|
|
34579
34565
|
}
|
|
34580
|
-
|
|
34581
|
-
|
|
34582
|
-
|
|
34583
|
-
|
|
34584
|
-
|
|
34585
|
-
failed: batchResult.failed.length,
|
|
34586
|
-
mergeConflicts: batchResult.mergeConflicts.length,
|
|
34587
|
-
batchCost: batchResult.totalCost
|
|
34566
|
+
pipelineEventBus.emit({
|
|
34567
|
+
type: "story:paused",
|
|
34568
|
+
storyId: ctx.story.id,
|
|
34569
|
+
reason: `Max attempts reached (${failureCategory ?? "unknown"} requires human review)`,
|
|
34570
|
+
cost: ctx.totalCost
|
|
34588
34571
|
});
|
|
34572
|
+
return { outcome: "paused", prdDirty: true, prd: pausedPrd };
|
|
34589
34573
|
}
|
|
34590
|
-
|
|
34591
|
-
|
|
34592
|
-
|
|
34574
|
+
const failedPrd = { ...ctx.prd };
|
|
34575
|
+
markStoryFailed(failedPrd, ctx.story.id, failureCategory, undefined);
|
|
34576
|
+
await savePRD(failedPrd, ctx.prdPath);
|
|
34577
|
+
logger?.error("execution", "Story failed - max attempts reached", {
|
|
34578
|
+
storyId: ctx.story.id,
|
|
34579
|
+
failureCategory
|
|
34580
|
+
});
|
|
34581
|
+
if (ctx.featureDir) {
|
|
34582
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Max attempts reached`);
|
|
34583
|
+
}
|
|
34584
|
+
pipelineEventBus.emit({
|
|
34585
|
+
type: "story:failed",
|
|
34586
|
+
storyId: ctx.story.id,
|
|
34587
|
+
story: ctx.story,
|
|
34588
|
+
reason: "Max attempts reached",
|
|
34589
|
+
countsTowardEscalation: true
|
|
34593
34590
|
});
|
|
34594
|
-
return {
|
|
34591
|
+
return { outcome: "failed", prdDirty: true, prd: failedPrd };
|
|
34595
34592
|
}
|
|
34596
|
-
var
|
|
34597
|
-
init_loader();
|
|
34593
|
+
var init_tier_outcome = __esm(() => {
|
|
34598
34594
|
init_logger2();
|
|
34595
|
+
init_event_bus();
|
|
34599
34596
|
init_prd();
|
|
34600
|
-
|
|
34601
|
-
|
|
34602
|
-
init_parallel_worker();
|
|
34603
|
-
});
|
|
34604
|
-
|
|
34605
|
-
// src/execution/parallel.ts
|
|
34606
|
-
var init_parallel = __esm(() => {
|
|
34607
|
-
init_parallel_coordinator();
|
|
34597
|
+
init_progress();
|
|
34598
|
+
init_tier_escalation();
|
|
34608
34599
|
});
|
|
34609
34600
|
|
|
34610
|
-
// src/execution/
|
|
34611
|
-
|
|
34612
|
-
|
|
34613
|
-
|
|
34614
|
-
|
|
34615
|
-
|
|
34616
|
-
|
|
34617
|
-
|
|
34618
|
-
|
|
34619
|
-
|
|
34620
|
-
|
|
34621
|
-
|
|
34622
|
-
|
|
34623
|
-
|
|
34624
|
-
|
|
34625
|
-
|
|
34626
|
-
|
|
34627
|
-
|
|
34628
|
-
|
|
34629
|
-
|
|
34630
|
-
|
|
34631
|
-
|
|
34632
|
-
|
|
34633
|
-
|
|
34634
|
-
|
|
34635
|
-
|
|
34636
|
-
|
|
34637
|
-
|
|
34638
|
-
|
|
34639
|
-
config: config2,
|
|
34640
|
-
effectiveConfig: config2,
|
|
34641
|
-
prd,
|
|
34642
|
-
story,
|
|
34643
|
-
stories: [story],
|
|
34644
|
-
workdir: worktreePath,
|
|
34645
|
-
featureDir: undefined,
|
|
34646
|
-
hooks,
|
|
34647
|
-
plugins: pluginRegistry,
|
|
34648
|
-
storyStartTime: new Date().toISOString(),
|
|
34649
|
-
routing,
|
|
34650
|
-
agentGetFn
|
|
34651
|
-
};
|
|
34652
|
-
const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
|
|
34653
|
-
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
34654
|
-
if (!pipelineResult.success) {
|
|
34655
|
-
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
34656
|
-
return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
|
|
34657
|
-
}
|
|
34658
|
-
const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
34659
|
-
const mergeResult = mergeResults[0];
|
|
34660
|
-
if (!mergeResult || !mergeResult.success) {
|
|
34661
|
-
const conflictFiles = mergeResult?.conflictFiles ?? [];
|
|
34662
|
-
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
34663
|
-
return { success: false, storyId, cost, finalConflict: true, conflictFiles };
|
|
34664
|
-
}
|
|
34665
|
-
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
34666
|
-
storyId,
|
|
34667
|
-
originalCost: options.originalCost,
|
|
34668
|
-
rectificationCost: cost
|
|
34669
|
-
});
|
|
34670
|
-
return { success: true, storyId, cost };
|
|
34671
|
-
} catch (error48) {
|
|
34672
|
-
logger?.error("parallel", "Rectification failed - preserving worktree", {
|
|
34673
|
-
storyId,
|
|
34674
|
-
error: errorMessage(error48)
|
|
34675
|
-
});
|
|
34676
|
-
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
34601
|
+
// src/execution/escalation/tier-escalation.ts
|
|
34602
|
+
function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
|
|
34603
|
+
const stage = reviewFindings && reviewFindings.length > 0 ? "review" : "escalation";
|
|
34604
|
+
return {
|
|
34605
|
+
attempt: (story.attempts ?? 0) + 1,
|
|
34606
|
+
modelTier: currentTier,
|
|
34607
|
+
stage,
|
|
34608
|
+
summary: `Failed with tier ${currentTier}, escalating to next tier`,
|
|
34609
|
+
reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
|
|
34610
|
+
cost: cost ?? 0,
|
|
34611
|
+
timestamp: new Date().toISOString()
|
|
34612
|
+
};
|
|
34613
|
+
}
|
|
34614
|
+
function resolveMaxAttemptsOutcome(failureCategory) {
|
|
34615
|
+
if (!failureCategory) {
|
|
34616
|
+
return "fail";
|
|
34617
|
+
}
|
|
34618
|
+
switch (failureCategory) {
|
|
34619
|
+
case "isolation-violation":
|
|
34620
|
+
case "verifier-rejected":
|
|
34621
|
+
case "greenfield-no-tests":
|
|
34622
|
+
return "pause";
|
|
34623
|
+
case "runtime-crash":
|
|
34624
|
+
return "pause";
|
|
34625
|
+
case "session-failure":
|
|
34626
|
+
case "tests-failing":
|
|
34627
|
+
return "fail";
|
|
34628
|
+
default:
|
|
34629
|
+
return "fail";
|
|
34677
34630
|
}
|
|
34678
34631
|
}
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
}
|
|
34682
|
-
|
|
34683
|
-
// src/execution/parallel-executor-rectification-pass.ts
|
|
34684
|
-
async function runRectificationPass(conflictedStories, options, prd, rectifyConflictedStory2) {
|
|
34632
|
+
function shouldRetrySameTier(verifyResult) {
|
|
34633
|
+
return verifyResult?.status === "RUNTIME_CRASH";
|
|
34634
|
+
}
|
|
34635
|
+
async function handleTierEscalation(ctx) {
|
|
34685
34636
|
const logger = getSafeLogger();
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
return importedRectify(opts);
|
|
34690
|
-
});
|
|
34691
|
-
const rectificationMetrics = [];
|
|
34692
|
-
let rectifiedCount = 0;
|
|
34693
|
-
let stillConflictingCount = 0;
|
|
34694
|
-
let additionalCost = 0;
|
|
34695
|
-
logger?.info("parallel", "Starting merge conflict rectification", {
|
|
34696
|
-
stories: conflictedStories.map((s) => s.storyId),
|
|
34697
|
-
totalConflicts: conflictedStories.length
|
|
34698
|
-
});
|
|
34699
|
-
for (const conflictInfo of conflictedStories) {
|
|
34700
|
-
const result = await rectify({
|
|
34701
|
-
...conflictInfo,
|
|
34702
|
-
workdir,
|
|
34703
|
-
config: config2,
|
|
34704
|
-
hooks,
|
|
34705
|
-
pluginRegistry,
|
|
34706
|
-
prd,
|
|
34707
|
-
eventEmitter,
|
|
34708
|
-
agentGetFn
|
|
34637
|
+
if (shouldRetrySameTier(ctx.verifyResult)) {
|
|
34638
|
+
logger?.warn("escalation", "Runtime crash detected \u2014 retrying same tier (transient, not a code issue)", {
|
|
34639
|
+
storyId: ctx.story.id
|
|
34709
34640
|
});
|
|
34710
|
-
|
|
34711
|
-
|
|
34712
|
-
|
|
34713
|
-
|
|
34714
|
-
|
|
34715
|
-
|
|
34716
|
-
|
|
34717
|
-
|
|
34718
|
-
|
|
34719
|
-
|
|
34720
|
-
|
|
34721
|
-
|
|
34722
|
-
|
|
34723
|
-
|
|
34724
|
-
|
|
34725
|
-
|
|
34726
|
-
|
|
34727
|
-
|
|
34728
|
-
|
|
34729
|
-
|
|
34730
|
-
|
|
34641
|
+
return { outcome: "retry-same", prdDirty: false, prd: ctx.prd };
|
|
34642
|
+
}
|
|
34643
|
+
const nextTier = escalateTier(ctx.routing.modelTier, ctx.config.autoMode.escalation.tierOrder);
|
|
34644
|
+
const escalateWholeBatch = ctx.config.autoMode.escalation.escalateEntireBatch ?? true;
|
|
34645
|
+
const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
|
|
34646
|
+
const escalateRetryAsLite = ctx.pipelineResult.context.retryAsLite === true;
|
|
34647
|
+
const escalateFailureCategory = ctx.pipelineResult.context.tddFailureCategory;
|
|
34648
|
+
const escalateReviewFindings = ctx.pipelineResult.context.reviewFindings;
|
|
34649
|
+
const escalateRetryAsTestAfter = escalateFailureCategory === "greenfield-no-tests";
|
|
34650
|
+
const routingMode = ctx.config.routing.llm?.mode ?? "hybrid";
|
|
34651
|
+
if (!nextTier || !ctx.config.autoMode.escalation.enabled) {
|
|
34652
|
+
return await handleNoTierAvailable(ctx, escalateFailureCategory);
|
|
34653
|
+
}
|
|
34654
|
+
const maxAttempts = calculateMaxIterations(ctx.config.autoMode.escalation.tierOrder);
|
|
34655
|
+
const canEscalate = storiesToEscalate.every((s) => (s.attempts ?? 0) < maxAttempts);
|
|
34656
|
+
if (!canEscalate) {
|
|
34657
|
+
return await handleMaxAttemptsReached(ctx, escalateFailureCategory);
|
|
34658
|
+
}
|
|
34659
|
+
for (const s of storiesToEscalate) {
|
|
34660
|
+
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34661
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34662
|
+
if (shouldSwitchToTestAfter) {
|
|
34663
|
+
logger?.warn("escalation", "Switching strategy to test-after (greenfield-no-tests fallback)", {
|
|
34664
|
+
storyId: s.id,
|
|
34665
|
+
fromStrategy: currentTestStrategy,
|
|
34666
|
+
toStrategy: "test-after"
|
|
34731
34667
|
});
|
|
34732
34668
|
} else {
|
|
34733
|
-
|
|
34734
|
-
|
|
34735
|
-
|
|
34736
|
-
|
|
34669
|
+
logger?.warn("escalation", "Escalating story to next tier", {
|
|
34670
|
+
storyId: s.id,
|
|
34671
|
+
nextTier,
|
|
34672
|
+
retryAsLite: escalateRetryAsLite
|
|
34673
|
+
});
|
|
34737
34674
|
}
|
|
34738
34675
|
}
|
|
34739
|
-
|
|
34740
|
-
|
|
34741
|
-
|
|
34742
|
-
|
|
34743
|
-
|
|
34676
|
+
const pipelineReason = ctx.pipelineResult.reason ? `: ${ctx.pipelineResult.reason}` : "";
|
|
34677
|
+
const errorMessage2 = `Attempt ${ctx.story.attempts + 1} failed with model tier: ${ctx.routing.modelTier}${ctx.isBatchExecution ? " (in batch)" : ""}${pipelineReason}`;
|
|
34678
|
+
const updatedPrd = {
|
|
34679
|
+
...ctx.prd,
|
|
34680
|
+
userStories: ctx.prd.userStories.map((s) => {
|
|
34681
|
+
const shouldEscalate = storiesToEscalate.some((story) => story.id === s.id);
|
|
34682
|
+
if (!shouldEscalate)
|
|
34683
|
+
return s;
|
|
34684
|
+
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34685
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34686
|
+
const baseRouting = s.routing ?? { ...ctx.routing };
|
|
34687
|
+
const updatedRouting = {
|
|
34688
|
+
...baseRouting,
|
|
34689
|
+
modelTier: shouldSwitchToTestAfter ? baseRouting.modelTier : nextTier,
|
|
34690
|
+
...escalateRetryAsLite ? { testStrategy: "three-session-tdd-lite" } : {},
|
|
34691
|
+
...shouldSwitchToTestAfter ? { testStrategy: "test-after" } : {}
|
|
34692
|
+
};
|
|
34693
|
+
const currentStoryTier = s.routing?.modelTier ?? ctx.routing.modelTier;
|
|
34694
|
+
const isChangingTier = currentStoryTier !== nextTier;
|
|
34695
|
+
const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
|
|
34696
|
+
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
|
|
34697
|
+
return {
|
|
34698
|
+
...s,
|
|
34699
|
+
attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
|
|
34700
|
+
routing: updatedRouting,
|
|
34701
|
+
priorErrors: [...s.priorErrors || [], errorMessage2],
|
|
34702
|
+
priorFailures: [...s.priorFailures || [], escalationFailure]
|
|
34703
|
+
};
|
|
34704
|
+
})
|
|
34705
|
+
};
|
|
34706
|
+
await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
|
|
34707
|
+
for (const story of storiesToEscalate) {
|
|
34708
|
+
clearCacheForStory(story.id);
|
|
34709
|
+
}
|
|
34710
|
+
if (routingMode === "hybrid") {
|
|
34711
|
+
await tryLlmBatchRoute(ctx.config, storiesToEscalate, "hybrid-re-route-pipeline");
|
|
34712
|
+
}
|
|
34713
|
+
return {
|
|
34714
|
+
outcome: "escalated",
|
|
34715
|
+
prdDirty: true,
|
|
34716
|
+
prd: updatedPrd
|
|
34717
|
+
};
|
|
34744
34718
|
}
|
|
34745
|
-
var
|
|
34719
|
+
var _tierEscalationDeps;
|
|
34720
|
+
var init_tier_escalation = __esm(() => {
|
|
34721
|
+
init_hooks();
|
|
34746
34722
|
init_logger2();
|
|
34747
34723
|
init_prd();
|
|
34724
|
+
init_routing();
|
|
34725
|
+
init_llm();
|
|
34726
|
+
init_escalation();
|
|
34727
|
+
init_helpers();
|
|
34728
|
+
init_progress();
|
|
34729
|
+
init_tier_outcome();
|
|
34730
|
+
_tierEscalationDeps = {
|
|
34731
|
+
savePRD
|
|
34732
|
+
};
|
|
34748
34733
|
});
|
|
34749
34734
|
|
|
34750
|
-
// src/execution/
|
|
34751
|
-
var
|
|
34752
|
-
|
|
34753
|
-
handleParallelCompletion: () => handleParallelCompletion
|
|
34735
|
+
// src/execution/escalation/index.ts
|
|
34736
|
+
var init_escalation = __esm(() => {
|
|
34737
|
+
init_tier_escalation();
|
|
34754
34738
|
});
|
|
34755
|
-
|
|
34739
|
+
|
|
34740
|
+
// src/execution/pipeline-result-handler.ts
|
|
34741
|
+
function filterOutputFiles(files) {
|
|
34742
|
+
const NOISE = [
|
|
34743
|
+
/\.test\.(ts|js|tsx|jsx)$/,
|
|
34744
|
+
/\.spec\.(ts|js|tsx|jsx)$/,
|
|
34745
|
+
/package-lock\.json$/,
|
|
34746
|
+
/bun\.lock(b?)$/,
|
|
34747
|
+
/\.gitignore$/,
|
|
34748
|
+
/^nax\//
|
|
34749
|
+
];
|
|
34750
|
+
return files.filter((f) => !NOISE.some((p) => p.test(f))).slice(0, 15);
|
|
34751
|
+
}
|
|
34752
|
+
async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
34756
34753
|
const logger = getSafeLogger();
|
|
34757
|
-
const
|
|
34758
|
-
|
|
34759
|
-
|
|
34760
|
-
|
|
34761
|
-
|
|
34762
|
-
|
|
34763
|
-
|
|
34764
|
-
|
|
34765
|
-
|
|
34766
|
-
|
|
34767
|
-
|
|
34768
|
-
|
|
34769
|
-
|
|
34770
|
-
|
|
34771
|
-
|
|
34772
|
-
|
|
34773
|
-
|
|
34774
|
-
|
|
34775
|
-
totalCost,
|
|
34776
|
-
totalStories: allStoryMetrics.length,
|
|
34777
|
-
storiesCompleted,
|
|
34778
|
-
storiesFailed: countStories(prd).failed,
|
|
34779
|
-
totalDurationMs: durationMs,
|
|
34780
|
-
stories: allStoryMetrics
|
|
34781
|
-
};
|
|
34782
|
-
await saveRunMetrics(workdir, runMetrics);
|
|
34783
|
-
const finalCounts = countStories(prd);
|
|
34784
|
-
logger?.info("run.complete", "Feature execution completed", {
|
|
34785
|
-
runId,
|
|
34786
|
-
feature,
|
|
34787
|
-
success: true,
|
|
34788
|
-
totalStories: finalCounts.total,
|
|
34789
|
-
storiesCompleted,
|
|
34790
|
-
storiesFailed: finalCounts.failed,
|
|
34791
|
-
storiesPending: finalCounts.pending,
|
|
34792
|
-
totalCost,
|
|
34793
|
-
durationMs
|
|
34794
|
-
});
|
|
34795
|
-
const reporters = pluginRegistry.getReporters();
|
|
34796
|
-
for (const reporter of reporters) {
|
|
34797
|
-
if (reporter.onRunEnd) {
|
|
34754
|
+
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
34755
|
+
const prd = ctx.prd;
|
|
34756
|
+
if (pipelineResult.context.storyMetrics) {
|
|
34757
|
+
ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
|
|
34758
|
+
}
|
|
34759
|
+
const storiesCompletedDelta = ctx.storiesToExecute.length;
|
|
34760
|
+
for (const completedStory of ctx.storiesToExecute) {
|
|
34761
|
+
const now = Date.now();
|
|
34762
|
+
logger?.info("story.complete", "Story completed successfully", {
|
|
34763
|
+
storyId: completedStory.id,
|
|
34764
|
+
storyTitle: completedStory.title,
|
|
34765
|
+
totalCost: ctx.totalCost + costDelta,
|
|
34766
|
+
runElapsedMs: now - ctx.startTime,
|
|
34767
|
+
storyDurationMs: ctx.storyStartTime ? now - ctx.storyStartTime : undefined
|
|
34768
|
+
});
|
|
34769
|
+
}
|
|
34770
|
+
if (ctx.storyGitRef) {
|
|
34771
|
+
for (const completedStory of ctx.storiesToExecute) {
|
|
34798
34772
|
try {
|
|
34799
|
-
await
|
|
34800
|
-
|
|
34801
|
-
|
|
34802
|
-
|
|
34803
|
-
|
|
34804
|
-
|
|
34805
|
-
|
|
34806
|
-
|
|
34807
|
-
|
|
34808
|
-
|
|
34773
|
+
const rawFiles = await captureOutputFiles(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
34774
|
+
const filtered = filterOutputFiles(rawFiles);
|
|
34775
|
+
if (filtered.length > 0) {
|
|
34776
|
+
completedStory.outputFiles = filtered;
|
|
34777
|
+
}
|
|
34778
|
+
const diffSummary = await captureDiffSummary(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
34779
|
+
if (diffSummary) {
|
|
34780
|
+
completedStory.diffSummary = diffSummary;
|
|
34781
|
+
}
|
|
34782
|
+
} catch {}
|
|
34783
|
+
}
|
|
34784
|
+
}
|
|
34785
|
+
const updatedCounts = countStories(prd);
|
|
34786
|
+
logger?.info("progress", "Progress update", {
|
|
34787
|
+
totalStories: updatedCounts.total,
|
|
34788
|
+
passedStories: updatedCounts.passed,
|
|
34789
|
+
failedStories: updatedCounts.failed,
|
|
34790
|
+
pendingStories: updatedCounts.pending,
|
|
34791
|
+
totalCost: ctx.totalCost + costDelta,
|
|
34792
|
+
costLimit: ctx.config.execution.costLimit,
|
|
34793
|
+
elapsedMs: Date.now() - ctx.startTime,
|
|
34794
|
+
storyDurationMs: ctx.storyStartTime ? Date.now() - ctx.storyStartTime : undefined
|
|
34795
|
+
});
|
|
34796
|
+
return { storiesCompletedDelta, costDelta, prd, prdDirty: true };
|
|
34797
|
+
}
|
|
34798
|
+
async function handlePipelineFailure(ctx, pipelineResult) {
|
|
34799
|
+
const logger = getSafeLogger();
|
|
34800
|
+
let prd = ctx.prd;
|
|
34801
|
+
let prdDirty = false;
|
|
34802
|
+
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
34803
|
+
switch (pipelineResult.finalAction) {
|
|
34804
|
+
case "pause":
|
|
34805
|
+
markStoryPaused(prd, ctx.story.id);
|
|
34806
|
+
await savePRD(prd, ctx.prdPath);
|
|
34807
|
+
prdDirty = true;
|
|
34808
|
+
logger?.warn("pipeline", "Story paused", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
34809
|
+
pipelineEventBus.emit({
|
|
34810
|
+
type: "story:paused",
|
|
34811
|
+
storyId: ctx.story.id,
|
|
34812
|
+
reason: pipelineResult.reason || "Pipeline paused",
|
|
34813
|
+
cost: ctx.totalCost
|
|
34814
|
+
});
|
|
34815
|
+
break;
|
|
34816
|
+
case "skip":
|
|
34817
|
+
logger?.warn("pipeline", "Story skipped", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
34818
|
+
prdDirty = true;
|
|
34819
|
+
break;
|
|
34820
|
+
case "fail":
|
|
34821
|
+
markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory, pipelineResult.stoppedAtStage);
|
|
34822
|
+
await savePRD(prd, ctx.prdPath);
|
|
34823
|
+
prdDirty = true;
|
|
34824
|
+
logger?.error("pipeline", "Story failed", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
34825
|
+
if (ctx.featureDir) {
|
|
34826
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 ${pipelineResult.reason}`);
|
|
34827
|
+
}
|
|
34828
|
+
pipelineEventBus.emit({
|
|
34829
|
+
type: "story:failed",
|
|
34830
|
+
storyId: ctx.story.id,
|
|
34831
|
+
story: ctx.story,
|
|
34832
|
+
reason: pipelineResult.reason || "Pipeline failed",
|
|
34833
|
+
countsTowardEscalation: true,
|
|
34834
|
+
feature: ctx.feature,
|
|
34835
|
+
attempts: ctx.story.attempts
|
|
34836
|
+
});
|
|
34837
|
+
if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxRetries) {
|
|
34838
|
+
await pipelineEventBus.emitAsync({
|
|
34839
|
+
type: "human-review:requested",
|
|
34840
|
+
storyId: ctx.story.id,
|
|
34841
|
+
reason: pipelineResult.reason || "Max retries exceeded",
|
|
34842
|
+
feature: ctx.feature,
|
|
34843
|
+
attempts: ctx.story.attempts
|
|
34809
34844
|
});
|
|
34810
|
-
} catch (error48) {
|
|
34811
|
-
logger?.warn("plugins", `Reporter '${reporter.name}' onRunEnd failed`, { error: error48 });
|
|
34812
34845
|
}
|
|
34846
|
+
break;
|
|
34847
|
+
case "escalate": {
|
|
34848
|
+
const escalationResult = await handleTierEscalation({
|
|
34849
|
+
story: ctx.story,
|
|
34850
|
+
storiesToExecute: ctx.storiesToExecute,
|
|
34851
|
+
isBatchExecution: ctx.isBatchExecution,
|
|
34852
|
+
routing: ctx.routing,
|
|
34853
|
+
pipelineResult,
|
|
34854
|
+
config: ctx.config,
|
|
34855
|
+
prd,
|
|
34856
|
+
prdPath: ctx.prdPath,
|
|
34857
|
+
featureDir: ctx.featureDir,
|
|
34858
|
+
hooks: ctx.hooks,
|
|
34859
|
+
feature: ctx.feature,
|
|
34860
|
+
totalCost: ctx.totalCost,
|
|
34861
|
+
workdir: ctx.workdir,
|
|
34862
|
+
attemptCost: pipelineResult.context.agentResult?.estimatedCost || 0
|
|
34863
|
+
});
|
|
34864
|
+
prd = escalationResult.prd;
|
|
34865
|
+
prdDirty = escalationResult.prdDirty;
|
|
34866
|
+
break;
|
|
34813
34867
|
}
|
|
34814
34868
|
}
|
|
34869
|
+
return { prd, prdDirty, costDelta };
|
|
34815
34870
|
}
|
|
34816
|
-
var
|
|
34871
|
+
var init_pipeline_result_handler = __esm(() => {
|
|
34817
34872
|
init_logger2();
|
|
34818
|
-
|
|
34873
|
+
init_event_bus();
|
|
34819
34874
|
init_prd();
|
|
34875
|
+
init_git();
|
|
34876
|
+
init_escalation();
|
|
34877
|
+
init_progress();
|
|
34820
34878
|
});
|
|
34821
34879
|
|
|
34822
|
-
// src/execution/
|
|
34823
|
-
|
|
34824
|
-
|
|
34825
|
-
runParallelExecution: () => runParallelExecution,
|
|
34826
|
-
_parallelExecutorDeps: () => _parallelExecutorDeps
|
|
34827
|
-
});
|
|
34828
|
-
import * as os4 from "os";
|
|
34829
|
-
import path16 from "path";
|
|
34830
|
-
async function runParallelExecution(options, initialPrd) {
|
|
34880
|
+
// src/execution/iteration-runner.ts
|
|
34881
|
+
import { join as join49 } from "path";
|
|
34882
|
+
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
34831
34883
|
const logger = getSafeLogger();
|
|
34832
|
-
const {
|
|
34833
|
-
|
|
34834
|
-
|
|
34835
|
-
config: config2,
|
|
34836
|
-
hooks,
|
|
34837
|
-
feature,
|
|
34838
|
-
featureDir,
|
|
34839
|
-
parallelCount,
|
|
34840
|
-
eventEmitter,
|
|
34841
|
-
statusWriter,
|
|
34842
|
-
runId,
|
|
34843
|
-
startedAt,
|
|
34844
|
-
startTime,
|
|
34845
|
-
pluginRegistry,
|
|
34846
|
-
formatterMode,
|
|
34847
|
-
headless
|
|
34848
|
-
} = options;
|
|
34849
|
-
let { totalCost, iterations, storiesCompleted, allStoryMetrics } = options;
|
|
34850
|
-
let prd = initialPrd;
|
|
34851
|
-
const readyStories = getAllReadyStories(prd);
|
|
34852
|
-
if (readyStories.length === 0) {
|
|
34853
|
-
logger?.info("parallel", "No stories ready for parallel execution");
|
|
34854
|
-
return {
|
|
34855
|
-
prd,
|
|
34856
|
-
totalCost,
|
|
34857
|
-
storiesCompleted,
|
|
34858
|
-
completed: false,
|
|
34859
|
-
storyMetrics: [],
|
|
34860
|
-
rectificationStats: { rectified: 0, stillConflicting: 0 }
|
|
34861
|
-
};
|
|
34862
|
-
}
|
|
34863
|
-
const maxConcurrency = parallelCount === 0 ? os4.cpus().length : Math.max(1, parallelCount);
|
|
34864
|
-
logger?.info("parallel", "Starting parallel execution mode", {
|
|
34865
|
-
totalStories: readyStories.length,
|
|
34866
|
-
maxConcurrency
|
|
34867
|
-
});
|
|
34868
|
-
statusWriter.setPrd(prd);
|
|
34869
|
-
await statusWriter.update(totalCost, iterations, {
|
|
34870
|
-
parallel: {
|
|
34871
|
-
enabled: true,
|
|
34872
|
-
maxConcurrency,
|
|
34873
|
-
activeStories: readyStories.map((s) => ({
|
|
34874
|
-
storyId: s.id,
|
|
34875
|
-
worktreePath: path16.join(workdir, ".nax-wt", s.id)
|
|
34876
|
-
}))
|
|
34877
|
-
}
|
|
34878
|
-
});
|
|
34879
|
-
const initialPassedIds = new Set(initialPrd.userStories.filter((s) => s.status === "passed").map((s) => s.id));
|
|
34880
|
-
const batchStartedAt = new Date().toISOString();
|
|
34881
|
-
const batchStartMs = Date.now();
|
|
34882
|
-
const batchStoryMetrics = [];
|
|
34883
|
-
let conflictedStories = [];
|
|
34884
|
-
try {
|
|
34885
|
-
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter, options.agentGetFn, options.pidRegistry, options.interactionChain);
|
|
34886
|
-
const batchDurationMs = Date.now() - batchStartMs;
|
|
34887
|
-
const batchCompletedAt = new Date().toISOString();
|
|
34888
|
-
prd = parallelResult.updatedPrd;
|
|
34889
|
-
storiesCompleted += parallelResult.storiesCompleted;
|
|
34890
|
-
totalCost += parallelResult.totalCost;
|
|
34891
|
-
conflictedStories = parallelResult.mergeConflicts ?? [];
|
|
34892
|
-
const newlyPassedStories = prd.userStories.filter((s) => s.status === "passed" && !initialPassedIds.has(s.id));
|
|
34893
|
-
const costPerStory = newlyPassedStories.length > 0 ? parallelResult.totalCost / newlyPassedStories.length : 0;
|
|
34894
|
-
for (const story of newlyPassedStories) {
|
|
34895
|
-
batchStoryMetrics.push({
|
|
34896
|
-
storyId: story.id,
|
|
34897
|
-
complexity: "unknown",
|
|
34898
|
-
modelTier: "parallel",
|
|
34899
|
-
modelUsed: "parallel",
|
|
34900
|
-
attempts: 1,
|
|
34901
|
-
finalTier: "parallel",
|
|
34902
|
-
success: true,
|
|
34903
|
-
cost: costPerStory,
|
|
34904
|
-
durationMs: batchDurationMs,
|
|
34905
|
-
firstPassSuccess: true,
|
|
34906
|
-
startedAt: batchStartedAt,
|
|
34907
|
-
completedAt: batchCompletedAt,
|
|
34908
|
-
source: "parallel"
|
|
34909
|
-
});
|
|
34910
|
-
}
|
|
34911
|
-
allStoryMetrics.push(...batchStoryMetrics);
|
|
34912
|
-
for (const conflict of conflictedStories) {
|
|
34913
|
-
logger?.info("parallel", "Merge conflict detected - scheduling for rectification", {
|
|
34914
|
-
storyId: conflict.storyId,
|
|
34915
|
-
conflictFiles: conflict.conflictFiles
|
|
34916
|
-
});
|
|
34917
|
-
}
|
|
34918
|
-
statusWriter.setPrd(prd);
|
|
34919
|
-
await statusWriter.update(totalCost, iterations, {
|
|
34920
|
-
parallel: {
|
|
34921
|
-
enabled: true,
|
|
34922
|
-
maxConcurrency,
|
|
34923
|
-
activeStories: []
|
|
34924
|
-
}
|
|
34925
|
-
});
|
|
34926
|
-
} catch (error48) {
|
|
34927
|
-
logger?.error("parallel", "Parallel execution failed", {
|
|
34928
|
-
error: errorMessage(error48)
|
|
34929
|
-
});
|
|
34930
|
-
await statusWriter.update(totalCost, iterations, {
|
|
34931
|
-
parallel: undefined
|
|
34932
|
-
});
|
|
34933
|
-
throw error48;
|
|
34934
|
-
}
|
|
34935
|
-
let rectificationStats = { rectified: 0, stillConflicting: 0 };
|
|
34936
|
-
if (conflictedStories.length > 0) {
|
|
34937
|
-
const rectResult = await runRectificationPass(conflictedStories, options, prd, _parallelExecutorDeps.rectifyConflictedStory);
|
|
34938
|
-
prd = rectResult.updatedPrd;
|
|
34939
|
-
storiesCompleted += rectResult.rectifiedCount;
|
|
34940
|
-
totalCost += rectResult.additionalCost;
|
|
34941
|
-
rectificationStats = {
|
|
34942
|
-
rectified: rectResult.rectifiedCount,
|
|
34943
|
-
stillConflicting: rectResult.stillConflictingCount
|
|
34944
|
-
};
|
|
34945
|
-
batchStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
34946
|
-
allStoryMetrics.push(...rectResult.rectificationMetrics);
|
|
34947
|
-
}
|
|
34948
|
-
if (isComplete(prd)) {
|
|
34949
|
-
logger?.info("execution", "All stories complete!", {
|
|
34950
|
-
feature,
|
|
34951
|
-
totalCost
|
|
34952
|
-
});
|
|
34953
|
-
await _parallelExecutorDeps.fireHook(hooks, "on-all-stories-complete", hookCtx(feature, { status: "passed", cost: totalCost }), workdir);
|
|
34954
|
-
await _parallelExecutorDeps.fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: totalCost }), workdir);
|
|
34955
|
-
const durationMs = Date.now() - startTime;
|
|
34956
|
-
const runCompletedAt = new Date().toISOString();
|
|
34957
|
-
const { handleParallelCompletion: handleParallelCompletion2 } = await Promise.resolve().then(() => (init_parallel_lifecycle(), exports_parallel_lifecycle));
|
|
34958
|
-
await handleParallelCompletion2({
|
|
34959
|
-
runId,
|
|
34960
|
-
feature,
|
|
34961
|
-
startedAt,
|
|
34962
|
-
completedAt: runCompletedAt,
|
|
34884
|
+
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
34885
|
+
if (ctx.dryRun) {
|
|
34886
|
+
const dryRunResult = await handleDryRun({
|
|
34963
34887
|
prd,
|
|
34964
|
-
|
|
34888
|
+
prdPath: ctx.prdPath,
|
|
34889
|
+
storiesToExecute,
|
|
34890
|
+
routing,
|
|
34891
|
+
statusWriter: ctx.statusWriter,
|
|
34892
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
34893
|
+
runId: ctx.runId,
|
|
34965
34894
|
totalCost,
|
|
34966
|
-
|
|
34967
|
-
durationMs,
|
|
34968
|
-
workdir,
|
|
34969
|
-
pluginRegistry
|
|
34895
|
+
iterations
|
|
34970
34896
|
});
|
|
34971
|
-
const finalCounts = countStories(prd);
|
|
34972
|
-
statusWriter.setPrd(prd);
|
|
34973
|
-
statusWriter.setCurrentStory(null);
|
|
34974
|
-
statusWriter.setRunStatus("completed");
|
|
34975
|
-
await statusWriter.update(totalCost, iterations);
|
|
34976
|
-
if (headless && formatterMode !== "json") {
|
|
34977
|
-
const { outputRunFooter: outputRunFooter2 } = await Promise.resolve().then(() => (init_headless_formatter(), exports_headless_formatter));
|
|
34978
|
-
outputRunFooter2({
|
|
34979
|
-
finalCounts: {
|
|
34980
|
-
total: finalCounts.total,
|
|
34981
|
-
passed: finalCounts.passed,
|
|
34982
|
-
failed: finalCounts.failed,
|
|
34983
|
-
skipped: finalCounts.skipped
|
|
34984
|
-
},
|
|
34985
|
-
durationMs,
|
|
34986
|
-
totalCost,
|
|
34987
|
-
startedAt,
|
|
34988
|
-
completedAt: runCompletedAt,
|
|
34989
|
-
formatterMode
|
|
34990
|
-
});
|
|
34991
|
-
}
|
|
34992
34897
|
return {
|
|
34993
34898
|
prd,
|
|
34994
|
-
|
|
34995
|
-
|
|
34996
|
-
|
|
34997
|
-
durationMs,
|
|
34998
|
-
storyMetrics: batchStoryMetrics,
|
|
34999
|
-
rectificationStats
|
|
34899
|
+
storiesCompletedDelta: dryRunResult.storiesCompletedDelta,
|
|
34900
|
+
costDelta: 0,
|
|
34901
|
+
prdDirty: dryRunResult.prdDirty
|
|
35000
34902
|
};
|
|
35001
34903
|
}
|
|
35002
|
-
|
|
35003
|
-
|
|
35004
|
-
|
|
35005
|
-
|
|
35006
|
-
|
|
35007
|
-
|
|
35008
|
-
|
|
35009
|
-
|
|
35010
|
-
|
|
35011
|
-
|
|
35012
|
-
|
|
35013
|
-
|
|
35014
|
-
|
|
35015
|
-
|
|
35016
|
-
|
|
34904
|
+
const storyStartTime = Date.now();
|
|
34905
|
+
let storyGitRef;
|
|
34906
|
+
if (story.storyGitRef && await isGitRefValid(ctx.workdir, story.storyGitRef)) {
|
|
34907
|
+
storyGitRef = story.storyGitRef;
|
|
34908
|
+
} else {
|
|
34909
|
+
storyGitRef = await captureGitRef(ctx.workdir);
|
|
34910
|
+
if (storyGitRef) {
|
|
34911
|
+
story.storyGitRef = storyGitRef;
|
|
34912
|
+
await savePRD(prd, ctx.prdPath);
|
|
34913
|
+
}
|
|
34914
|
+
}
|
|
34915
|
+
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
34916
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join49(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
34917
|
+
const pipelineContext = {
|
|
34918
|
+
config: ctx.config,
|
|
34919
|
+
effectiveConfig,
|
|
34920
|
+
prd,
|
|
34921
|
+
story,
|
|
34922
|
+
stories: storiesToExecute,
|
|
34923
|
+
routing,
|
|
34924
|
+
workdir: ctx.workdir,
|
|
34925
|
+
prdPath: ctx.prdPath,
|
|
34926
|
+
featureDir: ctx.featureDir,
|
|
34927
|
+
hooks: ctx.hooks,
|
|
34928
|
+
plugins: ctx.pluginRegistry,
|
|
34929
|
+
storyStartTime: new Date().toISOString(),
|
|
34930
|
+
storyGitRef: storyGitRef ?? undefined,
|
|
34931
|
+
interaction: ctx.interactionChain ?? undefined,
|
|
34932
|
+
agentGetFn: ctx.agentGetFn,
|
|
34933
|
+
pidRegistry: ctx.pidRegistry,
|
|
34934
|
+
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
35017
34935
|
};
|
|
35018
|
-
|
|
35019
|
-
|
|
35020
|
-
// src/pipeline/subscribers/events-writer.ts
|
|
35021
|
-
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
35022
|
-
import { homedir as homedir5 } from "os";
|
|
35023
|
-
import { basename as basename5, join as join49 } from "path";
|
|
35024
|
-
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
35025
|
-
const logger = getSafeLogger();
|
|
35026
|
-
const project = basename5(workdir);
|
|
35027
|
-
const eventsDir = join49(homedir5(), ".nax", "events", project);
|
|
35028
|
-
const eventsFile = join49(eventsDir, "events.jsonl");
|
|
35029
|
-
let dirReady = false;
|
|
35030
|
-
const write = (line) => {
|
|
35031
|
-
return (async () => {
|
|
35032
|
-
try {
|
|
35033
|
-
if (!dirReady) {
|
|
35034
|
-
await mkdir2(eventsDir, { recursive: true });
|
|
35035
|
-
dirReady = true;
|
|
35036
|
-
}
|
|
35037
|
-
await appendFile2(eventsFile, `${JSON.stringify(line)}
|
|
35038
|
-
`);
|
|
35039
|
-
} catch (err) {
|
|
35040
|
-
logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
|
|
35041
|
-
event: line.event,
|
|
35042
|
-
error: String(err)
|
|
35043
|
-
});
|
|
35044
|
-
}
|
|
35045
|
-
})();
|
|
35046
|
-
};
|
|
35047
|
-
const unsubs = [];
|
|
35048
|
-
unsubs.push(bus.on("run:started", (_ev) => {
|
|
35049
|
-
return write({ ts: new Date().toISOString(), event: "run:started", runId, feature, project });
|
|
35050
|
-
}));
|
|
35051
|
-
unsubs.push(bus.on("story:started", (ev) => {
|
|
35052
|
-
return write({
|
|
35053
|
-
ts: new Date().toISOString(),
|
|
35054
|
-
event: "story:started",
|
|
35055
|
-
runId,
|
|
35056
|
-
feature,
|
|
35057
|
-
project,
|
|
35058
|
-
storyId: ev.storyId
|
|
35059
|
-
});
|
|
35060
|
-
}));
|
|
35061
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35062
|
-
return write({
|
|
35063
|
-
ts: new Date().toISOString(),
|
|
35064
|
-
event: "story:completed",
|
|
35065
|
-
runId,
|
|
35066
|
-
feature,
|
|
35067
|
-
project,
|
|
35068
|
-
storyId: ev.storyId
|
|
35069
|
-
});
|
|
35070
|
-
}));
|
|
35071
|
-
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
35072
|
-
return write({
|
|
35073
|
-
ts: new Date().toISOString(),
|
|
35074
|
-
event: "story:decomposed",
|
|
35075
|
-
runId,
|
|
35076
|
-
feature,
|
|
35077
|
-
project,
|
|
35078
|
-
storyId: ev.storyId,
|
|
35079
|
-
data: { subStoryCount: ev.subStoryCount }
|
|
35080
|
-
});
|
|
35081
|
-
}));
|
|
35082
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35083
|
-
return write({
|
|
35084
|
-
ts: new Date().toISOString(),
|
|
35085
|
-
event: "story:failed",
|
|
35086
|
-
runId,
|
|
35087
|
-
feature,
|
|
35088
|
-
project,
|
|
35089
|
-
storyId: ev.storyId
|
|
35090
|
-
});
|
|
35091
|
-
}));
|
|
35092
|
-
unsubs.push(bus.on("run:completed", (_ev) => {
|
|
35093
|
-
return write({ ts: new Date().toISOString(), event: "on-complete", runId, feature, project });
|
|
35094
|
-
}));
|
|
35095
|
-
unsubs.push(bus.on("run:paused", (ev) => {
|
|
35096
|
-
return write({
|
|
35097
|
-
ts: new Date().toISOString(),
|
|
35098
|
-
event: "run:paused",
|
|
35099
|
-
runId,
|
|
35100
|
-
feature,
|
|
35101
|
-
project,
|
|
35102
|
-
...ev.storyId !== undefined && { storyId: ev.storyId }
|
|
35103
|
-
});
|
|
35104
|
-
}));
|
|
35105
|
-
return () => {
|
|
35106
|
-
for (const u of unsubs)
|
|
35107
|
-
u();
|
|
35108
|
-
};
|
|
35109
|
-
}
|
|
35110
|
-
var init_events_writer = __esm(() => {
|
|
35111
|
-
init_logger2();
|
|
35112
|
-
});
|
|
35113
|
-
|
|
35114
|
-
// src/pipeline/subscribers/hooks.ts
|
|
35115
|
-
function wireHooks(bus, hooks, workdir, feature) {
|
|
35116
|
-
const logger = getSafeLogger();
|
|
35117
|
-
const safe = (name, fn) => {
|
|
35118
|
-
return fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) })).catch(() => {});
|
|
35119
|
-
};
|
|
35120
|
-
const unsubs = [];
|
|
35121
|
-
unsubs.push(bus.on("run:started", (ev) => {
|
|
35122
|
-
return safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
|
|
35123
|
-
}));
|
|
35124
|
-
unsubs.push(bus.on("story:started", (ev) => {
|
|
35125
|
-
return safe("on-story-start", () => fireHook(hooks, "on-story-start", hookCtx(feature, { storyId: ev.storyId, model: ev.modelTier, agent: ev.agent }), workdir));
|
|
35126
|
-
}));
|
|
35127
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35128
|
-
return safe("on-story-complete", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "passed", cost: ev.cost }), workdir));
|
|
35129
|
-
}));
|
|
35130
|
-
unsubs.push(bus.on("story:decomposed", (ev) => {
|
|
35131
|
-
return safe("on-story-complete (decomposed)", () => fireHook(hooks, "on-story-complete", hookCtx(feature, { storyId: ev.storyId, status: "decomposed", subStoryCount: ev.subStoryCount }), workdir));
|
|
35132
|
-
}));
|
|
35133
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35134
|
-
return safe("on-story-fail", () => fireHook(hooks, "on-story-fail", hookCtx(feature, { storyId: ev.storyId, status: "failed", reason: ev.reason }), workdir));
|
|
35135
|
-
}));
|
|
35136
|
-
unsubs.push(bus.on("story:paused", (ev) => {
|
|
35137
|
-
return safe("on-pause (story)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
35138
|
-
}));
|
|
35139
|
-
unsubs.push(bus.on("run:paused", (ev) => {
|
|
35140
|
-
return safe("on-pause (run)", () => fireHook(hooks, "on-pause", hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }), workdir));
|
|
35141
|
-
}));
|
|
35142
|
-
unsubs.push(bus.on("run:completed", (ev) => {
|
|
35143
|
-
return safe("on-complete", () => fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir));
|
|
35144
|
-
}));
|
|
35145
|
-
unsubs.push(bus.on("run:resumed", (ev) => {
|
|
35146
|
-
return safe("on-resume", () => fireHook(hooks, "on-resume", hookCtx(feature, { status: "running" }), workdir));
|
|
35147
|
-
}));
|
|
35148
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35149
|
-
return safe("on-session-end (completed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "passed" }), workdir));
|
|
35150
|
-
}));
|
|
35151
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35152
|
-
return safe("on-session-end (failed)", () => fireHook(hooks, "on-session-end", hookCtx(feature, { storyId: ev.storyId, status: "failed" }), workdir));
|
|
35153
|
-
}));
|
|
35154
|
-
unsubs.push(bus.on("run:errored", (ev) => {
|
|
35155
|
-
return safe("on-error", () => fireHook(hooks, "on-error", hookCtx(feature, { reason: ev.reason }), workdir));
|
|
35156
|
-
}));
|
|
35157
|
-
return () => {
|
|
35158
|
-
for (const u of unsubs)
|
|
35159
|
-
u();
|
|
35160
|
-
};
|
|
35161
|
-
}
|
|
35162
|
-
var init_hooks2 = __esm(() => {
|
|
35163
|
-
init_story_context();
|
|
35164
|
-
init_hooks();
|
|
35165
|
-
init_logger2();
|
|
35166
|
-
});
|
|
35167
|
-
|
|
35168
|
-
// src/pipeline/subscribers/interaction.ts
|
|
35169
|
-
function wireInteraction(bus, interactionChain, config2) {
|
|
35170
|
-
const logger = getSafeLogger();
|
|
35171
|
-
const unsubs = [];
|
|
35172
|
-
if (interactionChain && isTriggerEnabled("human-review", config2)) {
|
|
35173
|
-
unsubs.push(bus.on("human-review:requested", (ev) => {
|
|
35174
|
-
executeTrigger("human-review", {
|
|
35175
|
-
featureName: ev.feature ?? "",
|
|
35176
|
-
storyId: ev.storyId,
|
|
35177
|
-
iteration: ev.attempts ?? 0,
|
|
35178
|
-
reason: ev.reason
|
|
35179
|
-
}, config2, interactionChain).catch((err) => {
|
|
35180
|
-
logger?.warn("interaction-subscriber", "human-review trigger failed", {
|
|
35181
|
-
storyId: ev.storyId,
|
|
35182
|
-
error: String(err)
|
|
35183
|
-
});
|
|
35184
|
-
});
|
|
35185
|
-
}));
|
|
35186
|
-
}
|
|
35187
|
-
if (interactionChain && isTriggerEnabled("max-retries", config2)) {
|
|
35188
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35189
|
-
if (!ev.countsTowardEscalation) {
|
|
35190
|
-
return;
|
|
35191
|
-
}
|
|
35192
|
-
executeTrigger("max-retries", {
|
|
35193
|
-
featureName: ev.feature ?? "",
|
|
35194
|
-
storyId: ev.storyId,
|
|
35195
|
-
iteration: ev.attempts ?? 0
|
|
35196
|
-
}, config2, interactionChain).then((response) => {
|
|
35197
|
-
if (response.action === "abort") {
|
|
35198
|
-
logger?.warn("interaction-subscriber", "max-retries abort requested", {
|
|
35199
|
-
storyId: ev.storyId
|
|
35200
|
-
});
|
|
35201
|
-
}
|
|
35202
|
-
}).catch((err) => {
|
|
35203
|
-
logger?.warn("interaction-subscriber", "max-retries trigger failed", {
|
|
35204
|
-
storyId: ev.storyId,
|
|
35205
|
-
error: String(err)
|
|
35206
|
-
});
|
|
35207
|
-
});
|
|
35208
|
-
}));
|
|
35209
|
-
}
|
|
35210
|
-
return () => {
|
|
35211
|
-
for (const u of unsubs)
|
|
35212
|
-
u();
|
|
35213
|
-
};
|
|
35214
|
-
}
|
|
35215
|
-
var init_interaction2 = __esm(() => {
|
|
35216
|
-
init_triggers();
|
|
35217
|
-
init_logger2();
|
|
35218
|
-
});
|
|
35219
|
-
|
|
35220
|
-
// src/pipeline/subscribers/registry.ts
|
|
35221
|
-
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
35222
|
-
import { homedir as homedir6 } from "os";
|
|
35223
|
-
import { basename as basename6, join as join50 } from "path";
|
|
35224
|
-
function wireRegistry(bus, feature, runId, workdir) {
|
|
35225
|
-
const logger = getSafeLogger();
|
|
35226
|
-
const project = basename6(workdir);
|
|
35227
|
-
const runDir = join50(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
35228
|
-
const metaFile = join50(runDir, "meta.json");
|
|
35229
|
-
const unsub = bus.on("run:started", (_ev) => {
|
|
35230
|
-
return (async () => {
|
|
35231
|
-
try {
|
|
35232
|
-
await mkdir3(runDir, { recursive: true });
|
|
35233
|
-
const meta3 = {
|
|
35234
|
-
runId,
|
|
35235
|
-
project,
|
|
35236
|
-
feature,
|
|
35237
|
-
workdir,
|
|
35238
|
-
statusPath: join50(workdir, ".nax", "features", feature, "status.json"),
|
|
35239
|
-
eventsDir: join50(workdir, ".nax", "features", feature, "runs"),
|
|
35240
|
-
registeredAt: new Date().toISOString()
|
|
35241
|
-
};
|
|
35242
|
-
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
35243
|
-
} catch (err) {
|
|
35244
|
-
logger?.warn("registry-writer", "Failed to write meta.json (non-fatal)", {
|
|
35245
|
-
path: metaFile,
|
|
35246
|
-
error: String(err)
|
|
35247
|
-
});
|
|
35248
|
-
}
|
|
35249
|
-
})();
|
|
35250
|
-
});
|
|
35251
|
-
return unsub;
|
|
35252
|
-
}
|
|
35253
|
-
var init_registry3 = __esm(() => {
|
|
35254
|
-
init_logger2();
|
|
35255
|
-
});
|
|
35256
|
-
|
|
35257
|
-
// src/pipeline/subscribers/reporters.ts
|
|
35258
|
-
function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
35259
|
-
const logger = getSafeLogger();
|
|
35260
|
-
const safe = (name, fn) => {
|
|
35261
|
-
return fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) })).catch(() => {});
|
|
35262
|
-
};
|
|
35263
|
-
const unsubs = [];
|
|
35264
|
-
unsubs.push(bus.on("run:started", (ev) => {
|
|
35265
|
-
return safe("onRunStart", async () => {
|
|
35266
|
-
const reporters = pluginRegistry.getReporters();
|
|
35267
|
-
for (const r of reporters) {
|
|
35268
|
-
if (r.onRunStart) {
|
|
35269
|
-
try {
|
|
35270
|
-
await r.onRunStart({
|
|
35271
|
-
runId,
|
|
35272
|
-
feature: ev.feature,
|
|
35273
|
-
totalStories: ev.totalStories,
|
|
35274
|
-
startTime: new Date(startTime).toISOString()
|
|
35275
|
-
});
|
|
35276
|
-
} catch (err) {
|
|
35277
|
-
logger?.warn("plugins", `Reporter '${r.name}' onRunStart failed`, { error: err });
|
|
35278
|
-
}
|
|
35279
|
-
}
|
|
35280
|
-
}
|
|
35281
|
-
});
|
|
35282
|
-
}));
|
|
35283
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
35284
|
-
return safe("onStoryComplete(completed)", async () => {
|
|
35285
|
-
const reporters = pluginRegistry.getReporters();
|
|
35286
|
-
for (const r of reporters) {
|
|
35287
|
-
if (r.onStoryComplete) {
|
|
35288
|
-
try {
|
|
35289
|
-
await r.onStoryComplete({
|
|
35290
|
-
runId,
|
|
35291
|
-
storyId: ev.storyId,
|
|
35292
|
-
status: "completed",
|
|
35293
|
-
runElapsedMs: ev.runElapsedMs,
|
|
35294
|
-
cost: ev.cost ?? 0,
|
|
35295
|
-
tier: ev.modelTier ?? "balanced",
|
|
35296
|
-
testStrategy: ev.testStrategy ?? "test-after"
|
|
35297
|
-
});
|
|
35298
|
-
} catch (err) {
|
|
35299
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
35300
|
-
}
|
|
35301
|
-
}
|
|
35302
|
-
}
|
|
35303
|
-
});
|
|
35304
|
-
}));
|
|
35305
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
35306
|
-
return safe("onStoryComplete(failed)", async () => {
|
|
35307
|
-
const reporters = pluginRegistry.getReporters();
|
|
35308
|
-
for (const r of reporters) {
|
|
35309
|
-
if (r.onStoryComplete) {
|
|
35310
|
-
try {
|
|
35311
|
-
await r.onStoryComplete({
|
|
35312
|
-
runId,
|
|
35313
|
-
storyId: ev.storyId,
|
|
35314
|
-
status: "failed",
|
|
35315
|
-
runElapsedMs: Date.now() - startTime,
|
|
35316
|
-
cost: 0,
|
|
35317
|
-
tier: "balanced",
|
|
35318
|
-
testStrategy: "test-after"
|
|
35319
|
-
});
|
|
35320
|
-
} catch (err) {
|
|
35321
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
35322
|
-
}
|
|
35323
|
-
}
|
|
35324
|
-
}
|
|
35325
|
-
});
|
|
35326
|
-
}));
|
|
35327
|
-
unsubs.push(bus.on("story:paused", (ev) => {
|
|
35328
|
-
return safe("onStoryComplete(paused)", async () => {
|
|
35329
|
-
const reporters = pluginRegistry.getReporters();
|
|
35330
|
-
for (const r of reporters) {
|
|
35331
|
-
if (r.onStoryComplete) {
|
|
35332
|
-
try {
|
|
35333
|
-
await r.onStoryComplete({
|
|
35334
|
-
runId,
|
|
35335
|
-
storyId: ev.storyId,
|
|
35336
|
-
status: "paused",
|
|
35337
|
-
runElapsedMs: Date.now() - startTime,
|
|
35338
|
-
cost: 0,
|
|
35339
|
-
tier: "balanced",
|
|
35340
|
-
testStrategy: "test-after"
|
|
35341
|
-
});
|
|
35342
|
-
} catch (err) {
|
|
35343
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
35344
|
-
}
|
|
35345
|
-
}
|
|
35346
|
-
}
|
|
35347
|
-
});
|
|
35348
|
-
}));
|
|
35349
|
-
unsubs.push(bus.on("run:completed", (ev) => {
|
|
35350
|
-
return safe("onRunEnd", async () => {
|
|
35351
|
-
const reporters = pluginRegistry.getReporters();
|
|
35352
|
-
for (const r of reporters) {
|
|
35353
|
-
if (r.onRunEnd) {
|
|
35354
|
-
try {
|
|
35355
|
-
await r.onRunEnd({
|
|
35356
|
-
runId,
|
|
35357
|
-
totalDurationMs: Date.now() - startTime,
|
|
35358
|
-
totalCost: ev.totalCost ?? 0,
|
|
35359
|
-
storySummary: {
|
|
35360
|
-
completed: ev.passedStories,
|
|
35361
|
-
failed: ev.failedStories,
|
|
35362
|
-
skipped: 0,
|
|
35363
|
-
paused: 0
|
|
35364
|
-
}
|
|
35365
|
-
});
|
|
35366
|
-
} catch (err) {
|
|
35367
|
-
logger?.warn("plugins", `Reporter '${r.name}' onRunEnd failed`, { error: err });
|
|
35368
|
-
}
|
|
35369
|
-
}
|
|
35370
|
-
}
|
|
35371
|
-
});
|
|
35372
|
-
}));
|
|
35373
|
-
return () => {
|
|
35374
|
-
for (const u of unsubs)
|
|
35375
|
-
u();
|
|
35376
|
-
};
|
|
35377
|
-
}
|
|
35378
|
-
var init_reporters = __esm(() => {
|
|
35379
|
-
init_logger2();
|
|
35380
|
-
});
|
|
35381
|
-
|
|
35382
|
-
// src/execution/deferred-review.ts
|
|
35383
|
-
var {spawn: spawn5 } = globalThis.Bun;
|
|
35384
|
-
async function captureRunStartRef(workdir) {
|
|
35385
|
-
try {
|
|
35386
|
-
const proc = _deferredReviewDeps.spawn({
|
|
35387
|
-
cmd: ["git", "rev-parse", "HEAD"],
|
|
35388
|
-
cwd: workdir,
|
|
35389
|
-
stdout: "pipe",
|
|
35390
|
-
stderr: "pipe"
|
|
35391
|
-
});
|
|
35392
|
-
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
35393
|
-
return stdout.trim();
|
|
35394
|
-
} catch {
|
|
35395
|
-
return "";
|
|
35396
|
-
}
|
|
35397
|
-
}
|
|
35398
|
-
async function getChangedFilesForDeferred(workdir, baseRef) {
|
|
35399
|
-
try {
|
|
35400
|
-
const proc = _deferredReviewDeps.spawn({
|
|
35401
|
-
cmd: ["git", "diff", "--name-only", `${baseRef}...HEAD`],
|
|
35402
|
-
cwd: workdir,
|
|
35403
|
-
stdout: "pipe",
|
|
35404
|
-
stderr: "pipe"
|
|
35405
|
-
});
|
|
35406
|
-
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
35407
|
-
return stdout.trim().split(`
|
|
35408
|
-
`).filter(Boolean);
|
|
35409
|
-
} catch {
|
|
35410
|
-
return [];
|
|
35411
|
-
}
|
|
35412
|
-
}
|
|
35413
|
-
async function runDeferredReview(workdir, reviewConfig, plugins, runStartRef) {
|
|
35414
|
-
if (!reviewConfig || reviewConfig.pluginMode !== "deferred") {
|
|
35415
|
-
return;
|
|
35416
|
-
}
|
|
35417
|
-
const reviewers = plugins.getReviewers();
|
|
35418
|
-
if (reviewers.length === 0) {
|
|
35419
|
-
return;
|
|
35420
|
-
}
|
|
35421
|
-
const changedFiles = await getChangedFilesForDeferred(workdir, runStartRef);
|
|
35422
|
-
const reviewerResults = [];
|
|
35423
|
-
let anyFailed = false;
|
|
35424
|
-
for (const reviewer of reviewers) {
|
|
35425
|
-
try {
|
|
35426
|
-
const result = await reviewer.check(workdir, changedFiles);
|
|
35427
|
-
reviewerResults.push({
|
|
35428
|
-
name: reviewer.name,
|
|
35429
|
-
passed: result.passed,
|
|
35430
|
-
output: result.output,
|
|
35431
|
-
exitCode: result.exitCode
|
|
35432
|
-
});
|
|
35433
|
-
if (!result.passed) {
|
|
35434
|
-
anyFailed = true;
|
|
35435
|
-
}
|
|
35436
|
-
} catch (error48) {
|
|
35437
|
-
const errorMsg = error48 instanceof Error ? error48.message : String(error48);
|
|
35438
|
-
reviewerResults.push({
|
|
35439
|
-
name: reviewer.name,
|
|
35440
|
-
passed: false,
|
|
35441
|
-
output: "",
|
|
35442
|
-
error: errorMsg
|
|
35443
|
-
});
|
|
35444
|
-
anyFailed = true;
|
|
35445
|
-
}
|
|
35446
|
-
}
|
|
35447
|
-
return { runStartRef, changedFiles, reviewerResults, anyFailed };
|
|
35448
|
-
}
|
|
35449
|
-
var _deferredReviewDeps;
|
|
35450
|
-
var init_deferred_review = __esm(() => {
|
|
35451
|
-
_deferredReviewDeps = { spawn: spawn5 };
|
|
35452
|
-
});
|
|
35453
|
-
|
|
35454
|
-
// src/execution/dry-run.ts
|
|
35455
|
-
async function handleDryRun(ctx) {
|
|
35456
|
-
const logger = getSafeLogger();
|
|
35457
|
-
ctx.statusWriter.setPrd(ctx.prd);
|
|
34936
|
+
ctx.statusWriter.setPrd(prd);
|
|
35458
34937
|
ctx.statusWriter.setCurrentStory({
|
|
35459
|
-
storyId:
|
|
35460
|
-
title:
|
|
35461
|
-
complexity:
|
|
35462
|
-
tddStrategy:
|
|
35463
|
-
model:
|
|
35464
|
-
attempt: (
|
|
34938
|
+
storyId: story.id,
|
|
34939
|
+
title: story.title,
|
|
34940
|
+
complexity: routing.complexity,
|
|
34941
|
+
tddStrategy: routing.testStrategy,
|
|
34942
|
+
model: routing.modelTier,
|
|
34943
|
+
attempt: (story.attempts ?? 0) + 1,
|
|
35465
34944
|
phase: "routing"
|
|
35466
34945
|
});
|
|
35467
|
-
await ctx.statusWriter.update(
|
|
35468
|
-
|
|
35469
|
-
|
|
35470
|
-
|
|
35471
|
-
|
|
35472
|
-
|
|
35473
|
-
|
|
35474
|
-
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35478
|
-
|
|
35479
|
-
|
|
35480
|
-
|
|
35481
|
-
|
|
35482
|
-
|
|
35483
|
-
|
|
35484
|
-
|
|
35485
|
-
|
|
35486
|
-
|
|
35487
|
-
|
|
35488
|
-
|
|
35489
|
-
|
|
35490
|
-
|
|
35491
|
-
|
|
34946
|
+
await ctx.statusWriter.update(totalCost, iterations);
|
|
34947
|
+
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
34948
|
+
const currentPrd = pipelineResult.context.prd;
|
|
34949
|
+
const handlerCtx = {
|
|
34950
|
+
config: ctx.config,
|
|
34951
|
+
prd: currentPrd,
|
|
34952
|
+
prdPath: ctx.prdPath,
|
|
34953
|
+
workdir: ctx.workdir,
|
|
34954
|
+
featureDir: ctx.featureDir,
|
|
34955
|
+
hooks: ctx.hooks,
|
|
34956
|
+
feature: ctx.feature,
|
|
34957
|
+
totalCost,
|
|
34958
|
+
startTime: ctx.startTime,
|
|
34959
|
+
runId: ctx.runId,
|
|
34960
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
34961
|
+
story,
|
|
34962
|
+
storiesToExecute,
|
|
34963
|
+
routing: pipelineResult.context.routing ?? routing,
|
|
34964
|
+
isBatchExecution,
|
|
34965
|
+
allStoryMetrics,
|
|
34966
|
+
storyGitRef,
|
|
34967
|
+
interactionChain: ctx.interactionChain,
|
|
34968
|
+
storyStartTime
|
|
34969
|
+
};
|
|
34970
|
+
if (pipelineResult.success) {
|
|
34971
|
+
const r2 = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
34972
|
+
return {
|
|
34973
|
+
prd: r2.prd,
|
|
34974
|
+
storiesCompletedDelta: r2.storiesCompletedDelta,
|
|
34975
|
+
costDelta: r2.costDelta,
|
|
34976
|
+
prdDirty: r2.prdDirty,
|
|
34977
|
+
finalAction: pipelineResult.finalAction
|
|
34978
|
+
};
|
|
35492
34979
|
}
|
|
35493
|
-
|
|
35494
|
-
|
|
35495
|
-
|
|
35496
|
-
|
|
34980
|
+
const r = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
34981
|
+
return {
|
|
34982
|
+
prd: r.prd,
|
|
34983
|
+
storiesCompletedDelta: 0,
|
|
34984
|
+
costDelta: r.costDelta,
|
|
34985
|
+
prdDirty: r.prdDirty,
|
|
34986
|
+
finalAction: pipelineResult.finalAction,
|
|
34987
|
+
reason: pipelineResult.reason,
|
|
34988
|
+
subStoryCount: pipelineResult.subStoryCount
|
|
34989
|
+
};
|
|
35497
34990
|
}
|
|
35498
|
-
var
|
|
34991
|
+
var _iterationRunnerDeps;
|
|
34992
|
+
var init_iteration_runner = __esm(() => {
|
|
34993
|
+
init_loader();
|
|
35499
34994
|
init_logger2();
|
|
35500
|
-
|
|
34995
|
+
init_runner();
|
|
34996
|
+
init_stages();
|
|
35501
34997
|
init_prd();
|
|
34998
|
+
init_git();
|
|
34999
|
+
init_dry_run();
|
|
35000
|
+
init_pipeline_result_handler();
|
|
35001
|
+
_iterationRunnerDeps = {
|
|
35002
|
+
loadConfigForWorkdir
|
|
35003
|
+
};
|
|
35502
35004
|
});
|
|
35503
35005
|
|
|
35504
|
-
// src/execution/
|
|
35505
|
-
|
|
35506
|
-
|
|
35507
|
-
|
|
35508
|
-
|
|
35509
|
-
|
|
35510
|
-
|
|
35511
|
-
await savePRD(pausedPrd, ctx.prdPath);
|
|
35512
|
-
logger?.warn("execution", "Story paused - no tier available (needs human review)", {
|
|
35513
|
-
storyId: ctx.story.id,
|
|
35514
|
-
failureCategory
|
|
35515
|
-
});
|
|
35516
|
-
if (ctx.featureDir) {
|
|
35517
|
-
await appendProgress(ctx.featureDir, ctx.story.id, "paused", `${ctx.story.title} \u2014 Execution stopped (needs human review)`);
|
|
35006
|
+
// src/execution/story-selector.ts
|
|
35007
|
+
function selectNextStories(prd, config2, batchPlan, currentBatchIndex, lastStoryId, useBatch) {
|
|
35008
|
+
if (useBatch && currentBatchIndex < batchPlan.length) {
|
|
35009
|
+
const batch = batchPlan[currentBatchIndex];
|
|
35010
|
+
const storiesToExecute = batch.stories.filter((s) => !s.passes && s.status !== "passed" && s.status !== "skipped" && s.status !== "blocked" && s.status !== "failed" && s.status !== "paused" && s.status !== "decomposed");
|
|
35011
|
+
if (storiesToExecute.length === 0) {
|
|
35012
|
+
return { selection: null, nextBatchIndex: currentBatchIndex + 1 };
|
|
35518
35013
|
}
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
|
|
35522
|
-
|
|
35523
|
-
|
|
35524
|
-
|
|
35525
|
-
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
|
|
35529
|
-
await savePRD(failedPrd, ctx.prdPath);
|
|
35530
|
-
logger?.error("execution", "Story failed - execution failed", {
|
|
35531
|
-
storyId: ctx.story.id
|
|
35532
|
-
});
|
|
35533
|
-
if (ctx.featureDir) {
|
|
35534
|
-
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Execution failed`);
|
|
35014
|
+
const story2 = storiesToExecute[0];
|
|
35015
|
+
return {
|
|
35016
|
+
selection: {
|
|
35017
|
+
story: story2,
|
|
35018
|
+
storiesToExecute,
|
|
35019
|
+
routing: buildPreviewRouting(story2, config2),
|
|
35020
|
+
isBatchExecution: batch.isBatch && storiesToExecute.length > 1
|
|
35021
|
+
},
|
|
35022
|
+
nextBatchIndex: currentBatchIndex + 1
|
|
35023
|
+
};
|
|
35535
35024
|
}
|
|
35536
|
-
|
|
35537
|
-
|
|
35538
|
-
|
|
35539
|
-
|
|
35540
|
-
|
|
35541
|
-
|
|
35542
|
-
|
|
35543
|
-
|
|
35025
|
+
const story = getNextStory(prd, lastStoryId, config2.execution.rectification?.maxRetries ?? 2);
|
|
35026
|
+
if (!story)
|
|
35027
|
+
return null;
|
|
35028
|
+
return {
|
|
35029
|
+
selection: {
|
|
35030
|
+
story,
|
|
35031
|
+
storiesToExecute: [story],
|
|
35032
|
+
routing: buildPreviewRouting(story, config2),
|
|
35033
|
+
isBatchExecution: false
|
|
35034
|
+
},
|
|
35035
|
+
nextBatchIndex: currentBatchIndex
|
|
35036
|
+
};
|
|
35544
35037
|
}
|
|
35545
|
-
|
|
35546
|
-
const
|
|
35547
|
-
const
|
|
35548
|
-
|
|
35549
|
-
|
|
35550
|
-
|
|
35551
|
-
|
|
35552
|
-
|
|
35553
|
-
|
|
35554
|
-
|
|
35038
|
+
function selectIndependentBatch(stories, maxCount) {
|
|
35039
|
+
const storyMap = new Map(stories.map((s) => [s.id, s]));
|
|
35040
|
+
const result = [];
|
|
35041
|
+
for (const story of stories) {
|
|
35042
|
+
if (result.length >= maxCount)
|
|
35043
|
+
break;
|
|
35044
|
+
if (story.passes || story.status === "passed" || story.status === "skipped" || story.status === "failed" || story.status === "paused" || story.status === "decomposed")
|
|
35045
|
+
continue;
|
|
35046
|
+
const allDepsFulfilled = story.dependencies.every((depId) => {
|
|
35047
|
+
const dep = storyMap.get(depId);
|
|
35048
|
+
if (!dep)
|
|
35049
|
+
return true;
|
|
35050
|
+
return dep.passes || dep.status === "passed";
|
|
35555
35051
|
});
|
|
35556
|
-
if (
|
|
35557
|
-
|
|
35052
|
+
if (allDepsFulfilled) {
|
|
35053
|
+
result.push(story);
|
|
35558
35054
|
}
|
|
35559
|
-
pipelineEventBus.emit({
|
|
35560
|
-
type: "story:paused",
|
|
35561
|
-
storyId: ctx.story.id,
|
|
35562
|
-
reason: `Max attempts reached (${failureCategory ?? "unknown"} requires human review)`,
|
|
35563
|
-
cost: ctx.totalCost
|
|
35564
|
-
});
|
|
35565
|
-
return { outcome: "paused", prdDirty: true, prd: pausedPrd };
|
|
35566
35055
|
}
|
|
35567
|
-
|
|
35568
|
-
markStoryFailed(failedPrd, ctx.story.id, failureCategory, undefined);
|
|
35569
|
-
await savePRD(failedPrd, ctx.prdPath);
|
|
35570
|
-
logger?.error("execution", "Story failed - max attempts reached", {
|
|
35571
|
-
storyId: ctx.story.id,
|
|
35572
|
-
failureCategory
|
|
35573
|
-
});
|
|
35574
|
-
if (ctx.featureDir) {
|
|
35575
|
-
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 Max attempts reached`);
|
|
35576
|
-
}
|
|
35577
|
-
pipelineEventBus.emit({
|
|
35578
|
-
type: "story:failed",
|
|
35579
|
-
storyId: ctx.story.id,
|
|
35580
|
-
story: ctx.story,
|
|
35581
|
-
reason: "Max attempts reached",
|
|
35582
|
-
countsTowardEscalation: true
|
|
35583
|
-
});
|
|
35584
|
-
return { outcome: "failed", prdDirty: true, prd: failedPrd };
|
|
35056
|
+
return result;
|
|
35585
35057
|
}
|
|
35586
|
-
var
|
|
35058
|
+
var init_story_selector = __esm(() => {
|
|
35587
35059
|
init_logger2();
|
|
35588
|
-
init_event_bus();
|
|
35589
35060
|
init_prd();
|
|
35590
|
-
init_progress();
|
|
35591
|
-
init_tier_escalation();
|
|
35592
35061
|
});
|
|
35593
35062
|
|
|
35594
|
-
// src/execution/
|
|
35595
|
-
|
|
35596
|
-
|
|
35597
|
-
|
|
35598
|
-
|
|
35599
|
-
|
|
35600
|
-
|
|
35601
|
-
|
|
35602
|
-
|
|
35603
|
-
|
|
35604
|
-
|
|
35605
|
-
|
|
35606
|
-
|
|
35607
|
-
|
|
35608
|
-
|
|
35609
|
-
|
|
35610
|
-
|
|
35611
|
-
|
|
35612
|
-
|
|
35613
|
-
|
|
35614
|
-
|
|
35615
|
-
|
|
35616
|
-
|
|
35617
|
-
|
|
35618
|
-
|
|
35619
|
-
|
|
35620
|
-
|
|
35621
|
-
|
|
35622
|
-
|
|
35063
|
+
// src/execution/parallel-worker.ts
|
|
35064
|
+
var exports_parallel_worker = {};
|
|
35065
|
+
__export(exports_parallel_worker, {
|
|
35066
|
+
executeStoryInWorktree: () => executeStoryInWorktree,
|
|
35067
|
+
executeParallelBatch: () => executeParallelBatch
|
|
35068
|
+
});
|
|
35069
|
+
async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
|
|
35070
|
+
const logger = getSafeLogger();
|
|
35071
|
+
try {
|
|
35072
|
+
const pipelineContext = {
|
|
35073
|
+
...context,
|
|
35074
|
+
effectiveConfig: context.effectiveConfig ?? context.config,
|
|
35075
|
+
story,
|
|
35076
|
+
stories: [story],
|
|
35077
|
+
workdir: worktreePath,
|
|
35078
|
+
routing
|
|
35079
|
+
};
|
|
35080
|
+
logger?.debug("parallel", "Executing story in worktree", {
|
|
35081
|
+
storyId: story.id,
|
|
35082
|
+
worktreePath
|
|
35083
|
+
});
|
|
35084
|
+
const result = await runPipeline(defaultPipeline, pipelineContext, eventEmitter);
|
|
35085
|
+
return {
|
|
35086
|
+
success: result.success,
|
|
35087
|
+
cost: result.context.agentResult?.estimatedCost || 0,
|
|
35088
|
+
error: result.success ? undefined : result.reason,
|
|
35089
|
+
pipelineResult: result
|
|
35090
|
+
};
|
|
35091
|
+
} catch (error48) {
|
|
35092
|
+
return {
|
|
35093
|
+
success: false,
|
|
35094
|
+
cost: 0,
|
|
35095
|
+
error: errorMessage(error48)
|
|
35096
|
+
};
|
|
35623
35097
|
}
|
|
35624
35098
|
}
|
|
35625
|
-
function
|
|
35626
|
-
return verifyResult?.status === "RUNTIME_CRASH";
|
|
35627
|
-
}
|
|
35628
|
-
async function handleTierEscalation(ctx) {
|
|
35099
|
+
async function executeParallelBatch(stories, projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs) {
|
|
35629
35100
|
const logger = getSafeLogger();
|
|
35630
|
-
|
|
35631
|
-
|
|
35632
|
-
|
|
35101
|
+
const results = {
|
|
35102
|
+
pipelinePassed: [],
|
|
35103
|
+
merged: [],
|
|
35104
|
+
failed: [],
|
|
35105
|
+
totalCost: 0,
|
|
35106
|
+
mergeConflicts: [],
|
|
35107
|
+
storyCosts: new Map
|
|
35108
|
+
};
|
|
35109
|
+
const executing = new Set;
|
|
35110
|
+
for (const story of stories) {
|
|
35111
|
+
const worktreePath = worktreePaths.get(story.id);
|
|
35112
|
+
if (!worktreePath) {
|
|
35113
|
+
results.failed.push({
|
|
35114
|
+
story,
|
|
35115
|
+
error: "Worktree not created"
|
|
35116
|
+
});
|
|
35117
|
+
continue;
|
|
35118
|
+
}
|
|
35119
|
+
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
35120
|
+
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
35121
|
+
const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
|
|
35122
|
+
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
35123
|
+
results.totalCost += result.cost;
|
|
35124
|
+
results.storyCosts.set(story.id, result.cost);
|
|
35125
|
+
if (result.success) {
|
|
35126
|
+
results.pipelinePassed.push(story);
|
|
35127
|
+
logger?.info("parallel", "Story execution succeeded", {
|
|
35128
|
+
storyId: story.id,
|
|
35129
|
+
cost: result.cost
|
|
35130
|
+
});
|
|
35131
|
+
} else {
|
|
35132
|
+
results.failed.push({ story, error: result.error || "Unknown error", pipelineResult: result.pipelineResult });
|
|
35133
|
+
logger?.error("parallel", "Story execution failed", {
|
|
35134
|
+
storyId: story.id,
|
|
35135
|
+
error: result.error
|
|
35136
|
+
});
|
|
35137
|
+
}
|
|
35138
|
+
}).finally(() => {
|
|
35139
|
+
executing.delete(executePromise);
|
|
35633
35140
|
});
|
|
35634
|
-
|
|
35635
|
-
|
|
35636
|
-
|
|
35637
|
-
|
|
35638
|
-
const storiesToEscalate = ctx.isBatchExecution && escalateWholeBatch ? ctx.storiesToExecute : [ctx.story];
|
|
35639
|
-
const escalateRetryAsLite = ctx.pipelineResult.context.retryAsLite === true;
|
|
35640
|
-
const escalateFailureCategory = ctx.pipelineResult.context.tddFailureCategory;
|
|
35641
|
-
const escalateReviewFindings = ctx.pipelineResult.context.reviewFindings;
|
|
35642
|
-
const escalateRetryAsTestAfter = escalateFailureCategory === "greenfield-no-tests";
|
|
35643
|
-
const routingMode = ctx.config.routing.llm?.mode ?? "hybrid";
|
|
35644
|
-
if (!nextTier || !ctx.config.autoMode.escalation.enabled) {
|
|
35645
|
-
return await handleNoTierAvailable(ctx, escalateFailureCategory);
|
|
35141
|
+
executing.add(executePromise);
|
|
35142
|
+
if (executing.size >= maxConcurrency) {
|
|
35143
|
+
await Promise.race(executing);
|
|
35144
|
+
}
|
|
35646
35145
|
}
|
|
35647
|
-
|
|
35648
|
-
|
|
35649
|
-
|
|
35650
|
-
|
|
35146
|
+
await Promise.all(executing);
|
|
35147
|
+
return results;
|
|
35148
|
+
}
|
|
35149
|
+
var init_parallel_worker = __esm(() => {
|
|
35150
|
+
init_logger2();
|
|
35151
|
+
init_runner();
|
|
35152
|
+
init_stages();
|
|
35153
|
+
init_routing();
|
|
35154
|
+
});
|
|
35155
|
+
|
|
35156
|
+
// src/worktree/manager.ts
|
|
35157
|
+
var exports_manager = {};
|
|
35158
|
+
__export(exports_manager, {
|
|
35159
|
+
_managerDeps: () => _managerDeps,
|
|
35160
|
+
WorktreeManager: () => WorktreeManager
|
|
35161
|
+
});
|
|
35162
|
+
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
35163
|
+
import { join as join50 } from "path";
|
|
35164
|
+
|
|
35165
|
+
class WorktreeManager {
|
|
35166
|
+
async create(projectRoot, storyId) {
|
|
35167
|
+
validateStoryId(storyId);
|
|
35168
|
+
const worktreePath = join50(projectRoot, ".nax-wt", storyId);
|
|
35169
|
+
const branchName = `nax/${storyId}`;
|
|
35170
|
+
try {
|
|
35171
|
+
const proc = _managerDeps.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
35172
|
+
cwd: projectRoot,
|
|
35173
|
+
stdout: "pipe",
|
|
35174
|
+
stderr: "pipe"
|
|
35175
|
+
});
|
|
35176
|
+
const exitCode = await proc.exited;
|
|
35177
|
+
if (exitCode !== 0) {
|
|
35178
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35179
|
+
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
35180
|
+
}
|
|
35181
|
+
} catch (error48) {
|
|
35182
|
+
if (error48 instanceof Error) {
|
|
35183
|
+
if (error48.message.includes("not a git repository")) {
|
|
35184
|
+
throw new Error(`Not a git repository: ${projectRoot}`);
|
|
35185
|
+
}
|
|
35186
|
+
if (error48.message.includes("already exists")) {
|
|
35187
|
+
throw new Error(`Worktree for story ${storyId} already exists at ${worktreePath}`);
|
|
35188
|
+
}
|
|
35189
|
+
throw error48;
|
|
35190
|
+
}
|
|
35191
|
+
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
35192
|
+
}
|
|
35193
|
+
const nodeModulesSource = join50(projectRoot, "node_modules");
|
|
35194
|
+
if (existsSync32(nodeModulesSource)) {
|
|
35195
|
+
const nodeModulesTarget = join50(worktreePath, "node_modules");
|
|
35196
|
+
try {
|
|
35197
|
+
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
35198
|
+
} catch (error48) {
|
|
35199
|
+
await this.remove(projectRoot, storyId);
|
|
35200
|
+
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
35201
|
+
}
|
|
35202
|
+
}
|
|
35203
|
+
const envSource = join50(projectRoot, ".env");
|
|
35204
|
+
if (existsSync32(envSource)) {
|
|
35205
|
+
const envTarget = join50(worktreePath, ".env");
|
|
35206
|
+
try {
|
|
35207
|
+
symlinkSync(envSource, envTarget, "file");
|
|
35208
|
+
} catch (error48) {
|
|
35209
|
+
await this.remove(projectRoot, storyId);
|
|
35210
|
+
throw new Error(`Failed to symlink .env: ${errorMessage(error48)}`);
|
|
35211
|
+
}
|
|
35212
|
+
}
|
|
35651
35213
|
}
|
|
35652
|
-
|
|
35653
|
-
|
|
35654
|
-
const
|
|
35655
|
-
|
|
35656
|
-
|
|
35657
|
-
|
|
35658
|
-
|
|
35659
|
-
|
|
35214
|
+
async remove(projectRoot, storyId) {
|
|
35215
|
+
validateStoryId(storyId);
|
|
35216
|
+
const worktreePath = join50(projectRoot, ".nax-wt", storyId);
|
|
35217
|
+
const branchName = `nax/${storyId}`;
|
|
35218
|
+
try {
|
|
35219
|
+
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
35220
|
+
cwd: projectRoot,
|
|
35221
|
+
stdout: "pipe",
|
|
35222
|
+
stderr: "pipe"
|
|
35660
35223
|
});
|
|
35661
|
-
|
|
35662
|
-
|
|
35663
|
-
|
|
35664
|
-
|
|
35665
|
-
|
|
35224
|
+
const exitCode = await proc.exited;
|
|
35225
|
+
if (exitCode !== 0) {
|
|
35226
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35227
|
+
if (stderr.includes("not found") || stderr.includes("does not exist") || stderr.includes("no such worktree") || stderr.includes("is not a working tree")) {
|
|
35228
|
+
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
35229
|
+
}
|
|
35230
|
+
throw new Error(`Failed to remove worktree: ${stderr || "unknown error"}`);
|
|
35231
|
+
}
|
|
35232
|
+
} catch (error48) {
|
|
35233
|
+
if (error48 instanceof Error) {
|
|
35234
|
+
throw error48;
|
|
35235
|
+
}
|
|
35236
|
+
throw new Error(`Failed to remove worktree: ${String(error48)}`);
|
|
35237
|
+
}
|
|
35238
|
+
try {
|
|
35239
|
+
const proc = _managerDeps.spawn(["git", "branch", "-D", branchName], {
|
|
35240
|
+
cwd: projectRoot,
|
|
35241
|
+
stdout: "pipe",
|
|
35242
|
+
stderr: "pipe"
|
|
35243
|
+
});
|
|
35244
|
+
const exitCode = await proc.exited;
|
|
35245
|
+
if (exitCode !== 0) {
|
|
35246
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35247
|
+
if (!stderr.includes("not found")) {
|
|
35248
|
+
const logger = getSafeLogger();
|
|
35249
|
+
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
35250
|
+
}
|
|
35251
|
+
}
|
|
35252
|
+
} catch (error48) {
|
|
35253
|
+
const logger = getSafeLogger();
|
|
35254
|
+
logger?.warn("worktree", `Failed to delete branch ${branchName}`, {
|
|
35255
|
+
error: errorMessage(error48)
|
|
35666
35256
|
});
|
|
35667
35257
|
}
|
|
35668
35258
|
}
|
|
35669
|
-
|
|
35670
|
-
|
|
35671
|
-
|
|
35672
|
-
|
|
35673
|
-
|
|
35674
|
-
|
|
35675
|
-
|
|
35676
|
-
|
|
35677
|
-
|
|
35678
|
-
|
|
35679
|
-
|
|
35680
|
-
|
|
35681
|
-
|
|
35682
|
-
|
|
35683
|
-
|
|
35684
|
-
|
|
35685
|
-
|
|
35686
|
-
|
|
35687
|
-
|
|
35688
|
-
|
|
35689
|
-
const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
|
|
35690
|
-
return {
|
|
35691
|
-
...s,
|
|
35692
|
-
attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
|
|
35693
|
-
routing: updatedRouting,
|
|
35694
|
-
priorErrors: [...s.priorErrors || [], errorMessage2],
|
|
35695
|
-
priorFailures: [...s.priorFailures || [], escalationFailure]
|
|
35696
|
-
};
|
|
35697
|
-
})
|
|
35698
|
-
};
|
|
35699
|
-
await _tierEscalationDeps.savePRD(updatedPrd, ctx.prdPath);
|
|
35700
|
-
for (const story of storiesToEscalate) {
|
|
35701
|
-
clearCacheForStory(story.id);
|
|
35259
|
+
async list(projectRoot) {
|
|
35260
|
+
try {
|
|
35261
|
+
const proc = _managerDeps.spawn(["git", "worktree", "list", "--porcelain"], {
|
|
35262
|
+
cwd: projectRoot,
|
|
35263
|
+
stdout: "pipe",
|
|
35264
|
+
stderr: "pipe"
|
|
35265
|
+
});
|
|
35266
|
+
const exitCode = await proc.exited;
|
|
35267
|
+
if (exitCode !== 0) {
|
|
35268
|
+
const stderr = await new Response(proc.stderr).text();
|
|
35269
|
+
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
35270
|
+
}
|
|
35271
|
+
const stdout = await new Response(proc.stdout).text();
|
|
35272
|
+
return this.parseWorktreeList(stdout);
|
|
35273
|
+
} catch (error48) {
|
|
35274
|
+
if (error48 instanceof Error) {
|
|
35275
|
+
throw error48;
|
|
35276
|
+
}
|
|
35277
|
+
throw new Error(`Failed to list worktrees: ${String(error48)}`);
|
|
35278
|
+
}
|
|
35702
35279
|
}
|
|
35703
|
-
|
|
35704
|
-
|
|
35280
|
+
parseWorktreeList(output) {
|
|
35281
|
+
const worktrees = [];
|
|
35282
|
+
const lines = output.trim().split(`
|
|
35283
|
+
`);
|
|
35284
|
+
let currentWorktree = {};
|
|
35285
|
+
for (const line of lines) {
|
|
35286
|
+
if (line.startsWith("worktree ")) {
|
|
35287
|
+
currentWorktree.path = line.substring("worktree ".length);
|
|
35288
|
+
} else if (line.startsWith("branch ")) {
|
|
35289
|
+
currentWorktree.branch = line.substring("branch ".length).replace("refs/heads/", "");
|
|
35290
|
+
} else if (line === "") {
|
|
35291
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
35292
|
+
worktrees.push(currentWorktree);
|
|
35293
|
+
}
|
|
35294
|
+
currentWorktree = {};
|
|
35295
|
+
}
|
|
35296
|
+
}
|
|
35297
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
35298
|
+
worktrees.push(currentWorktree);
|
|
35299
|
+
}
|
|
35300
|
+
return worktrees;
|
|
35705
35301
|
}
|
|
35706
|
-
return {
|
|
35707
|
-
outcome: "escalated",
|
|
35708
|
-
prdDirty: true,
|
|
35709
|
-
prd: updatedPrd
|
|
35710
|
-
};
|
|
35711
35302
|
}
|
|
35712
|
-
var
|
|
35713
|
-
var
|
|
35714
|
-
init_hooks();
|
|
35303
|
+
var _managerDeps;
|
|
35304
|
+
var init_manager = __esm(() => {
|
|
35715
35305
|
init_logger2();
|
|
35716
|
-
|
|
35717
|
-
|
|
35718
|
-
|
|
35719
|
-
init_escalation();
|
|
35720
|
-
init_helpers();
|
|
35721
|
-
init_progress();
|
|
35722
|
-
init_tier_outcome();
|
|
35723
|
-
_tierEscalationDeps = {
|
|
35724
|
-
savePRD
|
|
35306
|
+
init_bun_deps();
|
|
35307
|
+
_managerDeps = {
|
|
35308
|
+
spawn
|
|
35725
35309
|
};
|
|
35726
35310
|
});
|
|
35727
35311
|
|
|
35728
|
-
// src/
|
|
35729
|
-
var
|
|
35730
|
-
|
|
35312
|
+
// src/worktree/merge.ts
|
|
35313
|
+
var exports_merge = {};
|
|
35314
|
+
__export(exports_merge, {
|
|
35315
|
+
_mergeDeps: () => _mergeDeps,
|
|
35316
|
+
MergeEngine: () => MergeEngine
|
|
35731
35317
|
});
|
|
35732
35318
|
|
|
35733
|
-
|
|
35734
|
-
|
|
35735
|
-
|
|
35736
|
-
|
|
35737
|
-
/\.spec\.(ts|js|tsx|jsx)$/,
|
|
35738
|
-
/package-lock\.json$/,
|
|
35739
|
-
/bun\.lock(b?)$/,
|
|
35740
|
-
/\.gitignore$/,
|
|
35741
|
-
/^nax\//
|
|
35742
|
-
];
|
|
35743
|
-
return files.filter((f) => !NOISE.some((p) => p.test(f))).slice(0, 15);
|
|
35744
|
-
}
|
|
35745
|
-
async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
35746
|
-
const logger = getSafeLogger();
|
|
35747
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
35748
|
-
const prd = ctx.prd;
|
|
35749
|
-
if (pipelineResult.context.storyMetrics) {
|
|
35750
|
-
ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
|
|
35319
|
+
class MergeEngine {
|
|
35320
|
+
worktreeManager;
|
|
35321
|
+
constructor(worktreeManager) {
|
|
35322
|
+
this.worktreeManager = worktreeManager;
|
|
35751
35323
|
}
|
|
35752
|
-
|
|
35753
|
-
|
|
35754
|
-
|
|
35755
|
-
|
|
35756
|
-
|
|
35757
|
-
|
|
35758
|
-
|
|
35759
|
-
|
|
35760
|
-
|
|
35761
|
-
|
|
35324
|
+
async merge(projectRoot, storyId) {
|
|
35325
|
+
const branchName = `nax/${storyId}`;
|
|
35326
|
+
try {
|
|
35327
|
+
const mergeProc = _mergeDeps.spawn(["git", "merge", "--no-ff", branchName, "-m", `Merge branch '${branchName}'`], {
|
|
35328
|
+
cwd: projectRoot,
|
|
35329
|
+
stdout: "pipe",
|
|
35330
|
+
stderr: "pipe"
|
|
35331
|
+
});
|
|
35332
|
+
const exitCode = await mergeProc.exited;
|
|
35333
|
+
const stderr = await new Response(mergeProc.stderr).text();
|
|
35334
|
+
const stdout = await new Response(mergeProc.stdout).text();
|
|
35335
|
+
if (exitCode === 0) {
|
|
35336
|
+
try {
|
|
35337
|
+
await this.worktreeManager.remove(projectRoot, storyId);
|
|
35338
|
+
} catch (error48) {
|
|
35339
|
+
const logger = getSafeLogger();
|
|
35340
|
+
logger?.warn("worktree", `Failed to cleanup worktree for ${storyId}`, {
|
|
35341
|
+
error: errorMessage(error48)
|
|
35342
|
+
});
|
|
35343
|
+
}
|
|
35344
|
+
return { success: true };
|
|
35345
|
+
}
|
|
35346
|
+
const output = `${stdout}
|
|
35347
|
+
${stderr}`;
|
|
35348
|
+
if (output.includes("CONFLICT") || output.includes("conflict") || output.includes("Automatic merge failed")) {
|
|
35349
|
+
const conflictFiles = await this.getConflictFiles(projectRoot);
|
|
35350
|
+
await this.abortMerge(projectRoot);
|
|
35351
|
+
return {
|
|
35352
|
+
success: false,
|
|
35353
|
+
conflictFiles
|
|
35354
|
+
};
|
|
35355
|
+
}
|
|
35356
|
+
throw new Error(`Merge failed: ${stderr || stdout || "unknown error"}`);
|
|
35357
|
+
} catch (error48) {
|
|
35358
|
+
if (error48 instanceof Error) {
|
|
35359
|
+
throw error48;
|
|
35360
|
+
}
|
|
35361
|
+
throw new Error(`Failed to merge branch ${branchName}: ${String(error48)}`);
|
|
35362
|
+
}
|
|
35762
35363
|
}
|
|
35763
|
-
|
|
35764
|
-
|
|
35765
|
-
|
|
35766
|
-
|
|
35767
|
-
|
|
35768
|
-
|
|
35769
|
-
|
|
35364
|
+
async mergeAll(projectRoot, storyIds, dependencies) {
|
|
35365
|
+
const orderedStories = this.topologicalSort(storyIds, dependencies);
|
|
35366
|
+
const results = [];
|
|
35367
|
+
const failedStories = new Set;
|
|
35368
|
+
for (const storyId of orderedStories) {
|
|
35369
|
+
const deps = dependencies[storyId] || [];
|
|
35370
|
+
const hasFailedDeps = deps.some((dep) => failedStories.has(dep));
|
|
35371
|
+
if (hasFailedDeps) {
|
|
35372
|
+
results.push({
|
|
35373
|
+
success: false,
|
|
35374
|
+
storyId,
|
|
35375
|
+
conflictFiles: []
|
|
35376
|
+
});
|
|
35377
|
+
failedStories.add(storyId);
|
|
35378
|
+
continue;
|
|
35379
|
+
}
|
|
35380
|
+
let result = await this.merge(projectRoot, storyId);
|
|
35381
|
+
if (!result.success && result.conflictFiles) {
|
|
35382
|
+
try {
|
|
35383
|
+
await this.rebaseWorktree(projectRoot, storyId);
|
|
35384
|
+
result = await this.merge(projectRoot, storyId);
|
|
35385
|
+
if (!result.success) {
|
|
35386
|
+
results.push({
|
|
35387
|
+
success: false,
|
|
35388
|
+
storyId,
|
|
35389
|
+
conflictFiles: result.conflictFiles,
|
|
35390
|
+
retryCount: 1
|
|
35391
|
+
});
|
|
35392
|
+
failedStories.add(storyId);
|
|
35393
|
+
continue;
|
|
35394
|
+
}
|
|
35395
|
+
results.push({
|
|
35396
|
+
success: true,
|
|
35397
|
+
storyId,
|
|
35398
|
+
retryCount: 1
|
|
35399
|
+
});
|
|
35400
|
+
} catch (error48) {
|
|
35401
|
+
results.push({
|
|
35402
|
+
success: false,
|
|
35403
|
+
storyId,
|
|
35404
|
+
conflictFiles: result.conflictFiles,
|
|
35405
|
+
retryCount: 1
|
|
35406
|
+
});
|
|
35407
|
+
failedStories.add(storyId);
|
|
35770
35408
|
}
|
|
35771
|
-
|
|
35772
|
-
|
|
35773
|
-
|
|
35409
|
+
} else if (result.success) {
|
|
35410
|
+
results.push({
|
|
35411
|
+
success: true,
|
|
35412
|
+
storyId,
|
|
35413
|
+
retryCount: 0
|
|
35414
|
+
});
|
|
35415
|
+
} else {
|
|
35416
|
+
results.push({
|
|
35417
|
+
success: false,
|
|
35418
|
+
storyId,
|
|
35419
|
+
retryCount: 0
|
|
35420
|
+
});
|
|
35421
|
+
failedStories.add(storyId);
|
|
35422
|
+
}
|
|
35423
|
+
}
|
|
35424
|
+
return results;
|
|
35425
|
+
}
|
|
35426
|
+
topologicalSort(storyIds, dependencies) {
|
|
35427
|
+
const visited = new Set;
|
|
35428
|
+
const sorted = [];
|
|
35429
|
+
const visiting = new Set;
|
|
35430
|
+
const visit = (storyId) => {
|
|
35431
|
+
if (visited.has(storyId)) {
|
|
35432
|
+
return;
|
|
35433
|
+
}
|
|
35434
|
+
if (visiting.has(storyId)) {
|
|
35435
|
+
throw new Error(`Circular dependency detected involving ${storyId}`);
|
|
35436
|
+
}
|
|
35437
|
+
visiting.add(storyId);
|
|
35438
|
+
const deps = dependencies[storyId] || [];
|
|
35439
|
+
for (const dep of deps) {
|
|
35440
|
+
if (storyIds.includes(dep)) {
|
|
35441
|
+
visit(dep);
|
|
35774
35442
|
}
|
|
35775
|
-
}
|
|
35443
|
+
}
|
|
35444
|
+
visiting.delete(storyId);
|
|
35445
|
+
visited.add(storyId);
|
|
35446
|
+
sorted.push(storyId);
|
|
35447
|
+
};
|
|
35448
|
+
for (const storyId of storyIds) {
|
|
35449
|
+
visit(storyId);
|
|
35776
35450
|
}
|
|
35451
|
+
return sorted;
|
|
35777
35452
|
}
|
|
35778
|
-
|
|
35779
|
-
|
|
35780
|
-
|
|
35781
|
-
|
|
35782
|
-
|
|
35783
|
-
|
|
35784
|
-
|
|
35785
|
-
costLimit: ctx.config.execution.costLimit,
|
|
35786
|
-
elapsedMs: Date.now() - ctx.startTime,
|
|
35787
|
-
storyDurationMs: ctx.storyStartTime ? Date.now() - ctx.storyStartTime : undefined
|
|
35788
|
-
});
|
|
35789
|
-
return { storiesCompletedDelta, costDelta, prd, prdDirty: true };
|
|
35790
|
-
}
|
|
35791
|
-
async function handlePipelineFailure(ctx, pipelineResult) {
|
|
35792
|
-
const logger = getSafeLogger();
|
|
35793
|
-
let prd = ctx.prd;
|
|
35794
|
-
let prdDirty = false;
|
|
35795
|
-
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
35796
|
-
switch (pipelineResult.finalAction) {
|
|
35797
|
-
case "pause":
|
|
35798
|
-
markStoryPaused(prd, ctx.story.id);
|
|
35799
|
-
await savePRD(prd, ctx.prdPath);
|
|
35800
|
-
prdDirty = true;
|
|
35801
|
-
logger?.warn("pipeline", "Story paused", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
35802
|
-
pipelineEventBus.emit({
|
|
35803
|
-
type: "story:paused",
|
|
35804
|
-
storyId: ctx.story.id,
|
|
35805
|
-
reason: pipelineResult.reason || "Pipeline paused",
|
|
35806
|
-
cost: ctx.totalCost
|
|
35453
|
+
async rebaseWorktree(projectRoot, storyId) {
|
|
35454
|
+
const worktreePath = `${projectRoot}/.nax-wt/${storyId}`;
|
|
35455
|
+
try {
|
|
35456
|
+
const currentBranchProc = _mergeDeps.spawn(["git", "rev-parse", "--abbrev-ref", "HEAD"], {
|
|
35457
|
+
cwd: projectRoot,
|
|
35458
|
+
stdout: "pipe",
|
|
35459
|
+
stderr: "pipe"
|
|
35807
35460
|
});
|
|
35808
|
-
|
|
35809
|
-
|
|
35810
|
-
|
|
35811
|
-
prdDirty = true;
|
|
35812
|
-
break;
|
|
35813
|
-
case "fail":
|
|
35814
|
-
markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory, pipelineResult.stoppedAtStage);
|
|
35815
|
-
await savePRD(prd, ctx.prdPath);
|
|
35816
|
-
prdDirty = true;
|
|
35817
|
-
logger?.error("pipeline", "Story failed", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
35818
|
-
if (ctx.featureDir) {
|
|
35819
|
-
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 ${pipelineResult.reason}`);
|
|
35461
|
+
const exitCode = await currentBranchProc.exited;
|
|
35462
|
+
if (exitCode !== 0) {
|
|
35463
|
+
throw new Error("Failed to get current branch");
|
|
35820
35464
|
}
|
|
35821
|
-
|
|
35822
|
-
|
|
35823
|
-
|
|
35824
|
-
|
|
35825
|
-
|
|
35826
|
-
countsTowardEscalation: true,
|
|
35827
|
-
feature: ctx.feature,
|
|
35828
|
-
attempts: ctx.story.attempts
|
|
35465
|
+
const currentBranch = (await new Response(currentBranchProc.stdout).text()).trim();
|
|
35466
|
+
const rebaseProc = _mergeDeps.spawn(["git", "rebase", currentBranch], {
|
|
35467
|
+
cwd: worktreePath,
|
|
35468
|
+
stdout: "pipe",
|
|
35469
|
+
stderr: "pipe"
|
|
35829
35470
|
});
|
|
35830
|
-
|
|
35831
|
-
|
|
35832
|
-
|
|
35833
|
-
|
|
35834
|
-
|
|
35835
|
-
|
|
35836
|
-
|
|
35471
|
+
const rebaseExitCode = await rebaseProc.exited;
|
|
35472
|
+
if (rebaseExitCode !== 0) {
|
|
35473
|
+
const stderr = await new Response(rebaseProc.stderr).text();
|
|
35474
|
+
const abortProc = _mergeDeps.spawn(["git", "rebase", "--abort"], {
|
|
35475
|
+
cwd: worktreePath,
|
|
35476
|
+
stdout: "pipe",
|
|
35477
|
+
stderr: "pipe"
|
|
35837
35478
|
});
|
|
35479
|
+
await abortProc.exited;
|
|
35480
|
+
throw new Error(`Rebase failed: ${stderr || "unknown error"}`);
|
|
35838
35481
|
}
|
|
35839
|
-
|
|
35840
|
-
|
|
35841
|
-
|
|
35842
|
-
|
|
35843
|
-
|
|
35844
|
-
|
|
35845
|
-
|
|
35846
|
-
|
|
35847
|
-
|
|
35848
|
-
|
|
35849
|
-
|
|
35850
|
-
|
|
35851
|
-
|
|
35852
|
-
|
|
35853
|
-
|
|
35854
|
-
|
|
35855
|
-
|
|
35482
|
+
} catch (error48) {
|
|
35483
|
+
if (error48 instanceof Error) {
|
|
35484
|
+
throw error48;
|
|
35485
|
+
}
|
|
35486
|
+
throw new Error(`Failed to rebase worktree ${storyId}: ${String(error48)}`);
|
|
35487
|
+
}
|
|
35488
|
+
}
|
|
35489
|
+
async getConflictFiles(projectRoot) {
|
|
35490
|
+
try {
|
|
35491
|
+
const proc = _mergeDeps.spawn(["git", "diff", "--name-only", "--diff-filter=U"], {
|
|
35492
|
+
cwd: projectRoot,
|
|
35493
|
+
stdout: "pipe",
|
|
35494
|
+
stderr: "pipe"
|
|
35495
|
+
});
|
|
35496
|
+
const exitCode = await proc.exited;
|
|
35497
|
+
if (exitCode !== 0) {
|
|
35498
|
+
return [];
|
|
35499
|
+
}
|
|
35500
|
+
const stdout = await new Response(proc.stdout).text();
|
|
35501
|
+
return stdout.trim().split(`
|
|
35502
|
+
`).filter((line) => line.length > 0);
|
|
35503
|
+
} catch {
|
|
35504
|
+
return [];
|
|
35505
|
+
}
|
|
35506
|
+
}
|
|
35507
|
+
async abortMerge(projectRoot) {
|
|
35508
|
+
try {
|
|
35509
|
+
const proc = _mergeDeps.spawn(["git", "merge", "--abort"], {
|
|
35510
|
+
cwd: projectRoot,
|
|
35511
|
+
stdout: "pipe",
|
|
35512
|
+
stderr: "pipe"
|
|
35513
|
+
});
|
|
35514
|
+
await proc.exited;
|
|
35515
|
+
} catch (error48) {
|
|
35516
|
+
const logger = getSafeLogger();
|
|
35517
|
+
logger?.warn("worktree", "Failed to abort merge", {
|
|
35518
|
+
error: errorMessage(error48)
|
|
35856
35519
|
});
|
|
35857
|
-
prd = escalationResult.prd;
|
|
35858
|
-
prdDirty = escalationResult.prdDirty;
|
|
35859
|
-
break;
|
|
35860
35520
|
}
|
|
35861
35521
|
}
|
|
35862
|
-
return { prd, prdDirty, costDelta };
|
|
35863
35522
|
}
|
|
35864
|
-
var
|
|
35523
|
+
var _mergeDeps;
|
|
35524
|
+
var init_merge = __esm(() => {
|
|
35865
35525
|
init_logger2();
|
|
35866
|
-
|
|
35867
|
-
|
|
35868
|
-
|
|
35869
|
-
|
|
35870
|
-
init_progress();
|
|
35526
|
+
init_bun_deps();
|
|
35527
|
+
_mergeDeps = {
|
|
35528
|
+
spawn
|
|
35529
|
+
};
|
|
35871
35530
|
});
|
|
35872
35531
|
|
|
35873
|
-
// src/execution/
|
|
35874
|
-
|
|
35875
|
-
|
|
35532
|
+
// src/execution/merge-conflict-rectify.ts
|
|
35533
|
+
var exports_merge_conflict_rectify = {};
|
|
35534
|
+
__export(exports_merge_conflict_rectify, {
|
|
35535
|
+
rectifyConflictedStory: () => rectifyConflictedStory
|
|
35536
|
+
});
|
|
35537
|
+
import path15 from "path";
|
|
35538
|
+
async function rectifyConflictedStory(options) {
|
|
35539
|
+
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter, agentGetFn } = options;
|
|
35876
35540
|
const logger = getSafeLogger();
|
|
35877
|
-
|
|
35878
|
-
|
|
35879
|
-
const
|
|
35541
|
+
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
35542
|
+
try {
|
|
35543
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
35544
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
|
|
35545
|
+
const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
35546
|
+
const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
|
|
35547
|
+
const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
|
|
35548
|
+
const worktreeManager = new WorktreeManager2;
|
|
35549
|
+
const mergeEngine = new MergeEngine2(worktreeManager);
|
|
35550
|
+
try {
|
|
35551
|
+
await worktreeManager.remove(workdir, storyId);
|
|
35552
|
+
} catch {}
|
|
35553
|
+
await worktreeManager.create(workdir, storyId);
|
|
35554
|
+
const worktreePath = path15.join(workdir, ".nax-wt", storyId);
|
|
35555
|
+
const story = prd.userStories.find((s) => s.id === storyId);
|
|
35556
|
+
if (!story) {
|
|
35557
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
35558
|
+
}
|
|
35559
|
+
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
35560
|
+
const pipelineContext = {
|
|
35561
|
+
config: config2,
|
|
35562
|
+
effectiveConfig: config2,
|
|
35880
35563
|
prd,
|
|
35881
|
-
|
|
35882
|
-
|
|
35564
|
+
story,
|
|
35565
|
+
stories: [story],
|
|
35566
|
+
workdir: worktreePath,
|
|
35567
|
+
featureDir: undefined,
|
|
35568
|
+
hooks,
|
|
35569
|
+
plugins: pluginRegistry,
|
|
35570
|
+
storyStartTime: new Date().toISOString(),
|
|
35883
35571
|
routing,
|
|
35884
|
-
|
|
35885
|
-
pluginRegistry: ctx.pluginRegistry,
|
|
35886
|
-
runId: ctx.runId,
|
|
35887
|
-
totalCost,
|
|
35888
|
-
iterations
|
|
35889
|
-
});
|
|
35890
|
-
return {
|
|
35891
|
-
prd,
|
|
35892
|
-
storiesCompletedDelta: dryRunResult.storiesCompletedDelta,
|
|
35893
|
-
costDelta: 0,
|
|
35894
|
-
prdDirty: dryRunResult.prdDirty
|
|
35895
|
-
};
|
|
35896
|
-
}
|
|
35897
|
-
const storyStartTime = Date.now();
|
|
35898
|
-
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
35899
|
-
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
35900
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join51(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
35901
|
-
const pipelineContext = {
|
|
35902
|
-
config: ctx.config,
|
|
35903
|
-
effectiveConfig,
|
|
35904
|
-
prd,
|
|
35905
|
-
story,
|
|
35906
|
-
stories: storiesToExecute,
|
|
35907
|
-
routing,
|
|
35908
|
-
workdir: ctx.workdir,
|
|
35909
|
-
prdPath: ctx.prdPath,
|
|
35910
|
-
featureDir: ctx.featureDir,
|
|
35911
|
-
hooks: ctx.hooks,
|
|
35912
|
-
plugins: ctx.pluginRegistry,
|
|
35913
|
-
storyStartTime: new Date().toISOString(),
|
|
35914
|
-
storyGitRef: storyGitRef ?? undefined,
|
|
35915
|
-
interaction: ctx.interactionChain ?? undefined,
|
|
35916
|
-
agentGetFn: ctx.agentGetFn,
|
|
35917
|
-
pidRegistry: ctx.pidRegistry,
|
|
35918
|
-
accumulatedAttemptCost: accumulatedAttemptCost > 0 ? accumulatedAttemptCost : undefined
|
|
35919
|
-
};
|
|
35920
|
-
ctx.statusWriter.setPrd(prd);
|
|
35921
|
-
ctx.statusWriter.setCurrentStory({
|
|
35922
|
-
storyId: story.id,
|
|
35923
|
-
title: story.title,
|
|
35924
|
-
complexity: routing.complexity,
|
|
35925
|
-
tddStrategy: routing.testStrategy,
|
|
35926
|
-
model: routing.modelTier,
|
|
35927
|
-
attempt: (story.attempts ?? 0) + 1,
|
|
35928
|
-
phase: "routing"
|
|
35929
|
-
});
|
|
35930
|
-
await ctx.statusWriter.update(totalCost, iterations);
|
|
35931
|
-
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
35932
|
-
const currentPrd = pipelineResult.context.prd;
|
|
35933
|
-
const handlerCtx = {
|
|
35934
|
-
config: ctx.config,
|
|
35935
|
-
prd: currentPrd,
|
|
35936
|
-
prdPath: ctx.prdPath,
|
|
35937
|
-
workdir: ctx.workdir,
|
|
35938
|
-
featureDir: ctx.featureDir,
|
|
35939
|
-
hooks: ctx.hooks,
|
|
35940
|
-
feature: ctx.feature,
|
|
35941
|
-
totalCost,
|
|
35942
|
-
startTime: ctx.startTime,
|
|
35943
|
-
runId: ctx.runId,
|
|
35944
|
-
pluginRegistry: ctx.pluginRegistry,
|
|
35945
|
-
story,
|
|
35946
|
-
storiesToExecute,
|
|
35947
|
-
routing: pipelineResult.context.routing ?? routing,
|
|
35948
|
-
isBatchExecution,
|
|
35949
|
-
allStoryMetrics,
|
|
35950
|
-
storyGitRef,
|
|
35951
|
-
interactionChain: ctx.interactionChain,
|
|
35952
|
-
storyStartTime
|
|
35953
|
-
};
|
|
35954
|
-
if (pipelineResult.success) {
|
|
35955
|
-
const r2 = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
35956
|
-
return {
|
|
35957
|
-
prd: r2.prd,
|
|
35958
|
-
storiesCompletedDelta: r2.storiesCompletedDelta,
|
|
35959
|
-
costDelta: r2.costDelta,
|
|
35960
|
-
prdDirty: r2.prdDirty,
|
|
35961
|
-
finalAction: pipelineResult.finalAction
|
|
35572
|
+
agentGetFn
|
|
35962
35573
|
};
|
|
35574
|
+
const pipelineResult = await runPipeline2(defaultPipeline2, pipelineContext, eventEmitter);
|
|
35575
|
+
const cost = pipelineResult.context.agentResult?.estimatedCost ?? 0;
|
|
35576
|
+
if (!pipelineResult.success) {
|
|
35577
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
35578
|
+
return { success: false, storyId, cost, finalConflict: false, pipelineFailure: true };
|
|
35579
|
+
}
|
|
35580
|
+
const mergeResults = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
35581
|
+
const mergeResult = mergeResults[0];
|
|
35582
|
+
if (!mergeResult || !mergeResult.success) {
|
|
35583
|
+
const conflictFiles = mergeResult?.conflictFiles ?? [];
|
|
35584
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", { storyId });
|
|
35585
|
+
return { success: false, storyId, cost, finalConflict: true, conflictFiles };
|
|
35586
|
+
}
|
|
35587
|
+
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
35588
|
+
storyId,
|
|
35589
|
+
originalCost: options.originalCost,
|
|
35590
|
+
rectificationCost: cost
|
|
35591
|
+
});
|
|
35592
|
+
return { success: true, storyId, cost };
|
|
35593
|
+
} catch (error48) {
|
|
35594
|
+
logger?.error("parallel", "Rectification failed - preserving worktree", {
|
|
35595
|
+
storyId,
|
|
35596
|
+
error: errorMessage(error48)
|
|
35597
|
+
});
|
|
35598
|
+
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
35963
35599
|
}
|
|
35964
|
-
const r = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
35965
|
-
return {
|
|
35966
|
-
prd: r.prd,
|
|
35967
|
-
storiesCompletedDelta: 0,
|
|
35968
|
-
costDelta: r.costDelta,
|
|
35969
|
-
prdDirty: r.prdDirty,
|
|
35970
|
-
finalAction: pipelineResult.finalAction,
|
|
35971
|
-
reason: pipelineResult.reason,
|
|
35972
|
-
subStoryCount: pipelineResult.subStoryCount
|
|
35973
|
-
};
|
|
35974
35600
|
}
|
|
35975
|
-
var
|
|
35976
|
-
var init_iteration_runner = __esm(() => {
|
|
35977
|
-
init_loader();
|
|
35601
|
+
var init_merge_conflict_rectify = __esm(() => {
|
|
35978
35602
|
init_logger2();
|
|
35979
|
-
init_runner();
|
|
35980
|
-
init_stages();
|
|
35981
|
-
init_git();
|
|
35982
|
-
init_dry_run();
|
|
35983
|
-
init_pipeline_result_handler();
|
|
35984
|
-
_iterationRunnerDeps = {
|
|
35985
|
-
loadConfigForWorkdir
|
|
35986
|
-
};
|
|
35987
35603
|
});
|
|
35988
35604
|
|
|
35989
|
-
// src/execution/
|
|
35990
|
-
|
|
35991
|
-
|
|
35992
|
-
|
|
35993
|
-
|
|
35994
|
-
|
|
35995
|
-
|
|
35996
|
-
|
|
35997
|
-
|
|
35998
|
-
|
|
35999
|
-
|
|
36000
|
-
|
|
36001
|
-
|
|
36002
|
-
|
|
36003
|
-
|
|
36004
|
-
|
|
36005
|
-
|
|
36006
|
-
|
|
36007
|
-
|
|
36008
|
-
|
|
36009
|
-
|
|
35605
|
+
// src/execution/parallel-batch.ts
|
|
35606
|
+
var exports_parallel_batch = {};
|
|
35607
|
+
__export(exports_parallel_batch, {
|
|
35608
|
+
runParallelBatch: () => runParallelBatch,
|
|
35609
|
+
_parallelBatchDeps: () => _parallelBatchDeps
|
|
35610
|
+
});
|
|
35611
|
+
import path16 from "path";
|
|
35612
|
+
async function runParallelBatch(options) {
|
|
35613
|
+
const { stories, ctx, prd } = options;
|
|
35614
|
+
const { workdir, config: config2, maxConcurrency, pipelineContext, eventEmitter, agentGetFn, hooks, pluginRegistry } = ctx;
|
|
35615
|
+
const worktreeManager = await _parallelBatchDeps.createWorktreeManager();
|
|
35616
|
+
const worktreePaths = new Map;
|
|
35617
|
+
const storyStartTimes = new Map;
|
|
35618
|
+
for (const story of stories) {
|
|
35619
|
+
storyStartTimes.set(story.id, Date.now());
|
|
35620
|
+
await worktreeManager.create(workdir, story.id);
|
|
35621
|
+
worktreePaths.set(story.id, path16.join(workdir, ".nax-wt", story.id));
|
|
35622
|
+
}
|
|
35623
|
+
const workerResult = await _parallelBatchDeps.executeParallelBatch(stories, workdir, config2, pipelineContext, worktreePaths, maxConcurrency, eventEmitter);
|
|
35624
|
+
const batchEndMs = Date.now();
|
|
35625
|
+
const completed = workerResult.merged;
|
|
35626
|
+
const failed = workerResult.failed.map((f) => ({
|
|
35627
|
+
story: f.story,
|
|
35628
|
+
pipelineResult: f.pipelineResult ?? {
|
|
35629
|
+
success: false,
|
|
35630
|
+
finalAction: "fail",
|
|
35631
|
+
reason: f.error,
|
|
35632
|
+
context: { ...pipelineContext, story: f.story, stories: [f.story], workdir }
|
|
36010
35633
|
}
|
|
36011
|
-
|
|
36012
|
-
|
|
36013
|
-
|
|
36014
|
-
|
|
36015
|
-
storiesToExecute,
|
|
36016
|
-
routing: buildPreviewRouting(story2, config2),
|
|
36017
|
-
isBatchExecution: batch.isBatch && storiesToExecute.length > 1
|
|
36018
|
-
},
|
|
36019
|
-
nextBatchIndex: currentBatchIndex + 1
|
|
36020
|
-
};
|
|
35634
|
+
}));
|
|
35635
|
+
const storyEndTimes = new Map;
|
|
35636
|
+
for (const story of [...workerResult.pipelinePassed, ...workerResult.merged]) {
|
|
35637
|
+
storyEndTimes.set(story.id, batchEndMs);
|
|
36021
35638
|
}
|
|
36022
|
-
const story
|
|
36023
|
-
|
|
36024
|
-
|
|
36025
|
-
|
|
36026
|
-
|
|
36027
|
-
|
|
36028
|
-
|
|
36029
|
-
|
|
36030
|
-
|
|
35639
|
+
for (const { story } of workerResult.failed) {
|
|
35640
|
+
storyEndTimes.set(story.id, batchEndMs);
|
|
35641
|
+
}
|
|
35642
|
+
const mergeConflicts = [];
|
|
35643
|
+
for (const conflict of workerResult.mergeConflicts) {
|
|
35644
|
+
const story = stories.find((s) => s.id === conflict.storyId);
|
|
35645
|
+
if (!story)
|
|
35646
|
+
continue;
|
|
35647
|
+
try {
|
|
35648
|
+
const rectResult = await _parallelBatchDeps.rectifyConflictedStory({
|
|
35649
|
+
...conflict,
|
|
35650
|
+
workdir,
|
|
35651
|
+
config: config2,
|
|
35652
|
+
hooks,
|
|
35653
|
+
pluginRegistry,
|
|
35654
|
+
prd,
|
|
35655
|
+
eventEmitter,
|
|
35656
|
+
agentGetFn
|
|
35657
|
+
});
|
|
35658
|
+
mergeConflicts.push({ story, rectified: rectResult.success, cost: rectResult.cost });
|
|
35659
|
+
} catch (err) {
|
|
35660
|
+
const logger = getSafeLogger();
|
|
35661
|
+
logger?.warn("[parallel-batch]", "rectification failed for story", {
|
|
35662
|
+
storyId: story.id,
|
|
35663
|
+
error: err.message
|
|
35664
|
+
});
|
|
35665
|
+
mergeConflicts.push({ story, rectified: false, cost: 0 });
|
|
35666
|
+
}
|
|
35667
|
+
storyEndTimes.set(conflict.storyId, Date.now());
|
|
35668
|
+
}
|
|
35669
|
+
const storyCosts = workerResult.storyCosts;
|
|
35670
|
+
const totalCost = [...storyCosts.values()].reduce((sum, c) => sum + c, 0);
|
|
35671
|
+
const storyDurations = new Map;
|
|
35672
|
+
for (const story of stories) {
|
|
35673
|
+
const startMs = storyStartTimes.get(story.id);
|
|
35674
|
+
const endMs = storyEndTimes.get(story.id);
|
|
35675
|
+
if (startMs !== undefined && endMs !== undefined) {
|
|
35676
|
+
storyDurations.set(story.id, endMs - startMs);
|
|
35677
|
+
}
|
|
35678
|
+
}
|
|
35679
|
+
return { completed, failed, mergeConflicts, storyCosts, storyDurations, totalCost };
|
|
35680
|
+
}
|
|
35681
|
+
var _parallelBatchDeps;
|
|
35682
|
+
var init_parallel_batch = __esm(() => {
|
|
35683
|
+
init_logger2();
|
|
35684
|
+
_parallelBatchDeps = {
|
|
35685
|
+
executeParallelBatch: async (_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter) => {
|
|
35686
|
+
const { executeParallelBatch: executeParallelBatch2 } = await Promise.resolve().then(() => (init_parallel_worker(), exports_parallel_worker));
|
|
35687
|
+
return executeParallelBatch2(_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter);
|
|
36031
35688
|
},
|
|
36032
|
-
|
|
35689
|
+
createWorktreeManager: async () => {
|
|
35690
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
35691
|
+
return new WorktreeManager2;
|
|
35692
|
+
},
|
|
35693
|
+
createMergeEngine: async (worktreeManager) => {
|
|
35694
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_merge(), exports_merge));
|
|
35695
|
+
return new MergeEngine2(worktreeManager);
|
|
35696
|
+
},
|
|
35697
|
+
rectifyConflictedStory: async (opts) => {
|
|
35698
|
+
const { rectifyConflictedStory: rectifyConflictedStory2 } = await Promise.resolve().then(() => (init_merge_conflict_rectify(), exports_merge_conflict_rectify));
|
|
35699
|
+
return rectifyConflictedStory2(opts);
|
|
35700
|
+
}
|
|
36033
35701
|
};
|
|
36034
|
-
}
|
|
36035
|
-
var init_story_selector = __esm(() => {
|
|
36036
|
-
init_prd();
|
|
36037
35702
|
});
|
|
36038
35703
|
|
|
36039
|
-
// src/execution/
|
|
36040
|
-
var
|
|
36041
|
-
__export(
|
|
36042
|
-
|
|
35704
|
+
// src/execution/unified-executor.ts
|
|
35705
|
+
var exports_unified_executor = {};
|
|
35706
|
+
__export(exports_unified_executor, {
|
|
35707
|
+
executeUnified: () => executeUnified,
|
|
35708
|
+
_unifiedExecutorDeps: () => _unifiedExecutorDeps
|
|
36043
35709
|
});
|
|
36044
|
-
async function
|
|
35710
|
+
async function executeUnified(ctx, initialPrd) {
|
|
36045
35711
|
const logger = getSafeLogger();
|
|
36046
|
-
let
|
|
36047
|
-
|
|
36048
|
-
|
|
36049
|
-
|
|
36050
|
-
|
|
36051
|
-
|
|
36052
|
-
|
|
36053
|
-
0
|
|
36054
|
-
];
|
|
35712
|
+
let prd = initialPrd;
|
|
35713
|
+
let prdDirty = false;
|
|
35714
|
+
let iterations = 0;
|
|
35715
|
+
let storiesCompleted = 0;
|
|
35716
|
+
let totalCost = 0;
|
|
35717
|
+
let lastStoryId = null;
|
|
35718
|
+
let currentBatchIndex = 0;
|
|
36055
35719
|
const allStoryMetrics = [];
|
|
36056
35720
|
let warningSent = false;
|
|
36057
35721
|
let deferredReview;
|
|
@@ -36078,20 +35742,22 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36078
35742
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef);
|
|
36079
35743
|
return buildResult2("completed");
|
|
36080
35744
|
}
|
|
36081
|
-
|
|
36082
|
-
|
|
36083
|
-
|
|
36084
|
-
|
|
36085
|
-
|
|
36086
|
-
|
|
36087
|
-
|
|
36088
|
-
|
|
36089
|
-
|
|
36090
|
-
|
|
36091
|
-
|
|
36092
|
-
|
|
36093
|
-
|
|
36094
|
-
|
|
35745
|
+
if (ctx.config.acceptance?.enabled) {
|
|
35746
|
+
logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
|
|
35747
|
+
const preRunCtx = {
|
|
35748
|
+
config: ctx.config,
|
|
35749
|
+
effectiveConfig: ctx.config,
|
|
35750
|
+
prd,
|
|
35751
|
+
workdir: ctx.workdir,
|
|
35752
|
+
featureDir: ctx.featureDir,
|
|
35753
|
+
story: prd.userStories[0],
|
|
35754
|
+
stories: prd.userStories,
|
|
35755
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "" },
|
|
35756
|
+
hooks: ctx.hooks,
|
|
35757
|
+
agentGetFn: ctx.agentGetFn
|
|
35758
|
+
};
|
|
35759
|
+
await runPipeline(preRunPipeline, preRunCtx, ctx.eventEmitter);
|
|
35760
|
+
}
|
|
36095
35761
|
while (iterations < ctx.config.execution.maxIterations) {
|
|
36096
35762
|
iterations++;
|
|
36097
35763
|
if (Math.round(process.memoryUsage().heapUsed / 1024 / 1024) > 1024)
|
|
@@ -36103,13 +35769,203 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36103
35769
|
if (isComplete(prd)) {
|
|
36104
35770
|
if (ctx.interactionChain && isTriggerEnabled("pre-merge", ctx.config)) {
|
|
36105
35771
|
const shouldProceed = await checkPreMerge({ featureName: ctx.feature, totalStories: prd.userStories.length, cost: totalCost }, ctx.config, ctx.interactionChain);
|
|
36106
|
-
if (!shouldProceed)
|
|
35772
|
+
if (!shouldProceed)
|
|
36107
35773
|
return buildResult2("pre-merge-aborted");
|
|
36108
|
-
}
|
|
36109
35774
|
}
|
|
36110
35775
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef);
|
|
36111
35776
|
return buildResult2("completed");
|
|
36112
35777
|
}
|
|
35778
|
+
const costLimit = ctx.config.execution.costLimit;
|
|
35779
|
+
if ((ctx.parallelCount ?? 0) > 0) {
|
|
35780
|
+
const readyStories = getAllReadyStories(prd);
|
|
35781
|
+
const batch = _unifiedExecutorDeps.selectIndependentBatch(readyStories, ctx.parallelCount);
|
|
35782
|
+
if (batch.length > 1) {
|
|
35783
|
+
for (const story of batch) {
|
|
35784
|
+
pipelineEventBus.emit({
|
|
35785
|
+
type: "story:started",
|
|
35786
|
+
storyId: story.id,
|
|
35787
|
+
story,
|
|
35788
|
+
workdir: ctx.workdir,
|
|
35789
|
+
modelTier: story.routing?.modelTier ?? ctx.config.autoMode.complexityRouting?.[story.routing?.complexity ?? "medium"] ?? "balanced",
|
|
35790
|
+
agent: ctx.config.autoMode.defaultAgent,
|
|
35791
|
+
iteration: iterations
|
|
35792
|
+
});
|
|
35793
|
+
}
|
|
35794
|
+
const batchStartedAt = new Date().toISOString();
|
|
35795
|
+
const storyStartMs = new Map;
|
|
35796
|
+
for (const s of batch)
|
|
35797
|
+
storyStartMs.set(s.id, Date.now());
|
|
35798
|
+
const batchResult = await _unifiedExecutorDeps.runParallelBatch({
|
|
35799
|
+
stories: batch,
|
|
35800
|
+
ctx: {
|
|
35801
|
+
workdir: ctx.workdir,
|
|
35802
|
+
config: ctx.config,
|
|
35803
|
+
hooks: ctx.hooks,
|
|
35804
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
35805
|
+
maxConcurrency: ctx.parallelCount,
|
|
35806
|
+
pipelineContext: {
|
|
35807
|
+
config: ctx.config,
|
|
35808
|
+
effectiveConfig: ctx.config,
|
|
35809
|
+
prd,
|
|
35810
|
+
hooks: ctx.hooks,
|
|
35811
|
+
featureDir: ctx.featureDir,
|
|
35812
|
+
agentGetFn: ctx.agentGetFn,
|
|
35813
|
+
pidRegistry: ctx.pidRegistry
|
|
35814
|
+
},
|
|
35815
|
+
eventEmitter: ctx.eventEmitter,
|
|
35816
|
+
agentGetFn: ctx.agentGetFn
|
|
35817
|
+
},
|
|
35818
|
+
prd
|
|
35819
|
+
});
|
|
35820
|
+
for (const { story, pipelineResult } of batchResult.failed) {
|
|
35821
|
+
const storyRouting = prd.userStories.find((s) => s.id === story.id)?.routing;
|
|
35822
|
+
await handlePipelineFailure({
|
|
35823
|
+
config: ctx.config,
|
|
35824
|
+
prd,
|
|
35825
|
+
prdPath: ctx.prdPath,
|
|
35826
|
+
workdir: ctx.workdir,
|
|
35827
|
+
featureDir: ctx.featureDir,
|
|
35828
|
+
hooks: ctx.hooks,
|
|
35829
|
+
feature: ctx.feature,
|
|
35830
|
+
totalCost,
|
|
35831
|
+
startTime: ctx.startTime,
|
|
35832
|
+
runId: ctx.runId,
|
|
35833
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
35834
|
+
story,
|
|
35835
|
+
storiesToExecute: [story],
|
|
35836
|
+
routing: {
|
|
35837
|
+
complexity: storyRouting?.complexity ?? "medium",
|
|
35838
|
+
modelTier: storyRouting?.modelTier ?? "balanced",
|
|
35839
|
+
testStrategy: storyRouting?.testStrategy ?? "test-after",
|
|
35840
|
+
reasoning: storyRouting?.reasoning ?? ""
|
|
35841
|
+
},
|
|
35842
|
+
isBatchExecution: false,
|
|
35843
|
+
allStoryMetrics,
|
|
35844
|
+
storyGitRef: null,
|
|
35845
|
+
interactionChain: ctx.interactionChain
|
|
35846
|
+
}, pipelineResult);
|
|
35847
|
+
}
|
|
35848
|
+
totalCost += batchResult.totalCost;
|
|
35849
|
+
storiesCompleted += batchResult.completed.length;
|
|
35850
|
+
prdDirty = true;
|
|
35851
|
+
const batchCompletedAt = new Date().toISOString();
|
|
35852
|
+
for (const story of batchResult.completed) {
|
|
35853
|
+
const storyCost = batchResult.storyCosts.get(story.id) ?? 0;
|
|
35854
|
+
const storyStartTime = storyStartMs.get(story.id) ?? Date.now();
|
|
35855
|
+
const storyDuration = batchResult.storyDurations?.get(story.id) ?? Date.now() - storyStartTime;
|
|
35856
|
+
allStoryMetrics.push({
|
|
35857
|
+
storyId: story.id,
|
|
35858
|
+
complexity: story.routing?.complexity ?? "medium",
|
|
35859
|
+
modelTier: story.routing?.modelTier ?? "balanced",
|
|
35860
|
+
modelUsed: ctx.config.autoMode.defaultAgent,
|
|
35861
|
+
attempts: 1,
|
|
35862
|
+
finalTier: story.routing?.modelTier ?? "balanced",
|
|
35863
|
+
success: true,
|
|
35864
|
+
cost: storyCost,
|
|
35865
|
+
durationMs: storyDuration,
|
|
35866
|
+
firstPassSuccess: true,
|
|
35867
|
+
startedAt: batchStartedAt,
|
|
35868
|
+
completedAt: batchCompletedAt,
|
|
35869
|
+
source: "parallel"
|
|
35870
|
+
});
|
|
35871
|
+
}
|
|
35872
|
+
for (const conflict of batchResult.mergeConflicts) {
|
|
35873
|
+
if (conflict.rectified) {
|
|
35874
|
+
const storyStartTime = storyStartMs.get(conflict.story.id) ?? Date.now();
|
|
35875
|
+
const storyDuration = batchResult.storyDurations?.get(conflict.story.id) ?? Date.now() - storyStartTime;
|
|
35876
|
+
allStoryMetrics.push({
|
|
35877
|
+
storyId: conflict.story.id,
|
|
35878
|
+
complexity: conflict.story.routing?.complexity ?? "medium",
|
|
35879
|
+
modelTier: conflict.story.routing?.modelTier ?? "balanced",
|
|
35880
|
+
modelUsed: ctx.config.autoMode.defaultAgent,
|
|
35881
|
+
attempts: 1,
|
|
35882
|
+
finalTier: conflict.story.routing?.modelTier ?? "balanced",
|
|
35883
|
+
success: true,
|
|
35884
|
+
cost: batchResult.storyCosts.get(conflict.story.id) ?? 0,
|
|
35885
|
+
durationMs: storyDuration,
|
|
35886
|
+
firstPassSuccess: false,
|
|
35887
|
+
startedAt: batchStartedAt,
|
|
35888
|
+
completedAt: batchCompletedAt,
|
|
35889
|
+
source: "rectification",
|
|
35890
|
+
rectificationCost: conflict.cost
|
|
35891
|
+
});
|
|
35892
|
+
}
|
|
35893
|
+
}
|
|
35894
|
+
if (totalCost >= costLimit) {
|
|
35895
|
+
return buildResult2("cost-limit");
|
|
35896
|
+
}
|
|
35897
|
+
continue;
|
|
35898
|
+
}
|
|
35899
|
+
if (batch.length === 1) {
|
|
35900
|
+
const singleStory = batch[0];
|
|
35901
|
+
const singleSelection = {
|
|
35902
|
+
story: singleStory,
|
|
35903
|
+
storiesToExecute: [singleStory],
|
|
35904
|
+
routing: buildPreviewRouting(singleStory, ctx.config),
|
|
35905
|
+
isBatchExecution: false
|
|
35906
|
+
};
|
|
35907
|
+
if (!ctx.useBatch)
|
|
35908
|
+
lastStoryId = singleStory.id;
|
|
35909
|
+
if (totalCost >= costLimit) {
|
|
35910
|
+
const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: totalCost, limit: costLimit }, ctx.config, ctx.interactionChain) : false;
|
|
35911
|
+
if (!shouldProceed) {
|
|
35912
|
+
pipelineEventBus.emit({
|
|
35913
|
+
type: "run:paused",
|
|
35914
|
+
reason: `Cost limit reached: $${totalCost.toFixed(2)}`,
|
|
35915
|
+
storyId: singleStory.id,
|
|
35916
|
+
cost: totalCost
|
|
35917
|
+
});
|
|
35918
|
+
return buildResult2("cost-limit");
|
|
35919
|
+
}
|
|
35920
|
+
pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
|
|
35921
|
+
}
|
|
35922
|
+
pipelineEventBus.emit({
|
|
35923
|
+
type: "story:started",
|
|
35924
|
+
storyId: singleStory.id,
|
|
35925
|
+
story: singleStory,
|
|
35926
|
+
workdir: ctx.workdir,
|
|
35927
|
+
modelTier: singleSelection.routing.modelTier,
|
|
35928
|
+
agent: ctx.config.autoMode.defaultAgent,
|
|
35929
|
+
iteration: iterations
|
|
35930
|
+
});
|
|
35931
|
+
const singleIter = await _unifiedExecutorDeps.runIteration(ctx, prd, singleSelection, iterations, totalCost, allStoryMetrics);
|
|
35932
|
+
[prd, storiesCompleted, totalCost, prdDirty] = [
|
|
35933
|
+
singleIter.prd,
|
|
35934
|
+
storiesCompleted + singleIter.storiesCompletedDelta,
|
|
35935
|
+
totalCost + singleIter.costDelta,
|
|
35936
|
+
singleIter.prdDirty
|
|
35937
|
+
];
|
|
35938
|
+
if (singleIter.finalAction === "decomposed") {
|
|
35939
|
+
iterations--;
|
|
35940
|
+
pipelineEventBus.emit({
|
|
35941
|
+
type: "story:decomposed",
|
|
35942
|
+
storyId: singleStory.id,
|
|
35943
|
+
story: singleStory,
|
|
35944
|
+
subStoryCount: singleIter.subStoryCount ?? 0
|
|
35945
|
+
});
|
|
35946
|
+
if (singleIter.prdDirty) {
|
|
35947
|
+
prd = await loadPRD(ctx.prdPath);
|
|
35948
|
+
prdDirty = false;
|
|
35949
|
+
}
|
|
35950
|
+
ctx.statusWriter.setPrd(prd);
|
|
35951
|
+
continue;
|
|
35952
|
+
}
|
|
35953
|
+
if (singleIter.prdDirty) {
|
|
35954
|
+
prd = await loadPRD(ctx.prdPath);
|
|
35955
|
+
prdDirty = false;
|
|
35956
|
+
}
|
|
35957
|
+
ctx.statusWriter.setPrd(prd);
|
|
35958
|
+
ctx.statusWriter.setCurrentStory(null);
|
|
35959
|
+
await ctx.statusWriter.update(totalCost, iterations);
|
|
35960
|
+
if (isStalled(prd)) {
|
|
35961
|
+
pipelineEventBus.emit({ type: "run:paused", reason: "All remaining stories blocked", cost: totalCost });
|
|
35962
|
+
return buildResult2("stalled");
|
|
35963
|
+
}
|
|
35964
|
+
if (ctx.config.execution.iterationDelayMs > 0)
|
|
35965
|
+
await Bun.sleep(ctx.config.execution.iterationDelayMs);
|
|
35966
|
+
continue;
|
|
35967
|
+
}
|
|
35968
|
+
}
|
|
36113
35969
|
const selected = selectNextStories(prd, ctx.config, ctx.batchPlan, currentBatchIndex, lastStoryId, ctx.useBatch);
|
|
36114
35970
|
if (!selected)
|
|
36115
35971
|
return buildResult2("no-stories");
|
|
@@ -36121,8 +35977,8 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36121
35977
|
const { selection } = selected;
|
|
36122
35978
|
if (!ctx.useBatch)
|
|
36123
35979
|
lastStoryId = selection.story.id;
|
|
36124
|
-
if (totalCost >=
|
|
36125
|
-
const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: totalCost, limit:
|
|
35980
|
+
if (totalCost >= costLimit) {
|
|
35981
|
+
const shouldProceed = ctx.interactionChain && isTriggerEnabled("cost-exceeded", ctx.config) ? await checkCostExceeded({ featureName: ctx.feature, cost: totalCost, limit: costLimit }, ctx.config, ctx.interactionChain) : false;
|
|
36126
35982
|
if (!shouldProceed) {
|
|
36127
35983
|
pipelineEventBus.emit({
|
|
36128
35984
|
type: "run:paused",
|
|
@@ -36143,7 +35999,7 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36143
35999
|
agent: ctx.config.autoMode.defaultAgent,
|
|
36144
36000
|
iteration: iterations
|
|
36145
36001
|
});
|
|
36146
|
-
const iter = await runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics);
|
|
36002
|
+
const iter = await _unifiedExecutorDeps.runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics);
|
|
36147
36003
|
[prd, storiesCompleted, totalCost, prdDirty] = [
|
|
36148
36004
|
iter.prd,
|
|
36149
36005
|
storiesCompleted + iter.storiesCompletedDelta,
|
|
@@ -36166,7 +36022,6 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36166
36022
|
continue;
|
|
36167
36023
|
}
|
|
36168
36024
|
if (ctx.interactionChain && isTriggerEnabled("cost-warning", ctx.config) && !warningSent) {
|
|
36169
|
-
const costLimit = ctx.config.execution.costLimit;
|
|
36170
36025
|
const triggerCfg = ctx.config.interaction?.triggers?.["cost-warning"];
|
|
36171
36026
|
const threshold = typeof triggerCfg === "object" ? triggerCfg.threshold ?? 0.8 : 0.8;
|
|
36172
36027
|
if (totalCost >= costLimit * threshold) {
|
|
@@ -36188,12 +36043,17 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
36188
36043
|
if (ctx.config.execution.iterationDelayMs > 0)
|
|
36189
36044
|
await Bun.sleep(ctx.config.execution.iterationDelayMs);
|
|
36190
36045
|
}
|
|
36191
|
-
|
|
36192
|
-
|
|
36046
|
+
if (ctx.config.acceptance?.enabled) {
|
|
36047
|
+
logger?.info("execution", "Running post-run pipeline (acceptance tests)");
|
|
36048
|
+
await runPipeline(postRunPipeline, { config: ctx.config, prd, workdir: ctx.workdir, story: prd.userStories[0] }, ctx.eventEmitter);
|
|
36049
|
+
}
|
|
36193
36050
|
return buildResult2("max-iterations");
|
|
36194
|
-
} finally {
|
|
36051
|
+
} finally {
|
|
36052
|
+
stopHeartbeat();
|
|
36053
|
+
}
|
|
36195
36054
|
}
|
|
36196
|
-
var
|
|
36055
|
+
var _unifiedExecutorDeps;
|
|
36056
|
+
var init_unified_executor = __esm(() => {
|
|
36197
36057
|
init_triggers();
|
|
36198
36058
|
init_logger2();
|
|
36199
36059
|
init_event_bus();
|
|
@@ -36207,21 +36067,31 @@ var init_sequential_executor = __esm(() => {
|
|
|
36207
36067
|
init_prd();
|
|
36208
36068
|
init_crash_recovery();
|
|
36209
36069
|
init_deferred_review();
|
|
36070
|
+
init_helpers();
|
|
36210
36071
|
init_iteration_runner();
|
|
36072
|
+
init_pipeline_result_handler();
|
|
36211
36073
|
init_story_selector();
|
|
36074
|
+
_unifiedExecutorDeps = {
|
|
36075
|
+
runParallelBatch: async (opts) => {
|
|
36076
|
+
const { runParallelBatch: runParallelBatch2 } = await Promise.resolve().then(() => (init_parallel_batch(), exports_parallel_batch));
|
|
36077
|
+
return runParallelBatch2(opts);
|
|
36078
|
+
},
|
|
36079
|
+
runIteration,
|
|
36080
|
+
selectIndependentBatch
|
|
36081
|
+
};
|
|
36212
36082
|
});
|
|
36213
36083
|
|
|
36214
36084
|
// src/project/detector.ts
|
|
36215
|
-
import { join as
|
|
36085
|
+
import { join as join51 } from "path";
|
|
36216
36086
|
async function detectLanguage(workdir, pkg) {
|
|
36217
36087
|
const deps = _detectorDeps;
|
|
36218
|
-
if (await deps.fileExists(
|
|
36088
|
+
if (await deps.fileExists(join51(workdir, "go.mod")))
|
|
36219
36089
|
return "go";
|
|
36220
|
-
if (await deps.fileExists(
|
|
36090
|
+
if (await deps.fileExists(join51(workdir, "Cargo.toml")))
|
|
36221
36091
|
return "rust";
|
|
36222
|
-
if (await deps.fileExists(
|
|
36092
|
+
if (await deps.fileExists(join51(workdir, "pyproject.toml")))
|
|
36223
36093
|
return "python";
|
|
36224
|
-
if (await deps.fileExists(
|
|
36094
|
+
if (await deps.fileExists(join51(workdir, "requirements.txt")))
|
|
36225
36095
|
return "python";
|
|
36226
36096
|
if (pkg != null) {
|
|
36227
36097
|
const allDeps = {
|
|
@@ -36281,18 +36151,18 @@ async function detectLintTool(workdir, language) {
|
|
|
36281
36151
|
if (language === "python")
|
|
36282
36152
|
return "ruff";
|
|
36283
36153
|
const deps = _detectorDeps;
|
|
36284
|
-
if (await deps.fileExists(
|
|
36154
|
+
if (await deps.fileExists(join51(workdir, "biome.json")))
|
|
36285
36155
|
return "biome";
|
|
36286
|
-
if (await deps.fileExists(
|
|
36156
|
+
if (await deps.fileExists(join51(workdir, ".eslintrc")))
|
|
36287
36157
|
return "eslint";
|
|
36288
|
-
if (await deps.fileExists(
|
|
36158
|
+
if (await deps.fileExists(join51(workdir, ".eslintrc.js")))
|
|
36289
36159
|
return "eslint";
|
|
36290
|
-
if (await deps.fileExists(
|
|
36160
|
+
if (await deps.fileExists(join51(workdir, ".eslintrc.json")))
|
|
36291
36161
|
return "eslint";
|
|
36292
36162
|
return;
|
|
36293
36163
|
}
|
|
36294
36164
|
async function detectProjectProfile(workdir, existing) {
|
|
36295
|
-
const pkg = await _detectorDeps.readJson(
|
|
36165
|
+
const pkg = await _detectorDeps.readJson(join51(workdir, "package.json"));
|
|
36296
36166
|
const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
|
|
36297
36167
|
const type = existing.type !== undefined ? existing.type : detectType(pkg);
|
|
36298
36168
|
const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
|
|
@@ -36385,7 +36255,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
36385
36255
|
var init_status_file = () => {};
|
|
36386
36256
|
|
|
36387
36257
|
// src/execution/status-writer.ts
|
|
36388
|
-
import { join as
|
|
36258
|
+
import { join as join52 } from "path";
|
|
36389
36259
|
|
|
36390
36260
|
class StatusWriter {
|
|
36391
36261
|
statusFile;
|
|
@@ -36453,7 +36323,7 @@ class StatusWriter {
|
|
|
36453
36323
|
if (!this._prd)
|
|
36454
36324
|
return;
|
|
36455
36325
|
const safeLogger = getSafeLogger();
|
|
36456
|
-
const featureStatusPath =
|
|
36326
|
+
const featureStatusPath = join52(featureDir, "status.json");
|
|
36457
36327
|
try {
|
|
36458
36328
|
const base = this.getSnapshot(totalCost, iterations);
|
|
36459
36329
|
if (!base) {
|
|
@@ -36661,7 +36531,7 @@ __export(exports_run_initialization, {
|
|
|
36661
36531
|
initializeRun: () => initializeRun,
|
|
36662
36532
|
_reconcileDeps: () => _reconcileDeps
|
|
36663
36533
|
});
|
|
36664
|
-
import { join as
|
|
36534
|
+
import { join as join53 } from "path";
|
|
36665
36535
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
36666
36536
|
const logger = getSafeLogger();
|
|
36667
36537
|
let reconciledCount = 0;
|
|
@@ -36679,7 +36549,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
36679
36549
|
});
|
|
36680
36550
|
continue;
|
|
36681
36551
|
}
|
|
36682
|
-
const effectiveWorkdir = story.workdir ?
|
|
36552
|
+
const effectiveWorkdir = story.workdir ? join53(workdir, story.workdir) : workdir;
|
|
36683
36553
|
try {
|
|
36684
36554
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
36685
36555
|
if (!reviewResult.success) {
|
|
@@ -36781,7 +36651,7 @@ __export(exports_run_setup, {
|
|
|
36781
36651
|
setupRun: () => setupRun,
|
|
36782
36652
|
_runSetupDeps: () => _runSetupDeps
|
|
36783
36653
|
});
|
|
36784
|
-
import * as
|
|
36654
|
+
import * as os3 from "os";
|
|
36785
36655
|
import path18 from "path";
|
|
36786
36656
|
async function setupRun(options) {
|
|
36787
36657
|
const logger = getSafeLogger();
|
|
@@ -36878,7 +36748,7 @@ async function setupRun(options) {
|
|
|
36878
36748
|
explicit: Object.fromEntries(explicitFields.map((f) => [f, existingProjectConfig[f]])),
|
|
36879
36749
|
detected: Object.fromEntries(autodetectedFields.map((f) => [f, detectedProfile[f]]))
|
|
36880
36750
|
});
|
|
36881
|
-
const globalPluginsDir = path18.join(
|
|
36751
|
+
const globalPluginsDir = path18.join(os3.homedir(), ".nax", "plugins");
|
|
36882
36752
|
const projectPluginsDir = path18.join(workdir, ".nax", "plugins");
|
|
36883
36753
|
const configPlugins = config2.plugins || [];
|
|
36884
36754
|
const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
|
|
@@ -67887,9 +67757,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
67887
67757
|
|
|
67888
67758
|
// bin/nax.ts
|
|
67889
67759
|
init_source();
|
|
67890
|
-
import { existsSync as
|
|
67760
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
67891
67761
|
import { homedir as homedir8 } from "os";
|
|
67892
|
-
import { join as
|
|
67762
|
+
import { join as join55 } from "path";
|
|
67893
67763
|
|
|
67894
67764
|
// node_modules/commander/esm.mjs
|
|
67895
67765
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -70828,7 +70698,7 @@ import { existsSync as existsSync22 } from "fs";
|
|
|
70828
70698
|
import { join as join35 } from "path";
|
|
70829
70699
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
70830
70700
|
async function generateCommand(options) {
|
|
70831
|
-
const workdir = process.cwd();
|
|
70701
|
+
const workdir = options.dir ?? process.cwd();
|
|
70832
70702
|
const dryRun = options.dryRun ?? false;
|
|
70833
70703
|
let config2;
|
|
70834
70704
|
try {
|
|
@@ -72202,50 +72072,8 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
72202
72072
|
if (options.useBatch) {
|
|
72203
72073
|
await tryLlmBatchRoute(options.config, readyStories, "routing");
|
|
72204
72074
|
}
|
|
72205
|
-
|
|
72206
|
-
|
|
72207
|
-
const parallelResult = await runParallelExecution2({
|
|
72208
|
-
prdPath: options.prdPath,
|
|
72209
|
-
workdir: options.workdir,
|
|
72210
|
-
config: options.config,
|
|
72211
|
-
hooks: options.hooks,
|
|
72212
|
-
feature: options.feature,
|
|
72213
|
-
featureDir: options.featureDir,
|
|
72214
|
-
parallelCount: options.parallel,
|
|
72215
|
-
eventEmitter: options.eventEmitter,
|
|
72216
|
-
statusWriter: options.statusWriter,
|
|
72217
|
-
runId: options.runId,
|
|
72218
|
-
startedAt: options.startedAt,
|
|
72219
|
-
startTime: options.startTime,
|
|
72220
|
-
totalCost,
|
|
72221
|
-
iterations,
|
|
72222
|
-
storiesCompleted,
|
|
72223
|
-
allStoryMetrics,
|
|
72224
|
-
pluginRegistry,
|
|
72225
|
-
formatterMode: options.formatterMode,
|
|
72226
|
-
headless: options.headless,
|
|
72227
|
-
agentGetFn: options.agentGetFn,
|
|
72228
|
-
pidRegistry: options.pidRegistry,
|
|
72229
|
-
interactionChain: options.interactionChain
|
|
72230
|
-
}, prd);
|
|
72231
|
-
prd = parallelResult.prd;
|
|
72232
|
-
totalCost = parallelResult.totalCost;
|
|
72233
|
-
storiesCompleted = parallelResult.storiesCompleted;
|
|
72234
|
-
allStoryMetrics.push(...parallelResult.storyMetrics);
|
|
72235
|
-
if (parallelResult.completed && parallelResult.durationMs !== undefined) {
|
|
72236
|
-
return {
|
|
72237
|
-
prd,
|
|
72238
|
-
iterations,
|
|
72239
|
-
storiesCompleted,
|
|
72240
|
-
totalCost,
|
|
72241
|
-
allStoryMetrics,
|
|
72242
|
-
completedEarly: true,
|
|
72243
|
-
durationMs: parallelResult.durationMs
|
|
72244
|
-
};
|
|
72245
|
-
}
|
|
72246
|
-
}
|
|
72247
|
-
const { executeSequential: executeSequential2 } = await Promise.resolve().then(() => (init_sequential_executor(), exports_sequential_executor));
|
|
72248
|
-
const sequentialResult = await executeSequential2({
|
|
72075
|
+
const { executeUnified: executeUnified2 } = await Promise.resolve().then(() => (init_unified_executor(), exports_unified_executor));
|
|
72076
|
+
const unifiedResult = await executeUnified2({
|
|
72249
72077
|
prdPath: options.prdPath,
|
|
72250
72078
|
workdir: options.workdir,
|
|
72251
72079
|
config: options.config,
|
|
@@ -72260,23 +72088,18 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
72260
72088
|
logFilePath: options.logFilePath,
|
|
72261
72089
|
runId: options.runId,
|
|
72262
72090
|
startTime: options.startTime,
|
|
72263
|
-
|
|
72091
|
+
parallelCount: options.parallel,
|
|
72264
72092
|
agentGetFn: options.agentGetFn,
|
|
72265
72093
|
pidRegistry: options.pidRegistry,
|
|
72266
|
-
interactionChain: options.interactionChain
|
|
72094
|
+
interactionChain: options.interactionChain,
|
|
72095
|
+
batchPlan
|
|
72267
72096
|
}, prd);
|
|
72268
|
-
prd =
|
|
72269
|
-
iterations =
|
|
72270
|
-
|
|
72271
|
-
|
|
72272
|
-
allStoryMetrics.push(...
|
|
72273
|
-
return {
|
|
72274
|
-
prd,
|
|
72275
|
-
iterations,
|
|
72276
|
-
storiesCompleted,
|
|
72277
|
-
totalCost,
|
|
72278
|
-
allStoryMetrics
|
|
72279
|
-
};
|
|
72097
|
+
prd = unifiedResult.prd;
|
|
72098
|
+
iterations = unifiedResult.iterations;
|
|
72099
|
+
storiesCompleted = unifiedResult.storiesCompleted;
|
|
72100
|
+
totalCost = unifiedResult.totalCost;
|
|
72101
|
+
allStoryMetrics.push(...unifiedResult.allStoryMetrics);
|
|
72102
|
+
return { prd, iterations, storiesCompleted, totalCost, allStoryMetrics };
|
|
72280
72103
|
}
|
|
72281
72104
|
|
|
72282
72105
|
// src/execution/runner-setup.ts
|
|
@@ -72310,10 +72133,6 @@ async function runSetupPhase(options) {
|
|
|
72310
72133
|
// src/execution/runner.ts
|
|
72311
72134
|
init_escalation();
|
|
72312
72135
|
init_escalation();
|
|
72313
|
-
var _runnerDeps = {
|
|
72314
|
-
fireHook,
|
|
72315
|
-
runParallelExecution: null
|
|
72316
|
-
};
|
|
72317
72136
|
async function run(options) {
|
|
72318
72137
|
const {
|
|
72319
72138
|
prdPath,
|
|
@@ -72387,7 +72206,6 @@ async function run(options) {
|
|
|
72387
72206
|
formatterMode,
|
|
72388
72207
|
headless,
|
|
72389
72208
|
parallel,
|
|
72390
|
-
runParallelExecution: _runnerDeps.runParallelExecution ?? undefined,
|
|
72391
72209
|
agentGetFn,
|
|
72392
72210
|
pidRegistry,
|
|
72393
72211
|
interactionChain
|
|
@@ -72643,7 +72461,7 @@ __export(exports_base, {
|
|
|
72643
72461
|
ConEmu: () => ConEmu
|
|
72644
72462
|
});
|
|
72645
72463
|
import process4 from "process";
|
|
72646
|
-
import
|
|
72464
|
+
import os4 from "os";
|
|
72647
72465
|
|
|
72648
72466
|
// node_modules/environment/index.js
|
|
72649
72467
|
var isBrowser = globalThis.window?.document !== undefined;
|
|
@@ -72742,7 +72560,7 @@ var isOldWindows = () => {
|
|
|
72742
72560
|
if (isBrowser || !isWindows2) {
|
|
72743
72561
|
return false;
|
|
72744
72562
|
}
|
|
72745
|
-
const parts =
|
|
72563
|
+
const parts = os4.release().split(".");
|
|
72746
72564
|
const major = Number(parts[0]);
|
|
72747
72565
|
const build = Number(parts[2] ?? 0);
|
|
72748
72566
|
if (major < 10) {
|
|
@@ -79821,15 +79639,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
79821
79639
|
}
|
|
79822
79640
|
return;
|
|
79823
79641
|
}
|
|
79824
|
-
const naxDir =
|
|
79825
|
-
if (
|
|
79642
|
+
const naxDir = join55(workdir, ".nax");
|
|
79643
|
+
if (existsSync34(naxDir) && !options.force) {
|
|
79826
79644
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
79827
79645
|
return;
|
|
79828
79646
|
}
|
|
79829
|
-
mkdirSync6(
|
|
79830
|
-
mkdirSync6(
|
|
79831
|
-
await Bun.write(
|
|
79832
|
-
await Bun.write(
|
|
79647
|
+
mkdirSync6(join55(naxDir, "features"), { recursive: true });
|
|
79648
|
+
mkdirSync6(join55(naxDir, "hooks"), { recursive: true });
|
|
79649
|
+
await Bun.write(join55(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
79650
|
+
await Bun.write(join55(naxDir, "hooks.json"), JSON.stringify({
|
|
79833
79651
|
hooks: {
|
|
79834
79652
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
79835
79653
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -79837,12 +79655,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
79837
79655
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
79838
79656
|
}
|
|
79839
79657
|
}, null, 2));
|
|
79840
|
-
await Bun.write(
|
|
79658
|
+
await Bun.write(join55(naxDir, ".gitignore"), `# nax temp files
|
|
79841
79659
|
*.tmp
|
|
79842
79660
|
.paused.json
|
|
79843
79661
|
.nax-verifier-verdict.json
|
|
79844
79662
|
`);
|
|
79845
|
-
await Bun.write(
|
|
79663
|
+
await Bun.write(join55(naxDir, "context.md"), `# Project Context
|
|
79846
79664
|
|
|
79847
79665
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
79848
79666
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -79939,7 +79757,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79939
79757
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
79940
79758
|
process.exit(1);
|
|
79941
79759
|
}
|
|
79942
|
-
if (options.from && !
|
|
79760
|
+
if (options.from && !existsSync34(options.from)) {
|
|
79943
79761
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
79944
79762
|
process.exit(1);
|
|
79945
79763
|
}
|
|
@@ -79968,10 +79786,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79968
79786
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
79969
79787
|
process.exit(1);
|
|
79970
79788
|
}
|
|
79971
|
-
const featureDir =
|
|
79972
|
-
const prdPath =
|
|
79789
|
+
const featureDir = join55(naxDir, "features", options.feature);
|
|
79790
|
+
const prdPath = join55(featureDir, "prd.json");
|
|
79973
79791
|
if (options.plan && options.from) {
|
|
79974
|
-
if (
|
|
79792
|
+
if (existsSync34(prdPath) && !options.force) {
|
|
79975
79793
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
79976
79794
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
79977
79795
|
process.exit(1);
|
|
@@ -79991,10 +79809,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79991
79809
|
}
|
|
79992
79810
|
}
|
|
79993
79811
|
try {
|
|
79994
|
-
const planLogDir =
|
|
79812
|
+
const planLogDir = join55(featureDir, "plan");
|
|
79995
79813
|
mkdirSync6(planLogDir, { recursive: true });
|
|
79996
79814
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
79997
|
-
const planLogPath =
|
|
79815
|
+
const planLogPath = join55(planLogDir, `${planLogId}.jsonl`);
|
|
79998
79816
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
79999
79817
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
80000
79818
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -80027,15 +79845,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80027
79845
|
process.exit(1);
|
|
80028
79846
|
}
|
|
80029
79847
|
}
|
|
80030
|
-
if (!
|
|
79848
|
+
if (!existsSync34(prdPath)) {
|
|
80031
79849
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
80032
79850
|
process.exit(1);
|
|
80033
79851
|
}
|
|
80034
79852
|
resetLogger();
|
|
80035
|
-
const runsDir =
|
|
79853
|
+
const runsDir = join55(featureDir, "runs");
|
|
80036
79854
|
mkdirSync6(runsDir, { recursive: true });
|
|
80037
79855
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
80038
|
-
const logFilePath =
|
|
79856
|
+
const logFilePath = join55(runsDir, `${runId}.jsonl`);
|
|
80039
79857
|
const isTTY = process.stdout.isTTY ?? false;
|
|
80040
79858
|
const headlessFlag = options.headless ?? false;
|
|
80041
79859
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -80051,7 +79869,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80051
79869
|
config2.autoMode.defaultAgent = options.agent;
|
|
80052
79870
|
}
|
|
80053
79871
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
80054
|
-
const globalNaxDir =
|
|
79872
|
+
const globalNaxDir = join55(homedir8(), ".nax");
|
|
80055
79873
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
80056
79874
|
const eventEmitter = new PipelineEventEmitter;
|
|
80057
79875
|
let tuiInstance;
|
|
@@ -80074,7 +79892,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80074
79892
|
} else {
|
|
80075
79893
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
80076
79894
|
}
|
|
80077
|
-
const statusFilePath =
|
|
79895
|
+
const statusFilePath = join55(workdir, ".nax", "status.json");
|
|
80078
79896
|
let parallel;
|
|
80079
79897
|
if (options.parallel !== undefined) {
|
|
80080
79898
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -80100,9 +79918,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
80100
79918
|
headless: useHeadless,
|
|
80101
79919
|
skipPrecheck: options.skipPrecheck ?? false
|
|
80102
79920
|
});
|
|
80103
|
-
const latestSymlink =
|
|
79921
|
+
const latestSymlink = join55(runsDir, "latest.jsonl");
|
|
80104
79922
|
try {
|
|
80105
|
-
if (
|
|
79923
|
+
if (existsSync34(latestSymlink)) {
|
|
80106
79924
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
80107
79925
|
}
|
|
80108
79926
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -80138,9 +79956,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
80138
79956
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
80139
79957
|
process.exit(1);
|
|
80140
79958
|
}
|
|
80141
|
-
const featureDir =
|
|
79959
|
+
const featureDir = join55(naxDir, "features", name);
|
|
80142
79960
|
mkdirSync6(featureDir, { recursive: true });
|
|
80143
|
-
await Bun.write(
|
|
79961
|
+
await Bun.write(join55(featureDir, "spec.md"), `# Feature: ${name}
|
|
80144
79962
|
|
|
80145
79963
|
## Overview
|
|
80146
79964
|
|
|
@@ -80173,7 +79991,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
80173
79991
|
|
|
80174
79992
|
<!-- What this feature explicitly does NOT cover. -->
|
|
80175
79993
|
`);
|
|
80176
|
-
await Bun.write(
|
|
79994
|
+
await Bun.write(join55(featureDir, "progress.txt"), `# Progress: ${name}
|
|
80177
79995
|
|
|
80178
79996
|
Created: ${new Date().toISOString()}
|
|
80179
79997
|
|
|
@@ -80199,8 +80017,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
80199
80017
|
console.error(source_default.red("nax not initialized."));
|
|
80200
80018
|
process.exit(1);
|
|
80201
80019
|
}
|
|
80202
|
-
const featuresDir =
|
|
80203
|
-
if (!
|
|
80020
|
+
const featuresDir = join55(naxDir, "features");
|
|
80021
|
+
if (!existsSync34(featuresDir)) {
|
|
80204
80022
|
console.log(source_default.dim("No features yet."));
|
|
80205
80023
|
return;
|
|
80206
80024
|
}
|
|
@@ -80214,8 +80032,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
80214
80032
|
Features:
|
|
80215
80033
|
`));
|
|
80216
80034
|
for (const name of entries) {
|
|
80217
|
-
const prdPath =
|
|
80218
|
-
if (
|
|
80035
|
+
const prdPath = join55(featuresDir, name, "prd.json");
|
|
80036
|
+
if (existsSync34(prdPath)) {
|
|
80219
80037
|
const prd = await loadPRD(prdPath);
|
|
80220
80038
|
const c = countStories(prd);
|
|
80221
80039
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -80245,10 +80063,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
80245
80063
|
process.exit(1);
|
|
80246
80064
|
}
|
|
80247
80065
|
const config2 = await loadConfig(workdir);
|
|
80248
|
-
const featureLogDir =
|
|
80066
|
+
const featureLogDir = join55(naxDir, "features", options.feature, "plan");
|
|
80249
80067
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
80250
80068
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
80251
|
-
const planLogPath =
|
|
80069
|
+
const planLogPath = join55(featureLogDir, `${planLogId}.jsonl`);
|
|
80252
80070
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
80253
80071
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
80254
80072
|
try {
|
|
@@ -80285,8 +80103,8 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
80285
80103
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
80286
80104
|
process.exit(1);
|
|
80287
80105
|
}
|
|
80288
|
-
const featureDir =
|
|
80289
|
-
if (!
|
|
80106
|
+
const featureDir = join55(naxDir, "features", options.feature);
|
|
80107
|
+
if (!existsSync34(featureDir)) {
|
|
80290
80108
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
80291
80109
|
process.exit(1);
|
|
80292
80110
|
}
|
|
@@ -80301,7 +80119,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
80301
80119
|
specPath: options.from,
|
|
80302
80120
|
reclassify: options.reclassify
|
|
80303
80121
|
});
|
|
80304
|
-
const prdPath =
|
|
80122
|
+
const prdPath = join55(featureDir, "prd.json");
|
|
80305
80123
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
80306
80124
|
const c = countStories(prd);
|
|
80307
80125
|
console.log(source_default.green(`
|
|
@@ -80334,9 +80152,17 @@ program2.command("agents").description("List available coding agents with status
|
|
|
80334
80152
|
process.exit(1);
|
|
80335
80153
|
}
|
|
80336
80154
|
});
|
|
80337
|
-
program2.command("config").description("Display effective merged configuration").option("--explain", "Show detailed field descriptions", false).option("--diff", "Show only fields where project overrides global", false).action(async (options) => {
|
|
80155
|
+
program2.command("config").description("Display effective merged configuration").option("-d, --dir <path>", "Project directory", process.cwd()).option("--explain", "Show detailed field descriptions", false).option("--diff", "Show only fields where project overrides global", false).action(async (options) => {
|
|
80156
|
+
let workdir;
|
|
80157
|
+
try {
|
|
80158
|
+
workdir = validateDirectory(options.dir);
|
|
80159
|
+
} catch (err) {
|
|
80160
|
+
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
80161
|
+
process.exit(1);
|
|
80162
|
+
return;
|
|
80163
|
+
}
|
|
80338
80164
|
try {
|
|
80339
|
-
const config2 = await loadConfig();
|
|
80165
|
+
const config2 = await loadConfig(workdir);
|
|
80340
80166
|
await configCommand(config2, { explain: options.explain, diff: options.diff });
|
|
80341
80167
|
} catch (err) {
|
|
80342
80168
|
console.error(source_default.red(`Error: ${err.message}`));
|
|
@@ -80530,9 +80356,18 @@ program2.command("prompts").description("Assemble or initialize prompts").option
|
|
|
80530
80356
|
process.exit(1);
|
|
80531
80357
|
}
|
|
80532
80358
|
});
|
|
80533
|
-
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)").option("--all-packages", "Generate CLAUDE.md for all discovered packages", false).action(async (options) => {
|
|
80359
|
+
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-d, --dir <path>", "Project directory", process.cwd()).option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)").option("--all-packages", "Generate CLAUDE.md for all discovered packages", false).action(async (options) => {
|
|
80360
|
+
let workdir;
|
|
80361
|
+
try {
|
|
80362
|
+
workdir = validateDirectory(options.dir);
|
|
80363
|
+
} catch (err) {
|
|
80364
|
+
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
80365
|
+
process.exit(1);
|
|
80366
|
+
return;
|
|
80367
|
+
}
|
|
80534
80368
|
try {
|
|
80535
80369
|
await generateCommand({
|
|
80370
|
+
dir: workdir,
|
|
80536
80371
|
context: options.context,
|
|
80537
80372
|
output: options.output,
|
|
80538
80373
|
agent: options.agent,
|