@nathapp/nax 0.70.0-canary.2 → 0.70.0-canary.4
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 +152 -38
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -21022,6 +21022,7 @@ class AcpAgentAdapter {
|
|
|
21022
21022
|
const MAX_TURNS = opts.maxTurns ?? 10;
|
|
21023
21023
|
let totalTokenUsage = { inputTokens: 0, outputTokens: 0 };
|
|
21024
21024
|
let totalExactCostUsd;
|
|
21025
|
+
const interactions = [];
|
|
21025
21026
|
let turnCount = 0;
|
|
21026
21027
|
let lastResponse = null;
|
|
21027
21028
|
let timedOut = false;
|
|
@@ -21106,6 +21107,7 @@ class AcpAgentAdapter {
|
|
|
21106
21107
|
})
|
|
21107
21108
|
]);
|
|
21108
21109
|
if (response) {
|
|
21110
|
+
interactions.push({ turnIndex: turnCount, question, reply: response.answer });
|
|
21109
21111
|
currentPrompt = response.answer;
|
|
21110
21112
|
continue;
|
|
21111
21113
|
}
|
|
@@ -21136,7 +21138,8 @@ class AcpAgentAdapter {
|
|
|
21136
21138
|
tokenUsage,
|
|
21137
21139
|
estimatedCostUsd,
|
|
21138
21140
|
exactCostUsd,
|
|
21139
|
-
internalRoundTrips: turnCount
|
|
21141
|
+
internalRoundTrips: turnCount,
|
|
21142
|
+
...interactions.length > 0 ? { interactions } : {}
|
|
21140
21143
|
};
|
|
21141
21144
|
}
|
|
21142
21145
|
async closeSession(handle) {
|
|
@@ -22139,6 +22142,7 @@ class AgentManager {
|
|
|
22139
22142
|
sessionId: handle.protocolIds?.sessionId ?? null,
|
|
22140
22143
|
recordId: handle.protocolIds?.recordId ?? null
|
|
22141
22144
|
},
|
|
22145
|
+
...result.interactions?.length ? { interactions: result.interactions } : {},
|
|
22142
22146
|
origin: "runAsSession",
|
|
22143
22147
|
...opts.callId !== undefined ? { callId: opts.callId } : {},
|
|
22144
22148
|
...opts.scopeId !== undefined ? { scopeId: opts.scopeId } : {}
|
|
@@ -32088,6 +32092,19 @@ ${STEP3_SHARED_RULES}
|
|
|
32088
32092
|
- **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
|
|
32089
32093
|
- **Path anchor (CRITICAL \u2014 do NOT deviate)**: Write the test file to this exact path: \`${p.targetTestFilePath}\`. This path is intentional and computed by the orchestrator \u2014 do not change it based on what you observe in the project. In particular: if you see a \`.nax/features/\` directory at the repo root, that is for stories scoped to the repo root. When a story belongs to a specific package (e.g. \`packages/core\`), its acceptance test lives inside that package's \`.nax/features/\` directory so the test runner can resolve the package's imports correctly. The package root is 3 levels above the test file (\`../../../\` relative to the test file).
|
|
32090
32094
|
- **Process cwd**: When spawning child processes to invoke a CLI or binary, set the working directory to the **package root** (\`join(import.meta.dir, "../../..")\`) as your default \u2014 unless your Step 2 exploration reveals the CLI uses a different working directory convention (e.g. reads config from \`~/.config/\`, or resolves paths relative to a flag value). Always check how the CLI resolves file paths before assuming.${implSection}`;
|
|
32095
|
+
}
|
|
32096
|
+
buildPathCorrection(targetTestFilePath) {
|
|
32097
|
+
return `The acceptance test file was NOT found at the required path. You likely wrote it to a different filename or directory (for example, by renaming a dotfile or replacing dashes with underscores).
|
|
32098
|
+
|
|
32099
|
+
Move (or re-write) the acceptance test you just created so it lives at EXACTLY this path:
|
|
32100
|
+
${targetTestFilePath}
|
|
32101
|
+
|
|
32102
|
+
Requirements:
|
|
32103
|
+
- The file must be at that exact path \u2014 same directory and same filename, including any leading dot and dashes. Do NOT sanitize, rename, or relocate it.
|
|
32104
|
+
- Preserve the test content you already wrote. Do not regenerate, weaken, or stub the assertions.
|
|
32105
|
+
- If you wrote it somewhere else, delete the misplaced copy after moving it so only the canonical path remains.
|
|
32106
|
+
|
|
32107
|
+
After writing the file to the exact path above, reply with a brief confirmation only.`;
|
|
32091
32108
|
}
|
|
32092
32109
|
buildGeneratorFromSpecPrompt(p) {
|
|
32093
32110
|
return `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${p.featureName}" feature.
|
|
@@ -34755,6 +34772,40 @@ ${outputFormat}`, overridable: false }
|
|
|
34755
34772
|
};
|
|
34756
34773
|
});
|
|
34757
34774
|
|
|
34775
|
+
// src/operations/self-heal.ts
|
|
34776
|
+
function makeSelfHealStep(spec) {
|
|
34777
|
+
return {
|
|
34778
|
+
async run(ctx) {
|
|
34779
|
+
const deviations = await spec.detect(ctx.input);
|
|
34780
|
+
if (deviations.length === 0)
|
|
34781
|
+
return null;
|
|
34782
|
+
if (spec.log) {
|
|
34783
|
+
getSafeLogger()?.info(spec.log.kind, spec.log.message, spec.log.meta?.(ctx.input, deviations) ?? {});
|
|
34784
|
+
}
|
|
34785
|
+
return ctx.send(spec.buildRepair(deviations, ctx.input));
|
|
34786
|
+
}
|
|
34787
|
+
};
|
|
34788
|
+
}
|
|
34789
|
+
async function runSelfHealChain(ctx, seed, steps) {
|
|
34790
|
+
let last = seed;
|
|
34791
|
+
let totalCost = seed.estimatedCostUsd ?? 0;
|
|
34792
|
+
for (const step of steps) {
|
|
34793
|
+
try {
|
|
34794
|
+
const turn = await step.run(ctx);
|
|
34795
|
+
if (turn) {
|
|
34796
|
+
totalCost += turn.estimatedCostUsd ?? 0;
|
|
34797
|
+
last = turn;
|
|
34798
|
+
}
|
|
34799
|
+
} catch (err) {
|
|
34800
|
+
getSafeLogger()?.warn("self-heal", "step threw \u2014 skipping", { error: errorMessage(err) });
|
|
34801
|
+
}
|
|
34802
|
+
}
|
|
34803
|
+
return { ...last, estimatedCostUsd: totalCost };
|
|
34804
|
+
}
|
|
34805
|
+
var init_self_heal = __esm(() => {
|
|
34806
|
+
init_logger2();
|
|
34807
|
+
});
|
|
34808
|
+
|
|
34758
34809
|
// src/operations/plan-refine.ts
|
|
34759
34810
|
import { join as join21 } from "path";
|
|
34760
34811
|
function hasToken(text, tokens) {
|
|
@@ -34893,6 +34944,28 @@ async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
|
|
|
34893
34944
|
return prd;
|
|
34894
34945
|
return { ...prd, userStories: results.map((r) => r.story) };
|
|
34895
34946
|
}
|
|
34947
|
+
function verbatimSelfHealStep(builder) {
|
|
34948
|
+
return makeSelfHealStep({
|
|
34949
|
+
detect: (input) => readMissingVerbatimAcs(input),
|
|
34950
|
+
buildRepair: (missing, input) => builder.buildVerbatimRepair(missing, input.outputPath),
|
|
34951
|
+
log: {
|
|
34952
|
+
kind: "plan",
|
|
34953
|
+
message: "Refine dropped [verbatim] spec ACs \u2014 issuing one repair turn",
|
|
34954
|
+
meta: (input, missing) => ({ featureName: input.featureName, missingCount: missing.length })
|
|
34955
|
+
}
|
|
34956
|
+
});
|
|
34957
|
+
}
|
|
34958
|
+
function specDriftSelfHealStep(builder) {
|
|
34959
|
+
return makeSelfHealStep({
|
|
34960
|
+
detect: (input) => readSpecDriftViolations(input),
|
|
34961
|
+
buildRepair: (drifted, input) => builder.buildSpecDriftRepair(drifted, input.outputPath),
|
|
34962
|
+
log: {
|
|
34963
|
+
kind: "plan",
|
|
34964
|
+
message: "specGuard: spec-drift violations found \u2014 issuing one repair turn",
|
|
34965
|
+
meta: (input, drifted) => ({ featureName: input.featureName, violationCount: drifted.length })
|
|
34966
|
+
}
|
|
34967
|
+
});
|
|
34968
|
+
}
|
|
34896
34969
|
var _planRefineDeps, NEGATIVE_PATH_TOKENS, planRefineOp;
|
|
34897
34970
|
var init_plan_refine = __esm(() => {
|
|
34898
34971
|
init_retry();
|
|
@@ -34902,6 +34975,7 @@ var init_plan_refine = __esm(() => {
|
|
|
34902
34975
|
init_prd();
|
|
34903
34976
|
init_schema2();
|
|
34904
34977
|
init_prompts();
|
|
34978
|
+
init_self_heal();
|
|
34905
34979
|
init_verbatim_warn();
|
|
34906
34980
|
_planRefineDeps = {
|
|
34907
34981
|
readFile: async (path3) => {
|
|
@@ -34981,31 +35055,15 @@ ${outputFormat}`,
|
|
|
34981
35055
|
const specGuard = ctx.input.specGuard ?? false;
|
|
34982
35056
|
const turn1 = await ctx.sendWithParseRetry(initialPrompt);
|
|
34983
35057
|
const turn2 = await ctx.send(builder.buildRefineContinuation(ctx.input.outputPath, specGuard));
|
|
34984
|
-
|
|
34985
|
-
|
|
34986
|
-
|
|
34987
|
-
|
|
34988
|
-
|
|
34989
|
-
|
|
34990
|
-
|
|
34991
|
-
|
|
34992
|
-
|
|
34993
|
-
totalCost += turn3.estimatedCostUsd ?? 0;
|
|
34994
|
-
last = turn3;
|
|
34995
|
-
}
|
|
34996
|
-
if (specGuard) {
|
|
34997
|
-
const drifted = await readSpecDriftViolations(ctx.input);
|
|
34998
|
-
if (drifted.length > 0) {
|
|
34999
|
-
getSafeLogger()?.info("plan", "specGuard: spec-drift violations found \u2014 issuing one repair turn", {
|
|
35000
|
-
featureName: ctx.input.featureName,
|
|
35001
|
-
violationCount: drifted.length
|
|
35002
|
-
});
|
|
35003
|
-
const turn4 = await ctx.send(builder.buildSpecDriftRepair(drifted, ctx.input.outputPath));
|
|
35004
|
-
totalCost += turn4.estimatedCostUsd ?? 0;
|
|
35005
|
-
last = turn4;
|
|
35006
|
-
}
|
|
35007
|
-
}
|
|
35008
|
-
return { ...last, estimatedCostUsd: totalCost };
|
|
35058
|
+
const seed = {
|
|
35059
|
+
...turn2,
|
|
35060
|
+
estimatedCostUsd: (turn1.estimatedCostUsd ?? 0) + (turn2.estimatedCostUsd ?? 0)
|
|
35061
|
+
};
|
|
35062
|
+
const steps = [
|
|
35063
|
+
verbatimSelfHealStep(builder),
|
|
35064
|
+
...specGuard ? [specDriftSelfHealStep(builder)] : []
|
|
35065
|
+
];
|
|
35066
|
+
return runSelfHealChain(ctx, seed, steps);
|
|
35009
35067
|
},
|
|
35010
35068
|
parse(output, input) {
|
|
35011
35069
|
return validatePlanOutput(output, input.featureName, input.branchName);
|
|
@@ -36123,11 +36181,32 @@ function isStubTestContent(content) {
|
|
|
36123
36181
|
}
|
|
36124
36182
|
|
|
36125
36183
|
// src/operations/acceptance-generate.ts
|
|
36126
|
-
|
|
36184
|
+
function pathCorrectionStep() {
|
|
36185
|
+
return makeSelfHealStep({
|
|
36186
|
+
detect: async (input) => await _acceptanceGenerateDeps.fileExists(input.targetTestFilePath) ? [] : [input.targetTestFilePath],
|
|
36187
|
+
buildRepair: (_deviations, input) => new AcceptancePromptBuilder().buildPathCorrection(input.targetTestFilePath),
|
|
36188
|
+
log: {
|
|
36189
|
+
kind: "acceptance",
|
|
36190
|
+
message: "Acceptance test not found at target path \u2014 issuing one corrective turn",
|
|
36191
|
+
meta: (input) => ({ targetTestFilePath: input.targetTestFilePath })
|
|
36192
|
+
}
|
|
36193
|
+
});
|
|
36194
|
+
}
|
|
36195
|
+
var _acceptanceGenerateDeps, acceptanceGenerateOp;
|
|
36127
36196
|
var init_acceptance_generate = __esm(() => {
|
|
36128
36197
|
init_generator();
|
|
36129
36198
|
init_config();
|
|
36130
36199
|
init_prompts();
|
|
36200
|
+
init_self_heal();
|
|
36201
|
+
_acceptanceGenerateDeps = {
|
|
36202
|
+
fileExists: async (path4) => {
|
|
36203
|
+
try {
|
|
36204
|
+
return await Bun.file(path4).exists();
|
|
36205
|
+
} catch {
|
|
36206
|
+
return false;
|
|
36207
|
+
}
|
|
36208
|
+
}
|
|
36209
|
+
};
|
|
36131
36210
|
acceptanceGenerateOp = {
|
|
36132
36211
|
kind: "run",
|
|
36133
36212
|
name: "acceptance-generate",
|
|
@@ -36149,6 +36228,10 @@ var init_acceptance_generate = __esm(() => {
|
|
|
36149
36228
|
task: { id: "task", content: prompt, overridable: false }
|
|
36150
36229
|
};
|
|
36151
36230
|
},
|
|
36231
|
+
async hopBody(initialPrompt, ctx) {
|
|
36232
|
+
const turn1 = await ctx.sendWithParseRetry(initialPrompt);
|
|
36233
|
+
return runSelfHealChain(ctx, turn1, [pathCorrectionStep()]);
|
|
36234
|
+
},
|
|
36152
36235
|
parse(output, _input, _ctx) {
|
|
36153
36236
|
return { testCode: extractTestCode(output) };
|
|
36154
36237
|
},
|
|
@@ -40159,6 +40242,7 @@ var init_operations = __esm(() => {
|
|
|
40159
40242
|
init_call();
|
|
40160
40243
|
init_plan();
|
|
40161
40244
|
init_plan_refine();
|
|
40245
|
+
init_self_heal();
|
|
40162
40246
|
init_verbatim_warn();
|
|
40163
40247
|
init_decompose2();
|
|
40164
40248
|
init_build_hop_callback();
|
|
@@ -44729,11 +44813,22 @@ function buildTxtContent(entry) {
|
|
|
44729
44813
|
"",
|
|
44730
44814
|
"=== RESPONSE ===",
|
|
44731
44815
|
"",
|
|
44732
|
-
entry.response
|
|
44816
|
+
entry.response,
|
|
44817
|
+
...buildInteractionLines(entry.interactions)
|
|
44733
44818
|
];
|
|
44734
44819
|
return lines.join(`
|
|
44735
44820
|
`);
|
|
44736
44821
|
}
|
|
44822
|
+
function buildInteractionLines(interactions) {
|
|
44823
|
+
if (!interactions?.length)
|
|
44824
|
+
return [];
|
|
44825
|
+
const lines = ["", "=== INTERACTIONS ===", ""];
|
|
44826
|
+
for (const ix of interactions) {
|
|
44827
|
+
lines.push(`[turn ${ix.turnIndex}] Q: ${ix.question}`, ` A: ${ix.reply}`, "");
|
|
44828
|
+
}
|
|
44829
|
+
lines.pop();
|
|
44830
|
+
return lines;
|
|
44831
|
+
}
|
|
44737
44832
|
|
|
44738
44833
|
class PromptAuditor {
|
|
44739
44834
|
_queue = Promise.resolve();
|
|
@@ -45242,7 +45337,8 @@ function attachAuditSubscriber(bus, auditor, runId) {
|
|
|
45242
45337
|
...event.kind === "session-turn" && {
|
|
45243
45338
|
sessionId: event.protocolIds.sessionId ?? null,
|
|
45244
45339
|
recordId: event.protocolIds.recordId ?? null,
|
|
45245
|
-
turn: event.turn
|
|
45340
|
+
turn: event.turn,
|
|
45341
|
+
...event.interactions?.length ? { interactions: event.interactions } : {}
|
|
45246
45342
|
}
|
|
45247
45343
|
};
|
|
45248
45344
|
auditor.record(entry);
|
|
@@ -55155,7 +55251,8 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
|
|
|
55155
55251
|
break;
|
|
55156
55252
|
}
|
|
55157
55253
|
}
|
|
55158
|
-
const
|
|
55254
|
+
const postValidateFn = overrides?.postValidate ?? rectification.postValidate;
|
|
55255
|
+
const validated = postValidateFn ? await postValidateFn(findings, _validateCtx) : findings;
|
|
55159
55256
|
return { findings: validated, shortCircuited };
|
|
55160
55257
|
}
|
|
55161
55258
|
};
|
|
@@ -55325,7 +55422,8 @@ class ExecutionPlan {
|
|
|
55325
55422
|
strategies: this.state.nonBlockingFixStrategies ?? [],
|
|
55326
55423
|
excludePhaseKinds: nonBlockingExcludePhases(),
|
|
55327
55424
|
extraRevalidationKinds: nonBlockingExtraPhases(advCfg),
|
|
55328
|
-
maxAttempts
|
|
55425
|
+
maxAttempts,
|
|
55426
|
+
postValidate: this.state.nonBlockingFixPostValidate
|
|
55329
55427
|
})
|
|
55330
55428
|
});
|
|
55331
55429
|
}
|
|
@@ -55421,9 +55519,10 @@ class StoryOrchestratorBuilder {
|
|
|
55421
55519
|
this.state.rectification = opts;
|
|
55422
55520
|
return this;
|
|
55423
55521
|
}
|
|
55424
|
-
addNonBlockingFix(cfg, strategies) {
|
|
55522
|
+
addNonBlockingFix(cfg, strategies, postValidate) {
|
|
55425
55523
|
this.state.nonBlockingFix = cfg;
|
|
55426
55524
|
this.state.nonBlockingFixStrategies = strategies;
|
|
55525
|
+
this.state.nonBlockingFixPostValidate = postValidate;
|
|
55427
55526
|
return this;
|
|
55428
55527
|
}
|
|
55429
55528
|
build(ctx, opts = {}) {
|
|
@@ -55553,10 +55652,10 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
55553
55652
|
if (inputs.adversarialReview) {
|
|
55554
55653
|
builder.addAdversarialReview(inputs.adversarialReview);
|
|
55555
55654
|
}
|
|
55655
|
+
const packageDir = join47(ctx.packageDir, story.workdir ?? "");
|
|
55656
|
+
const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
|
|
55556
55657
|
if (shouldRunRectification(config2) && inputs.rectification) {
|
|
55557
55658
|
const sink = makeDeclarationSink();
|
|
55558
|
-
const packageDir = join47(ctx.packageDir, story.workdir ?? "");
|
|
55559
|
-
const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
|
|
55560
55659
|
const strategies = [];
|
|
55561
55660
|
const pkgQuality = ctx.packageView.select(qualityConfigSelector).quality;
|
|
55562
55661
|
if (pkgQuality?.commands?.lintFix || pkgQuality?.commands?.lintFixScoped) {
|
|
@@ -55612,7 +55711,22 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
55612
55711
|
}), makeAutofixTestWriterStrategy(story, config2, nbSink));
|
|
55613
55712
|
}
|
|
55614
55713
|
nbStrategies.push(makeFullSuiteRectifyStrategy(story, config2, nbSink));
|
|
55615
|
-
|
|
55714
|
+
const nbPostValidate = async (findings, _validateCtx) => {
|
|
55715
|
+
if (nbSink.testEdits.length === 0 && nbSink.mockHandoffs.length === 0)
|
|
55716
|
+
return findings;
|
|
55717
|
+
const pendingMock = nbSink.mockHandoffs.map((h) => ({
|
|
55718
|
+
reason: "mock_structure",
|
|
55719
|
+
file: h.files[0] ?? "",
|
|
55720
|
+
files: h.files,
|
|
55721
|
+
reasonDetail: h.reasonDetail
|
|
55722
|
+
}));
|
|
55723
|
+
const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir);
|
|
55724
|
+
nbSink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
|
|
55725
|
+
const allDeclarations = [...nbSink.testEdits, ...valid];
|
|
55726
|
+
nbSink.testEdits = [];
|
|
55727
|
+
return applyTestEditDeclarations(findings, allDeclarations, story, invalid);
|
|
55728
|
+
};
|
|
55729
|
+
builder.addNonBlockingFix(nbf, nbStrategies, nbPostValidate);
|
|
55616
55730
|
}
|
|
55617
55731
|
return builder.build(ctx, { isThreeSession });
|
|
55618
55732
|
}
|
|
@@ -60211,7 +60325,7 @@ var package_default;
|
|
|
60211
60325
|
var init_package = __esm(() => {
|
|
60212
60326
|
package_default = {
|
|
60213
60327
|
name: "@nathapp/nax",
|
|
60214
|
-
version: "0.70.0-canary.
|
|
60328
|
+
version: "0.70.0-canary.4",
|
|
60215
60329
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
60216
60330
|
type: "module",
|
|
60217
60331
|
bin: {
|
|
@@ -60306,8 +60420,8 @@ var init_version = __esm(() => {
|
|
|
60306
60420
|
NAX_VERSION = package_default.version;
|
|
60307
60421
|
NAX_COMMIT = (() => {
|
|
60308
60422
|
try {
|
|
60309
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
60310
|
-
return "
|
|
60423
|
+
if (/^[0-9a-f]{6,10}$/.test("e2a854e7"))
|
|
60424
|
+
return "e2a854e7";
|
|
60311
60425
|
} catch {}
|
|
60312
60426
|
try {
|
|
60313
60427
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|