@nathapp/nax 0.81.0-canary.2 → 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 +115 -223
- 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
|
|
@@ -50783,7 +50641,7 @@ var package_default;
|
|
|
50783
50641
|
var init_package = __esm(() => {
|
|
50784
50642
|
package_default = {
|
|
50785
50643
|
name: "@nathapp/nax",
|
|
50786
|
-
version: "0.81.0
|
|
50644
|
+
version: "0.81.0",
|
|
50787
50645
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
50788
50646
|
type: "module",
|
|
50789
50647
|
bin: {
|
|
@@ -50825,6 +50683,8 @@ var init_package = __esm(() => {
|
|
|
50825
50683
|
"test:e2e": "timeout -k 5s 180s bun test test/e2e/ --timeout=60000",
|
|
50826
50684
|
"test:coverage": "bun run scripts/check-coverage.ts",
|
|
50827
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",
|
|
50828
50688
|
"report:test-overlap": "bun run scripts/report-test-overlap.ts",
|
|
50829
50689
|
"report:dead-tests": "bun run scripts/report-dead-tests.ts",
|
|
50830
50690
|
"check:test-mocks": "bun scripts/check-inline-test-mocks.ts --strict",
|
|
@@ -50898,8 +50758,8 @@ var init_version = __esm(() => {
|
|
|
50898
50758
|
NAX_VERSION = package_default.version;
|
|
50899
50759
|
NAX_COMMIT = (() => {
|
|
50900
50760
|
try {
|
|
50901
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
50902
|
-
return "
|
|
50761
|
+
if (/^[0-9a-f]{6,10}$/.test("a2cc47be"))
|
|
50762
|
+
return "a2cc47be";
|
|
50903
50763
|
} catch {}
|
|
50904
50764
|
try {
|
|
50905
50765
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -62316,7 +62176,7 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
62316
62176
|
"**/.nax-acceptance*",
|
|
62317
62177
|
"**/_nax_acceptance_test.py",
|
|
62318
62178
|
"**/_nax_suggested_test.py",
|
|
62319
|
-
|
|
62179
|
+
`${PROJECT_FEATURES_DIR}/*/fragments/`
|
|
62320
62180
|
];
|
|
62321
62181
|
const missing = patterns.filter((pattern) => !content.includes(pattern));
|
|
62322
62182
|
const passed = missing.length === 0;
|
|
@@ -66110,7 +65970,7 @@ async function acquireLock(workdir) {
|
|
|
66110
65970
|
}
|
|
66111
65971
|
const tombstonePath = `${lockPath}.stale.${process.pid}.${Date.now()}`;
|
|
66112
65972
|
try {
|
|
66113
|
-
await
|
|
65973
|
+
await _lockDeps.rename(lockPath, tombstonePath);
|
|
66114
65974
|
} catch (renameError) {
|
|
66115
65975
|
if (renameError.code === "ENOENT") {
|
|
66116
65976
|
return false;
|
|
@@ -66175,8 +66035,12 @@ async function releaseLock(workdir) {
|
|
|
66175
66035
|
}
|
|
66176
66036
|
}
|
|
66177
66037
|
}
|
|
66038
|
+
var _lockDeps;
|
|
66178
66039
|
var init_lock = __esm(() => {
|
|
66179
66040
|
init_logger2();
|
|
66041
|
+
_lockDeps = {
|
|
66042
|
+
rename: rename2
|
|
66043
|
+
};
|
|
66180
66044
|
});
|
|
66181
66045
|
|
|
66182
66046
|
// src/execution/helpers.ts
|
|
@@ -75746,7 +75610,7 @@ ${missing.join(`
|
|
|
75746
75610
|
`);
|
|
75747
75611
|
return { created: false, added: [...missing] };
|
|
75748
75612
|
}
|
|
75749
|
-
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
|
|
75750
75614
|
# and verification scanning.
|
|
75751
75615
|
# gitignore syntax. Also honoured per-package.
|
|
75752
75616
|
|
|
@@ -75759,24 +75623,32 @@ var NAX_GITIGNORE_ENTRIES, NAX_NAXIGNORE_ENTRIES, NAX_NAXIGNORE_HEADER = `# nax
|
|
|
75759
75623
|
`;
|
|
75760
75624
|
var init_gitignore = __esm(() => {
|
|
75761
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}`);
|
|
75762
75641
|
NAX_GITIGNORE_ENTRIES = [
|
|
75763
75642
|
".nax-verifier-verdict.json",
|
|
75764
75643
|
"nax.lock",
|
|
75765
75644
|
".nax/**/runs/",
|
|
75766
75645
|
".nax/metrics.json",
|
|
75767
|
-
|
|
75768
|
-
`${PROJECT_FEATURES_DIR}/*/plan/`,
|
|
75769
|
-
`${PROJECT_FEATURES_DIR}/*/fragments/`,
|
|
75770
|
-
`${PROJECT_FEATURES_DIR}/*/acp-sessions.json`,
|
|
75771
|
-
`${PROJECT_FEATURES_DIR}/*/interactions/`,
|
|
75772
|
-
`${PROJECT_FEATURES_DIR}/*/progress.txt`,
|
|
75773
|
-
`${PROJECT_FEATURES_DIR}/*/acceptance-refined.json`,
|
|
75646
|
+
...FEATURE_RUN_ARTIFACTS,
|
|
75774
75647
|
".nax-pids",
|
|
75775
75648
|
".nax-wt/",
|
|
75776
75649
|
"**/.nax-acceptance*",
|
|
75777
75650
|
"**/_nax_acceptance_test.py",
|
|
75778
75651
|
"**/_nax_suggested_test.py",
|
|
75779
|
-
`**/${PROJECT_FEATURES_DIR}/*/`,
|
|
75780
75652
|
".nax/prompt-audit/",
|
|
75781
75653
|
".nax/finish-audit/",
|
|
75782
75654
|
".nax/mutation-journal/"
|
|
@@ -83758,6 +83630,7 @@ __export(exports_execution, {
|
|
|
83758
83630
|
_postRunDeps: () => _postRunDeps,
|
|
83759
83631
|
_pidRegistryDeps: () => _pidRegistryDeps,
|
|
83760
83632
|
_newPackageSetupDeps: () => _newPackageSetupDeps,
|
|
83633
|
+
_lockDeps: () => _lockDeps,
|
|
83761
83634
|
StoryOrchestratorBuilder: () => StoryOrchestratorBuilder,
|
|
83762
83635
|
StatusWriter: () => StatusWriter,
|
|
83763
83636
|
STRICT_VERDICT_PHASE_NAMES: () => STRICT_VERDICT_PHASE_NAMES,
|
|
@@ -85179,8 +85052,7 @@ var init_config_descriptions = __esm(() => {
|
|
|
85179
85052
|
"context.autoDetect.maxFiles": "Max files to auto-detect",
|
|
85180
85053
|
"context.autoDetect.traceImports": "Trace imports to find related files",
|
|
85181
85054
|
optimizer: "Prompt optimizer configuration",
|
|
85182
|
-
"optimizer.enabled": "Enable prompt optimizer",
|
|
85183
|
-
"optimizer.strategy": "Optimization strategy: rule-based | llm | noop",
|
|
85055
|
+
"optimizer.enabled": "Enable prompt optimizer (pass-through unless a plugin provides one)",
|
|
85184
85056
|
plugins: "Plugin configurations",
|
|
85185
85057
|
hooks: "Hooks configuration",
|
|
85186
85058
|
"hooks.skipGlobal": "Skip loading global hooks",
|
|
@@ -117609,6 +117481,7 @@ import { join as join106 } from "path";
|
|
|
117609
117481
|
// src/commands/logs-formatter.ts
|
|
117610
117482
|
init_source();
|
|
117611
117483
|
init_formatter();
|
|
117484
|
+
init_bun_deps();
|
|
117612
117485
|
import { readdirSync as readdirSync8 } from "fs";
|
|
117613
117486
|
import { join as join105 } from "path";
|
|
117614
117487
|
|
|
@@ -117795,13 +117668,57 @@ async function displayLogs(filePath, options) {
|
|
|
117795
117668
|
}
|
|
117796
117669
|
}
|
|
117797
117670
|
}
|
|
117798
|
-
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;
|
|
117799
117686
|
const mode = options.json ? "json" : "normal";
|
|
117800
|
-
|
|
117801
|
-
|
|
117802
|
-
|
|
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(`
|
|
117803
117718
|
`);
|
|
117804
|
-
|
|
117719
|
+
const complete = lastNewline === -1 ? "" : chunk.slice(0, lastNewline + 1);
|
|
117720
|
+
for (const line of complete.split(`
|
|
117721
|
+
`)) {
|
|
117805
117722
|
if (!line.trim())
|
|
117806
117723
|
continue;
|
|
117807
117724
|
try {
|
|
@@ -117811,36 +117728,11 @@ async function followLogs(filePath, options) {
|
|
|
117811
117728
|
}
|
|
117812
117729
|
const formatted = formatLogEntry(entry, { mode, useColor: true });
|
|
117813
117730
|
if (formatted.shouldDisplay && formatted.output) {
|
|
117814
|
-
|
|
117731
|
+
deps.emit(formatted.output);
|
|
117815
117732
|
}
|
|
117816
117733
|
} catch {}
|
|
117817
117734
|
}
|
|
117818
|
-
|
|
117819
|
-
while (true) {
|
|
117820
|
-
await Bun.sleep(500);
|
|
117821
|
-
const currentSize = (await Bun.file(filePath).stat()).size;
|
|
117822
|
-
if (currentSize > lastSize) {
|
|
117823
|
-
const newFile = Bun.file(filePath);
|
|
117824
|
-
const newContent = await newFile.text();
|
|
117825
|
-
const newLines = newContent.slice(lastSize).trim().split(`
|
|
117826
|
-
`);
|
|
117827
|
-
for (const line of newLines) {
|
|
117828
|
-
if (!line.trim())
|
|
117829
|
-
continue;
|
|
117830
|
-
try {
|
|
117831
|
-
const entry = JSON.parse(line);
|
|
117832
|
-
if (!shouldDisplayEntry(entry, options)) {
|
|
117833
|
-
continue;
|
|
117834
|
-
}
|
|
117835
|
-
const formatted = formatLogEntry(entry, { mode, useColor: true });
|
|
117836
|
-
if (formatted.shouldDisplay && formatted.output) {
|
|
117837
|
-
console.log(formatted.output);
|
|
117838
|
-
}
|
|
117839
|
-
} catch {}
|
|
117840
|
-
}
|
|
117841
|
-
lastSize = currentSize;
|
|
117842
|
-
}
|
|
117843
|
-
}
|
|
117735
|
+
return start + Buffer.byteLength(complete, "utf8");
|
|
117844
117736
|
}
|
|
117845
117737
|
function shouldDisplayEntry(entry, options) {
|
|
117846
117738
|
if (options.story && entry.storyId !== options.story) {
|
|
@@ -117864,7 +117756,7 @@ async function logsCommand(options) {
|
|
|
117864
117756
|
return;
|
|
117865
117757
|
}
|
|
117866
117758
|
if (options.follow) {
|
|
117867
|
-
await followLogs(runFile2, options);
|
|
117759
|
+
await followLogs(runFile2, options, { signal: options.signal });
|
|
117868
117760
|
} else {
|
|
117869
117761
|
await displayLogs(runFile2, options);
|
|
117870
117762
|
}
|
|
@@ -117887,7 +117779,7 @@ async function logsCommand(options) {
|
|
|
117887
117779
|
throw new Error("No runs found for this feature");
|
|
117888
117780
|
}
|
|
117889
117781
|
if (options.follow) {
|
|
117890
|
-
await followLogs(runFile, options);
|
|
117782
|
+
await followLogs(runFile, options, { signal: options.signal });
|
|
117891
117783
|
return;
|
|
117892
117784
|
}
|
|
117893
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",
|