@nathapp/nax 0.80.0 → 0.80.1
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 +1764 -1646
- package/flows/nax-finish/commit-message.ts +34 -10
- package/flows/nax-finish/findings-parse.ts +150 -0
- package/flows/nax-finish/flow-ctx.ts +5 -0
- package/flows/nax-finish/nax-finish.flow.ts +42 -8
- package/flows/nax-finish/review-prompts.ts +103 -40
- package/flows/nax-finish/steps/commit-round.ts +4 -1
- package/flows/nax-finish/steps/index.ts +1 -0
- package/flows/nax-finish/steps/pr-body.ts +22 -2
- package/flows/nax-finish/steps/review-audit.ts +91 -0
- package/flows/nax-finish/steps/review-round.ts +42 -7
- package/flows/nax-finish/types.ts +59 -1
- package/flows/nax-finish/verdict.ts +39 -5
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -16891,7 +16891,7 @@ var init_schemas_execution = __esm(() => {
|
|
|
16891
16891
|
return val;
|
|
16892
16892
|
}, SmartTestRunnerConfigSchema).default(SMART_TEST_RUNNER_DEFAULT);
|
|
16893
16893
|
WorktreeDependenciesConfigSchema = exports_external.object({
|
|
16894
|
-
mode: exports_external.enum(["
|
|
16894
|
+
mode: exports_external.enum(["provision", "off"]).default("off"),
|
|
16895
16895
|
setupCommand: exports_external.string().nullable().default(null)
|
|
16896
16896
|
}).superRefine((value, ctx) => {
|
|
16897
16897
|
if (value.mode !== "provision" && value.setupCommand !== null) {
|
|
@@ -17924,7 +17924,8 @@ function formatMutationSummary(summaries) {
|
|
|
17924
17924
|
|
|
17925
17925
|
// src/review/severity.ts
|
|
17926
17926
|
function isBlockingSeverity(sev, threshold = "error") {
|
|
17927
|
-
|
|
17927
|
+
const rank = SEVERITY_RANK[sev] ?? 0;
|
|
17928
|
+
return rank >= SEVERITY_RANK[threshold];
|
|
17928
17929
|
}
|
|
17929
17930
|
var SEVERITY_RANK;
|
|
17930
17931
|
var init_severity = __esm(() => {
|
|
@@ -18257,7 +18258,8 @@ function formatAdvisorySummary(findings, options) {
|
|
|
18257
18258
|
return JSON.stringify(findings);
|
|
18258
18259
|
}
|
|
18259
18260
|
const c = useColor ? source_default : createNoopChalk();
|
|
18260
|
-
const
|
|
18261
|
+
const rank = SEVERITY_RANK;
|
|
18262
|
+
const sorted = [...findings].sort((a, b) => (rank[b.severity] ?? 0) - (rank[a.severity] ?? 0));
|
|
18261
18263
|
const lines = [];
|
|
18262
18264
|
lines.push("");
|
|
18263
18265
|
lines.push(c.yellow("\u2500".repeat(60)));
|
|
@@ -18399,6 +18401,9 @@ function redactString(value) {
|
|
|
18399
18401
|
}
|
|
18400
18402
|
return out;
|
|
18401
18403
|
}
|
|
18404
|
+
function redactSecrets(input) {
|
|
18405
|
+
return redactValue(input);
|
|
18406
|
+
}
|
|
18402
18407
|
function redactEntry(entry) {
|
|
18403
18408
|
return {
|
|
18404
18409
|
...entry,
|
|
@@ -18691,6 +18696,7 @@ var init_logger = __esm(() => {
|
|
|
18691
18696
|
var exports_logger = {};
|
|
18692
18697
|
__export(exports_logger, {
|
|
18693
18698
|
resetLogger: () => resetLogger,
|
|
18699
|
+
redactSecrets: () => redactSecrets,
|
|
18694
18700
|
initLogger: () => initLogger,
|
|
18695
18701
|
getSafeLogger: () => getSafeLogger,
|
|
18696
18702
|
getLogger: () => getLogger,
|
|
@@ -18702,6 +18708,7 @@ __export(exports_logger, {
|
|
|
18702
18708
|
var init_logger2 = __esm(() => {
|
|
18703
18709
|
init_logger();
|
|
18704
18710
|
init_formatters();
|
|
18711
|
+
init_redact();
|
|
18705
18712
|
});
|
|
18706
18713
|
|
|
18707
18714
|
// src/utils/json-file.ts
|
|
@@ -18853,6 +18860,17 @@ function _applyRemovedRoutingKeysShim(conf, warn = defaultConfigWarn) {
|
|
|
18853
18860
|
}
|
|
18854
18861
|
return newRouting === routing ? conf : { ...conf, routing: newRouting };
|
|
18855
18862
|
}
|
|
18863
|
+
function _applyRemovedWorktreeInheritShim(conf, warn = defaultConfigWarn) {
|
|
18864
|
+
const execution = conf.execution;
|
|
18865
|
+
const worktreeDependencies = execution?.worktreeDependencies;
|
|
18866
|
+
if (worktreeDependencies?.mode !== "inherit")
|
|
18867
|
+
return conf;
|
|
18868
|
+
warn('execution.worktreeDependencies.mode="inherit" was removed (issue #574) and is mapped to "off". ' + "For JS/TS repos that is the same behaviour. If this repo needs its own install (a Python venv, bundler, composer), " + 'set mode "provision" with a setupCommand: "inherit" used to fail the run outright in that case, and "off" will now ' + "proceed without installing.");
|
|
18869
|
+
return {
|
|
18870
|
+
...conf,
|
|
18871
|
+
execution: { ...execution, worktreeDependencies: { ...worktreeDependencies, mode: "off" } }
|
|
18872
|
+
};
|
|
18873
|
+
}
|
|
18856
18874
|
function applyBatchModeCompat(conf, warn = defaultConfigWarn) {
|
|
18857
18875
|
const routing = conf.routing;
|
|
18858
18876
|
const llm = routing?.llm;
|
|
@@ -18940,7 +18958,7 @@ function warnSecuritySensitiveOverrides(beforeConfig, afterConfig, warn = defaul
|
|
|
18940
18958
|
function applyConfigCompatShims(conf, logger, dedupe) {
|
|
18941
18959
|
const log = dedupe.wrapLogger(logger);
|
|
18942
18960
|
const warn = dedupe.warn;
|
|
18943
|
-
return _applyLegacyReviewExecutionShim(_applyRemovedRoutingKeysShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(conf, log), log), warn), warn), warn), warn), warn);
|
|
18961
|
+
return _applyRemovedWorktreeInheritShim(_applyLegacyReviewExecutionShim(_applyRemovedRoutingKeysShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(conf, log), log), warn), warn), warn), warn), warn), warn);
|
|
18944
18962
|
}
|
|
18945
18963
|
var SECURITY_SENSITIVE_KEY_PATHS;
|
|
18946
18964
|
var init_compat_shims = __esm(() => {
|
|
@@ -19511,6 +19529,26 @@ var init_path_security = () => {};
|
|
|
19511
19529
|
// src/config/paths.ts
|
|
19512
19530
|
import { homedir } from "os";
|
|
19513
19531
|
import { join, resolve as resolve2 } from "path";
|
|
19532
|
+
function validateFeatureId(featureId) {
|
|
19533
|
+
if (!featureId || featureId.length === 0) {
|
|
19534
|
+
throw new NaxError("Feature ID cannot be empty", "INVALID_FEATURE_ID", { stage: "config" });
|
|
19535
|
+
}
|
|
19536
|
+
if (featureId.includes("..")) {
|
|
19537
|
+
throw new NaxError("Feature ID cannot contain path traversal (..)", "INVALID_FEATURE_ID", {
|
|
19538
|
+
stage: "config",
|
|
19539
|
+
featureId
|
|
19540
|
+
});
|
|
19541
|
+
}
|
|
19542
|
+
if (featureId.startsWith("--")) {
|
|
19543
|
+
throw new NaxError("Feature ID cannot start with git flags (--)", "INVALID_FEATURE_ID", {
|
|
19544
|
+
stage: "config",
|
|
19545
|
+
featureId
|
|
19546
|
+
});
|
|
19547
|
+
}
|
|
19548
|
+
if (!FEATURE_ID_PATTERN.test(featureId)) {
|
|
19549
|
+
throw new NaxError(`Feature ID must match pattern [a-zA-Z0-9_][a-zA-Z0-9._-]{0,63}. Got: ${featureId}`, "INVALID_FEATURE_ID", { stage: "config", featureId });
|
|
19550
|
+
}
|
|
19551
|
+
}
|
|
19514
19552
|
function globalConfigDir() {
|
|
19515
19553
|
const override = process.env[GLOBAL_CONFIG_DIR_ENV];
|
|
19516
19554
|
if (override)
|
|
@@ -19524,10 +19562,13 @@ function featuresDir(root) {
|
|
|
19524
19562
|
return join(root, PROJECT_NAX_DIR, "features");
|
|
19525
19563
|
}
|
|
19526
19564
|
function featureDir(root, featureId) {
|
|
19565
|
+
validateFeatureId(featureId);
|
|
19527
19566
|
return join(featuresDir(root), featureId);
|
|
19528
19567
|
}
|
|
19529
|
-
var GLOBAL_CONFIG_DIR_ENV = "NAX_GLOBAL_CONFIG_DIR", PROJECT_NAX_DIR = ".nax", PROJECT_FEATURES_DIR;
|
|
19568
|
+
var GLOBAL_CONFIG_DIR_ENV = "NAX_GLOBAL_CONFIG_DIR", FEATURE_ID_PATTERN, PROJECT_NAX_DIR = ".nax", PROJECT_FEATURES_DIR;
|
|
19530
19569
|
var init_paths = __esm(() => {
|
|
19570
|
+
init_errors();
|
|
19571
|
+
FEATURE_ID_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,63}$/;
|
|
19531
19572
|
PROJECT_FEATURES_DIR = `${PROJECT_NAX_DIR}/features`;
|
|
19532
19573
|
});
|
|
19533
19574
|
|
|
@@ -19714,55 +19755,41 @@ function resolveEnvVarsWarnOnFailure(config2, logger, layerName) {
|
|
|
19714
19755
|
function globalConfigPath() {
|
|
19715
19756
|
return join3(globalConfigDir(), "config.json");
|
|
19716
19757
|
}
|
|
19717
|
-
function
|
|
19718
|
-
let dir = resolve3(startDir);
|
|
19719
|
-
let depth = 0;
|
|
19720
|
-
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
19721
|
-
const candidate = join3(dir, PROJECT_NAX_DIR);
|
|
19722
|
-
if (existsSync3(join3(candidate, "config.json"))) {
|
|
19723
|
-
return candidate;
|
|
19724
|
-
}
|
|
19725
|
-
const parent = join3(dir, "..");
|
|
19726
|
-
if (parent === dir)
|
|
19727
|
-
break;
|
|
19728
|
-
dir = parent;
|
|
19729
|
-
depth++;
|
|
19730
|
-
}
|
|
19731
|
-
return null;
|
|
19732
|
-
}
|
|
19733
|
-
async function loadConfig(startDir, cliOverrides) {
|
|
19734
|
-
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
19735
|
-
const warnDedupe = createConfigWarnDedupe();
|
|
19758
|
+
function resolveProjectPaths(startDir) {
|
|
19736
19759
|
const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
19737
19760
|
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname(startDir) : startDir : process.cwd();
|
|
19738
|
-
|
|
19739
|
-
|
|
19761
|
+
return { projDir, projectRoot };
|
|
19762
|
+
}
|
|
19763
|
+
async function applyGlobalLayer(rawConfig, ctx) {
|
|
19740
19764
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
19741
|
-
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
|
|
19749
|
-
|
|
19750
|
-
|
|
19751
|
-
|
|
19752
|
-
if (projDir)
|
|
19753
|
-
|
|
19754
|
-
|
|
19755
|
-
|
|
19756
|
-
|
|
19757
|
-
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
|
|
19761
|
-
|
|
19762
|
-
|
|
19763
|
-
|
|
19764
|
-
}
|
|
19765
|
-
|
|
19765
|
+
if (!globalConfRaw)
|
|
19766
|
+
return { rawConfig, globalConfRaw: null, globalLayerConf: null };
|
|
19767
|
+
const { profile: _gProfile, ...globalConfStripped } = globalConfRaw;
|
|
19768
|
+
const globalConf = applyConfigCompatShims(globalConfStripped, ctx.logger, ctx.warnDedupe);
|
|
19769
|
+
return {
|
|
19770
|
+
rawConfig: deepMergeConfig(rawConfig, globalConf),
|
|
19771
|
+
globalConfRaw,
|
|
19772
|
+
globalLayerConf: globalConf
|
|
19773
|
+
};
|
|
19774
|
+
}
|
|
19775
|
+
async function applyProjectLayer(rawConfig, projDir, globalLayerConf, ctx) {
|
|
19776
|
+
if (!projDir)
|
|
19777
|
+
return rawConfig;
|
|
19778
|
+
const projConf = await loadJsonFile(join3(projDir, "config.json"), "config");
|
|
19779
|
+
if (!projConf)
|
|
19780
|
+
return rawConfig;
|
|
19781
|
+
const { profile: _pProfile, ...projConfStripped } = projConf;
|
|
19782
|
+
const resolvedProjConf = applyConfigCompatShims(projConfStripped, ctx.logger, ctx.warnDedupe);
|
|
19783
|
+
const preProjectMergeConfig = rawConfig;
|
|
19784
|
+
const merged = deepMergeConfig(rawConfig, resolvedProjConf);
|
|
19785
|
+
warnSecuritySensitiveOverrides(preProjectMergeConfig, merged, ctx.warnDedupe.warn, {
|
|
19786
|
+
layerName: "project",
|
|
19787
|
+
sourceLayerConf: globalLayerConf
|
|
19788
|
+
});
|
|
19789
|
+
return merged;
|
|
19790
|
+
}
|
|
19791
|
+
async function applyProfileChainLayer(rawConfig, overlayChain, projectRoot, ctx) {
|
|
19792
|
+
let merged = rawConfig;
|
|
19766
19793
|
for (const name of overlayChain) {
|
|
19767
19794
|
const profileData = await loadProfile(name, projectRoot);
|
|
19768
19795
|
const profileEnv = await loadProfileEnv(name, projectRoot);
|
|
@@ -19774,36 +19801,36 @@ async function loadConfig(startDir, cliOverrides) {
|
|
|
19774
19801
|
const path = err instanceof UnresolvedEnvVarError ? err.path.join(".") : undefined;
|
|
19775
19802
|
throw new NaxError(`Profile "${name}" references an undefined environment variable${varName ? ` $${varName}` : ""}${path ? ` at "${path}"` : ""}.`, "PROFILE_ENV_VAR_UNRESOLVED", { stage: "config", profileName: name, varName, path, cause: err });
|
|
19776
19803
|
}
|
|
19777
|
-
const shimmedProfileData = applyConfigCompatShims(resolvedProfileData, logger, warnDedupe);
|
|
19778
|
-
const preProfileMergeConfig =
|
|
19779
|
-
|
|
19780
|
-
warnSecuritySensitiveOverrides(preProfileMergeConfig,
|
|
19804
|
+
const shimmedProfileData = applyConfigCompatShims(resolvedProfileData, ctx.logger, ctx.warnDedupe);
|
|
19805
|
+
const preProfileMergeConfig = merged;
|
|
19806
|
+
merged = deepMergeConfig(merged, shimmedProfileData);
|
|
19807
|
+
warnSecuritySensitiveOverrides(preProfileMergeConfig, merged, ctx.warnDedupe.warn, {
|
|
19781
19808
|
layerName: `profile:${name}`,
|
|
19782
19809
|
sourceLayerConf: shimmedProfileData
|
|
19783
19810
|
});
|
|
19784
19811
|
}
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19789
|
-
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
19812
|
+
return merged;
|
|
19813
|
+
}
|
|
19814
|
+
function applyCliOverridesLayer(rawConfig, cliOverrides, ctx) {
|
|
19815
|
+
if (!cliOverrides)
|
|
19816
|
+
return rawConfig;
|
|
19817
|
+
const shimmedCliOverrides = applyConfigCompatShims(cliOverrides, ctx.logger, ctx.warnDedupe);
|
|
19818
|
+
const preCliMergeConfig = rawConfig;
|
|
19819
|
+
const merged = deepMergeConfig(rawConfig, shimmedCliOverrides);
|
|
19820
|
+
warnSecuritySensitiveOverrides(preCliMergeConfig, merged, ctx.warnDedupe.warn, {
|
|
19821
|
+
layerName: "CLI override",
|
|
19822
|
+
sourceLayerConf: shimmedCliOverrides
|
|
19823
|
+
});
|
|
19824
|
+
return merged;
|
|
19825
|
+
}
|
|
19826
|
+
function finalizeAndValidateRootConfig(rawConfig) {
|
|
19800
19827
|
rejectLegacyAgentKeys(rawConfig);
|
|
19801
19828
|
rejectLegacyRectificationKeys(rawConfig);
|
|
19802
19829
|
rejectDeadQualityFlags(rawConfig);
|
|
19803
19830
|
rejectUnimplementedScopedProfile(rawConfig);
|
|
19804
19831
|
rejectUnimplementedPermissionsBlock(rawConfig);
|
|
19805
|
-
|
|
19806
|
-
const result = NaxConfigSchema.safeParse(
|
|
19832
|
+
const stripped = stripRemovedNoOpKeys(rawConfig, defaultConfigWarn);
|
|
19833
|
+
const result = NaxConfigSchema.safeParse(stripped);
|
|
19807
19834
|
if (!result.success) {
|
|
19808
19835
|
const errors3 = result.error.issues.map((err) => {
|
|
19809
19836
|
const path = String(err.path.join("."));
|
|
@@ -19815,6 +19842,47 @@ ${errors3.join(`
|
|
|
19815
19842
|
}
|
|
19816
19843
|
return result.data;
|
|
19817
19844
|
}
|
|
19845
|
+
function findProjectDir(startDir = process.cwd()) {
|
|
19846
|
+
let dir = resolve3(startDir);
|
|
19847
|
+
let depth = 0;
|
|
19848
|
+
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
19849
|
+
const candidate = join3(dir, PROJECT_NAX_DIR);
|
|
19850
|
+
if (existsSync3(join3(candidate, "config.json"))) {
|
|
19851
|
+
return candidate;
|
|
19852
|
+
}
|
|
19853
|
+
const parent = join3(dir, "..");
|
|
19854
|
+
if (parent === dir)
|
|
19855
|
+
break;
|
|
19856
|
+
dir = parent;
|
|
19857
|
+
depth++;
|
|
19858
|
+
}
|
|
19859
|
+
return null;
|
|
19860
|
+
}
|
|
19861
|
+
async function loadConfig(startDir, cliOverrides) {
|
|
19862
|
+
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
19863
|
+
const warnDedupe = createConfigWarnDedupe();
|
|
19864
|
+
const { projDir, projectRoot } = resolveProjectPaths(startDir);
|
|
19865
|
+
const profileChain = await resolveProfileNames(cliOverrides ?? {}, process.env, projectRoot);
|
|
19866
|
+
const overlayChain = profileChain.filter((name) => name && name !== "default");
|
|
19867
|
+
let logger = null;
|
|
19868
|
+
try {
|
|
19869
|
+
logger = getLogger();
|
|
19870
|
+
} catch {}
|
|
19871
|
+
const ctx = { logger, warnDedupe };
|
|
19872
|
+
const globalLayer = await applyGlobalLayer(rawConfig, ctx);
|
|
19873
|
+
rawConfig = globalLayer.rawConfig;
|
|
19874
|
+
rawConfig = await applyProjectLayer(rawConfig, projDir, globalLayer.globalLayerConf, ctx);
|
|
19875
|
+
rawConfig = resolveEnvVarsWarnOnFailure(rawConfig, logger, "global/project config");
|
|
19876
|
+
rawConfig = await applyProfileChainLayer(rawConfig, overlayChain, projectRoot, ctx);
|
|
19877
|
+
rawConfig = applyCliOverridesLayer(rawConfig, cliOverrides, ctx);
|
|
19878
|
+
rawConfig.profile = overlayChain.length > 0 ? overlayChain.join("+") : "default";
|
|
19879
|
+
rawConfig.profileChain = overlayChain;
|
|
19880
|
+
const hasMergedConfigs = globalLayer.globalConfRaw || projDir !== null || cliOverrides !== undefined || overlayChain.length > 0;
|
|
19881
|
+
if (!hasMergedConfigs) {
|
|
19882
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
19883
|
+
}
|
|
19884
|
+
return finalizeAndValidateRootConfig(rawConfig);
|
|
19885
|
+
}
|
|
19818
19886
|
async function loadPackageOverride(repoRoot, packageDir) {
|
|
19819
19887
|
const packageConfigPath = join3(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
|
|
19820
19888
|
const override = await loadJsonFile(packageConfigPath, "config");
|
|
@@ -19859,8 +19927,10 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19859
19927
|
}
|
|
19860
19928
|
logger.debug("config", "Per-package config loaded", { packageConfigPath, packageDir });
|
|
19861
19929
|
const { profile: packageProfile, ...packageFields } = packageOverride;
|
|
19862
|
-
|
|
19863
|
-
|
|
19930
|
+
const warnDedupe = createConfigWarnDedupe();
|
|
19931
|
+
const shimmedPackageFields = applyConfigCompatShims(packageFields, logger, warnDedupe);
|
|
19932
|
+
let merged = mergePackageConfig(rootConfig, shimmedPackageFields);
|
|
19933
|
+
merged = stripRemovedNoOpKeys(merged, warnDedupe.warn);
|
|
19864
19934
|
const envResolvedMerged = resolveEnvVarsWarnOnFailure(merged, logger, `per-package config (${packageDir})`);
|
|
19865
19935
|
const packageChain = parseProfileList(packageProfile).filter((name) => name && name !== "default");
|
|
19866
19936
|
let rawMerged = envResolvedMerged;
|
|
@@ -19877,7 +19947,8 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19877
19947
|
const path = err instanceof UnresolvedEnvVarError ? err.path.join(".") : undefined;
|
|
19878
19948
|
throw new NaxError(`Per-package profile "${name}" (${packageDir}) references an undefined environment variable${varName ? ` $${varName}` : ""}${path ? ` at "${path}"` : ""}.`, "PROFILE_ENV_VAR_UNRESOLVED", { stage: "config", profileName: name, packageDir, varName, path, cause: err });
|
|
19879
19949
|
}
|
|
19880
|
-
|
|
19950
|
+
const shimmedProfileData = applyConfigCompatShims(resolvedProfileData, logger, warnDedupe);
|
|
19951
|
+
rawMerged = deepMergeConfig(rawMerged, shimmedProfileData);
|
|
19881
19952
|
}
|
|
19882
19953
|
rawMerged.profile = packageChain.join("+");
|
|
19883
19954
|
rawMerged.profileChain = packageChain;
|
|
@@ -19887,7 +19958,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
|
|
|
19887
19958
|
rejectDeadQualityFlags(rawMerged);
|
|
19888
19959
|
rejectUnimplementedScopedProfile(rawMerged);
|
|
19889
19960
|
rejectUnimplementedPermissionsBlock(rawMerged);
|
|
19890
|
-
rawMerged = stripRemovedNoOpKeys(rawMerged,
|
|
19961
|
+
rawMerged = stripRemovedNoOpKeys(rawMerged, warnDedupe.warn);
|
|
19891
19962
|
const result = NaxConfigSchema.safeParse(rawMerged);
|
|
19892
19963
|
if (!result.success) {
|
|
19893
19964
|
const errors3 = result.error.issues.map((err) => {
|
|
@@ -29603,7 +29674,7 @@ function scanDirectory(sourceGlob, workdir, ignoreMatchers, maxGlobFiles, globCt
|
|
|
29603
29674
|
return { workdir, files, truncated };
|
|
29604
29675
|
}
|
|
29605
29676
|
async function collectNeighbors(filePath, workdir, scannedDirs, contentCacheState, siblingTestContext) {
|
|
29606
|
-
const
|
|
29677
|
+
const forwardNeighbors = new Set;
|
|
29607
29678
|
let anyTruncated = false;
|
|
29608
29679
|
const ownAbsPath = join14(workdir, filePath);
|
|
29609
29680
|
if (await _codeNeighborDeps.fileExists(ownAbsPath)) {
|
|
@@ -29612,18 +29683,19 @@ async function collectNeighbors(filePath, workdir, scannedDirs, contentCacheStat
|
|
|
29612
29683
|
for (const spec of parseImportSpecifiers(ownContent)) {
|
|
29613
29684
|
const resolved = resolveImport(spec, filePath, workdir);
|
|
29614
29685
|
if (resolved && resolved !== filePath)
|
|
29615
|
-
|
|
29686
|
+
forwardNeighbors.add(resolved);
|
|
29616
29687
|
}
|
|
29617
29688
|
}
|
|
29618
29689
|
}
|
|
29619
29690
|
const fileBaseName = (filePath.split("/").pop() ?? filePath).replace(/\.[^.]+$/, "");
|
|
29620
29691
|
const fileNoExt = filePath.replace(/\.[^.]+$/, "");
|
|
29692
|
+
const reverseNeighbors = new Set;
|
|
29621
29693
|
outer:
|
|
29622
29694
|
for (const { workdir: scanWorkdir, files: srcFiles, truncated } of scannedDirs) {
|
|
29623
29695
|
if (truncated)
|
|
29624
29696
|
anyTruncated = true;
|
|
29625
29697
|
for (const srcFile of srcFiles) {
|
|
29626
|
-
if (
|
|
29698
|
+
if (reverseNeighbors.size >= MAX_NEIGHBORS_PER_FILE)
|
|
29627
29699
|
break outer;
|
|
29628
29700
|
if (srcFile === filePath)
|
|
29629
29701
|
continue;
|
|
@@ -29632,13 +29704,26 @@ async function collectNeighbors(filePath, workdir, scannedDirs, contentCacheStat
|
|
|
29632
29704
|
for (const spec of parseImportSpecifiers(content)) {
|
|
29633
29705
|
const resolved = resolveImport(spec, srcFile, scanWorkdir);
|
|
29634
29706
|
if (resolved === filePath || resolved === fileNoExt) {
|
|
29635
|
-
|
|
29707
|
+
reverseNeighbors.add(srcFile);
|
|
29636
29708
|
break;
|
|
29637
29709
|
}
|
|
29638
29710
|
}
|
|
29639
29711
|
}
|
|
29640
29712
|
}
|
|
29641
29713
|
}
|
|
29714
|
+
const minReverseSlots = Math.min(reverseNeighbors.size, Math.ceil(MAX_NEIGHBORS_PER_FILE / 2));
|
|
29715
|
+
const forwardSlots = MAX_NEIGHBORS_PER_FILE - minReverseSlots;
|
|
29716
|
+
const neighbors = new Set;
|
|
29717
|
+
for (const f of forwardNeighbors) {
|
|
29718
|
+
if (neighbors.size >= forwardSlots)
|
|
29719
|
+
break;
|
|
29720
|
+
neighbors.add(f);
|
|
29721
|
+
}
|
|
29722
|
+
for (const r of reverseNeighbors) {
|
|
29723
|
+
if (neighbors.size >= MAX_NEIGHBORS_PER_FILE)
|
|
29724
|
+
break;
|
|
29725
|
+
neighbors.add(r);
|
|
29726
|
+
}
|
|
29642
29727
|
if (siblingTestContext && !isTestFile2(filePath, siblingTestContext.regex)) {
|
|
29643
29728
|
const candidates = deriveSiblingTestCandidates(filePath, siblingTestContext.globs);
|
|
29644
29729
|
let chosen = null;
|
|
@@ -35269,6 +35354,47 @@ var init_canonical_rules_cache = __esm(() => {
|
|
|
35269
35354
|
canonicalRulesCache = new Map;
|
|
35270
35355
|
});
|
|
35271
35356
|
|
|
35357
|
+
// src/context/engine/providers/static-rules-budget-notice.ts
|
|
35358
|
+
function buildSectionBudgetPressure(allSections, budgetResult) {
|
|
35359
|
+
const { droppedIds, overageTokens } = budgetResult;
|
|
35360
|
+
const droppedCount = droppedIds.length;
|
|
35361
|
+
if (droppedCount === 0 && overageTokens <= 0)
|
|
35362
|
+
return null;
|
|
35363
|
+
const kept = new Set(budgetResult.retainedSections);
|
|
35364
|
+
let droppedTokens = 0;
|
|
35365
|
+
for (const section of allSections) {
|
|
35366
|
+
if (!kept.has(section)) {
|
|
35367
|
+
droppedTokens += section.tokens;
|
|
35368
|
+
}
|
|
35369
|
+
}
|
|
35370
|
+
return { overageTokens, droppedCount, droppedTokens, droppedIds };
|
|
35371
|
+
}
|
|
35372
|
+
function budgetNoticeText(droppedCount, droppedIds) {
|
|
35373
|
+
let detail = "";
|
|
35374
|
+
if (droppedIds && droppedIds.length > 0) {
|
|
35375
|
+
const shown = droppedIds.slice(0, MAX_LISTED_DROPPED_IDS);
|
|
35376
|
+
const remainder = droppedIds.length - shown.length;
|
|
35377
|
+
detail = ` (${shown.join(", ")}${remainder > 0 ? `, +${remainder} more` : ""})`;
|
|
35378
|
+
}
|
|
35379
|
+
return `Note: rule budget exceeded \u2014 ${droppedCount} lower-priority section(s) dropped${detail}. Increase \`context.v2.rules.rulesShare\` / \`context.v2.rules.budgetTokens\`, or set \`context.v2.rules.enforceBudget: false\`, to see the full ruleset.`;
|
|
35380
|
+
}
|
|
35381
|
+
function buildBudgetNoticeChunk(storyId, droppedCount, droppedIds) {
|
|
35382
|
+
const content = `> ${budgetNoticeText(droppedCount, droppedIds)}`;
|
|
35383
|
+
return {
|
|
35384
|
+
id: `static-rules:__budget-notice__:${storyId}`,
|
|
35385
|
+
kind: "static",
|
|
35386
|
+
scope: "project",
|
|
35387
|
+
role: ["all"],
|
|
35388
|
+
content,
|
|
35389
|
+
tokens: estimateTokens2(content),
|
|
35390
|
+
rawScore: 1
|
|
35391
|
+
};
|
|
35392
|
+
}
|
|
35393
|
+
var MAX_LISTED_DROPPED_IDS = 10;
|
|
35394
|
+
var init_static_rules_budget_notice = __esm(() => {
|
|
35395
|
+
init_optimizer();
|
|
35396
|
+
});
|
|
35397
|
+
|
|
35272
35398
|
// src/context/engine/providers/static-rules.ts
|
|
35273
35399
|
import { createHash as createHash10 } from "crypto";
|
|
35274
35400
|
import { join as join20, relative as relative7 } from "path";
|
|
@@ -35349,20 +35475,6 @@ function ruleMatchesPackage(paths, repoRoot, packageDir) {
|
|
|
35349
35475
|
const patterns = paths.map((p) => globToRegex3(normalizePath2(p)));
|
|
35350
35476
|
return patterns.some((pattern) => pattern.test(rel) || pattern.test(`${rel}/`));
|
|
35351
35477
|
}
|
|
35352
|
-
function buildSectionBudgetPressure(allSections, budgetResult) {
|
|
35353
|
-
const { droppedIds, overageTokens } = budgetResult;
|
|
35354
|
-
const droppedCount = droppedIds.length;
|
|
35355
|
-
if (droppedCount === 0 && overageTokens <= 0)
|
|
35356
|
-
return null;
|
|
35357
|
-
const kept = new Set(budgetResult.retainedSections);
|
|
35358
|
-
let droppedTokens = 0;
|
|
35359
|
-
for (const section of allSections) {
|
|
35360
|
-
if (!kept.has(section)) {
|
|
35361
|
-
droppedTokens += section.tokens;
|
|
35362
|
-
}
|
|
35363
|
-
}
|
|
35364
|
-
return { overageTokens, droppedCount, droppedTokens, droppedIds };
|
|
35365
|
-
}
|
|
35366
35478
|
|
|
35367
35479
|
class StaticRulesProvider {
|
|
35368
35480
|
id = "static-rules";
|
|
@@ -35516,8 +35628,9 @@ class StaticRulesProvider {
|
|
|
35516
35628
|
});
|
|
35517
35629
|
}
|
|
35518
35630
|
const emptyPressure = buildSectionBudgetPressure(allSections, budgetResult);
|
|
35631
|
+
const emptyChunks = this.enforceBudget && budgetResult.droppedIds.length > 0 ? [buildBudgetNoticeChunk(request.storyId, budgetResult.droppedIds.length, budgetResult.droppedIds)] : [];
|
|
35519
35632
|
return {
|
|
35520
|
-
chunks:
|
|
35633
|
+
chunks: emptyChunks,
|
|
35521
35634
|
pullTools: [],
|
|
35522
35635
|
...emptyPressure && { budgetPressure: emptyPressure },
|
|
35523
35636
|
scopingReport
|
|
@@ -35549,6 +35662,9 @@ ${section.content}`;
|
|
|
35549
35662
|
totalCanonicalRules: mergedRules.length,
|
|
35550
35663
|
files: effectiveSections.map((s) => s.rulePath ?? "unknown")
|
|
35551
35664
|
});
|
|
35665
|
+
if (this.enforceBudget && budgetResult.droppedIds.length > 0) {
|
|
35666
|
+
chunks.push(buildBudgetNoticeChunk(request.storyId, budgetResult.droppedIds.length, budgetResult.droppedIds));
|
|
35667
|
+
}
|
|
35552
35668
|
const pressure = buildSectionBudgetPressure(allSections, budgetResult);
|
|
35553
35669
|
return {
|
|
35554
35670
|
chunks,
|
|
@@ -35664,6 +35780,7 @@ var init_static_rules = __esm(() => {
|
|
|
35664
35780
|
init_optimizer();
|
|
35665
35781
|
init_canonical_loader();
|
|
35666
35782
|
init_canonical_rules_cache();
|
|
35783
|
+
init_static_rules_budget_notice();
|
|
35667
35784
|
init_canonical_rules_cache();
|
|
35668
35785
|
_staticRulesDeps = {
|
|
35669
35786
|
readFile: async (path4) => Bun.file(path4).text(),
|
|
@@ -36312,7 +36429,17 @@ function toAbsolutePath2(projectDir, pathValue) {
|
|
|
36312
36429
|
}
|
|
36313
36430
|
async function discoverSessionScratchDirsOnDisk(projectDir, featureName, storyId, ttlMs) {
|
|
36314
36431
|
const logger = getLogger();
|
|
36315
|
-
|
|
36432
|
+
let sessionsRoot;
|
|
36433
|
+
try {
|
|
36434
|
+
sessionsRoot = join22(featureDir(projectDir, featureName), "sessions");
|
|
36435
|
+
} catch (err) {
|
|
36436
|
+
logger.debug("context-v2", "discoverSessionScratchDirsOnDisk: invalid featureName \u2014 treating as no sessions", {
|
|
36437
|
+
storyId,
|
|
36438
|
+
featureName,
|
|
36439
|
+
error: errorMessage(err)
|
|
36440
|
+
});
|
|
36441
|
+
return [];
|
|
36442
|
+
}
|
|
36316
36443
|
let entries;
|
|
36317
36444
|
try {
|
|
36318
36445
|
entries = await _stageAssemblerDeps.readdir(sessionsRoot);
|
|
@@ -48822,31 +48949,6 @@ var init_review_iteration_store = __esm(() => {
|
|
|
48822
48949
|
init_findings();
|
|
48823
48950
|
});
|
|
48824
48951
|
|
|
48825
|
-
// src/review/adversarial-audit-event.ts
|
|
48826
|
-
function recordAdversarialAudit(opts) {
|
|
48827
|
-
opts.runtime?.dispatchEvents.emitReviewDecision({
|
|
48828
|
-
kind: "review-decision",
|
|
48829
|
-
reviewer: "adversarial",
|
|
48830
|
-
workdir: opts.workdir,
|
|
48831
|
-
projectDir: opts.projectDir,
|
|
48832
|
-
storyId: opts.storyId,
|
|
48833
|
-
featureName: opts.featureName,
|
|
48834
|
-
timestamp: Date.now(),
|
|
48835
|
-
parsed: opts.parsed,
|
|
48836
|
-
looksLikeFail: opts.looksLikeFail,
|
|
48837
|
-
failOpen: opts.failOpen,
|
|
48838
|
-
passed: opts.passed,
|
|
48839
|
-
passReason: opts.passReason,
|
|
48840
|
-
blockingThreshold: opts.blockingThreshold,
|
|
48841
|
-
result: opts.result,
|
|
48842
|
-
advisoryFindings: opts.advisoryFindings,
|
|
48843
|
-
acks: opts.acks,
|
|
48844
|
-
diffAvailable: opts.diffAvailable,
|
|
48845
|
-
adversarialDropAnalysis: opts.adversarialDropAnalysis,
|
|
48846
|
-
adversarialAcceptAnalysis: opts.adversarialAcceptAnalysis
|
|
48847
|
-
});
|
|
48848
|
-
}
|
|
48849
|
-
|
|
48850
48952
|
// src/review/adversarial-counterfactual-telemetry.ts
|
|
48851
48953
|
function buildCounterfactualTelemetry({
|
|
48852
48954
|
acDropped,
|
|
@@ -48884,172 +48986,30 @@ var init_adversarial_counterfactual_telemetry = __esm(() => {
|
|
|
48884
48986
|
init_ac_structural_counterfactual();
|
|
48885
48987
|
});
|
|
48886
48988
|
|
|
48887
|
-
// src/review/
|
|
48888
|
-
|
|
48889
|
-
|
|
48890
|
-
|
|
48891
|
-
|
|
48892
|
-
|
|
48893
|
-
|
|
48894
|
-
|
|
48895
|
-
|
|
48896
|
-
|
|
48897
|
-
|
|
48898
|
-
|
|
48899
|
-
|
|
48900
|
-
|
|
48901
|
-
|
|
48902
|
-
|
|
48903
|
-
|
|
48904
|
-
|
|
48905
|
-
|
|
48906
|
-
|
|
48907
|
-
|
|
48908
|
-
|
|
48909
|
-
proc.exited,
|
|
48910
|
-
new Response(proc.stdout).text(),
|
|
48911
|
-
new Response(proc.stderr).text()
|
|
48912
|
-
]);
|
|
48913
|
-
if (exitCode !== 0) {
|
|
48914
|
-
getSafeLogger()?.warn("diff-utils", "git diff failed \u2014 skipping review diff", { storyGitRef, stderr });
|
|
48915
|
-
return null;
|
|
48916
|
-
}
|
|
48917
|
-
return stdout;
|
|
48918
|
-
}
|
|
48919
|
-
async function collectDiffStat(workdir, storyGitRef, options) {
|
|
48920
|
-
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
48921
|
-
const merged = [...new Set([...naxIgnoreExcludes, ...ALWAYS_EXCLUDED])];
|
|
48922
|
-
const proc = _diffUtilsDeps.spawn({
|
|
48923
|
-
cmd: ["git", "diff", "--stat", `${storyGitRef}..HEAD`, "--", ".", ...merged],
|
|
48924
|
-
cwd: workdir,
|
|
48925
|
-
stdout: "pipe",
|
|
48926
|
-
stderr: "pipe"
|
|
48927
|
-
});
|
|
48928
|
-
const [exitCode, stdout] = await Promise.all([
|
|
48929
|
-
proc.exited,
|
|
48930
|
-
new Response(proc.stdout).text(),
|
|
48931
|
-
new Response(proc.stderr).text()
|
|
48932
|
-
]);
|
|
48933
|
-
return exitCode === 0 ? stdout.trim() : "";
|
|
48934
|
-
}
|
|
48935
|
-
function truncateDiff(diff, stat4) {
|
|
48936
|
-
if (diff.length <= DIFF_CAP_BYTES) {
|
|
48937
|
-
return diff;
|
|
48938
|
-
}
|
|
48939
|
-
const truncated = diff.slice(0, DIFF_CAP_BYTES);
|
|
48940
|
-
const visibleFiles = (truncated.match(/^diff --git/gm) ?? []).length;
|
|
48941
|
-
const totalFiles = (diff.match(/^diff --git/gm) ?? []).length;
|
|
48942
|
-
const statPreamble = stat4 ? `## File Summary (all changed files)
|
|
48943
|
-
${stat4}
|
|
48944
|
-
|
|
48945
|
-
## Diff (truncated \u2014 ${visibleFiles}/${totalFiles} files shown)
|
|
48946
|
-
` : "";
|
|
48947
|
-
return `${statPreamble}${truncated}
|
|
48948
|
-
... (truncated at ${DIFF_CAP_BYTES} bytes, showing ${visibleFiles}/${totalFiles} files)`;
|
|
48949
|
-
}
|
|
48950
|
-
async function resolveEffectiveRef(workdir, storyGitRef, storyId) {
|
|
48951
|
-
const logger = getSafeLogger();
|
|
48952
|
-
if (storyGitRef && await _diffUtilsDeps.isGitRefValid(workdir, storyGitRef)) {
|
|
48953
|
-
return storyGitRef;
|
|
48954
|
-
}
|
|
48955
|
-
const fallback = await _diffUtilsDeps.getMergeBase(workdir);
|
|
48956
|
-
if (fallback) {
|
|
48957
|
-
logger?.info("review", "storyGitRef missing or invalid \u2014 using merge-base fallback", {
|
|
48958
|
-
storyId,
|
|
48959
|
-
storyGitRef,
|
|
48960
|
-
fallback
|
|
48961
|
-
});
|
|
48962
|
-
return fallback;
|
|
48963
|
-
}
|
|
48964
|
-
return;
|
|
48965
|
-
}
|
|
48966
|
-
function buildSuffixStrippers(testFilePatterns) {
|
|
48967
|
-
if (!testFilePatterns || testFilePatterns.length === 0)
|
|
48968
|
-
return DEFAULT_SUFFIX_STRIPPERS;
|
|
48969
|
-
const regexes = [];
|
|
48970
|
-
for (const pattern of testFilePatterns) {
|
|
48971
|
-
const lastStar = pattern.lastIndexOf("*");
|
|
48972
|
-
if (lastStar === -1)
|
|
48973
|
-
continue;
|
|
48974
|
-
const suffix = pattern.slice(lastStar + 1);
|
|
48975
|
-
if (suffix.length > 0) {
|
|
48976
|
-
regexes.push(new RegExp(`${suffix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`));
|
|
48977
|
-
}
|
|
48978
|
-
}
|
|
48979
|
-
return regexes.length > 0 ? regexes : DEFAULT_SUFFIX_STRIPPERS;
|
|
48980
|
-
}
|
|
48981
|
-
function stripTestSuffix(base, strippers) {
|
|
48982
|
-
for (const re of strippers) {
|
|
48983
|
-
const stripped = base.replace(re, "");
|
|
48984
|
-
if (stripped !== base)
|
|
48985
|
-
return stripped;
|
|
48986
|
-
}
|
|
48987
|
-
return base;
|
|
48988
|
-
}
|
|
48989
|
-
async function computeTestInventory(workdir, storyGitRef, testFilePatterns, options) {
|
|
48990
|
-
const proc = _diffUtilsDeps.spawn({
|
|
48991
|
-
cmd: ["git", "diff", "--name-only", "--diff-filter=A", `${storyGitRef}..HEAD`],
|
|
48992
|
-
cwd: workdir,
|
|
48993
|
-
stdout: "pipe",
|
|
48994
|
-
stderr: "pipe"
|
|
48995
|
-
});
|
|
48996
|
-
const [exitCode, stdout] = await Promise.all([
|
|
48997
|
-
proc.exited,
|
|
48998
|
-
new Response(proc.stdout).text(),
|
|
48999
|
-
new Response(proc.stderr).text()
|
|
49000
|
-
]);
|
|
49001
|
-
if (exitCode !== 0) {
|
|
49002
|
-
return { addedTestFiles: [], newSourceFilesWithoutTests: [] };
|
|
49003
|
-
}
|
|
49004
|
-
const addedFiles = stdout.trim().split(`
|
|
49005
|
-
`).filter(Boolean);
|
|
49006
|
-
const ignoreMatchers = options?.naxIgnoreIndex?.getMatchers(options.packageDir) ?? await resolveNaxIgnorePatterns(workdir, options?.packageDir);
|
|
49007
|
-
const visibleAddedFiles = filterNaxInternalPaths(addedFiles, ignoreMatchers);
|
|
49008
|
-
const addedTestFiles = visibleAddedFiles.filter((f) => isTestFile(f, testFilePatterns));
|
|
49009
|
-
const addedSourceFiles = visibleAddedFiles.filter((f) => !isTestFile(f, testFilePatterns));
|
|
49010
|
-
const suffixStrippers = buildSuffixStrippers(testFilePatterns);
|
|
49011
|
-
const testFileBasenames = new Set(addedTestFiles.map((f) => {
|
|
49012
|
-
const base = f.split("/").at(-1) ?? f;
|
|
49013
|
-
return stripTestSuffix(base, suffixStrippers);
|
|
49014
|
-
}));
|
|
49015
|
-
const newSourceFilesWithoutTests = addedSourceFiles.filter((f) => {
|
|
49016
|
-
const base = (f.split("/").at(-1) ?? f).replace(/\.(ts|js|tsx|jsx|go)$/, "");
|
|
49017
|
-
return !testFileBasenames.has(base);
|
|
49018
|
-
});
|
|
49019
|
-
return { addedTestFiles, newSourceFilesWithoutTests };
|
|
49020
|
-
}
|
|
49021
|
-
async function collectDiffFileList(workdir, storyGitRef, options) {
|
|
49022
|
-
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
49023
|
-
const merged = [...new Set([...naxIgnoreExcludes, ...ALWAYS_EXCLUDED])];
|
|
49024
|
-
const proc = _diffUtilsDeps.spawn({
|
|
49025
|
-
cmd: ["git", "diff", "--name-only", `${storyGitRef}..HEAD`, "--", ".", ...merged],
|
|
49026
|
-
cwd: workdir,
|
|
49027
|
-
stdout: "pipe",
|
|
49028
|
-
stderr: "pipe"
|
|
48989
|
+
// src/review/adversarial-audit-event.ts
|
|
48990
|
+
function recordAdversarialAudit(opts) {
|
|
48991
|
+
opts.runtime?.dispatchEvents.emitReviewDecision({
|
|
48992
|
+
kind: "review-decision",
|
|
48993
|
+
reviewer: "adversarial",
|
|
48994
|
+
workdir: opts.workdir,
|
|
48995
|
+
projectDir: opts.projectDir,
|
|
48996
|
+
storyId: opts.storyId,
|
|
48997
|
+
featureName: opts.featureName,
|
|
48998
|
+
timestamp: Date.now(),
|
|
48999
|
+
parsed: opts.parsed,
|
|
49000
|
+
looksLikeFail: opts.looksLikeFail,
|
|
49001
|
+
failOpen: opts.failOpen,
|
|
49002
|
+
passed: opts.passed,
|
|
49003
|
+
passReason: opts.passReason,
|
|
49004
|
+
blockingThreshold: opts.blockingThreshold,
|
|
49005
|
+
result: opts.result,
|
|
49006
|
+
advisoryFindings: opts.advisoryFindings,
|
|
49007
|
+
acks: opts.acks,
|
|
49008
|
+
diffAvailable: opts.diffAvailable,
|
|
49009
|
+
adversarialDropAnalysis: opts.adversarialDropAnalysis,
|
|
49010
|
+
adversarialAcceptAnalysis: opts.adversarialAcceptAnalysis
|
|
49029
49011
|
});
|
|
49030
|
-
const [exitCode, stdout] = await Promise.all([
|
|
49031
|
-
proc.exited,
|
|
49032
|
-
new Response(proc.stdout).text(),
|
|
49033
|
-
new Response(proc.stderr).text()
|
|
49034
|
-
]);
|
|
49035
|
-
if (exitCode !== 0)
|
|
49036
|
-
return;
|
|
49037
|
-
return stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
49038
49012
|
}
|
|
49039
|
-
var DIFF_CAP_BYTES = 51200, ALWAYS_EXCLUDED, _diffUtilsDeps, DEFAULT_SUFFIX_STRIPPERS;
|
|
49040
|
-
var init_diff_utils = __esm(() => {
|
|
49041
|
-
init_logger2();
|
|
49042
|
-
init_test_runners();
|
|
49043
|
-
init_git();
|
|
49044
|
-
init_path_filters();
|
|
49045
|
-
ALWAYS_EXCLUDED = [":!.nax/", ":!.nax-pids"];
|
|
49046
|
-
_diffUtilsDeps = {
|
|
49047
|
-
spawn: spawn3,
|
|
49048
|
-
isGitRefValid,
|
|
49049
|
-
getMergeBase
|
|
49050
|
-
};
|
|
49051
|
-
DEFAULT_SUFFIX_STRIPPERS = [/\.(test|spec)\.(ts|js|tsx|jsx)$/, /_test\.go$/];
|
|
49052
|
-
});
|
|
49053
49013
|
|
|
49054
49014
|
// src/review/finding-projection.ts
|
|
49055
49015
|
function narrowSeverity(raw) {
|
|
@@ -49134,6 +49094,505 @@ var init_finding_projection = __esm(() => {
|
|
|
49134
49094
|
};
|
|
49135
49095
|
});
|
|
49136
49096
|
|
|
49097
|
+
// src/review/adversarial-outcomes.ts
|
|
49098
|
+
function skipResult(output, startTime) {
|
|
49099
|
+
return {
|
|
49100
|
+
check: "adversarial",
|
|
49101
|
+
success: true,
|
|
49102
|
+
command: "",
|
|
49103
|
+
exitCode: 0,
|
|
49104
|
+
output,
|
|
49105
|
+
durationMs: Date.now() - startTime
|
|
49106
|
+
};
|
|
49107
|
+
}
|
|
49108
|
+
function buildFeatureCtxBlock(contextBundle, featureContextMarkdown) {
|
|
49109
|
+
if (contextBundle) {
|
|
49110
|
+
const md = contextBundle.pushMarkdown.trim();
|
|
49111
|
+
return md ? `${md}
|
|
49112
|
+
|
|
49113
|
+
---
|
|
49114
|
+
|
|
49115
|
+
` : "";
|
|
49116
|
+
}
|
|
49117
|
+
if (featureContextMarkdown) {
|
|
49118
|
+
const filtered = filterContextByRole(featureContextMarkdown, "reviewer-adversarial");
|
|
49119
|
+
if (filtered.trim())
|
|
49120
|
+
return `${filtered}
|
|
49121
|
+
|
|
49122
|
+
---
|
|
49123
|
+
|
|
49124
|
+
`;
|
|
49125
|
+
}
|
|
49126
|
+
return "";
|
|
49127
|
+
}
|
|
49128
|
+
function catchDispatchFailure(err, ctx) {
|
|
49129
|
+
const { runtime, workdir, projectDir, storyId, featureName, blockingThreshold, startTime, logger } = ctx;
|
|
49130
|
+
logger?.warn("adversarial", "LLM call failed \u2014 fail-open", { storyId, cause: String(err) });
|
|
49131
|
+
recordAdversarialAudit({
|
|
49132
|
+
runtime,
|
|
49133
|
+
workdir,
|
|
49134
|
+
projectDir,
|
|
49135
|
+
storyId,
|
|
49136
|
+
featureName,
|
|
49137
|
+
parsed: false,
|
|
49138
|
+
looksLikeFail: false,
|
|
49139
|
+
failOpen: true,
|
|
49140
|
+
passed: true,
|
|
49141
|
+
blockingThreshold,
|
|
49142
|
+
result: null
|
|
49143
|
+
});
|
|
49144
|
+
return {
|
|
49145
|
+
check: "adversarial",
|
|
49146
|
+
success: true,
|
|
49147
|
+
failOpen: true,
|
|
49148
|
+
command: "",
|
|
49149
|
+
exitCode: 0,
|
|
49150
|
+
output: `skipped: LLM call failed \u2014 ${String(err)}`,
|
|
49151
|
+
durationMs: Date.now() - startTime
|
|
49152
|
+
};
|
|
49153
|
+
}
|
|
49154
|
+
function handleRetryExhaustedFailOpen(ctx) {
|
|
49155
|
+
const { runtime, workdir, projectDir, storyId, featureName, blockingThreshold, startTime, logger } = ctx;
|
|
49156
|
+
logger?.warn("adversarial", "Retry exhausted \u2014 fail-open", { storyId });
|
|
49157
|
+
recordAdversarialAudit({
|
|
49158
|
+
runtime,
|
|
49159
|
+
workdir,
|
|
49160
|
+
projectDir,
|
|
49161
|
+
storyId,
|
|
49162
|
+
featureName,
|
|
49163
|
+
parsed: false,
|
|
49164
|
+
looksLikeFail: false,
|
|
49165
|
+
failOpen: true,
|
|
49166
|
+
passed: true,
|
|
49167
|
+
blockingThreshold,
|
|
49168
|
+
result: null
|
|
49169
|
+
});
|
|
49170
|
+
return {
|
|
49171
|
+
check: "adversarial",
|
|
49172
|
+
success: true,
|
|
49173
|
+
failOpen: true,
|
|
49174
|
+
command: "",
|
|
49175
|
+
exitCode: 0,
|
|
49176
|
+
output: "adversarial review: could not parse LLM response (fail-open)",
|
|
49177
|
+
durationMs: Date.now() - startTime
|
|
49178
|
+
};
|
|
49179
|
+
}
|
|
49180
|
+
function handleTruncatedLooksLikeFail(ctx) {
|
|
49181
|
+
const { runtime, workdir, projectDir, storyId, featureName, blockingThreshold, startTime, logger } = ctx;
|
|
49182
|
+
logger?.warn("adversarial", "LLM returned truncated JSON with passed:false \u2014 treating as failure", { storyId });
|
|
49183
|
+
recordAdversarialAudit({
|
|
49184
|
+
runtime,
|
|
49185
|
+
workdir,
|
|
49186
|
+
projectDir,
|
|
49187
|
+
storyId,
|
|
49188
|
+
featureName,
|
|
49189
|
+
parsed: false,
|
|
49190
|
+
looksLikeFail: true,
|
|
49191
|
+
failOpen: false,
|
|
49192
|
+
passed: false,
|
|
49193
|
+
blockingThreshold,
|
|
49194
|
+
result: null
|
|
49195
|
+
});
|
|
49196
|
+
return {
|
|
49197
|
+
check: "adversarial",
|
|
49198
|
+
success: false,
|
|
49199
|
+
command: "",
|
|
49200
|
+
exitCode: 1,
|
|
49201
|
+
output: "adversarial review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
49202
|
+
durationMs: Date.now() - startTime
|
|
49203
|
+
};
|
|
49204
|
+
}
|
|
49205
|
+
function classifyAdversarialFindings(opResult, blockingThreshold, priorAdversarialIterations, adversarialConfig, resolvedTestPatterns) {
|
|
49206
|
+
const threshold = blockingThreshold ?? "error";
|
|
49207
|
+
const allFindings = opResult.findings;
|
|
49208
|
+
const patterns = resolvedTestPatterns?.regex ?? [];
|
|
49209
|
+
const testFileMatch = (file3) => patterns.some((re) => re.test(file3));
|
|
49210
|
+
const recurrenceCfg = adversarialConfig.recurrenceDemotion ?? { enabled: true, maxBlockingRounds: 2 };
|
|
49211
|
+
const {
|
|
49212
|
+
blocking: blockingFindings,
|
|
49213
|
+
advisory: advisoryOnly,
|
|
49214
|
+
demoted
|
|
49215
|
+
} = classifyRecurrence(allFindings, priorAdversarialIterations ?? [], recurrenceCfg, testFileMatch, threshold);
|
|
49216
|
+
const advisoryFindings = [...advisoryOnly, ...demoted];
|
|
49217
|
+
const advisoryReviewFindings = [
|
|
49218
|
+
...llmFindingsToReviewFindings(advisoryOnly, { source: "adversarial-review", isTestFile: testFileMatch }),
|
|
49219
|
+
...tagCoverageGap(llmFindingsToReviewFindings(demoted, { source: "adversarial-review", isTestFile: testFileMatch }))
|
|
49220
|
+
];
|
|
49221
|
+
const advisoryFindingsAsFindings = [
|
|
49222
|
+
...toAdversarialReviewFindings(advisoryOnly, { isTestFile: testFileMatch }),
|
|
49223
|
+
...tagCoverageGap(toAdversarialReviewFindings(demoted, { isTestFile: testFileMatch }))
|
|
49224
|
+
];
|
|
49225
|
+
return {
|
|
49226
|
+
threshold,
|
|
49227
|
+
allFindings,
|
|
49228
|
+
testFileMatch,
|
|
49229
|
+
blockingFindings,
|
|
49230
|
+
advisoryFindings,
|
|
49231
|
+
advisoryReviewFindings,
|
|
49232
|
+
advisoryFindingsAsFindings,
|
|
49233
|
+
acDropped: opResult.acDropped ?? [],
|
|
49234
|
+
acks: opResult.acks
|
|
49235
|
+
};
|
|
49236
|
+
}
|
|
49237
|
+
async function resolveDiffFileSet(diff, workdir, projectDir, effectiveRef, naxIgnoreIndex, collectDiffFileListFn) {
|
|
49238
|
+
if (diff && diff.length > 0) {
|
|
49239
|
+
return { diffFiles: extractDiffFiles(diff), diffAvailable: true };
|
|
49240
|
+
}
|
|
49241
|
+
const repoRoot = projectDir ?? workdir;
|
|
49242
|
+
const packageDir = workdir !== repoRoot ? workdir : undefined;
|
|
49243
|
+
const list = await collectDiffFileListFn(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
|
|
49244
|
+
if (list === undefined)
|
|
49245
|
+
return { diffFiles: new Set, diffAvailable: false };
|
|
49246
|
+
return { diffFiles: new Set(list), diffAvailable: true };
|
|
49247
|
+
}
|
|
49248
|
+
function buildBlockingFailureResult(ctx, classification, telemetry, diffAvailable, durationMs) {
|
|
49249
|
+
const { runtime, workdir, projectDir, storyId, featureName, logger } = ctx;
|
|
49250
|
+
const { threshold, allFindings, testFileMatch, blockingFindings, advisoryFindings, advisoryReviewFindings, acks } = classification;
|
|
49251
|
+
logger?.warn("review", `Adversarial review failed: ${blockingFindings.length} blocking findings`, {
|
|
49252
|
+
storyId,
|
|
49253
|
+
durationMs,
|
|
49254
|
+
findings: blockingFindings.map((f) => ({
|
|
49255
|
+
severity: f.severity,
|
|
49256
|
+
category: f.category,
|
|
49257
|
+
file: f.file,
|
|
49258
|
+
line: f.line,
|
|
49259
|
+
issue: f.issue
|
|
49260
|
+
}))
|
|
49261
|
+
});
|
|
49262
|
+
recordAdversarialAudit({
|
|
49263
|
+
runtime,
|
|
49264
|
+
workdir,
|
|
49265
|
+
projectDir,
|
|
49266
|
+
storyId,
|
|
49267
|
+
featureName,
|
|
49268
|
+
parsed: true,
|
|
49269
|
+
failOpen: false,
|
|
49270
|
+
passed: false,
|
|
49271
|
+
blockingThreshold: threshold,
|
|
49272
|
+
result: {
|
|
49273
|
+
passed: false,
|
|
49274
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
49275
|
+
},
|
|
49276
|
+
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
49277
|
+
diffAvailable,
|
|
49278
|
+
adversarialDropAnalysis: telemetry.adversarialDropAnalysis,
|
|
49279
|
+
adversarialAcceptAnalysis: telemetry.adversarialAcceptAnalysis,
|
|
49280
|
+
acks
|
|
49281
|
+
});
|
|
49282
|
+
return {
|
|
49283
|
+
check: "adversarial",
|
|
49284
|
+
success: false,
|
|
49285
|
+
command: "",
|
|
49286
|
+
exitCode: 1,
|
|
49287
|
+
output: `Adversarial review failed:
|
|
49288
|
+
|
|
49289
|
+
${formatFindings(blockingFindings)}`,
|
|
49290
|
+
durationMs,
|
|
49291
|
+
findings: toAdversarialReviewFindings(blockingFindings, { isTestFile: testFileMatch }),
|
|
49292
|
+
advisoryFindings: advisoryFindings.length > 0 ? classification.advisoryFindingsAsFindings : undefined,
|
|
49293
|
+
cost: 0
|
|
49294
|
+
};
|
|
49295
|
+
}
|
|
49296
|
+
function buildHallucinatedAcQuoteResult(ctx, classification, telemetry, diffAvailable, durationMs) {
|
|
49297
|
+
const { runtime, workdir, projectDir, storyId, featureName, logger } = ctx;
|
|
49298
|
+
const { threshold, testFileMatch, acDropped, acks, advisoryFindingsAsFindings } = classification;
|
|
49299
|
+
const demotedFindings = toAdversarialReviewFindings(acDropped.map((d) => ({ ...d.finding, severity: "warning", acQuote: undefined, acIndex: undefined })), { isTestFile: testFileMatch });
|
|
49300
|
+
const allAdvisory = [...advisoryFindingsAsFindings, ...demotedFindings];
|
|
49301
|
+
logger?.warn("review", "Adversarial review passed: all blocking findings discarded as hallucinated AC quotes", {
|
|
49302
|
+
storyId,
|
|
49303
|
+
durationMs,
|
|
49304
|
+
droppedCount: acDropped.length,
|
|
49305
|
+
drops: acDropped.map((d) => ({ file: d.finding.file, issue: d.finding.issue }))
|
|
49306
|
+
});
|
|
49307
|
+
recordAdversarialAudit({
|
|
49308
|
+
runtime,
|
|
49309
|
+
workdir,
|
|
49310
|
+
projectDir,
|
|
49311
|
+
storyId,
|
|
49312
|
+
featureName,
|
|
49313
|
+
parsed: true,
|
|
49314
|
+
acks,
|
|
49315
|
+
failOpen: false,
|
|
49316
|
+
passed: true,
|
|
49317
|
+
passReason: "ac_quote_not_substring_demoted",
|
|
49318
|
+
blockingThreshold: threshold,
|
|
49319
|
+
result: { passed: true, findings: [] },
|
|
49320
|
+
advisoryFindings: allAdvisory.length > 0 ? allAdvisory : undefined,
|
|
49321
|
+
diffAvailable,
|
|
49322
|
+
adversarialDropAnalysis: telemetry.adversarialDropAnalysis,
|
|
49323
|
+
adversarialAcceptAnalysis: []
|
|
49324
|
+
});
|
|
49325
|
+
return {
|
|
49326
|
+
check: "adversarial",
|
|
49327
|
+
success: true,
|
|
49328
|
+
passReason: "ac_quote_not_substring_demoted",
|
|
49329
|
+
command: "",
|
|
49330
|
+
exitCode: 0,
|
|
49331
|
+
output: `Adversarial review passed: ${acDropped.length} blocking finding(s) demoted to advisory \u2014 all cited AC quotes were fabricated and could not be validated.`,
|
|
49332
|
+
durationMs,
|
|
49333
|
+
advisoryFindings: allAdvisory.length > 0 ? allAdvisory : undefined,
|
|
49334
|
+
cost: 0
|
|
49335
|
+
};
|
|
49336
|
+
}
|
|
49337
|
+
function buildUngroundedFailClosedResult(ctx, classification, telemetry, diffAvailable, durationMs) {
|
|
49338
|
+
const { runtime, workdir, projectDir, storyId, featureName, logger } = ctx;
|
|
49339
|
+
const { threshold, acDropped, acks, advisoryFindings, advisoryReviewFindings, advisoryFindingsAsFindings } = classification;
|
|
49340
|
+
logger?.warn("review", "Adversarial review fail-closed: blocking findings dropped as ungrounded", {
|
|
49341
|
+
storyId,
|
|
49342
|
+
durationMs,
|
|
49343
|
+
droppedCount: acDropped.length,
|
|
49344
|
+
dropCodes: acDropped.map((d) => d.code)
|
|
49345
|
+
});
|
|
49346
|
+
const dropSummary = acDropped.map((d, i) => `${i + 1}. [${d.code}] ${d.finding.file ?? "<unknown>"}: ${d.finding.issue}`).join(`
|
|
49347
|
+
`);
|
|
49348
|
+
recordAdversarialAudit({
|
|
49349
|
+
runtime,
|
|
49350
|
+
workdir,
|
|
49351
|
+
projectDir,
|
|
49352
|
+
storyId,
|
|
49353
|
+
featureName,
|
|
49354
|
+
parsed: true,
|
|
49355
|
+
acks,
|
|
49356
|
+
failOpen: false,
|
|
49357
|
+
passed: false,
|
|
49358
|
+
blockingThreshold: threshold,
|
|
49359
|
+
result: { passed: false, findings: [] },
|
|
49360
|
+
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
49361
|
+
diffAvailable,
|
|
49362
|
+
adversarialDropAnalysis: telemetry.adversarialDropAnalysis,
|
|
49363
|
+
adversarialAcceptAnalysis: []
|
|
49364
|
+
});
|
|
49365
|
+
return {
|
|
49366
|
+
check: "adversarial",
|
|
49367
|
+
success: false,
|
|
49368
|
+
command: "",
|
|
49369
|
+
exitCode: 1,
|
|
49370
|
+
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. Drops:
|
|
49371
|
+
|
|
49372
|
+
${dropSummary}`,
|
|
49373
|
+
durationMs,
|
|
49374
|
+
advisoryFindings: advisoryFindings.length > 0 ? advisoryFindingsAsFindings : undefined,
|
|
49375
|
+
cost: 0
|
|
49376
|
+
};
|
|
49377
|
+
}
|
|
49378
|
+
function buildPassedResult(ctx, classification, telemetry, diffAvailable, durationMs) {
|
|
49379
|
+
const { runtime, workdir, projectDir, storyId, featureName, logger } = ctx;
|
|
49380
|
+
const {
|
|
49381
|
+
threshold,
|
|
49382
|
+
allFindings,
|
|
49383
|
+
testFileMatch,
|
|
49384
|
+
advisoryFindings,
|
|
49385
|
+
advisoryReviewFindings,
|
|
49386
|
+
advisoryFindingsAsFindings,
|
|
49387
|
+
acks
|
|
49388
|
+
} = classification;
|
|
49389
|
+
logger?.info("review", "Adversarial review passed", { storyId, durationMs });
|
|
49390
|
+
recordAdversarialAudit({
|
|
49391
|
+
runtime,
|
|
49392
|
+
workdir,
|
|
49393
|
+
projectDir,
|
|
49394
|
+
storyId,
|
|
49395
|
+
featureName,
|
|
49396
|
+
parsed: true,
|
|
49397
|
+
acks,
|
|
49398
|
+
failOpen: false,
|
|
49399
|
+
passed: true,
|
|
49400
|
+
blockingThreshold: threshold,
|
|
49401
|
+
result: {
|
|
49402
|
+
passed: true,
|
|
49403
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
49404
|
+
},
|
|
49405
|
+
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
49406
|
+
diffAvailable,
|
|
49407
|
+
adversarialDropAnalysis: telemetry.adversarialDropAnalysis,
|
|
49408
|
+
adversarialAcceptAnalysis: []
|
|
49409
|
+
});
|
|
49410
|
+
return {
|
|
49411
|
+
check: "adversarial",
|
|
49412
|
+
success: true,
|
|
49413
|
+
command: "",
|
|
49414
|
+
exitCode: 0,
|
|
49415
|
+
output: allFindings.length === 0 ? "Adversarial review passed" : "Adversarial review passed (all findings were advisory \u2014 below blocking threshold)",
|
|
49416
|
+
durationMs,
|
|
49417
|
+
advisoryFindings: advisoryFindings.length > 0 ? advisoryFindingsAsFindings : undefined,
|
|
49418
|
+
cost: 0
|
|
49419
|
+
};
|
|
49420
|
+
}
|
|
49421
|
+
var init_adversarial_outcomes = __esm(() => {
|
|
49422
|
+
init_context();
|
|
49423
|
+
init_diff_files();
|
|
49424
|
+
init_adversarial_helpers();
|
|
49425
|
+
init_finding_projection();
|
|
49426
|
+
init_recurrence_demotion();
|
|
49427
|
+
});
|
|
49428
|
+
|
|
49429
|
+
// src/review/diff-utils.ts
|
|
49430
|
+
var {spawn: spawn3 } = globalThis.Bun;
|
|
49431
|
+
async function resolveNaxIgnorePathspecExcludes(workdir, options) {
|
|
49432
|
+
if (options?.naxIgnoreIndex)
|
|
49433
|
+
return options.naxIgnoreIndex.toPathspecExcludes(options.packageDir);
|
|
49434
|
+
const matchers = await resolveNaxIgnorePatterns(workdir, options?.packageDir);
|
|
49435
|
+
const pathspec = new Set;
|
|
49436
|
+
for (const matcher of matchers)
|
|
49437
|
+
pathspec.add(`:!${matcher.pattern}`);
|
|
49438
|
+
return [...pathspec];
|
|
49439
|
+
}
|
|
49440
|
+
async function collectDiff(workdir, storyGitRef, excludePatterns, options) {
|
|
49441
|
+
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
49442
|
+
const merged = [...new Set([...excludePatterns, ...naxIgnoreExcludes, ...ALWAYS_EXCLUDED])];
|
|
49443
|
+
const cmd = ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`, "--", ".", ...merged];
|
|
49444
|
+
const proc = _diffUtilsDeps.spawn({
|
|
49445
|
+
cmd,
|
|
49446
|
+
cwd: workdir,
|
|
49447
|
+
stdout: "pipe",
|
|
49448
|
+
stderr: "pipe"
|
|
49449
|
+
});
|
|
49450
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
49451
|
+
proc.exited,
|
|
49452
|
+
new Response(proc.stdout).text(),
|
|
49453
|
+
new Response(proc.stderr).text()
|
|
49454
|
+
]);
|
|
49455
|
+
if (exitCode !== 0) {
|
|
49456
|
+
getSafeLogger()?.warn("diff-utils", "git diff failed \u2014 skipping review diff", { storyGitRef, stderr });
|
|
49457
|
+
return null;
|
|
49458
|
+
}
|
|
49459
|
+
return stdout;
|
|
49460
|
+
}
|
|
49461
|
+
async function collectDiffStat(workdir, storyGitRef, options) {
|
|
49462
|
+
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
49463
|
+
const merged = [...new Set([...naxIgnoreExcludes, ...ALWAYS_EXCLUDED])];
|
|
49464
|
+
const proc = _diffUtilsDeps.spawn({
|
|
49465
|
+
cmd: ["git", "diff", "--stat", `${storyGitRef}..HEAD`, "--", ".", ...merged],
|
|
49466
|
+
cwd: workdir,
|
|
49467
|
+
stdout: "pipe",
|
|
49468
|
+
stderr: "pipe"
|
|
49469
|
+
});
|
|
49470
|
+
const [exitCode, stdout] = await Promise.all([
|
|
49471
|
+
proc.exited,
|
|
49472
|
+
new Response(proc.stdout).text(),
|
|
49473
|
+
new Response(proc.stderr).text()
|
|
49474
|
+
]);
|
|
49475
|
+
return exitCode === 0 ? stdout.trim() : "";
|
|
49476
|
+
}
|
|
49477
|
+
function truncateDiff(diff, stat4) {
|
|
49478
|
+
if (diff.length <= DIFF_CAP_BYTES) {
|
|
49479
|
+
return diff;
|
|
49480
|
+
}
|
|
49481
|
+
const truncated = diff.slice(0, DIFF_CAP_BYTES);
|
|
49482
|
+
const visibleFiles = (truncated.match(/^diff --git/gm) ?? []).length;
|
|
49483
|
+
const totalFiles = (diff.match(/^diff --git/gm) ?? []).length;
|
|
49484
|
+
const statPreamble = stat4 ? `## File Summary (all changed files)
|
|
49485
|
+
${stat4}
|
|
49486
|
+
|
|
49487
|
+
## Diff (truncated \u2014 ${visibleFiles}/${totalFiles} files shown)
|
|
49488
|
+
` : "";
|
|
49489
|
+
return `${statPreamble}${truncated}
|
|
49490
|
+
... (truncated at ${DIFF_CAP_BYTES} bytes, showing ${visibleFiles}/${totalFiles} files)`;
|
|
49491
|
+
}
|
|
49492
|
+
async function resolveEffectiveRef(workdir, storyGitRef, storyId) {
|
|
49493
|
+
const logger = getSafeLogger();
|
|
49494
|
+
if (storyGitRef && await _diffUtilsDeps.isGitRefValid(workdir, storyGitRef)) {
|
|
49495
|
+
return storyGitRef;
|
|
49496
|
+
}
|
|
49497
|
+
const fallback = await _diffUtilsDeps.getMergeBase(workdir);
|
|
49498
|
+
if (fallback) {
|
|
49499
|
+
logger?.info("review", "storyGitRef missing or invalid \u2014 using merge-base fallback", {
|
|
49500
|
+
storyId,
|
|
49501
|
+
storyGitRef,
|
|
49502
|
+
fallback
|
|
49503
|
+
});
|
|
49504
|
+
return fallback;
|
|
49505
|
+
}
|
|
49506
|
+
return;
|
|
49507
|
+
}
|
|
49508
|
+
function buildSuffixStrippers(testFilePatterns) {
|
|
49509
|
+
if (!testFilePatterns || testFilePatterns.length === 0)
|
|
49510
|
+
return DEFAULT_SUFFIX_STRIPPERS;
|
|
49511
|
+
const regexes = [];
|
|
49512
|
+
for (const pattern of testFilePatterns) {
|
|
49513
|
+
const lastStar = pattern.lastIndexOf("*");
|
|
49514
|
+
if (lastStar === -1)
|
|
49515
|
+
continue;
|
|
49516
|
+
const suffix = pattern.slice(lastStar + 1);
|
|
49517
|
+
if (suffix.length > 0) {
|
|
49518
|
+
regexes.push(new RegExp(`${suffix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`));
|
|
49519
|
+
}
|
|
49520
|
+
}
|
|
49521
|
+
return regexes.length > 0 ? regexes : DEFAULT_SUFFIX_STRIPPERS;
|
|
49522
|
+
}
|
|
49523
|
+
function stripTestSuffix(base, strippers) {
|
|
49524
|
+
for (const re of strippers) {
|
|
49525
|
+
const stripped = base.replace(re, "");
|
|
49526
|
+
if (stripped !== base)
|
|
49527
|
+
return stripped;
|
|
49528
|
+
}
|
|
49529
|
+
return base;
|
|
49530
|
+
}
|
|
49531
|
+
async function computeTestInventory(workdir, storyGitRef, testFilePatterns, options) {
|
|
49532
|
+
const proc = _diffUtilsDeps.spawn({
|
|
49533
|
+
cmd: ["git", "diff", "--name-only", "--diff-filter=A", `${storyGitRef}..HEAD`],
|
|
49534
|
+
cwd: workdir,
|
|
49535
|
+
stdout: "pipe",
|
|
49536
|
+
stderr: "pipe"
|
|
49537
|
+
});
|
|
49538
|
+
const [exitCode, stdout] = await Promise.all([
|
|
49539
|
+
proc.exited,
|
|
49540
|
+
new Response(proc.stdout).text(),
|
|
49541
|
+
new Response(proc.stderr).text()
|
|
49542
|
+
]);
|
|
49543
|
+
if (exitCode !== 0) {
|
|
49544
|
+
return { addedTestFiles: [], newSourceFilesWithoutTests: [] };
|
|
49545
|
+
}
|
|
49546
|
+
const addedFiles = stdout.trim().split(`
|
|
49547
|
+
`).filter(Boolean);
|
|
49548
|
+
const ignoreMatchers = options?.naxIgnoreIndex?.getMatchers(options.packageDir) ?? await resolveNaxIgnorePatterns(workdir, options?.packageDir);
|
|
49549
|
+
const visibleAddedFiles = filterNaxInternalPaths(addedFiles, ignoreMatchers);
|
|
49550
|
+
const addedTestFiles = visibleAddedFiles.filter((f) => isTestFile(f, testFilePatterns));
|
|
49551
|
+
const addedSourceFiles = visibleAddedFiles.filter((f) => !isTestFile(f, testFilePatterns));
|
|
49552
|
+
const suffixStrippers = buildSuffixStrippers(testFilePatterns);
|
|
49553
|
+
const testFileBasenames = new Set(addedTestFiles.map((f) => {
|
|
49554
|
+
const base = f.split("/").at(-1) ?? f;
|
|
49555
|
+
return stripTestSuffix(base, suffixStrippers);
|
|
49556
|
+
}));
|
|
49557
|
+
const newSourceFilesWithoutTests = addedSourceFiles.filter((f) => {
|
|
49558
|
+
const base = (f.split("/").at(-1) ?? f).replace(/\.(ts|js|tsx|jsx|go)$/, "");
|
|
49559
|
+
return !testFileBasenames.has(base);
|
|
49560
|
+
});
|
|
49561
|
+
return { addedTestFiles, newSourceFilesWithoutTests };
|
|
49562
|
+
}
|
|
49563
|
+
async function collectDiffFileList(workdir, storyGitRef, options) {
|
|
49564
|
+
const naxIgnoreExcludes = await resolveNaxIgnorePathspecExcludes(workdir, options);
|
|
49565
|
+
const merged = [...new Set([...naxIgnoreExcludes, ...ALWAYS_EXCLUDED])];
|
|
49566
|
+
const proc = _diffUtilsDeps.spawn({
|
|
49567
|
+
cmd: ["git", "diff", "--name-only", `${storyGitRef}..HEAD`, "--", ".", ...merged],
|
|
49568
|
+
cwd: workdir,
|
|
49569
|
+
stdout: "pipe",
|
|
49570
|
+
stderr: "pipe"
|
|
49571
|
+
});
|
|
49572
|
+
const [exitCode, stdout] = await Promise.all([
|
|
49573
|
+
proc.exited,
|
|
49574
|
+
new Response(proc.stdout).text(),
|
|
49575
|
+
new Response(proc.stderr).text()
|
|
49576
|
+
]);
|
|
49577
|
+
if (exitCode !== 0)
|
|
49578
|
+
return;
|
|
49579
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
49580
|
+
}
|
|
49581
|
+
var DIFF_CAP_BYTES = 51200, ALWAYS_EXCLUDED, _diffUtilsDeps, DEFAULT_SUFFIX_STRIPPERS;
|
|
49582
|
+
var init_diff_utils = __esm(() => {
|
|
49583
|
+
init_logger2();
|
|
49584
|
+
init_test_runners();
|
|
49585
|
+
init_git();
|
|
49586
|
+
init_path_filters();
|
|
49587
|
+
ALWAYS_EXCLUDED = [":!.nax/", ":!.nax-pids"];
|
|
49588
|
+
_diffUtilsDeps = {
|
|
49589
|
+
spawn: spawn3,
|
|
49590
|
+
isGitRefValid,
|
|
49591
|
+
getMergeBase
|
|
49592
|
+
};
|
|
49593
|
+
DEFAULT_SUFFIX_STRIPPERS = [/\.(test|spec)\.(ts|js|tsx|jsx)$/, /_test\.go$/];
|
|
49594
|
+
});
|
|
49595
|
+
|
|
49137
49596
|
// src/review/prepare-inputs.ts
|
|
49138
49597
|
import { relative as relative11, sep as sep3 } from "path";
|
|
49139
49598
|
function derivePackageDirs(workdir, projectDir) {
|
|
@@ -49282,7 +49741,7 @@ var package_default;
|
|
|
49282
49741
|
var init_package = __esm(() => {
|
|
49283
49742
|
package_default = {
|
|
49284
49743
|
name: "@nathapp/nax",
|
|
49285
|
-
version: "0.80.
|
|
49744
|
+
version: "0.80.1",
|
|
49286
49745
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
49287
49746
|
type: "module",
|
|
49288
49747
|
bin: {
|
|
@@ -49397,8 +49856,8 @@ var init_version = __esm(() => {
|
|
|
49397
49856
|
NAX_VERSION = package_default.version;
|
|
49398
49857
|
NAX_COMMIT = (() => {
|
|
49399
49858
|
try {
|
|
49400
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
49401
|
-
return "
|
|
49859
|
+
if (/^[0-9a-f]{6,10}$/.test("8daaf106"))
|
|
49860
|
+
return "8daaf106";
|
|
49402
49861
|
} catch {}
|
|
49403
49862
|
try {
|
|
49404
49863
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -49603,14 +50062,7 @@ async function runAdversarialReview(opts) {
|
|
|
49603
50062
|
adversarialConfig
|
|
49604
50063
|
});
|
|
49605
50064
|
if (prepared.skipReason === "no git ref") {
|
|
49606
|
-
return
|
|
49607
|
-
check: "adversarial",
|
|
49608
|
-
success: true,
|
|
49609
|
-
command: "",
|
|
49610
|
-
exitCode: 0,
|
|
49611
|
-
output: "skipped: no git ref",
|
|
49612
|
-
durationMs: Date.now() - startTime
|
|
49613
|
-
};
|
|
50065
|
+
return skipResult("skipped: no git ref", startTime);
|
|
49614
50066
|
}
|
|
49615
50067
|
const diffMode = adversarialConfig.diffMode ?? "ref";
|
|
49616
50068
|
logger?.info("review", "Running adversarial check", {
|
|
@@ -49619,24 +50071,10 @@ async function runAdversarialReview(opts) {
|
|
|
49619
50071
|
diffMode
|
|
49620
50072
|
});
|
|
49621
50073
|
if (prepared.skipReason === "no changes detected") {
|
|
49622
|
-
return
|
|
49623
|
-
check: "adversarial",
|
|
49624
|
-
success: true,
|
|
49625
|
-
command: "",
|
|
49626
|
-
exitCode: 0,
|
|
49627
|
-
output: "skipped: no changes detected",
|
|
49628
|
-
durationMs: Date.now() - startTime
|
|
49629
|
-
};
|
|
50074
|
+
return skipResult("skipped: no changes detected", startTime);
|
|
49630
50075
|
}
|
|
49631
50076
|
if (prepared.skipReason === "no code changes") {
|
|
49632
|
-
return
|
|
49633
|
-
check: "adversarial",
|
|
49634
|
-
success: true,
|
|
49635
|
-
command: "",
|
|
49636
|
-
exitCode: 0,
|
|
49637
|
-
output: "skipped: no code changes",
|
|
49638
|
-
durationMs: Date.now() - startTime
|
|
49639
|
-
};
|
|
50077
|
+
return skipResult("skipped: no code changes", startTime);
|
|
49640
50078
|
}
|
|
49641
50079
|
const effectiveRef = prepared.effectiveRef;
|
|
49642
50080
|
const stat4 = prepared.stat;
|
|
@@ -49650,37 +50088,22 @@ async function runAdversarialReview(opts) {
|
|
|
49650
50088
|
storyId: story.id,
|
|
49651
50089
|
model: adversarialConfig.model
|
|
49652
50090
|
});
|
|
49653
|
-
return
|
|
49654
|
-
check: "adversarial",
|
|
49655
|
-
success: true,
|
|
49656
|
-
command: "",
|
|
49657
|
-
exitCode: 0,
|
|
49658
|
-
output: "skipped: no agent available for model tier",
|
|
49659
|
-
durationMs: Date.now() - startTime
|
|
49660
|
-
};
|
|
49661
|
-
}
|
|
49662
|
-
let featureCtxBlock = "";
|
|
49663
|
-
if (contextBundle) {
|
|
49664
|
-
const md = contextBundle.pushMarkdown.trim();
|
|
49665
|
-
if (md)
|
|
49666
|
-
featureCtxBlock = `${md}
|
|
49667
|
-
|
|
49668
|
-
---
|
|
49669
|
-
|
|
49670
|
-
`;
|
|
49671
|
-
} else if (featureContextMarkdown) {
|
|
49672
|
-
const filtered = filterContextByRole(featureContextMarkdown, "reviewer-adversarial");
|
|
49673
|
-
if (filtered.trim())
|
|
49674
|
-
featureCtxBlock = `${filtered}
|
|
49675
|
-
|
|
49676
|
-
---
|
|
49677
|
-
|
|
49678
|
-
`;
|
|
50091
|
+
return skipResult("skipped: no agent available for model tier", startTime);
|
|
49679
50092
|
}
|
|
50093
|
+
const featureCtxBlock = buildFeatureCtxBlock(contextBundle, featureContextMarkdown);
|
|
49680
50094
|
if (!runtime) {
|
|
49681
50095
|
throw new NaxError("runtime required \u2014 legacy agentManager.run path removed (ADR-019 Wave 3, issue #762)", "DISPATCH_NO_RUNTIME", { stage: "review-adversarial", storyId: story.id });
|
|
49682
50096
|
}
|
|
49683
|
-
const
|
|
50097
|
+
const outcomeCtx = {
|
|
50098
|
+
runtime,
|
|
50099
|
+
workdir,
|
|
50100
|
+
projectDir,
|
|
50101
|
+
storyId: story.id,
|
|
50102
|
+
featureName,
|
|
50103
|
+
blockingThreshold,
|
|
50104
|
+
startTime,
|
|
50105
|
+
logger
|
|
50106
|
+
};
|
|
49684
50107
|
const callCtx = {
|
|
49685
50108
|
runtime,
|
|
49686
50109
|
packageView: runtime.packages.resolve(workdir),
|
|
@@ -49711,81 +50134,12 @@ async function runAdversarialReview(opts) {
|
|
|
49711
50134
|
resolvedTestPatterns
|
|
49712
50135
|
});
|
|
49713
50136
|
} catch (err) {
|
|
49714
|
-
|
|
49715
|
-
recordAdversarialAudit({
|
|
49716
|
-
runtime,
|
|
49717
|
-
workdir,
|
|
49718
|
-
projectDir,
|
|
49719
|
-
storyId: story.id,
|
|
49720
|
-
featureName,
|
|
49721
|
-
parsed: false,
|
|
49722
|
-
looksLikeFail: false,
|
|
49723
|
-
failOpen: true,
|
|
49724
|
-
passed: true,
|
|
49725
|
-
blockingThreshold,
|
|
49726
|
-
result: null
|
|
49727
|
-
});
|
|
49728
|
-
return {
|
|
49729
|
-
check: "adversarial",
|
|
49730
|
-
success: true,
|
|
49731
|
-
failOpen: true,
|
|
49732
|
-
command: "",
|
|
49733
|
-
exitCode: 0,
|
|
49734
|
-
output: `skipped: LLM call failed \u2014 ${String(err)}`,
|
|
49735
|
-
durationMs: Date.now() - startTime
|
|
49736
|
-
};
|
|
49737
|
-
}
|
|
49738
|
-
if (opResult.failOpen) {
|
|
49739
|
-
logger?.warn("adversarial", "Retry exhausted \u2014 fail-open", { storyId: story.id });
|
|
49740
|
-
recordAdversarialAudit({
|
|
49741
|
-
runtime,
|
|
49742
|
-
workdir,
|
|
49743
|
-
projectDir,
|
|
49744
|
-
storyId: story.id,
|
|
49745
|
-
featureName,
|
|
49746
|
-
parsed: false,
|
|
49747
|
-
looksLikeFail: false,
|
|
49748
|
-
failOpen: true,
|
|
49749
|
-
passed: true,
|
|
49750
|
-
blockingThreshold,
|
|
49751
|
-
result: null
|
|
49752
|
-
});
|
|
49753
|
-
return {
|
|
49754
|
-
check: "adversarial",
|
|
49755
|
-
success: true,
|
|
49756
|
-
failOpen: true,
|
|
49757
|
-
command: "",
|
|
49758
|
-
exitCode: 0,
|
|
49759
|
-
output: "adversarial review: could not parse LLM response (fail-open)",
|
|
49760
|
-
durationMs: Date.now() - startTime
|
|
49761
|
-
};
|
|
49762
|
-
}
|
|
49763
|
-
if (opResult.looksLikeFail) {
|
|
49764
|
-
logger?.warn("adversarial", "LLM returned truncated JSON with passed:false \u2014 treating as failure", {
|
|
49765
|
-
storyId: story.id
|
|
49766
|
-
});
|
|
49767
|
-
recordAdversarialAudit({
|
|
49768
|
-
runtime,
|
|
49769
|
-
workdir,
|
|
49770
|
-
projectDir,
|
|
49771
|
-
storyId: story.id,
|
|
49772
|
-
featureName,
|
|
49773
|
-
parsed: false,
|
|
49774
|
-
looksLikeFail: true,
|
|
49775
|
-
failOpen: false,
|
|
49776
|
-
passed: false,
|
|
49777
|
-
blockingThreshold,
|
|
49778
|
-
result: null
|
|
49779
|
-
});
|
|
49780
|
-
return {
|
|
49781
|
-
check: "adversarial",
|
|
49782
|
-
success: false,
|
|
49783
|
-
command: "",
|
|
49784
|
-
exitCode: 1,
|
|
49785
|
-
output: "adversarial review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
49786
|
-
durationMs: Date.now() - startTime
|
|
49787
|
-
};
|
|
50137
|
+
return catchDispatchFailure(err, outcomeCtx);
|
|
49788
50138
|
}
|
|
50139
|
+
if (opResult.failOpen)
|
|
50140
|
+
return handleRetryExhaustedFailOpen(outcomeCtx);
|
|
50141
|
+
if (opResult.looksLikeFail)
|
|
50142
|
+
return handleTruncatedLooksLikeFail(outcomeCtx);
|
|
49789
50143
|
if (opResult.repromptEvent) {
|
|
49790
50144
|
runtime.dispatchEvents.emitReviewReprompt({
|
|
49791
50145
|
kind: "review-reprompt-on-drop",
|
|
@@ -49796,44 +50150,9 @@ async function runAdversarialReview(opts) {
|
|
|
49796
50150
|
costUsd: opResult.repromptEvent.costUsd
|
|
49797
50151
|
});
|
|
49798
50152
|
}
|
|
49799
|
-
const
|
|
49800
|
-
const
|
|
49801
|
-
const
|
|
49802
|
-
const testFileMatch = (file3) => patterns.some((re) => re.test(file3));
|
|
49803
|
-
const recurrenceCfg = adversarialConfig.recurrenceDemotion ?? { enabled: true, maxBlockingRounds: 2 };
|
|
49804
|
-
const {
|
|
49805
|
-
blocking: blockingFindings,
|
|
49806
|
-
advisory: advisoryOnly,
|
|
49807
|
-
demoted
|
|
49808
|
-
} = classifyRecurrence(allFindings, priorAdversarialIterations ?? [], recurrenceCfg, testFileMatch, threshold);
|
|
49809
|
-
const advisoryFindings = [...advisoryOnly, ...demoted];
|
|
49810
|
-
const advisoryReviewFindings = [
|
|
49811
|
-
...llmFindingsToReviewFindings(advisoryOnly, { source: "adversarial-review", isTestFile: testFileMatch }),
|
|
49812
|
-
...tagCoverageGap(llmFindingsToReviewFindings(demoted, { source: "adversarial-review", isTestFile: testFileMatch }))
|
|
49813
|
-
];
|
|
49814
|
-
const advisoryFindingsAsFindings = [
|
|
49815
|
-
...toAdversarialReviewFindings(advisoryOnly, { isTestFile: testFileMatch }),
|
|
49816
|
-
...tagCoverageGap(toAdversarialReviewFindings(demoted, { isTestFile: testFileMatch }))
|
|
49817
|
-
];
|
|
49818
|
-
const acDropped = opResult.acDropped ?? [];
|
|
49819
|
-
const acks = opResult.acks;
|
|
49820
|
-
let diffFiles;
|
|
49821
|
-
let diffAvailable;
|
|
49822
|
-
if (diff && diff.length > 0) {
|
|
49823
|
-
diffFiles = extractDiffFiles(diff);
|
|
49824
|
-
diffAvailable = true;
|
|
49825
|
-
} else {
|
|
49826
|
-
const repoRoot = projectDir ?? workdir;
|
|
49827
|
-
const packageDir = workdir !== repoRoot ? workdir : undefined;
|
|
49828
|
-
const list = await _adversarialDeps.collectDiffFileList(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
|
|
49829
|
-
if (list === undefined) {
|
|
49830
|
-
diffFiles = new Set;
|
|
49831
|
-
diffAvailable = false;
|
|
49832
|
-
} else {
|
|
49833
|
-
diffFiles = new Set(list);
|
|
49834
|
-
diffAvailable = true;
|
|
49835
|
-
}
|
|
49836
|
-
}
|
|
50153
|
+
const classification = classifyAdversarialFindings(opResult, blockingThreshold, priorAdversarialIterations, adversarialConfig, resolvedTestPatterns);
|
|
50154
|
+
const { threshold, blockingFindings, advisoryFindings, acDropped } = classification;
|
|
50155
|
+
const { diffFiles, diffAvailable } = await resolveDiffFileSet(diff, workdir, projectDir, effectiveRef, naxIgnoreIndex, _adversarialDeps.collectDiffFileList);
|
|
49837
50156
|
const { adversarialDropAnalysis, adversarialAcceptAnalysis } = buildCounterfactualTelemetry({
|
|
49838
50157
|
acDropped,
|
|
49839
50158
|
blockingFindings,
|
|
@@ -49852,177 +50171,28 @@ async function runAdversarialReview(opts) {
|
|
|
49852
50171
|
});
|
|
49853
50172
|
}
|
|
49854
50173
|
const durationMs = Date.now() - startTime;
|
|
50174
|
+
const telemetry = { adversarialDropAnalysis, adversarialAcceptAnalysis };
|
|
49855
50175
|
if (blockingFindings.length > 0) {
|
|
49856
|
-
|
|
49857
|
-
storyId: story.id,
|
|
49858
|
-
durationMs,
|
|
49859
|
-
findings: blockingFindings.map((f) => ({
|
|
49860
|
-
severity: f.severity,
|
|
49861
|
-
category: f.category,
|
|
49862
|
-
file: f.file,
|
|
49863
|
-
line: f.line,
|
|
49864
|
-
issue: f.issue
|
|
49865
|
-
}))
|
|
49866
|
-
});
|
|
49867
|
-
recordAdversarialAudit({
|
|
49868
|
-
runtime,
|
|
49869
|
-
workdir,
|
|
49870
|
-
projectDir,
|
|
49871
|
-
storyId: story.id,
|
|
49872
|
-
featureName,
|
|
49873
|
-
parsed: true,
|
|
49874
|
-
failOpen: false,
|
|
49875
|
-
passed: false,
|
|
49876
|
-
blockingThreshold: threshold,
|
|
49877
|
-
result: {
|
|
49878
|
-
passed: false,
|
|
49879
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
49880
|
-
},
|
|
49881
|
-
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
49882
|
-
diffAvailable,
|
|
49883
|
-
adversarialDropAnalysis,
|
|
49884
|
-
adversarialAcceptAnalysis,
|
|
49885
|
-
acks
|
|
49886
|
-
});
|
|
49887
|
-
const output = blockingFindings.length > 0 ? `Adversarial review failed:
|
|
49888
|
-
|
|
49889
|
-
${formatFindings(blockingFindings)}` : "Adversarial review failed (no findings)";
|
|
49890
|
-
return {
|
|
49891
|
-
check: "adversarial",
|
|
49892
|
-
success: false,
|
|
49893
|
-
command: "",
|
|
49894
|
-
exitCode: 1,
|
|
49895
|
-
output,
|
|
49896
|
-
durationMs,
|
|
49897
|
-
findings: blockingFindings.length > 0 ? toAdversarialReviewFindings(blockingFindings, { isTestFile: testFileMatch }) : undefined,
|
|
49898
|
-
advisoryFindings: advisoryFindings.length > 0 ? advisoryFindingsAsFindings : undefined,
|
|
49899
|
-
cost: llmCost
|
|
49900
|
-
};
|
|
50176
|
+
return buildBlockingFailureResult(outcomeCtx, classification, telemetry, diffAvailable, durationMs);
|
|
49901
50177
|
}
|
|
49902
50178
|
if (!opResult.modelPassed && acDropped.length > 0) {
|
|
49903
50179
|
if (acDropped.every((d) => d.code === "ac_quote_not_substring")) {
|
|
49904
|
-
|
|
49905
|
-
const allAdvisory = [...advisoryFindingsAsFindings, ...demotedFindings];
|
|
49906
|
-
logger?.warn("review", "Adversarial review passed: all blocking findings discarded as hallucinated AC quotes", {
|
|
49907
|
-
storyId: story.id,
|
|
49908
|
-
durationMs,
|
|
49909
|
-
droppedCount: acDropped.length,
|
|
49910
|
-
drops: acDropped.map((d) => ({ file: d.finding.file, issue: d.finding.issue }))
|
|
49911
|
-
});
|
|
49912
|
-
recordAdversarialAudit({
|
|
49913
|
-
runtime,
|
|
49914
|
-
workdir,
|
|
49915
|
-
projectDir,
|
|
49916
|
-
storyId: story.id,
|
|
49917
|
-
featureName,
|
|
49918
|
-
parsed: true,
|
|
49919
|
-
acks,
|
|
49920
|
-
failOpen: false,
|
|
49921
|
-
passed: true,
|
|
49922
|
-
passReason: "ac_quote_not_substring_demoted",
|
|
49923
|
-
blockingThreshold: threshold,
|
|
49924
|
-
result: { passed: true, findings: [] },
|
|
49925
|
-
advisoryFindings: allAdvisory.length > 0 ? allAdvisory : undefined,
|
|
49926
|
-
diffAvailable,
|
|
49927
|
-
adversarialDropAnalysis,
|
|
49928
|
-
adversarialAcceptAnalysis: []
|
|
49929
|
-
});
|
|
49930
|
-
return {
|
|
49931
|
-
check: "adversarial",
|
|
49932
|
-
success: true,
|
|
49933
|
-
passReason: "ac_quote_not_substring_demoted",
|
|
49934
|
-
command: "",
|
|
49935
|
-
exitCode: 0,
|
|
49936
|
-
output: `Adversarial review passed: ${acDropped.length} blocking finding(s) demoted to advisory \u2014 all cited AC quotes were fabricated and could not be validated.`,
|
|
49937
|
-
durationMs,
|
|
49938
|
-
advisoryFindings: allAdvisory.length > 0 ? allAdvisory : undefined,
|
|
49939
|
-
cost: llmCost
|
|
49940
|
-
};
|
|
50180
|
+
return buildHallucinatedAcQuoteResult(outcomeCtx, classification, telemetry, diffAvailable, durationMs);
|
|
49941
50181
|
}
|
|
49942
|
-
|
|
49943
|
-
storyId: story.id,
|
|
49944
|
-
durationMs,
|
|
49945
|
-
droppedCount: acDropped.length,
|
|
49946
|
-
dropCodes: acDropped.map((d) => d.code)
|
|
49947
|
-
});
|
|
49948
|
-
const dropSummary = acDropped.map((d, i) => `${i + 1}. [${d.code}] ${d.finding.file ?? "<unknown>"}: ${d.finding.issue}`).join(`
|
|
49949
|
-
`);
|
|
49950
|
-
recordAdversarialAudit({
|
|
49951
|
-
runtime,
|
|
49952
|
-
workdir,
|
|
49953
|
-
projectDir,
|
|
49954
|
-
storyId: story.id,
|
|
49955
|
-
featureName,
|
|
49956
|
-
parsed: true,
|
|
49957
|
-
acks,
|
|
49958
|
-
failOpen: false,
|
|
49959
|
-
passed: false,
|
|
49960
|
-
blockingThreshold: threshold,
|
|
49961
|
-
result: { passed: false, findings: [] },
|
|
49962
|
-
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
49963
|
-
diffAvailable,
|
|
49964
|
-
adversarialDropAnalysis,
|
|
49965
|
-
adversarialAcceptAnalysis: []
|
|
49966
|
-
});
|
|
49967
|
-
return {
|
|
49968
|
-
check: "adversarial",
|
|
49969
|
-
success: false,
|
|
49970
|
-
command: "",
|
|
49971
|
-
exitCode: 1,
|
|
49972
|
-
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. Drops:
|
|
49973
|
-
|
|
49974
|
-
${dropSummary}`,
|
|
49975
|
-
durationMs,
|
|
49976
|
-
advisoryFindings: advisoryFindings.length > 0 ? advisoryFindingsAsFindings : undefined,
|
|
49977
|
-
cost: llmCost
|
|
49978
|
-
};
|
|
50182
|
+
return buildUngroundedFailClosedResult(outcomeCtx, classification, telemetry, diffAvailable, durationMs);
|
|
49979
50183
|
}
|
|
49980
|
-
|
|
49981
|
-
recordAdversarialAudit({
|
|
49982
|
-
runtime,
|
|
49983
|
-
workdir,
|
|
49984
|
-
projectDir,
|
|
49985
|
-
storyId: story.id,
|
|
49986
|
-
featureName,
|
|
49987
|
-
parsed: true,
|
|
49988
|
-
acks,
|
|
49989
|
-
failOpen: false,
|
|
49990
|
-
passed: true,
|
|
49991
|
-
blockingThreshold: threshold,
|
|
49992
|
-
result: {
|
|
49993
|
-
passed: true,
|
|
49994
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "adversarial-review", isTestFile: testFileMatch })
|
|
49995
|
-
},
|
|
49996
|
-
advisoryFindings: advisoryFindings.length > 0 ? advisoryReviewFindings : undefined,
|
|
49997
|
-
diffAvailable,
|
|
49998
|
-
adversarialDropAnalysis,
|
|
49999
|
-
adversarialAcceptAnalysis: []
|
|
50000
|
-
});
|
|
50001
|
-
return {
|
|
50002
|
-
check: "adversarial",
|
|
50003
|
-
success: true,
|
|
50004
|
-
command: "",
|
|
50005
|
-
exitCode: 0,
|
|
50006
|
-
output: allFindings.length === 0 ? "Adversarial review passed" : "Adversarial review passed (all findings were advisory \u2014 below blocking threshold)",
|
|
50007
|
-
durationMs,
|
|
50008
|
-
advisoryFindings: advisoryFindings.length > 0 ? advisoryFindingsAsFindings : undefined,
|
|
50009
|
-
cost: llmCost
|
|
50010
|
-
};
|
|
50184
|
+
return buildPassedResult(outcomeCtx, classification, telemetry, diffAvailable, durationMs);
|
|
50011
50185
|
}
|
|
50012
50186
|
var _adversarialDeps;
|
|
50013
50187
|
var init_adversarial = __esm(() => {
|
|
50014
|
-
init_context();
|
|
50015
50188
|
init_errors();
|
|
50016
50189
|
init_logger2();
|
|
50017
50190
|
init_adversarial_review();
|
|
50018
50191
|
init_call();
|
|
50019
|
-
init_diff_files();
|
|
50020
50192
|
init_adversarial_counterfactual_telemetry();
|
|
50021
|
-
|
|
50193
|
+
init_adversarial_outcomes();
|
|
50022
50194
|
init_diff_utils();
|
|
50023
|
-
init_finding_projection();
|
|
50024
50195
|
init_prepare_inputs();
|
|
50025
|
-
init_recurrence_demotion();
|
|
50026
50196
|
init_review_audit();
|
|
50027
50197
|
_adversarialDeps = {
|
|
50028
50198
|
writeReviewAudit,
|
|
@@ -50664,7 +50834,37 @@ var init_semantic_debate = __esm(() => {
|
|
|
50664
50834
|
init_semantic_helpers();
|
|
50665
50835
|
});
|
|
50666
50836
|
|
|
50667
|
-
// src/review/semantic.ts
|
|
50837
|
+
// src/review/semantic-outcomes.ts
|
|
50838
|
+
function skipResult2(output, startTime) {
|
|
50839
|
+
return {
|
|
50840
|
+
check: "semantic",
|
|
50841
|
+
success: true,
|
|
50842
|
+
command: "",
|
|
50843
|
+
exitCode: 0,
|
|
50844
|
+
output,
|
|
50845
|
+
durationMs: Date.now() - startTime
|
|
50846
|
+
};
|
|
50847
|
+
}
|
|
50848
|
+
function buildFeatureCtxBlock2(contextBundle, featureContextMarkdown) {
|
|
50849
|
+
if (contextBundle) {
|
|
50850
|
+
const md = contextBundle.pushMarkdown.trim();
|
|
50851
|
+
return md ? `${md}
|
|
50852
|
+
|
|
50853
|
+
---
|
|
50854
|
+
|
|
50855
|
+
` : "";
|
|
50856
|
+
}
|
|
50857
|
+
if (featureContextMarkdown) {
|
|
50858
|
+
const filtered = filterContextByRole(featureContextMarkdown, "reviewer-semantic");
|
|
50859
|
+
if (filtered.trim())
|
|
50860
|
+
return `${filtered}
|
|
50861
|
+
|
|
50862
|
+
---
|
|
50863
|
+
|
|
50864
|
+
`;
|
|
50865
|
+
}
|
|
50866
|
+
return "";
|
|
50867
|
+
}
|
|
50668
50868
|
function recordSemanticAudit(opts) {
|
|
50669
50869
|
opts.runtime?.dispatchEvents.emitReviewDecision({
|
|
50670
50870
|
kind: "review-decision",
|
|
@@ -50684,6 +50884,216 @@ function recordSemanticAudit(opts) {
|
|
|
50684
50884
|
acks: opts.acks
|
|
50685
50885
|
});
|
|
50686
50886
|
}
|
|
50887
|
+
function catchDispatchFailure2(err, ctx) {
|
|
50888
|
+
const { runtime, workdir, projectDir, storyId, featureName, blockingThreshold, startTime, logger } = ctx;
|
|
50889
|
+
logger?.warn("semantic", "LLM call failed \u2014 fail-open", { storyId, cause: String(err) });
|
|
50890
|
+
recordSemanticAudit({
|
|
50891
|
+
runtime,
|
|
50892
|
+
workdir,
|
|
50893
|
+
projectDir,
|
|
50894
|
+
storyId,
|
|
50895
|
+
featureName,
|
|
50896
|
+
parsed: false,
|
|
50897
|
+
looksLikeFail: false,
|
|
50898
|
+
failOpen: true,
|
|
50899
|
+
passed: true,
|
|
50900
|
+
blockingThreshold,
|
|
50901
|
+
result: null
|
|
50902
|
+
});
|
|
50903
|
+
return {
|
|
50904
|
+
check: "semantic",
|
|
50905
|
+
success: true,
|
|
50906
|
+
failOpen: true,
|
|
50907
|
+
command: "",
|
|
50908
|
+
exitCode: 0,
|
|
50909
|
+
output: `skipped: LLM call failed \u2014 ${String(err)}`,
|
|
50910
|
+
durationMs: Date.now() - startTime
|
|
50911
|
+
};
|
|
50912
|
+
}
|
|
50913
|
+
function handleRetryExhaustedFailOpen2(ctx) {
|
|
50914
|
+
const { runtime, workdir, projectDir, storyId, featureName, blockingThreshold, startTime, logger } = ctx;
|
|
50915
|
+
logger?.warn("semantic", "Retry exhausted \u2014 fail-open", { storyId });
|
|
50916
|
+
recordSemanticAudit({
|
|
50917
|
+
runtime,
|
|
50918
|
+
workdir,
|
|
50919
|
+
projectDir,
|
|
50920
|
+
storyId,
|
|
50921
|
+
featureName,
|
|
50922
|
+
parsed: false,
|
|
50923
|
+
looksLikeFail: false,
|
|
50924
|
+
failOpen: true,
|
|
50925
|
+
passed: true,
|
|
50926
|
+
blockingThreshold,
|
|
50927
|
+
result: null
|
|
50928
|
+
});
|
|
50929
|
+
return {
|
|
50930
|
+
check: "semantic",
|
|
50931
|
+
success: true,
|
|
50932
|
+
failOpen: true,
|
|
50933
|
+
command: "",
|
|
50934
|
+
exitCode: 0,
|
|
50935
|
+
output: "semantic review: could not parse LLM response (fail-open)",
|
|
50936
|
+
durationMs: Date.now() - startTime
|
|
50937
|
+
};
|
|
50938
|
+
}
|
|
50939
|
+
function handleTruncatedLooksLikeFail2(ctx) {
|
|
50940
|
+
const { runtime, workdir, projectDir, storyId, featureName, blockingThreshold, startTime, logger } = ctx;
|
|
50941
|
+
logger?.warn("semantic", "LLM returned truncated JSON with passed:false \u2014 treating as failure", { storyId });
|
|
50942
|
+
recordSemanticAudit({
|
|
50943
|
+
runtime,
|
|
50944
|
+
workdir,
|
|
50945
|
+
projectDir,
|
|
50946
|
+
storyId,
|
|
50947
|
+
featureName,
|
|
50948
|
+
parsed: false,
|
|
50949
|
+
looksLikeFail: true,
|
|
50950
|
+
failOpen: false,
|
|
50951
|
+
passed: false,
|
|
50952
|
+
blockingThreshold,
|
|
50953
|
+
result: null
|
|
50954
|
+
});
|
|
50955
|
+
return {
|
|
50956
|
+
check: "semantic",
|
|
50957
|
+
success: false,
|
|
50958
|
+
command: "",
|
|
50959
|
+
exitCode: 1,
|
|
50960
|
+
output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
50961
|
+
durationMs: Date.now() - startTime
|
|
50962
|
+
};
|
|
50963
|
+
}
|
|
50964
|
+
function classifySemanticFindings(opResult, blockingThreshold) {
|
|
50965
|
+
const threshold = blockingThreshold ?? "error";
|
|
50966
|
+
const allFindings = opResult.findings;
|
|
50967
|
+
const blockingFindings = allFindings.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
50968
|
+
const advisoryFindings = allFindings.filter((f) => !isBlockingSeverity(f.severity, threshold));
|
|
50969
|
+
return {
|
|
50970
|
+
threshold,
|
|
50971
|
+
allFindings,
|
|
50972
|
+
acks: opResult.acks,
|
|
50973
|
+
blockingFindings,
|
|
50974
|
+
advisoryFindings
|
|
50975
|
+
};
|
|
50976
|
+
}
|
|
50977
|
+
function buildBlockingFailureResult2(ctx, classification, durationMs) {
|
|
50978
|
+
const { runtime, workdir, projectDir, storyId, featureName, logger, testFileMatch } = ctx;
|
|
50979
|
+
const { threshold, allFindings, acks, blockingFindings, advisoryFindings } = classification;
|
|
50980
|
+
logger?.warn("review", `Semantic review failed: ${blockingFindings.length} blocking findings`, {
|
|
50981
|
+
storyId,
|
|
50982
|
+
durationMs
|
|
50983
|
+
});
|
|
50984
|
+
logger?.debug("review", "Semantic review findings", {
|
|
50985
|
+
storyId,
|
|
50986
|
+
findings: blockingFindings.map((f) => ({
|
|
50987
|
+
severity: f.severity,
|
|
50988
|
+
file: f.file,
|
|
50989
|
+
line: f.line,
|
|
50990
|
+
issue: f.issue,
|
|
50991
|
+
suggestion: f.suggestion
|
|
50992
|
+
}))
|
|
50993
|
+
});
|
|
50994
|
+
const output = `Semantic review failed:
|
|
50995
|
+
|
|
50996
|
+
${formatFindings2(blockingFindings)}`;
|
|
50997
|
+
recordSemanticAudit({
|
|
50998
|
+
runtime,
|
|
50999
|
+
workdir,
|
|
51000
|
+
projectDir,
|
|
51001
|
+
storyId,
|
|
51002
|
+
featureName,
|
|
51003
|
+
parsed: true,
|
|
51004
|
+
failOpen: false,
|
|
51005
|
+
passed: false,
|
|
51006
|
+
blockingThreshold: threshold,
|
|
51007
|
+
acks,
|
|
51008
|
+
result: {
|
|
51009
|
+
passed: false,
|
|
51010
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
51011
|
+
},
|
|
51012
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
51013
|
+
});
|
|
51014
|
+
return {
|
|
51015
|
+
check: "semantic",
|
|
51016
|
+
success: false,
|
|
51017
|
+
command: "",
|
|
51018
|
+
exitCode: 1,
|
|
51019
|
+
output,
|
|
51020
|
+
durationMs,
|
|
51021
|
+
findings: toReviewFindings(blockingFindings, { isTestFile: testFileMatch }),
|
|
51022
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
51023
|
+
cost: 0
|
|
51024
|
+
};
|
|
51025
|
+
}
|
|
51026
|
+
function buildAcIndexDroppedFailClosedResult(ctx, classification, durationMs) {
|
|
51027
|
+
const { runtime, workdir, projectDir, storyId, featureName, logger, testFileMatch } = ctx;
|
|
51028
|
+
const { threshold, acks, advisoryFindings } = classification;
|
|
51029
|
+
logger?.warn("review", "Semantic review fail-closed: blocking findings dropped (acIndex invalid)", {
|
|
51030
|
+
storyId,
|
|
51031
|
+
durationMs
|
|
51032
|
+
});
|
|
51033
|
+
recordSemanticAudit({
|
|
51034
|
+
runtime,
|
|
51035
|
+
workdir,
|
|
51036
|
+
projectDir,
|
|
51037
|
+
storyId,
|
|
51038
|
+
featureName,
|
|
51039
|
+
parsed: true,
|
|
51040
|
+
acks,
|
|
51041
|
+
failOpen: false,
|
|
51042
|
+
passed: false,
|
|
51043
|
+
blockingThreshold: threshold,
|
|
51044
|
+
result: { passed: false, findings: [] },
|
|
51045
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
51046
|
+
});
|
|
51047
|
+
return {
|
|
51048
|
+
check: "semantic",
|
|
51049
|
+
success: false,
|
|
51050
|
+
command: "",
|
|
51051
|
+
exitCode: 1,
|
|
51052
|
+
output: 'Semantic review failed: blocking finding(s) were dropped \u2014 acIndex was missing or out of range. The model emitted "passed: false" without valid AC attribution.',
|
|
51053
|
+
durationMs,
|
|
51054
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
51055
|
+
cost: 0
|
|
51056
|
+
};
|
|
51057
|
+
}
|
|
51058
|
+
function buildPassedResult2(ctx, classification, durationMs) {
|
|
51059
|
+
const { runtime, workdir, projectDir, storyId, featureName, logger, testFileMatch } = ctx;
|
|
51060
|
+
const { threshold, allFindings, acks, advisoryFindings } = classification;
|
|
51061
|
+
logger?.info("review", "Semantic review passed", { storyId, durationMs });
|
|
51062
|
+
recordSemanticAudit({
|
|
51063
|
+
runtime,
|
|
51064
|
+
workdir,
|
|
51065
|
+
projectDir,
|
|
51066
|
+
storyId,
|
|
51067
|
+
featureName,
|
|
51068
|
+
parsed: true,
|
|
51069
|
+
acks,
|
|
51070
|
+
failOpen: false,
|
|
51071
|
+
passed: true,
|
|
51072
|
+
blockingThreshold: threshold,
|
|
51073
|
+
result: {
|
|
51074
|
+
passed: true,
|
|
51075
|
+
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
51076
|
+
},
|
|
51077
|
+
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
51078
|
+
});
|
|
51079
|
+
return {
|
|
51080
|
+
check: "semantic",
|
|
51081
|
+
success: true,
|
|
51082
|
+
command: "",
|
|
51083
|
+
exitCode: 0,
|
|
51084
|
+
output: allFindings.length === 0 ? "Semantic review passed" : "Semantic review passed (all findings were advisory \u2014 below blocking threshold)",
|
|
51085
|
+
durationMs,
|
|
51086
|
+
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
51087
|
+
cost: 0
|
|
51088
|
+
};
|
|
51089
|
+
}
|
|
51090
|
+
var init_semantic_outcomes = __esm(() => {
|
|
51091
|
+
init_context();
|
|
51092
|
+
init_finding_projection();
|
|
51093
|
+
init_semantic_helpers();
|
|
51094
|
+
});
|
|
51095
|
+
|
|
51096
|
+
// src/review/semantic.ts
|
|
50687
51097
|
async function runSemanticReview(opts) {
|
|
50688
51098
|
const {
|
|
50689
51099
|
workdir,
|
|
@@ -50721,14 +51131,7 @@ async function runSemanticReview(opts) {
|
|
|
50721
51131
|
semanticConfig
|
|
50722
51132
|
});
|
|
50723
51133
|
if (prepared.skipReason === "no git ref") {
|
|
50724
|
-
return
|
|
50725
|
-
check: "semantic",
|
|
50726
|
-
success: true,
|
|
50727
|
-
command: "",
|
|
50728
|
-
exitCode: 0,
|
|
50729
|
-
output: "skipped: no git ref",
|
|
50730
|
-
durationMs: Date.now() - startTime
|
|
50731
|
-
};
|
|
51134
|
+
return skipResult2("skipped: no git ref", startTime);
|
|
50732
51135
|
}
|
|
50733
51136
|
const diffMode = semanticConfig.diffMode ?? "ref";
|
|
50734
51137
|
logger?.info("review", "Running semantic check", {
|
|
@@ -50738,24 +51141,10 @@ async function runSemanticReview(opts) {
|
|
|
50738
51141
|
configProvided: !!naxConfig
|
|
50739
51142
|
});
|
|
50740
51143
|
if (prepared.skipReason === "no changes detected") {
|
|
50741
|
-
return
|
|
50742
|
-
check: "semantic",
|
|
50743
|
-
success: true,
|
|
50744
|
-
command: "",
|
|
50745
|
-
exitCode: 0,
|
|
50746
|
-
output: "skipped: no changes detected",
|
|
50747
|
-
durationMs: Date.now() - startTime
|
|
50748
|
-
};
|
|
51144
|
+
return skipResult2("skipped: no changes detected", startTime);
|
|
50749
51145
|
}
|
|
50750
51146
|
if (prepared.skipReason === "no production code changes") {
|
|
50751
|
-
return
|
|
50752
|
-
check: "semantic",
|
|
50753
|
-
success: true,
|
|
50754
|
-
command: "",
|
|
50755
|
-
exitCode: 0,
|
|
50756
|
-
output: "skipped: no production code changes",
|
|
50757
|
-
durationMs: Date.now() - startTime
|
|
50758
|
-
};
|
|
51147
|
+
return skipResult2("skipped: no production code changes", startTime);
|
|
50759
51148
|
}
|
|
50760
51149
|
const effectiveRef = prepared.effectiveRef;
|
|
50761
51150
|
const stat4 = prepared.stat;
|
|
@@ -50767,33 +51156,9 @@ async function runSemanticReview(opts) {
|
|
|
50767
51156
|
storyId: story.id,
|
|
50768
51157
|
model: semanticConfig.model
|
|
50769
51158
|
});
|
|
50770
|
-
return
|
|
50771
|
-
check: "semantic",
|
|
50772
|
-
success: true,
|
|
50773
|
-
command: "",
|
|
50774
|
-
exitCode: 0,
|
|
50775
|
-
output: "skipped: no agent available for model tier",
|
|
50776
|
-
durationMs: Date.now() - startTime
|
|
50777
|
-
};
|
|
50778
|
-
}
|
|
50779
|
-
let featureCtxBlock = "";
|
|
50780
|
-
if (contextBundle) {
|
|
50781
|
-
const md = contextBundle.pushMarkdown.trim();
|
|
50782
|
-
if (md)
|
|
50783
|
-
featureCtxBlock = `${md}
|
|
50784
|
-
|
|
50785
|
-
---
|
|
50786
|
-
|
|
50787
|
-
`;
|
|
50788
|
-
} else if (featureContextMarkdown) {
|
|
50789
|
-
const filtered = filterContextByRole(featureContextMarkdown, "reviewer-semantic");
|
|
50790
|
-
if (filtered.trim())
|
|
50791
|
-
featureCtxBlock = `${filtered}
|
|
50792
|
-
|
|
50793
|
-
---
|
|
50794
|
-
|
|
50795
|
-
`;
|
|
51159
|
+
return skipResult2("skipped: no agent available for model tier", startTime);
|
|
50796
51160
|
}
|
|
51161
|
+
const featureCtxBlock = buildFeatureCtxBlock2(contextBundle, featureContextMarkdown);
|
|
50797
51162
|
const basePrompt = new ReviewPromptBuilder().buildSemanticReviewPrompt(story, semanticConfig, {
|
|
50798
51163
|
mode: diffMode,
|
|
50799
51164
|
diff,
|
|
@@ -50845,7 +51210,17 @@ async function runSemanticReview(opts) {
|
|
|
50845
51210
|
if (!runtime) {
|
|
50846
51211
|
throw new NaxError("runtime required \u2014 legacy agentManager.run path removed (ADR-019 Wave 3, issue #762)", "DISPATCH_NO_RUNTIME", { stage: "review-semantic", storyId: story.id });
|
|
50847
51212
|
}
|
|
50848
|
-
const
|
|
51213
|
+
const outcomeCtx = {
|
|
51214
|
+
runtime,
|
|
51215
|
+
workdir,
|
|
51216
|
+
projectDir,
|
|
51217
|
+
storyId: story.id,
|
|
51218
|
+
featureName,
|
|
51219
|
+
blockingThreshold,
|
|
51220
|
+
startTime,
|
|
51221
|
+
logger,
|
|
51222
|
+
testFileMatch
|
|
51223
|
+
};
|
|
50849
51224
|
const callCtx = {
|
|
50850
51225
|
runtime,
|
|
50851
51226
|
packageView: runtime.packages.resolve(workdir),
|
|
@@ -50872,81 +51247,12 @@ async function runSemanticReview(opts) {
|
|
|
50872
51247
|
blockingThreshold
|
|
50873
51248
|
});
|
|
50874
51249
|
} catch (err) {
|
|
50875
|
-
|
|
50876
|
-
recordSemanticAudit({
|
|
50877
|
-
runtime,
|
|
50878
|
-
workdir,
|
|
50879
|
-
projectDir,
|
|
50880
|
-
storyId: story.id,
|
|
50881
|
-
featureName,
|
|
50882
|
-
parsed: false,
|
|
50883
|
-
looksLikeFail: false,
|
|
50884
|
-
failOpen: true,
|
|
50885
|
-
passed: true,
|
|
50886
|
-
blockingThreshold,
|
|
50887
|
-
result: null
|
|
50888
|
-
});
|
|
50889
|
-
return {
|
|
50890
|
-
check: "semantic",
|
|
50891
|
-
success: true,
|
|
50892
|
-
failOpen: true,
|
|
50893
|
-
command: "",
|
|
50894
|
-
exitCode: 0,
|
|
50895
|
-
output: `skipped: LLM call failed \u2014 ${String(err)}`,
|
|
50896
|
-
durationMs: Date.now() - startTime
|
|
50897
|
-
};
|
|
50898
|
-
}
|
|
50899
|
-
if (opResult.failOpen) {
|
|
50900
|
-
logger?.warn("semantic", "Retry exhausted \u2014 fail-open", { storyId: story.id });
|
|
50901
|
-
recordSemanticAudit({
|
|
50902
|
-
runtime,
|
|
50903
|
-
workdir,
|
|
50904
|
-
projectDir,
|
|
50905
|
-
storyId: story.id,
|
|
50906
|
-
featureName,
|
|
50907
|
-
parsed: false,
|
|
50908
|
-
looksLikeFail: false,
|
|
50909
|
-
failOpen: true,
|
|
50910
|
-
passed: true,
|
|
50911
|
-
blockingThreshold,
|
|
50912
|
-
result: null
|
|
50913
|
-
});
|
|
50914
|
-
return {
|
|
50915
|
-
check: "semantic",
|
|
50916
|
-
success: true,
|
|
50917
|
-
failOpen: true,
|
|
50918
|
-
command: "",
|
|
50919
|
-
exitCode: 0,
|
|
50920
|
-
output: "semantic review: could not parse LLM response (fail-open)",
|
|
50921
|
-
durationMs: Date.now() - startTime
|
|
50922
|
-
};
|
|
50923
|
-
}
|
|
50924
|
-
if (opResult.looksLikeFail) {
|
|
50925
|
-
logger?.warn("semantic", "LLM returned truncated JSON with passed:false \u2014 treating as failure", {
|
|
50926
|
-
storyId: story.id
|
|
50927
|
-
});
|
|
50928
|
-
recordSemanticAudit({
|
|
50929
|
-
runtime,
|
|
50930
|
-
workdir,
|
|
50931
|
-
projectDir,
|
|
50932
|
-
storyId: story.id,
|
|
50933
|
-
featureName,
|
|
50934
|
-
parsed: false,
|
|
50935
|
-
looksLikeFail: true,
|
|
50936
|
-
failOpen: false,
|
|
50937
|
-
passed: false,
|
|
50938
|
-
blockingThreshold,
|
|
50939
|
-
result: null
|
|
50940
|
-
});
|
|
50941
|
-
return {
|
|
50942
|
-
check: "semantic",
|
|
50943
|
-
success: false,
|
|
50944
|
-
command: "",
|
|
50945
|
-
exitCode: 1,
|
|
50946
|
-
output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
50947
|
-
durationMs: Date.now() - startTime
|
|
50948
|
-
};
|
|
51250
|
+
return catchDispatchFailure2(err, outcomeCtx);
|
|
50949
51251
|
}
|
|
51252
|
+
if (opResult.failOpen)
|
|
51253
|
+
return handleRetryExhaustedFailOpen2(outcomeCtx);
|
|
51254
|
+
if (opResult.looksLikeFail)
|
|
51255
|
+
return handleTruncatedLooksLikeFail2(outcomeCtx);
|
|
50950
51256
|
if (opResult.repromptEvent) {
|
|
50951
51257
|
runtime.dispatchEvents.emitReviewReprompt({
|
|
50952
51258
|
kind: "review-reprompt-on-drop",
|
|
@@ -50957,138 +51263,35 @@ async function runSemanticReview(opts) {
|
|
|
50957
51263
|
costUsd: opResult.repromptEvent.costUsd
|
|
50958
51264
|
});
|
|
50959
51265
|
}
|
|
50960
|
-
const
|
|
50961
|
-
const allFindings =
|
|
50962
|
-
const acks = opResult.acks;
|
|
50963
|
-
const blockingFindings = allFindings.filter((f) => isBlockingSeverity(f.severity, threshold));
|
|
50964
|
-
const advisoryFindings = allFindings.filter((f) => !isBlockingSeverity(f.severity, threshold));
|
|
51266
|
+
const classification = classifySemanticFindings(opResult, blockingThreshold);
|
|
51267
|
+
const { allFindings, blockingFindings, advisoryFindings } = classification;
|
|
50965
51268
|
if (advisoryFindings.length > 0) {
|
|
50966
|
-
logger?.debug("review", `Semantic review: ${advisoryFindings.length} advisory findings (below threshold '${threshold}')`, {
|
|
51269
|
+
logger?.debug("review", `Semantic review: ${advisoryFindings.length} advisory findings (below threshold '${classification.threshold}')`, {
|
|
50967
51270
|
storyId: story.id,
|
|
50968
51271
|
findings: advisoryFindings.map((f) => ({ severity: f.severity, file: f.file, issue: f.issue }))
|
|
50969
51272
|
});
|
|
50970
51273
|
}
|
|
50971
51274
|
const durationMs = Date.now() - startTime;
|
|
50972
51275
|
if (blockingFindings.length > 0) {
|
|
50973
|
-
|
|
50974
|
-
storyId: story.id,
|
|
50975
|
-
durationMs
|
|
50976
|
-
});
|
|
50977
|
-
logger?.debug("review", "Semantic review findings", {
|
|
50978
|
-
storyId: story.id,
|
|
50979
|
-
findings: blockingFindings.map((f) => ({
|
|
50980
|
-
severity: f.severity,
|
|
50981
|
-
file: f.file,
|
|
50982
|
-
line: f.line,
|
|
50983
|
-
issue: f.issue,
|
|
50984
|
-
suggestion: f.suggestion
|
|
50985
|
-
}))
|
|
50986
|
-
});
|
|
50987
|
-
const output = `Semantic review failed:
|
|
50988
|
-
|
|
50989
|
-
${formatFindings2(blockingFindings)}`;
|
|
50990
|
-
recordSemanticAudit({
|
|
50991
|
-
runtime,
|
|
50992
|
-
workdir,
|
|
50993
|
-
projectDir,
|
|
50994
|
-
storyId: story.id,
|
|
50995
|
-
featureName,
|
|
50996
|
-
parsed: true,
|
|
50997
|
-
failOpen: false,
|
|
50998
|
-
passed: false,
|
|
50999
|
-
blockingThreshold: threshold,
|
|
51000
|
-
acks,
|
|
51001
|
-
result: {
|
|
51002
|
-
passed: false,
|
|
51003
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
51004
|
-
},
|
|
51005
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
51006
|
-
});
|
|
51007
|
-
return {
|
|
51008
|
-
check: "semantic",
|
|
51009
|
-
success: false,
|
|
51010
|
-
command: "",
|
|
51011
|
-
exitCode: 1,
|
|
51012
|
-
output,
|
|
51013
|
-
durationMs,
|
|
51014
|
-
findings: toReviewFindings(blockingFindings, { isTestFile: testFileMatch }),
|
|
51015
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
51016
|
-
cost: llmCost
|
|
51017
|
-
};
|
|
51276
|
+
return buildBlockingFailureResult2(outcomeCtx, classification, durationMs);
|
|
51018
51277
|
}
|
|
51019
51278
|
if (!opResult.passed && allFindings.length === 0) {
|
|
51020
|
-
|
|
51021
|
-
storyId: story.id,
|
|
51022
|
-
durationMs
|
|
51023
|
-
});
|
|
51024
|
-
recordSemanticAudit({
|
|
51025
|
-
runtime,
|
|
51026
|
-
workdir,
|
|
51027
|
-
projectDir,
|
|
51028
|
-
storyId: story.id,
|
|
51029
|
-
featureName,
|
|
51030
|
-
parsed: true,
|
|
51031
|
-
acks,
|
|
51032
|
-
failOpen: false,
|
|
51033
|
-
passed: false,
|
|
51034
|
-
blockingThreshold: threshold,
|
|
51035
|
-
result: { passed: false, findings: [] },
|
|
51036
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
51037
|
-
});
|
|
51038
|
-
return {
|
|
51039
|
-
check: "semantic",
|
|
51040
|
-
success: false,
|
|
51041
|
-
command: "",
|
|
51042
|
-
exitCode: 1,
|
|
51043
|
-
output: 'Semantic review failed: blocking finding(s) were dropped \u2014 acIndex was missing or out of range. The model emitted "passed: false" without valid AC attribution.',
|
|
51044
|
-
durationMs,
|
|
51045
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
51046
|
-
cost: llmCost
|
|
51047
|
-
};
|
|
51279
|
+
return buildAcIndexDroppedFailClosedResult(outcomeCtx, classification, durationMs);
|
|
51048
51280
|
}
|
|
51049
|
-
|
|
51050
|
-
recordSemanticAudit({
|
|
51051
|
-
runtime,
|
|
51052
|
-
workdir,
|
|
51053
|
-
projectDir,
|
|
51054
|
-
storyId: story.id,
|
|
51055
|
-
featureName,
|
|
51056
|
-
parsed: true,
|
|
51057
|
-
acks,
|
|
51058
|
-
failOpen: false,
|
|
51059
|
-
passed: true,
|
|
51060
|
-
blockingThreshold: threshold,
|
|
51061
|
-
result: {
|
|
51062
|
-
passed: true,
|
|
51063
|
-
findings: llmFindingsToReviewFindings(allFindings, { source: "semantic-review", isTestFile: testFileMatch })
|
|
51064
|
-
},
|
|
51065
|
-
advisoryFindings: advisoryFindings.length > 0 ? llmFindingsToReviewFindings(advisoryFindings, { source: "semantic-review", isTestFile: testFileMatch }) : undefined
|
|
51066
|
-
});
|
|
51067
|
-
return {
|
|
51068
|
-
check: "semantic",
|
|
51069
|
-
success: true,
|
|
51070
|
-
command: "",
|
|
51071
|
-
exitCode: 0,
|
|
51072
|
-
output: allFindings.length === 0 ? "Semantic review passed" : "Semantic review passed (all findings were advisory \u2014 below blocking threshold)",
|
|
51073
|
-
durationMs,
|
|
51074
|
-
advisoryFindings: advisoryFindings.length > 0 ? toReviewFindings(advisoryFindings, { isTestFile: testFileMatch }) : undefined,
|
|
51075
|
-
cost: llmCost
|
|
51076
|
-
};
|
|
51281
|
+
return buildPassedResult2(outcomeCtx, classification, durationMs);
|
|
51077
51282
|
}
|
|
51078
51283
|
var _semanticDeps;
|
|
51079
51284
|
var init_semantic = __esm(() => {
|
|
51080
|
-
init_context();
|
|
51081
51285
|
init_debate();
|
|
51082
51286
|
init_errors();
|
|
51083
51287
|
init_logger2();
|
|
51084
51288
|
init_call();
|
|
51085
51289
|
init_semantic_review();
|
|
51086
51290
|
init_prompts();
|
|
51087
|
-
init_finding_projection();
|
|
51088
51291
|
init_prepare_inputs();
|
|
51089
51292
|
init_review_audit();
|
|
51090
51293
|
init_semantic_debate();
|
|
51091
|
-
|
|
51294
|
+
init_semantic_outcomes();
|
|
51092
51295
|
_semanticDeps = {
|
|
51093
51296
|
createDebateRunner: (opts) => new DebateRunner(opts),
|
|
51094
51297
|
writeReviewAudit,
|
|
@@ -51181,68 +51384,155 @@ async function getUncommittedFilesImpl(workdir) {
|
|
|
51181
51384
|
return [];
|
|
51182
51385
|
}
|
|
51183
51386
|
}
|
|
51184
|
-
async function
|
|
51387
|
+
async function guardUncommittedFiles(workdir, storyId, runtime, naxIgnoreIndex, logger) {
|
|
51388
|
+
await autoCommitIfDirty(workdir, "review", "agent", storyId ?? "review", runtime?.dirtyWorktrees);
|
|
51389
|
+
const allUncommittedFiles = await _reviewGitDeps.getUncommittedFiles(workdir);
|
|
51390
|
+
const afterRuntimeFilter = allUncommittedFiles.filter((f) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(f)));
|
|
51391
|
+
const uncommittedFiles = naxIgnoreIndex ? naxIgnoreIndex.filter(afterRuntimeFilter, workdir) : afterRuntimeFilter;
|
|
51392
|
+
if (uncommittedFiles.length > 0) {
|
|
51393
|
+
const fileList = uncommittedFiles.join(", ");
|
|
51394
|
+
logger?.warn("review", `Uncommitted changes detected before review (proceeding): ${fileList}`, {
|
|
51395
|
+
storyId,
|
|
51396
|
+
uncommittedCount: uncommittedFiles.length
|
|
51397
|
+
});
|
|
51398
|
+
}
|
|
51399
|
+
}
|
|
51400
|
+
function buildReviewStory(storyId, story) {
|
|
51401
|
+
return {
|
|
51402
|
+
id: storyId ?? "",
|
|
51403
|
+
title: story?.title ?? "",
|
|
51404
|
+
description: story?.description ?? "",
|
|
51405
|
+
acceptanceCriteria: story?.acceptanceCriteria ?? []
|
|
51406
|
+
};
|
|
51407
|
+
}
|
|
51408
|
+
async function runSemanticCheck(opts) {
|
|
51185
51409
|
const {
|
|
51186
|
-
config: config2,
|
|
51187
51410
|
workdir,
|
|
51188
|
-
|
|
51189
|
-
|
|
51411
|
+
storyGitRef,
|
|
51412
|
+
story,
|
|
51190
51413
|
storyId,
|
|
51414
|
+
config: config2,
|
|
51415
|
+
agentManager,
|
|
51416
|
+
naxConfig,
|
|
51417
|
+
featureName,
|
|
51418
|
+
priorSemanticIterations,
|
|
51419
|
+
featureContextMarkdown,
|
|
51420
|
+
contextBundles,
|
|
51421
|
+
projectDir,
|
|
51422
|
+
naxIgnoreIndex,
|
|
51423
|
+
runtime
|
|
51424
|
+
} = opts;
|
|
51425
|
+
const semanticCfg = config2.semantic ?? {
|
|
51426
|
+
model: "balanced",
|
|
51427
|
+
diffMode: "ref",
|
|
51428
|
+
resetRefOnRerun: false,
|
|
51429
|
+
rules: [],
|
|
51430
|
+
timeoutMs: 600000
|
|
51431
|
+
};
|
|
51432
|
+
const runSemantic = _reviewSemanticDeps.runSemanticReview;
|
|
51433
|
+
return runSemantic({
|
|
51434
|
+
workdir,
|
|
51435
|
+
storyGitRef,
|
|
51436
|
+
story: buildReviewStory(storyId, story),
|
|
51437
|
+
semanticConfig: semanticCfg,
|
|
51438
|
+
agentManager,
|
|
51439
|
+
naxConfig,
|
|
51440
|
+
featureName,
|
|
51441
|
+
priorSemanticIterations,
|
|
51442
|
+
blockingThreshold: config2.blockingThreshold,
|
|
51443
|
+
featureContextMarkdown,
|
|
51444
|
+
contextBundle: contextBundles?.semantic,
|
|
51445
|
+
projectDir,
|
|
51446
|
+
naxIgnoreIndex,
|
|
51447
|
+
runtime
|
|
51448
|
+
});
|
|
51449
|
+
}
|
|
51450
|
+
async function runAdversarialCheck(opts) {
|
|
51451
|
+
const {
|
|
51452
|
+
workdir,
|
|
51191
51453
|
storyGitRef,
|
|
51192
51454
|
story,
|
|
51455
|
+
storyId,
|
|
51456
|
+
config: config2,
|
|
51193
51457
|
agentManager,
|
|
51194
51458
|
naxConfig,
|
|
51195
|
-
retrySkipChecks,
|
|
51196
51459
|
featureName,
|
|
51197
51460
|
priorFailures,
|
|
51198
|
-
priorSemanticIterations,
|
|
51199
51461
|
featureContextMarkdown,
|
|
51200
51462
|
contextBundles,
|
|
51201
51463
|
projectDir,
|
|
51202
|
-
env: env2,
|
|
51203
51464
|
naxIgnoreIndex,
|
|
51204
51465
|
runtime,
|
|
51205
51466
|
priorAdversarialIterations
|
|
51206
51467
|
} = opts;
|
|
51468
|
+
const adversarialCfg = config2.adversarial ?? {
|
|
51469
|
+
model: "balanced",
|
|
51470
|
+
diffMode: "ref",
|
|
51471
|
+
rules: [],
|
|
51472
|
+
timeoutMs: 600000,
|
|
51473
|
+
parallel: false,
|
|
51474
|
+
maxConcurrentSessions: 2
|
|
51475
|
+
};
|
|
51476
|
+
const runAdversarial = _reviewAdversarialDeps.runAdversarialReview;
|
|
51477
|
+
return runAdversarial({
|
|
51478
|
+
workdir,
|
|
51479
|
+
storyGitRef,
|
|
51480
|
+
story: buildReviewStory(storyId, story),
|
|
51481
|
+
adversarialConfig: adversarialCfg,
|
|
51482
|
+
agentManager,
|
|
51483
|
+
config: naxConfig,
|
|
51484
|
+
featureName,
|
|
51485
|
+
priorFailures,
|
|
51486
|
+
blockingThreshold: config2.blockingThreshold,
|
|
51487
|
+
featureContextMarkdown,
|
|
51488
|
+
contextBundle: contextBundles?.adversarial,
|
|
51489
|
+
projectDir,
|
|
51490
|
+
naxIgnoreIndex,
|
|
51491
|
+
runtime,
|
|
51492
|
+
priorAdversarialIterations
|
|
51493
|
+
});
|
|
51494
|
+
}
|
|
51495
|
+
async function runMechanicalCheck(checkName, opts) {
|
|
51496
|
+
const {
|
|
51497
|
+
config: config2,
|
|
51498
|
+
workdir,
|
|
51499
|
+
executionConfig,
|
|
51500
|
+
qualityCommands,
|
|
51501
|
+
storyId,
|
|
51502
|
+
story,
|
|
51503
|
+
storyGitRef,
|
|
51504
|
+
naxConfig,
|
|
51505
|
+
projectDir,
|
|
51506
|
+
env: env2,
|
|
51507
|
+
naxIgnoreIndex
|
|
51508
|
+
} = opts;
|
|
51509
|
+
const command = await resolveCommand(checkName, config2, executionConfig, workdir, qualityCommands);
|
|
51510
|
+
if (command === null) {
|
|
51511
|
+
getSafeLogger()?.warn("review", `Skipping ${checkName} check (command not configured or disabled)`);
|
|
51512
|
+
return null;
|
|
51513
|
+
}
|
|
51514
|
+
return checkName === "lint" ? await _reviewLintDeps.runScopedLintCheck({
|
|
51515
|
+
resolvedLintCommand: command,
|
|
51516
|
+
configCommands: config2.commands,
|
|
51517
|
+
qualityCommands,
|
|
51518
|
+
lintOutputFormat: naxConfig?.quality?.lintOutput?.format ?? "auto",
|
|
51519
|
+
workdir,
|
|
51520
|
+
projectDir,
|
|
51521
|
+
storyId,
|
|
51522
|
+
story,
|
|
51523
|
+
storyGitRef,
|
|
51524
|
+
env: env2,
|
|
51525
|
+
stripEnvVars: naxConfig?.quality?.stripEnvVars ?? [],
|
|
51526
|
+
naxIgnoreIndex
|
|
51527
|
+
}) : normalizeMechanicalFindings(checkName, await runCheck(checkName, command, workdir, storyId, env2, naxConfig?.quality?.stripEnvVars ?? []), workdir);
|
|
51528
|
+
}
|
|
51529
|
+
async function runReview(opts) {
|
|
51530
|
+
const { config: config2, workdir, storyId, retrySkipChecks, naxIgnoreIndex, runtime } = opts;
|
|
51207
51531
|
const startTime = Date.now();
|
|
51208
51532
|
const logger = getSafeLogger();
|
|
51209
51533
|
const checks3 = [];
|
|
51210
51534
|
let firstFailure;
|
|
51211
|
-
await
|
|
51212
|
-
const allUncommittedFiles = await _reviewGitDeps.getUncommittedFiles(workdir);
|
|
51213
|
-
const NAX_RUNTIME_PATTERNS = [
|
|
51214
|
-
/nax\.lock$/,
|
|
51215
|
-
/nax\/metrics\.json$/,
|
|
51216
|
-
/nax\/status\.json$/,
|
|
51217
|
-
/nax\/features\/[^/]+\/status\.json$/,
|
|
51218
|
-
/nax\/features\/[^/]+\/prd\.json$/,
|
|
51219
|
-
/nax\/features\/[^/]+\/runs\//,
|
|
51220
|
-
/nax\/features\/[^/]+\/plan\//,
|
|
51221
|
-
/nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
51222
|
-
/nax\/features\/[^/]+\/interactions\//,
|
|
51223
|
-
/nax\/features\/[^/]+\/progress\.txt$/,
|
|
51224
|
-
/nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
51225
|
-
/nax\/features\/[^/]+\/stories\/[^/]+\/context-manifest-[^/]+\.json$/,
|
|
51226
|
-
/nax\/features\/[^/]+\/stories\/[^/]+\/rebuild-manifest\.json$/,
|
|
51227
|
-
/\.nax-verifier-verdict\.json$/,
|
|
51228
|
-
/\.nax-pids$/,
|
|
51229
|
-
/\.nax-wt\//,
|
|
51230
|
-
/\.nax-acceptance[^/]*$/,
|
|
51231
|
-
/_nax_acceptance_test\.py$/,
|
|
51232
|
-
/_nax_suggested_test\.py$/,
|
|
51233
|
-
/(?:^|\/)test\/.*\.jsonl$/,
|
|
51234
|
-
/(?:^|\/)coverage\//,
|
|
51235
|
-
/\.lcov$/
|
|
51236
|
-
];
|
|
51237
|
-
const afterRuntimeFilter = allUncommittedFiles.filter((f) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(f)));
|
|
51238
|
-
const uncommittedFiles = naxIgnoreIndex ? naxIgnoreIndex.filter(afterRuntimeFilter, workdir) : afterRuntimeFilter;
|
|
51239
|
-
if (uncommittedFiles.length > 0) {
|
|
51240
|
-
const fileList = uncommittedFiles.join(", ");
|
|
51241
|
-
logger?.warn("review", `Uncommitted changes detected before review (proceeding): ${fileList}`, {
|
|
51242
|
-
storyId,
|
|
51243
|
-
uncommittedCount: uncommittedFiles.length
|
|
51244
|
-
});
|
|
51245
|
-
}
|
|
51535
|
+
await guardUncommittedFiles(workdir, storyId, runtime, naxIgnoreIndex, logger);
|
|
51246
51536
|
for (const checkName of config2.checks) {
|
|
51247
51537
|
if (retrySkipChecks?.has(checkName)) {
|
|
51248
51538
|
getSafeLogger()?.debug("review", `Skipping ${checkName} check (already passed in previous review pass)`, {
|
|
@@ -51250,37 +51540,8 @@ async function runReview(opts) {
|
|
|
51250
51540
|
});
|
|
51251
51541
|
continue;
|
|
51252
51542
|
}
|
|
51253
|
-
if (checkName === "semantic") {
|
|
51254
|
-
const
|
|
51255
|
-
id: storyId ?? "",
|
|
51256
|
-
title: story?.title ?? "",
|
|
51257
|
-
description: story?.description ?? "",
|
|
51258
|
-
acceptanceCriteria: story?.acceptanceCriteria ?? []
|
|
51259
|
-
};
|
|
51260
|
-
const semanticCfg = config2.semantic ?? {
|
|
51261
|
-
model: "balanced",
|
|
51262
|
-
diffMode: "ref",
|
|
51263
|
-
resetRefOnRerun: false,
|
|
51264
|
-
rules: [],
|
|
51265
|
-
timeoutMs: 600000
|
|
51266
|
-
};
|
|
51267
|
-
const runSemantic = _reviewSemanticDeps.runSemanticReview;
|
|
51268
|
-
const result2 = await runSemantic({
|
|
51269
|
-
workdir,
|
|
51270
|
-
storyGitRef,
|
|
51271
|
-
story: semanticStory,
|
|
51272
|
-
semanticConfig: semanticCfg,
|
|
51273
|
-
agentManager,
|
|
51274
|
-
naxConfig,
|
|
51275
|
-
featureName,
|
|
51276
|
-
priorSemanticIterations,
|
|
51277
|
-
blockingThreshold: config2.blockingThreshold,
|
|
51278
|
-
featureContextMarkdown,
|
|
51279
|
-
contextBundle: contextBundles?.semantic,
|
|
51280
|
-
projectDir,
|
|
51281
|
-
naxIgnoreIndex,
|
|
51282
|
-
runtime
|
|
51283
|
-
});
|
|
51543
|
+
if (checkName === "semantic" || checkName === "adversarial") {
|
|
51544
|
+
const result2 = checkName === "semantic" ? await runSemanticCheck(opts) : await runAdversarialCheck(opts);
|
|
51284
51545
|
checks3.push(result2);
|
|
51285
51546
|
if (!result2.success && !firstFailure) {
|
|
51286
51547
|
firstFailure = `${checkName} failed`;
|
|
@@ -51290,67 +51551,9 @@ async function runReview(opts) {
|
|
|
51290
51551
|
}
|
|
51291
51552
|
continue;
|
|
51292
51553
|
}
|
|
51293
|
-
|
|
51294
|
-
|
|
51295
|
-
id: storyId ?? "",
|
|
51296
|
-
title: story?.title ?? "",
|
|
51297
|
-
description: story?.description ?? "",
|
|
51298
|
-
acceptanceCriteria: story?.acceptanceCriteria ?? []
|
|
51299
|
-
};
|
|
51300
|
-
const adversarialCfg = config2.adversarial ?? {
|
|
51301
|
-
model: "balanced",
|
|
51302
|
-
diffMode: "ref",
|
|
51303
|
-
rules: [],
|
|
51304
|
-
timeoutMs: 600000,
|
|
51305
|
-
parallel: false,
|
|
51306
|
-
maxConcurrentSessions: 2
|
|
51307
|
-
};
|
|
51308
|
-
const runAdversarial = _reviewAdversarialDeps.runAdversarialReview;
|
|
51309
|
-
const result2 = await runAdversarial({
|
|
51310
|
-
workdir,
|
|
51311
|
-
storyGitRef,
|
|
51312
|
-
story: adversarialStory,
|
|
51313
|
-
adversarialConfig: adversarialCfg,
|
|
51314
|
-
agentManager,
|
|
51315
|
-
config: naxConfig,
|
|
51316
|
-
featureName,
|
|
51317
|
-
priorFailures,
|
|
51318
|
-
blockingThreshold: config2.blockingThreshold,
|
|
51319
|
-
featureContextMarkdown,
|
|
51320
|
-
contextBundle: contextBundles?.adversarial,
|
|
51321
|
-
projectDir,
|
|
51322
|
-
naxIgnoreIndex,
|
|
51323
|
-
runtime,
|
|
51324
|
-
priorAdversarialIterations
|
|
51325
|
-
});
|
|
51326
|
-
checks3.push(result2);
|
|
51327
|
-
if (!result2.success && !firstFailure) {
|
|
51328
|
-
firstFailure = `${checkName} failed`;
|
|
51329
|
-
}
|
|
51330
|
-
if (!result2.success) {
|
|
51331
|
-
break;
|
|
51332
|
-
}
|
|
51333
|
-
continue;
|
|
51334
|
-
}
|
|
51335
|
-
const command = await resolveCommand(checkName, config2, executionConfig, workdir, qualityCommands);
|
|
51336
|
-
if (command === null) {
|
|
51337
|
-
getSafeLogger()?.warn("review", `Skipping ${checkName} check (command not configured or disabled)`);
|
|
51554
|
+
const result = await runMechanicalCheck(checkName, opts);
|
|
51555
|
+
if (result === null)
|
|
51338
51556
|
continue;
|
|
51339
|
-
}
|
|
51340
|
-
const result = checkName === "lint" ? await _reviewLintDeps.runScopedLintCheck({
|
|
51341
|
-
resolvedLintCommand: command,
|
|
51342
|
-
configCommands: config2.commands,
|
|
51343
|
-
qualityCommands,
|
|
51344
|
-
lintOutputFormat: naxConfig?.quality?.lintOutput?.format ?? "auto",
|
|
51345
|
-
workdir,
|
|
51346
|
-
projectDir,
|
|
51347
|
-
storyId,
|
|
51348
|
-
story,
|
|
51349
|
-
storyGitRef,
|
|
51350
|
-
env: env2,
|
|
51351
|
-
stripEnvVars: naxConfig?.quality?.stripEnvVars ?? [],
|
|
51352
|
-
naxIgnoreIndex
|
|
51353
|
-
}) : normalizeMechanicalFindings(checkName, await runCheck(checkName, command, workdir, storyId, env2, naxConfig?.quality?.stripEnvVars ?? []), workdir);
|
|
51354
51557
|
checks3.push(result);
|
|
51355
51558
|
if (result.success) {
|
|
51356
51559
|
logger?.info("review", `${checkName} passed`, {
|
|
@@ -51379,7 +51582,7 @@ async function runReview(opts) {
|
|
|
51379
51582
|
failureReason: firstFailure
|
|
51380
51583
|
};
|
|
51381
51584
|
}
|
|
51382
|
-
var _reviewSemanticDeps, _reviewAdversarialDeps, _reviewLintDeps, _reviewRunnerDeps, _reviewGitDeps;
|
|
51585
|
+
var _reviewSemanticDeps, _reviewAdversarialDeps, _reviewLintDeps, _reviewRunnerDeps, _reviewGitDeps, NAX_RUNTIME_PATTERNS;
|
|
51383
51586
|
var init_runner2 = __esm(() => {
|
|
51384
51587
|
init_logger2();
|
|
51385
51588
|
init_quality();
|
|
@@ -51405,6 +51608,30 @@ var init_runner2 = __esm(() => {
|
|
|
51405
51608
|
_reviewGitDeps = {
|
|
51406
51609
|
getUncommittedFiles: getUncommittedFilesImpl
|
|
51407
51610
|
};
|
|
51611
|
+
NAX_RUNTIME_PATTERNS = [
|
|
51612
|
+
/nax\.lock$/,
|
|
51613
|
+
/nax\/metrics\.json$/,
|
|
51614
|
+
/nax\/status\.json$/,
|
|
51615
|
+
/nax\/features\/[^/]+\/status\.json$/,
|
|
51616
|
+
/nax\/features\/[^/]+\/prd\.json$/,
|
|
51617
|
+
/nax\/features\/[^/]+\/runs\//,
|
|
51618
|
+
/nax\/features\/[^/]+\/plan\//,
|
|
51619
|
+
/nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
51620
|
+
/nax\/features\/[^/]+\/interactions\//,
|
|
51621
|
+
/nax\/features\/[^/]+\/progress\.txt$/,
|
|
51622
|
+
/nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
51623
|
+
/nax\/features\/[^/]+\/stories\/[^/]+\/context-manifest-[^/]+\.json$/,
|
|
51624
|
+
/nax\/features\/[^/]+\/stories\/[^/]+\/rebuild-manifest\.json$/,
|
|
51625
|
+
/\.nax-verifier-verdict\.json$/,
|
|
51626
|
+
/\.nax-pids$/,
|
|
51627
|
+
/\.nax-wt\//,
|
|
51628
|
+
/\.nax-acceptance[^/]*$/,
|
|
51629
|
+
/_nax_acceptance_test\.py$/,
|
|
51630
|
+
/_nax_suggested_test\.py$/,
|
|
51631
|
+
/(?:^|\/)test\/.*\.jsonl$/,
|
|
51632
|
+
/(?:^|\/)coverage\//,
|
|
51633
|
+
/\.lcov$/
|
|
51634
|
+
];
|
|
51408
51635
|
});
|
|
51409
51636
|
|
|
51410
51637
|
// src/review/index.ts
|
|
@@ -54284,18 +54511,18 @@ class PromptAuditor {
|
|
|
54284
54511
|
}
|
|
54285
54512
|
this._dirCreated = true;
|
|
54286
54513
|
}
|
|
54514
|
+
const safeEntry = redactSecrets(entry);
|
|
54287
54515
|
try {
|
|
54288
|
-
await _promptAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(
|
|
54516
|
+
await _promptAuditorDeps.appendLine(this._jsonlPath, `${JSON.stringify(safeEntry)}
|
|
54289
54517
|
`);
|
|
54290
54518
|
} catch (err) {
|
|
54291
54519
|
throw tagAuditError(err, "jsonl");
|
|
54292
54520
|
}
|
|
54293
54521
|
if (!("prompt" in entry) || !("response" in entry))
|
|
54294
54522
|
return;
|
|
54295
|
-
const
|
|
54296
|
-
const filename = deriveTxtFilename(auditEntry);
|
|
54523
|
+
const filename = deriveTxtFilename(entry);
|
|
54297
54524
|
try {
|
|
54298
|
-
await _promptAuditorDeps.write(join37(this._featureDir, filename), buildTxtContent(
|
|
54525
|
+
await _promptAuditorDeps.write(join37(this._featureDir, filename), buildTxtContent(safeEntry));
|
|
54299
54526
|
} catch (err) {
|
|
54300
54527
|
throw tagAuditError(err, "txt");
|
|
54301
54528
|
}
|
|
@@ -60089,7 +60316,7 @@ async function checkWorkingTreeClean(workdir) {
|
|
|
60089
60316
|
const exitCode = await proc.exited;
|
|
60090
60317
|
const lines = output.trim() === "" ? [] : output.split(`
|
|
60091
60318
|
`).filter(Boolean);
|
|
60092
|
-
const nonNaxDirtyFiles = lines.filter((line) => !
|
|
60319
|
+
const nonNaxDirtyFiles = lines.filter((line) => !NAX_RUNTIME_PATTERNS2.some((pattern) => pattern.test(line)));
|
|
60093
60320
|
const passed = exitCode === 0 && nonNaxDirtyFiles.length === 0;
|
|
60094
60321
|
return {
|
|
60095
60322
|
name: "working-tree-clean",
|
|
@@ -60120,9 +60347,9 @@ async function checkGitUserConfigured(workdir) {
|
|
|
60120
60347
|
message: passed ? "Git user is configured" : !hasName && !hasEmail ? "Git user.name and user.email not configured" : !hasName ? "Git user.name not configured" : "Git user.email not configured"
|
|
60121
60348
|
};
|
|
60122
60349
|
}
|
|
60123
|
-
var
|
|
60350
|
+
var NAX_RUNTIME_PATTERNS2;
|
|
60124
60351
|
var init_checks_git = __esm(() => {
|
|
60125
|
-
|
|
60352
|
+
NAX_RUNTIME_PATTERNS2 = [
|
|
60126
60353
|
/^.{2} nax\.lock$/,
|
|
60127
60354
|
/^.{2} \.nax\/$/,
|
|
60128
60355
|
/^.{2} \.nax\/metrics\.json$/,
|
|
@@ -66527,7 +66754,7 @@ function assemblePlanInputs(story, config2, resolvedTestPatterns) {
|
|
|
66527
66754
|
function hasReviewEscalation(story) {
|
|
66528
66755
|
return (story.priorFailures ?? []).some((f) => f.stage === "review");
|
|
66529
66756
|
}
|
|
66530
|
-
function
|
|
66757
|
+
function buildFeatureCtxBlock3(ctx, role) {
|
|
66531
66758
|
const bundleMarkdown = ctx.contextBundle?.pushMarkdown.trim();
|
|
66532
66759
|
if (bundleMarkdown) {
|
|
66533
66760
|
return `${bundleMarkdown}
|
|
@@ -66628,7 +66855,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
66628
66855
|
stat: prepared.stat,
|
|
66629
66856
|
diff: prepared.diff,
|
|
66630
66857
|
excludePatterns: prepared.excludePatterns,
|
|
66631
|
-
featureCtxBlock:
|
|
66858
|
+
featureCtxBlock: buildFeatureCtxBlock3(ctx, "reviewer-semantic"),
|
|
66632
66859
|
resolvedTestPatterns,
|
|
66633
66860
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
66634
66861
|
_refresh: {
|
|
@@ -66665,7 +66892,7 @@ async function assemblePlanInputsFromCtx(ctx) {
|
|
|
66665
66892
|
excludePatterns: prepared.excludePatterns,
|
|
66666
66893
|
testGlobs: prepared.testGlobs,
|
|
66667
66894
|
refExcludePatterns: prepared.refExcludePatterns,
|
|
66668
|
-
featureCtxBlock:
|
|
66895
|
+
featureCtxBlock: buildFeatureCtxBlock3(ctx, "reviewer-adversarial"),
|
|
66669
66896
|
resolvedTestPatterns,
|
|
66670
66897
|
blockingThreshold: ctx.config.review.blockingThreshold,
|
|
66671
66898
|
_refresh: {
|
|
@@ -71426,7 +71653,6 @@ var init_types11 = __esm(() => {
|
|
|
71426
71653
|
});
|
|
71427
71654
|
|
|
71428
71655
|
// src/worktree/dependencies.ts
|
|
71429
|
-
import { existsSync as existsSync20 } from "fs";
|
|
71430
71656
|
import { join as join66 } from "path";
|
|
71431
71657
|
async function prepareWorktreeDependencies(options) {
|
|
71432
71658
|
const mode = options.config.execution.worktreeDependencies.mode;
|
|
@@ -71434,8 +71660,6 @@ async function prepareWorktreeDependencies(options) {
|
|
|
71434
71660
|
switch (mode) {
|
|
71435
71661
|
case "off":
|
|
71436
71662
|
return { cwd: resolvedCwd };
|
|
71437
|
-
case "inherit":
|
|
71438
|
-
return resolveInheritedDependencies(options, resolvedCwd);
|
|
71439
71663
|
case "provision":
|
|
71440
71664
|
return provisionDependencies(options.config, options.worktreeRoot, resolvedCwd);
|
|
71441
71665
|
}
|
|
@@ -71443,20 +71667,10 @@ async function prepareWorktreeDependencies(options) {
|
|
|
71443
71667
|
function resolveDependencyCwd(options) {
|
|
71444
71668
|
return options.storyWorkdir ? join66(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
|
|
71445
71669
|
}
|
|
71446
|
-
function resolveInheritedDependencies(options, resolvedCwd) {
|
|
71447
|
-
if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
|
|
71448
|
-
throw new WorktreeDependencyPreparationError(`[worktree-deps] inherit mode is unsupported for dependency-managed worktrees in phase 1. Use mode "provision" with execution.worktreeDependencies.setupCommand, or switch to "off".`, "inherit");
|
|
71449
|
-
}
|
|
71450
|
-
return { cwd: resolvedCwd };
|
|
71451
|
-
}
|
|
71452
|
-
function hasDependencyManifests(worktreeRoot, resolvedCwd) {
|
|
71453
|
-
const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
|
|
71454
|
-
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join66(directory, filename))));
|
|
71455
|
-
}
|
|
71456
71670
|
async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
|
|
71457
71671
|
const setupCommand = config2.execution.worktreeDependencies.setupCommand;
|
|
71458
71672
|
if (!setupCommand) {
|
|
71459
|
-
throw new WorktreeDependencyPreparationError("[worktree-deps] provision mode requires execution.worktreeDependencies.setupCommand
|
|
71673
|
+
throw new WorktreeDependencyPreparationError("[worktree-deps] provision mode requires execution.worktreeDependencies.setupCommand.", "provision");
|
|
71460
71674
|
}
|
|
71461
71675
|
const argv = parseCommandToArgv(setupCommand);
|
|
71462
71676
|
if (argv.length === 0) {
|
|
@@ -71479,36 +71693,62 @@ async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
|
|
|
71479
71693
|
}
|
|
71480
71694
|
return { cwd: resolvedCwd };
|
|
71481
71695
|
}
|
|
71482
|
-
var
|
|
71696
|
+
var _worktreeDependencyDeps;
|
|
71483
71697
|
var init_dependencies = __esm(() => {
|
|
71484
71698
|
init_bun_deps();
|
|
71485
71699
|
init_command_argv();
|
|
71486
71700
|
init_types11();
|
|
71487
|
-
PHASE_ONE_INHERIT_UNSUPPORTED_FILES = [
|
|
71488
|
-
"package.json",
|
|
71489
|
-
"bun.lock",
|
|
71490
|
-
"bun.lockb",
|
|
71491
|
-
"package-lock.json",
|
|
71492
|
-
"pnpm-lock.yaml",
|
|
71493
|
-
"yarn.lock",
|
|
71494
|
-
"requirements.txt",
|
|
71495
|
-
"pyproject.toml",
|
|
71496
|
-
"Cargo.toml",
|
|
71497
|
-
"go.mod",
|
|
71498
|
-
"Gemfile",
|
|
71499
|
-
"composer.json",
|
|
71500
|
-
"pom.xml",
|
|
71501
|
-
"build.gradle",
|
|
71502
|
-
"build.gradle.kts"
|
|
71503
|
-
];
|
|
71504
71701
|
_worktreeDependencyDeps = {
|
|
71505
|
-
existsSync: existsSync20,
|
|
71506
71702
|
spawn
|
|
71507
71703
|
};
|
|
71508
71704
|
});
|
|
71509
71705
|
|
|
71510
71706
|
// src/utils/gitignore.ts
|
|
71511
|
-
|
|
71707
|
+
function activeIgnoreLines(content) {
|
|
71708
|
+
return content.split(`
|
|
71709
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
71710
|
+
}
|
|
71711
|
+
function hasOpinionOn(activeLines, entry) {
|
|
71712
|
+
return activeLines.has(entry) || activeLines.has(`!${entry}`);
|
|
71713
|
+
}
|
|
71714
|
+
async function patchIgnoreFile(filePath, entries, options = {}) {
|
|
71715
|
+
const { footer = "", sectionComment = "# nax - generated files" } = options;
|
|
71716
|
+
const header = options.header ?? `${sectionComment}
|
|
71717
|
+
`;
|
|
71718
|
+
const file3 = Bun.file(filePath);
|
|
71719
|
+
const existing = await file3.exists() ? await file3.text() : "";
|
|
71720
|
+
const isNew = existing.trim().length === 0;
|
|
71721
|
+
const active = new Set(activeIgnoreLines(existing));
|
|
71722
|
+
const missing = entries.filter((entry) => !hasOpinionOn(active, entry));
|
|
71723
|
+
if (missing.length === 0)
|
|
71724
|
+
return { created: false, added: [] };
|
|
71725
|
+
if (isNew) {
|
|
71726
|
+
await Bun.write(filePath, `${header}${missing.join(`
|
|
71727
|
+
`)}
|
|
71728
|
+
${footer}`);
|
|
71729
|
+
return { created: true, added: [...missing] };
|
|
71730
|
+
}
|
|
71731
|
+
const separator = existing.endsWith(`
|
|
71732
|
+
`) ? "" : `
|
|
71733
|
+
`;
|
|
71734
|
+
await Bun.write(filePath, `${existing}${separator}
|
|
71735
|
+
${sectionComment}
|
|
71736
|
+
${missing.join(`
|
|
71737
|
+
`)}
|
|
71738
|
+
`);
|
|
71739
|
+
return { created: false, added: [...missing] };
|
|
71740
|
+
}
|
|
71741
|
+
var NAX_GITIGNORE_ENTRIES, NAX_NAXIGNORE_ENTRIES, NAX_NAXIGNORE_HEADER = `# nax - paths excluded from context, review
|
|
71742
|
+
# and verification scanning.
|
|
71743
|
+
# gitignore syntax. Also honoured per-package.
|
|
71744
|
+
|
|
71745
|
+
`, NAX_NAXIGNORE_SUGGESTIONS = `
|
|
71746
|
+
# Uncomment what applies to this repo:
|
|
71747
|
+
# examples/
|
|
71748
|
+
# fixtures/
|
|
71749
|
+
# vendor/
|
|
71750
|
+
# *.generated.*
|
|
71751
|
+
`;
|
|
71512
71752
|
var init_gitignore = __esm(() => {
|
|
71513
71753
|
init_config();
|
|
71514
71754
|
NAX_GITIGNORE_ENTRIES = [
|
|
@@ -71533,10 +71773,17 @@ var init_gitignore = __esm(() => {
|
|
|
71533
71773
|
".nax/finish-audit/",
|
|
71534
71774
|
".nax/mutation-journal/"
|
|
71535
71775
|
];
|
|
71776
|
+
NAX_NAXIGNORE_ENTRIES = [
|
|
71777
|
+
".nax/",
|
|
71778
|
+
"dist/",
|
|
71779
|
+
"build/",
|
|
71780
|
+
"coverage/",
|
|
71781
|
+
"node_modules/"
|
|
71782
|
+
];
|
|
71536
71783
|
});
|
|
71537
71784
|
|
|
71538
71785
|
// src/worktree/manager.ts
|
|
71539
|
-
import { existsSync as
|
|
71786
|
+
import { existsSync as existsSync20, symlinkSync } from "fs";
|
|
71540
71787
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
71541
71788
|
import { join as join67 } from "path";
|
|
71542
71789
|
|
|
@@ -71548,7 +71795,7 @@ class WorktreeManager {
|
|
|
71548
71795
|
try {
|
|
71549
71796
|
await mkdir12(infoDir, { recursive: true });
|
|
71550
71797
|
let existing = "";
|
|
71551
|
-
if (
|
|
71798
|
+
if (existsSync20(excludePath)) {
|
|
71552
71799
|
existing = await Bun.file(excludePath).text();
|
|
71553
71800
|
}
|
|
71554
71801
|
const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
@@ -71635,7 +71882,7 @@ ${missing.join(`
|
|
|
71635
71882
|
});
|
|
71636
71883
|
}
|
|
71637
71884
|
const envSource = join67(projectRoot, ".env");
|
|
71638
|
-
if (
|
|
71885
|
+
if (existsSync20(envSource)) {
|
|
71639
71886
|
const envTarget = join67(worktreePath, ".env");
|
|
71640
71887
|
try {
|
|
71641
71888
|
symlinkSync(envSource, envTarget, "file");
|
|
@@ -72575,7 +72822,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
72575
72822
|
});
|
|
72576
72823
|
|
|
72577
72824
|
// src/execution/iteration-runner.ts
|
|
72578
|
-
import { existsSync as
|
|
72825
|
+
import { existsSync as existsSync21 } from "fs";
|
|
72579
72826
|
import { join as join70 } from "path";
|
|
72580
72827
|
function releaseHeavyPipelineContext(ctx) {
|
|
72581
72828
|
ctx.agentResult = undefined;
|
|
@@ -72792,7 +73039,7 @@ var init_iteration_runner = __esm(() => {
|
|
|
72792
73039
|
loadConfigForWorkdir,
|
|
72793
73040
|
prepareWorktreeDependencies,
|
|
72794
73041
|
runPipeline,
|
|
72795
|
-
existsSync:
|
|
73042
|
+
existsSync: existsSync21,
|
|
72796
73043
|
worktreeManager: new WorktreeManager
|
|
72797
73044
|
};
|
|
72798
73045
|
});
|
|
@@ -75550,7 +75797,14 @@ async function defaultReadResult(resultPath) {
|
|
|
75550
75797
|
const f = Bun.file(resultPath);
|
|
75551
75798
|
if (!await f.exists())
|
|
75552
75799
|
return null;
|
|
75553
|
-
|
|
75800
|
+
try {
|
|
75801
|
+
return JSON.parse(await f.text());
|
|
75802
|
+
} catch (err) {
|
|
75803
|
+
getSafeLogger()?.warn("plugins", `nax-finish: malformed result file at ${resultPath}`, {
|
|
75804
|
+
error: errorMessage(err)
|
|
75805
|
+
});
|
|
75806
|
+
return null;
|
|
75807
|
+
}
|
|
75554
75808
|
}
|
|
75555
75809
|
async function defaultClearResult(resultPath) {
|
|
75556
75810
|
const file3 = Bun.file(resultPath);
|
|
@@ -75741,6 +75995,7 @@ async function finalizeFinishOutcome(options) {
|
|
|
75741
75995
|
}
|
|
75742
75996
|
var PLUGIN_NAME4 = "nax-finish", PLUGIN_VERSION4 = "0.1.0", PACKAGE_ROOT_SEARCH_DEPTH = 6, _naxFinishDeps, naxFinishAction, naxFinishPlugin;
|
|
75743
75997
|
var init_nax_finish = __esm(() => {
|
|
75998
|
+
init_logger2();
|
|
75744
75999
|
init_config2();
|
|
75745
76000
|
init_telegram2();
|
|
75746
76001
|
init_telegram2();
|
|
@@ -77353,12 +77608,19 @@ function resolveModulePath(modulePath, projectRoot) {
|
|
|
77353
77608
|
}
|
|
77354
77609
|
return path25.resolve(modulePath);
|
|
77355
77610
|
}
|
|
77356
|
-
async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots
|
|
77611
|
+
async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots, originalPath) {
|
|
77357
77612
|
let attemptedPath = initialModulePath;
|
|
77358
77613
|
try {
|
|
77359
77614
|
let modulePath = initialModulePath;
|
|
77360
77615
|
const isFilePath = modulePath.startsWith("/") || modulePath.startsWith("./") || modulePath.startsWith("../");
|
|
77361
|
-
if (isFilePath
|
|
77616
|
+
if (isFilePath) {
|
|
77617
|
+
if (allowedRoots.length === 0) {
|
|
77618
|
+
const msg = `no allowed roots configured for file-path plugin module '${initialModulePath}'`;
|
|
77619
|
+
const logger = getSafeLogger6();
|
|
77620
|
+
logger?.error("plugins", `Security: ${msg}`);
|
|
77621
|
+
_pluginErrorSink(`[plugins] Security: ${msg}`);
|
|
77622
|
+
return null;
|
|
77623
|
+
}
|
|
77362
77624
|
const validation = validateModulePath(modulePath, allowedRoots);
|
|
77363
77625
|
if (!validation.valid) {
|
|
77364
77626
|
const logger = getSafeLogger6();
|
|
@@ -77632,14 +77894,6 @@ var init_status_writer = __esm(() => {
|
|
|
77632
77894
|
});
|
|
77633
77895
|
|
|
77634
77896
|
// src/cli/init-context.ts
|
|
77635
|
-
var exports_init_context = {};
|
|
77636
|
-
__export(exports_init_context, {
|
|
77637
|
-
scanProject: () => scanProject,
|
|
77638
|
-
initPackage: () => initPackage,
|
|
77639
|
-
initContext: () => initContext,
|
|
77640
|
-
generatePackageContextTemplate: () => generatePackageContextTemplate,
|
|
77641
|
-
generateContextTemplate: () => generateContextTemplate
|
|
77642
|
-
});
|
|
77643
77897
|
import { basename as basename16, join as join78 } from "path";
|
|
77644
77898
|
async function bunFileExists(path26) {
|
|
77645
77899
|
return Bun.file(path26).exists();
|
|
@@ -77893,11 +78147,11 @@ var init_init_context = __esm(() => {
|
|
|
77893
78147
|
});
|
|
77894
78148
|
|
|
77895
78149
|
// src/cli/init-detect.ts
|
|
77896
|
-
import { existsSync as
|
|
78150
|
+
import { existsSync as existsSync22, readFileSync } from "fs";
|
|
77897
78151
|
import { join as join79 } from "path";
|
|
77898
78152
|
function readPackageJson(projectRoot) {
|
|
77899
78153
|
const pkgPath = join79(projectRoot, "package.json");
|
|
77900
|
-
if (!
|
|
78154
|
+
if (!existsSync22(pkgPath))
|
|
77901
78155
|
return;
|
|
77902
78156
|
try {
|
|
77903
78157
|
return JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
@@ -77939,41 +78193,41 @@ function detectStack(projectRoot) {
|
|
|
77939
78193
|
};
|
|
77940
78194
|
}
|
|
77941
78195
|
function detectRuntime(projectRoot) {
|
|
77942
|
-
if (
|
|
78196
|
+
if (existsSync22(join79(projectRoot, "bun.lockb")) || existsSync22(join79(projectRoot, "bunfig.toml"))) {
|
|
77943
78197
|
return "bun";
|
|
77944
78198
|
}
|
|
77945
|
-
if (
|
|
78199
|
+
if (existsSync22(join79(projectRoot, "package-lock.json")) || existsSync22(join79(projectRoot, "yarn.lock")) || existsSync22(join79(projectRoot, "pnpm-lock.yaml"))) {
|
|
77946
78200
|
return "node";
|
|
77947
78201
|
}
|
|
77948
78202
|
return "unknown";
|
|
77949
78203
|
}
|
|
77950
78204
|
function detectLanguage2(projectRoot) {
|
|
77951
|
-
if (
|
|
78205
|
+
if (existsSync22(join79(projectRoot, "tsconfig.json")))
|
|
77952
78206
|
return "typescript";
|
|
77953
|
-
if (
|
|
78207
|
+
if (existsSync22(join79(projectRoot, "pyproject.toml")) || existsSync22(join79(projectRoot, "setup.py"))) {
|
|
77954
78208
|
return "python";
|
|
77955
78209
|
}
|
|
77956
|
-
if (
|
|
78210
|
+
if (existsSync22(join79(projectRoot, "Cargo.toml")))
|
|
77957
78211
|
return "rust";
|
|
77958
|
-
if (
|
|
78212
|
+
if (existsSync22(join79(projectRoot, "go.mod")))
|
|
77959
78213
|
return "go";
|
|
77960
78214
|
return "unknown";
|
|
77961
78215
|
}
|
|
77962
78216
|
function detectLinter(projectRoot) {
|
|
77963
|
-
if (
|
|
78217
|
+
if (existsSync22(join79(projectRoot, "biome.json")) || existsSync22(join79(projectRoot, "biome.jsonc"))) {
|
|
77964
78218
|
return "biome";
|
|
77965
78219
|
}
|
|
77966
|
-
if (
|
|
78220
|
+
if (existsSync22(join79(projectRoot, ".eslintrc.json")) || existsSync22(join79(projectRoot, ".eslintrc.js")) || existsSync22(join79(projectRoot, "eslint.config.js"))) {
|
|
77967
78221
|
return "eslint";
|
|
77968
78222
|
}
|
|
77969
78223
|
return "unknown";
|
|
77970
78224
|
}
|
|
77971
78225
|
function detectMonorepo(projectRoot) {
|
|
77972
|
-
if (
|
|
78226
|
+
if (existsSync22(join79(projectRoot, "turbo.json")))
|
|
77973
78227
|
return "turborepo";
|
|
77974
|
-
if (
|
|
78228
|
+
if (existsSync22(join79(projectRoot, "nx.json")))
|
|
77975
78229
|
return "nx";
|
|
77976
|
-
if (
|
|
78230
|
+
if (existsSync22(join79(projectRoot, "pnpm-workspace.yaml")))
|
|
77977
78231
|
return "pnpm-workspaces";
|
|
77978
78232
|
const pkg = readPackageJson(projectRoot);
|
|
77979
78233
|
if (pkg?.workspaces)
|
|
@@ -78115,7 +78369,7 @@ __export(exports_init, {
|
|
|
78115
78369
|
checkInitCollision: () => checkInitCollision,
|
|
78116
78370
|
_initDeps: () => _initDeps
|
|
78117
78371
|
});
|
|
78118
|
-
import { existsSync as
|
|
78372
|
+
import { existsSync as existsSync23 } from "fs";
|
|
78119
78373
|
import { mkdir as mkdir15 } from "fs/promises";
|
|
78120
78374
|
import { join as join80 } from "path";
|
|
78121
78375
|
function validateProjectName(name) {
|
|
@@ -78154,24 +78408,32 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
|
|
|
78154
78408
|
async function updateGitignore(projectRoot) {
|
|
78155
78409
|
const logger = getLogger();
|
|
78156
78410
|
const gitignorePath = join80(projectRoot, ".gitignore");
|
|
78157
|
-
|
|
78158
|
-
if (
|
|
78159
|
-
existing = await Bun.file(gitignorePath).text();
|
|
78160
|
-
}
|
|
78161
|
-
const missingEntries = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
78162
|
-
if (missingEntries.length === 0) {
|
|
78411
|
+
const result = await patchIgnoreFile(gitignorePath, NAX_GITIGNORE_ENTRIES);
|
|
78412
|
+
if (result.added.length === 0) {
|
|
78163
78413
|
logger.info("init", ".gitignore already has nax entries", { path: gitignorePath });
|
|
78164
78414
|
return;
|
|
78165
78415
|
}
|
|
78166
|
-
const naxSection = `
|
|
78167
|
-
# nax \u2014 generated files
|
|
78168
|
-
${missingEntries.join(`
|
|
78169
|
-
`)}
|
|
78170
|
-
`;
|
|
78171
|
-
await Bun.write(gitignorePath, existing + naxSection);
|
|
78172
78416
|
logger.info("init", "Updated .gitignore with nax entries", {
|
|
78173
78417
|
path: gitignorePath,
|
|
78174
|
-
|
|
78418
|
+
created: result.created,
|
|
78419
|
+
added: result.added
|
|
78420
|
+
});
|
|
78421
|
+
}
|
|
78422
|
+
async function updateNaxignore(projectRoot) {
|
|
78423
|
+
const logger = getLogger();
|
|
78424
|
+
const naxignorePath = join80(projectRoot, ".naxignore");
|
|
78425
|
+
const result = await patchIgnoreFile(naxignorePath, NAX_NAXIGNORE_ENTRIES, {
|
|
78426
|
+
header: NAX_NAXIGNORE_HEADER,
|
|
78427
|
+
footer: NAX_NAXIGNORE_SUGGESTIONS,
|
|
78428
|
+
sectionComment: "# nax - scanning exclusions"
|
|
78429
|
+
});
|
|
78430
|
+
if (result.added.length === 0) {
|
|
78431
|
+
logger.info("init", ".naxignore already has nax entries", { path: naxignorePath });
|
|
78432
|
+
return;
|
|
78433
|
+
}
|
|
78434
|
+
logger.info("init", result.created ? "Created .naxignore" : "Updated .naxignore with nax entries", {
|
|
78435
|
+
path: naxignorePath,
|
|
78436
|
+
added: result.added
|
|
78175
78437
|
});
|
|
78176
78438
|
}
|
|
78177
78439
|
function buildConstitution(stack) {
|
|
@@ -78235,12 +78497,12 @@ function buildConstitution(stack) {
|
|
|
78235
78497
|
async function initGlobal() {
|
|
78236
78498
|
const logger = getLogger();
|
|
78237
78499
|
const globalDir = globalConfigDir();
|
|
78238
|
-
if (!
|
|
78500
|
+
if (!existsSync23(globalDir)) {
|
|
78239
78501
|
await mkdir15(globalDir, { recursive: true });
|
|
78240
78502
|
logger.info("init", "Created global config directory", { path: globalDir });
|
|
78241
78503
|
}
|
|
78242
78504
|
const configPath = join80(globalDir, "config.json");
|
|
78243
|
-
if (!
|
|
78505
|
+
if (!existsSync23(configPath)) {
|
|
78244
78506
|
await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
|
|
78245
78507
|
`);
|
|
78246
78508
|
logger.info("init", "Created global config", { path: configPath });
|
|
@@ -78248,14 +78510,14 @@ async function initGlobal() {
|
|
|
78248
78510
|
logger.info("init", "Global config already exists", { path: configPath });
|
|
78249
78511
|
}
|
|
78250
78512
|
const constitutionPath = join80(globalDir, "constitution.md");
|
|
78251
|
-
if (!
|
|
78513
|
+
if (!existsSync23(constitutionPath)) {
|
|
78252
78514
|
await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
|
|
78253
78515
|
logger.info("init", "Created global constitution", { path: constitutionPath });
|
|
78254
78516
|
} else {
|
|
78255
78517
|
logger.info("init", "Global constitution already exists", { path: constitutionPath });
|
|
78256
78518
|
}
|
|
78257
78519
|
const hooksDir = join80(globalDir, "hooks");
|
|
78258
|
-
if (!
|
|
78520
|
+
if (!existsSync23(hooksDir)) {
|
|
78259
78521
|
await mkdir15(hooksDir, { recursive: true });
|
|
78260
78522
|
logger.info("init", "Created global hooks directory", { path: hooksDir });
|
|
78261
78523
|
} else {
|
|
@@ -78300,7 +78562,7 @@ async function initProject(projectRoot, options) {
|
|
|
78300
78562
|
`), "INIT_NAME_COLLISION", { stage: "init", name: detectedName });
|
|
78301
78563
|
}
|
|
78302
78564
|
}
|
|
78303
|
-
if (!
|
|
78565
|
+
if (!existsSync23(projectDir)) {
|
|
78304
78566
|
await mkdir15(projectDir, { recursive: true });
|
|
78305
78567
|
logger.info("init", "Created project config directory", { path: projectDir });
|
|
78306
78568
|
}
|
|
@@ -78316,7 +78578,7 @@ async function initProject(projectRoot, options) {
|
|
|
78316
78578
|
monorepo: stack.monorepo
|
|
78317
78579
|
});
|
|
78318
78580
|
const configPath = join80(projectDir, "config.json");
|
|
78319
|
-
if (!
|
|
78581
|
+
if (!existsSync23(configPath)) {
|
|
78320
78582
|
await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
|
|
78321
78583
|
`);
|
|
78322
78584
|
logger.info("init", "Created project config", { path: configPath });
|
|
@@ -78325,35 +78587,44 @@ async function initProject(projectRoot, options) {
|
|
|
78325
78587
|
}
|
|
78326
78588
|
await initContext(projectRoot, { force: options?.force });
|
|
78327
78589
|
const constitutionPath = join80(projectDir, "constitution.md");
|
|
78328
|
-
if (!
|
|
78590
|
+
if (!existsSync23(constitutionPath) || options?.force) {
|
|
78329
78591
|
await Bun.write(constitutionPath, buildConstitution(stack));
|
|
78330
78592
|
logger.info("init", "Created project constitution", { path: constitutionPath });
|
|
78331
78593
|
} else {
|
|
78332
78594
|
logger.info("init", "Project constitution already exists", { path: constitutionPath });
|
|
78333
78595
|
}
|
|
78334
|
-
const
|
|
78335
|
-
|
|
78336
|
-
|
|
78337
|
-
|
|
78338
|
-
|
|
78339
|
-
|
|
78596
|
+
for (const [label, dir] of [
|
|
78597
|
+
["hooks", join80(projectDir, "hooks")],
|
|
78598
|
+
["features", featuresDir(projectRoot)]
|
|
78599
|
+
]) {
|
|
78600
|
+
if (!existsSync23(dir)) {
|
|
78601
|
+
await mkdir15(dir, { recursive: true });
|
|
78602
|
+
logger.info("init", `Created project ${label} directory`, { path: dir });
|
|
78603
|
+
} else {
|
|
78604
|
+
logger.info("init", `Project ${label} directory already exists`, { path: dir });
|
|
78605
|
+
}
|
|
78340
78606
|
}
|
|
78341
78607
|
await updateGitignore(projectRoot);
|
|
78342
|
-
await
|
|
78608
|
+
await updateNaxignore(projectRoot);
|
|
78343
78609
|
_initDeps.log(`
|
|
78344
78610
|
[OK] nax init complete. Created files:`);
|
|
78345
78611
|
_initDeps.log(" - .nax/config.json");
|
|
78346
78612
|
_initDeps.log(" - .nax/context.md");
|
|
78347
78613
|
_initDeps.log(" - .nax/constitution.md");
|
|
78348
78614
|
_initDeps.log(" - .nax/hooks/");
|
|
78349
|
-
_initDeps.log(
|
|
78615
|
+
_initDeps.log(` - ${PROJECT_FEATURES_DIR}/`);
|
|
78616
|
+
_initDeps.log(" - .gitignore (nax entries)");
|
|
78617
|
+
_initDeps.log(" - .naxignore");
|
|
78350
78618
|
_initDeps.log(`
|
|
78351
78619
|
Next steps:`);
|
|
78352
78620
|
_initDeps.log(" 1. Review .nax/context.md and fill in TODOs");
|
|
78353
|
-
_initDeps.log(" 2. Review .
|
|
78354
|
-
_initDeps.log(" 3.
|
|
78355
|
-
_initDeps.log(" 4. Run: nax
|
|
78356
|
-
_initDeps.log(" 5. Run: nax
|
|
78621
|
+
_initDeps.log(" 2. Review .naxignore and add paths nax should not scan");
|
|
78622
|
+
_initDeps.log(" 3. Review .nax/config.json and adjust quality commands");
|
|
78623
|
+
_initDeps.log(" 4. Run: nax generate");
|
|
78624
|
+
_initDeps.log(" 5. Run: nax plan");
|
|
78625
|
+
_initDeps.log(" 6. Run: nax run");
|
|
78626
|
+
_initDeps.log(`
|
|
78627
|
+
Optional: nax prompts --init (scaffold overridable prompt templates)`);
|
|
78357
78628
|
logger.info("init", "Project config initialized successfully", { path: projectDir });
|
|
78358
78629
|
}
|
|
78359
78630
|
async function initCommand(options = {}) {
|
|
@@ -78361,7 +78632,7 @@ async function initCommand(options = {}) {
|
|
|
78361
78632
|
await initGlobal();
|
|
78362
78633
|
} else if (options.package) {
|
|
78363
78634
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
78364
|
-
await initPackage(projectRoot, options.package);
|
|
78635
|
+
await initPackage(projectRoot, options.package, options.force);
|
|
78365
78636
|
_initDeps.log(`
|
|
78366
78637
|
[OK] Package scaffold created.`);
|
|
78367
78638
|
_initDeps.log(` Created: .nax/mono/${options.package}/context.md`);
|
|
@@ -78383,7 +78654,6 @@ var init_init2 = __esm(() => {
|
|
|
78383
78654
|
init_gitignore();
|
|
78384
78655
|
init_init_context();
|
|
78385
78656
|
init_init_detect();
|
|
78386
|
-
init_prompts2();
|
|
78387
78657
|
_initDeps = {
|
|
78388
78658
|
log: console.log.bind(console)
|
|
78389
78659
|
};
|
|
@@ -78398,11 +78668,11 @@ __export(exports_migrate, {
|
|
|
78398
78668
|
migrateCommand: () => migrateCommand,
|
|
78399
78669
|
detectGeneratedContent: () => detectGeneratedContent
|
|
78400
78670
|
});
|
|
78401
|
-
import { existsSync as
|
|
78671
|
+
import { existsSync as existsSync24 } from "fs";
|
|
78402
78672
|
import { mkdir as mkdir16, readdir as readdir4, rename as rename5 } from "fs/promises";
|
|
78403
78673
|
import path26 from "path";
|
|
78404
78674
|
async function detectGeneratedContent(naxDir) {
|
|
78405
|
-
if (!
|
|
78675
|
+
if (!existsSync24(naxDir))
|
|
78406
78676
|
return [];
|
|
78407
78677
|
const candidates = [];
|
|
78408
78678
|
let entries = [];
|
|
@@ -78417,7 +78687,7 @@ async function detectGeneratedContent(naxDir) {
|
|
|
78417
78687
|
}
|
|
78418
78688
|
}
|
|
78419
78689
|
const featuresDir2 = path26.join(naxDir, "features");
|
|
78420
|
-
if (
|
|
78690
|
+
if (existsSync24(featuresDir2)) {
|
|
78421
78691
|
let featureDirs = [];
|
|
78422
78692
|
try {
|
|
78423
78693
|
featureDirs = await readdir4(featuresDir2);
|
|
@@ -78479,7 +78749,7 @@ async function migrateCommand(options) {
|
|
|
78479
78749
|
});
|
|
78480
78750
|
}
|
|
78481
78751
|
const src = path26.join(globalConfigDir(), options.reclaim);
|
|
78482
|
-
if (!
|
|
78752
|
+
if (!existsSync24(src)) {
|
|
78483
78753
|
throw new NaxError(`Nothing to reclaim: ~/.nax/${options.reclaim} does not exist`, "MIGRATE_RECLAIM_NOT_FOUND", {
|
|
78484
78754
|
stage: "migrate",
|
|
78485
78755
|
name: options.reclaim
|
|
@@ -78525,7 +78795,7 @@ async function migrateCommand(options) {
|
|
|
78525
78795
|
}
|
|
78526
78796
|
const naxDir = path26.join(options.workdir, ".nax");
|
|
78527
78797
|
const configPath = path26.join(naxDir, "config.json");
|
|
78528
|
-
if (!
|
|
78798
|
+
if (!existsSync24(configPath)) {
|
|
78529
78799
|
throw new NaxError("No .nax/config.json found \u2014 run nax init first", "MIGRATE_NO_CONFIG", {
|
|
78530
78800
|
stage: "migrate",
|
|
78531
78801
|
workdir: options.workdir
|
|
@@ -78560,7 +78830,7 @@ async function migrateCommand(options) {
|
|
|
78560
78830
|
for (const candidate of candidates) {
|
|
78561
78831
|
const dest = path26.join(destBase, candidate.name);
|
|
78562
78832
|
await mkdir16(path26.dirname(dest), { recursive: true });
|
|
78563
|
-
if (
|
|
78833
|
+
if (existsSync24(dest)) {
|
|
78564
78834
|
throw new NaxError(`Migration conflict: destination already exists.
|
|
78565
78835
|
Source: ${candidate.srcPath}
|
|
78566
78836
|
Destination: ${dest}
|
|
@@ -80458,380 +80728,6 @@ var init_pipeline = __esm(() => {
|
|
|
80458
80728
|
init_reporters();
|
|
80459
80729
|
});
|
|
80460
80730
|
|
|
80461
|
-
// src/cli/prompts-shared.ts
|
|
80462
|
-
function buildFrontmatter(story, ctx, role) {
|
|
80463
|
-
const lines = [];
|
|
80464
|
-
lines.push(`storyId: ${story.id}`);
|
|
80465
|
-
lines.push(`title: "${story.title}"`);
|
|
80466
|
-
lines.push(`testStrategy: ${ctx.routing.testStrategy}`);
|
|
80467
|
-
lines.push(`modelTier: ${ctx.routing.modelTier}`);
|
|
80468
|
-
if (role) {
|
|
80469
|
-
lines.push(`role: ${role}`);
|
|
80470
|
-
}
|
|
80471
|
-
const builtContext = ctx.builtContext;
|
|
80472
|
-
const contextTokens = builtContext?.totalTokens ?? 0;
|
|
80473
|
-
const promptTokens = ctx.prompt ? Math.ceil(ctx.prompt.length / 3) : 0;
|
|
80474
|
-
lines.push(`contextTokens: ${contextTokens}`);
|
|
80475
|
-
lines.push(`promptTokens: ${promptTokens}`);
|
|
80476
|
-
if (story.dependencies && story.dependencies.length > 0) {
|
|
80477
|
-
lines.push(`dependencies: [${story.dependencies.join(", ")}]`);
|
|
80478
|
-
}
|
|
80479
|
-
lines.push("contextElements:");
|
|
80480
|
-
if (builtContext) {
|
|
80481
|
-
for (const element of builtContext.elements) {
|
|
80482
|
-
lines.push(` - type: ${element.type}`);
|
|
80483
|
-
if (element.storyId) {
|
|
80484
|
-
lines.push(` storyId: ${element.storyId}`);
|
|
80485
|
-
}
|
|
80486
|
-
if (element.filePath) {
|
|
80487
|
-
lines.push(` filePath: ${element.filePath}`);
|
|
80488
|
-
}
|
|
80489
|
-
lines.push(` tokens: ${element.tokens}`);
|
|
80490
|
-
}
|
|
80491
|
-
}
|
|
80492
|
-
if (builtContext?.truncated) {
|
|
80493
|
-
lines.push("truncated: true");
|
|
80494
|
-
}
|
|
80495
|
-
return `${lines.join(`
|
|
80496
|
-
`)}
|
|
80497
|
-
`;
|
|
80498
|
-
}
|
|
80499
|
-
|
|
80500
|
-
// src/cli/prompts-tdd.ts
|
|
80501
|
-
import { join as join82 } from "path";
|
|
80502
|
-
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
80503
|
-
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
80504
|
-
TddPromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
80505
|
-
TddPromptBuilder.for("implementer", { variant: "standard" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
80506
|
-
TddPromptBuilder.for("verifier").withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build()
|
|
80507
|
-
]);
|
|
80508
|
-
const sessions = [
|
|
80509
|
-
{ role: "test-writer", prompt: testWriterPrompt },
|
|
80510
|
-
{ role: "implementer", prompt: implementerPrompt },
|
|
80511
|
-
{ role: "verifier", prompt: verifierPrompt }
|
|
80512
|
-
];
|
|
80513
|
-
for (const session of sessions) {
|
|
80514
|
-
const frontmatter = buildFrontmatter(story, ctx, session.role);
|
|
80515
|
-
const fullOutput = `---
|
|
80516
|
-
${frontmatter}---
|
|
80517
|
-
|
|
80518
|
-
${session.prompt}`;
|
|
80519
|
-
if (outputDir) {
|
|
80520
|
-
const promptFile = join82(outputDir, `${story.id}.${session.role}.md`);
|
|
80521
|
-
await Bun.write(promptFile, fullOutput);
|
|
80522
|
-
logger.info("cli", "Written TDD prompt file", {
|
|
80523
|
-
storyId: story.id,
|
|
80524
|
-
role: session.role,
|
|
80525
|
-
promptFile
|
|
80526
|
-
});
|
|
80527
|
-
} else {
|
|
80528
|
-
console.log(`
|
|
80529
|
-
${"=".repeat(80)}`);
|
|
80530
|
-
console.log(`Story: ${story.id} \u2014 ${story.title} [${session.role}]`);
|
|
80531
|
-
console.log("=".repeat(80));
|
|
80532
|
-
console.log(fullOutput);
|
|
80533
|
-
}
|
|
80534
|
-
}
|
|
80535
|
-
if (outputDir && ctx.contextMarkdown) {
|
|
80536
|
-
const contextFile = join82(outputDir, `${story.id}.context.md`);
|
|
80537
|
-
const frontmatter = buildFrontmatter(story, ctx);
|
|
80538
|
-
const contextOutput = `---
|
|
80539
|
-
${frontmatter}---
|
|
80540
|
-
|
|
80541
|
-
${ctx.contextMarkdown}`;
|
|
80542
|
-
await Bun.write(contextFile, contextOutput);
|
|
80543
|
-
}
|
|
80544
|
-
}
|
|
80545
|
-
var init_prompts_tdd = __esm(() => {
|
|
80546
|
-
init_prompts();
|
|
80547
|
-
});
|
|
80548
|
-
|
|
80549
|
-
// src/cli/prompts-main.ts
|
|
80550
|
-
import { existsSync as existsSync26, mkdirSync as mkdirSync4 } from "fs";
|
|
80551
|
-
import { join as join83 } from "path";
|
|
80552
|
-
async function promptsCommand(options) {
|
|
80553
|
-
const logger = getLogger();
|
|
80554
|
-
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
80555
|
-
const naxDir = join83(workdir, ".nax");
|
|
80556
|
-
if (!existsSync26(naxDir)) {
|
|
80557
|
-
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
80558
|
-
}
|
|
80559
|
-
const featureDir2 = join83(naxDir, "features", feature);
|
|
80560
|
-
const prdPath = join83(featureDir2, "prd.json");
|
|
80561
|
-
if (!existsSync26(prdPath)) {
|
|
80562
|
-
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
80563
|
-
}
|
|
80564
|
-
const prd = await loadPRD(prdPath);
|
|
80565
|
-
const runtime = _promptsMainDeps.createRuntime(config2, workdir);
|
|
80566
|
-
try {
|
|
80567
|
-
const stories = storyId ? prd.userStories.filter((s) => s.id === storyId) : prd.userStories;
|
|
80568
|
-
if (stories.length === 0) {
|
|
80569
|
-
throw new Error(storyId ? `Story "${storyId}" not found in feature "${feature}"` : `No stories found in feature "${feature}"`);
|
|
80570
|
-
}
|
|
80571
|
-
if (outputDir) {
|
|
80572
|
-
mkdirSync4(outputDir, { recursive: true });
|
|
80573
|
-
}
|
|
80574
|
-
logger.info("cli", "Assembling prompts", {
|
|
80575
|
-
feature,
|
|
80576
|
-
storyCount: stories.length,
|
|
80577
|
-
outputMode: outputDir ? "files" : "stdout"
|
|
80578
|
-
});
|
|
80579
|
-
const processedStories = [];
|
|
80580
|
-
const promptPipeline = [routingStage, constitutionStage, contextStage, promptStage];
|
|
80581
|
-
for (const story of stories) {
|
|
80582
|
-
const ctx = {
|
|
80583
|
-
config: config2,
|
|
80584
|
-
rootConfig: config2,
|
|
80585
|
-
prd,
|
|
80586
|
-
story,
|
|
80587
|
-
stories: [story],
|
|
80588
|
-
routing: {
|
|
80589
|
-
complexity: "simple",
|
|
80590
|
-
modelTier: "fast",
|
|
80591
|
-
testStrategy: "test-after",
|
|
80592
|
-
reasoning: "Placeholder routing"
|
|
80593
|
-
},
|
|
80594
|
-
projectDir: workdir,
|
|
80595
|
-
workdir,
|
|
80596
|
-
featureDir: featureDir2,
|
|
80597
|
-
hooks: { hooks: {} },
|
|
80598
|
-
agentManager: runtime.agentManager,
|
|
80599
|
-
sessionManager: runtime.sessionManager,
|
|
80600
|
-
runtime,
|
|
80601
|
-
abortSignal: runtime.signal
|
|
80602
|
-
};
|
|
80603
|
-
const result = await runPipeline(promptPipeline, ctx);
|
|
80604
|
-
if (!result.success) {
|
|
80605
|
-
logger.warn("cli", "Failed to assemble prompt for story", {
|
|
80606
|
-
storyId: story.id,
|
|
80607
|
-
reason: result.reason
|
|
80608
|
-
});
|
|
80609
|
-
continue;
|
|
80610
|
-
}
|
|
80611
|
-
if (ctx.routing.testStrategy === "three-session-tdd") {
|
|
80612
|
-
await handleThreeSessionTddPrompts(story, ctx, outputDir, logger);
|
|
80613
|
-
processedStories.push(story.id);
|
|
80614
|
-
continue;
|
|
80615
|
-
}
|
|
80616
|
-
if (!ctx.prompt) {
|
|
80617
|
-
logger.warn("cli", "No prompt generated for story", {
|
|
80618
|
-
storyId: story.id
|
|
80619
|
-
});
|
|
80620
|
-
continue;
|
|
80621
|
-
}
|
|
80622
|
-
const frontmatter = buildFrontmatter(story, ctx);
|
|
80623
|
-
const fullOutput = `---
|
|
80624
|
-
${frontmatter}---
|
|
80625
|
-
|
|
80626
|
-
${ctx.prompt}`;
|
|
80627
|
-
if (outputDir) {
|
|
80628
|
-
const promptFile = join83(outputDir, `${story.id}.prompt.md`);
|
|
80629
|
-
await Bun.write(promptFile, fullOutput);
|
|
80630
|
-
if (ctx.contextMarkdown) {
|
|
80631
|
-
const contextFile = join83(outputDir, `${story.id}.context.md`);
|
|
80632
|
-
const contextOutput = `---
|
|
80633
|
-
${frontmatter}---
|
|
80634
|
-
|
|
80635
|
-
${ctx.contextMarkdown}`;
|
|
80636
|
-
await Bun.write(contextFile, contextOutput);
|
|
80637
|
-
}
|
|
80638
|
-
logger.info("cli", "Written prompt files", {
|
|
80639
|
-
storyId: story.id,
|
|
80640
|
-
promptFile
|
|
80641
|
-
});
|
|
80642
|
-
} else {
|
|
80643
|
-
console.log(`
|
|
80644
|
-
${"=".repeat(80)}`);
|
|
80645
|
-
console.log(`Story: ${story.id} \u2014 ${story.title}`);
|
|
80646
|
-
console.log("=".repeat(80));
|
|
80647
|
-
console.log(fullOutput);
|
|
80648
|
-
}
|
|
80649
|
-
processedStories.push(story.id);
|
|
80650
|
-
}
|
|
80651
|
-
logger.info("cli", "Prompt assembly complete", {
|
|
80652
|
-
processedCount: processedStories.length
|
|
80653
|
-
});
|
|
80654
|
-
return processedStories;
|
|
80655
|
-
} finally {
|
|
80656
|
-
await runtime.close();
|
|
80657
|
-
}
|
|
80658
|
-
}
|
|
80659
|
-
var _promptsMainDeps;
|
|
80660
|
-
var init_prompts_main = __esm(() => {
|
|
80661
|
-
init_logger2();
|
|
80662
|
-
init_pipeline();
|
|
80663
|
-
init_stages();
|
|
80664
|
-
init_prd();
|
|
80665
|
-
init_runtime();
|
|
80666
|
-
init_prompts_tdd();
|
|
80667
|
-
_promptsMainDeps = { createRuntime };
|
|
80668
|
-
});
|
|
80669
|
-
|
|
80670
|
-
// src/cli/prompts-init.ts
|
|
80671
|
-
import { existsSync as existsSync27, mkdirSync as mkdirSync5 } from "fs";
|
|
80672
|
-
import { join as join84 } from "path";
|
|
80673
|
-
async function promptsInitCommand(options) {
|
|
80674
|
-
const { workdir, force = false, autoWireConfig = true } = options;
|
|
80675
|
-
const templatesDir = join84(workdir, ".nax", "templates");
|
|
80676
|
-
mkdirSync5(templatesDir, { recursive: true });
|
|
80677
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync27(join84(templatesDir, f)));
|
|
80678
|
-
if (existingFiles.length > 0 && !force) {
|
|
80679
|
-
_promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
80680
|
-
Pass --force to overwrite existing templates.`);
|
|
80681
|
-
return [];
|
|
80682
|
-
}
|
|
80683
|
-
const written = [];
|
|
80684
|
-
for (const template of TEMPLATE_ROLES) {
|
|
80685
|
-
const filePath = join84(templatesDir, template.file);
|
|
80686
|
-
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
80687
|
-
const content = TEMPLATE_HEADER + roleBody;
|
|
80688
|
-
await Bun.write(filePath, content);
|
|
80689
|
-
written.push(filePath);
|
|
80690
|
-
}
|
|
80691
|
-
_promptsInitDeps.log(`[OK] Written ${written.length} template files to nax/templates/:`);
|
|
80692
|
-
for (const filePath of written) {
|
|
80693
|
-
_promptsInitDeps.log(` - ${filePath.replace(`${workdir}/`, "")}`);
|
|
80694
|
-
}
|
|
80695
|
-
if (autoWireConfig) {
|
|
80696
|
-
await autoWirePromptsConfig(workdir);
|
|
80697
|
-
}
|
|
80698
|
-
return written;
|
|
80699
|
-
}
|
|
80700
|
-
async function autoWirePromptsConfig(workdir) {
|
|
80701
|
-
const configPath = join84(workdir, "nax.config.json");
|
|
80702
|
-
if (!existsSync27(configPath)) {
|
|
80703
|
-
const exampleConfig = JSON.stringify({
|
|
80704
|
-
prompts: {
|
|
80705
|
-
overrides: {
|
|
80706
|
-
"test-writer": ".nax/templates/test-writer.md",
|
|
80707
|
-
implementer: ".nax/templates/implementer.md",
|
|
80708
|
-
verifier: ".nax/templates/verifier.md",
|
|
80709
|
-
"single-session": ".nax/templates/single-session.md",
|
|
80710
|
-
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
80711
|
-
}
|
|
80712
|
-
}
|
|
80713
|
-
}, null, 2);
|
|
80714
|
-
_promptsInitDeps.log(`
|
|
80715
|
-
No nax.config.json found. To activate overrides, create nax/config.json with:
|
|
80716
|
-
${exampleConfig}`);
|
|
80717
|
-
return;
|
|
80718
|
-
}
|
|
80719
|
-
const configFile = Bun.file(configPath);
|
|
80720
|
-
const configContent = await configFile.text();
|
|
80721
|
-
const config2 = JSON.parse(configContent);
|
|
80722
|
-
if (config2.prompts?.overrides && Object.keys(config2.prompts.overrides).length > 0) {
|
|
80723
|
-
_promptsInitDeps.log(`[INFO] prompts.overrides already configured in nax.config.json. Skipping auto-wiring.
|
|
80724
|
-
` + " To reset overrides, remove the prompts.overrides section and re-run this command.");
|
|
80725
|
-
return;
|
|
80726
|
-
}
|
|
80727
|
-
const overrides = {
|
|
80728
|
-
"test-writer": ".nax/templates/test-writer.md",
|
|
80729
|
-
implementer: ".nax/templates/implementer.md",
|
|
80730
|
-
verifier: ".nax/templates/verifier.md",
|
|
80731
|
-
"single-session": ".nax/templates/single-session.md",
|
|
80732
|
-
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
80733
|
-
};
|
|
80734
|
-
if (!config2.prompts) {
|
|
80735
|
-
config2.prompts = {};
|
|
80736
|
-
}
|
|
80737
|
-
config2.prompts.overrides = overrides;
|
|
80738
|
-
const updatedConfig = formatConfigJson(config2);
|
|
80739
|
-
await Bun.write(configPath, updatedConfig);
|
|
80740
|
-
_promptsInitDeps.log("[OK] Auto-wired prompts.overrides in nax.config.json");
|
|
80741
|
-
}
|
|
80742
|
-
function formatConfigJson(config2) {
|
|
80743
|
-
const lines = ["{"];
|
|
80744
|
-
const keys = Object.keys(config2);
|
|
80745
|
-
for (let i = 0;i < keys.length; i++) {
|
|
80746
|
-
const key = keys[i];
|
|
80747
|
-
const value = config2[key];
|
|
80748
|
-
const isLast = i === keys.length - 1;
|
|
80749
|
-
if (key === "prompts" && typeof value === "object" && value !== null) {
|
|
80750
|
-
const promptsObj = value;
|
|
80751
|
-
if (promptsObj.overrides) {
|
|
80752
|
-
const overridesJson = JSON.stringify(promptsObj.overrides);
|
|
80753
|
-
lines.push(` "${key}": { "overrides": ${overridesJson} }${isLast ? "" : ","}`);
|
|
80754
|
-
} else {
|
|
80755
|
-
lines.push(` "${key}": ${JSON.stringify(value)}${isLast ? "" : ","}`);
|
|
80756
|
-
}
|
|
80757
|
-
} else {
|
|
80758
|
-
lines.push(` "${key}": ${JSON.stringify(value)}${isLast ? "" : ","}`);
|
|
80759
|
-
}
|
|
80760
|
-
}
|
|
80761
|
-
lines.push("}");
|
|
80762
|
-
return lines.join(`
|
|
80763
|
-
`);
|
|
80764
|
-
}
|
|
80765
|
-
var _promptsInitDeps, TEMPLATE_ROLES, TEMPLATE_HEADER = `<!--
|
|
80766
|
-
This file controls the role-body section of the nax prompt for this role.
|
|
80767
|
-
Edit the content below to customize the task instructions given to the agent.
|
|
80768
|
-
|
|
80769
|
-
NON-OVERRIDABLE SECTIONS (always injected by nax, cannot be changed here):
|
|
80770
|
-
- Isolation rules (scope, file access boundaries)
|
|
80771
|
-
- Story context (acceptance criteria, description, dependencies)
|
|
80772
|
-
- Conventions (project coding standards)
|
|
80773
|
-
|
|
80774
|
-
To activate overrides, add to your nax/config.json:
|
|
80775
|
-
{ "prompts": { "overrides": { "<role>": ".nax/templates/<role>.md" } } }
|
|
80776
|
-
-->
|
|
80777
|
-
|
|
80778
|
-
`;
|
|
80779
|
-
var init_prompts_init = __esm(() => {
|
|
80780
|
-
init_role_task();
|
|
80781
|
-
_promptsInitDeps = {
|
|
80782
|
-
log: console.log.bind(console),
|
|
80783
|
-
warn: console.warn.bind(console)
|
|
80784
|
-
};
|
|
80785
|
-
TEMPLATE_ROLES = [
|
|
80786
|
-
{ file: "test-writer.md", role: "test-writer" },
|
|
80787
|
-
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
80788
|
-
{ file: "verifier.md", role: "verifier" },
|
|
80789
|
-
{ file: "single-session.md", role: "single-session" },
|
|
80790
|
-
{ file: "tdd-simple.md", role: "tdd-simple" }
|
|
80791
|
-
];
|
|
80792
|
-
});
|
|
80793
|
-
|
|
80794
|
-
// src/cli/prompts-export.ts
|
|
80795
|
-
async function exportPromptCommand(options) {
|
|
80796
|
-
const { role, out } = options;
|
|
80797
|
-
if (!VALID_EXPORT_ROLES.includes(role)) {
|
|
80798
|
-
console.error(`[ERROR] Invalid role: "${role}". Valid roles: ${VALID_EXPORT_ROLES.join(", ")}`);
|
|
80799
|
-
process.exit(1);
|
|
80800
|
-
}
|
|
80801
|
-
const stubStory = {
|
|
80802
|
-
id: "EXAMPLE",
|
|
80803
|
-
title: "Example story",
|
|
80804
|
-
description: "Story ID: EXAMPLE. This is a placeholder story used to demonstrate the default prompt.",
|
|
80805
|
-
acceptanceCriteria: ["AC-1: Example criterion"],
|
|
80806
|
-
tags: [],
|
|
80807
|
-
dependencies: [],
|
|
80808
|
-
status: "pending",
|
|
80809
|
-
passes: false,
|
|
80810
|
-
escalations: [],
|
|
80811
|
-
attempts: 0
|
|
80812
|
-
};
|
|
80813
|
-
const prompt = await TddPromptBuilder.for(role).story(stubStory).build();
|
|
80814
|
-
if (out) {
|
|
80815
|
-
await Bun.write(out, prompt);
|
|
80816
|
-
console.log(`[OK] Exported prompt for "${role}" to ${out}`);
|
|
80817
|
-
} else {
|
|
80818
|
-
console.log(prompt);
|
|
80819
|
-
}
|
|
80820
|
-
}
|
|
80821
|
-
var VALID_EXPORT_ROLES;
|
|
80822
|
-
var init_prompts_export = __esm(() => {
|
|
80823
|
-
init_prompts();
|
|
80824
|
-
VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"];
|
|
80825
|
-
});
|
|
80826
|
-
|
|
80827
|
-
// src/cli/prompts.ts
|
|
80828
|
-
var init_prompts2 = __esm(() => {
|
|
80829
|
-
init_prompts_main();
|
|
80830
|
-
init_prompts_init();
|
|
80831
|
-
init_prompts_export();
|
|
80832
|
-
init_prompts_tdd();
|
|
80833
|
-
});
|
|
80834
|
-
|
|
80835
80731
|
// src/cli/setup-analyze.ts
|
|
80836
80732
|
import { join as join85 } from "path";
|
|
80837
80733
|
async function detectPackageManager(workdir) {
|
|
@@ -111331,7 +111227,7 @@ var init_curator2 = __esm(() => {
|
|
|
111331
111227
|
|
|
111332
111228
|
// bin/nax.ts
|
|
111333
111229
|
init_source();
|
|
111334
|
-
import { existsSync as
|
|
111230
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync8 } from "fs";
|
|
111335
111231
|
import { homedir as homedir3 } from "os";
|
|
111336
111232
|
import { basename as basename24, join as join115 } from "path";
|
|
111337
111233
|
|
|
@@ -112130,9 +112026,361 @@ async function runsShowCommand(options) {
|
|
|
112130
112026
|
});
|
|
112131
112027
|
}
|
|
112132
112028
|
}
|
|
112029
|
+
// src/cli/prompts-main.ts
|
|
112030
|
+
init_logger2();
|
|
112031
|
+
init_pipeline();
|
|
112032
|
+
init_stages();
|
|
112033
|
+
init_prd();
|
|
112034
|
+
init_runtime();
|
|
112035
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync4 } from "fs";
|
|
112036
|
+
import { join as join83 } from "path";
|
|
112037
|
+
|
|
112038
|
+
// src/cli/prompts-shared.ts
|
|
112039
|
+
function buildFrontmatter(story, ctx, role) {
|
|
112040
|
+
const lines = [];
|
|
112041
|
+
lines.push(`storyId: ${story.id}`);
|
|
112042
|
+
lines.push(`title: "${story.title}"`);
|
|
112043
|
+
lines.push(`testStrategy: ${ctx.routing.testStrategy}`);
|
|
112044
|
+
lines.push(`modelTier: ${ctx.routing.modelTier}`);
|
|
112045
|
+
if (role) {
|
|
112046
|
+
lines.push(`role: ${role}`);
|
|
112047
|
+
}
|
|
112048
|
+
const builtContext = ctx.builtContext;
|
|
112049
|
+
const contextTokens = builtContext?.totalTokens ?? 0;
|
|
112050
|
+
const promptTokens = ctx.prompt ? Math.ceil(ctx.prompt.length / 3) : 0;
|
|
112051
|
+
lines.push(`contextTokens: ${contextTokens}`);
|
|
112052
|
+
lines.push(`promptTokens: ${promptTokens}`);
|
|
112053
|
+
if (story.dependencies && story.dependencies.length > 0) {
|
|
112054
|
+
lines.push(`dependencies: [${story.dependencies.join(", ")}]`);
|
|
112055
|
+
}
|
|
112056
|
+
lines.push("contextElements:");
|
|
112057
|
+
if (builtContext) {
|
|
112058
|
+
for (const element of builtContext.elements) {
|
|
112059
|
+
lines.push(` - type: ${element.type}`);
|
|
112060
|
+
if (element.storyId) {
|
|
112061
|
+
lines.push(` storyId: ${element.storyId}`);
|
|
112062
|
+
}
|
|
112063
|
+
if (element.filePath) {
|
|
112064
|
+
lines.push(` filePath: ${element.filePath}`);
|
|
112065
|
+
}
|
|
112066
|
+
lines.push(` tokens: ${element.tokens}`);
|
|
112067
|
+
}
|
|
112068
|
+
}
|
|
112069
|
+
if (builtContext?.truncated) {
|
|
112070
|
+
lines.push("truncated: true");
|
|
112071
|
+
}
|
|
112072
|
+
return `${lines.join(`
|
|
112073
|
+
`)}
|
|
112074
|
+
`;
|
|
112075
|
+
}
|
|
112133
112076
|
|
|
112077
|
+
// src/cli/prompts-tdd.ts
|
|
112078
|
+
init_prompts();
|
|
112079
|
+
import { join as join82 } from "path";
|
|
112080
|
+
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
112081
|
+
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
112082
|
+
TddPromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
112083
|
+
TddPromptBuilder.for("implementer", { variant: "standard" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
112084
|
+
TddPromptBuilder.for("verifier").withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build()
|
|
112085
|
+
]);
|
|
112086
|
+
const sessions = [
|
|
112087
|
+
{ role: "test-writer", prompt: testWriterPrompt },
|
|
112088
|
+
{ role: "implementer", prompt: implementerPrompt },
|
|
112089
|
+
{ role: "verifier", prompt: verifierPrompt }
|
|
112090
|
+
];
|
|
112091
|
+
for (const session of sessions) {
|
|
112092
|
+
const frontmatter = buildFrontmatter(story, ctx, session.role);
|
|
112093
|
+
const fullOutput = `---
|
|
112094
|
+
${frontmatter}---
|
|
112095
|
+
|
|
112096
|
+
${session.prompt}`;
|
|
112097
|
+
if (outputDir) {
|
|
112098
|
+
const promptFile = join82(outputDir, `${story.id}.${session.role}.md`);
|
|
112099
|
+
await Bun.write(promptFile, fullOutput);
|
|
112100
|
+
logger.info("cli", "Written TDD prompt file", {
|
|
112101
|
+
storyId: story.id,
|
|
112102
|
+
role: session.role,
|
|
112103
|
+
promptFile
|
|
112104
|
+
});
|
|
112105
|
+
} else {
|
|
112106
|
+
console.log(`
|
|
112107
|
+
${"=".repeat(80)}`);
|
|
112108
|
+
console.log(`Story: ${story.id} \u2014 ${story.title} [${session.role}]`);
|
|
112109
|
+
console.log("=".repeat(80));
|
|
112110
|
+
console.log(fullOutput);
|
|
112111
|
+
}
|
|
112112
|
+
}
|
|
112113
|
+
if (outputDir && ctx.contextMarkdown) {
|
|
112114
|
+
const contextFile = join82(outputDir, `${story.id}.context.md`);
|
|
112115
|
+
const frontmatter = buildFrontmatter(story, ctx);
|
|
112116
|
+
const contextOutput = `---
|
|
112117
|
+
${frontmatter}---
|
|
112118
|
+
|
|
112119
|
+
${ctx.contextMarkdown}`;
|
|
112120
|
+
await Bun.write(contextFile, contextOutput);
|
|
112121
|
+
}
|
|
112122
|
+
}
|
|
112123
|
+
|
|
112124
|
+
// src/cli/prompts-main.ts
|
|
112125
|
+
var _promptsMainDeps = { createRuntime };
|
|
112126
|
+
async function promptsCommand(options) {
|
|
112127
|
+
const logger = getLogger();
|
|
112128
|
+
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
112129
|
+
const naxDir = join83(workdir, ".nax");
|
|
112130
|
+
if (!existsSync25(naxDir)) {
|
|
112131
|
+
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
112132
|
+
}
|
|
112133
|
+
const featureDir2 = join83(naxDir, "features", feature);
|
|
112134
|
+
const prdPath = join83(featureDir2, "prd.json");
|
|
112135
|
+
if (!existsSync25(prdPath)) {
|
|
112136
|
+
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
112137
|
+
}
|
|
112138
|
+
const prd = await loadPRD(prdPath);
|
|
112139
|
+
const runtime = _promptsMainDeps.createRuntime(config2, workdir);
|
|
112140
|
+
try {
|
|
112141
|
+
const stories = storyId ? prd.userStories.filter((s) => s.id === storyId) : prd.userStories;
|
|
112142
|
+
if (stories.length === 0) {
|
|
112143
|
+
throw new Error(storyId ? `Story "${storyId}" not found in feature "${feature}"` : `No stories found in feature "${feature}"`);
|
|
112144
|
+
}
|
|
112145
|
+
if (outputDir) {
|
|
112146
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
112147
|
+
}
|
|
112148
|
+
logger.info("cli", "Assembling prompts", {
|
|
112149
|
+
feature,
|
|
112150
|
+
storyCount: stories.length,
|
|
112151
|
+
outputMode: outputDir ? "files" : "stdout"
|
|
112152
|
+
});
|
|
112153
|
+
const processedStories = [];
|
|
112154
|
+
const promptPipeline = [routingStage, constitutionStage, contextStage, promptStage];
|
|
112155
|
+
for (const story of stories) {
|
|
112156
|
+
const ctx = {
|
|
112157
|
+
config: config2,
|
|
112158
|
+
rootConfig: config2,
|
|
112159
|
+
prd,
|
|
112160
|
+
story,
|
|
112161
|
+
stories: [story],
|
|
112162
|
+
routing: {
|
|
112163
|
+
complexity: "simple",
|
|
112164
|
+
modelTier: "fast",
|
|
112165
|
+
testStrategy: "test-after",
|
|
112166
|
+
reasoning: "Placeholder routing"
|
|
112167
|
+
},
|
|
112168
|
+
projectDir: workdir,
|
|
112169
|
+
workdir,
|
|
112170
|
+
featureDir: featureDir2,
|
|
112171
|
+
hooks: { hooks: {} },
|
|
112172
|
+
agentManager: runtime.agentManager,
|
|
112173
|
+
sessionManager: runtime.sessionManager,
|
|
112174
|
+
runtime,
|
|
112175
|
+
abortSignal: runtime.signal
|
|
112176
|
+
};
|
|
112177
|
+
const result = await runPipeline(promptPipeline, ctx);
|
|
112178
|
+
if (!result.success) {
|
|
112179
|
+
logger.warn("cli", "Failed to assemble prompt for story", {
|
|
112180
|
+
storyId: story.id,
|
|
112181
|
+
reason: result.reason
|
|
112182
|
+
});
|
|
112183
|
+
continue;
|
|
112184
|
+
}
|
|
112185
|
+
if (ctx.routing.testStrategy === "three-session-tdd") {
|
|
112186
|
+
await handleThreeSessionTddPrompts(story, ctx, outputDir, logger);
|
|
112187
|
+
processedStories.push(story.id);
|
|
112188
|
+
continue;
|
|
112189
|
+
}
|
|
112190
|
+
if (!ctx.prompt) {
|
|
112191
|
+
logger.warn("cli", "No prompt generated for story", {
|
|
112192
|
+
storyId: story.id
|
|
112193
|
+
});
|
|
112194
|
+
continue;
|
|
112195
|
+
}
|
|
112196
|
+
const frontmatter = buildFrontmatter(story, ctx);
|
|
112197
|
+
const fullOutput = `---
|
|
112198
|
+
${frontmatter}---
|
|
112199
|
+
|
|
112200
|
+
${ctx.prompt}`;
|
|
112201
|
+
if (outputDir) {
|
|
112202
|
+
const promptFile = join83(outputDir, `${story.id}.prompt.md`);
|
|
112203
|
+
await Bun.write(promptFile, fullOutput);
|
|
112204
|
+
if (ctx.contextMarkdown) {
|
|
112205
|
+
const contextFile = join83(outputDir, `${story.id}.context.md`);
|
|
112206
|
+
const contextOutput = `---
|
|
112207
|
+
${frontmatter}---
|
|
112208
|
+
|
|
112209
|
+
${ctx.contextMarkdown}`;
|
|
112210
|
+
await Bun.write(contextFile, contextOutput);
|
|
112211
|
+
}
|
|
112212
|
+
logger.info("cli", "Written prompt files", {
|
|
112213
|
+
storyId: story.id,
|
|
112214
|
+
promptFile
|
|
112215
|
+
});
|
|
112216
|
+
} else {
|
|
112217
|
+
console.log(`
|
|
112218
|
+
${"=".repeat(80)}`);
|
|
112219
|
+
console.log(`Story: ${story.id} \u2014 ${story.title}`);
|
|
112220
|
+
console.log("=".repeat(80));
|
|
112221
|
+
console.log(fullOutput);
|
|
112222
|
+
}
|
|
112223
|
+
processedStories.push(story.id);
|
|
112224
|
+
}
|
|
112225
|
+
logger.info("cli", "Prompt assembly complete", {
|
|
112226
|
+
processedCount: processedStories.length
|
|
112227
|
+
});
|
|
112228
|
+
return processedStories;
|
|
112229
|
+
} finally {
|
|
112230
|
+
await runtime.close();
|
|
112231
|
+
}
|
|
112232
|
+
}
|
|
112233
|
+
// src/cli/prompts-init.ts
|
|
112234
|
+
init_role_task();
|
|
112235
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync5 } from "fs";
|
|
112236
|
+
import { join as join84 } from "path";
|
|
112237
|
+
var _promptsInitDeps = {
|
|
112238
|
+
log: console.log.bind(console),
|
|
112239
|
+
warn: console.warn.bind(console)
|
|
112240
|
+
};
|
|
112241
|
+
var TEMPLATE_ROLES = [
|
|
112242
|
+
{ file: "test-writer.md", role: "test-writer" },
|
|
112243
|
+
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
112244
|
+
{ file: "verifier.md", role: "verifier" },
|
|
112245
|
+
{ file: "single-session.md", role: "single-session" },
|
|
112246
|
+
{ file: "tdd-simple.md", role: "tdd-simple" }
|
|
112247
|
+
];
|
|
112248
|
+
var TEMPLATE_HEADER = `<!--
|
|
112249
|
+
This file controls the role-body section of the nax prompt for this role.
|
|
112250
|
+
Edit the content below to customize the task instructions given to the agent.
|
|
112251
|
+
|
|
112252
|
+
NON-OVERRIDABLE SECTIONS (always injected by nax, cannot be changed here):
|
|
112253
|
+
- Isolation rules (scope, file access boundaries)
|
|
112254
|
+
- Story context (acceptance criteria, description, dependencies)
|
|
112255
|
+
- Conventions (project coding standards)
|
|
112256
|
+
|
|
112257
|
+
To activate overrides, add to your nax/config.json:
|
|
112258
|
+
{ "prompts": { "overrides": { "<role>": ".nax/templates/<role>.md" } } }
|
|
112259
|
+
-->
|
|
112260
|
+
|
|
112261
|
+
`;
|
|
112262
|
+
async function promptsInitCommand(options) {
|
|
112263
|
+
const { workdir, force = false, autoWireConfig = true } = options;
|
|
112264
|
+
const templatesDir = join84(workdir, ".nax", "templates");
|
|
112265
|
+
mkdirSync5(templatesDir, { recursive: true });
|
|
112266
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync26(join84(templatesDir, f)));
|
|
112267
|
+
if (existingFiles.length > 0 && !force) {
|
|
112268
|
+
_promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
112269
|
+
Pass --force to overwrite existing templates.`);
|
|
112270
|
+
return [];
|
|
112271
|
+
}
|
|
112272
|
+
const written = [];
|
|
112273
|
+
for (const template of TEMPLATE_ROLES) {
|
|
112274
|
+
const filePath = join84(templatesDir, template.file);
|
|
112275
|
+
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
112276
|
+
const content = TEMPLATE_HEADER + roleBody;
|
|
112277
|
+
await Bun.write(filePath, content);
|
|
112278
|
+
written.push(filePath);
|
|
112279
|
+
}
|
|
112280
|
+
_promptsInitDeps.log(`[OK] Written ${written.length} template files to nax/templates/:`);
|
|
112281
|
+
for (const filePath of written) {
|
|
112282
|
+
_promptsInitDeps.log(` - ${filePath.replace(`${workdir}/`, "")}`);
|
|
112283
|
+
}
|
|
112284
|
+
if (autoWireConfig) {
|
|
112285
|
+
await autoWirePromptsConfig(workdir);
|
|
112286
|
+
}
|
|
112287
|
+
return written;
|
|
112288
|
+
}
|
|
112289
|
+
async function autoWirePromptsConfig(workdir) {
|
|
112290
|
+
const configPath = join84(workdir, "nax.config.json");
|
|
112291
|
+
if (!existsSync26(configPath)) {
|
|
112292
|
+
const exampleConfig = JSON.stringify({
|
|
112293
|
+
prompts: {
|
|
112294
|
+
overrides: {
|
|
112295
|
+
"test-writer": ".nax/templates/test-writer.md",
|
|
112296
|
+
implementer: ".nax/templates/implementer.md",
|
|
112297
|
+
verifier: ".nax/templates/verifier.md",
|
|
112298
|
+
"single-session": ".nax/templates/single-session.md",
|
|
112299
|
+
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
112300
|
+
}
|
|
112301
|
+
}
|
|
112302
|
+
}, null, 2);
|
|
112303
|
+
_promptsInitDeps.log(`
|
|
112304
|
+
No nax.config.json found. To activate overrides, create nax/config.json with:
|
|
112305
|
+
${exampleConfig}`);
|
|
112306
|
+
return;
|
|
112307
|
+
}
|
|
112308
|
+
const configFile = Bun.file(configPath);
|
|
112309
|
+
const configContent = await configFile.text();
|
|
112310
|
+
const config2 = JSON.parse(configContent);
|
|
112311
|
+
if (config2.prompts?.overrides && Object.keys(config2.prompts.overrides).length > 0) {
|
|
112312
|
+
_promptsInitDeps.log(`[INFO] prompts.overrides already configured in nax.config.json. Skipping auto-wiring.
|
|
112313
|
+
` + " To reset overrides, remove the prompts.overrides section and re-run this command.");
|
|
112314
|
+
return;
|
|
112315
|
+
}
|
|
112316
|
+
const overrides = {
|
|
112317
|
+
"test-writer": ".nax/templates/test-writer.md",
|
|
112318
|
+
implementer: ".nax/templates/implementer.md",
|
|
112319
|
+
verifier: ".nax/templates/verifier.md",
|
|
112320
|
+
"single-session": ".nax/templates/single-session.md",
|
|
112321
|
+
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
112322
|
+
};
|
|
112323
|
+
if (!config2.prompts) {
|
|
112324
|
+
config2.prompts = {};
|
|
112325
|
+
}
|
|
112326
|
+
config2.prompts.overrides = overrides;
|
|
112327
|
+
const updatedConfig = formatConfigJson(config2);
|
|
112328
|
+
await Bun.write(configPath, updatedConfig);
|
|
112329
|
+
_promptsInitDeps.log("[OK] Auto-wired prompts.overrides in nax.config.json");
|
|
112330
|
+
}
|
|
112331
|
+
function formatConfigJson(config2) {
|
|
112332
|
+
const lines = ["{"];
|
|
112333
|
+
const keys = Object.keys(config2);
|
|
112334
|
+
for (let i = 0;i < keys.length; i++) {
|
|
112335
|
+
const key = keys[i];
|
|
112336
|
+
const value = config2[key];
|
|
112337
|
+
const isLast = i === keys.length - 1;
|
|
112338
|
+
if (key === "prompts" && typeof value === "object" && value !== null) {
|
|
112339
|
+
const promptsObj = value;
|
|
112340
|
+
if (promptsObj.overrides) {
|
|
112341
|
+
const overridesJson = JSON.stringify(promptsObj.overrides);
|
|
112342
|
+
lines.push(` "${key}": { "overrides": ${overridesJson} }${isLast ? "" : ","}`);
|
|
112343
|
+
} else {
|
|
112344
|
+
lines.push(` "${key}": ${JSON.stringify(value)}${isLast ? "" : ","}`);
|
|
112345
|
+
}
|
|
112346
|
+
} else {
|
|
112347
|
+
lines.push(` "${key}": ${JSON.stringify(value)}${isLast ? "" : ","}`);
|
|
112348
|
+
}
|
|
112349
|
+
}
|
|
112350
|
+
lines.push("}");
|
|
112351
|
+
return lines.join(`
|
|
112352
|
+
`);
|
|
112353
|
+
}
|
|
112354
|
+
// src/cli/prompts-export.ts
|
|
112355
|
+
init_prompts();
|
|
112356
|
+
var VALID_EXPORT_ROLES = ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"];
|
|
112357
|
+
async function exportPromptCommand(options) {
|
|
112358
|
+
const { role, out } = options;
|
|
112359
|
+
if (!VALID_EXPORT_ROLES.includes(role)) {
|
|
112360
|
+
console.error(`[ERROR] Invalid role: "${role}". Valid roles: ${VALID_EXPORT_ROLES.join(", ")}`);
|
|
112361
|
+
process.exit(1);
|
|
112362
|
+
}
|
|
112363
|
+
const stubStory = {
|
|
112364
|
+
id: "EXAMPLE",
|
|
112365
|
+
title: "Example story",
|
|
112366
|
+
description: "Story ID: EXAMPLE. This is a placeholder story used to demonstrate the default prompt.",
|
|
112367
|
+
acceptanceCriteria: ["AC-1: Example criterion"],
|
|
112368
|
+
tags: [],
|
|
112369
|
+
dependencies: [],
|
|
112370
|
+
status: "pending",
|
|
112371
|
+
passes: false,
|
|
112372
|
+
escalations: [],
|
|
112373
|
+
attempts: 0
|
|
112374
|
+
};
|
|
112375
|
+
const prompt = await TddPromptBuilder.for(role).story(stubStory).build();
|
|
112376
|
+
if (out) {
|
|
112377
|
+
await Bun.write(out, prompt);
|
|
112378
|
+
console.log(`[OK] Exported prompt for "${role}" to ${out}`);
|
|
112379
|
+
} else {
|
|
112380
|
+
console.log(prompt);
|
|
112381
|
+
}
|
|
112382
|
+
}
|
|
112134
112383
|
// src/cli/index.ts
|
|
112135
|
-
init_prompts2();
|
|
112136
112384
|
init_init2();
|
|
112137
112385
|
init_setup();
|
|
112138
112386
|
init_setup_write();
|
|
@@ -112207,7 +112455,7 @@ function pad(str, width) {
|
|
|
112207
112455
|
init_source();
|
|
112208
112456
|
init_loader();
|
|
112209
112457
|
init_generator2();
|
|
112210
|
-
import { existsSync as
|
|
112458
|
+
import { existsSync as existsSync27 } from "fs";
|
|
112211
112459
|
import { join as join90 } from "path";
|
|
112212
112460
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
112213
112461
|
async function generateCommand(options) {
|
|
@@ -112274,7 +112522,7 @@ async function generateCommand(options) {
|
|
|
112274
112522
|
const contextPath = options.context ? join90(workdir, options.context) : join90(workdir, ".nax/context.md");
|
|
112275
112523
|
const outputDir = options.output ? join90(workdir, options.output) : workdir;
|
|
112276
112524
|
const autoInject = !options.noAutoInject;
|
|
112277
|
-
if (!
|
|
112525
|
+
if (!existsSync27(contextPath)) {
|
|
112278
112526
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
112279
112527
|
console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
|
|
112280
112528
|
process.exit(1);
|
|
@@ -112377,7 +112625,7 @@ async function generateCommand(options) {
|
|
|
112377
112625
|
}
|
|
112378
112626
|
// src/cli/config-display.ts
|
|
112379
112627
|
init_loader();
|
|
112380
|
-
import { existsSync as
|
|
112628
|
+
import { existsSync as existsSync29 } from "fs";
|
|
112381
112629
|
import { join as join93 } from "path";
|
|
112382
112630
|
|
|
112383
112631
|
// src/cli/config-descriptions.ts
|
|
@@ -112442,6 +112690,9 @@ var FIELD_DESCRIPTIONS = {
|
|
|
112442
112690
|
"execution.mutationCheck.maxMutants": "Mutants tested per story (default: 3). Candidates are gathered across all changed files and sampled evenly, so raising this widens coverage at a cost of one scoped test run per mutant.",
|
|
112443
112691
|
"execution.mutationCheck.timeoutSeconds": "Per-mutant scoped test-run timeout in seconds (default: 60). Worst-case added wall clock per story is maxMutants x timeoutSeconds.",
|
|
112444
112692
|
"execution.storyIsolation": 'Story isolation mode. "shared" (default): all stories run on the main branch. "worktree": each story runs in an isolated git worktree (.nax-wt/<storyId>/); passed stories merge into main, failed commits never reach main.',
|
|
112693
|
+
"execution.worktreeDependencies": 'How a story worktree gets its dependencies (only applies when storyIsolation="worktree"). "off" (default): install nothing. Because worktrees live at <projectRoot>/.nax-wt/<storyId>/ \u2014 inside the project root \u2014 Node/Bun resolution walks up to the root node_modules, so JS/TS repos need nothing here. "provision": run execution.worktreeDependencies.setupCommand from the worktree root before the story starts (required for ecosystems with no upward resolution: a Python venv, bundler, composer).',
|
|
112694
|
+
"execution.worktreeDependencies.mode": 'Dependency preparation strategy for story worktrees: "off" (default, install nothing) or "provision" (run setupCommand first).',
|
|
112695
|
+
"execution.worktreeDependencies.setupCommand": 'Install command run from the worktree root before a story starts, e.g. "bun install --frozen-lockfile". Rejected unless mode is "provision".',
|
|
112445
112696
|
quality: "Quality gate configuration",
|
|
112446
112697
|
"quality.commands": "Custom quality commands",
|
|
112447
112698
|
"quality.commands.typecheck": "Custom typecheck command",
|
|
@@ -112625,10 +112876,10 @@ function deepEqual(a, b) {
|
|
|
112625
112876
|
init_defaults();
|
|
112626
112877
|
init_loader();
|
|
112627
112878
|
init_merger();
|
|
112628
|
-
import { existsSync as
|
|
112879
|
+
import { existsSync as existsSync28 } from "fs";
|
|
112629
112880
|
import { join as join91 } from "path";
|
|
112630
112881
|
async function loadConfigFile(path32) {
|
|
112631
|
-
if (!
|
|
112882
|
+
if (!existsSync28(path32))
|
|
112632
112883
|
return null;
|
|
112633
112884
|
try {
|
|
112634
112885
|
return await Bun.file(path32).json();
|
|
@@ -112825,7 +113076,7 @@ function determineConfigSources() {
|
|
|
112825
113076
|
};
|
|
112826
113077
|
}
|
|
112827
113078
|
function fileExists(path32) {
|
|
112828
|
-
return
|
|
113079
|
+
return existsSync29(path32);
|
|
112829
113080
|
}
|
|
112830
113081
|
function displayConfigWithDescriptions(obj, path32, sources, indent = 0) {
|
|
112831
113082
|
const indentStr = " ".repeat(indent);
|
|
@@ -113070,7 +113321,7 @@ function pad2(str, width) {
|
|
|
113070
113321
|
init_source();
|
|
113071
113322
|
init_engine();
|
|
113072
113323
|
init_effectiveness();
|
|
113073
|
-
import { existsSync as
|
|
113324
|
+
import { existsSync as existsSync30 } from "fs";
|
|
113074
113325
|
|
|
113075
113326
|
// src/context/engine/effectiveness-eval.ts
|
|
113076
113327
|
init_errors();
|
|
@@ -113320,7 +113571,7 @@ async function contextInspectCommand(options) {
|
|
|
113320
113571
|
}
|
|
113321
113572
|
}
|
|
113322
113573
|
var _effectivenessEvalDeps2 = {
|
|
113323
|
-
existsSync:
|
|
113574
|
+
existsSync: existsSync30,
|
|
113324
113575
|
readLabels: async (path32) => {
|
|
113325
113576
|
return await Bun.file(path32).text();
|
|
113326
113577
|
},
|
|
@@ -113409,11 +113660,11 @@ init_config();
|
|
|
113409
113660
|
init_fragments();
|
|
113410
113661
|
init_logger2();
|
|
113411
113662
|
init_source();
|
|
113412
|
-
import { existsSync as
|
|
113663
|
+
import { existsSync as existsSync31 } from "fs";
|
|
113413
113664
|
import { join as join94 } from "path";
|
|
113414
113665
|
var _contextFragmentsDeps = {
|
|
113415
113666
|
loadPRD: async (path32) => {
|
|
113416
|
-
if (!
|
|
113667
|
+
if (!existsSync31(path32)) {
|
|
113417
113668
|
return { kind: "missing" };
|
|
113418
113669
|
}
|
|
113419
113670
|
try {
|
|
@@ -114066,7 +114317,7 @@ async function resolveRunProfileOverride(opts) {
|
|
|
114066
114317
|
// src/cli/features-resolve.ts
|
|
114067
114318
|
init_config();
|
|
114068
114319
|
init_test_runners();
|
|
114069
|
-
import { existsSync as
|
|
114320
|
+
import { existsSync as existsSync33, readdirSync as readdirSync6 } from "fs";
|
|
114070
114321
|
import { join as join99, relative as relative17 } from "path";
|
|
114071
114322
|
|
|
114072
114323
|
// src/cli/features-acceptance.ts
|
|
@@ -114074,7 +114325,7 @@ init_acceptance2();
|
|
|
114074
114325
|
init_config();
|
|
114075
114326
|
init_logger2();
|
|
114076
114327
|
init_prd();
|
|
114077
|
-
import { existsSync as
|
|
114328
|
+
import { existsSync as existsSync32 } from "fs";
|
|
114078
114329
|
import { join as join98, relative as relative16 } from "path";
|
|
114079
114330
|
async function resolveFeatureAcceptance(featureName, workdir) {
|
|
114080
114331
|
let enabled = true;
|
|
@@ -114090,7 +114341,7 @@ async function resolveFeatureAcceptance(featureName, workdir) {
|
|
|
114090
114341
|
return { status: "disabled", enabled: false, groups: [] };
|
|
114091
114342
|
}
|
|
114092
114343
|
const prdPath = join98(naxDir, "features", featureName, "prd.json");
|
|
114093
|
-
if (!
|
|
114344
|
+
if (!existsSync32(prdPath)) {
|
|
114094
114345
|
return { status: "no-prd", enabled, groups: [] };
|
|
114095
114346
|
}
|
|
114096
114347
|
const prd = await loadPRD(prdPath);
|
|
@@ -114134,7 +114385,7 @@ async function resolveTestPatterns(workdir) {
|
|
|
114134
114385
|
}
|
|
114135
114386
|
}
|
|
114136
114387
|
async function isNonEmptyFile(absolutePath) {
|
|
114137
|
-
if (!
|
|
114388
|
+
if (!existsSync33(absolutePath))
|
|
114138
114389
|
return false;
|
|
114139
114390
|
const content = await Bun.file(absolutePath).text();
|
|
114140
114391
|
return content.trim().length > 0;
|
|
@@ -114159,7 +114410,7 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
114159
114410
|
return { source: { kind: "markdown", path: relative17(repoRoot, docsSpecExact) }, checked };
|
|
114160
114411
|
}
|
|
114161
114412
|
const docsSpecsDir = join99(repoRoot, "docs", "specs");
|
|
114162
|
-
if (
|
|
114413
|
+
if (existsSync33(docsSpecsDir)) {
|
|
114163
114414
|
const glob = new Bun.Glob(`*${name}*.md`);
|
|
114164
114415
|
for (const match of glob.scanSync({ cwd: docsSpecsDir, absolute: false })) {
|
|
114165
114416
|
const abs = join99(docsSpecsDir, match);
|
|
@@ -114175,20 +114426,20 @@ async function searchSpecSource(naxDir, repoRoot, name) {
|
|
|
114175
114426
|
const prdRel = relative17(repoRoot, prdAbs);
|
|
114176
114427
|
if (!checked.includes(prdRel))
|
|
114177
114428
|
checked.push(prdRel);
|
|
114178
|
-
if (
|
|
114429
|
+
if (existsSync33(prdAbs)) {
|
|
114179
114430
|
return { source: { kind: "prd", path: prdRel }, checked };
|
|
114180
114431
|
}
|
|
114181
114432
|
return { source: null, checked };
|
|
114182
114433
|
}
|
|
114183
114434
|
function discoverCandidates(naxDir) {
|
|
114184
114435
|
const featuresDir2 = join99(naxDir, "features");
|
|
114185
|
-
if (!
|
|
114436
|
+
if (!existsSync33(featuresDir2))
|
|
114186
114437
|
return [];
|
|
114187
114438
|
return readdirSync6(featuresDir2, { withFileTypes: true }).filter((e) => {
|
|
114188
114439
|
if (!e.isDirectory())
|
|
114189
114440
|
return false;
|
|
114190
114441
|
const dir = join99(featuresDir2, e.name);
|
|
114191
|
-
return
|
|
114442
|
+
return existsSync33(join99(dir, "prd.json")) || existsSync33(join99(dir, "spec.md"));
|
|
114192
114443
|
}).map((e) => e.name).sort();
|
|
114193
114444
|
}
|
|
114194
114445
|
async function resolveFeatureSpec(name, workdir) {
|
|
@@ -114202,7 +114453,7 @@ async function resolveFeatureSpec(name, workdir) {
|
|
|
114202
114453
|
const repoRoot = join99(naxDir, "..");
|
|
114203
114454
|
if (name !== undefined && (name.startsWith("./") || name.startsWith("/") || name.endsWith(".md"))) {
|
|
114204
114455
|
const abs = name.startsWith("/") ? name : join99(workdir, name);
|
|
114205
|
-
if (!
|
|
114456
|
+
if (!existsSync33(abs)) {
|
|
114206
114457
|
return {
|
|
114207
114458
|
status: "missing",
|
|
114208
114459
|
featureName: null,
|
|
@@ -114242,7 +114493,7 @@ async function resolveFeatureSpec(name, workdir) {
|
|
|
114242
114493
|
};
|
|
114243
114494
|
}
|
|
114244
114495
|
const featureDir2 = join99(naxDir, "features", name);
|
|
114245
|
-
if (
|
|
114496
|
+
if (existsSync33(featureDir2)) {
|
|
114246
114497
|
return {
|
|
114247
114498
|
status: "missing",
|
|
114248
114499
|
featureName: name,
|
|
@@ -114624,7 +114875,7 @@ async function detectCommand(options) {
|
|
|
114624
114875
|
|
|
114625
114876
|
// src/commands/logs.ts
|
|
114626
114877
|
init_common();
|
|
114627
|
-
import { existsSync as
|
|
114878
|
+
import { existsSync as existsSync35 } from "fs";
|
|
114628
114879
|
import { join as join104 } from "path";
|
|
114629
114880
|
|
|
114630
114881
|
// src/commands/logs-formatter.ts
|
|
@@ -114635,7 +114886,7 @@ import { join as join103 } from "path";
|
|
|
114635
114886
|
|
|
114636
114887
|
// src/commands/logs-reader.ts
|
|
114637
114888
|
init_paths3();
|
|
114638
|
-
import { existsSync as
|
|
114889
|
+
import { existsSync as existsSync34, readdirSync as readdirSync7 } from "fs";
|
|
114639
114890
|
import { readdir as readdir6 } from "fs/promises";
|
|
114640
114891
|
import { join as join102 } from "path";
|
|
114641
114892
|
var _logsReaderDeps = {
|
|
@@ -114675,7 +114926,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
114675
114926
|
if (!matched) {
|
|
114676
114927
|
throw new Error(`Run not found in registry: ${runId}`);
|
|
114677
114928
|
}
|
|
114678
|
-
if (!
|
|
114929
|
+
if (!existsSync34(matched.eventsDir)) {
|
|
114679
114930
|
console.log(`Log directory unavailable for run: ${runId}`);
|
|
114680
114931
|
return null;
|
|
114681
114932
|
}
|
|
@@ -114896,7 +115147,7 @@ async function logsCommand(options) {
|
|
|
114896
115147
|
const featureName = resolveSingleFeature(naxDir, "pass -r <runId> (see `nax runs list`)");
|
|
114897
115148
|
const featureDir2 = join104(naxDir, "features", featureName);
|
|
114898
115149
|
const runsDir = join104(featureDir2, "runs");
|
|
114899
|
-
if (!
|
|
115150
|
+
if (!existsSync35(runsDir)) {
|
|
114900
115151
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
114901
115152
|
}
|
|
114902
115153
|
if (options.list) {
|
|
@@ -114920,7 +115171,7 @@ init_config();
|
|
|
114920
115171
|
init_prd();
|
|
114921
115172
|
init_precheck();
|
|
114922
115173
|
init_common();
|
|
114923
|
-
import { existsSync as
|
|
115174
|
+
import { existsSync as existsSync36 } from "fs";
|
|
114924
115175
|
import { join as join105 } from "path";
|
|
114925
115176
|
async function precheckCommand(options) {
|
|
114926
115177
|
const resolved = resolveProject2({
|
|
@@ -114945,11 +115196,11 @@ async function precheckCommand(options) {
|
|
|
114945
115196
|
}
|
|
114946
115197
|
const featureDir2 = join105(naxDir, "features", featureName);
|
|
114947
115198
|
const prdPath = join105(featureDir2, "prd.json");
|
|
114948
|
-
if (!
|
|
115199
|
+
if (!existsSync36(featureDir2)) {
|
|
114949
115200
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
114950
115201
|
process.exit(1);
|
|
114951
115202
|
}
|
|
114952
|
-
if (!
|
|
115203
|
+
if (!existsSync36(prdPath)) {
|
|
114953
115204
|
console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
|
|
114954
115205
|
console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
|
|
114955
115206
|
process.exit(EXIT_CODES.INVALID_PRD);
|
|
@@ -114966,7 +115217,7 @@ async function precheckCommand(options) {
|
|
|
114966
115217
|
// src/commands/replay.ts
|
|
114967
115218
|
init_errors();
|
|
114968
115219
|
init_metrics();
|
|
114969
|
-
import { existsSync as
|
|
115220
|
+
import { existsSync as existsSync37 } from "fs";
|
|
114970
115221
|
import { dirname as dirname19 } from "path";
|
|
114971
115222
|
|
|
114972
115223
|
// src/replay/discovery.ts
|
|
@@ -115253,7 +115504,7 @@ function renderReport(timeline, options = {}) {
|
|
|
115253
115504
|
|
|
115254
115505
|
// src/commands/replay.ts
|
|
115255
115506
|
async function readJsonlLenient(path32) {
|
|
115256
|
-
if (!
|
|
115507
|
+
if (!existsSync37(path32))
|
|
115257
115508
|
return [];
|
|
115258
115509
|
const content = await Bun.file(path32).text();
|
|
115259
115510
|
const lines = content.split(`
|
|
@@ -115270,7 +115521,7 @@ async function readJsonlLenient(path32) {
|
|
|
115270
115521
|
return entries;
|
|
115271
115522
|
}
|
|
115272
115523
|
async function readJsonOrUndefined(path32) {
|
|
115273
|
-
if (!
|
|
115524
|
+
if (!existsSync37(path32))
|
|
115274
115525
|
return;
|
|
115275
115526
|
try {
|
|
115276
115527
|
return await Bun.file(path32).json();
|
|
@@ -115362,12 +115613,12 @@ init_paths();
|
|
|
115362
115613
|
init_errors();
|
|
115363
115614
|
init_checkpoint();
|
|
115364
115615
|
init_runtime();
|
|
115365
|
-
import { existsSync as
|
|
115616
|
+
import { existsSync as existsSync38 } from "fs";
|
|
115366
115617
|
import { basename as basename21, join as join107 } from "path";
|
|
115367
115618
|
async function defaultCheckpointExists(featureDir2) {
|
|
115368
|
-
if (!featureDir2 || !
|
|
115619
|
+
if (!featureDir2 || !existsSync38(featureDir2))
|
|
115369
115620
|
return false;
|
|
115370
|
-
return
|
|
115621
|
+
return existsSync38(join107(featureDir2, "checkpoint.jsonl"));
|
|
115371
115622
|
}
|
|
115372
115623
|
async function defaultLoadCheckpoints(featureDir2) {
|
|
115373
115624
|
return loadCheckpoints(featureDir2);
|
|
@@ -115417,7 +115668,7 @@ function registerResumeCommand(program2) {
|
|
|
115417
115668
|
const { findProjectDir: findProjectDir2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
115418
115669
|
const { run: run2 } = await Promise.resolve().then(() => (init_execution2(), exports_execution));
|
|
115419
115670
|
const { applyResumeModeDeps: applyResumeModeDeps2 } = await Promise.resolve().then(() => (init_checkpoint(), exports_checkpoint));
|
|
115420
|
-
const { existsSync:
|
|
115671
|
+
const { existsSync: existsSync39, mkdirSync: mkdirSync8 } = await import("fs");
|
|
115421
115672
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
115422
115673
|
const { loadPRD: loadPRD2 } = await Promise.resolve().then(() => (init_prd(), exports_prd));
|
|
115423
115674
|
const { loadHooksConfig: loadHooksConfig2 } = await Promise.resolve().then(() => (init_hooks(), exports_hooks));
|
|
@@ -115434,7 +115685,7 @@ function registerResumeCommand(program2) {
|
|
|
115434
115685
|
runInvocation: async (feature, opts) => {
|
|
115435
115686
|
const config2 = await loadConfig2(naxDir ?? undefined);
|
|
115436
115687
|
const prdPath = join107(opts.featureDir ?? "", "prd.json");
|
|
115437
|
-
if (!
|
|
115688
|
+
if (!existsSync39(prdPath)) {
|
|
115438
115689
|
process.stderr.write(`Feature "${feature}" not found or missing prd.json
|
|
115439
115690
|
`);
|
|
115440
115691
|
return 1;
|
|
@@ -123569,151 +123820,18 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
123569
123820
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
123570
123821
|
process.exit(1);
|
|
123571
123822
|
}
|
|
123572
|
-
|
|
123573
|
-
const { initPackage: initPkg } = await Promise.resolve().then(() => (init_init_context(), exports_init_context));
|
|
123574
|
-
try {
|
|
123575
|
-
await initPkg(workdir, options.package, options.force);
|
|
123576
|
-
console.log(source_default.green(`
|
|
123577
|
-
[OK] Package scaffold created.`));
|
|
123578
|
-
console.log(source_default.dim(` Created: ${options.package}/nax/context.md`));
|
|
123579
|
-
console.log(source_default.dim(`
|
|
123580
|
-
Next: nax generate --package ${options.package}`));
|
|
123581
|
-
} catch (err) {
|
|
123582
|
-
console.error(source_default.red(`Error: ${err.message}`));
|
|
123583
|
-
process.exit(1);
|
|
123584
|
-
}
|
|
123585
|
-
return;
|
|
123586
|
-
}
|
|
123587
|
-
const naxDir = join115(workdir, ".nax");
|
|
123588
|
-
if (existsSync41(naxDir) && !options.force) {
|
|
123589
|
-
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
123590
|
-
return;
|
|
123591
|
-
}
|
|
123592
|
-
if (options.name) {
|
|
123593
|
-
const { validateProjectName: validateProjectName2, checkInitCollision: checkInitCollision2 } = await Promise.resolve().then(() => (init_init2(), exports_init));
|
|
123594
|
-
const nameValidation = validateProjectName2(options.name);
|
|
123595
|
-
if (!nameValidation.valid) {
|
|
123596
|
-
console.error(source_default.red(`Invalid project name "${options.name}": ${nameValidation.error}`));
|
|
123597
|
-
process.exit(1);
|
|
123598
|
-
}
|
|
123599
|
-
if (!options.force) {
|
|
123600
|
-
const collision = await checkInitCollision2(options.name, workdir, null);
|
|
123601
|
-
if (collision.collision && collision.existing) {
|
|
123602
|
-
console.error(source_default.red([
|
|
123603
|
-
`Project name collision: "${options.name}"`,
|
|
123604
|
-
` This project: ${workdir}`,
|
|
123605
|
-
` Already in use: ${collision.existing.workdir} (last run: ${collision.existing.lastSeen})`,
|
|
123606
|
-
" Resolve:",
|
|
123607
|
-
" 1. Rename: choose a different --name",
|
|
123608
|
-
` 2. Reclaim: nax migrate --reclaim ${options.name}`,
|
|
123609
|
-
` 3. Merge: nax migrate --merge ${options.name}`
|
|
123610
|
-
].join(`
|
|
123611
|
-
`)));
|
|
123612
|
-
process.exit(1);
|
|
123613
|
-
}
|
|
123614
|
-
}
|
|
123615
|
-
}
|
|
123616
|
-
mkdirSync8(join115(naxDir, "features"), { recursive: true });
|
|
123617
|
-
mkdirSync8(join115(naxDir, "hooks"), { recursive: true });
|
|
123618
|
-
const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
|
|
123619
|
-
await Bun.write(join115(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
|
|
123620
|
-
await Bun.write(join115(naxDir, "hooks.json"), JSON.stringify({
|
|
123621
|
-
hooks: {
|
|
123622
|
-
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
123623
|
-
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
123624
|
-
"on-pause": { command: 'echo "nax paused: $NAX_REASON"', enabled: false },
|
|
123625
|
-
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
123626
|
-
}
|
|
123627
|
-
}, null, 2));
|
|
123628
|
-
await Bun.write(join115(naxDir, ".gitignore"), `# nax temp files
|
|
123629
|
-
*.tmp
|
|
123630
|
-
.paused.json
|
|
123631
|
-
.nax-verifier-verdict.json
|
|
123632
|
-
`);
|
|
123633
|
-
await Bun.write(join115(naxDir, "context.md"), `# Project Context
|
|
123634
|
-
|
|
123635
|
-
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
123636
|
-
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
123637
|
-
|
|
123638
|
-
> Project metadata (dependencies, commands) is auto-injected by \`nax generate\`.
|
|
123639
|
-
|
|
123640
|
-
## Coding Standards
|
|
123641
|
-
|
|
123642
|
-
- Follow the project's existing code style and conventions
|
|
123643
|
-
- Write clear, self-documenting code with meaningful names
|
|
123644
|
-
- Keep functions small and focused (single responsibility)
|
|
123645
|
-
- Prefer immutability over mutation
|
|
123646
|
-
- Use consistent formatting throughout the codebase
|
|
123647
|
-
|
|
123648
|
-
## Testing Requirements
|
|
123649
|
-
|
|
123650
|
-
- All new code must include tests
|
|
123651
|
-
- Tests should cover happy paths, edge cases, and error conditions
|
|
123652
|
-
- Aim for high test coverage (80%+ recommended)
|
|
123653
|
-
- Tests must pass before marking a story as complete
|
|
123654
|
-
- Before writing tests, read existing test files to understand what is already covered
|
|
123655
|
-
- Do not duplicate test coverage that prior stories already wrote
|
|
123656
|
-
- Focus on testing NEW behavior introduced by this story
|
|
123657
|
-
|
|
123658
|
-
## Architecture Rules
|
|
123659
|
-
|
|
123660
|
-
- Follow the project's existing architecture patterns
|
|
123661
|
-
- Each module should have a clear, single purpose
|
|
123662
|
-
- Avoid tight coupling between modules
|
|
123663
|
-
- Use dependency injection where appropriate
|
|
123664
|
-
- Document architectural decisions in comments or docs
|
|
123665
|
-
|
|
123666
|
-
## Forbidden Patterns
|
|
123667
|
-
|
|
123668
|
-
- No hardcoded secrets, API keys, or credentials
|
|
123669
|
-
- No console.log in production code (use proper logging)
|
|
123670
|
-
- No \`any\` types in TypeScript (use proper typing)
|
|
123671
|
-
- No commented-out code (use version control instead)
|
|
123672
|
-
- No large files (split into smaller, focused modules)
|
|
123673
|
-
|
|
123674
|
-
## Commit Standards
|
|
123675
|
-
|
|
123676
|
-
- Write clear, descriptive commit messages
|
|
123677
|
-
- Follow conventional commits format (feat:, fix:, refactor:, etc.)
|
|
123678
|
-
- Commit early and often with atomic changes
|
|
123679
|
-
- Reference story IDs in commit messages
|
|
123680
|
-
|
|
123681
|
-
## Documentation
|
|
123682
|
-
|
|
123683
|
-
- Add JSDoc comments for public APIs
|
|
123684
|
-
- Update README when adding new features
|
|
123685
|
-
- Document complex algorithms or business logic
|
|
123686
|
-
- Keep documentation up-to-date with code changes
|
|
123687
|
-
|
|
123688
|
-
---
|
|
123689
|
-
|
|
123690
|
-
**Note:** Customize this file to match your project's specific needs.
|
|
123691
|
-
`);
|
|
123823
|
+
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init2(), exports_init));
|
|
123692
123824
|
try {
|
|
123693
|
-
await
|
|
123694
|
-
workdir,
|
|
123825
|
+
await initCommand2({
|
|
123826
|
+
projectRoot: workdir,
|
|
123827
|
+
name: options.name,
|
|
123695
123828
|
force: options.force,
|
|
123696
|
-
|
|
123829
|
+
package: options.package
|
|
123697
123830
|
});
|
|
123698
123831
|
} catch (err) {
|
|
123699
|
-
console.error(source_default.red(`
|
|
123832
|
+
console.error(source_default.red(`Error: ${err.message}`));
|
|
123700
123833
|
process.exit(1);
|
|
123701
123834
|
}
|
|
123702
|
-
console.log(source_default.green("\u2705 Initialized nax"));
|
|
123703
|
-
console.log(source_default.dim(` ${naxDir}/`));
|
|
123704
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 config.json"));
|
|
123705
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 context.md"));
|
|
123706
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 hooks.json"));
|
|
123707
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 features/"));
|
|
123708
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 hooks/"));
|
|
123709
|
-
console.log(source_default.dim(" \u2514\u2500\u2500 templates/"));
|
|
123710
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 test-writer.md"));
|
|
123711
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 implementer.md"));
|
|
123712
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 verifier.md"));
|
|
123713
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 single-session.md"));
|
|
123714
|
-
console.log(source_default.dim(" \u2514\u2500\u2500 tdd-simple.md"));
|
|
123715
|
-
console.log(source_default.dim(`
|
|
123716
|
-
Next: nax features create <name>`));
|
|
123717
123835
|
});
|
|
123718
123836
|
program2.command("setup").description("Analyze repo and generate .nax/config.json via LLM").option("-d, --dir <path>", "Project directory", process.cwd()).option("-a, --agent <name>", "Force a specific agent").option("--fill-scripts", "Add missing quality-gate scripts to package.json", false).option("--dry-run", "Preview planned config without writing files", false).option("--force", "Overwrite existing .nax/config.json", false).action(async (options) => {
|
|
123719
123837
|
let workdir;
|
|
@@ -123793,7 +123911,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
123793
123911
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
123794
123912
|
process.exit(1);
|
|
123795
123913
|
}
|
|
123796
|
-
if (options.from && !
|
|
123914
|
+
if (options.from && !existsSync40(options.from)) {
|
|
123797
123915
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
123798
123916
|
process.exit(1);
|
|
123799
123917
|
}
|
|
@@ -123837,7 +123955,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
123837
123955
|
const featureDir2 = join115(naxDir, "features", options.feature);
|
|
123838
123956
|
const prdPath = join115(featureDir2, "prd.json");
|
|
123839
123957
|
if (options.plan && options.from) {
|
|
123840
|
-
if (
|
|
123958
|
+
if (existsSync40(prdPath) && !options.force) {
|
|
123841
123959
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
123842
123960
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
123843
123961
|
process.exit(1);
|
|
@@ -123901,7 +124019,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
123901
124019
|
process.exit(1);
|
|
123902
124020
|
}
|
|
123903
124021
|
}
|
|
123904
|
-
if (!
|
|
124022
|
+
if (!existsSync40(prdPath)) {
|
|
123905
124023
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
123906
124024
|
process.exit(1);
|
|
123907
124025
|
}
|
|
@@ -124040,7 +124158,7 @@ Scheduled run cancelled.`));
|
|
|
124040
124158
|
}
|
|
124041
124159
|
const latestSymlink = join115(runsDir, "latest.jsonl");
|
|
124042
124160
|
try {
|
|
124043
|
-
if (
|
|
124161
|
+
if (existsSync40(latestSymlink)) {
|
|
124044
124162
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
124045
124163
|
}
|
|
124046
124164
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -124200,7 +124318,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
124200
124318
|
process.exit(1);
|
|
124201
124319
|
}
|
|
124202
124320
|
const featuresDir2 = join115(naxDir, "features");
|
|
124203
|
-
if (!
|
|
124321
|
+
if (!existsSync40(featuresDir2)) {
|
|
124204
124322
|
console.log(source_default.dim("No features yet."));
|
|
124205
124323
|
return;
|
|
124206
124324
|
}
|
|
@@ -124215,7 +124333,7 @@ Features:
|
|
|
124215
124333
|
`));
|
|
124216
124334
|
for (const name of entries) {
|
|
124217
124335
|
const prdPath = join115(featuresDir2, name, "prd.json");
|
|
124218
|
-
if (
|
|
124336
|
+
if (existsSync40(prdPath)) {
|
|
124219
124337
|
const prd = await loadPRD(prdPath);
|
|
124220
124338
|
const c = countStories(prd);
|
|
124221
124339
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|