audit-tools 0.30.49 → 0.30.51
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/audit/cli/advanceAuditCommand.d.ts.map +1 -1
- package/dist/audit/cli/advanceAuditCommand.js +8 -4
- package/dist/audit/cli/advanceAuditCommand.js.map +1 -1
- package/dist/audit/cli/args.d.ts.map +1 -1
- package/dist/audit/cli/args.js +9 -2
- package/dist/audit/cli/args.js.map +1 -1
- package/dist/audit/cli/mergeAndIngestCommand.d.ts.map +1 -1
- package/dist/audit/cli/mergeAndIngestCommand.js +6 -22
- package/dist/audit/cli/mergeAndIngestCommand.js.map +1 -1
- package/dist/audit/cli/validateResultCommand.d.ts.map +1 -1
- package/dist/audit/cli/validateResultCommand.js +19 -6
- package/dist/audit/cli/validateResultCommand.js.map +1 -1
- package/dist/audit/cli/workerRunCommand.d.ts.map +1 -1
- package/dist/audit/cli/workerRunCommand.js +6 -21
- package/dist/audit/cli/workerRunCommand.js.map +1 -1
- package/dist/audit/contracts/workerSchemas.d.ts +27 -3
- package/dist/audit/contracts/workerSchemas.d.ts.map +1 -1
- package/dist/audit/contracts/workerSchemas.js +5 -1
- package/dist/audit/contracts/workerSchemas.js.map +1 -1
- package/dist/audit/extractors/analyzers/html.d.ts +1 -0
- package/dist/audit/extractors/analyzers/html.d.ts.map +1 -1
- package/dist/audit/extractors/analyzers/html.js +5 -3
- package/dist/audit/extractors/analyzers/html.js.map +1 -1
- package/dist/audit/extractors/browserExtension.d.ts.map +1 -1
- package/dist/audit/extractors/browserExtension.js +1 -8
- package/dist/audit/extractors/browserExtension.js.map +1 -1
- package/dist/audit/extractors/graphRoutes.d.ts.map +1 -1
- package/dist/audit/extractors/graphRoutes.js +4 -1
- package/dist/audit/extractors/graphRoutes.js.map +1 -1
- package/dist/audit/orchestrator/autoFixExecutor.d.ts +8 -0
- package/dist/audit/orchestrator/autoFixExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/autoFixExecutor.js +68 -35
- package/dist/audit/orchestrator/autoFixExecutor.js.map +1 -1
- package/dist/audit/orchestrator/runtimeCommand.d.ts.map +1 -1
- package/dist/audit/orchestrator/runtimeCommand.js +7 -1
- package/dist/audit/orchestrator/runtimeCommand.js.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/index.d.ts.map +1 -1
- package/dist/audit/orchestrator/selectiveDeepening/index.js +12 -8
- package/dist/audit/orchestrator/selectiveDeepening/index.js.map +1 -1
- package/dist/audit/orchestrator/taskBuilder.d.ts.map +1 -1
- package/dist/audit/orchestrator/taskBuilder.js +0 -32
- package/dist/audit/orchestrator/taskBuilder.js.map +1 -1
- package/dist/audit/quota/discoveredLimits.d.ts +12 -0
- package/dist/audit/quota/discoveredLimits.d.ts.map +1 -1
- package/dist/audit/quota/discoveredLimits.js +30 -25
- package/dist/audit/quota/discoveredLimits.js.map +1 -1
- package/dist/audit/validation/auditResults.d.ts +16 -0
- package/dist/audit/validation/auditResults.d.ts.map +1 -1
- package/dist/audit/validation/auditResults.js +35 -1
- package/dist/audit/validation/auditResults.js.map +1 -1
- package/dist/remediate/phases/close.d.ts.map +1 -1
- package/dist/remediate/phases/close.js +16 -6
- package/dist/remediate/phases/close.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +4 -1
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +63 -47
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.d.ts +12 -1
- package/dist/remediate/steps/contractPipelinePrompts.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipelinePrompts.js +29 -18
- package/dist/remediate/steps/contractPipelinePrompts.js.map +1 -1
- package/dist/remediate/steps/dispatch.d.ts +20 -3
- package/dist/remediate/steps/dispatch.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch.js +193 -116
- package/dist/remediate/steps/dispatch.js.map +1 -1
- package/dist/remediate/steps/finalGate.d.ts +88 -0
- package/dist/remediate/steps/finalGate.d.ts.map +1 -0
- package/dist/remediate/steps/finalGate.js +171 -0
- package/dist/remediate/steps/finalGate.js.map +1 -0
- package/dist/remediate/steps/leanFastPath.d.ts.map +1 -1
- package/dist/remediate/steps/leanFastPath.js +12 -0
- package/dist/remediate/steps/leanFastPath.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts +3 -81
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +36 -169
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipeline.js +17 -3
- package/dist/remediate/validation/contractPipeline.js.map +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/io/frictionCapture.d.ts +14 -1
- package/dist/shared/io/frictionCapture.d.ts.map +1 -1
- package/dist/shared/io/frictionCapture.js +28 -3
- package/dist/shared/io/frictionCapture.js.map +1 -1
- package/dist/shared/observability/runLog.d.ts +1 -1
- package/dist/shared/observability/runLog.d.ts.map +1 -1
- package/dist/shared/observability/runLog.js.map +1 -1
- package/dist/shared/providers/providerConfirmation.d.ts +16 -1
- package/dist/shared/providers/providerConfirmation.d.ts.map +1 -1
- package/dist/shared/providers/providerConfirmation.js +13 -13
- package/dist/shared/providers/providerConfirmation.js.map +1 -1
- package/dist/shared/providers/providerFactory.d.ts.map +1 -1
- package/dist/shared/providers/providerFactory.js +10 -14
- package/dist/shared/providers/providerFactory.js.map +1 -1
- package/dist/shared/providers/providerPathGuard.d.ts +21 -0
- package/dist/shared/providers/providerPathGuard.d.ts.map +1 -0
- package/dist/shared/providers/providerPathGuard.js +69 -0
- package/dist/shared/providers/providerPathGuard.js.map +1 -0
- package/dist/shared/providers/sharedProviderConfirmation.d.ts +14 -2
- package/dist/shared/providers/sharedProviderConfirmation.d.ts.map +1 -1
- package/dist/shared/providers/sharedProviderConfirmation.js +26 -5
- package/dist/shared/providers/sharedProviderConfirmation.js.map +1 -1
- package/dist/shared/quota/apiPool.d.ts +6 -1
- package/dist/shared/quota/apiPool.d.ts.map +1 -1
- package/dist/shared/quota/apiPool.js +13 -2
- package/dist/shared/quota/apiPool.js.map +1 -1
- package/dist/shared/quota/fileLock.d.ts +17 -2
- package/dist/shared/quota/fileLock.d.ts.map +1 -1
- package/dist/shared/quota/fileLock.js +21 -19
- package/dist/shared/quota/fileLock.js.map +1 -1
- package/dist/shared/types/providerConfirmation.d.ts +9 -0
- package/dist/shared/types/providerConfirmation.d.ts.map +1 -1
- package/package.json +2 -2
- package/scripts/audit/postinstall.mjs +22 -14
|
@@ -69,6 +69,92 @@ function _capacityPoolSummary(poolId, slots, schedule) {
|
|
|
69
69
|
function sortRosterMostCapableFirst(roster) {
|
|
70
70
|
return [...roster].sort((a, b) => compareTier(b.rank, a.rank));
|
|
71
71
|
}
|
|
72
|
+
async function buildHostPoolPreamble(input) {
|
|
73
|
+
const sessionConfig = input.sessionConfig ?? {};
|
|
74
|
+
const providerName = input.providerName ??
|
|
75
|
+
sessionConfig.provider ??
|
|
76
|
+
"claude-code";
|
|
77
|
+
const hostModel = input.hostModel ??
|
|
78
|
+
sessionConfig.block_quota
|
|
79
|
+
?.host_model ??
|
|
80
|
+
null;
|
|
81
|
+
// Quota-key identity: resolved model name, else the host's opaque id, else
|
|
82
|
+
// null → `provider/*`. Per-roster-rank `model_id` overrides per pool below.
|
|
83
|
+
const quotaModelKeySegment = hostModel ?? input.hostModelId ?? null;
|
|
84
|
+
const roster = input.hostModels?.length
|
|
85
|
+
? sortRosterMostCapableFirst(input.hostModels)
|
|
86
|
+
: null;
|
|
87
|
+
const hostLimit = resolveHostConcurrencyLimit({
|
|
88
|
+
hostMaxConcurrent: input.hostMaxConcurrent,
|
|
89
|
+
sessionConfig,
|
|
90
|
+
env: input.env,
|
|
91
|
+
});
|
|
92
|
+
// The capability handshake: the host reported its dispatch model's real
|
|
93
|
+
// context/output window this session (the roster's most capable entry under
|
|
94
|
+
// the multi-rank handshake). Carried into the pool's discoveredLimits so the
|
|
95
|
+
// shared discovered_capability rung sizes the budget to the real window
|
|
96
|
+
// instead of the conservative 32k floor. RPM/TPM stay null and fill from the
|
|
97
|
+
// learned quota state.
|
|
98
|
+
const hostContextTokens = input.hostContextTokens ?? roster?.[0]?.context_tokens ?? null;
|
|
99
|
+
const hostOutputTokens = input.hostOutputTokens ?? roster?.[0]?.output_tokens ?? null;
|
|
100
|
+
const hostCapabilityLimits = hostContextTokens != null || hostOutputTokens != null
|
|
101
|
+
? { context_tokens: hostContextTokens, output_tokens: hostOutputTokens }
|
|
102
|
+
: null;
|
|
103
|
+
let quotaEntries = {};
|
|
104
|
+
try {
|
|
105
|
+
const state = await readQuotaState();
|
|
106
|
+
quotaEntries = state.entries;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
process.stderr.write(`[waveScheduler] readQuotaState failed; degrading to no learned quota entry. ${err instanceof Error ? err.message : String(err)}\n`);
|
|
110
|
+
}
|
|
111
|
+
// The proactive quota snapshot (Claude OAuth source, then learned) so the
|
|
112
|
+
// scheduler can throttle/cooldown from live remaining quota — mirrors
|
|
113
|
+
// audit-code's buildDispatchPool. PREPEND the host-session source keyed on
|
|
114
|
+
// this host pool's own (provider, model) key — first-class PRE-WALL source
|
|
115
|
+
// (graduated remaining_pct → LOW/CRITICAL throttle before a 429), gating on the
|
|
116
|
+
// exact key so it never masks the proactive/learned sources.
|
|
117
|
+
const quotaSource = buildQuotaSource({
|
|
118
|
+
halfLifeHours: sessionConfig.quota
|
|
119
|
+
?.empirical_half_life_hours,
|
|
120
|
+
hostSession: input.hostSession ??
|
|
121
|
+
new HostSessionQuotaSource({
|
|
122
|
+
providerModelKey: buildProviderModelKey(providerName, quotaModelKeySegment),
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
// One capacity pool per reported roster rank (most capable first), each with
|
|
126
|
+
// its own discovered window and quota key; a single pool for the scalar/absent
|
|
127
|
+
// handshake. Built via the shared host-pool-from-roster core so the pool shape
|
|
128
|
+
// + account-keyed pool ids can't drift across the two consumers.
|
|
129
|
+
const primaryPools = await buildHostModelPools({
|
|
130
|
+
providerName,
|
|
131
|
+
hostModel,
|
|
132
|
+
hostConcurrencyLimit: hostLimit,
|
|
133
|
+
quotaSource,
|
|
134
|
+
quotaEntries,
|
|
135
|
+
roster,
|
|
136
|
+
resolve: (entry) => ({
|
|
137
|
+
poolKey: buildProviderModelKey(providerName, entry?.model_id ?? quotaModelKeySegment),
|
|
138
|
+
discoveredLimits: entry
|
|
139
|
+
? { context_tokens: entry.context_tokens, output_tokens: entry.output_tokens }
|
|
140
|
+
: hostCapabilityLimits,
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
sessionConfig,
|
|
145
|
+
providerName,
|
|
146
|
+
hostModel,
|
|
147
|
+
quotaModelKeySegment,
|
|
148
|
+
roster,
|
|
149
|
+
hostLimit,
|
|
150
|
+
hostContextTokens,
|
|
151
|
+
hostOutputTokens,
|
|
152
|
+
hostCapabilityLimits,
|
|
153
|
+
quotaEntries,
|
|
154
|
+
quotaSource,
|
|
155
|
+
primaryPools,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
72
158
|
export async function scheduleWave(input) {
|
|
73
159
|
const sessionConfig = input.sessionConfig ?? {};
|
|
74
160
|
const providerName = input.providerName ?? sessionConfig.provider ?? "claude-code";
|
|
@@ -92,12 +178,6 @@ export async function scheduleWave(input) {
|
|
|
92
178
|
// learned quota state.
|
|
93
179
|
const hostContextTokens = input.hostContextTokens ?? roster?.[0]?.context_tokens ?? null;
|
|
94
180
|
const hostOutputTokens = input.hostOutputTokens ?? roster?.[0]?.output_tokens ?? null;
|
|
95
|
-
const hostCapabilityLimits = hostContextTokens != null || hostOutputTokens != null
|
|
96
|
-
? {
|
|
97
|
-
context_tokens: hostContextTokens,
|
|
98
|
-
output_tokens: hostOutputTokens,
|
|
99
|
-
}
|
|
100
|
-
: null;
|
|
101
181
|
const quota = sessionConfig.quota;
|
|
102
182
|
if (!quota || quota.enabled === false) {
|
|
103
183
|
const cap = hostLimit?.active_subagents ?? DEFAULT_WAVE_SIZE;
|
|
@@ -127,48 +207,19 @@ export async function scheduleWave(input) {
|
|
|
127
207
|
capacity_pools: [_capacityPoolSummary(buildProviderModelKey(providerName, quotaModelKeySegment), waveSize, schedule)],
|
|
128
208
|
};
|
|
129
209
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// audit-code's buildDispatchPool. Cached per key, so one probe per burst.
|
|
141
|
-
// PREPEND the host-session source keyed on this host pool's own (provider,
|
|
142
|
-
// model) key — first-class PRE-WALL source (graduated remaining_pct →
|
|
143
|
-
// LOW/CRITICAL throttle before a 429), gating on the exact key so it never masks
|
|
144
|
-
// the proactive/learned sources. Mirrors audit-code's buildDispatchPool.
|
|
145
|
-
const quotaSource = buildQuotaSource({
|
|
146
|
-
halfLifeHours: sessionConfig.quota
|
|
147
|
-
?.empirical_half_life_hours,
|
|
148
|
-
hostSession: new HostSessionQuotaSource({
|
|
149
|
-
providerModelKey: buildProviderModelKey(providerName, quotaModelKeySegment),
|
|
150
|
-
}),
|
|
151
|
-
});
|
|
152
|
-
// One capacity pool per reported roster rank (most capable first), each with its
|
|
153
|
-
// own discovered window and quota key; a single pool for the scalar/absent
|
|
154
|
-
// handshake. Built via the shared host-pool-from-roster core (same as
|
|
155
|
-
// buildConfirmedPools) so the pool shape + account-keyed pool ids can't drift.
|
|
156
|
-
const pools = await buildHostModelPools({
|
|
157
|
-
providerName,
|
|
158
|
-
hostModel,
|
|
159
|
-
hostConcurrencyLimit: hostLimit,
|
|
160
|
-
quotaSource,
|
|
161
|
-
quotaEntries,
|
|
162
|
-
roster,
|
|
163
|
-
resolve: (entry) => ({
|
|
164
|
-
poolKey: buildProviderModelKey(providerName, entry?.model_id ?? quotaModelKeySegment),
|
|
165
|
-
discoveredLimits: entry
|
|
166
|
-
? { context_tokens: entry.context_tokens, output_tokens: entry.output_tokens }
|
|
167
|
-
: hostCapabilityLimits,
|
|
168
|
-
}),
|
|
210
|
+
const preamble = await buildHostPoolPreamble({
|
|
211
|
+
sessionConfig: input.sessionConfig,
|
|
212
|
+
providerName: input.providerName,
|
|
213
|
+
hostModel: input.hostModel,
|
|
214
|
+
hostMaxConcurrent: input.hostMaxConcurrent,
|
|
215
|
+
hostContextTokens: input.hostContextTokens,
|
|
216
|
+
hostOutputTokens: input.hostOutputTokens,
|
|
217
|
+
hostModels: input.hostModels,
|
|
218
|
+
hostModelId: input.hostModelId,
|
|
219
|
+
env: input.env,
|
|
169
220
|
});
|
|
170
221
|
const capacity = computeDispatchCapacity({
|
|
171
|
-
pools,
|
|
222
|
+
pools: preamble.primaryPools,
|
|
172
223
|
sessionConfig,
|
|
173
224
|
pendingItemTokens: normalizeSlotTokens(input.estimatedSlotTokens, input.itemCount),
|
|
174
225
|
});
|
|
@@ -187,55 +238,17 @@ export async function scheduleWave(input) {
|
|
|
187
238
|
* `driveRollingImplementDispatch`.
|
|
188
239
|
*/
|
|
189
240
|
export async function buildConfirmedPools(input) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
const quotaModelKeySegment = hostModel ?? input.hostModelId ?? null;
|
|
195
|
-
const roster = input.hostModels?.length
|
|
196
|
-
? sortRosterMostCapableFirst(input.hostModels)
|
|
197
|
-
: null;
|
|
198
|
-
const hostLimit = resolveHostConcurrencyLimit({
|
|
241
|
+
// Resolve identity/limits and build the per-rank host-model pools via the
|
|
242
|
+
// SAME preamble `scheduleWave` uses — the two no longer keep parallel copies.
|
|
243
|
+
const { sessionConfig, providerName, quotaSource, quotaEntries, primaryPools } = await buildHostPoolPreamble({
|
|
244
|
+
sessionConfig: input.sessionConfig,
|
|
199
245
|
hostMaxConcurrent: input.hostMaxConcurrent,
|
|
200
|
-
|
|
246
|
+
hostContextTokens: input.hostContextTokens,
|
|
247
|
+
hostOutputTokens: input.hostOutputTokens,
|
|
248
|
+
hostModels: input.hostModels,
|
|
249
|
+
hostModelId: input.hostModelId,
|
|
201
250
|
env: input.env,
|
|
202
|
-
|
|
203
|
-
const hostContextTokens = input.hostContextTokens ?? roster?.[0]?.context_tokens ?? null;
|
|
204
|
-
const hostOutputTokens = input.hostOutputTokens ?? roster?.[0]?.output_tokens ?? null;
|
|
205
|
-
const hostCapabilityLimits = hostContextTokens != null || hostOutputTokens != null
|
|
206
|
-
? { context_tokens: hostContextTokens, output_tokens: hostOutputTokens }
|
|
207
|
-
: null;
|
|
208
|
-
let quotaEntries = {};
|
|
209
|
-
try {
|
|
210
|
-
const state = await readQuotaState();
|
|
211
|
-
quotaEntries = state.entries;
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
// Non-fatal: a missing/locked quota state degrades to no learned entry.
|
|
215
|
-
}
|
|
216
|
-
const quotaSource = buildQuotaSource({
|
|
217
|
-
halfLifeHours: sessionConfig.quota
|
|
218
|
-
?.empirical_half_life_hours,
|
|
219
|
-
hostSession: input.hostSession ??
|
|
220
|
-
new HostSessionQuotaSource({
|
|
221
|
-
providerModelKey: buildProviderModelKey(providerName, quotaModelKeySegment),
|
|
222
|
-
}),
|
|
223
|
-
});
|
|
224
|
-
// Host-model pools via the shared host-pool-from-roster core (identical shape across
|
|
225
|
-
// audit + remediate). The per-tool part is just the key + the roster/capability limits.
|
|
226
|
-
const primaryPools = await buildHostModelPools({
|
|
227
|
-
providerName,
|
|
228
|
-
hostModel,
|
|
229
|
-
hostConcurrencyLimit: hostLimit,
|
|
230
|
-
quotaSource,
|
|
231
|
-
quotaEntries,
|
|
232
|
-
roster,
|
|
233
|
-
resolve: (entry) => ({
|
|
234
|
-
poolKey: buildProviderModelKey(providerName, entry?.model_id ?? quotaModelKeySegment),
|
|
235
|
-
discoveredLimits: entry
|
|
236
|
-
? { context_tokens: entry.context_tokens, output_tokens: entry.output_tokens }
|
|
237
|
-
: hostCapabilityLimits,
|
|
238
|
-
}),
|
|
251
|
+
hostSession: input.hostSession,
|
|
239
252
|
});
|
|
240
253
|
// Every configured dispatchable backend source (any non-IDE source: NIM/vLLM API,
|
|
241
254
|
// a CLI pool, …) becomes a CapacityPool alongside the primary, so the scheduler's
|
|
@@ -1329,17 +1342,48 @@ export function normalizeNodeTestCommand(cmd) {
|
|
|
1329
1342
|
return cmd;
|
|
1330
1343
|
return trimmed.replace(/^node\b/, "node --import tsx/esm");
|
|
1331
1344
|
}
|
|
1345
|
+
/**
|
|
1346
|
+
* True when `cmd` is a test-runner invocation whose target is a whole directory
|
|
1347
|
+
* or the entire suite rather than specific test files — e.g. `npx vitest run
|
|
1348
|
+
* tests/remediate`, `vitest run` (no path), `node --test tests/audit/`. Such a
|
|
1349
|
+
* command, run as an *additional* per-node verify alongside the scoped derive,
|
|
1350
|
+
* re-enters the FULL suite inside a per-node worktree. That is the structural
|
|
1351
|
+
* deadlock proven 2026-06-30: a source node's whole-suite verify fails on a
|
|
1352
|
+
* stale test owned by a DIFFERENT node, and concurrent worktrees race shared
|
|
1353
|
+
* test temp dirs. The derived verify already runs this node's OWN touched
|
|
1354
|
+
* tests, so a host/DAG-authored whole-suite command adds only risk — the tool
|
|
1355
|
+
* drops it rather than relying on the author to scope it (enforce-in-tooling,
|
|
1356
|
+
* never host discretion). A command naming at least one concrete `.test.<ext>`
|
|
1357
|
+
* file is scoped and kept.
|
|
1358
|
+
*/
|
|
1359
|
+
export function isWholeSuiteTestCommand(cmd) {
|
|
1360
|
+
const c = cmd.trim();
|
|
1361
|
+
const isVitest = /\bvitest\b\s+run\b/.test(c) || /\bvitest\b(?!\s+run)/.test(c);
|
|
1362
|
+
const isNodeTest = /^node\b/.test(c) && /\s--test\b/.test(c);
|
|
1363
|
+
if (!isVitest && !isNodeTest)
|
|
1364
|
+
return false;
|
|
1365
|
+
// Tokenise; a concrete test file makes it scoped. Anything that is a runner
|
|
1366
|
+
// with no concrete test-file target (bare runner, or a directory/glob target)
|
|
1367
|
+
// is whole-suite.
|
|
1368
|
+
const namesConcreteTestFile = /(^|\s)[^\s]*\.test\.(ts|tsx|mjs|cjs|js)(\s|$)/.test(c);
|
|
1369
|
+
return !namesConcreteTestFile;
|
|
1370
|
+
}
|
|
1332
1371
|
/**
|
|
1333
1372
|
* Filter a node's `targeted_commands` to the build-free subset for the per-node
|
|
1334
1373
|
* verify section. Build-prepending or build commands are dropped (the host runs
|
|
1335
|
-
* the build centrally) rather than emitted into the prompt.
|
|
1336
|
-
*
|
|
1374
|
+
* the build centrally) rather than emitted into the prompt. Whole-suite /
|
|
1375
|
+
* whole-directory test runs are dropped too — they re-enter the full suite in a
|
|
1376
|
+
* per-node worktree and re-create the cross-node verify deadlock (the scoped
|
|
1377
|
+
* derive already covers this node's own tests). Surviving `node --test`
|
|
1378
|
+
* commands are normalized to carry the tsx loader.
|
|
1337
1379
|
*/
|
|
1338
1380
|
function buildFreeVerifyCommands(commands) {
|
|
1339
1381
|
if (!Array.isArray(commands))
|
|
1340
1382
|
return [];
|
|
1341
1383
|
return commands
|
|
1342
|
-
.filter((c) => typeof c === "string" &&
|
|
1384
|
+
.filter((c) => typeof c === "string" &&
|
|
1385
|
+
isBuildFreeVerifyCommand(c) &&
|
|
1386
|
+
!isWholeSuiteTestCommand(c))
|
|
1343
1387
|
.map(normalizeNodeTestCommand);
|
|
1344
1388
|
}
|
|
1345
1389
|
/** A repo-relative test path → the runner that executes that file directly. */
|
|
@@ -1419,6 +1463,18 @@ function markTerminal(item) {
|
|
|
1419
1463
|
item.started_at ??= now;
|
|
1420
1464
|
item.completed_at = now;
|
|
1421
1465
|
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Load a worker's already-written implement result, distinguishing an ABSENT
|
|
1468
|
+
* file (the worker hasn't run yet → re-dispatch from scratch) from a PRESENT but
|
|
1469
|
+
* INVALID one (the worker ran but emitted malformed/unparseable JSON or a result
|
|
1470
|
+
* that fails the contract). A bare `catch → undefined` conflated the two: a
|
|
1471
|
+
* written-but-invalid file looked identical to "never produced", so the merge
|
|
1472
|
+
* loop silently `continue`d past the block (the missing-file branch never fires
|
|
1473
|
+
* because the file DOES exist) and the node could neither converge nor surface
|
|
1474
|
+
* the corruption. We now archive the invalid file (so a clean re-dispatch can
|
|
1475
|
+
* write a fresh one) and report it loudly, returning `undefined` only for the
|
|
1476
|
+
* genuinely-absent case.
|
|
1477
|
+
*/
|
|
1422
1478
|
async function tryLoadExistingImplementResult(resultPath) {
|
|
1423
1479
|
if (!existsSync(resultPath))
|
|
1424
1480
|
return undefined;
|
|
@@ -1427,7 +1483,13 @@ async function tryLoadExistingImplementResult(resultPath) {
|
|
|
1427
1483
|
assertImplementWorkerResult(result, resultPath);
|
|
1428
1484
|
return result;
|
|
1429
1485
|
}
|
|
1430
|
-
catch {
|
|
1486
|
+
catch (err) {
|
|
1487
|
+
// Present but invalid: do NOT treat it as absent (which would let the block
|
|
1488
|
+
// be silently dropped from the merge). Archive the corrupt file and surface
|
|
1489
|
+
// the reason so a clean re-dispatch produces a valid result.
|
|
1490
|
+
process.stderr.write(`[remediate-code] dispatch: existing implement result ${resultPath} is present but ` +
|
|
1491
|
+
`invalid (${err instanceof Error ? err.message : String(err)}); archiving and re-dispatching\n`);
|
|
1492
|
+
await archiveIncompleteImplementResult(resultPath);
|
|
1431
1493
|
return undefined;
|
|
1432
1494
|
}
|
|
1433
1495
|
}
|
|
@@ -1773,32 +1835,47 @@ allowed in addition to the file access above.
|
|
|
1773
1835
|
// Infra-modifying block detection
|
|
1774
1836
|
// ---------------------------------------------------------------------------
|
|
1775
1837
|
/**
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1838
|
+
* The live dispatch/orchestration modules whose modification can break the
|
|
1839
|
+
* running engine mid-run. Derived from the REAL post-A12 source layout
|
|
1840
|
+
* (`src/remediate/...`) — this module IS one of them (`steps/dispatch.ts`), so
|
|
1841
|
+
* the list is anchored to the actual files on disk, not a hand-typed monorepo
|
|
1842
|
+
* path that drifts when the tree is reorganised (the pre-A12
|
|
1843
|
+
* `packages/remediate-code/...` list silently matched NOTHING after the
|
|
1844
|
+
* collapse, so every infra block rendered as non-infra). Each entry is the
|
|
1845
|
+
* module's path relative to the `src/remediate` area, forward-slash form.
|
|
1778
1846
|
*/
|
|
1779
|
-
const
|
|
1780
|
-
"
|
|
1781
|
-
"
|
|
1782
|
-
"
|
|
1783
|
-
"
|
|
1784
|
-
"
|
|
1785
|
-
|
|
1786
|
-
]);
|
|
1847
|
+
const INFRA_MODULE_SUBPATHS = [
|
|
1848
|
+
"steps/nextStep.ts",
|
|
1849
|
+
"steps/dispatch.ts",
|
|
1850
|
+
"state/store.ts",
|
|
1851
|
+
"steps/contractPipeline.ts",
|
|
1852
|
+
"steps/stepWriter.ts",
|
|
1853
|
+
];
|
|
1787
1854
|
/**
|
|
1788
|
-
*
|
|
1789
|
-
*
|
|
1790
|
-
*
|
|
1855
|
+
* The infra module sub-paths anchored under `src/remediate/` — the canonical
|
|
1856
|
+
* repo-relative form for the current (post-A12) single-package layout. A write
|
|
1857
|
+
* path matches when its normalised (forward-slash) form ends with one of these
|
|
1858
|
+
* segments, so an absolute worktree path
|
|
1859
|
+
* (`.../worktrees/foo/src/remediate/steps/dispatch.ts`), a repo-relative path
|
|
1860
|
+
* (`src/remediate/steps/dispatch.ts`), or a Windows backslash path all match,
|
|
1861
|
+
* while a same-basename file in another area (`src/audit/steps/dispatch.ts`)
|
|
1862
|
+
* does not.
|
|
1863
|
+
*/
|
|
1864
|
+
const INFRA_FILE_SEGMENTS = INFRA_MODULE_SUBPATHS.map((sub) => `src/remediate/${sub}`);
|
|
1865
|
+
/**
|
|
1866
|
+
* Returns true when any path in `writePaths` is one of the live infra modules.
|
|
1867
|
+
* Paths are normalised to forward-slash form (win32 backslash → `/`) and matched
|
|
1868
|
+
* by trailing repo-relative segment so absolute/worktree/relative spellings all
|
|
1869
|
+
* resolve identically. Used to gate the live-surface verification section in the
|
|
1870
|
+
* implement prompt.
|
|
1791
1871
|
*/
|
|
1792
1872
|
export function isInfraModifyingBlock(writePaths) {
|
|
1793
1873
|
for (const p of writePaths) {
|
|
1794
1874
|
const normalized = p.replace(/\\/g, "/");
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
// Also match if the path ends with the infra file's relative segment,
|
|
1798
|
-
// e.g. an absolute path like /abs/root/packages/remediate-code/src/steps/dispatch.ts
|
|
1799
|
-
for (const infraPath of INFRA_FILE_PATHS) {
|
|
1800
|
-
if (normalized.endsWith("/" + infraPath))
|
|
1875
|
+
for (const segment of INFRA_FILE_SEGMENTS) {
|
|
1876
|
+
if (normalized === segment || normalized.endsWith("/" + segment)) {
|
|
1801
1877
|
return true;
|
|
1878
|
+
}
|
|
1802
1879
|
}
|
|
1803
1880
|
}
|
|
1804
1881
|
return false;
|