@nathapp/nax 0.54.12 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +368 -219
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -3259,6 +3259,14 @@ async function withProcessTimeout(proc, timeoutMs, opts) {
|
|
|
3259
3259
|
}
|
|
3260
3260
|
|
|
3261
3261
|
// src/utils/bun-deps.ts
|
|
3262
|
+
var exports_bun_deps = {};
|
|
3263
|
+
__export(exports_bun_deps, {
|
|
3264
|
+
which: () => which,
|
|
3265
|
+
typedSpawn: () => typedSpawn,
|
|
3266
|
+
spawn: () => spawn,
|
|
3267
|
+
sleep: () => sleep,
|
|
3268
|
+
file: () => file
|
|
3269
|
+
});
|
|
3262
3270
|
function typedSpawn(cmd, opts) {
|
|
3263
3271
|
return Bun.spawn(cmd, opts);
|
|
3264
3272
|
}
|
|
@@ -3268,6 +3276,9 @@ function which(name) {
|
|
|
3268
3276
|
function sleep(ms) {
|
|
3269
3277
|
return Bun.sleep(ms);
|
|
3270
3278
|
}
|
|
3279
|
+
function file(path) {
|
|
3280
|
+
return Bun.file(path);
|
|
3281
|
+
}
|
|
3271
3282
|
var spawn;
|
|
3272
3283
|
var init_bun_deps = __esm(() => {
|
|
3273
3284
|
spawn = Bun.spawn;
|
|
@@ -14942,26 +14953,26 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
|
|
|
14942
14953
|
_json.pattern = pattern.source;
|
|
14943
14954
|
}, fileProcessor = (schema, _ctx, json, _params) => {
|
|
14944
14955
|
const _json = json;
|
|
14945
|
-
const
|
|
14956
|
+
const file2 = {
|
|
14946
14957
|
type: "string",
|
|
14947
14958
|
format: "binary",
|
|
14948
14959
|
contentEncoding: "binary"
|
|
14949
14960
|
};
|
|
14950
14961
|
const { minimum, maximum, mime } = schema._zod.bag;
|
|
14951
14962
|
if (minimum !== undefined)
|
|
14952
|
-
|
|
14963
|
+
file2.minLength = minimum;
|
|
14953
14964
|
if (maximum !== undefined)
|
|
14954
|
-
|
|
14965
|
+
file2.maxLength = maximum;
|
|
14955
14966
|
if (mime) {
|
|
14956
14967
|
if (mime.length === 1) {
|
|
14957
|
-
|
|
14958
|
-
Object.assign(_json,
|
|
14968
|
+
file2.contentMediaType = mime[0];
|
|
14969
|
+
Object.assign(_json, file2);
|
|
14959
14970
|
} else {
|
|
14960
|
-
Object.assign(_json,
|
|
14971
|
+
Object.assign(_json, file2);
|
|
14961
14972
|
_json.anyOf = mime.map((m) => ({ contentMediaType: m }));
|
|
14962
14973
|
}
|
|
14963
14974
|
} else {
|
|
14964
|
-
Object.assign(_json,
|
|
14975
|
+
Object.assign(_json, file2);
|
|
14965
14976
|
}
|
|
14966
14977
|
}, successProcessor = (_schema, _ctx, json, _params) => {
|
|
14967
14978
|
json.type = "boolean";
|
|
@@ -15833,7 +15844,7 @@ __export(exports_schemas2, {
|
|
|
15833
15844
|
function: () => _function,
|
|
15834
15845
|
float64: () => float64,
|
|
15835
15846
|
float32: () => float32,
|
|
15836
|
-
file: () =>
|
|
15847
|
+
file: () => file2,
|
|
15837
15848
|
exactOptional: () => exactOptional,
|
|
15838
15849
|
enum: () => _enum2,
|
|
15839
15850
|
emoji: () => emoji2,
|
|
@@ -16214,7 +16225,7 @@ function literal(value, params) {
|
|
|
16214
16225
|
...exports_util.normalizeParams(params)
|
|
16215
16226
|
});
|
|
16216
16227
|
}
|
|
16217
|
-
function
|
|
16228
|
+
function file2(params) {
|
|
16218
16229
|
return _file(ZodFile, params);
|
|
16219
16230
|
}
|
|
16220
16231
|
function transform(fn) {
|
|
@@ -17662,7 +17673,7 @@ __export(exports_external, {
|
|
|
17662
17673
|
float64: () => float64,
|
|
17663
17674
|
float32: () => float32,
|
|
17664
17675
|
flattenError: () => flattenError,
|
|
17665
|
-
file: () =>
|
|
17676
|
+
file: () => file2,
|
|
17666
17677
|
exactOptional: () => exactOptional,
|
|
17667
17678
|
enum: () => _enum2,
|
|
17668
17679
|
endsWith: () => _endsWith,
|
|
@@ -19586,7 +19597,8 @@ class SpawnAcpSession {
|
|
|
19586
19597
|
const stderr = await new Response(proc.stderr).text();
|
|
19587
19598
|
if (exitCode !== 0) {
|
|
19588
19599
|
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
19589
|
-
|
|
19600
|
+
exitCode,
|
|
19601
|
+
stderr: stderr.slice(0, 500)
|
|
19590
19602
|
});
|
|
19591
19603
|
return {
|
|
19592
19604
|
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
@@ -19912,10 +19924,10 @@ async function sweepFeatureSessions(workdir, featureName) {
|
|
|
19912
19924
|
}
|
|
19913
19925
|
async function sweepStaleFeatureSessions(workdir, featureName, maxAgeMs = MAX_SESSION_AGE_MS) {
|
|
19914
19926
|
const path = acpSessionsPath(workdir, featureName);
|
|
19915
|
-
const
|
|
19916
|
-
if (!await
|
|
19927
|
+
const file3 = Bun.file(path);
|
|
19928
|
+
if (!await file3.exists())
|
|
19917
19929
|
return;
|
|
19918
|
-
const ageMs = Date.now() -
|
|
19930
|
+
const ageMs = Date.now() - file3.lastModified;
|
|
19919
19931
|
if (ageMs < maxAgeMs)
|
|
19920
19932
|
return;
|
|
19921
19933
|
getSafeLogger()?.info("acp-adapter", `[sweep] Sidecar is ${Math.round(ageMs / 60000)}m old \u2014 sweeping stale sessions`, {
|
|
@@ -19994,11 +20006,23 @@ class AcpAgentAdapter {
|
|
|
19994
20006
|
storyId: options.storyId,
|
|
19995
20007
|
sessionRole: options.sessionRole
|
|
19996
20008
|
});
|
|
20009
|
+
let sessionErrorRetried = false;
|
|
19997
20010
|
for (let attempt = 0;attempt < MAX_RATE_LIMIT_RETRIES; attempt++) {
|
|
19998
20011
|
try {
|
|
19999
20012
|
const result = await this._runWithClient(options, startTime);
|
|
20000
20013
|
if (!result.success) {
|
|
20001
20014
|
getSafeLogger()?.warn("acp-adapter", `Run failed for ${this.name}`, { exitCode: result.exitCode });
|
|
20015
|
+
if (result.sessionError && _acpAdapterDeps.shouldRetrySessionError && !sessionErrorRetried) {
|
|
20016
|
+
sessionErrorRetried = true;
|
|
20017
|
+
getSafeLogger()?.warn("acp-adapter", "Session error \u2014 retrying with fresh session", {
|
|
20018
|
+
storyId: options.storyId,
|
|
20019
|
+
featureName: options.featureName
|
|
20020
|
+
});
|
|
20021
|
+
if (options.featureName && options.storyId) {
|
|
20022
|
+
await clearAcpSession(options.workdir, options.featureName, options.storyId);
|
|
20023
|
+
}
|
|
20024
|
+
continue;
|
|
20025
|
+
}
|
|
20002
20026
|
}
|
|
20003
20027
|
return result;
|
|
20004
20028
|
} catch (err) {
|
|
@@ -20127,6 +20151,7 @@ class AcpAgentAdapter {
|
|
|
20127
20151
|
};
|
|
20128
20152
|
}
|
|
20129
20153
|
const success2 = lastResponse?.stopReason === "end_turn";
|
|
20154
|
+
const isSessionError = lastResponse?.stopReason === "error";
|
|
20130
20155
|
const output = extractOutput(lastResponse);
|
|
20131
20156
|
const estimatedCost = totalExactCostUsd ?? (totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0);
|
|
20132
20157
|
return {
|
|
@@ -20134,6 +20159,7 @@ class AcpAgentAdapter {
|
|
|
20134
20159
|
exitCode: success2 ? 0 : 1,
|
|
20135
20160
|
output: output.slice(-MAX_AGENT_OUTPUT_CHARS2),
|
|
20136
20161
|
rateLimited: false,
|
|
20162
|
+
sessionError: isSessionError,
|
|
20137
20163
|
durationMs,
|
|
20138
20164
|
estimatedCost
|
|
20139
20165
|
};
|
|
@@ -20328,6 +20354,7 @@ var init_adapter2 = __esm(() => {
|
|
|
20328
20354
|
_acpAdapterDeps = {
|
|
20329
20355
|
which,
|
|
20330
20356
|
sleep,
|
|
20357
|
+
shouldRetrySessionError: true,
|
|
20331
20358
|
createClient(cmdStr, cwd, timeoutSeconds, pidRegistry) {
|
|
20332
20359
|
return createSpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry);
|
|
20333
20360
|
}
|
|
@@ -21190,17 +21217,24 @@ ${errors3.join(`
|
|
|
21190
21217
|
return result.data;
|
|
21191
21218
|
}
|
|
21192
21219
|
async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
21220
|
+
const logger = getLogger();
|
|
21193
21221
|
const rootNaxDir = dirname(rootConfigPath);
|
|
21194
21222
|
const rootConfig = await loadConfig(rootNaxDir);
|
|
21195
21223
|
if (!packageDir) {
|
|
21224
|
+
logger.debug("config", "No packageDir \u2014 using root config");
|
|
21196
21225
|
return rootConfig;
|
|
21197
21226
|
}
|
|
21198
21227
|
const repoRoot = dirname(rootNaxDir);
|
|
21199
21228
|
const packageConfigPath = join6(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
|
|
21200
21229
|
const packageOverride = await loadJsonFile(packageConfigPath, "config");
|
|
21201
21230
|
if (!packageOverride) {
|
|
21231
|
+
logger.debug("config", "Per-package config not found \u2014 falling back to root config", {
|
|
21232
|
+
packageConfigPath,
|
|
21233
|
+
packageDir
|
|
21234
|
+
});
|
|
21202
21235
|
return rootConfig;
|
|
21203
21236
|
}
|
|
21237
|
+
logger.debug("config", "Per-package config loaded", { packageConfigPath, packageDir });
|
|
21204
21238
|
return mergePackageConfig(rootConfig, packageOverride);
|
|
21205
21239
|
}
|
|
21206
21240
|
var init_loader = __esm(() => {
|
|
@@ -22370,7 +22404,7 @@ var package_default;
|
|
|
22370
22404
|
var init_package = __esm(() => {
|
|
22371
22405
|
package_default = {
|
|
22372
22406
|
name: "@nathapp/nax",
|
|
22373
|
-
version: "0.
|
|
22407
|
+
version: "0.55.0",
|
|
22374
22408
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22375
22409
|
type: "module",
|
|
22376
22410
|
bin: {
|
|
@@ -22449,8 +22483,8 @@ var init_version = __esm(() => {
|
|
|
22449
22483
|
NAX_VERSION = package_default.version;
|
|
22450
22484
|
NAX_COMMIT = (() => {
|
|
22451
22485
|
try {
|
|
22452
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22453
|
-
return "
|
|
22486
|
+
if (/^[0-9a-f]{6,10}$/.test("e16efa0"))
|
|
22487
|
+
return "e16efa0";
|
|
22454
22488
|
} catch {}
|
|
22455
22489
|
try {
|
|
22456
22490
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -23886,12 +23920,12 @@ async function loadPendingInteraction(requestId, featureDir) {
|
|
|
23886
23920
|
const filename = `${requestId}.json`;
|
|
23887
23921
|
const filePath = path3.join(interactionsDir, filename);
|
|
23888
23922
|
try {
|
|
23889
|
-
const
|
|
23890
|
-
const exists = await
|
|
23923
|
+
const file3 = Bun.file(filePath);
|
|
23924
|
+
const exists = await file3.exists();
|
|
23891
23925
|
if (!exists) {
|
|
23892
23926
|
return null;
|
|
23893
23927
|
}
|
|
23894
|
-
const json2 = await
|
|
23928
|
+
const json2 = await file3.text();
|
|
23895
23929
|
const request = JSON.parse(json2);
|
|
23896
23930
|
return request;
|
|
23897
23931
|
} catch {
|
|
@@ -24220,9 +24254,9 @@ var init_acceptance2 = __esm(() => {
|
|
|
24220
24254
|
async execute(ctx) {
|
|
24221
24255
|
const logger = getLogger();
|
|
24222
24256
|
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24223
|
-
logger.info("acceptance", "Running acceptance tests");
|
|
24257
|
+
logger.info("acceptance", "Running acceptance tests", { storyId: ctx.story.id });
|
|
24224
24258
|
if (!ctx.featureDir) {
|
|
24225
|
-
logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests");
|
|
24259
|
+
logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests", { storyId: ctx.story.id });
|
|
24226
24260
|
return { action: "continue" };
|
|
24227
24261
|
}
|
|
24228
24262
|
const testGroups = ctx.acceptanceTestPaths ?? [
|
|
@@ -24239,11 +24273,12 @@ var init_acceptance2 = __esm(() => {
|
|
|
24239
24273
|
const testFile = Bun.file(testPath);
|
|
24240
24274
|
const exists = await testFile.exists();
|
|
24241
24275
|
if (!exists) {
|
|
24242
|
-
logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { testPath });
|
|
24276
|
+
logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { storyId: ctx.story.id, testPath });
|
|
24243
24277
|
continue;
|
|
24244
24278
|
}
|
|
24245
24279
|
const testCmdParts = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
|
|
24246
24280
|
logger.info("acceptance", "Running acceptance command", {
|
|
24281
|
+
storyId: ctx.story.id,
|
|
24247
24282
|
cmd: testCmdParts.join(" "),
|
|
24248
24283
|
packageDir
|
|
24249
24284
|
});
|
|
@@ -24266,12 +24301,14 @@ ${stderr}`;
|
|
|
24266
24301
|
const overriddenFailures = failedACs.filter((acId) => overrides[acId]);
|
|
24267
24302
|
if (overriddenFailures.length > 0) {
|
|
24268
24303
|
logger.warn("acceptance", "Skipped failures (overridden)", {
|
|
24304
|
+
storyId: ctx.story.id,
|
|
24269
24305
|
overriddenFailures,
|
|
24270
24306
|
overrides: overriddenFailures.map((acId) => ({ acId, reason: overrides[acId] }))
|
|
24271
24307
|
});
|
|
24272
24308
|
}
|
|
24273
24309
|
if (failedACs.length === 0 && exitCode !== 0) {
|
|
24274
24310
|
logger.error("acceptance", "Tests errored with no AC failures parsed", {
|
|
24311
|
+
storyId: ctx.story.id,
|
|
24275
24312
|
exitCode,
|
|
24276
24313
|
packageDir
|
|
24277
24314
|
});
|
|
@@ -24288,18 +24325,19 @@ ${stderr}`;
|
|
|
24288
24325
|
}
|
|
24289
24326
|
if (actualFailures.length > 0) {
|
|
24290
24327
|
logger.error("acceptance", "Acceptance tests failed", {
|
|
24328
|
+
storyId: ctx.story.id,
|
|
24291
24329
|
failedACs: actualFailures,
|
|
24292
24330
|
packageDir
|
|
24293
24331
|
});
|
|
24294
24332
|
logTestOutput(logger, "acceptance", output);
|
|
24295
24333
|
} else if (exitCode === 0) {
|
|
24296
|
-
logger.info("acceptance", "Package acceptance tests passed", { packageDir });
|
|
24334
|
+
logger.info("acceptance", "Package acceptance tests passed", { storyId: ctx.story.id, packageDir });
|
|
24297
24335
|
}
|
|
24298
24336
|
}
|
|
24299
24337
|
const combinedOutput = allOutputParts.join(`
|
|
24300
24338
|
`);
|
|
24301
24339
|
if (allFailedACs.length === 0) {
|
|
24302
|
-
logger.info("acceptance", "All acceptance tests passed");
|
|
24340
|
+
logger.info("acceptance", "All acceptance tests passed", { storyId: ctx.story.id });
|
|
24303
24341
|
return { action: "continue" };
|
|
24304
24342
|
}
|
|
24305
24343
|
ctx.acceptanceFailures = {
|
|
@@ -24639,6 +24677,99 @@ ${stderr}` };
|
|
|
24639
24677
|
};
|
|
24640
24678
|
});
|
|
24641
24679
|
|
|
24680
|
+
// src/quality/runner.ts
|
|
24681
|
+
var {spawn: spawn2 } = globalThis.Bun;
|
|
24682
|
+
async function runQualityCommand(opts) {
|
|
24683
|
+
const { commandName, command, workdir, storyId, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
|
|
24684
|
+
const startTime = Date.now();
|
|
24685
|
+
const logger = getSafeLogger();
|
|
24686
|
+
logger?.info("quality", `Running ${commandName}`, { storyId, commandName, command, workdir });
|
|
24687
|
+
try {
|
|
24688
|
+
const parts = command.split(/\s+/);
|
|
24689
|
+
const [executable, ...args] = parts;
|
|
24690
|
+
const proc = _qualityRunnerDeps.spawn({
|
|
24691
|
+
cmd: [executable, ...args],
|
|
24692
|
+
cwd: workdir,
|
|
24693
|
+
stdout: "pipe",
|
|
24694
|
+
stderr: "pipe"
|
|
24695
|
+
});
|
|
24696
|
+
let timedOut = false;
|
|
24697
|
+
const killTimer = setTimeout(() => {
|
|
24698
|
+
timedOut = true;
|
|
24699
|
+
try {
|
|
24700
|
+
proc.kill("SIGTERM");
|
|
24701
|
+
} catch {}
|
|
24702
|
+
setTimeout(() => {
|
|
24703
|
+
try {
|
|
24704
|
+
proc.kill("SIGKILL");
|
|
24705
|
+
} catch {}
|
|
24706
|
+
}, SIGKILL_GRACE_PERIOD_MS2);
|
|
24707
|
+
}, timeoutMs);
|
|
24708
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
24709
|
+
proc.exited,
|
|
24710
|
+
new Response(proc.stdout).text(),
|
|
24711
|
+
new Response(proc.stderr).text()
|
|
24712
|
+
]);
|
|
24713
|
+
clearTimeout(killTimer);
|
|
24714
|
+
const durationMs = Date.now() - startTime;
|
|
24715
|
+
if (timedOut) {
|
|
24716
|
+
logger?.warn("quality", `${commandName} timed out`, {
|
|
24717
|
+
storyId,
|
|
24718
|
+
commandName,
|
|
24719
|
+
command,
|
|
24720
|
+
workdir,
|
|
24721
|
+
durationMs,
|
|
24722
|
+
timedOut: true
|
|
24723
|
+
});
|
|
24724
|
+
return {
|
|
24725
|
+
commandName,
|
|
24726
|
+
command,
|
|
24727
|
+
success: false,
|
|
24728
|
+
exitCode: -1,
|
|
24729
|
+
output: `[nax] ${commandName} timed out after ${timeoutMs / 1000}s`,
|
|
24730
|
+
durationMs,
|
|
24731
|
+
timedOut: true
|
|
24732
|
+
};
|
|
24733
|
+
}
|
|
24734
|
+
const output = [stdout, stderr].filter(Boolean).join(`
|
|
24735
|
+
`);
|
|
24736
|
+
const success2 = exitCode === 0;
|
|
24737
|
+
logger?.info("quality", `${commandName} completed`, {
|
|
24738
|
+
storyId,
|
|
24739
|
+
commandName,
|
|
24740
|
+
command,
|
|
24741
|
+
workdir,
|
|
24742
|
+
exitCode,
|
|
24743
|
+
durationMs,
|
|
24744
|
+
timedOut: false
|
|
24745
|
+
});
|
|
24746
|
+
return { commandName, command, success: success2, exitCode, output, durationMs, timedOut: false };
|
|
24747
|
+
} catch (error48) {
|
|
24748
|
+
const durationMs = Date.now() - startTime;
|
|
24749
|
+
return {
|
|
24750
|
+
commandName,
|
|
24751
|
+
command,
|
|
24752
|
+
success: false,
|
|
24753
|
+
exitCode: -1,
|
|
24754
|
+
output: errorMessage(error48),
|
|
24755
|
+
durationMs,
|
|
24756
|
+
timedOut: false
|
|
24757
|
+
};
|
|
24758
|
+
}
|
|
24759
|
+
}
|
|
24760
|
+
var DEFAULT_TIMEOUT_MS = 120000, SIGKILL_GRACE_PERIOD_MS2 = 5000, _qualityRunnerDeps;
|
|
24761
|
+
var init_runner2 = __esm(() => {
|
|
24762
|
+
init_logger2();
|
|
24763
|
+
_qualityRunnerDeps = {
|
|
24764
|
+
spawn: spawn2
|
|
24765
|
+
};
|
|
24766
|
+
});
|
|
24767
|
+
|
|
24768
|
+
// src/quality/index.ts
|
|
24769
|
+
var init_quality = __esm(() => {
|
|
24770
|
+
init_runner2();
|
|
24771
|
+
});
|
|
24772
|
+
|
|
24642
24773
|
// src/pipeline/event-bus.ts
|
|
24643
24774
|
class PipelineEventBus {
|
|
24644
24775
|
subscribers = new Map;
|
|
@@ -24913,7 +25044,7 @@ var init_language_commands = __esm(() => {
|
|
|
24913
25044
|
});
|
|
24914
25045
|
|
|
24915
25046
|
// src/review/semantic.ts
|
|
24916
|
-
var {spawn:
|
|
25047
|
+
var {spawn: spawn3 } = globalThis.Bun;
|
|
24917
25048
|
async function collectDiff(workdir, storyGitRef, excludePatterns) {
|
|
24918
25049
|
const cmd = ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`];
|
|
24919
25050
|
if (excludePatterns.length > 0) {
|
|
@@ -25016,7 +25147,7 @@ If all ACs are correctly implemented, respond with { "passed": true, "findings":
|
|
|
25016
25147
|
function parseLLMResponse(raw) {
|
|
25017
25148
|
try {
|
|
25018
25149
|
let cleaned = raw.trim();
|
|
25019
|
-
const fenceMatch = cleaned.match(/^```(?:json)?\s*\n
|
|
25150
|
+
const fenceMatch = cleaned.match(/^```(?:json)?\s*\n([\s\S]*?)\n```/);
|
|
25020
25151
|
if (fenceMatch)
|
|
25021
25152
|
cleaned = fenceMatch[1].trim();
|
|
25022
25153
|
const parsed = JSON.parse(cleaned);
|
|
@@ -25198,18 +25329,17 @@ var init_semantic = __esm(() => {
|
|
|
25198
25329
|
init_logger2();
|
|
25199
25330
|
init_git();
|
|
25200
25331
|
_semanticDeps = {
|
|
25201
|
-
spawn:
|
|
25332
|
+
spawn: spawn3,
|
|
25202
25333
|
isGitRefValid,
|
|
25203
25334
|
getMergeBase
|
|
25204
25335
|
};
|
|
25205
25336
|
});
|
|
25206
25337
|
|
|
25207
25338
|
// src/review/runner.ts
|
|
25208
|
-
var {spawn: spawn3 } = globalThis.Bun;
|
|
25209
25339
|
async function loadPackageJson(workdir) {
|
|
25210
25340
|
try {
|
|
25211
|
-
const
|
|
25212
|
-
const content = await
|
|
25341
|
+
const file3 = _reviewRunnerDeps.file(`${workdir}/package.json`);
|
|
25342
|
+
const content = await file3.text();
|
|
25213
25343
|
return JSON.parse(content);
|
|
25214
25344
|
} catch {
|
|
25215
25345
|
return null;
|
|
@@ -25257,81 +25387,20 @@ async function resolveCommand(check2, config2, executionConfig, workdir, quality
|
|
|
25257
25387
|
}
|
|
25258
25388
|
return null;
|
|
25259
25389
|
}
|
|
25260
|
-
async function runCheck(check2, command, workdir) {
|
|
25261
|
-
const
|
|
25262
|
-
|
|
25263
|
-
|
|
25264
|
-
|
|
25265
|
-
|
|
25266
|
-
|
|
25267
|
-
|
|
25268
|
-
|
|
25269
|
-
|
|
25270
|
-
cwd: workdir,
|
|
25271
|
-
stdout: "pipe",
|
|
25272
|
-
stderr: "pipe"
|
|
25273
|
-
});
|
|
25274
|
-
let timedOut = false;
|
|
25275
|
-
const timerId = setTimeout(() => {
|
|
25276
|
-
timedOut = true;
|
|
25277
|
-
try {
|
|
25278
|
-
proc.kill("SIGTERM");
|
|
25279
|
-
} catch {}
|
|
25280
|
-
setTimeout(() => {
|
|
25281
|
-
try {
|
|
25282
|
-
proc.kill("SIGKILL");
|
|
25283
|
-
} catch {}
|
|
25284
|
-
}, SIGKILL_GRACE_PERIOD_MS2);
|
|
25285
|
-
}, REVIEW_CHECK_TIMEOUT_MS);
|
|
25286
|
-
const exitCode = await proc.exited;
|
|
25287
|
-
clearTimeout(timerId);
|
|
25288
|
-
if (timedOut) {
|
|
25289
|
-
return {
|
|
25290
|
-
check: check2,
|
|
25291
|
-
command,
|
|
25292
|
-
success: false,
|
|
25293
|
-
exitCode: -1,
|
|
25294
|
-
output: `[nax] ${check2} timed out after ${REVIEW_CHECK_TIMEOUT_MS / 1000}s`,
|
|
25295
|
-
durationMs: Date.now() - startTime
|
|
25296
|
-
};
|
|
25297
|
-
}
|
|
25298
|
-
const stdout = await new Response(proc.stdout).text();
|
|
25299
|
-
const stderr = await new Response(proc.stderr).text();
|
|
25300
|
-
const output = [stdout, stderr].filter(Boolean).join(`
|
|
25301
|
-
`);
|
|
25302
|
-
if (exitCode !== 0) {
|
|
25303
|
-
logger?.warn("review", `${check2} check failed`, {
|
|
25304
|
-
check: check2,
|
|
25305
|
-
command,
|
|
25306
|
-
workdir,
|
|
25307
|
-
exitCode,
|
|
25308
|
-
output: output.slice(0, 2000)
|
|
25309
|
-
});
|
|
25310
|
-
} else {
|
|
25311
|
-
logger?.debug("review", `${check2} check passed`, { check: check2, command, durationMs: Date.now() - startTime });
|
|
25312
|
-
}
|
|
25313
|
-
return {
|
|
25314
|
-
check: check2,
|
|
25315
|
-
command,
|
|
25316
|
-
success: exitCode === 0,
|
|
25317
|
-
exitCode,
|
|
25318
|
-
output,
|
|
25319
|
-
durationMs: Date.now() - startTime
|
|
25320
|
-
};
|
|
25321
|
-
} catch (error48) {
|
|
25322
|
-
return {
|
|
25323
|
-
check: check2,
|
|
25324
|
-
command,
|
|
25325
|
-
success: false,
|
|
25326
|
-
exitCode: -1,
|
|
25327
|
-
output: errorMessage(error48),
|
|
25328
|
-
durationMs: Date.now() - startTime
|
|
25329
|
-
};
|
|
25330
|
-
}
|
|
25390
|
+
async function runCheck(check2, command, workdir, storyId) {
|
|
25391
|
+
const result = await runQualityCommand({ commandName: check2, command, workdir, storyId });
|
|
25392
|
+
return {
|
|
25393
|
+
check: check2,
|
|
25394
|
+
command: result.command,
|
|
25395
|
+
success: result.success,
|
|
25396
|
+
exitCode: result.exitCode,
|
|
25397
|
+
output: result.output,
|
|
25398
|
+
durationMs: result.durationMs
|
|
25399
|
+
};
|
|
25331
25400
|
}
|
|
25332
25401
|
async function getUncommittedFilesImpl(workdir) {
|
|
25333
25402
|
try {
|
|
25334
|
-
const proc =
|
|
25403
|
+
const proc = Bun.spawn({
|
|
25335
25404
|
cmd: ["git", "diff", "--name-only", "HEAD"],
|
|
25336
25405
|
cwd: workdir,
|
|
25337
25406
|
stdout: "pipe",
|
|
@@ -25348,7 +25417,7 @@ async function getUncommittedFilesImpl(workdir) {
|
|
|
25348
25417
|
return [];
|
|
25349
25418
|
}
|
|
25350
25419
|
}
|
|
25351
|
-
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig) {
|
|
25420
|
+
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks) {
|
|
25352
25421
|
const startTime = Date.now();
|
|
25353
25422
|
const logger = getSafeLogger();
|
|
25354
25423
|
const checks3 = [];
|
|
@@ -25388,6 +25457,12 @@ Stage and commit these files before running review.`
|
|
|
25388
25457
|
};
|
|
25389
25458
|
}
|
|
25390
25459
|
for (const checkName of config2.checks) {
|
|
25460
|
+
if (retrySkipChecks?.has(checkName)) {
|
|
25461
|
+
getSafeLogger()?.debug("review", `Skipping ${checkName} check (already passed in previous review pass)`, {
|
|
25462
|
+
storyId
|
|
25463
|
+
});
|
|
25464
|
+
continue;
|
|
25465
|
+
}
|
|
25391
25466
|
if (checkName === "semantic") {
|
|
25392
25467
|
const semanticStory = {
|
|
25393
25468
|
id: storyId ?? "",
|
|
@@ -25416,7 +25491,7 @@ Stage and commit these files before running review.`
|
|
|
25416
25491
|
getSafeLogger()?.warn("review", `Skipping ${checkName} check (command not configured or disabled)`);
|
|
25417
25492
|
continue;
|
|
25418
25493
|
}
|
|
25419
|
-
const result = await runCheck(checkName, command, workdir);
|
|
25494
|
+
const result = await runCheck(checkName, command, workdir, storyId);
|
|
25420
25495
|
checks3.push(result);
|
|
25421
25496
|
if (!result.success && !firstFailure) {
|
|
25422
25497
|
firstFailure = `${checkName} failed (exit code ${result.exitCode})`;
|
|
@@ -25433,9 +25508,10 @@ Stage and commit these files before running review.`
|
|
|
25433
25508
|
failureReason: firstFailure
|
|
25434
25509
|
};
|
|
25435
25510
|
}
|
|
25436
|
-
var _reviewSemanticDeps, _reviewRunnerDeps,
|
|
25437
|
-
var
|
|
25511
|
+
var _reviewSemanticDeps, _reviewRunnerDeps, _reviewGitDeps;
|
|
25512
|
+
var init_runner3 = __esm(() => {
|
|
25438
25513
|
init_logger2();
|
|
25514
|
+
init_quality();
|
|
25439
25515
|
init_git();
|
|
25440
25516
|
init_language_commands();
|
|
25441
25517
|
init_semantic();
|
|
@@ -25443,7 +25519,6 @@ var init_runner2 = __esm(() => {
|
|
|
25443
25519
|
runSemanticReview
|
|
25444
25520
|
};
|
|
25445
25521
|
_reviewRunnerDeps = {
|
|
25446
|
-
spawn: spawn3,
|
|
25447
25522
|
file: Bun.file,
|
|
25448
25523
|
which: Bun.which
|
|
25449
25524
|
};
|
|
@@ -25483,9 +25558,9 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
25483
25558
|
}
|
|
25484
25559
|
|
|
25485
25560
|
class ReviewOrchestrator {
|
|
25486
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig) {
|
|
25561
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig, retrySkipChecks) {
|
|
25487
25562
|
const logger = getSafeLogger();
|
|
25488
|
-
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig);
|
|
25563
|
+
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks);
|
|
25489
25564
|
if (!builtIn.success) {
|
|
25490
25565
|
return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
|
|
25491
25566
|
}
|
|
@@ -25550,7 +25625,7 @@ class ReviewOrchestrator {
|
|
|
25550
25625
|
var _orchestratorDeps, reviewOrchestrator;
|
|
25551
25626
|
var init_orchestrator = __esm(() => {
|
|
25552
25627
|
init_logger2();
|
|
25553
|
-
|
|
25628
|
+
init_runner3();
|
|
25554
25629
|
_orchestratorDeps = { spawn: spawn4 };
|
|
25555
25630
|
reviewOrchestrator = new ReviewOrchestrator;
|
|
25556
25631
|
});
|
|
@@ -25579,12 +25654,14 @@ var init_review = __esm(() => {
|
|
|
25579
25654
|
const agentResolver = ctx.agentGetFn ?? getAgent;
|
|
25580
25655
|
const agentName = effectiveConfig.autoMode?.defaultAgent;
|
|
25581
25656
|
const modelResolver = (_tier) => agentName ? agentResolver(agentName) ?? null : null;
|
|
25657
|
+
const retrySkipChecks = ctx.retrySkipChecks;
|
|
25658
|
+
ctx.retrySkipChecks = undefined;
|
|
25582
25659
|
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir, effectiveConfig.quality?.commands, ctx.story.id, {
|
|
25583
25660
|
id: ctx.story.id,
|
|
25584
25661
|
title: ctx.story.title,
|
|
25585
25662
|
description: ctx.story.description,
|
|
25586
25663
|
acceptanceCriteria: ctx.story.acceptanceCriteria
|
|
25587
|
-
}, modelResolver, ctx.config);
|
|
25664
|
+
}, modelResolver, ctx.config, retrySkipChecks);
|
|
25588
25665
|
ctx.reviewResult = result.builtIn;
|
|
25589
25666
|
if (!result.success) {
|
|
25590
25667
|
const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
@@ -25626,17 +25703,6 @@ var init_review = __esm(() => {
|
|
|
25626
25703
|
|
|
25627
25704
|
// src/pipeline/stages/autofix.ts
|
|
25628
25705
|
import { join as join17 } from "path";
|
|
25629
|
-
async function runCommand(cmd, cwd) {
|
|
25630
|
-
const parts = cmd.split(/\s+/);
|
|
25631
|
-
const proc = Bun.spawn(parts, { cwd, stdout: "pipe", stderr: "pipe" });
|
|
25632
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
25633
|
-
proc.exited,
|
|
25634
|
-
new Response(proc.stdout).text(),
|
|
25635
|
-
new Response(proc.stderr).text()
|
|
25636
|
-
]);
|
|
25637
|
-
return { exitCode, output: `${stdout}
|
|
25638
|
-
${stderr}` };
|
|
25639
|
-
}
|
|
25640
25706
|
async function recheckReview(ctx) {
|
|
25641
25707
|
const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
|
|
25642
25708
|
if (!reviewStage2.enabled(ctx))
|
|
@@ -25732,6 +25798,7 @@ var init_autofix = __esm(() => {
|
|
|
25732
25798
|
init_config();
|
|
25733
25799
|
init_loader();
|
|
25734
25800
|
init_logger2();
|
|
25801
|
+
init_quality();
|
|
25735
25802
|
init_event_bus();
|
|
25736
25803
|
autofixStage = {
|
|
25737
25804
|
name: "autofix",
|
|
@@ -25768,7 +25835,12 @@ var init_autofix = __esm(() => {
|
|
|
25768
25835
|
if (hasLintFailure && (lintFixCmd || formatFixCmd)) {
|
|
25769
25836
|
if (lintFixCmd) {
|
|
25770
25837
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
|
|
25771
|
-
const lintResult = await _autofixDeps.
|
|
25838
|
+
const lintResult = await _autofixDeps.runQualityCommand({
|
|
25839
|
+
commandName: "lintFix",
|
|
25840
|
+
command: lintFixCmd,
|
|
25841
|
+
workdir: effectiveWorkdir,
|
|
25842
|
+
storyId: ctx.story.id
|
|
25843
|
+
});
|
|
25772
25844
|
logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id, command: lintFixCmd });
|
|
25773
25845
|
if (lintResult.exitCode !== 0) {
|
|
25774
25846
|
logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
|
|
@@ -25779,7 +25851,12 @@ var init_autofix = __esm(() => {
|
|
|
25779
25851
|
}
|
|
25780
25852
|
if (formatFixCmd) {
|
|
25781
25853
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
|
|
25782
|
-
const fmtResult = await _autofixDeps.
|
|
25854
|
+
const fmtResult = await _autofixDeps.runQualityCommand({
|
|
25855
|
+
commandName: "formatFix",
|
|
25856
|
+
command: formatFixCmd,
|
|
25857
|
+
workdir: effectiveWorkdir,
|
|
25858
|
+
storyId: ctx.story.id
|
|
25859
|
+
});
|
|
25783
25860
|
logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, {
|
|
25784
25861
|
storyId: ctx.story.id,
|
|
25785
25862
|
command: formatFixCmd
|
|
@@ -25805,6 +25882,14 @@ var init_autofix = __esm(() => {
|
|
|
25805
25882
|
if (agentFixed) {
|
|
25806
25883
|
if (ctx.reviewResult)
|
|
25807
25884
|
ctx.reviewResult = { ...ctx.reviewResult, success: true };
|
|
25885
|
+
const passedChecks = (ctx.reviewResult?.checks ?? []).filter((c) => c.success).map((c) => c.check);
|
|
25886
|
+
if (passedChecks.length > 0) {
|
|
25887
|
+
ctx.retrySkipChecks = new Set(passedChecks);
|
|
25888
|
+
logger.debug("autofix", "Skipping already-passed checks on retry", {
|
|
25889
|
+
storyId: ctx.story.id,
|
|
25890
|
+
skippedChecks: passedChecks
|
|
25891
|
+
});
|
|
25892
|
+
}
|
|
25808
25893
|
logger.info("autofix", "Agent rectification succeeded \u2014 retrying review", { storyId: ctx.story.id });
|
|
25809
25894
|
return { action: "retry", fromStage: "review" };
|
|
25810
25895
|
}
|
|
@@ -25814,7 +25899,7 @@ var init_autofix = __esm(() => {
|
|
|
25814
25899
|
};
|
|
25815
25900
|
_autofixDeps = {
|
|
25816
25901
|
getAgent,
|
|
25817
|
-
|
|
25902
|
+
runQualityCommand,
|
|
25818
25903
|
recheckReview,
|
|
25819
25904
|
runAgentRectification,
|
|
25820
25905
|
loadConfigForWorkdir
|
|
@@ -25830,8 +25915,8 @@ async function appendProgress(featureDir, storyId, status, message) {
|
|
|
25830
25915
|
const timestamp = new Date().toISOString();
|
|
25831
25916
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
25832
25917
|
`;
|
|
25833
|
-
const
|
|
25834
|
-
const existing = await
|
|
25918
|
+
const file3 = Bun.file(progressPath);
|
|
25919
|
+
const existing = await file3.exists() ? await file3.text() : "";
|
|
25835
25920
|
await Bun.write(progressPath, existing + entry);
|
|
25836
25921
|
}
|
|
25837
25922
|
var init_progress = () => {};
|
|
@@ -25890,6 +25975,7 @@ var init_completion = __esm(() => {
|
|
|
25890
25975
|
await savePRD(ctx.prd, prdPath);
|
|
25891
25976
|
const updatedCounts = countStories(ctx.prd);
|
|
25892
25977
|
logger.info("completion", "Progress update", {
|
|
25978
|
+
storyId: ctx.story.id,
|
|
25893
25979
|
completed: updatedCounts.passed + updatedCounts.failed,
|
|
25894
25980
|
total: updatedCounts.total,
|
|
25895
25981
|
passed: updatedCounts.passed,
|
|
@@ -26347,7 +26433,7 @@ function deriveTestPatterns(contextFiles) {
|
|
|
26347
26433
|
async function detectTestDir(workdir) {
|
|
26348
26434
|
for (const dir of COMMON_TEST_DIRS) {
|
|
26349
26435
|
const fullPath = path6.join(workdir, dir);
|
|
26350
|
-
const
|
|
26436
|
+
const file3 = Bun.file(path6.join(fullPath, "."));
|
|
26351
26437
|
try {
|
|
26352
26438
|
const dirStat = await Bun.file(fullPath).exists();
|
|
26353
26439
|
const proc = Bun.spawn(["test", "-d", fullPath], { stdout: "pipe", stderr: "pipe" });
|
|
@@ -26410,21 +26496,21 @@ function formatTestSummary(files, detail) {
|
|
|
26410
26496
|
lines.push("The following tests already exist. DO NOT duplicate this coverage.");
|
|
26411
26497
|
lines.push("Focus only on testing NEW behavior introduced by this story.");
|
|
26412
26498
|
lines.push("");
|
|
26413
|
-
for (const
|
|
26499
|
+
for (const file3 of files) {
|
|
26414
26500
|
switch (detail) {
|
|
26415
26501
|
case "names-only":
|
|
26416
|
-
lines.push(`- **${
|
|
26502
|
+
lines.push(`- **${file3.relativePath}** (${file3.testCount} tests)`);
|
|
26417
26503
|
break;
|
|
26418
26504
|
case "names-and-counts":
|
|
26419
|
-
lines.push(`### ${
|
|
26420
|
-
for (const desc of
|
|
26505
|
+
lines.push(`### ${file3.relativePath} (${file3.testCount} tests)`);
|
|
26506
|
+
for (const desc of file3.describes) {
|
|
26421
26507
|
lines.push(`- ${desc.name} (${desc.tests.length} tests)`);
|
|
26422
26508
|
}
|
|
26423
26509
|
lines.push("");
|
|
26424
26510
|
break;
|
|
26425
26511
|
case "describe-blocks":
|
|
26426
|
-
lines.push(`### ${
|
|
26427
|
-
for (const desc of
|
|
26512
|
+
lines.push(`### ${file3.relativePath} (${file3.testCount} tests)`);
|
|
26513
|
+
for (const desc of file3.describes) {
|
|
26428
26514
|
lines.push(`- **${desc.name}** (${desc.tests.length} tests)`);
|
|
26429
26515
|
for (const test of desc.tests) {
|
|
26430
26516
|
lines.push(` - ${test}`);
|
|
@@ -26553,8 +26639,8 @@ function renderErrorSection(sections, byType) {
|
|
|
26553
26639
|
const fileList = match[1].split(",").map((f) => f.trim());
|
|
26554
26640
|
sections.push(`**Required files:**
|
|
26555
26641
|
`);
|
|
26556
|
-
for (const
|
|
26557
|
-
sections.push(`- \`${
|
|
26642
|
+
for (const file3 of fileList) {
|
|
26643
|
+
sections.push(`- \`${file3}\``);
|
|
26558
26644
|
}
|
|
26559
26645
|
sections.push(`
|
|
26560
26646
|
`);
|
|
@@ -26748,24 +26834,24 @@ async function addFileElements(elements, storyContext, story) {
|
|
|
26748
26834
|
for (const relativeFilePath of filesToLoad) {
|
|
26749
26835
|
try {
|
|
26750
26836
|
const absolutePath = path7.resolve(workdir, relativeFilePath);
|
|
26751
|
-
const
|
|
26752
|
-
if (!await
|
|
26837
|
+
const file3 = Bun.file(absolutePath);
|
|
26838
|
+
if (!await file3.exists()) {
|
|
26753
26839
|
const logger = getLogger();
|
|
26754
26840
|
logger.warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
|
|
26755
26841
|
continue;
|
|
26756
26842
|
}
|
|
26757
|
-
if (
|
|
26843
|
+
if (file3.size > MAX_FILE_SIZE_BYTES) {
|
|
26758
26844
|
const logger = getLogger();
|
|
26759
26845
|
logger.warn("context", "File too large for inline \u2014 using path-only", {
|
|
26760
26846
|
filePath: relativeFilePath,
|
|
26761
|
-
sizeKB: Math.round(
|
|
26847
|
+
sizeKB: Math.round(file3.size / 1024),
|
|
26762
26848
|
maxKB: 10,
|
|
26763
26849
|
storyId: story.id
|
|
26764
26850
|
});
|
|
26765
|
-
elements.push(createFileContext(relativeFilePath, `_File too large to inline (${Math.round(
|
|
26851
|
+
elements.push(createFileContext(relativeFilePath, `_File too large to inline (${Math.round(file3.size / 1024)}KB). Path: \`${relativeFilePath}\` \u2014 read it directly if needed._`, 5));
|
|
26766
26852
|
continue;
|
|
26767
26853
|
}
|
|
26768
|
-
const content = await
|
|
26854
|
+
const content = await file3.text();
|
|
26769
26855
|
const ext = path7.extname(relativeFilePath).slice(1) || "txt";
|
|
26770
26856
|
elements.push(createFileContext(relativeFilePath, `\`\`\`${ext}
|
|
26771
26857
|
// File: ${relativeFilePath}
|
|
@@ -26818,10 +26904,10 @@ function hookCtx(feature, opts) {
|
|
|
26818
26904
|
}
|
|
26819
26905
|
async function loadPackageContextMd(packageWorkdir) {
|
|
26820
26906
|
const contextPath = `${packageWorkdir}/.nax/context.md`;
|
|
26821
|
-
const
|
|
26822
|
-
if (!await
|
|
26907
|
+
const file3 = Bun.file(contextPath);
|
|
26908
|
+
if (!await file3.exists())
|
|
26823
26909
|
return null;
|
|
26824
|
-
return
|
|
26910
|
+
return file3.text();
|
|
26825
26911
|
}
|
|
26826
26912
|
async function buildStoryContextFull(prd, story, config2, packageWorkdir) {
|
|
26827
26913
|
try {
|
|
@@ -27073,11 +27159,11 @@ async function verifyTestWriterIsolation(workdir, beforeRef, allowedPaths = ["sr
|
|
|
27073
27159
|
const sourceFiles = changed.filter((f) => isSourceFile(f) && !isTestFile(f));
|
|
27074
27160
|
const softViolations = [];
|
|
27075
27161
|
const violations = [];
|
|
27076
|
-
for (const
|
|
27077
|
-
if (matchesAllowedPath(
|
|
27078
|
-
softViolations.push(
|
|
27162
|
+
for (const file3 of sourceFiles) {
|
|
27163
|
+
if (matchesAllowedPath(file3, allowedPaths)) {
|
|
27164
|
+
softViolations.push(file3);
|
|
27079
27165
|
} else {
|
|
27080
|
-
violations.push(
|
|
27166
|
+
violations.push(file3);
|
|
27081
27167
|
}
|
|
27082
27168
|
}
|
|
27083
27169
|
return {
|
|
@@ -27493,9 +27579,9 @@ async function verifyAssets(workingDirectory, expectedFiles) {
|
|
|
27493
27579
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
27494
27580
|
return { success: true, missingFiles: [] };
|
|
27495
27581
|
const missingFiles = [];
|
|
27496
|
-
for (const
|
|
27497
|
-
if (!existsSync16(join22(workingDirectory,
|
|
27498
|
-
missingFiles.push(
|
|
27582
|
+
for (const file3 of expectedFiles) {
|
|
27583
|
+
if (!existsSync16(join22(workingDirectory, file3)))
|
|
27584
|
+
missingFiles.push(file3);
|
|
27499
27585
|
}
|
|
27500
27586
|
if (missingFiles.length > 0) {
|
|
27501
27587
|
return {
|
|
@@ -27601,7 +27687,7 @@ function createRectificationPrompt(failures, story, config2) {
|
|
|
27601
27687
|
const maxChars = config2?.maxFailureSummaryChars ?? 2000;
|
|
27602
27688
|
const failureSummary = formatFailureSummary(failures, maxChars);
|
|
27603
27689
|
const failingFiles = Array.from(new Set(failures.map((f) => f.file)));
|
|
27604
|
-
const testCommands = failingFiles.map((
|
|
27690
|
+
const testCommands = failingFiles.map((file3) => ` bun test ${file3}`).join(`
|
|
27605
27691
|
`);
|
|
27606
27692
|
return `# Rectification Required
|
|
27607
27693
|
|
|
@@ -27648,7 +27734,7 @@ function createEscalatedRectificationPrompt(failures, story, priorAttempts, orig
|
|
|
27648
27734
|
const maxChars = config2?.maxFailureSummaryChars ?? 2000;
|
|
27649
27735
|
const failureSummary = formatFailureSummary(failures, maxChars);
|
|
27650
27736
|
const failingFiles = Array.from(new Set(failures.map((f) => f.file)));
|
|
27651
|
-
const testCommands = failingFiles.map((
|
|
27737
|
+
const testCommands = failingFiles.map((file3) => ` bun test ${file3}`).join(`
|
|
27652
27738
|
`);
|
|
27653
27739
|
const failingTestNames = failures.map((f) => f.testName);
|
|
27654
27740
|
let failingTestsSection = "";
|
|
@@ -28359,12 +28445,12 @@ async function loadOverride(role, workdir, config2) {
|
|
|
28359
28445
|
return null;
|
|
28360
28446
|
}
|
|
28361
28447
|
const absolutePath = join23(workdir, overridePath);
|
|
28362
|
-
const
|
|
28363
|
-
if (!await
|
|
28448
|
+
const file3 = Bun.file(absolutePath);
|
|
28449
|
+
if (!await file3.exists()) {
|
|
28364
28450
|
return null;
|
|
28365
28451
|
}
|
|
28366
28452
|
try {
|
|
28367
|
-
return await
|
|
28453
|
+
return await file3.text();
|
|
28368
28454
|
} catch (err) {
|
|
28369
28455
|
throw new Error(`Cannot read prompt override for role "${role}" at "${absolutePath}": ${err instanceof Error ? err.message : String(err)}`);
|
|
28370
28456
|
}
|
|
@@ -28486,9 +28572,9 @@ ${this._contextMd}
|
|
|
28486
28572
|
}
|
|
28487
28573
|
if (this._overridePath) {
|
|
28488
28574
|
try {
|
|
28489
|
-
const
|
|
28490
|
-
if (await
|
|
28491
|
-
return await
|
|
28575
|
+
const file3 = Bun.file(this._overridePath);
|
|
28576
|
+
if (await file3.exists()) {
|
|
28577
|
+
return await file3.text();
|
|
28492
28578
|
}
|
|
28493
28579
|
} catch {}
|
|
28494
28580
|
}
|
|
@@ -28824,14 +28910,14 @@ async function readVerdict(workdir) {
|
|
|
28824
28910
|
const logger = getLogger();
|
|
28825
28911
|
const verdictPath = path9.join(workdir, VERDICT_FILE);
|
|
28826
28912
|
try {
|
|
28827
|
-
const
|
|
28828
|
-
const exists = await
|
|
28913
|
+
const file3 = Bun.file(verdictPath);
|
|
28914
|
+
const exists = await file3.exists();
|
|
28829
28915
|
if (!exists) {
|
|
28830
28916
|
return null;
|
|
28831
28917
|
}
|
|
28832
28918
|
let rawText;
|
|
28833
28919
|
try {
|
|
28834
|
-
rawText = await
|
|
28920
|
+
rawText = await file3.text();
|
|
28835
28921
|
} catch (readErr) {
|
|
28836
28922
|
logger.warn("tdd", "Failed to read verifier verdict file", {
|
|
28837
28923
|
path: verdictPath,
|
|
@@ -29756,8 +29842,8 @@ async function readQueueFile(workdir) {
|
|
|
29756
29842
|
const processingPath = path10.join(workdir, ".queue.txt.processing");
|
|
29757
29843
|
const logger = getSafeLogger4();
|
|
29758
29844
|
try {
|
|
29759
|
-
const
|
|
29760
|
-
const exists = await
|
|
29845
|
+
const file3 = Bun.file(queuePath);
|
|
29846
|
+
const exists = await file3.exists();
|
|
29761
29847
|
if (!exists) {
|
|
29762
29848
|
return [];
|
|
29763
29849
|
}
|
|
@@ -29781,8 +29867,8 @@ async function clearQueueFile(workdir) {
|
|
|
29781
29867
|
const processingPath = path10.join(workdir, ".queue.txt.processing");
|
|
29782
29868
|
const logger = getSafeLogger4();
|
|
29783
29869
|
try {
|
|
29784
|
-
const
|
|
29785
|
-
const exists = await
|
|
29870
|
+
const file3 = Bun.file(processingPath);
|
|
29871
|
+
const exists = await file3.exists();
|
|
29786
29872
|
if (exists) {
|
|
29787
29873
|
await Bun.spawn(["rm", processingPath], { stdout: "pipe" }).exited;
|
|
29788
29874
|
}
|
|
@@ -30378,8 +30464,8 @@ async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
|
30378
30464
|
const testFilePaths = [];
|
|
30379
30465
|
for (const pattern of testFilePatterns) {
|
|
30380
30466
|
const glob = _bunDeps.glob(pattern);
|
|
30381
|
-
for await (const
|
|
30382
|
-
testFilePaths.push(`${workdir}/${
|
|
30467
|
+
for await (const file3 of glob.scan(workdir)) {
|
|
30468
|
+
testFilePaths.push(`${workdir}/${file3}`);
|
|
30383
30469
|
}
|
|
30384
30470
|
}
|
|
30385
30471
|
const matched = [];
|
|
@@ -31289,8 +31375,8 @@ function generateContextTemplate(scan) {
|
|
|
31289
31375
|
lines.push(`## Project Structure
|
|
31290
31376
|
`);
|
|
31291
31377
|
lines.push("```");
|
|
31292
|
-
for (const
|
|
31293
|
-
lines.push(
|
|
31378
|
+
for (const file3 of scan.fileTree.slice(0, 20)) {
|
|
31379
|
+
lines.push(file3);
|
|
31294
31380
|
}
|
|
31295
31381
|
if (scan.fileTree.length > 20) {
|
|
31296
31382
|
lines.push(`... and ${scan.fileTree.length - 20} more files`);
|
|
@@ -32109,8 +32195,8 @@ async function checkStaleLock(workdir) {
|
|
|
32109
32195
|
};
|
|
32110
32196
|
}
|
|
32111
32197
|
try {
|
|
32112
|
-
const
|
|
32113
|
-
const content = await
|
|
32198
|
+
const file3 = Bun.file(lockPath);
|
|
32199
|
+
const content = await file3.text();
|
|
32114
32200
|
const lockData = JSON.parse(content);
|
|
32115
32201
|
let lockTimeMs;
|
|
32116
32202
|
if (lockData.timestamp) {
|
|
@@ -32396,8 +32482,8 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
32396
32482
|
message: ".gitignore not found"
|
|
32397
32483
|
};
|
|
32398
32484
|
}
|
|
32399
|
-
const
|
|
32400
|
-
const content = await
|
|
32485
|
+
const file3 = Bun.file(gitignorePath);
|
|
32486
|
+
const content = await file3.text();
|
|
32401
32487
|
const patterns = [
|
|
32402
32488
|
"nax.lock",
|
|
32403
32489
|
".nax/**/runs/",
|
|
@@ -33081,14 +33167,14 @@ async function fireHook(config2, event, ctx, workdir) {
|
|
|
33081
33167
|
}
|
|
33082
33168
|
}
|
|
33083
33169
|
var DEFAULT_TIMEOUT = 5000;
|
|
33084
|
-
var
|
|
33170
|
+
var init_runner4 = __esm(() => {
|
|
33085
33171
|
init_logger2();
|
|
33086
33172
|
init_json_file();
|
|
33087
33173
|
});
|
|
33088
33174
|
|
|
33089
33175
|
// src/hooks/index.ts
|
|
33090
33176
|
var init_hooks = __esm(() => {
|
|
33091
|
-
|
|
33177
|
+
init_runner4();
|
|
33092
33178
|
});
|
|
33093
33179
|
|
|
33094
33180
|
// src/execution/crash-heartbeat.ts
|
|
@@ -33982,7 +34068,7 @@ async function handleRunCompletion(options) {
|
|
|
33982
34068
|
}
|
|
33983
34069
|
var _runCompletionDeps;
|
|
33984
34070
|
var init_run_completion = __esm(() => {
|
|
33985
|
-
|
|
34071
|
+
init_runner4();
|
|
33986
34072
|
init_logger2();
|
|
33987
34073
|
init_metrics();
|
|
33988
34074
|
init_event_bus();
|
|
@@ -35101,13 +35187,23 @@ __export(exports_parallel_worker, {
|
|
|
35101
35187
|
async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
|
|
35102
35188
|
const logger = getSafeLogger();
|
|
35103
35189
|
try {
|
|
35190
|
+
let storyGitRef;
|
|
35191
|
+
if (story.storyGitRef && await isGitRefValid(worktreePath, story.storyGitRef)) {
|
|
35192
|
+
storyGitRef = story.storyGitRef;
|
|
35193
|
+
} else {
|
|
35194
|
+
storyGitRef = await captureGitRef(worktreePath);
|
|
35195
|
+
if (storyGitRef) {
|
|
35196
|
+
story.storyGitRef = storyGitRef;
|
|
35197
|
+
}
|
|
35198
|
+
}
|
|
35104
35199
|
const pipelineContext = {
|
|
35105
35200
|
...context,
|
|
35106
35201
|
effectiveConfig: context.effectiveConfig ?? context.config,
|
|
35107
35202
|
story,
|
|
35108
35203
|
stories: [story],
|
|
35109
35204
|
workdir: worktreePath,
|
|
35110
|
-
routing
|
|
35205
|
+
routing,
|
|
35206
|
+
storyGitRef: storyGitRef ?? undefined
|
|
35111
35207
|
};
|
|
35112
35208
|
logger?.debug("parallel", "Executing story in worktree", {
|
|
35113
35209
|
storyId: story.id,
|
|
@@ -35183,6 +35279,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
35183
35279
|
init_runner();
|
|
35184
35280
|
init_stages();
|
|
35185
35281
|
init_routing();
|
|
35282
|
+
init_git();
|
|
35186
35283
|
});
|
|
35187
35284
|
|
|
35188
35285
|
// src/worktree/manager.ts
|
|
@@ -35613,6 +35710,16 @@ __export(exports_merge_conflict_rectify, {
|
|
|
35613
35710
|
rectifyConflictedStory: () => rectifyConflictedStory
|
|
35614
35711
|
});
|
|
35615
35712
|
import path15 from "path";
|
|
35713
|
+
async function closeStaleAcpSession(worktreePath, sessionName) {
|
|
35714
|
+
const logger = getSafeLogger();
|
|
35715
|
+
try {
|
|
35716
|
+
const { typedSpawn: typedSpawn2 } = await Promise.resolve().then(() => (init_bun_deps(), exports_bun_deps));
|
|
35717
|
+
const cmd = ["acpx", "--cwd", worktreePath, "claude", "sessions", "close", sessionName];
|
|
35718
|
+
logger?.debug("parallel", "Closing stale ACP session before rectification", { sessionName });
|
|
35719
|
+
const proc = typedSpawn2(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
35720
|
+
await proc.exited;
|
|
35721
|
+
} catch {}
|
|
35722
|
+
}
|
|
35616
35723
|
async function rectifyConflictedStory(options) {
|
|
35617
35724
|
const { storyId, workdir, config: config2, hooks, pluginRegistry, prd, eventEmitter, agentGetFn } = options;
|
|
35618
35725
|
const logger = getSafeLogger();
|
|
@@ -35630,6 +35737,9 @@ async function rectifyConflictedStory(options) {
|
|
|
35630
35737
|
} catch {}
|
|
35631
35738
|
await worktreeManager.create(workdir, storyId);
|
|
35632
35739
|
const worktreePath = path15.join(workdir, ".nax-wt", storyId);
|
|
35740
|
+
const { buildSessionName: buildSessionName2 } = await Promise.resolve().then(() => (init_adapter2(), exports_adapter));
|
|
35741
|
+
const staleSessionName = buildSessionName2(worktreePath, prd.feature, storyId);
|
|
35742
|
+
await closeStaleAcpSession(worktreePath, staleSessionName);
|
|
35633
35743
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
35634
35744
|
if (!story) {
|
|
35635
35745
|
return { success: false, storyId, cost: 0, finalConflict: false, pipelineFailure: true };
|
|
@@ -35707,9 +35817,47 @@ async function runParallelBatch(options) {
|
|
|
35707
35817
|
}
|
|
35708
35818
|
worktreePaths.set(story.id, path16.join(workdir, ".nax-wt", story.id));
|
|
35709
35819
|
}
|
|
35710
|
-
const
|
|
35820
|
+
const rootConfigPath = path16.join(workdir, ".nax", "config.json");
|
|
35821
|
+
const storyEffectiveConfigs = new Map;
|
|
35822
|
+
for (const story of stories) {
|
|
35823
|
+
if (story.workdir) {
|
|
35824
|
+
const effectiveConfig = await loadConfigForWorkdir(rootConfigPath, story.workdir);
|
|
35825
|
+
storyEffectiveConfigs.set(story.id, effectiveConfig);
|
|
35826
|
+
}
|
|
35827
|
+
}
|
|
35828
|
+
const workerResult = await _parallelBatchDeps.executeParallelBatch(stories, workdir, config2, pipelineContext, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs.size > 0 ? storyEffectiveConfigs : undefined);
|
|
35711
35829
|
const batchEndMs = Date.now();
|
|
35712
|
-
const completed =
|
|
35830
|
+
const completed = [];
|
|
35831
|
+
if (workerResult.pipelinePassed.length > 0) {
|
|
35832
|
+
const mergeEngine = await _parallelBatchDeps.createMergeEngine(worktreeManager);
|
|
35833
|
+
const successfulIds = workerResult.pipelinePassed.map((s) => s.id);
|
|
35834
|
+
const deps = {};
|
|
35835
|
+
for (const s of stories)
|
|
35836
|
+
deps[s.id] = s.dependencies ?? [];
|
|
35837
|
+
const mergeResults = await mergeEngine.mergeAll(workdir, successfulIds, deps);
|
|
35838
|
+
for (const mergeResult of mergeResults) {
|
|
35839
|
+
const story = workerResult.pipelinePassed.find((s) => s.id === mergeResult.storyId);
|
|
35840
|
+
if (!story)
|
|
35841
|
+
continue;
|
|
35842
|
+
if (mergeResult.success) {
|
|
35843
|
+
completed.push(story);
|
|
35844
|
+
workerResult.merged.push(story);
|
|
35845
|
+
logger?.info("parallel-batch", "Story merged successfully", {
|
|
35846
|
+
storyId: mergeResult.storyId
|
|
35847
|
+
});
|
|
35848
|
+
} else {
|
|
35849
|
+
workerResult.mergeConflicts.push({
|
|
35850
|
+
storyId: mergeResult.storyId,
|
|
35851
|
+
conflictFiles: mergeResult.conflictFiles || [],
|
|
35852
|
+
originalCost: workerResult.storyCosts.get(mergeResult.storyId) ?? 0
|
|
35853
|
+
});
|
|
35854
|
+
logger?.warn("parallel-batch", "Merge conflict \u2014 will attempt rectification", {
|
|
35855
|
+
storyId: mergeResult.storyId,
|
|
35856
|
+
conflictFiles: mergeResult.conflictFiles
|
|
35857
|
+
});
|
|
35858
|
+
}
|
|
35859
|
+
}
|
|
35860
|
+
}
|
|
35713
35861
|
const failed = workerResult.failed.map((f) => ({
|
|
35714
35862
|
story: f.story,
|
|
35715
35863
|
pipelineResult: f.pipelineResult ?? {
|
|
@@ -35767,11 +35915,12 @@ async function runParallelBatch(options) {
|
|
|
35767
35915
|
}
|
|
35768
35916
|
var _parallelBatchDeps;
|
|
35769
35917
|
var init_parallel_batch = __esm(() => {
|
|
35918
|
+
init_loader();
|
|
35770
35919
|
init_logger2();
|
|
35771
35920
|
_parallelBatchDeps = {
|
|
35772
|
-
executeParallelBatch: async (_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter) => {
|
|
35921
|
+
executeParallelBatch: async (_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter, _storyEffectiveConfigs) => {
|
|
35773
35922
|
const { executeParallelBatch: executeParallelBatch2 } = await Promise.resolve().then(() => (init_parallel_worker(), exports_parallel_worker));
|
|
35774
|
-
return executeParallelBatch2(_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter);
|
|
35923
|
+
return executeParallelBatch2(_stories, _projectRoot, _config, _context, _worktreePaths, _maxConcurrency, _eventEmitter, _storyEffectiveConfigs);
|
|
35775
35924
|
},
|
|
35776
35925
|
createWorktreeManager: async () => {
|
|
35777
35926
|
const { WorktreeManager: WorktreeManager2 } = await Promise.resolve().then(() => (init_manager(), exports_manager));
|
|
@@ -36260,15 +36409,15 @@ var _detectorDeps, WEB_DEPS, API_DEPS;
|
|
|
36260
36409
|
var init_detector = __esm(() => {
|
|
36261
36410
|
_detectorDeps = {
|
|
36262
36411
|
async fileExists(path17) {
|
|
36263
|
-
const
|
|
36264
|
-
return
|
|
36412
|
+
const file3 = Bun.file(path17);
|
|
36413
|
+
return file3.exists();
|
|
36265
36414
|
},
|
|
36266
36415
|
async readJson(path17) {
|
|
36267
36416
|
try {
|
|
36268
|
-
const
|
|
36269
|
-
if (!await
|
|
36417
|
+
const file3 = Bun.file(path17);
|
|
36418
|
+
if (!await file3.exists())
|
|
36270
36419
|
return null;
|
|
36271
|
-
const text = await
|
|
36420
|
+
const text = await file3.text();
|
|
36272
36421
|
return JSON.parse(text);
|
|
36273
36422
|
} catch {
|
|
36274
36423
|
return null;
|
|
@@ -36723,7 +36872,7 @@ var init_run_initialization = __esm(() => {
|
|
|
36723
36872
|
init_errors3();
|
|
36724
36873
|
init_logger2();
|
|
36725
36874
|
init_prd();
|
|
36726
|
-
|
|
36875
|
+
init_runner3();
|
|
36727
36876
|
init_git();
|
|
36728
36877
|
_reconcileDeps = {
|
|
36729
36878
|
getAgent,
|
|
@@ -65896,7 +66045,7 @@ var require_stack_utils = __commonJS((exports, module) => {
|
|
|
65896
66045
|
const evalFile = match[4];
|
|
65897
66046
|
const evalLine = Number(match[5]);
|
|
65898
66047
|
const evalCol = Number(match[6]);
|
|
65899
|
-
let
|
|
66048
|
+
let file3 = match[7];
|
|
65900
66049
|
const lnum = match[8];
|
|
65901
66050
|
const col = match[9];
|
|
65902
66051
|
const native = match[10] === "native";
|
|
@@ -65909,17 +66058,17 @@ var require_stack_utils = __commonJS((exports, module) => {
|
|
|
65909
66058
|
if (col) {
|
|
65910
66059
|
res.column = Number(col);
|
|
65911
66060
|
}
|
|
65912
|
-
if (closeParen &&
|
|
66061
|
+
if (closeParen && file3) {
|
|
65913
66062
|
let closes = 0;
|
|
65914
|
-
for (let i =
|
|
65915
|
-
if (
|
|
66063
|
+
for (let i = file3.length - 1;i > 0; i--) {
|
|
66064
|
+
if (file3.charAt(i) === ")") {
|
|
65916
66065
|
closes++;
|
|
65917
|
-
} else if (
|
|
66066
|
+
} else if (file3.charAt(i) === "(" && file3.charAt(i - 1) === " ") {
|
|
65918
66067
|
closes--;
|
|
65919
|
-
if (closes === -1 &&
|
|
65920
|
-
const before2 =
|
|
65921
|
-
const after2 =
|
|
65922
|
-
|
|
66068
|
+
if (closes === -1 && file3.charAt(i - 1) === " ") {
|
|
66069
|
+
const before2 = file3.slice(0, i - 1);
|
|
66070
|
+
const after2 = file3.slice(i + 1);
|
|
66071
|
+
file3 = after2;
|
|
65923
66072
|
fname += ` (${before2}`;
|
|
65924
66073
|
break;
|
|
65925
66074
|
}
|
|
@@ -65933,7 +66082,7 @@ var require_stack_utils = __commonJS((exports, module) => {
|
|
|
65933
66082
|
method2 = methodMatch[2];
|
|
65934
66083
|
}
|
|
65935
66084
|
}
|
|
65936
|
-
setFile(res,
|
|
66085
|
+
setFile(res, file3, this._cwd);
|
|
65937
66086
|
if (ctor) {
|
|
65938
66087
|
Object.defineProperty(res, "constructor", {
|
|
65939
66088
|
value: true,
|
|
@@ -68377,8 +68526,8 @@ async function detectNode(workdir) {
|
|
|
68377
68526
|
if (!existsSync9(pkgPath))
|
|
68378
68527
|
return null;
|
|
68379
68528
|
try {
|
|
68380
|
-
const
|
|
68381
|
-
const pkg = await
|
|
68529
|
+
const file3 = Bun.file(pkgPath);
|
|
68530
|
+
const pkg = await file3.json();
|
|
68382
68531
|
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
68383
68532
|
const notable = [
|
|
68384
68533
|
...new Set(Object.keys(allDeps).filter((dep) => NOTABLE_NODE_DEPS.some((kw) => dep === kw || dep.startsWith(`${kw}/`) || dep.includes(kw))))
|
|
@@ -68454,8 +68603,8 @@ async function detectPhp(workdir) {
|
|
|
68454
68603
|
if (!existsSync9(composerPath))
|
|
68455
68604
|
return null;
|
|
68456
68605
|
try {
|
|
68457
|
-
const
|
|
68458
|
-
const composer = await
|
|
68606
|
+
const file3 = Bun.file(composerPath);
|
|
68607
|
+
const composer = await file3.json();
|
|
68459
68608
|
const deps = Object.keys({ ...composer.require ?? {}, ...composer["require-dev"] ?? {} }).filter((d) => d !== "php").map((d) => d.split("/").pop() ?? d).slice(0, 10);
|
|
68460
68609
|
return { name: composer.name, lang: "PHP", dependencies: deps };
|
|
68461
68610
|
} catch {
|
|
@@ -69970,16 +70119,16 @@ async function runsListCommand(options) {
|
|
|
69970
70119
|
return;
|
|
69971
70120
|
}
|
|
69972
70121
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
69973
|
-
for (const
|
|
69974
|
-
const logPath = join15(runsDir,
|
|
70122
|
+
for (const file3 of files.sort().reverse()) {
|
|
70123
|
+
const logPath = join15(runsDir, file3);
|
|
69975
70124
|
const entries = await parseRunLog(logPath);
|
|
69976
70125
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
69977
70126
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
69978
70127
|
if (!startEvent) {
|
|
69979
|
-
logger.warn("cli", "Run log missing run.start event", { file:
|
|
70128
|
+
logger.warn("cli", "Run log missing run.start event", { file: file3 });
|
|
69980
70129
|
continue;
|
|
69981
70130
|
}
|
|
69982
|
-
const runId = startEvent.data?.runId ||
|
|
70131
|
+
const runId = startEvent.data?.runId || file3.replace(".jsonl", "");
|
|
69983
70132
|
const startedAt = startEvent.timestamp;
|
|
69984
70133
|
const status = completeEvent ? "completed" : "in-progress";
|
|
69985
70134
|
const totalCost = completeEvent?.data?.totalCost || 0;
|
|
@@ -71500,8 +71649,8 @@ async function selectRunFile(runsDir) {
|
|
|
71500
71649
|
return join39(runsDir, files[0]);
|
|
71501
71650
|
}
|
|
71502
71651
|
async function extractRunSummary(filePath) {
|
|
71503
|
-
const
|
|
71504
|
-
const content = await
|
|
71652
|
+
const file3 = Bun.file(filePath);
|
|
71653
|
+
const content = await file3.text();
|
|
71505
71654
|
const lines = content.trim().split(`
|
|
71506
71655
|
`);
|
|
71507
71656
|
let total = 0;
|
|
@@ -71581,10 +71730,10 @@ Runs:
|
|
|
71581
71730
|
`));
|
|
71582
71731
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
71583
71732
|
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"));
|
|
71584
|
-
for (const
|
|
71585
|
-
const filePath = join40(runsDir,
|
|
71733
|
+
for (const file3 of files) {
|
|
71734
|
+
const filePath = join40(runsDir, file3);
|
|
71586
71735
|
const summary = await extractRunSummary(filePath);
|
|
71587
|
-
const timestamp =
|
|
71736
|
+
const timestamp = file3.replace(".jsonl", "");
|
|
71588
71737
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
71589
71738
|
const duration3 = summary ? formatDuration(summary.durationMs) : "?";
|
|
71590
71739
|
const cost = summary ? `$${summary.totalCost.toFixed(4)}` : "$?.????";
|
|
@@ -71594,8 +71743,8 @@ Runs:
|
|
|
71594
71743
|
console.log();
|
|
71595
71744
|
}
|
|
71596
71745
|
async function displayLogs(filePath, options) {
|
|
71597
|
-
const
|
|
71598
|
-
const content = await
|
|
71746
|
+
const file3 = Bun.file(filePath);
|
|
71747
|
+
const content = await file3.text();
|
|
71599
71748
|
const lines = content.trim().split(`
|
|
71600
71749
|
`);
|
|
71601
71750
|
const mode = options.json ? "json" : "normal";
|
|
@@ -71622,8 +71771,8 @@ async function displayLogs(filePath, options) {
|
|
|
71622
71771
|
}
|
|
71623
71772
|
async function followLogs(filePath, options) {
|
|
71624
71773
|
const mode = options.json ? "json" : "normal";
|
|
71625
|
-
const
|
|
71626
|
-
const content = await
|
|
71774
|
+
const file3 = Bun.file(filePath);
|
|
71775
|
+
const content = await file3.text();
|
|
71627
71776
|
const lines = content.trim().split(`
|
|
71628
71777
|
`);
|
|
71629
71778
|
for (const line of lines) {
|
|
@@ -78552,8 +78701,8 @@ async function writeQueueCommand(queueFilePath, command) {
|
|
|
78552
78701
|
throw new Error(`Unhandled queue command: ${_exhaustive}`);
|
|
78553
78702
|
}
|
|
78554
78703
|
}
|
|
78555
|
-
const
|
|
78556
|
-
const existingContent = await
|
|
78704
|
+
const file3 = Bun.file(queueFilePath);
|
|
78705
|
+
const existingContent = await file3.text().catch(() => "");
|
|
78557
78706
|
const newContent = existingContent ? `${existingContent.trimEnd()}
|
|
78558
78707
|
${commandLine}
|
|
78559
78708
|
` : `${commandLine}
|