@nathapp/nax 0.65.1 → 0.65.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 +551 -88
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -19957,7 +19957,9 @@ var init_schemas_execution = __esm(() => {
|
|
|
19957
19957
|
test: exports_external.string().optional(),
|
|
19958
19958
|
testScoped: exports_external.string().optional(),
|
|
19959
19959
|
lintFix: exports_external.string().optional(),
|
|
19960
|
+
lintFixScoped: exports_external.string().optional(),
|
|
19960
19961
|
formatFix: exports_external.string().optional(),
|
|
19962
|
+
formatFixScoped: exports_external.string().optional(),
|
|
19961
19963
|
build: exports_external.string().optional()
|
|
19962
19964
|
}).default({}),
|
|
19963
19965
|
lintOutput: exports_external.object({
|
|
@@ -20045,7 +20047,7 @@ var init_schemas_execution = __esm(() => {
|
|
|
20045
20047
|
});
|
|
20046
20048
|
|
|
20047
20049
|
// src/config/schemas-infra.ts
|
|
20048
|
-
var PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, AgentFallbackConfigSchema, AgentIdleWatchdogConfigSchema, AgentAcpConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, CuratorThresholdsSchema, CuratorConfigSchema;
|
|
20050
|
+
var PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, PromptAuditConfigSchema, AgentFallbackConfigSchema, DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG, AgentIdleWatchdogConfigSchema, AgentAcpConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, CuratorThresholdsSchema, CuratorConfigSchema;
|
|
20049
20051
|
var init_schemas_infra = __esm(() => {
|
|
20050
20052
|
init_zod();
|
|
20051
20053
|
init_schemas_model();
|
|
@@ -20147,6 +20149,14 @@ var init_schemas_infra = __esm(() => {
|
|
|
20147
20149
|
onQualityFailure: exports_external.boolean().default(false),
|
|
20148
20150
|
rebuildContext: exports_external.boolean().default(true)
|
|
20149
20151
|
});
|
|
20152
|
+
DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG = {
|
|
20153
|
+
enabled: true,
|
|
20154
|
+
mode: "warn-then-cancel",
|
|
20155
|
+
idleTimeoutSeconds: 900,
|
|
20156
|
+
activityKinds: ["message_update", "thinking_update", "usage_update"],
|
|
20157
|
+
cancelGraceSeconds: 10,
|
|
20158
|
+
maxRetryAttempts: 3
|
|
20159
|
+
};
|
|
20150
20160
|
AgentIdleWatchdogConfigSchema = exports_external.object({
|
|
20151
20161
|
enabled: exports_external.boolean().default(true),
|
|
20152
20162
|
mode: exports_external.enum(["off", "observe", "warn-then-cancel", "cancel"]).default("warn-then-cancel"),
|
|
@@ -20173,7 +20183,7 @@ var init_schemas_infra = __esm(() => {
|
|
|
20173
20183
|
rebuildContext: true
|
|
20174
20184
|
}),
|
|
20175
20185
|
acp: AgentAcpConfigSchema.default({ promptRetries: 0 }),
|
|
20176
|
-
idleWatchdog: AgentIdleWatchdogConfigSchema.
|
|
20186
|
+
idleWatchdog: AgentIdleWatchdogConfigSchema.default(DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG)
|
|
20177
20187
|
});
|
|
20178
20188
|
PrecheckConfigSchema = exports_external.object({
|
|
20179
20189
|
storySizeGate: StorySizeGateConfigSchema
|
|
@@ -20255,7 +20265,9 @@ var init_schemas_review = __esm(() => {
|
|
|
20255
20265
|
test: exports_external.string().optional(),
|
|
20256
20266
|
build: exports_external.string().optional(),
|
|
20257
20267
|
lintFix: exports_external.string().optional(),
|
|
20258
|
-
|
|
20268
|
+
lintFixScoped: exports_external.string().optional(),
|
|
20269
|
+
formatFix: exports_external.string().optional(),
|
|
20270
|
+
formatFixScoped: exports_external.string().optional()
|
|
20259
20271
|
}),
|
|
20260
20272
|
pluginMode: exports_external.enum(["per-story", "deferred"]).default("per-story"),
|
|
20261
20273
|
audit: exports_external.object({ enabled: exports_external.boolean().default(false) }).default({ enabled: false }),
|
|
@@ -20546,7 +20558,8 @@ var init_schemas3 = __esm(() => {
|
|
|
20546
20558
|
maxInteractionTurns: 20,
|
|
20547
20559
|
promptAudit: { enabled: false },
|
|
20548
20560
|
fallback: { enabled: false, map: {}, maxHopsPerStory: 2, onQualityFailure: false, rebuildContext: true },
|
|
20549
|
-
acp: { promptRetries: 0 }
|
|
20561
|
+
acp: { promptRetries: 0 },
|
|
20562
|
+
idleWatchdog: DEFAULT_AGENT_IDLE_WATCHDOG_CONFIG
|
|
20550
20563
|
}),
|
|
20551
20564
|
precheck: PrecheckConfigSchema.optional().default({
|
|
20552
20565
|
storySizeGate: {
|
|
@@ -20716,6 +20729,15 @@ function mergePackageConfig(root, packageOverride) {
|
|
|
20716
20729
|
...packageOverride.quality?.commands?.lintFix !== undefined && {
|
|
20717
20730
|
lintFix: packageOverride.quality.commands.lintFix
|
|
20718
20731
|
},
|
|
20732
|
+
...packageOverride.quality?.commands?.lintFixScoped !== undefined && {
|
|
20733
|
+
lintFixScoped: packageOverride.quality.commands.lintFixScoped
|
|
20734
|
+
},
|
|
20735
|
+
...packageOverride.quality?.commands?.formatFix !== undefined && {
|
|
20736
|
+
formatFix: packageOverride.quality.commands.formatFix
|
|
20737
|
+
},
|
|
20738
|
+
...packageOverride.quality?.commands?.formatFixScoped !== undefined && {
|
|
20739
|
+
formatFixScoped: packageOverride.quality.commands.formatFixScoped
|
|
20740
|
+
},
|
|
20719
20741
|
...packageOverride.quality?.commands?.typecheck !== undefined && {
|
|
20720
20742
|
typecheck: packageOverride.quality.commands.typecheck
|
|
20721
20743
|
},
|
|
@@ -29412,10 +29434,14 @@ For each acceptance criterion, verify the current codebase implements it correct
|
|
|
29412
29434
|
- The \`verifiedBy.observed\` field MUST be a **verbatim** 1-3 line code excerpt copy-pasted from the file \u2014 not a description. Paste the actual source lines (or a substring of them) that prove your claim. A description like "function X does not check Y" is not a verifiable observation; quote the lines that demonstrate the omission instead. If you cannot quote an exact excerpt that proves your point, downgrade the finding to "unverifiable".
|
|
29413
29435
|
|
|
29414
29436
|
**AC-grounding rule \u2014 required for every "error" finding:**
|
|
29415
|
-
- Every "error" finding MUST include \`acQuote\`: a verbatim substring of one AC bullet that names or constrains the exact
|
|
29437
|
+
- Every "error" finding MUST include \`acQuote\`: a verbatim substring of one AC bullet that names or constrains the exact **symbol** you are flagging \u2014 not merely the file the symbol lives in.
|
|
29416
29438
|
- Include \`acIndex\` (1-based) indicating which AC bullet you are quoting.
|
|
29417
|
-
-
|
|
29418
|
-
|
|
29439
|
+
- Copy \`acQuote\` directly from the Acceptance Criteria \u2014 including any backticks, asterisks, or punctuation. Do not paraphrase, strip formatting, or rewrite.
|
|
29440
|
+
|
|
29441
|
+
**The "AC names the file but not the symbol" trap (most common failure mode):**
|
|
29442
|
+
If the AC bullet mentions a file or component but the **specific symbol you are flagging** (the function, class, interface, type, or convention) is not named in that bullet, the AC does **not** constrain your finding. Emit it as \`severity: "info"\` \u2014 not \`"error"\`. **Convention / coding-standard violations almost always belong as \`"info"\`** unless an AC specifically names the convention or the symbol it concerns.
|
|
29443
|
+
|
|
29444
|
+
If you cannot find an AC that names the **specific symbol** in your finding, downgrade to \`"info"\`. A finding dropped by the validator as ungrounded is worse than one correctly classified as advisory.
|
|
29419
29445
|
|
|
29420
29446
|
Flag issues only when you have confirmed:
|
|
29421
29447
|
1. An AC is not implemented or partially implemented (verified by reading the actual files)
|
|
@@ -29663,10 +29689,22 @@ Severity guide:
|
|
|
29663
29689
|
\`passed\` may be \`true\` with findings if all findings are \`"info"\` or \`"unverifiable"\`.
|
|
29664
29690
|
|
|
29665
29691
|
**AC-grounding rule \u2014 required for every "error" finding:**
|
|
29666
|
-
- \`acQuote\` must be a verbatim substring of one AC bullet (from the Acceptance Criteria above) that names or constrains the exact
|
|
29692
|
+
- \`acQuote\` must be a verbatim substring of one AC bullet (from the Acceptance Criteria above) that names or constrains the exact **symbol** you are flagging \u2014 not merely the file the symbol lives in.
|
|
29667
29693
|
- \`acIndex\` is the 1-based position of that AC bullet in the list.
|
|
29668
|
-
-
|
|
29669
|
-
|
|
29694
|
+
- Copy \`acQuote\` **exactly** from the AC text, including any backticks, asterisks, or punctuation. Do not paraphrase, strip formatting, or rewrite.
|
|
29695
|
+
|
|
29696
|
+
**The "AC names the file but not the symbol" trap (most common failure mode):**
|
|
29697
|
+
If the AC bullet mentions a file or component but the **specific symbol you are flagging** (the function, class, interface, type, or convention) is not named in that bullet, the AC does **not** constrain your finding. Emit it as \`"info"\` \u2014 not \`"error"\`.
|
|
29698
|
+
|
|
29699
|
+
Worked example:
|
|
29700
|
+
- AC#1 reads: \`\`\`\`AstIndexService.indexCommit() is called by the code_commit outbox handler\`\`\`\`
|
|
29701
|
+
- You found that \`code-commit-outbox-handler.ts\` defines a custom \`ExtendedPrismaClient\` interface, violating a project convention rule.
|
|
29702
|
+
- WRONG: severity \`"error"\`, \`acQuote: "AstIndexService.indexCommit() is called by the code_commit outbox handler"\`, \`acIndex: 1\`. \u2014 AC#1 is about *who calls indexCommit*; it says nothing about Prisma typing. Picking it because the file is named is mis-grounding.
|
|
29703
|
+
- RIGHT: severity \`"info"\`, no \`acQuote\`. The convention violation is real, but no AC constrains \`ExtendedPrismaClient\`, so it cannot block the story.
|
|
29704
|
+
|
|
29705
|
+
**Convention / coding-standard violations almost always belong as \`"info"\`** unless an AC specifically names the convention or the symbol it concerns.
|
|
29706
|
+
|
|
29707
|
+
If you cannot find an AC that names the **specific symbol** in your finding, downgrade to \`"info"\`. A finding dropped by the validator is worse than one correctly classified as advisory.`;
|
|
29670
29708
|
var init_adversarial_review_builder = () => {};
|
|
29671
29709
|
|
|
29672
29710
|
// src/prompts/builders/acceptance-builder-helpers.ts
|
|
@@ -32468,7 +32506,8 @@ function acceptanceTestFilename(language) {
|
|
|
32468
32506
|
}
|
|
32469
32507
|
}
|
|
32470
32508
|
function resolveAcceptanceTestFile(language, testPathConfig) {
|
|
32471
|
-
|
|
32509
|
+
const candidate = testPathConfig ?? acceptanceTestFilename(language);
|
|
32510
|
+
return sanitizeTestFileName(candidate, "acceptance.testPath");
|
|
32472
32511
|
}
|
|
32473
32512
|
function resolveAcceptanceFeatureTestPath(featureDir, testPathConfig, language) {
|
|
32474
32513
|
return path3.join(featureDir, resolveAcceptanceTestFile(language, testPathConfig));
|
|
@@ -32522,7 +32561,21 @@ function suggestedTestFilename(language) {
|
|
|
32522
32561
|
}
|
|
32523
32562
|
}
|
|
32524
32563
|
function resolveSuggestedTestFile(language, testPathConfig) {
|
|
32525
|
-
|
|
32564
|
+
const candidate = testPathConfig ?? suggestedTestFilename(language);
|
|
32565
|
+
return sanitizeTestFileName(candidate, "acceptance.suggestedTestPath");
|
|
32566
|
+
}
|
|
32567
|
+
function sanitizeTestFileName(value, fieldName) {
|
|
32568
|
+
const filename = value.trim();
|
|
32569
|
+
if (filename.length === 0) {
|
|
32570
|
+
throw new Error(`${fieldName} must be non-empty`);
|
|
32571
|
+
}
|
|
32572
|
+
if (filename.includes("/") || filename.includes("\\")) {
|
|
32573
|
+
throw new Error(`${fieldName} must be a filename, not a path: ${filename}`);
|
|
32574
|
+
}
|
|
32575
|
+
if (filename.includes("..")) {
|
|
32576
|
+
throw new Error(`${fieldName} cannot contain '..': ${filename}`);
|
|
32577
|
+
}
|
|
32578
|
+
return filename;
|
|
32526
32579
|
}
|
|
32527
32580
|
function resolveSuggestedPackageFeatureTestPath(packageDir, featureName, testPathConfig, language) {
|
|
32528
32581
|
return path3.join(packageDir, ".nax", "features", featureName, resolveSuggestedTestFile(language, testPathConfig));
|
|
@@ -33670,12 +33723,79 @@ var init_rectify = __esm(() => {
|
|
|
33670
33723
|
init_prompts();
|
|
33671
33724
|
});
|
|
33672
33725
|
|
|
33726
|
+
// src/operations/test-edit-declaration.ts
|
|
33727
|
+
function readBlockField(block, key) {
|
|
33728
|
+
const re = new RegExp(`^${key}:\\s*(.+)$`, "m");
|
|
33729
|
+
const m = block.match(re);
|
|
33730
|
+
if (!m?.[1])
|
|
33731
|
+
return null;
|
|
33732
|
+
return m[1].trim();
|
|
33733
|
+
}
|
|
33734
|
+
function unwrapQuotes(s) {
|
|
33735
|
+
if (s.length >= 2 && s.startsWith('"') && s.endsWith('"'))
|
|
33736
|
+
return s.slice(1, -1);
|
|
33737
|
+
return s;
|
|
33738
|
+
}
|
|
33739
|
+
function parseTestEditDeclarations(output) {
|
|
33740
|
+
const result = [];
|
|
33741
|
+
const blocks = output.split(/\n\s*\n/);
|
|
33742
|
+
for (const block of blocks) {
|
|
33743
|
+
const reasonMatch = block.match(REASON_RE);
|
|
33744
|
+
if (!reasonMatch?.[1])
|
|
33745
|
+
continue;
|
|
33746
|
+
const reason = reasonMatch[1];
|
|
33747
|
+
if (reason === "prd_contract") {
|
|
33748
|
+
const file3 = readBlockField(block, "FILE");
|
|
33749
|
+
const prdQuote = readBlockField(block, "PRD_QUOTE");
|
|
33750
|
+
const testBefore = readBlockField(block, "TEST_BEFORE");
|
|
33751
|
+
const testAfter = readBlockField(block, "TEST_AFTER");
|
|
33752
|
+
if (!file3 || !prdQuote || !testBefore || !testAfter)
|
|
33753
|
+
continue;
|
|
33754
|
+
result.push({
|
|
33755
|
+
reason,
|
|
33756
|
+
file: file3,
|
|
33757
|
+
prdQuote: unwrapQuotes(prdQuote),
|
|
33758
|
+
testBefore,
|
|
33759
|
+
testAfter
|
|
33760
|
+
});
|
|
33761
|
+
} else if (reason === "lint_only") {
|
|
33762
|
+
const file3 = readBlockField(block, "FILE");
|
|
33763
|
+
const finding = readBlockField(block, "FINDING");
|
|
33764
|
+
if (!file3 || !finding)
|
|
33765
|
+
continue;
|
|
33766
|
+
result.push({ reason, file: file3, finding });
|
|
33767
|
+
} else if (reason === "sibling_scope") {
|
|
33768
|
+
const file3 = readBlockField(block, "SIBLING_FILE");
|
|
33769
|
+
const finding = readBlockField(block, "FINDING");
|
|
33770
|
+
if (!file3 || !finding)
|
|
33771
|
+
continue;
|
|
33772
|
+
result.push({ reason, file: file3, finding });
|
|
33773
|
+
}
|
|
33774
|
+
}
|
|
33775
|
+
return result;
|
|
33776
|
+
}
|
|
33777
|
+
function normaliseWs(s) {
|
|
33778
|
+
return s.replace(/\s+/g, " ").replace(/\s*([(),<>])\s*/g, "$1").replace(/\s*:\s*/g, ": ").trim();
|
|
33779
|
+
}
|
|
33780
|
+
function validatePrdQuote(prdQuote, story) {
|
|
33781
|
+
if (!prdQuote.trim())
|
|
33782
|
+
return false;
|
|
33783
|
+
const needle = normaliseWs(prdQuote);
|
|
33784
|
+
const haystack = normaliseWs([story.description, ...story.acceptanceCriteria].join(" "));
|
|
33785
|
+
return haystack.includes(needle);
|
|
33786
|
+
}
|
|
33787
|
+
var REASON_RE;
|
|
33788
|
+
var init_test_edit_declaration = __esm(() => {
|
|
33789
|
+
REASON_RE = /^TEST_EDIT_REASON:\s*(prd_contract|lint_only|sibling_scope)\s*$/m;
|
|
33790
|
+
});
|
|
33791
|
+
|
|
33673
33792
|
// src/operations/autofix-implementer.ts
|
|
33674
33793
|
var implementerRectifyOp;
|
|
33675
33794
|
var init_autofix_implementer = __esm(() => {
|
|
33676
33795
|
init_config();
|
|
33677
33796
|
init_logger2();
|
|
33678
33797
|
init_prompts();
|
|
33798
|
+
init_test_edit_declaration();
|
|
33679
33799
|
implementerRectifyOp = {
|
|
33680
33800
|
kind: "run",
|
|
33681
33801
|
name: "autofix-implementer",
|
|
@@ -33690,15 +33810,20 @@ var init_autofix_implementer = __esm(() => {
|
|
|
33690
33810
|
};
|
|
33691
33811
|
},
|
|
33692
33812
|
parse(output, input, _ctx) {
|
|
33693
|
-
const
|
|
33694
|
-
const
|
|
33695
|
-
|
|
33813
|
+
const unresolvedMatch = output.match(/^UNRESOLVED:\s*(.+)$/m);
|
|
33814
|
+
const declarations = parseTestEditDeclarations(output);
|
|
33815
|
+
for (const d of declarations) {
|
|
33696
33816
|
getSafeLogger()?.info("autofix", "test_edit_declared", {
|
|
33697
33817
|
storyId: input.story.id,
|
|
33698
|
-
reason:
|
|
33818
|
+
reason: d.reason,
|
|
33819
|
+
file: d.file
|
|
33699
33820
|
});
|
|
33700
33821
|
}
|
|
33701
|
-
return {
|
|
33822
|
+
return {
|
|
33823
|
+
applied: true,
|
|
33824
|
+
testEditDeclarations: declarations,
|
|
33825
|
+
...unresolvedMatch ? { unresolvedReason: unresolvedMatch[1]?.trim() } : {}
|
|
33826
|
+
};
|
|
33702
33827
|
}
|
|
33703
33828
|
};
|
|
33704
33829
|
});
|
|
@@ -33805,6 +33930,20 @@ function killProcessGroup(pid, signal) {
|
|
|
33805
33930
|
|
|
33806
33931
|
// src/quality/runner.ts
|
|
33807
33932
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
33933
|
+
function createDrainDeadline(deadlineMs) {
|
|
33934
|
+
let timeoutId;
|
|
33935
|
+
const promise2 = new Promise((resolve11) => {
|
|
33936
|
+
timeoutId = setTimeout(() => resolve11(""), deadlineMs);
|
|
33937
|
+
});
|
|
33938
|
+
return {
|
|
33939
|
+
promise: promise2,
|
|
33940
|
+
cancel: () => {
|
|
33941
|
+
if (timeoutId !== undefined) {
|
|
33942
|
+
clearTimeout(timeoutId);
|
|
33943
|
+
}
|
|
33944
|
+
}
|
|
33945
|
+
};
|
|
33946
|
+
}
|
|
33808
33947
|
async function runQualityCommand(opts) {
|
|
33809
33948
|
const { commandName, command, workdir, storyId, timeoutMs = DEFAULT_TIMEOUT_MS, env: env2 } = opts;
|
|
33810
33949
|
const startTime = Date.now();
|
|
@@ -33837,11 +33976,22 @@ async function runQualityCommand(opts) {
|
|
|
33837
33976
|
}
|
|
33838
33977
|
}, SIGKILL_GRACE_PERIOD_MS);
|
|
33839
33978
|
}, timeoutMs);
|
|
33840
|
-
const
|
|
33841
|
-
|
|
33842
|
-
|
|
33843
|
-
|
|
33844
|
-
|
|
33979
|
+
const stdoutPromise = new Response(proc.stdout).text().catch(() => "");
|
|
33980
|
+
const stderrPromise = new Response(proc.stderr).text().catch(() => "");
|
|
33981
|
+
const exitCode = await proc.exited;
|
|
33982
|
+
const [stdout, stderr] = timedOut ? await (async () => {
|
|
33983
|
+
const stdoutDrain = createDrainDeadline(STREAM_DRAIN_TIMEOUT_MS);
|
|
33984
|
+
const stderrDrain = createDrainDeadline(STREAM_DRAIN_TIMEOUT_MS);
|
|
33985
|
+
try {
|
|
33986
|
+
return await Promise.all([
|
|
33987
|
+
Promise.race([stdoutPromise, stdoutDrain.promise]),
|
|
33988
|
+
Promise.race([stderrPromise, stderrDrain.promise])
|
|
33989
|
+
]);
|
|
33990
|
+
} finally {
|
|
33991
|
+
stdoutDrain.cancel();
|
|
33992
|
+
stderrDrain.cancel();
|
|
33993
|
+
}
|
|
33994
|
+
})() : await Promise.all([stdoutPromise, stderrPromise]);
|
|
33845
33995
|
clearTimeout(killTimer);
|
|
33846
33996
|
if (sigkillTimer !== undefined) {
|
|
33847
33997
|
clearTimeout(sigkillTimer);
|
|
@@ -33893,7 +34043,7 @@ async function runQualityCommand(opts) {
|
|
|
33893
34043
|
};
|
|
33894
34044
|
}
|
|
33895
34045
|
}
|
|
33896
|
-
var DEFAULT_TIMEOUT_MS = 120000, SIGKILL_GRACE_PERIOD_MS = 5000, _qualityRunnerDeps;
|
|
34046
|
+
var DEFAULT_TIMEOUT_MS = 120000, SIGKILL_GRACE_PERIOD_MS = 5000, STREAM_DRAIN_TIMEOUT_MS = 2000, _qualityRunnerDeps;
|
|
33897
34047
|
var init_runner = __esm(() => {
|
|
33898
34048
|
init_logger2();
|
|
33899
34049
|
_qualityRunnerDeps = {
|
|
@@ -34258,6 +34408,8 @@ function buildSmartTestCommand(testFiles, baseCommand) {
|
|
|
34258
34408
|
if (testFiles.length === 0) {
|
|
34259
34409
|
return baseCommand;
|
|
34260
34410
|
}
|
|
34411
|
+
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
34412
|
+
const quotedTestFiles = testFiles.map(shellQuote);
|
|
34261
34413
|
const parts = baseCommand.trim().split(/\s+/);
|
|
34262
34414
|
let lastPathIndex = -1;
|
|
34263
34415
|
for (let i = parts.length - 1;i >= 0; i--) {
|
|
@@ -34267,11 +34419,11 @@ function buildSmartTestCommand(testFiles, baseCommand) {
|
|
|
34267
34419
|
}
|
|
34268
34420
|
}
|
|
34269
34421
|
if (lastPathIndex === -1) {
|
|
34270
|
-
return `${baseCommand} ${
|
|
34422
|
+
return `${baseCommand} ${quotedTestFiles.join(" ")}`;
|
|
34271
34423
|
}
|
|
34272
34424
|
const beforePath = parts.slice(0, lastPathIndex);
|
|
34273
34425
|
const afterPath = parts.slice(lastPathIndex + 1);
|
|
34274
|
-
const newParts = [...beforePath, ...
|
|
34426
|
+
const newParts = [...beforePath, ...quotedTestFiles, ...afterPath];
|
|
34275
34427
|
return newParts.join(" ");
|
|
34276
34428
|
}
|
|
34277
34429
|
async function getChangedNonTestFiles(workdir, baseRef, packagePrefix, testFileRegex = [], naxIgnoreIndex, repoRoot) {
|
|
@@ -34358,7 +34510,8 @@ function coerceSmartRunner(val) {
|
|
|
34358
34510
|
}
|
|
34359
34511
|
function buildScopedCommand(testFiles, baseCommand, testScopedTemplate) {
|
|
34360
34512
|
if (testScopedTemplate) {
|
|
34361
|
-
|
|
34513
|
+
const quotedFiles = testFiles.map((file3) => `'${file3.replaceAll("'", "'\\''")}'`);
|
|
34514
|
+
return testScopedTemplate.replace("{{files}}", quotedFiles.join(" "));
|
|
34362
34515
|
}
|
|
34363
34516
|
return _scopedDeps.buildSmartTestCommand(testFiles, baseCommand);
|
|
34364
34517
|
}
|
|
@@ -35142,6 +35295,7 @@ var init_operations = __esm(() => {
|
|
|
35142
35295
|
init_adversarial_review();
|
|
35143
35296
|
init_rectify();
|
|
35144
35297
|
init_autofix_implementer();
|
|
35298
|
+
init_test_edit_declaration();
|
|
35145
35299
|
init_autofix_test_writer();
|
|
35146
35300
|
init_debate_propose();
|
|
35147
35301
|
init_debate_rebut();
|
|
@@ -35151,6 +35305,23 @@ var init_operations = __esm(() => {
|
|
|
35151
35305
|
init_auto_approve();
|
|
35152
35306
|
});
|
|
35153
35307
|
|
|
35308
|
+
// src/utils/feature-name.ts
|
|
35309
|
+
function validateFeatureName(feature) {
|
|
35310
|
+
if (!feature || feature.trim() === "") {
|
|
35311
|
+
throw new Error("Feature name must be non-empty");
|
|
35312
|
+
}
|
|
35313
|
+
if (feature.includes("/") || feature.includes("\\")) {
|
|
35314
|
+
throw new Error(`Feature name must be a single path segment: ${feature}`);
|
|
35315
|
+
}
|
|
35316
|
+
if (feature.includes("..")) {
|
|
35317
|
+
throw new Error(`Feature name cannot contain '..': ${feature}`);
|
|
35318
|
+
}
|
|
35319
|
+
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
35320
|
+
if (!validPattern.test(feature)) {
|
|
35321
|
+
throw new Error(`Feature name contains invalid characters: ${feature}`);
|
|
35322
|
+
}
|
|
35323
|
+
}
|
|
35324
|
+
|
|
35154
35325
|
// src/cli/plan-helpers.ts
|
|
35155
35326
|
import { createInterface } from "readline";
|
|
35156
35327
|
function createCliInteractionBridge() {
|
|
@@ -39313,7 +39484,7 @@ class SessionManager {
|
|
|
39313
39484
|
_pidRegistry;
|
|
39314
39485
|
_watchdogControllerRegistry;
|
|
39315
39486
|
_onStreamActivity;
|
|
39316
|
-
|
|
39487
|
+
_watchdogCancelledCallsBySession = new Map;
|
|
39317
39488
|
_agentStreamUnsubscribe;
|
|
39318
39489
|
constructor(opts) {
|
|
39319
39490
|
this._getAdapter = opts?.getAdapter ?? (() => {
|
|
@@ -39343,22 +39514,26 @@ class SessionManager {
|
|
|
39343
39514
|
this._agentStreamUnsubscribe = opts.agentStreamEvents.onAgentStream((event) => {
|
|
39344
39515
|
if (event.kind === "agent.call_ended") {
|
|
39345
39516
|
this._watchdogControllerRegistry?.delete(event.callId);
|
|
39346
|
-
this._watchdogCancelledCalls.delete(event.callId);
|
|
39347
39517
|
}
|
|
39348
39518
|
});
|
|
39349
39519
|
}
|
|
39350
39520
|
}
|
|
39351
|
-
_buildOnActiveCall() {
|
|
39521
|
+
_buildOnActiveCall(sessionName) {
|
|
39352
39522
|
const registry2 = this._watchdogControllerRegistry;
|
|
39353
39523
|
if (!registry2)
|
|
39354
39524
|
return;
|
|
39355
39525
|
return (callId, cancel) => {
|
|
39356
39526
|
registry2.set(callId, async () => {
|
|
39357
|
-
this.
|
|
39527
|
+
const cancelledCalls = this._watchdogCancelledCallsBySession.get(sessionName) ?? new Set;
|
|
39528
|
+
cancelledCalls.add(callId);
|
|
39529
|
+
this._watchdogCancelledCallsBySession.set(sessionName, cancelledCalls);
|
|
39358
39530
|
await cancel();
|
|
39359
39531
|
});
|
|
39360
39532
|
};
|
|
39361
39533
|
}
|
|
39534
|
+
_clearWatchdogCancelledCalls(sessionName) {
|
|
39535
|
+
this._watchdogCancelledCallsBySession.delete(sessionName);
|
|
39536
|
+
}
|
|
39362
39537
|
_persistDescriptor(descriptor) {
|
|
39363
39538
|
if (!descriptor.scratchDir)
|
|
39364
39539
|
return;
|
|
@@ -39581,7 +39756,7 @@ class SessionManager {
|
|
|
39581
39756
|
onSessionEstablished: opts.onSessionEstablished,
|
|
39582
39757
|
signal: opts.signal,
|
|
39583
39758
|
resume,
|
|
39584
|
-
onActiveCall: this._buildOnActiveCall(),
|
|
39759
|
+
onActiveCall: this._buildOnActiveCall(name),
|
|
39585
39760
|
onStreamActivity: this._onStreamActivity
|
|
39586
39761
|
});
|
|
39587
39762
|
this._liveHandles.set(name, handle);
|
|
@@ -39668,8 +39843,7 @@ class SessionManager {
|
|
|
39668
39843
|
return { ...result, protocolIds: result.protocolIds ?? handle.protocolIds };
|
|
39669
39844
|
} catch (err) {
|
|
39670
39845
|
if (err instanceof SessionTurnError && err.cancelled) {
|
|
39671
|
-
const wasWatchdog = this.
|
|
39672
|
-
this._watchdogCancelledCalls.clear();
|
|
39846
|
+
const wasWatchdog = (this._watchdogCancelledCallsBySession.get(handle.id)?.size ?? 0) > 0;
|
|
39673
39847
|
if (wasWatchdog) {
|
|
39674
39848
|
throw new SessionFailureError("idle watchdog cancelled session \u2014 no stream activity", {
|
|
39675
39849
|
category: "availability",
|
|
@@ -39688,6 +39862,7 @@ class SessionManager {
|
|
|
39688
39862
|
}
|
|
39689
39863
|
throw err;
|
|
39690
39864
|
} finally {
|
|
39865
|
+
this._clearWatchdogCancelledCalls(handle.id);
|
|
39691
39866
|
this._busySessions.delete(handle.id);
|
|
39692
39867
|
}
|
|
39693
39868
|
}
|
|
@@ -40367,8 +40542,10 @@ import { basename as basename5, join as join28 } from "path";
|
|
|
40367
40542
|
function createRuntime(config2, workdir, opts) {
|
|
40368
40543
|
const runId = crypto.randomUUID();
|
|
40369
40544
|
const controller = new AbortController;
|
|
40545
|
+
let parentAbortHandler;
|
|
40370
40546
|
if (opts?.parentSignal) {
|
|
40371
|
-
|
|
40547
|
+
parentAbortHandler = () => controller.abort(opts.parentSignal?.reason);
|
|
40548
|
+
opts.parentSignal.addEventListener("abort", parentAbortHandler, { once: true });
|
|
40372
40549
|
}
|
|
40373
40550
|
const configLoader = createConfigLoader(config2);
|
|
40374
40551
|
const dispatchEvents = new DispatchEventBus;
|
|
@@ -40469,6 +40646,9 @@ function createRuntime(config2, workdir, opts) {
|
|
|
40469
40646
|
offReviewAudit();
|
|
40470
40647
|
offAgentStreamLogging();
|
|
40471
40648
|
offWatchdog();
|
|
40649
|
+
if (opts?.parentSignal && parentAbortHandler) {
|
|
40650
|
+
opts.parentSignal.removeEventListener("abort", parentAbortHandler);
|
|
40651
|
+
}
|
|
40472
40652
|
const results = await Promise.allSettled([promptAuditor.flush(), reviewAuditor.flush(), costAggregator.drain()]);
|
|
40473
40653
|
for (const r of results) {
|
|
40474
40654
|
if (r.status === "rejected") {
|
|
@@ -42188,6 +42368,11 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
|
|
|
42188
42368
|
}
|
|
42189
42369
|
let featureDir;
|
|
42190
42370
|
if (feature) {
|
|
42371
|
+
try {
|
|
42372
|
+
validateFeatureName(feature);
|
|
42373
|
+
} catch (error48) {
|
|
42374
|
+
throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
|
|
42375
|
+
}
|
|
42191
42376
|
const featuresDir = join32(naxDir, "features");
|
|
42192
42377
|
featureDir = join32(featuresDir, feature);
|
|
42193
42378
|
if (!existsSync16(featureDir)) {
|
|
@@ -43417,7 +43602,7 @@ function collectAdversarialSourceChecks(ctx) {
|
|
|
43417
43602
|
function buildAutofixStrategies(ctx, maxAttempts) {
|
|
43418
43603
|
const implementer = {
|
|
43419
43604
|
name: "autofix-implementer",
|
|
43420
|
-
appliesTo: (f) => (f.fixTarget ?? "source") === "source",
|
|
43605
|
+
appliesTo: (f) => (f.fixTarget ?? "source") === "source" && f.category !== "prd_quote_mismatch",
|
|
43421
43606
|
fixOp: implementerRectifyOp,
|
|
43422
43607
|
maxAttempts,
|
|
43423
43608
|
coRun: "co-run-sequential",
|
|
@@ -43425,16 +43610,22 @@ function buildAutofixStrategies(ctx, maxAttempts) {
|
|
|
43425
43610
|
failedChecks: collectFailedChecks(ctx),
|
|
43426
43611
|
story: ctx.story
|
|
43427
43612
|
}),
|
|
43428
|
-
extractApplied: (output) =>
|
|
43429
|
-
|
|
43430
|
-
|
|
43431
|
-
|
|
43613
|
+
extractApplied: (output) => {
|
|
43614
|
+
const decls = output.testEditDeclarations ?? [];
|
|
43615
|
+
if (decls.length > 0) {
|
|
43616
|
+
ctx.testEditDeclarations = [...ctx.testEditDeclarations ?? [], ...decls];
|
|
43617
|
+
}
|
|
43618
|
+
return {
|
|
43619
|
+
summary: output.unresolvedReason ?? "",
|
|
43620
|
+
unresolved: output.unresolvedReason
|
|
43621
|
+
};
|
|
43622
|
+
}
|
|
43432
43623
|
};
|
|
43433
43624
|
const testWriter = {
|
|
43434
43625
|
name: "autofix-test-writer",
|
|
43435
43626
|
appliesTo: (f) => f.fixTarget === "test" || (f.fixTarget ?? "source") === "source" && f.severity === "error" && f.source === "adversarial-review",
|
|
43436
43627
|
fixOp: testWriterRectifyOp,
|
|
43437
|
-
maxAttempts:
|
|
43628
|
+
maxAttempts: 2,
|
|
43438
43629
|
coRun: "co-run-sequential",
|
|
43439
43630
|
buildInput: (findings, _prior, _cycleCtx) => {
|
|
43440
43631
|
const hasSourceBug = findings.some((f) => (f.fixTarget ?? "source") === "source" && f.source === "adversarial-review");
|
|
@@ -43455,6 +43646,25 @@ function buildAutofixStrategies(ctx, maxAttempts) {
|
|
|
43455
43646
|
};
|
|
43456
43647
|
return [testWriter, implementer];
|
|
43457
43648
|
}
|
|
43649
|
+
function autofixCapacityExhausted(ctx) {
|
|
43650
|
+
const findings = collectCurrentFindings(ctx);
|
|
43651
|
+
if (findings.length === 0)
|
|
43652
|
+
return false;
|
|
43653
|
+
const maxAttempts = ctx.config.quality.autofix?.maxAttempts ?? 3;
|
|
43654
|
+
const maxTotal = ctx.config.quality.autofix?.maxTotalAttempts ?? 12;
|
|
43655
|
+
const prior = ctx.autofixPriorIterations ?? [];
|
|
43656
|
+
const totalUsed = prior.reduce((sum, iter) => sum + iter.fixesApplied.length, 0);
|
|
43657
|
+
if (totalUsed >= maxTotal)
|
|
43658
|
+
return true;
|
|
43659
|
+
const strategies = buildAutofixStrategies(ctx, maxAttempts);
|
|
43660
|
+
const active = strategies.filter((s) => findings.some((f) => s.appliesTo(f)));
|
|
43661
|
+
if (active.length === 0)
|
|
43662
|
+
return true;
|
|
43663
|
+
return active.some((s) => {
|
|
43664
|
+
const used = prior.reduce((sum, iter) => sum + iter.fixesApplied.filter((fa) => fa.strategyName === s.name).length, 0);
|
|
43665
|
+
return used >= s.maxAttempts;
|
|
43666
|
+
});
|
|
43667
|
+
}
|
|
43458
43668
|
function buildEscalationDigest(findings) {
|
|
43459
43669
|
const byFile = new Map;
|
|
43460
43670
|
for (const f of findings) {
|
|
@@ -43494,6 +43704,50 @@ async function writeShadowReport(ctx, result, initialFindingsCount) {
|
|
|
43494
43704
|
});
|
|
43495
43705
|
}
|
|
43496
43706
|
}
|
|
43707
|
+
function applyTestEditDeclarations(findings, declarations, story) {
|
|
43708
|
+
if (declarations.length === 0)
|
|
43709
|
+
return findings;
|
|
43710
|
+
const out = [...findings];
|
|
43711
|
+
const originalLength = findings.length;
|
|
43712
|
+
const reTaggedKeys = new Set;
|
|
43713
|
+
for (const decl of declarations) {
|
|
43714
|
+
if (decl.reason !== "prd_contract")
|
|
43715
|
+
continue;
|
|
43716
|
+
if (!validatePrdQuote(decl.prdQuote ?? "", story)) {
|
|
43717
|
+
out.push({
|
|
43718
|
+
source: "adversarial-review",
|
|
43719
|
+
severity: "warning",
|
|
43720
|
+
category: "prd_quote_mismatch",
|
|
43721
|
+
message: `Implementer declared TEST_EDIT_REASON: prd_contract with PRD_QUOTE not found in story description or AC text: ${decl.prdQuote}`,
|
|
43722
|
+
file: decl.file,
|
|
43723
|
+
fixTarget: "source"
|
|
43724
|
+
});
|
|
43725
|
+
continue;
|
|
43726
|
+
}
|
|
43727
|
+
for (let i = 0;i < originalLength; i++) {
|
|
43728
|
+
if (reTaggedKeys.has(i))
|
|
43729
|
+
continue;
|
|
43730
|
+
if (out[i].file !== decl.file)
|
|
43731
|
+
continue;
|
|
43732
|
+
if ((out[i].fixTarget ?? "source") === "test")
|
|
43733
|
+
continue;
|
|
43734
|
+
out[i] = {
|
|
43735
|
+
...out[i],
|
|
43736
|
+
fixTarget: "test",
|
|
43737
|
+
meta: {
|
|
43738
|
+
...out[i].meta ?? {},
|
|
43739
|
+
prdContractDeclaration: {
|
|
43740
|
+
prdQuote: decl.prdQuote,
|
|
43741
|
+
testBefore: decl.testBefore,
|
|
43742
|
+
testAfter: decl.testAfter
|
|
43743
|
+
}
|
|
43744
|
+
}
|
|
43745
|
+
};
|
|
43746
|
+
reTaggedKeys.add(i);
|
|
43747
|
+
}
|
|
43748
|
+
}
|
|
43749
|
+
return out;
|
|
43750
|
+
}
|
|
43497
43751
|
async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effectiveWorkdir) {
|
|
43498
43752
|
const logger = getLogger();
|
|
43499
43753
|
const storyId = ctx.story.id;
|
|
@@ -43517,7 +43771,18 @@ async function runAgentRectificationV2(ctx, _lintFixCmd, _formatFixCmd, _effecti
|
|
|
43517
43771
|
},
|
|
43518
43772
|
async validate(_cycleCtx) {
|
|
43519
43773
|
await _autofixDeps.recheckReview(ctx);
|
|
43520
|
-
|
|
43774
|
+
const fresh = collectCurrentFindings(ctx);
|
|
43775
|
+
const pending = ctx.testEditDeclarations ?? [];
|
|
43776
|
+
if (pending.length === 0)
|
|
43777
|
+
return fresh;
|
|
43778
|
+
const retagged = applyTestEditDeclarations(fresh, pending, ctx.story);
|
|
43779
|
+
ctx.testEditDeclarations = [];
|
|
43780
|
+
logger.info("autofix-cycle", "applied test-edit declarations", {
|
|
43781
|
+
storyId: ctx.story.id,
|
|
43782
|
+
declarationCount: pending.length,
|
|
43783
|
+
reTaggedCount: retagged.filter((f) => f.fixTarget === "test").length - fresh.filter((f) => f.fixTarget === "test").length
|
|
43784
|
+
});
|
|
43785
|
+
return retagged;
|
|
43521
43786
|
}
|
|
43522
43787
|
};
|
|
43523
43788
|
const result = await runFixCycle(cycle, cycleCtx, "autofix-v2");
|
|
@@ -45170,6 +45435,42 @@ ${formatFindings2(blockingFindings)}`,
|
|
|
45170
45435
|
};
|
|
45171
45436
|
}
|
|
45172
45437
|
if (!parsed.passed && blockingFindings.length === 0) {
|
|
45438
|
+
if (acDropped.length > 0) {
|
|
45439
|
+
const durationMs3 = Date.now() - startTime;
|
|
45440
|
+
logger?.warn("review", "Adversarial review fail-closed: blocking findings dropped as ungrounded", {
|
|
45441
|
+
storyId: story.id,
|
|
45442
|
+
durationMs: durationMs3,
|
|
45443
|
+
droppedCount: acDropped.length,
|
|
45444
|
+
dropCodes: acDropped.map((d) => d.code)
|
|
45445
|
+
});
|
|
45446
|
+
const dropSummary = acDropped.map((d, i) => `${i + 1}. [${d.code}] ${d.finding.file ?? "<unknown>"}: ${d.finding.issue}`).join(`
|
|
45447
|
+
`);
|
|
45448
|
+
recordAdversarialAudit({
|
|
45449
|
+
runtime,
|
|
45450
|
+
workdir,
|
|
45451
|
+
projectDir,
|
|
45452
|
+
storyId: story.id,
|
|
45453
|
+
featureName,
|
|
45454
|
+
parsed: true,
|
|
45455
|
+
failOpen: false,
|
|
45456
|
+
passed: false,
|
|
45457
|
+
blockingThreshold: threshold,
|
|
45458
|
+
result: { passed: false, findings: [] },
|
|
45459
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "adversarial-review" }) : undefined
|
|
45460
|
+
});
|
|
45461
|
+
return {
|
|
45462
|
+
check: "adversarial",
|
|
45463
|
+
success: false,
|
|
45464
|
+
command: "",
|
|
45465
|
+
exitCode: 1,
|
|
45466
|
+
output: `Adversarial review failed: ${acDropped.length} blocking finding(s) dropped as ungrounded \u2014 the model emitted "passed: false" with concerns it could not ground in any acceptance criterion. Either re-classify these as "info" upstream or extend the ACs. Drops:
|
|
45467
|
+
|
|
45468
|
+
${dropSummary}`,
|
|
45469
|
+
durationMs: durationMs3,
|
|
45470
|
+
advisoryFindings: advisoryFindings.length > 0 ? toAdversarialReviewFindings(advisoryFindings) : undefined,
|
|
45471
|
+
cost: llmCost
|
|
45472
|
+
};
|
|
45473
|
+
}
|
|
45173
45474
|
const durationMs2 = Date.now() - startTime;
|
|
45174
45475
|
logger?.info("review", "Adversarial review passed (all findings below blocking threshold)", {
|
|
45175
45476
|
storyId: story.id,
|
|
@@ -46228,6 +46529,42 @@ ${formatFindings(blockingFindings)}`;
|
|
|
46228
46529
|
};
|
|
46229
46530
|
}
|
|
46230
46531
|
if (!sanitizedParsed.passed && blockingFindings.length === 0) {
|
|
46532
|
+
if (acDropped.length > 0) {
|
|
46533
|
+
const durationMs3 = Date.now() - startTime;
|
|
46534
|
+
logger?.warn("review", "Semantic review fail-closed: blocking findings dropped as ungrounded", {
|
|
46535
|
+
storyId: story.id,
|
|
46536
|
+
durationMs: durationMs3,
|
|
46537
|
+
droppedCount: acDropped.length,
|
|
46538
|
+
dropCodes: acDropped.map((d) => d.code)
|
|
46539
|
+
});
|
|
46540
|
+
const dropSummary = acDropped.map((d, i) => `${i + 1}. [${d.code}] ${d.finding.file ?? "<unknown>"}: ${d.finding.issue}`).join(`
|
|
46541
|
+
`);
|
|
46542
|
+
recordSemanticAudit({
|
|
46543
|
+
runtime,
|
|
46544
|
+
workdir,
|
|
46545
|
+
projectDir,
|
|
46546
|
+
storyId: story.id,
|
|
46547
|
+
featureName,
|
|
46548
|
+
parsed: true,
|
|
46549
|
+
failOpen: false,
|
|
46550
|
+
passed: false,
|
|
46551
|
+
blockingThreshold: threshold,
|
|
46552
|
+
result: { passed: false, findings: [] },
|
|
46553
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review" }) : undefined
|
|
46554
|
+
});
|
|
46555
|
+
return {
|
|
46556
|
+
check: "semantic",
|
|
46557
|
+
success: false,
|
|
46558
|
+
command: "",
|
|
46559
|
+
exitCode: 1,
|
|
46560
|
+
output: `Semantic review failed: ${acDropped.length} blocking finding(s) dropped as ungrounded \u2014 the model emitted "passed: false" with concerns it could not ground in any acceptance criterion. Either re-classify these as "info" upstream or extend the ACs. Drops:
|
|
46561
|
+
|
|
46562
|
+
${dropSummary}`,
|
|
46563
|
+
durationMs: durationMs3,
|
|
46564
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings) : undefined,
|
|
46565
|
+
cost: llmCost
|
|
46566
|
+
};
|
|
46567
|
+
}
|
|
46231
46568
|
const durationMs2 = Date.now() - startTime;
|
|
46232
46569
|
logger?.info("review", "Semantic review passed (all findings below blocking threshold)", {
|
|
46233
46570
|
storyId: story.id,
|
|
@@ -47315,12 +47652,115 @@ async function recheckReview(ctx) {
|
|
|
47315
47652
|
return false;
|
|
47316
47653
|
return ctx.reviewResult?.success === true;
|
|
47317
47654
|
}
|
|
47655
|
+
async function runMechanicalFixes(ctx, failedCheckNames) {
|
|
47656
|
+
const commands = resolveMechanicalFixCommands(ctx, failedCheckNames);
|
|
47657
|
+
for (const resolved of commands) {
|
|
47658
|
+
if (resolved.skipped)
|
|
47659
|
+
continue;
|
|
47660
|
+
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: resolved.command });
|
|
47661
|
+
const result = await _autofixDeps.runQualityCommand({
|
|
47662
|
+
commandName: resolved.commandName,
|
|
47663
|
+
command: resolved.command,
|
|
47664
|
+
workdir: ctx.workdir,
|
|
47665
|
+
storyId: ctx.story.id
|
|
47666
|
+
});
|
|
47667
|
+
logMechanicalFixResult(ctx, resolved, result.exitCode);
|
|
47668
|
+
}
|
|
47669
|
+
}
|
|
47670
|
+
function resolveMechanicalFixCommands(ctx, failedCheckNames) {
|
|
47671
|
+
if (!failedCheckNames.has("lint"))
|
|
47672
|
+
return [];
|
|
47673
|
+
const scopeFiles = collectLintScopeFiles(ctx.reviewResult?.checks ?? []);
|
|
47674
|
+
return [resolveFixCommand(ctx, "lintFix", scopeFiles), resolveFixCommand(ctx, "formatFix", scopeFiles)].filter((cmd) => cmd !== undefined);
|
|
47675
|
+
}
|
|
47676
|
+
function resolveFixCommand(ctx, commandName, scopeFiles) {
|
|
47677
|
+
const broad = resolveBroadFixCommand(ctx, commandName);
|
|
47678
|
+
const template = resolveScopedFixTemplate(ctx, commandName);
|
|
47679
|
+
if (!broad && !template)
|
|
47680
|
+
return;
|
|
47681
|
+
if (!scopeFiles)
|
|
47682
|
+
return warnAndUseFullFix(ctx, commandName, broad, "missing_lint_scope");
|
|
47683
|
+
if (scopeFiles.length === 0)
|
|
47684
|
+
return logEmptyFixScope(ctx, commandName, broad ?? template ?? "");
|
|
47685
|
+
if (template) {
|
|
47686
|
+
return {
|
|
47687
|
+
commandName,
|
|
47688
|
+
command: template.replaceAll("{{files}}", scopeFiles.map(shellQuotePath2).join(" ")),
|
|
47689
|
+
scoped: true
|
|
47690
|
+
};
|
|
47691
|
+
}
|
|
47692
|
+
if (!broad)
|
|
47693
|
+
return;
|
|
47694
|
+
const derived = deriveScopedFixCommand(broad, scopeFiles);
|
|
47695
|
+
if (derived)
|
|
47696
|
+
return { commandName, command: derived, scoped: true };
|
|
47697
|
+
return warnAndUseFullFix(ctx, commandName, broad, "unsupported_scoped_command_shape");
|
|
47698
|
+
}
|
|
47699
|
+
function collectLintScopeFiles(checks3) {
|
|
47700
|
+
const lintChecks = checks3.filter((check2) => check2.check === "lint" && !check2.success);
|
|
47701
|
+
if (lintChecks.length === 0)
|
|
47702
|
+
return;
|
|
47703
|
+
if (lintChecks.some((check2) => !check2.lintScope))
|
|
47704
|
+
return;
|
|
47705
|
+
if (lintChecks.some((check2) => check2.lintScope?.status === "degraded"))
|
|
47706
|
+
return;
|
|
47707
|
+
const files = lintChecks.flatMap((check2) => check2.lintScope?.packageGroups.flatMap((group) => group.files) ?? []);
|
|
47708
|
+
return [...new Set(files)];
|
|
47709
|
+
}
|
|
47710
|
+
function resolveBroadFixCommand(ctx, commandName) {
|
|
47711
|
+
return commandName === "lintFix" ? ctx.config.quality.commands.lintFix ?? ctx.config.review.commands.lintFix : ctx.config.quality.commands.formatFix ?? ctx.config.review.commands.formatFix;
|
|
47712
|
+
}
|
|
47713
|
+
function hasMechanicalFixCommand(ctx) {
|
|
47714
|
+
return ["lintFix", "formatFix"].some((name) => resolveBroadFixCommand(ctx, name) ?? resolveScopedFixTemplate(ctx, name));
|
|
47715
|
+
}
|
|
47716
|
+
function resolveScopedFixTemplate(ctx, commandName) {
|
|
47717
|
+
return commandName === "lintFix" ? ctx.config.review.commands.lintFixScoped ?? ctx.config.quality.commands.lintFixScoped : ctx.config.review.commands.formatFixScoped ?? ctx.config.quality.commands.formatFixScoped;
|
|
47718
|
+
}
|
|
47719
|
+
function deriveScopedFixCommand(command, files) {
|
|
47720
|
+
const trimmed = command.trim();
|
|
47721
|
+
const supportedTools = ["eslint", "biome", "ruff", "flake8", "prettier"];
|
|
47722
|
+
const isSupported = supportedTools.some((tool) => trimmed === tool || trimmed.startsWith(`${tool} `)) || supportedTools.some((tool) => trimmed.startsWith(`bunx ${tool}`));
|
|
47723
|
+
if (!isSupported)
|
|
47724
|
+
return;
|
|
47725
|
+
return `${command} ${files.map(shellQuotePath2).join(" ")}`;
|
|
47726
|
+
}
|
|
47727
|
+
function shellQuotePath2(path9) {
|
|
47728
|
+
return `'${path9.replaceAll("'", "'\\''")}'`;
|
|
47729
|
+
}
|
|
47730
|
+
function logEmptyFixScope(ctx, commandName, command) {
|
|
47731
|
+
getLogger().info("autofix", `${toScopeLogPrefix(commandName)}_scope_empty`, { storyId: ctx.story.id });
|
|
47732
|
+
return { commandName, command, scoped: true, skipped: true };
|
|
47733
|
+
}
|
|
47734
|
+
function warnAndUseFullFix(ctx, commandName, command, reason) {
|
|
47735
|
+
getLogger().warn("autofix", `${toScopeLogPrefix(commandName)}_scope_degraded`, { storyId: ctx.story.id, reason });
|
|
47736
|
+
if (!command)
|
|
47737
|
+
return { commandName, command: "", scoped: false, skipped: true };
|
|
47738
|
+
return { commandName, command, scoped: false };
|
|
47739
|
+
}
|
|
47740
|
+
function toScopeLogPrefix(commandName) {
|
|
47741
|
+
return commandName === "lintFix" ? "lint_fix" : "format_fix";
|
|
47742
|
+
}
|
|
47743
|
+
function logMechanicalFixResult(ctx, resolved, exitCode) {
|
|
47744
|
+
const logger = getLogger();
|
|
47745
|
+
logger.debug("autofix", `${resolved.commandName} exit=${exitCode}`, {
|
|
47746
|
+
storyId: ctx.story.id,
|
|
47747
|
+
command: resolved.command,
|
|
47748
|
+
scoped: resolved.scoped
|
|
47749
|
+
});
|
|
47750
|
+
if (exitCode !== 0) {
|
|
47751
|
+
logger.warn("autofix", `${resolved.commandName} command failed \u2014 may not have fixed all issues`, {
|
|
47752
|
+
storyId: ctx.story.id,
|
|
47753
|
+
exitCode
|
|
47754
|
+
});
|
|
47755
|
+
}
|
|
47756
|
+
}
|
|
47318
47757
|
var NON_FIXABLE_BY_RECTIFICATION, autofixStage, _autofixDeps;
|
|
47319
47758
|
var init_autofix = __esm(() => {
|
|
47320
47759
|
init_logger2();
|
|
47321
47760
|
init_quality();
|
|
47322
47761
|
init_event_bus();
|
|
47323
47762
|
init_autofix_agent();
|
|
47763
|
+
init_autofix_cycle();
|
|
47324
47764
|
init_autofix_scope_split();
|
|
47325
47765
|
init_autofix_test_writer2();
|
|
47326
47766
|
NON_FIXABLE_BY_RECTIFICATION = new Set(["git-clean"]);
|
|
@@ -47345,8 +47785,7 @@ var init_autofix = __esm(() => {
|
|
|
47345
47785
|
if (!reviewResult || reviewResult.success) {
|
|
47346
47786
|
return { action: "continue" };
|
|
47347
47787
|
}
|
|
47348
|
-
|
|
47349
|
-
const formatFixCmd = ctx.config.quality.commands.formatFix ?? ctx.config.review.commands.formatFix;
|
|
47788
|
+
ctx.autofixAttempt = (ctx.autofixAttempt ?? 0) + 1;
|
|
47350
47789
|
const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
|
|
47351
47790
|
const hasLintFailure = failedCheckNames.has("lint");
|
|
47352
47791
|
const totalFindingCount = (reviewResult.checks ?? []).reduce((n, c) => n + (c.findings?.length ?? 0), 0);
|
|
@@ -47367,42 +47806,8 @@ var init_autofix = __esm(() => {
|
|
|
47367
47806
|
failedChecks: [...failedCheckNames],
|
|
47368
47807
|
workdir: ctx.workdir
|
|
47369
47808
|
});
|
|
47370
|
-
if (hasLintFailure && (
|
|
47371
|
-
|
|
47372
|
-
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
|
|
47373
|
-
const lintResult = await _autofixDeps.runQualityCommand({
|
|
47374
|
-
commandName: "lintFix",
|
|
47375
|
-
command: lintFixCmd,
|
|
47376
|
-
workdir: ctx.workdir,
|
|
47377
|
-
storyId: ctx.story.id
|
|
47378
|
-
});
|
|
47379
|
-
logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id, command: lintFixCmd });
|
|
47380
|
-
if (lintResult.exitCode !== 0) {
|
|
47381
|
-
logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
|
|
47382
|
-
storyId: ctx.story.id,
|
|
47383
|
-
exitCode: lintResult.exitCode
|
|
47384
|
-
});
|
|
47385
|
-
}
|
|
47386
|
-
}
|
|
47387
|
-
if (formatFixCmd) {
|
|
47388
|
-
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
|
|
47389
|
-
const fmtResult = await _autofixDeps.runQualityCommand({
|
|
47390
|
-
commandName: "formatFix",
|
|
47391
|
-
command: formatFixCmd,
|
|
47392
|
-
workdir: ctx.workdir,
|
|
47393
|
-
storyId: ctx.story.id
|
|
47394
|
-
});
|
|
47395
|
-
logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, {
|
|
47396
|
-
storyId: ctx.story.id,
|
|
47397
|
-
command: formatFixCmd
|
|
47398
|
-
});
|
|
47399
|
-
if (fmtResult.exitCode !== 0) {
|
|
47400
|
-
logger.warn("autofix", "formatFix command failed \u2014 may not have fixed all issues", {
|
|
47401
|
-
storyId: ctx.story.id,
|
|
47402
|
-
exitCode: fmtResult.exitCode
|
|
47403
|
-
});
|
|
47404
|
-
}
|
|
47405
|
-
}
|
|
47809
|
+
if (hasLintFailure && hasMechanicalFixCommand(ctx)) {
|
|
47810
|
+
await runMechanicalFixes(ctx, failedCheckNames);
|
|
47406
47811
|
const recheckPassed = await _autofixDeps.recheckReview(ctx);
|
|
47407
47812
|
pipelineEventBus.emit({ type: "autofix:completed", storyId: ctx.story.id, fixed: recheckPassed });
|
|
47408
47813
|
if (recheckPassed) {
|
|
@@ -47445,7 +47850,7 @@ var init_autofix = __esm(() => {
|
|
|
47445
47850
|
cost: agentCost,
|
|
47446
47851
|
unresolvedReason,
|
|
47447
47852
|
escalationDigest
|
|
47448
|
-
} = await _autofixDeps.runAgentRectification(ctx,
|
|
47853
|
+
} = await _autofixDeps.runAgentRectification(ctx, resolveBroadFixCommand(ctx, "lintFix"), resolveBroadFixCommand(ctx, "formatFix"), ctx.workdir);
|
|
47449
47854
|
if (!agentFixed && unresolvedReason) {
|
|
47450
47855
|
if (ctx.mechanicalFailedOnly) {
|
|
47451
47856
|
logger.warn("autofix", "Mechanical-only failure unfixable \u2014 proceeding (LLM review passed)", {
|
|
@@ -47480,7 +47885,8 @@ var init_autofix = __esm(() => {
|
|
|
47480
47885
|
const totalUsed = ctx.autofixAttempt ?? 0;
|
|
47481
47886
|
const currentlyFailing = new Set((ctx.reviewResult?.checks ?? []).filter((c) => !c.success || c.failOpen).map((c) => c.check));
|
|
47482
47887
|
const nowPassing = [...failedCheckNames].filter((c) => !currentlyFailing.has(c));
|
|
47483
|
-
|
|
47888
|
+
const capacityExhausted = autofixCapacityExhausted(ctx);
|
|
47889
|
+
if (nowPassing.length > 0 && totalUsed < maxTotal && !capacityExhausted) {
|
|
47484
47890
|
ctx.retrySkipChecks = new Set([...ctx.retrySkipChecks ?? [], ...nowPassing]);
|
|
47485
47891
|
logger.info("autofix", "Partial progress \u2014 retrying review with updated skip list", {
|
|
47486
47892
|
storyId: ctx.story.id,
|
|
@@ -47490,6 +47896,13 @@ var init_autofix = __esm(() => {
|
|
|
47490
47896
|
});
|
|
47491
47897
|
return { action: "retry", fromStage: "review", cost: agentCost };
|
|
47492
47898
|
}
|
|
47899
|
+
if (nowPassing.length > 0 && capacityExhausted) {
|
|
47900
|
+
logger.info("autofix", "Partial progress \u2014 but autofix capacity exhausted; escalating instead of retrying review", {
|
|
47901
|
+
storyId: ctx.story.id,
|
|
47902
|
+
nowPassing,
|
|
47903
|
+
remaining: [...currentlyFailing]
|
|
47904
|
+
});
|
|
47905
|
+
}
|
|
47493
47906
|
logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
|
|
47494
47907
|
return {
|
|
47495
47908
|
action: "escalate",
|
|
@@ -54120,6 +54533,20 @@ var init_command_argv = __esm(() => {
|
|
|
54120
54533
|
|
|
54121
54534
|
// src/hooks/runner.ts
|
|
54122
54535
|
import { join as join67 } from "path";
|
|
54536
|
+
function createDrainDeadline2(deadlineMs) {
|
|
54537
|
+
let timeoutId;
|
|
54538
|
+
const promise2 = new Promise((resolve16) => {
|
|
54539
|
+
timeoutId = setTimeout(() => resolve16(""), deadlineMs);
|
|
54540
|
+
});
|
|
54541
|
+
return {
|
|
54542
|
+
promise: promise2,
|
|
54543
|
+
cancel: () => {
|
|
54544
|
+
if (timeoutId !== undefined) {
|
|
54545
|
+
clearTimeout(timeoutId);
|
|
54546
|
+
}
|
|
54547
|
+
}
|
|
54548
|
+
};
|
|
54549
|
+
}
|
|
54123
54550
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
54124
54551
|
let globalHooks = { hooks: {} };
|
|
54125
54552
|
let projectHooks = { hooks: {} };
|
|
@@ -54222,15 +54649,30 @@ async function executeHook(hookDef, ctx, workdir) {
|
|
|
54222
54649
|
stderr: "pipe",
|
|
54223
54650
|
env: buildAllowedEnv({ env: env2 })
|
|
54224
54651
|
});
|
|
54652
|
+
let timedOut = false;
|
|
54225
54653
|
const timeoutId = setTimeout(() => {
|
|
54654
|
+
timedOut = true;
|
|
54226
54655
|
killProcessGroup(proc.pid, "SIGTERM");
|
|
54227
54656
|
}, timeout);
|
|
54657
|
+
const stdoutPromise = new Response(proc.stdout).text().catch(() => "");
|
|
54658
|
+
const stderrPromise = new Response(proc.stderr).text().catch(() => "");
|
|
54228
54659
|
const exitCode = await proc.exited;
|
|
54229
54660
|
clearTimeout(timeoutId);
|
|
54230
|
-
const stdout = await
|
|
54231
|
-
|
|
54661
|
+
const [stdout, stderr] = timedOut ? await (async () => {
|
|
54662
|
+
const stdoutDrain = createDrainDeadline2(STREAM_DRAIN_TIMEOUT_MS2);
|
|
54663
|
+
const stderrDrain = createDrainDeadline2(STREAM_DRAIN_TIMEOUT_MS2);
|
|
54664
|
+
try {
|
|
54665
|
+
return await Promise.all([
|
|
54666
|
+
Promise.race([stdoutPromise, stdoutDrain.promise]),
|
|
54667
|
+
Promise.race([stderrPromise, stderrDrain.promise])
|
|
54668
|
+
]);
|
|
54669
|
+
} finally {
|
|
54670
|
+
stdoutDrain.cancel();
|
|
54671
|
+
stderrDrain.cancel();
|
|
54672
|
+
}
|
|
54673
|
+
})() : await Promise.all([stdoutPromise, stderrPromise]);
|
|
54232
54674
|
const output = (stdout + stderr).trim();
|
|
54233
|
-
if (
|
|
54675
|
+
if (timedOut) {
|
|
54234
54676
|
return {
|
|
54235
54677
|
success: false,
|
|
54236
54678
|
output: `Hook timed out after ${timeout}ms`
|
|
@@ -54268,7 +54710,7 @@ async function fireHook(config2, event, ctx, workdir) {
|
|
|
54268
54710
|
}
|
|
54269
54711
|
}
|
|
54270
54712
|
}
|
|
54271
|
-
var DEFAULT_TIMEOUT = 5000;
|
|
54713
|
+
var DEFAULT_TIMEOUT = 5000, STREAM_DRAIN_TIMEOUT_MS2 = 2000;
|
|
54272
54714
|
var init_runner5 = __esm(() => {
|
|
54273
54715
|
init_env();
|
|
54274
54716
|
init_logger2();
|
|
@@ -54286,7 +54728,7 @@ var package_default;
|
|
|
54286
54728
|
var init_package = __esm(() => {
|
|
54287
54729
|
package_default = {
|
|
54288
54730
|
name: "@nathapp/nax",
|
|
54289
|
-
version: "0.65.
|
|
54731
|
+
version: "0.65.3",
|
|
54290
54732
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
54291
54733
|
type: "module",
|
|
54292
54734
|
bin: {
|
|
@@ -54372,8 +54814,8 @@ var init_version = __esm(() => {
|
|
|
54372
54814
|
NAX_VERSION = package_default.version;
|
|
54373
54815
|
NAX_COMMIT = (() => {
|
|
54374
54816
|
try {
|
|
54375
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
54376
|
-
return "
|
|
54817
|
+
if (/^[0-9a-f]{6,10}$/.test("9ff2ea7d"))
|
|
54818
|
+
return "9ff2ea7d";
|
|
54377
54819
|
} catch {}
|
|
54378
54820
|
try {
|
|
54379
54821
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -90724,6 +91166,7 @@ __export(exports_curator, {
|
|
|
90724
91166
|
_curatorCmdDeps: () => _curatorCmdDeps
|
|
90725
91167
|
});
|
|
90726
91168
|
import { readdirSync as readdirSync10 } from "fs";
|
|
91169
|
+
import { unlink as unlink4 } from "fs/promises";
|
|
90727
91170
|
import { basename as basename16, join as join79 } from "path";
|
|
90728
91171
|
function getProjectKey(config2, projectDir) {
|
|
90729
91172
|
return config2.name?.trim() || basename16(projectDir);
|
|
@@ -90994,6 +91437,16 @@ async function curatorGc(options) {
|
|
|
90994
91437
|
`)}
|
|
90995
91438
|
`;
|
|
90996
91439
|
await _curatorCmdDeps.writeFile(rollupPath, newContent);
|
|
91440
|
+
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
91441
|
+
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
91442
|
+
const perRunsDir = join79(outputDir, "runs");
|
|
91443
|
+
for (const runId of uniqueRunIds) {
|
|
91444
|
+
if (!keepSet.has(runId)) {
|
|
91445
|
+
const runDir = join79(perRunsDir, runId);
|
|
91446
|
+
await _curatorCmdDeps.removeFile(join79(runDir, "observations.jsonl"));
|
|
91447
|
+
await _curatorCmdDeps.removeFile(join79(runDir, "curator-proposals.md"));
|
|
91448
|
+
}
|
|
91449
|
+
}
|
|
90997
91450
|
console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
|
|
90998
91451
|
}
|
|
90999
91452
|
var _curatorCmdDeps, DEFAULT_KEEP = 50;
|
|
@@ -91017,6 +91470,11 @@ var init_curator2 = __esm(() => {
|
|
|
91017
91470
|
const prev = await existing.exists() ? await existing.text() : "";
|
|
91018
91471
|
await Bun.write(p, prev + content);
|
|
91019
91472
|
},
|
|
91473
|
+
removeFile: async (p) => {
|
|
91474
|
+
try {
|
|
91475
|
+
await unlink4(p);
|
|
91476
|
+
} catch {}
|
|
91477
|
+
},
|
|
91020
91478
|
openInEditor: async (filePath) => {
|
|
91021
91479
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
91022
91480
|
const proc = Bun.spawnSync([editor, filePath], { stdio: ["inherit", "inherit", "inherit"] });
|
|
@@ -91067,6 +91525,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
91067
91525
|
if (!existsSync15(naxDir)) {
|
|
91068
91526
|
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
91069
91527
|
}
|
|
91528
|
+
validateFeatureName(options.feature);
|
|
91070
91529
|
const logger = getLogger();
|
|
91071
91530
|
logger?.info("plan", "Reading spec", { from: options.from });
|
|
91072
91531
|
const specContent = await _planDeps.readFile(options.from);
|
|
@@ -92516,6 +92975,8 @@ var FIELD_DESCRIPTIONS = {
|
|
|
92516
92975
|
"quality.commands.typecheck": "Custom typecheck command",
|
|
92517
92976
|
"quality.commands.lint": "Custom lint command",
|
|
92518
92977
|
"quality.commands.lintScoped": "Scoped lint command template for story-owned files (supports {{files}})",
|
|
92978
|
+
"quality.commands.lintFixScoped": "Scoped lint fix command template for story-owned files (supports {{files}})",
|
|
92979
|
+
"quality.commands.formatFixScoped": "Scoped format fix command template for story-owned files (supports {{files}})",
|
|
92519
92980
|
"quality.commands.test": "Custom test command",
|
|
92520
92981
|
"quality.commands.build": "Custom build command",
|
|
92521
92982
|
"quality.forceExit": "Append --forceExit to test command (prevents hangs)",
|
|
@@ -92549,6 +93010,8 @@ var FIELD_DESCRIPTIONS = {
|
|
|
92549
93010
|
"review.commands.typecheck": "Custom typecheck command for review",
|
|
92550
93011
|
"review.commands.lint": "Custom lint command for review",
|
|
92551
93012
|
"review.commands.lintScoped": "Scoped lint command template for review (supports {{files}})",
|
|
93013
|
+
"review.commands.lintFixScoped": "Scoped lint fix command template for review (supports {{files}})",
|
|
93014
|
+
"review.commands.formatFixScoped": "Scoped format fix command template for review (supports {{files}})",
|
|
92552
93015
|
"review.commands.test": "Custom test command for review",
|
|
92553
93016
|
"review.commands.build": "Custom build command for review",
|
|
92554
93017
|
"review.semantic": "Semantic review configuration (code quality analysis)",
|