@nathapp/nax 0.75.2 → 0.75.3
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 +1487 -541
- package/flows/nax-finish/exec.ts +78 -29
- package/flows/nax-finish/steps/quality.ts +11 -2
- package/flows/nax-finish/steps/result.ts +9 -1
- package/package.json +3 -2
package/dist/nax.js
CHANGED
|
@@ -17294,7 +17294,7 @@ var HeadersSchema, ReporterEventSchema, WebhookReporterConfigSchema, OtelReporte
|
|
|
17294
17294
|
var init_schemas_reporters = __esm(() => {
|
|
17295
17295
|
init_zod();
|
|
17296
17296
|
HeadersSchema = exports_external.record(exports_external.string(), exports_external.string()).default({});
|
|
17297
|
-
ReporterEventSchema = exports_external.enum(["onRunStart", "onStoryComplete", "onRunEnd"]);
|
|
17297
|
+
ReporterEventSchema = exports_external.enum(["onRunStart", "onStoryComplete", "onRunEnd", "onPhaseStart", "onPhaseComplete"]);
|
|
17298
17298
|
WebhookReporterConfigSchema = exports_external.object({
|
|
17299
17299
|
enabled: exports_external.boolean().default(false),
|
|
17300
17300
|
url: exports_external.string().url().optional(),
|
|
@@ -17311,12 +17311,23 @@ var init_schemas_reporters = __esm(() => {
|
|
|
17311
17311
|
endpoint: exports_external.string().url().optional(),
|
|
17312
17312
|
headers: HeadersSchema,
|
|
17313
17313
|
serviceName: exports_external.string().default("nax"),
|
|
17314
|
-
timeoutMs: exports_external.number().int().positive().default(5000)
|
|
17314
|
+
timeoutMs: exports_external.number().int().positive().default(5000),
|
|
17315
|
+
detail: exports_external.enum(["counts", "verbose"]).default("counts"),
|
|
17316
|
+
heartbeatIntervalMs: exports_external.number().int().nonnegative().default(1e4),
|
|
17317
|
+
maxBatchSize: exports_external.number().int().positive().default(64),
|
|
17318
|
+
flushIntervalMs: exports_external.number().int().positive().default(5000),
|
|
17319
|
+
maxQueueSize: exports_external.number().int().positive().default(2048),
|
|
17320
|
+
phases: exports_external.array(exports_external.string()).optional()
|
|
17315
17321
|
}).default({
|
|
17316
17322
|
enabled: false,
|
|
17317
17323
|
headers: {},
|
|
17318
17324
|
serviceName: "nax",
|
|
17319
|
-
timeoutMs: 5000
|
|
17325
|
+
timeoutMs: 5000,
|
|
17326
|
+
detail: "counts",
|
|
17327
|
+
heartbeatIntervalMs: 1e4,
|
|
17328
|
+
maxBatchSize: 64,
|
|
17329
|
+
flushIntervalMs: 5000,
|
|
17330
|
+
maxQueueSize: 2048
|
|
17320
17331
|
});
|
|
17321
17332
|
ReportersConfigSchema = exports_external.object({
|
|
17322
17333
|
webhook: WebhookReporterConfigSchema,
|
|
@@ -17331,7 +17342,12 @@ var init_schemas_reporters = __esm(() => {
|
|
|
17331
17342
|
enabled: false,
|
|
17332
17343
|
headers: {},
|
|
17333
17344
|
serviceName: "nax",
|
|
17334
|
-
timeoutMs: 5000
|
|
17345
|
+
timeoutMs: 5000,
|
|
17346
|
+
detail: "counts",
|
|
17347
|
+
heartbeatIntervalMs: 1e4,
|
|
17348
|
+
maxBatchSize: 64,
|
|
17349
|
+
flushIntervalMs: 5000,
|
|
17350
|
+
maxQueueSize: 2048
|
|
17335
17351
|
}
|
|
17336
17352
|
});
|
|
17337
17353
|
});
|
|
@@ -18222,6 +18238,10 @@ function formatAdvisorySummary(findings, options) {
|
|
|
18222
18238
|
if (coverageGapCount > 0) {
|
|
18223
18239
|
lines.push(c.gray(` ${coverageGapCount} of ${findings.length} were coverage-gap demotions (recurred past the block limit \u2014 candidate for spec/AC review)`));
|
|
18224
18240
|
}
|
|
18241
|
+
const noActionCount = findings.filter((f) => f.actionRequired === false).length;
|
|
18242
|
+
if (noActionCount > 0) {
|
|
18243
|
+
lines.push(c.gray(` ${noActionCount} of ${findings.length} asked for no change (compliance notes \u2014 the best-effort fix pass skipped them)`));
|
|
18244
|
+
}
|
|
18225
18245
|
lines.push(c.yellow("\u2500".repeat(60)));
|
|
18226
18246
|
for (const f of sorted) {
|
|
18227
18247
|
const location = f.file ? `${f.file}${f.line ? `:${f.line}` : ""}` : undefined;
|
|
@@ -18230,7 +18250,8 @@ function formatAdvisorySummary(findings, options) {
|
|
|
18230
18250
|
f.storyId ?? "unknown",
|
|
18231
18251
|
location,
|
|
18232
18252
|
f.category,
|
|
18233
|
-
f.coverageGap ? "coverage-gap" : undefined
|
|
18253
|
+
f.coverageGap ? "coverage-gap" : undefined,
|
|
18254
|
+
f.actionRequired === false ? "no-action" : undefined
|
|
18234
18255
|
].filter((v) => typeof v === "string" && v.length > 0);
|
|
18235
18256
|
lines.push(` ${c.gray(parts.join(" \xB7 "))}`);
|
|
18236
18257
|
lines.push(` ${f.issue}`);
|
|
@@ -33112,6 +33133,7 @@ Respond with ONLY a JSON object \u2014 no preamble, no explanation outside the J
|
|
|
33112
33133
|
"acIndex": 2,
|
|
33113
33134
|
"scopeQuote": "<out-of-scope findings ONLY: verbatim substring of one Out of Scope entry>",
|
|
33114
33135
|
"scopeIndex": 1,
|
|
33136
|
+
"actionRequired": true,
|
|
33115
33137
|
"verifiedBy": {
|
|
33116
33138
|
"command": "command used to inspect the current codebase",
|
|
33117
33139
|
"file": "relative/path/to/file.ts",
|
|
@@ -33165,6 +33187,11 @@ A finding about code that crossed one of these boundaries must NOT cite an AC \u
|
|
|
33165
33187
|
- When the boundary is only a description "Out:" bullet, quote it in \`issue\` and leave \`scopeQuote\`/\`scopeIndex\` unset.
|
|
33166
33188
|
- Emit scope-violation findings as \`"warning"\` \u2014 never \`"error"\`. Reporting the boundary is the goal; it does not block the story.
|
|
33167
33189
|
|
|
33190
|
+
**Do not report compliance as a finding:**
|
|
33191
|
+
A finding is a request for a change. If you inspected something and the code was CORRECT \u2014 it honoured an out-of-scope boundary, satisfied a convention, handled the edge case \u2014 that is not a finding. Say it in \`passed\`, not in \`findings\`. Emitting "this is correct per Out of Scope #10 / no action needed" as a finding causes an automated fix pass to be dispatched against code that needs no fix; one such report edited working code and broke a test.
|
|
33192
|
+
|
|
33193
|
+
If you judge a no-change note genuinely worth recording, set \`actionRequired: false\` on it. Every finding that asks for a change must leave \`actionRequired\` unset or \`true\`. Never pair \`actionRequired: false\` with a \`suggestion\` that requests an edit \u2014 pick one.
|
|
33194
|
+
|
|
33168
33195
|
Never use \`acIndex: 0\`; \`acIndex\` is 1-based (first AC bullet = 1). The same applies to \`scopeIndex\`.
|
|
33169
33196
|
|
|
33170
33197
|
If you cannot find an AC that names the **specific symbol** in your finding, downgrade to \`"info"\` or \`"warning"\`. A finding dropped by the validator is worse than one correctly classified as advisory.`, AdversarialReviewPromptBuilder;
|
|
@@ -35887,6 +35914,7 @@ function toAdversarialReviewFindings(findings, opts = {}) {
|
|
|
35887
35914
|
message: f.issue,
|
|
35888
35915
|
suggestion: f.suggestion,
|
|
35889
35916
|
fixTarget: resolveFixTarget({ base: categoryToFixTarget(f.category), file: f.file, isTestFile: opts.isTestFile }),
|
|
35917
|
+
...f.actionRequired === false ? { actionRequired: false } : {},
|
|
35890
35918
|
meta: Object.keys(metaExtras).length > 0 ? metaExtras : undefined
|
|
35891
35919
|
};
|
|
35892
35920
|
});
|
|
@@ -36826,11 +36854,11 @@ var init_adversarial_review = __esm(() => {
|
|
|
36826
36854
|
throw new ParseValidationError("[adversarial-review] parse failed: invalid JSON shape");
|
|
36827
36855
|
},
|
|
36828
36856
|
async verify(parsed, input, _verifyCtx) {
|
|
36857
|
+
const threshold = input.blockingThreshold ?? "error";
|
|
36829
36858
|
if (parsed.failOpen || parsed.looksLikeFail)
|
|
36830
|
-
return parsed;
|
|
36859
|
+
return { ...parsed, blockingThreshold: threshold };
|
|
36831
36860
|
if (parsed.findings.length === 0)
|
|
36832
|
-
return parsed;
|
|
36833
|
-
const threshold = input.blockingThreshold ?? "error";
|
|
36861
|
+
return { ...parsed, blockingThreshold: threshold };
|
|
36834
36862
|
const findings = parsed.findings;
|
|
36835
36863
|
const substantiated = await substantiateAdversarialFindings({
|
|
36836
36864
|
findings,
|
|
@@ -36877,6 +36905,7 @@ var init_adversarial_review = __esm(() => {
|
|
|
36877
36905
|
return {
|
|
36878
36906
|
...parsed,
|
|
36879
36907
|
passed,
|
|
36908
|
+
blockingThreshold: threshold,
|
|
36880
36909
|
modelPassed: parsed.passed,
|
|
36881
36910
|
findings: accepted,
|
|
36882
36911
|
normalizedFindings: toAdversarialReviewFindings(blocking, { isTestFile: testFileMatch }),
|
|
@@ -39901,9 +39930,29 @@ var init_apply_test_edit_declarations = __esm(() => {
|
|
|
39901
39930
|
});
|
|
39902
39931
|
|
|
39903
39932
|
// src/operations/validate-mock-structure-files.ts
|
|
39904
|
-
import { join as join23 } from "path";
|
|
39905
|
-
|
|
39906
|
-
|
|
39933
|
+
import { isAbsolute as isAbsolute9, join as join23, relative as relative9 } from "path";
|
|
39934
|
+
function resolutionCandidates(file3, packageDir, repoRoot) {
|
|
39935
|
+
if (isAbsolute9(file3))
|
|
39936
|
+
return [file3];
|
|
39937
|
+
const viaPackageDir = join23(packageDir, file3);
|
|
39938
|
+
if (repoRoot === undefined)
|
|
39939
|
+
return [viaPackageDir];
|
|
39940
|
+
const viaRepoRoot = join23(repoRoot, file3);
|
|
39941
|
+
return viaRepoRoot === viaPackageDir ? [viaPackageDir] : [viaPackageDir, viaRepoRoot];
|
|
39942
|
+
}
|
|
39943
|
+
async function resolvePackageRelative(file3, opts) {
|
|
39944
|
+
for (const candidate of resolutionCandidates(file3, opts.packageDir, opts.repoRoot)) {
|
|
39945
|
+
if (!await opts.fileExists(candidate))
|
|
39946
|
+
continue;
|
|
39947
|
+
const rel = relative9(opts.packageDir, candidate);
|
|
39948
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute9(rel))
|
|
39949
|
+
return null;
|
|
39950
|
+
return rel;
|
|
39951
|
+
}
|
|
39952
|
+
return null;
|
|
39953
|
+
}
|
|
39954
|
+
async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, opts) {
|
|
39955
|
+
const fileExists = opts?.fileExists ?? defaultFileExists;
|
|
39907
39956
|
const valid = [];
|
|
39908
39957
|
const invalid = [];
|
|
39909
39958
|
for (const d of declarations) {
|
|
@@ -39914,13 +39963,16 @@ async function validateMockStructureFiles(declarations, resolvedTestPatterns, pa
|
|
|
39914
39963
|
const files = d.files ?? [d.file];
|
|
39915
39964
|
let allValid = true;
|
|
39916
39965
|
for (const file3 of files) {
|
|
39917
|
-
const
|
|
39918
|
-
|
|
39919
|
-
|
|
39966
|
+
const packageRelative = await resolvePackageRelative(file3, {
|
|
39967
|
+
packageDir,
|
|
39968
|
+
repoRoot: opts?.repoRoot,
|
|
39969
|
+
fileExists
|
|
39970
|
+
});
|
|
39971
|
+
if (packageRelative === null) {
|
|
39920
39972
|
allValid = false;
|
|
39921
39973
|
break;
|
|
39922
39974
|
}
|
|
39923
|
-
const matchesPattern = resolvedTestPatterns.regex.some((re) => re.test(
|
|
39975
|
+
const matchesPattern = resolvedTestPatterns.regex.some((re) => re.test(packageRelative));
|
|
39924
39976
|
if (!matchesPattern) {
|
|
39925
39977
|
allValid = false;
|
|
39926
39978
|
break;
|
|
@@ -41458,7 +41510,7 @@ var init_mutation = __esm(() => {
|
|
|
41458
41510
|
});
|
|
41459
41511
|
|
|
41460
41512
|
// src/operations/mutation-check.ts
|
|
41461
|
-
import { isAbsolute as
|
|
41513
|
+
import { isAbsolute as isAbsolute10, join as join24 } from "path";
|
|
41462
41514
|
var _mutationCheckDeps, mutationCheckOp;
|
|
41463
41515
|
var init_mutation_check = __esm(() => {
|
|
41464
41516
|
init_config();
|
|
@@ -41501,7 +41553,7 @@ var init_mutation_check = __esm(() => {
|
|
|
41501
41553
|
}
|
|
41502
41554
|
const changedFiles = await deps.getChangedNonTestFiles(input.workdir, input.storyGitRef, input.packagePrefix, [...input.resolvedTestPatterns.regex], undefined, input.repoRoot);
|
|
41503
41555
|
const anchor = input.repoRoot ?? input.workdir;
|
|
41504
|
-
const absoluteChangedFiles = changedFiles.map((f) =>
|
|
41556
|
+
const absoluteChangedFiles = changedFiles.map((f) => isAbsolute10(f) ? f : join24(anchor, f));
|
|
41505
41557
|
const survivors = [];
|
|
41506
41558
|
const mutants = [];
|
|
41507
41559
|
for (const file3 of absoluteChangedFiles) {
|
|
@@ -41626,6 +41678,31 @@ var init_operations = __esm(() => {
|
|
|
41626
41678
|
init_mutation_check();
|
|
41627
41679
|
});
|
|
41628
41680
|
|
|
41681
|
+
// src/findings/cycle-retirement.ts
|
|
41682
|
+
function createDeclineLedger() {
|
|
41683
|
+
const declinedByStrategy = new Map;
|
|
41684
|
+
const hasDeclined = (strategyName, finding) => declinedByStrategy.get(strategyName)?.has(findingKey(finding)) === true;
|
|
41685
|
+
const isRetiredFor = (strategy, findings) => {
|
|
41686
|
+
const claimed = findings.filter((f) => strategy.appliesTo(f));
|
|
41687
|
+
return claimed.length > 0 && claimed.every((f) => hasDeclined(strategy.name, f));
|
|
41688
|
+
};
|
|
41689
|
+
return {
|
|
41690
|
+
recordDeclined(strategy, dispatched) {
|
|
41691
|
+
const declined = declinedByStrategy.get(strategy.name) ?? new Set;
|
|
41692
|
+
for (const f of dispatched.filter((x) => strategy.appliesTo(x)))
|
|
41693
|
+
declined.add(findingKey(f));
|
|
41694
|
+
declinedByStrategy.set(strategy.name, declined);
|
|
41695
|
+
},
|
|
41696
|
+
isRetiredFor,
|
|
41697
|
+
retiredNames(strategies, findings) {
|
|
41698
|
+
return strategies.filter((s) => isRetiredFor(s, findings)).map((s) => s.name);
|
|
41699
|
+
}
|
|
41700
|
+
};
|
|
41701
|
+
}
|
|
41702
|
+
var init_cycle_retirement = __esm(() => {
|
|
41703
|
+
init_types6();
|
|
41704
|
+
});
|
|
41705
|
+
|
|
41629
41706
|
// src/findings/cycle.ts
|
|
41630
41707
|
function normalizeValidateResult(r) {
|
|
41631
41708
|
return Array.isArray(r) ? { findings: r, shortCircuited: false } : r;
|
|
@@ -41695,17 +41772,18 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41695
41772
|
const storyId = ctx.storyId;
|
|
41696
41773
|
const packageDir = ctx.packageDir;
|
|
41697
41774
|
let totalCostUsd = 0;
|
|
41698
|
-
const
|
|
41775
|
+
const declines = createDeclineLedger();
|
|
41699
41776
|
let unresolvedDetail;
|
|
41700
41777
|
const finish = (result) => unresolvedDetail !== undefined && result.unresolvedDetail === undefined ? { ...result, unresolvedDetail } : result;
|
|
41701
41778
|
for (;; ) {
|
|
41702
41779
|
if (cycle.findings.length === 0 && cycle.verdict === undefined) {
|
|
41703
41780
|
return { iterations: cycle.iterations, finalFindings: [], exitReason: "resolved", costUsd: totalCostUsd };
|
|
41704
41781
|
}
|
|
41705
|
-
const selectable = cycle.strategies.filter((s) => !
|
|
41782
|
+
const selectable = cycle.strategies.filter((s) => !declines.isRetiredFor(s, cycle.findings));
|
|
41706
41783
|
const active = selectActiveStrategies(selectable, cycle.findings, cycle.verdict);
|
|
41707
41784
|
if (active.length === 0) {
|
|
41708
41785
|
const orphanSources = [...new Set(cycle.findings.map((f) => f.source))];
|
|
41786
|
+
const retiredStrategies = declines.retiredNames(cycle.strategies, cycle.findings);
|
|
41709
41787
|
logger?.warn("findings.cycle", "cycle exited \u2014 no matching strategy (orphaned findings)", {
|
|
41710
41788
|
storyId,
|
|
41711
41789
|
packageDir,
|
|
@@ -41713,7 +41791,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41713
41791
|
reason: "no-strategy",
|
|
41714
41792
|
findingsCount: cycle.findings.length,
|
|
41715
41793
|
orphanSources,
|
|
41716
|
-
...
|
|
41794
|
+
...retiredStrategies.length > 0 ? { retiredStrategies } : {}
|
|
41717
41795
|
});
|
|
41718
41796
|
return finish({
|
|
41719
41797
|
iterations: cycle.iterations,
|
|
@@ -41784,7 +41862,11 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41784
41862
|
for (const strategy of group) {
|
|
41785
41863
|
const relevantFindings = findingsBefore.filter((f) => strategy.appliesTo(f));
|
|
41786
41864
|
const input = strategy.buildInput(relevantFindings, cycle.iterations, ctx);
|
|
41787
|
-
const
|
|
41865
|
+
const fixCtx = {
|
|
41866
|
+
...ctx,
|
|
41867
|
+
fixStrategy: { name: strategy.name, findingsBefore: findingsBefore.length }
|
|
41868
|
+
};
|
|
41869
|
+
const output = await doCallOp(fixCtx, strategy.fixOp, input);
|
|
41788
41870
|
const extracted = await (strategy.extractApplied?.(output, input) ?? {});
|
|
41789
41871
|
fixesApplied.push({
|
|
41790
41872
|
strategyName: strategy.name,
|
|
@@ -41799,8 +41881,11 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
|
|
|
41799
41881
|
if (unresolvedFas.length > 0) {
|
|
41800
41882
|
const firstUnresolved = unresolvedFas[0];
|
|
41801
41883
|
unresolvedDetail = firstUnresolved.unresolved;
|
|
41802
|
-
for (const fa of unresolvedFas)
|
|
41803
|
-
|
|
41884
|
+
for (const fa of unresolvedFas) {
|
|
41885
|
+
const strategy = group.find((s) => s.name === fa.strategyName);
|
|
41886
|
+
if (strategy)
|
|
41887
|
+
declines.recordDeclined(strategy, findingsBefore);
|
|
41888
|
+
}
|
|
41804
41889
|
const allGaveUp = unresolvedFas.length === fixesApplied.length;
|
|
41805
41890
|
if (allGaveUp) {
|
|
41806
41891
|
const finishedAt2 = now();
|
|
@@ -42015,6 +42100,7 @@ var _cycleDeps;
|
|
|
42015
42100
|
var init_cycle = __esm(() => {
|
|
42016
42101
|
init_logger2();
|
|
42017
42102
|
init_operations();
|
|
42103
|
+
init_cycle_retirement();
|
|
42018
42104
|
init_types6();
|
|
42019
42105
|
_cycleDeps = {
|
|
42020
42106
|
callOp,
|
|
@@ -42346,13 +42432,13 @@ var init_finding_projection = __esm(() => {
|
|
|
42346
42432
|
});
|
|
42347
42433
|
|
|
42348
42434
|
// src/review/prepare-inputs.ts
|
|
42349
|
-
import { relative as
|
|
42435
|
+
import { relative as relative10, sep } from "path";
|
|
42350
42436
|
function derivePackageDirs(workdir, projectDir) {
|
|
42351
42437
|
const repoRoot = projectDir ?? workdir;
|
|
42352
42438
|
const packageDir = workdir !== repoRoot ? workdir : undefined;
|
|
42353
42439
|
let packageDirRelative;
|
|
42354
42440
|
if (projectDir && workdir !== projectDir) {
|
|
42355
|
-
const rel =
|
|
42441
|
+
const rel = relative10(projectDir, workdir);
|
|
42356
42442
|
if (rel !== ".." && !rel.startsWith(`..${sep}`)) {
|
|
42357
42443
|
packageDirRelative = rel && rel !== "." ? rel : undefined;
|
|
42358
42444
|
}
|
|
@@ -42493,7 +42579,7 @@ var package_default;
|
|
|
42493
42579
|
var init_package = __esm(() => {
|
|
42494
42580
|
package_default = {
|
|
42495
42581
|
name: "@nathapp/nax",
|
|
42496
|
-
version: "0.75.
|
|
42582
|
+
version: "0.75.3",
|
|
42497
42583
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
42498
42584
|
type: "module",
|
|
42499
42585
|
bin: {
|
|
@@ -42504,9 +42590,10 @@ var init_package = __esm(() => {
|
|
|
42504
42590
|
dev: "bun run bin/nax.ts",
|
|
42505
42591
|
build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
|
|
42506
42592
|
typecheck: "bun x tsc --noEmit && bun x tsc --noEmit -p tsconfig.contracts.json",
|
|
42507
|
-
lint: "bun x biome check src/ bin/ flows/ && bun run check:no-real-global-nax && bun run check:alias-internals && bun run check:deep-relatives && bun run check:nax-error && bun run check:logger-storyid && bun run check:log-format-layering && bun run check:file-sizes",
|
|
42593
|
+
lint: "bun x biome check src/ bin/ flows/ && bun run check:flows-no-bun && bun run check:no-real-global-nax && bun run check:alias-internals && bun run check:deep-relatives && bun run check:nax-error && bun run check:logger-storyid && bun run check:log-format-layering && bun run check:file-sizes",
|
|
42508
42594
|
"lint:json": "bun x biome check src/ bin/ flows/ --reporter json && bun run check:nax-error 1>&2 && bun run check:logger-storyid 1>&2",
|
|
42509
42595
|
"lint:fix": "bun x biome check --write src/ bin/ flows/",
|
|
42596
|
+
"check:flows-no-bun": "bun run scripts/check-flows-no-bun.ts",
|
|
42510
42597
|
"check:no-real-global-nax": "bun run scripts/check-no-real-global-nax.ts",
|
|
42511
42598
|
"check:alias-internals": "bun run scripts/check-alias-internals.ts",
|
|
42512
42599
|
"check:deep-relatives": "bun run scripts/check-deep-relatives.ts",
|
|
@@ -42596,8 +42683,8 @@ var init_version = __esm(() => {
|
|
|
42596
42683
|
NAX_VERSION = package_default.version;
|
|
42597
42684
|
NAX_COMMIT = (() => {
|
|
42598
42685
|
try {
|
|
42599
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
42600
|
-
return "
|
|
42686
|
+
if (/^[0-9a-f]{6,10}$/.test("ae716fd4"))
|
|
42687
|
+
return "ae716fd4";
|
|
42601
42688
|
} catch {}
|
|
42602
42689
|
try {
|
|
42603
42690
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -42686,7 +42773,8 @@ function toAdvisorySummaryEntries(entry) {
|
|
|
42686
42773
|
file: f.file,
|
|
42687
42774
|
line: f.line,
|
|
42688
42775
|
issue: f.issue ?? "(no description)",
|
|
42689
|
-
coverageGap: f.meta?.coverageGap === true ? true : undefined
|
|
42776
|
+
coverageGap: f.meta?.coverageGap === true ? true : undefined,
|
|
42777
|
+
actionRequired: f.actionRequired === false ? false : undefined
|
|
42690
42778
|
};
|
|
42691
42779
|
});
|
|
42692
42780
|
}
|
|
@@ -43420,7 +43508,7 @@ var init_language_commands = __esm(() => {
|
|
|
43420
43508
|
});
|
|
43421
43509
|
|
|
43422
43510
|
// src/review/scoped-lint.ts
|
|
43423
|
-
import { join as join27, relative as
|
|
43511
|
+
import { join as join27, relative as relative11 } from "path";
|
|
43424
43512
|
function shellQuotePath4(path6) {
|
|
43425
43513
|
return `'${path6.replaceAll("'", "'\\''")}'`;
|
|
43426
43514
|
}
|
|
@@ -43456,7 +43544,7 @@ async function listChangedFiles(workdir, baseRef) {
|
|
|
43456
43544
|
function inferActivePackageDir(workdir, projectDir) {
|
|
43457
43545
|
if (!projectDir)
|
|
43458
43546
|
return;
|
|
43459
|
-
const rel = normalizePath3(
|
|
43547
|
+
const rel = normalizePath3(relative11(projectDir, workdir));
|
|
43460
43548
|
if (!rel || rel === "." || rel.startsWith(".."))
|
|
43461
43549
|
return;
|
|
43462
43550
|
return rel;
|
|
@@ -44610,6 +44698,7 @@ var init_runner2 = __esm(() => {
|
|
|
44610
44698
|
// src/review/index.ts
|
|
44611
44699
|
var init_review = __esm(() => {
|
|
44612
44700
|
init_semantic_helpers();
|
|
44701
|
+
init_adversarial_helpers();
|
|
44613
44702
|
init_category_fix_target();
|
|
44614
44703
|
init_finding_filters();
|
|
44615
44704
|
init_ac_quote_validator();
|
|
@@ -44894,7 +44983,10 @@ REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses,
|
|
|
44894
44983
|
Rules:
|
|
44895
44984
|
- Do NOT make any edits yourself; the test-writer will fulfill.
|
|
44896
44985
|
- Do NOT also emit \`UNRESOLVED:\` in the same turn \u2014 this declaration IS the handoff.
|
|
44897
|
-
- FILES must list real test files. Each path must exist and be a test file
|
|
44986
|
+
- FILES must list real test files. Each path must exist and be a test file.
|
|
44987
|
+
- Write each path exactly as it appears in the findings above (repository-relative).
|
|
44988
|
+
Paths that resolve under neither the repository root nor the package directory are
|
|
44989
|
+
rejected and the handoff is dropped \u2014 the findings then have no owner.`, SINGLE_SESSION_PERMIT_HEADLINE = "You authored these tests in the same session as the implementation, so you MAY edit test files \u2014 but ONLY to resolve a genuine contradiction between a test and this story's acceptance criteria (or between two acceptance criteria). NEVER weaken, delete, loosen, or skip a test merely to make it pass. See the test-edit guidance appended below.", SINGLE_SESSION_TEST_EDIT_POLICY = `
|
|
44898
44990
|
|
|
44899
44991
|
## Test-edit guidance (single-session implementer)
|
|
44900
44992
|
|
|
@@ -48676,7 +48768,7 @@ var init_pid_registry = __esm(() => {
|
|
|
48676
48768
|
// src/session/manager-deps.ts
|
|
48677
48769
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
48678
48770
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
48679
|
-
import { isAbsolute as
|
|
48771
|
+
import { isAbsolute as isAbsolute11, join as join30, relative as relative12, sep as sep2 } from "path";
|
|
48680
48772
|
function resolveProjectDirFromScratchDir(scratchDir) {
|
|
48681
48773
|
const marker = `${sep2}.nax${sep2}features${sep2}`;
|
|
48682
48774
|
const markerIdx = scratchDir.lastIndexOf(marker);
|
|
@@ -48688,7 +48780,7 @@ function resolveProjectDirFromScratchDir(scratchDir) {
|
|
|
48688
48780
|
return;
|
|
48689
48781
|
}
|
|
48690
48782
|
function toProjectRelativePath(projectDir, pathValue) {
|
|
48691
|
-
const relativePath =
|
|
48783
|
+
const relativePath = isAbsolute11(pathValue) ? relative12(projectDir, pathValue) : pathValue;
|
|
48692
48784
|
return relativePath === "" ? "." : relativePath;
|
|
48693
48785
|
}
|
|
48694
48786
|
var _sessionManagerDeps;
|
|
@@ -50198,7 +50290,7 @@ var init_windsurf = __esm(() => {
|
|
|
50198
50290
|
|
|
50199
50291
|
// src/context/generator.ts
|
|
50200
50292
|
import { existsSync as existsSync9 } from "fs";
|
|
50201
|
-
import { join as join33, relative as
|
|
50293
|
+
import { join as join33, relative as relative13 } from "path";
|
|
50202
50294
|
async function loadContextContent(options, config2) {
|
|
50203
50295
|
if (!_generatorDeps.existsSync(options.contextPath)) {
|
|
50204
50296
|
throw new Error(`Context file not found: ${options.contextPath}`);
|
|
@@ -50326,7 +50418,7 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
50326
50418
|
}
|
|
50327
50419
|
async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
|
|
50328
50420
|
const resolvedRepoRoot = repoRoot ?? packageDir;
|
|
50329
|
-
const relativePkgPath =
|
|
50421
|
+
const relativePkgPath = relative13(resolvedRepoRoot, packageDir);
|
|
50330
50422
|
const contextPath = join33(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
|
|
50331
50423
|
if (!_generatorDeps.existsSync(contextPath)) {
|
|
50332
50424
|
return [
|
|
@@ -54173,7 +54265,7 @@ var init_checks_blockers = __esm(() => {
|
|
|
54173
54265
|
|
|
54174
54266
|
// src/precheck/checks-warnings.ts
|
|
54175
54267
|
import { existsSync as existsSync14 } from "fs";
|
|
54176
|
-
import { isAbsolute as
|
|
54268
|
+
import { isAbsolute as isAbsolute12 } from "path";
|
|
54177
54269
|
async function checkClaudeMdExists(workdir) {
|
|
54178
54270
|
const claudeMdPath = `${workdir}/CLAUDE.md`;
|
|
54179
54271
|
const passed = existsSync14(claudeMdPath);
|
|
@@ -54308,7 +54400,7 @@ async function checkPromptOverrideFiles(config2, workdir) {
|
|
|
54308
54400
|
}
|
|
54309
54401
|
async function checkHomeEnvValid() {
|
|
54310
54402
|
const home = process.env.HOME ?? "";
|
|
54311
|
-
const passed = home !== "" &&
|
|
54403
|
+
const passed = home !== "" && isAbsolute12(home);
|
|
54312
54404
|
return {
|
|
54313
54405
|
name: "home-env-valid",
|
|
54314
54406
|
tier: "warning",
|
|
@@ -57012,6 +57104,79 @@ ${stderr}`;
|
|
|
57012
57104
|
};
|
|
57013
57105
|
});
|
|
57014
57106
|
|
|
57107
|
+
// src/pipeline/event-bus.ts
|
|
57108
|
+
class PipelineEventBus {
|
|
57109
|
+
subscribers = new Map;
|
|
57110
|
+
_pending = new Set;
|
|
57111
|
+
on(eventType, subscriber) {
|
|
57112
|
+
const list = this.subscribers.get(eventType) ?? [];
|
|
57113
|
+
list.push(subscriber);
|
|
57114
|
+
this.subscribers.set(eventType, list);
|
|
57115
|
+
return () => {
|
|
57116
|
+
const current = this.subscribers.get(eventType) ?? [];
|
|
57117
|
+
this.subscribers.set(eventType, current.filter((s) => s !== subscriber));
|
|
57118
|
+
};
|
|
57119
|
+
}
|
|
57120
|
+
onAll(subscriber) {
|
|
57121
|
+
const list = this.subscribers.get("*") ?? [];
|
|
57122
|
+
list.push(subscriber);
|
|
57123
|
+
this.subscribers.set("*", list);
|
|
57124
|
+
return () => {
|
|
57125
|
+
const current = this.subscribers.get("*") ?? [];
|
|
57126
|
+
this.subscribers.set("*", current.filter((s) => s !== subscriber));
|
|
57127
|
+
};
|
|
57128
|
+
}
|
|
57129
|
+
emit(event) {
|
|
57130
|
+
const logger = getLogger();
|
|
57131
|
+
const specific = this.subscribers.get(event.type) ?? [];
|
|
57132
|
+
const all = this.subscribers.get("*") ?? [];
|
|
57133
|
+
const targets = [...specific, ...all];
|
|
57134
|
+
for (const sub of targets) {
|
|
57135
|
+
try {
|
|
57136
|
+
const result = sub(event);
|
|
57137
|
+
if (result instanceof Promise) {
|
|
57138
|
+
const tracked = result.catch((err) => {
|
|
57139
|
+
logger.warn("event-bus", `Subscriber error on ${event.type}`, { error: String(err) });
|
|
57140
|
+
});
|
|
57141
|
+
this._pending.add(tracked);
|
|
57142
|
+
tracked.finally(() => this._pending.delete(tracked));
|
|
57143
|
+
}
|
|
57144
|
+
} catch (err) {
|
|
57145
|
+
logger.warn("event-bus", `Subscriber threw on ${event.type}`, { error: String(err) });
|
|
57146
|
+
}
|
|
57147
|
+
}
|
|
57148
|
+
}
|
|
57149
|
+
async emitAsync(event) {
|
|
57150
|
+
const logger = getLogger();
|
|
57151
|
+
const specific = this.subscribers.get(event.type) ?? [];
|
|
57152
|
+
const all = this.subscribers.get("*") ?? [];
|
|
57153
|
+
const targets = [...specific, ...all];
|
|
57154
|
+
await Promise.allSettled(targets.map(async (sub) => {
|
|
57155
|
+
try {
|
|
57156
|
+
await sub(event);
|
|
57157
|
+
} catch (err) {
|
|
57158
|
+
logger.warn("event-bus", `Subscriber error on ${event.type}`, { error: String(err) });
|
|
57159
|
+
}
|
|
57160
|
+
}));
|
|
57161
|
+
}
|
|
57162
|
+
async drain() {
|
|
57163
|
+
if (this._pending.size === 0)
|
|
57164
|
+
return;
|
|
57165
|
+
await Promise.allSettled([...this._pending]);
|
|
57166
|
+
}
|
|
57167
|
+
clear() {
|
|
57168
|
+
this.subscribers.clear();
|
|
57169
|
+
}
|
|
57170
|
+
subscriberCount(eventType) {
|
|
57171
|
+
return (this.subscribers.get(eventType) ?? []).length;
|
|
57172
|
+
}
|
|
57173
|
+
}
|
|
57174
|
+
var pipelineEventBus;
|
|
57175
|
+
var init_event_bus = __esm(() => {
|
|
57176
|
+
init_logger2();
|
|
57177
|
+
pipelineEventBus = new PipelineEventBus;
|
|
57178
|
+
});
|
|
57179
|
+
|
|
57015
57180
|
// src/pipeline/stages/acceptance-setup.ts
|
|
57016
57181
|
var exports_acceptance_setup = {};
|
|
57017
57182
|
__export(exports_acceptance_setup, {
|
|
@@ -57027,6 +57192,214 @@ function computeACFingerprint(criteria) {
|
|
|
57027
57192
|
hasher.update(sorted);
|
|
57028
57193
|
return `sha256:${hasher.digest("hex")}`;
|
|
57029
57194
|
}
|
|
57195
|
+
async function runAcceptanceSetup(ctx, featureDir, phaseStartTime) {
|
|
57196
|
+
const language = ctx.config.project?.language;
|
|
57197
|
+
const testPathConfig = ctx.config.acceptance.testPath;
|
|
57198
|
+
const metaPath = path12.join(featureDir, "acceptance-meta.json");
|
|
57199
|
+
const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed").flatMap((s) => s.acceptanceCriteria);
|
|
57200
|
+
const featureName = ctx.prd.feature ?? ctx.prd.featureName;
|
|
57201
|
+
const groups = await groupStoriesByPackage(ctx.prd, ctx.workdir, featureName, testPathConfig, language);
|
|
57202
|
+
const nonFixStories = groups.flatMap((g) => g.stories);
|
|
57203
|
+
let totalCriteria = 0;
|
|
57204
|
+
let testableCount = 0;
|
|
57205
|
+
const fingerprint = computeACFingerprint(allCriteria);
|
|
57206
|
+
const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
|
|
57207
|
+
getSafeLogger()?.debug("acceptance-setup", "Fingerprint check", {
|
|
57208
|
+
currentFingerprint: fingerprint,
|
|
57209
|
+
storedFingerprint: meta3?.acFingerprint ?? "none",
|
|
57210
|
+
match: meta3?.acFingerprint === fingerprint
|
|
57211
|
+
});
|
|
57212
|
+
let shouldGenerate = false;
|
|
57213
|
+
let regenerated = false;
|
|
57214
|
+
if (!meta3 || meta3.acFingerprint !== fingerprint) {
|
|
57215
|
+
if (!meta3) {
|
|
57216
|
+
getSafeLogger()?.info("acceptance-setup", "No acceptance meta \u2014 generating acceptance tests");
|
|
57217
|
+
} else {
|
|
57218
|
+
getSafeLogger()?.info("acceptance-setup", "ACs changed \u2014 regenerating acceptance tests", {
|
|
57219
|
+
reason: "fingerprint mismatch",
|
|
57220
|
+
currentFingerprint: fingerprint,
|
|
57221
|
+
storedFingerprint: meta3.acFingerprint
|
|
57222
|
+
});
|
|
57223
|
+
}
|
|
57224
|
+
for (const { testPath } of groups) {
|
|
57225
|
+
if (await _acceptanceSetupDeps.fileExists(testPath)) {
|
|
57226
|
+
await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
|
|
57227
|
+
await _acceptanceSetupDeps.deleteFile(testPath);
|
|
57228
|
+
}
|
|
57229
|
+
}
|
|
57230
|
+
await _acceptanceSetupDeps.deleteSemanticVerdicts(featureDir);
|
|
57231
|
+
shouldGenerate = true;
|
|
57232
|
+
regenerated = true;
|
|
57233
|
+
} else {
|
|
57234
|
+
getSafeLogger()?.info("acceptance-setup", "Reusing existing acceptance tests (fingerprint match)");
|
|
57235
|
+
}
|
|
57236
|
+
if (shouldGenerate) {
|
|
57237
|
+
totalCriteria = allCriteria.length;
|
|
57238
|
+
let allRefinedCriteria;
|
|
57239
|
+
if (ctx.config.acceptance.refinement) {
|
|
57240
|
+
const maxConcurrency = ctx.config.acceptance.refinementConcurrency ?? 3;
|
|
57241
|
+
const results = new Array(nonFixStories.length);
|
|
57242
|
+
const executing = new Set;
|
|
57243
|
+
for (let i = 0;i < nonFixStories.length; i++) {
|
|
57244
|
+
const story = nonFixStories[i];
|
|
57245
|
+
const task = _acceptanceSetupDeps.callOp(ctx, ctx.workdir, acceptanceRefineOp, {
|
|
57246
|
+
criteria: story.acceptanceCriteria,
|
|
57247
|
+
codebaseContext: "",
|
|
57248
|
+
storyId: story.id,
|
|
57249
|
+
testStrategy: ctx.config.acceptance.testStrategy,
|
|
57250
|
+
testFramework: ctx.config.acceptance.testFramework,
|
|
57251
|
+
storyTitle: story.title,
|
|
57252
|
+
storyDescription: story.description
|
|
57253
|
+
}, story.id).then((refined) => {
|
|
57254
|
+
results[i] = refined;
|
|
57255
|
+
}).catch(() => {
|
|
57256
|
+
getSafeLogger()?.warn("acceptance-setup", "AC refinement failed after retries \u2014 using unrefined criteria", {
|
|
57257
|
+
storyId: story.id
|
|
57258
|
+
});
|
|
57259
|
+
results[i] = story.acceptanceCriteria.map((c) => ({
|
|
57260
|
+
original: c,
|
|
57261
|
+
refined: c,
|
|
57262
|
+
testable: true,
|
|
57263
|
+
storyId: story.id
|
|
57264
|
+
}));
|
|
57265
|
+
}).finally(() => {
|
|
57266
|
+
executing.delete(task);
|
|
57267
|
+
});
|
|
57268
|
+
executing.add(task);
|
|
57269
|
+
if (executing.size >= maxConcurrency) {
|
|
57270
|
+
await Promise.race(executing);
|
|
57271
|
+
}
|
|
57272
|
+
}
|
|
57273
|
+
await Promise.all(executing);
|
|
57274
|
+
allRefinedCriteria = results.flat();
|
|
57275
|
+
} else {
|
|
57276
|
+
allRefinedCriteria = nonFixStories.flatMap((story) => story.acceptanceCriteria.map((c) => ({
|
|
57277
|
+
original: c,
|
|
57278
|
+
refined: c,
|
|
57279
|
+
testable: true,
|
|
57280
|
+
storyId: story.id
|
|
57281
|
+
})));
|
|
57282
|
+
}
|
|
57283
|
+
testableCount = allRefinedCriteria.filter((r) => r.testable).length;
|
|
57284
|
+
for (const group of groups) {
|
|
57285
|
+
const { testPath, packageDir } = group;
|
|
57286
|
+
const groupStoryIds = new Set(group.stories.map((s) => s.id));
|
|
57287
|
+
const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
|
|
57288
|
+
const criteriaList = groupRefined.map((c, i) => `AC-${i + 1}: ${c.refined}`).join(`
|
|
57289
|
+
`);
|
|
57290
|
+
const frameworkOverrideLine = ctx.config.acceptance.testFramework ? `
|
|
57291
|
+
[FRAMEWORK OVERRIDE: Use ${ctx.config.acceptance.testFramework} as the test framework regardless of what you detect.]` : "";
|
|
57292
|
+
const groupStoryId = group.stories[0]?.id;
|
|
57293
|
+
const genResult = await _acceptanceSetupDeps.callOp(ctx, packageDir, acceptanceGenerateOp, {
|
|
57294
|
+
featureName: featureName ?? "",
|
|
57295
|
+
criteriaList,
|
|
57296
|
+
frameworkOverrideLine,
|
|
57297
|
+
targetTestFilePath: testPath,
|
|
57298
|
+
..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {}
|
|
57299
|
+
}, groupStoryId);
|
|
57300
|
+
const testCode = genResult.testCode;
|
|
57301
|
+
if (testCode) {
|
|
57302
|
+
await _acceptanceSetupDeps.writeFile(testPath, testCode);
|
|
57303
|
+
} else {
|
|
57304
|
+
const skeletonCriteria = groupRefined.map((c, i) => ({
|
|
57305
|
+
id: `AC-${i + 1}`,
|
|
57306
|
+
text: c.refined,
|
|
57307
|
+
lineNumber: i + 1
|
|
57308
|
+
}));
|
|
57309
|
+
const skeletonCode = generateSkeletonTests(featureName, skeletonCriteria, ctx.config.acceptance.testFramework, group.language);
|
|
57310
|
+
await _acceptanceSetupDeps.writeFile(testPath, skeletonCode);
|
|
57311
|
+
getSafeLogger()?.warn("acceptance-setup", "agent did not produce test content; using skeleton", {
|
|
57312
|
+
storyId: groupStoryId,
|
|
57313
|
+
testPath
|
|
57314
|
+
});
|
|
57315
|
+
}
|
|
57316
|
+
}
|
|
57317
|
+
if (allRefinedCriteria.length > 0) {
|
|
57318
|
+
const refinedJsonContent = JSON.stringify(allRefinedCriteria.map((c, i) => ({
|
|
57319
|
+
acId: `AC-${i + 1}`,
|
|
57320
|
+
original: c.original,
|
|
57321
|
+
refined: c.refined,
|
|
57322
|
+
testable: c.testable,
|
|
57323
|
+
storyId: c.storyId
|
|
57324
|
+
})), null, 2);
|
|
57325
|
+
await _acceptanceSetupDeps.writeFile(path12.join(featureDir, "acceptance-refined.json"), refinedJsonContent);
|
|
57326
|
+
}
|
|
57327
|
+
const fingerprint2 = computeACFingerprint(allCriteria);
|
|
57328
|
+
await _acceptanceSetupDeps.writeMeta(metaPath, {
|
|
57329
|
+
generatedAt: new Date().toISOString(),
|
|
57330
|
+
acFingerprint: fingerprint2,
|
|
57331
|
+
storyCount: ctx.prd.userStories.length,
|
|
57332
|
+
acCount: totalCriteria,
|
|
57333
|
+
generator: "nax"
|
|
57334
|
+
});
|
|
57335
|
+
await _acceptanceSetupDeps.autoCommitIfDirty(ctx.workdir, "acceptance-setup", "pre-run", ctx.prd.feature ?? "feature");
|
|
57336
|
+
}
|
|
57337
|
+
const acceptanceTestPaths = [];
|
|
57338
|
+
for (const g of groups) {
|
|
57339
|
+
const relativeWorkdir = path12.relative(ctx.projectDir, g.packageDir);
|
|
57340
|
+
let groupConfig = ctx.config;
|
|
57341
|
+
if (relativeWorkdir && relativeWorkdir !== ".") {
|
|
57342
|
+
try {
|
|
57343
|
+
groupConfig = await _acceptanceSetupDeps.loadGroupConfig(ctx.projectDir, relativeWorkdir);
|
|
57344
|
+
} catch {
|
|
57345
|
+
groupConfig = ctx.config;
|
|
57346
|
+
}
|
|
57347
|
+
}
|
|
57348
|
+
acceptanceTestPaths.push({
|
|
57349
|
+
testPath: g.testPath,
|
|
57350
|
+
packageDir: g.packageDir,
|
|
57351
|
+
testFramework: groupConfig.project?.testFramework,
|
|
57352
|
+
commandOverride: groupConfig.acceptance.command
|
|
57353
|
+
});
|
|
57354
|
+
}
|
|
57355
|
+
ctx.acceptanceTestPaths = acceptanceTestPaths;
|
|
57356
|
+
if (ctx.config.acceptance.redGate === false) {
|
|
57357
|
+
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
57358
|
+
pipelineEventBus.emit({
|
|
57359
|
+
type: "postrun:phase:completed",
|
|
57360
|
+
phase: "acceptance-setup",
|
|
57361
|
+
passed: true,
|
|
57362
|
+
durationMs: Date.now() - phaseStartTime,
|
|
57363
|
+
details: { totalCriteria, testableCount, redFailCount: 0, regenerated }
|
|
57364
|
+
});
|
|
57365
|
+
return { action: "continue" };
|
|
57366
|
+
}
|
|
57367
|
+
let redFailCount = 0;
|
|
57368
|
+
for (const { testPath, packageDir, testFramework, commandOverride } of acceptanceTestPaths) {
|
|
57369
|
+
const runCmd = buildAcceptanceRunCommand(testPath, testFramework, commandOverride, packageDir);
|
|
57370
|
+
getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
|
|
57371
|
+
cmd: runCmd.join(" "),
|
|
57372
|
+
packageDir
|
|
57373
|
+
});
|
|
57374
|
+
const { exitCode } = await _acceptanceSetupDeps.runTest(testPath, packageDir, runCmd);
|
|
57375
|
+
if (exitCode !== 0) {
|
|
57376
|
+
redFailCount++;
|
|
57377
|
+
}
|
|
57378
|
+
}
|
|
57379
|
+
if (redFailCount === 0) {
|
|
57380
|
+
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
57381
|
+
pipelineEventBus.emit({
|
|
57382
|
+
type: "postrun:phase:completed",
|
|
57383
|
+
phase: "acceptance-setup",
|
|
57384
|
+
passed: true,
|
|
57385
|
+
durationMs: Date.now() - phaseStartTime,
|
|
57386
|
+
details: { totalCriteria, testableCount, redFailCount: 0, regenerated }
|
|
57387
|
+
});
|
|
57388
|
+
return {
|
|
57389
|
+
action: "skip",
|
|
57390
|
+
reason: "[acceptance-setup] Acceptance tests already pass \u2014 they are not testing new behavior. Skipping acceptance gate."
|
|
57391
|
+
};
|
|
57392
|
+
}
|
|
57393
|
+
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount };
|
|
57394
|
+
pipelineEventBus.emit({
|
|
57395
|
+
type: "postrun:phase:completed",
|
|
57396
|
+
phase: "acceptance-setup",
|
|
57397
|
+
passed: true,
|
|
57398
|
+
durationMs: Date.now() - phaseStartTime,
|
|
57399
|
+
details: { totalCriteria, testableCount, redFailCount, regenerated }
|
|
57400
|
+
});
|
|
57401
|
+
return { action: "continue" };
|
|
57402
|
+
}
|
|
57030
57403
|
var _acceptanceSetupDeps, acceptanceSetupStage;
|
|
57031
57404
|
var init_acceptance_setup = __esm(() => {
|
|
57032
57405
|
init_acceptance2();
|
|
@@ -57035,6 +57408,7 @@ var init_acceptance_setup = __esm(() => {
|
|
|
57035
57408
|
init_logger2();
|
|
57036
57409
|
init_operations();
|
|
57037
57410
|
init_git();
|
|
57411
|
+
init_event_bus();
|
|
57038
57412
|
_acceptanceSetupDeps = {
|
|
57039
57413
|
getAgent: (_name) => {
|
|
57040
57414
|
return;
|
|
@@ -57134,189 +57508,19 @@ ${stderr}` };
|
|
|
57134
57508
|
if (!ctx.featureDir) {
|
|
57135
57509
|
return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
|
|
57136
57510
|
}
|
|
57137
|
-
const
|
|
57138
|
-
|
|
57139
|
-
|
|
57140
|
-
|
|
57141
|
-
|
|
57142
|
-
|
|
57143
|
-
|
|
57144
|
-
|
|
57145
|
-
|
|
57146
|
-
|
|
57147
|
-
const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
|
|
57148
|
-
getSafeLogger()?.debug("acceptance-setup", "Fingerprint check", {
|
|
57149
|
-
currentFingerprint: fingerprint,
|
|
57150
|
-
storedFingerprint: meta3?.acFingerprint ?? "none",
|
|
57151
|
-
match: meta3?.acFingerprint === fingerprint
|
|
57152
|
-
});
|
|
57153
|
-
let shouldGenerate = false;
|
|
57154
|
-
if (!meta3 || meta3.acFingerprint !== fingerprint) {
|
|
57155
|
-
if (!meta3) {
|
|
57156
|
-
getSafeLogger()?.info("acceptance-setup", "No acceptance meta \u2014 generating acceptance tests");
|
|
57157
|
-
} else {
|
|
57158
|
-
getSafeLogger()?.info("acceptance-setup", "ACs changed \u2014 regenerating acceptance tests", {
|
|
57159
|
-
reason: "fingerprint mismatch",
|
|
57160
|
-
currentFingerprint: fingerprint,
|
|
57161
|
-
storedFingerprint: meta3.acFingerprint
|
|
57162
|
-
});
|
|
57163
|
-
}
|
|
57164
|
-
for (const { testPath } of groups) {
|
|
57165
|
-
if (await _acceptanceSetupDeps.fileExists(testPath)) {
|
|
57166
|
-
await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
|
|
57167
|
-
await _acceptanceSetupDeps.deleteFile(testPath);
|
|
57168
|
-
}
|
|
57169
|
-
}
|
|
57170
|
-
await _acceptanceSetupDeps.deleteSemanticVerdicts(ctx.featureDir);
|
|
57171
|
-
shouldGenerate = true;
|
|
57172
|
-
} else {
|
|
57173
|
-
getSafeLogger()?.info("acceptance-setup", "Reusing existing acceptance tests (fingerprint match)");
|
|
57174
|
-
}
|
|
57175
|
-
if (shouldGenerate) {
|
|
57176
|
-
totalCriteria = allCriteria.length;
|
|
57177
|
-
let allRefinedCriteria;
|
|
57178
|
-
if (ctx.config.acceptance.refinement) {
|
|
57179
|
-
const maxConcurrency = ctx.config.acceptance.refinementConcurrency ?? 3;
|
|
57180
|
-
const results = new Array(nonFixStories.length);
|
|
57181
|
-
const executing = new Set;
|
|
57182
|
-
for (let i = 0;i < nonFixStories.length; i++) {
|
|
57183
|
-
const story = nonFixStories[i];
|
|
57184
|
-
const task = _acceptanceSetupDeps.callOp(ctx, ctx.workdir, acceptanceRefineOp, {
|
|
57185
|
-
criteria: story.acceptanceCriteria,
|
|
57186
|
-
codebaseContext: "",
|
|
57187
|
-
storyId: story.id,
|
|
57188
|
-
testStrategy: ctx.config.acceptance.testStrategy,
|
|
57189
|
-
testFramework: ctx.config.acceptance.testFramework,
|
|
57190
|
-
storyTitle: story.title,
|
|
57191
|
-
storyDescription: story.description
|
|
57192
|
-
}, story.id).then((refined) => {
|
|
57193
|
-
results[i] = refined;
|
|
57194
|
-
}).catch(() => {
|
|
57195
|
-
getSafeLogger()?.warn("acceptance-setup", "AC refinement failed after retries \u2014 using unrefined criteria", {
|
|
57196
|
-
storyId: story.id
|
|
57197
|
-
});
|
|
57198
|
-
results[i] = story.acceptanceCriteria.map((c) => ({
|
|
57199
|
-
original: c,
|
|
57200
|
-
refined: c,
|
|
57201
|
-
testable: true,
|
|
57202
|
-
storyId: story.id
|
|
57203
|
-
}));
|
|
57204
|
-
}).finally(() => {
|
|
57205
|
-
executing.delete(task);
|
|
57206
|
-
});
|
|
57207
|
-
executing.add(task);
|
|
57208
|
-
if (executing.size >= maxConcurrency) {
|
|
57209
|
-
await Promise.race(executing);
|
|
57210
|
-
}
|
|
57211
|
-
}
|
|
57212
|
-
await Promise.all(executing);
|
|
57213
|
-
allRefinedCriteria = results.flat();
|
|
57214
|
-
} else {
|
|
57215
|
-
allRefinedCriteria = nonFixStories.flatMap((story) => story.acceptanceCriteria.map((c) => ({
|
|
57216
|
-
original: c,
|
|
57217
|
-
refined: c,
|
|
57218
|
-
testable: true,
|
|
57219
|
-
storyId: story.id
|
|
57220
|
-
})));
|
|
57221
|
-
}
|
|
57222
|
-
testableCount = allRefinedCriteria.filter((r) => r.testable).length;
|
|
57223
|
-
for (const group of groups) {
|
|
57224
|
-
const { testPath, packageDir } = group;
|
|
57225
|
-
const groupStoryIds = new Set(group.stories.map((s) => s.id));
|
|
57226
|
-
const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
|
|
57227
|
-
const criteriaList = groupRefined.map((c, i) => `AC-${i + 1}: ${c.refined}`).join(`
|
|
57228
|
-
`);
|
|
57229
|
-
const frameworkOverrideLine = ctx.config.acceptance.testFramework ? `
|
|
57230
|
-
[FRAMEWORK OVERRIDE: Use ${ctx.config.acceptance.testFramework} as the test framework regardless of what you detect.]` : "";
|
|
57231
|
-
const groupStoryId = group.stories[0]?.id;
|
|
57232
|
-
const genResult = await _acceptanceSetupDeps.callOp(ctx, packageDir, acceptanceGenerateOp, {
|
|
57233
|
-
featureName: featureName ?? "",
|
|
57234
|
-
criteriaList,
|
|
57235
|
-
frameworkOverrideLine,
|
|
57236
|
-
targetTestFilePath: testPath,
|
|
57237
|
-
..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {}
|
|
57238
|
-
}, groupStoryId);
|
|
57239
|
-
const testCode = genResult.testCode;
|
|
57240
|
-
if (testCode) {
|
|
57241
|
-
await _acceptanceSetupDeps.writeFile(testPath, testCode);
|
|
57242
|
-
} else {
|
|
57243
|
-
const skeletonCriteria = groupRefined.map((c, i) => ({
|
|
57244
|
-
id: `AC-${i + 1}`,
|
|
57245
|
-
text: c.refined,
|
|
57246
|
-
lineNumber: i + 1
|
|
57247
|
-
}));
|
|
57248
|
-
const skeletonCode = generateSkeletonTests(featureName, skeletonCriteria, ctx.config.acceptance.testFramework, group.language);
|
|
57249
|
-
await _acceptanceSetupDeps.writeFile(testPath, skeletonCode);
|
|
57250
|
-
getSafeLogger()?.warn("acceptance-setup", "agent did not produce test content; using skeleton", {
|
|
57251
|
-
storyId: groupStoryId,
|
|
57252
|
-
testPath
|
|
57253
|
-
});
|
|
57254
|
-
}
|
|
57255
|
-
}
|
|
57256
|
-
if (allRefinedCriteria.length > 0) {
|
|
57257
|
-
const refinedJsonContent = JSON.stringify(allRefinedCriteria.map((c, i) => ({
|
|
57258
|
-
acId: `AC-${i + 1}`,
|
|
57259
|
-
original: c.original,
|
|
57260
|
-
refined: c.refined,
|
|
57261
|
-
testable: c.testable,
|
|
57262
|
-
storyId: c.storyId
|
|
57263
|
-
})), null, 2);
|
|
57264
|
-
await _acceptanceSetupDeps.writeFile(path12.join(ctx.featureDir, "acceptance-refined.json"), refinedJsonContent);
|
|
57265
|
-
}
|
|
57266
|
-
const fingerprint2 = computeACFingerprint(allCriteria);
|
|
57267
|
-
await _acceptanceSetupDeps.writeMeta(metaPath, {
|
|
57268
|
-
generatedAt: new Date().toISOString(),
|
|
57269
|
-
acFingerprint: fingerprint2,
|
|
57270
|
-
storyCount: ctx.prd.userStories.length,
|
|
57271
|
-
acCount: totalCriteria,
|
|
57272
|
-
generator: "nax"
|
|
57273
|
-
});
|
|
57274
|
-
await _acceptanceSetupDeps.autoCommitIfDirty(ctx.workdir, "acceptance-setup", "pre-run", ctx.prd.feature ?? "feature");
|
|
57275
|
-
}
|
|
57276
|
-
const acceptanceTestPaths = [];
|
|
57277
|
-
for (const g of groups) {
|
|
57278
|
-
const relativeWorkdir = path12.relative(ctx.projectDir, g.packageDir);
|
|
57279
|
-
let groupConfig = ctx.config;
|
|
57280
|
-
if (relativeWorkdir && relativeWorkdir !== ".") {
|
|
57281
|
-
try {
|
|
57282
|
-
groupConfig = await _acceptanceSetupDeps.loadGroupConfig(ctx.projectDir, relativeWorkdir);
|
|
57283
|
-
} catch {
|
|
57284
|
-
groupConfig = ctx.config;
|
|
57285
|
-
}
|
|
57286
|
-
}
|
|
57287
|
-
acceptanceTestPaths.push({
|
|
57288
|
-
testPath: g.testPath,
|
|
57289
|
-
packageDir: g.packageDir,
|
|
57290
|
-
testFramework: groupConfig.project?.testFramework,
|
|
57291
|
-
commandOverride: groupConfig.acceptance.command
|
|
57292
|
-
});
|
|
57293
|
-
}
|
|
57294
|
-
ctx.acceptanceTestPaths = acceptanceTestPaths;
|
|
57295
|
-
if (ctx.config.acceptance.redGate === false) {
|
|
57296
|
-
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
57297
|
-
return { action: "continue" };
|
|
57298
|
-
}
|
|
57299
|
-
let redFailCount = 0;
|
|
57300
|
-
for (const { testPath, packageDir, testFramework, commandOverride } of acceptanceTestPaths) {
|
|
57301
|
-
const runCmd = buildAcceptanceRunCommand(testPath, testFramework, commandOverride, packageDir);
|
|
57302
|
-
getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
|
|
57303
|
-
cmd: runCmd.join(" "),
|
|
57304
|
-
packageDir
|
|
57511
|
+
const phaseStartTime = Date.now();
|
|
57512
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance-setup" });
|
|
57513
|
+
try {
|
|
57514
|
+
return await runAcceptanceSetup(ctx, ctx.featureDir, phaseStartTime);
|
|
57515
|
+
} catch (err) {
|
|
57516
|
+
pipelineEventBus.emit({
|
|
57517
|
+
type: "postrun:phase:completed",
|
|
57518
|
+
phase: "acceptance-setup",
|
|
57519
|
+
passed: false,
|
|
57520
|
+
durationMs: Date.now() - phaseStartTime
|
|
57305
57521
|
});
|
|
57306
|
-
|
|
57307
|
-
if (exitCode !== 0) {
|
|
57308
|
-
redFailCount++;
|
|
57309
|
-
}
|
|
57310
|
-
}
|
|
57311
|
-
if (redFailCount === 0) {
|
|
57312
|
-
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
57313
|
-
return {
|
|
57314
|
-
action: "skip",
|
|
57315
|
-
reason: "[acceptance-setup] Acceptance tests already pass \u2014 they are not testing new behavior. Skipping acceptance gate."
|
|
57316
|
-
};
|
|
57522
|
+
throw err;
|
|
57317
57523
|
}
|
|
57318
|
-
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount };
|
|
57319
|
-
return { action: "continue" };
|
|
57320
57524
|
}
|
|
57321
57525
|
};
|
|
57322
57526
|
});
|
|
@@ -57462,79 +57666,6 @@ async function appendProgress(featureDir, storyId, status, message) {
|
|
|
57462
57666
|
}
|
|
57463
57667
|
var init_progress = () => {};
|
|
57464
57668
|
|
|
57465
|
-
// src/pipeline/event-bus.ts
|
|
57466
|
-
class PipelineEventBus {
|
|
57467
|
-
subscribers = new Map;
|
|
57468
|
-
_pending = new Set;
|
|
57469
|
-
on(eventType, subscriber) {
|
|
57470
|
-
const list = this.subscribers.get(eventType) ?? [];
|
|
57471
|
-
list.push(subscriber);
|
|
57472
|
-
this.subscribers.set(eventType, list);
|
|
57473
|
-
return () => {
|
|
57474
|
-
const current = this.subscribers.get(eventType) ?? [];
|
|
57475
|
-
this.subscribers.set(eventType, current.filter((s) => s !== subscriber));
|
|
57476
|
-
};
|
|
57477
|
-
}
|
|
57478
|
-
onAll(subscriber) {
|
|
57479
|
-
const list = this.subscribers.get("*") ?? [];
|
|
57480
|
-
list.push(subscriber);
|
|
57481
|
-
this.subscribers.set("*", list);
|
|
57482
|
-
return () => {
|
|
57483
|
-
const current = this.subscribers.get("*") ?? [];
|
|
57484
|
-
this.subscribers.set("*", current.filter((s) => s !== subscriber));
|
|
57485
|
-
};
|
|
57486
|
-
}
|
|
57487
|
-
emit(event) {
|
|
57488
|
-
const logger = getLogger();
|
|
57489
|
-
const specific = this.subscribers.get(event.type) ?? [];
|
|
57490
|
-
const all = this.subscribers.get("*") ?? [];
|
|
57491
|
-
const targets = [...specific, ...all];
|
|
57492
|
-
for (const sub of targets) {
|
|
57493
|
-
try {
|
|
57494
|
-
const result = sub(event);
|
|
57495
|
-
if (result instanceof Promise) {
|
|
57496
|
-
const tracked = result.catch((err) => {
|
|
57497
|
-
logger.warn("event-bus", `Subscriber error on ${event.type}`, { error: String(err) });
|
|
57498
|
-
});
|
|
57499
|
-
this._pending.add(tracked);
|
|
57500
|
-
tracked.finally(() => this._pending.delete(tracked));
|
|
57501
|
-
}
|
|
57502
|
-
} catch (err) {
|
|
57503
|
-
logger.warn("event-bus", `Subscriber threw on ${event.type}`, { error: String(err) });
|
|
57504
|
-
}
|
|
57505
|
-
}
|
|
57506
|
-
}
|
|
57507
|
-
async emitAsync(event) {
|
|
57508
|
-
const logger = getLogger();
|
|
57509
|
-
const specific = this.subscribers.get(event.type) ?? [];
|
|
57510
|
-
const all = this.subscribers.get("*") ?? [];
|
|
57511
|
-
const targets = [...specific, ...all];
|
|
57512
|
-
await Promise.allSettled(targets.map(async (sub) => {
|
|
57513
|
-
try {
|
|
57514
|
-
await sub(event);
|
|
57515
|
-
} catch (err) {
|
|
57516
|
-
logger.warn("event-bus", `Subscriber error on ${event.type}`, { error: String(err) });
|
|
57517
|
-
}
|
|
57518
|
-
}));
|
|
57519
|
-
}
|
|
57520
|
-
async drain() {
|
|
57521
|
-
if (this._pending.size === 0)
|
|
57522
|
-
return;
|
|
57523
|
-
await Promise.allSettled([...this._pending]);
|
|
57524
|
-
}
|
|
57525
|
-
clear() {
|
|
57526
|
-
this.subscribers.clear();
|
|
57527
|
-
}
|
|
57528
|
-
subscriberCount(eventType) {
|
|
57529
|
-
return (this.subscribers.get(eventType) ?? []).length;
|
|
57530
|
-
}
|
|
57531
|
-
}
|
|
57532
|
-
var pipelineEventBus;
|
|
57533
|
-
var init_event_bus = __esm(() => {
|
|
57534
|
-
init_logger2();
|
|
57535
|
-
pipelineEventBus = new PipelineEventBus;
|
|
57536
|
-
});
|
|
57537
|
-
|
|
57538
57669
|
// src/pipeline/stages/completion.ts
|
|
57539
57670
|
async function getDiffText(workdir, baseRef) {
|
|
57540
57671
|
if (!baseRef)
|
|
@@ -58326,11 +58457,11 @@ var init_rollback = __esm(() => {
|
|
|
58326
58457
|
});
|
|
58327
58458
|
|
|
58328
58459
|
// src/utils/paths.ts
|
|
58329
|
-
import { join as join49, relative as
|
|
58460
|
+
import { join as join49, relative as relative14, sep as sep4 } from "path";
|
|
58330
58461
|
function packageDirRelative(projectDir, workdir) {
|
|
58331
58462
|
if (!projectDir || !workdir || workdir === projectDir)
|
|
58332
58463
|
return;
|
|
58333
|
-
const rel =
|
|
58464
|
+
const rel = relative14(projectDir, workdir);
|
|
58334
58465
|
if (rel === ".." || rel.startsWith(`..${sep4}`))
|
|
58335
58466
|
return;
|
|
58336
58467
|
return rel && rel !== "." ? rel : undefined;
|
|
@@ -58346,6 +58477,9 @@ var init_paths3 = __esm(() => {
|
|
|
58346
58477
|
});
|
|
58347
58478
|
|
|
58348
58479
|
// src/execution/non-blocking-fix.ts
|
|
58480
|
+
function actionableAdvisoryFindings(findings) {
|
|
58481
|
+
return findings.filter((f) => f.actionRequired !== false);
|
|
58482
|
+
}
|
|
58349
58483
|
function shouldRunNonBlockingFix(cfg, advisoryCount) {
|
|
58350
58484
|
return cfg?.enabled === true && advisoryCount > 0;
|
|
58351
58485
|
}
|
|
@@ -58418,9 +58552,16 @@ async function runNonBlockingFix(args, overrides = {}) {
|
|
|
58418
58552
|
exhausted = true;
|
|
58419
58553
|
}
|
|
58420
58554
|
if (!exhausted) {
|
|
58421
|
-
|
|
58555
|
+
const gateVerdict = args.keptTreeRegressed?.();
|
|
58556
|
+
if (gateVerdict?.regressed) {
|
|
58422
58557
|
logger?.info("non-blocking-fix", "kept tree regressed the full-suite gate \u2014 restoring (ADR-024 \xA73)", {
|
|
58423
|
-
storyId: args.storyId
|
|
58558
|
+
storyId: args.storyId,
|
|
58559
|
+
regressedKeys: gateVerdict.regressedKeys.slice(0, MAX_LOGGED_REGRESSED_KEYS),
|
|
58560
|
+
regressedKeyCount: gateVerdict.regressedKeys.length,
|
|
58561
|
+
baselineKeySize: gateVerdict.baselineKeySize,
|
|
58562
|
+
keyless: gateVerdict.keyless,
|
|
58563
|
+
memoExcludedKeyCount: gateVerdict.memoExcludedKeys.length,
|
|
58564
|
+
flakeTriageRan: false
|
|
58424
58565
|
});
|
|
58425
58566
|
return restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot, phaseCostsSnapshot, logger);
|
|
58426
58567
|
}
|
|
@@ -58464,7 +58605,7 @@ async function restoreToSnapshot(args, _deps, restoreRef, phaseOutputsSnapshot,
|
|
|
58464
58605
|
});
|
|
58465
58606
|
return { ran: true, kept: false, restored: true };
|
|
58466
58607
|
}
|
|
58467
|
-
var REVIEW_PHASE_KINDS, _nonBlockingFixDeps, DEFAULT_DEPS;
|
|
58608
|
+
var REVIEW_PHASE_KINDS, MAX_LOGGED_REGRESSED_KEYS = 10, _nonBlockingFixDeps, DEFAULT_DEPS;
|
|
58468
58609
|
var init_non_blocking_fix = __esm(() => {
|
|
58469
58610
|
init_logger2();
|
|
58470
58611
|
init_rollback();
|
|
@@ -58607,13 +58748,29 @@ function gateFailureKeys(gateOutput) {
|
|
|
58607
58748
|
}
|
|
58608
58749
|
return keys;
|
|
58609
58750
|
}
|
|
58610
|
-
function
|
|
58611
|
-
|
|
58612
|
-
|
|
58613
|
-
|
|
58614
|
-
|
|
58615
|
-
|
|
58616
|
-
|
|
58751
|
+
function describeGateRegression(input) {
|
|
58752
|
+
const { gateOutput, baselineKeys, gateName, storyId, quarantineMemo } = input;
|
|
58753
|
+
const notRegressed = {
|
|
58754
|
+
regressed: false,
|
|
58755
|
+
regressedKeys: [],
|
|
58756
|
+
memoExcludedKeys: [],
|
|
58757
|
+
baselineKeySize: baselineKeys.size,
|
|
58758
|
+
keyless: false
|
|
58759
|
+
};
|
|
58760
|
+
if (gateName === undefined || phasePassed(gateName, gateOutput, storyId))
|
|
58761
|
+
return notRegressed;
|
|
58762
|
+
const allKeys = gateFailureKeys(gateOutput);
|
|
58763
|
+
const memoExcludedKeys = quarantineMemo ? [...allKeys].filter((k) => quarantineMemo.has(k)) : [];
|
|
58764
|
+
const excluded = new Set(memoExcludedKeys);
|
|
58765
|
+
const regressedKeys = [...allKeys].filter((k) => !baselineKeys.has(k) && !excluded.has(k));
|
|
58766
|
+
const keyless = allKeys.size === 0 || allKeys.has(KEYLESS_GATE_FAILURE_KEY);
|
|
58767
|
+
return {
|
|
58768
|
+
regressed: regressedKeys.length > 0 || keyless,
|
|
58769
|
+
regressedKeys,
|
|
58770
|
+
memoExcludedKeys,
|
|
58771
|
+
baselineKeySize: baselineKeys.size,
|
|
58772
|
+
keyless
|
|
58773
|
+
};
|
|
58617
58774
|
}
|
|
58618
58775
|
function phasesToRevalidate(strategiesRun, allPhases) {
|
|
58619
58776
|
if (!strategiesRun || strategiesRun.length === 0)
|
|
@@ -58760,6 +58917,9 @@ function buildPhaseOutcomeLogData(storyId, opName, output, durationMs) {
|
|
|
58760
58917
|
const data = { storyId, phase: opName, durationMs };
|
|
58761
58918
|
if (findingsCount !== undefined)
|
|
58762
58919
|
data.findingsCount = findingsCount;
|
|
58920
|
+
const identities = extractPhaseFindings(output).slice(0, MAX_LOGGED_FINDING_IDENTITIES).map((f) => `${f.file ?? ""}::${f.rule ?? ""}`);
|
|
58921
|
+
if (identities.length > 0)
|
|
58922
|
+
data.findingIdentities = identities;
|
|
58763
58923
|
if (status !== undefined)
|
|
58764
58924
|
data.status = status;
|
|
58765
58925
|
if (typeof r.failureCategory === "string")
|
|
@@ -58791,8 +58951,10 @@ function logDeterministicPhaseOutcome(storyId, opName, output, durationMs, isTdd
|
|
|
58791
58951
|
logger?.warn("story-orchestrator", message, data);
|
|
58792
58952
|
}
|
|
58793
58953
|
}
|
|
58954
|
+
var MAX_LOGGED_FINDING_IDENTITIES = 10;
|
|
58794
58955
|
var init_story_orchestrator_logging = __esm(() => {
|
|
58795
58956
|
init_logger2();
|
|
58957
|
+
init_phase_eval();
|
|
58796
58958
|
});
|
|
58797
58959
|
// src/verification/flake-baseline-diff.ts
|
|
58798
58960
|
async function resolveFlakeBaselineDiff(config2, workdir, storyWorkdir) {
|
|
@@ -59065,10 +59227,13 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
59065
59227
|
const beforeRef = isTddPhase ? await _storyOrchestratorDeps.captureGitRef(ctx.packageDir) : undefined;
|
|
59066
59228
|
let dispatchInput = isTddPhase && beforeRef ? { ...slot.input, beforeRef } : slot.input;
|
|
59067
59229
|
dispatchInput = await refreshReviewInputForDispatch(opName, dispatchInput);
|
|
59230
|
+
let advIterationBefore = 0;
|
|
59068
59231
|
if (opName === "adversarial-review" && ctx.storyId) {
|
|
59232
|
+
const priorIterations = getAdversarialIterations(ctx.runtime.adversarialIterations, ctx.storyId);
|
|
59233
|
+
advIterationBefore = priorIterations.length;
|
|
59069
59234
|
dispatchInput = {
|
|
59070
59235
|
...dispatchInput,
|
|
59071
|
-
priorAdversarialIterations:
|
|
59236
|
+
priorAdversarialIterations: priorIterations
|
|
59072
59237
|
};
|
|
59073
59238
|
}
|
|
59074
59239
|
if (isTddPhase) {
|
|
@@ -59085,6 +59250,7 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
59085
59250
|
}
|
|
59086
59251
|
const phaseStartedAt = Date.now();
|
|
59087
59252
|
const scope = ctx.runtime.costAggregator.openScope();
|
|
59253
|
+
let outcome = "passed";
|
|
59088
59254
|
try {
|
|
59089
59255
|
const output = await _storyOrchestratorDeps.callOp({ ...ctx, scopeId: scope.scopeId }, slot.op, dispatchInput);
|
|
59090
59256
|
phaseOutputs[opName] = output;
|
|
@@ -59098,6 +59264,7 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
59098
59264
|
}
|
|
59099
59265
|
logUnifiedReviewPhaseResult(ctx.storyId, opName, output);
|
|
59100
59266
|
logDeterministicPhaseOutcome(ctx.storyId, opName, output, Date.now() - phaseStartedAt, isTddPhase, slot.op.stage, progressData);
|
|
59267
|
+
outcome = derivePhaseOutcome(output);
|
|
59101
59268
|
if (isTddPhase) {
|
|
59102
59269
|
const durationMs = Date.now() - phaseStartedAt;
|
|
59103
59270
|
logger?.info("tdd", `Session complete: ${opName}`, {
|
|
@@ -59127,11 +59294,95 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
59127
59294
|
}
|
|
59128
59295
|
}
|
|
59129
59296
|
return output;
|
|
59297
|
+
} catch (err) {
|
|
59298
|
+
outcome = "error";
|
|
59299
|
+
throw err;
|
|
59130
59300
|
} finally {
|
|
59131
|
-
|
|
59301
|
+
const snapshot = scope.snapshot();
|
|
59302
|
+
phaseCosts[opName] = (phaseCosts[opName] ?? 0) + snapshot.totalCostUsd;
|
|
59132
59303
|
scope.close();
|
|
59304
|
+
if (ctx.storyId) {
|
|
59305
|
+
const phaseDetails = buildPhaseDetails(opName, phaseOutputs[opName], isThreeSession, ctx.packageView.config, advIterationBefore, ctx.fixStrategy);
|
|
59306
|
+
const event = {
|
|
59307
|
+
type: "story:phase:completed",
|
|
59308
|
+
storyId: ctx.storyId,
|
|
59309
|
+
phase: opName,
|
|
59310
|
+
outcome,
|
|
59311
|
+
durationMs: Date.now() - phaseStartedAt,
|
|
59312
|
+
costUsd: snapshot.totalCostUsd,
|
|
59313
|
+
...ctx.phaseTelemetry ? {
|
|
59314
|
+
tier: ctx.phaseTelemetry.tier,
|
|
59315
|
+
testStrategy: ctx.phaseTelemetry.testStrategy,
|
|
59316
|
+
sessionModel: ctx.phaseTelemetry.sessionModel
|
|
59317
|
+
} : {},
|
|
59318
|
+
...phaseDetails !== undefined ? { details: phaseDetails } : {}
|
|
59319
|
+
};
|
|
59320
|
+
pipelineEventBus.emit(event);
|
|
59321
|
+
}
|
|
59133
59322
|
}
|
|
59134
59323
|
}
|
|
59324
|
+
function buildPhaseDetails(opName, output, isThreeSession, config2, advIterationBefore, fixStrategy) {
|
|
59325
|
+
if (fixStrategy) {
|
|
59326
|
+
return { kind: "fix", strategy: fixStrategy.name, findingsBefore: fixStrategy.findingsBefore };
|
|
59327
|
+
}
|
|
59328
|
+
if (output === null || typeof output !== "object")
|
|
59329
|
+
return;
|
|
59330
|
+
if (opName === "adversarial-review") {
|
|
59331
|
+
const adv = output;
|
|
59332
|
+
const findings = adv?.normalizedFindings ?? [];
|
|
59333
|
+
const threshold = adv?.blockingThreshold ?? "error";
|
|
59334
|
+
const bySeverity = Object.fromEntries(ALL_FINDING_SEVERITIES.map((sev) => [sev, findings.filter((f) => f.severity === sev).length]));
|
|
59335
|
+
const blockingCount = findings.filter((f) => isBlockingSeverity(f.severity, threshold)).length;
|
|
59336
|
+
const advisoryCount = findings.length - blockingCount;
|
|
59337
|
+
return {
|
|
59338
|
+
kind: "review",
|
|
59339
|
+
reviewer: "adversarial",
|
|
59340
|
+
iteration: advIterationBefore,
|
|
59341
|
+
bySeverity,
|
|
59342
|
+
blockingCount,
|
|
59343
|
+
advisoryCount,
|
|
59344
|
+
...config2.reporters?.otel?.detail === "verbose" ? {
|
|
59345
|
+
items: findings.map((f) => ({
|
|
59346
|
+
message: f.message,
|
|
59347
|
+
severity: f.severity,
|
|
59348
|
+
...f.rule ? { rule: f.rule } : {},
|
|
59349
|
+
...f.file ? { file: f.file } : {}
|
|
59350
|
+
}))
|
|
59351
|
+
} : {}
|
|
59352
|
+
};
|
|
59353
|
+
}
|
|
59354
|
+
if (opName === "implementer") {
|
|
59355
|
+
const impl = output;
|
|
59356
|
+
const filesChanged = (impl?.filesChanged ?? []).length;
|
|
59357
|
+
if (isThreeSession) {
|
|
59358
|
+
return { kind: "authoring", role: "implementer", filesChanged, isolationPassed: impl?.isolation?.passed };
|
|
59359
|
+
}
|
|
59360
|
+
return { kind: "authoring", role: "implementer", filesChanged };
|
|
59361
|
+
}
|
|
59362
|
+
if (opName === "test-writer") {
|
|
59363
|
+
const tw = output;
|
|
59364
|
+
return { kind: "authoring", role: "test-writer", filesChanged: (tw?.filesChanged ?? []).length };
|
|
59365
|
+
}
|
|
59366
|
+
if (opName === "full-suite-gate") {
|
|
59367
|
+
const gate = output;
|
|
59368
|
+
return { kind: "gate", gate: "full-suite", failureCount: gate?.failureCount ?? 0 };
|
|
59369
|
+
}
|
|
59370
|
+
if (opName === "verifier" || opName === "verify-scoped") {
|
|
59371
|
+
const verdict = output;
|
|
59372
|
+
return { kind: "verdict", role: opName, passed: verdict.passed ?? false, failureCount: verdict.failureCount ?? 0 };
|
|
59373
|
+
}
|
|
59374
|
+
return;
|
|
59375
|
+
}
|
|
59376
|
+
function derivePhaseOutcome(output) {
|
|
59377
|
+
const built = buildPhaseOutcomeLogData(undefined, "", output, 0);
|
|
59378
|
+
if (!built)
|
|
59379
|
+
return "passed";
|
|
59380
|
+
if (built.success)
|
|
59381
|
+
return "passed";
|
|
59382
|
+
if (built.data.status === "skipped")
|
|
59383
|
+
return "skipped";
|
|
59384
|
+
return "failed";
|
|
59385
|
+
}
|
|
59135
59386
|
function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
|
|
59136
59387
|
if (!enabled)
|
|
59137
59388
|
return strategies;
|
|
@@ -59155,7 +59406,7 @@ function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
|
|
|
59155
59406
|
}
|
|
59156
59407
|
}));
|
|
59157
59408
|
}
|
|
59158
|
-
var _storyOrchestratorDeps;
|
|
59409
|
+
var _storyOrchestratorDeps, ALL_FINDING_SEVERITIES;
|
|
59159
59410
|
var init_run_phase = __esm(() => {
|
|
59160
59411
|
init_findings();
|
|
59161
59412
|
init_logger2();
|
|
@@ -59185,6 +59436,14 @@ var init_run_phase = __esm(() => {
|
|
|
59185
59436
|
},
|
|
59186
59437
|
loadCheckpoints: async (_featureDir) => new Map
|
|
59187
59438
|
};
|
|
59439
|
+
ALL_FINDING_SEVERITIES = [
|
|
59440
|
+
"critical",
|
|
59441
|
+
"error",
|
|
59442
|
+
"warning",
|
|
59443
|
+
"info",
|
|
59444
|
+
"low",
|
|
59445
|
+
"unverifiable"
|
|
59446
|
+
];
|
|
59188
59447
|
});
|
|
59189
59448
|
|
|
59190
59449
|
// src/execution/story-orchestrator/rectification.ts
|
|
@@ -59408,6 +59667,15 @@ class ExecutionPlan {
|
|
|
59408
59667
|
this.state = state;
|
|
59409
59668
|
this.isThreeSession = isThreeSession;
|
|
59410
59669
|
}
|
|
59670
|
+
describeGateRegressionNow(phaseOutputs, gateName, baselineKeys) {
|
|
59671
|
+
return describeGateRegression({
|
|
59672
|
+
gateOutput: gateName === undefined ? undefined : phaseOutputs[gateName],
|
|
59673
|
+
baselineKeys,
|
|
59674
|
+
gateName,
|
|
59675
|
+
storyId: this.ctx.storyId,
|
|
59676
|
+
quarantineMemo: this.ctx.runtime.quarantineMemo
|
|
59677
|
+
});
|
|
59678
|
+
}
|
|
59411
59679
|
phaseNames() {
|
|
59412
59680
|
const names = collectOrderedPhases(this.state).map((p) => p.slot.op.name);
|
|
59413
59681
|
if (this.state.rectification) {
|
|
@@ -59544,7 +59812,7 @@ class ExecutionPlan {
|
|
|
59544
59812
|
const storyCurrentlyGreen = !rectResult.rectificationExhausted && Object.entries(phaseOutputs).every(([name, output]) => phasePassed(name, output, this.ctx.storyId));
|
|
59545
59813
|
const advCfg = this.state.adversarialReview ? this.state.nonBlockingFix : undefined;
|
|
59546
59814
|
const advisoryOut = phaseOutputs["adversarial-review"];
|
|
59547
|
-
const advisoryFindings = advisoryOut?.advisoryFindings ?? [];
|
|
59815
|
+
const advisoryFindings = actionableAdvisoryFindings(advisoryOut?.advisoryFindings ?? []);
|
|
59548
59816
|
if (advCfg && storyCurrentlyGreen && this.state.rectification && this.ctx.storyId && shouldRunNonBlockingFix(advCfg, advisoryFindings.length)) {
|
|
59549
59817
|
await _storyOrchestratorDeps.runNonBlockingFix({
|
|
59550
59818
|
workdir: this.ctx.packageDir,
|
|
@@ -59561,7 +59829,7 @@ class ExecutionPlan {
|
|
|
59561
59829
|
maxAttempts,
|
|
59562
59830
|
postValidate: this.state.nonBlockingFixPostValidate
|
|
59563
59831
|
}),
|
|
59564
|
-
keptTreeRegressed: () =>
|
|
59832
|
+
keptTreeRegressed: () => this.describeGateRegressionNow(phaseOutputs, gateName, preRectGateFailureKeys)
|
|
59565
59833
|
}, {
|
|
59566
59834
|
measureSourceDiff: createMeasureSourceDiff({
|
|
59567
59835
|
config: this.ctx.runtime.configLoader.current(),
|
|
@@ -59572,7 +59840,7 @@ class ExecutionPlan {
|
|
|
59572
59840
|
}
|
|
59573
59841
|
const verifierName = this.state.verifier?.slot.op.name;
|
|
59574
59842
|
const verifierExplicitlyPassed = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
|
|
59575
|
-
const gateRegressedDuringRect =
|
|
59843
|
+
const gateRegressedDuringRect = this.describeGateRegressionNow(phaseOutputs, gateName, preRectGateFailureKeys).regressed;
|
|
59576
59844
|
const verifierPassedSsot = verifierExplicitlyPassed && !gateRegressedDuringRect;
|
|
59577
59845
|
if (verifierExplicitlyPassed && gateRegressedDuringRect) {
|
|
59578
59846
|
logger?.warn("story-orchestrator", "Gate regressed during rectification after verifier passed \u2014 verifier verdict is stale, failing story", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
|
|
@@ -59792,8 +60060,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
59792
60060
|
if (inputs.adversarialReview) {
|
|
59793
60061
|
builder.addAdversarialReview(inputs.adversarialReview);
|
|
59794
60062
|
}
|
|
59795
|
-
const
|
|
59796
|
-
const
|
|
60063
|
+
const repoRoot = ctx.packageDir;
|
|
60064
|
+
const packageDir = join50(repoRoot, story.workdir ?? "");
|
|
60065
|
+
const resolvedTestPatterns = await resolveTestFilePatterns(config2, repoRoot, story.workdir);
|
|
59797
60066
|
if (shouldRunRectification(config2) && inputs.rectification) {
|
|
59798
60067
|
const sink = makeDeclarationSink();
|
|
59799
60068
|
const strategies = [];
|
|
@@ -59829,7 +60098,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
59829
60098
|
files: h.files,
|
|
59830
60099
|
reasonDetail: h.reasonDetail
|
|
59831
60100
|
}));
|
|
59832
|
-
const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir
|
|
60101
|
+
const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir, {
|
|
60102
|
+
repoRoot
|
|
60103
|
+
});
|
|
59833
60104
|
sink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
|
|
59834
60105
|
const allDeclarations = [...sink.testEdits, ...valid];
|
|
59835
60106
|
sink.testEdits = [];
|
|
@@ -59888,7 +60159,9 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
59888
60159
|
files: h.files,
|
|
59889
60160
|
reasonDetail: h.reasonDetail
|
|
59890
60161
|
}));
|
|
59891
|
-
const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir
|
|
60162
|
+
const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir, {
|
|
60163
|
+
repoRoot
|
|
60164
|
+
});
|
|
59892
60165
|
nbSink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
|
|
59893
60166
|
const allDeclarations = [...nbSink.testEdits, ...valid];
|
|
59894
60167
|
nbSink.testEdits = [];
|
|
@@ -60827,7 +61100,12 @@ var init_execution = __esm(() => {
|
|
|
60827
61100
|
featureName: ctx.prd.feature,
|
|
60828
61101
|
story: ctx.story,
|
|
60829
61102
|
...ctx.featureDir ? { featureDir: ctx.featureDir } : {},
|
|
60830
|
-
...interactionBridge ? { interactionBridge } : {}
|
|
61103
|
+
...interactionBridge ? { interactionBridge } : {},
|
|
61104
|
+
phaseTelemetry: {
|
|
61105
|
+
testStrategy: ctx.routing.testStrategy,
|
|
61106
|
+
sessionModel: isThreeSessionStrategy(ctx.routing.testStrategy) ? "three-session" : "single-session",
|
|
61107
|
+
tier: effectiveTier
|
|
61108
|
+
}
|
|
60831
61109
|
};
|
|
60832
61110
|
let capturedTokenUsage;
|
|
60833
61111
|
let capturedResponse = "";
|
|
@@ -61590,6 +61868,196 @@ var init_stages = __esm(() => {
|
|
|
61590
61868
|
preRunPipeline = [acceptanceSetupStage];
|
|
61591
61869
|
});
|
|
61592
61870
|
|
|
61871
|
+
// src/pipeline/subscribers/reporters.ts
|
|
61872
|
+
async function fanOutReporters(reporters, hook, invoke) {
|
|
61873
|
+
const logger = getSafeLogger();
|
|
61874
|
+
for (const reporter of reporters) {
|
|
61875
|
+
try {
|
|
61876
|
+
await invoke(reporter);
|
|
61877
|
+
} catch (err) {
|
|
61878
|
+
try {
|
|
61879
|
+
logger?.warn("plugins", `Reporter '${reporter.name}' ${hook} failed`, { error: err });
|
|
61880
|
+
} catch {}
|
|
61881
|
+
}
|
|
61882
|
+
}
|
|
61883
|
+
}
|
|
61884
|
+
function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
61885
|
+
const logger = getSafeLogger();
|
|
61886
|
+
const safe = (name, fn) => {
|
|
61887
|
+
return fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) })).catch(() => {});
|
|
61888
|
+
};
|
|
61889
|
+
const unsubs = [];
|
|
61890
|
+
const phaseStart = (event) => fanOutReporters(pluginRegistry.getReporters(), "onPhaseStart", (reporter) => reporter.onPhaseStart?.(event));
|
|
61891
|
+
const phaseComplete = (event) => fanOutReporters(pluginRegistry.getReporters(), "onPhaseComplete", (reporter) => reporter.onPhaseComplete?.(event));
|
|
61892
|
+
unsubs.push(bus.on("story:step", (ev) => phaseStart({
|
|
61893
|
+
runId,
|
|
61894
|
+
scope: "story",
|
|
61895
|
+
storyId: ev.storyId,
|
|
61896
|
+
phase: ev.step,
|
|
61897
|
+
startTime: new Date().toISOString()
|
|
61898
|
+
})), bus.on("story:phase:completed", (phaseEvent) => phaseComplete({
|
|
61899
|
+
runId,
|
|
61900
|
+
scope: "story",
|
|
61901
|
+
storyId: phaseEvent.storyId,
|
|
61902
|
+
phase: phaseEvent.phase,
|
|
61903
|
+
outcome: phaseEvent.outcome,
|
|
61904
|
+
durationMs: phaseEvent.durationMs,
|
|
61905
|
+
costUsd: phaseEvent.costUsd,
|
|
61906
|
+
tier: phaseEvent.tier,
|
|
61907
|
+
testStrategy: phaseEvent.testStrategy,
|
|
61908
|
+
sessionModel: phaseEvent.sessionModel,
|
|
61909
|
+
details: phaseEvent.details
|
|
61910
|
+
})), bus.on("postrun:phase:started", (ev) => phaseStart({
|
|
61911
|
+
runId,
|
|
61912
|
+
scope: "run",
|
|
61913
|
+
phase: ev.phase,
|
|
61914
|
+
startTime: new Date().toISOString()
|
|
61915
|
+
})), bus.on("postrun:phase:completed", (phaseEvent) => phaseComplete({
|
|
61916
|
+
runId,
|
|
61917
|
+
scope: "run",
|
|
61918
|
+
phase: phaseEvent.phase,
|
|
61919
|
+
outcome: phaseEvent.passed ? "passed" : "failed",
|
|
61920
|
+
durationMs: phaseEvent.durationMs ?? 0,
|
|
61921
|
+
costUsd: phaseEvent.costUsd,
|
|
61922
|
+
details: phaseEvent.details
|
|
61923
|
+
})));
|
|
61924
|
+
unsubs.push(bus.on("run:started", (ev) => {
|
|
61925
|
+
return safe("onRunStart", async () => {
|
|
61926
|
+
const reporters = pluginRegistry.getReporters();
|
|
61927
|
+
for (const r of reporters) {
|
|
61928
|
+
if (r.onRunStart) {
|
|
61929
|
+
try {
|
|
61930
|
+
await r.onRunStart({
|
|
61931
|
+
runId,
|
|
61932
|
+
feature: ev.feature,
|
|
61933
|
+
totalStories: ev.totalStories,
|
|
61934
|
+
startTime: new Date(startTime).toISOString()
|
|
61935
|
+
});
|
|
61936
|
+
} catch (err) {
|
|
61937
|
+
logger?.warn("plugins", `Reporter '${r.name}' onRunStart failed`, { error: err });
|
|
61938
|
+
}
|
|
61939
|
+
}
|
|
61940
|
+
}
|
|
61941
|
+
});
|
|
61942
|
+
}));
|
|
61943
|
+
unsubs.push(bus.on("story:completed", (ev) => {
|
|
61944
|
+
return safe("onStoryComplete(completed)", async () => {
|
|
61945
|
+
const reporters = pluginRegistry.getReporters();
|
|
61946
|
+
for (const r of reporters) {
|
|
61947
|
+
if (r.onStoryComplete) {
|
|
61948
|
+
try {
|
|
61949
|
+
await r.onStoryComplete({
|
|
61950
|
+
runId,
|
|
61951
|
+
storyId: ev.storyId,
|
|
61952
|
+
status: "completed",
|
|
61953
|
+
runElapsedMs: ev.runElapsedMs,
|
|
61954
|
+
cost: ev.cost ?? 0,
|
|
61955
|
+
tier: ev.modelTier ?? "balanced",
|
|
61956
|
+
testStrategy: ev.testStrategy ?? "test-after"
|
|
61957
|
+
});
|
|
61958
|
+
} catch (err) {
|
|
61959
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
61960
|
+
}
|
|
61961
|
+
}
|
|
61962
|
+
}
|
|
61963
|
+
});
|
|
61964
|
+
}));
|
|
61965
|
+
unsubs.push(bus.on("story:failed", (ev) => {
|
|
61966
|
+
return safe("onStoryComplete(failed)", async () => {
|
|
61967
|
+
const reporters = pluginRegistry.getReporters();
|
|
61968
|
+
for (const r of reporters) {
|
|
61969
|
+
if (r.onStoryComplete) {
|
|
61970
|
+
try {
|
|
61971
|
+
await r.onStoryComplete({
|
|
61972
|
+
runId,
|
|
61973
|
+
storyId: ev.storyId,
|
|
61974
|
+
status: "failed",
|
|
61975
|
+
runElapsedMs: Date.now() - startTime,
|
|
61976
|
+
cost: 0,
|
|
61977
|
+
tier: "balanced",
|
|
61978
|
+
testStrategy: "test-after"
|
|
61979
|
+
});
|
|
61980
|
+
} catch (err) {
|
|
61981
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
61982
|
+
}
|
|
61983
|
+
}
|
|
61984
|
+
}
|
|
61985
|
+
});
|
|
61986
|
+
}));
|
|
61987
|
+
unsubs.push(bus.on("story:paused", (ev) => {
|
|
61988
|
+
return safe("onStoryComplete(paused)", async () => {
|
|
61989
|
+
const reporters = pluginRegistry.getReporters();
|
|
61990
|
+
for (const r of reporters) {
|
|
61991
|
+
if (r.onStoryComplete) {
|
|
61992
|
+
try {
|
|
61993
|
+
await r.onStoryComplete({
|
|
61994
|
+
runId,
|
|
61995
|
+
storyId: ev.storyId,
|
|
61996
|
+
status: "paused",
|
|
61997
|
+
runElapsedMs: Date.now() - startTime,
|
|
61998
|
+
cost: 0,
|
|
61999
|
+
tier: "balanced",
|
|
62000
|
+
testStrategy: "test-after"
|
|
62001
|
+
});
|
|
62002
|
+
} catch (err) {
|
|
62003
|
+
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
62004
|
+
}
|
|
62005
|
+
}
|
|
62006
|
+
}
|
|
62007
|
+
});
|
|
62008
|
+
}));
|
|
62009
|
+
unsubs.push(bus.on("story:escalated", (ev) => {
|
|
62010
|
+
return safe("onEscalation", async () => {
|
|
62011
|
+
const reporters = pluginRegistry.getReporters();
|
|
62012
|
+
for (const r of reporters) {
|
|
62013
|
+
if (r.onEscalation) {
|
|
62014
|
+
try {
|
|
62015
|
+
await r.onEscalation({
|
|
62016
|
+
runId,
|
|
62017
|
+
storyId: ev.storyId,
|
|
62018
|
+
fromTier: ev.fromTier,
|
|
62019
|
+
toTier: ev.toTier
|
|
62020
|
+
});
|
|
62021
|
+
} catch (err) {
|
|
62022
|
+
logger?.warn("plugins", `Reporter '${r.name}' onEscalation failed`, { error: err });
|
|
62023
|
+
}
|
|
62024
|
+
}
|
|
62025
|
+
}
|
|
62026
|
+
});
|
|
62027
|
+
}));
|
|
62028
|
+
unsubs.push(bus.on("run:completed", (ev) => {
|
|
62029
|
+
return safe("onRunEnd", async () => {
|
|
62030
|
+
const reporters = pluginRegistry.getReporters();
|
|
62031
|
+
for (const r of reporters) {
|
|
62032
|
+
if (r.onRunEnd) {
|
|
62033
|
+
try {
|
|
62034
|
+
await r.onRunEnd({
|
|
62035
|
+
runId,
|
|
62036
|
+
totalDurationMs: Date.now() - startTime,
|
|
62037
|
+
totalCost: ev.totalCost ?? 0,
|
|
62038
|
+
storySummary: {
|
|
62039
|
+
completed: ev.passedStories,
|
|
62040
|
+
failed: ev.failedStories,
|
|
62041
|
+
skipped: ev.skippedStories,
|
|
62042
|
+
paused: ev.pausedStories
|
|
62043
|
+
}
|
|
62044
|
+
});
|
|
62045
|
+
} catch (err) {
|
|
62046
|
+
logger?.warn("plugins", `Reporter '${r.name}' onRunEnd failed`, { error: err });
|
|
62047
|
+
}
|
|
62048
|
+
}
|
|
62049
|
+
}
|
|
62050
|
+
});
|
|
62051
|
+
}));
|
|
62052
|
+
return () => {
|
|
62053
|
+
for (const u of unsubs)
|
|
62054
|
+
u();
|
|
62055
|
+
};
|
|
62056
|
+
}
|
|
62057
|
+
var init_reporters = __esm(() => {
|
|
62058
|
+
init_logger2();
|
|
62059
|
+
});
|
|
62060
|
+
|
|
61593
62061
|
// src/pipeline/index.ts
|
|
61594
62062
|
var init_pipeline = __esm(() => {
|
|
61595
62063
|
init_runner4();
|
|
@@ -61597,7 +62065,9 @@ var init_pipeline = __esm(() => {
|
|
|
61597
62065
|
init_stages();
|
|
61598
62066
|
init_queue_check();
|
|
61599
62067
|
init_execution_helpers();
|
|
62068
|
+
init_acceptance_setup();
|
|
61600
62069
|
init_event_bus();
|
|
62070
|
+
init_reporters();
|
|
61601
62071
|
});
|
|
61602
62072
|
|
|
61603
62073
|
// src/cli/prompts-shared.ts
|
|
@@ -64352,6 +64822,19 @@ var init_telegram2 = __esm(() => {
|
|
|
64352
64822
|
|
|
64353
64823
|
// src/plugins/builtin/nax-finish/index.ts
|
|
64354
64824
|
import * as path21 from "path";
|
|
64825
|
+
function logTail(stream) {
|
|
64826
|
+
if (stream.length <= LOG_TAIL_CHARS)
|
|
64827
|
+
return stream;
|
|
64828
|
+
return `[\u2026${stream.length - LOG_TAIL_CHARS} chars truncated\u2026]
|
|
64829
|
+
${stream.slice(-LOG_TAIL_CHARS)}`;
|
|
64830
|
+
}
|
|
64831
|
+
function stderrTail(stderr) {
|
|
64832
|
+
const trimmed = stderr.trim();
|
|
64833
|
+
if (!trimmed)
|
|
64834
|
+
return "";
|
|
64835
|
+
const tail = trimmed.length > STDERR_TAIL_CHARS ? `\u2026${trimmed.slice(-STDERR_TAIL_CHARS)}` : trimmed;
|
|
64836
|
+
return tail.replace(/\s+/g, " ");
|
|
64837
|
+
}
|
|
64355
64838
|
async function defaultRun2(cmd, opts) {
|
|
64356
64839
|
const proc = Bun.spawn(cmd, { cwd: opts.cwd, env: opts.env, stdout: "pipe", stderr: "pipe" });
|
|
64357
64840
|
let timedOut = false;
|
|
@@ -64425,7 +64908,7 @@ function buildFlowEnv(cfg) {
|
|
|
64425
64908
|
env2.NAX_FINISH_QUALITY_PROFILE = cfg.reviewers.quality;
|
|
64426
64909
|
return env2;
|
|
64427
64910
|
}
|
|
64428
|
-
var PLUGIN_NAME4 = "nax-finish", PLUGIN_VERSION4 = "0.1.0", PACKAGE_ROOT_SEARCH_DEPTH = 6, _naxFinishDeps, naxFinishAction, naxFinishPlugin;
|
|
64911
|
+
var PLUGIN_NAME4 = "nax-finish", PLUGIN_VERSION4 = "0.1.0", PACKAGE_ROOT_SEARCH_DEPTH = 6, STDERR_TAIL_CHARS = 400, LOG_TAIL_CHARS = 20000, _naxFinishDeps, naxFinishAction, naxFinishPlugin;
|
|
64429
64912
|
var init_nax_finish = __esm(() => {
|
|
64430
64913
|
init_config2();
|
|
64431
64914
|
init_telegram2();
|
|
@@ -64476,7 +64959,16 @@ var init_nax_finish = __esm(() => {
|
|
|
64476
64959
|
});
|
|
64477
64960
|
const result = await _naxFinishDeps.readResult(ctx.workdir);
|
|
64478
64961
|
if (!result) {
|
|
64479
|
-
|
|
64962
|
+
ctx.logger.warn("nax-finish flow produced no result file", {
|
|
64963
|
+
exitCode: res.exitCode,
|
|
64964
|
+
stdout: logTail(res.stdout),
|
|
64965
|
+
stderr: logTail(res.stderr)
|
|
64966
|
+
});
|
|
64967
|
+
const tail = stderrTail(res.stderr);
|
|
64968
|
+
return {
|
|
64969
|
+
success: false,
|
|
64970
|
+
message: `nax-finish flow exited ${res.exitCode} (no result file)${tail ? `: ${tail}` : ""}`
|
|
64971
|
+
};
|
|
64480
64972
|
}
|
|
64481
64973
|
if (result.status === "escalated" && escalateTelegram && creds) {
|
|
64482
64974
|
await _naxFinishDeps.notify(creds, `nax-finish escalated *${result.feature}*: ${result.escalationReason ?? ""}`);
|
|
@@ -64554,16 +65046,72 @@ var init_reporter_shared = __esm(() => {
|
|
|
64554
65046
|
init_post_json();
|
|
64555
65047
|
});
|
|
64556
65048
|
|
|
64557
|
-
// src/plugins/builtin/otel-reporter/
|
|
64558
|
-
function
|
|
64559
|
-
const
|
|
64560
|
-
|
|
64561
|
-
let
|
|
64562
|
-
|
|
64563
|
-
|
|
64564
|
-
|
|
65049
|
+
// src/plugins/builtin/otel-reporter/batch-queue.ts
|
|
65050
|
+
function createBatchQueue(opts) {
|
|
65051
|
+
const { maxBatchSize, flushIntervalMs, maxQueueSize, send } = opts;
|
|
65052
|
+
let queue = [];
|
|
65053
|
+
let dropCount = 0;
|
|
65054
|
+
let overflowing = false;
|
|
65055
|
+
let tornDown = false;
|
|
65056
|
+
let timer;
|
|
65057
|
+
const armTimer = () => {
|
|
65058
|
+
timer = setTimeout(() => {
|
|
65059
|
+
doFlush();
|
|
65060
|
+
if (!tornDown)
|
|
65061
|
+
armTimer();
|
|
65062
|
+
}, flushIntervalMs);
|
|
65063
|
+
};
|
|
65064
|
+
const sendWithRetry = async (batch) => {
|
|
65065
|
+
for (let attempt = 0;attempt < RETRY_ATTEMPTS; attempt++) {
|
|
65066
|
+
try {
|
|
65067
|
+
const ok = await send(batch);
|
|
65068
|
+
if (ok)
|
|
65069
|
+
return;
|
|
65070
|
+
} catch (err) {
|
|
65071
|
+
getSafeLogger()?.warn(STAGE, "Batch export threw", { error: err instanceof Error ? err.message : String(err) });
|
|
65072
|
+
}
|
|
65073
|
+
}
|
|
65074
|
+
};
|
|
65075
|
+
const doFlush = () => {
|
|
65076
|
+
if (tornDown || queue.length === 0)
|
|
65077
|
+
return Promise.resolve();
|
|
65078
|
+
const batch = queue;
|
|
65079
|
+
queue = [];
|
|
65080
|
+
sendWithRetry(batch);
|
|
65081
|
+
return Promise.resolve();
|
|
65082
|
+
};
|
|
65083
|
+
const enqueue = (item) => {
|
|
65084
|
+
queue.push(item);
|
|
65085
|
+
if (queue.length > maxQueueSize) {
|
|
65086
|
+
queue.shift();
|
|
65087
|
+
dropCount++;
|
|
65088
|
+
if (!overflowing) {
|
|
65089
|
+
overflowing = true;
|
|
65090
|
+
getSafeLogger()?.warn(STAGE, "Batch queue overflow \u2014 dropping oldest entries", { maxQueueSize });
|
|
65091
|
+
}
|
|
65092
|
+
} else {
|
|
65093
|
+
overflowing = false;
|
|
65094
|
+
}
|
|
65095
|
+
if (queue.length >= maxBatchSize) {
|
|
65096
|
+
doFlush();
|
|
65097
|
+
}
|
|
65098
|
+
};
|
|
65099
|
+
armTimer();
|
|
65100
|
+
return {
|
|
65101
|
+
enqueue,
|
|
65102
|
+
flushNow: () => doFlush(),
|
|
65103
|
+
teardown: () => {
|
|
65104
|
+
tornDown = true;
|
|
65105
|
+
if (timer !== undefined)
|
|
65106
|
+
clearTimeout(timer);
|
|
65107
|
+
},
|
|
65108
|
+
getMetrics: () => ({ size: queue.length, dropCount })
|
|
65109
|
+
};
|
|
64565
65110
|
}
|
|
64566
|
-
var
|
|
65111
|
+
var STAGE = "otel-batch-queue", RETRY_ATTEMPTS = 2;
|
|
65112
|
+
var init_batch_queue = __esm(() => {
|
|
65113
|
+
init_logger2();
|
|
65114
|
+
});
|
|
64567
65115
|
|
|
64568
65116
|
// src/plugins/builtin/otel-reporter/otlp.ts
|
|
64569
65117
|
function attr(key, value) {
|
|
@@ -64572,10 +65120,27 @@ function attr(key, value) {
|
|
|
64572
65120
|
function msToUnixNano(ms) {
|
|
64573
65121
|
return (BigInt(Math.round(ms)) * 1000000n).toString();
|
|
64574
65122
|
}
|
|
65123
|
+
function buildHistogramPoint(values, bounds, attributes, timeUnixNano) {
|
|
65124
|
+
const bucketCounts = new Array(bounds.length + 1).fill(0);
|
|
65125
|
+
let sum = 0;
|
|
65126
|
+
for (const value of values) {
|
|
65127
|
+
sum += value;
|
|
65128
|
+
const bucketIndex = bounds.findIndex((bound) => value <= bound);
|
|
65129
|
+
bucketCounts[bucketIndex === -1 ? bounds.length : bucketIndex]++;
|
|
65130
|
+
}
|
|
65131
|
+
return { attributes, timeUnixNano, count: values.length, sum, bucketCounts, explicitBounds: bounds };
|
|
65132
|
+
}
|
|
65133
|
+
function buildCounterPoint(count, attributes, timeUnixNano) {
|
|
65134
|
+
return { attributes, timeUnixNano, asInt: String(count) };
|
|
65135
|
+
}
|
|
65136
|
+
function buildResourceAttributes(serviceName, runId) {
|
|
65137
|
+
return [attr("service.name", serviceName), attr("nax.run_id", runId)];
|
|
65138
|
+
}
|
|
64575
65139
|
function buildTracesPayload(p) {
|
|
64576
65140
|
const span = {
|
|
64577
65141
|
traceId: p.traceId,
|
|
64578
65142
|
spanId: p.spanId,
|
|
65143
|
+
...p.parentSpanId ? { parentSpanId: p.parentSpanId } : {},
|
|
64579
65144
|
name: "nax.run",
|
|
64580
65145
|
kind: 1,
|
|
64581
65146
|
startTimeUnixNano: p.startUnixNano,
|
|
@@ -64596,7 +65161,7 @@ function buildTracesPayload(p) {
|
|
|
64596
65161
|
resourceSpans: [
|
|
64597
65162
|
{
|
|
64598
65163
|
resource: { attributes: [attr("service.name", p.serviceName)] },
|
|
64599
|
-
scopeSpans: [{ scope: { name: "nax" }, spans: [span] }]
|
|
65164
|
+
scopeSpans: [{ scope: { name: "nax" }, spans: [span, ...p.extraSpans ?? []] }]
|
|
64600
65165
|
}
|
|
64601
65166
|
]
|
|
64602
65167
|
};
|
|
@@ -64634,16 +65199,356 @@ function buildMetricsPayload(p) {
|
|
|
64634
65199
|
};
|
|
64635
65200
|
}
|
|
64636
65201
|
|
|
65202
|
+
// src/plugins/builtin/otel-reporter/heartbeat.ts
|
|
65203
|
+
function startHeartbeat(opts) {
|
|
65204
|
+
const { intervalMs, getSnapshot, onTick } = opts;
|
|
65205
|
+
if (intervalMs <= 0)
|
|
65206
|
+
return { stop() {} };
|
|
65207
|
+
let stopped = false;
|
|
65208
|
+
let timer;
|
|
65209
|
+
const armTimer = () => {
|
|
65210
|
+
timer = setTimeout(() => {
|
|
65211
|
+
try {
|
|
65212
|
+
Promise.resolve(onTick(getSnapshot())).catch((err) => getSafeLogger()?.warn(STAGE2, "Heartbeat tick failed", {
|
|
65213
|
+
error: err instanceof Error ? err.message : String(err)
|
|
65214
|
+
}));
|
|
65215
|
+
} catch (err) {
|
|
65216
|
+
getSafeLogger()?.warn(STAGE2, "Heartbeat tick failed", {
|
|
65217
|
+
error: err instanceof Error ? err.message : String(err)
|
|
65218
|
+
});
|
|
65219
|
+
}
|
|
65220
|
+
if (!stopped)
|
|
65221
|
+
armTimer();
|
|
65222
|
+
}, intervalMs);
|
|
65223
|
+
};
|
|
65224
|
+
armTimer();
|
|
65225
|
+
return {
|
|
65226
|
+
stop() {
|
|
65227
|
+
stopped = true;
|
|
65228
|
+
if (timer !== undefined)
|
|
65229
|
+
clearTimeout(timer);
|
|
65230
|
+
}
|
|
65231
|
+
};
|
|
65232
|
+
}
|
|
65233
|
+
function heartbeatAttributes(a) {
|
|
65234
|
+
return [
|
|
65235
|
+
attr("run_id", a.runId),
|
|
65236
|
+
attr("feature", a.feature),
|
|
65237
|
+
attr("project", a.project),
|
|
65238
|
+
attr("story_id", a.storyId),
|
|
65239
|
+
attr("phase", a.phase),
|
|
65240
|
+
attr("tier", a.tier),
|
|
65241
|
+
attr("test_strategy", a.testStrategy)
|
|
65242
|
+
];
|
|
65243
|
+
}
|
|
65244
|
+
function buildHeartbeatMetricsPayload(p) {
|
|
65245
|
+
const attributes = heartbeatAttributes(p.snapshot.attributes);
|
|
65246
|
+
const gauge = (name, value) => ({
|
|
65247
|
+
name,
|
|
65248
|
+
gauge: { dataPoints: [{ asDouble: value, timeUnixNano: p.timeUnixNano, attributes }] }
|
|
65249
|
+
});
|
|
65250
|
+
return {
|
|
65251
|
+
resourceMetrics: [
|
|
65252
|
+
{
|
|
65253
|
+
resource: { attributes: [attr("service.name", p.serviceName)] },
|
|
65254
|
+
scopeMetrics: [
|
|
65255
|
+
{
|
|
65256
|
+
scope: { name: "nax" },
|
|
65257
|
+
metrics: [
|
|
65258
|
+
gauge("nax.run.active", 1),
|
|
65259
|
+
gauge("nax.run.phase_elapsed_ms", p.snapshot.phaseElapsedMs),
|
|
65260
|
+
gauge("nax.run.cost_usd", p.snapshot.costUsd)
|
|
65261
|
+
]
|
|
65262
|
+
}
|
|
65263
|
+
]
|
|
65264
|
+
}
|
|
65265
|
+
]
|
|
65266
|
+
};
|
|
65267
|
+
}
|
|
65268
|
+
var STAGE2 = "otel-reporter-heartbeat";
|
|
65269
|
+
var init_heartbeat = __esm(() => {
|
|
65270
|
+
init_logger2();
|
|
65271
|
+
});
|
|
65272
|
+
|
|
65273
|
+
// src/plugins/builtin/otel-reporter/ids.ts
|
|
65274
|
+
function randomHex(bytes) {
|
|
65275
|
+
const arr = new Uint8Array(bytes);
|
|
65276
|
+
crypto.getRandomValues(arr);
|
|
65277
|
+
let out = "";
|
|
65278
|
+
for (const b of arr)
|
|
65279
|
+
out += b.toString(16).padStart(2, "0");
|
|
65280
|
+
return out;
|
|
65281
|
+
}
|
|
65282
|
+
var newTraceId = () => randomHex(16), newSpanId = () => randomHex(8);
|
|
65283
|
+
|
|
65284
|
+
// src/plugins/builtin/otel-reporter/span-tree.ts
|
|
65285
|
+
function createSpanTree(traceId, runSpanId) {
|
|
65286
|
+
const storySpanIds = new Map;
|
|
65287
|
+
function storySpanId(storyId) {
|
|
65288
|
+
let spanId = storySpanIds.get(storyId);
|
|
65289
|
+
if (!spanId) {
|
|
65290
|
+
spanId = newSpanId();
|
|
65291
|
+
storySpanIds.set(storyId, spanId);
|
|
65292
|
+
}
|
|
65293
|
+
return spanId;
|
|
65294
|
+
}
|
|
65295
|
+
function buildStorySpan(storyId, startUnixNano, endUnixNano) {
|
|
65296
|
+
return {
|
|
65297
|
+
traceId,
|
|
65298
|
+
spanId: storySpanId(storyId),
|
|
65299
|
+
parentSpanId: runSpanId,
|
|
65300
|
+
name: "nax.story",
|
|
65301
|
+
startTimeUnixNano: startUnixNano,
|
|
65302
|
+
endTimeUnixNano: endUnixNano,
|
|
65303
|
+
attributes: [attr("nax.story_id", storyId)]
|
|
65304
|
+
};
|
|
65305
|
+
}
|
|
65306
|
+
function buildPhaseSpan({ event, traceId: spanTraceId, startUnixNano, endUnixNano }) {
|
|
65307
|
+
const parentSpanId = event.scope === "story" && event.storyId !== undefined ? storySpanId(event.storyId) : runSpanId;
|
|
65308
|
+
const attributes = [attr("phase", event.phase), attr("outcome", event.outcome)];
|
|
65309
|
+
if (event.testStrategy)
|
|
65310
|
+
attributes.push(attr("nax.test_strategy", event.testStrategy));
|
|
65311
|
+
return {
|
|
65312
|
+
traceId: spanTraceId,
|
|
65313
|
+
spanId: newSpanId(),
|
|
65314
|
+
parentSpanId,
|
|
65315
|
+
name: "nax.phase",
|
|
65316
|
+
startTimeUnixNano: startUnixNano,
|
|
65317
|
+
endTimeUnixNano: endUnixNano,
|
|
65318
|
+
attributes
|
|
65319
|
+
};
|
|
65320
|
+
}
|
|
65321
|
+
return { traceId, runSpanId, storySpanId, buildStorySpan, buildPhaseSpan };
|
|
65322
|
+
}
|
|
65323
|
+
function counterKey(attributes) {
|
|
65324
|
+
return attributes.map((a) => `${a.key}=${a.value.stringValue ?? a.value.doubleValue}`).join("|");
|
|
65325
|
+
}
|
|
65326
|
+
function bumpCounter(groups, attributes, count) {
|
|
65327
|
+
const key = counterKey(attributes);
|
|
65328
|
+
const existing = groups.get(key);
|
|
65329
|
+
if (existing) {
|
|
65330
|
+
existing.count += count;
|
|
65331
|
+
} else {
|
|
65332
|
+
groups.set(key, { attributes, count });
|
|
65333
|
+
}
|
|
65334
|
+
}
|
|
65335
|
+
function createPhaseMetricsAggregator() {
|
|
65336
|
+
const phaseGroups = new Map;
|
|
65337
|
+
const reviewFindings = new Map;
|
|
65338
|
+
const fixIterations = new Map;
|
|
65339
|
+
const escalations = new Map;
|
|
65340
|
+
function recordPhase(event) {
|
|
65341
|
+
const attributes = [
|
|
65342
|
+
attr("phase", event.phase),
|
|
65343
|
+
attr("outcome", event.outcome),
|
|
65344
|
+
attr("tier", event.tier ?? "unknown"),
|
|
65345
|
+
attr("test_strategy", event.testStrategy ?? "unknown"),
|
|
65346
|
+
attr("session_model", event.sessionModel ?? "unknown")
|
|
65347
|
+
];
|
|
65348
|
+
const key = counterKey(attributes);
|
|
65349
|
+
let group = phaseGroups.get(key);
|
|
65350
|
+
if (!group) {
|
|
65351
|
+
group = { attributes, durations: [], costs: [] };
|
|
65352
|
+
phaseGroups.set(key, group);
|
|
65353
|
+
}
|
|
65354
|
+
group.durations.push(event.durationMs);
|
|
65355
|
+
if (event.costUsd !== undefined)
|
|
65356
|
+
group.costs.push(event.costUsd);
|
|
65357
|
+
}
|
|
65358
|
+
function recordReviewFindings(phase, severity2, count) {
|
|
65359
|
+
bumpCounter(reviewFindings, [attr("phase", phase), attr("severity", severity2)], count);
|
|
65360
|
+
}
|
|
65361
|
+
function recordFixIterations(phase, strategy, count) {
|
|
65362
|
+
bumpCounter(fixIterations, [attr("phase", phase), attr("strategy", strategy)], count);
|
|
65363
|
+
}
|
|
65364
|
+
function recordEscalation(toTier, count) {
|
|
65365
|
+
bumpCounter(escalations, [attr("to_tier", toTier)], count);
|
|
65366
|
+
}
|
|
65367
|
+
function buildMetricsPayload2(serviceName, runId, timeUnixNano) {
|
|
65368
|
+
const groups = [...phaseGroups.values()];
|
|
65369
|
+
const counterMetric = (name, source) => ({
|
|
65370
|
+
name,
|
|
65371
|
+
sum: {
|
|
65372
|
+
aggregationTemporality: 2,
|
|
65373
|
+
isMonotonic: true,
|
|
65374
|
+
dataPoints: [...source.values()].map((g) => buildCounterPoint(g.count, g.attributes, timeUnixNano))
|
|
65375
|
+
}
|
|
65376
|
+
});
|
|
65377
|
+
const metrics = [];
|
|
65378
|
+
if (groups.length > 0) {
|
|
65379
|
+
metrics.push({
|
|
65380
|
+
name: "nax.phase.duration",
|
|
65381
|
+
histogram: {
|
|
65382
|
+
aggregationTemporality: 2,
|
|
65383
|
+
dataPoints: groups.map((g) => buildHistogramPoint(g.durations, PHASE_DURATION_BOUNDS, g.attributes, timeUnixNano))
|
|
65384
|
+
}
|
|
65385
|
+
});
|
|
65386
|
+
metrics.push({
|
|
65387
|
+
name: "nax.phase.cost_usd",
|
|
65388
|
+
histogram: {
|
|
65389
|
+
aggregationTemporality: 2,
|
|
65390
|
+
dataPoints: groups.map((g) => buildHistogramPoint(g.costs, PHASE_COST_BOUNDS, g.attributes, timeUnixNano))
|
|
65391
|
+
}
|
|
65392
|
+
});
|
|
65393
|
+
}
|
|
65394
|
+
if (reviewFindings.size > 0)
|
|
65395
|
+
metrics.push(counterMetric("nax.review.findings", reviewFindings));
|
|
65396
|
+
if (fixIterations.size > 0)
|
|
65397
|
+
metrics.push(counterMetric("nax.fix.iterations", fixIterations));
|
|
65398
|
+
if (escalations.size > 0)
|
|
65399
|
+
metrics.push(counterMetric("nax.escalations", escalations));
|
|
65400
|
+
return {
|
|
65401
|
+
resourceMetrics: [
|
|
65402
|
+
{
|
|
65403
|
+
resource: { attributes: buildResourceAttributes(serviceName, runId) },
|
|
65404
|
+
scopeMetrics: [{ scope: { name: "nax" }, metrics }]
|
|
65405
|
+
}
|
|
65406
|
+
]
|
|
65407
|
+
};
|
|
65408
|
+
}
|
|
65409
|
+
return { recordPhase, recordReviewFindings, recordFixIterations, recordEscalation, buildMetricsPayload: buildMetricsPayload2 };
|
|
65410
|
+
}
|
|
65411
|
+
var PHASE_DURATION_BOUNDS, PHASE_COST_BOUNDS;
|
|
65412
|
+
var init_span_tree = __esm(() => {
|
|
65413
|
+
PHASE_DURATION_BOUNDS = [100, 500, 1000, 5000, 15000, 60000, 300000, 900000];
|
|
65414
|
+
PHASE_COST_BOUNDS = [0.001, 0.01, 0.05, 0.1, 0.5, 1, 5];
|
|
65415
|
+
});
|
|
65416
|
+
|
|
65417
|
+
// src/plugins/builtin/otel-reporter/traceparent.ts
|
|
65418
|
+
function parseTraceparent(value) {
|
|
65419
|
+
if (!value)
|
|
65420
|
+
return null;
|
|
65421
|
+
const match = TRACEPARENT_PATTERN.exec(value);
|
|
65422
|
+
if (!match)
|
|
65423
|
+
return null;
|
|
65424
|
+
const [, traceId, spanId] = match;
|
|
65425
|
+
if (!traceId || !spanId || ALL_ZERO.test(traceId))
|
|
65426
|
+
return null;
|
|
65427
|
+
return { traceId, spanId };
|
|
65428
|
+
}
|
|
65429
|
+
var TRACEPARENT_PATTERN, ALL_ZERO;
|
|
65430
|
+
var init_traceparent = __esm(() => {
|
|
65431
|
+
TRACEPARENT_PATTERN = /^[0-9a-f]{2}-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
|
|
65432
|
+
ALL_ZERO = /^0+$/;
|
|
65433
|
+
});
|
|
65434
|
+
|
|
64637
65435
|
// src/plugins/builtin/otel-reporter/index.ts
|
|
65436
|
+
function rootSpanIdentity() {
|
|
65437
|
+
const adopted = parseTraceparent(process.env.TRACEPARENT);
|
|
65438
|
+
if (!adopted)
|
|
65439
|
+
return { traceId: newTraceId(), spanId: newSpanId() };
|
|
65440
|
+
return { traceId: adopted.traceId, spanId: newSpanId(), parentSpanId: adopted.spanId };
|
|
65441
|
+
}
|
|
65442
|
+
function heartbeatSnapshotOf(runId, st) {
|
|
65443
|
+
const last = st.lastPhase;
|
|
65444
|
+
return {
|
|
65445
|
+
attributes: {
|
|
65446
|
+
runId,
|
|
65447
|
+
feature: st.feature,
|
|
65448
|
+
project: st.project,
|
|
65449
|
+
storyId: last?.storyId ?? "",
|
|
65450
|
+
phase: last?.phase ?? "",
|
|
65451
|
+
tier: last?.tier ?? "",
|
|
65452
|
+
testStrategy: last?.testStrategy ?? ""
|
|
65453
|
+
},
|
|
65454
|
+
phaseElapsedMs: last ? Date.now() - last.atMs : 0,
|
|
65455
|
+
costUsd: st.costUsd
|
|
65456
|
+
};
|
|
65457
|
+
}
|
|
65458
|
+
function recordDetailMetrics(metrics, phase, details) {
|
|
65459
|
+
if (typeof details !== "object" || details === null)
|
|
65460
|
+
return;
|
|
65461
|
+
const record2 = details;
|
|
65462
|
+
if (record2.kind === "review" && typeof record2.bySeverity === "object" && record2.bySeverity !== null) {
|
|
65463
|
+
for (const [severity2, count] of Object.entries(record2.bySeverity)) {
|
|
65464
|
+
if (count > 0)
|
|
65465
|
+
metrics.recordReviewFindings(phase, severity2, count);
|
|
65466
|
+
}
|
|
65467
|
+
} else if (record2.kind === "fix" && typeof record2.strategy === "string") {
|
|
65468
|
+
metrics.recordFixIterations(phase, record2.strategy, 1);
|
|
65469
|
+
}
|
|
65470
|
+
}
|
|
65471
|
+
function reviewSpanEvents(details, timeUnixNano, verbose) {
|
|
65472
|
+
if (!verbose)
|
|
65473
|
+
return [];
|
|
65474
|
+
if (typeof details !== "object" || details === null)
|
|
65475
|
+
return [];
|
|
65476
|
+
const record2 = details;
|
|
65477
|
+
if (record2.kind !== "review" || !Array.isArray(record2.items))
|
|
65478
|
+
return [];
|
|
65479
|
+
return record2.items.map((item) => {
|
|
65480
|
+
const finding = typeof item === "object" && item !== null ? item : {};
|
|
65481
|
+
const attributes = [attr("message", String(finding.message ?? ""))];
|
|
65482
|
+
if (typeof finding.file === "string")
|
|
65483
|
+
attributes.push(attr("file", finding.file));
|
|
65484
|
+
return { timeUnixNano, name: "review.finding", attributes };
|
|
65485
|
+
});
|
|
65486
|
+
}
|
|
64638
65487
|
function createOtelReporterPlugin(cfg, deps) {
|
|
64639
65488
|
const states = new Map;
|
|
64640
65489
|
const base = cfg.endpoint?.replace(/\/$/, "");
|
|
65490
|
+
let tornDown = false;
|
|
65491
|
+
const sendSpanBatch = async (batch) => {
|
|
65492
|
+
if (!base || batch.length === 0)
|
|
65493
|
+
return true;
|
|
65494
|
+
const { resolved, missing } = interpolateHeaders(cfg.headers);
|
|
65495
|
+
if (missing.length > 0) {
|
|
65496
|
+
getSafeLogger()?.warn(STAGE3, "Skipping OTLP export \u2014 unresolved env vars", { missing });
|
|
65497
|
+
return true;
|
|
65498
|
+
}
|
|
65499
|
+
const payload = {
|
|
65500
|
+
resourceSpans: [
|
|
65501
|
+
{
|
|
65502
|
+
resource: { attributes: [attr("service.name", cfg.serviceName)] },
|
|
65503
|
+
scopeSpans: [{ scope: { name: "nax" }, spans: batch }]
|
|
65504
|
+
}
|
|
65505
|
+
]
|
|
65506
|
+
};
|
|
65507
|
+
return postJson(`${base}/v1/traces`, payload, { headers: resolved, timeoutMs: cfg.timeoutMs, stage: STAGE3, deps });
|
|
65508
|
+
};
|
|
65509
|
+
const makeSpanQueue = () => createBatchQueue({
|
|
65510
|
+
maxBatchSize: cfg.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE,
|
|
65511
|
+
flushIntervalMs: cfg.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
65512
|
+
maxQueueSize: cfg.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
|
|
65513
|
+
send: sendSpanBatch
|
|
65514
|
+
});
|
|
65515
|
+
const buildOrphanState = (startMs) => {
|
|
65516
|
+
const identity = rootSpanIdentity();
|
|
65517
|
+
return {
|
|
65518
|
+
...identity,
|
|
65519
|
+
startMs,
|
|
65520
|
+
feature: "",
|
|
65521
|
+
project: "",
|
|
65522
|
+
events: [],
|
|
65523
|
+
spanTree: createSpanTree(identity.traceId, identity.spanId),
|
|
65524
|
+
spanQueue: makeSpanQueue(),
|
|
65525
|
+
metrics: createPhaseMetricsAggregator(),
|
|
65526
|
+
storyBounds: new Map,
|
|
65527
|
+
costUsd: 0,
|
|
65528
|
+
heartbeat: { stop() {} }
|
|
65529
|
+
};
|
|
65530
|
+
};
|
|
65531
|
+
const exportHeartbeat = async (snapshot) => {
|
|
65532
|
+
if (!base)
|
|
65533
|
+
return;
|
|
65534
|
+
const { resolved, missing } = interpolateHeaders(cfg.headers);
|
|
65535
|
+
if (missing.length > 0) {
|
|
65536
|
+
getSafeLogger()?.warn(STAGE3, "Skipping OTLP export \u2014 unresolved env vars", { missing });
|
|
65537
|
+
return;
|
|
65538
|
+
}
|
|
65539
|
+
const metrics = buildHeartbeatMetricsPayload({
|
|
65540
|
+
serviceName: cfg.serviceName,
|
|
65541
|
+
timeUnixNano: msToUnixNano(Date.now()),
|
|
65542
|
+
snapshot
|
|
65543
|
+
});
|
|
65544
|
+
await postJson(`${base}/v1/metrics`, metrics, { headers: resolved, timeoutMs: cfg.timeoutMs, stage: STAGE3, deps });
|
|
65545
|
+
};
|
|
64641
65546
|
const flush = async (st, endMs, e) => {
|
|
64642
65547
|
if (!base)
|
|
64643
65548
|
return;
|
|
64644
65549
|
const { resolved, missing } = interpolateHeaders(cfg.headers);
|
|
64645
65550
|
if (missing.length > 0) {
|
|
64646
|
-
getSafeLogger()?.warn(
|
|
65551
|
+
getSafeLogger()?.warn(STAGE3, "Skipping OTLP export \u2014 unresolved env vars", { missing });
|
|
64647
65552
|
return;
|
|
64648
65553
|
}
|
|
64649
65554
|
const startUnixNano = msToUnixNano(st.startMs);
|
|
@@ -64652,6 +65557,7 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
64652
65557
|
serviceName: cfg.serviceName,
|
|
64653
65558
|
traceId: st.traceId,
|
|
64654
65559
|
spanId: st.spanId,
|
|
65560
|
+
parentSpanId: st.parentSpanId,
|
|
64655
65561
|
startUnixNano,
|
|
64656
65562
|
endUnixNano,
|
|
64657
65563
|
feature: st.feature,
|
|
@@ -64668,20 +65574,35 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
64668
65574
|
totalCost: e.totalCost,
|
|
64669
65575
|
totalDurationMs: e.totalDurationMs
|
|
64670
65576
|
});
|
|
64671
|
-
const
|
|
65577
|
+
const aggMetrics = st.metrics.buildMetricsPayload(cfg.serviceName, e.runId, endUnixNano);
|
|
65578
|
+
metrics.resourceMetrics[0].scopeMetrics[0].metrics.push(...aggMetrics.resourceMetrics[0].scopeMetrics[0].metrics);
|
|
65579
|
+
const opts = { headers: resolved, timeoutMs: cfg.timeoutMs, stage: STAGE3, deps };
|
|
64672
65580
|
await postJson(`${base}/v1/traces`, traces, opts);
|
|
64673
65581
|
await postJson(`${base}/v1/metrics`, metrics, opts);
|
|
64674
65582
|
};
|
|
64675
65583
|
const reporter = {
|
|
64676
|
-
name:
|
|
65584
|
+
name: STAGE3,
|
|
64677
65585
|
async onRunStart(event) {
|
|
64678
|
-
|
|
64679
|
-
|
|
64680
|
-
|
|
65586
|
+
const identity = rootSpanIdentity();
|
|
65587
|
+
const runId = event.runId;
|
|
65588
|
+
const state = {
|
|
65589
|
+
...identity,
|
|
64681
65590
|
startMs: Date.parse(event.startTime),
|
|
64682
65591
|
feature: event.feature,
|
|
64683
|
-
|
|
64684
|
-
|
|
65592
|
+
project: event.project ?? "",
|
|
65593
|
+
events: [],
|
|
65594
|
+
spanTree: createSpanTree(identity.traceId, identity.spanId),
|
|
65595
|
+
spanQueue: makeSpanQueue(),
|
|
65596
|
+
metrics: createPhaseMetricsAggregator(),
|
|
65597
|
+
storyBounds: new Map,
|
|
65598
|
+
costUsd: 0,
|
|
65599
|
+
heartbeat: startHeartbeat({
|
|
65600
|
+
intervalMs: cfg.heartbeatIntervalMs ?? 0,
|
|
65601
|
+
getSnapshot: () => heartbeatSnapshotOf(runId, state),
|
|
65602
|
+
onTick: (snapshot) => exportHeartbeat(snapshot)
|
|
65603
|
+
})
|
|
65604
|
+
};
|
|
65605
|
+
states.set(runId, state);
|
|
64685
65606
|
},
|
|
64686
65607
|
async onStoryComplete(event) {
|
|
64687
65608
|
const st = states.get(event.runId);
|
|
@@ -64698,32 +65619,98 @@ function createOtelReporterPlugin(cfg, deps) {
|
|
|
64698
65619
|
attr("testStrategy", event.testStrategy)
|
|
64699
65620
|
]
|
|
64700
65621
|
});
|
|
65622
|
+
const bounds = st.storyBounds.get(event.storyId);
|
|
65623
|
+
if (bounds) {
|
|
65624
|
+
st.spanQueue.enqueue(st.spanTree.buildStorySpan(event.storyId, msToUnixNano(bounds.startMs), msToUnixNano(bounds.endMs)));
|
|
65625
|
+
st.storyBounds.delete(event.storyId);
|
|
65626
|
+
}
|
|
65627
|
+
},
|
|
65628
|
+
async onPhaseComplete(event) {
|
|
65629
|
+
const st = states.get(event.runId);
|
|
65630
|
+
if (!st)
|
|
65631
|
+
return;
|
|
65632
|
+
st.costUsd += event.costUsd ?? 0;
|
|
65633
|
+
st.lastPhase = {
|
|
65634
|
+
phase: event.phase,
|
|
65635
|
+
storyId: event.storyId ?? "",
|
|
65636
|
+
tier: event.tier ?? "",
|
|
65637
|
+
testStrategy: event.testStrategy ?? "",
|
|
65638
|
+
atMs: Date.now()
|
|
65639
|
+
};
|
|
65640
|
+
const endMs = Date.now();
|
|
65641
|
+
const startMs = endMs - event.durationMs;
|
|
65642
|
+
const endUnixNano = msToUnixNano(endMs);
|
|
65643
|
+
const span = st.spanTree.buildPhaseSpan({
|
|
65644
|
+
event,
|
|
65645
|
+
traceId: st.traceId,
|
|
65646
|
+
startUnixNano: msToUnixNano(startMs),
|
|
65647
|
+
endUnixNano
|
|
65648
|
+
});
|
|
65649
|
+
const events = reviewSpanEvents(event.details, endUnixNano, cfg.detail === "verbose");
|
|
65650
|
+
if (events.length > 0)
|
|
65651
|
+
span.events = events;
|
|
65652
|
+
st.spanQueue.enqueue(span);
|
|
65653
|
+
st.metrics.recordPhase(event);
|
|
65654
|
+
recordDetailMetrics(st.metrics, event.phase, event.details);
|
|
65655
|
+
if (event.scope === "story" && event.storyId !== undefined) {
|
|
65656
|
+
const bounds = st.storyBounds.get(event.storyId);
|
|
65657
|
+
st.storyBounds.set(event.storyId, {
|
|
65658
|
+
startMs: bounds ? Math.min(bounds.startMs, startMs) : startMs,
|
|
65659
|
+
endMs: bounds ? Math.max(bounds.endMs, endMs) : endMs
|
|
65660
|
+
});
|
|
65661
|
+
}
|
|
65662
|
+
},
|
|
65663
|
+
async onEscalation(event) {
|
|
65664
|
+
const st = states.get(event.runId);
|
|
65665
|
+
if (!st)
|
|
65666
|
+
return;
|
|
65667
|
+
st.metrics.recordEscalation(event.toTier, 1);
|
|
64701
65668
|
},
|
|
64702
65669
|
async onRunEnd(event) {
|
|
64703
65670
|
const existing = states.get(event.runId);
|
|
65671
|
+
existing?.heartbeat.stop();
|
|
64704
65672
|
const startMs = existing?.startMs ?? Date.now() - event.totalDurationMs;
|
|
64705
|
-
const st = existing ??
|
|
64706
|
-
traceId: newTraceId(),
|
|
64707
|
-
spanId: newSpanId(),
|
|
64708
|
-
startMs,
|
|
64709
|
-
feature: "",
|
|
64710
|
-
events: []
|
|
64711
|
-
};
|
|
65673
|
+
const st = existing ?? buildOrphanState(startMs);
|
|
64712
65674
|
states.delete(event.runId);
|
|
65675
|
+
await st.spanQueue.flushNow();
|
|
65676
|
+
st.spanQueue.teardown();
|
|
64713
65677
|
await flush(st, startMs + event.totalDurationMs, event);
|
|
64714
65678
|
}
|
|
64715
65679
|
};
|
|
64716
65680
|
return {
|
|
64717
|
-
name:
|
|
65681
|
+
name: STAGE3,
|
|
64718
65682
|
version: "1.0.0",
|
|
64719
65683
|
provides: ["reporter"],
|
|
65684
|
+
async teardown() {
|
|
65685
|
+
if (tornDown)
|
|
65686
|
+
return;
|
|
65687
|
+
tornDown = true;
|
|
65688
|
+
const entries = [...states.entries()];
|
|
65689
|
+
states.clear();
|
|
65690
|
+
for (const [runId, st] of entries) {
|
|
65691
|
+
st.heartbeat.stop();
|
|
65692
|
+
await st.spanQueue.flushNow();
|
|
65693
|
+
st.spanQueue.teardown();
|
|
65694
|
+
const endMs = Date.now();
|
|
65695
|
+
await flush(st, endMs, {
|
|
65696
|
+
runId,
|
|
65697
|
+
totalDurationMs: endMs - st.startMs,
|
|
65698
|
+
totalCost: st.costUsd,
|
|
65699
|
+
storySummary: { completed: 0, failed: 0, skipped: 0, paused: 0 }
|
|
65700
|
+
});
|
|
65701
|
+
}
|
|
65702
|
+
},
|
|
64720
65703
|
extensions: { reporter }
|
|
64721
65704
|
};
|
|
64722
65705
|
}
|
|
64723
|
-
var
|
|
65706
|
+
var STAGE3 = "otel-reporter", DEFAULT_MAX_BATCH_SIZE = 64, DEFAULT_FLUSH_INTERVAL_MS = 5000, DEFAULT_MAX_QUEUE_SIZE = 2048;
|
|
64724
65707
|
var init_otel_reporter = __esm(() => {
|
|
64725
65708
|
init_logger2();
|
|
64726
65709
|
init_reporter_shared();
|
|
65710
|
+
init_batch_queue();
|
|
65711
|
+
init_heartbeat();
|
|
65712
|
+
init_span_tree();
|
|
65713
|
+
init_traceparent();
|
|
64727
65714
|
});
|
|
64728
65715
|
|
|
64729
65716
|
// src/plugins/builtin/webhook-reporter/index.ts
|
|
@@ -64734,25 +65721,27 @@ function createWebhookReporterPlugin(cfg, deps) {
|
|
|
64734
65721
|
return;
|
|
64735
65722
|
const { resolved, missing } = interpolateHeaders(cfg.headers);
|
|
64736
65723
|
if (missing.length > 0) {
|
|
64737
|
-
getSafeLogger()?.warn(
|
|
65724
|
+
getSafeLogger()?.warn(STAGE4, "Skipping webhook \u2014 unresolved env vars", { missing });
|
|
64738
65725
|
return;
|
|
64739
65726
|
}
|
|
64740
|
-
await postJson(cfg.url, { type, emittedAt: new Date().toISOString(), data }, { headers: resolved, timeoutMs: cfg.timeoutMs, stage:
|
|
65727
|
+
await postJson(cfg.url, { type, emittedAt: new Date().toISOString(), data }, { headers: resolved, timeoutMs: cfg.timeoutMs, stage: STAGE4, deps });
|
|
64741
65728
|
};
|
|
64742
65729
|
const reporter = {
|
|
64743
|
-
name:
|
|
65730
|
+
name: STAGE4,
|
|
64744
65731
|
onRunStart: (event) => emit("onRunStart", event),
|
|
64745
65732
|
onStoryComplete: (event) => emit("onStoryComplete", event),
|
|
64746
|
-
onRunEnd: (event) => emit("onRunEnd", event)
|
|
65733
|
+
onRunEnd: (event) => emit("onRunEnd", event),
|
|
65734
|
+
onPhaseStart: (event) => emit("onPhaseStart", event),
|
|
65735
|
+
onPhaseComplete: (event) => emit("onPhaseComplete", event)
|
|
64747
65736
|
};
|
|
64748
65737
|
return {
|
|
64749
|
-
name:
|
|
65738
|
+
name: STAGE4,
|
|
64750
65739
|
version: "1.0.0",
|
|
64751
65740
|
provides: ["reporter"],
|
|
64752
65741
|
extensions: { reporter }
|
|
64753
65742
|
};
|
|
64754
65743
|
}
|
|
64755
|
-
var
|
|
65744
|
+
var STAGE4 = "webhook-reporter";
|
|
64756
65745
|
var init_webhook_reporter = __esm(() => {
|
|
64757
65746
|
init_logger2();
|
|
64758
65747
|
init_reporter_shared();
|
|
@@ -65787,7 +66776,7 @@ async function heartbeatLoop(gen, statusWriter, getTotalCost, getIterations, jso
|
|
|
65787
66776
|
}
|
|
65788
66777
|
}
|
|
65789
66778
|
}
|
|
65790
|
-
function
|
|
66779
|
+
function startHeartbeat2(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
65791
66780
|
const logger = _heartbeatDeps.getSafeLogger();
|
|
65792
66781
|
_heartbeatActive = true;
|
|
65793
66782
|
const gen = ++_heartbeatGen;
|
|
@@ -67381,28 +68370,47 @@ async function handleRunCompletion(options) {
|
|
|
67381
68370
|
const regressionMode = config2.execution.regressionGate?.mode;
|
|
67382
68371
|
if (options.skipRegression) {} else if ((regressionMode === "deferred" || regressionMode === "per-story") && config2.quality.commands.test) {
|
|
67383
68372
|
statusWriter.setPostRunPhase("regression", { status: "running" });
|
|
68373
|
+
const regressionStartTime = Date.now();
|
|
67384
68374
|
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "regression" });
|
|
67385
|
-
|
|
67386
|
-
|
|
67387
|
-
|
|
67388
|
-
|
|
67389
|
-
|
|
67390
|
-
|
|
67391
|
-
|
|
67392
|
-
|
|
67393
|
-
|
|
67394
|
-
|
|
67395
|
-
|
|
67396
|
-
|
|
68375
|
+
let regressionResult;
|
|
68376
|
+
try {
|
|
68377
|
+
regressionResult = await _runCompletionDeps.runDeferredRegression({
|
|
68378
|
+
config: config2,
|
|
68379
|
+
prd,
|
|
68380
|
+
workdir,
|
|
68381
|
+
runtime: options.runtime,
|
|
68382
|
+
quarantineMemo: options.runtime.quarantineMemo,
|
|
68383
|
+
storyMetrics: options.isSequential === false ? undefined : allStoryMetrics.map((m) => ({
|
|
68384
|
+
storyId: m.storyId,
|
|
68385
|
+
completedAt: m.completedAt,
|
|
68386
|
+
failingTestFiles: m.failingTestFiles
|
|
68387
|
+
}))
|
|
68388
|
+
});
|
|
68389
|
+
} catch (err) {
|
|
68390
|
+
pipelineEventBus.emit({
|
|
68391
|
+
type: "postrun:phase:completed",
|
|
68392
|
+
phase: "regression",
|
|
68393
|
+
passed: false,
|
|
68394
|
+
durationMs: Date.now() - regressionStartTime
|
|
68395
|
+
});
|
|
68396
|
+
throw err;
|
|
68397
|
+
}
|
|
67397
68398
|
const lastRunAt = new Date().toISOString();
|
|
67398
68399
|
logger?.info("regression", "Deferred regression gate completed", {
|
|
67399
68400
|
success: regressionResult.success,
|
|
67400
68401
|
failedTests: regressionResult.failedTests,
|
|
67401
68402
|
affectedStories: regressionResult.affectedStories
|
|
67402
68403
|
});
|
|
68404
|
+
const regressionDurationMs = Date.now() - regressionStartTime;
|
|
67403
68405
|
if (regressionResult.success) {
|
|
67404
68406
|
statusWriter.setPostRunPhase("regression", { status: "passed", lastRunAt });
|
|
67405
|
-
pipelineEventBus.emit({
|
|
68407
|
+
pipelineEventBus.emit({
|
|
68408
|
+
type: "postrun:phase:completed",
|
|
68409
|
+
phase: "regression",
|
|
68410
|
+
passed: true,
|
|
68411
|
+
durationMs: regressionDurationMs,
|
|
68412
|
+
details: { mode: regressionMode, failedTests: 0 }
|
|
68413
|
+
});
|
|
67406
68414
|
} else {
|
|
67407
68415
|
statusWriter.setPostRunPhase("regression", {
|
|
67408
68416
|
status: "failed",
|
|
@@ -67410,7 +68418,16 @@ async function handleRunCompletion(options) {
|
|
|
67410
68418
|
affectedStories: regressionResult.affectedStories,
|
|
67411
68419
|
lastRunAt
|
|
67412
68420
|
});
|
|
67413
|
-
pipelineEventBus.emit({
|
|
68421
|
+
pipelineEventBus.emit({
|
|
68422
|
+
type: "postrun:phase:completed",
|
|
68423
|
+
phase: "regression",
|
|
68424
|
+
passed: false,
|
|
68425
|
+
durationMs: regressionDurationMs,
|
|
68426
|
+
details: {
|
|
68427
|
+
mode: regressionMode,
|
|
68428
|
+
failedTests: regressionResult.failedTests
|
|
68429
|
+
}
|
|
68430
|
+
});
|
|
67414
68431
|
for (const storyId of regressionResult.affectedStories) {
|
|
67415
68432
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
67416
68433
|
if (story) {
|
|
@@ -67478,7 +68495,15 @@ async function handleRunCompletion(options) {
|
|
|
67478
68495
|
let pluginGateFailed = false;
|
|
67479
68496
|
const deferredReview = options.deferredReview;
|
|
67480
68497
|
if (deferredReview !== undefined) {
|
|
67481
|
-
|
|
68498
|
+
const findingCount = deferredReview.reviewerResults.filter((r) => !r.passed).length;
|
|
68499
|
+
const reviewDurationMs = Date.now() - (options.deferredReviewStartedAt ?? Date.now());
|
|
68500
|
+
pipelineEventBus.emit({
|
|
68501
|
+
type: "postrun:phase:completed",
|
|
68502
|
+
phase: "review",
|
|
68503
|
+
passed: !deferredReview.anyFailed,
|
|
68504
|
+
durationMs: reviewDurationMs,
|
|
68505
|
+
details: { findingCount, anyFailed: deferredReview.anyFailed }
|
|
68506
|
+
});
|
|
67482
68507
|
}
|
|
67483
68508
|
if (deferredReview?.anyFailed) {
|
|
67484
68509
|
const failedReviewers = deferredReview.reviewerResults.filter((r) => !r.passed).map((r) => r.name);
|
|
@@ -67727,6 +68752,7 @@ async function runCompletionPhase(options) {
|
|
|
67727
68752
|
logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
|
|
67728
68753
|
} else if (options.config.acceptance.enabled && isComplete(options.prd)) {
|
|
67729
68754
|
options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
|
|
68755
|
+
const acceptanceStartTime = Date.now();
|
|
67730
68756
|
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
|
|
67731
68757
|
const acceptanceTestPaths = options.featureDir ? await Promise.all((await groupStoriesByPackage(options.prd, options.workdir, options.feature, options.config.acceptance.testPath, options.config.project?.language)).map(async (g) => {
|
|
67732
68758
|
const relativeWorkdir = path25.relative(options.workdir, g.packageDir);
|
|
@@ -67749,32 +68775,54 @@ async function runCompletionPhase(options) {
|
|
|
67749
68775
|
commandOverride: groupConfig.acceptance.command
|
|
67750
68776
|
};
|
|
67751
68777
|
})) : undefined;
|
|
67752
|
-
|
|
67753
|
-
|
|
67754
|
-
|
|
67755
|
-
|
|
67756
|
-
|
|
67757
|
-
|
|
67758
|
-
|
|
67759
|
-
|
|
67760
|
-
|
|
67761
|
-
|
|
67762
|
-
|
|
67763
|
-
|
|
67764
|
-
|
|
67765
|
-
|
|
67766
|
-
|
|
67767
|
-
|
|
67768
|
-
|
|
67769
|
-
|
|
67770
|
-
|
|
67771
|
-
|
|
67772
|
-
|
|
67773
|
-
|
|
68778
|
+
let acceptanceResult;
|
|
68779
|
+
try {
|
|
68780
|
+
acceptanceResult = await _runnerCompletionDeps.runAcceptanceLoop({
|
|
68781
|
+
config: options.config,
|
|
68782
|
+
prd: options.prd,
|
|
68783
|
+
prdPath: options.prdPath,
|
|
68784
|
+
workdir: options.workdir,
|
|
68785
|
+
featureDir: options.featureDir,
|
|
68786
|
+
hooks: options.hooks,
|
|
68787
|
+
feature: options.feature,
|
|
68788
|
+
totalCost: options.totalCost,
|
|
68789
|
+
iterations: options.iterations,
|
|
68790
|
+
storiesCompleted: options.storiesCompleted,
|
|
68791
|
+
allStoryMetrics: options.allStoryMetrics,
|
|
68792
|
+
pluginRegistry: options.pluginRegistry,
|
|
68793
|
+
eventEmitter: options.eventEmitter,
|
|
68794
|
+
statusWriter: options.statusWriter,
|
|
68795
|
+
agentGetFn: options.agentGetFn,
|
|
68796
|
+
agentManager: options.agentManager,
|
|
68797
|
+
sessionManager: options.sessionManager,
|
|
68798
|
+
runtime: options.runtime,
|
|
68799
|
+
abortSignal: options.abortSignal,
|
|
68800
|
+
acceptanceTestPaths
|
|
68801
|
+
});
|
|
68802
|
+
} catch (err) {
|
|
68803
|
+
pipelineEventBus.emit({
|
|
68804
|
+
type: "postrun:phase:completed",
|
|
68805
|
+
phase: "acceptance",
|
|
68806
|
+
passed: false,
|
|
68807
|
+
durationMs: Date.now() - acceptanceStartTime
|
|
68808
|
+
});
|
|
68809
|
+
throw err;
|
|
68810
|
+
}
|
|
67774
68811
|
const lastRunAt = new Date().toISOString();
|
|
68812
|
+
const acceptanceDurationMs = Date.now() - acceptanceStartTime;
|
|
67775
68813
|
if (acceptanceResult.success) {
|
|
67776
68814
|
options.statusWriter.setPostRunPhase("acceptance", { status: "passed", lastRunAt });
|
|
67777
|
-
pipelineEventBus.emit({
|
|
68815
|
+
pipelineEventBus.emit({
|
|
68816
|
+
type: "postrun:phase:completed",
|
|
68817
|
+
phase: "acceptance",
|
|
68818
|
+
passed: true,
|
|
68819
|
+
durationMs: acceptanceDurationMs,
|
|
68820
|
+
details: {
|
|
68821
|
+
retries: acceptanceResult.retries ?? 0,
|
|
68822
|
+
failedACCount: acceptanceResult.failedACs?.length ?? 0,
|
|
68823
|
+
fixStoriesCreated: 0
|
|
68824
|
+
}
|
|
68825
|
+
});
|
|
67778
68826
|
} else {
|
|
67779
68827
|
acceptancePassed = false;
|
|
67780
68828
|
options.statusWriter.setPostRunPhase("acceptance", {
|
|
@@ -67783,7 +68831,17 @@ async function runCompletionPhase(options) {
|
|
|
67783
68831
|
retries: acceptanceResult.retries ?? 0,
|
|
67784
68832
|
lastRunAt
|
|
67785
68833
|
});
|
|
67786
|
-
pipelineEventBus.emit({
|
|
68834
|
+
pipelineEventBus.emit({
|
|
68835
|
+
type: "postrun:phase:completed",
|
|
68836
|
+
phase: "acceptance",
|
|
68837
|
+
passed: false,
|
|
68838
|
+
durationMs: acceptanceDurationMs,
|
|
68839
|
+
details: {
|
|
68840
|
+
retries: acceptanceResult.retries ?? 0,
|
|
68841
|
+
failedACCount: acceptanceResult.failedACs?.length ?? 0,
|
|
68842
|
+
fixStoriesCreated: 0
|
|
68843
|
+
}
|
|
68844
|
+
});
|
|
67787
68845
|
}
|
|
67788
68846
|
Object.assign(options, {
|
|
67789
68847
|
prd: acceptanceResult.prd,
|
|
@@ -67814,6 +68872,7 @@ async function runCompletionPhase(options) {
|
|
|
67814
68872
|
sessionManager: options.sessionManager,
|
|
67815
68873
|
pluginProviderCache: options.pluginProviderCache,
|
|
67816
68874
|
deferredReview: options.deferredReview,
|
|
68875
|
+
deferredReviewStartedAt: options.deferredReviewStartedAt,
|
|
67817
68876
|
exitReason: options.exitReason,
|
|
67818
68877
|
runtime: options.runtime,
|
|
67819
68878
|
abortSignal: options.abortSignal
|
|
@@ -67890,7 +68949,7 @@ var init_runner_completion = __esm(() => {
|
|
|
67890
68949
|
});
|
|
67891
68950
|
|
|
67892
68951
|
// src/execution/batching.ts
|
|
67893
|
-
function groupStoriesIntoBatches(stories, maxBatchSize =
|
|
68952
|
+
function groupStoriesIntoBatches(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE2) {
|
|
67894
68953
|
const batches = [];
|
|
67895
68954
|
let currentBatch = [];
|
|
67896
68955
|
for (const story of stories) {
|
|
@@ -67923,7 +68982,7 @@ function groupStoriesIntoBatches(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE)
|
|
|
67923
68982
|
}
|
|
67924
68983
|
return batches;
|
|
67925
68984
|
}
|
|
67926
|
-
function precomputeBatchPlan(stories, maxBatchSize =
|
|
68985
|
+
function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE2) {
|
|
67927
68986
|
const batches = [];
|
|
67928
68987
|
let currentBatch = [];
|
|
67929
68988
|
for (const story of stories) {
|
|
@@ -67960,7 +69019,7 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
|
|
|
67960
69019
|
}
|
|
67961
69020
|
return batches;
|
|
67962
69021
|
}
|
|
67963
|
-
var
|
|
69022
|
+
var DEFAULT_MAX_BATCH_SIZE2 = 4;
|
|
67964
69023
|
|
|
67965
69024
|
// src/execution/ensure-package-dirs.ts
|
|
67966
69025
|
import path26 from "path";
|
|
@@ -68240,131 +69299,6 @@ var init_registry6 = __esm(() => {
|
|
|
68240
69299
|
init_paths3();
|
|
68241
69300
|
});
|
|
68242
69301
|
|
|
68243
|
-
// src/pipeline/subscribers/reporters.ts
|
|
68244
|
-
function wireReporters(bus, pluginRegistry, runId, startTime) {
|
|
68245
|
-
const logger = getSafeLogger();
|
|
68246
|
-
const safe = (name, fn) => {
|
|
68247
|
-
return fn().catch((err) => logger?.warn("reporters-subscriber", `Reporter "${name}" error`, { error: String(err) })).catch(() => {});
|
|
68248
|
-
};
|
|
68249
|
-
const unsubs = [];
|
|
68250
|
-
unsubs.push(bus.on("run:started", (ev) => {
|
|
68251
|
-
return safe("onRunStart", async () => {
|
|
68252
|
-
const reporters = pluginRegistry.getReporters();
|
|
68253
|
-
for (const r of reporters) {
|
|
68254
|
-
if (r.onRunStart) {
|
|
68255
|
-
try {
|
|
68256
|
-
await r.onRunStart({
|
|
68257
|
-
runId,
|
|
68258
|
-
feature: ev.feature,
|
|
68259
|
-
totalStories: ev.totalStories,
|
|
68260
|
-
startTime: new Date(startTime).toISOString()
|
|
68261
|
-
});
|
|
68262
|
-
} catch (err) {
|
|
68263
|
-
logger?.warn("plugins", `Reporter '${r.name}' onRunStart failed`, { error: err });
|
|
68264
|
-
}
|
|
68265
|
-
}
|
|
68266
|
-
}
|
|
68267
|
-
});
|
|
68268
|
-
}));
|
|
68269
|
-
unsubs.push(bus.on("story:completed", (ev) => {
|
|
68270
|
-
return safe("onStoryComplete(completed)", async () => {
|
|
68271
|
-
const reporters = pluginRegistry.getReporters();
|
|
68272
|
-
for (const r of reporters) {
|
|
68273
|
-
if (r.onStoryComplete) {
|
|
68274
|
-
try {
|
|
68275
|
-
await r.onStoryComplete({
|
|
68276
|
-
runId,
|
|
68277
|
-
storyId: ev.storyId,
|
|
68278
|
-
status: "completed",
|
|
68279
|
-
runElapsedMs: ev.runElapsedMs,
|
|
68280
|
-
cost: ev.cost ?? 0,
|
|
68281
|
-
tier: ev.modelTier ?? "balanced",
|
|
68282
|
-
testStrategy: ev.testStrategy ?? "test-after"
|
|
68283
|
-
});
|
|
68284
|
-
} catch (err) {
|
|
68285
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
68286
|
-
}
|
|
68287
|
-
}
|
|
68288
|
-
}
|
|
68289
|
-
});
|
|
68290
|
-
}));
|
|
68291
|
-
unsubs.push(bus.on("story:failed", (ev) => {
|
|
68292
|
-
return safe("onStoryComplete(failed)", async () => {
|
|
68293
|
-
const reporters = pluginRegistry.getReporters();
|
|
68294
|
-
for (const r of reporters) {
|
|
68295
|
-
if (r.onStoryComplete) {
|
|
68296
|
-
try {
|
|
68297
|
-
await r.onStoryComplete({
|
|
68298
|
-
runId,
|
|
68299
|
-
storyId: ev.storyId,
|
|
68300
|
-
status: "failed",
|
|
68301
|
-
runElapsedMs: Date.now() - startTime,
|
|
68302
|
-
cost: 0,
|
|
68303
|
-
tier: "balanced",
|
|
68304
|
-
testStrategy: "test-after"
|
|
68305
|
-
});
|
|
68306
|
-
} catch (err) {
|
|
68307
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
68308
|
-
}
|
|
68309
|
-
}
|
|
68310
|
-
}
|
|
68311
|
-
});
|
|
68312
|
-
}));
|
|
68313
|
-
unsubs.push(bus.on("story:paused", (ev) => {
|
|
68314
|
-
return safe("onStoryComplete(paused)", async () => {
|
|
68315
|
-
const reporters = pluginRegistry.getReporters();
|
|
68316
|
-
for (const r of reporters) {
|
|
68317
|
-
if (r.onStoryComplete) {
|
|
68318
|
-
try {
|
|
68319
|
-
await r.onStoryComplete({
|
|
68320
|
-
runId,
|
|
68321
|
-
storyId: ev.storyId,
|
|
68322
|
-
status: "paused",
|
|
68323
|
-
runElapsedMs: Date.now() - startTime,
|
|
68324
|
-
cost: 0,
|
|
68325
|
-
tier: "balanced",
|
|
68326
|
-
testStrategy: "test-after"
|
|
68327
|
-
});
|
|
68328
|
-
} catch (err) {
|
|
68329
|
-
logger?.warn("plugins", `Reporter '${r.name}' onStoryComplete failed`, { error: err });
|
|
68330
|
-
}
|
|
68331
|
-
}
|
|
68332
|
-
}
|
|
68333
|
-
});
|
|
68334
|
-
}));
|
|
68335
|
-
unsubs.push(bus.on("run:completed", (ev) => {
|
|
68336
|
-
return safe("onRunEnd", async () => {
|
|
68337
|
-
const reporters = pluginRegistry.getReporters();
|
|
68338
|
-
for (const r of reporters) {
|
|
68339
|
-
if (r.onRunEnd) {
|
|
68340
|
-
try {
|
|
68341
|
-
await r.onRunEnd({
|
|
68342
|
-
runId,
|
|
68343
|
-
totalDurationMs: Date.now() - startTime,
|
|
68344
|
-
totalCost: ev.totalCost ?? 0,
|
|
68345
|
-
storySummary: {
|
|
68346
|
-
completed: ev.passedStories,
|
|
68347
|
-
failed: ev.failedStories,
|
|
68348
|
-
skipped: ev.skippedStories,
|
|
68349
|
-
paused: ev.pausedStories
|
|
68350
|
-
}
|
|
68351
|
-
});
|
|
68352
|
-
} catch (err) {
|
|
68353
|
-
logger?.warn("plugins", `Reporter '${r.name}' onRunEnd failed`, { error: err });
|
|
68354
|
-
}
|
|
68355
|
-
}
|
|
68356
|
-
}
|
|
68357
|
-
});
|
|
68358
|
-
}));
|
|
68359
|
-
return () => {
|
|
68360
|
-
for (const u of unsubs)
|
|
68361
|
-
u();
|
|
68362
|
-
};
|
|
68363
|
-
}
|
|
68364
|
-
var init_reporters = __esm(() => {
|
|
68365
|
-
init_logger2();
|
|
68366
|
-
});
|
|
68367
|
-
|
|
68368
69302
|
// src/execution/deferred-review.ts
|
|
68369
69303
|
var {spawn: spawn4 } = globalThis.Bun;
|
|
68370
69304
|
async function captureRunStartRef(workdir) {
|
|
@@ -70378,6 +71312,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
70378
71312
|
const allStoryMetrics = [];
|
|
70379
71313
|
let warningSent = false;
|
|
70380
71314
|
let deferredReview;
|
|
71315
|
+
let deferredReviewStartedAt;
|
|
70381
71316
|
const runStartRef = await captureRunStartRef(ctx.workdir);
|
|
70382
71317
|
let cachedNaxIgnoreKey;
|
|
70383
71318
|
const getRunNaxIgnoreIndex = async (currentPrd) => {
|
|
@@ -70416,14 +71351,16 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
70416
71351
|
totalCost: totalCost2,
|
|
70417
71352
|
allStoryMetrics,
|
|
70418
71353
|
exitReason,
|
|
70419
|
-
deferredReview
|
|
71354
|
+
deferredReview,
|
|
71355
|
+
deferredReviewStartedAt
|
|
70420
71356
|
});
|
|
70421
|
-
|
|
71357
|
+
startHeartbeat2(ctx.statusWriter, () => totalCost2, () => iterations, ctx.logFilePath);
|
|
70422
71358
|
let _executeThrew = false;
|
|
70423
71359
|
try {
|
|
70424
71360
|
if (isComplete(prd)) {
|
|
70425
71361
|
logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
|
|
70426
71362
|
const naxIgnoreIndex = await getRunNaxIgnoreIndex(prd);
|
|
71363
|
+
deferredReviewStartedAt = Date.now();
|
|
70427
71364
|
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
|
|
70428
71365
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
|
|
70429
71366
|
return buildResult2("completed");
|
|
@@ -70478,6 +71415,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
70478
71415
|
return buildResult2("pre-merge-aborted");
|
|
70479
71416
|
}
|
|
70480
71417
|
logger?.debug("execution", "Running deferred review");
|
|
71418
|
+
deferredReviewStartedAt = Date.now();
|
|
70481
71419
|
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
|
|
70482
71420
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
|
|
70483
71421
|
logger?.debug("execution", "Deferred review done \u2014 returning completed");
|
|
@@ -70988,6 +71926,7 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
70988
71926
|
totalCost: totalCost2,
|
|
70989
71927
|
allStoryMetrics,
|
|
70990
71928
|
deferredReview: unifiedResult.deferredReview,
|
|
71929
|
+
deferredReviewStartedAt: unifiedResult.deferredReviewStartedAt,
|
|
70991
71930
|
exitReason: unifiedResult.exitReason
|
|
70992
71931
|
};
|
|
70993
71932
|
}
|
|
@@ -72241,10 +73180,10 @@ async function cleanupRun(options) {
|
|
|
72241
73180
|
}
|
|
72242
73181
|
const actions = pluginRegistry.getPostRunActions();
|
|
72243
73182
|
const pluginLogger = {
|
|
72244
|
-
debug: (msg) => logger?.debug("post-run", msg),
|
|
72245
|
-
info: (msg) => logger?.info("post-run", msg),
|
|
72246
|
-
warn: (msg) => logger?.warn("post-run", msg),
|
|
72247
|
-
error: (msg) => logger?.error("post-run", msg)
|
|
73183
|
+
debug: (msg, data) => logger?.debug("post-run", msg, data),
|
|
73184
|
+
info: (msg, data) => logger?.info("post-run", msg, data),
|
|
73185
|
+
warn: (msg, data) => logger?.warn("post-run", msg, data),
|
|
73186
|
+
error: (msg, data) => logger?.error("post-run", msg, data)
|
|
72248
73187
|
};
|
|
72249
73188
|
const ctx = buildPostRunContext(options, durationMs, pluginLogger);
|
|
72250
73189
|
for (const action of actions) {
|
|
@@ -72440,6 +73379,7 @@ async function run(options) {
|
|
|
72440
73379
|
agentManager,
|
|
72441
73380
|
pluginProviderCache,
|
|
72442
73381
|
deferredReview: executionResult.deferredReview,
|
|
73382
|
+
deferredReviewStartedAt: executionResult.deferredReviewStartedAt,
|
|
72443
73383
|
exitReason: executionResult.exitReason,
|
|
72444
73384
|
runtime,
|
|
72445
73385
|
abortSignal: shutdownController.signal
|
|
@@ -72538,8 +73478,11 @@ __export(exports_execution, {
|
|
|
72538
73478
|
withIncreasingFailuresBail: () => withIncreasingFailuresBail,
|
|
72539
73479
|
synthesizeBackfillMetric: () => synthesizeBackfillMetric,
|
|
72540
73480
|
stopHeartbeat: () => stopHeartbeat,
|
|
72541
|
-
startHeartbeat: () =>
|
|
73481
|
+
startHeartbeat: () => startHeartbeat2,
|
|
73482
|
+
runRectification: () => runRectification,
|
|
73483
|
+
runPhase: () => runPhase,
|
|
72542
73484
|
runDeferredRegression: () => runDeferredRegression,
|
|
73485
|
+
runCompletionPhase: () => runCompletionPhase,
|
|
72543
73486
|
run: () => run,
|
|
72544
73487
|
resolveMaxAttemptsOutcome: () => resolveMaxAttemptsOutcome,
|
|
72545
73488
|
resetCrashHandlers: () => resetCrashHandlers,
|
|
@@ -72565,7 +73508,6 @@ __export(exports_execution, {
|
|
|
72565
73508
|
getTierConfig: () => getTierConfig,
|
|
72566
73509
|
getOscillations: () => getOscillations,
|
|
72567
73510
|
getAllReadyStories: () => getAllReadyStories,
|
|
72568
|
-
gateRegressedAfterRectification: () => gateRegressedAfterRectification,
|
|
72569
73511
|
gateFailureKeys: () => gateFailureKeys,
|
|
72570
73512
|
formatProgress: () => formatProgress,
|
|
72571
73513
|
formatPhaseResultMessage: () => formatPhaseResultMessage,
|
|
@@ -72574,11 +73516,13 @@ __export(exports_execution, {
|
|
|
72574
73516
|
extractPauseReason: () => extractPauseReason,
|
|
72575
73517
|
escalateTier: () => escalateTier,
|
|
72576
73518
|
ensureStoryPackageDirs: () => ensureStoryPackageDirs,
|
|
73519
|
+
describeGateRegression: () => describeGateRegression,
|
|
72577
73520
|
deriveTddFailureCategory: () => deriveTddFailureCategory,
|
|
72578
73521
|
decideStageAction: () => decideStageAction,
|
|
72579
73522
|
createCheckpointWriter: () => createCheckpointWriter,
|
|
72580
73523
|
countOscillationOutcomes: () => countOscillationOutcomes,
|
|
72581
73524
|
clearQueueFile: () => clearQueueFile,
|
|
73525
|
+
cleanupRun: () => cleanupRun,
|
|
72582
73526
|
captureTreeState: () => captureTreeState,
|
|
72583
73527
|
calculateMaxIterations: () => calculateMaxIterations,
|
|
72584
73528
|
buildStoryContext: () => buildStoryContext,
|
|
@@ -72595,6 +73539,7 @@ __export(exports_execution, {
|
|
|
72595
73539
|
_storyOrchestratorDeps: () => _storyOrchestratorDeps,
|
|
72596
73540
|
_runnerReentrancyGuard: () => _runnerReentrancyGuard,
|
|
72597
73541
|
_runnerDeps: () => _runnerDeps,
|
|
73542
|
+
_runnerCompletionDeps: () => _runnerCompletionDeps,
|
|
72598
73543
|
_runCompletionDeps: () => _runCompletionDeps,
|
|
72599
73544
|
_regressionDeps: () => _regressionDeps,
|
|
72600
73545
|
_postRunDeps: () => _postRunDeps,
|
|
@@ -72626,6 +73571,7 @@ var init_execution2 = __esm(() => {
|
|
|
72626
73571
|
init_plan_inputs();
|
|
72627
73572
|
init_build_plan_for_strategy();
|
|
72628
73573
|
init_checkpoint();
|
|
73574
|
+
init_runner_completion();
|
|
72629
73575
|
init_post_run();
|
|
72630
73576
|
});
|
|
72631
73577
|
|
|
@@ -104405,7 +105351,7 @@ async function resolveRunProfileOverride(opts) {
|
|
|
104405
105351
|
// src/cli/features-resolve.ts
|
|
104406
105352
|
init_config();
|
|
104407
105353
|
import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
|
|
104408
|
-
import { join as join74, relative as
|
|
105354
|
+
import { join as join74, relative as relative17 } from "path";
|
|
104409
105355
|
|
|
104410
105356
|
// src/cli/features-acceptance.ts
|
|
104411
105357
|
init_acceptance2();
|
|
@@ -104413,7 +105359,7 @@ init_config();
|
|
|
104413
105359
|
init_logger2();
|
|
104414
105360
|
init_prd();
|
|
104415
105361
|
import { existsSync as existsSync27 } from "fs";
|
|
104416
|
-
import { join as join73, relative as
|
|
105362
|
+
import { join as join73, relative as relative16 } from "path";
|
|
104417
105363
|
async function resolveFeatureAcceptance(featureName, workdir) {
|
|
104418
105364
|
let enabled = true;
|
|
104419
105365
|
try {
|
|
@@ -104434,11 +105380,11 @@ async function resolveFeatureAcceptance(featureName, workdir) {
|
|
|
104434
105380
|
const prd = await loadPRD(prdPath);
|
|
104435
105381
|
const testGroups = await groupStoriesByPackage(prd, repoRoot, featureName, config2.acceptance?.testPath, config2.project?.language);
|
|
104436
105382
|
const groups = await Promise.all(testGroups.map(async (g) => {
|
|
104437
|
-
const packageDir =
|
|
105383
|
+
const packageDir = relative16(repoRoot, g.packageDir);
|
|
104438
105384
|
const command = await resolveGroupCommand(repoRoot, packageDir, config2.acceptance?.command);
|
|
104439
105385
|
return {
|
|
104440
105386
|
packageDir,
|
|
104441
|
-
testPath:
|
|
105387
|
+
testPath: relative16(repoRoot, g.testPath),
|
|
104442
105388
|
exists: await Bun.file(g.testPath).exists(),
|
|
104443
105389
|
command,
|
|
104444
105390
|
cwd: packageDir,
|
|
@@ -104475,17 +105421,17 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
104475
105421
|
];
|
|
104476
105422
|
const docsSpecExact = join74(repoRoot, "docs", "specs", `SPEC-${name}.md`);
|
|
104477
105423
|
candidates.push({ abs: docsSpecExact, kind: "markdown" });
|
|
104478
|
-
const checked = candidates.map((c) =>
|
|
105424
|
+
const checked = candidates.map((c) => relative17(repoRoot, c.abs));
|
|
104479
105425
|
for (const { abs, kind } of candidates.slice(0, 2)) {
|
|
104480
105426
|
if (kind === "markdown") {
|
|
104481
105427
|
const nonEmpty = await isNonEmptyFile(abs);
|
|
104482
105428
|
if (nonEmpty) {
|
|
104483
|
-
return { source: { kind, path:
|
|
105429
|
+
return { source: { kind, path: relative17(repoRoot, abs) }, checked };
|
|
104484
105430
|
}
|
|
104485
105431
|
}
|
|
104486
105432
|
}
|
|
104487
105433
|
if (await isNonEmptyFile(docsSpecExact)) {
|
|
104488
|
-
return { source: { kind: "markdown", path:
|
|
105434
|
+
return { source: { kind: "markdown", path: relative17(repoRoot, docsSpecExact) }, checked };
|
|
104489
105435
|
}
|
|
104490
105436
|
const docsSpecsDir = join74(repoRoot, "docs", "specs");
|
|
104491
105437
|
if (existsSync28(docsSpecsDir)) {
|
|
@@ -104493,7 +105439,7 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
104493
105439
|
for (const match of glob.scanSync({ cwd: docsSpecsDir, absolute: false })) {
|
|
104494
105440
|
const abs = join74(docsSpecsDir, match);
|
|
104495
105441
|
if (await isNonEmptyFile(abs)) {
|
|
104496
|
-
const relPath =
|
|
105442
|
+
const relPath = relative17(repoRoot, abs);
|
|
104497
105443
|
if (!checked.includes(relPath))
|
|
104498
105444
|
checked.push(relPath);
|
|
104499
105445
|
return { source: { kind: "markdown", path: relPath }, checked };
|
|
@@ -104501,7 +105447,7 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
104501
105447
|
}
|
|
104502
105448
|
}
|
|
104503
105449
|
const prdAbs = join74(naxDir, "features", name, "prd.json");
|
|
104504
|
-
const prdRel =
|
|
105450
|
+
const prdRel = relative17(repoRoot, prdAbs);
|
|
104505
105451
|
if (!checked.includes(prdRel))
|
|
104506
105452
|
checked.push(prdRel);
|
|
104507
105453
|
if (existsSync28(prdAbs)) {
|
|
@@ -104553,8 +105499,8 @@ async function resolveFeatureSpec(name, workdir) {
|
|
|
104553
105499
|
return {
|
|
104554
105500
|
status: "ok",
|
|
104555
105501
|
featureName: null,
|
|
104556
|
-
specSource: { kind: "markdown", path:
|
|
104557
|
-
message: `resolved spec: ${
|
|
105502
|
+
specSource: { kind: "markdown", path: relative17(repoRoot, abs) },
|
|
105503
|
+
message: `resolved spec: ${relative17(repoRoot, abs)}`
|
|
104558
105504
|
};
|
|
104559
105505
|
}
|
|
104560
105506
|
if (name !== undefined && name.trim() !== "") {
|
|
@@ -106052,9 +106998,9 @@ function parseSchedule(input, now2) {
|
|
|
106052
106998
|
const trimmed = input.trim();
|
|
106053
106999
|
if (trimmed === "")
|
|
106054
107000
|
return { ok: false, error: `Empty schedule value. ${ACCEPTED}` };
|
|
106055
|
-
const
|
|
106056
|
-
if (
|
|
106057
|
-
return
|
|
107001
|
+
const relative18 = parseRelative(trimmed, now2);
|
|
107002
|
+
if (relative18)
|
|
107003
|
+
return relative18;
|
|
106058
107004
|
const timeOfDay = parseTimeOfDay(trimmed, now2);
|
|
106059
107005
|
if (timeOfDay)
|
|
106060
107006
|
return timeOfDay;
|