@nathapp/nax 0.81.0-canary.1 → 0.81.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 +225 -269
- package/package.json +3 -1
package/dist/nax.js
CHANGED
|
@@ -17148,8 +17148,7 @@ var init_schemas_infra = __esm(() => {
|
|
|
17148
17148
|
agents: AgentRoutingConfigSchema.default({ enabled: true, strategy: "off", profiles: [] })
|
|
17149
17149
|
});
|
|
17150
17150
|
OptimizerConfigSchema = exports_external.object({
|
|
17151
|
-
enabled: exports_external.boolean()
|
|
17152
|
-
strategy: exports_external.enum(["rule-based", "llm", "noop"]).optional()
|
|
17151
|
+
enabled: exports_external.boolean()
|
|
17153
17152
|
});
|
|
17154
17153
|
PluginConfigEntrySchema = exports_external.object({
|
|
17155
17154
|
module: exports_external.string().min(1, "plugin.module must be non-empty"),
|
|
@@ -18840,6 +18839,21 @@ function _applyRemovedRoutingKeysShim(conf, warn = defaultConfigWarn) {
|
|
|
18840
18839
|
}
|
|
18841
18840
|
return newRouting === routing ? conf : { ...conf, routing: newRouting };
|
|
18842
18841
|
}
|
|
18842
|
+
function _applyRemovedOptimizerKeysShim(conf, warn = defaultConfigWarn) {
|
|
18843
|
+
const optimizer = conf.optimizer;
|
|
18844
|
+
if (!optimizer || typeof optimizer !== "object")
|
|
18845
|
+
return conf;
|
|
18846
|
+
const REMOVED_OPTIMIZER_KEYS = ["strategy", "strategies"];
|
|
18847
|
+
let newOptimizer = optimizer;
|
|
18848
|
+
for (const key of REMOVED_OPTIMIZER_KEYS) {
|
|
18849
|
+
if (key in newOptimizer) {
|
|
18850
|
+
warn(`optimizer.${key} was removed along with the rule-based optimizer and has no effect. Remove it from your config; set optimizer.enabled and provide a plugin optimizer instead.`);
|
|
18851
|
+
const { [key]: _removed, ...rest } = newOptimizer;
|
|
18852
|
+
newOptimizer = rest;
|
|
18853
|
+
}
|
|
18854
|
+
}
|
|
18855
|
+
return newOptimizer === optimizer ? conf : { ...conf, optimizer: newOptimizer };
|
|
18856
|
+
}
|
|
18843
18857
|
function _applyRemovedWorktreeInheritShim(conf, warn = defaultConfigWarn) {
|
|
18844
18858
|
const execution = conf.execution;
|
|
18845
18859
|
const worktreeDependencies = execution?.worktreeDependencies;
|
|
@@ -18968,7 +18982,17 @@ function _applyFinishAutoFlowShim(conf, warn = defaultConfigWarn) {
|
|
|
18968
18982
|
function applyConfigCompatShims(conf, logger, dedupe) {
|
|
18969
18983
|
const log = dedupe.wrapLogger(logger);
|
|
18970
18984
|
const warn = dedupe.warn;
|
|
18971
|
-
|
|
18985
|
+
let out = migrateLegacyTestPattern(conf, log);
|
|
18986
|
+
out = migrateLegacyReviewModelKey(out, log);
|
|
18987
|
+
out = applyRemovedStrategyCompat(out, warn);
|
|
18988
|
+
out = applyBatchModeCompat(out, warn);
|
|
18989
|
+
out = applyRoutingRetryDeprecationWarning(out, warn);
|
|
18990
|
+
out = _applyRemovedRoutingKeysShim(out, warn);
|
|
18991
|
+
out = _applyLegacyReviewExecutionShim(out, warn);
|
|
18992
|
+
out = _applyRemovedWorktreeInheritShim(out, warn);
|
|
18993
|
+
out = _applyFinishAutoFlowShim(out, warn);
|
|
18994
|
+
out = _applyRemovedOptimizerKeysShim(out, warn);
|
|
18995
|
+
return out;
|
|
18972
18996
|
}
|
|
18973
18997
|
var SECURITY_SENSITIVE_KEY_PATHS, REMOVED_FINISH_KEYS;
|
|
18974
18998
|
var init_compat_shims = __esm(() => {
|
|
@@ -29969,186 +29993,20 @@ class NoopOptimizer {
|
|
|
29969
29993
|
}
|
|
29970
29994
|
var init_noop_optimizer = () => {};
|
|
29971
29995
|
|
|
29972
|
-
// src/optimizer/rule-based.optimizer.ts
|
|
29973
|
-
class RuleBasedOptimizer {
|
|
29974
|
-
name = "rule-based";
|
|
29975
|
-
async optimize(input) {
|
|
29976
|
-
const originalTokens = estimateTokens2(input.prompt);
|
|
29977
|
-
const appliedRules = [];
|
|
29978
|
-
let optimized = input.prompt;
|
|
29979
|
-
const config2 = {
|
|
29980
|
-
...DEFAULT_CONFIG2,
|
|
29981
|
-
...input.config.optimizer?.strategies?.["rule-based"]
|
|
29982
|
-
};
|
|
29983
|
-
if (config2.stripWhitespace) {
|
|
29984
|
-
const before = optimized;
|
|
29985
|
-
optimized = this.stripWhitespace(optimized);
|
|
29986
|
-
if (optimized !== before) {
|
|
29987
|
-
appliedRules.push("stripWhitespace");
|
|
29988
|
-
}
|
|
29989
|
-
}
|
|
29990
|
-
if (config2.compactCriteria) {
|
|
29991
|
-
const before = optimized;
|
|
29992
|
-
optimized = this.compactCriteria(optimized);
|
|
29993
|
-
if (optimized !== before) {
|
|
29994
|
-
appliedRules.push("compactCriteria");
|
|
29995
|
-
}
|
|
29996
|
-
}
|
|
29997
|
-
if (config2.deduplicateContext && input.contextMarkdown) {
|
|
29998
|
-
const before = optimized;
|
|
29999
|
-
optimized = this.deduplicateContext(optimized, input.contextMarkdown);
|
|
30000
|
-
if (optimized !== before) {
|
|
30001
|
-
appliedRules.push("deduplicateContext");
|
|
30002
|
-
}
|
|
30003
|
-
}
|
|
30004
|
-
if (config2.maxPromptTokens) {
|
|
30005
|
-
const currentTokens = estimateTokens2(optimized);
|
|
30006
|
-
if (currentTokens > config2.maxPromptTokens) {
|
|
30007
|
-
optimized = this.trimToMaxTokens(optimized, config2.maxPromptTokens);
|
|
30008
|
-
appliedRules.push("maxPromptTokens");
|
|
30009
|
-
}
|
|
30010
|
-
}
|
|
30011
|
-
const optimizedTokens = estimateTokens2(optimized);
|
|
30012
|
-
const savings = originalTokens > 0 ? (originalTokens - optimizedTokens) / originalTokens : 0;
|
|
30013
|
-
return {
|
|
30014
|
-
prompt: optimized,
|
|
30015
|
-
originalTokens,
|
|
30016
|
-
optimizedTokens,
|
|
30017
|
-
savings,
|
|
30018
|
-
appliedRules
|
|
30019
|
-
};
|
|
30020
|
-
}
|
|
30021
|
-
stripWhitespace(prompt) {
|
|
30022
|
-
return prompt.split(`
|
|
30023
|
-
`).map((line) => line.trimEnd()).join(`
|
|
30024
|
-
`).replace(/\n{3,}/g, `
|
|
30025
|
-
|
|
30026
|
-
`);
|
|
30027
|
-
}
|
|
30028
|
-
compactCriteria(prompt) {
|
|
30029
|
-
return prompt.replace(/The user should be able to /gi, "").replace(/The system must /gi, "").replace(/The system should /gi, "").replace(/When the /gi, "").replace(/When a /gi, "").replace(/it should validate all fields/gi, "validate all fields").replace(/display an error message/gi, "show error").replace(/error message/gi, "error");
|
|
30030
|
-
}
|
|
30031
|
-
deduplicateContext(prompt, contextMarkdown) {
|
|
30032
|
-
const contextSectionMatch = prompt.match(/# Context\n([\s\S]*?)(?=\n#|$)/i);
|
|
30033
|
-
if (!contextSectionMatch) {
|
|
30034
|
-
return prompt;
|
|
30035
|
-
}
|
|
30036
|
-
const contextSection = contextSectionMatch[1];
|
|
30037
|
-
const contextLines = contextSection.split(`
|
|
30038
|
-
`);
|
|
30039
|
-
const dedupedLines = contextLines.filter((line) => {
|
|
30040
|
-
const trimmed = line.trim();
|
|
30041
|
-
if (!trimmed)
|
|
30042
|
-
return true;
|
|
30043
|
-
return !contextMarkdown.includes(trimmed);
|
|
30044
|
-
});
|
|
30045
|
-
if (dedupedLines.length < contextLines.length) {
|
|
30046
|
-
const newContextSection = dedupedLines.join(`
|
|
30047
|
-
`);
|
|
30048
|
-
return prompt.replace(contextSectionMatch[0], `# Context
|
|
30049
|
-
${newContextSection}`);
|
|
30050
|
-
}
|
|
30051
|
-
return prompt;
|
|
30052
|
-
}
|
|
30053
|
-
trimToMaxTokens(prompt, maxTokens) {
|
|
30054
|
-
const currentTokens = estimateTokens2(prompt);
|
|
30055
|
-
if (currentTokens <= maxTokens) {
|
|
30056
|
-
return prompt;
|
|
30057
|
-
}
|
|
30058
|
-
const sections = this.extractSections(prompt);
|
|
30059
|
-
const targetChars = maxTokens * 4;
|
|
30060
|
-
const trimmedMessage = `
|
|
30061
|
-
... (context trimmed)`;
|
|
30062
|
-
let result = "";
|
|
30063
|
-
let remainingChars = targetChars;
|
|
30064
|
-
if (sections.task) {
|
|
30065
|
-
result += sections.task;
|
|
30066
|
-
remainingChars -= sections.task.length;
|
|
30067
|
-
}
|
|
30068
|
-
if (sections.acceptanceCriteria) {
|
|
30069
|
-
result += sections.acceptanceCriteria;
|
|
30070
|
-
remainingChars -= sections.acceptanceCriteria.length;
|
|
30071
|
-
}
|
|
30072
|
-
const trimmable = sections.context ?? sections.other;
|
|
30073
|
-
if (trimmable && remainingChars > 0) {
|
|
30074
|
-
const reserveForMessage = trimmable.length > remainingChars ? trimmedMessage.length : 0;
|
|
30075
|
-
const maxContextChars = Math.max(0, remainingChars - reserveForMessage);
|
|
30076
|
-
const trimmedContext = trimmable.substring(0, maxContextChars);
|
|
30077
|
-
result += trimmedContext;
|
|
30078
|
-
if (trimmedContext.length < trimmable.length) {
|
|
30079
|
-
result += trimmedMessage;
|
|
30080
|
-
}
|
|
30081
|
-
}
|
|
30082
|
-
return result.trim() ? result : prompt;
|
|
30083
|
-
}
|
|
30084
|
-
extractSections(prompt) {
|
|
30085
|
-
const sections = {};
|
|
30086
|
-
const taskMatch = prompt.match(/# Task\n([\s\S]*?)(?=\n#|$)/i);
|
|
30087
|
-
if (taskMatch) {
|
|
30088
|
-
sections.task = taskMatch[0];
|
|
30089
|
-
}
|
|
30090
|
-
const contextMatch = prompt.match(/# Context\n([\s\S]*?)(?=\n#|$)/i);
|
|
30091
|
-
if (contextMatch) {
|
|
30092
|
-
sections.context = contextMatch[0];
|
|
30093
|
-
}
|
|
30094
|
-
const acMatch = prompt.match(/# Acceptance Criteria\n([\s\S]*?)(?=\n#|$)/i);
|
|
30095
|
-
if (acMatch) {
|
|
30096
|
-
sections.acceptanceCriteria = acMatch[0];
|
|
30097
|
-
}
|
|
30098
|
-
let other = prompt;
|
|
30099
|
-
if (sections.task) {
|
|
30100
|
-
other = other.replace(sections.task, "");
|
|
30101
|
-
}
|
|
30102
|
-
if (sections.context) {
|
|
30103
|
-
other = other.replace(sections.context, "");
|
|
30104
|
-
}
|
|
30105
|
-
if (sections.acceptanceCriteria) {
|
|
30106
|
-
other = other.replace(sections.acceptanceCriteria, "");
|
|
30107
|
-
}
|
|
30108
|
-
if (other.trim()) {
|
|
30109
|
-
sections.other = other;
|
|
30110
|
-
}
|
|
30111
|
-
return sections;
|
|
30112
|
-
}
|
|
30113
|
-
}
|
|
30114
|
-
var DEFAULT_CONFIG2;
|
|
30115
|
-
var init_rule_based_optimizer = __esm(() => {
|
|
30116
|
-
DEFAULT_CONFIG2 = {
|
|
30117
|
-
stripWhitespace: true,
|
|
30118
|
-
compactCriteria: true,
|
|
30119
|
-
deduplicateContext: true,
|
|
30120
|
-
maxPromptTokens: 8000
|
|
30121
|
-
};
|
|
30122
|
-
});
|
|
30123
|
-
|
|
30124
29996
|
// src/optimizer/index.ts
|
|
30125
29997
|
function resolveOptimizer(config2, pluginRegistry) {
|
|
30126
29998
|
if (!config2.optimizer?.enabled) {
|
|
30127
29999
|
return new NoopOptimizer;
|
|
30128
30000
|
}
|
|
30129
|
-
|
|
30130
|
-
|
|
30131
|
-
|
|
30132
|
-
return pluginOptimizers[0];
|
|
30133
|
-
}
|
|
30134
|
-
}
|
|
30135
|
-
const strategy = config2.optimizer.strategy ?? "noop";
|
|
30136
|
-
switch (strategy) {
|
|
30137
|
-
case "rule-based":
|
|
30138
|
-
return new RuleBasedOptimizer;
|
|
30139
|
-
case "noop":
|
|
30140
|
-
return new NoopOptimizer;
|
|
30141
|
-
default:
|
|
30142
|
-
getSafeLogger()?.warn("optimizer", `Unknown optimizer strategy '${strategy}', using noop`);
|
|
30143
|
-
return new NoopOptimizer;
|
|
30001
|
+
const pluginOptimizers = pluginRegistry?.getOptimizers() ?? [];
|
|
30002
|
+
if (pluginOptimizers.length > 0) {
|
|
30003
|
+
return pluginOptimizers[0];
|
|
30144
30004
|
}
|
|
30005
|
+
return new NoopOptimizer;
|
|
30145
30006
|
}
|
|
30146
30007
|
var init_optimizer = __esm(() => {
|
|
30147
30008
|
init_noop_optimizer();
|
|
30148
|
-
init_rule_based_optimizer();
|
|
30149
|
-
init_logger2();
|
|
30150
30009
|
init_noop_optimizer();
|
|
30151
|
-
init_rule_based_optimizer();
|
|
30152
30010
|
});
|
|
30153
30011
|
|
|
30154
30012
|
// src/context/fragments/store.ts
|
|
@@ -33625,24 +33483,23 @@ async function saveRunMetrics(outputDir, runMetrics) {
|
|
|
33625
33483
|
cacheCreationInputTokens: totalCacheCreationInputTokens
|
|
33626
33484
|
})
|
|
33627
33485
|
} : runMetrics;
|
|
33628
|
-
|
|
33486
|
+
await withPathFileLock(metricsPath, async () => {
|
|
33629
33487
|
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
33630
|
-
const
|
|
33631
|
-
|
|
33632
|
-
|
|
33488
|
+
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
33489
|
+
allMetrics.push(finalMetrics);
|
|
33490
|
+
const isTruncating = allMetrics.length > MAX_RETAINED_RUNS;
|
|
33491
|
+
const cappedMetrics = isTruncating ? allMetrics.slice(allMetrics.length - MAX_RETAINED_RUNS) : allMetrics;
|
|
33492
|
+
if (isTruncating && !hasWarnedAboutRunTruncation) {
|
|
33493
|
+
hasWarnedAboutRunTruncation = true;
|
|
33494
|
+
const droppedCount = allMetrics.length - MAX_RETAINED_RUNS;
|
|
33495
|
+
getLogger().warn("metrics", "Run-history cap reached \u2014 oldest run-entries dropped from metrics.json; aggregate metrics now cover only the retained window", {
|
|
33496
|
+
droppedCount,
|
|
33497
|
+
maxRetainedRuns: MAX_RETAINED_RUNS,
|
|
33498
|
+
metricsPath
|
|
33499
|
+
});
|
|
33500
|
+
}
|
|
33501
|
+
await saveJsonFile(metricsPath, cappedMetrics, "metrics");
|
|
33633
33502
|
});
|
|
33634
|
-
const isTruncating = allMetrics.length > MAX_RETAINED_RUNS;
|
|
33635
|
-
const cappedMetrics = isTruncating ? allMetrics.slice(allMetrics.length - MAX_RETAINED_RUNS) : allMetrics;
|
|
33636
|
-
if (isTruncating && !hasWarnedAboutRunTruncation) {
|
|
33637
|
-
hasWarnedAboutRunTruncation = true;
|
|
33638
|
-
const droppedCount = allMetrics.length - MAX_RETAINED_RUNS;
|
|
33639
|
-
getLogger().warn("metrics", "Run-history cap reached \u2014 oldest run-entries dropped from metrics.json; aggregate metrics now cover only the retained window", {
|
|
33640
|
-
droppedCount,
|
|
33641
|
-
maxRetainedRuns: MAX_RETAINED_RUNS,
|
|
33642
|
-
metricsPath
|
|
33643
|
-
});
|
|
33644
|
-
}
|
|
33645
|
-
await saveJsonFile(metricsPath, cappedMetrics, "metrics");
|
|
33646
33503
|
}
|
|
33647
33504
|
async function loadRunMetrics(outputDir) {
|
|
33648
33505
|
const metricsPath = metricsPathFor(outputDir);
|
|
@@ -48370,7 +48227,35 @@ async function exists(workdir, rel) {
|
|
|
48370
48227
|
return false;
|
|
48371
48228
|
}
|
|
48372
48229
|
}
|
|
48373
|
-
|
|
48230
|
+
function isNoise(rel) {
|
|
48231
|
+
const segments = rel.split("/");
|
|
48232
|
+
if (segments.includes(".nax"))
|
|
48233
|
+
return true;
|
|
48234
|
+
const name = segments[segments.length - 1];
|
|
48235
|
+
if (name.endsWith(".lock") || name === "package-lock.json" || name === "pnpm-lock.yaml")
|
|
48236
|
+
return true;
|
|
48237
|
+
if (/(^|\/)(dist|build|\.next|\.turbo|__pycache__)(\/|$)/.test(rel))
|
|
48238
|
+
return true;
|
|
48239
|
+
if (/\.generated\./.test(name))
|
|
48240
|
+
return true;
|
|
48241
|
+
return false;
|
|
48242
|
+
}
|
|
48243
|
+
function walkPathToken(line) {
|
|
48244
|
+
const token = line.trim().split(/\s+/)[0];
|
|
48245
|
+
return token.length > 0 ? token : null;
|
|
48246
|
+
}
|
|
48247
|
+
async function changedFiles(workdir, range) {
|
|
48248
|
+
try {
|
|
48249
|
+
const { stdout, exitCode } = await gitWithTimeout(["diff", "--name-only", `${range.base}...${range.head}`], workdir);
|
|
48250
|
+
if (exitCode !== 0)
|
|
48251
|
+
return [];
|
|
48252
|
+
return stdout.split(`
|
|
48253
|
+
`).map((s) => s.trim()).filter((s) => s.length > 0 && !isNoise(s));
|
|
48254
|
+
} catch {
|
|
48255
|
+
return [];
|
|
48256
|
+
}
|
|
48257
|
+
}
|
|
48258
|
+
async function auditGaps(report, workdir, range, phase = "spec") {
|
|
48374
48259
|
const gaps = [];
|
|
48375
48260
|
const touchpoints = report.touchpoints ?? [];
|
|
48376
48261
|
if (!report.sawTouchpointsSection || touchpoints.length === 0) {
|
|
@@ -48378,13 +48263,29 @@ async function auditGaps(report, workdir) {
|
|
|
48378
48263
|
} else if (!touchpoints.some((t) => t.path === "none")) {
|
|
48379
48264
|
const checked = touchpoints.slice(0, MAX_CHECKED);
|
|
48380
48265
|
const found = await Promise.all(checked.map((t) => exists(workdir, t.path)));
|
|
48381
|
-
|
|
48266
|
+
const foundCount = found.filter(Boolean).length;
|
|
48267
|
+
if (foundCount * 2 <= checked.length) {
|
|
48382
48268
|
gaps.push(`touchpoint path does not exist in the repo (checked: ${checked.map((t) => t.path).join(", ")}) \u2014 list files you actually opened`);
|
|
48383
48269
|
}
|
|
48384
48270
|
}
|
|
48385
48271
|
if (!report.sawWalkSection || (report.walk ?? []).length === 0) {
|
|
48386
48272
|
gaps.push("no `## WALK` section: the per-AC (spec) or per-function (quality) enumeration is required");
|
|
48387
48273
|
}
|
|
48274
|
+
if (phase === "quality" && range) {
|
|
48275
|
+
const required2 = await changedFiles(workdir, range);
|
|
48276
|
+
if (required2.length > 0) {
|
|
48277
|
+
const walked = new Set;
|
|
48278
|
+
for (const line of report.walk ?? []) {
|
|
48279
|
+
const token = walkPathToken(line);
|
|
48280
|
+
if (token && required2.includes(token))
|
|
48281
|
+
walked.add(token);
|
|
48282
|
+
}
|
|
48283
|
+
const unwalked = required2.filter((file3) => !walked.has(file3));
|
|
48284
|
+
if (unwalked.length > 0) {
|
|
48285
|
+
gaps.push(`unwalked changed files not named in \`## WALK\` (${unwalked.length}): ${unwalked.join(", ")} \u2014 walk every changed file`);
|
|
48286
|
+
}
|
|
48287
|
+
}
|
|
48288
|
+
}
|
|
48388
48289
|
return gaps;
|
|
48389
48290
|
}
|
|
48390
48291
|
async function validateDispositions(workdir, dispositions) {
|
|
@@ -48396,7 +48297,9 @@ async function validateDispositions(workdir, dispositions) {
|
|
|
48396
48297
|
}));
|
|
48397
48298
|
}
|
|
48398
48299
|
var MAX_CHECKED = 20;
|
|
48399
|
-
var init_audit_gaps = () => {
|
|
48300
|
+
var init_audit_gaps = __esm(() => {
|
|
48301
|
+
init_git();
|
|
48302
|
+
});
|
|
48400
48303
|
|
|
48401
48304
|
// src/finish/review/parse.ts
|
|
48402
48305
|
function parseTouchpoint(text) {
|
|
@@ -48429,13 +48332,15 @@ function parseReviewReport(text) {
|
|
|
48429
48332
|
let section = "findings";
|
|
48430
48333
|
let current = null;
|
|
48431
48334
|
let lastField = null;
|
|
48335
|
+
const normalized = text.replace(GLUED_HEADING, `$1
|
|
48336
|
+
$2`);
|
|
48432
48337
|
const flush = () => {
|
|
48433
48338
|
if (current)
|
|
48434
48339
|
report.findings.push(current);
|
|
48435
48340
|
current = null;
|
|
48436
48341
|
lastField = null;
|
|
48437
48342
|
};
|
|
48438
|
-
for (const line of
|
|
48343
|
+
for (const line of normalized.split(`
|
|
48439
48344
|
`)) {
|
|
48440
48345
|
const heading = HEADING.exec(line);
|
|
48441
48346
|
if (heading) {
|
|
@@ -48514,9 +48419,10 @@ function parseDispositions(text) {
|
|
|
48514
48419
|
}
|
|
48515
48420
|
return out;
|
|
48516
48421
|
}
|
|
48517
|
-
var HEADING, BLOCK, FIELD, NO_FINDINGS, BULLET, DISPOSITION, EVIDENCE;
|
|
48422
|
+
var HEADING, GLUED_HEADING, BLOCK, FIELD, NO_FINDINGS, BULLET, DISPOSITION, EVIDENCE;
|
|
48518
48423
|
var init_parse5 = __esm(() => {
|
|
48519
48424
|
HEADING = /^\s*#{1,6}\s*(TOUCHPOINTS|WALK|FINDINGS|DISPOSITIONS)\s*:?\s*$/i;
|
|
48425
|
+
GLUED_HEADING = /([^\s#])(#{1,6}[ \t]*(?:TOUCHPOINTS|WALK|FINDINGS|DISPOSITIONS)[ \t]*:?)(?=[ \t]*(?:\r?\n|$))/gi;
|
|
48520
48426
|
BLOCK = /^\s*\[(CRITICAL|HIGH|MEDIUM|LOW)\]\s+(.+?)\s*$/;
|
|
48521
48427
|
FIELD = /^\s*(Problem|Fix|Judgment)\s*:\s*(.*)$/i;
|
|
48522
48428
|
NO_FINDINGS = /^\s*no findings\.?\s*$/i;
|
|
@@ -48678,12 +48584,20 @@ test code \u2014 and judge each changed function on its own merits.
|
|
|
48678
48584
|
|
|
48679
48585
|
## Forcing function \u2014 enumerate before you conclude
|
|
48680
48586
|
|
|
48681
|
-
Before reporting, walk **every function/method the diff adds or changes
|
|
48682
|
-
|
|
48683
|
-
|
|
48684
|
-
|
|
48685
|
-
|
|
48686
|
-
|
|
48587
|
+
Before reporting, walk **every function/method the diff adds or changes \u2014 in
|
|
48588
|
+
test files as well as production ones** \u2014 and write yourself a one-line verdict
|
|
48589
|
+
for each: *earns its place* or *concern: \u2026*. Skipping it is how real
|
|
48590
|
+
maintainability issues get missed: an agent that pattern-matches a few obvious
|
|
48591
|
+
smells and stops will always under-report. Look at each changed function
|
|
48592
|
+
deliberately.
|
|
48593
|
+
|
|
48594
|
+
This per-function walk is **private scratch work** \u2014 it stays in your reasoning,
|
|
48595
|
+
you do not transcribe it into the reply. What you emit, in the \`## WALK\` section
|
|
48596
|
+
the reply contract requires, is **one line per changed file**, carrying that
|
|
48597
|
+
file's verdict (\`path \u2014 earns its place|concern: <one clause>\`). The
|
|
48598
|
+
per-function walk is what finds the defects; the per-file WALK is the
|
|
48599
|
+
checkable evidence that you walked the diff. A reply without the \`## WALK\`
|
|
48600
|
+
section is treated as an incomplete review and sent back.
|
|
48687
48601
|
|
|
48688
48602
|
## What to look for
|
|
48689
48603
|
|
|
@@ -48874,7 +48788,7 @@ reads to judge an integration-shaped defect.)
|
|
|
48874
48788
|
|
|
48875
48789
|
// src/finish/review/prompt.ts
|
|
48876
48790
|
function outputContract(phase) {
|
|
48877
|
-
const walk = phase === "spec" ? "one line per AC in the spec: `AC-3 Covered|Partial|Missing \u2014 <one clause>`" : "one line per
|
|
48791
|
+
const walk = phase === "spec" ? "one line per AC in the spec: `AC-3 Covered|Partial|Missing \u2014 <one clause>`" : "one line per file the diff adds or changes: `path.ts \u2014 earns its place|concern: <one clause>`";
|
|
48878
48792
|
return `# Reply contract \u2014 your reply must be these three sections, in this order
|
|
48879
48793
|
|
|
48880
48794
|
## TOUCHPOINTS
|
|
@@ -48908,10 +48822,11 @@ function buildReviewPrompt(phase, args) {
|
|
|
48908
48822
|
`)
|
|
48909
48823
|
] : [];
|
|
48910
48824
|
if (!args.since) {
|
|
48825
|
+
const specNotice = phase === "spec" && args.specPath ? [`The spec/requirements source is: ${args.specPath}. Read it in full.`] : [];
|
|
48911
48826
|
return [
|
|
48912
48827
|
...gapNotice,
|
|
48913
48828
|
`You are the ${phase.toUpperCase()} reviewer for a completed feature.`,
|
|
48914
|
-
|
|
48829
|
+
...specNotice,
|
|
48915
48830
|
`Fetch and review the diff: \`git diff ${args.base}...HEAD\` (also \`--name-only\` for the file list).`,
|
|
48916
48831
|
WORKER_PROTOCOL_MECHANICS,
|
|
48917
48832
|
dims,
|
|
@@ -48921,8 +48836,7 @@ function buildReviewPrompt(phase, args) {
|
|
|
48921
48836
|
|
|
48922
48837
|
`);
|
|
48923
48838
|
}
|
|
48924
|
-
|
|
48925
|
-
...gapNotice,
|
|
48839
|
+
const reReviewBody = [
|
|
48926
48840
|
`You are the ${phase.toUpperCase()} reviewer for a completed feature, continuing a review you already started.`,
|
|
48927
48841
|
`On your previous pass over \`git diff ${args.base}...HEAD\` you raised the findings below, and they have since been fixed and committed. Everything else in that diff you already judged acceptable \u2014 do not re-derive a verdict on it.`,
|
|
48928
48842
|
`Your findings from the previous pass:
|
|
@@ -48932,14 +48846,19 @@ ${JSON.stringify(args.priorFindings ?? [], null, 2)}`,
|
|
|
48932
48846
|
"1. **Resolved?** Does the fix actually resolve each finding above? A finding that was papered over (assertion weakened, test deleted, check disabled) is NOT resolved \u2014 re-raise it.",
|
|
48933
48847
|
"2. **Broken?** Did the fix introduce a new problem, in the changed lines or in the unchanged code they now call into?",
|
|
48934
48848
|
"",
|
|
48935
|
-
|
|
48849
|
+
...phase === "spec" && args.specPath ? [
|
|
48850
|
+
`Read whatever files you need \u2014 the spec is at ${args.specPath} and the whole repo is available. Scope means *what you judge*, not *what you may read*.`
|
|
48851
|
+
] : [
|
|
48852
|
+
"Read whatever files you need \u2014 the whole repo is available. Scope means *what you judge*, not *what you may read*."
|
|
48853
|
+
]
|
|
48936
48854
|
].join(`
|
|
48937
48855
|
`),
|
|
48938
48856
|
WORKER_PROTOCOL_MECHANICS,
|
|
48939
48857
|
dims,
|
|
48940
48858
|
CLASSIFIER,
|
|
48941
48859
|
outputContract(phase)
|
|
48942
|
-
]
|
|
48860
|
+
];
|
|
48861
|
+
return [...gapNotice, ...reReviewBody].join(`
|
|
48943
48862
|
|
|
48944
48863
|
`);
|
|
48945
48864
|
}
|
|
@@ -49087,7 +49006,10 @@ var init_finish_review = __esm(() => {
|
|
|
49087
49006
|
exhaustedFallback: () => EMPTY_REVIEW_REPORT
|
|
49088
49007
|
}),
|
|
49089
49008
|
async verify(parsed, input, _verifyCtx) {
|
|
49090
|
-
return {
|
|
49009
|
+
return {
|
|
49010
|
+
...parsed,
|
|
49011
|
+
gaps: await auditGaps(parsed, input.workdir, { base: input.base, head: "HEAD" }, input.phase)
|
|
49012
|
+
};
|
|
49091
49013
|
}
|
|
49092
49014
|
};
|
|
49093
49015
|
});
|
|
@@ -50719,7 +50641,7 @@ var package_default;
|
|
|
50719
50641
|
var init_package = __esm(() => {
|
|
50720
50642
|
package_default = {
|
|
50721
50643
|
name: "@nathapp/nax",
|
|
50722
|
-
version: "0.81.0
|
|
50644
|
+
version: "0.81.0",
|
|
50723
50645
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
50724
50646
|
type: "module",
|
|
50725
50647
|
bin: {
|
|
@@ -50761,6 +50683,8 @@ var init_package = __esm(() => {
|
|
|
50761
50683
|
"test:e2e": "timeout -k 5s 180s bun test test/e2e/ --timeout=60000",
|
|
50762
50684
|
"test:coverage": "bun run scripts/check-coverage.ts",
|
|
50763
50685
|
"test:coverage:report": "bun run scripts/check-coverage.ts --report",
|
|
50686
|
+
"test:coverage:update": "bun run scripts/check-coverage.ts --update-baseline",
|
|
50687
|
+
"test:coverage:list": "bun run scripts/check-coverage.ts --list",
|
|
50764
50688
|
"report:test-overlap": "bun run scripts/report-test-overlap.ts",
|
|
50765
50689
|
"report:dead-tests": "bun run scripts/report-dead-tests.ts",
|
|
50766
50690
|
"check:test-mocks": "bun scripts/check-inline-test-mocks.ts --strict",
|
|
@@ -50834,8 +50758,8 @@ var init_version = __esm(() => {
|
|
|
50834
50758
|
NAX_VERSION = package_default.version;
|
|
50835
50759
|
NAX_COMMIT = (() => {
|
|
50836
50760
|
try {
|
|
50837
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
50838
|
-
return "
|
|
50761
|
+
if (/^[0-9a-f]{6,10}$/.test("a2cc47be"))
|
|
50762
|
+
return "a2cc47be";
|
|
50839
50763
|
} catch {}
|
|
50840
50764
|
try {
|
|
50841
50765
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -53582,10 +53506,10 @@ function formatDuration2(ms) {
|
|
|
53582
53506
|
return `${minutes}m ${seconds}s`;
|
|
53583
53507
|
}
|
|
53584
53508
|
function timeoutRetry(input) {
|
|
53585
|
-
const { prompt, changedFiles, elapsedMs, attempt } = input;
|
|
53509
|
+
const { prompt, changedFiles: changedFiles2, elapsedMs, attempt } = input;
|
|
53586
53510
|
const duration3 = formatDuration2(elapsedMs);
|
|
53587
53511
|
const attemptNumber = attempt + 1;
|
|
53588
|
-
if (
|
|
53512
|
+
if (changedFiles2.length === 0) {
|
|
53589
53513
|
return `The previous attempt hit a timeout after ${elapsedMs}ms (${duration3}) with no file changes on disk.
|
|
53590
53514
|
This is attempt ${attemptNumber} of the same story \u2014 the previous attempt left nothing behind, so the approach was wrong.
|
|
53591
53515
|
Change your approach: pick a narrower scope, fewer file edits, or a different angle on the acceptance criteria.
|
|
@@ -53594,7 +53518,7 @@ Change your approach: pick a narrower scope, fewer file edits, or a different an
|
|
|
53594
53518
|
|
|
53595
53519
|
${prompt}`;
|
|
53596
53520
|
}
|
|
53597
|
-
const fileList =
|
|
53521
|
+
const fileList = changedFiles2.map((p) => `- ${p}`).join(`
|
|
53598
53522
|
`);
|
|
53599
53523
|
return `The previous attempt hit a timeout after ${elapsedMs}ms (${duration3}), but left these files on disk:
|
|
53600
53524
|
|
|
@@ -54606,11 +54530,11 @@ function buildHopCallback(ctx, sessionId, _initialOptions) {
|
|
|
54606
54530
|
}
|
|
54607
54531
|
if (hopKind.kind === "timeout-retry") {
|
|
54608
54532
|
const preAttemptGitRef = preAttemptGitRefPromise ? await preAttemptGitRefPromise : undefined;
|
|
54609
|
-
const
|
|
54533
|
+
const changedFiles2 = preAttemptGitRef ? await _buildHopCallbackDeps.captureWorkingTreeChanges(workdir, preAttemptGitRef) : [];
|
|
54610
54534
|
const elapsedMs = elapsedSincePriorHop;
|
|
54611
54535
|
prompt = _buildHopCallbackDeps.timeoutRetry({
|
|
54612
54536
|
prompt: resolvedRunOptions.prompt,
|
|
54613
|
-
changedFiles,
|
|
54537
|
+
changedFiles: changedFiles2,
|
|
54614
54538
|
elapsedMs,
|
|
54615
54539
|
attempt: hopKind.attempt
|
|
54616
54540
|
});
|
|
@@ -62252,7 +62176,7 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
62252
62176
|
"**/.nax-acceptance*",
|
|
62253
62177
|
"**/_nax_acceptance_test.py",
|
|
62254
62178
|
"**/_nax_suggested_test.py",
|
|
62255
|
-
|
|
62179
|
+
`${PROJECT_FEATURES_DIR}/*/fragments/`
|
|
62256
62180
|
];
|
|
62257
62181
|
const missing = patterns.filter((pattern) => !content.includes(pattern));
|
|
62258
62182
|
const passed = missing.length === 0;
|
|
@@ -65650,8 +65574,8 @@ var init_completion = __esm(() => {
|
|
|
65650
65574
|
}
|
|
65651
65575
|
if (fragmentsEnabled) {
|
|
65652
65576
|
try {
|
|
65653
|
-
const
|
|
65654
|
-
const body = _completionDeps.renderFragmentBody(ctx.story.id, ctx.story.title, ctx.story.acceptanceCriteria,
|
|
65577
|
+
const changedFiles2 = [...await _completionDeps.getDiffFilePaths(ctx.workdir, ctx.storyGitRef)];
|
|
65578
|
+
const body = _completionDeps.renderFragmentBody(ctx.story.id, ctx.story.title, ctx.story.acceptanceCriteria, changedFiles2);
|
|
65655
65579
|
const maxTokens = ctx.config.context.v2.fragments.maxTokens;
|
|
65656
65580
|
await _completionDeps.writeFragment(ctx.projectDir, featureId, ctx.story.id, body, maxTokens);
|
|
65657
65581
|
} catch (err) {
|
|
@@ -66046,7 +65970,7 @@ async function acquireLock(workdir) {
|
|
|
66046
65970
|
}
|
|
66047
65971
|
const tombstonePath = `${lockPath}.stale.${process.pid}.${Date.now()}`;
|
|
66048
65972
|
try {
|
|
66049
|
-
await
|
|
65973
|
+
await _lockDeps.rename(lockPath, tombstonePath);
|
|
66050
65974
|
} catch (renameError) {
|
|
66051
65975
|
if (renameError.code === "ENOENT") {
|
|
66052
65976
|
return false;
|
|
@@ -66111,8 +66035,12 @@ async function releaseLock(workdir) {
|
|
|
66111
66035
|
}
|
|
66112
66036
|
}
|
|
66113
66037
|
}
|
|
66038
|
+
var _lockDeps;
|
|
66114
66039
|
var init_lock = __esm(() => {
|
|
66115
66040
|
init_logger2();
|
|
66041
|
+
_lockDeps = {
|
|
66042
|
+
rename: rename2
|
|
66043
|
+
};
|
|
66116
66044
|
});
|
|
66117
66045
|
|
|
66118
66046
|
// src/execution/helpers.ts
|
|
@@ -70664,11 +70592,11 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
70664
70592
|
const repoRoot = acceptanceContext.projectDir ?? workdir;
|
|
70665
70593
|
const packageDir = acceptanceContext.story.workdir && acceptanceContext.projectDir ? path15.join(acceptanceContext.projectDir, acceptanceContext.story.workdir) : undefined;
|
|
70666
70594
|
const ignoreMatchers = acceptanceContext.naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
|
|
70667
|
-
const
|
|
70595
|
+
const changedFiles2 = filterNaxInternalPaths(changedFilesRaw, ignoreMatchers);
|
|
70668
70596
|
const MAX_BYTES = 51200;
|
|
70669
70597
|
let totalBytes = 0;
|
|
70670
70598
|
const entries = [];
|
|
70671
|
-
for (const file3 of
|
|
70599
|
+
for (const file3 of changedFiles2) {
|
|
70672
70600
|
if (totalBytes >= MAX_BYTES)
|
|
70673
70601
|
break;
|
|
70674
70602
|
const filePath = path15.join(workdir, file3);
|
|
@@ -75017,12 +74945,12 @@ async function runDeferredReview(workdir, _reviewConfig, plugins, runStartRef, n
|
|
|
75017
74945
|
}
|
|
75018
74946
|
const changedFilesRaw = await getChangedFilesForDeferred(workdir, runStartRef);
|
|
75019
74947
|
const ignoreMatchers = naxIgnoreIndex?.getMatchers() ?? await resolveNaxIgnorePatterns(workdir);
|
|
75020
|
-
const
|
|
74948
|
+
const changedFiles2 = filterNaxInternalPaths(changedFilesRaw, ignoreMatchers);
|
|
75021
74949
|
const reviewerResults = [];
|
|
75022
74950
|
let anyFailed = false;
|
|
75023
74951
|
for (const reviewer of reviewers) {
|
|
75024
74952
|
try {
|
|
75025
|
-
const result = await reviewer.check(workdir,
|
|
74953
|
+
const result = await reviewer.check(workdir, changedFiles2);
|
|
75026
74954
|
reviewerResults.push({
|
|
75027
74955
|
name: reviewer.name,
|
|
75028
74956
|
passed: result.passed,
|
|
@@ -75043,7 +74971,7 @@ async function runDeferredReview(workdir, _reviewConfig, plugins, runStartRef, n
|
|
|
75043
74971
|
anyFailed = true;
|
|
75044
74972
|
}
|
|
75045
74973
|
}
|
|
75046
|
-
return { runStartRef, changedFiles, reviewerResults, anyFailed };
|
|
74974
|
+
return { runStartRef, changedFiles: changedFiles2, reviewerResults, anyFailed };
|
|
75047
74975
|
}
|
|
75048
74976
|
var _deferredReviewDeps;
|
|
75049
74977
|
var init_deferred_review = __esm(() => {
|
|
@@ -75682,7 +75610,7 @@ ${missing.join(`
|
|
|
75682
75610
|
`);
|
|
75683
75611
|
return { created: false, added: [...missing] };
|
|
75684
75612
|
}
|
|
75685
|
-
var NAX_GITIGNORE_ENTRIES, NAX_NAXIGNORE_ENTRIES, NAX_NAXIGNORE_HEADER = `# nax - paths excluded from context, review
|
|
75613
|
+
var FEATURE_RUN_ARTIFACTS, NAX_GITIGNORE_ENTRIES, NAX_NAXIGNORE_ENTRIES, NAX_NAXIGNORE_HEADER = `# nax - paths excluded from context, review
|
|
75686
75614
|
# and verification scanning.
|
|
75687
75615
|
# gitignore syntax. Also honoured per-package.
|
|
75688
75616
|
|
|
@@ -75695,24 +75623,32 @@ var NAX_GITIGNORE_ENTRIES, NAX_NAXIGNORE_ENTRIES, NAX_NAXIGNORE_HEADER = `# nax
|
|
|
75695
75623
|
`;
|
|
75696
75624
|
var init_gitignore = __esm(() => {
|
|
75697
75625
|
init_config();
|
|
75626
|
+
FEATURE_RUN_ARTIFACTS = [
|
|
75627
|
+
"runs/",
|
|
75628
|
+
"plan/",
|
|
75629
|
+
"sessions/",
|
|
75630
|
+
"stories/",
|
|
75631
|
+
"fragments/",
|
|
75632
|
+
"interactions/",
|
|
75633
|
+
"semantic-verdicts/",
|
|
75634
|
+
"status.json",
|
|
75635
|
+
"checkpoint.jsonl",
|
|
75636
|
+
"progress.txt",
|
|
75637
|
+
"acp-sessions.json",
|
|
75638
|
+
"acceptance-refined.json",
|
|
75639
|
+
"*.bak"
|
|
75640
|
+
].map((artifact) => `**/${PROJECT_FEATURES_DIR}/*/${artifact}`);
|
|
75698
75641
|
NAX_GITIGNORE_ENTRIES = [
|
|
75699
75642
|
".nax-verifier-verdict.json",
|
|
75700
75643
|
"nax.lock",
|
|
75701
75644
|
".nax/**/runs/",
|
|
75702
75645
|
".nax/metrics.json",
|
|
75703
|
-
|
|
75704
|
-
`${PROJECT_FEATURES_DIR}/*/plan/`,
|
|
75705
|
-
`${PROJECT_FEATURES_DIR}/*/fragments/`,
|
|
75706
|
-
`${PROJECT_FEATURES_DIR}/*/acp-sessions.json`,
|
|
75707
|
-
`${PROJECT_FEATURES_DIR}/*/interactions/`,
|
|
75708
|
-
`${PROJECT_FEATURES_DIR}/*/progress.txt`,
|
|
75709
|
-
`${PROJECT_FEATURES_DIR}/*/acceptance-refined.json`,
|
|
75646
|
+
...FEATURE_RUN_ARTIFACTS,
|
|
75710
75647
|
".nax-pids",
|
|
75711
75648
|
".nax-wt/",
|
|
75712
75649
|
"**/.nax-acceptance*",
|
|
75713
75650
|
"**/_nax_acceptance_test.py",
|
|
75714
75651
|
"**/_nax_suggested_test.py",
|
|
75715
|
-
`**/${PROJECT_FEATURES_DIR}/*/`,
|
|
75716
75652
|
".nax/prompt-audit/",
|
|
75717
75653
|
".nax/finish-audit/",
|
|
75718
75654
|
".nax/mutation-journal/"
|
|
@@ -83694,6 +83630,7 @@ __export(exports_execution, {
|
|
|
83694
83630
|
_postRunDeps: () => _postRunDeps,
|
|
83695
83631
|
_pidRegistryDeps: () => _pidRegistryDeps,
|
|
83696
83632
|
_newPackageSetupDeps: () => _newPackageSetupDeps,
|
|
83633
|
+
_lockDeps: () => _lockDeps,
|
|
83697
83634
|
StoryOrchestratorBuilder: () => StoryOrchestratorBuilder,
|
|
83698
83635
|
StatusWriter: () => StatusWriter,
|
|
83699
83636
|
STRICT_VERDICT_PHASE_NAMES: () => STRICT_VERDICT_PHASE_NAMES,
|
|
@@ -85115,8 +85052,7 @@ var init_config_descriptions = __esm(() => {
|
|
|
85115
85052
|
"context.autoDetect.maxFiles": "Max files to auto-detect",
|
|
85116
85053
|
"context.autoDetect.traceImports": "Trace imports to find related files",
|
|
85117
85054
|
optimizer: "Prompt optimizer configuration",
|
|
85118
|
-
"optimizer.enabled": "Enable prompt optimizer",
|
|
85119
|
-
"optimizer.strategy": "Optimization strategy: rule-based | llm | noop",
|
|
85055
|
+
"optimizer.enabled": "Enable prompt optimizer (pass-through unless a plugin provides one)",
|
|
85120
85056
|
plugins: "Plugin configurations",
|
|
85121
85057
|
hooks: "Hooks configuration",
|
|
85122
85058
|
"hooks.skipGlobal": "Skip loading global hooks",
|
|
@@ -117545,6 +117481,7 @@ import { join as join106 } from "path";
|
|
|
117545
117481
|
// src/commands/logs-formatter.ts
|
|
117546
117482
|
init_source();
|
|
117547
117483
|
init_formatter();
|
|
117484
|
+
init_bun_deps();
|
|
117548
117485
|
import { readdirSync as readdirSync8 } from "fs";
|
|
117549
117486
|
import { join as join105 } from "path";
|
|
117550
117487
|
|
|
@@ -117731,13 +117668,57 @@ async function displayLogs(filePath, options) {
|
|
|
117731
117668
|
}
|
|
117732
117669
|
}
|
|
117733
117670
|
}
|
|
117734
|
-
async function
|
|
117671
|
+
async function defaultReadRange(filePath, start) {
|
|
117672
|
+
return Bun.file(filePath).slice(start).text();
|
|
117673
|
+
}
|
|
117674
|
+
async function defaultSize(filePath) {
|
|
117675
|
+
return (await Bun.file(filePath).stat()).size;
|
|
117676
|
+
}
|
|
117677
|
+
var DEFAULT_FOLLOW_LOGS_DEPS = {
|
|
117678
|
+
emit: (line) => console.log(line),
|
|
117679
|
+
sleep: (ms, signal) => cancellableDelay(ms, signal),
|
|
117680
|
+
readRange: defaultReadRange,
|
|
117681
|
+
size: defaultSize
|
|
117682
|
+
};
|
|
117683
|
+
async function followLogs(filePath, options, opts) {
|
|
117684
|
+
const deps = { ...DEFAULT_FOLLOW_LOGS_DEPS, ...opts?._deps };
|
|
117685
|
+
const signal = opts?.signal;
|
|
117735
117686
|
const mode = options.json ? "json" : "normal";
|
|
117736
|
-
|
|
117737
|
-
|
|
117738
|
-
|
|
117687
|
+
if (signal?.aborted)
|
|
117688
|
+
return "cancelled";
|
|
117689
|
+
let lastOffset = await consumeRange(filePath, 0, deps, options, mode);
|
|
117690
|
+
while (true) {
|
|
117691
|
+
if (signal?.aborted)
|
|
117692
|
+
return "cancelled";
|
|
117693
|
+
try {
|
|
117694
|
+
await deps.sleep(500, signal);
|
|
117695
|
+
} catch (err) {
|
|
117696
|
+
if (signal?.aborted)
|
|
117697
|
+
return "cancelled";
|
|
117698
|
+
throw err;
|
|
117699
|
+
}
|
|
117700
|
+
let currentSize;
|
|
117701
|
+
try {
|
|
117702
|
+
currentSize = await deps.size(filePath);
|
|
117703
|
+
} catch {
|
|
117704
|
+
return "cancelled";
|
|
117705
|
+
}
|
|
117706
|
+
if (currentSize > lastOffset) {
|
|
117707
|
+
lastOffset = await consumeRange(filePath, lastOffset, deps, options, mode);
|
|
117708
|
+
} else if (currentSize < lastOffset) {
|
|
117709
|
+
lastOffset = currentSize;
|
|
117710
|
+
}
|
|
117711
|
+
}
|
|
117712
|
+
}
|
|
117713
|
+
async function consumeRange(filePath, start, deps, options, mode) {
|
|
117714
|
+
const chunk = await deps.readRange(filePath, start);
|
|
117715
|
+
if (!chunk)
|
|
117716
|
+
return start;
|
|
117717
|
+
const lastNewline = chunk.lastIndexOf(`
|
|
117739
117718
|
`);
|
|
117740
|
-
|
|
117719
|
+
const complete = lastNewline === -1 ? "" : chunk.slice(0, lastNewline + 1);
|
|
117720
|
+
for (const line of complete.split(`
|
|
117721
|
+
`)) {
|
|
117741
117722
|
if (!line.trim())
|
|
117742
117723
|
continue;
|
|
117743
117724
|
try {
|
|
@@ -117747,36 +117728,11 @@ async function followLogs(filePath, options) {
|
|
|
117747
117728
|
}
|
|
117748
117729
|
const formatted = formatLogEntry(entry, { mode, useColor: true });
|
|
117749
117730
|
if (formatted.shouldDisplay && formatted.output) {
|
|
117750
|
-
|
|
117731
|
+
deps.emit(formatted.output);
|
|
117751
117732
|
}
|
|
117752
117733
|
} catch {}
|
|
117753
117734
|
}
|
|
117754
|
-
|
|
117755
|
-
while (true) {
|
|
117756
|
-
await Bun.sleep(500);
|
|
117757
|
-
const currentSize = (await Bun.file(filePath).stat()).size;
|
|
117758
|
-
if (currentSize > lastSize) {
|
|
117759
|
-
const newFile = Bun.file(filePath);
|
|
117760
|
-
const newContent = await newFile.text();
|
|
117761
|
-
const newLines = newContent.slice(lastSize).trim().split(`
|
|
117762
|
-
`);
|
|
117763
|
-
for (const line of newLines) {
|
|
117764
|
-
if (!line.trim())
|
|
117765
|
-
continue;
|
|
117766
|
-
try {
|
|
117767
|
-
const entry = JSON.parse(line);
|
|
117768
|
-
if (!shouldDisplayEntry(entry, options)) {
|
|
117769
|
-
continue;
|
|
117770
|
-
}
|
|
117771
|
-
const formatted = formatLogEntry(entry, { mode, useColor: true });
|
|
117772
|
-
if (formatted.shouldDisplay && formatted.output) {
|
|
117773
|
-
console.log(formatted.output);
|
|
117774
|
-
}
|
|
117775
|
-
} catch {}
|
|
117776
|
-
}
|
|
117777
|
-
lastSize = currentSize;
|
|
117778
|
-
}
|
|
117779
|
-
}
|
|
117735
|
+
return start + Buffer.byteLength(complete, "utf8");
|
|
117780
117736
|
}
|
|
117781
117737
|
function shouldDisplayEntry(entry, options) {
|
|
117782
117738
|
if (options.story && entry.storyId !== options.story) {
|
|
@@ -117800,7 +117756,7 @@ async function logsCommand(options) {
|
|
|
117800
117756
|
return;
|
|
117801
117757
|
}
|
|
117802
117758
|
if (options.follow) {
|
|
117803
|
-
await followLogs(runFile2, options);
|
|
117759
|
+
await followLogs(runFile2, options, { signal: options.signal });
|
|
117804
117760
|
} else {
|
|
117805
117761
|
await displayLogs(runFile2, options);
|
|
117806
117762
|
}
|
|
@@ -117823,7 +117779,7 @@ async function logsCommand(options) {
|
|
|
117823
117779
|
throw new Error("No runs found for this feature");
|
|
117824
117780
|
}
|
|
117825
117781
|
if (options.follow) {
|
|
117826
|
-
await followLogs(runFile, options);
|
|
117782
|
+
await followLogs(runFile, options, { signal: options.signal });
|
|
117827
117783
|
return;
|
|
117828
117784
|
}
|
|
117829
117785
|
await displayLogs(runFile, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax",
|
|
3
|
-
"version": "0.81.0
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "AI Coding Agent Orchestrator — loops until done",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"test:e2e": "timeout -k 5s 180s bun test test/e2e/ --timeout=60000",
|
|
43
43
|
"test:coverage": "bun run scripts/check-coverage.ts",
|
|
44
44
|
"test:coverage:report": "bun run scripts/check-coverage.ts --report",
|
|
45
|
+
"test:coverage:update": "bun run scripts/check-coverage.ts --update-baseline",
|
|
46
|
+
"test:coverage:list": "bun run scripts/check-coverage.ts --list",
|
|
45
47
|
"report:test-overlap": "bun run scripts/report-test-overlap.ts",
|
|
46
48
|
"report:dead-tests": "bun run scripts/report-dead-tests.ts",
|
|
47
49
|
"check:test-mocks": "bun scripts/check-inline-test-mocks.ts --strict",
|