@nathapp/nax 0.77.3 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +950 -608
- package/flows/nax-finish/flow-ctx.ts +25 -5
- package/flows/nax-finish/nax-finish.flow.ts +62 -136
- package/flows/nax-finish/review-prompts.ts +6 -3
- package/flows/nax-finish/steps/context.ts +63 -7
- package/flows/nax-finish/steps/gates.ts +183 -0
- package/flows/nax-finish/steps/index.ts +1 -0
- package/package.json +8 -4
package/dist/nax.js
CHANGED
|
@@ -19356,18 +19356,36 @@ function findProjectDir(startDir = process.cwd()) {
|
|
|
19356
19356
|
}
|
|
19357
19357
|
return null;
|
|
19358
19358
|
}
|
|
19359
|
+
function defaultConfigWarn(msg) {
|
|
19360
|
+
try {
|
|
19361
|
+
getLogger().warn("config", msg);
|
|
19362
|
+
} catch {}
|
|
19363
|
+
}
|
|
19359
19364
|
function applyRemovedStrategyCompat(conf) {
|
|
19360
19365
|
const routing = conf.routing;
|
|
19361
19366
|
const strategy = routing?.strategy;
|
|
19362
19367
|
const REMOVED_STRATEGIES = ["manual", "adaptive", "custom"];
|
|
19363
19368
|
if (typeof strategy === "string" && REMOVED_STRATEGIES.includes(strategy)) {
|
|
19364
|
-
|
|
19365
|
-
getLogger().warn("config", `routing.strategy="${strategy}" was removed in ROUTE-001 and is no longer supported. Falling back to "keyword". Update your config to use "keyword" or "llm".`);
|
|
19366
|
-
} catch {}
|
|
19369
|
+
defaultConfigWarn(`routing.strategy="${strategy}" was removed in ROUTE-001 and is no longer supported. Falling back to "keyword". Update your config to use "keyword" or "llm".`);
|
|
19367
19370
|
return { ...conf, routing: { ...routing, strategy: "keyword" } };
|
|
19368
19371
|
}
|
|
19369
19372
|
return conf;
|
|
19370
19373
|
}
|
|
19374
|
+
function _applyRemovedRoutingKeysShim(conf, warn = defaultConfigWarn) {
|
|
19375
|
+
const routing = conf.routing;
|
|
19376
|
+
if (!routing || typeof routing !== "object")
|
|
19377
|
+
return conf;
|
|
19378
|
+
const REMOVED_ROUTING_KEYS = ["customStrategyPath", "adaptive"];
|
|
19379
|
+
let newRouting = routing;
|
|
19380
|
+
for (const key of REMOVED_ROUTING_KEYS) {
|
|
19381
|
+
if (key in newRouting) {
|
|
19382
|
+
warn(`routing.${key} was removed in ROUTE-001 along with the "custom"/"adaptive" strategies and has no effect. Remove it from your config.`);
|
|
19383
|
+
const { [key]: _removed, ...rest } = newRouting;
|
|
19384
|
+
newRouting = rest;
|
|
19385
|
+
}
|
|
19386
|
+
}
|
|
19387
|
+
return newRouting === routing ? conf : { ...conf, routing: newRouting };
|
|
19388
|
+
}
|
|
19371
19389
|
function applyBatchModeCompat(conf) {
|
|
19372
19390
|
const routing = conf.routing;
|
|
19373
19391
|
const llm = routing?.llm;
|
|
@@ -19375,9 +19393,7 @@ function applyBatchModeCompat(conf) {
|
|
|
19375
19393
|
const batchMode = llm.batchMode;
|
|
19376
19394
|
if (typeof batchMode === "boolean") {
|
|
19377
19395
|
const mappedMode = batchMode ? "one-shot" : "per-story";
|
|
19378
|
-
|
|
19379
|
-
getLogger().warn("config", `routing.llm.batchMode is deprecated and will be removed in v1.0. Mapped to mode="${mappedMode}". Update your config to use routing.llm.mode instead.`);
|
|
19380
|
-
} catch {}
|
|
19396
|
+
defaultConfigWarn(`routing.llm.batchMode is deprecated and will be removed in v1.0. Mapped to mode="${mappedMode}". Update your config to use routing.llm.mode instead.`);
|
|
19381
19397
|
return {
|
|
19382
19398
|
...conf,
|
|
19383
19399
|
routing: {
|
|
@@ -19389,11 +19405,7 @@ function applyBatchModeCompat(conf) {
|
|
|
19389
19405
|
}
|
|
19390
19406
|
return conf;
|
|
19391
19407
|
}
|
|
19392
|
-
function _applyLegacyReviewExecutionShim(conf, warn =
|
|
19393
|
-
try {
|
|
19394
|
-
getLogger().warn("config", msg);
|
|
19395
|
-
} catch {}
|
|
19396
|
-
}) {
|
|
19408
|
+
function _applyLegacyReviewExecutionShim(conf, warn = defaultConfigWarn) {
|
|
19397
19409
|
let result = conf;
|
|
19398
19410
|
const execution = conf.execution;
|
|
19399
19411
|
if (execution && typeof execution === "object" && "inlineReview" in execution) {
|
|
@@ -19420,11 +19432,7 @@ function _applyLegacyReviewExecutionShim(conf, warn = (msg) => {
|
|
|
19420
19432
|
}
|
|
19421
19433
|
return result;
|
|
19422
19434
|
}
|
|
19423
|
-
function applyRoutingRetryDeprecationWarning(conf, warn =
|
|
19424
|
-
try {
|
|
19425
|
-
getLogger().warn("config", msg);
|
|
19426
|
-
} catch {}
|
|
19427
|
-
}) {
|
|
19435
|
+
function applyRoutingRetryDeprecationWarning(conf, warn = defaultConfigWarn) {
|
|
19428
19436
|
const routing = conf.routing;
|
|
19429
19437
|
const llm = routing?.llm;
|
|
19430
19438
|
if (!llm)
|
|
@@ -19450,14 +19458,14 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
19450
19458
|
} catch {}
|
|
19451
19459
|
if (globalConfRaw) {
|
|
19452
19460
|
const { profile: _gProfile, ...globalConfStripped } = globalConfRaw;
|
|
19453
|
-
const globalConf = _applyLegacyReviewExecutionShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(globalConfStripped, logger), logger)))));
|
|
19461
|
+
const globalConf = _applyLegacyReviewExecutionShim(_applyRemovedRoutingKeysShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(globalConfStripped, logger), logger))))));
|
|
19454
19462
|
rawConfig = deepMergeConfig(rawConfig, globalConf);
|
|
19455
19463
|
}
|
|
19456
19464
|
if (projDir) {
|
|
19457
19465
|
const projConf = await loadJsonFile(join3(projDir, "config.json"), "config");
|
|
19458
19466
|
if (projConf) {
|
|
19459
19467
|
const { profile: _pProfile, ...projConfStripped } = projConf;
|
|
19460
|
-
const resolvedProjConf = _applyLegacyReviewExecutionShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(projConfStripped, logger), logger)))));
|
|
19468
|
+
const resolvedProjConf = _applyLegacyReviewExecutionShim(_applyRemovedRoutingKeysShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(projConfStripped, logger), logger))))));
|
|
19461
19469
|
rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
|
|
19462
19470
|
}
|
|
19463
19471
|
}
|
|
@@ -20033,6 +20041,7 @@ __export(exports_config, {
|
|
|
20033
20041
|
TEST_STRATEGY_GUIDE: () => TEST_STRATEGY_GUIDE,
|
|
20034
20042
|
SPEC_ANCHOR_RULES: () => SPEC_ANCHOR_RULES,
|
|
20035
20043
|
SINGLE_SESSION_TEST_OWNING_STRATEGIES: () => SINGLE_SESSION_TEST_OWNING_STRATEGIES,
|
|
20044
|
+
RoutingConfigSchema: () => RoutingConfigSchema,
|
|
20036
20045
|
ReviewConfigSchema: () => ReviewConfigSchema,
|
|
20037
20046
|
PlanConfigSchema: () => PlanConfigSchema,
|
|
20038
20047
|
NaxConfigSchema: () => NaxConfigSchema,
|
|
@@ -24339,17 +24348,25 @@ function buildManifest(inputs) {
|
|
|
24339
24348
|
...staleChunkIds.length > 0 && { staleChunks: staleChunkIds }
|
|
24340
24349
|
};
|
|
24341
24350
|
}
|
|
24342
|
-
function rebuildUsedTokens(prior, packed, newPriorStageDigest) {
|
|
24343
|
-
const priorChunksTokens = prior.chunks.reduce((sum, c) => sum + c.tokens, 0);
|
|
24344
|
-
const extraTokens = packed.filter((c) => !prior.chunks.some((pc) => pc.id === c.id)).reduce((sum, c) => sum + c.tokens, 0);
|
|
24345
|
-
const packedTokens = priorChunksTokens + extraTokens;
|
|
24346
|
-
const newDigestContent = newPriorStageDigest?.trim();
|
|
24347
|
-
const newDigestContribution = newDigestContent ? Math.ceil(newDigestContent.length / 4) : 0;
|
|
24348
|
-
return Math.max(0, packedTokens + newDigestContribution);
|
|
24349
|
-
}
|
|
24350
24351
|
var CHUNK_SUMMARY_CHARS = 300;
|
|
24351
24352
|
|
|
24352
24353
|
// src/context/engine/orchestrator-rebuild-helpers.ts
|
|
24354
|
+
function toContextChunk(packed) {
|
|
24355
|
+
const providerId = packed.providerId ?? packed.id.split(":")[0] ?? "unknown";
|
|
24356
|
+
return {
|
|
24357
|
+
id: packed.id,
|
|
24358
|
+
providerId,
|
|
24359
|
+
kind: packed.kind,
|
|
24360
|
+
scope: packed.scope,
|
|
24361
|
+
role: packed.role,
|
|
24362
|
+
content: packed.content,
|
|
24363
|
+
tokens: packed.tokens,
|
|
24364
|
+
rawScore: packed.rawScore,
|
|
24365
|
+
score: packed.score,
|
|
24366
|
+
reason: packed.reason,
|
|
24367
|
+
...packed.staleCandidate && { staleCandidate: true }
|
|
24368
|
+
};
|
|
24369
|
+
}
|
|
24353
24370
|
function buildFailureNoteChunk(priorAgentId, newAgentId, failure) {
|
|
24354
24371
|
const lines = [
|
|
24355
24372
|
"## Agent swap (availability fallback)",
|
|
@@ -24385,17 +24402,62 @@ var DEFAULT_REBUILD_AGENT_ID = "claude";
|
|
|
24385
24402
|
function scoreDensity(chunk) {
|
|
24386
24403
|
return chunk.tokens > 0 ? chunk.score / chunk.tokens : Number.POSITIVE_INFINITY;
|
|
24387
24404
|
}
|
|
24405
|
+
function greedyNonFloor(nonFloor, remainingBudget) {
|
|
24406
|
+
const sorted = [...nonFloor].sort((a, b) => scoreDensity(b) - scoreDensity(a));
|
|
24407
|
+
const selected = [];
|
|
24408
|
+
const excludedIds = [];
|
|
24409
|
+
let used = 0;
|
|
24410
|
+
for (const chunk of sorted) {
|
|
24411
|
+
if (used + chunk.tokens <= remainingBudget) {
|
|
24412
|
+
selected.push(chunk);
|
|
24413
|
+
used += chunk.tokens;
|
|
24414
|
+
} else {
|
|
24415
|
+
excludedIds.push(chunk.id);
|
|
24416
|
+
}
|
|
24417
|
+
}
|
|
24418
|
+
return { selected, excludedIds };
|
|
24419
|
+
}
|
|
24420
|
+
function largestSingleItem(nonFloor, remainingBudget) {
|
|
24421
|
+
let best = null;
|
|
24422
|
+
for (const chunk of nonFloor) {
|
|
24423
|
+
if (chunk.tokens > remainingBudget)
|
|
24424
|
+
continue;
|
|
24425
|
+
if (best === null || chunk.score > best.score)
|
|
24426
|
+
best = chunk;
|
|
24427
|
+
}
|
|
24428
|
+
return best ? [best] : [];
|
|
24429
|
+
}
|
|
24430
|
+
function repairNonFloor(nonFloor, remainingBudget) {
|
|
24431
|
+
const greedy = greedyNonFloor(nonFloor, remainingBudget);
|
|
24432
|
+
const greedyScore = greedy.selected.reduce((s, c) => s + c.score, 0);
|
|
24433
|
+
const largest = largestSingleItem(nonFloor, remainingBudget);
|
|
24434
|
+
const largestScore = largest.reduce((s, c) => s + c.score, 0);
|
|
24435
|
+
const candidates = [
|
|
24436
|
+
{ selected: greedy.selected, totalScore: greedyScore },
|
|
24437
|
+
{ selected: largest, totalScore: largestScore }
|
|
24438
|
+
];
|
|
24439
|
+
let best = candidates[0];
|
|
24440
|
+
for (const candidate of candidates) {
|
|
24441
|
+
if (candidate.totalScore > best.totalScore) {
|
|
24442
|
+
best = candidate;
|
|
24443
|
+
}
|
|
24444
|
+
}
|
|
24445
|
+
const winnerIds = new Set(best.selected.map((c) => c.id));
|
|
24446
|
+
const excludedIds = nonFloor.filter((c) => !winnerIds.has(c.id)).map((c) => c.id);
|
|
24447
|
+
return { selected: best.selected, excludedIds };
|
|
24448
|
+
}
|
|
24388
24449
|
function packChunks(chunks, budgetTokens, availableBudgetTokens) {
|
|
24389
24450
|
const effectiveBudget = availableBudgetTokens !== undefined ? Math.min(budgetTokens, availableBudgetTokens) : budgetTokens;
|
|
24390
24451
|
const floorChunks = chunks.filter((c) => FLOOR_KINDS.includes(c.kind));
|
|
24391
|
-
const nonFloorChunks = chunks.filter((c) => !FLOOR_KINDS.includes(c.kind))
|
|
24452
|
+
const nonFloorChunks = chunks.filter((c) => !FLOOR_KINDS.includes(c.kind));
|
|
24392
24453
|
const packed = [];
|
|
24393
|
-
const budgetExcludedIds = [];
|
|
24394
24454
|
const floorPackedIds = [];
|
|
24395
24455
|
const floorOverageIds = [];
|
|
24396
24456
|
let usedTokens = 0;
|
|
24457
|
+
const totalFloorTokens = floorChunks.reduce((sum, c) => sum + c.tokens, 0);
|
|
24458
|
+
const floorCollectivelyOverflows = totalFloorTokens > effectiveBudget;
|
|
24397
24459
|
for (const chunk of floorChunks) {
|
|
24398
|
-
const overflows = usedTokens + chunk.tokens > effectiveBudget;
|
|
24460
|
+
const overflows = floorCollectivelyOverflows || usedTokens + chunk.tokens > effectiveBudget;
|
|
24399
24461
|
const packedChunk = { ...chunk };
|
|
24400
24462
|
if (overflows) {
|
|
24401
24463
|
packedChunk.reason = "budget-exceeded-by-floor";
|
|
@@ -24405,15 +24467,20 @@ function packChunks(chunks, budgetTokens, availableBudgetTokens) {
|
|
|
24405
24467
|
packed.push(packedChunk);
|
|
24406
24468
|
usedTokens += chunk.tokens;
|
|
24407
24469
|
}
|
|
24408
|
-
|
|
24409
|
-
|
|
24410
|
-
|
|
24411
|
-
|
|
24412
|
-
|
|
24413
|
-
budgetExcludedIds.push(chunk.id);
|
|
24414
|
-
}
|
|
24470
|
+
const remainingBudget = Math.max(0, effectiveBudget - usedTokens);
|
|
24471
|
+
const { selected, excludedIds } = repairNonFloor(nonFloorChunks, remainingBudget);
|
|
24472
|
+
for (const chunk of selected) {
|
|
24473
|
+
packed.push({ ...chunk });
|
|
24474
|
+
usedTokens += chunk.tokens;
|
|
24415
24475
|
}
|
|
24416
|
-
return {
|
|
24476
|
+
return {
|
|
24477
|
+
packed,
|
|
24478
|
+
budgetExcludedIds: excludedIds,
|
|
24479
|
+
usedTokens,
|
|
24480
|
+
effectiveBudget,
|
|
24481
|
+
floorPackedIds,
|
|
24482
|
+
floorOverageIds
|
|
24483
|
+
};
|
|
24417
24484
|
}
|
|
24418
24485
|
var FLOOR_KINDS;
|
|
24419
24486
|
var init_packing = __esm(() => {
|
|
@@ -28491,6 +28558,147 @@ var init_render = __esm(() => {
|
|
|
28491
28558
|
FIXED_RENDER_OVERHEAD_TOKENS = Math.ceil(FIXED_RENDER_OVERHEAD_CHARS / 4);
|
|
28492
28559
|
});
|
|
28493
28560
|
|
|
28561
|
+
// src/context/engine/scratch-neutralizer.ts
|
|
28562
|
+
function neutralizeForAgent(content, sourceAgent, targetAgent) {
|
|
28563
|
+
if (!content || !sourceAgent || !targetAgent || sourceAgent === targetAgent)
|
|
28564
|
+
return content;
|
|
28565
|
+
if (sourceAgent.toLowerCase() !== "claude")
|
|
28566
|
+
return content;
|
|
28567
|
+
let result = content;
|
|
28568
|
+
for (const [pattern, replacement] of CLAUDE_TOOL_SUBSTITUTIONS) {
|
|
28569
|
+
result = result.replace(pattern, replacement);
|
|
28570
|
+
}
|
|
28571
|
+
return result;
|
|
28572
|
+
}
|
|
28573
|
+
var CLAUDE_TOOL_SUBSTITUTIONS;
|
|
28574
|
+
var init_scratch_neutralizer = __esm(() => {
|
|
28575
|
+
CLAUDE_TOOL_SUBSTITUTIONS = [
|
|
28576
|
+
[/\bthe\s+Read\s+tool\b/gi, "a file read"],
|
|
28577
|
+
[/\bthe\s+Edit\s+tool\b/gi, "a file edit"],
|
|
28578
|
+
[/\bthe\s+Write\s+tool\b/gi, "a file write"],
|
|
28579
|
+
[/\bthe\s+Bash\s+tool\b/gi, "a shell command"],
|
|
28580
|
+
[/\bthe\s+Grep\s+tool\b/gi, "a code search"],
|
|
28581
|
+
[/\bthe\s+Glob\s+tool\b/gi, "a file search"],
|
|
28582
|
+
[/\bthe\s+Agent\s+tool\b/gi, "a sub-agent"],
|
|
28583
|
+
[/\bthe\s+Task\s+tool\b/gi, "a sub-agent"]
|
|
28584
|
+
];
|
|
28585
|
+
});
|
|
28586
|
+
|
|
28587
|
+
// src/context/engine/rebuild.ts
|
|
28588
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
28589
|
+
function toPackedChunks(prior, newAgentId, targetAgentId) {
|
|
28590
|
+
const priorAgentForNeutralize = prior.agentId ?? "";
|
|
28591
|
+
return prior.chunks.map((c) => {
|
|
28592
|
+
const content = newAgentId && newAgentId !== priorAgentForNeutralize && c.kind === "session" ? neutralizeForAgent(c.content, priorAgentForNeutralize, targetAgentId) : c.content;
|
|
28593
|
+
return { ...c, content, rawScore: c.score, roleFiltered: false, belowMinScore: false };
|
|
28594
|
+
});
|
|
28595
|
+
}
|
|
28596
|
+
function rebuild(prior, options = {}, deps = DEFAULT_REBUILD_DEPS) {
|
|
28597
|
+
const { newAgentId, failure, priorStageDigest, storyId } = options;
|
|
28598
|
+
const targetAgentId = newAgentId ?? prior.agentId ?? DEFAULT_REBUILD_AGENT_ID;
|
|
28599
|
+
const logger = deps.getLogger();
|
|
28600
|
+
if (newAgentId && !AGENT_PROFILES[newAgentId]) {
|
|
28601
|
+
logger.warn("context-v2", "rebuildForAgent: unknown agent id \u2014 using conservative defaults", {
|
|
28602
|
+
...storyId && { storyId },
|
|
28603
|
+
stage: prior.manifest.stage,
|
|
28604
|
+
agentId: newAgentId
|
|
28605
|
+
});
|
|
28606
|
+
}
|
|
28607
|
+
const priorChunkIds = prior.chunks.map((c) => c.id);
|
|
28608
|
+
const packedChunks = toPackedChunks(prior, newAgentId, targetAgentId);
|
|
28609
|
+
const targetProfile = getAgentProfile(targetAgentId).profile;
|
|
28610
|
+
const rebuiltPullTools = targetProfile.caps.supportsToolCalls ? prior.pullTools : [];
|
|
28611
|
+
const effectiveBudget = Math.min(prior.manifest.effectiveBudget ?? Number.POSITIVE_INFINITY, targetProfile.caps.preferredPromptTokens);
|
|
28612
|
+
let failureNoteChunk;
|
|
28613
|
+
if (failure && newAgentId && !prior.manifest.rebuildInfo) {
|
|
28614
|
+
failureNoteChunk = buildFailureNoteChunk(prior.agentId || "unknown", newAgentId, failure);
|
|
28615
|
+
packedChunks.push(failureNoteChunk);
|
|
28616
|
+
}
|
|
28617
|
+
let packResult = packChunks(packedChunks, effectiveBudget);
|
|
28618
|
+
if (failureNoteChunk && !packResult.packed.some((c) => c.id === failureNoteChunk.id)) {
|
|
28619
|
+
packResult = {
|
|
28620
|
+
...packResult,
|
|
28621
|
+
packed: [...packResult.packed, failureNoteChunk],
|
|
28622
|
+
usedTokens: packResult.usedTokens + failureNoteChunk.tokens
|
|
28623
|
+
};
|
|
28624
|
+
}
|
|
28625
|
+
const chunkById = new Map(packResult.packed.map((c) => [c.id, c]));
|
|
28626
|
+
const orderedChunks = [];
|
|
28627
|
+
for (const priorId of priorChunkIds) {
|
|
28628
|
+
const chunk = chunkById.get(priorId);
|
|
28629
|
+
if (chunk)
|
|
28630
|
+
orderedChunks.push(chunk);
|
|
28631
|
+
}
|
|
28632
|
+
if (failureNoteChunk && chunkById.has(failureNoteChunk.id)) {
|
|
28633
|
+
orderedChunks.push(failureNoteChunk);
|
|
28634
|
+
}
|
|
28635
|
+
const pushMarkdown = newAgentId ? renderForAgent(orderedChunks, targetAgentId, { priorStageDigest }) : renderChunks(orderedChunks, { priorStageDigest });
|
|
28636
|
+
const digest = buildDigest(orderedChunks);
|
|
28637
|
+
const dTokens = digestTokens(digest);
|
|
28638
|
+
const newDigestContent = priorStageDigest?.trim();
|
|
28639
|
+
const digestContribution = newDigestContent ? Math.ceil(newDigestContent.length / 4) : 0;
|
|
28640
|
+
const usedTokens = packResult.usedTokens + digestContribution;
|
|
28641
|
+
const rebuildInfo = failure && newAgentId ? {
|
|
28642
|
+
priorAgentId: prior.agentId ?? "unknown",
|
|
28643
|
+
newAgentId: targetAgentId,
|
|
28644
|
+
failureCategory: failure.category,
|
|
28645
|
+
failureOutcome: failure.outcome,
|
|
28646
|
+
priorChunkIds,
|
|
28647
|
+
newChunkIds: orderedChunks.map((c) => c.id),
|
|
28648
|
+
chunkIdMap: [
|
|
28649
|
+
...priorChunkIds.map((priorId) => {
|
|
28650
|
+
const entry = chunkById.get(priorId);
|
|
28651
|
+
return entry ? { priorChunkId: priorId, newChunkId: priorId } : null;
|
|
28652
|
+
}).filter((entry) => entry !== null),
|
|
28653
|
+
...failureNoteChunk ? [{ priorChunkId: failureNoteChunk.id, newChunkId: failureNoteChunk.id }] : []
|
|
28654
|
+
]
|
|
28655
|
+
} : undefined;
|
|
28656
|
+
const includedChunkIds = new Set(orderedChunks.map((c) => c.id));
|
|
28657
|
+
const chunkSummaries = Object.fromEntries(orderedChunks.map((c) => [c.id, c.content.slice(0, CHUNK_SUMMARY_CHARS)]));
|
|
28658
|
+
const chunkEffectiveness = prior.manifest.chunkEffectiveness ? Object.fromEntries(Object.entries(prior.manifest.chunkEffectiveness).filter(([id]) => includedChunkIds.has(id))) : undefined;
|
|
28659
|
+
const excludedChunks = packResult.budgetExcludedIds.filter((id) => !includedChunkIds.has(id)).map((id) => ({ id, reason: "budget" }));
|
|
28660
|
+
const manifest = {
|
|
28661
|
+
...prior.manifest,
|
|
28662
|
+
requestId: deps.uuid(),
|
|
28663
|
+
includedChunks: orderedChunks.map((c) => c.id),
|
|
28664
|
+
excludedChunks,
|
|
28665
|
+
chunkTokens: Object.fromEntries(orderedChunks.map((c) => [c.id, c.tokens])),
|
|
28666
|
+
usedTokens,
|
|
28667
|
+
digestTokens: dTokens,
|
|
28668
|
+
buildMs: 0,
|
|
28669
|
+
rebuildInfo,
|
|
28670
|
+
effectiveBudget,
|
|
28671
|
+
floorItems: packResult.floorPackedIds,
|
|
28672
|
+
floorOverageItems: packResult.floorOverageIds.length > 0 ? packResult.floorOverageIds : undefined,
|
|
28673
|
+
chunkSummaries: Object.keys(chunkSummaries).length > 0 ? chunkSummaries : undefined,
|
|
28674
|
+
staleChunks: orderedChunks.some((c) => c.staleCandidate) ? orderedChunks.filter((c) => c.staleCandidate).map((c) => c.id) : undefined,
|
|
28675
|
+
chunkEffectiveness: chunkEffectiveness && Object.keys(chunkEffectiveness).length > 0 ? chunkEffectiveness : undefined
|
|
28676
|
+
};
|
|
28677
|
+
const rebuiltChunks = orderedChunks.map(toContextChunk);
|
|
28678
|
+
return {
|
|
28679
|
+
pushMarkdown,
|
|
28680
|
+
pullTools: rebuiltPullTools,
|
|
28681
|
+
digest,
|
|
28682
|
+
manifest,
|
|
28683
|
+
chunks: rebuiltChunks,
|
|
28684
|
+
agentId: targetAgentId
|
|
28685
|
+
};
|
|
28686
|
+
}
|
|
28687
|
+
var DEFAULT_REBUILD_DEPS;
|
|
28688
|
+
var init_rebuild = __esm(() => {
|
|
28689
|
+
init_logger2();
|
|
28690
|
+
init_agent_profiles();
|
|
28691
|
+
init_agent_renderer();
|
|
28692
|
+
init_digest();
|
|
28693
|
+
init_packing();
|
|
28694
|
+
init_render();
|
|
28695
|
+
init_scratch_neutralizer();
|
|
28696
|
+
DEFAULT_REBUILD_DEPS = {
|
|
28697
|
+
uuid: () => randomUUID2(),
|
|
28698
|
+
getLogger: () => getLogger()
|
|
28699
|
+
};
|
|
28700
|
+
});
|
|
28701
|
+
|
|
28494
28702
|
// src/context/engine/scoring.ts
|
|
28495
28703
|
function roleMultiplier(chunkRoles, callerRole) {
|
|
28496
28704
|
if (chunkRoles.includes(callerRole))
|
|
@@ -28527,34 +28735,8 @@ var init_scoring = __esm(() => {
|
|
|
28527
28735
|
};
|
|
28528
28736
|
});
|
|
28529
28737
|
|
|
28530
|
-
// src/context/engine/scratch-neutralizer.ts
|
|
28531
|
-
function neutralizeForAgent(content, sourceAgent, targetAgent) {
|
|
28532
|
-
if (!content || !sourceAgent || !targetAgent || sourceAgent === targetAgent)
|
|
28533
|
-
return content;
|
|
28534
|
-
if (sourceAgent.toLowerCase() !== "claude")
|
|
28535
|
-
return content;
|
|
28536
|
-
let result = content;
|
|
28537
|
-
for (const [pattern, replacement] of CLAUDE_TOOL_SUBSTITUTIONS) {
|
|
28538
|
-
result = result.replace(pattern, replacement);
|
|
28539
|
-
}
|
|
28540
|
-
return result;
|
|
28541
|
-
}
|
|
28542
|
-
var CLAUDE_TOOL_SUBSTITUTIONS;
|
|
28543
|
-
var init_scratch_neutralizer = __esm(() => {
|
|
28544
|
-
CLAUDE_TOOL_SUBSTITUTIONS = [
|
|
28545
|
-
[/\bthe\s+Read\s+tool\b/gi, "a file read"],
|
|
28546
|
-
[/\bthe\s+Edit\s+tool\b/gi, "a file edit"],
|
|
28547
|
-
[/\bthe\s+Write\s+tool\b/gi, "a file write"],
|
|
28548
|
-
[/\bthe\s+Bash\s+tool\b/gi, "a shell command"],
|
|
28549
|
-
[/\bthe\s+Grep\s+tool\b/gi, "a code search"],
|
|
28550
|
-
[/\bthe\s+Glob\s+tool\b/gi, "a file search"],
|
|
28551
|
-
[/\bthe\s+Agent\s+tool\b/gi, "a sub-agent"],
|
|
28552
|
-
[/\bthe\s+Task\s+tool\b/gi, "a sub-agent"]
|
|
28553
|
-
];
|
|
28554
|
-
});
|
|
28555
|
-
|
|
28556
28738
|
// src/context/engine/orchestrator.ts
|
|
28557
|
-
import { createHash as createHash5, randomUUID as
|
|
28739
|
+
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
28558
28740
|
function buildPullToolDescriptors(stageToolNames, pullConfig) {
|
|
28559
28741
|
if (!pullConfig?.enabled || stageToolNames.length === 0)
|
|
28560
28742
|
return [];
|
|
@@ -28584,22 +28766,6 @@ async function fetchWithTimeout(provider, request, timeoutMs = PROVIDER_FETCH_TI
|
|
|
28584
28766
|
clearTimeout(handle);
|
|
28585
28767
|
}
|
|
28586
28768
|
}
|
|
28587
|
-
function toContextChunk(packed) {
|
|
28588
|
-
const providerId = packed.providerId ?? packed.id.split(":")[0] ?? "unknown";
|
|
28589
|
-
return {
|
|
28590
|
-
id: packed.id,
|
|
28591
|
-
providerId,
|
|
28592
|
-
kind: packed.kind,
|
|
28593
|
-
scope: packed.scope,
|
|
28594
|
-
role: packed.role,
|
|
28595
|
-
content: packed.content,
|
|
28596
|
-
tokens: packed.tokens,
|
|
28597
|
-
rawScore: packed.rawScore,
|
|
28598
|
-
score: packed.score,
|
|
28599
|
-
reason: packed.reason,
|
|
28600
|
-
...packed.staleCandidate && { staleCandidate: true }
|
|
28601
|
-
};
|
|
28602
|
-
}
|
|
28603
28769
|
function enrichRaw(chunk, providerId) {
|
|
28604
28770
|
return { ...chunk, providerId };
|
|
28605
28771
|
}
|
|
@@ -28760,9 +28926,8 @@ class ContextOrchestrator {
|
|
|
28760
28926
|
excludedNonFloorChunkCount: budgetExcludedIds.length
|
|
28761
28927
|
});
|
|
28762
28928
|
}
|
|
28763
|
-
const
|
|
28764
|
-
|
|
28765
|
-
});
|
|
28929
|
+
const renderOptions = { priorStageDigest: request.priorStageDigest };
|
|
28930
|
+
const pushMarkdown = request.agentId !== undefined ? renderForAgent(packed, request.agentId, renderOptions) : renderChunks(packed, renderOptions);
|
|
28766
28931
|
const digest = buildDigest(packed);
|
|
28767
28932
|
const dTokens = digestTokens(digest);
|
|
28768
28933
|
const buildMs = _orchestratorDeps.now() - startMs;
|
|
@@ -28799,62 +28964,10 @@ class ContextOrchestrator {
|
|
|
28799
28964
|
};
|
|
28800
28965
|
}
|
|
28801
28966
|
rebuildForAgent(prior, options = {}) {
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
if (newAgentId && !AGENT_PROFILES[newAgentId]) {
|
|
28806
|
-
logger.warn("context-v2", "rebuildForAgent: unknown agent id \u2014 using conservative defaults", {
|
|
28807
|
-
...storyId && { storyId },
|
|
28808
|
-
stage: prior.manifest.stage,
|
|
28809
|
-
agentId: newAgentId
|
|
28810
|
-
});
|
|
28811
|
-
}
|
|
28812
|
-
const priorChunkIds = prior.chunks.map((c) => c.id);
|
|
28813
|
-
const priorAgentForNeutralize = prior.agentId ?? "";
|
|
28814
|
-
const packedChunks = prior.chunks.map((c) => {
|
|
28815
|
-
const content = newAgentId && newAgentId !== priorAgentForNeutralize && c.kind === "session" ? neutralizeForAgent(c.content, priorAgentForNeutralize, targetAgentId) : c.content;
|
|
28816
|
-
return { ...c, content, rawScore: c.score, roleFiltered: false, belowMinScore: false };
|
|
28967
|
+
return _orchestratorDeps.rebuild(prior, options, {
|
|
28968
|
+
uuid: _orchestratorDeps.uuid,
|
|
28969
|
+
getLogger: _orchestratorDeps.getLogger
|
|
28817
28970
|
});
|
|
28818
|
-
if (failure && newAgentId) {
|
|
28819
|
-
packedChunks.push(buildFailureNoteChunk(priorAgentForNeutralize || "unknown", newAgentId, failure));
|
|
28820
|
-
}
|
|
28821
|
-
const pushMarkdown = newAgentId ? renderForAgent(packedChunks, targetAgentId, { priorStageDigest }) : renderChunks(packedChunks, { priorStageDigest });
|
|
28822
|
-
const digest = buildDigest(packedChunks);
|
|
28823
|
-
const dTokens = digestTokens(digest);
|
|
28824
|
-
const rebuildInfo = failure && newAgentId ? {
|
|
28825
|
-
priorAgentId: prior.agentId ?? "unknown",
|
|
28826
|
-
newAgentId: targetAgentId,
|
|
28827
|
-
failureCategory: failure.category,
|
|
28828
|
-
failureOutcome: failure.outcome,
|
|
28829
|
-
priorChunkIds,
|
|
28830
|
-
newChunkIds: packedChunks.map((c) => c.id),
|
|
28831
|
-
chunkIdMap: priorChunkIds.map((priorChunkId, index) => {
|
|
28832
|
-
const newChunkId = packedChunks[index]?.id;
|
|
28833
|
-
return newChunkId ? { priorChunkId, newChunkId } : null;
|
|
28834
|
-
}).filter((entry) => entry !== null)
|
|
28835
|
-
} : undefined;
|
|
28836
|
-
const usedTokens = rebuildUsedTokens(prior, packedChunks, priorStageDigest);
|
|
28837
|
-
const targetProfile = getAgentProfile(targetAgentId).profile;
|
|
28838
|
-
const rebuiltPullTools = targetProfile.caps.supportsToolCalls ? prior.pullTools : [];
|
|
28839
|
-
const manifest = {
|
|
28840
|
-
...prior.manifest,
|
|
28841
|
-
requestId: _orchestratorDeps.uuid(),
|
|
28842
|
-
includedChunks: packedChunks.map((c) => c.id),
|
|
28843
|
-
chunkTokens: Object.fromEntries(packedChunks.map((c) => [c.id, c.tokens])),
|
|
28844
|
-
usedTokens,
|
|
28845
|
-
digestTokens: dTokens,
|
|
28846
|
-
buildMs: 0,
|
|
28847
|
-
rebuildInfo,
|
|
28848
|
-
effectiveBudget: Math.min(prior.manifest.effectiveBudget ?? Number.POSITIVE_INFINITY, targetProfile.caps.preferredPromptTokens)
|
|
28849
|
-
};
|
|
28850
|
-
return {
|
|
28851
|
-
pushMarkdown,
|
|
28852
|
-
pullTools: rebuiltPullTools,
|
|
28853
|
-
digest,
|
|
28854
|
-
manifest,
|
|
28855
|
-
chunks: packedChunks.map(toContextChunk),
|
|
28856
|
-
agentId: targetAgentId
|
|
28857
|
-
};
|
|
28858
28971
|
}
|
|
28859
28972
|
}
|
|
28860
28973
|
var _orchestratorDeps, PROVIDER_FETCH_TIMEOUT_MS = 5000;
|
|
@@ -28867,14 +28980,15 @@ var init_orchestrator = __esm(() => {
|
|
|
28867
28980
|
init_digest();
|
|
28868
28981
|
init_packing();
|
|
28869
28982
|
init_pull_tools();
|
|
28983
|
+
init_rebuild();
|
|
28870
28984
|
init_render();
|
|
28871
28985
|
init_scoring();
|
|
28872
|
-
init_scratch_neutralizer();
|
|
28873
28986
|
init_stage_config();
|
|
28874
28987
|
_orchestratorDeps = {
|
|
28875
28988
|
now: () => Date.now(),
|
|
28876
|
-
uuid: () =>
|
|
28877
|
-
getLogger
|
|
28989
|
+
uuid: () => randomUUID3(),
|
|
28990
|
+
getLogger,
|
|
28991
|
+
rebuild
|
|
28878
28992
|
};
|
|
28879
28993
|
});
|
|
28880
28994
|
|
|
@@ -30432,9 +30546,10 @@ function resetFailedStoriesToPending(prd, opts = {}) {
|
|
|
30432
30546
|
}
|
|
30433
30547
|
function markStorySkipped(prd, storyId) {
|
|
30434
30548
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
30435
|
-
if (story)
|
|
30436
|
-
|
|
30437
|
-
|
|
30549
|
+
if (!story)
|
|
30550
|
+
return false;
|
|
30551
|
+
story.status = "skipped";
|
|
30552
|
+
return true;
|
|
30438
30553
|
}
|
|
30439
30554
|
function resetStoryToPending(prd, storyId) {
|
|
30440
30555
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
@@ -32804,6 +32919,7 @@ var init_engine = __esm(() => {
|
|
|
32804
32919
|
init_packing();
|
|
32805
32920
|
init_render();
|
|
32806
32921
|
init_digest();
|
|
32922
|
+
init_rebuild();
|
|
32807
32923
|
init_available_budget();
|
|
32808
32924
|
init_stage_config();
|
|
32809
32925
|
init_static_rules();
|
|
@@ -33384,15 +33500,23 @@ Set \`approved: false\` when ANY of these conditions are true:
|
|
|
33384
33500
|
- The implementer loosened test assertions to mask bugs
|
|
33385
33501
|
- The implementer made illegitimate test changes
|
|
33386
33502
|
|
|
33503
|
+
When tests fail but the implementation satisfies every acceptance criterion and
|
|
33504
|
+
a specific test assertion contradicts the specification, set
|
|
33505
|
+
\`testFailureDiagnosis.cause\` to \`"test-incorrect"\` and identify each
|
|
33506
|
+
assertion with its file, test name, and reasoning. Do not use this diagnosis if
|
|
33507
|
+
the implementer modified tests or any acceptance criterion is unmet.
|
|
33508
|
+
|
|
33387
33509
|
**JSON schema** (fill in all fields with real values):
|
|
33388
33510
|
|
|
33389
33511
|
\`\`\`json
|
|
33390
|
-
{"version":1,"approved":true,"tests":{"allPassing":true,"passCount":42,"failCount":0},"testModifications":{"detected":false,"files":[],"legitimate":true,"reasoning":"..."},"acceptanceCriteria":{"allMet":true,"criteria":[{"criterion":"...","met":true}]},"quality":{"rating":"good","issues":[]},"fixes":[],"reasoning":"..."}
|
|
33512
|
+
{"version":1,"approved":true,"tests":{"allPassing":true,"passCount":42,"failCount":0},"testModifications":{"detected":false,"files":[],"legitimate":true,"reasoning":"..."},"testFailureDiagnosis":null,"acceptanceCriteria":{"allMet":true,"criteria":[{"criterion":"...","met":true}]},"quality":{"rating":"good","issues":[]},"fixes":[],"reasoning":"..."}
|
|
33391
33513
|
\`\`\`
|
|
33392
33514
|
|
|
33393
33515
|
**Field notes:**
|
|
33394
33516
|
- \`quality.rating\` must be one of: \`"good"\`, \`"acceptable"\`, \`"poor"\`
|
|
33395
33517
|
- \`testModifications.files\` \u2014 list any test files the implementer changed
|
|
33518
|
+
- \`testFailureDiagnosis\` \u2014 normally \`null\`; for a concrete incorrect-test
|
|
33519
|
+
diagnosis use \`{"cause":"test-incorrect","assertions":[{"file":"...","testName":"...","reasoning":"..."}]}\`
|
|
33396
33520
|
- \`acceptanceCriteria\` and \`quality\` are advisory in this TDD verifier verdict; do not use them to reject semantic correctness
|
|
33397
33521
|
- \`fixes\` \u2014 keep this empty; the verifier must not apply code or test fixes
|
|
33398
33522
|
- \`reasoning\` \u2014 brief summary of your overall assessment
|
|
@@ -33833,7 +33957,8 @@ class TddPromptBuilder {
|
|
|
33833
33957
|
` + `Re-emit the verdict as the FINAL content of your reply.
|
|
33834
33958
|
` + `Output ONLY the JSON object \u2014 no markdown fences, no explanation, no prose.
|
|
33835
33959
|
` + `The reply must start with { and end with } on its own line.
|
|
33836
|
-
` +
|
|
33960
|
+
` + `Required top-level fields: version, approved, tests, testModifications, acceptanceCriteria, quality, fixes, reasoning.
|
|
33961
|
+
` + 'Optional testFailureDiagnosis: null, or {"cause":"test-incorrect","assertions":[{"file":"...","testName":"...","reasoning":"..."}]}.';
|
|
33837
33962
|
}
|
|
33838
33963
|
static verdictRetryCondensed() {
|
|
33839
33964
|
return `Your previous reply was truncated and could not be parsed as valid JSON.
|
|
@@ -33844,7 +33969,7 @@ class TddPromptBuilder {
|
|
|
33844
33969
|
` + `- Set reasoning to a single sentence.
|
|
33845
33970
|
` + `Output ONLY the JSON object \u2014 no markdown fences, no prose.
|
|
33846
33971
|
` + `Schema (minimal):
|
|
33847
|
-
` + `{"version":1,"approved":boolean,"tests":{"allPassing":boolean,"passCount":number,"failCount":number},"testModifications":{"detected":boolean,"files":[],"legitimate":boolean,"reasoning":"..."},"acceptanceCriteria":{"allMet":boolean,"criteria":[]},"quality":{"rating":"good"|"acceptable"|"poor","issues":[]},"fixes":[],"reasoning":"..."}`;
|
|
33972
|
+
` + `{"version":1,"approved":boolean,"tests":{"allPassing":boolean,"passCount":number,"failCount":number},"testModifications":{"detected":boolean,"files":[],"legitimate":boolean,"reasoning":"..."},"testFailureDiagnosis":null,"acceptanceCriteria":{"allMet":boolean,"criteria":[]},"quality":{"rating":"good"|"acceptable"|"poor","issues":[]},"fixes":[],"reasoning":"..."}`;
|
|
33848
33973
|
}
|
|
33849
33974
|
s(id, content) {
|
|
33850
33975
|
return { id, content, overridable: false };
|
|
@@ -39861,6 +39986,21 @@ var init_implement = __esm(() => {
|
|
|
39861
39986
|
// src/tdd/verdict-reader.ts
|
|
39862
39987
|
import { unlink } from "fs/promises";
|
|
39863
39988
|
import path4 from "path";
|
|
39989
|
+
function isValidTestFailureDiagnosis(value) {
|
|
39990
|
+
if (!value || typeof value !== "object")
|
|
39991
|
+
return false;
|
|
39992
|
+
const diagnosis = value;
|
|
39993
|
+
if (!["implementation", "test-incorrect", "unknown"].includes(diagnosis.cause))
|
|
39994
|
+
return false;
|
|
39995
|
+
if (!Array.isArray(diagnosis.assertions))
|
|
39996
|
+
return false;
|
|
39997
|
+
return diagnosis.assertions.every((assertion) => {
|
|
39998
|
+
if (!assertion || typeof assertion !== "object")
|
|
39999
|
+
return false;
|
|
40000
|
+
const item = assertion;
|
|
40001
|
+
return typeof item.file === "string" && typeof item.reasoning === "string" && (item.testName === undefined || typeof item.testName === "string");
|
|
40002
|
+
});
|
|
40003
|
+
}
|
|
39864
40004
|
function isValidVerdict(obj) {
|
|
39865
40005
|
if (!obj || typeof obj !== "object")
|
|
39866
40006
|
return false;
|
|
@@ -39889,6 +40029,9 @@ function isValidVerdict(obj) {
|
|
|
39889
40029
|
return false;
|
|
39890
40030
|
if (typeof mods.reasoning !== "string")
|
|
39891
40031
|
return false;
|
|
40032
|
+
if (v.testFailureDiagnosis !== undefined && v.testFailureDiagnosis !== null && !isValidTestFailureDiagnosis(v.testFailureDiagnosis)) {
|
|
40033
|
+
return false;
|
|
40034
|
+
}
|
|
39892
40035
|
if (!v.acceptanceCriteria || typeof v.acceptanceCriteria !== "object")
|
|
39893
40036
|
return false;
|
|
39894
40037
|
const ac = v.acceptanceCriteria;
|
|
@@ -39989,6 +40132,7 @@ function coerceVerdict(obj) {
|
|
|
39989
40132
|
legitimate: true,
|
|
39990
40133
|
reasoning: "Not assessed in free-form verdict"
|
|
39991
40134
|
},
|
|
40135
|
+
...isValidTestFailureDiagnosis(obj.testFailureDiagnosis) ? { testFailureDiagnosis: obj.testFailureDiagnosis } : {},
|
|
39992
40136
|
acceptanceCriteria: { allMet, criteria },
|
|
39993
40137
|
quality: { rating, issues: [] },
|
|
39994
40138
|
fixes: Array.isArray(obj.fixes) ? obj.fixes : [],
|
|
@@ -40088,9 +40232,6 @@ function categorizeVerdict(verdict, testsPass) {
|
|
|
40088
40232
|
reviewReason: "Tests failing after all sessions (no verdict file)"
|
|
40089
40233
|
};
|
|
40090
40234
|
}
|
|
40091
|
-
if (verdict.approved) {
|
|
40092
|
-
return { success: true };
|
|
40093
|
-
}
|
|
40094
40235
|
if (verdict.testModifications.detected && !verdict.testModifications.legitimate) {
|
|
40095
40236
|
const files = verdict.testModifications.files.join(", ") || "unknown files";
|
|
40096
40237
|
return {
|
|
@@ -40100,6 +40241,14 @@ function categorizeVerdict(verdict, testsPass) {
|
|
|
40100
40241
|
};
|
|
40101
40242
|
}
|
|
40102
40243
|
if (!verdict.tests.allPassing) {
|
|
40244
|
+
if (hasAdmissibleIncorrectTestDiagnosis(verdict)) {
|
|
40245
|
+
const files = verdict.testFailureDiagnosis?.assertions.map((assertion) => assertion.file).join(", ");
|
|
40246
|
+
return {
|
|
40247
|
+
success: false,
|
|
40248
|
+
failureCategory: "test-incorrect",
|
|
40249
|
+
reviewReason: `Verifier identified incorrect test assertion(s) in ${files}; human review required before changing tests or source.`
|
|
40250
|
+
};
|
|
40251
|
+
}
|
|
40103
40252
|
return {
|
|
40104
40253
|
success: false,
|
|
40105
40254
|
failureCategory: "tests-failing",
|
|
@@ -40108,6 +40257,10 @@ function categorizeVerdict(verdict, testsPass) {
|
|
|
40108
40257
|
}
|
|
40109
40258
|
return { success: true };
|
|
40110
40259
|
}
|
|
40260
|
+
function hasAdmissibleIncorrectTestDiagnosis(verdict) {
|
|
40261
|
+
const diagnosis = verdict.testFailureDiagnosis;
|
|
40262
|
+
return verdict.approved === false && verdict.testModifications.detected === false && verdict.acceptanceCriteria.allMet === true && diagnosis?.cause === "test-incorrect" && diagnosis.assertions.length > 0 && diagnosis.assertions.every((assertion) => assertion.file.trim() !== "" && assertion.reasoning.trim() !== "");
|
|
40263
|
+
}
|
|
40111
40264
|
var init_verdict = __esm(() => {
|
|
40112
40265
|
init_verdict_reader();
|
|
40113
40266
|
});
|
|
@@ -40149,6 +40302,23 @@ function buildVerifierFindings(verdict, categorization) {
|
|
|
40149
40302
|
}
|
|
40150
40303
|
];
|
|
40151
40304
|
}
|
|
40305
|
+
case "test-incorrect": {
|
|
40306
|
+
const assertions = verdict.testFailureDiagnosis?.assertions ?? [];
|
|
40307
|
+
const files = assertions.map((assertion) => assertion.file);
|
|
40308
|
+
return [
|
|
40309
|
+
{
|
|
40310
|
+
source: "tdd-verifier",
|
|
40311
|
+
severity: "error",
|
|
40312
|
+
category: "incorrect-test-assertion",
|
|
40313
|
+
fixTarget: "test",
|
|
40314
|
+
message: `Verifier identified incorrect test assertion(s) in ${files.join(", ")}; human review required`,
|
|
40315
|
+
meta: {
|
|
40316
|
+
assertions,
|
|
40317
|
+
reasoning: verdict.reasoning
|
|
40318
|
+
}
|
|
40319
|
+
}
|
|
40320
|
+
];
|
|
40321
|
+
}
|
|
40152
40322
|
default:
|
|
40153
40323
|
return [];
|
|
40154
40324
|
}
|
|
@@ -44093,6 +44263,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44093
44263
|
const now = _deps.now ?? _cycleDeps.now;
|
|
44094
44264
|
const storyId = ctx.storyId;
|
|
44095
44265
|
const packageDir = ctx.packageDir;
|
|
44266
|
+
const logCtx = { storyId, packageDir, cycleName };
|
|
44096
44267
|
let totalCostUsd = 0;
|
|
44097
44268
|
const declines = createDeclineLedger(_deps.declineBacking);
|
|
44098
44269
|
let unresolvedDetail;
|
|
@@ -44108,9 +44279,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44108
44279
|
const orphanSources = [...new Set(cycle.findings.map((f) => f.source))];
|
|
44109
44280
|
const retiredStrategies = declines.retiredNames(cycle.strategies, cycle.findings);
|
|
44110
44281
|
logger?.warn("findings.cycle", "cycle exited \u2014 no matching strategy (orphaned findings)", {
|
|
44111
|
-
|
|
44112
|
-
packageDir,
|
|
44113
|
-
cycleName,
|
|
44282
|
+
...logCtx,
|
|
44114
44283
|
reason: "no-strategy",
|
|
44115
44284
|
findingsCount: cycle.findings.length,
|
|
44116
44285
|
orphanSources,
|
|
@@ -44127,9 +44296,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44127
44296
|
if (uncappedActive.length === 0) {
|
|
44128
44297
|
const exhaustedStrategy = active.find((s) => countStrategyAttempts(history, s.name) >= s.maxAttempts);
|
|
44129
44298
|
logger?.info("findings.cycle", "cycle exited \u2014 all active strategies exhausted", {
|
|
44130
|
-
|
|
44131
|
-
packageDir,
|
|
44132
|
-
cycleName,
|
|
44299
|
+
...logCtx,
|
|
44133
44300
|
reason: "max-attempts-per-strategy",
|
|
44134
44301
|
exhaustedStrategy: exhaustedStrategy?.name
|
|
44135
44302
|
});
|
|
@@ -44144,9 +44311,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44144
44311
|
const totalAttempts = countTotalAttempts(history);
|
|
44145
44312
|
if (totalAttempts >= cycle.config.maxAttemptsTotal) {
|
|
44146
44313
|
logger?.info("findings.cycle", "cycle exited \u2014 total attempt cap reached", {
|
|
44147
|
-
|
|
44148
|
-
packageDir,
|
|
44149
|
-
cycleName,
|
|
44314
|
+
...logCtx,
|
|
44150
44315
|
reason: "max-attempts-total",
|
|
44151
44316
|
totalAttempts,
|
|
44152
44317
|
maxAttemptsTotal: cycle.config.maxAttemptsTotal
|
|
@@ -44161,13 +44326,14 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44161
44326
|
for (const strategy of uncappedActive) {
|
|
44162
44327
|
const bailReason = strategy.bailWhen?.(history) ?? null;
|
|
44163
44328
|
if (bailReason !== null) {
|
|
44329
|
+
const inheritedIterations = cycle.priorIterations?.length ?? 0;
|
|
44164
44330
|
logger?.info("findings.cycle", "cycle exited \u2014 bail predicate fired", {
|
|
44165
|
-
|
|
44166
|
-
packageDir,
|
|
44167
|
-
cycleName,
|
|
44331
|
+
...logCtx,
|
|
44168
44332
|
reason: "bail-when",
|
|
44169
44333
|
strategyName: strategy.name,
|
|
44170
|
-
bailDetail: bailReason
|
|
44334
|
+
bailDetail: bailReason,
|
|
44335
|
+
cycleIterations: cycle.iterations.length,
|
|
44336
|
+
...inheritedIterations > 0 ? { inheritedIterations } : {}
|
|
44171
44337
|
});
|
|
44172
44338
|
return finish({
|
|
44173
44339
|
iterations: cycle.iterations,
|
|
@@ -44222,9 +44388,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44222
44388
|
}, { storyId, packageDir, cycleName }, logger);
|
|
44223
44389
|
totalCostUsd += fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
|
|
44224
44390
|
logger?.info("findings.cycle", "cycle exited \u2014 agent gave up", {
|
|
44225
|
-
|
|
44226
|
-
packageDir,
|
|
44227
|
-
cycleName,
|
|
44391
|
+
...logCtx,
|
|
44228
44392
|
reason: "agent-gave-up",
|
|
44229
44393
|
strategyName: firstUnresolved.strategyName,
|
|
44230
44394
|
unresolvedDetail: firstUnresolved.unresolved
|
|
@@ -44238,9 +44402,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44238
44402
|
});
|
|
44239
44403
|
}
|
|
44240
44404
|
logger?.info("findings.cycle", "strategy gave up \u2014 retired, continuing with co-run siblings", {
|
|
44241
|
-
|
|
44242
|
-
packageDir,
|
|
44243
|
-
cycleName,
|
|
44405
|
+
...logCtx,
|
|
44244
44406
|
strategyName: firstUnresolved.strategyName,
|
|
44245
44407
|
unresolvedDetail: firstUnresolved.unresolved,
|
|
44246
44408
|
ranWithoutGivingUp: fixesApplied.filter((fa) => !fa.unresolved).map((fa) => fa.strategyName)
|
|
@@ -44271,9 +44433,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44271
44433
|
finishedAt: finishedAt3
|
|
44272
44434
|
}, { storyId, packageDir, cycleName }, logger);
|
|
44273
44435
|
logger?.warn("findings.cycle", "lite validate failed on terminal exhausted branch", {
|
|
44274
|
-
|
|
44275
|
-
packageDir,
|
|
44276
|
-
cycleName,
|
|
44436
|
+
...logCtx,
|
|
44277
44437
|
error: errorMessage(err)
|
|
44278
44438
|
});
|
|
44279
44439
|
return finish({
|
|
@@ -44297,9 +44457,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44297
44457
|
cycle.findings = liteFindingsAfter;
|
|
44298
44458
|
if (liteFindingsAfter.length === 0 && !liteShortCircuited) {
|
|
44299
44459
|
logger?.info("findings.cycle", "cycle exited \u2014 resolved after terminal lite validate", {
|
|
44300
|
-
|
|
44301
|
-
packageDir,
|
|
44302
|
-
cycleName,
|
|
44460
|
+
...logCtx,
|
|
44303
44461
|
reason: "resolved"
|
|
44304
44462
|
});
|
|
44305
44463
|
return finish({
|
|
@@ -44313,18 +44471,14 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44313
44471
|
const companions = uncappedActive.filter((s) => !group.includes(s));
|
|
44314
44472
|
if (companions.length > 0) {
|
|
44315
44473
|
logger?.info("findings.cycle", "exclusive strategy exhausted \u2014 continuing to companion strategies", {
|
|
44316
|
-
|
|
44317
|
-
packageDir,
|
|
44318
|
-
cycleName,
|
|
44474
|
+
...logCtx,
|
|
44319
44475
|
exhaustedStrategies: group.map((s) => s.name),
|
|
44320
44476
|
remainingStrategies: companions.map((s) => s.name)
|
|
44321
44477
|
});
|
|
44322
44478
|
continue;
|
|
44323
44479
|
}
|
|
44324
44480
|
logger?.info("findings.cycle", "cycle exited \u2014 validate short-circuited", {
|
|
44325
|
-
|
|
44326
|
-
packageDir,
|
|
44327
|
-
cycleName,
|
|
44481
|
+
...logCtx,
|
|
44328
44482
|
reason: "validate-short-circuit",
|
|
44329
44483
|
liteFindingsAfterCount: liteFindingsAfter.length
|
|
44330
44484
|
});
|
|
@@ -44336,9 +44490,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44336
44490
|
});
|
|
44337
44491
|
}
|
|
44338
44492
|
logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached (lite validate)", {
|
|
44339
|
-
|
|
44340
|
-
packageDir,
|
|
44341
|
-
cycleName,
|
|
44493
|
+
...logCtx,
|
|
44342
44494
|
reason: "max-attempts-per-strategy",
|
|
44343
44495
|
exhaustedStrategy: group[0]?.name,
|
|
44344
44496
|
liteFindingsAfterCount: liteFindingsAfter.length
|
|
@@ -44375,9 +44527,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
44375
44527
|
});
|
|
44376
44528
|
}
|
|
44377
44529
|
logger?.warn("findings.cycle", "validator retry", {
|
|
44378
|
-
|
|
44379
|
-
packageDir,
|
|
44380
|
-
cycleName,
|
|
44530
|
+
...logCtx,
|
|
44381
44531
|
attempt: validatorAttempt + 1,
|
|
44382
44532
|
error: errorMessage(err)
|
|
44383
44533
|
});
|
|
@@ -44431,8 +44581,8 @@ var NAX_BAIL_WRAPPER = "__naxBailWrapper";
|
|
|
44431
44581
|
function createStoryFixHistory() {
|
|
44432
44582
|
return new Map;
|
|
44433
44583
|
}
|
|
44434
|
-
function storyFixKey(storyId, tier) {
|
|
44435
|
-
return `${storyId}::${tier ?? "default"}`;
|
|
44584
|
+
function storyFixKey(storyId, tier, agent) {
|
|
44585
|
+
return `${storyId}::${tier ?? "default"}::${agent ?? "default"}`;
|
|
44436
44586
|
}
|
|
44437
44587
|
function getStoryFixState(store, key) {
|
|
44438
44588
|
let existing = store.get(key);
|
|
@@ -44958,7 +45108,7 @@ var package_default;
|
|
|
44958
45108
|
var init_package = __esm(() => {
|
|
44959
45109
|
package_default = {
|
|
44960
45110
|
name: "@nathapp/nax",
|
|
44961
|
-
version: "0.
|
|
45111
|
+
version: "0.78.0",
|
|
44962
45112
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
44963
45113
|
type: "module",
|
|
44964
45114
|
bin: {
|
|
@@ -44997,13 +45147,17 @@ var init_package = __esm(() => {
|
|
|
44997
45147
|
"test:e2e": "timeout -k 5s 180s bun test test/e2e/ --timeout=60000",
|
|
44998
45148
|
"test:coverage": "bun run scripts/check-coverage.ts",
|
|
44999
45149
|
"test:coverage:report": "bun run scripts/check-coverage.ts --report",
|
|
45000
|
-
"
|
|
45001
|
-
"
|
|
45002
|
-
"check:test-sizes": "bun run scripts/check-test-sizes.ts",
|
|
45150
|
+
"report:test-overlap": "bun run scripts/report-test-overlap.ts",
|
|
45151
|
+
"report:dead-tests": "bun run scripts/report-dead-tests.ts",
|
|
45003
45152
|
"check:test-mocks": "bun scripts/check-inline-test-mocks.ts --strict",
|
|
45004
45153
|
"check:process-cwd": "bash scripts/check-process-cwd.sh",
|
|
45005
45154
|
"check:no-adapter-wrap": "bash scripts/check-no-adapter-wrap.sh",
|
|
45006
45155
|
"check:dispatch-context": "bash scripts/check-dispatch-context.sh",
|
|
45156
|
+
"check:naxconfig-cast": "bash scripts/check-no-silent-naxconfig-cast.sh",
|
|
45157
|
+
"check:runtime-cleanup": "bash scripts/check-runtime-cleanup.sh",
|
|
45158
|
+
"check:adapter-no-config-import": "bash scripts/check-adapter-no-config-import.sh",
|
|
45159
|
+
"check:gate-reachability": "bun run scripts/check-gate-reachability.ts",
|
|
45160
|
+
"check:all": "bun run lint && bun run check:test-mocks && bun run check:process-cwd && bun run check:no-adapter-wrap && bun run check:dispatch-context && bun run check:naxconfig-cast && bun run check:runtime-cleanup && bun run check:adapter-no-config-import && bun run check:gate-reachability",
|
|
45007
45161
|
prepublishOnly: "bun run build",
|
|
45008
45162
|
"test:full": "FULL=1 NAX_PRECHECK=1 bun test test/ --timeout=60000"
|
|
45009
45163
|
},
|
|
@@ -45062,8 +45216,8 @@ var init_version = __esm(() => {
|
|
|
45062
45216
|
NAX_VERSION = package_default.version;
|
|
45063
45217
|
NAX_COMMIT = (() => {
|
|
45064
45218
|
try {
|
|
45065
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
45066
|
-
return "
|
|
45219
|
+
if (/^[0-9a-f]{6,10}$/.test("af86acb4"))
|
|
45220
|
+
return "af86acb4";
|
|
45067
45221
|
} catch {}
|
|
45068
45222
|
try {
|
|
45069
45223
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -51129,7 +51283,7 @@ var init_pid_registry = __esm(() => {
|
|
|
51129
51283
|
});
|
|
51130
51284
|
|
|
51131
51285
|
// src/session/manager-deps.ts
|
|
51132
|
-
import { randomUUID as
|
|
51286
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
51133
51287
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
51134
51288
|
import { isAbsolute as isAbsolute11, join as join32, relative as relative12, sep as sep3 } from "path";
|
|
51135
51289
|
function resolveProjectDirFromScratchDir(scratchDir) {
|
|
@@ -51151,7 +51305,7 @@ var init_manager_deps = __esm(() => {
|
|
|
51151
51305
|
_sessionManagerDeps = {
|
|
51152
51306
|
now: () => new Date().toISOString(),
|
|
51153
51307
|
nowMs: () => Date.now(),
|
|
51154
|
-
uuid: () =>
|
|
51308
|
+
uuid: () => randomUUID4(),
|
|
51155
51309
|
sessionScratchDir: (projectDir, featureName, sessionId) => join32(projectDir, ".nax", "features", featureName, "sessions", sessionId),
|
|
51156
51310
|
writeDescriptor: async (scratchDir, descriptor, projectDir) => {
|
|
51157
51311
|
await mkdir5(scratchDir, { recursive: true });
|
|
@@ -60272,7 +60426,7 @@ var init_completion = __esm(() => {
|
|
|
60272
60426
|
const isBatch = ctx.stories.length > 1;
|
|
60273
60427
|
const sessionCost = ctx.runtime.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? 0;
|
|
60274
60428
|
const persistPrd2 = ctx.skipPrdPersistence !== true;
|
|
60275
|
-
const prdPath = ctx.prdPath ?? (ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}
|
|
60429
|
+
const prdPath = ctx.prdPath ?? (ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/.nax/features/unknown/prd.json`);
|
|
60276
60430
|
const storyStartTime = ctx.storyStartTime || new Date().toISOString();
|
|
60277
60431
|
if (isBatch) {
|
|
60278
60432
|
ctx.storyMetrics = collectBatchMetrics(ctx, storyStartTime);
|
|
@@ -60719,7 +60873,7 @@ var init_helpers = __esm(() => {
|
|
|
60719
60873
|
});
|
|
60720
60874
|
|
|
60721
60875
|
// src/pipeline/stages/context.ts
|
|
60722
|
-
import { randomUUID as
|
|
60876
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
60723
60877
|
async function runV2Path(ctx) {
|
|
60724
60878
|
const logger = getLogger();
|
|
60725
60879
|
const agentName = ctx.routing.agent ?? ctx.agentManager?.getDefault() ?? "claude";
|
|
@@ -60943,7 +61097,7 @@ var init_context2 = __esm(() => {
|
|
|
60943
61097
|
createOrchestrator: createDefaultOrchestrator,
|
|
60944
61098
|
loadPlugins: loadPluginProviders,
|
|
60945
61099
|
v1FeatureProvider: () => new FeatureContextProvider,
|
|
60946
|
-
uuid: () =>
|
|
61100
|
+
uuid: () => randomUUID5(),
|
|
60947
61101
|
readDigest: readDigestFile,
|
|
60948
61102
|
writeDigest: writeDigestFile
|
|
60949
61103
|
};
|
|
@@ -60991,6 +61145,23 @@ async function spawnWithTimeout(proc, timeoutMs) {
|
|
|
60991
61145
|
function captureFailureSentinel() {
|
|
60992
61146
|
return `__capture_failed__:${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
|
60993
61147
|
}
|
|
61148
|
+
async function untrackedDigestInput(deps, workdir) {
|
|
61149
|
+
const listed = await spawnWithTimeout(spawnGit(deps, ["ls-files", "--others", "--exclude-standard", "-z"], workdir), TREE_CAPTURE_TIMEOUT_MS);
|
|
61150
|
+
if (listed.exitCode !== 0)
|
|
61151
|
+
return null;
|
|
61152
|
+
const paths = listed.stdout.split("\x00").filter((p) => p !== "");
|
|
61153
|
+
if (paths.length === 0)
|
|
61154
|
+
return "0";
|
|
61155
|
+
paths.sort();
|
|
61156
|
+
const capped = paths.slice(0, MAX_UNTRACKED_HASHED);
|
|
61157
|
+
const hashed = await spawnWithTimeout(spawnGit(deps, ["hash-object", "--", ...capped], workdir), UNTRACKED_HASH_TIMEOUT_MS);
|
|
61158
|
+
if (hashed.exitCode !== 0)
|
|
61159
|
+
return null;
|
|
61160
|
+
return `${paths.length}
|
|
61161
|
+
${capped.join(`
|
|
61162
|
+
`)}
|
|
61163
|
+
${hashed.stdout.trim()}`;
|
|
61164
|
+
}
|
|
60994
61165
|
async function captureTreeState(workdir, options) {
|
|
60995
61166
|
let headSha = "";
|
|
60996
61167
|
let dirtyDigest = "";
|
|
@@ -61002,14 +61173,29 @@ async function captureTreeState(workdir, options) {
|
|
|
61002
61173
|
headSha = captureFailureSentinel();
|
|
61003
61174
|
}
|
|
61004
61175
|
try {
|
|
61005
|
-
const
|
|
61006
|
-
const
|
|
61007
|
-
if (exitCode === 0) {
|
|
61008
|
-
const
|
|
61009
|
-
if (
|
|
61010
|
-
const
|
|
61011
|
-
|
|
61012
|
-
|
|
61176
|
+
const statusProc = spawnGit(options._deps, ["status", "--porcelain"], workdir);
|
|
61177
|
+
const status = await spawnWithTimeout(statusProc, TREE_CAPTURE_TIMEOUT_MS);
|
|
61178
|
+
if (status.exitCode === 0) {
|
|
61179
|
+
const trimmedStatus = status.stdout.trim();
|
|
61180
|
+
if (trimmedStatus) {
|
|
61181
|
+
const diffProc = spawnGit(options._deps, ["diff"], workdir);
|
|
61182
|
+
const diff = await spawnWithTimeout(diffProc, TREE_CAPTURE_TIMEOUT_MS);
|
|
61183
|
+
const cachedDiffProc = spawnGit(options._deps, ["diff", "--cached"], workdir);
|
|
61184
|
+
const cachedDiff = await spawnWithTimeout(cachedDiffProc, TREE_CAPTURE_TIMEOUT_MS);
|
|
61185
|
+
const untracked = await untrackedDigestInput(options._deps, workdir);
|
|
61186
|
+
if (diff.exitCode === 0 && cachedDiff.exitCode === 0 && untracked !== null) {
|
|
61187
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
61188
|
+
hasher.update(trimmedStatus);
|
|
61189
|
+
hasher.update("\x00");
|
|
61190
|
+
hasher.update(diff.stdout);
|
|
61191
|
+
hasher.update("\x00");
|
|
61192
|
+
hasher.update(cachedDiff.stdout);
|
|
61193
|
+
hasher.update("\x00");
|
|
61194
|
+
hasher.update(untracked);
|
|
61195
|
+
dirtyDigest = hasher.digest("hex");
|
|
61196
|
+
} else {
|
|
61197
|
+
dirtyDigest = captureFailureSentinel();
|
|
61198
|
+
}
|
|
61013
61199
|
}
|
|
61014
61200
|
} else {
|
|
61015
61201
|
dirtyDigest = captureFailureSentinel();
|
|
@@ -61028,7 +61214,7 @@ function buildCheckpointLogData(meta3) {
|
|
|
61028
61214
|
const { storyId, ...rest } = meta3;
|
|
61029
61215
|
return { storyId, ...rest };
|
|
61030
61216
|
}
|
|
61031
|
-
var TREE_CAPTURE_TIMEOUT_MS = 75;
|
|
61217
|
+
var TREE_CAPTURE_TIMEOUT_MS = 75, MAX_UNTRACKED_HASHED = 500, UNTRACKED_HASH_TIMEOUT_MS = 250;
|
|
61032
61218
|
|
|
61033
61219
|
// src/tdd/rollback.ts
|
|
61034
61220
|
async function rollbackToRef(workdir, ref) {
|
|
@@ -62350,12 +62536,19 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
62350
62536
|
if (initialFindings.length === 0) {
|
|
62351
62537
|
return {};
|
|
62352
62538
|
}
|
|
62539
|
+
if (initialFindings.some((finding) => finding.category === "incorrect-test-assertion")) {
|
|
62540
|
+
getSafeLogger()?.warn("story-orchestrator", "Incorrect test diagnosis requires human review", {
|
|
62541
|
+
storyId: ctx.storyId,
|
|
62542
|
+
findingCount: initialFindings.length
|
|
62543
|
+
});
|
|
62544
|
+
return { terminalReviewRequired: true, unfixedFindings: initialFindings };
|
|
62545
|
+
}
|
|
62353
62546
|
if (!ctx.storyId) {
|
|
62354
62547
|
return {};
|
|
62355
62548
|
}
|
|
62356
62549
|
const storyFixBudgetEnabled = !nbfPath && ctx.runtime.configLoader.current().execution?.rectification?.storyScopedFixBudget === true;
|
|
62357
62550
|
const store = ctx.runtime.storyFixHistory;
|
|
62358
|
-
const fixKey = storyFixBudgetEnabled ? storyFixKey(ctx.storyId, ctx.phaseTelemetry?.tier) : undefined;
|
|
62551
|
+
const fixKey = storyFixBudgetEnabled ? storyFixKey(ctx.storyId, ctx.phaseTelemetry?.tier, ctx.agentName) : undefined;
|
|
62359
62552
|
const fixState = fixKey !== undefined && store ? getStoryFixState(store, fixKey) : undefined;
|
|
62360
62553
|
const priorIterationCount = fixState?.iterations.length ?? 0;
|
|
62361
62554
|
const declineSnapshot = fixState ? new Map([...fixState.declines].map(([name, keys]) => [name, new Set(keys)])) : undefined;
|
|
@@ -62570,7 +62763,7 @@ class ExecutionPlan {
|
|
|
62570
62763
|
const rectResult = await runRectification(this.ctx, this.state, phaseCosts, phaseOutputs, {
|
|
62571
62764
|
gateBaselineKeys: preRectGateFailureKeys
|
|
62572
62765
|
});
|
|
62573
|
-
if (this.state.rectification && (!rectResult.rectificationExhausted || rectResult.liteScopeIncomplete)) {
|
|
62766
|
+
if (this.state.rectification && !rectResult.terminalReviewRequired && (!rectResult.rectificationExhausted || rectResult.liteScopeIncomplete)) {
|
|
62574
62767
|
let resumeRectifyUsed = false;
|
|
62575
62768
|
for (const phase of collectOrderedPhases(this.state)) {
|
|
62576
62769
|
const name = phase.slot.op.name;
|
|
@@ -63342,6 +63535,8 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason, failure
|
|
|
63342
63535
|
};
|
|
63343
63536
|
case "dependency-prep":
|
|
63344
63537
|
return pauseFallback;
|
|
63538
|
+
case "test-incorrect":
|
|
63539
|
+
return pauseFallback;
|
|
63345
63540
|
default:
|
|
63346
63541
|
return pauseFallback;
|
|
63347
63542
|
}
|
|
@@ -64220,6 +64415,7 @@ function parseQueueFile(content) {
|
|
|
64220
64415
|
var init_queue = () => {};
|
|
64221
64416
|
|
|
64222
64417
|
// src/execution/queue-handler.ts
|
|
64418
|
+
import { rename, unlink as unlink3 } from "fs/promises";
|
|
64223
64419
|
import path15 from "path";
|
|
64224
64420
|
function getSafeLogger4() {
|
|
64225
64421
|
try {
|
|
@@ -64239,8 +64435,11 @@ async function readQueueFile(workdir) {
|
|
|
64239
64435
|
return [];
|
|
64240
64436
|
}
|
|
64241
64437
|
try {
|
|
64242
|
-
await
|
|
64438
|
+
await rename(queuePath, processingPath);
|
|
64243
64439
|
} catch (error48) {
|
|
64440
|
+
logger?.warn("queue", "Failed to rename queue file for processing", {
|
|
64441
|
+
error: error48.message
|
|
64442
|
+
});
|
|
64244
64443
|
return [];
|
|
64245
64444
|
}
|
|
64246
64445
|
const processingFile = Bun.file(processingPath);
|
|
@@ -64261,7 +64460,7 @@ async function clearQueueFile(workdir) {
|
|
|
64261
64460
|
const file3 = Bun.file(processingPath);
|
|
64262
64461
|
const exists = await file3.exists();
|
|
64263
64462
|
if (exists) {
|
|
64264
|
-
await
|
|
64463
|
+
await unlink3(processingPath);
|
|
64265
64464
|
}
|
|
64266
64465
|
} catch (error48) {
|
|
64267
64466
|
logger?.warn("queue", "Failed to clear queue file", {
|
|
@@ -64276,6 +64475,20 @@ var init_queue_handler = __esm(() => {
|
|
|
64276
64475
|
|
|
64277
64476
|
// src/pipeline/stages/queue-check.ts
|
|
64278
64477
|
import path16 from "path";
|
|
64478
|
+
function resolvePrdPath(ctx) {
|
|
64479
|
+
return ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/.nax/features/unknown/prd.json`;
|
|
64480
|
+
}
|
|
64481
|
+
function logDroppedCommands(logger, ctx, queueCommands, currentIndex) {
|
|
64482
|
+
const dropped = queueCommands.slice(currentIndex + 1);
|
|
64483
|
+
if (dropped.length === 0) {
|
|
64484
|
+
return;
|
|
64485
|
+
}
|
|
64486
|
+
logger.warn("queue", "Dropped unprocessed queue commands", {
|
|
64487
|
+
storyId: ctx.story?.id ?? "unknown",
|
|
64488
|
+
droppedCount: dropped.length,
|
|
64489
|
+
droppedTypes: dropped.map((c) => c.type)
|
|
64490
|
+
});
|
|
64491
|
+
}
|
|
64279
64492
|
var queueCheckStage;
|
|
64280
64493
|
var init_queue_check = __esm(() => {
|
|
64281
64494
|
init_config();
|
|
@@ -64292,9 +64505,10 @@ var init_queue_check = __esm(() => {
|
|
|
64292
64505
|
if (queueCommands.length === 0) {
|
|
64293
64506
|
return { action: "continue" };
|
|
64294
64507
|
}
|
|
64295
|
-
for (const cmd of queueCommands) {
|
|
64508
|
+
for (const [index, cmd] of queueCommands.entries()) {
|
|
64296
64509
|
if (cmd.type === "PAUSE") {
|
|
64297
64510
|
logger.warn("queue", "Paused by user", { storyId: ctx.story?.id ?? "unknown", command: "PAUSE" });
|
|
64511
|
+
logDroppedCommands(logger, ctx, queueCommands, index);
|
|
64298
64512
|
await clearQueueFile(ctx.workdir);
|
|
64299
64513
|
return { action: "pause", reason: "User requested pause via .queue.txt" };
|
|
64300
64514
|
}
|
|
@@ -64305,16 +64519,15 @@ var init_queue_check = __esm(() => {
|
|
|
64305
64519
|
markStorySkipped(ctx.prd, s.id);
|
|
64306
64520
|
}
|
|
64307
64521
|
}
|
|
64308
|
-
|
|
64309
|
-
|
|
64522
|
+
await savePRD(ctx.prd, resolvePrdPath(ctx));
|
|
64523
|
+
logDroppedCommands(logger, ctx, queueCommands, index);
|
|
64310
64524
|
await clearQueueFile(ctx.workdir);
|
|
64311
64525
|
return { action: "pause", reason: "User requested abort" };
|
|
64312
64526
|
}
|
|
64313
64527
|
if (cmd.type === "RETRY") {
|
|
64314
64528
|
logger.warn("queue", "Retrying story by user request", { storyId: cmd.storyId });
|
|
64315
64529
|
resetStoryToPending(ctx.prd, cmd.storyId);
|
|
64316
|
-
|
|
64317
|
-
await savePRD(ctx.prd, prdPath);
|
|
64530
|
+
await savePRD(ctx.prd, resolvePrdPath(ctx));
|
|
64318
64531
|
continue;
|
|
64319
64532
|
}
|
|
64320
64533
|
if (cmd.type === "PRIORITY") {
|
|
@@ -64323,8 +64536,7 @@ var init_queue_check = __esm(() => {
|
|
|
64323
64536
|
priority: cmd.value
|
|
64324
64537
|
});
|
|
64325
64538
|
setStoryPriority(ctx.prd, cmd.storyId, cmd.value);
|
|
64326
|
-
|
|
64327
|
-
await savePRD(ctx.prd, prdPath);
|
|
64539
|
+
await savePRD(ctx.prd, resolvePrdPath(ctx));
|
|
64328
64540
|
continue;
|
|
64329
64541
|
}
|
|
64330
64542
|
if (cmd.type === "INJECT") {
|
|
@@ -64342,8 +64554,7 @@ var init_queue_check = __esm(() => {
|
|
|
64342
64554
|
injectedStoryId: story.id,
|
|
64343
64555
|
storyFile: cmd.storyFile
|
|
64344
64556
|
});
|
|
64345
|
-
|
|
64346
|
-
await savePRD(ctx.prd, prdPath);
|
|
64557
|
+
await savePRD(ctx.prd, resolvePrdPath(ctx));
|
|
64347
64558
|
} catch (err) {
|
|
64348
64559
|
logger.error("queue", "Failed to inject story \u2014 skipping INJECT command", {
|
|
64349
64560
|
storyId: ctx.story?.id ?? "unknown",
|
|
@@ -64354,16 +64565,19 @@ var init_queue_check = __esm(() => {
|
|
|
64354
64565
|
continue;
|
|
64355
64566
|
}
|
|
64356
64567
|
if (cmd.type === "SKIP") {
|
|
64357
|
-
|
|
64358
|
-
|
|
64359
|
-
|
|
64568
|
+
if (markStorySkipped(ctx.prd, cmd.storyId)) {
|
|
64569
|
+
logger.warn("queue", "Skipping story by user request", { storyId: cmd.storyId });
|
|
64570
|
+
await savePRD(ctx.prd, resolvePrdPath(ctx));
|
|
64571
|
+
} else {
|
|
64572
|
+
logger.warn("queue", "SKIP names a story that is not in the PRD \u2014 ignoring", {
|
|
64360
64573
|
storyId: cmd.storyId
|
|
64361
64574
|
});
|
|
64362
|
-
|
|
64363
|
-
|
|
64364
|
-
|
|
64575
|
+
}
|
|
64576
|
+
const isTargeted = ctx.stories.some((s) => s.id === cmd.storyId);
|
|
64577
|
+
if (isTargeted) {
|
|
64365
64578
|
ctx.stories = ctx.stories.filter((s) => s.id !== cmd.storyId);
|
|
64366
64579
|
if (ctx.stories.length === 0) {
|
|
64580
|
+
logDroppedCommands(logger, ctx, queueCommands, index);
|
|
64367
64581
|
await clearQueueFile(ctx.workdir);
|
|
64368
64582
|
return { action: "skip", reason: "All stories in batch were skipped" };
|
|
64369
64583
|
}
|
|
@@ -64409,7 +64623,7 @@ var init_routing2 = __esm(() => {
|
|
|
64409
64623
|
candidateTier
|
|
64410
64624
|
});
|
|
64411
64625
|
}
|
|
64412
|
-
const isEscalated = previousTier !== undefined && (previousRank !== undefined && candidateRank !== undefined
|
|
64626
|
+
const isEscalated = previousTier !== undefined && (hasEscalationRecords || previousRank !== undefined && candidateRank !== undefined && previousRank > candidateRank);
|
|
64413
64627
|
const modelTier = isEscalated ? previousTier : candidateTier;
|
|
64414
64628
|
const routing = { ...decision, modelTier, agent: ctx.story.routing?.agent ?? decision.agent };
|
|
64415
64629
|
const neverEscalated = !hasEscalationRecords;
|
|
@@ -69779,7 +69993,7 @@ var init_writer = __esm(() => {
|
|
|
69779
69993
|
});
|
|
69780
69994
|
|
|
69781
69995
|
// src/execution/checkpoint/reader.ts
|
|
69782
|
-
import { join as
|
|
69996
|
+
import { join as join86 } from "path";
|
|
69783
69997
|
function isValidRecord(value) {
|
|
69784
69998
|
if (!value || typeof value !== "object")
|
|
69785
69999
|
return false;
|
|
@@ -69812,7 +70026,7 @@ async function defaultRead(filePath) {
|
|
|
69812
70026
|
return file3.text();
|
|
69813
70027
|
}
|
|
69814
70028
|
async function loadCheckpoints(featureDir, options = { _deps: { read: defaultRead } }) {
|
|
69815
|
-
const filePath =
|
|
70029
|
+
const filePath = join86(featureDir, "checkpoint.jsonl");
|
|
69816
70030
|
const deps = options._deps ?? { read: defaultRead };
|
|
69817
70031
|
let content;
|
|
69818
70032
|
try {
|
|
@@ -69882,7 +70096,7 @@ var init_reader = __esm(() => {
|
|
|
69882
70096
|
});
|
|
69883
70097
|
|
|
69884
70098
|
// src/execution/checkpoint/resume-cli.ts
|
|
69885
|
-
import { join as
|
|
70099
|
+
import { join as join87 } from "path";
|
|
69886
70100
|
function applyResumeModeDeps(featureDir, mode = "auto") {
|
|
69887
70101
|
if (mode === "fresh" || mode === "no-resume") {
|
|
69888
70102
|
_storyOrchestratorDeps.loadCheckpoints = async (_fd) => new Map;
|
|
@@ -69892,7 +70106,7 @@ function applyResumeModeDeps(featureDir, mode = "auto") {
|
|
|
69892
70106
|
_storyOrchestratorDeps.loadCheckpoints = async (_fd) => loadCheckpoints(target);
|
|
69893
70107
|
}
|
|
69894
70108
|
function applyRecordGreenDeps(featureDir, runId) {
|
|
69895
|
-
const writer = createCheckpointWriter(
|
|
70109
|
+
const writer = createCheckpointWriter(join87(featureDir, "checkpoint.jsonl"), runId);
|
|
69896
70110
|
_storyOrchestratorDeps.recordGreen = (storyId, phase, tree) => writer.recordGreen(storyId, phase, tree);
|
|
69897
70111
|
}
|
|
69898
70112
|
var init_resume_cli = __esm(() => {
|
|
@@ -69941,11 +70155,11 @@ var init_types10 = __esm(() => {
|
|
|
69941
70155
|
});
|
|
69942
70156
|
|
|
69943
70157
|
// src/hooks/runner.ts
|
|
69944
|
-
import { join as
|
|
70158
|
+
import { join as join88 } from "path";
|
|
69945
70159
|
function createDrainDeadline2(deadlineMs) {
|
|
69946
70160
|
let timeoutId;
|
|
69947
|
-
const promise2 = new Promise((
|
|
69948
|
-
timeoutId = setTimeout(() =>
|
|
70161
|
+
const promise2 = new Promise((resolve21) => {
|
|
70162
|
+
timeoutId = setTimeout(() => resolve21(""), deadlineMs);
|
|
69949
70163
|
});
|
|
69950
70164
|
return {
|
|
69951
70165
|
promise: promise2,
|
|
@@ -69960,14 +70174,14 @@ async function loadHooksConfig(projectDir, globalDir) {
|
|
|
69960
70174
|
let globalHooks = { hooks: {} };
|
|
69961
70175
|
let projectHooks = { hooks: {} };
|
|
69962
70176
|
let skipGlobal = false;
|
|
69963
|
-
const projectPath =
|
|
70177
|
+
const projectPath = join88(projectDir, "hooks.json");
|
|
69964
70178
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
69965
70179
|
if (projectData) {
|
|
69966
70180
|
projectHooks = projectData;
|
|
69967
70181
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
69968
70182
|
}
|
|
69969
70183
|
if (!skipGlobal && globalDir) {
|
|
69970
|
-
const globalPath =
|
|
70184
|
+
const globalPath = join88(globalDir, "hooks.json");
|
|
69971
70185
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
69972
70186
|
if (globalData) {
|
|
69973
70187
|
globalHooks = globalData;
|
|
@@ -70483,7 +70697,7 @@ var init_crash_recovery = __esm(() => {
|
|
|
70483
70697
|
});
|
|
70484
70698
|
|
|
70485
70699
|
// src/acceptance/import-resolution.ts
|
|
70486
|
-
import { resolve as
|
|
70700
|
+
import { resolve as resolve21, sep as sep9 } from "path";
|
|
70487
70701
|
function languageFromExtension(testFilePath) {
|
|
70488
70702
|
if (!testFilePath)
|
|
70489
70703
|
return;
|
|
@@ -70505,9 +70719,9 @@ async function resolveLanguage(opts) {
|
|
|
70505
70719
|
return "typescript";
|
|
70506
70720
|
}
|
|
70507
70721
|
async function readCapped(relPath, packageDir) {
|
|
70508
|
-
const resolvedPackageDir =
|
|
70509
|
-
const fullPath =
|
|
70510
|
-
if (fullPath !== resolvedPackageDir && !fullPath.startsWith(resolvedPackageDir +
|
|
70722
|
+
const resolvedPackageDir = resolve21(packageDir);
|
|
70723
|
+
const fullPath = resolve21(resolvedPackageDir, relPath);
|
|
70724
|
+
if (fullPath !== resolvedPackageDir && !fullPath.startsWith(resolvedPackageDir + sep9)) {
|
|
70511
70725
|
return null;
|
|
70512
70726
|
}
|
|
70513
70727
|
try {
|
|
@@ -70754,12 +70968,12 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
70754
70968
|
const content = await Bun.file(testPath).text();
|
|
70755
70969
|
await Bun.write(bakPath, content);
|
|
70756
70970
|
logger?.info("acceptance", `Backed up acceptance test -> ${bakPath}`);
|
|
70757
|
-
const { unlink:
|
|
70758
|
-
await
|
|
70971
|
+
const { unlink: unlink4 } = await import("fs/promises");
|
|
70972
|
+
await unlink4(testPath);
|
|
70759
70973
|
if (acceptanceContext.featureDir) {
|
|
70760
70974
|
const metaPath = path25.join(acceptanceContext.featureDir, "acceptance-meta.json");
|
|
70761
70975
|
try {
|
|
70762
|
-
await
|
|
70976
|
+
await unlink4(metaPath);
|
|
70763
70977
|
} catch {}
|
|
70764
70978
|
}
|
|
70765
70979
|
let implementationContext;
|
|
@@ -71226,16 +71440,16 @@ var init_acceptance_loop = __esm(() => {
|
|
|
71226
71440
|
});
|
|
71227
71441
|
|
|
71228
71442
|
// src/session/scratch-purge.ts
|
|
71229
|
-
import { mkdir as mkdir12, rename, rm } from "fs/promises";
|
|
71230
|
-
import { dirname as dirname15, join as
|
|
71443
|
+
import { mkdir as mkdir12, rename as rename2, rm } from "fs/promises";
|
|
71444
|
+
import { dirname as dirname15, join as join89 } from "path";
|
|
71231
71445
|
async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
|
|
71232
|
-
const sessionsDir =
|
|
71446
|
+
const sessionsDir = join89(projectDir, ".nax", "features", featureName, "sessions");
|
|
71233
71447
|
const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
|
|
71234
71448
|
const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
|
|
71235
71449
|
let purged = 0;
|
|
71236
71450
|
for (const sessionId of sessionIds) {
|
|
71237
|
-
const sessionDir =
|
|
71238
|
-
const descriptorPath =
|
|
71451
|
+
const sessionDir = join89(sessionsDir, sessionId);
|
|
71452
|
+
const descriptorPath = join89(sessionDir, "descriptor.json");
|
|
71239
71453
|
if (!await _scratchPurgeDeps.fileExists(descriptorPath))
|
|
71240
71454
|
continue;
|
|
71241
71455
|
let lastActivityAt;
|
|
@@ -71251,7 +71465,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
|
|
|
71251
71465
|
if (new Date(lastActivityAt).getTime() >= cutoffMs)
|
|
71252
71466
|
continue;
|
|
71253
71467
|
if (archiveInsteadOfDelete) {
|
|
71254
|
-
const archiveDest =
|
|
71468
|
+
const archiveDest = join89(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
|
|
71255
71469
|
await _scratchPurgeDeps.move(sessionDir, archiveDest);
|
|
71256
71470
|
} else {
|
|
71257
71471
|
await _scratchPurgeDeps.remove(sessionDir);
|
|
@@ -71279,7 +71493,7 @@ var init_scratch_purge = __esm(() => {
|
|
|
71279
71493
|
remove: (path26) => rm(path26, { recursive: true, force: true }),
|
|
71280
71494
|
move: async (src, dest) => {
|
|
71281
71495
|
await mkdir12(dirname15(dest), { recursive: true });
|
|
71282
|
-
await
|
|
71496
|
+
await rename2(src, dest);
|
|
71283
71497
|
},
|
|
71284
71498
|
now: () => Date.now()
|
|
71285
71499
|
};
|
|
@@ -71432,18 +71646,6 @@ ${rawOutput.slice(0, SYNTHETIC_FINDING_OUTPUT_LIMIT)}`,
|
|
|
71432
71646
|
}
|
|
71433
71647
|
];
|
|
71434
71648
|
}
|
|
71435
|
-
async function findResponsibleStory(testFile, workdir, passedStories) {
|
|
71436
|
-
const logger = getSafeLogger();
|
|
71437
|
-
for (let i = passedStories.length - 1;i >= 0; i--) {
|
|
71438
|
-
const story = passedStories[i];
|
|
71439
|
-
const hasCommits = await hasCommitsForStory(workdir, story.id, 50);
|
|
71440
|
-
if (hasCommits) {
|
|
71441
|
-
logger?.info("regression", `Mapped test to story ${story.id}`, { testFile });
|
|
71442
|
-
return story;
|
|
71443
|
-
}
|
|
71444
|
-
}
|
|
71445
|
-
return;
|
|
71446
|
-
}
|
|
71447
71649
|
function findResponsibleStoryByTransition(testFile, snapshots) {
|
|
71448
71650
|
const ordered = [...snapshots].sort((a, b) => a.completedAt.localeCompare(b.completedAt) || a.storyId.localeCompare(b.storyId));
|
|
71449
71651
|
for (const snap of ordered) {
|
|
@@ -71587,32 +71789,25 @@ async function runDeferredRegression(options) {
|
|
|
71587
71789
|
const affectedStoriesObjs = new Map;
|
|
71588
71790
|
if (testFilesInFailures.size === 0) {
|
|
71589
71791
|
logger?.warn("regression", "No test files found in failures (unmapped)");
|
|
71590
|
-
for (const story of passedStories) {
|
|
71591
|
-
affectedStories.add(story.id);
|
|
71592
|
-
affectedStoriesObjs.set(story.id, story);
|
|
71593
|
-
}
|
|
71594
71792
|
} else {
|
|
71595
71793
|
const testFilesArray = Array.from(testFilesInFailures);
|
|
71596
71794
|
const snapshots = options.storyMetrics ?? [];
|
|
71597
71795
|
const passedById = new Map(passedStories.map((s) => [s.id, s]));
|
|
71598
71796
|
for (const testFile of testFilesArray) {
|
|
71599
|
-
let responsibleStory;
|
|
71600
71797
|
const transitionId = findResponsibleStoryByTransition(testFile, snapshots);
|
|
71601
|
-
|
|
71602
|
-
|
|
71798
|
+
const responsibleStory = transitionId ? passedById.get(transitionId) : undefined;
|
|
71799
|
+
if (responsibleStory) {
|
|
71603
71800
|
logger?.info("regression", "Mapped test to story via gate transition", {
|
|
71604
71801
|
storyId: transitionId,
|
|
71605
71802
|
testFile
|
|
71606
71803
|
});
|
|
71607
|
-
}
|
|
71608
|
-
if (!responsibleStory) {
|
|
71609
|
-
responsibleStory = await findResponsibleStory(testFile, workdir, passedStories);
|
|
71610
|
-
}
|
|
71611
|
-
if (responsibleStory) {
|
|
71612
71804
|
affectedStories.add(responsibleStory.id);
|
|
71613
71805
|
affectedStoriesObjs.set(responsibleStory.id, responsibleStory);
|
|
71614
71806
|
} else {
|
|
71615
|
-
logger?.warn("regression", "Could not map test file to story", {
|
|
71807
|
+
logger?.warn("regression", "Could not safely map test file to a passed story", {
|
|
71808
|
+
testFile,
|
|
71809
|
+
...transitionId ? { transitionStoryId: transitionId } : {}
|
|
71810
|
+
});
|
|
71616
71811
|
}
|
|
71617
71812
|
}
|
|
71618
71813
|
}
|
|
@@ -71756,7 +71951,6 @@ var init_run_regression = __esm(() => {
|
|
|
71756
71951
|
init_pipeline();
|
|
71757
71952
|
init_prd();
|
|
71758
71953
|
init_test_runners();
|
|
71759
|
-
init_git();
|
|
71760
71954
|
init_verification();
|
|
71761
71955
|
init_run_regression_triage();
|
|
71762
71956
|
_regressionDeps = {
|
|
@@ -72316,7 +72510,8 @@ async function runCompletionPhase(options) {
|
|
|
72316
72510
|
deferredReviewStartedAt: options.deferredReviewStartedAt,
|
|
72317
72511
|
exitReason: options.exitReason,
|
|
72318
72512
|
runtime: options.runtime,
|
|
72319
|
-
abortSignal: options.abortSignal
|
|
72513
|
+
abortSignal: options.abortSignal,
|
|
72514
|
+
isSequential: options.parallel === undefined
|
|
72320
72515
|
});
|
|
72321
72516
|
const { durationMs, runCompletedAt, finalCounts, reportedTotal, pluginGateFailed } = completionResult;
|
|
72322
72517
|
if (options.featureDir) {
|
|
@@ -72528,12 +72723,12 @@ var init_ensure_package_dirs = __esm(() => {
|
|
|
72528
72723
|
|
|
72529
72724
|
// src/pipeline/subscribers/events-writer.ts
|
|
72530
72725
|
import { appendFile as appendFile5, mkdir as mkdir13 } from "fs/promises";
|
|
72531
|
-
import { basename as basename17, join as
|
|
72726
|
+
import { basename as basename17, join as join90 } from "path";
|
|
72532
72727
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
72533
72728
|
const logger = getSafeLogger();
|
|
72534
72729
|
const project = basename17(workdir);
|
|
72535
|
-
const eventsDir =
|
|
72536
|
-
const eventsFile =
|
|
72730
|
+
const eventsDir = join90(getEventsRootDir(), project);
|
|
72731
|
+
const eventsFile = join90(eventsDir, "events.jsonl");
|
|
72537
72732
|
let dirReady = false;
|
|
72538
72733
|
const write = (line) => {
|
|
72539
72734
|
return (async () => {
|
|
@@ -72714,12 +72909,12 @@ var init_interaction2 = __esm(() => {
|
|
|
72714
72909
|
|
|
72715
72910
|
// src/pipeline/subscribers/registry.ts
|
|
72716
72911
|
import { mkdir as mkdir14, writeFile as writeFile2 } from "fs/promises";
|
|
72717
|
-
import { basename as basename18, join as
|
|
72912
|
+
import { basename as basename18, join as join91 } from "path";
|
|
72718
72913
|
function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
72719
72914
|
const logger = getSafeLogger();
|
|
72720
72915
|
const project = basename18(workdir);
|
|
72721
|
-
const runDir =
|
|
72722
|
-
const metaFile =
|
|
72916
|
+
const runDir = join91(getRunsDir(), `${project}-${feature}-${runId}`);
|
|
72917
|
+
const metaFile = join91(runDir, "meta.json");
|
|
72723
72918
|
const unsub = bus.on("run:started", (_ev) => {
|
|
72724
72919
|
return (async () => {
|
|
72725
72920
|
try {
|
|
@@ -72729,8 +72924,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
|
72729
72924
|
project,
|
|
72730
72925
|
feature,
|
|
72731
72926
|
workdir,
|
|
72732
|
-
statusPath:
|
|
72733
|
-
eventsDir:
|
|
72927
|
+
statusPath: join91(outputDir, "features", feature, "status.json"),
|
|
72928
|
+
eventsDir: join91(outputDir, "features", feature, "runs"),
|
|
72734
72929
|
registeredAt: new Date().toISOString()
|
|
72735
72930
|
};
|
|
72736
72931
|
await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -72851,7 +73046,7 @@ var init_types11 = __esm(() => {
|
|
|
72851
73046
|
|
|
72852
73047
|
// src/worktree/dependencies.ts
|
|
72853
73048
|
import { existsSync as existsSync33 } from "fs";
|
|
72854
|
-
import { join as
|
|
73049
|
+
import { join as join92 } from "path";
|
|
72855
73050
|
async function prepareWorktreeDependencies(options) {
|
|
72856
73051
|
const mode = options.config.execution.worktreeDependencies.mode;
|
|
72857
73052
|
const resolvedCwd = resolveDependencyCwd(options);
|
|
@@ -72865,7 +73060,7 @@ async function prepareWorktreeDependencies(options) {
|
|
|
72865
73060
|
}
|
|
72866
73061
|
}
|
|
72867
73062
|
function resolveDependencyCwd(options) {
|
|
72868
|
-
return options.storyWorkdir ?
|
|
73063
|
+
return options.storyWorkdir ? join92(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
|
|
72869
73064
|
}
|
|
72870
73065
|
function resolveInheritedDependencies(options, resolvedCwd) {
|
|
72871
73066
|
if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
|
|
@@ -72875,7 +73070,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
|
|
|
72875
73070
|
}
|
|
72876
73071
|
function hasDependencyManifests(worktreeRoot, resolvedCwd) {
|
|
72877
73072
|
const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
|
|
72878
|
-
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(
|
|
73073
|
+
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join92(directory, filename))));
|
|
72879
73074
|
}
|
|
72880
73075
|
async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
|
|
72881
73076
|
const setupCommand2 = config2.execution.worktreeDependencies.setupCommand;
|
|
@@ -72932,20 +73127,15 @@ var init_dependencies = __esm(() => {
|
|
|
72932
73127
|
});
|
|
72933
73128
|
|
|
72934
73129
|
// src/worktree/manager.ts
|
|
72935
|
-
var exports_manager = {};
|
|
72936
|
-
__export(exports_manager, {
|
|
72937
|
-
_managerDeps: () => _managerDeps,
|
|
72938
|
-
WorktreeManager: () => WorktreeManager
|
|
72939
|
-
});
|
|
72940
73130
|
import { existsSync as existsSync34, symlinkSync } from "fs";
|
|
72941
73131
|
import { mkdir as mkdir15 } from "fs/promises";
|
|
72942
|
-
import { join as
|
|
73132
|
+
import { join as join93 } from "path";
|
|
72943
73133
|
|
|
72944
73134
|
class WorktreeManager {
|
|
72945
73135
|
async ensureGitExcludes(projectRoot) {
|
|
72946
73136
|
const logger = getSafeLogger();
|
|
72947
|
-
const infoDir =
|
|
72948
|
-
const excludePath =
|
|
73137
|
+
const infoDir = join93(projectRoot, ".git", "info");
|
|
73138
|
+
const excludePath = join93(infoDir, "exclude");
|
|
72949
73139
|
try {
|
|
72950
73140
|
await mkdir15(infoDir, { recursive: true });
|
|
72951
73141
|
let existing = "";
|
|
@@ -72972,7 +73162,7 @@ ${missing.join(`
|
|
|
72972
73162
|
}
|
|
72973
73163
|
async create(projectRoot, storyId) {
|
|
72974
73164
|
validateStoryId(storyId);
|
|
72975
|
-
const worktreePath =
|
|
73165
|
+
const worktreePath = join93(projectRoot, ".nax-wt", storyId);
|
|
72976
73166
|
const branchName = `nax/${storyId}`;
|
|
72977
73167
|
try {
|
|
72978
73168
|
const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
|
|
@@ -73033,9 +73223,9 @@ ${missing.join(`
|
|
|
73033
73223
|
projectRoot
|
|
73034
73224
|
});
|
|
73035
73225
|
}
|
|
73036
|
-
const envSource =
|
|
73226
|
+
const envSource = join93(projectRoot, ".env");
|
|
73037
73227
|
if (existsSync34(envSource)) {
|
|
73038
|
-
const envTarget =
|
|
73228
|
+
const envTarget = join93(worktreePath, ".env");
|
|
73039
73229
|
try {
|
|
73040
73230
|
symlinkSync(envSource, envTarget, "file");
|
|
73041
73231
|
} catch (error48) {
|
|
@@ -73051,7 +73241,7 @@ ${missing.join(`
|
|
|
73051
73241
|
}
|
|
73052
73242
|
async remove(projectRoot, storyId) {
|
|
73053
73243
|
validateStoryId(storyId);
|
|
73054
|
-
const worktreePath =
|
|
73244
|
+
const worktreePath = join93(projectRoot, ".nax-wt", storyId);
|
|
73055
73245
|
const branchName = `nax/${storyId}`;
|
|
73056
73246
|
try {
|
|
73057
73247
|
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -73222,20 +73412,30 @@ var init_dry_run = __esm(() => {
|
|
|
73222
73412
|
});
|
|
73223
73413
|
|
|
73224
73414
|
// src/worktree/merge.ts
|
|
73225
|
-
var exports_merge = {};
|
|
73226
|
-
__export(exports_merge, {
|
|
73227
|
-
_mergeDeps: () => _mergeDeps,
|
|
73228
|
-
MergeEngine: () => MergeEngine
|
|
73229
|
-
});
|
|
73230
|
-
|
|
73231
73415
|
class MergeEngine {
|
|
73232
73416
|
worktreeManager;
|
|
73233
73417
|
constructor(worktreeManager) {
|
|
73234
73418
|
this.worktreeManager = worktreeManager;
|
|
73235
73419
|
}
|
|
73420
|
+
async isMidMerge(projectRoot) {
|
|
73421
|
+
const proc = _mergeDeps.spawn(["git", "rev-parse", "-q", "--verify", "MERGE_HEAD"], {
|
|
73422
|
+
cwd: projectRoot,
|
|
73423
|
+
stdout: "pipe",
|
|
73424
|
+
stderr: "pipe"
|
|
73425
|
+
});
|
|
73426
|
+
return await proc.exited === 0;
|
|
73427
|
+
}
|
|
73236
73428
|
async merge(projectRoot, storyId) {
|
|
73237
73429
|
const branchName = `nax/${storyId}`;
|
|
73238
73430
|
try {
|
|
73431
|
+
if (await this.isMidMerge(projectRoot)) {
|
|
73432
|
+
const error48 = `Repository has an unresolved merge in progress; refusing to merge ${branchName}`;
|
|
73433
|
+
getSafeLogger()?.error("worktree", "Refusing to merge into a mid-merge repository", {
|
|
73434
|
+
storyId,
|
|
73435
|
+
projectRoot
|
|
73436
|
+
});
|
|
73437
|
+
return { success: false, failureKind: "error", error: error48 };
|
|
73438
|
+
}
|
|
73239
73439
|
const mergeProc = _mergeDeps.spawn(["git", "merge", "--no-ff", branchName, "-m", `Merge branch '${branchName}'`], {
|
|
73240
73440
|
cwd: projectRoot,
|
|
73241
73441
|
stdout: "pipe",
|
|
@@ -73257,24 +73457,38 @@ class MergeEngine {
|
|
|
73257
73457
|
}
|
|
73258
73458
|
return { success: true };
|
|
73259
73459
|
}
|
|
73260
|
-
|
|
73261
|
-
${stderr}
|
|
73262
|
-
if (output.includes("CONFLICT") || output.includes("conflict") || output.includes("Automatic merge failed")) {
|
|
73263
|
-
const conflictFiles = await this.getConflictFiles(projectRoot);
|
|
73264
|
-
await this.abortMerge(projectRoot);
|
|
73265
|
-
return {
|
|
73266
|
-
success: false,
|
|
73267
|
-
conflictFiles
|
|
73268
|
-
};
|
|
73269
|
-
}
|
|
73270
|
-
throw new Error(`Merge failed: ${stderr || stdout || "unknown error"}`);
|
|
73460
|
+
return await this.classifyMergeFailure(projectRoot, storyId, `${stdout}
|
|
73461
|
+
${stderr}`);
|
|
73271
73462
|
} catch (error48) {
|
|
73272
|
-
|
|
73273
|
-
|
|
73274
|
-
|
|
73275
|
-
|
|
73463
|
+
getSafeLogger()?.error("worktree", "Merge failed before git could report", {
|
|
73464
|
+
storyId,
|
|
73465
|
+
error: errorMessage(error48)
|
|
73466
|
+
});
|
|
73467
|
+
return { success: false, failureKind: "error", error: errorMessage(error48) };
|
|
73276
73468
|
}
|
|
73277
73469
|
}
|
|
73470
|
+
async classifyMergeFailure(projectRoot, storyId, output) {
|
|
73471
|
+
const logger = getSafeLogger();
|
|
73472
|
+
const conflictFiles = await this.getConflictFiles(projectRoot);
|
|
73473
|
+
const midMerge = await this.isMidMerge(projectRoot);
|
|
73474
|
+
if (!midMerge && conflictFiles.length === 0) {
|
|
73475
|
+
const error48 = output.trim() || "unknown error";
|
|
73476
|
+
logger?.error("worktree", "Merge failed for a non-conflict reason", {
|
|
73477
|
+
storyId,
|
|
73478
|
+
error: error48
|
|
73479
|
+
});
|
|
73480
|
+
return { success: false, failureKind: "error", error: error48 };
|
|
73481
|
+
}
|
|
73482
|
+
if (!await this.abortMerge(projectRoot)) {
|
|
73483
|
+
const error48 = `Merge conflict in ${storyId} could not be aborted; repository left mid-merge`;
|
|
73484
|
+
logger?.error("worktree", "git merge --abort failed \u2014 repository left mid-merge", {
|
|
73485
|
+
storyId,
|
|
73486
|
+
conflictFiles
|
|
73487
|
+
});
|
|
73488
|
+
return { success: false, failureKind: "error", conflictFiles, error: error48 };
|
|
73489
|
+
}
|
|
73490
|
+
return { success: false, failureKind: "conflict", conflictFiles };
|
|
73491
|
+
}
|
|
73278
73492
|
async mergeAll(projectRoot, storyIds, dependencies) {
|
|
73279
73493
|
const orderedStories = this.topologicalSort(storyIds, dependencies);
|
|
73280
73494
|
const results = [];
|
|
@@ -73286,13 +73500,15 @@ ${stderr}`;
|
|
|
73286
73500
|
results.push({
|
|
73287
73501
|
success: false,
|
|
73288
73502
|
storyId,
|
|
73289
|
-
conflictFiles: []
|
|
73503
|
+
conflictFiles: [],
|
|
73504
|
+
failureKind: "error",
|
|
73505
|
+
error: `Skipped: depends on a story that failed to merge (${deps.filter((d) => failedStories.has(d)).join(", ")})`
|
|
73290
73506
|
});
|
|
73291
73507
|
failedStories.add(storyId);
|
|
73292
73508
|
continue;
|
|
73293
73509
|
}
|
|
73294
73510
|
let result = await this.merge(projectRoot, storyId);
|
|
73295
|
-
if (
|
|
73511
|
+
if (result.failureKind === "conflict") {
|
|
73296
73512
|
try {
|
|
73297
73513
|
await this.rebaseWorktree(projectRoot, storyId);
|
|
73298
73514
|
result = await this.merge(projectRoot, storyId);
|
|
@@ -73301,7 +73517,9 @@ ${stderr}`;
|
|
|
73301
73517
|
success: false,
|
|
73302
73518
|
storyId,
|
|
73303
73519
|
conflictFiles: result.conflictFiles,
|
|
73304
|
-
retryCount: 1
|
|
73520
|
+
retryCount: 1,
|
|
73521
|
+
failureKind: result.failureKind ?? "error",
|
|
73522
|
+
error: result.error
|
|
73305
73523
|
});
|
|
73306
73524
|
failedStories.add(storyId);
|
|
73307
73525
|
continue;
|
|
@@ -73316,7 +73534,9 @@ ${stderr}`;
|
|
|
73316
73534
|
success: false,
|
|
73317
73535
|
storyId,
|
|
73318
73536
|
conflictFiles: result.conflictFiles,
|
|
73319
|
-
retryCount: 1
|
|
73537
|
+
retryCount: 1,
|
|
73538
|
+
failureKind: "error",
|
|
73539
|
+
error: errorMessage(error48)
|
|
73320
73540
|
});
|
|
73321
73541
|
failedStories.add(storyId);
|
|
73322
73542
|
}
|
|
@@ -73330,7 +73550,9 @@ ${stderr}`;
|
|
|
73330
73550
|
results.push({
|
|
73331
73551
|
success: false,
|
|
73332
73552
|
storyId,
|
|
73333
|
-
retryCount: 0
|
|
73553
|
+
retryCount: 0,
|
|
73554
|
+
failureKind: result.failureKind ?? "error",
|
|
73555
|
+
error: result.error
|
|
73334
73556
|
});
|
|
73335
73557
|
failedStories.add(storyId);
|
|
73336
73558
|
}
|
|
@@ -73430,12 +73652,20 @@ ${stderr}`;
|
|
|
73430
73652
|
stdout: "pipe",
|
|
73431
73653
|
stderr: "pipe"
|
|
73432
73654
|
});
|
|
73433
|
-
await proc.exited;
|
|
73655
|
+
const [exitCode, stderr] = await Promise.all([proc.exited, new Response(proc.stderr).text()]);
|
|
73656
|
+
if (exitCode !== 0) {
|
|
73657
|
+
getSafeLogger()?.error("worktree", "Failed to abort merge", {
|
|
73658
|
+
exitCode,
|
|
73659
|
+
stderr: stderr.trim()
|
|
73660
|
+
});
|
|
73661
|
+
return false;
|
|
73662
|
+
}
|
|
73663
|
+
return true;
|
|
73434
73664
|
} catch (error48) {
|
|
73435
|
-
|
|
73436
|
-
logger?.warn("worktree", "Failed to abort merge", {
|
|
73665
|
+
getSafeLogger()?.error("worktree", "Failed to abort merge", {
|
|
73437
73666
|
error: errorMessage(error48)
|
|
73438
73667
|
});
|
|
73668
|
+
return false;
|
|
73439
73669
|
}
|
|
73440
73670
|
}
|
|
73441
73671
|
}
|
|
@@ -73448,6 +73678,21 @@ var init_merge = __esm(() => {
|
|
|
73448
73678
|
};
|
|
73449
73679
|
});
|
|
73450
73680
|
|
|
73681
|
+
// src/worktree/index.ts
|
|
73682
|
+
var exports_worktree = {};
|
|
73683
|
+
__export(exports_worktree, {
|
|
73684
|
+
prepareWorktreeDependencies: () => prepareWorktreeDependencies,
|
|
73685
|
+
_mergeDeps: () => _mergeDeps,
|
|
73686
|
+
WorktreeManager: () => WorktreeManager,
|
|
73687
|
+
WorktreeDependencyPreparationError: () => WorktreeDependencyPreparationError,
|
|
73688
|
+
MergeEngine: () => MergeEngine
|
|
73689
|
+
});
|
|
73690
|
+
var init_worktree = __esm(() => {
|
|
73691
|
+
init_manager3();
|
|
73692
|
+
init_merge();
|
|
73693
|
+
init_dependencies();
|
|
73694
|
+
});
|
|
73695
|
+
|
|
73451
73696
|
// src/execution/escalation/escalation.ts
|
|
73452
73697
|
function escalateTier(currentRung, tierOrder) {
|
|
73453
73698
|
const i = currentRung.agent !== undefined ? tierOrder.findIndex((t) => t.tier === currentRung.tier && t.agent === currentRung.agent) : tierOrder.findIndex((t) => t.tier === currentRung.tier);
|
|
@@ -73659,6 +73904,7 @@ function resolveMaxAttemptsOutcome(failureCategory) {
|
|
|
73659
73904
|
case "verifier-rejected":
|
|
73660
73905
|
case "greenfield-no-tests":
|
|
73661
73906
|
case "no-tests-authored":
|
|
73907
|
+
case "test-incorrect":
|
|
73662
73908
|
return "pause";
|
|
73663
73909
|
case "runtime-crash":
|
|
73664
73910
|
return "pause";
|
|
@@ -73809,6 +74055,7 @@ var init_escalation = __esm(() => {
|
|
|
73809
74055
|
// src/execution/merge-conflict-rectify.ts
|
|
73810
74056
|
var exports_merge_conflict_rectify = {};
|
|
73811
74057
|
__export(exports_merge_conflict_rectify, {
|
|
74058
|
+
rectifyMergeFailure: () => rectifyMergeFailure,
|
|
73812
74059
|
rectifyConflictedStory: () => rectifyConflictedStory
|
|
73813
74060
|
});
|
|
73814
74061
|
import path28 from "path";
|
|
@@ -73822,13 +74069,22 @@ async function closeStaleAcpSession(worktreePath, sessionName) {
|
|
|
73822
74069
|
await proc.exited;
|
|
73823
74070
|
} catch {}
|
|
73824
74071
|
}
|
|
74072
|
+
function rectifyMergeFailure(storyId, cost, mergeResult) {
|
|
74073
|
+
return {
|
|
74074
|
+
success: false,
|
|
74075
|
+
storyId,
|
|
74076
|
+
cost,
|
|
74077
|
+
finalConflict: mergeResult?.failureKind !== "error",
|
|
74078
|
+
conflictFiles: mergeResult?.conflictFiles ?? []
|
|
74079
|
+
};
|
|
74080
|
+
}
|
|
73825
74081
|
async function rectifyConflictedStory(options) {
|
|
73826
74082
|
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter, agentGetFn } = options;
|
|
73827
74083
|
const logger = getSafeLogger();
|
|
73828
74084
|
logger?.info("parallel", "Rectifying story on updated base", { storyId, attempt: "rectification" });
|
|
73829
74085
|
try {
|
|
73830
|
-
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (
|
|
73831
|
-
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (
|
|
74086
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_worktree(), exports_worktree));
|
|
74087
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_worktree(), exports_worktree));
|
|
73832
74088
|
const { runPipeline: runPipeline2 } = await Promise.resolve().then(() => (init_runner4(), exports_runner));
|
|
73833
74089
|
const { defaultPipeline: defaultPipeline2 } = await Promise.resolve().then(() => (init_stages(), exports_stages));
|
|
73834
74090
|
const { routeTask: routeTask2 } = await Promise.resolve().then(() => (init_routing(), exports_routing));
|
|
@@ -73880,9 +74136,12 @@ async function rectifyConflictedStory(options) {
|
|
|
73880
74136
|
const mergeResults2 = await mergeEngine.mergeAll(workdir, [storyId], { [storyId]: [] });
|
|
73881
74137
|
const mergeResult = mergeResults2[0];
|
|
73882
74138
|
if (!mergeResult || !mergeResult.success) {
|
|
73883
|
-
|
|
73884
|
-
|
|
73885
|
-
|
|
74139
|
+
logger?.info("parallel", "Rectification failed - preserving worktree", {
|
|
74140
|
+
storyId,
|
|
74141
|
+
failureKind: mergeResult?.failureKind,
|
|
74142
|
+
error: mergeResult?.error
|
|
74143
|
+
});
|
|
74144
|
+
return rectifyMergeFailure(storyId, cost, mergeResult);
|
|
73886
74145
|
}
|
|
73887
74146
|
logger?.info("parallel", "Rectification succeeded - story merged", {
|
|
73888
74147
|
storyId,
|
|
@@ -73903,10 +74162,10 @@ var init_merge_conflict_rectify = __esm(() => {
|
|
|
73903
74162
|
});
|
|
73904
74163
|
|
|
73905
74164
|
// src/execution/pipeline-result-handler.ts
|
|
73906
|
-
import { join as
|
|
74165
|
+
import { join as join94 } from "path";
|
|
73907
74166
|
async function removeWorktreeDirectory(projectRoot, storyId) {
|
|
73908
74167
|
const logger = getSafeLogger();
|
|
73909
|
-
const worktreePath =
|
|
74168
|
+
const worktreePath = join94(projectRoot, ".nax-wt", storyId);
|
|
73910
74169
|
try {
|
|
73911
74170
|
const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
73912
74171
|
cwd: projectRoot,
|
|
@@ -73922,6 +74181,23 @@ async function removeWorktreeDirectory(projectRoot, storyId) {
|
|
|
73922
74181
|
});
|
|
73923
74182
|
}
|
|
73924
74183
|
}
|
|
74184
|
+
async function failStoryAfterMerge(ctx, prd, reason) {
|
|
74185
|
+
markStoryFailed(prd, ctx.story.id, undefined, undefined, ctx.statusWriter);
|
|
74186
|
+
await savePRD(prd, ctx.prdPath);
|
|
74187
|
+
if (ctx.featureDir) {
|
|
74188
|
+
await appendProgress(ctx.featureDir, ctx.story.id, "failed", `${ctx.story.title} \u2014 ${reason}`);
|
|
74189
|
+
}
|
|
74190
|
+
pipelineEventBus.emit({
|
|
74191
|
+
type: "story:failed",
|
|
74192
|
+
storyId: ctx.story.id,
|
|
74193
|
+
story: { id: ctx.story.id, title: ctx.story.title, status: ctx.story.status, attempts: ctx.story.attempts },
|
|
74194
|
+
reason,
|
|
74195
|
+
countsTowardEscalation: false,
|
|
74196
|
+
feature: ctx.feature,
|
|
74197
|
+
attempts: ctx.story.attempts,
|
|
74198
|
+
cost: ctx.runtime.costAggregator.byStory()[ctx.story.id]?.totalCostUsd ?? ctx.totalCost
|
|
74199
|
+
});
|
|
74200
|
+
}
|
|
73925
74201
|
function filterOutputFiles(files) {
|
|
73926
74202
|
const NOISE = [
|
|
73927
74203
|
/\.test\.(ts|js|tsx|jsx)$/,
|
|
@@ -73974,6 +74250,16 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
73974
74250
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
73975
74251
|
const story = ctx.story;
|
|
73976
74252
|
const mergeResult = await _resultHandlerDeps.mergeEngine.merge(ctx.workdir, story.id);
|
|
74253
|
+
if (!mergeResult.success && mergeResult.failureKind === "error") {
|
|
74254
|
+
const reason = `Merge failed for a non-conflict reason: ${mergeResult.error ?? "unknown error"}`;
|
|
74255
|
+
logger?.error("worktree", "Merge failed for a non-conflict reason \u2014 marking story as failed", {
|
|
74256
|
+
storyId: story.id,
|
|
74257
|
+
error: mergeResult.error
|
|
74258
|
+
});
|
|
74259
|
+
await failStoryAfterMerge(ctx, prd, reason);
|
|
74260
|
+
await removeWorktreeDirectory(ctx.workdir, story.id);
|
|
74261
|
+
return { storiesCompletedDelta: 0, costDelta, prd, prdDirty: true };
|
|
74262
|
+
}
|
|
73977
74263
|
if (!mergeResult.success) {
|
|
73978
74264
|
const { rectifyConflictedStory: rectifyConflictedStory2 } = await Promise.resolve().then(() => (init_merge_conflict_rectify(), exports_merge_conflict_rectify));
|
|
73979
74265
|
const rectifyResult = await rectifyConflictedStory2({
|
|
@@ -73995,7 +74281,9 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
73995
74281
|
storyId: story.id,
|
|
73996
74282
|
conflictFiles: mergeResult.conflictFiles
|
|
73997
74283
|
});
|
|
73998
|
-
|
|
74284
|
+
const files = (mergeResult.conflictFiles ?? []).join(", ");
|
|
74285
|
+
await failStoryAfterMerge(ctx, prd, `Merge conflict could not be rectified${files ? ` (${files})` : ""} \u2014 the branch did not land`);
|
|
74286
|
+
return { storiesCompletedDelta: 0, costDelta, prd, prdDirty: true };
|
|
73999
74287
|
}
|
|
74000
74288
|
}
|
|
74001
74289
|
logger?.info("worktree", "Merged story to main", { storyId: story.id });
|
|
@@ -74110,8 +74398,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
74110
74398
|
init_prd();
|
|
74111
74399
|
init_bun_deps();
|
|
74112
74400
|
init_git();
|
|
74113
|
-
|
|
74114
|
-
init_merge();
|
|
74401
|
+
init_worktree();
|
|
74115
74402
|
init_escalation();
|
|
74116
74403
|
init_progress();
|
|
74117
74404
|
_resultHandlerDeps = {
|
|
@@ -74123,7 +74410,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
74123
74410
|
|
|
74124
74411
|
// src/execution/iteration-runner.ts
|
|
74125
74412
|
import { existsSync as existsSync35 } from "fs";
|
|
74126
|
-
import { join as
|
|
74413
|
+
import { join as join95 } from "path";
|
|
74127
74414
|
function releaseHeavyPipelineContext(ctx) {
|
|
74128
74415
|
ctx.agentResult = undefined;
|
|
74129
74416
|
ctx.prompt = undefined;
|
|
@@ -74162,7 +74449,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost2, allStor
|
|
|
74162
74449
|
const storyStartTime = Date.now();
|
|
74163
74450
|
let effectiveWorkdir = ctx.workdir;
|
|
74164
74451
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
74165
|
-
const worktreePath =
|
|
74452
|
+
const worktreePath = join95(ctx.workdir, ".nax-wt", story.id);
|
|
74166
74453
|
const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
|
|
74167
74454
|
if (!worktreeExists) {
|
|
74168
74455
|
await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
|
|
@@ -74182,7 +74469,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost2, allStor
|
|
|
74182
74469
|
}
|
|
74183
74470
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
74184
74471
|
const profileOverride = profileOverrideFromConfig(ctx.config);
|
|
74185
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
74472
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join95(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
|
|
74186
74473
|
let dependencyContext;
|
|
74187
74474
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
74188
74475
|
try {
|
|
@@ -74209,7 +74496,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost2, allStor
|
|
|
74209
74496
|
};
|
|
74210
74497
|
}
|
|
74211
74498
|
}
|
|
74212
|
-
const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ?
|
|
74499
|
+
const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join95(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join95(ctx.workdir, story.workdir) : ctx.workdir;
|
|
74213
74500
|
const pipelineContext = {
|
|
74214
74501
|
config: effectiveConfig,
|
|
74215
74502
|
rootConfig: ctx.config,
|
|
@@ -74408,7 +74695,7 @@ __export(exports_parallel_worker, {
|
|
|
74408
74695
|
buildWorktreePipelineContext: () => buildWorktreePipelineContext,
|
|
74409
74696
|
_parallelWorkerDeps: () => _parallelWorkerDeps
|
|
74410
74697
|
});
|
|
74411
|
-
import { join as
|
|
74698
|
+
import { join as join96 } from "path";
|
|
74412
74699
|
function buildWorktreePipelineContext(base, _story) {
|
|
74413
74700
|
return { ...base, prd: structuredClone(base.prd) };
|
|
74414
74701
|
}
|
|
@@ -74431,7 +74718,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
|
|
|
74431
74718
|
story,
|
|
74432
74719
|
stories: [story],
|
|
74433
74720
|
projectDir: context.projectDir,
|
|
74434
|
-
workdir: dependencyContext.cwd ?? (story.workdir ?
|
|
74721
|
+
workdir: dependencyContext.cwd ?? (story.workdir ? join96(worktreePath, story.workdir) : worktreePath),
|
|
74435
74722
|
worktreeDependencyContext: dependencyContext,
|
|
74436
74723
|
routing,
|
|
74437
74724
|
storyGitRef: storyGitRef ?? undefined
|
|
@@ -74644,6 +74931,12 @@ async function runParallelBatch(options) {
|
|
|
74644
74931
|
logger?.info("parallel-batch", "Story merged successfully", {
|
|
74645
74932
|
storyId: mergeResult.storyId
|
|
74646
74933
|
});
|
|
74934
|
+
} else if (mergeResult.failureKind === "error") {
|
|
74935
|
+
workerResult.failed.push({ story, error: mergeResult.error ?? "merge failed" });
|
|
74936
|
+
logger?.error("parallel-batch", "Merge failed for a non-conflict reason", {
|
|
74937
|
+
storyId: mergeResult.storyId,
|
|
74938
|
+
error: mergeResult.error
|
|
74939
|
+
});
|
|
74647
74940
|
} else {
|
|
74648
74941
|
workerResult.mergeConflicts.push({
|
|
74649
74942
|
storyId: mergeResult.storyId,
|
|
@@ -74731,11 +75024,11 @@ var init_parallel_batch = __esm(() => {
|
|
|
74731
75024
|
return executeParallelBatch2(_stories, _projectRoot, _config, _context, _worktreePaths, _dependencyContexts, _maxConcurrency, _eventEmitter, _storyEffectiveConfigs);
|
|
74732
75025
|
},
|
|
74733
75026
|
createWorktreeManager: async () => {
|
|
74734
|
-
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (
|
|
75027
|
+
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_worktree(), exports_worktree));
|
|
74735
75028
|
return new WorktreeManager2;
|
|
74736
75029
|
},
|
|
74737
75030
|
createMergeEngine: async (worktreeManager) => {
|
|
74738
|
-
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (
|
|
75031
|
+
const { MergeEngine: MergeEngine2 } = await Promise.resolve().then(() => (init_worktree(), exports_worktree));
|
|
74739
75032
|
return new MergeEngine2(worktreeManager);
|
|
74740
75033
|
},
|
|
74741
75034
|
rectifyConflictedStory: async (opts) => {
|
|
@@ -75401,8 +75694,8 @@ var init_runner_execution = __esm(() => {
|
|
|
75401
75694
|
});
|
|
75402
75695
|
|
|
75403
75696
|
// src/execution/status-file.ts
|
|
75404
|
-
import { rename as
|
|
75405
|
-
import { resolve as
|
|
75697
|
+
import { rename as rename3, unlink as unlink4 } from "fs/promises";
|
|
75698
|
+
import { resolve as resolve22 } from "path";
|
|
75406
75699
|
function countProgress(prd) {
|
|
75407
75700
|
const stories = prd.userStories;
|
|
75408
75701
|
const passed = stories.filter((s) => s.status === "passed").length;
|
|
@@ -75447,21 +75740,21 @@ function buildStatusSnapshot(state) {
|
|
|
75447
75740
|
return snapshot;
|
|
75448
75741
|
}
|
|
75449
75742
|
async function writeStatusFile(filePath, status) {
|
|
75450
|
-
const resolvedPath =
|
|
75743
|
+
const resolvedPath = resolve22(filePath);
|
|
75451
75744
|
if (filePath.includes("../") || filePath.includes("..\\")) {
|
|
75452
75745
|
throw new Error("Invalid status file path: path traversal detected");
|
|
75453
75746
|
}
|
|
75454
75747
|
const tmpPath = `${resolvedPath}.tmp`;
|
|
75455
75748
|
try {
|
|
75456
|
-
await
|
|
75749
|
+
await unlink4(tmpPath);
|
|
75457
75750
|
} catch {}
|
|
75458
75751
|
await Bun.write(tmpPath, JSON.stringify(status, null, 2));
|
|
75459
|
-
await
|
|
75752
|
+
await rename3(tmpPath, resolvedPath);
|
|
75460
75753
|
}
|
|
75461
75754
|
var init_status_file = () => {};
|
|
75462
75755
|
|
|
75463
75756
|
// src/execution/status-writer.ts
|
|
75464
|
-
import { join as
|
|
75757
|
+
import { join as join97 } from "path";
|
|
75465
75758
|
|
|
75466
75759
|
class StatusWriter {
|
|
75467
75760
|
statusFile;
|
|
@@ -75580,7 +75873,7 @@ class StatusWriter {
|
|
|
75580
75873
|
if (!this._prd)
|
|
75581
75874
|
return;
|
|
75582
75875
|
const safeLogger = getSafeLogger();
|
|
75583
|
-
const featureStatusPath =
|
|
75876
|
+
const featureStatusPath = join97(featureDir, "status.json");
|
|
75584
75877
|
const write = async () => {
|
|
75585
75878
|
try {
|
|
75586
75879
|
const base = this.getSnapshot(totalCost2, iterations);
|
|
@@ -75612,7 +75905,7 @@ __export(exports_migrate, {
|
|
|
75612
75905
|
detectGeneratedContent: () => detectGeneratedContent
|
|
75613
75906
|
});
|
|
75614
75907
|
import { existsSync as existsSync36 } from "fs";
|
|
75615
|
-
import { mkdir as mkdir16, readdir as readdir5, rename as
|
|
75908
|
+
import { mkdir as mkdir16, readdir as readdir5, rename as rename4 } from "fs/promises";
|
|
75616
75909
|
import path30 from "path";
|
|
75617
75910
|
async function detectGeneratedContent(naxDir) {
|
|
75618
75911
|
if (!existsSync36(naxDir))
|
|
@@ -75701,7 +75994,7 @@ async function migrateCommand(options) {
|
|
|
75701
75994
|
const archiveBase = path30.join(globalConfigDir(), "_archive");
|
|
75702
75995
|
const archiveDest = path30.join(archiveBase, `${options.reclaim}-${Date.now()}`);
|
|
75703
75996
|
await mkdir16(archiveBase, { recursive: true });
|
|
75704
|
-
await
|
|
75997
|
+
await rename4(src, archiveDest);
|
|
75705
75998
|
logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
|
|
75706
75999
|
return;
|
|
75707
76000
|
}
|
|
@@ -75780,7 +76073,7 @@ async function migrateCommand(options) {
|
|
|
75780
76073
|
Remove the destination or run nax migrate --dry-run to inspect.`, "MIGRATE_CONFLICT", { stage: "migrate", src: candidate.srcPath, dest });
|
|
75781
76074
|
}
|
|
75782
76075
|
try {
|
|
75783
|
-
await
|
|
76076
|
+
await rename4(candidate.srcPath, dest);
|
|
75784
76077
|
} catch (err) {
|
|
75785
76078
|
const isXdev = err instanceof Error && "code" in err && err.code === "EXDEV";
|
|
75786
76079
|
if (isXdev) {
|
|
@@ -76024,7 +76317,7 @@ __export(exports_run_initialization, {
|
|
|
76024
76317
|
initializeRun: () => initializeRun,
|
|
76025
76318
|
_reconcileDeps: () => _reconcileDeps
|
|
76026
76319
|
});
|
|
76027
|
-
import { join as
|
|
76320
|
+
import { join as join98 } from "path";
|
|
76028
76321
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
76029
76322
|
const logger = getSafeLogger();
|
|
76030
76323
|
let reconciledCount = 0;
|
|
@@ -76041,7 +76334,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
76041
76334
|
});
|
|
76042
76335
|
continue;
|
|
76043
76336
|
}
|
|
76044
|
-
const effectiveWorkdir = story.workdir ?
|
|
76337
|
+
const effectiveWorkdir = story.workdir ? join98(workdir, story.workdir) : workdir;
|
|
76045
76338
|
try {
|
|
76046
76339
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
76047
76340
|
if (!reviewResult.success) {
|
|
@@ -76857,6 +77150,7 @@ async function run(options) {
|
|
|
76857
77150
|
hooks,
|
|
76858
77151
|
feature,
|
|
76859
77152
|
workdir,
|
|
77153
|
+
parallel,
|
|
76860
77154
|
prdPath,
|
|
76861
77155
|
statusFile,
|
|
76862
77156
|
logFilePath,
|
|
@@ -77446,14 +77740,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
77446
77740
|
prevActScopeDepth !== actScopeDepth - 1 && console.error("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ");
|
|
77447
77741
|
actScopeDepth = prevActScopeDepth;
|
|
77448
77742
|
}
|
|
77449
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
77743
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve23, reject) {
|
|
77450
77744
|
var queue = ReactSharedInternals.actQueue;
|
|
77451
77745
|
if (queue !== null)
|
|
77452
77746
|
if (queue.length !== 0)
|
|
77453
77747
|
try {
|
|
77454
77748
|
flushActQueue(queue);
|
|
77455
77749
|
enqueueTask(function() {
|
|
77456
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
77750
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve23, reject);
|
|
77457
77751
|
});
|
|
77458
77752
|
return;
|
|
77459
77753
|
} catch (error48) {
|
|
@@ -77461,7 +77755,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
77461
77755
|
}
|
|
77462
77756
|
else
|
|
77463
77757
|
ReactSharedInternals.actQueue = null;
|
|
77464
|
-
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) :
|
|
77758
|
+
0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve23(returnValue);
|
|
77465
77759
|
}
|
|
77466
77760
|
function flushActQueue(queue) {
|
|
77467
77761
|
if (!isFlushing) {
|
|
@@ -77637,14 +77931,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
77637
77931
|
didAwaitActCall || didWarnNoAwaitAct || (didWarnNoAwaitAct = true, console.error("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"));
|
|
77638
77932
|
});
|
|
77639
77933
|
return {
|
|
77640
|
-
then: function(
|
|
77934
|
+
then: function(resolve23, reject) {
|
|
77641
77935
|
didAwaitActCall = true;
|
|
77642
77936
|
thenable.then(function(returnValue) {
|
|
77643
77937
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
77644
77938
|
if (prevActScopeDepth === 0) {
|
|
77645
77939
|
try {
|
|
77646
77940
|
flushActQueue(queue), enqueueTask(function() {
|
|
77647
|
-
return recursivelyFlushAsyncActWork(returnValue,
|
|
77941
|
+
return recursivelyFlushAsyncActWork(returnValue, resolve23, reject);
|
|
77648
77942
|
});
|
|
77649
77943
|
} catch (error$0) {
|
|
77650
77944
|
ReactSharedInternals.thrownErrors.push(error$0);
|
|
@@ -77655,7 +77949,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
77655
77949
|
reject(_thrownError);
|
|
77656
77950
|
}
|
|
77657
77951
|
} else
|
|
77658
|
-
|
|
77952
|
+
resolve23(returnValue);
|
|
77659
77953
|
}, function(error48) {
|
|
77660
77954
|
popActScope(prevActQueue, prevActScopeDepth);
|
|
77661
77955
|
0 < ReactSharedInternals.thrownErrors.length ? (error48 = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(error48)) : reject(error48);
|
|
@@ -77671,11 +77965,11 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
|
|
|
77671
77965
|
if (0 < ReactSharedInternals.thrownErrors.length)
|
|
77672
77966
|
throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
|
|
77673
77967
|
return {
|
|
77674
|
-
then: function(
|
|
77968
|
+
then: function(resolve23, reject) {
|
|
77675
77969
|
didAwaitActCall = true;
|
|
77676
77970
|
prevActScopeDepth === 0 ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
|
|
77677
|
-
return recursivelyFlushAsyncActWork(returnValue$jscomp$0,
|
|
77678
|
-
})) :
|
|
77971
|
+
return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve23, reject);
|
|
77972
|
+
})) : resolve23(returnValue$jscomp$0);
|
|
77679
77973
|
}
|
|
77680
77974
|
};
|
|
77681
77975
|
};
|
|
@@ -80517,8 +80811,8 @@ It can also happen if the client has a browser extension installed which messes
|
|
|
80517
80811
|
currentEntangledActionThenable = {
|
|
80518
80812
|
status: "pending",
|
|
80519
80813
|
value: undefined,
|
|
80520
|
-
then: function(
|
|
80521
|
-
entangledListeners.push(
|
|
80814
|
+
then: function(resolve23) {
|
|
80815
|
+
entangledListeners.push(resolve23);
|
|
80522
80816
|
}
|
|
80523
80817
|
};
|
|
80524
80818
|
}
|
|
@@ -80542,8 +80836,8 @@ It can also happen if the client has a browser extension installed which messes
|
|
|
80542
80836
|
status: "pending",
|
|
80543
80837
|
value: null,
|
|
80544
80838
|
reason: null,
|
|
80545
|
-
then: function(
|
|
80546
|
-
listeners.push(
|
|
80839
|
+
then: function(resolve23) {
|
|
80840
|
+
listeners.push(resolve23);
|
|
80547
80841
|
}
|
|
80548
80842
|
};
|
|
80549
80843
|
thenable.then(function() {
|
|
@@ -106442,9 +106736,9 @@ var init_ranking = __esm(() => {
|
|
|
106442
106736
|
});
|
|
106443
106737
|
|
|
106444
106738
|
// src/bakeoff/coordinator.ts
|
|
106445
|
-
import { join as
|
|
106739
|
+
import { join as join103 } from "path";
|
|
106446
106740
|
async function persistBakeoffResult(result2, outputDir) {
|
|
106447
|
-
const filePath =
|
|
106741
|
+
const filePath = join103(outputDir, "bakeoff.json");
|
|
106448
106742
|
await Bun.write(filePath, JSON.stringify(result2, null, 2));
|
|
106449
106743
|
}
|
|
106450
106744
|
async function runBakeoff(options, deps = {}) {
|
|
@@ -106622,7 +106916,7 @@ var init_bakeoff = __esm(() => {
|
|
|
106622
106916
|
});
|
|
106623
106917
|
|
|
106624
106918
|
// src/plugins/builtin/curator/rollup-prune.ts
|
|
106625
|
-
import { rename as
|
|
106919
|
+
import { rename as rename5, unlink as unlink5, writeFile as writeFile3 } from "fs/promises";
|
|
106626
106920
|
import { appendFile as appendFile6 } from "fs/promises";
|
|
106627
106921
|
async function scanProjectRunIds(rollupPath, projectKey) {
|
|
106628
106922
|
const maxTsByRunId = new Map;
|
|
@@ -106691,9 +106985,9 @@ async function pruneRollup(input) {
|
|
|
106691
106985
|
await flush();
|
|
106692
106986
|
}
|
|
106693
106987
|
await flush();
|
|
106694
|
-
await
|
|
106988
|
+
await rename5(tmpPath, rollupPath);
|
|
106695
106989
|
} catch (err) {
|
|
106696
|
-
await
|
|
106990
|
+
await unlink5(tmpPath).catch(() => {});
|
|
106697
106991
|
throw err;
|
|
106698
106992
|
}
|
|
106699
106993
|
return result2;
|
|
@@ -106714,8 +107008,8 @@ __export(exports_curator, {
|
|
|
106714
107008
|
_curatorCmdDeps: () => _curatorCmdDeps
|
|
106715
107009
|
});
|
|
106716
107010
|
import { readdirSync as readdirSync9 } from "fs";
|
|
106717
|
-
import { unlink as
|
|
106718
|
-
import { join as
|
|
107011
|
+
import { unlink as unlink6 } from "fs/promises";
|
|
107012
|
+
import { join as join104, resolve as resolve23, sep as sep10 } from "path";
|
|
106719
107013
|
function listRunIds(runsDir) {
|
|
106720
107014
|
try {
|
|
106721
107015
|
return readdirSync9(runsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
@@ -106795,7 +107089,7 @@ async function curatorStatus(options) {
|
|
|
106795
107089
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
106796
107090
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
106797
107091
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
106798
|
-
const runsDir =
|
|
107092
|
+
const runsDir = join104(outputDir, "runs");
|
|
106799
107093
|
const runIds = listRunIds(runsDir);
|
|
106800
107094
|
let runId;
|
|
106801
107095
|
if (options.run) {
|
|
@@ -106812,8 +107106,8 @@ async function curatorStatus(options) {
|
|
|
106812
107106
|
runId = runIds[runIds.length - 1];
|
|
106813
107107
|
}
|
|
106814
107108
|
console.log(`Run: ${runId}`);
|
|
106815
|
-
const runDir =
|
|
106816
|
-
const observationsPath =
|
|
107109
|
+
const runDir = join104(runsDir, runId);
|
|
107110
|
+
const observationsPath = join104(runDir, "observations.jsonl");
|
|
106817
107111
|
const observations = await parseObservations(observationsPath);
|
|
106818
107112
|
const counts = new Map;
|
|
106819
107113
|
for (const obs of observations) {
|
|
@@ -106823,7 +107117,7 @@ async function curatorStatus(options) {
|
|
|
106823
107117
|
for (const [kind, count] of counts.entries()) {
|
|
106824
107118
|
console.log(` ${kind}: ${count}`);
|
|
106825
107119
|
}
|
|
106826
|
-
const proposalsPath =
|
|
107120
|
+
const proposalsPath = join104(runDir, "curator-proposals.md");
|
|
106827
107121
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
106828
107122
|
if (proposalText !== null) {
|
|
106829
107123
|
console.log("");
|
|
@@ -106833,24 +107127,24 @@ async function curatorStatus(options) {
|
|
|
106833
107127
|
}
|
|
106834
107128
|
}
|
|
106835
107129
|
function resolveCanonicalTargetPath(projectDir, canonicalFile) {
|
|
106836
|
-
const target =
|
|
106837
|
-
const root =
|
|
106838
|
-
if (target !== root && !target.startsWith(root +
|
|
107130
|
+
const target = resolve23(projectDir, canonicalFile);
|
|
107131
|
+
const root = resolve23(projectDir);
|
|
107132
|
+
if (target !== root && !target.startsWith(root + sep10))
|
|
106839
107133
|
return null;
|
|
106840
|
-
const relative18 = target.slice(root.length + 1).replaceAll(
|
|
107134
|
+
const relative18 = target.slice(root.length + 1).replaceAll(sep10, "/");
|
|
106841
107135
|
return CURATOR_TARGET_SHAPES.some((shape) => shape.test(relative18)) ? target : null;
|
|
106842
107136
|
}
|
|
106843
107137
|
function isWithinCanonicalRulesDir(projectDir, targetPath) {
|
|
106844
|
-
const rulesRoot =
|
|
106845
|
-
return targetPath === rulesRoot || targetPath.startsWith(rulesRoot +
|
|
107138
|
+
const rulesRoot = resolve23(projectDir, CANONICAL_RULES_DIR);
|
|
107139
|
+
return targetPath === rulesRoot || targetPath.startsWith(rulesRoot + sep10);
|
|
106846
107140
|
}
|
|
106847
107141
|
async function curatorCommit(options) {
|
|
106848
107142
|
const resolved = await _curatorCmdDeps.resolveProject({ dir: options.project });
|
|
106849
107143
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
106850
107144
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
106851
107145
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
106852
|
-
const runDir =
|
|
106853
|
-
const proposalsPath =
|
|
107146
|
+
const runDir = join104(outputDir, "runs", options.runId);
|
|
107147
|
+
const proposalsPath = join104(runDir, "curator-proposals.md");
|
|
106854
107148
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
106855
107149
|
if (proposalText === null) {
|
|
106856
107150
|
console.log(`curator-proposals.md not found for run ${options.runId}.`);
|
|
@@ -106977,7 +107271,7 @@ async function curatorDryrun(options) {
|
|
|
106977
107271
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
106978
107272
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
106979
107273
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
106980
|
-
const runsDir =
|
|
107274
|
+
const runsDir = join104(outputDir, "runs");
|
|
106981
107275
|
const runIds = listRunIds(runsDir);
|
|
106982
107276
|
if (runIds.length === 0) {
|
|
106983
107277
|
console.log("No runs found.");
|
|
@@ -106988,7 +107282,7 @@ async function curatorDryrun(options) {
|
|
|
106988
107282
|
console.log(`Run ${options.run} not found in ${runsDir}.`);
|
|
106989
107283
|
return;
|
|
106990
107284
|
}
|
|
106991
|
-
const observationsPath =
|
|
107285
|
+
const observationsPath = join104(runsDir, runId, "observations.jsonl");
|
|
106992
107286
|
const observations = await parseObservations(observationsPath);
|
|
106993
107287
|
const thresholds = getThresholds(config2);
|
|
106994
107288
|
const proposals = runHeuristics(observations, thresholds);
|
|
@@ -107020,12 +107314,12 @@ async function curatorGc(options) {
|
|
|
107020
107314
|
dropUnattributed: sweep
|
|
107021
107315
|
});
|
|
107022
107316
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
107023
|
-
const perRunsDir =
|
|
107317
|
+
const perRunsDir = join104(outputDir, "runs");
|
|
107024
107318
|
for (const runId of uniqueRunIds) {
|
|
107025
107319
|
if (!keepSet.has(runId)) {
|
|
107026
|
-
const runDir =
|
|
107027
|
-
await _curatorCmdDeps.removeFile(
|
|
107028
|
-
await _curatorCmdDeps.removeFile(
|
|
107320
|
+
const runDir = join104(perRunsDir, runId);
|
|
107321
|
+
await _curatorCmdDeps.removeFile(join104(runDir, "observations.jsonl"));
|
|
107322
|
+
await _curatorCmdDeps.removeFile(join104(runDir, "curator-proposals.md"));
|
|
107029
107323
|
}
|
|
107030
107324
|
}
|
|
107031
107325
|
const droppedRuns = Math.max(0, uniqueRunIds.length - keepSet.size);
|
|
@@ -107065,7 +107359,7 @@ var init_curator2 = __esm(() => {
|
|
|
107065
107359
|
},
|
|
107066
107360
|
removeFile: async (p) => {
|
|
107067
107361
|
try {
|
|
107068
|
-
await
|
|
107362
|
+
await unlink6(p);
|
|
107069
107363
|
} catch {}
|
|
107070
107364
|
},
|
|
107071
107365
|
openInEditor: async (filePath) => {
|
|
@@ -107084,7 +107378,7 @@ var init_curator2 = __esm(() => {
|
|
|
107084
107378
|
init_source();
|
|
107085
107379
|
import { existsSync as existsSync39, mkdirSync as mkdirSync8 } from "fs";
|
|
107086
107380
|
import { homedir as homedir3 } from "os";
|
|
107087
|
-
import { basename as basename21, join as
|
|
107381
|
+
import { basename as basename21, join as join105 } from "path";
|
|
107088
107382
|
|
|
107089
107383
|
// node_modules/commander/esm.mjs
|
|
107090
107384
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -108139,12 +108433,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
108139
108433
|
"autoMode.escalation.tierOrder": 'Ordered tier escalation chain with per-tier attempt budgets. Format: [{"tier": "fast", "attempts": 2}, {"tier": "balanced", "attempts": 2}, {"tier": "powerful", "attempts": 1}]. Allows each tier to attempt fixes before escalating to the next.',
|
|
108140
108434
|
"autoMode.escalation.escalateEntireBatch": "When enabled, escalate all stories in a batch if one fails. When disabled, only the failing story escalates (allows parallel attempts at different tiers).",
|
|
108141
108435
|
routing: "Model routing strategy configuration",
|
|
108142
|
-
"routing.strategy": "Routing strategy: keyword | llm
|
|
108143
|
-
"routing.customStrategyPath": "Path to custom routing strategy (if strategy=custom)",
|
|
108144
|
-
"routing.adaptive": "Adaptive routing settings",
|
|
108145
|
-
"routing.adaptive.minSamples": "Minimum samples before adaptive routing activates",
|
|
108146
|
-
"routing.adaptive.costThreshold": "Cost threshold for strategy switching (0-1)",
|
|
108147
|
-
"routing.adaptive.fallbackStrategy": "Fallback strategy if adaptive fails",
|
|
108436
|
+
"routing.strategy": "Routing strategy: keyword | llm",
|
|
108148
108437
|
"routing.llm": "LLM-based routing settings",
|
|
108149
108438
|
"routing.llm.model": 'Model selector for routing decisions. Accepts a tier string (for example "fast") or an explicit object like { agent: "codex", model: "gpt-5.4" }.',
|
|
108150
108439
|
"routing.llm.fallbackToKeywords": "Fall back to keyword routing on LLM failure",
|
|
@@ -108809,11 +109098,12 @@ init_canonical_loader();
|
|
|
108809
109098
|
init_errors();
|
|
108810
109099
|
init_logger2();
|
|
108811
109100
|
import { mkdir as mkdir11 } from "fs/promises";
|
|
108812
|
-
import {
|
|
109101
|
+
import { join as join76, resolve as resolve20, sep as sep8 } from "path";
|
|
108813
109102
|
|
|
108814
109103
|
// src/cli/rules-lint.ts
|
|
108815
109104
|
init_engine();
|
|
108816
109105
|
init_canonical_loader();
|
|
109106
|
+
init_errors();
|
|
108817
109107
|
init_logger2();
|
|
108818
109108
|
init_test_runners();
|
|
108819
109109
|
import { join as join74 } from "path";
|
|
@@ -108894,8 +109184,15 @@ async function rulesLintCommand(options, deps = _rulesLintDeps) {
|
|
|
108894
109184
|
}
|
|
108895
109185
|
let totalRuleFiles = 0;
|
|
108896
109186
|
let warningCount = 0;
|
|
109187
|
+
const failedRoots = [];
|
|
108897
109188
|
for (const root of roots) {
|
|
108898
|
-
|
|
109189
|
+
let rules;
|
|
109190
|
+
try {
|
|
109191
|
+
rules = await deps.loadCanonicalRules(root);
|
|
109192
|
+
} catch (err) {
|
|
109193
|
+
failedRoots.push({ root, cause: err });
|
|
109194
|
+
continue;
|
|
109195
|
+
}
|
|
108899
109196
|
totalRuleFiles += rules.length;
|
|
108900
109197
|
for (const rule of rules) {
|
|
108901
109198
|
for (const warning of rule.warnings ?? []) {
|
|
@@ -108927,6 +109224,18 @@ async function rulesLintCommand(options, deps = _rulesLintDeps) {
|
|
|
108927
109224
|
}
|
|
108928
109225
|
}
|
|
108929
109226
|
}
|
|
109227
|
+
if (totalRuleFiles === 0 && failedRoots.length === 0) {
|
|
109228
|
+
warningCount++;
|
|
109229
|
+
logger.warn("rules-lint", "Canonical rules store is empty \u2014 no rule files found across any rule root. Run `nax rules migrate` to seed the store.", { code: "EMPTY_STORE", roots: roots.length });
|
|
109230
|
+
}
|
|
109231
|
+
if (failedRoots.length > 0) {
|
|
109232
|
+
const failedRootPaths = failedRoots.map((f) => f.root);
|
|
109233
|
+
throw new NaxError(`Failed to load canonical rules from ${failedRootPaths.length} rule root(s): ${failedRootPaths.join(", ")}`, "RULES_LINT_ROOT_FAILED", {
|
|
109234
|
+
stage: "rules-lint",
|
|
109235
|
+
failedRoots: failedRootPaths,
|
|
109236
|
+
causes: failedRoots.map((f) => f.cause)
|
|
109237
|
+
});
|
|
109238
|
+
}
|
|
108930
109239
|
const scopeLabel = roots.length === 1 ? "repo root" : `${roots.length} rule roots`;
|
|
108931
109240
|
if (warningCount > 0) {
|
|
108932
109241
|
console.log(`[WARN] Canonical rules lint completed with ${warningCount} warning(s) (${totalRuleFiles} file(s) across ${scopeLabel}).`);
|
|
@@ -108934,6 +109243,150 @@ async function rulesLintCommand(options, deps = _rulesLintDeps) {
|
|
|
108934
109243
|
console.log(`[OK] Canonical rules lint passed (${totalRuleFiles} file(s) across ${scopeLabel}).`);
|
|
108935
109244
|
}
|
|
108936
109245
|
}
|
|
109246
|
+
// src/cli/rules-migrate.ts
|
|
109247
|
+
init_canonical_loader();
|
|
109248
|
+
init_errors();
|
|
109249
|
+
import { basename as basename15, join as join75 } from "path";
|
|
109250
|
+
|
|
109251
|
+
// src/cli/rules-migrate-plan.ts
|
|
109252
|
+
init_errors();
|
|
109253
|
+
import { resolve as resolve19, sep as sep7 } from "path";
|
|
109254
|
+
async function planMigration(sources, options) {
|
|
109255
|
+
const writes = [];
|
|
109256
|
+
const skips = [];
|
|
109257
|
+
const resolvedTargetDir = resolve19(options.targetDir);
|
|
109258
|
+
for (const source of sources) {
|
|
109259
|
+
const resolvedTargetPath = resolve19(source.targetPath);
|
|
109260
|
+
if (!resolvedTargetPath.startsWith(`${resolvedTargetDir}${sep7}`) && resolvedTargetPath !== resolvedTargetDir) {
|
|
109261
|
+
throw new NaxError(`Migration target escapes ${options.targetDir}: ${source.targetFileName} -> ${source.targetPath}`, "RULES_MIGRATE_TARGET_ESCAPE", { stage: "rules-migrate-plan", targetDir: options.targetDir, entry: source.targetPath });
|
|
109262
|
+
}
|
|
109263
|
+
const exists = await options.fileExists(source.targetPath);
|
|
109264
|
+
if (exists && !options.force) {
|
|
109265
|
+
skips.push(source);
|
|
109266
|
+
} else {
|
|
109267
|
+
writes.push(source);
|
|
109268
|
+
}
|
|
109269
|
+
}
|
|
109270
|
+
return { writes, skips };
|
|
109271
|
+
}
|
|
109272
|
+
|
|
109273
|
+
// src/cli/rules-migrate.ts
|
|
109274
|
+
function neutralizeContent(content) {
|
|
109275
|
+
let result = content;
|
|
109276
|
+
let replacements = 0;
|
|
109277
|
+
for (const rule of NEUTRALITY_RULES) {
|
|
109278
|
+
for (const { pattern, replacement } of rule.neutralizeSteps ?? []) {
|
|
109279
|
+
const matches = [...result.matchAll(pattern)].length;
|
|
109280
|
+
if (matches > 0) {
|
|
109281
|
+
result = result.replace(pattern, replacement);
|
|
109282
|
+
replacements += matches;
|
|
109283
|
+
}
|
|
109284
|
+
}
|
|
109285
|
+
}
|
|
109286
|
+
return { content: result.trim(), replacements };
|
|
109287
|
+
}
|
|
109288
|
+
function toYamlListLiteral(scalar) {
|
|
109289
|
+
const trimmed = scalar.trim();
|
|
109290
|
+
const unquoted = trimmed.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
109291
|
+
return `[${JSON.stringify(unquoted)}]`;
|
|
109292
|
+
}
|
|
109293
|
+
function translateLegacyFrontmatter(content) {
|
|
109294
|
+
const fm = /^---(\r?\n)([\s\S]*?)\r?\n---\r?\n/.exec(content);
|
|
109295
|
+
if (!fm?.[2])
|
|
109296
|
+
return { content, translated: false };
|
|
109297
|
+
const eol = fm[1] ?? `
|
|
109298
|
+
`;
|
|
109299
|
+
const block = fm[2];
|
|
109300
|
+
if (!/^paths:/m.test(block) || /^appliesTo:/m.test(block))
|
|
109301
|
+
return { content, translated: false };
|
|
109302
|
+
const scalarMatch = /^paths:[ \t]*(\S.*)$/m.exec(block);
|
|
109303
|
+
const isInlineList = scalarMatch?.[1]?.trim().startsWith("[") ?? false;
|
|
109304
|
+
const rewritten = scalarMatch && !isInlineList ? block.replace(/^paths:[ \t]*(\S.*)$/m, `appliesTo: ${toYamlListLiteral(scalarMatch[1])}`) : block.replace(/^paths:/m, "appliesTo:");
|
|
109305
|
+
const head = content.slice(0, fm.index);
|
|
109306
|
+
const tail = content.slice(fm.index + fm[0].length);
|
|
109307
|
+
return { content: `${head}---${eol}${rewritten}${eol}---${eol}${tail}`, translated: true };
|
|
109308
|
+
}
|
|
109309
|
+
function withReviewNotice(content, replacements) {
|
|
109310
|
+
if (replacements <= 0)
|
|
109311
|
+
return content;
|
|
109312
|
+
const notice = `<!-- NOTE: ${replacements} neutralization(s) applied \u2014 review before committing -->
|
|
109313
|
+
|
|
109314
|
+
`;
|
|
109315
|
+
const fm = /^---\r?\n[\s\S]*?\r?\n---\r?\n/.exec(content);
|
|
109316
|
+
if (!fm)
|
|
109317
|
+
return notice + content;
|
|
109318
|
+
return content.slice(0, fm[0].length) + notice + content.slice(fm[0].length).replace(/^(?:\r?\n)+/, "");
|
|
109319
|
+
}
|
|
109320
|
+
async function collectMigrationSources(workdir) {
|
|
109321
|
+
const sources = [];
|
|
109322
|
+
const rulesDir = join75(workdir, ".claude", "rules");
|
|
109323
|
+
const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
|
|
109324
|
+
for (const filePath of ruleFiles) {
|
|
109325
|
+
try {
|
|
109326
|
+
const content = await _rulesCLIDeps.readFile(filePath);
|
|
109327
|
+
if (content.trim()) {
|
|
109328
|
+
sources.push({ sourcePath: filePath, targetFileName: basename15(filePath), content });
|
|
109329
|
+
}
|
|
109330
|
+
} catch {}
|
|
109331
|
+
}
|
|
109332
|
+
return sources;
|
|
109333
|
+
}
|
|
109334
|
+
async function rulesMigrateCommand(options) {
|
|
109335
|
+
const workdir = options.dir ?? process.cwd();
|
|
109336
|
+
const force = options.force ?? false;
|
|
109337
|
+
const dryRun = options.dryRun === true;
|
|
109338
|
+
const sources = await collectMigrationSources(workdir);
|
|
109339
|
+
if (sources.length === 0) {
|
|
109340
|
+
console.log("[WARN] No source files found (checked .claude/rules/*.md). Nothing to migrate.");
|
|
109341
|
+
console.log("[WARN] Root CLAUDE.md is not a migration source. Move the rules you want migrated into .claude/rules/, or author .nax/rules/ directly.");
|
|
109342
|
+
return { written: [], skipped: [] };
|
|
109343
|
+
}
|
|
109344
|
+
const targetDir = join75(workdir, CANONICAL_RULES_DIR);
|
|
109345
|
+
const planEntries = sources.map((source) => ({
|
|
109346
|
+
sourcePath: source.sourcePath,
|
|
109347
|
+
targetFileName: source.targetFileName,
|
|
109348
|
+
targetPath: join75(targetDir, source.targetFileName),
|
|
109349
|
+
content: source.content
|
|
109350
|
+
}));
|
|
109351
|
+
const plan = await planMigration(planEntries, {
|
|
109352
|
+
targetDir,
|
|
109353
|
+
force,
|
|
109354
|
+
fileExists: _rulesCLIDeps.fileExists
|
|
109355
|
+
});
|
|
109356
|
+
if (!dryRun) {
|
|
109357
|
+
try {
|
|
109358
|
+
await _rulesCLIDeps.mkdir(targetDir);
|
|
109359
|
+
} catch (err) {
|
|
109360
|
+
throw new NaxError(`Failed to create ${CANONICAL_RULES_DIR}: ${errorMessage(err)}`, "RULES_MIGRATE_MKDIR_FAILED", { stage: "rules-migrate", targetDir });
|
|
109361
|
+
}
|
|
109362
|
+
}
|
|
109363
|
+
const written = [];
|
|
109364
|
+
const skipped = [];
|
|
109365
|
+
for (const entry of plan.writes) {
|
|
109366
|
+
const { content: scoped2 } = translateLegacyFrontmatter(entry.content);
|
|
109367
|
+
const { content: neutralized, replacements } = neutralizeContent(scoped2);
|
|
109368
|
+
const output = withReviewNotice(neutralized, replacements);
|
|
109369
|
+
if (dryRun) {
|
|
109370
|
+
console.log(`[dry-run] Would write ${entry.targetFileName} from ${entry.sourcePath} (${replacements} replacements)`);
|
|
109371
|
+
} else {
|
|
109372
|
+
await _rulesCLIDeps.writeFile(entry.targetPath, output);
|
|
109373
|
+
console.log(`[OK] ${entry.targetFileName} <- ${entry.sourcePath}${replacements > 0 ? ` (${replacements} replacements)` : ""}`);
|
|
109374
|
+
}
|
|
109375
|
+
written.push(entry.targetFileName);
|
|
109376
|
+
}
|
|
109377
|
+
for (const entry of plan.skips) {
|
|
109378
|
+
console.log(`[skip] ${entry.targetFileName} already exists (use --force to overwrite)`);
|
|
109379
|
+
skipped.push(entry.targetFileName);
|
|
109380
|
+
}
|
|
109381
|
+
console.log(dryRun ? `
|
|
109382
|
+
Dry run: ${written.length} file(s) would be written, ${skipped.length} skipped.` : `
|
|
109383
|
+
Migration complete: ${written.length} file(s) written, ${skipped.length} skipped.`);
|
|
109384
|
+
if (!dryRun && written.length > 0) {
|
|
109385
|
+
console.log(`Review ${CANONICAL_RULES_DIR}/ before committing. Run \`nax rules export --agent=claude\` to regenerate CLAUDE.md.`);
|
|
109386
|
+
}
|
|
109387
|
+
return { written, skipped };
|
|
109388
|
+
}
|
|
109389
|
+
|
|
108937
109390
|
// src/cli/rules.ts
|
|
108938
109391
|
var _rulesCLIDeps = {
|
|
108939
109392
|
readFile: async (path25) => Bun.file(path25).text(),
|
|
@@ -108943,7 +109396,7 @@ var _rulesCLIDeps = {
|
|
|
108943
109396
|
fileExists: async (path25) => Bun.file(path25).exists(),
|
|
108944
109397
|
globInDir: (dir) => {
|
|
108945
109398
|
try {
|
|
108946
|
-
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) =>
|
|
109399
|
+
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join76(dir, f));
|
|
108947
109400
|
} catch {
|
|
108948
109401
|
return [];
|
|
108949
109402
|
}
|
|
@@ -109016,8 +109469,8 @@ async function exportRuleDirectory(input) {
|
|
|
109016
109469
|
const drifted = [];
|
|
109017
109470
|
for (const rule of rules) {
|
|
109018
109471
|
const rel = rule.path ?? rule.fileName;
|
|
109019
|
-
const target =
|
|
109020
|
-
if (!target.startsWith(`${
|
|
109472
|
+
const target = resolve20(workdir, ruleDir, rel);
|
|
109473
|
+
if (!target.startsWith(`${resolve20(workdir, ruleDir)}${sep8}`)) {
|
|
109021
109474
|
throw new NaxError(`Rule path escapes ${ruleDir}: ${rel}`, "RULES_EXPORT_PATH_ESCAPE", {
|
|
109022
109475
|
stage: "rules-export",
|
|
109023
109476
|
rule: rel
|
|
@@ -109037,9 +109490,9 @@ async function exportRuleDirectory(input) {
|
|
|
109037
109490
|
}
|
|
109038
109491
|
await _rulesCLIDeps.writeFile(target, content);
|
|
109039
109492
|
}
|
|
109040
|
-
const expected = new Set(rules.map((r) =>
|
|
109041
|
-
for (const existing of _rulesCLIDeps.globInDir(
|
|
109042
|
-
if (!expected.has(
|
|
109493
|
+
const expected = new Set(rules.map((r) => resolve20(workdir, ruleDir, r.path ?? r.fileName)));
|
|
109494
|
+
for (const existing of _rulesCLIDeps.globInDir(join76(workdir, ruleDir))) {
|
|
109495
|
+
if (!expected.has(resolve20(existing))) {
|
|
109043
109496
|
_rulesCLIDeps.getLogger().warn("rules-export", "Generated rules dir contains a file with no canonical source", {
|
|
109044
109497
|
file: existing,
|
|
109045
109498
|
hint: `Delete it, or add the rule to ${CANONICAL_RULES_DIR}/`
|
|
@@ -109094,7 +109547,7 @@ ${r.content}`).join(`
|
|
|
109094
109547
|
`);
|
|
109095
109548
|
const shimContent = `${header + body}
|
|
109096
109549
|
`;
|
|
109097
|
-
const shimPath =
|
|
109550
|
+
const shimPath = join76(workdir, shimFileName);
|
|
109098
109551
|
if (options.dryRun) {
|
|
109099
109552
|
console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
|
|
109100
109553
|
return;
|
|
@@ -109102,117 +109555,6 @@ ${r.content}`).join(`
|
|
|
109102
109555
|
await _rulesCLIDeps.writeFile(shimPath, shimContent);
|
|
109103
109556
|
console.log(`[OK] Wrote ${shimFileName} (${rules.length} rule file(s) from ${CANONICAL_RULES_DIR}/)`);
|
|
109104
109557
|
}
|
|
109105
|
-
function neutralizeContent(content) {
|
|
109106
|
-
let result = content;
|
|
109107
|
-
let replacements = 0;
|
|
109108
|
-
for (const rule of NEUTRALITY_RULES) {
|
|
109109
|
-
for (const { pattern, replacement } of rule.neutralizeSteps ?? []) {
|
|
109110
|
-
const matches = [...result.matchAll(pattern)].length;
|
|
109111
|
-
if (matches > 0) {
|
|
109112
|
-
result = result.replace(pattern, replacement);
|
|
109113
|
-
replacements += matches;
|
|
109114
|
-
}
|
|
109115
|
-
}
|
|
109116
|
-
}
|
|
109117
|
-
return { content: result.trim(), replacements };
|
|
109118
|
-
}
|
|
109119
|
-
function toYamlListLiteral(scalar) {
|
|
109120
|
-
const trimmed = scalar.trim();
|
|
109121
|
-
const unquoted = trimmed.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1");
|
|
109122
|
-
return `[${JSON.stringify(unquoted)}]`;
|
|
109123
|
-
}
|
|
109124
|
-
function translateLegacyFrontmatter(content) {
|
|
109125
|
-
const fm = /^---(\r?\n)([\s\S]*?)\r?\n---\r?\n/.exec(content);
|
|
109126
|
-
if (!fm?.[2])
|
|
109127
|
-
return { content, translated: false };
|
|
109128
|
-
const eol = fm[1] ?? `
|
|
109129
|
-
`;
|
|
109130
|
-
const block = fm[2];
|
|
109131
|
-
if (!/^paths:/m.test(block) || /^appliesTo:/m.test(block))
|
|
109132
|
-
return { content, translated: false };
|
|
109133
|
-
const scalarMatch = /^paths:[ \t]*(\S.*)$/m.exec(block);
|
|
109134
|
-
const isInlineList = scalarMatch?.[1]?.trim().startsWith("[") ?? false;
|
|
109135
|
-
const rewritten = scalarMatch && !isInlineList ? block.replace(/^paths:[ \t]*(\S.*)$/m, `appliesTo: ${toYamlListLiteral(scalarMatch[1])}`) : block.replace(/^paths:/m, "appliesTo:");
|
|
109136
|
-
const head = content.slice(0, fm.index);
|
|
109137
|
-
const tail = content.slice(fm.index + fm[0].length);
|
|
109138
|
-
return { content: `${head}---${eol}${rewritten}${eol}---${eol}${tail}`, translated: true };
|
|
109139
|
-
}
|
|
109140
|
-
function withReviewNotice(content, replacements) {
|
|
109141
|
-
if (replacements <= 0)
|
|
109142
|
-
return content;
|
|
109143
|
-
const notice = `<!-- NOTE: ${replacements} neutralization(s) applied \u2014 review before committing -->
|
|
109144
|
-
|
|
109145
|
-
`;
|
|
109146
|
-
const fm = /^---\r?\n[\s\S]*?\r?\n---\r?\n/.exec(content);
|
|
109147
|
-
if (!fm)
|
|
109148
|
-
return notice + content;
|
|
109149
|
-
return content.slice(0, fm[0].length) + notice + content.slice(fm[0].length).replace(/^(?:\r?\n)+/, "");
|
|
109150
|
-
}
|
|
109151
|
-
async function collectMigrationSources(workdir) {
|
|
109152
|
-
const sources = [];
|
|
109153
|
-
const claudeMdPath = join75(workdir, "CLAUDE.md");
|
|
109154
|
-
if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
|
|
109155
|
-
const content = await _rulesCLIDeps.readFile(claudeMdPath);
|
|
109156
|
-
if (content.trim()) {
|
|
109157
|
-
sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
|
|
109158
|
-
}
|
|
109159
|
-
}
|
|
109160
|
-
const rulesDir = join75(workdir, ".claude", "rules");
|
|
109161
|
-
const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
|
|
109162
|
-
for (const filePath of ruleFiles) {
|
|
109163
|
-
try {
|
|
109164
|
-
const content = await _rulesCLIDeps.readFile(filePath);
|
|
109165
|
-
if (content.trim()) {
|
|
109166
|
-
sources.push({ sourcePath: filePath, targetFileName: basename15(filePath), content });
|
|
109167
|
-
}
|
|
109168
|
-
} catch {}
|
|
109169
|
-
}
|
|
109170
|
-
return sources;
|
|
109171
|
-
}
|
|
109172
|
-
async function rulesMigrateCommand(options) {
|
|
109173
|
-
const workdir = options.dir ?? process.cwd();
|
|
109174
|
-
const force = options.force ?? false;
|
|
109175
|
-
const sources = await collectMigrationSources(workdir);
|
|
109176
|
-
if (sources.length === 0) {
|
|
109177
|
-
console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
|
|
109178
|
-
return;
|
|
109179
|
-
}
|
|
109180
|
-
const targetDir = join75(workdir, CANONICAL_RULES_DIR);
|
|
109181
|
-
if (!options.dryRun) {
|
|
109182
|
-
try {
|
|
109183
|
-
await _rulesCLIDeps.mkdir(targetDir);
|
|
109184
|
-
} catch (err) {
|
|
109185
|
-
throw new NaxError(`Failed to create ${CANONICAL_RULES_DIR}: ${errorMessage(err)}`, "RULES_MIGRATE_MKDIR_FAILED", { stage: "rules-migrate", targetDir });
|
|
109186
|
-
}
|
|
109187
|
-
}
|
|
109188
|
-
let written = 0;
|
|
109189
|
-
let skipped = 0;
|
|
109190
|
-
for (const { sourcePath, targetFileName, content } of sources) {
|
|
109191
|
-
const targetPath = join75(targetDir, targetFileName);
|
|
109192
|
-
if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
|
|
109193
|
-
console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
|
|
109194
|
-
skipped++;
|
|
109195
|
-
continue;
|
|
109196
|
-
}
|
|
109197
|
-
const { content: scoped2 } = translateLegacyFrontmatter(content);
|
|
109198
|
-
const { content: neutralized, replacements } = neutralizeContent(scoped2);
|
|
109199
|
-
const output = withReviewNotice(neutralized, replacements);
|
|
109200
|
-
if (options.dryRun) {
|
|
109201
|
-
console.log(`[dry-run] Would write ${targetFileName} from ${sourcePath} (${replacements} replacements)`);
|
|
109202
|
-
} else {
|
|
109203
|
-
await _rulesCLIDeps.writeFile(targetPath, output);
|
|
109204
|
-
console.log(`[OK] ${targetFileName} <- ${sourcePath}${replacements > 0 ? ` (${replacements} replacements)` : ""}`);
|
|
109205
|
-
}
|
|
109206
|
-
written++;
|
|
109207
|
-
}
|
|
109208
|
-
if (!options.dryRun) {
|
|
109209
|
-
console.log(`
|
|
109210
|
-
Migration complete: ${written} file(s) written, ${skipped} skipped.`);
|
|
109211
|
-
if (written > 0) {
|
|
109212
|
-
console.log(`Review ${CANONICAL_RULES_DIR}/ before committing. Run \`nax rules export --agent=claude\` to regenerate CLAUDE.md.`);
|
|
109213
|
-
}
|
|
109214
|
-
}
|
|
109215
|
-
}
|
|
109216
109558
|
// src/cli/resolve-run-profile.ts
|
|
109217
109559
|
init_config();
|
|
109218
109560
|
init_logger2();
|
|
@@ -109247,7 +109589,7 @@ async function resolveRunProfileOverride(opts) {
|
|
|
109247
109589
|
init_config();
|
|
109248
109590
|
init_test_runners();
|
|
109249
109591
|
import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
|
|
109250
|
-
import { join as
|
|
109592
|
+
import { join as join78, relative as relative17 } from "path";
|
|
109251
109593
|
|
|
109252
109594
|
// src/cli/features-acceptance.ts
|
|
109253
109595
|
init_acceptance2();
|
|
@@ -109255,7 +109597,7 @@ init_config();
|
|
|
109255
109597
|
init_logger2();
|
|
109256
109598
|
init_prd();
|
|
109257
109599
|
import { existsSync as existsSync27 } from "fs";
|
|
109258
|
-
import { join as
|
|
109600
|
+
import { join as join77, relative as relative16 } from "path";
|
|
109259
109601
|
async function resolveFeatureAcceptance(featureName, workdir) {
|
|
109260
109602
|
let enabled = true;
|
|
109261
109603
|
try {
|
|
@@ -109263,13 +109605,13 @@ async function resolveFeatureAcceptance(featureName, workdir) {
|
|
|
109263
109605
|
if (!naxDir) {
|
|
109264
109606
|
return { status: "no-prd", enabled, groups: [] };
|
|
109265
109607
|
}
|
|
109266
|
-
const repoRoot =
|
|
109608
|
+
const repoRoot = join77(naxDir, "..");
|
|
109267
109609
|
const config2 = await loadConfig(workdir);
|
|
109268
109610
|
enabled = config2.acceptance?.enabled ?? true;
|
|
109269
109611
|
if (!enabled) {
|
|
109270
109612
|
return { status: "disabled", enabled: false, groups: [] };
|
|
109271
109613
|
}
|
|
109272
|
-
const prdPath =
|
|
109614
|
+
const prdPath = join77(naxDir, "features", featureName, "prd.json");
|
|
109273
109615
|
if (!existsSync27(prdPath)) {
|
|
109274
109616
|
return { status: "no-prd", enabled, groups: [] };
|
|
109275
109617
|
}
|
|
@@ -109321,10 +109663,10 @@ async function isNonEmptyFile(absolutePath) {
|
|
|
109321
109663
|
}
|
|
109322
109664
|
async function searchSpecSource(naxDir, repoRoot, name) {
|
|
109323
109665
|
const candidates = [
|
|
109324
|
-
{ abs:
|
|
109325
|
-
{ abs:
|
|
109666
|
+
{ abs: join78(naxDir, "features", name, "spec.md"), kind: "markdown" },
|
|
109667
|
+
{ abs: join78(naxDir, "specs", `${name}.md`), kind: "markdown" }
|
|
109326
109668
|
];
|
|
109327
|
-
const docsSpecExact =
|
|
109669
|
+
const docsSpecExact = join78(repoRoot, "docs", "specs", `SPEC-${name}.md`);
|
|
109328
109670
|
candidates.push({ abs: docsSpecExact, kind: "markdown" });
|
|
109329
109671
|
const checked = candidates.map((c) => relative17(repoRoot, c.abs));
|
|
109330
109672
|
for (const { abs, kind } of candidates.slice(0, 2)) {
|
|
@@ -109338,11 +109680,11 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
109338
109680
|
if (await isNonEmptyFile(docsSpecExact)) {
|
|
109339
109681
|
return { source: { kind: "markdown", path: relative17(repoRoot, docsSpecExact) }, checked };
|
|
109340
109682
|
}
|
|
109341
|
-
const docsSpecsDir =
|
|
109683
|
+
const docsSpecsDir = join78(repoRoot, "docs", "specs");
|
|
109342
109684
|
if (existsSync28(docsSpecsDir)) {
|
|
109343
109685
|
const glob = new Bun.Glob(`*${name}*.md`);
|
|
109344
109686
|
for (const match of glob.scanSync({ cwd: docsSpecsDir, absolute: false })) {
|
|
109345
|
-
const abs =
|
|
109687
|
+
const abs = join78(docsSpecsDir, match);
|
|
109346
109688
|
if (await isNonEmptyFile(abs)) {
|
|
109347
109689
|
const relPath = relative17(repoRoot, abs);
|
|
109348
109690
|
if (!checked.includes(relPath))
|
|
@@ -109351,7 +109693,7 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
109351
109693
|
}
|
|
109352
109694
|
}
|
|
109353
109695
|
}
|
|
109354
|
-
const prdAbs =
|
|
109696
|
+
const prdAbs = join78(naxDir, "features", name, "prd.json");
|
|
109355
109697
|
const prdRel = relative17(repoRoot, prdAbs);
|
|
109356
109698
|
if (!checked.includes(prdRel))
|
|
109357
109699
|
checked.push(prdRel);
|
|
@@ -109361,14 +109703,14 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
109361
109703
|
return { source: null, checked };
|
|
109362
109704
|
}
|
|
109363
109705
|
function discoverCandidates(naxDir) {
|
|
109364
|
-
const featuresDir =
|
|
109706
|
+
const featuresDir = join78(naxDir, "features");
|
|
109365
109707
|
if (!existsSync28(featuresDir))
|
|
109366
109708
|
return [];
|
|
109367
109709
|
return readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => {
|
|
109368
109710
|
if (!e.isDirectory())
|
|
109369
109711
|
return false;
|
|
109370
|
-
const dir =
|
|
109371
|
-
return existsSync28(
|
|
109712
|
+
const dir = join78(featuresDir, e.name);
|
|
109713
|
+
return existsSync28(join78(dir, "prd.json")) || existsSync28(join78(dir, "spec.md"));
|
|
109372
109714
|
}).map((e) => e.name).sort();
|
|
109373
109715
|
}
|
|
109374
109716
|
async function resolveFeatureSpec(name, workdir) {
|
|
@@ -109379,9 +109721,9 @@ async function resolveFeatureSpec(name, workdir) {
|
|
|
109379
109721
|
message: `not a nax repo: no .nax/config.json found from ${workdir}`
|
|
109380
109722
|
};
|
|
109381
109723
|
}
|
|
109382
|
-
const repoRoot =
|
|
109724
|
+
const repoRoot = join78(naxDir, "..");
|
|
109383
109725
|
if (name !== undefined && (name.startsWith("./") || name.startsWith("/") || name.endsWith(".md"))) {
|
|
109384
|
-
const abs = name.startsWith("/") ? name :
|
|
109726
|
+
const abs = name.startsWith("/") ? name : join78(workdir, name);
|
|
109385
109727
|
if (!existsSync28(abs)) {
|
|
109386
109728
|
return {
|
|
109387
109729
|
status: "missing",
|
|
@@ -109421,7 +109763,7 @@ async function resolveFeatureSpec(name, workdir) {
|
|
|
109421
109763
|
message: `resolved spec: ${source2.path}`
|
|
109422
109764
|
};
|
|
109423
109765
|
}
|
|
109424
|
-
const featureDir =
|
|
109766
|
+
const featureDir = join78(naxDir, "features", name);
|
|
109425
109767
|
if (existsSync28(featureDir)) {
|
|
109426
109768
|
return {
|
|
109427
109769
|
status: "missing",
|
|
@@ -109484,14 +109826,14 @@ init_runtime();
|
|
|
109484
109826
|
init_json_file();
|
|
109485
109827
|
init_routing();
|
|
109486
109828
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
109487
|
-
import { basename as basename16, join as
|
|
109829
|
+
import { basename as basename16, join as join79 } from "path";
|
|
109488
109830
|
var _routingCalibrateDeps = {
|
|
109489
109831
|
loadRunMetrics: (outputDir) => loadRunMetrics(outputDir),
|
|
109490
109832
|
readConfig: (workdir) => loadConfig(workdir),
|
|
109491
109833
|
writeConfig: async (workdir, config2) => {
|
|
109492
109834
|
const dir = projectInputDir(workdir);
|
|
109493
109835
|
mkdirSync6(dir, { recursive: true });
|
|
109494
|
-
const filePath =
|
|
109836
|
+
const filePath = join79(dir, "config.json");
|
|
109495
109837
|
await saveJsonFile(filePath, config2, "routing-calibrate");
|
|
109496
109838
|
},
|
|
109497
109839
|
stdout: (msg) => {
|
|
@@ -109618,7 +109960,7 @@ init_logger2();
|
|
|
109618
109960
|
init_detect2();
|
|
109619
109961
|
init_workspace();
|
|
109620
109962
|
init_common();
|
|
109621
|
-
import { join as
|
|
109963
|
+
import { join as join80 } from "path";
|
|
109622
109964
|
function resolveEffective(detected, configPatterns) {
|
|
109623
109965
|
if (configPatterns !== undefined)
|
|
109624
109966
|
return "config";
|
|
@@ -109703,7 +110045,7 @@ async function detectCommand(options) {
|
|
|
109703
110045
|
const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
109704
110046
|
const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
|
|
109705
110047
|
const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
109706
|
-
const pkgConfigPath =
|
|
110048
|
+
const pkgConfigPath = join80(workdir, ".nax", "mono", dir, "config.json");
|
|
109707
110049
|
const pkgRaw = await loadRawConfig(pkgConfigPath);
|
|
109708
110050
|
const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
|
|
109709
110051
|
const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
|
|
@@ -109757,13 +110099,13 @@ async function detectCommand(options) {
|
|
|
109757
110099
|
if (rootDetected.confidence === "empty") {
|
|
109758
110100
|
console.log(source_default.yellow(" root: skipped (empty detection)"));
|
|
109759
110101
|
} else {
|
|
109760
|
-
const rootConfigPath =
|
|
110102
|
+
const rootConfigPath = join80(workdir, ".nax", "config.json");
|
|
109761
110103
|
try {
|
|
109762
110104
|
const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
|
|
109763
110105
|
if (status === "skipped") {
|
|
109764
110106
|
console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
|
|
109765
110107
|
} else {
|
|
109766
|
-
console.log(source_default.green(` root: ${status} \u2192 ${
|
|
110108
|
+
console.log(source_default.green(` root: ${status} \u2192 ${join80(".nax", "config.json")}`));
|
|
109767
110109
|
}
|
|
109768
110110
|
} catch (err) {
|
|
109769
110111
|
console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
|
|
@@ -109776,13 +110118,13 @@ async function detectCommand(options) {
|
|
|
109776
110118
|
console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
|
|
109777
110119
|
continue;
|
|
109778
110120
|
}
|
|
109779
|
-
const pkgConfigPath =
|
|
110121
|
+
const pkgConfigPath = join80(workdir, ".nax", "mono", dir, "config.json");
|
|
109780
110122
|
try {
|
|
109781
110123
|
const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
|
|
109782
110124
|
if (status === "skipped") {
|
|
109783
110125
|
console.log(source_default.dim(` ${dir}: skipped (already set)`));
|
|
109784
110126
|
} else {
|
|
109785
|
-
console.log(source_default.green(` ${dir}: ${status} \u2192 ${
|
|
110127
|
+
console.log(source_default.green(` ${dir}: ${status} \u2192 ${join80(".nax", "mono", dir, "config.json")}`));
|
|
109786
110128
|
}
|
|
109787
110129
|
} catch (err) {
|
|
109788
110130
|
console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
|
|
@@ -109800,19 +110142,19 @@ async function detectCommand(options) {
|
|
|
109800
110142
|
// src/commands/logs.ts
|
|
109801
110143
|
init_common();
|
|
109802
110144
|
import { existsSync as existsSync30 } from "fs";
|
|
109803
|
-
import { join as
|
|
110145
|
+
import { join as join83 } from "path";
|
|
109804
110146
|
|
|
109805
110147
|
// src/commands/logs-formatter.ts
|
|
109806
110148
|
init_source();
|
|
109807
110149
|
init_formatter();
|
|
109808
110150
|
import { readdirSync as readdirSync8 } from "fs";
|
|
109809
|
-
import { join as
|
|
110151
|
+
import { join as join82 } from "path";
|
|
109810
110152
|
|
|
109811
110153
|
// src/commands/logs-reader.ts
|
|
109812
110154
|
init_paths3();
|
|
109813
110155
|
import { existsSync as existsSync29, readdirSync as readdirSync7 } from "fs";
|
|
109814
110156
|
import { readdir as readdir3 } from "fs/promises";
|
|
109815
|
-
import { join as
|
|
110157
|
+
import { join as join81 } from "path";
|
|
109816
110158
|
var _logsReaderDeps = {
|
|
109817
110159
|
getRunsDir
|
|
109818
110160
|
};
|
|
@@ -109826,7 +110168,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
109826
110168
|
}
|
|
109827
110169
|
let matched = null;
|
|
109828
110170
|
for (const entry of entries) {
|
|
109829
|
-
const metaPath =
|
|
110171
|
+
const metaPath = join81(runsDir, entry, "meta.json");
|
|
109830
110172
|
try {
|
|
109831
110173
|
const meta3 = await Bun.file(metaPath).json();
|
|
109832
110174
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -109848,14 +110190,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
109848
110190
|
return null;
|
|
109849
110191
|
}
|
|
109850
110192
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
109851
|
-
return
|
|
110193
|
+
return join81(matched.eventsDir, specificFile ?? files[0]);
|
|
109852
110194
|
}
|
|
109853
110195
|
async function selectRunFile(runsDir) {
|
|
109854
110196
|
const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
109855
110197
|
if (files.length === 0) {
|
|
109856
110198
|
return null;
|
|
109857
110199
|
}
|
|
109858
|
-
return
|
|
110200
|
+
return join81(runsDir, files[0]);
|
|
109859
110201
|
}
|
|
109860
110202
|
async function extractRunSummary(filePath) {
|
|
109861
110203
|
const file3 = Bun.file(filePath);
|
|
@@ -109941,7 +110283,7 @@ Runs:
|
|
|
109941
110283
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
109942
110284
|
console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
109943
110285
|
for (const file3 of files) {
|
|
109944
|
-
const filePath =
|
|
110286
|
+
const filePath = join82(runsDir, file3);
|
|
109945
110287
|
const summary = await extractRunSummary(filePath);
|
|
109946
110288
|
const timestamp = file3.replace(".jsonl", "");
|
|
109947
110289
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -110055,7 +110397,7 @@ async function logsCommand(options) {
|
|
|
110055
110397
|
return;
|
|
110056
110398
|
}
|
|
110057
110399
|
const resolved = resolveProject2({ dir: options.dir });
|
|
110058
|
-
const naxDir =
|
|
110400
|
+
const naxDir = join83(resolved.projectDir, ".nax");
|
|
110059
110401
|
const configPath = resolved.configPath;
|
|
110060
110402
|
const configFile = Bun.file(configPath);
|
|
110061
110403
|
const config2 = await configFile.json();
|
|
@@ -110063,8 +110405,8 @@ async function logsCommand(options) {
|
|
|
110063
110405
|
if (!featureName) {
|
|
110064
110406
|
throw new Error("No feature specified in config.json");
|
|
110065
110407
|
}
|
|
110066
|
-
const featureDir =
|
|
110067
|
-
const runsDir =
|
|
110408
|
+
const featureDir = join83(naxDir, "features", featureName);
|
|
110409
|
+
const runsDir = join83(featureDir, "runs");
|
|
110068
110410
|
if (!existsSync30(runsDir)) {
|
|
110069
110411
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
110070
110412
|
}
|
|
@@ -110090,7 +110432,7 @@ init_prd();
|
|
|
110090
110432
|
init_precheck();
|
|
110091
110433
|
init_common();
|
|
110092
110434
|
import { existsSync as existsSync31 } from "fs";
|
|
110093
|
-
import { join as
|
|
110435
|
+
import { join as join84 } from "path";
|
|
110094
110436
|
async function precheckCommand(options) {
|
|
110095
110437
|
const resolved = resolveProject2({
|
|
110096
110438
|
dir: options.dir,
|
|
@@ -110112,9 +110454,9 @@ async function precheckCommand(options) {
|
|
|
110112
110454
|
process.exit(1);
|
|
110113
110455
|
}
|
|
110114
110456
|
}
|
|
110115
|
-
const naxDir =
|
|
110116
|
-
const featureDir =
|
|
110117
|
-
const prdPath =
|
|
110457
|
+
const naxDir = join84(resolved.projectDir, ".nax");
|
|
110458
|
+
const featureDir = join84(naxDir, "features", featureName);
|
|
110459
|
+
const prdPath = join84(featureDir, "prd.json");
|
|
110118
110460
|
if (!existsSync31(featureDir)) {
|
|
110119
110461
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
110120
110462
|
process.exit(1);
|
|
@@ -110142,7 +110484,7 @@ import { dirname as dirname14 } from "path";
|
|
|
110142
110484
|
init_errors();
|
|
110143
110485
|
init_paths3();
|
|
110144
110486
|
import { readdir as readdir4 } from "fs/promises";
|
|
110145
|
-
import { join as
|
|
110487
|
+
import { join as join85 } from "path";
|
|
110146
110488
|
var _discoveryDeps = {
|
|
110147
110489
|
getRunsDir
|
|
110148
110490
|
};
|
|
@@ -110156,7 +110498,7 @@ async function loadMetas(runsDir) {
|
|
|
110156
110498
|
const metas = [];
|
|
110157
110499
|
for (const entry of entries) {
|
|
110158
110500
|
try {
|
|
110159
|
-
const meta3 = await Bun.file(
|
|
110501
|
+
const meta3 = await Bun.file(join85(runsDir, entry, "meta.json")).json();
|
|
110160
110502
|
metas.push(meta3);
|
|
110161
110503
|
} catch {}
|
|
110162
110504
|
}
|
|
@@ -110173,7 +110515,7 @@ async function discoverRun(query, depsArg = _discoveryDeps) {
|
|
|
110173
110515
|
throw new NaxError("No runs registered", "RUN_NOT_FOUND", { runsDir });
|
|
110174
110516
|
}
|
|
110175
110517
|
const latest = metas.reduce((acc, m) => m.runId > acc.runId ? m : acc);
|
|
110176
|
-
return { meta: latest, jsonlPath:
|
|
110518
|
+
return { meta: latest, jsonlPath: join85(latest.eventsDir, `${latest.runId}.jsonl`) };
|
|
110177
110519
|
}
|
|
110178
110520
|
const matched = metas.filter((m) => matches(m, query));
|
|
110179
110521
|
if (matched.length === 0) {
|
|
@@ -110190,7 +110532,7 @@ async function discoverRun(query, depsArg = _discoveryDeps) {
|
|
|
110190
110532
|
});
|
|
110191
110533
|
}
|
|
110192
110534
|
const meta3 = matched[0];
|
|
110193
|
-
return { meta: meta3, jsonlPath:
|
|
110535
|
+
return { meta: meta3, jsonlPath: join85(meta3.eventsDir, `${meta3.runId}.jsonl`) };
|
|
110194
110536
|
}
|
|
110195
110537
|
|
|
110196
110538
|
// src/replay/json.ts
|
|
@@ -110528,11 +110870,11 @@ init_errors();
|
|
|
110528
110870
|
init_checkpoint();
|
|
110529
110871
|
init_runtime();
|
|
110530
110872
|
import { existsSync as existsSync37 } from "fs";
|
|
110531
|
-
import { basename as basename19, join as
|
|
110873
|
+
import { basename as basename19, join as join99 } from "path";
|
|
110532
110874
|
async function defaultCheckpointExists(featureDir) {
|
|
110533
110875
|
if (!featureDir || !existsSync37(featureDir))
|
|
110534
110876
|
return false;
|
|
110535
|
-
return existsSync37(
|
|
110877
|
+
return existsSync37(join99(featureDir, "checkpoint.jsonl"));
|
|
110536
110878
|
}
|
|
110537
110879
|
async function defaultLoadCheckpoints(featureDir) {
|
|
110538
110880
|
return loadCheckpoints(featureDir);
|
|
@@ -110592,12 +110934,12 @@ function registerResumeCommand(program2) {
|
|
|
110592
110934
|
`);
|
|
110593
110935
|
process.exit(1);
|
|
110594
110936
|
}
|
|
110595
|
-
const featureDir =
|
|
110937
|
+
const featureDir = join99(naxDir, "features", cmdOpts.feature);
|
|
110596
110938
|
const deps = {
|
|
110597
110939
|
..._resumeCmdDeps,
|
|
110598
110940
|
runInvocation: async (feature, opts) => {
|
|
110599
110941
|
const config2 = await loadConfig2(naxDir ?? undefined);
|
|
110600
|
-
const prdPath =
|
|
110942
|
+
const prdPath = join99(opts.featureDir ?? "", "prd.json");
|
|
110601
110943
|
if (!existsSync38(prdPath)) {
|
|
110602
110944
|
process.stderr.write(`Feature "${feature}" not found or missing prd.json
|
|
110603
110945
|
`);
|
|
@@ -110609,7 +110951,7 @@ function registerResumeCommand(program2) {
|
|
|
110609
110951
|
applyResumeModeDeps2(opts.featureDir ?? "", "auto");
|
|
110610
110952
|
const projectKey = config2.name?.trim() || basename19(cmdOpts.dir);
|
|
110611
110953
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
110612
|
-
const statusFilePath =
|
|
110954
|
+
const statusFilePath = join99(outputDir, "status.json");
|
|
110613
110955
|
const result = await run2({
|
|
110614
110956
|
prdPath,
|
|
110615
110957
|
workdir: cmdOpts.dir,
|
|
@@ -110644,7 +110986,7 @@ function registerResumeCommand(program2) {
|
|
|
110644
110986
|
init_source();
|
|
110645
110987
|
init_paths3();
|
|
110646
110988
|
import { readdir as readdir6 } from "fs/promises";
|
|
110647
|
-
import { join as
|
|
110989
|
+
import { join as join100 } from "path";
|
|
110648
110990
|
var DEFAULT_LIMIT = 20;
|
|
110649
110991
|
var _runsCmdDeps = {
|
|
110650
110992
|
getRunsDir
|
|
@@ -110701,7 +111043,7 @@ async function runsCommand(options = {}) {
|
|
|
110701
111043
|
}
|
|
110702
111044
|
const rows = [];
|
|
110703
111045
|
for (const entry of entries) {
|
|
110704
|
-
const metaPath =
|
|
111046
|
+
const metaPath = join100(runsDir, entry, "meta.json");
|
|
110705
111047
|
let meta3;
|
|
110706
111048
|
try {
|
|
110707
111049
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -110778,7 +111120,7 @@ async function runsCommand(options = {}) {
|
|
|
110778
111120
|
|
|
110779
111121
|
// src/commands/unlock.ts
|
|
110780
111122
|
init_source();
|
|
110781
|
-
import { join as
|
|
111123
|
+
import { join as join101 } from "path";
|
|
110782
111124
|
function isProcessAlive2(pid) {
|
|
110783
111125
|
try {
|
|
110784
111126
|
process.kill(pid, 0);
|
|
@@ -110793,7 +111135,7 @@ function formatLockAge(ageMs) {
|
|
|
110793
111135
|
}
|
|
110794
111136
|
async function unlockCommand(options) {
|
|
110795
111137
|
const workdir = options.dir ?? process.cwd();
|
|
110796
|
-
const lockPath =
|
|
111138
|
+
const lockPath = join101(workdir, "nax.lock");
|
|
110797
111139
|
const lockFile = Bun.file(lockPath);
|
|
110798
111140
|
const exists = await lockFile.exists();
|
|
110799
111141
|
if (!exists) {
|
|
@@ -116443,8 +116785,8 @@ class Ink {
|
|
|
116443
116785
|
}
|
|
116444
116786
|
}
|
|
116445
116787
|
async waitUntilExit() {
|
|
116446
|
-
this.exitPromise ||= new Promise((
|
|
116447
|
-
this.resolveExitPromise =
|
|
116788
|
+
this.exitPromise ||= new Promise((resolve23, reject2) => {
|
|
116789
|
+
this.resolveExitPromise = resolve23;
|
|
116448
116790
|
this.rejectExitPromise = reject2;
|
|
116449
116791
|
});
|
|
116450
116792
|
if (!this.beforeExitHandler) {
|
|
@@ -118709,7 +119051,7 @@ async function promptForConfirmation(question) {
|
|
|
118709
119051
|
if (!process.stdin.isTTY) {
|
|
118710
119052
|
return true;
|
|
118711
119053
|
}
|
|
118712
|
-
return new Promise((
|
|
119054
|
+
return new Promise((resolve24) => {
|
|
118713
119055
|
process.stdout.write(source_default.bold(`${question} [Y/n] `));
|
|
118714
119056
|
process.stdin.setRawMode(true);
|
|
118715
119057
|
process.stdin.resume();
|
|
@@ -118722,9 +119064,9 @@ async function promptForConfirmation(question) {
|
|
|
118722
119064
|
process.stdout.write(`
|
|
118723
119065
|
`);
|
|
118724
119066
|
if (answer === "n") {
|
|
118725
|
-
|
|
119067
|
+
resolve24(false);
|
|
118726
119068
|
} else {
|
|
118727
|
-
|
|
119069
|
+
resolve24(true);
|
|
118728
119070
|
}
|
|
118729
119071
|
};
|
|
118730
119072
|
process.stdin.on("data", handler);
|
|
@@ -118753,7 +119095,7 @@ Next: nax generate --package ${options.package}`));
|
|
|
118753
119095
|
}
|
|
118754
119096
|
return;
|
|
118755
119097
|
}
|
|
118756
|
-
const naxDir =
|
|
119098
|
+
const naxDir = join105(workdir, ".nax");
|
|
118757
119099
|
if (existsSync39(naxDir) && !options.force) {
|
|
118758
119100
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
118759
119101
|
return;
|
|
@@ -118782,11 +119124,11 @@ Next: nax generate --package ${options.package}`));
|
|
|
118782
119124
|
}
|
|
118783
119125
|
}
|
|
118784
119126
|
}
|
|
118785
|
-
mkdirSync8(
|
|
118786
|
-
mkdirSync8(
|
|
119127
|
+
mkdirSync8(join105(naxDir, "features"), { recursive: true });
|
|
119128
|
+
mkdirSync8(join105(naxDir, "hooks"), { recursive: true });
|
|
118787
119129
|
const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
|
|
118788
|
-
await Bun.write(
|
|
118789
|
-
await Bun.write(
|
|
119130
|
+
await Bun.write(join105(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
|
|
119131
|
+
await Bun.write(join105(naxDir, "hooks.json"), JSON.stringify({
|
|
118790
119132
|
hooks: {
|
|
118791
119133
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
118792
119134
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -118794,12 +119136,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
118794
119136
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
118795
119137
|
}
|
|
118796
119138
|
}, null, 2));
|
|
118797
|
-
await Bun.write(
|
|
119139
|
+
await Bun.write(join105(naxDir, ".gitignore"), `# nax temp files
|
|
118798
119140
|
*.tmp
|
|
118799
119141
|
.paused.json
|
|
118800
119142
|
.nax-verifier-verdict.json
|
|
118801
119143
|
`);
|
|
118802
|
-
await Bun.write(
|
|
119144
|
+
await Bun.write(join105(naxDir, "context.md"), `# Project Context
|
|
118803
119145
|
|
|
118804
119146
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
118805
119147
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -118983,7 +119325,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
118983
119325
|
const cliOverrides = {};
|
|
118984
119326
|
const cliProfiles = options.profile ?? [];
|
|
118985
119327
|
const profileOverride = naxDir ? await resolveRunProfileOverride({
|
|
118986
|
-
prdPath:
|
|
119328
|
+
prdPath: join105(naxDir, "features", options.feature, "prd.json"),
|
|
118987
119329
|
projectRoot: workdir,
|
|
118988
119330
|
cliProfile: cliProfiles,
|
|
118989
119331
|
envProfile: process.env.NAX_PROFILE
|
|
@@ -118996,8 +119338,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
118996
119338
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
118997
119339
|
process.exit(1);
|
|
118998
119340
|
}
|
|
118999
|
-
const featureDir =
|
|
119000
|
-
const prdPath =
|
|
119341
|
+
const featureDir = join105(naxDir, "features", options.feature);
|
|
119342
|
+
const prdPath = join105(featureDir, "prd.json");
|
|
119001
119343
|
if (options.plan && options.from) {
|
|
119002
119344
|
if (existsSync39(prdPath) && !options.force) {
|
|
119003
119345
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -119019,10 +119361,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
119019
119361
|
}
|
|
119020
119362
|
}
|
|
119021
119363
|
try {
|
|
119022
|
-
const planLogDir =
|
|
119364
|
+
const planLogDir = join105(featureDir, "plan");
|
|
119023
119365
|
mkdirSync8(planLogDir, { recursive: true });
|
|
119024
119366
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
119025
|
-
const planLogPath =
|
|
119367
|
+
const planLogPath = join105(planLogDir, `${planLogId}.jsonl`);
|
|
119026
119368
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
119027
119369
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
119028
119370
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -119070,10 +119412,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
119070
119412
|
resetLogger();
|
|
119071
119413
|
const projectKey = config2.name?.trim() || basename21(workdir);
|
|
119072
119414
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
119073
|
-
const runsDir =
|
|
119415
|
+
const runsDir = join105(outputDir, "features", options.feature, "runs");
|
|
119074
119416
|
mkdirSync8(runsDir, { recursive: true });
|
|
119075
119417
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
119076
|
-
const logFilePath =
|
|
119418
|
+
const logFilePath = join105(runsDir, `${runId}.jsonl`);
|
|
119077
119419
|
const isTTY = process.stdout.isTTY ?? false;
|
|
119078
119420
|
const headlessFlag = options.headless ?? false;
|
|
119079
119421
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -119099,7 +119441,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
119099
119441
|
}
|
|
119100
119442
|
config2.execution.costLimit = maxCost;
|
|
119101
119443
|
}
|
|
119102
|
-
const globalNaxDir =
|
|
119444
|
+
const globalNaxDir = join105(homedir3(), ".nax");
|
|
119103
119445
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
119104
119446
|
const eventEmitter = new PipelineEventEmitter;
|
|
119105
119447
|
const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
|
|
@@ -119118,12 +119460,12 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
119118
119460
|
stories: initialStories,
|
|
119119
119461
|
events: eventEmitter,
|
|
119120
119462
|
agentStreamEvents,
|
|
119121
|
-
queueFilePath:
|
|
119463
|
+
queueFilePath: join105(workdir, ".queue.txt")
|
|
119122
119464
|
});
|
|
119123
119465
|
} else {
|
|
119124
119466
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
119125
119467
|
}
|
|
119126
|
-
const statusFilePath =
|
|
119468
|
+
const statusFilePath = join105(outputDir, "status.json");
|
|
119127
119469
|
let parallel;
|
|
119128
119470
|
if (options.parallel !== undefined) {
|
|
119129
119471
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -119186,7 +119528,7 @@ Scheduled run cancelled.`));
|
|
|
119186
119528
|
agentStreamEvents,
|
|
119187
119529
|
resumeMode: options.fresh === true || options.resume === false ? "fresh" : "auto"
|
|
119188
119530
|
});
|
|
119189
|
-
const latestSymlink =
|
|
119531
|
+
const latestSymlink = join105(runsDir, "latest.jsonl");
|
|
119190
119532
|
try {
|
|
119191
119533
|
if (existsSync39(latestSymlink)) {
|
|
119192
119534
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -119283,9 +119625,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
119283
119625
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
119284
119626
|
process.exit(1);
|
|
119285
119627
|
}
|
|
119286
|
-
const featureDir =
|
|
119628
|
+
const featureDir = join105(naxDir, "features", name);
|
|
119287
119629
|
mkdirSync8(featureDir, { recursive: true });
|
|
119288
|
-
await Bun.write(
|
|
119630
|
+
await Bun.write(join105(featureDir, "spec.md"), `# Feature: ${name}
|
|
119289
119631
|
|
|
119290
119632
|
## Overview
|
|
119291
119633
|
|
|
@@ -119318,7 +119660,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
119318
119660
|
|
|
119319
119661
|
<!-- What this feature explicitly does NOT cover. -->
|
|
119320
119662
|
`);
|
|
119321
|
-
await Bun.write(
|
|
119663
|
+
await Bun.write(join105(featureDir, "progress.txt"), `# Progress: ${name}
|
|
119322
119664
|
|
|
119323
119665
|
Created: ${new Date().toISOString()}
|
|
119324
119666
|
|
|
@@ -119344,7 +119686,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
119344
119686
|
console.error(source_default.red("nax not initialized."));
|
|
119345
119687
|
process.exit(1);
|
|
119346
119688
|
}
|
|
119347
|
-
const featuresDir =
|
|
119689
|
+
const featuresDir = join105(naxDir, "features");
|
|
119348
119690
|
if (!existsSync39(featuresDir)) {
|
|
119349
119691
|
console.log(source_default.dim("No features yet."));
|
|
119350
119692
|
return;
|
|
@@ -119359,7 +119701,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
119359
119701
|
Features:
|
|
119360
119702
|
`));
|
|
119361
119703
|
for (const name of entries) {
|
|
119362
|
-
const prdPath =
|
|
119704
|
+
const prdPath = join105(featuresDir, name, "prd.json");
|
|
119363
119705
|
if (existsSync39(prdPath)) {
|
|
119364
119706
|
const prd = await loadPRD(prdPath);
|
|
119365
119707
|
const c = countStories(prd);
|
|
@@ -119432,10 +119774,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
119432
119774
|
cliOverrides.profile = cliProfiles;
|
|
119433
119775
|
}
|
|
119434
119776
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
119435
|
-
const featureLogDir =
|
|
119777
|
+
const featureLogDir = join105(naxDir, "features", options.feature, "plan");
|
|
119436
119778
|
mkdirSync8(featureLogDir, { recursive: true });
|
|
119437
119779
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
119438
|
-
const planLogPath =
|
|
119780
|
+
const planLogPath = join105(featureLogDir, `${planLogId}.jsonl`);
|
|
119439
119781
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
119440
119782
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
119441
119783
|
try {
|