@nathapp/nax 0.67.10 → 0.67.12
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 +1235 -572
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -16833,7 +16833,8 @@ var init_schemas_execution = __esm(() => {
|
|
|
16833
16833
|
});
|
|
16834
16834
|
RectificationConfigSchema = exports_external.object({
|
|
16835
16835
|
enabled: exports_external.boolean().default(true),
|
|
16836
|
-
|
|
16836
|
+
maxAttemptsTotal: exports_external.number().int().min(1).max(50).default(12),
|
|
16837
|
+
maxAttemptsPerStrategy: exports_external.number().int().min(1).max(20).default(3),
|
|
16837
16838
|
fullSuiteTimeoutSeconds: exports_external.number().int().min(10).max(600).default(120),
|
|
16838
16839
|
maxFailureSummaryChars: exports_external.number().int().min(500).max(1e4).default(2000),
|
|
16839
16840
|
abortOnIncreasingFailures: exports_external.boolean().default(true),
|
|
@@ -16845,8 +16846,7 @@ var init_schemas_execution = __esm(() => {
|
|
|
16845
16846
|
enabled: exports_external.boolean().default(true),
|
|
16846
16847
|
timeoutSeconds: exports_external.number().int().min(10).max(600).default(120),
|
|
16847
16848
|
acceptOnTimeout: exports_external.boolean().default(true),
|
|
16848
|
-
mode: exports_external.enum(["deferred", "per-story", "disabled"]).default("deferred")
|
|
16849
|
-
maxRectificationAttempts: exports_external.number().int().min(1).default(2)
|
|
16849
|
+
mode: exports_external.enum(["deferred", "per-story", "disabled"]).default("deferred")
|
|
16850
16850
|
});
|
|
16851
16851
|
SmartTestRunnerConfigSchema = exports_external.object({
|
|
16852
16852
|
enabled: exports_external.boolean().default(true),
|
|
@@ -16928,16 +16928,10 @@ var init_schemas_execution = __esm(() => {
|
|
|
16928
16928
|
autofix: exports_external.object({
|
|
16929
16929
|
enabled: exports_external.boolean().default(true),
|
|
16930
16930
|
maxAttempts: exports_external.number().int().min(1).default(3),
|
|
16931
|
-
maxTotalAttempts: exports_external.number().int().min(1).default(12),
|
|
16932
|
-
rethinkAtAttempt: exports_external.number().int().min(1).default(2),
|
|
16933
|
-
urgencyAtAttempt: exports_external.number().int().min(1).default(3),
|
|
16934
16931
|
enforceTestWriterIsolation: exports_external.boolean().default(true)
|
|
16935
16932
|
}).default({
|
|
16936
16933
|
enabled: true,
|
|
16937
16934
|
maxAttempts: 3,
|
|
16938
|
-
maxTotalAttempts: 12,
|
|
16939
|
-
rethinkAtAttempt: 2,
|
|
16940
|
-
urgencyAtAttempt: 3,
|
|
16941
16935
|
enforceTestWriterIsolation: true
|
|
16942
16936
|
}),
|
|
16943
16937
|
forceExit: exports_external.boolean().default(false),
|
|
@@ -17225,6 +17219,7 @@ var init_schemas_review = __esm(() => {
|
|
|
17225
17219
|
excludePatterns: exports_external.array(exports_external.string()).optional(),
|
|
17226
17220
|
parallel: exports_external.boolean().default(false),
|
|
17227
17221
|
maxConcurrentSessions: exports_external.number().int().min(1).max(4).default(2),
|
|
17222
|
+
acRegroundOnDrop: exports_external.boolean().default(true),
|
|
17228
17223
|
substantiation: exports_external.object({
|
|
17229
17224
|
requote: exports_external.boolean().default(true),
|
|
17230
17225
|
maxRequotes: exports_external.number().int().min(0).default(5)
|
|
@@ -17324,7 +17319,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17324
17319
|
maxStoriesPerFeature: 500,
|
|
17325
17320
|
rectification: {
|
|
17326
17321
|
enabled: true,
|
|
17327
|
-
|
|
17322
|
+
maxAttemptsTotal: 12,
|
|
17323
|
+
maxAttemptsPerStrategy: 3,
|
|
17328
17324
|
fullSuiteTimeoutSeconds: 300,
|
|
17329
17325
|
maxFailureSummaryChars: 2000,
|
|
17330
17326
|
abortOnIncreasingFailures: true,
|
|
@@ -17336,8 +17332,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17336
17332
|
enabled: true,
|
|
17337
17333
|
timeoutSeconds: 300,
|
|
17338
17334
|
acceptOnTimeout: true,
|
|
17339
|
-
mode: "deferred"
|
|
17340
|
-
maxRectificationAttempts: 3
|
|
17335
|
+
mode: "deferred"
|
|
17341
17336
|
},
|
|
17342
17337
|
contextProviderTokenBudget: 2000,
|
|
17343
17338
|
permissionProfile: "unrestricted",
|
|
@@ -17363,9 +17358,6 @@ var init_schemas3 = __esm(() => {
|
|
|
17363
17358
|
autofix: {
|
|
17364
17359
|
enabled: true,
|
|
17365
17360
|
maxAttempts: 3,
|
|
17366
|
-
maxTotalAttempts: 12,
|
|
17367
|
-
rethinkAtAttempt: 2,
|
|
17368
|
-
urgencyAtAttempt: 3,
|
|
17369
17361
|
enforceTestWriterIsolation: true
|
|
17370
17362
|
},
|
|
17371
17363
|
forceExit: false,
|
|
@@ -17462,6 +17454,7 @@ var init_schemas3 = __esm(() => {
|
|
|
17462
17454
|
timeoutMs: 600000,
|
|
17463
17455
|
parallel: false,
|
|
17464
17456
|
maxConcurrentSessions: 2,
|
|
17457
|
+
acRegroundOnDrop: true,
|
|
17465
17458
|
substantiation: {
|
|
17466
17459
|
requote: true,
|
|
17467
17460
|
maxRequotes: 5
|
|
@@ -18678,6 +18671,47 @@ function rejectLegacyAgentKeys(conf) {
|
|
|
18678
18671
|
`);
|
|
18679
18672
|
throw new NaxError(message, "CONFIG_LEGACY_AGENT_KEYS", { stage: "config", legacyKeys });
|
|
18680
18673
|
}
|
|
18674
|
+
function rejectLegacyRectificationKeys(conf) {
|
|
18675
|
+
const legacyKeys = [];
|
|
18676
|
+
const migrationHints = [];
|
|
18677
|
+
const quality = conf.quality;
|
|
18678
|
+
const autofix = quality?.autofix;
|
|
18679
|
+
if (autofix && typeof autofix === "object") {
|
|
18680
|
+
if ("maxTotalAttempts" in autofix) {
|
|
18681
|
+
legacyKeys.push("quality.autofix.maxTotalAttempts");
|
|
18682
|
+
migrationHints.push("- Move `quality.autofix.maxTotalAttempts` \u2192 `execution.rectification.maxAttemptsTotal`");
|
|
18683
|
+
}
|
|
18684
|
+
if ("rethinkAtAttempt" in autofix) {
|
|
18685
|
+
legacyKeys.push("quality.autofix.rethinkAtAttempt");
|
|
18686
|
+
migrationHints.push("- Move `quality.autofix.rethinkAtAttempt` \u2192 `execution.rectification.rethinkAtAttempt`");
|
|
18687
|
+
}
|
|
18688
|
+
if ("urgencyAtAttempt" in autofix) {
|
|
18689
|
+
legacyKeys.push("quality.autofix.urgencyAtAttempt");
|
|
18690
|
+
migrationHints.push("- Move `quality.autofix.urgencyAtAttempt` \u2192 `execution.rectification.urgencyAtAttempt`");
|
|
18691
|
+
}
|
|
18692
|
+
}
|
|
18693
|
+
const execution = conf.execution;
|
|
18694
|
+
const rectification = execution?.rectification;
|
|
18695
|
+
if (rectification && typeof rectification === "object" && "maxRetries" in rectification) {
|
|
18696
|
+
legacyKeys.push("execution.rectification.maxRetries");
|
|
18697
|
+
migrationHints.push("- Rename `execution.rectification.maxRetries` \u2192 `execution.rectification.maxAttemptsTotal` (default changed from 2 to 12)");
|
|
18698
|
+
}
|
|
18699
|
+
const regressionGate = execution?.regressionGate;
|
|
18700
|
+
if (regressionGate && typeof regressionGate === "object" && "maxRectificationAttempts" in regressionGate) {
|
|
18701
|
+
legacyKeys.push("execution.regressionGate.maxRectificationAttempts");
|
|
18702
|
+
migrationHints.push("- Remove `execution.regressionGate.maxRectificationAttempts` \u2014 the regression cycle now shares `execution.rectification.maxAttemptsTotal`");
|
|
18703
|
+
}
|
|
18704
|
+
if (legacyKeys.length === 0)
|
|
18705
|
+
return;
|
|
18706
|
+
const message = [
|
|
18707
|
+
`Invalid configuration \u2014 legacy rectification-cap keys detected: ${legacyKeys.join(", ")}.`,
|
|
18708
|
+
"These were consolidated under `execution.rectification.*` so one config controls the unified",
|
|
18709
|
+
"fix cycle (semantic + adversarial + mechanical + regression). Migrate as follows:",
|
|
18710
|
+
...migrationHints
|
|
18711
|
+
].join(`
|
|
18712
|
+
`);
|
|
18713
|
+
throw new NaxError(message, "CONFIG_LEGACY_RECTIFICATION_KEYS", { stage: "config", legacyKeys });
|
|
18714
|
+
}
|
|
18681
18715
|
function applyBatchModeCompat(conf) {
|
|
18682
18716
|
const routing = conf.routing;
|
|
18683
18717
|
const llm = routing?.llm;
|
|
@@ -18784,6 +18818,7 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
18784
18818
|
return structuredClone(DEFAULT_CONFIG);
|
|
18785
18819
|
}
|
|
18786
18820
|
rejectLegacyAgentKeys(rawConfig);
|
|
18821
|
+
rejectLegacyRectificationKeys(rawConfig);
|
|
18787
18822
|
const result = NaxConfigSchema.safeParse(rawConfig);
|
|
18788
18823
|
if (!result.success) {
|
|
18789
18824
|
const errors3 = result.error.issues.map((err) => {
|
|
@@ -18836,6 +18871,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
18836
18871
|
const rawMerged = deepMergeConfig(merged, profileData);
|
|
18837
18872
|
rawMerged.profile = packageProfile;
|
|
18838
18873
|
rejectLegacyAgentKeys(rawMerged);
|
|
18874
|
+
rejectLegacyRectificationKeys(rawMerged);
|
|
18839
18875
|
const result = NaxConfigSchema.safeParse(rawMerged);
|
|
18840
18876
|
if (result.success) {
|
|
18841
18877
|
merged = result.data;
|
|
@@ -21059,6 +21095,7 @@ class DispatchEventBus {
|
|
|
21059
21095
|
_completedListeners = new Set;
|
|
21060
21096
|
_errorListeners = new Set;
|
|
21061
21097
|
_reviewDecisionListeners = new Set;
|
|
21098
|
+
_reviewRepromptListeners = new Set;
|
|
21062
21099
|
onDispatch(l) {
|
|
21063
21100
|
this._dispatchListeners.add(l);
|
|
21064
21101
|
return () => this._dispatchListeners.delete(l);
|
|
@@ -21075,6 +21112,10 @@ class DispatchEventBus {
|
|
|
21075
21112
|
this._reviewDecisionListeners.add(l);
|
|
21076
21113
|
return () => this._reviewDecisionListeners.delete(l);
|
|
21077
21114
|
}
|
|
21115
|
+
onReviewReprompt(l) {
|
|
21116
|
+
this._reviewRepromptListeners.add(l);
|
|
21117
|
+
return () => this._reviewRepromptListeners.delete(l);
|
|
21118
|
+
}
|
|
21078
21119
|
emitDispatch(event) {
|
|
21079
21120
|
for (const l of this._dispatchListeners) {
|
|
21080
21121
|
try {
|
|
@@ -21111,6 +21152,15 @@ class DispatchEventBus {
|
|
|
21111
21152
|
}
|
|
21112
21153
|
}
|
|
21113
21154
|
}
|
|
21155
|
+
emitReviewReprompt(event) {
|
|
21156
|
+
for (const l of this._reviewRepromptListeners) {
|
|
21157
|
+
try {
|
|
21158
|
+
l(event);
|
|
21159
|
+
} catch (err) {
|
|
21160
|
+
getSafeLogger()?.warn("dispatch-bus", "review-reprompt-listener threw", { error: errorMessage(err) });
|
|
21161
|
+
}
|
|
21162
|
+
}
|
|
21163
|
+
}
|
|
21114
21164
|
}
|
|
21115
21165
|
var init_dispatch_events = __esm(() => {
|
|
21116
21166
|
init_logger2();
|
|
@@ -30342,78 +30392,6 @@ function truncate(s, max) {
|
|
|
30342
30392
|
var MAX_BLOCK_CHARS = 6000;
|
|
30343
30393
|
|
|
30344
30394
|
// src/prompts/builders/review-builder.ts
|
|
30345
|
-
class ReviewPromptBuilder {
|
|
30346
|
-
buildSemanticReviewPrompt(story, semanticConfig, options) {
|
|
30347
|
-
const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
30348
|
-
`);
|
|
30349
|
-
const customRulesBlock = semanticConfig.rules.length > 0 ? `
|
|
30350
|
-
## Additional Review Rules
|
|
30351
|
-
${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
30352
|
-
`)}
|
|
30353
|
-
` : "";
|
|
30354
|
-
const priorIterationsBlock = buildPriorIterationsBlock(options.priorSemanticIterations ?? []);
|
|
30355
|
-
let diffSection;
|
|
30356
|
-
if (options.mode === "ref") {
|
|
30357
|
-
diffSection = buildRefDiffSection(options.storyGitRef ?? "", options.stat ?? "", options.excludePatterns ?? []);
|
|
30358
|
-
} else {
|
|
30359
|
-
diffSection = buildEmbeddedDiffSection(options.diff ?? "");
|
|
30360
|
-
}
|
|
30361
|
-
const core2 = `${SEMANTIC_ROLE}
|
|
30362
|
-
|
|
30363
|
-
## Story: ${story.title}
|
|
30364
|
-
|
|
30365
|
-
### Description
|
|
30366
|
-
${story.description}
|
|
30367
|
-
|
|
30368
|
-
### Acceptance Criteria
|
|
30369
|
-
${acList}
|
|
30370
|
-
${customRulesBlock}${priorIterationsBlock}
|
|
30371
|
-
${SEMANTIC_INSTRUCTIONS}
|
|
30372
|
-
${SEMANTIC_OUTPUT_SCHEMA}
|
|
30373
|
-
|
|
30374
|
-
${diffSection}`;
|
|
30375
|
-
return wrapJsonPrompt(core2);
|
|
30376
|
-
}
|
|
30377
|
-
static jsonRetry() {
|
|
30378
|
-
return `Your previous response could not be parsed as valid JSON.
|
|
30379
|
-
` + `Output ONLY the JSON object from your review \u2014 no markdown fences, no explanation.
|
|
30380
|
-
` + "The object must start with { and end with }.";
|
|
30381
|
-
}
|
|
30382
|
-
static jsonRetryCondensed(opts) {
|
|
30383
|
-
const threshold = opts?.blockingThreshold ?? "error";
|
|
30384
|
-
const advisoryCap = opts?.advisoryCap ?? 3;
|
|
30385
|
-
const blockingList = threshold === "error" ? '"error"' : threshold === "warning" ? '"error" and "warning"' : '"error", "warning", and "info"';
|
|
30386
|
-
const blockingClause = threshold === "info" ? "Include ALL findings \u2014 do not drop any by severity." : `Include ALL findings with severity ${blockingList} (these are blocking \u2014 do not drop them).`;
|
|
30387
|
-
const advisoryClause = threshold === "info" ? "If your response would still exceed limits, prioritize the highest-severity findings first." : `Below that, include at most ${advisoryCap} additional findings (highest severity first).`;
|
|
30388
|
-
return `Your previous response was truncated and could not be parsed as valid JSON.
|
|
30389
|
-
Respond with a condensed summary:
|
|
30390
|
-
- ${blockingClause}
|
|
30391
|
-
- ${advisoryClause}
|
|
30392
|
-
- Keep \`verifiedBy\` for every finding. If \`verifiedBy.observed\` is long, abbreviate it to one line \u2014 never drop the field.
|
|
30393
|
-
Output ONLY a complete, valid JSON object. It must start with { and end with }.
|
|
30394
|
-
Schema: {"passed": boolean, "findings": [{"severity": string, "category": string, "file": string, "line": number, "issue": string, "suggestion": string, "verifiedBy": {"command": string, "file": string, "line": number, "observed": string}}]}`;
|
|
30395
|
-
}
|
|
30396
|
-
static requoteVerbatim(opts) {
|
|
30397
|
-
const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
|
|
30398
|
-
const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
|
|
30399
|
-
return `Your previous verifiedBy.observed value did not match the referenced file on disk.
|
|
30400
|
-
|
|
30401
|
-
You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
|
|
30402
|
-
|
|
30403
|
-
Return ONLY this JSON object:
|
|
30404
|
-
{"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
|
|
30405
|
-
|
|
30406
|
-
Finding issue: ${opts.finding.issue}
|
|
30407
|
-
Referenced file: ${file3}
|
|
30408
|
-
Referenced line: ${line}
|
|
30409
|
-
|
|
30410
|
-
Rules:
|
|
30411
|
-
- Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
|
|
30412
|
-
- observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
|
|
30413
|
-
- If after reading the file you cannot find anything that proves the claim, set observed to "".
|
|
30414
|
-
- Do not return a full review. Do not include markdown fences or explanation.`;
|
|
30415
|
-
}
|
|
30416
|
-
}
|
|
30417
30395
|
function buildEmbeddedDiffSection(diff) {
|
|
30418
30396
|
return `## Git Diff (production code only \u2014 test files excluded)
|
|
30419
30397
|
|
|
@@ -30493,9 +30471,114 @@ Notes:
|
|
|
30493
30471
|
- \`acIndex\` is required when severity is "error" (1-based, into the Acceptance Criteria list above).
|
|
30494
30472
|
- \`acQuote\` is optional advisory metadata for human auditors \u2014 not validated.
|
|
30495
30473
|
- Omit both for "warning", "info", "unverifiable".
|
|
30496
|
-
If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }
|
|
30474
|
+
If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }.`, ReviewPromptBuilder;
|
|
30497
30475
|
var init_review_builder = __esm(() => {
|
|
30498
30476
|
SEMANTIC_ROLE = "You are a semantic code reviewer with access to the repository files. " + "Your job is to walk each acceptance criterion (AC) and judge whether the production code fulfills it \u2014 fully, partially, or not at all. " + "Test coverage gaps and convention/lint issues are out of scope \u2014 adversarial review and lint/typecheck handle those.";
|
|
30477
|
+
ReviewPromptBuilder = class ReviewPromptBuilder {
|
|
30478
|
+
buildSemanticReviewPrompt(story, semanticConfig, options) {
|
|
30479
|
+
const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
30480
|
+
`);
|
|
30481
|
+
const customRulesBlock = semanticConfig.rules.length > 0 ? `
|
|
30482
|
+
## Additional Review Rules
|
|
30483
|
+
${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
30484
|
+
`)}
|
|
30485
|
+
` : "";
|
|
30486
|
+
const priorIterationsBlock = buildPriorIterationsBlock(options.priorSemanticIterations ?? []);
|
|
30487
|
+
let diffSection;
|
|
30488
|
+
if (options.mode === "ref") {
|
|
30489
|
+
diffSection = buildRefDiffSection(options.storyGitRef ?? "", options.stat ?? "", options.excludePatterns ?? []);
|
|
30490
|
+
} else {
|
|
30491
|
+
diffSection = buildEmbeddedDiffSection(options.diff ?? "");
|
|
30492
|
+
}
|
|
30493
|
+
const core2 = `${SEMANTIC_ROLE}
|
|
30494
|
+
|
|
30495
|
+
## Story: ${story.title}
|
|
30496
|
+
|
|
30497
|
+
### Description
|
|
30498
|
+
${story.description}
|
|
30499
|
+
|
|
30500
|
+
### Acceptance Criteria
|
|
30501
|
+
${acList}
|
|
30502
|
+
${customRulesBlock}${priorIterationsBlock}
|
|
30503
|
+
${SEMANTIC_INSTRUCTIONS}
|
|
30504
|
+
${SEMANTIC_OUTPUT_SCHEMA}
|
|
30505
|
+
|
|
30506
|
+
${diffSection}`;
|
|
30507
|
+
return wrapJsonPrompt(core2);
|
|
30508
|
+
}
|
|
30509
|
+
static jsonRetry() {
|
|
30510
|
+
return `Your previous response could not be parsed as valid JSON.
|
|
30511
|
+
` + `Output ONLY the JSON object from your review \u2014 no markdown fences, no explanation.
|
|
30512
|
+
` + "The object must start with { and end with }.";
|
|
30513
|
+
}
|
|
30514
|
+
static jsonRetryCondensed(opts) {
|
|
30515
|
+
const threshold = opts?.blockingThreshold ?? "error";
|
|
30516
|
+
const advisoryCap = opts?.advisoryCap ?? 3;
|
|
30517
|
+
const blockingList = threshold === "error" ? '"error"' : threshold === "warning" ? '"error" and "warning"' : '"error", "warning", and "info"';
|
|
30518
|
+
const blockingClause = threshold === "info" ? "Include ALL findings \u2014 do not drop any by severity." : `Include ALL findings with severity ${blockingList} (these are blocking \u2014 do not drop them).`;
|
|
30519
|
+
const advisoryClause = threshold === "info" ? "If your response would still exceed limits, prioritize the highest-severity findings first." : `Below that, include at most ${advisoryCap} additional findings (highest severity first).`;
|
|
30520
|
+
return `Your previous response was truncated and could not be parsed as valid JSON.
|
|
30521
|
+
Respond with a condensed summary:
|
|
30522
|
+
- ${blockingClause}
|
|
30523
|
+
- ${advisoryClause}
|
|
30524
|
+
- Keep \`verifiedBy\` for every finding. If \`verifiedBy.observed\` is long, abbreviate it to one line \u2014 never drop the field.
|
|
30525
|
+
Output ONLY a complete, valid JSON object. It must start with { and end with }.
|
|
30526
|
+
Schema: {"passed": boolean, "findings": [{"severity": string, "category": string, "file": string, "line": number, "issue": string, "suggestion": string, "verifiedBy": {"command": string, "file": string, "line": number, "observed": string}}]}`;
|
|
30527
|
+
}
|
|
30528
|
+
static requoteVerbatim(opts) {
|
|
30529
|
+
const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
|
|
30530
|
+
const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
|
|
30531
|
+
return `Your previous verifiedBy.observed value did not match the referenced file on disk.
|
|
30532
|
+
|
|
30533
|
+
You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
|
|
30534
|
+
|
|
30535
|
+
Return ONLY this JSON object:
|
|
30536
|
+
{"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
|
|
30537
|
+
|
|
30538
|
+
Finding issue: ${opts.finding.issue}
|
|
30539
|
+
Referenced file: ${file3}
|
|
30540
|
+
Referenced line: ${line}
|
|
30541
|
+
|
|
30542
|
+
Rules:
|
|
30543
|
+
- Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
|
|
30544
|
+
- observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
|
|
30545
|
+
- If after reading the file you cannot find anything that proves the claim, set observed to "".
|
|
30546
|
+
- Do not return a full review. Do not include markdown fences or explanation.`;
|
|
30547
|
+
}
|
|
30548
|
+
static DROP_CODE_MESSAGES_MINIMAL = {
|
|
30549
|
+
missing_ac_index: "no `acIndex` field was provided \u2014 every blocking finding must cite an AC by 1-based index",
|
|
30550
|
+
ac_index_out_of_range: "`acIndex` is 0 or larger than the AC list \u2014 ACs are 1-indexed; the lowest valid value is 1"
|
|
30551
|
+
};
|
|
30552
|
+
static regroundDroppedFindings(opts) {
|
|
30553
|
+
const { drops, acceptanceCriteria } = opts;
|
|
30554
|
+
if (drops.length === 0)
|
|
30555
|
+
return "";
|
|
30556
|
+
const firstDrop = drops[0];
|
|
30557
|
+
const codeMessage = ReviewPromptBuilder.DROP_CODE_MESSAGES_MINIMAL[firstDrop.code];
|
|
30558
|
+
const acList = acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
30559
|
+
`);
|
|
30560
|
+
return `Your previous review produced ${drops.length} finding${drops.length > 1 ? "s" : ""} that ${drops.length > 1 ? "were" : "was"} dropped because:
|
|
30561
|
+
|
|
30562
|
+
${codeMessage}
|
|
30563
|
+
|
|
30564
|
+
The dropped finding${drops.length > 1 ? "s" : ""} ${drops.length > 1 ? "are" : "is"}:
|
|
30565
|
+
${drops.map((d, i) => `${i + 1}. [${d.finding.severity}] ${d.finding.issue}`).join(`
|
|
30566
|
+
`)}
|
|
30567
|
+
|
|
30568
|
+
Please re-review the code and re-issue any valid findings. For each finding you re-issue:
|
|
30569
|
+
- You MUST include a valid \`acIndex\` (1-based index into the AC list below)
|
|
30570
|
+
- You MUST include a \`verifiedBy\` field with verified evidence
|
|
30571
|
+
|
|
30572
|
+
## Acceptance Criteria
|
|
30573
|
+
${acList}
|
|
30574
|
+
|
|
30575
|
+
## Rules
|
|
30576
|
+
- If a finding's locus (file / symbol) is not named in any AC bullet, downgrade it to \`"info"\` or \`"warning"\`
|
|
30577
|
+
- Only re-issue findings that are genuinely substantiated by the code and constrained by an AC
|
|
30578
|
+
- Return ONLY a JSON object with the same shape as before:
|
|
30579
|
+
{"passed":true|false,"findings":[...]}`;
|
|
30580
|
+
}
|
|
30581
|
+
};
|
|
30499
30582
|
});
|
|
30500
30583
|
|
|
30501
30584
|
// src/prompts/builders/adversarial-review-builder.ts
|
|
@@ -30578,92 +30661,6 @@ ${diff}\`\`\`
|
|
|
30578
30661
|
|
|
30579
30662
|
`;
|
|
30580
30663
|
}
|
|
30581
|
-
|
|
30582
|
-
class AdversarialReviewPromptBuilder {
|
|
30583
|
-
buildAdversarialReviewPrompt(story, config2, options) {
|
|
30584
|
-
const {
|
|
30585
|
-
mode,
|
|
30586
|
-
diff,
|
|
30587
|
-
storyGitRef,
|
|
30588
|
-
stat,
|
|
30589
|
-
testInventory,
|
|
30590
|
-
excludePatterns,
|
|
30591
|
-
testGlobs,
|
|
30592
|
-
refExcludePatterns,
|
|
30593
|
-
priorAdversarialIterations,
|
|
30594
|
-
blockingThreshold
|
|
30595
|
-
} = options;
|
|
30596
|
-
const priorFindingsBlock = buildPriorIterationsBlock(priorAdversarialIterations ?? []);
|
|
30597
|
-
const storyBlock = `## Story Under Review
|
|
30598
|
-
|
|
30599
|
-
**ID:** ${story.id}
|
|
30600
|
-
**Title:** ${story.title}
|
|
30601
|
-
**Description:** ${story.description || "(none)"}
|
|
30602
|
-
|
|
30603
|
-
**Acceptance Criteria:**
|
|
30604
|
-
${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
30605
|
-
`)}
|
|
30606
|
-
|
|
30607
|
-
`;
|
|
30608
|
-
const customRulesBlock = config2.rules.length > 0 ? `## Project-Specific Adversarial Rules
|
|
30609
|
-
|
|
30610
|
-
${config2.rules.map((r) => `- ${r}`).join(`
|
|
30611
|
-
`)}
|
|
30612
|
-
|
|
30613
|
-
` : "";
|
|
30614
|
-
let diffBlock;
|
|
30615
|
-
if (mode === "ref" && storyGitRef) {
|
|
30616
|
-
diffBlock = buildAdversarialRefDiffSection(storyGitRef, stat, excludePatterns ?? [], testGlobs ?? [], refExcludePatterns ?? []);
|
|
30617
|
-
} else if (mode === "embedded" && diff) {
|
|
30618
|
-
diffBlock = buildAdversarialEmbeddedDiffSection(diff, testInventory);
|
|
30619
|
-
} else {
|
|
30620
|
-
diffBlock = `## Diff
|
|
30621
|
-
|
|
30622
|
-
(No diff available \u2014 review based on story context only)
|
|
30623
|
-
|
|
30624
|
-
`;
|
|
30625
|
-
}
|
|
30626
|
-
return [
|
|
30627
|
-
ADVERSARIAL_ROLE,
|
|
30628
|
-
`
|
|
30629
|
-
|
|
30630
|
-
`,
|
|
30631
|
-
priorFindingsBlock,
|
|
30632
|
-
storyBlock,
|
|
30633
|
-
ADVERSARIAL_INSTRUCTIONS,
|
|
30634
|
-
`
|
|
30635
|
-
|
|
30636
|
-
`,
|
|
30637
|
-
customRulesBlock,
|
|
30638
|
-
buildBlockingThresholdBlock(blockingThreshold ?? "error"),
|
|
30639
|
-
OUTPUT_SCHEMA,
|
|
30640
|
-
`
|
|
30641
|
-
|
|
30642
|
-
`,
|
|
30643
|
-
diffBlock
|
|
30644
|
-
].join("");
|
|
30645
|
-
}
|
|
30646
|
-
static requoteVerbatim(opts) {
|
|
30647
|
-
const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
|
|
30648
|
-
const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
|
|
30649
|
-
return `Your previous verifiedBy.observed value did not match the referenced file on disk.
|
|
30650
|
-
|
|
30651
|
-
You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
|
|
30652
|
-
|
|
30653
|
-
Return ONLY this JSON object:
|
|
30654
|
-
{"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
|
|
30655
|
-
|
|
30656
|
-
Finding issue: ${opts.finding.issue}
|
|
30657
|
-
Referenced file: ${file3}
|
|
30658
|
-
Referenced line: ${line}
|
|
30659
|
-
|
|
30660
|
-
Rules:
|
|
30661
|
-
- Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
|
|
30662
|
-
- observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
|
|
30663
|
-
- If after reading the file you cannot find anything that proves the claim, set observed to "".
|
|
30664
|
-
- Do not return a full review. Do not include markdown fences or explanation.`;
|
|
30665
|
-
}
|
|
30666
|
-
}
|
|
30667
30664
|
var ADVERSARIAL_ROLE = `You are an adversarial code reviewer with full access to the repository.
|
|
30668
30665
|
|
|
30669
30666
|
Your job is NOT to re-verify that the code satisfies the acceptance criteria \u2014 semantic review owns that question. Don't re-litigate AC correctness.
|
|
@@ -30772,8 +30769,130 @@ Worked example:
|
|
|
30772
30769
|
**Scope constraints are not Acceptance Criteria:**
|
|
30773
30770
|
The story description may contain a "Scope" section with "In:" and "Out:" bullets. These are implementation guidelines, not ACs. A finding about code changed outside the stated scope (e.g., a file listed under "Out:") cannot cite a scope constraint as its \`acQuote\`/\`acIndex\` because scope text is not in the numbered AC list. Emit scope-violation findings as \`"warning"\` \u2014 never \`"error"\`. Never use \`acIndex: 0\`; \`acIndex\` is 1-based (first AC bullet = 1).
|
|
30774
30771
|
|
|
30775
|
-
If you cannot find an AC that names the **specific symbol** in your finding, downgrade to \`"info"\` or \`"warning"\`. A finding dropped by the validator is worse than one correctly classified as advisory
|
|
30776
|
-
var init_adversarial_review_builder = () => {
|
|
30772
|
+
If you cannot find an AC that names the **specific symbol** in your finding, downgrade to \`"info"\` or \`"warning"\`. A finding dropped by the validator is worse than one correctly classified as advisory.`, AdversarialReviewPromptBuilder;
|
|
30773
|
+
var init_adversarial_review_builder = __esm(() => {
|
|
30774
|
+
AdversarialReviewPromptBuilder = class AdversarialReviewPromptBuilder {
|
|
30775
|
+
buildAdversarialReviewPrompt(story, config2, options) {
|
|
30776
|
+
const {
|
|
30777
|
+
mode,
|
|
30778
|
+
diff,
|
|
30779
|
+
storyGitRef,
|
|
30780
|
+
stat,
|
|
30781
|
+
testInventory,
|
|
30782
|
+
excludePatterns,
|
|
30783
|
+
testGlobs,
|
|
30784
|
+
refExcludePatterns,
|
|
30785
|
+
priorAdversarialIterations,
|
|
30786
|
+
blockingThreshold
|
|
30787
|
+
} = options;
|
|
30788
|
+
const priorFindingsBlock = buildPriorIterationsBlock(priorAdversarialIterations ?? []);
|
|
30789
|
+
const storyBlock = `## Story Under Review
|
|
30790
|
+
|
|
30791
|
+
**ID:** ${story.id}
|
|
30792
|
+
**Title:** ${story.title}
|
|
30793
|
+
**Description:** ${story.description || "(none)"}
|
|
30794
|
+
|
|
30795
|
+
**Acceptance Criteria:**
|
|
30796
|
+
${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
30797
|
+
`)}
|
|
30798
|
+
|
|
30799
|
+
`;
|
|
30800
|
+
const customRulesBlock = config2.rules.length > 0 ? `## Project-Specific Adversarial Rules
|
|
30801
|
+
|
|
30802
|
+
${config2.rules.map((r) => `- ${r}`).join(`
|
|
30803
|
+
`)}
|
|
30804
|
+
|
|
30805
|
+
` : "";
|
|
30806
|
+
let diffBlock;
|
|
30807
|
+
if (mode === "ref" && storyGitRef) {
|
|
30808
|
+
diffBlock = buildAdversarialRefDiffSection(storyGitRef, stat, excludePatterns ?? [], testGlobs ?? [], refExcludePatterns ?? []);
|
|
30809
|
+
} else if (mode === "embedded" && diff) {
|
|
30810
|
+
diffBlock = buildAdversarialEmbeddedDiffSection(diff, testInventory);
|
|
30811
|
+
} else {
|
|
30812
|
+
diffBlock = `## Diff
|
|
30813
|
+
|
|
30814
|
+
(No diff available \u2014 review based on story context only)
|
|
30815
|
+
|
|
30816
|
+
`;
|
|
30817
|
+
}
|
|
30818
|
+
return [
|
|
30819
|
+
ADVERSARIAL_ROLE,
|
|
30820
|
+
`
|
|
30821
|
+
|
|
30822
|
+
`,
|
|
30823
|
+
priorFindingsBlock,
|
|
30824
|
+
storyBlock,
|
|
30825
|
+
ADVERSARIAL_INSTRUCTIONS,
|
|
30826
|
+
`
|
|
30827
|
+
|
|
30828
|
+
`,
|
|
30829
|
+
customRulesBlock,
|
|
30830
|
+
buildBlockingThresholdBlock(blockingThreshold ?? "error"),
|
|
30831
|
+
OUTPUT_SCHEMA,
|
|
30832
|
+
`
|
|
30833
|
+
|
|
30834
|
+
`,
|
|
30835
|
+
diffBlock
|
|
30836
|
+
].join("");
|
|
30837
|
+
}
|
|
30838
|
+
static requoteVerbatim(opts) {
|
|
30839
|
+
const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
|
|
30840
|
+
const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
|
|
30841
|
+
return `Your previous verifiedBy.observed value did not match the referenced file on disk.
|
|
30842
|
+
|
|
30843
|
+
You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
|
|
30844
|
+
|
|
30845
|
+
Return ONLY this JSON object:
|
|
30846
|
+
{"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
|
|
30847
|
+
|
|
30848
|
+
Finding issue: ${opts.finding.issue}
|
|
30849
|
+
Referenced file: ${file3}
|
|
30850
|
+
Referenced line: ${line}
|
|
30851
|
+
|
|
30852
|
+
Rules:
|
|
30853
|
+
- Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
|
|
30854
|
+
- observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
|
|
30855
|
+
- If after reading the file you cannot find anything that proves the claim, set observed to "".
|
|
30856
|
+
- Do not return a full review. Do not include markdown fences or explanation.`;
|
|
30857
|
+
}
|
|
30858
|
+
static DROP_CODE_MESSAGES_QUOTE = {
|
|
30859
|
+
missing_ac_quote: "no `acQuote` field was provided \u2014 every blocking finding must cite an AC",
|
|
30860
|
+
ac_index_out_of_range: "`acIndex` is 0 or larger than the AC list \u2014 ACs are 1-indexed; the lowest valid value is 1",
|
|
30861
|
+
ac_quote_not_substring: "`acQuote` text does not appear verbatim in any AC bullet \u2014 copy the AC text character-for-character",
|
|
30862
|
+
ac_quote_does_not_constrain_locus: "the cited AC mentions the file but not the specific symbol your finding flags \u2014 pick a different AC, or downgrade to `info` / `warning`"
|
|
30863
|
+
};
|
|
30864
|
+
static regroundDroppedFindings(opts) {
|
|
30865
|
+
const { drops, acceptanceCriteria } = opts;
|
|
30866
|
+
if (drops.length === 0)
|
|
30867
|
+
return "";
|
|
30868
|
+
const firstDrop = drops[0];
|
|
30869
|
+
const codeMessage = AdversarialReviewPromptBuilder.DROP_CODE_MESSAGES_QUOTE[firstDrop.code] ?? `rejection code: ${firstDrop.code}`;
|
|
30870
|
+
const acList = acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
30871
|
+
`);
|
|
30872
|
+
return `Your previous review produced ${drops.length} finding${drops.length > 1 ? "s" : ""} that ${drops.length > 1 ? "were" : "was"} dropped because:
|
|
30873
|
+
|
|
30874
|
+
${codeMessage}
|
|
30875
|
+
|
|
30876
|
+
The dropped finding${drops.length > 1 ? "s" : ""} ${drops.length > 1 ? "are" : "is"}:
|
|
30877
|
+
${drops.map((d, i) => `${i + 1}. [${d.finding.severity}] ${d.finding.issue}`).join(`
|
|
30878
|
+
`)}
|
|
30879
|
+
|
|
30880
|
+
Please re-review the code and re-issue any valid findings. For each finding you re-issue:
|
|
30881
|
+
- You MUST include a valid \`acQuote\` that appears verbatim in one of the AC bullets below
|
|
30882
|
+
- You MUST include a valid \`acIndex\` (1-based index into the AC list)
|
|
30883
|
+
- The \`acQuote\` must cite the specific symbol you are flagging, not just the file
|
|
30884
|
+
|
|
30885
|
+
## Acceptance Criteria
|
|
30886
|
+
${acList}
|
|
30887
|
+
|
|
30888
|
+
## Rules
|
|
30889
|
+
- If a finding's locus (file / symbol) is not named in any AC bullet, downgrade it to \`"info"\` or \`"warning"\`
|
|
30890
|
+
- Only re-issue findings that are genuinely substantiated by the code and constrained by an AC
|
|
30891
|
+
- Return ONLY a JSON object with the same shape as before:
|
|
30892
|
+
{"passed":true|false,"findings":[...]}`;
|
|
30893
|
+
}
|
|
30894
|
+
};
|
|
30895
|
+
});
|
|
30777
30896
|
|
|
30778
30897
|
// src/prompts/builders/acceptance-builder-helpers.ts
|
|
30779
30898
|
function formatTestOutputForFix(rawOutput) {
|
|
@@ -31546,6 +31665,28 @@ function isRecord(value) {
|
|
|
31546
31665
|
var init_requote_response = () => {};
|
|
31547
31666
|
|
|
31548
31667
|
// src/operations/adversarial-review.ts
|
|
31668
|
+
function withRepromptMarker(output, info) {
|
|
31669
|
+
const parsed = tryParseLLMJson(output);
|
|
31670
|
+
if (!parsed || typeof parsed !== "object")
|
|
31671
|
+
return output;
|
|
31672
|
+
return JSON.stringify({ ...parsed, _repromptInfo: info });
|
|
31673
|
+
}
|
|
31674
|
+
function extractRepromptInfo(raw) {
|
|
31675
|
+
if (!raw || typeof raw !== "object")
|
|
31676
|
+
return;
|
|
31677
|
+
const info = raw._repromptInfo;
|
|
31678
|
+
if (!info || typeof info !== "object")
|
|
31679
|
+
return;
|
|
31680
|
+
const i = info;
|
|
31681
|
+
if (typeof i.dropCount !== "number" || typeof i.costUsd !== "number" || typeof i.outcome !== "string") {
|
|
31682
|
+
return;
|
|
31683
|
+
}
|
|
31684
|
+
return {
|
|
31685
|
+
dropCount: i.dropCount,
|
|
31686
|
+
costUsd: i.costUsd,
|
|
31687
|
+
outcome: i.outcome
|
|
31688
|
+
};
|
|
31689
|
+
}
|
|
31549
31690
|
async function requoteBlockingAdversarialFindings(findings, ctx) {
|
|
31550
31691
|
const threshold = ctx.input.blockingThreshold ?? "error";
|
|
31551
31692
|
const maxRequotes = ctx.input.adversarialConfig.substantiation?.maxRequotes ?? DEFAULT_MAX_REQUOTES;
|
|
@@ -31614,6 +31755,69 @@ async function requoteBlockingAdversarialFindings(findings, ctx) {
|
|
|
31614
31755
|
}
|
|
31615
31756
|
return { findings: next, changed, extraCostUsd };
|
|
31616
31757
|
}
|
|
31758
|
+
function evaluateRepromptTrigger(shape, input) {
|
|
31759
|
+
if (input.adversarialConfig.acRegroundOnDrop === false)
|
|
31760
|
+
return { shouldReprompt: false };
|
|
31761
|
+
if (shape.passed)
|
|
31762
|
+
return { shouldReprompt: false };
|
|
31763
|
+
const { accepted, dropped } = filterByAcQuote(shape.findings, input.story.acceptanceCriteria);
|
|
31764
|
+
const threshold = input.blockingThreshold ?? "error";
|
|
31765
|
+
const blockingAccepted = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
31766
|
+
if (blockingAccepted.length > 0)
|
|
31767
|
+
return { shouldReprompt: false };
|
|
31768
|
+
if (dropped.length === 0)
|
|
31769
|
+
return { shouldReprompt: false };
|
|
31770
|
+
return { shouldReprompt: true, acDropped: dropped };
|
|
31771
|
+
}
|
|
31772
|
+
async function performAdversarialReground(turn, firstParsed, drops, ctx) {
|
|
31773
|
+
const threshold = ctx.input.blockingThreshold ?? "error";
|
|
31774
|
+
const acceptanceCriteria = ctx.input.story.acceptanceCriteria;
|
|
31775
|
+
const { accepted: firstAccepted } = filterByAcQuote(firstParsed.findings, acceptanceCriteria);
|
|
31776
|
+
const firstAdvisory = firstAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
|
|
31777
|
+
const repromptPrompt = AdversarialReviewPromptBuilder.regroundDroppedFindings({
|
|
31778
|
+
drops,
|
|
31779
|
+
acceptanceCriteria
|
|
31780
|
+
});
|
|
31781
|
+
const secondTurn = await ctx.send(repromptPrompt);
|
|
31782
|
+
const secondParsed = validateAdversarialShape(tryParseLLMJson(secondTurn.output));
|
|
31783
|
+
const costUsd = (turn.estimatedCostUsd ?? 0) + (secondTurn.estimatedCostUsd ?? 0);
|
|
31784
|
+
const dropCount = drops.length;
|
|
31785
|
+
if (!secondParsed) {
|
|
31786
|
+
return {
|
|
31787
|
+
...turn,
|
|
31788
|
+
output: withRepromptMarker(turn.output, { dropCount, outcome: "parse-failed", costUsd })
|
|
31789
|
+
};
|
|
31790
|
+
}
|
|
31791
|
+
const { accepted: secondAccepted } = filterByAcQuote(secondParsed.findings, acceptanceCriteria);
|
|
31792
|
+
const secondBlocking = secondAccepted.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
31793
|
+
if (secondBlocking.length > 0) {
|
|
31794
|
+
return {
|
|
31795
|
+
...turn,
|
|
31796
|
+
output: JSON.stringify({
|
|
31797
|
+
passed: false,
|
|
31798
|
+
findings: secondParsed.findings,
|
|
31799
|
+
_repromptInfo: { dropCount, outcome: "recovered-blocking", costUsd }
|
|
31800
|
+
}),
|
|
31801
|
+
estimatedCostUsd: costUsd
|
|
31802
|
+
};
|
|
31803
|
+
}
|
|
31804
|
+
if (secondParsed.passed) {
|
|
31805
|
+
const secondAdvisory = secondAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
|
|
31806
|
+
return {
|
|
31807
|
+
...turn,
|
|
31808
|
+
output: JSON.stringify({
|
|
31809
|
+
passed: true,
|
|
31810
|
+
findings: [...firstAdvisory, ...secondAdvisory],
|
|
31811
|
+
_repromptInfo: { dropCount, outcome: "recovered-advisory-only", costUsd }
|
|
31812
|
+
}),
|
|
31813
|
+
estimatedCostUsd: costUsd
|
|
31814
|
+
};
|
|
31815
|
+
}
|
|
31816
|
+
return {
|
|
31817
|
+
...turn,
|
|
31818
|
+
output: withRepromptMarker(turn.output, { dropCount, outcome: "still-dropped", costUsd })
|
|
31819
|
+
};
|
|
31820
|
+
}
|
|
31617
31821
|
var FAIL_OPEN, ADVERSARIAL_REQUOTE_RECOVERED_EVENT = "review.adversarial.finding.requote_recovered", ADVERSARIAL_REQUOTE_FAILED_EVENT = "review.adversarial.finding.requote_failed", DEFAULT_MAX_REQUOTES = 5, adversarialParseRetry = (input) => makeParseRetryStrategy({
|
|
31618
31822
|
validate: (parsed) => validateAdversarialShape(parsed) !== null,
|
|
31619
31823
|
reviewerKind: "adversarial",
|
|
@@ -31654,15 +31858,30 @@ var init_adversarial_review = __esm(() => {
|
|
|
31654
31858
|
const parsed = validateAdversarialShape(tryParseLLMJson(turn.output));
|
|
31655
31859
|
if (!parsed)
|
|
31656
31860
|
return turn;
|
|
31657
|
-
|
|
31658
|
-
if (!requoted.changed)
|
|
31861
|
+
if (ctx.input.mode !== "ref")
|
|
31659
31862
|
return turn;
|
|
31660
|
-
const
|
|
31661
|
-
|
|
31662
|
-
|
|
31663
|
-
|
|
31664
|
-
|
|
31665
|
-
|
|
31863
|
+
const regroundEnabled = ctx.input.adversarialConfig.acRegroundOnDrop !== false;
|
|
31864
|
+
if (regroundEnabled) {
|
|
31865
|
+
const firstShape = { passed: parsed.passed, findings: parsed.findings };
|
|
31866
|
+
const trigger = evaluateRepromptTrigger(firstShape, ctx.input);
|
|
31867
|
+
if (trigger.shouldReprompt) {
|
|
31868
|
+
return await performAdversarialReground(turn, parsed, trigger.acDropped, ctx);
|
|
31869
|
+
}
|
|
31870
|
+
}
|
|
31871
|
+
const requoteEnabled = ctx.input.adversarialConfig.substantiation?.requote ?? true;
|
|
31872
|
+
const maxRequotes = ctx.input.adversarialConfig.substantiation?.maxRequotes ?? DEFAULT_MAX_REQUOTES;
|
|
31873
|
+
if (!requoteEnabled || maxRequotes <= 0)
|
|
31874
|
+
return turn;
|
|
31875
|
+
const requoted = await requoteBlockingAdversarialFindings(parsed.findings, ctx);
|
|
31876
|
+
if (requoted.changed) {
|
|
31877
|
+
const passed = !requoted.findings.some((finding) => isBlockingSeverity(finding.severity, ctx.input.blockingThreshold ?? "error"));
|
|
31878
|
+
return {
|
|
31879
|
+
...turn,
|
|
31880
|
+
output: JSON.stringify({ passed, findings: requoted.findings }),
|
|
31881
|
+
estimatedCostUsd: (turn.estimatedCostUsd ?? 0) + requoted.extraCostUsd
|
|
31882
|
+
};
|
|
31883
|
+
}
|
|
31884
|
+
return turn;
|
|
31666
31885
|
},
|
|
31667
31886
|
build(input, _ctx) {
|
|
31668
31887
|
const base = new AdversarialReviewPromptBuilder().buildAdversarialReviewPrompt(input.story, input.adversarialConfig, {
|
|
@@ -31686,16 +31905,25 @@ var init_adversarial_review = __esm(() => {
|
|
|
31686
31905
|
parse(output, _input, _ctx) {
|
|
31687
31906
|
const raw = tryParseLLMJson(output);
|
|
31688
31907
|
const parsed = validateAdversarialShape(raw);
|
|
31908
|
+
const repromptEvent = extractRepromptInfo(raw);
|
|
31689
31909
|
if (parsed) {
|
|
31690
31910
|
return {
|
|
31691
31911
|
passed: parsed.passed,
|
|
31692
31912
|
findings: parsed.findings,
|
|
31693
31913
|
normalizedFindings: [],
|
|
31694
|
-
acDropped: []
|
|
31914
|
+
acDropped: [],
|
|
31915
|
+
repromptEvent
|
|
31695
31916
|
};
|
|
31696
31917
|
}
|
|
31697
31918
|
if (/"passed"\s*:\s*false/.test(output) && !/"findings"\s*:\s*\[\s*\{/.test(output)) {
|
|
31698
|
-
return {
|
|
31919
|
+
return {
|
|
31920
|
+
passed: false,
|
|
31921
|
+
findings: [],
|
|
31922
|
+
normalizedFindings: [],
|
|
31923
|
+
acDropped: [],
|
|
31924
|
+
looksLikeFail: true,
|
|
31925
|
+
repromptEvent
|
|
31926
|
+
};
|
|
31699
31927
|
}
|
|
31700
31928
|
throw new ParseValidationError("[adversarial-review] parse failed: invalid JSON shape");
|
|
31701
31929
|
},
|
|
@@ -32198,7 +32426,7 @@ async function runAdversarialReview(opts) {
|
|
|
32198
32426
|
} = opts;
|
|
32199
32427
|
const startTime = Date.now();
|
|
32200
32428
|
const logger = getSafeLogger();
|
|
32201
|
-
const effectiveRef = await resolveEffectiveRef(workdir, storyGitRef, story.id);
|
|
32429
|
+
const effectiveRef = await _adversarialDeps.resolveEffectiveRef(workdir, storyGitRef, story.id);
|
|
32202
32430
|
if (!effectiveRef) {
|
|
32203
32431
|
return {
|
|
32204
32432
|
check: "adversarial",
|
|
@@ -32217,7 +32445,7 @@ async function runAdversarialReview(opts) {
|
|
|
32217
32445
|
});
|
|
32218
32446
|
const repoRoot = projectDir ?? workdir;
|
|
32219
32447
|
const packageDir = workdir !== repoRoot ? workdir : undefined;
|
|
32220
|
-
const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
|
|
32448
|
+
const stat = await _adversarialDeps.collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
|
|
32221
32449
|
if (!stat) {
|
|
32222
32450
|
return {
|
|
32223
32451
|
check: "adversarial",
|
|
@@ -32399,6 +32627,16 @@ async function runAdversarialReview(opts) {
|
|
|
32399
32627
|
durationMs: Date.now() - startTime
|
|
32400
32628
|
};
|
|
32401
32629
|
}
|
|
32630
|
+
if (opResult.repromptEvent) {
|
|
32631
|
+
runtime.dispatchEvents.emitReviewReprompt({
|
|
32632
|
+
kind: "review-reprompt-on-drop",
|
|
32633
|
+
storyId: story.id,
|
|
32634
|
+
reviewer: "adversarial",
|
|
32635
|
+
dropCount: opResult.repromptEvent.dropCount,
|
|
32636
|
+
repromptOutcome: opResult.repromptEvent.outcome,
|
|
32637
|
+
costUsd: opResult.repromptEvent.costUsd
|
|
32638
|
+
});
|
|
32639
|
+
}
|
|
32402
32640
|
const threshold = blockingThreshold ?? "error";
|
|
32403
32641
|
const allFindings = opResult.findings;
|
|
32404
32642
|
const blockingFindings = allFindings.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
@@ -32410,7 +32648,7 @@ async function runAdversarialReview(opts) {
|
|
|
32410
32648
|
diffFiles = extractDiffFiles(diff);
|
|
32411
32649
|
diffAvailable = true;
|
|
32412
32650
|
} else {
|
|
32413
|
-
const list = await collectDiffFileList(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
|
|
32651
|
+
const list = await _adversarialDeps.collectDiffFileList(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
|
|
32414
32652
|
if (list === undefined) {
|
|
32415
32653
|
diffFiles = new Set;
|
|
32416
32654
|
diffAvailable = false;
|
|
@@ -32586,7 +32824,10 @@ var init_adversarial = __esm(() => {
|
|
|
32586
32824
|
init_review_audit();
|
|
32587
32825
|
_adversarialDeps = {
|
|
32588
32826
|
writeReviewAudit,
|
|
32589
|
-
callOp
|
|
32827
|
+
callOp,
|
|
32828
|
+
resolveEffectiveRef,
|
|
32829
|
+
collectDiffStat,
|
|
32830
|
+
collectDiffFileList
|
|
32590
32831
|
};
|
|
32591
32832
|
});
|
|
32592
32833
|
|
|
@@ -33289,6 +33530,13 @@ class ScopedStrategy {
|
|
|
33289
33530
|
const durationMs = Date.now() - start;
|
|
33290
33531
|
if (result.success) {
|
|
33291
33532
|
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33533
|
+
logger.info("verify[scoped]", "Scoped tests passed", {
|
|
33534
|
+
storyId: ctx.storyId,
|
|
33535
|
+
passCount: parsed2.passed,
|
|
33536
|
+
durationMs,
|
|
33537
|
+
scopeTestFallback: scopeTestFallback ?? false,
|
|
33538
|
+
isFullSuite
|
|
33539
|
+
});
|
|
33292
33540
|
return makePassResult(ctx.storyId, "scoped", {
|
|
33293
33541
|
rawOutput: result.output,
|
|
33294
33542
|
passCount: parsed2.passed,
|
|
@@ -33297,6 +33545,12 @@ class ScopedStrategy {
|
|
|
33297
33545
|
});
|
|
33298
33546
|
}
|
|
33299
33547
|
if (result.status === "TIMEOUT") {
|
|
33548
|
+
logger.warn("verify[scoped]", "Scoped tests timed out", {
|
|
33549
|
+
storyId: ctx.storyId,
|
|
33550
|
+
durationMs,
|
|
33551
|
+
scopeTestFallback: scopeTestFallback ?? false,
|
|
33552
|
+
isFullSuite
|
|
33553
|
+
});
|
|
33300
33554
|
return makeFailResult(ctx.storyId, "scoped", "TIMEOUT", {
|
|
33301
33555
|
rawOutput: result.output,
|
|
33302
33556
|
durationMs,
|
|
@@ -33305,6 +33559,14 @@ class ScopedStrategy {
|
|
|
33305
33559
|
});
|
|
33306
33560
|
}
|
|
33307
33561
|
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
33562
|
+
logger.warn("verify[scoped]", "Scoped tests failed", {
|
|
33563
|
+
storyId: ctx.storyId,
|
|
33564
|
+
passCount: parsed.passed,
|
|
33565
|
+
failCount: parsed.failed,
|
|
33566
|
+
durationMs,
|
|
33567
|
+
scopeTestFallback: scopeTestFallback ?? false,
|
|
33568
|
+
isFullSuite
|
|
33569
|
+
});
|
|
33308
33570
|
return makeFailResult(ctx.storyId, "scoped", "TEST_FAILURE", {
|
|
33309
33571
|
rawOutput: result.output,
|
|
33310
33572
|
passCount: parsed.passed,
|
|
@@ -35168,6 +35430,91 @@ var init_acceptance_fix = __esm(() => {
|
|
|
35168
35430
|
});
|
|
35169
35431
|
|
|
35170
35432
|
// src/operations/semantic-review.ts
|
|
35433
|
+
function withRepromptMarker2(output, info) {
|
|
35434
|
+
const parsed = tryParseLLMJson(output);
|
|
35435
|
+
if (!parsed || typeof parsed !== "object")
|
|
35436
|
+
return output;
|
|
35437
|
+
return JSON.stringify({ ...parsed, _repromptInfo: info });
|
|
35438
|
+
}
|
|
35439
|
+
function extractRepromptInfo2(raw) {
|
|
35440
|
+
if (!raw || typeof raw !== "object")
|
|
35441
|
+
return;
|
|
35442
|
+
const info = raw._repromptInfo;
|
|
35443
|
+
if (!info || typeof info !== "object")
|
|
35444
|
+
return;
|
|
35445
|
+
const i = info;
|
|
35446
|
+
if (typeof i.dropCount !== "number" || typeof i.costUsd !== "number" || typeof i.outcome !== "string") {
|
|
35447
|
+
return;
|
|
35448
|
+
}
|
|
35449
|
+
return {
|
|
35450
|
+
dropCount: i.dropCount,
|
|
35451
|
+
costUsd: i.costUsd,
|
|
35452
|
+
outcome: i.outcome
|
|
35453
|
+
};
|
|
35454
|
+
}
|
|
35455
|
+
function evaluateRepromptTrigger2(shape, input) {
|
|
35456
|
+
if (input.semanticConfig.acRegroundOnDrop === false)
|
|
35457
|
+
return { shouldReprompt: false };
|
|
35458
|
+
if (shape.passed)
|
|
35459
|
+
return { shouldReprompt: false };
|
|
35460
|
+
const { accepted, dropped } = filterByAcGroundingMinimal(shape.findings, input.story.acceptanceCriteria);
|
|
35461
|
+
const threshold = input.blockingThreshold ?? "error";
|
|
35462
|
+
const blockingAccepted = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
35463
|
+
if (blockingAccepted.length > 0)
|
|
35464
|
+
return { shouldReprompt: false };
|
|
35465
|
+
if (dropped.length === 0)
|
|
35466
|
+
return { shouldReprompt: false };
|
|
35467
|
+
return { shouldReprompt: true, acDropped: dropped };
|
|
35468
|
+
}
|
|
35469
|
+
async function performSemanticReground(turn, firstParsed, drops, ctx) {
|
|
35470
|
+
const threshold = ctx.input.blockingThreshold ?? "error";
|
|
35471
|
+
const acceptanceCriteria = ctx.input.story.acceptanceCriteria;
|
|
35472
|
+
const { accepted: firstAccepted } = filterByAcGroundingMinimal(firstParsed.findings, acceptanceCriteria);
|
|
35473
|
+
const firstAdvisory = firstAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
|
|
35474
|
+
const repromptPrompt = ReviewPromptBuilder.regroundDroppedFindings({
|
|
35475
|
+
drops,
|
|
35476
|
+
acceptanceCriteria
|
|
35477
|
+
});
|
|
35478
|
+
const secondTurn = await ctx.send(repromptPrompt);
|
|
35479
|
+
const secondParsed = validateLLMShape(tryParseLLMJson(secondTurn.output));
|
|
35480
|
+
const costUsd = (turn.estimatedCostUsd ?? 0) + (secondTurn.estimatedCostUsd ?? 0);
|
|
35481
|
+
const dropCount = drops.length;
|
|
35482
|
+
if (!secondParsed) {
|
|
35483
|
+
return {
|
|
35484
|
+
...turn,
|
|
35485
|
+
output: withRepromptMarker2(turn.output, { dropCount, outcome: "parse-failed", costUsd })
|
|
35486
|
+
};
|
|
35487
|
+
}
|
|
35488
|
+
const { accepted: secondAccepted } = filterByAcGroundingMinimal(secondParsed.findings, acceptanceCriteria);
|
|
35489
|
+
const secondBlocking = secondAccepted.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
35490
|
+
if (secondBlocking.length > 0) {
|
|
35491
|
+
return {
|
|
35492
|
+
...turn,
|
|
35493
|
+
output: JSON.stringify({
|
|
35494
|
+
passed: false,
|
|
35495
|
+
findings: secondParsed.findings,
|
|
35496
|
+
_repromptInfo: { dropCount, outcome: "recovered-blocking", costUsd }
|
|
35497
|
+
}),
|
|
35498
|
+
estimatedCostUsd: costUsd
|
|
35499
|
+
};
|
|
35500
|
+
}
|
|
35501
|
+
if (secondParsed.passed) {
|
|
35502
|
+
const secondAdvisory = secondAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
|
|
35503
|
+
return {
|
|
35504
|
+
...turn,
|
|
35505
|
+
output: JSON.stringify({
|
|
35506
|
+
passed: true,
|
|
35507
|
+
findings: [...firstAdvisory, ...secondAdvisory],
|
|
35508
|
+
_repromptInfo: { dropCount, outcome: "recovered-advisory-only", costUsd }
|
|
35509
|
+
}),
|
|
35510
|
+
estimatedCostUsd: costUsd
|
|
35511
|
+
};
|
|
35512
|
+
}
|
|
35513
|
+
return {
|
|
35514
|
+
...turn,
|
|
35515
|
+
output: withRepromptMarker2(turn.output, { dropCount, outcome: "still-dropped", costUsd })
|
|
35516
|
+
};
|
|
35517
|
+
}
|
|
35171
35518
|
async function requoteBlockingFindings(findings, ctx) {
|
|
35172
35519
|
const threshold = ctx.input.blockingThreshold ?? "error";
|
|
35173
35520
|
const maxRequotes = ctx.input.semanticConfig.substantiation?.maxRequotes ?? DEFAULT_MAX_REQUOTES2;
|
|
@@ -35243,14 +35590,24 @@ var FAIL_OPEN2, SEMANTIC_REQUOTE_RECOVERED_EVENT = "review.semantic.finding.requ
|
|
|
35243
35590
|
if (!parsed)
|
|
35244
35591
|
return turn;
|
|
35245
35592
|
const requoted = await requoteBlockingFindings(parsed.findings, ctx);
|
|
35246
|
-
if (
|
|
35593
|
+
if (requoted.changed) {
|
|
35594
|
+
const passed = !requoted.findings.some((finding) => isBlockingSeverity(finding.severity, ctx.input.blockingThreshold ?? "error"));
|
|
35595
|
+
return {
|
|
35596
|
+
...turn,
|
|
35597
|
+
output: JSON.stringify({ passed, findings: requoted.findings }),
|
|
35598
|
+
estimatedCostUsd: (turn.estimatedCostUsd ?? 0) + requoted.extraCostUsd
|
|
35599
|
+
};
|
|
35600
|
+
}
|
|
35601
|
+
if (ctx.input.mode !== "ref")
|
|
35247
35602
|
return turn;
|
|
35248
|
-
const
|
|
35249
|
-
|
|
35250
|
-
|
|
35251
|
-
|
|
35252
|
-
|
|
35253
|
-
|
|
35603
|
+
const regroundEnabled = ctx.input.semanticConfig.acRegroundOnDrop !== false;
|
|
35604
|
+
if (!regroundEnabled)
|
|
35605
|
+
return turn;
|
|
35606
|
+
const firstShape = { passed: parsed.passed, findings: requoted.findings };
|
|
35607
|
+
const trigger = evaluateRepromptTrigger2(firstShape, ctx.input);
|
|
35608
|
+
if (!trigger.shouldReprompt)
|
|
35609
|
+
return turn;
|
|
35610
|
+
return performSemanticReground(turn, firstShape, trigger.acDropped, ctx);
|
|
35254
35611
|
}, semanticReviewOp;
|
|
35255
35612
|
var init_semantic_review = __esm(() => {
|
|
35256
35613
|
init_retry();
|
|
@@ -35259,7 +35616,13 @@ var init_semantic_review = __esm(() => {
|
|
|
35259
35616
|
init_prompts();
|
|
35260
35617
|
init_finding_filters();
|
|
35261
35618
|
init_requote_response();
|
|
35262
|
-
FAIL_OPEN2 = {
|
|
35619
|
+
FAIL_OPEN2 = {
|
|
35620
|
+
passed: true,
|
|
35621
|
+
findings: [],
|
|
35622
|
+
normalizedFindings: [],
|
|
35623
|
+
acDropped: [],
|
|
35624
|
+
failOpen: true
|
|
35625
|
+
};
|
|
35263
35626
|
semanticReviewOp = {
|
|
35264
35627
|
kind: "run",
|
|
35265
35628
|
name: "semantic-review",
|
|
@@ -35276,7 +35639,7 @@ var init_semantic_review = __esm(() => {
|
|
|
35276
35639
|
invalid: () => ReviewPromptBuilder.jsonRetry(),
|
|
35277
35640
|
truncated: () => ReviewPromptBuilder.jsonRetryCondensed({ blockingThreshold: input.blockingThreshold })
|
|
35278
35641
|
},
|
|
35279
|
-
exhaustedFallback: (lastOutput) => /"passed"\s*:\s*false/.test(lastOutput) ? { passed: false, findings: [], normalizedFindings: [], looksLikeFail: true } : FAIL_OPEN2,
|
|
35642
|
+
exhaustedFallback: (lastOutput) => /"passed"\s*:\s*false/.test(lastOutput) ? { passed: false, findings: [], normalizedFindings: [], acDropped: [], looksLikeFail: true } : FAIL_OPEN2,
|
|
35280
35643
|
logContext: { blockingThreshold: input.blockingThreshold ?? "error" }
|
|
35281
35644
|
}),
|
|
35282
35645
|
hopBody: semanticReviewHopBody,
|
|
@@ -35298,15 +35661,25 @@ var init_semantic_review = __esm(() => {
|
|
|
35298
35661
|
parse(output, _input, _ctx) {
|
|
35299
35662
|
const raw = tryParseLLMJson(output);
|
|
35300
35663
|
const parsed = validateLLMShape(raw);
|
|
35664
|
+
const repromptEvent = extractRepromptInfo2(raw);
|
|
35301
35665
|
if (parsed) {
|
|
35302
35666
|
return {
|
|
35303
35667
|
passed: parsed.passed,
|
|
35304
35668
|
findings: parsed.findings,
|
|
35305
|
-
normalizedFindings: []
|
|
35669
|
+
normalizedFindings: [],
|
|
35670
|
+
acDropped: [],
|
|
35671
|
+
repromptEvent
|
|
35306
35672
|
};
|
|
35307
35673
|
}
|
|
35308
35674
|
if (/"passed"\s*:\s*false/.test(output)) {
|
|
35309
|
-
return {
|
|
35675
|
+
return {
|
|
35676
|
+
passed: false,
|
|
35677
|
+
findings: [],
|
|
35678
|
+
normalizedFindings: [],
|
|
35679
|
+
acDropped: [],
|
|
35680
|
+
looksLikeFail: true,
|
|
35681
|
+
repromptEvent
|
|
35682
|
+
};
|
|
35310
35683
|
}
|
|
35311
35684
|
return FAIL_OPEN2;
|
|
35312
35685
|
},
|
|
@@ -35319,14 +35692,15 @@ var init_semantic_review = __esm(() => {
|
|
|
35319
35692
|
const findings = parsed.findings;
|
|
35320
35693
|
const sanitized = sanitizeRefModeFindings(findings, input.mode, threshold);
|
|
35321
35694
|
const substantiated = await substantiateSemanticEvidence(sanitized, input.mode, input.workdir, input.story.id, threshold);
|
|
35322
|
-
const { accepted } = filterByAcGroundingMinimal(substantiated, input.story.acceptanceCriteria);
|
|
35695
|
+
const { accepted, dropped } = filterByAcGroundingMinimal(substantiated, input.story.acceptanceCriteria);
|
|
35323
35696
|
const blocking = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
35324
35697
|
const passed = parsed.passed && blocking.length === 0;
|
|
35325
35698
|
return {
|
|
35326
35699
|
...parsed,
|
|
35327
35700
|
passed,
|
|
35328
35701
|
findings: accepted,
|
|
35329
|
-
normalizedFindings: toReviewFindings(blocking)
|
|
35702
|
+
normalizedFindings: toReviewFindings(blocking),
|
|
35703
|
+
acDropped: dropped
|
|
35330
35704
|
};
|
|
35331
35705
|
}
|
|
35332
35706
|
};
|
|
@@ -35398,6 +35772,16 @@ function parseTestEditDeclarations(output) {
|
|
|
35398
35772
|
}
|
|
35399
35773
|
return result;
|
|
35400
35774
|
}
|
|
35775
|
+
function normaliseWs(s) {
|
|
35776
|
+
return s.replace(/\s+/g, " ").replace(/\s*([(),<>])\s*/g, "$1").replace(/\s*:\s*/g, ": ").trim();
|
|
35777
|
+
}
|
|
35778
|
+
function validatePrdQuote(prdQuote, story) {
|
|
35779
|
+
if (!prdQuote.trim())
|
|
35780
|
+
return false;
|
|
35781
|
+
const needle = normaliseWs(prdQuote);
|
|
35782
|
+
const haystack = normaliseWs([story.description, ...story.acceptanceCriteria].join(" "));
|
|
35783
|
+
return haystack.includes(needle);
|
|
35784
|
+
}
|
|
35401
35785
|
var REASON_RE;
|
|
35402
35786
|
var init_test_edit_declaration = __esm(() => {
|
|
35403
35787
|
REASON_RE = /^TEST_EDIT_REASON:\s*(prd_contract|lint_only|sibling_scope|mock_structure)\s*$/m;
|
|
@@ -37368,7 +37752,7 @@ var init_greenfield_gate = __esm(() => {
|
|
|
37368
37752
|
});
|
|
37369
37753
|
// src/verification/rectification.ts
|
|
37370
37754
|
function shouldRetryRectification(state, config2) {
|
|
37371
|
-
if (state.attempt >= config2.
|
|
37755
|
+
if (state.attempt >= config2.maxAttemptsTotal) {
|
|
37372
37756
|
return false;
|
|
37373
37757
|
}
|
|
37374
37758
|
if (state.lastExitCode !== undefined && state.lastExitCode !== 0 && state.currentFailures === 0) {
|
|
@@ -37471,7 +37855,7 @@ var init_full_suite_gate = __esm(() => {
|
|
|
37471
37855
|
});
|
|
37472
37856
|
|
|
37473
37857
|
// src/operations/full-suite-rectify.ts
|
|
37474
|
-
function makeFullSuiteRectifyStrategy(story) {
|
|
37858
|
+
function makeFullSuiteRectifyStrategy(story, config2) {
|
|
37475
37859
|
return {
|
|
37476
37860
|
name: "full-suite-rectify",
|
|
37477
37861
|
appliesTo: (finding) => finding.source === "test-runner" && finding.category === "failed-test",
|
|
@@ -37481,7 +37865,7 @@ function makeFullSuiteRectifyStrategy(story) {
|
|
|
37481
37865
|
contextMarkdown: RectifierPromptBuilder.failingTestContext(findings)
|
|
37482
37866
|
}),
|
|
37483
37867
|
extractApplied: () => ({ targetFiles: [], summary: "Fixed failing tests" }),
|
|
37484
|
-
maxAttempts:
|
|
37868
|
+
maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
|
|
37485
37869
|
coRun: "exclusive"
|
|
37486
37870
|
};
|
|
37487
37871
|
}
|
|
@@ -37522,7 +37906,7 @@ var init__finding_to_check = __esm(() => {
|
|
|
37522
37906
|
});
|
|
37523
37907
|
|
|
37524
37908
|
// src/operations/autofix-implementer-strategy.ts
|
|
37525
|
-
function makeAutofixImplementerStrategy(story) {
|
|
37909
|
+
function makeAutofixImplementerStrategy(story, config2, sink) {
|
|
37526
37910
|
return {
|
|
37527
37911
|
name: "autofix-implementer",
|
|
37528
37912
|
appliesTo: (f) => f.fixTarget === "source" && IMPLEMENTER_SOURCES.has(f.source),
|
|
@@ -37531,11 +37915,20 @@ function makeAutofixImplementerStrategy(story) {
|
|
|
37531
37915
|
failedChecks: findingsToFailedChecks(findings),
|
|
37532
37916
|
story
|
|
37533
37917
|
}),
|
|
37534
|
-
extractApplied: (output) =>
|
|
37535
|
-
|
|
37536
|
-
|
|
37537
|
-
|
|
37538
|
-
|
|
37918
|
+
extractApplied: (output) => {
|
|
37919
|
+
for (const decl of output.testEditDeclarations) {
|
|
37920
|
+
if (decl.reason === "mock_structure" && decl.files && decl.reasonDetail) {
|
|
37921
|
+
sink.mockHandoffs.push({ files: decl.files, reasonDetail: decl.reasonDetail });
|
|
37922
|
+
} else if (decl.reason !== "mock_structure") {
|
|
37923
|
+
sink.testEdits.push(decl);
|
|
37924
|
+
}
|
|
37925
|
+
}
|
|
37926
|
+
return {
|
|
37927
|
+
summary: output.unresolvedReason ?? "",
|
|
37928
|
+
unresolved: output.unresolvedReason
|
|
37929
|
+
};
|
|
37930
|
+
},
|
|
37931
|
+
maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
|
|
37539
37932
|
coRun: "co-run-sequential"
|
|
37540
37933
|
};
|
|
37541
37934
|
}
|
|
@@ -37547,17 +37940,43 @@ var init_autofix_implementer_strategy = __esm(() => {
|
|
|
37547
37940
|
});
|
|
37548
37941
|
|
|
37549
37942
|
// src/operations/autofix-test-writer-strategy.ts
|
|
37550
|
-
function makeAutofixTestWriterStrategy(story, config2) {
|
|
37943
|
+
function makeAutofixTestWriterStrategy(story, config2, sink) {
|
|
37551
37944
|
return {
|
|
37552
37945
|
name: "autofix-test-writer",
|
|
37553
|
-
appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review",
|
|
37946
|
+
appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review" || sink.mockHandoffs.length > 0,
|
|
37554
37947
|
fixOp: testWriterRectifyOp,
|
|
37555
|
-
buildInput: (findings, _prior, _cycleCtx) =>
|
|
37556
|
-
|
|
37557
|
-
|
|
37558
|
-
|
|
37559
|
-
|
|
37560
|
-
|
|
37948
|
+
buildInput: (findings, _prior, _cycleCtx) => {
|
|
37949
|
+
if (sink.mockHandoffs.length > 0) {
|
|
37950
|
+
const handoffs = sink.mockHandoffs.splice(0);
|
|
37951
|
+
const seenFiles = new Set;
|
|
37952
|
+
const handoffFiles = [];
|
|
37953
|
+
for (const h of handoffs) {
|
|
37954
|
+
for (const f of h.files) {
|
|
37955
|
+
if (!seenFiles.has(f)) {
|
|
37956
|
+
seenFiles.add(f);
|
|
37957
|
+
handoffFiles.push(f);
|
|
37958
|
+
}
|
|
37959
|
+
}
|
|
37960
|
+
}
|
|
37961
|
+
const handoffReason = handoffs.map((h) => h.reasonDetail).join(`
|
|
37962
|
+
---
|
|
37963
|
+
`);
|
|
37964
|
+
return {
|
|
37965
|
+
failedChecks: findingsToFailedChecks(findings),
|
|
37966
|
+
story,
|
|
37967
|
+
mode: "mock-restructure",
|
|
37968
|
+
blockingThreshold: config2.review?.blockingThreshold,
|
|
37969
|
+
handoffReason,
|
|
37970
|
+
handoffFiles
|
|
37971
|
+
};
|
|
37972
|
+
}
|
|
37973
|
+
return {
|
|
37974
|
+
failedChecks: findingsToFailedChecks(findings),
|
|
37975
|
+
story,
|
|
37976
|
+
blockingThreshold: config2.review?.blockingThreshold
|
|
37977
|
+
};
|
|
37978
|
+
},
|
|
37979
|
+
maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
|
|
37561
37980
|
coRun: "co-run-sequential"
|
|
37562
37981
|
};
|
|
37563
37982
|
}
|
|
@@ -37566,6 +37985,100 @@ var init_autofix_test_writer_strategy = __esm(() => {
|
|
|
37566
37985
|
init_autofix_test_writer();
|
|
37567
37986
|
});
|
|
37568
37987
|
|
|
37988
|
+
// src/operations/apply-test-edit-declarations.ts
|
|
37989
|
+
function applyTestEditDeclarations(findings, declarations, story, invalidMockStructure) {
|
|
37990
|
+
let result = [...findings];
|
|
37991
|
+
const advisories = [];
|
|
37992
|
+
for (const d of declarations) {
|
|
37993
|
+
if (d.reason === "prd_contract") {
|
|
37994
|
+
const prdQuote = d.prdQuote ?? "";
|
|
37995
|
+
const valid = validatePrdQuote(prdQuote, story);
|
|
37996
|
+
if (valid) {
|
|
37997
|
+
result = result.map((f) => {
|
|
37998
|
+
if (f.file === d.file && f.fixTarget === "source") {
|
|
37999
|
+
return {
|
|
38000
|
+
...f,
|
|
38001
|
+
fixTarget: "test",
|
|
38002
|
+
meta: {
|
|
38003
|
+
...f.meta,
|
|
38004
|
+
prdContractDeclaration: d
|
|
38005
|
+
}
|
|
38006
|
+
};
|
|
38007
|
+
}
|
|
38008
|
+
return f;
|
|
38009
|
+
});
|
|
38010
|
+
} else {
|
|
38011
|
+
advisories.push({
|
|
38012
|
+
source: "autofix",
|
|
38013
|
+
severity: "warning",
|
|
38014
|
+
category: "prd_quote_mismatch",
|
|
38015
|
+
message: `PRD quote not found verbatim in story text for file: ${d.file}`,
|
|
38016
|
+
file: d.file,
|
|
38017
|
+
fixTarget: "source"
|
|
38018
|
+
});
|
|
38019
|
+
}
|
|
38020
|
+
}
|
|
38021
|
+
}
|
|
38022
|
+
if (invalidMockStructure && invalidMockStructure.length > 0) {
|
|
38023
|
+
for (const d of invalidMockStructure) {
|
|
38024
|
+
const fileList = (d.files ?? [d.file]).join(", ");
|
|
38025
|
+
advisories.push({
|
|
38026
|
+
source: "autofix",
|
|
38027
|
+
severity: "warning",
|
|
38028
|
+
category: "mock_structure_invalid_files",
|
|
38029
|
+
message: `Mock structure handoff references file that does not exist or is not a test file: ${fileList}`,
|
|
38030
|
+
fixTarget: "source"
|
|
38031
|
+
});
|
|
38032
|
+
}
|
|
38033
|
+
}
|
|
38034
|
+
return [...result, ...advisories];
|
|
38035
|
+
}
|
|
38036
|
+
var init_apply_test_edit_declarations = __esm(() => {
|
|
38037
|
+
init_test_edit_declaration();
|
|
38038
|
+
});
|
|
38039
|
+
|
|
38040
|
+
// src/operations/validate-mock-structure-files.ts
|
|
38041
|
+
import { join as join23 } from "path";
|
|
38042
|
+
async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, deps) {
|
|
38043
|
+
const fileExists = deps?.fileExists ?? defaultFileExists;
|
|
38044
|
+
const valid = [];
|
|
38045
|
+
const invalid = [];
|
|
38046
|
+
for (const d of declarations) {
|
|
38047
|
+
if (d.reason !== "mock_structure") {
|
|
38048
|
+
valid.push(d);
|
|
38049
|
+
continue;
|
|
38050
|
+
}
|
|
38051
|
+
const files = d.files ?? [d.file];
|
|
38052
|
+
let allValid = true;
|
|
38053
|
+
for (const file3 of files) {
|
|
38054
|
+
const absolutePath = join23(packageDir, file3);
|
|
38055
|
+
const exists = await fileExists(absolutePath);
|
|
38056
|
+
if (!exists) {
|
|
38057
|
+
allValid = false;
|
|
38058
|
+
break;
|
|
38059
|
+
}
|
|
38060
|
+
const matchesPattern = resolvedTestPatterns.regex.some((re) => re.test(file3));
|
|
38061
|
+
if (!matchesPattern) {
|
|
38062
|
+
allValid = false;
|
|
38063
|
+
break;
|
|
38064
|
+
}
|
|
38065
|
+
}
|
|
38066
|
+
if (allValid) {
|
|
38067
|
+
valid.push(d);
|
|
38068
|
+
} else {
|
|
38069
|
+
invalid.push(d);
|
|
38070
|
+
}
|
|
38071
|
+
}
|
|
38072
|
+
return { valid, invalid };
|
|
38073
|
+
}
|
|
38074
|
+
var defaultFileExists = (p) => Bun.file(p).exists();
|
|
38075
|
+
var init_validate_mock_structure_files = () => {};
|
|
38076
|
+
|
|
38077
|
+
// src/operations/declaration-sink.ts
|
|
38078
|
+
function makeDeclarationSink() {
|
|
38079
|
+
return { testEdits: [], mockHandoffs: [] };
|
|
38080
|
+
}
|
|
38081
|
+
|
|
37569
38082
|
// src/operations/mechanical-lintfix-strategy.ts
|
|
37570
38083
|
function shellQuotePath2(path5) {
|
|
37571
38084
|
return `'${path5.replaceAll("'", `'\\''`)}'`;
|
|
@@ -38046,6 +38559,8 @@ var init_operations = __esm(() => {
|
|
|
38046
38559
|
init_full_suite_rectify();
|
|
38047
38560
|
init_autofix_implementer_strategy();
|
|
38048
38561
|
init_autofix_test_writer_strategy();
|
|
38562
|
+
init_apply_test_edit_declarations();
|
|
38563
|
+
init_validate_mock_structure_files();
|
|
38049
38564
|
init__finding_to_check();
|
|
38050
38565
|
init_mechanical_lintfix_strategy();
|
|
38051
38566
|
init_mechanical_formatfix_strategy();
|
|
@@ -38696,7 +39211,7 @@ var init_lint_parsing = __esm(() => {
|
|
|
38696
39211
|
});
|
|
38697
39212
|
|
|
38698
39213
|
// src/review/scoped-lint.ts
|
|
38699
|
-
import { join as
|
|
39214
|
+
import { join as join24, relative as relative10 } from "path";
|
|
38700
39215
|
function shellQuotePath4(path5) {
|
|
38701
39216
|
return `'${path5.replaceAll("'", "'\\''")}'`;
|
|
38702
39217
|
}
|
|
@@ -38744,7 +39259,7 @@ function uniqueFiles(files) {
|
|
|
38744
39259
|
async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
|
|
38745
39260
|
const inScope = [];
|
|
38746
39261
|
for (const relPath of files) {
|
|
38747
|
-
const absPath =
|
|
39262
|
+
const absPath = join24(workdir, relPath);
|
|
38748
39263
|
const exists = await _scopedLintDeps.fileExists(absPath);
|
|
38749
39264
|
if (!exists)
|
|
38750
39265
|
continue;
|
|
@@ -39502,6 +40017,42 @@ async function runSemanticReview(opts) {
|
|
|
39502
40017
|
durationMs: Date.now() - startTime
|
|
39503
40018
|
};
|
|
39504
40019
|
}
|
|
40020
|
+
if (opResult.looksLikeFail) {
|
|
40021
|
+
logger?.warn("semantic", "LLM returned truncated JSON with passed:false \u2014 treating as failure", {
|
|
40022
|
+
storyId: story.id
|
|
40023
|
+
});
|
|
40024
|
+
recordSemanticAudit({
|
|
40025
|
+
runtime,
|
|
40026
|
+
workdir,
|
|
40027
|
+
projectDir,
|
|
40028
|
+
storyId: story.id,
|
|
40029
|
+
featureName,
|
|
40030
|
+
parsed: false,
|
|
40031
|
+
looksLikeFail: true,
|
|
40032
|
+
failOpen: false,
|
|
40033
|
+
passed: false,
|
|
40034
|
+
blockingThreshold,
|
|
40035
|
+
result: null
|
|
40036
|
+
});
|
|
40037
|
+
return {
|
|
40038
|
+
check: "semantic",
|
|
40039
|
+
success: false,
|
|
40040
|
+
command: "",
|
|
40041
|
+
exitCode: 1,
|
|
40042
|
+
output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
40043
|
+
durationMs: Date.now() - startTime
|
|
40044
|
+
};
|
|
40045
|
+
}
|
|
40046
|
+
if (opResult.repromptEvent) {
|
|
40047
|
+
runtime.dispatchEvents.emitReviewReprompt({
|
|
40048
|
+
kind: "review-reprompt-on-drop",
|
|
40049
|
+
storyId: story.id,
|
|
40050
|
+
reviewer: "semantic",
|
|
40051
|
+
dropCount: opResult.repromptEvent.dropCount,
|
|
40052
|
+
repromptOutcome: opResult.repromptEvent.outcome,
|
|
40053
|
+
costUsd: opResult.repromptEvent.costUsd
|
|
40054
|
+
});
|
|
40055
|
+
}
|
|
39505
40056
|
const threshold = blockingThreshold ?? "error";
|
|
39506
40057
|
const allFindings = opResult.findings;
|
|
39507
40058
|
const blockingFindings = allFindings.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
@@ -39903,6 +40454,18 @@ async function runReview(opts) {
|
|
|
39903
40454
|
naxIgnoreIndex
|
|
39904
40455
|
}) : normalizeMechanicalFindings(checkName, await runCheck(checkName, command, workdir, storyId, env2), workdir);
|
|
39905
40456
|
checks3.push(result);
|
|
40457
|
+
if (result.success) {
|
|
40458
|
+
logger?.info("review", `${checkName} passed`, {
|
|
40459
|
+
storyId,
|
|
40460
|
+
durationMs: result.durationMs
|
|
40461
|
+
});
|
|
40462
|
+
} else {
|
|
40463
|
+
logger?.warn("review", `${checkName} failed`, {
|
|
40464
|
+
storyId,
|
|
40465
|
+
exitCode: result.exitCode,
|
|
40466
|
+
durationMs: result.durationMs
|
|
40467
|
+
});
|
|
40468
|
+
}
|
|
39906
40469
|
if (!result.success && !firstFailure) {
|
|
39907
40470
|
firstFailure = `${checkName} failed (exit code ${result.exitCode})`;
|
|
39908
40471
|
}
|
|
@@ -39962,9 +40525,34 @@ var init_review = __esm(() => {
|
|
|
39962
40525
|
});
|
|
39963
40526
|
|
|
39964
40527
|
// src/prompts/builders/rectifier-builder-helpers.ts
|
|
40528
|
+
function buildEscapeHatch(opts) {
|
|
40529
|
+
const exceptions = [EXCEPTION_1_LINT_ONLY, EXCEPTION_2_PRD_CONTRACT, EXCEPTION_3_SIBLING_SCOPE];
|
|
40530
|
+
if (opts.includeMockHandoff)
|
|
40531
|
+
exceptions.push(EXCEPTION_4_MOCK_HANDOFF);
|
|
40532
|
+
const count = exceptions.length;
|
|
40533
|
+
const countWord = ["zero", "one", "two", "three", "four"][count];
|
|
40534
|
+
return `
|
|
40535
|
+
If two findings in this list contradict each other and you cannot satisfy both, do not guess.
|
|
40536
|
+
Emit fixes for defects you can resolve, then output a line in this exact format:
|
|
40537
|
+
UNRESOLVED: <brief explanation of which findings conflicted and why they cannot both be satisfied>
|
|
40538
|
+
|
|
40539
|
+
Before emitting UNRESOLVED, confirm none of Exceptions 1\u2013${count} apply.
|
|
40540
|
+
|
|
40541
|
+
## Test-file edit exceptions
|
|
40542
|
+
|
|
40543
|
+
The "do not modify test files" rule has ${countWord} narrow escape valves. Each requires a
|
|
40544
|
+
declaration in your output. Outside these ${countWord} cases the rule is absolute.
|
|
40545
|
+
|
|
40546
|
+
${exceptions.join(`
|
|
40547
|
+
|
|
40548
|
+
`)}`;
|
|
40549
|
+
}
|
|
40550
|
+
function exceptionCountWord(story) {
|
|
40551
|
+
return THREE_SESSION_STRATEGIES.has(story.routing?.testStrategy ?? "") ? "four" : "three";
|
|
40552
|
+
}
|
|
39965
40553
|
function escapeHatchFor(story) {
|
|
39966
40554
|
const isTdd = THREE_SESSION_STRATEGIES.has(story.routing?.testStrategy ?? "");
|
|
39967
|
-
return
|
|
40555
|
+
return buildEscapeHatch({ includeMockHandoff: isTdd });
|
|
39968
40556
|
}
|
|
39969
40557
|
function noTestIsolationBlock(story) {
|
|
39970
40558
|
if (story.routing?.testStrategy !== "no-test")
|
|
@@ -40034,7 +40622,7 @@ ${errors3}
|
|
|
40034
40622
|
2. Only fix findings that are actually valid problems
|
|
40035
40623
|
3. Do NOT add keys, functions, or imports that already exist \u2014 check first
|
|
40036
40624
|
|
|
40037
|
-
Do NOT change test files or test behavior \u2014 see the
|
|
40625
|
+
Do NOT change test files or test behavior \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below.
|
|
40038
40626
|
Do NOT add new features \u2014 only fix valid issues.
|
|
40039
40627
|
Commit your fixes when done.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
|
|
40040
40628
|
}
|
|
@@ -40096,21 +40684,11 @@ The following quality checks failed after implementation:
|
|
|
40096
40684
|
|
|
40097
40685
|
${errors3}
|
|
40098
40686
|
|
|
40099
|
-
Fix all errors listed above that are within this story's scope \u2014 see the
|
|
40687
|
+
Fix all errors listed above that are within this story's scope \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below for sibling-story spillover. Do NOT change test files or test behavior except via those exceptions.
|
|
40100
40688
|
Do NOT add new features \u2014 only fix the quality check errors.
|
|
40101
40689
|
After fixing, re-run the failing check(s) to verify they pass, then commit your changes.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
|
|
40102
40690
|
}
|
|
40103
|
-
var
|
|
40104
|
-
If two findings in this list contradict each other and you cannot satisfy both, do not guess.
|
|
40105
|
-
Emit fixes for defects you can resolve, then output a line in this exact format:
|
|
40106
|
-
UNRESOLVED: <brief explanation of which findings conflicted and why they cannot both be satisfied>
|
|
40107
|
-
|
|
40108
|
-
## Test-file edit exceptions
|
|
40109
|
-
|
|
40110
|
-
The "do not modify test files" rule has three narrow escape valves. Each requires a
|
|
40111
|
-
declaration in your output. Outside these three cases the rule is absolute.
|
|
40112
|
-
|
|
40113
|
-
### Exception 1 \u2014 Lint-only edit
|
|
40691
|
+
var EXCEPTION_1_LINT_ONLY = `### Exception 1 \u2014 Lint-only edit
|
|
40114
40692
|
|
|
40115
40693
|
You MAY edit a test file ONLY when ALL of the following hold:
|
|
40116
40694
|
- The failing check is \`lint\` \u2014 not \`test\`, \`typecheck\`, \`semantic\`, or \`adversarial\`.
|
|
@@ -40126,9 +40704,7 @@ TEST_EDIT_REASON: lint_only
|
|
|
40126
40704
|
FILE: <test file path>
|
|
40127
40705
|
FINDING: <lint rule or message verbatim>
|
|
40128
40706
|
CHANGE: <before line> \u2192 <after line>
|
|
40129
|
-
|
|
40130
|
-
|
|
40131
|
-
### Exception 2 \u2014 PRD-contract mismatch
|
|
40707
|
+
\`\`\``, EXCEPTION_2_PRD_CONTRACT = `### Exception 2 \u2014 PRD-contract mismatch
|
|
40132
40708
|
|
|
40133
40709
|
You MAY correct a test's argument arity, type, or return-handling ONLY when the test's
|
|
40134
40710
|
call contradicts a literal interface signature stated in this story's description or
|
|
@@ -40144,9 +40720,7 @@ TEST_AFTER: <corrected call line>
|
|
|
40144
40720
|
\`\`\`
|
|
40145
40721
|
|
|
40146
40722
|
Do NOT use this exception to change test logic, assertions, or mock setup \u2014 only call
|
|
40147
|
-
signatures that directly contradict a quoted PRD interface
|
|
40148
|
-
|
|
40149
|
-
### Exception 3 \u2014 Unrelated sibling spillover
|
|
40723
|
+
signatures that directly contradict a quoted PRD interface.`, EXCEPTION_3_SIBLING_SCOPE = `### Exception 3 \u2014 Unrelated sibling spillover
|
|
40150
40724
|
|
|
40151
40725
|
When a lint or typecheck error is outside this story's intended scope, do NOT edit that
|
|
40152
40726
|
file. If the smallest package-local fix is required to satisfy this story's acceptance
|
|
@@ -40156,46 +40730,37 @@ TEST_EDIT_REASON: sibling_scope
|
|
|
40156
40730
|
SIBLING_FILE: <file path>
|
|
40157
40731
|
FINDING: <error summary>
|
|
40158
40732
|
\`\`\`
|
|
40159
|
-
and continue. Sibling-scope failures do not block your story
|
|
40160
|
-
|
|
40161
|
-
### Exception 4 \u2014 Mock-structure handoff
|
|
40733
|
+
and continue. Sibling-scope failures do not block your story.`, EXCEPTION_4_MOCK_HANDOFF = `### Exception 4 \u2014 Mock-structure handoff
|
|
40162
40734
|
|
|
40163
40735
|
Use ONLY when the only path to satisfy the ACs requires a structural test rewrite
|
|
40164
|
-
that does NOT fit Exception 2.
|
|
40165
|
-
bypasses; assertion topology must change to match a new dispatch shape.
|
|
40166
|
-
|
|
40167
|
-
Declare with:
|
|
40168
|
-
\`\`\`
|
|
40169
|
-
TEST_EDIT_REASON: mock_structure
|
|
40170
|
-
FILES: <comma-separated test file paths>
|
|
40171
|
-
REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses>
|
|
40172
|
-
\`\`\`
|
|
40736
|
+
that does NOT fit Exception 2. Two cases qualify:
|
|
40173
40737
|
|
|
40174
|
-
|
|
40175
|
-
|
|
40176
|
-
- Do NOT also emit \`UNRESOLVED:\` in the same turn \u2014 this declaration IS the handoff.
|
|
40177
|
-
- FILES must list real test files. Each path must exist and be a test file.`, EXCEPTION_4_MOCK_HANDOFF = `
|
|
40178
|
-
### Exception 4 \u2014 Mock-structure handoff
|
|
40738
|
+
(a) Existing mocks are wrong \u2014 mocks reference primitives the new code bypasses,
|
|
40739
|
+
or assertion topology must change to match a new dispatch shape.
|
|
40179
40740
|
|
|
40180
|
-
|
|
40181
|
-
|
|
40182
|
-
|
|
40741
|
+
(b) Required test-infrastructure does not yet exist and must be introduced \u2014
|
|
40742
|
+
e.g. in-process fake servers, network-level request interception, hermetic
|
|
40743
|
+
fixture-backed HTTP, or equivalent. Applies whenever the AC describes a
|
|
40744
|
+
hermetic/fixture-backed test surface that the current test setup cannot
|
|
40745
|
+
satisfy without new infrastructure.
|
|
40183
40746
|
|
|
40184
40747
|
Declare with:
|
|
40185
40748
|
\`\`\`
|
|
40186
40749
|
TEST_EDIT_REASON: mock_structure
|
|
40187
40750
|
FILES: <comma-separated test file paths>
|
|
40188
|
-
REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses
|
|
40751
|
+
REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses,
|
|
40752
|
+
or what infrastructure must be introduced>
|
|
40189
40753
|
\`\`\`
|
|
40190
40754
|
|
|
40191
40755
|
Rules:
|
|
40192
40756
|
- Do NOT make any edits yourself; the test-writer will fulfill.
|
|
40193
40757
|
- Do NOT also emit \`UNRESOLVED:\` in the same turn \u2014 this declaration IS the handoff.
|
|
40194
|
-
- FILES must list real test files. Each path must exist and be a test file.`, THREE_SESSION_STRATEGIES, MAX_STRUCTURED_FINDINGS = 10, RAW_WITH_FINDINGS_LIMIT = 1000, RAW_FALLBACK_LIMIT = 4000;
|
|
40758
|
+
- FILES must list real test files. Each path must exist and be a test file.`, THREE_SESSION_STRATEGIES, CONTRADICTION_ESCAPE_HATCH, MAX_STRUCTURED_FINDINGS = 10, RAW_WITH_FINDINGS_LIMIT = 1000, RAW_FALLBACK_LIMIT = 4000;
|
|
40195
40759
|
var init_rectifier_builder_helpers = __esm(() => {
|
|
40196
40760
|
init_review();
|
|
40197
40761
|
init_sections2();
|
|
40198
40762
|
THREE_SESSION_STRATEGIES = new Set(["three-session-tdd", "three-session-tdd-lite"]);
|
|
40763
|
+
CONTRADICTION_ESCAPE_HATCH = buildEscapeHatch({ includeMockHandoff: false });
|
|
40199
40764
|
});
|
|
40200
40765
|
|
|
40201
40766
|
// src/prompts/builders/rectifier-builder.ts
|
|
@@ -40276,15 +40841,16 @@ function renderPrioritizedFailures(failedChecks, opts) {
|
|
|
40276
40841
|
}
|
|
40277
40842
|
|
|
40278
40843
|
class RectifierPromptBuilder {
|
|
40279
|
-
static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel) {
|
|
40844
|
+
static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel, story) {
|
|
40280
40845
|
const parts = [];
|
|
40281
40846
|
const attemptWord = maxAttempts === 1 ? "1 attempt" : `${maxAttempts} attempts`;
|
|
40847
|
+
const exCount = story ? exceptionCountWord(story) : "three";
|
|
40282
40848
|
parts.push(`Review failed after your implementation. Fix the following issues (${attemptWord} available before escalation):
|
|
40283
40849
|
`);
|
|
40284
40850
|
parts.push(renderPrioritizedFailures(failedChecks));
|
|
40285
40851
|
parts.push(`
|
|
40286
|
-
Fix in priority order. After fixing each priority, re-run the failing check(s) at that level to verify they pass before moving on. Do NOT change test files or test behavior \u2014 see the
|
|
40287
|
-
parts.push(CONTRADICTION_ESCAPE_HATCH);
|
|
40852
|
+
Fix in priority order. After fixing each priority, re-run the failing check(s) at that level to verify they pass before moving on. Do NOT change test files or test behavior \u2014 see the ${exCount} narrow exceptions appended below. Commit your changes when all checks pass.`);
|
|
40853
|
+
parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
|
|
40288
40854
|
const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
|
|
40289
40855
|
if (guardrails) {
|
|
40290
40856
|
parts.push(`
|
|
@@ -40294,7 +40860,7 @@ ${guardrails}`);
|
|
|
40294
40860
|
return parts.join(`
|
|
40295
40861
|
`);
|
|
40296
40862
|
}
|
|
40297
|
-
static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel) {
|
|
40863
|
+
static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel, story) {
|
|
40298
40864
|
const parts = [];
|
|
40299
40865
|
parts.push(`Your previous fix attempt did not resolve all issues. Here are the remaining failures:
|
|
40300
40866
|
`);
|
|
@@ -40307,7 +40873,7 @@ ${guardrails}`);
|
|
|
40307
40873
|
if (attempt >= urgencyAtAttempt) {
|
|
40308
40874
|
parts.push("\n**URGENT: This is your final attempt.** If you cannot fix all issues, emit `UNRESOLVED: <reason>` to escalate.\n");
|
|
40309
40875
|
}
|
|
40310
|
-
parts.push(CONTRADICTION_ESCAPE_HATCH);
|
|
40876
|
+
parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
|
|
40311
40877
|
const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
|
|
40312
40878
|
if (guardrails) {
|
|
40313
40879
|
parts.push(`
|
|
@@ -40429,7 +40995,7 @@ ${importantNote}
|
|
|
40429
40995
|
|
|
40430
40996
|
Commit your fixes when done.${scopeConstraint}`;
|
|
40431
40997
|
}
|
|
40432
|
-
static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts) {
|
|
40998
|
+
static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts, story) {
|
|
40433
40999
|
const parts = [];
|
|
40434
41000
|
parts.push(`**Your previous turn produced no committed file changes.**
|
|
40435
41001
|
|
|
@@ -40470,7 +41036,7 @@ ${output}
|
|
|
40470
41036
|
`);
|
|
40471
41037
|
}
|
|
40472
41038
|
}
|
|
40473
|
-
parts.push(CONTRADICTION_ESCAPE_HATCH);
|
|
41039
|
+
parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
|
|
40474
41040
|
return parts.join("");
|
|
40475
41041
|
}
|
|
40476
41042
|
static escalated(failures, story, priorAttempts, originalTier, targetTier, config2, testCommand, testScopedTemplate) {
|
|
@@ -40546,11 +41112,11 @@ ${testCommands}
|
|
|
40546
41112
|
6. Ensure ALL tests pass before completing.
|
|
40547
41113
|
|
|
40548
41114
|
**IMPORTANT:**
|
|
40549
|
-
- Do NOT modify test files \u2014 see the
|
|
41115
|
+
- Do NOT modify test files \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below if you believe a test has a lint error, a PRD-contract mismatch, or belongs to a sibling story.
|
|
40550
41116
|
- Do NOT loosen assertions to mask implementation bugs.
|
|
40551
41117
|
- Focus on fixing the source code to meet the test requirements.
|
|
40552
41118
|
- When running tests, run ONLY the failing test files shown above${cmd ? ` \u2014 NEVER run \`${cmd}\` without a file filter` : " \u2014 never run the full test suite without a file filter"}.
|
|
40553
|
-
`;
|
|
41119
|
+
${escapeHatchFor(story)}`;
|
|
40554
41120
|
}
|
|
40555
41121
|
static reviewRectification(failedChecks, story, opts) {
|
|
40556
41122
|
const scopeConstraint = story.workdir ? `
|
|
@@ -40607,7 +41173,7 @@ ${llmSection}
|
|
|
40607
41173
|
**Important:** LLM reviewers may flag false positives. Before making changes for LLM review findings, read the relevant files to verify each finding is a real issue. Do NOT add keys, functions, or imports that already exist.
|
|
40608
41174
|
|
|
40609
41175
|
Do NOT add new features \u2014 only fix the identified issues.
|
|
40610
|
-
Commit your fixes when done.${scopeConstraint}${
|
|
41176
|
+
Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
|
|
40611
41177
|
}
|
|
40612
41178
|
static dialogueAwareRectification(failedChecks, story, opts) {
|
|
40613
41179
|
const scopeConstraint = story.workdir ? `
|
|
@@ -40646,9 +41212,9 @@ ${errors3}${reasoningSection}${historySection}
|
|
|
40646
41212
|
2. Only fix findings that are actually valid problems
|
|
40647
41213
|
3. Do NOT add keys, functions, or imports that already exist \u2014 check first
|
|
40648
41214
|
|
|
40649
|
-
Do NOT change test files or test behavior \u2014 see the
|
|
41215
|
+
Do NOT change test files or test behavior \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below.
|
|
40650
41216
|
Do NOT add new features \u2014 only fix valid issues.
|
|
40651
|
-
Commit your fixes when done.${scopeConstraint}${
|
|
41217
|
+
Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
|
|
40652
41218
|
}
|
|
40653
41219
|
static swapHandoff(basePrompt, pushMarkdown) {
|
|
40654
41220
|
const trimmed = pushMarkdown?.trim();
|
|
@@ -40740,9 +41306,10 @@ Tests are failing. Fix the source so all tests pass \u2014 not just the ones lis
|
|
|
40740
41306
|
4. Do not declare done until step 3 shows 0 failures.
|
|
40741
41307
|
|
|
40742
41308
|
**IMPORTANT:**
|
|
40743
|
-
- Do NOT modify test files \u2014 see the
|
|
41309
|
+
- Do NOT modify test files \u2014 see the ${exceptionCountWord(opts.story)} narrow exceptions appended below if you believe a test has a lint error, a PRD-contract mismatch, or belongs to a sibling story.
|
|
40744
41310
|
- Do NOT loosen assertions to mask implementation bugs.
|
|
40745
41311
|
- Focus on fixing the source code to meet the test requirements.`);
|
|
41312
|
+
parts.push(escapeHatchFor(opts.story));
|
|
40746
41313
|
return parts.join("");
|
|
40747
41314
|
}
|
|
40748
41315
|
static failingTestContext(findings) {
|
|
@@ -42076,7 +42643,7 @@ var init_call = __esm(() => {
|
|
|
42076
42643
|
|
|
42077
42644
|
// src/runtime/cost-aggregator.ts
|
|
42078
42645
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
42079
|
-
import { join as
|
|
42646
|
+
import { join as join25 } from "path";
|
|
42080
42647
|
function makeCorrelationId() {
|
|
42081
42648
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
42082
42649
|
}
|
|
@@ -42267,7 +42834,7 @@ class CostAggregator {
|
|
|
42267
42834
|
if (events.length === 0 && errors3.length === 0)
|
|
42268
42835
|
return;
|
|
42269
42836
|
mkdirSync2(this._drainDir, { recursive: true });
|
|
42270
|
-
const path5 =
|
|
42837
|
+
const path5 = join25(this._drainDir, `${this._runId}.jsonl`);
|
|
42271
42838
|
const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
|
|
42272
42839
|
await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
|
|
42273
42840
|
`)}
|
|
@@ -42307,7 +42874,7 @@ var init_cost_aggregator = __esm(() => {
|
|
|
42307
42874
|
// src/runtime/prompt-auditor.ts
|
|
42308
42875
|
import { appendFileSync } from "fs";
|
|
42309
42876
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
42310
|
-
import { join as
|
|
42877
|
+
import { join as join26 } from "path";
|
|
42311
42878
|
function createNoOpPromptAuditor() {
|
|
42312
42879
|
return {
|
|
42313
42880
|
record() {},
|
|
@@ -42373,8 +42940,8 @@ class PromptAuditor {
|
|
|
42373
42940
|
_jsonlPath;
|
|
42374
42941
|
_featureDir;
|
|
42375
42942
|
constructor(runId, flushDir, featureName) {
|
|
42376
|
-
this._featureDir =
|
|
42377
|
-
this._jsonlPath =
|
|
42943
|
+
this._featureDir = join26(flushDir, featureName);
|
|
42944
|
+
this._jsonlPath = join26(this._featureDir, `${runId}.jsonl`);
|
|
42378
42945
|
}
|
|
42379
42946
|
record(entry) {
|
|
42380
42947
|
this._enqueue(entry);
|
|
@@ -42423,7 +42990,7 @@ class PromptAuditor {
|
|
|
42423
42990
|
const auditEntry = entry;
|
|
42424
42991
|
const filename = deriveTxtFilename(auditEntry);
|
|
42425
42992
|
try {
|
|
42426
|
-
await _promptAuditorDeps.write(
|
|
42993
|
+
await _promptAuditorDeps.write(join26(this._featureDir, filename), buildTxtContent(auditEntry));
|
|
42427
42994
|
} catch (err) {
|
|
42428
42995
|
throw tagAuditError(err, "txt");
|
|
42429
42996
|
}
|
|
@@ -43511,7 +44078,7 @@ var init_pid_registry = __esm(() => {
|
|
|
43511
44078
|
// src/session/manager-deps.ts
|
|
43512
44079
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
43513
44080
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
43514
|
-
import { isAbsolute as isAbsolute9, join as
|
|
44081
|
+
import { isAbsolute as isAbsolute9, join as join27, relative as relative12, sep as sep3 } from "path";
|
|
43515
44082
|
function resolveProjectDirFromScratchDir(scratchDir) {
|
|
43516
44083
|
const marker = `${sep3}.nax${sep3}features${sep3}`;
|
|
43517
44084
|
const markerIdx = scratchDir.lastIndexOf(marker);
|
|
@@ -43532,7 +44099,7 @@ var init_manager_deps = __esm(() => {
|
|
|
43532
44099
|
now: () => new Date().toISOString(),
|
|
43533
44100
|
nowMs: () => Date.now(),
|
|
43534
44101
|
uuid: () => randomUUID3(),
|
|
43535
|
-
sessionScratchDir: (projectDir, featureName, sessionId) =>
|
|
44102
|
+
sessionScratchDir: (projectDir, featureName, sessionId) => join27(projectDir, ".nax", "features", featureName, "sessions", sessionId),
|
|
43536
44103
|
writeDescriptor: async (scratchDir, descriptor, projectDir) => {
|
|
43537
44104
|
await mkdir5(scratchDir, { recursive: true });
|
|
43538
44105
|
const { handle: _handle, ...persistable } = descriptor;
|
|
@@ -43543,7 +44110,7 @@ var init_manager_deps = __esm(() => {
|
|
|
43543
44110
|
persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
|
|
43544
44111
|
}
|
|
43545
44112
|
}
|
|
43546
|
-
await Bun.write(
|
|
44113
|
+
await Bun.write(join27(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
|
|
43547
44114
|
}
|
|
43548
44115
|
};
|
|
43549
44116
|
});
|
|
@@ -44294,7 +44861,7 @@ __export(exports_runtime, {
|
|
|
44294
44861
|
CostAggregator: () => CostAggregator,
|
|
44295
44862
|
AgentStreamEventBus: () => AgentStreamEventBus
|
|
44296
44863
|
});
|
|
44297
|
-
import { basename as basename5, join as
|
|
44864
|
+
import { basename as basename5, join as join28 } from "path";
|
|
44298
44865
|
function createRuntime(config2, workdir, opts) {
|
|
44299
44866
|
const runId = crypto.randomUUID();
|
|
44300
44867
|
const controller = new AbortController;
|
|
@@ -44310,10 +44877,10 @@ function createRuntime(config2, workdir, opts) {
|
|
|
44310
44877
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
44311
44878
|
const globalDir = globalOutputDir();
|
|
44312
44879
|
const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
|
|
44313
|
-
const costDir =
|
|
44880
|
+
const costDir = join28(outputDir, "cost");
|
|
44314
44881
|
const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
|
|
44315
44882
|
const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
|
|
44316
|
-
const auditDir = config2.agent?.promptAudit?.dir ??
|
|
44883
|
+
const auditDir = config2.agent?.promptAudit?.dir ?? join28(outputDir, "prompt-audit");
|
|
44317
44884
|
let promptAuditor;
|
|
44318
44885
|
if (opts?.promptAuditor) {
|
|
44319
44886
|
promptAuditor = opts.promptAuditor;
|
|
@@ -44464,9 +45031,9 @@ async function allSettledBounded(tasks, limit) {
|
|
|
44464
45031
|
|
|
44465
45032
|
// src/context/injector.ts
|
|
44466
45033
|
import { existsSync as existsSync8 } from "fs";
|
|
44467
|
-
import { join as
|
|
45034
|
+
import { join as join29 } from "path";
|
|
44468
45035
|
async function detectNode(workdir) {
|
|
44469
|
-
const pkgPath =
|
|
45036
|
+
const pkgPath = join29(workdir, "package.json");
|
|
44470
45037
|
if (!existsSync8(pkgPath))
|
|
44471
45038
|
return null;
|
|
44472
45039
|
try {
|
|
@@ -44483,7 +45050,7 @@ async function detectNode(workdir) {
|
|
|
44483
45050
|
}
|
|
44484
45051
|
}
|
|
44485
45052
|
async function detectGo(workdir) {
|
|
44486
|
-
const goMod =
|
|
45053
|
+
const goMod = join29(workdir, "go.mod");
|
|
44487
45054
|
if (!existsSync8(goMod))
|
|
44488
45055
|
return null;
|
|
44489
45056
|
try {
|
|
@@ -44507,7 +45074,7 @@ async function detectGo(workdir) {
|
|
|
44507
45074
|
}
|
|
44508
45075
|
}
|
|
44509
45076
|
async function detectRust(workdir) {
|
|
44510
|
-
const cargoPath =
|
|
45077
|
+
const cargoPath = join29(workdir, "Cargo.toml");
|
|
44511
45078
|
if (!existsSync8(cargoPath))
|
|
44512
45079
|
return null;
|
|
44513
45080
|
try {
|
|
@@ -44523,8 +45090,8 @@ async function detectRust(workdir) {
|
|
|
44523
45090
|
}
|
|
44524
45091
|
}
|
|
44525
45092
|
async function detectPython(workdir) {
|
|
44526
|
-
const pyproject =
|
|
44527
|
-
const requirements =
|
|
45093
|
+
const pyproject = join29(workdir, "pyproject.toml");
|
|
45094
|
+
const requirements = join29(workdir, "requirements.txt");
|
|
44528
45095
|
if (!existsSync8(pyproject) && !existsSync8(requirements))
|
|
44529
45096
|
return null;
|
|
44530
45097
|
try {
|
|
@@ -44543,7 +45110,7 @@ async function detectPython(workdir) {
|
|
|
44543
45110
|
}
|
|
44544
45111
|
}
|
|
44545
45112
|
async function detectPhp(workdir) {
|
|
44546
|
-
const composerPath =
|
|
45113
|
+
const composerPath = join29(workdir, "composer.json");
|
|
44547
45114
|
if (!existsSync8(composerPath))
|
|
44548
45115
|
return null;
|
|
44549
45116
|
try {
|
|
@@ -44556,7 +45123,7 @@ async function detectPhp(workdir) {
|
|
|
44556
45123
|
}
|
|
44557
45124
|
}
|
|
44558
45125
|
async function detectRuby(workdir) {
|
|
44559
|
-
const gemfile =
|
|
45126
|
+
const gemfile = join29(workdir, "Gemfile");
|
|
44560
45127
|
if (!existsSync8(gemfile))
|
|
44561
45128
|
return null;
|
|
44562
45129
|
try {
|
|
@@ -44568,9 +45135,9 @@ async function detectRuby(workdir) {
|
|
|
44568
45135
|
}
|
|
44569
45136
|
}
|
|
44570
45137
|
async function detectJvm(workdir) {
|
|
44571
|
-
const pom =
|
|
44572
|
-
const gradle =
|
|
44573
|
-
const gradleKts =
|
|
45138
|
+
const pom = join29(workdir, "pom.xml");
|
|
45139
|
+
const gradle = join29(workdir, "build.gradle");
|
|
45140
|
+
const gradleKts = join29(workdir, "build.gradle.kts");
|
|
44574
45141
|
if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
|
|
44575
45142
|
return null;
|
|
44576
45143
|
try {
|
|
@@ -44578,7 +45145,7 @@ async function detectJvm(workdir) {
|
|
|
44578
45145
|
const content2 = await Bun.file(pom).text();
|
|
44579
45146
|
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
44580
45147
|
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
44581
|
-
const lang2 = existsSync8(
|
|
45148
|
+
const lang2 = existsSync8(join29(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
44582
45149
|
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
44583
45150
|
}
|
|
44584
45151
|
const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
|
|
@@ -44832,7 +45399,7 @@ var init_windsurf = __esm(() => {
|
|
|
44832
45399
|
|
|
44833
45400
|
// src/context/generator.ts
|
|
44834
45401
|
import { existsSync as existsSync9 } from "fs";
|
|
44835
|
-
import { join as
|
|
45402
|
+
import { join as join30, relative as relative13 } from "path";
|
|
44836
45403
|
async function loadContextContent(options, config2) {
|
|
44837
45404
|
if (!_generatorDeps.existsSync(options.contextPath)) {
|
|
44838
45405
|
throw new Error(`Context file not found: ${options.contextPath}`);
|
|
@@ -44850,7 +45417,7 @@ async function generateFor(agent, options, config2) {
|
|
|
44850
45417
|
try {
|
|
44851
45418
|
const context = await loadContextContent(options, config2);
|
|
44852
45419
|
const content = generator.generate(context);
|
|
44853
|
-
const outputPath =
|
|
45420
|
+
const outputPath = join30(options.outputDir, generator.outputFile);
|
|
44854
45421
|
validateFilePath(outputPath, options.outputDir);
|
|
44855
45422
|
if (!options.dryRun) {
|
|
44856
45423
|
await _generatorDeps.writeFile(outputPath, content);
|
|
@@ -44868,7 +45435,7 @@ async function generateAll(options, config2, agentFilter) {
|
|
|
44868
45435
|
for (const [agentKey, generator] of entries) {
|
|
44869
45436
|
try {
|
|
44870
45437
|
const content = generator.generate(context);
|
|
44871
|
-
const outputPath =
|
|
45438
|
+
const outputPath = join30(options.outputDir, generator.outputFile);
|
|
44872
45439
|
validateFilePath(outputPath, options.outputDir);
|
|
44873
45440
|
if (!options.dryRun) {
|
|
44874
45441
|
await _generatorDeps.writeFile(outputPath, content);
|
|
@@ -44888,7 +45455,7 @@ async function discoverPackages(repoRoot) {
|
|
|
44888
45455
|
const glob = new Bun.Glob(pattern);
|
|
44889
45456
|
for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
|
|
44890
45457
|
const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
|
|
44891
|
-
const pkgAbsolute =
|
|
45458
|
+
const pkgAbsolute = join30(repoRoot, pkgRelative);
|
|
44892
45459
|
if (!seen.has(pkgAbsolute)) {
|
|
44893
45460
|
seen.add(pkgAbsolute);
|
|
44894
45461
|
packages.push(pkgAbsolute);
|
|
@@ -44920,14 +45487,14 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
44920
45487
|
}
|
|
44921
45488
|
}
|
|
44922
45489
|
}
|
|
44923
|
-
const turboPath =
|
|
45490
|
+
const turboPath = join30(repoRoot, "turbo.json");
|
|
44924
45491
|
try {
|
|
44925
45492
|
const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
|
|
44926
45493
|
if (Array.isArray(turbo.packages)) {
|
|
44927
45494
|
await resolveGlobs(turbo.packages);
|
|
44928
45495
|
}
|
|
44929
45496
|
} catch {}
|
|
44930
|
-
const pkgPath =
|
|
45497
|
+
const pkgPath = join30(repoRoot, "package.json");
|
|
44931
45498
|
try {
|
|
44932
45499
|
const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
|
|
44933
45500
|
const ws = pkg.workspaces;
|
|
@@ -44935,7 +45502,7 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
44935
45502
|
if (patterns.length > 0)
|
|
44936
45503
|
await resolveGlobs(patterns);
|
|
44937
45504
|
} catch {}
|
|
44938
|
-
const pnpmPath =
|
|
45505
|
+
const pnpmPath = join30(repoRoot, "pnpm-workspace.yaml");
|
|
44939
45506
|
try {
|
|
44940
45507
|
const raw = await _generatorDeps.readTextFile(pnpmPath);
|
|
44941
45508
|
const lines = raw.split(`
|
|
@@ -44961,7 +45528,7 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
44961
45528
|
async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
|
|
44962
45529
|
const resolvedRepoRoot = repoRoot ?? packageDir;
|
|
44963
45530
|
const relativePkgPath = relative13(resolvedRepoRoot, packageDir);
|
|
44964
|
-
const contextPath =
|
|
45531
|
+
const contextPath = join30(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
|
|
44965
45532
|
if (!_generatorDeps.existsSync(contextPath)) {
|
|
44966
45533
|
return [
|
|
44967
45534
|
{
|
|
@@ -45029,7 +45596,7 @@ var init_generator2 = __esm(() => {
|
|
|
45029
45596
|
});
|
|
45030
45597
|
|
|
45031
45598
|
// src/analyze/scanner.ts
|
|
45032
|
-
import { join as
|
|
45599
|
+
import { join as join31 } from "path";
|
|
45033
45600
|
function resolveFrameworkAndRunner(language, pkg) {
|
|
45034
45601
|
if (language === "go")
|
|
45035
45602
|
return { framework: "", testRunner: "go-test" };
|
|
@@ -45051,7 +45618,7 @@ async function scanSourceRoots(workdir) {
|
|
|
45051
45618
|
});
|
|
45052
45619
|
try {
|
|
45053
45620
|
const language = await deps.detectLanguage(workdir);
|
|
45054
|
-
const pkg = await deps.readPackageJson(
|
|
45621
|
+
const pkg = await deps.readPackageJson(join31(workdir, "package.json"));
|
|
45055
45622
|
const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
|
|
45056
45623
|
return [{ path: ".", language, framework, testRunner }];
|
|
45057
45624
|
} catch {
|
|
@@ -45069,9 +45636,9 @@ async function scanSourceRoots(workdir) {
|
|
|
45069
45636
|
packages = packages.slice(0, MAX_SOURCE_ROOTS);
|
|
45070
45637
|
}
|
|
45071
45638
|
return Promise.all(packages.map(async (pkgPath) => {
|
|
45072
|
-
const pkgDir = pkgPath === "." ? workdir :
|
|
45639
|
+
const pkgDir = pkgPath === "." ? workdir : join31(workdir, pkgPath);
|
|
45073
45640
|
const language = await deps.detectLanguage(pkgDir);
|
|
45074
|
-
const pkg = await deps.readPackageJson(
|
|
45641
|
+
const pkg = await deps.readPackageJson(join31(pkgDir, "package.json"));
|
|
45075
45642
|
const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
|
|
45076
45643
|
return { path: pkgPath, language, framework, testRunner };
|
|
45077
45644
|
}));
|
|
@@ -45104,7 +45671,7 @@ var init_analyze = __esm(() => {
|
|
|
45104
45671
|
});
|
|
45105
45672
|
|
|
45106
45673
|
// src/debate/pre-phase/grounder.ts
|
|
45107
|
-
import { join as
|
|
45674
|
+
import { join as join32 } from "path";
|
|
45108
45675
|
async function buildCodebaseContext(workdir) {
|
|
45109
45676
|
const roots = await _grounderDeps.scanSourceRoots(workdir);
|
|
45110
45677
|
return buildSourceRootsSection(normalizeRoots(workdir, roots));
|
|
@@ -45116,7 +45683,7 @@ function normalizeRoots(workdir, roots) {
|
|
|
45116
45683
|
}));
|
|
45117
45684
|
}
|
|
45118
45685
|
async function writeManifestArtifact(ctx, manifest) {
|
|
45119
|
-
const manifestPath =
|
|
45686
|
+
const manifestPath = join32(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
|
|
45120
45687
|
await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
|
|
45121
45688
|
}
|
|
45122
45689
|
var _grounderDeps, grounderStrategy = async (ctx) => {
|
|
@@ -45477,7 +46044,7 @@ function formatSpecDeltas(blockers, manifest) {
|
|
|
45477
46044
|
|
|
45478
46045
|
// src/debate/verifiers/checks.ts
|
|
45479
46046
|
import { existsSync as defaultExistsSync } from "fs";
|
|
45480
|
-
import { join as
|
|
46047
|
+
import { join as join33 } from "path";
|
|
45481
46048
|
function checkFilesExist(prd, workdir, deps) {
|
|
45482
46049
|
const existsSync10 = deps?.existsSync ?? defaultExistsSync;
|
|
45483
46050
|
const findings = [];
|
|
@@ -45487,7 +46054,7 @@ function checkFilesExist(prd, workdir, deps) {
|
|
|
45487
46054
|
for (const entry of story.contextFiles) {
|
|
45488
46055
|
const filePath = typeof entry === "string" ? entry : entry.path;
|
|
45489
46056
|
const factId = typeof entry === "string" ? undefined : entry.factId;
|
|
45490
|
-
const absPath =
|
|
46057
|
+
const absPath = join33(workdir, filePath);
|
|
45491
46058
|
if (existsSync10(absPath))
|
|
45492
46059
|
continue;
|
|
45493
46060
|
if (factId) {
|
|
@@ -45598,7 +46165,7 @@ var init_checks3 = () => {};
|
|
|
45598
46165
|
|
|
45599
46166
|
// src/debate/verifiers/plan-checklist.ts
|
|
45600
46167
|
import { existsSync as existsSync10 } from "fs";
|
|
45601
|
-
import { join as
|
|
46168
|
+
import { join as join34 } from "path";
|
|
45602
46169
|
function parsePrd(output) {
|
|
45603
46170
|
if (!output)
|
|
45604
46171
|
return null;
|
|
@@ -45609,7 +46176,7 @@ function parsePrd(output) {
|
|
|
45609
46176
|
}
|
|
45610
46177
|
}
|
|
45611
46178
|
async function loadManifest(ctx) {
|
|
45612
|
-
const manifestPath =
|
|
46179
|
+
const manifestPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
|
|
45613
46180
|
const raw = await _planChecklistDeps.readFile(manifestPath);
|
|
45614
46181
|
if (!raw)
|
|
45615
46182
|
return null;
|
|
@@ -45622,7 +46189,7 @@ async function loadManifest(ctx) {
|
|
|
45622
46189
|
}
|
|
45623
46190
|
}
|
|
45624
46191
|
async function emitSpecDeltas(ctx, blockers, manifest) {
|
|
45625
|
-
const artifactPath =
|
|
46192
|
+
const artifactPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
|
|
45626
46193
|
const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
|
|
45627
46194
|
await _planChecklistDeps.write(artifactPath, content);
|
|
45628
46195
|
return artifactPath;
|
|
@@ -45973,7 +46540,7 @@ var init_runner_plan_helpers = __esm(() => {
|
|
|
45973
46540
|
});
|
|
45974
46541
|
|
|
45975
46542
|
// src/debate/runner-plan.ts
|
|
45976
|
-
import { join as
|
|
46543
|
+
import { join as join35 } from "path";
|
|
45977
46544
|
async function runPlan(ctx, taskContext, outputFormat, opts) {
|
|
45978
46545
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
45979
46546
|
const config2 = ctx.stageConfig;
|
|
@@ -46032,7 +46599,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
|
|
|
46032
46599
|
sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
|
|
46033
46600
|
proposers: ctx.stageConfig.proposers
|
|
46034
46601
|
});
|
|
46035
|
-
const outputPaths = resolved.map((_, i) =>
|
|
46602
|
+
const outputPaths = resolved.map((_, i) => join35(opts.outputDir, `prd-debate-${i}.json`));
|
|
46036
46603
|
const successful = [];
|
|
46037
46604
|
let rebuttalList;
|
|
46038
46605
|
if (selectorKind === "verifier-pick") {
|
|
@@ -48280,9 +48847,9 @@ function validateFeatureName(feature) {
|
|
|
48280
48847
|
|
|
48281
48848
|
// src/plan/critic.ts
|
|
48282
48849
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
48283
|
-
import { dirname as dirname7, join as
|
|
48850
|
+
import { dirname as dirname7, join as join38 } from "path";
|
|
48284
48851
|
async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
|
|
48285
|
-
const path7 =
|
|
48852
|
+
const path7 = join38(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
|
|
48286
48853
|
await mkdir6(dirname7(path7), { recursive: true });
|
|
48287
48854
|
await Bun.write(path7, formatSpecDeltas(findings, manifest));
|
|
48288
48855
|
return path7;
|
|
@@ -49495,9 +50062,9 @@ __export(exports_plan_decompose, {
|
|
|
49495
50062
|
runReplanLoop: () => runReplanLoop,
|
|
49496
50063
|
planDecomposeCommand: () => planDecomposeCommand
|
|
49497
50064
|
});
|
|
49498
|
-
import { join as
|
|
50065
|
+
import { join as join39 } from "path";
|
|
49499
50066
|
async function planDecomposeCommand(workdir, config2, options) {
|
|
49500
|
-
const prdPath =
|
|
50067
|
+
const prdPath = join39(workdir, ".nax", "features", options.feature, "prd.json");
|
|
49501
50068
|
if (!_planDeps.existsSync(prdPath)) {
|
|
49502
50069
|
throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
|
|
49503
50070
|
stage: "decompose",
|
|
@@ -49671,7 +50238,7 @@ var init_plan_decompose = __esm(() => {
|
|
|
49671
50238
|
|
|
49672
50239
|
// src/cli/plan-runtime.ts
|
|
49673
50240
|
import { existsSync as existsSync15 } from "fs";
|
|
49674
|
-
import { join as
|
|
50241
|
+
import { join as join40 } from "path";
|
|
49675
50242
|
function isRuntimeWithAgentManager(value) {
|
|
49676
50243
|
return typeof value === "object" && value !== null && "agentManager" in value;
|
|
49677
50244
|
}
|
|
@@ -49723,7 +50290,7 @@ var init_plan_runtime = __esm(() => {
|
|
|
49723
50290
|
writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
|
|
49724
50291
|
scanSourceRoots: (workdir) => scanSourceRoots(workdir),
|
|
49725
50292
|
createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
|
|
49726
|
-
readPackageJson: (workdir) => Bun.file(
|
|
50293
|
+
readPackageJson: (workdir) => Bun.file(join40(workdir, "package.json")).json().catch(() => null),
|
|
49727
50294
|
spawnSync: (cmd, opts) => {
|
|
49728
50295
|
const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
|
|
49729
50296
|
return { stdout: result.stdout, exitCode: result.exitCode };
|
|
@@ -50108,7 +50675,7 @@ var init_metrics = __esm(() => {
|
|
|
50108
50675
|
|
|
50109
50676
|
// src/commands/common.ts
|
|
50110
50677
|
import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
|
|
50111
|
-
import { join as
|
|
50678
|
+
import { join as join41, resolve as resolve13 } from "path";
|
|
50112
50679
|
function resolveProject(options = {}) {
|
|
50113
50680
|
const { dir, feature } = options;
|
|
50114
50681
|
let projectRoot;
|
|
@@ -50116,12 +50683,12 @@ function resolveProject(options = {}) {
|
|
|
50116
50683
|
let configPath;
|
|
50117
50684
|
if (dir) {
|
|
50118
50685
|
projectRoot = realpathSync3(resolve13(dir));
|
|
50119
|
-
naxDir =
|
|
50686
|
+
naxDir = join41(projectRoot, ".nax");
|
|
50120
50687
|
if (!existsSync16(naxDir)) {
|
|
50121
50688
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
50122
50689
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
50123
50690
|
}
|
|
50124
|
-
configPath =
|
|
50691
|
+
configPath = join41(naxDir, "config.json");
|
|
50125
50692
|
if (!existsSync16(configPath)) {
|
|
50126
50693
|
throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
|
|
50127
50694
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
@@ -50129,17 +50696,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
|
50129
50696
|
} else {
|
|
50130
50697
|
const found = findProjectRoot(process.cwd());
|
|
50131
50698
|
if (!found) {
|
|
50132
|
-
const cwdNaxDir =
|
|
50699
|
+
const cwdNaxDir = join41(process.cwd(), ".nax");
|
|
50133
50700
|
if (existsSync16(cwdNaxDir)) {
|
|
50134
|
-
const cwdConfigPath =
|
|
50701
|
+
const cwdConfigPath = join41(cwdNaxDir, "config.json");
|
|
50135
50702
|
throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
|
|
50136
50703
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
50137
50704
|
}
|
|
50138
50705
|
throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
|
|
50139
50706
|
}
|
|
50140
50707
|
projectRoot = found;
|
|
50141
|
-
naxDir =
|
|
50142
|
-
configPath =
|
|
50708
|
+
naxDir = join41(projectRoot, ".nax");
|
|
50709
|
+
configPath = join41(naxDir, "config.json");
|
|
50143
50710
|
}
|
|
50144
50711
|
let featureDir;
|
|
50145
50712
|
if (feature) {
|
|
@@ -50148,8 +50715,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
|
|
|
50148
50715
|
} catch (error48) {
|
|
50149
50716
|
throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
|
|
50150
50717
|
}
|
|
50151
|
-
const featuresDir =
|
|
50152
|
-
featureDir =
|
|
50718
|
+
const featuresDir = join41(naxDir, "features");
|
|
50719
|
+
featureDir = join41(featuresDir, feature);
|
|
50153
50720
|
if (!existsSync16(featureDir)) {
|
|
50154
50721
|
const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
50155
50722
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
@@ -50182,7 +50749,7 @@ async function resolveProjectAsync(options = {}) {
|
|
|
50182
50749
|
}
|
|
50183
50750
|
const isPlainName = !dir.includes("/") && !dir.includes("\\");
|
|
50184
50751
|
if (isPlainName) {
|
|
50185
|
-
const registryIdentityPath =
|
|
50752
|
+
const registryIdentityPath = join41(globalConfigDir(), dir, ".identity");
|
|
50186
50753
|
const identityFile = Bun.file(registryIdentityPath);
|
|
50187
50754
|
if (await identityFile.exists()) {
|
|
50188
50755
|
try {
|
|
@@ -50214,12 +50781,12 @@ function findProjectRoot(startDir) {
|
|
|
50214
50781
|
let current = resolve13(startDir);
|
|
50215
50782
|
let depth = 0;
|
|
50216
50783
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
50217
|
-
const naxDir =
|
|
50218
|
-
const configPath =
|
|
50784
|
+
const naxDir = join41(current, ".nax");
|
|
50785
|
+
const configPath = join41(naxDir, "config.json");
|
|
50219
50786
|
if (existsSync16(configPath)) {
|
|
50220
50787
|
return realpathSync3(current);
|
|
50221
50788
|
}
|
|
50222
|
-
const parent =
|
|
50789
|
+
const parent = join41(current, "..");
|
|
50223
50790
|
if (parent === current) {
|
|
50224
50791
|
break;
|
|
50225
50792
|
}
|
|
@@ -51378,10 +51945,10 @@ var init_effectiveness = __esm(() => {
|
|
|
51378
51945
|
|
|
51379
51946
|
// src/execution/progress.ts
|
|
51380
51947
|
import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
|
|
51381
|
-
import { join as
|
|
51948
|
+
import { join as join44 } from "path";
|
|
51382
51949
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
51383
51950
|
await mkdir7(featureDir, { recursive: true });
|
|
51384
|
-
const progressPath =
|
|
51951
|
+
const progressPath = join44(featureDir, "progress.txt");
|
|
51385
51952
|
const timestamp = new Date().toISOString();
|
|
51386
51953
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
51387
51954
|
`;
|
|
@@ -51570,7 +52137,7 @@ var init_completion = __esm(() => {
|
|
|
51570
52137
|
|
|
51571
52138
|
// src/constitution/loader.ts
|
|
51572
52139
|
import { existsSync as existsSync19 } from "fs";
|
|
51573
|
-
import { join as
|
|
52140
|
+
import { join as join45 } from "path";
|
|
51574
52141
|
function truncateToTokens(text, maxTokens) {
|
|
51575
52142
|
const maxChars = maxTokens * 3;
|
|
51576
52143
|
if (text.length <= maxChars) {
|
|
@@ -51592,7 +52159,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
51592
52159
|
}
|
|
51593
52160
|
let combinedContent = "";
|
|
51594
52161
|
if (!config2.skipGlobal) {
|
|
51595
|
-
const globalPath =
|
|
52162
|
+
const globalPath = join45(globalConfigDir(), config2.path);
|
|
51596
52163
|
if (existsSync19(globalPath)) {
|
|
51597
52164
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
51598
52165
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -51602,7 +52169,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
51602
52169
|
}
|
|
51603
52170
|
}
|
|
51604
52171
|
}
|
|
51605
|
-
const projectPath =
|
|
52172
|
+
const projectPath = join45(projectDir, config2.path);
|
|
51606
52173
|
if (existsSync19(projectPath)) {
|
|
51607
52174
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
51608
52175
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -52209,11 +52776,24 @@ function toReviewDecisionPayload(opName, output) {
|
|
|
52209
52776
|
if (typeof record2.passed !== "boolean" || !Array.isArray(record2.findings)) {
|
|
52210
52777
|
return null;
|
|
52211
52778
|
}
|
|
52779
|
+
const acDropped = Array.isArray(record2.acDropped) ? record2.acDropped.map((d) => {
|
|
52780
|
+
const entry = d ?? {};
|
|
52781
|
+
const finding = entry.finding ?? {};
|
|
52782
|
+
return {
|
|
52783
|
+
code: typeof entry.code === "string" ? entry.code : undefined,
|
|
52784
|
+
severity: typeof finding.severity === "string" ? finding.severity : undefined,
|
|
52785
|
+
file: typeof finding.file === "string" ? finding.file : undefined,
|
|
52786
|
+
line: typeof finding.line === "number" ? finding.line : undefined,
|
|
52787
|
+
issue: typeof finding.issue === "string" ? finding.issue : undefined,
|
|
52788
|
+
acIndex: typeof finding.acIndex === "number" ? finding.acIndex : undefined
|
|
52789
|
+
};
|
|
52790
|
+
}) : undefined;
|
|
52212
52791
|
return {
|
|
52213
52792
|
reviewer,
|
|
52214
52793
|
parsed: true,
|
|
52215
52794
|
passed: record2.passed,
|
|
52216
|
-
result: { passed: record2.passed, findings: record2.findings }
|
|
52795
|
+
result: { passed: record2.passed, findings: record2.findings },
|
|
52796
|
+
acDropped
|
|
52217
52797
|
};
|
|
52218
52798
|
}
|
|
52219
52799
|
function emitReviewDecision(ctx, opName, output) {
|
|
@@ -52262,12 +52842,38 @@ function logUnifiedReviewPhaseResult(storyId, opName, output) {
|
|
|
52262
52842
|
const title = payload.reviewer === "semantic" ? "Semantic review" : "Adversarial review";
|
|
52263
52843
|
if (payload.passed) {
|
|
52264
52844
|
logger?.info("review", `${title} passed`, { storyId });
|
|
52265
|
-
|
|
52266
|
-
|
|
52845
|
+
return;
|
|
52846
|
+
}
|
|
52847
|
+
if (findingsCount === 0) {
|
|
52848
|
+
const dropped = payload.acDropped ?? [];
|
|
52849
|
+
const droppedSummary = dropped.slice(0, 5);
|
|
52850
|
+
logger?.warn("review", `${title} failed: 0 findings \u2014 ${dropped.length > 0 ? `${dropped.length} blocking finding(s) dropped as ungrounded by AC-grounding filter` : "model emitted passed:false but produced no findings (likely empty output)"}`, {
|
|
52267
52851
|
storyId,
|
|
52268
|
-
findingsCount
|
|
52852
|
+
findingsCount,
|
|
52853
|
+
reason: dropped.length > 0 ? "ac-grounding-drop" : "passed-false-no-findings",
|
|
52854
|
+
droppedCount: dropped.length || undefined,
|
|
52855
|
+
droppedFindings: droppedSummary.length > 0 ? droppedSummary : undefined,
|
|
52856
|
+
droppedTruncated: dropped.length > droppedSummary.length || undefined
|
|
52269
52857
|
});
|
|
52858
|
+
return;
|
|
52270
52859
|
}
|
|
52860
|
+
const findingsSummary = payload.result.findings.slice(0, 5).map((f) => {
|
|
52861
|
+
const r = f ?? {};
|
|
52862
|
+
return {
|
|
52863
|
+
severity: typeof r.severity === "string" ? r.severity : undefined,
|
|
52864
|
+
file: typeof r.file === "string" ? r.file : undefined,
|
|
52865
|
+
line: typeof r.line === "number" ? r.line : undefined,
|
|
52866
|
+
rule: typeof r.rule === "string" ? r.rule : undefined,
|
|
52867
|
+
issue: typeof r.issue === "string" ? r.issue : typeof r.message === "string" ? r.message : undefined,
|
|
52868
|
+
acIndex: typeof r.acIndex === "number" ? r.acIndex : undefined
|
|
52869
|
+
};
|
|
52870
|
+
});
|
|
52871
|
+
logger?.warn("review", `${title} failed: ${findingsCount} findings`, {
|
|
52872
|
+
storyId,
|
|
52873
|
+
findingsCount,
|
|
52874
|
+
findings: findingsSummary,
|
|
52875
|
+
truncated: findingsCount > findingsSummary.length
|
|
52876
|
+
});
|
|
52271
52877
|
}
|
|
52272
52878
|
async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = false) {
|
|
52273
52879
|
const logger = getSafeLogger();
|
|
@@ -52386,7 +52992,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
|
|
|
52386
52992
|
continue;
|
|
52387
52993
|
findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
|
|
52388
52994
|
}
|
|
52389
|
-
return findings;
|
|
52995
|
+
return rectification2.postValidate ? await rectification2.postValidate(findings, _validateCtx) : findings;
|
|
52390
52996
|
}
|
|
52391
52997
|
};
|
|
52392
52998
|
const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp });
|
|
@@ -52400,7 +53006,8 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
|
|
|
52400
53006
|
"max-attempts-total",
|
|
52401
53007
|
"max-attempts-per-strategy",
|
|
52402
53008
|
"bail-when",
|
|
52403
|
-
"no-strategy"
|
|
53009
|
+
"no-strategy",
|
|
53010
|
+
"agent-gave-up"
|
|
52404
53011
|
]);
|
|
52405
53012
|
if (exhaustedReasons.has(cycleResult.exitReason) && cycleResult.finalFindings.length > 0) {
|
|
52406
53013
|
return { rectificationExhausted: true, unfixedFindings: cycleResult.finalFindings };
|
|
@@ -52585,6 +53192,7 @@ var init_story_orchestrator = __esm(() => {
|
|
|
52585
53192
|
});
|
|
52586
53193
|
|
|
52587
53194
|
// src/execution/build-plan-for-strategy.ts
|
|
53195
|
+
import { join as join46 } from "path";
|
|
52588
53196
|
function isThreeSessionStrategy(strategy) {
|
|
52589
53197
|
return THREE_SESSION_STRATEGIES2.has(strategy);
|
|
52590
53198
|
}
|
|
@@ -52596,7 +53204,7 @@ function isFreshRun(story) {
|
|
|
52596
53204
|
const hasReviewEscalation = (story.priorFailures ?? []).some((f) => f.stage === "review");
|
|
52597
53205
|
return !hasAttempts && !hasReviewEscalation;
|
|
52598
53206
|
}
|
|
52599
|
-
function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
53207
|
+
async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
52600
53208
|
const isThreeSession = isThreeSessionStrategy(testStrategy);
|
|
52601
53209
|
const freshRun = isFreshRun(story);
|
|
52602
53210
|
const builder = new StoryOrchestratorBuilder;
|
|
@@ -52631,6 +53239,9 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
52631
53239
|
builder.addAdversarialReview(inputs.adversarialReview);
|
|
52632
53240
|
}
|
|
52633
53241
|
if (shouldRunRectification(config2) && inputs.rectification) {
|
|
53242
|
+
const sink = makeDeclarationSink();
|
|
53243
|
+
const packageDir = join46(ctx.packageDir, story.workdir ?? "");
|
|
53244
|
+
const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
|
|
52634
53245
|
const strategies = [];
|
|
52635
53246
|
if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
|
|
52636
53247
|
strategies.push(makeMechanicalLintFixStrategy());
|
|
@@ -52639,15 +53250,31 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
52639
53250
|
strategies.push(makeMechanicalFormatFixStrategy());
|
|
52640
53251
|
}
|
|
52641
53252
|
if (isThreeSession && inputs.fullSuiteGate) {
|
|
52642
|
-
strategies.push(makeFullSuiteRectifyStrategy(story));
|
|
53253
|
+
strategies.push(makeFullSuiteRectifyStrategy(story, config2));
|
|
52643
53254
|
}
|
|
52644
53255
|
if (config2.quality.autofix?.enabled !== false) {
|
|
52645
|
-
strategies.push(makeAutofixImplementerStrategy(story));
|
|
52646
|
-
strategies.push(makeAutofixTestWriterStrategy(story, config2));
|
|
52647
|
-
}
|
|
53256
|
+
strategies.push(makeAutofixImplementerStrategy(story, config2, sink));
|
|
53257
|
+
strategies.push(makeAutofixTestWriterStrategy(story, config2, sink));
|
|
53258
|
+
}
|
|
53259
|
+
const postValidate = async (findings, _validateCtx) => {
|
|
53260
|
+
if (sink.testEdits.length === 0 && sink.mockHandoffs.length === 0)
|
|
53261
|
+
return findings;
|
|
53262
|
+
const pendingMock = sink.mockHandoffs.map((h) => ({
|
|
53263
|
+
reason: "mock_structure",
|
|
53264
|
+
file: h.files[0] ?? "",
|
|
53265
|
+
files: h.files,
|
|
53266
|
+
reasonDetail: h.reasonDetail
|
|
53267
|
+
}));
|
|
53268
|
+
const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir);
|
|
53269
|
+
sink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
|
|
53270
|
+
const allDeclarations = [...sink.testEdits, ...valid];
|
|
53271
|
+
sink.testEdits = [];
|
|
53272
|
+
return applyTestEditDeclarations(findings, allDeclarations, story, invalid);
|
|
53273
|
+
};
|
|
52648
53274
|
const rectOpts = {
|
|
52649
53275
|
...inputs.rectification,
|
|
52650
|
-
strategies: [...strategies, ...inputs.rectification.strategies]
|
|
53276
|
+
strategies: [...strategies, ...inputs.rectification.strategies],
|
|
53277
|
+
postValidate
|
|
52651
53278
|
};
|
|
52652
53279
|
builder.addRectification(rectOpts);
|
|
52653
53280
|
}
|
|
@@ -52658,6 +53285,7 @@ var init_build_plan_for_strategy = __esm(() => {
|
|
|
52658
53285
|
init_operations();
|
|
52659
53286
|
init_execution_gates();
|
|
52660
53287
|
init_full_suite_rectify();
|
|
53288
|
+
init_test_runners();
|
|
52661
53289
|
init_story_orchestrator();
|
|
52662
53290
|
THREE_SESSION_STRATEGIES2 = new Set(["three-session-tdd", "three-session-tdd-lite"]);
|
|
52663
53291
|
});
|
|
@@ -52783,9 +53411,9 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
52783
53411
|
blockingThreshold: ctx.config.review.blockingThreshold
|
|
52784
53412
|
} : undefined;
|
|
52785
53413
|
const rectificationInput = ctx.config.execution?.rectification?.enabled === true ? {
|
|
52786
|
-
maxAttempts: ctx.config.execution.rectification.
|
|
53414
|
+
maxAttempts: ctx.config.execution.rectification.maxAttemptsTotal,
|
|
52787
53415
|
strategies: [],
|
|
52788
|
-
abortOnIncreasingFailures: ctx.config.execution.rectification.abortOnIncreasingFailures
|
|
53416
|
+
abortOnIncreasingFailures: ctx.config.execution.rectification.abortOnIncreasingFailures
|
|
52789
53417
|
} : undefined;
|
|
52790
53418
|
return {
|
|
52791
53419
|
story,
|
|
@@ -53249,10 +53877,29 @@ Category: ${failureCategory ?? "unknown"}`,
|
|
|
53249
53877
|
}
|
|
53250
53878
|
}
|
|
53251
53879
|
if (!planResult.success) {
|
|
53880
|
+
const failedPhases = {};
|
|
53881
|
+
for (const [name, output] of Object.entries(planResult.phaseOutputs)) {
|
|
53882
|
+
if (!output || typeof output !== "object")
|
|
53883
|
+
continue;
|
|
53884
|
+
const r = output;
|
|
53885
|
+
const passed = typeof r.passed === "boolean" ? r.passed : undefined;
|
|
53886
|
+
const success2 = typeof r.success === "boolean" ? r.success : undefined;
|
|
53887
|
+
const explicitFail = passed === false || success2 === false;
|
|
53888
|
+
if (!explicitFail)
|
|
53889
|
+
continue;
|
|
53890
|
+
const findings = Array.isArray(r.findings) ? r.findings.length : undefined;
|
|
53891
|
+
failedPhases[name] = { passed, success: success2, findingsCount: findings };
|
|
53892
|
+
}
|
|
53893
|
+
const stderrTail = (agentResult.stderr ?? "").slice(-500);
|
|
53894
|
+
const outputTail = (agentResult.output ?? "").slice(-500);
|
|
53252
53895
|
logger.error("execution", "Agent session failed", {
|
|
53253
53896
|
storyId: ctx.story.id,
|
|
53254
53897
|
exitCode: agentResult.exitCode,
|
|
53255
|
-
rateLimited: agentResult.rateLimited
|
|
53898
|
+
rateLimited: agentResult.rateLimited,
|
|
53899
|
+
failureCategory: failureCategory ?? "unknown",
|
|
53900
|
+
failedPhases: Object.keys(failedPhases).length > 0 ? failedPhases : undefined,
|
|
53901
|
+
stderrTail: stderrTail || undefined,
|
|
53902
|
+
outputTail: outputTail || undefined
|
|
53256
53903
|
});
|
|
53257
53904
|
if (agentResult.rateLimited) {
|
|
53258
53905
|
logger.warn("execution", "Rate limited \u2014 will retry", { storyId: ctx.story.id });
|
|
@@ -53357,7 +54004,7 @@ var init_execution = __esm(() => {
|
|
|
53357
54004
|
} : null;
|
|
53358
54005
|
const initialRef = tddMode ? await _executionDeps.captureGitRef(ctx.workdir) ?? "HEAD" : null;
|
|
53359
54006
|
const inputs = await _executionDeps.assemblePlanInputsFromCtx(ctx);
|
|
53360
|
-
const plan = buildPlanForStrategy(callCtx, ctx.story, ctx.config, ctx.routing.testStrategy, inputs);
|
|
54007
|
+
const plan = await buildPlanForStrategy(callCtx, ctx.story, ctx.config, ctx.routing.testStrategy, inputs);
|
|
53361
54008
|
let planResult;
|
|
53362
54009
|
try {
|
|
53363
54010
|
planResult = await plan.run();
|
|
@@ -53976,6 +54623,11 @@ class RegressionStrategy {
|
|
|
53976
54623
|
const durationMs = Date.now() - start;
|
|
53977
54624
|
if (result.success) {
|
|
53978
54625
|
const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
54626
|
+
logger?.info("verify[regression]", "Full-suite regression gate passed", {
|
|
54627
|
+
storyId: ctx.storyId,
|
|
54628
|
+
passCount: parsed2.passed,
|
|
54629
|
+
durationMs
|
|
54630
|
+
});
|
|
53979
54631
|
return makePassResult(ctx.storyId, "regression", {
|
|
53980
54632
|
rawOutput: result.output,
|
|
53981
54633
|
passCount: parsed2.passed,
|
|
@@ -53989,9 +54641,19 @@ class RegressionStrategy {
|
|
|
53989
54641
|
return makePassResult(ctx.storyId, "regression", { durationMs });
|
|
53990
54642
|
}
|
|
53991
54643
|
if (result.status === "TIMEOUT") {
|
|
54644
|
+
logger?.warn("verify[regression]", "Full-suite regression gate timed out", {
|
|
54645
|
+
storyId: ctx.storyId,
|
|
54646
|
+
durationMs
|
|
54647
|
+
});
|
|
53992
54648
|
return makeFailResult(ctx.storyId, "regression", "TIMEOUT", { rawOutput: result.output, durationMs });
|
|
53993
54649
|
}
|
|
53994
54650
|
const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
|
|
54651
|
+
logger?.warn("verify[regression]", "Full-suite regression gate failed", {
|
|
54652
|
+
storyId: ctx.storyId,
|
|
54653
|
+
passCount: parsed.passed,
|
|
54654
|
+
failCount: parsed.failed,
|
|
54655
|
+
durationMs
|
|
54656
|
+
});
|
|
53995
54657
|
return makeFailResult(ctx.storyId, "regression", "TEST_FAILURE", {
|
|
53996
54658
|
rawOutput: result.output,
|
|
53997
54659
|
passCount: parsed.passed,
|
|
@@ -54289,7 +54951,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
54289
54951
|
}
|
|
54290
54952
|
|
|
54291
54953
|
// src/cli/prompts-tdd.ts
|
|
54292
|
-
import { join as
|
|
54954
|
+
import { join as join47 } from "path";
|
|
54293
54955
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
54294
54956
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
54295
54957
|
TddPromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
@@ -54308,7 +54970,7 @@ ${frontmatter}---
|
|
|
54308
54970
|
|
|
54309
54971
|
${session.prompt}`;
|
|
54310
54972
|
if (outputDir) {
|
|
54311
|
-
const promptFile =
|
|
54973
|
+
const promptFile = join47(outputDir, `${story.id}.${session.role}.md`);
|
|
54312
54974
|
await Bun.write(promptFile, fullOutput);
|
|
54313
54975
|
logger.info("cli", "Written TDD prompt file", {
|
|
54314
54976
|
storyId: story.id,
|
|
@@ -54324,7 +54986,7 @@ ${"=".repeat(80)}`);
|
|
|
54324
54986
|
}
|
|
54325
54987
|
}
|
|
54326
54988
|
if (outputDir && ctx.contextMarkdown) {
|
|
54327
|
-
const contextFile =
|
|
54989
|
+
const contextFile = join47(outputDir, `${story.id}.context.md`);
|
|
54328
54990
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
54329
54991
|
const contextOutput = `---
|
|
54330
54992
|
${frontmatter}---
|
|
@@ -54339,16 +55001,16 @@ var init_prompts_tdd = __esm(() => {
|
|
|
54339
55001
|
|
|
54340
55002
|
// src/cli/prompts-main.ts
|
|
54341
55003
|
import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
|
|
54342
|
-
import { join as
|
|
55004
|
+
import { join as join48 } from "path";
|
|
54343
55005
|
async function promptsCommand(options) {
|
|
54344
55006
|
const logger = getLogger();
|
|
54345
55007
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
54346
|
-
const naxDir =
|
|
55008
|
+
const naxDir = join48(workdir, ".nax");
|
|
54347
55009
|
if (!existsSync20(naxDir)) {
|
|
54348
55010
|
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
54349
55011
|
}
|
|
54350
|
-
const featureDir =
|
|
54351
|
-
const prdPath =
|
|
55012
|
+
const featureDir = join48(naxDir, "features", feature);
|
|
55013
|
+
const prdPath = join48(featureDir, "prd.json");
|
|
54352
55014
|
if (!existsSync20(prdPath)) {
|
|
54353
55015
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
54354
55016
|
}
|
|
@@ -54415,10 +55077,10 @@ ${frontmatter}---
|
|
|
54415
55077
|
|
|
54416
55078
|
${ctx.prompt}`;
|
|
54417
55079
|
if (outputDir) {
|
|
54418
|
-
const promptFile =
|
|
55080
|
+
const promptFile = join48(outputDir, `${story.id}.prompt.md`);
|
|
54419
55081
|
await Bun.write(promptFile, fullOutput);
|
|
54420
55082
|
if (ctx.contextMarkdown) {
|
|
54421
|
-
const contextFile =
|
|
55083
|
+
const contextFile = join48(outputDir, `${story.id}.context.md`);
|
|
54422
55084
|
const contextOutput = `---
|
|
54423
55085
|
${frontmatter}---
|
|
54424
55086
|
|
|
@@ -54454,12 +55116,12 @@ var init_prompts_main = __esm(() => {
|
|
|
54454
55116
|
|
|
54455
55117
|
// src/cli/prompts-init.ts
|
|
54456
55118
|
import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
|
|
54457
|
-
import { join as
|
|
55119
|
+
import { join as join49 } from "path";
|
|
54458
55120
|
async function promptsInitCommand(options) {
|
|
54459
55121
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
54460
|
-
const templatesDir =
|
|
55122
|
+
const templatesDir = join49(workdir, ".nax", "templates");
|
|
54461
55123
|
mkdirSync4(templatesDir, { recursive: true });
|
|
54462
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(
|
|
55124
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join49(templatesDir, f)));
|
|
54463
55125
|
if (existingFiles.length > 0 && !force) {
|
|
54464
55126
|
_promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
54465
55127
|
Pass --force to overwrite existing templates.`);
|
|
@@ -54467,7 +55129,7 @@ async function promptsInitCommand(options) {
|
|
|
54467
55129
|
}
|
|
54468
55130
|
const written = [];
|
|
54469
55131
|
for (const template of TEMPLATE_ROLES) {
|
|
54470
|
-
const filePath =
|
|
55132
|
+
const filePath = join49(templatesDir, template.file);
|
|
54471
55133
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
54472
55134
|
const content = TEMPLATE_HEADER + roleBody;
|
|
54473
55135
|
await Bun.write(filePath, content);
|
|
@@ -54483,7 +55145,7 @@ async function promptsInitCommand(options) {
|
|
|
54483
55145
|
return written;
|
|
54484
55146
|
}
|
|
54485
55147
|
async function autoWirePromptsConfig(workdir) {
|
|
54486
|
-
const configPath =
|
|
55148
|
+
const configPath = join49(workdir, "nax.config.json");
|
|
54487
55149
|
if (!existsSync21(configPath)) {
|
|
54488
55150
|
const exampleConfig = JSON.stringify({
|
|
54489
55151
|
prompts: {
|
|
@@ -54653,7 +55315,7 @@ __export(exports_init_context, {
|
|
|
54653
55315
|
});
|
|
54654
55316
|
import { existsSync as existsSync22 } from "fs";
|
|
54655
55317
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
54656
|
-
import { basename as basename9, join as
|
|
55318
|
+
import { basename as basename9, join as join50 } from "path";
|
|
54657
55319
|
async function findFiles(dir, maxFiles = 200) {
|
|
54658
55320
|
try {
|
|
54659
55321
|
const proc = Bun.spawnSync([
|
|
@@ -54681,7 +55343,7 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
54681
55343
|
return [];
|
|
54682
55344
|
}
|
|
54683
55345
|
async function readPackageManifest(projectRoot) {
|
|
54684
|
-
const packageJsonPath =
|
|
55346
|
+
const packageJsonPath = join50(projectRoot, "package.json");
|
|
54685
55347
|
if (!existsSync22(packageJsonPath)) {
|
|
54686
55348
|
return null;
|
|
54687
55349
|
}
|
|
@@ -54699,7 +55361,7 @@ async function readPackageManifest(projectRoot) {
|
|
|
54699
55361
|
}
|
|
54700
55362
|
}
|
|
54701
55363
|
async function readReadmeSnippet(projectRoot) {
|
|
54702
|
-
const readmePath =
|
|
55364
|
+
const readmePath = join50(projectRoot, "README.md");
|
|
54703
55365
|
if (!existsSync22(readmePath)) {
|
|
54704
55366
|
return null;
|
|
54705
55367
|
}
|
|
@@ -54717,7 +55379,7 @@ async function detectEntryPoints(projectRoot) {
|
|
|
54717
55379
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
54718
55380
|
const found = [];
|
|
54719
55381
|
for (const candidate of candidates) {
|
|
54720
|
-
const path14 =
|
|
55382
|
+
const path14 = join50(projectRoot, candidate);
|
|
54721
55383
|
if (existsSync22(path14)) {
|
|
54722
55384
|
found.push(candidate);
|
|
54723
55385
|
}
|
|
@@ -54728,7 +55390,7 @@ async function detectConfigFiles(projectRoot) {
|
|
|
54728
55390
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
54729
55391
|
const found = [];
|
|
54730
55392
|
for (const candidate of candidates) {
|
|
54731
|
-
const path14 =
|
|
55393
|
+
const path14 = join50(projectRoot, candidate);
|
|
54732
55394
|
if (existsSync22(path14)) {
|
|
54733
55395
|
found.push(candidate);
|
|
54734
55396
|
}
|
|
@@ -54889,8 +55551,8 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
54889
55551
|
}
|
|
54890
55552
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
54891
55553
|
const logger = getLogger();
|
|
54892
|
-
const naxDir =
|
|
54893
|
-
const contextPath =
|
|
55554
|
+
const naxDir = join50(repoRoot, ".nax", "mono", packagePath);
|
|
55555
|
+
const contextPath = join50(naxDir, "context.md");
|
|
54894
55556
|
if (existsSync22(contextPath) && !force) {
|
|
54895
55557
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
54896
55558
|
return;
|
|
@@ -54904,8 +55566,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
54904
55566
|
}
|
|
54905
55567
|
async function initContext(projectRoot, options = {}) {
|
|
54906
55568
|
const logger = getLogger();
|
|
54907
|
-
const naxDir =
|
|
54908
|
-
const contextPath =
|
|
55569
|
+
const naxDir = join50(projectRoot, ".nax");
|
|
55570
|
+
const contextPath = join50(naxDir, "context.md");
|
|
54909
55571
|
if (existsSync22(contextPath) && !options.force) {
|
|
54910
55572
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
54911
55573
|
return;
|
|
@@ -54935,9 +55597,9 @@ var init_init_context = __esm(() => {
|
|
|
54935
55597
|
|
|
54936
55598
|
// src/cli/init-detect.ts
|
|
54937
55599
|
import { existsSync as existsSync23, readFileSync } from "fs";
|
|
54938
|
-
import { join as
|
|
55600
|
+
import { join as join51 } from "path";
|
|
54939
55601
|
function readPackageJson(projectRoot) {
|
|
54940
|
-
const pkgPath =
|
|
55602
|
+
const pkgPath = join51(projectRoot, "package.json");
|
|
54941
55603
|
if (!existsSync23(pkgPath))
|
|
54942
55604
|
return;
|
|
54943
55605
|
try {
|
|
@@ -54980,41 +55642,41 @@ function detectStack(projectRoot) {
|
|
|
54980
55642
|
};
|
|
54981
55643
|
}
|
|
54982
55644
|
function detectRuntime(projectRoot) {
|
|
54983
|
-
if (existsSync23(
|
|
55645
|
+
if (existsSync23(join51(projectRoot, "bun.lockb")) || existsSync23(join51(projectRoot, "bunfig.toml"))) {
|
|
54984
55646
|
return "bun";
|
|
54985
55647
|
}
|
|
54986
|
-
if (existsSync23(
|
|
55648
|
+
if (existsSync23(join51(projectRoot, "package-lock.json")) || existsSync23(join51(projectRoot, "yarn.lock")) || existsSync23(join51(projectRoot, "pnpm-lock.yaml"))) {
|
|
54987
55649
|
return "node";
|
|
54988
55650
|
}
|
|
54989
55651
|
return "unknown";
|
|
54990
55652
|
}
|
|
54991
55653
|
function detectLanguage2(projectRoot) {
|
|
54992
|
-
if (existsSync23(
|
|
55654
|
+
if (existsSync23(join51(projectRoot, "tsconfig.json")))
|
|
54993
55655
|
return "typescript";
|
|
54994
|
-
if (existsSync23(
|
|
55656
|
+
if (existsSync23(join51(projectRoot, "pyproject.toml")) || existsSync23(join51(projectRoot, "setup.py"))) {
|
|
54995
55657
|
return "python";
|
|
54996
55658
|
}
|
|
54997
|
-
if (existsSync23(
|
|
55659
|
+
if (existsSync23(join51(projectRoot, "Cargo.toml")))
|
|
54998
55660
|
return "rust";
|
|
54999
|
-
if (existsSync23(
|
|
55661
|
+
if (existsSync23(join51(projectRoot, "go.mod")))
|
|
55000
55662
|
return "go";
|
|
55001
55663
|
return "unknown";
|
|
55002
55664
|
}
|
|
55003
55665
|
function detectLinter(projectRoot) {
|
|
55004
|
-
if (existsSync23(
|
|
55666
|
+
if (existsSync23(join51(projectRoot, "biome.json")) || existsSync23(join51(projectRoot, "biome.jsonc"))) {
|
|
55005
55667
|
return "biome";
|
|
55006
55668
|
}
|
|
55007
|
-
if (existsSync23(
|
|
55669
|
+
if (existsSync23(join51(projectRoot, ".eslintrc.json")) || existsSync23(join51(projectRoot, ".eslintrc.js")) || existsSync23(join51(projectRoot, "eslint.config.js"))) {
|
|
55008
55670
|
return "eslint";
|
|
55009
55671
|
}
|
|
55010
55672
|
return "unknown";
|
|
55011
55673
|
}
|
|
55012
55674
|
function detectMonorepo(projectRoot) {
|
|
55013
|
-
if (existsSync23(
|
|
55675
|
+
if (existsSync23(join51(projectRoot, "turbo.json")))
|
|
55014
55676
|
return "turborepo";
|
|
55015
|
-
if (existsSync23(
|
|
55677
|
+
if (existsSync23(join51(projectRoot, "nx.json")))
|
|
55016
55678
|
return "nx";
|
|
55017
|
-
if (existsSync23(
|
|
55679
|
+
if (existsSync23(join51(projectRoot, "pnpm-workspace.yaml")))
|
|
55018
55680
|
return "pnpm-workspaces";
|
|
55019
55681
|
const pkg = readPackageJson(projectRoot);
|
|
55020
55682
|
if (pkg?.workspaces)
|
|
@@ -55158,7 +55820,7 @@ __export(exports_init, {
|
|
|
55158
55820
|
});
|
|
55159
55821
|
import { existsSync as existsSync24 } from "fs";
|
|
55160
55822
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
55161
|
-
import { join as
|
|
55823
|
+
import { join as join52 } from "path";
|
|
55162
55824
|
function validateProjectName(name) {
|
|
55163
55825
|
if (!name)
|
|
55164
55826
|
return { valid: false, error: "name must be non-empty" };
|
|
@@ -55194,7 +55856,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
|
|
|
55194
55856
|
}
|
|
55195
55857
|
async function updateGitignore(projectRoot) {
|
|
55196
55858
|
const logger = getLogger();
|
|
55197
|
-
const gitignorePath =
|
|
55859
|
+
const gitignorePath = join52(projectRoot, ".gitignore");
|
|
55198
55860
|
let existing = "";
|
|
55199
55861
|
if (existsSync24(gitignorePath)) {
|
|
55200
55862
|
existing = await Bun.file(gitignorePath).text();
|
|
@@ -55280,7 +55942,7 @@ async function initGlobal() {
|
|
|
55280
55942
|
await mkdir9(globalDir, { recursive: true });
|
|
55281
55943
|
logger.info("init", "Created global config directory", { path: globalDir });
|
|
55282
55944
|
}
|
|
55283
|
-
const configPath =
|
|
55945
|
+
const configPath = join52(globalDir, "config.json");
|
|
55284
55946
|
if (!existsSync24(configPath)) {
|
|
55285
55947
|
await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
|
|
55286
55948
|
`);
|
|
@@ -55288,14 +55950,14 @@ async function initGlobal() {
|
|
|
55288
55950
|
} else {
|
|
55289
55951
|
logger.info("init", "Global config already exists", { path: configPath });
|
|
55290
55952
|
}
|
|
55291
|
-
const constitutionPath =
|
|
55953
|
+
const constitutionPath = join52(globalDir, "constitution.md");
|
|
55292
55954
|
if (!existsSync24(constitutionPath)) {
|
|
55293
55955
|
await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
|
|
55294
55956
|
logger.info("init", "Created global constitution", { path: constitutionPath });
|
|
55295
55957
|
} else {
|
|
55296
55958
|
logger.info("init", "Global constitution already exists", { path: constitutionPath });
|
|
55297
55959
|
}
|
|
55298
|
-
const hooksDir =
|
|
55960
|
+
const hooksDir = join52(globalDir, "hooks");
|
|
55299
55961
|
if (!existsSync24(hooksDir)) {
|
|
55300
55962
|
await mkdir9(hooksDir, { recursive: true });
|
|
55301
55963
|
logger.info("init", "Created global hooks directory", { path: hooksDir });
|
|
@@ -55328,7 +55990,7 @@ async function initProject(projectRoot, options) {
|
|
|
55328
55990
|
if (detectedName && !options?.force) {
|
|
55329
55991
|
const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
|
|
55330
55992
|
if (collision.collision && collision.existing) {
|
|
55331
|
-
const configPath2 =
|
|
55993
|
+
const configPath2 = join52(projectDir, "config.json");
|
|
55332
55994
|
throw new NaxError([
|
|
55333
55995
|
`Project name collision: "${detectedName}"`,
|
|
55334
55996
|
` This project: ${projectRoot}`,
|
|
@@ -55356,7 +56018,7 @@ async function initProject(projectRoot, options) {
|
|
|
55356
56018
|
linter: stack.linter,
|
|
55357
56019
|
monorepo: stack.monorepo
|
|
55358
56020
|
});
|
|
55359
|
-
const configPath =
|
|
56021
|
+
const configPath = join52(projectDir, "config.json");
|
|
55360
56022
|
if (!existsSync24(configPath)) {
|
|
55361
56023
|
await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
|
|
55362
56024
|
`);
|
|
@@ -55365,14 +56027,14 @@ async function initProject(projectRoot, options) {
|
|
|
55365
56027
|
logger.info("init", "Project config already exists", { path: configPath });
|
|
55366
56028
|
}
|
|
55367
56029
|
await initContext(projectRoot, { ai: options?.ai, force: options?.force });
|
|
55368
|
-
const constitutionPath =
|
|
56030
|
+
const constitutionPath = join52(projectDir, "constitution.md");
|
|
55369
56031
|
if (!existsSync24(constitutionPath) || options?.force) {
|
|
55370
56032
|
await Bun.write(constitutionPath, buildConstitution(stack));
|
|
55371
56033
|
logger.info("init", "Created project constitution", { path: constitutionPath });
|
|
55372
56034
|
} else {
|
|
55373
56035
|
logger.info("init", "Project constitution already exists", { path: constitutionPath });
|
|
55374
56036
|
}
|
|
55375
|
-
const hooksDir =
|
|
56037
|
+
const hooksDir = join52(projectDir, "hooks");
|
|
55376
56038
|
if (!existsSync24(hooksDir)) {
|
|
55377
56039
|
await mkdir9(hooksDir, { recursive: true });
|
|
55378
56040
|
logger.info("init", "Created project hooks directory", { path: hooksDir });
|
|
@@ -56807,12 +57469,12 @@ var init_loader4 = __esm(() => {
|
|
|
56807
57469
|
});
|
|
56808
57470
|
|
|
56809
57471
|
// src/utils/paths.ts
|
|
56810
|
-
import { join as
|
|
57472
|
+
import { join as join64 } from "path";
|
|
56811
57473
|
function getRunsDir() {
|
|
56812
|
-
return process.env.NAX_RUNS_DIR ??
|
|
57474
|
+
return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
|
|
56813
57475
|
}
|
|
56814
57476
|
function getEventsRootDir() {
|
|
56815
|
-
return
|
|
57477
|
+
return join64(globalConfigDir(), "events");
|
|
56816
57478
|
}
|
|
56817
57479
|
var init_paths3 = __esm(() => {
|
|
56818
57480
|
init_paths();
|
|
@@ -56872,7 +57534,7 @@ var init_command_argv = __esm(() => {
|
|
|
56872
57534
|
});
|
|
56873
57535
|
|
|
56874
57536
|
// src/hooks/runner.ts
|
|
56875
|
-
import { join as
|
|
57537
|
+
import { join as join71 } from "path";
|
|
56876
57538
|
function createDrainDeadline2(deadlineMs) {
|
|
56877
57539
|
let timeoutId;
|
|
56878
57540
|
const promise2 = new Promise((resolve16) => {
|
|
@@ -56891,14 +57553,14 @@ async function loadHooksConfig(projectDir, globalDir) {
|
|
|
56891
57553
|
let globalHooks = { hooks: {} };
|
|
56892
57554
|
let projectHooks = { hooks: {} };
|
|
56893
57555
|
let skipGlobal = false;
|
|
56894
|
-
const projectPath =
|
|
57556
|
+
const projectPath = join71(projectDir, "hooks.json");
|
|
56895
57557
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
56896
57558
|
if (projectData) {
|
|
56897
57559
|
projectHooks = projectData;
|
|
56898
57560
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
56899
57561
|
}
|
|
56900
57562
|
if (!skipGlobal && globalDir) {
|
|
56901
|
-
const globalPath =
|
|
57563
|
+
const globalPath = join71(globalDir, "hooks.json");
|
|
56902
57564
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
56903
57565
|
if (globalData) {
|
|
56904
57566
|
globalHooks = globalData;
|
|
@@ -57068,7 +57730,7 @@ var package_default;
|
|
|
57068
57730
|
var init_package = __esm(() => {
|
|
57069
57731
|
package_default = {
|
|
57070
57732
|
name: "@nathapp/nax",
|
|
57071
|
-
version: "0.67.
|
|
57733
|
+
version: "0.67.12",
|
|
57072
57734
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
57073
57735
|
type: "module",
|
|
57074
57736
|
bin: {
|
|
@@ -57163,8 +57825,8 @@ var init_version = __esm(() => {
|
|
|
57163
57825
|
NAX_VERSION = package_default.version;
|
|
57164
57826
|
NAX_COMMIT = (() => {
|
|
57165
57827
|
try {
|
|
57166
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
57167
|
-
return "
|
|
57828
|
+
if (/^[0-9a-f]{6,10}$/.test("c747dea2"))
|
|
57829
|
+
return "c747dea2";
|
|
57168
57830
|
} catch {}
|
|
57169
57831
|
try {
|
|
57170
57832
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -58033,15 +58695,15 @@ var init_acceptance_loop = __esm(() => {
|
|
|
58033
58695
|
|
|
58034
58696
|
// src/session/scratch-purge.ts
|
|
58035
58697
|
import { mkdir as mkdir13, rename, rm } from "fs/promises";
|
|
58036
|
-
import { dirname as dirname12, join as
|
|
58698
|
+
import { dirname as dirname12, join as join72 } from "path";
|
|
58037
58699
|
async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
|
|
58038
|
-
const sessionsDir =
|
|
58700
|
+
const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
|
|
58039
58701
|
const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
|
|
58040
58702
|
const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
|
|
58041
58703
|
let purged = 0;
|
|
58042
58704
|
for (const sessionId of sessionIds) {
|
|
58043
|
-
const sessionDir =
|
|
58044
|
-
const descriptorPath =
|
|
58705
|
+
const sessionDir = join72(sessionsDir, sessionId);
|
|
58706
|
+
const descriptorPath = join72(sessionDir, "descriptor.json");
|
|
58045
58707
|
if (!await _scratchPurgeDeps.fileExists(descriptorPath))
|
|
58046
58708
|
continue;
|
|
58047
58709
|
let lastActivityAt;
|
|
@@ -58057,7 +58719,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
|
|
|
58057
58719
|
if (new Date(lastActivityAt).getTime() >= cutoffMs)
|
|
58058
58720
|
continue;
|
|
58059
58721
|
if (archiveInsteadOfDelete) {
|
|
58060
|
-
const archiveDest =
|
|
58722
|
+
const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
|
|
58061
58723
|
await _scratchPurgeDeps.move(sessionDir, archiveDest);
|
|
58062
58724
|
} else {
|
|
58063
58725
|
await _scratchPurgeDeps.remove(sessionDir);
|
|
@@ -58138,7 +58800,7 @@ async function runDeferredRegression(options) {
|
|
|
58138
58800
|
}
|
|
58139
58801
|
const testCommand = config2.quality.commands.test ?? "bun test";
|
|
58140
58802
|
const timeoutSeconds = config2.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
58141
|
-
const maxRectificationAttempts = config2.execution.
|
|
58803
|
+
const maxRectificationAttempts = config2.execution.rectification.maxAttemptsTotal;
|
|
58142
58804
|
const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
|
|
58143
58805
|
const verifyOpts = {
|
|
58144
58806
|
workdir,
|
|
@@ -58302,7 +58964,7 @@ async function runDeferredRegression(options) {
|
|
|
58302
58964
|
const cycle = {
|
|
58303
58965
|
findings: initialFindings,
|
|
58304
58966
|
iterations: [],
|
|
58305
|
-
strategies: [makeFullSuiteRectifyStrategy(story)],
|
|
58967
|
+
strategies: [makeFullSuiteRectifyStrategy(story, config2)],
|
|
58306
58968
|
config: { maxAttemptsTotal: maxRectificationAttempts, validatorRetries: 1 },
|
|
58307
58969
|
validate: async (_cycleCtx, _opts) => {
|
|
58308
58970
|
const verification = await _regressionDeps.runVerification(verifyOpts);
|
|
@@ -58766,12 +59428,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
|
|
|
58766
59428
|
|
|
58767
59429
|
// src/pipeline/subscribers/events-writer.ts
|
|
58768
59430
|
import { appendFile as appendFile5, mkdir as mkdir14 } from "fs/promises";
|
|
58769
|
-
import { basename as basename14, join as
|
|
59431
|
+
import { basename as basename14, join as join73 } from "path";
|
|
58770
59432
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
58771
59433
|
const logger = getSafeLogger();
|
|
58772
59434
|
const project = basename14(workdir);
|
|
58773
|
-
const eventsDir =
|
|
58774
|
-
const eventsFile =
|
|
59435
|
+
const eventsDir = join73(getEventsRootDir(), project);
|
|
59436
|
+
const eventsFile = join73(eventsDir, "events.jsonl");
|
|
58775
59437
|
let dirReady = false;
|
|
58776
59438
|
const write = (line) => {
|
|
58777
59439
|
return (async () => {
|
|
@@ -58952,12 +59614,12 @@ var init_interaction2 = __esm(() => {
|
|
|
58952
59614
|
|
|
58953
59615
|
// src/pipeline/subscribers/registry.ts
|
|
58954
59616
|
import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
|
|
58955
|
-
import { basename as basename15, join as
|
|
59617
|
+
import { basename as basename15, join as join74 } from "path";
|
|
58956
59618
|
function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
58957
59619
|
const logger = getSafeLogger();
|
|
58958
59620
|
const project = basename15(workdir);
|
|
58959
|
-
const runDir =
|
|
58960
|
-
const metaFile =
|
|
59621
|
+
const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
|
|
59622
|
+
const metaFile = join74(runDir, "meta.json");
|
|
58961
59623
|
const unsub = bus.on("run:started", (_ev) => {
|
|
58962
59624
|
return (async () => {
|
|
58963
59625
|
try {
|
|
@@ -58967,8 +59629,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
|
58967
59629
|
project,
|
|
58968
59630
|
feature,
|
|
58969
59631
|
workdir,
|
|
58970
|
-
statusPath:
|
|
58971
|
-
eventsDir:
|
|
59632
|
+
statusPath: join74(outputDir, "features", feature, "status.json"),
|
|
59633
|
+
eventsDir: join74(outputDir, "features", feature, "runs"),
|
|
58972
59634
|
registeredAt: new Date().toISOString()
|
|
58973
59635
|
};
|
|
58974
59636
|
await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -59214,7 +59876,7 @@ var init_types9 = __esm(() => {
|
|
|
59214
59876
|
|
|
59215
59877
|
// src/worktree/dependencies.ts
|
|
59216
59878
|
import { existsSync as existsSync32 } from "fs";
|
|
59217
|
-
import { join as
|
|
59879
|
+
import { join as join75 } from "path";
|
|
59218
59880
|
async function prepareWorktreeDependencies(options) {
|
|
59219
59881
|
const mode = options.config.execution.worktreeDependencies.mode;
|
|
59220
59882
|
const resolvedCwd = resolveDependencyCwd(options);
|
|
@@ -59228,7 +59890,7 @@ async function prepareWorktreeDependencies(options) {
|
|
|
59228
59890
|
}
|
|
59229
59891
|
}
|
|
59230
59892
|
function resolveDependencyCwd(options) {
|
|
59231
|
-
return options.storyWorkdir ?
|
|
59893
|
+
return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
|
|
59232
59894
|
}
|
|
59233
59895
|
function resolveInheritedDependencies(options, resolvedCwd) {
|
|
59234
59896
|
if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
|
|
@@ -59238,7 +59900,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
|
|
|
59238
59900
|
}
|
|
59239
59901
|
function hasDependencyManifests(worktreeRoot, resolvedCwd) {
|
|
59240
59902
|
const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
|
|
59241
|
-
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(
|
|
59903
|
+
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
|
|
59242
59904
|
}
|
|
59243
59905
|
async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
|
|
59244
59906
|
const setupCommand = config2.execution.worktreeDependencies.setupCommand;
|
|
@@ -59302,13 +59964,13 @@ __export(exports_manager, {
|
|
|
59302
59964
|
});
|
|
59303
59965
|
import { existsSync as existsSync33, symlinkSync } from "fs";
|
|
59304
59966
|
import { mkdir as mkdir16 } from "fs/promises";
|
|
59305
|
-
import { join as
|
|
59967
|
+
import { join as join76 } from "path";
|
|
59306
59968
|
|
|
59307
59969
|
class WorktreeManager {
|
|
59308
59970
|
async ensureGitExcludes(projectRoot) {
|
|
59309
59971
|
const logger = getSafeLogger();
|
|
59310
|
-
const infoDir =
|
|
59311
|
-
const excludePath =
|
|
59972
|
+
const infoDir = join76(projectRoot, ".git", "info");
|
|
59973
|
+
const excludePath = join76(infoDir, "exclude");
|
|
59312
59974
|
try {
|
|
59313
59975
|
await mkdir16(infoDir, { recursive: true });
|
|
59314
59976
|
let existing = "";
|
|
@@ -59335,7 +59997,7 @@ ${missing.join(`
|
|
|
59335
59997
|
}
|
|
59336
59998
|
async create(projectRoot, storyId) {
|
|
59337
59999
|
validateStoryId(storyId);
|
|
59338
|
-
const worktreePath =
|
|
60000
|
+
const worktreePath = join76(projectRoot, ".nax-wt", storyId);
|
|
59339
60001
|
const branchName = `nax/${storyId}`;
|
|
59340
60002
|
try {
|
|
59341
60003
|
const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
|
|
@@ -59376,9 +60038,9 @@ ${missing.join(`
|
|
|
59376
60038
|
}
|
|
59377
60039
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
59378
60040
|
}
|
|
59379
|
-
const envSource =
|
|
60041
|
+
const envSource = join76(projectRoot, ".env");
|
|
59380
60042
|
if (existsSync33(envSource)) {
|
|
59381
|
-
const envTarget =
|
|
60043
|
+
const envTarget = join76(worktreePath, ".env");
|
|
59382
60044
|
try {
|
|
59383
60045
|
symlinkSync(envSource, envTarget, "file");
|
|
59384
60046
|
} catch (error48) {
|
|
@@ -59389,7 +60051,7 @@ ${missing.join(`
|
|
|
59389
60051
|
}
|
|
59390
60052
|
async remove(projectRoot, storyId) {
|
|
59391
60053
|
validateStoryId(storyId);
|
|
59392
|
-
const worktreePath =
|
|
60054
|
+
const worktreePath = join76(projectRoot, ".nax-wt", storyId);
|
|
59393
60055
|
const branchName = `nax/${storyId}`;
|
|
59394
60056
|
try {
|
|
59395
60057
|
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -60189,10 +60851,10 @@ var init_merge_conflict_rectify = __esm(() => {
|
|
|
60189
60851
|
});
|
|
60190
60852
|
|
|
60191
60853
|
// src/execution/pipeline-result-handler.ts
|
|
60192
|
-
import { join as
|
|
60854
|
+
import { join as join77 } from "path";
|
|
60193
60855
|
async function removeWorktreeDirectory(projectRoot, storyId) {
|
|
60194
60856
|
const logger = getSafeLogger();
|
|
60195
|
-
const worktreePath =
|
|
60857
|
+
const worktreePath = join77(projectRoot, ".nax-wt", storyId);
|
|
60196
60858
|
try {
|
|
60197
60859
|
const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
60198
60860
|
cwd: projectRoot,
|
|
@@ -60348,7 +61010,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
|
|
|
60348
61010
|
feature: ctx.feature,
|
|
60349
61011
|
attempts: ctx.story.attempts
|
|
60350
61012
|
});
|
|
60351
|
-
if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.
|
|
61013
|
+
if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxAttemptsTotal) {
|
|
60352
61014
|
await pipelineEventBus.emitAsync({
|
|
60353
61015
|
type: "human-review:requested",
|
|
60354
61016
|
storyId: ctx.story.id,
|
|
@@ -60403,7 +61065,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
60403
61065
|
|
|
60404
61066
|
// src/execution/iteration-runner.ts
|
|
60405
61067
|
import { existsSync as existsSync34 } from "fs";
|
|
60406
|
-
import { join as
|
|
61068
|
+
import { join as join78 } from "path";
|
|
60407
61069
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
60408
61070
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
60409
61071
|
if (ctx.dryRun) {
|
|
@@ -60428,7 +61090,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
60428
61090
|
const storyStartTime = Date.now();
|
|
60429
61091
|
let effectiveWorkdir = ctx.workdir;
|
|
60430
61092
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
60431
|
-
const worktreePath =
|
|
61093
|
+
const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
|
|
60432
61094
|
const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
|
|
60433
61095
|
if (!worktreeExists) {
|
|
60434
61096
|
await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
|
|
@@ -60448,7 +61110,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
60448
61110
|
}
|
|
60449
61111
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
60450
61112
|
const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
|
|
60451
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
61113
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
|
|
60452
61114
|
let dependencyContext;
|
|
60453
61115
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
60454
61116
|
try {
|
|
@@ -60475,7 +61137,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
60475
61137
|
};
|
|
60476
61138
|
}
|
|
60477
61139
|
}
|
|
60478
|
-
const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ?
|
|
61140
|
+
const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join78(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join78(ctx.workdir, story.workdir) : ctx.workdir;
|
|
60479
61141
|
const pipelineContext = {
|
|
60480
61142
|
config: effectiveConfig,
|
|
60481
61143
|
rootConfig: ctx.config,
|
|
@@ -60631,7 +61293,7 @@ function selectNextStories(prd, config2, batchPlan, currentBatchIndex, lastStory
|
|
|
60631
61293
|
nextBatchIndex: currentBatchIndex + 1
|
|
60632
61294
|
};
|
|
60633
61295
|
}
|
|
60634
|
-
const story = getNextStory(prd, lastStoryId, config2.execution.rectification?.
|
|
61296
|
+
const story = getNextStory(prd, lastStoryId, config2.execution.rectification?.maxAttemptsTotal ?? 12);
|
|
60635
61297
|
if (!story)
|
|
60636
61298
|
return null;
|
|
60637
61299
|
return {
|
|
@@ -60676,7 +61338,7 @@ __export(exports_parallel_worker, {
|
|
|
60676
61338
|
executeParallelBatch: () => executeParallelBatch,
|
|
60677
61339
|
_parallelWorkerDeps: () => _parallelWorkerDeps
|
|
60678
61340
|
});
|
|
60679
|
-
import { join as
|
|
61341
|
+
import { join as join79 } from "path";
|
|
60680
61342
|
async function executeStoryInWorktree(story, worktreePath, dependencyContext, context, routing, eventEmitter) {
|
|
60681
61343
|
const logger = getSafeLogger();
|
|
60682
61344
|
try {
|
|
@@ -60696,7 +61358,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
|
|
|
60696
61358
|
story,
|
|
60697
61359
|
stories: [story],
|
|
60698
61360
|
projectDir: context.projectDir,
|
|
60699
|
-
workdir: dependencyContext.cwd ?? (story.workdir ?
|
|
61361
|
+
workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
|
|
60700
61362
|
worktreeDependencyContext: dependencyContext,
|
|
60701
61363
|
routing,
|
|
60702
61364
|
storyGitRef: storyGitRef ?? undefined
|
|
@@ -61525,7 +62187,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
61525
62187
|
var init_status_file = () => {};
|
|
61526
62188
|
|
|
61527
62189
|
// src/execution/status-writer.ts
|
|
61528
|
-
import { join as
|
|
62190
|
+
import { join as join80 } from "path";
|
|
61529
62191
|
|
|
61530
62192
|
class StatusWriter {
|
|
61531
62193
|
statusFile;
|
|
@@ -61644,7 +62306,7 @@ class StatusWriter {
|
|
|
61644
62306
|
if (!this._prd)
|
|
61645
62307
|
return;
|
|
61646
62308
|
const safeLogger = getSafeLogger();
|
|
61647
|
-
const featureStatusPath =
|
|
62309
|
+
const featureStatusPath = join80(featureDir, "status.json");
|
|
61648
62310
|
const write = async () => {
|
|
61649
62311
|
try {
|
|
61650
62312
|
const base = this.getSnapshot(totalCost, iterations);
|
|
@@ -62078,7 +62740,7 @@ __export(exports_run_initialization, {
|
|
|
62078
62740
|
initializeRun: () => initializeRun,
|
|
62079
62741
|
_reconcileDeps: () => _reconcileDeps
|
|
62080
62742
|
});
|
|
62081
|
-
import { join as
|
|
62743
|
+
import { join as join81 } from "path";
|
|
62082
62744
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
62083
62745
|
const logger = getSafeLogger();
|
|
62084
62746
|
let reconciledCount = 0;
|
|
@@ -62095,7 +62757,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
62095
62757
|
});
|
|
62096
62758
|
continue;
|
|
62097
62759
|
}
|
|
62098
|
-
const effectiveWorkdir = story.workdir ?
|
|
62760
|
+
const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
|
|
62099
62761
|
try {
|
|
62100
62762
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
62101
62763
|
if (!reviewResult.success) {
|
|
@@ -93527,7 +94189,7 @@ __export(exports_curator, {
|
|
|
93527
94189
|
});
|
|
93528
94190
|
import { readdirSync as readdirSync9 } from "fs";
|
|
93529
94191
|
import { unlink as unlink4 } from "fs/promises";
|
|
93530
|
-
import { basename as basename16, join as
|
|
94192
|
+
import { basename as basename16, join as join83 } from "path";
|
|
93531
94193
|
function getProjectKey(config2, projectDir) {
|
|
93532
94194
|
return config2.name?.trim() || basename16(projectDir);
|
|
93533
94195
|
}
|
|
@@ -93610,7 +94272,7 @@ async function curatorStatus(options) {
|
|
|
93610
94272
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93611
94273
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93612
94274
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93613
|
-
const runsDir =
|
|
94275
|
+
const runsDir = join83(outputDir, "runs");
|
|
93614
94276
|
const runIds = listRunIds(runsDir);
|
|
93615
94277
|
let runId;
|
|
93616
94278
|
if (options.run) {
|
|
@@ -93627,8 +94289,8 @@ async function curatorStatus(options) {
|
|
|
93627
94289
|
runId = runIds[runIds.length - 1];
|
|
93628
94290
|
}
|
|
93629
94291
|
console.log(`Run: ${runId}`);
|
|
93630
|
-
const runDir =
|
|
93631
|
-
const observationsPath =
|
|
94292
|
+
const runDir = join83(runsDir, runId);
|
|
94293
|
+
const observationsPath = join83(runDir, "observations.jsonl");
|
|
93632
94294
|
const observations = await parseObservations(observationsPath);
|
|
93633
94295
|
const counts = new Map;
|
|
93634
94296
|
for (const obs of observations) {
|
|
@@ -93638,7 +94300,7 @@ async function curatorStatus(options) {
|
|
|
93638
94300
|
for (const [kind, count] of counts.entries()) {
|
|
93639
94301
|
console.log(` ${kind}: ${count}`);
|
|
93640
94302
|
}
|
|
93641
|
-
const proposalsPath =
|
|
94303
|
+
const proposalsPath = join83(runDir, "curator-proposals.md");
|
|
93642
94304
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
93643
94305
|
if (proposalText !== null) {
|
|
93644
94306
|
console.log("");
|
|
@@ -93652,8 +94314,8 @@ async function curatorCommit(options) {
|
|
|
93652
94314
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93653
94315
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93654
94316
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93655
|
-
const runDir =
|
|
93656
|
-
const proposalsPath =
|
|
94317
|
+
const runDir = join83(outputDir, "runs", options.runId);
|
|
94318
|
+
const proposalsPath = join83(runDir, "curator-proposals.md");
|
|
93657
94319
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
93658
94320
|
if (proposalText === null) {
|
|
93659
94321
|
console.log(`curator-proposals.md not found for run ${options.runId}.`);
|
|
@@ -93669,7 +94331,7 @@ async function curatorCommit(options) {
|
|
|
93669
94331
|
const dropFileState = new Map;
|
|
93670
94332
|
const skippedDrops = new Set;
|
|
93671
94333
|
for (const drop2 of drops) {
|
|
93672
|
-
const targetPath =
|
|
94334
|
+
const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
|
|
93673
94335
|
if (!dropFileState.has(targetPath)) {
|
|
93674
94336
|
const fileExists2 = await Bun.file(targetPath).exists();
|
|
93675
94337
|
const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
|
|
@@ -93703,7 +94365,7 @@ async function curatorCommit(options) {
|
|
|
93703
94365
|
if (skippedDrops.has(drop2)) {
|
|
93704
94366
|
continue;
|
|
93705
94367
|
}
|
|
93706
|
-
const targetPath =
|
|
94368
|
+
const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
|
|
93707
94369
|
const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
|
|
93708
94370
|
const filtered = filterDropContent(existing, drop2.description);
|
|
93709
94371
|
await _curatorCmdDeps.writeFile(targetPath, filtered);
|
|
@@ -93712,7 +94374,7 @@ async function curatorCommit(options) {
|
|
|
93712
94374
|
}
|
|
93713
94375
|
const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
|
|
93714
94376
|
for (const add2 of adds) {
|
|
93715
|
-
const targetPath =
|
|
94377
|
+
const targetPath = join83(resolved.projectDir, add2.canonicalFile);
|
|
93716
94378
|
const content = buildAddContent(add2);
|
|
93717
94379
|
await _curatorCmdDeps.appendFile(targetPath, content);
|
|
93718
94380
|
modifiedFiles.add(targetPath);
|
|
@@ -93749,7 +94411,7 @@ async function curatorDryrun(options) {
|
|
|
93749
94411
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93750
94412
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93751
94413
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93752
|
-
const runsDir =
|
|
94414
|
+
const runsDir = join83(outputDir, "runs");
|
|
93753
94415
|
const runIds = listRunIds(runsDir);
|
|
93754
94416
|
if (runIds.length === 0) {
|
|
93755
94417
|
console.log("No runs found.");
|
|
@@ -93760,7 +94422,7 @@ async function curatorDryrun(options) {
|
|
|
93760
94422
|
console.log(`Run ${options.run} not found in ${runsDir}.`);
|
|
93761
94423
|
return;
|
|
93762
94424
|
}
|
|
93763
|
-
const observationsPath =
|
|
94425
|
+
const observationsPath = join83(runsDir, runId, "observations.jsonl");
|
|
93764
94426
|
const observations = await parseObservations(observationsPath);
|
|
93765
94427
|
const thresholds = getThresholds(config2);
|
|
93766
94428
|
const proposals = runHeuristics(observations, thresholds);
|
|
@@ -93801,12 +94463,12 @@ async function curatorGc(options) {
|
|
|
93801
94463
|
await _curatorCmdDeps.writeFile(rollupPath, newContent);
|
|
93802
94464
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93803
94465
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93804
|
-
const perRunsDir =
|
|
94466
|
+
const perRunsDir = join83(outputDir, "runs");
|
|
93805
94467
|
for (const runId of uniqueRunIds) {
|
|
93806
94468
|
if (!keepSet.has(runId)) {
|
|
93807
|
-
const runDir =
|
|
93808
|
-
await _curatorCmdDeps.removeFile(
|
|
93809
|
-
await _curatorCmdDeps.removeFile(
|
|
94469
|
+
const runDir = join83(perRunsDir, runId);
|
|
94470
|
+
await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
|
|
94471
|
+
await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
|
|
93810
94472
|
}
|
|
93811
94473
|
}
|
|
93812
94474
|
console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
|
|
@@ -93851,7 +94513,7 @@ var init_curator2 = __esm(() => {
|
|
|
93851
94513
|
init_source();
|
|
93852
94514
|
import { existsSync as existsSync37, mkdirSync as mkdirSync7 } from "fs";
|
|
93853
94515
|
import { homedir as homedir3 } from "os";
|
|
93854
|
-
import { basename as basename17, join as
|
|
94516
|
+
import { basename as basename17, join as join84 } from "path";
|
|
93855
94517
|
|
|
93856
94518
|
// node_modules/commander/esm.mjs
|
|
93857
94519
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -93875,12 +94537,12 @@ init_errors();
|
|
|
93875
94537
|
init_operations();
|
|
93876
94538
|
|
|
93877
94539
|
// src/plan/strategies/context-builder.ts
|
|
93878
|
-
import { join as
|
|
94540
|
+
import { join as join37 } from "path";
|
|
93879
94541
|
init_config();
|
|
93880
94542
|
init_errors();
|
|
93881
94543
|
init_interaction();
|
|
93882
94544
|
async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
93883
|
-
const naxDir =
|
|
94545
|
+
const naxDir = join37(workdir, ".nax");
|
|
93884
94546
|
if (!deps.existsSync(naxDir)) {
|
|
93885
94547
|
throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
|
|
93886
94548
|
stage: "plan",
|
|
@@ -93888,8 +94550,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
|
93888
94550
|
});
|
|
93889
94551
|
}
|
|
93890
94552
|
validateFeatureName(options.feature);
|
|
93891
|
-
const outputDir =
|
|
93892
|
-
const outputPath =
|
|
94553
|
+
const outputDir = join37(naxDir, "features", options.feature);
|
|
94554
|
+
const outputPath = join37(outputDir, "prd.json");
|
|
93893
94555
|
const [specContent, sourceRoots, pkg] = await Promise.all([
|
|
93894
94556
|
deps.readFile(options.from),
|
|
93895
94557
|
deps.scanSourceRoots(workdir),
|
|
@@ -93904,7 +94566,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
|
93904
94566
|
...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
|
|
93905
94567
|
];
|
|
93906
94568
|
const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
|
|
93907
|
-
const packageJson = await deps.readPackageJsonAt(
|
|
94569
|
+
const packageJson = await deps.readPackageJsonAt(join37(workdir, relativePath, "package.json"));
|
|
93908
94570
|
return buildPackageSummary(relativePath, packageJson);
|
|
93909
94571
|
}));
|
|
93910
94572
|
const projectName = detectProjectName(workdir, pkg);
|
|
@@ -94505,7 +95167,7 @@ init_interaction();
|
|
|
94505
95167
|
init_prd();
|
|
94506
95168
|
init_runtime();
|
|
94507
95169
|
import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
|
|
94508
|
-
import { basename as basename7, join as
|
|
95170
|
+
import { basename as basename7, join as join42, resolve as resolve14 } from "path";
|
|
94509
95171
|
var _statusFeaturesDeps = {
|
|
94510
95172
|
projectOutputDir,
|
|
94511
95173
|
loadConfig
|
|
@@ -94519,7 +95181,7 @@ function isPidAlive(pid) {
|
|
|
94519
95181
|
}
|
|
94520
95182
|
}
|
|
94521
95183
|
async function loadStatusFile(featureDir) {
|
|
94522
|
-
const statusPath =
|
|
95184
|
+
const statusPath = join42(featureDir, "status.json");
|
|
94523
95185
|
if (!existsSync17(statusPath)) {
|
|
94524
95186
|
return null;
|
|
94525
95187
|
}
|
|
@@ -94534,7 +95196,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
94534
95196
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94535
95197
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94536
95198
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94537
|
-
const statusPath =
|
|
95199
|
+
const statusPath = join42(outputDir, "status.json");
|
|
94538
95200
|
if (!existsSync17(statusPath)) {
|
|
94539
95201
|
return null;
|
|
94540
95202
|
}
|
|
@@ -94546,7 +95208,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
94546
95208
|
}
|
|
94547
95209
|
}
|
|
94548
95210
|
async function getFeatureSummary(featureName, featureDir) {
|
|
94549
|
-
const prdPath =
|
|
95211
|
+
const prdPath = join42(featureDir, "prd.json");
|
|
94550
95212
|
if (!existsSync17(prdPath)) {
|
|
94551
95213
|
return {
|
|
94552
95214
|
name: featureName,
|
|
@@ -94589,7 +95251,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
94589
95251
|
};
|
|
94590
95252
|
}
|
|
94591
95253
|
}
|
|
94592
|
-
const runsDir =
|
|
95254
|
+
const runsDir = join42(featureDir, "runs");
|
|
94593
95255
|
if (existsSync17(runsDir)) {
|
|
94594
95256
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
|
|
94595
95257
|
if (runs.length > 0) {
|
|
@@ -94603,7 +95265,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94603
95265
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94604
95266
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94605
95267
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94606
|
-
const featuresDir =
|
|
95268
|
+
const featuresDir = join42(outputDir, "features");
|
|
94607
95269
|
if (!existsSync17(featuresDir)) {
|
|
94608
95270
|
console.log(source_default.dim("No features found."));
|
|
94609
95271
|
return;
|
|
@@ -94644,7 +95306,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94644
95306
|
console.log();
|
|
94645
95307
|
}
|
|
94646
95308
|
}
|
|
94647
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
95309
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join42(featuresDir, name))));
|
|
94648
95310
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
94649
95311
|
`));
|
|
94650
95312
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -94670,7 +95332,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94670
95332
|
console.log();
|
|
94671
95333
|
}
|
|
94672
95334
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
94673
|
-
const prdPath =
|
|
95335
|
+
const prdPath = join42(featureDir, "prd.json");
|
|
94674
95336
|
if (!existsSync17(prdPath)) {
|
|
94675
95337
|
console.log(source_default.bold(`
|
|
94676
95338
|
\uD83D\uDCCA ${featureName}
|
|
@@ -94816,7 +95478,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
94816
95478
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94817
95479
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94818
95480
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94819
|
-
featureDir =
|
|
95481
|
+
featureDir = join42(outputDir, "features", options.feature);
|
|
94820
95482
|
} else {
|
|
94821
95483
|
const resolved = resolveProject({ feature: options.feature });
|
|
94822
95484
|
if (!resolved.featureDir) {
|
|
@@ -94836,7 +95498,7 @@ init_errors();
|
|
|
94836
95498
|
init_logger2();
|
|
94837
95499
|
init_runtime();
|
|
94838
95500
|
import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
|
|
94839
|
-
import { basename as basename8, join as
|
|
95501
|
+
import { basename as basename8, join as join43 } from "path";
|
|
94840
95502
|
async function resolveOutputDir2(workdir, override) {
|
|
94841
95503
|
if (override)
|
|
94842
95504
|
return override;
|
|
@@ -94860,7 +95522,7 @@ async function runsListCommand(options) {
|
|
|
94860
95522
|
const logger = getLogger();
|
|
94861
95523
|
const { feature, workdir } = options;
|
|
94862
95524
|
const outputDir = await resolveOutputDir2(workdir, options.outputDir);
|
|
94863
|
-
const runsDir =
|
|
95525
|
+
const runsDir = join43(outputDir, "features", feature, "runs");
|
|
94864
95526
|
if (!existsSync18(runsDir)) {
|
|
94865
95527
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
94866
95528
|
return;
|
|
@@ -94872,7 +95534,7 @@ async function runsListCommand(options) {
|
|
|
94872
95534
|
}
|
|
94873
95535
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
94874
95536
|
for (const file3 of files.sort().reverse()) {
|
|
94875
|
-
const logPath =
|
|
95537
|
+
const logPath = join43(runsDir, file3);
|
|
94876
95538
|
const entries = await parseRunLog(logPath);
|
|
94877
95539
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
94878
95540
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -94899,7 +95561,7 @@ async function runsShowCommand(options) {
|
|
|
94899
95561
|
const logger = getLogger();
|
|
94900
95562
|
const { runId, feature, workdir } = options;
|
|
94901
95563
|
const outputDir = await resolveOutputDir2(workdir, options.outputDir);
|
|
94902
|
-
const logPath =
|
|
95564
|
+
const logPath = join43(outputDir, "features", feature, "runs", `${runId}.jsonl`);
|
|
94903
95565
|
if (!existsSync18(logPath)) {
|
|
94904
95566
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
94905
95567
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -95012,7 +95674,7 @@ init_logger2();
|
|
|
95012
95674
|
init_prd();
|
|
95013
95675
|
init_runtime();
|
|
95014
95676
|
import { existsSync as existsSync25, readdirSync as readdirSync5 } from "fs";
|
|
95015
|
-
import { basename as basename12, join as
|
|
95677
|
+
import { basename as basename12, join as join57 } from "path";
|
|
95016
95678
|
|
|
95017
95679
|
// src/cli/diagnose-analysis.ts
|
|
95018
95680
|
function detectFailurePattern(story, _prd, status) {
|
|
@@ -95215,7 +95877,7 @@ function isProcessAlive2(pid) {
|
|
|
95215
95877
|
}
|
|
95216
95878
|
}
|
|
95217
95879
|
async function loadStatusFile2(outputDir) {
|
|
95218
|
-
const statusPath =
|
|
95880
|
+
const statusPath = join57(outputDir, "status.json");
|
|
95219
95881
|
if (!existsSync25(statusPath))
|
|
95220
95882
|
return null;
|
|
95221
95883
|
try {
|
|
@@ -95243,7 +95905,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
95243
95905
|
}
|
|
95244
95906
|
}
|
|
95245
95907
|
async function checkLock(workdir) {
|
|
95246
|
-
const lockFile = Bun.file(
|
|
95908
|
+
const lockFile = Bun.file(join57(workdir, "nax.lock"));
|
|
95247
95909
|
if (!await lockFile.exists())
|
|
95248
95910
|
return { lockPresent: false };
|
|
95249
95911
|
try {
|
|
@@ -95261,8 +95923,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
95261
95923
|
const logger = getLogger();
|
|
95262
95924
|
const workdir = options.workdir ?? process.cwd();
|
|
95263
95925
|
const naxSubdir = findProjectDir(workdir);
|
|
95264
|
-
let projectDir = naxSubdir ?
|
|
95265
|
-
if (!projectDir && existsSync25(
|
|
95926
|
+
let projectDir = naxSubdir ? join57(naxSubdir, "..") : null;
|
|
95927
|
+
if (!projectDir && existsSync25(join57(workdir, ".nax"))) {
|
|
95266
95928
|
projectDir = workdir;
|
|
95267
95929
|
}
|
|
95268
95930
|
if (!projectDir)
|
|
@@ -95276,7 +95938,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
95276
95938
|
if (status2) {
|
|
95277
95939
|
feature = status2.run.feature;
|
|
95278
95940
|
} else {
|
|
95279
|
-
const featuresDir =
|
|
95941
|
+
const featuresDir = join57(outputDir, "features");
|
|
95280
95942
|
if (!existsSync25(featuresDir))
|
|
95281
95943
|
throw new Error("No features found in project");
|
|
95282
95944
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -95286,8 +95948,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
95286
95948
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
95287
95949
|
}
|
|
95288
95950
|
}
|
|
95289
|
-
const featureDir =
|
|
95290
|
-
const prdPath =
|
|
95951
|
+
const featureDir = join57(outputDir, "features", feature);
|
|
95952
|
+
const prdPath = join57(featureDir, "prd.json");
|
|
95291
95953
|
if (!existsSync25(prdPath))
|
|
95292
95954
|
throw new Error(`Feature not found: ${feature}`);
|
|
95293
95955
|
const prd = await loadPRD(prdPath);
|
|
@@ -95332,7 +95994,7 @@ init_source();
|
|
|
95332
95994
|
init_loader();
|
|
95333
95995
|
init_generator2();
|
|
95334
95996
|
import { existsSync as existsSync26 } from "fs";
|
|
95335
|
-
import { join as
|
|
95997
|
+
import { join as join58 } from "path";
|
|
95336
95998
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
95337
95999
|
async function generateCommand(options) {
|
|
95338
96000
|
const workdir = options.dir ?? process.cwd();
|
|
@@ -95375,7 +96037,7 @@ async function generateCommand(options) {
|
|
|
95375
96037
|
return;
|
|
95376
96038
|
}
|
|
95377
96039
|
if (options.package) {
|
|
95378
|
-
const packageDir =
|
|
96040
|
+
const packageDir = join58(workdir, options.package);
|
|
95379
96041
|
if (dryRun) {
|
|
95380
96042
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
95381
96043
|
}
|
|
@@ -95395,8 +96057,8 @@ async function generateCommand(options) {
|
|
|
95395
96057
|
process.exit(1);
|
|
95396
96058
|
return;
|
|
95397
96059
|
}
|
|
95398
|
-
const contextPath = options.context ?
|
|
95399
|
-
const outputDir = options.output ?
|
|
96060
|
+
const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
|
|
96061
|
+
const outputDir = options.output ? join58(workdir, options.output) : workdir;
|
|
95400
96062
|
const autoInject = !options.noAutoInject;
|
|
95401
96063
|
if (!existsSync26(contextPath)) {
|
|
95402
96064
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
@@ -95502,7 +96164,7 @@ async function generateCommand(options) {
|
|
|
95502
96164
|
// src/cli/config-display.ts
|
|
95503
96165
|
init_loader();
|
|
95504
96166
|
import { existsSync as existsSync28 } from "fs";
|
|
95505
|
-
import { join as
|
|
96167
|
+
import { join as join60 } from "path";
|
|
95506
96168
|
|
|
95507
96169
|
// src/cli/config-descriptions.ts
|
|
95508
96170
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -95551,15 +96213,16 @@ var FIELD_DESCRIPTIONS = {
|
|
|
95551
96213
|
"execution.contextProviderTokenBudget": "Token budget for plugin context providers",
|
|
95552
96214
|
"execution.lintCommand": "Lint command override (null=disabled, undefined=auto-detect)",
|
|
95553
96215
|
"execution.typecheckCommand": "Typecheck command override (null=disabled, undefined=auto-detect)",
|
|
95554
|
-
"execution.rectification": "
|
|
96216
|
+
"execution.rectification": "Unified fix-cycle settings \u2014 shared by story-orchestrator (semantic + adversarial + mechanical) and post-run regression cycles",
|
|
95555
96217
|
"execution.rectification.enabled": "Enable rectification loop",
|
|
95556
|
-
"execution.rectification.
|
|
96218
|
+
"execution.rectification.maxAttemptsTotal": "Total iteration cap for the unified fix cycle (default: 12). Per-strategy caps are the granular bound.",
|
|
96219
|
+
"execution.rectification.maxAttemptsPerStrategy": "Default per-strategy cap for LLM-driven strategies \u2014 autofix-implementer / autofix-test-writer / full-suite-rectify (default: 3). Mechanical strategies stay at 1.",
|
|
95557
96220
|
"execution.rectification.fullSuiteTimeoutSeconds": "Timeout for full test suite run in seconds",
|
|
95558
96221
|
"execution.rectification.maxFailureSummaryChars": "Max characters in failure summary",
|
|
95559
96222
|
"execution.rectification.abortOnIncreasingFailures": "Abort if failure count increases",
|
|
95560
|
-
"execution.rectification.escalateOnExhaustion": "Enable model tier escalation when
|
|
95561
|
-
"execution.rectification.rethinkAtAttempt": "Attempt number at which 'rethink your approach' language is injected into the prompt (default: 2
|
|
95562
|
-
"execution.rectification.urgencyAtAttempt": "Attempt number at which 'final chance before escalation' urgency is added
|
|
96223
|
+
"execution.rectification.escalateOnExhaustion": "Enable model tier escalation when attempts are exhausted with remaining failures",
|
|
96224
|
+
"execution.rectification.rethinkAtAttempt": "Attempt number at which 'rethink your approach' language is injected into the prompt (default: 2)",
|
|
96225
|
+
"execution.rectification.urgencyAtAttempt": "Attempt number at which 'final chance before escalation' urgency is added (default: 3)",
|
|
95563
96226
|
"execution.regressionGate": "Regression gate settings (full suite after scoped tests)",
|
|
95564
96227
|
"execution.regressionGate.enabled": "Enable full-suite regression gate",
|
|
95565
96228
|
"execution.regressionGate.timeoutSeconds": "Timeout for regression run in seconds",
|
|
@@ -95752,7 +96415,7 @@ function deepEqual(a, b) {
|
|
|
95752
96415
|
init_defaults();
|
|
95753
96416
|
init_loader();
|
|
95754
96417
|
import { existsSync as existsSync27 } from "fs";
|
|
95755
|
-
import { join as
|
|
96418
|
+
import { join as join59 } from "path";
|
|
95756
96419
|
async function loadConfigFile(path19) {
|
|
95757
96420
|
if (!existsSync27(path19))
|
|
95758
96421
|
return null;
|
|
@@ -95774,7 +96437,7 @@ async function loadProjectConfig() {
|
|
|
95774
96437
|
const projectDir = findProjectDir();
|
|
95775
96438
|
if (!projectDir)
|
|
95776
96439
|
return null;
|
|
95777
|
-
const projectPath =
|
|
96440
|
+
const projectPath = join59(projectDir, "config.json");
|
|
95778
96441
|
return await loadConfigFile(projectPath);
|
|
95779
96442
|
}
|
|
95780
96443
|
|
|
@@ -95834,7 +96497,7 @@ async function configCommand(config2, options = {}) {
|
|
|
95834
96497
|
function determineConfigSources() {
|
|
95835
96498
|
const globalPath = globalConfigPath();
|
|
95836
96499
|
const projectDir = findProjectDir();
|
|
95837
|
-
const projectPath = projectDir ?
|
|
96500
|
+
const projectPath = projectDir ? join60(projectDir, "config.json") : null;
|
|
95838
96501
|
return {
|
|
95839
96502
|
global: fileExists(globalPath) ? globalPath : null,
|
|
95840
96503
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -95983,15 +96646,15 @@ init_paths();
|
|
|
95983
96646
|
init_profile();
|
|
95984
96647
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
95985
96648
|
import { readdirSync as readdirSync6 } from "fs";
|
|
95986
|
-
import { join as
|
|
96649
|
+
import { join as join61 } from "path";
|
|
95987
96650
|
var _profileCLIDeps = {
|
|
95988
96651
|
env: process.env
|
|
95989
96652
|
};
|
|
95990
96653
|
var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
|
|
95991
96654
|
var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
|
|
95992
96655
|
async function profileListCommand(startDir) {
|
|
95993
|
-
const globalProfilesDir =
|
|
95994
|
-
const projectProfilesDir =
|
|
96656
|
+
const globalProfilesDir = join61(globalConfigDir(), "profiles");
|
|
96657
|
+
const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
|
|
95995
96658
|
const globalProfiles = scanProfileDir(globalProfilesDir);
|
|
95996
96659
|
const projectProfiles = scanProfileDir(projectProfilesDir);
|
|
95997
96660
|
const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
@@ -96050,7 +96713,7 @@ function maskProfileValues(obj) {
|
|
|
96050
96713
|
return result;
|
|
96051
96714
|
}
|
|
96052
96715
|
async function profileUseCommand(profileName, startDir) {
|
|
96053
|
-
const configPath =
|
|
96716
|
+
const configPath = join61(projectConfigDir(startDir), "config.json");
|
|
96054
96717
|
const configFile = Bun.file(configPath);
|
|
96055
96718
|
let existing = {};
|
|
96056
96719
|
if (await configFile.exists()) {
|
|
@@ -96069,8 +96732,8 @@ async function profileCurrentCommand(startDir) {
|
|
|
96069
96732
|
return resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
96070
96733
|
}
|
|
96071
96734
|
async function profileCreateCommand(profileName, startDir) {
|
|
96072
|
-
const profilesDir =
|
|
96073
|
-
const profilePath =
|
|
96735
|
+
const profilesDir = join61(projectConfigDir(startDir), "profiles");
|
|
96736
|
+
const profilePath = join61(profilesDir, `${profileName}.json`);
|
|
96074
96737
|
const profileFile = Bun.file(profilePath);
|
|
96075
96738
|
if (await profileFile.exists()) {
|
|
96076
96739
|
throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
|
|
@@ -96192,7 +96855,7 @@ async function contextInspectCommand(options) {
|
|
|
96192
96855
|
init_canonical_loader();
|
|
96193
96856
|
init_errors();
|
|
96194
96857
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
96195
|
-
import { basename as basename13, join as
|
|
96858
|
+
import { basename as basename13, join as join62 } from "path";
|
|
96196
96859
|
var _rulesCLIDeps = {
|
|
96197
96860
|
readFile: async (path19) => Bun.file(path19).text(),
|
|
96198
96861
|
writeFile: async (path19, content) => {
|
|
@@ -96201,7 +96864,7 @@ var _rulesCLIDeps = {
|
|
|
96201
96864
|
fileExists: async (path19) => Bun.file(path19).exists(),
|
|
96202
96865
|
globInDir: (dir) => {
|
|
96203
96866
|
try {
|
|
96204
|
-
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) =>
|
|
96867
|
+
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
|
|
96205
96868
|
} catch {
|
|
96206
96869
|
return [];
|
|
96207
96870
|
}
|
|
@@ -96250,7 +96913,7 @@ ${r.content}`).join(`
|
|
|
96250
96913
|
`);
|
|
96251
96914
|
const shimContent = `${header + body}
|
|
96252
96915
|
`;
|
|
96253
|
-
const shimPath =
|
|
96916
|
+
const shimPath = join62(workdir, shimFileName);
|
|
96254
96917
|
if (options.dryRun) {
|
|
96255
96918
|
console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
|
|
96256
96919
|
return;
|
|
@@ -96279,14 +96942,14 @@ function neutralizeContent(content) {
|
|
|
96279
96942
|
}
|
|
96280
96943
|
async function collectMigrationSources(workdir) {
|
|
96281
96944
|
const sources = [];
|
|
96282
|
-
const claudeMdPath =
|
|
96945
|
+
const claudeMdPath = join62(workdir, "CLAUDE.md");
|
|
96283
96946
|
if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
|
|
96284
96947
|
const content = await _rulesCLIDeps.readFile(claudeMdPath);
|
|
96285
96948
|
if (content.trim()) {
|
|
96286
96949
|
sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
|
|
96287
96950
|
}
|
|
96288
96951
|
}
|
|
96289
|
-
const rulesDir =
|
|
96952
|
+
const rulesDir = join62(workdir, ".claude", "rules");
|
|
96290
96953
|
const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
|
|
96291
96954
|
for (const filePath of ruleFiles) {
|
|
96292
96955
|
try {
|
|
@@ -96306,7 +96969,7 @@ async function rulesMigrateCommand(options) {
|
|
|
96306
96969
|
console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
|
|
96307
96970
|
return;
|
|
96308
96971
|
}
|
|
96309
|
-
const targetDir =
|
|
96972
|
+
const targetDir = join62(workdir, CANONICAL_RULES_DIR);
|
|
96310
96973
|
if (!options.dryRun) {
|
|
96311
96974
|
try {
|
|
96312
96975
|
await _rulesCLIDeps.mkdir(targetDir);
|
|
@@ -96317,7 +96980,7 @@ async function rulesMigrateCommand(options) {
|
|
|
96317
96980
|
let written = 0;
|
|
96318
96981
|
let skipped = 0;
|
|
96319
96982
|
for (const { sourcePath, targetFileName, content } of sources) {
|
|
96320
|
-
const targetPath =
|
|
96983
|
+
const targetPath = join62(targetDir, targetFileName);
|
|
96321
96984
|
if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
|
|
96322
96985
|
console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
|
|
96323
96986
|
skipped++;
|
|
@@ -96356,7 +97019,7 @@ function collectCanonicalRuleRoots(workdir) {
|
|
|
96356
97019
|
const packageRel = normalized.slice(0, idx);
|
|
96357
97020
|
if (!packageRel)
|
|
96358
97021
|
continue;
|
|
96359
|
-
roots.add(
|
|
97022
|
+
roots.add(join62(workdir, packageRel));
|
|
96360
97023
|
}
|
|
96361
97024
|
return [...roots].sort();
|
|
96362
97025
|
}
|
|
@@ -96378,7 +97041,7 @@ init_logger2();
|
|
|
96378
97041
|
init_detect2();
|
|
96379
97042
|
init_workspace();
|
|
96380
97043
|
init_common();
|
|
96381
|
-
import { join as
|
|
97044
|
+
import { join as join63 } from "path";
|
|
96382
97045
|
function resolveEffective(detected, configPatterns) {
|
|
96383
97046
|
if (configPatterns !== undefined)
|
|
96384
97047
|
return "config";
|
|
@@ -96463,7 +97126,7 @@ async function detectCommand(options) {
|
|
|
96463
97126
|
const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
96464
97127
|
const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
|
|
96465
97128
|
const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
96466
|
-
const pkgConfigPath =
|
|
97129
|
+
const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
|
|
96467
97130
|
const pkgRaw = await loadRawConfig(pkgConfigPath);
|
|
96468
97131
|
const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
|
|
96469
97132
|
const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
|
|
@@ -96517,13 +97180,13 @@ async function detectCommand(options) {
|
|
|
96517
97180
|
if (rootDetected.confidence === "empty") {
|
|
96518
97181
|
console.log(source_default.yellow(" root: skipped (empty detection)"));
|
|
96519
97182
|
} else {
|
|
96520
|
-
const rootConfigPath =
|
|
97183
|
+
const rootConfigPath = join63(workdir, ".nax", "config.json");
|
|
96521
97184
|
try {
|
|
96522
97185
|
const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
|
|
96523
97186
|
if (status === "skipped") {
|
|
96524
97187
|
console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
|
|
96525
97188
|
} else {
|
|
96526
|
-
console.log(source_default.green(` root: ${status} \u2192 ${
|
|
97189
|
+
console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
|
|
96527
97190
|
}
|
|
96528
97191
|
} catch (err) {
|
|
96529
97192
|
console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
|
|
@@ -96536,13 +97199,13 @@ async function detectCommand(options) {
|
|
|
96536
97199
|
console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
|
|
96537
97200
|
continue;
|
|
96538
97201
|
}
|
|
96539
|
-
const pkgConfigPath =
|
|
97202
|
+
const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
|
|
96540
97203
|
try {
|
|
96541
97204
|
const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
|
|
96542
97205
|
if (status === "skipped") {
|
|
96543
97206
|
console.log(source_default.dim(` ${dir}: skipped (already set)`));
|
|
96544
97207
|
} else {
|
|
96545
|
-
console.log(source_default.green(` ${dir}: ${status} \u2192 ${
|
|
97208
|
+
console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
|
|
96546
97209
|
}
|
|
96547
97210
|
} catch (err) {
|
|
96548
97211
|
console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
|
|
@@ -96565,19 +97228,19 @@ async function diagnose(options) {
|
|
|
96565
97228
|
// src/commands/logs.ts
|
|
96566
97229
|
init_common();
|
|
96567
97230
|
import { existsSync as existsSync30 } from "fs";
|
|
96568
|
-
import { join as
|
|
97231
|
+
import { join as join67 } from "path";
|
|
96569
97232
|
|
|
96570
97233
|
// src/commands/logs-formatter.ts
|
|
96571
97234
|
init_source();
|
|
96572
97235
|
init_formatter();
|
|
96573
97236
|
import { readdirSync as readdirSync8 } from "fs";
|
|
96574
|
-
import { join as
|
|
97237
|
+
import { join as join66 } from "path";
|
|
96575
97238
|
|
|
96576
97239
|
// src/commands/logs-reader.ts
|
|
96577
97240
|
init_paths3();
|
|
96578
97241
|
import { existsSync as existsSync29, readdirSync as readdirSync7 } from "fs";
|
|
96579
97242
|
import { readdir as readdir4 } from "fs/promises";
|
|
96580
|
-
import { join as
|
|
97243
|
+
import { join as join65 } from "path";
|
|
96581
97244
|
var _logsReaderDeps = {
|
|
96582
97245
|
getRunsDir
|
|
96583
97246
|
};
|
|
@@ -96591,7 +97254,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
96591
97254
|
}
|
|
96592
97255
|
let matched = null;
|
|
96593
97256
|
for (const entry of entries) {
|
|
96594
|
-
const metaPath =
|
|
97257
|
+
const metaPath = join65(runsDir, entry, "meta.json");
|
|
96595
97258
|
try {
|
|
96596
97259
|
const meta3 = await Bun.file(metaPath).json();
|
|
96597
97260
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -96613,14 +97276,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
96613
97276
|
return null;
|
|
96614
97277
|
}
|
|
96615
97278
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
96616
|
-
return
|
|
97279
|
+
return join65(matched.eventsDir, specificFile ?? files[0]);
|
|
96617
97280
|
}
|
|
96618
97281
|
async function selectRunFile(runsDir) {
|
|
96619
97282
|
const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
96620
97283
|
if (files.length === 0) {
|
|
96621
97284
|
return null;
|
|
96622
97285
|
}
|
|
96623
|
-
return
|
|
97286
|
+
return join65(runsDir, files[0]);
|
|
96624
97287
|
}
|
|
96625
97288
|
async function extractRunSummary(filePath) {
|
|
96626
97289
|
const file3 = Bun.file(filePath);
|
|
@@ -96706,7 +97369,7 @@ Runs:
|
|
|
96706
97369
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
96707
97370
|
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"));
|
|
96708
97371
|
for (const file3 of files) {
|
|
96709
|
-
const filePath =
|
|
97372
|
+
const filePath = join66(runsDir, file3);
|
|
96710
97373
|
const summary = await extractRunSummary(filePath);
|
|
96711
97374
|
const timestamp = file3.replace(".jsonl", "");
|
|
96712
97375
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -96820,7 +97483,7 @@ async function logsCommand(options) {
|
|
|
96820
97483
|
return;
|
|
96821
97484
|
}
|
|
96822
97485
|
const resolved = resolveProject({ dir: options.dir });
|
|
96823
|
-
const naxDir =
|
|
97486
|
+
const naxDir = join67(resolved.projectDir, ".nax");
|
|
96824
97487
|
const configPath = resolved.configPath;
|
|
96825
97488
|
const configFile = Bun.file(configPath);
|
|
96826
97489
|
const config2 = await configFile.json();
|
|
@@ -96828,8 +97491,8 @@ async function logsCommand(options) {
|
|
|
96828
97491
|
if (!featureName) {
|
|
96829
97492
|
throw new Error("No feature specified in config.json");
|
|
96830
97493
|
}
|
|
96831
|
-
const featureDir =
|
|
96832
|
-
const runsDir =
|
|
97494
|
+
const featureDir = join67(naxDir, "features", featureName);
|
|
97495
|
+
const runsDir = join67(featureDir, "runs");
|
|
96833
97496
|
if (!existsSync30(runsDir)) {
|
|
96834
97497
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
96835
97498
|
}
|
|
@@ -96855,7 +97518,7 @@ init_prd();
|
|
|
96855
97518
|
init_precheck();
|
|
96856
97519
|
init_common();
|
|
96857
97520
|
import { existsSync as existsSync31 } from "fs";
|
|
96858
|
-
import { join as
|
|
97521
|
+
import { join as join68 } from "path";
|
|
96859
97522
|
async function precheckCommand(options) {
|
|
96860
97523
|
const resolved = resolveProject({
|
|
96861
97524
|
dir: options.dir,
|
|
@@ -96877,9 +97540,9 @@ async function precheckCommand(options) {
|
|
|
96877
97540
|
process.exit(1);
|
|
96878
97541
|
}
|
|
96879
97542
|
}
|
|
96880
|
-
const naxDir =
|
|
96881
|
-
const featureDir =
|
|
96882
|
-
const prdPath =
|
|
97543
|
+
const naxDir = join68(resolved.projectDir, ".nax");
|
|
97544
|
+
const featureDir = join68(naxDir, "features", featureName);
|
|
97545
|
+
const prdPath = join68(featureDir, "prd.json");
|
|
96883
97546
|
if (!existsSync31(featureDir)) {
|
|
96884
97547
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
96885
97548
|
process.exit(1);
|
|
@@ -96902,7 +97565,7 @@ async function precheckCommand(options) {
|
|
|
96902
97565
|
init_source();
|
|
96903
97566
|
init_paths3();
|
|
96904
97567
|
import { readdir as readdir5 } from "fs/promises";
|
|
96905
|
-
import { join as
|
|
97568
|
+
import { join as join69 } from "path";
|
|
96906
97569
|
var DEFAULT_LIMIT = 20;
|
|
96907
97570
|
var _runsCmdDeps = {
|
|
96908
97571
|
getRunsDir
|
|
@@ -96957,7 +97620,7 @@ async function runsCommand(options = {}) {
|
|
|
96957
97620
|
}
|
|
96958
97621
|
const rows = [];
|
|
96959
97622
|
for (const entry of entries) {
|
|
96960
|
-
const metaPath =
|
|
97623
|
+
const metaPath = join69(runsDir, entry, "meta.json");
|
|
96961
97624
|
let meta3;
|
|
96962
97625
|
try {
|
|
96963
97626
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -97034,7 +97697,7 @@ async function runsCommand(options = {}) {
|
|
|
97034
97697
|
|
|
97035
97698
|
// src/commands/unlock.ts
|
|
97036
97699
|
init_source();
|
|
97037
|
-
import { join as
|
|
97700
|
+
import { join as join70 } from "path";
|
|
97038
97701
|
function isProcessAlive3(pid) {
|
|
97039
97702
|
try {
|
|
97040
97703
|
process.kill(pid, 0);
|
|
@@ -97049,7 +97712,7 @@ function formatLockAge(ageMs) {
|
|
|
97049
97712
|
}
|
|
97050
97713
|
async function unlockCommand(options) {
|
|
97051
97714
|
const workdir = options.dir ?? process.cwd();
|
|
97052
|
-
const lockPath =
|
|
97715
|
+
const lockPath = join70(workdir, "nax.lock");
|
|
97053
97716
|
const lockFile = Bun.file(lockPath);
|
|
97054
97717
|
const exists = await lockFile.exists();
|
|
97055
97718
|
if (!exists) {
|
|
@@ -105122,7 +105785,7 @@ Next: nax generate --package ${options.package}`));
|
|
|
105122
105785
|
}
|
|
105123
105786
|
return;
|
|
105124
105787
|
}
|
|
105125
|
-
const naxDir =
|
|
105788
|
+
const naxDir = join84(workdir, ".nax");
|
|
105126
105789
|
if (existsSync37(naxDir) && !options.force) {
|
|
105127
105790
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
105128
105791
|
return;
|
|
@@ -105151,11 +105814,11 @@ Next: nax generate --package ${options.package}`));
|
|
|
105151
105814
|
}
|
|
105152
105815
|
}
|
|
105153
105816
|
}
|
|
105154
|
-
mkdirSync7(
|
|
105155
|
-
mkdirSync7(
|
|
105817
|
+
mkdirSync7(join84(naxDir, "features"), { recursive: true });
|
|
105818
|
+
mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
|
|
105156
105819
|
const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
|
|
105157
|
-
await Bun.write(
|
|
105158
|
-
await Bun.write(
|
|
105820
|
+
await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
|
|
105821
|
+
await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
|
|
105159
105822
|
hooks: {
|
|
105160
105823
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
105161
105824
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -105163,12 +105826,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
105163
105826
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
105164
105827
|
}
|
|
105165
105828
|
}, null, 2));
|
|
105166
|
-
await Bun.write(
|
|
105829
|
+
await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
|
|
105167
105830
|
*.tmp
|
|
105168
105831
|
.paused.json
|
|
105169
105832
|
.nax-verifier-verdict.json
|
|
105170
105833
|
`);
|
|
105171
|
-
await Bun.write(
|
|
105834
|
+
await Bun.write(join84(naxDir, "context.md"), `# Project Context
|
|
105172
105835
|
|
|
105173
105836
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
105174
105837
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -105298,8 +105961,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105298
105961
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
105299
105962
|
process.exit(1);
|
|
105300
105963
|
}
|
|
105301
|
-
const featureDir =
|
|
105302
|
-
const prdPath =
|
|
105964
|
+
const featureDir = join84(naxDir, "features", options.feature);
|
|
105965
|
+
const prdPath = join84(featureDir, "prd.json");
|
|
105303
105966
|
if (options.plan && options.from) {
|
|
105304
105967
|
if (existsSync37(prdPath) && !options.force) {
|
|
105305
105968
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -105321,10 +105984,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105321
105984
|
}
|
|
105322
105985
|
}
|
|
105323
105986
|
try {
|
|
105324
|
-
const planLogDir =
|
|
105987
|
+
const planLogDir = join84(featureDir, "plan");
|
|
105325
105988
|
mkdirSync7(planLogDir, { recursive: true });
|
|
105326
105989
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105327
|
-
const planLogPath =
|
|
105990
|
+
const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
|
|
105328
105991
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
105329
105992
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
105330
105993
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -105370,10 +106033,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105370
106033
|
resetLogger();
|
|
105371
106034
|
const projectKey = config2.name?.trim() || basename17(workdir);
|
|
105372
106035
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
105373
|
-
const runsDir =
|
|
106036
|
+
const runsDir = join84(outputDir, "features", options.feature, "runs");
|
|
105374
106037
|
mkdirSync7(runsDir, { recursive: true });
|
|
105375
106038
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105376
|
-
const logFilePath =
|
|
106039
|
+
const logFilePath = join84(runsDir, `${runId}.jsonl`);
|
|
105377
106040
|
const isTTY = process.stdout.isTTY ?? false;
|
|
105378
106041
|
const headlessFlag = options.headless ?? false;
|
|
105379
106042
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -105390,7 +106053,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105390
106053
|
config2.agent.default = options.agent;
|
|
105391
106054
|
}
|
|
105392
106055
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
105393
|
-
const globalNaxDir =
|
|
106056
|
+
const globalNaxDir = join84(homedir3(), ".nax");
|
|
105394
106057
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
105395
106058
|
const eventEmitter = new PipelineEventEmitter;
|
|
105396
106059
|
let tuiInstance;
|
|
@@ -105413,7 +106076,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105413
106076
|
} else {
|
|
105414
106077
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
105415
106078
|
}
|
|
105416
|
-
const statusFilePath =
|
|
106079
|
+
const statusFilePath = join84(outputDir, "status.json");
|
|
105417
106080
|
let parallel;
|
|
105418
106081
|
if (options.parallel !== undefined) {
|
|
105419
106082
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -105439,7 +106102,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105439
106102
|
headless: useHeadless,
|
|
105440
106103
|
skipPrecheck: options.skipPrecheck ?? false
|
|
105441
106104
|
});
|
|
105442
|
-
const latestSymlink =
|
|
106105
|
+
const latestSymlink = join84(runsDir, "latest.jsonl");
|
|
105443
106106
|
try {
|
|
105444
106107
|
if (existsSync37(latestSymlink)) {
|
|
105445
106108
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -105500,9 +106163,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
105500
106163
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
105501
106164
|
process.exit(1);
|
|
105502
106165
|
}
|
|
105503
|
-
const featureDir =
|
|
106166
|
+
const featureDir = join84(naxDir, "features", name);
|
|
105504
106167
|
mkdirSync7(featureDir, { recursive: true });
|
|
105505
|
-
await Bun.write(
|
|
106168
|
+
await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
|
|
105506
106169
|
|
|
105507
106170
|
## Overview
|
|
105508
106171
|
|
|
@@ -105535,7 +106198,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
105535
106198
|
|
|
105536
106199
|
<!-- What this feature explicitly does NOT cover. -->
|
|
105537
106200
|
`);
|
|
105538
|
-
await Bun.write(
|
|
106201
|
+
await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
|
|
105539
106202
|
|
|
105540
106203
|
Created: ${new Date().toISOString()}
|
|
105541
106204
|
|
|
@@ -105561,7 +106224,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
105561
106224
|
console.error(source_default.red("nax not initialized."));
|
|
105562
106225
|
process.exit(1);
|
|
105563
106226
|
}
|
|
105564
|
-
const featuresDir =
|
|
106227
|
+
const featuresDir = join84(naxDir, "features");
|
|
105565
106228
|
if (!existsSync37(featuresDir)) {
|
|
105566
106229
|
console.log(source_default.dim("No features yet."));
|
|
105567
106230
|
return;
|
|
@@ -105576,7 +106239,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
105576
106239
|
Features:
|
|
105577
106240
|
`));
|
|
105578
106241
|
for (const name of entries) {
|
|
105579
|
-
const prdPath =
|
|
106242
|
+
const prdPath = join84(featuresDir, name, "prd.json");
|
|
105580
106243
|
if (existsSync37(prdPath)) {
|
|
105581
106244
|
const prd = await loadPRD(prdPath);
|
|
105582
106245
|
const c = countStories(prd);
|
|
@@ -105611,10 +106274,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
105611
106274
|
cliOverrides.profile = options.profile;
|
|
105612
106275
|
}
|
|
105613
106276
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
105614
|
-
const featureLogDir =
|
|
106277
|
+
const featureLogDir = join84(naxDir, "features", options.feature, "plan");
|
|
105615
106278
|
mkdirSync7(featureLogDir, { recursive: true });
|
|
105616
106279
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105617
|
-
const planLogPath =
|
|
106280
|
+
const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
|
|
105618
106281
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
105619
106282
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
105620
106283
|
try {
|