mandrel 2.66.0 → 2.68.0
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/.agents/agents/acceptance-critic.md +2 -2
- package/.agents/agents/story-worker.md +15 -11
- package/.agents/docs/agentrc-reference.json +5 -2
- package/.agents/docs/configuration.md +38 -2
- package/.agents/docs/workflows.md +4 -2
- package/.agents/instructions.md +2 -1
- package/.agents/rules/git-conventions-reference.md +5 -5
- package/.agents/rules/git-conventions.md +1 -1
- package/.agents/schemas/agentrc.schema.json +20 -2
- package/.agents/schemas/story-deliver-terminal.schema.json +23 -1
- package/.agents/schemas/validation-evidence.schema.json +3 -1
- package/.agents/scripts/boot-sweep.js +97 -9
- package/.agents/scripts/{git-cleanup.js → clean-git.js} +2 -2
- package/.agents/scripts/clean-temp.js +54 -0
- package/.agents/scripts/clean-worktrees.js +593 -0
- package/.agents/scripts/coverage-capture.js +65 -9
- package/.agents/scripts/drain-pending-cleanup.js +5 -4
- package/.agents/scripts/evidence-gate.js +106 -8
- package/.agents/scripts/lib/baselines/coverage-refresh-scope.js +60 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +101 -4
- package/.agents/scripts/lib/baselines/refresh-service.js +1 -1
- package/.agents/scripts/lib/baselines/seat-missing.js +228 -0
- package/.agents/scripts/lib/child-exec.js +39 -1
- package/.agents/scripts/lib/clean-temp.js +440 -0
- package/.agents/scripts/lib/close-validation/gates.js +59 -19
- package/.agents/scripts/lib/close-validation/process.js +23 -24
- package/.agents/scripts/lib/close-validation/runner.js +71 -40
- package/.agents/scripts/lib/config/gates/coverage.schema.js +21 -0
- package/.agents/scripts/lib/config/quality.js +7 -1
- package/.agents/scripts/lib/config/temp-paths.js +15 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +12 -3
- package/.agents/scripts/lib/coverage-baseline.js +78 -5
- package/.agents/scripts/lib/coverage-capture-affected.js +345 -0
- package/.agents/scripts/lib/coverage-capture-delta.js +180 -0
- package/.agents/scripts/lib/coverage-capture-fullscope.js +53 -32
- package/.agents/scripts/lib/coverage-capture-incremental.js +49 -26
- package/.agents/scripts/lib/coverage-capture-usage.js +1 -1
- package/.agents/scripts/lib/coverage-capture.js +121 -81
- package/.agents/scripts/lib/full-suite-lock.js +49 -46
- package/.agents/scripts/lib/full-suite-queue.js +83 -8
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +4 -1
- package/.agents/scripts/lib/orchestration/code-review.js +15 -3
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/merge-poll.js +5 -0
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +149 -97
- package/.agents/scripts/lib/orchestration/review-deposit.js +219 -0
- package/.agents/scripts/lib/orchestration/review-providers/code-review.js +11 -7
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +29 -10
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +124 -73
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +38 -20
- package/.agents/scripts/lib/orchestration/single-story-close/phases/lock-wait-pending.js +8 -2
- package/.agents/scripts/lib/orchestration/single-story-close/review-overlap.js +161 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +47 -7
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +8 -16
- package/.agents/scripts/lib/process-group.js +1 -1
- package/.agents/scripts/lib/single-story-sweep.js +2 -2
- package/.agents/scripts/lib/supervised-suite.js +247 -0
- package/.agents/scripts/lib/temp-removal.js +110 -0
- package/.agents/scripts/lib/temp-retention.js +122 -73
- package/.agents/scripts/lib/wave-runner/cross-run-overlap.js +120 -0
- package/.agents/scripts/lib/wave-runner/live-probe.js +5 -1
- package/.agents/scripts/lib/worktree/canonical-path.js +34 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +15 -4
- package/.agents/scripts/quality-preview.js +112 -14
- package/.agents/scripts/single-story-init.js +120 -17
- package/.agents/scripts/stories-wave-tick.js +47 -0
- package/.agents/scripts/story-review-compute.js +207 -0
- package/.agents/scripts/update-coverage-baseline.js +15 -10
- package/.agents/scripts/update-crap-baseline.js +12 -2
- package/.agents/scripts/update-maintainability-baseline.js +12 -2
- package/.agents/workflows/{git-cleanup.md → clean-git.md} +10 -10
- package/.agents/workflows/clean-temp.md +67 -0
- package/.agents/workflows/clean-worktrees.md +63 -0
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +3 -2
- package/.agents/workflows/helpers/code-review.md +7 -5
- package/.agents/workflows/helpers/deliver-digest.md +39 -36
- package/.agents/workflows/helpers/deliver-reference.md +115 -5
- package/.agents/workflows/helpers/deliver-story-reference.md +2 -2
- package/.agents/workflows/helpers/deliver-story.md +2 -1
- package/docs/CHANGELOG.md +39 -0
- package/lib/cli/registry.js +125 -18
- package/lib/migrations/steps/strip-removed-agentrc-keys.js +0 -5
- package/package.json +1 -1
|
@@ -131,18 +131,84 @@ export function isFirstInLine({
|
|
|
131
131
|
return true;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* @typedef {{ ownerId: string|null, pid: number|null, ageSeconds: number|null }} LockHolder
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Lockfile lines: owner id, creation stamp, pid. Unreadable parts are null.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} lockPath
|
|
142
|
+
* @param {{ fsImpl?: object, nowFn?: () => number }} [opts]
|
|
143
|
+
* @returns {LockHolder}
|
|
144
|
+
*/
|
|
145
|
+
export function readLockHolder(
|
|
146
|
+
lockPath,
|
|
147
|
+
{ fsImpl = fs, nowFn = Date.now } = {},
|
|
148
|
+
) {
|
|
149
|
+
let lines = [];
|
|
150
|
+
try {
|
|
151
|
+
lines = String(fsImpl.readFileSync(lockPath, 'utf8')).split('\n');
|
|
152
|
+
} catch {
|
|
153
|
+
// No lockfile.
|
|
154
|
+
}
|
|
155
|
+
const created = Date.parse(lines[1] ?? '');
|
|
156
|
+
return {
|
|
157
|
+
ownerId: lines[0] ? lines[0] : null,
|
|
158
|
+
pid: readLockHolderPid(lockPath, fsImpl),
|
|
159
|
+
ageSeconds: Number.isFinite(created)
|
|
160
|
+
? Math.max(0, Math.round((nowFn() - created) / 1000))
|
|
161
|
+
: null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** @param {LockHolder} holder */
|
|
166
|
+
function describeHolder({ ownerId, pid, ageSeconds }) {
|
|
167
|
+
return `holder ${ownerId ?? 'unknown'}, pid ${pid ?? 'unknown'}, lock age ${ageSeconds ?? 'unknown'}s`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const HOLDER_RE =
|
|
171
|
+
/holder ([^,\s]+), pid (\d+|unknown), lock age (\d+|unknown)s/u;
|
|
172
|
+
|
|
134
173
|
/**
|
|
135
174
|
* Log lines are the one channel that reaches close from a child capture.
|
|
136
175
|
*
|
|
137
176
|
* @param {string} line
|
|
138
|
-
* @returns {{ waitedSeconds: number, expired: boolean }|null}
|
|
177
|
+
* @returns {{ waitedSeconds: number, expired: boolean, holder?: LockHolder }|null}
|
|
139
178
|
*/
|
|
140
179
|
export function parseLockWaitOutcome(line) {
|
|
180
|
+
const text = String(line ?? '');
|
|
141
181
|
const match = /\[full-suite-lock\] (✅|⌛) [^\n]*\(waited (\d+)s[,)]/u.exec(
|
|
142
|
-
|
|
182
|
+
text,
|
|
143
183
|
);
|
|
144
184
|
if (!match) return null;
|
|
145
|
-
return
|
|
185
|
+
return withHolder(
|
|
186
|
+
{ waitedSeconds: Number(match[2]), expired: match[1] === '⌛' },
|
|
187
|
+
text,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** An expiry line also names the holder it gave up on. */
|
|
192
|
+
function withHolder(outcome, text) {
|
|
193
|
+
const holder = outcome.expired ? HOLDER_RE.exec(text) : null;
|
|
194
|
+
if (!holder) return outcome;
|
|
195
|
+
const [, ownerId, pid, ageSeconds] = holder.map(unknownAsNull);
|
|
196
|
+
return {
|
|
197
|
+
...outcome,
|
|
198
|
+
holder: {
|
|
199
|
+
ownerId,
|
|
200
|
+
pid: numberOrNull(pid),
|
|
201
|
+
ageSeconds: numberOrNull(ageSeconds),
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function unknownAsNull(raw) {
|
|
207
|
+
return raw === 'unknown' ? null : raw;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function numberOrNull(raw) {
|
|
211
|
+
return raw === null ? null : Number(raw);
|
|
146
212
|
}
|
|
147
213
|
|
|
148
214
|
function holderLabel(lockPath, fsImpl) {
|
|
@@ -154,9 +220,9 @@ function holderLabel(lockPath, fsImpl) {
|
|
|
154
220
|
* lockPath: string, waitMs: number, pollMs: number, staleMs: number,
|
|
155
221
|
* reportMs: number, fsImpl: object, nowFn: () => number,
|
|
156
222
|
* sleepFn: (ms: number) => Promise<void>, acquireOnceFn: Function,
|
|
157
|
-
* log: (m: string) => void,
|
|
223
|
+
* log: (m: string) => void, rerunCommand: string,
|
|
158
224
|
* }} opts
|
|
159
|
-
* @returns {Promise<{ held: object|null, expired: boolean, waited: true }>}
|
|
225
|
+
* @returns {Promise<{ held: object|null, expired: boolean, waited: true, waitedMs: number, holder?: LockHolder }>}
|
|
160
226
|
*/
|
|
161
227
|
export async function waitInLine(opts) {
|
|
162
228
|
const { lockPath, waitMs, fsImpl, nowFn, log } = opts;
|
|
@@ -164,6 +230,7 @@ export async function waitInLine(opts) {
|
|
|
164
230
|
const clock = {
|
|
165
231
|
deadline: startedAt + Math.max(0, waitMs),
|
|
166
232
|
nextReport: startedAt + opts.reportMs,
|
|
233
|
+
waitedMs: () => Math.max(0, nowFn() - startedAt),
|
|
167
234
|
waited: () => Math.round((nowFn() - startedAt) / 1000),
|
|
168
235
|
};
|
|
169
236
|
log(
|
|
@@ -172,14 +239,22 @@ export async function waitInLine(opts) {
|
|
|
172
239
|
const ticket = enqueueWaiter({ lockPath, nowFn, fsImpl });
|
|
173
240
|
try {
|
|
174
241
|
const outcome = await pollForTurn(opts, ticket, clock);
|
|
175
|
-
if (outcome)
|
|
242
|
+
if (outcome)
|
|
243
|
+
return { ...outcome, waited: true, waitedMs: clock.waitedMs() };
|
|
176
244
|
} finally {
|
|
177
245
|
dequeueWaiter(ticket, { fsImpl });
|
|
178
246
|
}
|
|
247
|
+
const holder = readLockHolder(lockPath, { fsImpl, nowFn });
|
|
179
248
|
log(
|
|
180
|
-
`${LOCK_TAG} ⌛ gave up waiting for the full-suite lock (waited ${clock.waited()}s, ${
|
|
249
|
+
`${LOCK_TAG} ⌛ gave up waiting for the full-suite lock (waited ${clock.waited()}s, ${describeHolder(holder)}) — not spawning, so two full suites never overlap. Re-run once it finishes: ${opts.rerunCommand}`,
|
|
181
250
|
);
|
|
182
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
held: null,
|
|
253
|
+
expired: true,
|
|
254
|
+
waited: true,
|
|
255
|
+
waitedMs: clock.waitedMs(),
|
|
256
|
+
holder,
|
|
257
|
+
};
|
|
183
258
|
}
|
|
184
259
|
|
|
185
260
|
/** `null` means the deadline passed. */
|