@deftai/directive-core 0.103.0 → 0.105.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/dist/authz/classify.js +682 -35
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/gate-lists.d.ts +3 -2
- package/dist/check/gate-lists.js +5 -2
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- package/dist/check/session-completed-ac.d.ts +39 -0
- package/dist/check/session-completed-ac.js +198 -0
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -14
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +242 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.js +17 -15
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +74 -0
- package/dist/intake/clause-derivation.js +409 -0
- package/dist/intake/index.d.ts +1 -0
- package/dist/intake/index.js +1 -0
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +36 -43
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +224 -75
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +4 -14
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +9 -0
- package/dist/preflight/evaluate.js +49 -1
- package/dist/preflight/index.d.ts +5 -1
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +5 -2
- package/dist/product-first-done-gate/empty-resolution.js +13 -2
- package/dist/product-first-done-gate/evaluate.d.ts +29 -0
- package/dist/product-first-done-gate/evaluate.js +307 -33
- package/dist/product-first-done-gate/index.d.ts +3 -1
- package/dist/product-first-done-gate/index.js +2 -1
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +50 -4
- package/dist/run-summary/emit.js +262 -26
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/share.d.ts +1 -1
- package/dist/run-summary/share.js +2 -2
- package/dist/run-summary/types.d.ts +38 -6
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-activate-gate.d.ts +1 -1
- package/dist/scope/acceptance-activate-gate.js +4 -4
- package/dist/scope/acceptance-evidence.d.ts +27 -0
- package/dist/scope/acceptance-evidence.js +66 -0
- package/dist/scope/capacity-stamp.d.ts +2 -0
- package/dist/scope/capacity-stamp.js +4 -0
- package/dist/scope/delivery-evidence.d.ts +9 -3
- package/dist/scope/delivery-evidence.js +39 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/transition.js +66 -4
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +64 -4
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +4 -36
- package/dist/session/ac-pass-banking.js +30 -77
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/ceremony-dial-evidence.d.ts +42 -0
- package/dist/session/ceremony-dial-evidence.js +194 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +683 -0
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +88 -5
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +5 -0
- package/dist/session/verify-session-ritual.js +9 -0
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +21 -4
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/slice/record.d.ts +8 -5
- package/dist/slice/record.js +21 -13
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort.js +33 -2
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +13 -3
- package/dist/swarm/routing-verify.d.ts +12 -0
- package/dist/swarm/routing-verify.js +59 -20
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/evaluate.d.ts +42 -0
- package/dist/telemetry-coverage/evaluate.js +148 -0
- package/dist/telemetry-coverage/fake-trial.d.ts +45 -0
- package/dist/telemetry-coverage/fake-trial.js +204 -0
- package/dist/telemetry-coverage/index.d.ts +5 -0
- package/dist/telemetry-coverage/index.js +5 -0
- package/dist/telemetry-coverage/kinds.d.ts +19 -0
- package/dist/telemetry-coverage/kinds.js +54 -0
- package/dist/telemetry-coverage/scan-callers.d.ts +25 -0
- package/dist/telemetry-coverage/scan-callers.js +168 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +3 -1
- package/dist/vbrief-reconcile/index.js +2 -1
- package/dist/vbrief-reconcile/origin-freshness.d.ts +44 -0
- package/dist/vbrief-reconcile/origin-freshness.js +221 -0
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +3 -3
package/dist/slice/existing.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { ContainedWriteError } from "../fs/contained-write.js";
|
|
1
2
|
import { call as scmCall } from "../scm/call.js";
|
|
2
3
|
import { pyRepr } from "../scm/py-format.js";
|
|
4
|
+
import { triageCacheRelPath } from "../triage/cache-path.js";
|
|
3
5
|
import { DEFAULT_ACTOR, DEFAULT_EXPECTED_CLOSE_SIGNAL, DEFAULT_ROLE, WAVE_FLAG_RE, } from "./constants.js";
|
|
4
6
|
import { IssueValidationError, SliceRecordError } from "./errors.js";
|
|
5
7
|
import { pythonJsonPretty } from "./json.js";
|
|
@@ -170,6 +172,10 @@ export function summariseWaves(waveMap, totalChildren) {
|
|
|
170
172
|
.map((waveN) => `wave-${waveN}=${placedByWave.get(waveN)}`);
|
|
171
173
|
return `${parts.length} wave(s): ${parts.join(", ")}`;
|
|
172
174
|
}
|
|
175
|
+
/** Layout-aware display path for the slices log actually read or written (#3386). */
|
|
176
|
+
function slicesLogDisplayPath(projectRoot) {
|
|
177
|
+
return triageCacheRelPath(projectRoot, "slices.jsonl");
|
|
178
|
+
}
|
|
173
179
|
function duplicateMessage(umbrella, duplicate) {
|
|
174
180
|
return (`slice:record-existing: umbrella #${umbrella} already has a matching record (slice_id=${String(duplicate.slice_id)}, ` +
|
|
175
181
|
`actor=${String(duplicate.actor)}). Re-run with --force to write a second record.`);
|
|
@@ -278,7 +284,10 @@ export function runRecordExisting(args, waveMap, deps = {}) {
|
|
|
278
284
|
if (authoritativeDup !== null && !args.force) {
|
|
279
285
|
return { kind: "duplicate", duplicate: authoritativeDup };
|
|
280
286
|
}
|
|
281
|
-
const id = writeSliceUnlocked(record, {
|
|
287
|
+
const id = writeSliceUnlocked(record, {
|
|
288
|
+
path: logPath,
|
|
289
|
+
projectRoot: resolved.projectRoot,
|
|
290
|
+
});
|
|
282
291
|
return { kind: "written", sliceId: id };
|
|
283
292
|
});
|
|
284
293
|
if (outcome.kind === "duplicate") {
|
|
@@ -291,7 +300,7 @@ export function runRecordExisting(args, waveMap, deps = {}) {
|
|
|
291
300
|
const waveSummary = summariseWaves(waveMap, children.length);
|
|
292
301
|
return {
|
|
293
302
|
exitCode: 0,
|
|
294
|
-
stdout: `Wrote
|
|
303
|
+
stdout: `Wrote ${slicesLogDisplayPath(resolved.projectRoot)} entry for umbrella ` +
|
|
295
304
|
`#${args.umbrella} (${children.length} children, ${waveSummary}). ` +
|
|
296
305
|
`slice_id=${outcome.sliceId}\n`,
|
|
297
306
|
stderr: "",
|
|
@@ -301,6 +310,13 @@ export function runRecordExisting(args, waveMap, deps = {}) {
|
|
|
301
310
|
if (err instanceof SliceRecordError) {
|
|
302
311
|
return { exitCode: 1, stdout: "", stderr: `error: invalid record -- ${err.message}\n` };
|
|
303
312
|
}
|
|
313
|
+
if (err instanceof ContainedWriteError) {
|
|
314
|
+
return {
|
|
315
|
+
exitCode: 1,
|
|
316
|
+
stdout: "",
|
|
317
|
+
stderr: `error: refused slices.jsonl write -- ${err.message}\n`,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
304
320
|
throw err;
|
|
305
321
|
}
|
|
306
322
|
}
|
|
@@ -309,6 +325,7 @@ export function runList(args) {
|
|
|
309
325
|
if (resolved.exitCode !== 0) {
|
|
310
326
|
return { exitCode: 2, stdout: "", stderr: `${formatMissingRootError()}\n` };
|
|
311
327
|
}
|
|
328
|
+
const logDisplay = slicesLogDisplayPath(resolved.projectRoot);
|
|
312
329
|
const records = readAll({ path: slicesPath(resolved.projectRoot) });
|
|
313
330
|
if (args.asJson) {
|
|
314
331
|
return { exitCode: 0, stdout: `${pythonJsonPretty(records)}\n`, stderr: "" };
|
|
@@ -316,11 +333,11 @@ export function runList(args) {
|
|
|
316
333
|
if (records.length === 0) {
|
|
317
334
|
return {
|
|
318
335
|
exitCode: 0,
|
|
319
|
-
stdout:
|
|
336
|
+
stdout: `slice:list: no records found in ${logDisplay} (file absent or empty).\n`,
|
|
320
337
|
stderr: "",
|
|
321
338
|
};
|
|
322
339
|
}
|
|
323
|
-
const lines = [`slice:list: ${records.length} record(s) in
|
|
340
|
+
const lines = [`slice:list: ${records.length} record(s) in ${logDisplay}`];
|
|
324
341
|
for (const record of records) {
|
|
325
342
|
const umbrella = record.umbrella ?? "?";
|
|
326
343
|
const actor = record.actor ?? "?";
|
package/dist/slice/lock.d.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
|
+
/** 10× occupancy heartbeat TTL. Off-Linux PID reuse / stalled holder reclaim. */
|
|
2
|
+
export declare const STALE_LOCK_HARD_CAP_MS: number;
|
|
1
3
|
export interface LockDeps {
|
|
2
4
|
readonly sleepMs?: (ms: number) => void;
|
|
3
5
|
readonly now?: () => number;
|
|
4
6
|
}
|
|
7
|
+
export interface HeldLock {
|
|
8
|
+
readonly lockPath: string;
|
|
9
|
+
readonly pid: number;
|
|
10
|
+
readonly token: string;
|
|
11
|
+
readonly acquiredAt: number;
|
|
12
|
+
}
|
|
13
|
+
export interface LockRecord {
|
|
14
|
+
readonly pid: number | null;
|
|
15
|
+
readonly token: string;
|
|
16
|
+
readonly acquiredAt: number | null;
|
|
17
|
+
}
|
|
18
|
+
export declare function parseLockRecord(lockPath: string): LockRecord;
|
|
19
|
+
export declare function assertAppendLockOwned(held: HeldLock): void;
|
|
5
20
|
/** Serialise appenders across threads AND processes (sidecar lock file). */
|
|
6
|
-
export declare function withAppendLock<T>(logPath: string, fn: () => T, deps?: LockDeps): T;
|
|
21
|
+
export declare function withAppendLock<T>(logPath: string, fn: (held: HeldLock) => T, deps?: LockDeps): T;
|
|
7
22
|
/** Public alias mirroring Python append_lock. */
|
|
8
23
|
export declare const appendLock: typeof withAppendLock;
|
|
9
24
|
//# sourceMappingURL=lock.d.ts.map
|
package/dist/slice/lock.js
CHANGED
|
@@ -1,12 +1,127 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync, } from "node:fs";
|
|
2
3
|
import { dirname } from "node:path";
|
|
3
4
|
const threadLocked = { held: false };
|
|
5
|
+
/** 10× occupancy heartbeat TTL. Off-Linux PID reuse / stalled holder reclaim. */
|
|
6
|
+
export const STALE_LOCK_HARD_CAP_MS = 20 * 60 * 1000 * 10;
|
|
4
7
|
function defaultSleep(ms) {
|
|
5
8
|
const end = Date.now() + ms;
|
|
6
9
|
while (Date.now() < end) {
|
|
7
10
|
/* spin */
|
|
8
11
|
}
|
|
9
12
|
}
|
|
13
|
+
export function parseLockRecord(lockPath) {
|
|
14
|
+
try {
|
|
15
|
+
const lines = readFileSync(lockPath, { encoding: "utf8" }).trim().split(/\r?\n/);
|
|
16
|
+
const pidRaw = Number(lines[0]);
|
|
17
|
+
const pid = Number.isInteger(pidRaw) && pidRaw > 0 ? pidRaw : null;
|
|
18
|
+
if (lines.length >= 3) {
|
|
19
|
+
const acquiredRaw = Number(lines[2]);
|
|
20
|
+
return {
|
|
21
|
+
pid,
|
|
22
|
+
token: lines[1] ?? "",
|
|
23
|
+
acquiredAt: Number.isInteger(acquiredRaw) && acquiredRaw > 0 ? acquiredRaw : null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const acquiredRaw = Number(lines[1]);
|
|
27
|
+
return {
|
|
28
|
+
pid,
|
|
29
|
+
token: "",
|
|
30
|
+
acquiredAt: Number.isInteger(acquiredRaw) && acquiredRaw > 0 ? acquiredRaw : null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { pid: null, token: "", acquiredAt: null };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function assertAppendLockOwned(held) {
|
|
38
|
+
const rec = parseLockRecord(held.lockPath);
|
|
39
|
+
if (rec.pid !== held.pid || rec.token !== held.token || rec.token.length === 0) {
|
|
40
|
+
throw new Error(`lock compromised: ${held.lockPath}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function processExists(pid) {
|
|
44
|
+
try {
|
|
45
|
+
process.kill(pid, 0);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return err.code !== "ESRCH";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function linuxPidStartedAfter(pid, acquiredAt) {
|
|
53
|
+
try {
|
|
54
|
+
const stat = readFileSync(`/proc/${pid}/stat`, { encoding: "utf8" });
|
|
55
|
+
const closeParen = stat.lastIndexOf(")");
|
|
56
|
+
if (closeParen < 0)
|
|
57
|
+
return false;
|
|
58
|
+
const startTicks = Number(stat.slice(closeParen + 2).split(" ")[19]);
|
|
59
|
+
if (!Number.isFinite(startTicks))
|
|
60
|
+
return false;
|
|
61
|
+
const btime = /(?:^|\n)btime (\d+)/.exec(readFileSync("/proc/stat", { encoding: "utf8" }));
|
|
62
|
+
if (btime === null)
|
|
63
|
+
return false;
|
|
64
|
+
const startMs = Number(btime[1]) * 1000 + (startTicks * 1000) / 100;
|
|
65
|
+
return startMs > acquiredAt + 1000;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function lockMtimeMs(lockPath) {
|
|
72
|
+
try {
|
|
73
|
+
return statSync(lockPath).mtimeMs;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function isAbandoned(rec, now, lockPath) {
|
|
80
|
+
if (rec.pid === null) {
|
|
81
|
+
const mtime = lockMtimeMs(lockPath);
|
|
82
|
+
return mtime !== null && now - mtime > STALE_LOCK_HARD_CAP_MS;
|
|
83
|
+
}
|
|
84
|
+
if (!processExists(rec.pid))
|
|
85
|
+
return true;
|
|
86
|
+
if (rec.acquiredAt !== null && linuxPidStartedAfter(rec.pid, rec.acquiredAt))
|
|
87
|
+
return true;
|
|
88
|
+
if (rec.acquiredAt !== null && now - rec.acquiredAt > STALE_LOCK_HARD_CAP_MS)
|
|
89
|
+
return true;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
function recordsMatch(a, b) {
|
|
93
|
+
return a.pid === b.pid && a.token === b.token && a.acquiredAt === b.acquiredAt;
|
|
94
|
+
}
|
|
95
|
+
/** Atomic rename reclaim: one waiter wins; losers see ENOENT. */
|
|
96
|
+
function tryReclaimAbandonedOwner(lockPath, now) {
|
|
97
|
+
const observed = parseLockRecord(lockPath);
|
|
98
|
+
if (!isAbandoned(observed, now, lockPath))
|
|
99
|
+
return false;
|
|
100
|
+
const quarantine = `${lockPath}.reclaim.${process.pid}.${randomUUID()}`;
|
|
101
|
+
try {
|
|
102
|
+
renameSync(lockPath, quarantine);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const quarantined = parseLockRecord(quarantine);
|
|
108
|
+
if (recordsMatch(observed, quarantined)) {
|
|
109
|
+
try {
|
|
110
|
+
unlinkSync(quarantine);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* already gone */
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
renameSync(quarantine, lockPath);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* best-effort restore */
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
10
125
|
/** Serialise appenders across threads AND processes (sidecar lock file). */
|
|
11
126
|
export function withAppendLock(logPath, fn, deps = {}) {
|
|
12
127
|
const sleepMs = deps.sleepMs ?? defaultSleep;
|
|
@@ -18,13 +133,18 @@ export function withAppendLock(logPath, fn, deps = {}) {
|
|
|
18
133
|
}
|
|
19
134
|
threadLocked.held = true;
|
|
20
135
|
let fd;
|
|
136
|
+
let held;
|
|
137
|
+
let reclaimedStale = false;
|
|
21
138
|
try {
|
|
22
139
|
const deadline = now() + 30_000;
|
|
23
140
|
while (true) {
|
|
24
141
|
try {
|
|
25
142
|
fd = openSync(lockPath, "wx");
|
|
26
|
-
|
|
27
|
-
|
|
143
|
+
const token = randomUUID();
|
|
144
|
+
const acquiredAt = now();
|
|
145
|
+
writeSync(fd, Buffer.from(`${process.pid}\n${token}\n${acquiredAt}\n`));
|
|
146
|
+
held = { lockPath, pid: process.pid, token, acquiredAt };
|
|
147
|
+
return fn(held);
|
|
28
148
|
}
|
|
29
149
|
catch (err) {
|
|
30
150
|
const code = err.code;
|
|
@@ -32,26 +152,32 @@ export function withAppendLock(logPath, fn, deps = {}) {
|
|
|
32
152
|
throw err;
|
|
33
153
|
}
|
|
34
154
|
if (now() > deadline) {
|
|
155
|
+
if (!reclaimedStale && tryReclaimAbandonedOwner(lockPath, now())) {
|
|
156
|
+
reclaimedStale = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
35
159
|
throw new Error(`timed out acquiring lock for ${logPath}`);
|
|
36
160
|
}
|
|
37
161
|
sleepMs(20);
|
|
38
162
|
}
|
|
39
163
|
}
|
|
40
|
-
return fn();
|
|
41
164
|
}
|
|
42
165
|
finally {
|
|
43
166
|
if (fd !== undefined) {
|
|
44
167
|
closeSync(fd);
|
|
45
168
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
169
|
+
if (held !== undefined) {
|
|
170
|
+
try {
|
|
171
|
+
assertAppendLockOwned(held);
|
|
172
|
+
if (existsSync(lockPath)) {
|
|
173
|
+
unlinkSync(lockPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
/* not ours anymore */
|
|
50
178
|
}
|
|
51
179
|
}
|
|
52
|
-
|
|
53
|
-
/* best-effort */
|
|
54
|
-
}
|
|
180
|
+
threadLocked.held = false;
|
|
55
181
|
}
|
|
56
182
|
}
|
|
57
183
|
/** Public alias mirroring Python append_lock. */
|
package/dist/slice/record.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { withAppendLock } from "./lock.js";
|
|
3
3
|
export interface WriteSliceOptions {
|
|
4
4
|
readonly umbrellaUrl: string;
|
|
@@ -14,13 +14,15 @@ export interface WriteSliceOptions {
|
|
|
14
14
|
export interface RecordModuleDeps {
|
|
15
15
|
readonly readFile?: typeof readFileSync;
|
|
16
16
|
readonly exists?: typeof existsSync;
|
|
17
|
-
readonly append?: typeof appendFileSync;
|
|
18
|
-
readonly fsync?: typeof fsyncSync;
|
|
19
|
-
readonly open?: typeof openSync;
|
|
20
|
-
readonly close?: typeof closeSync;
|
|
21
17
|
readonly mkdir?: typeof mkdirSync;
|
|
22
18
|
readonly withLock?: typeof withAppendLock;
|
|
23
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Containment root for slices.jsonl append (#3354 / #3288).
|
|
22
|
+
* Prefer `projectRoot` when the log is nested under it so out-of-tree dests refuse.
|
|
23
|
+
* Otherwise use the log parent (still refuses leaf symlink follow).
|
|
24
|
+
*/
|
|
25
|
+
export declare function containmentRootForSliceLog(logPath: string, projectRoot?: string): string;
|
|
24
26
|
export declare function newSliceId(): string;
|
|
25
27
|
/** Return the current UTC time in canonical ISO-8601 form with Z suffix (no fractional seconds). */
|
|
26
28
|
export declare function nowIso(): string;
|
|
@@ -29,6 +31,7 @@ export declare function writeSliceUnlocked(record: Record<string, unknown>, opti
|
|
|
29
31
|
path?: string;
|
|
30
32
|
deps?: RecordModuleDeps;
|
|
31
33
|
warn?: (message: string) => void;
|
|
34
|
+
projectRoot?: string;
|
|
32
35
|
}): string;
|
|
33
36
|
/** Validate and atomically append a cohort record to slices.jsonl. */
|
|
34
37
|
export declare function writeSlice(umbrella: number, children: Iterable<Record<string, unknown>>, options: WriteSliceOptions, deps?: RecordModuleDeps): string;
|
package/dist/slice/record.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
5
|
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
5
6
|
import { pythonJsonStringify } from "./json.js";
|
|
6
7
|
import { withAppendLock } from "./lock.js";
|
|
@@ -8,13 +9,20 @@ import { validateRecord } from "./validate.js";
|
|
|
8
9
|
const defaultDeps = {
|
|
9
10
|
readFile: readFileSync,
|
|
10
11
|
exists: existsSync,
|
|
11
|
-
append: appendFileSync,
|
|
12
|
-
fsync: fsyncSync,
|
|
13
|
-
open: openSync,
|
|
14
|
-
close: closeSync,
|
|
15
12
|
mkdir: mkdirSync,
|
|
16
13
|
withLock: withAppendLock,
|
|
17
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Containment root for slices.jsonl append (#3354 / #3288).
|
|
17
|
+
* Prefer `projectRoot` when the log is nested under it so out-of-tree dests refuse.
|
|
18
|
+
* Otherwise use the log parent (still refuses leaf symlink follow).
|
|
19
|
+
*/
|
|
20
|
+
export function containmentRootForSliceLog(logPath, projectRoot) {
|
|
21
|
+
if (projectRoot !== undefined) {
|
|
22
|
+
return resolve(projectRoot);
|
|
23
|
+
}
|
|
24
|
+
return resolve(dirname(logPath));
|
|
25
|
+
}
|
|
18
26
|
function resolvePath(path) {
|
|
19
27
|
return path !== undefined ? resolve(path) : resolveTriageCachePath(process.cwd(), "slices.jsonl");
|
|
20
28
|
}
|
|
@@ -64,14 +72,14 @@ export function writeSliceUnlocked(record, options = {}) {
|
|
|
64
72
|
return resolvedId;
|
|
65
73
|
}
|
|
66
74
|
const line = `${pythonJsonStringify(record)}\n`;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
+
const root = containmentRootForSliceLog(logPath, options.projectRoot);
|
|
76
|
+
deps.mkdir(root, { recursive: true });
|
|
77
|
+
containedWrite({
|
|
78
|
+
root,
|
|
79
|
+
target: logPath,
|
|
80
|
+
data: line,
|
|
81
|
+
mode: "append",
|
|
82
|
+
});
|
|
75
83
|
return resolvedId;
|
|
76
84
|
}
|
|
77
85
|
/** Validate and atomically append a cohort record to slices.jsonl. */
|
|
@@ -20,6 +20,8 @@ export interface EvaluateOptions {
|
|
|
20
20
|
readonly projectRoot?: string;
|
|
21
21
|
/** Skip parent-lineage check (tests / opt-out). Default false. */
|
|
22
22
|
readonly skipParentLineage?: boolean;
|
|
23
|
+
/** Skip project-invariant coverage (#3425). Default false. */
|
|
24
|
+
readonly skipProjectInvariants?: boolean;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
27
|
* Pure evaluator — returns exit code + human message. Faithful to
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { evaluateProjectInvariantsGate, resolveProjectRootForInvariants, } from "../preflight/project-invariants-gate.js";
|
|
3
4
|
import { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
|
|
4
5
|
import { parseAllocationSection, SOLO_KIND, SWARM_COHORT_KIND, VALID_DISPATCH_KINDS, } from "./allocation.js";
|
|
5
6
|
export const ACTIVE_FOLDER = "active";
|
|
@@ -168,6 +169,18 @@ export function evaluate(vbriefPath, options = {}) {
|
|
|
168
169
|
`\n${formatParentLineageLine(lineage)}`,
|
|
169
170
|
};
|
|
170
171
|
}
|
|
172
|
+
const invariants = evaluateProjectInvariantsGate(vbriefCheck.payload, {
|
|
173
|
+
projectRoot: resolveProjectRootForInvariants(vbriefCheck.path, options.projectRoot),
|
|
174
|
+
skip: options.skipProjectInvariants === true,
|
|
175
|
+
});
|
|
176
|
+
if (!invariants.ok) {
|
|
177
|
+
return {
|
|
178
|
+
exitCode: 1,
|
|
179
|
+
dispatchKind: null,
|
|
180
|
+
parentLineage: lineage,
|
|
181
|
+
message: `not ready: ${invariants.message}`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
171
184
|
const lineageNote = lineage.applicable
|
|
172
185
|
? `parent lineage OK (${lineage.parent_requirement_ids.length} req IDs` +
|
|
173
186
|
(lineage.negative_invariant_ids.length > 0
|
|
@@ -45,9 +45,12 @@ export declare function completeCohort(args: {
|
|
|
45
45
|
emitJson?: boolean;
|
|
46
46
|
/** Per-story delivery evidence; without it code-bearing stories fail closed (#3041). */
|
|
47
47
|
delivery?: CohortDeliveryContext | null;
|
|
48
|
+
/** Optional launch allocation plan id so close-out reads this cohort's occupancy slot. */
|
|
49
|
+
allocationPlanId?: string | null;
|
|
48
50
|
}): {
|
|
49
51
|
exitCode: number;
|
|
50
52
|
stdout: string;
|
|
51
53
|
stderr: string;
|
|
54
|
+
sweep: SweepResult | null;
|
|
52
55
|
};
|
|
53
56
|
//# sourceMappingURL=complete-cohort.d.ts.map
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
3
|
+
import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
|
|
4
4
|
import { detectLifecycleFolder } from "../scope/decomposed-refs.js";
|
|
5
5
|
import { classifyStoredDeliveryDisposition, } from "../scope/delivery-evidence.js";
|
|
6
6
|
import { runTransition } from "../scope/transition.js";
|
|
7
7
|
import { collectChildUris, collectPlanRefs, resolveVbriefRef } from "../scope/vbrief-ref.js";
|
|
8
|
+
import { readOccupancy, releaseSwarmOccupancy } from "../session/occupancy.js";
|
|
8
9
|
import { MAX_FIXPOINT_PASSES, TERMINAL_FOLDERS } from "./constants.js";
|
|
10
|
+
import { resolveLaunchOccupancySessionId } from "./launch.js";
|
|
9
11
|
function loadPlan(path) {
|
|
10
12
|
try {
|
|
11
13
|
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -21,6 +23,14 @@ function loadPlan(path) {
|
|
|
21
23
|
return null;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
26
|
+
function storyIdFromPath(path) {
|
|
27
|
+
const plan = loadPlan(path);
|
|
28
|
+
const id = plan !== null && typeof plan.id === "string" ? plan.id.trim() : "";
|
|
29
|
+
if (id.length > 0)
|
|
30
|
+
return id;
|
|
31
|
+
const name = basename(path);
|
|
32
|
+
return hasArtifactSuffix(name) ? stripArtifactSuffix(name) : name.replace(/\.[^.]+$/, "");
|
|
33
|
+
}
|
|
24
34
|
function rel(path, projectRoot) {
|
|
25
35
|
try {
|
|
26
36
|
return resolve(path)
|
|
@@ -226,6 +236,7 @@ function completeParent(args) {
|
|
|
226
236
|
}
|
|
227
237
|
let current = parentPath;
|
|
228
238
|
let action = "complete";
|
|
239
|
+
let activateNotice = "";
|
|
229
240
|
if (folder === "pending") {
|
|
230
241
|
action = "activate+complete";
|
|
231
242
|
const activateResult = runTransition("activate", current);
|
|
@@ -238,18 +249,22 @@ function completeParent(args) {
|
|
|
238
249
|
detail: `activate failed: ${activateResult.message}`,
|
|
239
250
|
};
|
|
240
251
|
}
|
|
252
|
+
activateNotice = activateResult.message.trim();
|
|
241
253
|
current = join(vbriefDir, "active", parentPath.split(/[/\\]/).pop() ?? "");
|
|
242
254
|
}
|
|
243
255
|
const completeResult = runTransition("complete", current, new Date(), transitionOptionsFor(current, delivery));
|
|
244
256
|
if (completeResult.ok) {
|
|
245
257
|
settled.add(resolve(join(vbriefDir, "completed", parentPath.split(/[/\\]/).pop() ?? "")));
|
|
246
258
|
}
|
|
259
|
+
const detail = activateNotice.length > 0
|
|
260
|
+
? `${activateNotice}\n${completeResult.message}`
|
|
261
|
+
: completeResult.message;
|
|
247
262
|
return {
|
|
248
263
|
kind: "epic",
|
|
249
264
|
path: relpath,
|
|
250
265
|
action: completeResult.ok ? action : "failed",
|
|
251
266
|
ok: completeResult.ok,
|
|
252
|
-
detail
|
|
267
|
+
detail,
|
|
253
268
|
};
|
|
254
269
|
}
|
|
255
270
|
export function sweepCohort(storyPaths, projectRoot, dryRun, delivery) {
|
|
@@ -412,6 +427,7 @@ export function completeCohort(args) {
|
|
|
412
427
|
exitCode: 2,
|
|
413
428
|
stdout: "",
|
|
414
429
|
stderr: `Error: project root does not exist: ${projectRoot}\n`,
|
|
430
|
+
sweep: null,
|
|
415
431
|
};
|
|
416
432
|
}
|
|
417
433
|
if (!existsSync(resolveLifecycleRoot(projectRoot))) {
|
|
@@ -419,31 +435,33 @@ export function completeCohort(args) {
|
|
|
419
435
|
exitCode: 2,
|
|
420
436
|
stdout: "",
|
|
421
437
|
stderr: `Error: no vbrief/ directory under project root: ${projectRoot}\n`,
|
|
438
|
+
sweep: null,
|
|
422
439
|
};
|
|
423
440
|
}
|
|
424
441
|
const { paths, errors } = resolveCohortPaths(args.stories ?? [], args.cohortGlobs ?? [], projectRoot);
|
|
425
442
|
if (paths.length === 0) {
|
|
426
443
|
const msg = "Error: empty cohort. Pass one or more story vBRIEF paths as positional arguments and/or --cohort <glob>.";
|
|
444
|
+
const empty = {
|
|
445
|
+
project_root: projectRoot,
|
|
446
|
+
dry_run: args.dryRun ?? false,
|
|
447
|
+
stories: [],
|
|
448
|
+
parents: [],
|
|
449
|
+
errors: errors.length > 0 ? errors : [msg],
|
|
450
|
+
ok: false,
|
|
451
|
+
};
|
|
427
452
|
if (args.emitJson) {
|
|
428
|
-
const empty = {
|
|
429
|
-
project_root: projectRoot,
|
|
430
|
-
dry_run: args.dryRun ?? false,
|
|
431
|
-
stories: [],
|
|
432
|
-
parents: [],
|
|
433
|
-
errors: errors.length > 0 ? errors : [msg],
|
|
434
|
-
ok: false,
|
|
435
|
-
};
|
|
436
453
|
return {
|
|
437
454
|
exitCode: 2,
|
|
438
455
|
stdout: `${JSON.stringify(sweepResultToDict(empty), null, 2)}\n`,
|
|
439
456
|
stderr: "",
|
|
457
|
+
sweep: empty,
|
|
440
458
|
};
|
|
441
459
|
}
|
|
442
460
|
let stderr = `${msg}\n`;
|
|
443
461
|
for (const err of errors) {
|
|
444
462
|
stderr += ` - ${err}\n`;
|
|
445
463
|
}
|
|
446
|
-
return { exitCode: 2, stdout: "", stderr };
|
|
464
|
+
return { exitCode: 2, stdout: "", stderr, sweep: empty };
|
|
447
465
|
}
|
|
448
466
|
const result = sweepCohortWithArgs({
|
|
449
467
|
storyPaths: paths,
|
|
@@ -452,17 +470,47 @@ export function completeCohort(args) {
|
|
|
452
470
|
delivery: args.delivery ?? null,
|
|
453
471
|
});
|
|
454
472
|
result.errors.push(...errors);
|
|
473
|
+
if (result.ok && args.dryRun !== true) {
|
|
474
|
+
const envSession = process.env.DEFT_SESSION_ID?.trim() ?? "";
|
|
475
|
+
let sessionId = envSession;
|
|
476
|
+
if (sessionId.length === 0) {
|
|
477
|
+
const resolved = resolveLaunchOccupancySessionId(projectRoot, {
|
|
478
|
+
allocationPlanId: args.allocationPlanId ?? null,
|
|
479
|
+
storyIds: paths.map(storyIdFromPath),
|
|
480
|
+
});
|
|
481
|
+
if (resolved.reason === "ok" && resolved.sessionId.length > 0) {
|
|
482
|
+
sessionId = resolved.sessionId;
|
|
483
|
+
}
|
|
484
|
+
else if (readOccupancy(projectRoot) !== null) {
|
|
485
|
+
result.ok = false;
|
|
486
|
+
result.errors.push("swarm close-out occupancy record is missing or belongs to a different cohort. " +
|
|
487
|
+
"Steal with occupancy:steal --confirm --occupant <id>.");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (result.ok && sessionId.length > 0) {
|
|
491
|
+
const released = releaseSwarmOccupancy(projectRoot, {
|
|
492
|
+
env: process.env,
|
|
493
|
+
sessionId,
|
|
494
|
+
});
|
|
495
|
+
if (released.code !== 0) {
|
|
496
|
+
result.ok = false;
|
|
497
|
+
result.errors.push(released.message);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
455
501
|
if (args.emitJson) {
|
|
456
502
|
return {
|
|
457
503
|
exitCode: result.ok ? 0 : 1,
|
|
458
504
|
stdout: `${JSON.stringify(sweepResultToDict(result), null, 2)}\n`,
|
|
459
505
|
stderr: "",
|
|
506
|
+
sweep: result,
|
|
460
507
|
};
|
|
461
508
|
}
|
|
462
509
|
return {
|
|
463
510
|
exitCode: result.ok ? 0 : 1,
|
|
464
511
|
stdout: `${renderSweepText(result)}\n`,
|
|
465
512
|
stderr: "",
|
|
513
|
+
sweep: result,
|
|
466
514
|
};
|
|
467
515
|
}
|
|
468
516
|
//# sourceMappingURL=complete-cohort.js.map
|
|
@@ -51,6 +51,18 @@ function safeSegment(text) {
|
|
|
51
51
|
function oneLine(text) {
|
|
52
52
|
return text.replace(/\r?\n/g, " ");
|
|
53
53
|
}
|
|
54
|
+
/** Existing sweep records already carry refused-stamp remediations (#3398). */
|
|
55
|
+
function sweepDetailLines(sweep) {
|
|
56
|
+
const lines = [];
|
|
57
|
+
for (const rec of [...sweep.stories, ...sweep.parents]) {
|
|
58
|
+
const detail = rec.detail.trim();
|
|
59
|
+
if (detail.length > 0) {
|
|
60
|
+
lines.push(`${rec.path}: ${detail}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
lines.push(...sweep.errors);
|
|
64
|
+
return lines;
|
|
65
|
+
}
|
|
54
66
|
function parseRepo(repo) {
|
|
55
67
|
const slash = repo.indexOf("/");
|
|
56
68
|
if (slash <= 0 || slash >= repo.length - 1) {
|
|
@@ -640,8 +652,15 @@ export function finalizeCohort(args) {
|
|
|
640
652
|
}
|
|
641
653
|
: null,
|
|
642
654
|
});
|
|
655
|
+
const sweep = sweepResult.sweep;
|
|
643
656
|
if (sweepResult.exitCode !== 0) {
|
|
644
657
|
errors.push("cohort completion sweep failed.");
|
|
658
|
+
if (sweep !== null) {
|
|
659
|
+
errors.push(...sweepDetailLines(sweep));
|
|
660
|
+
}
|
|
661
|
+
else if (sweepResult.stdout.trim().length > 0) {
|
|
662
|
+
errors.push(sweepResult.stdout.trim());
|
|
663
|
+
}
|
|
645
664
|
return buildResponse({
|
|
646
665
|
projectRoot,
|
|
647
666
|
dryRun,
|
|
@@ -649,7 +668,7 @@ export function finalizeCohort(args) {
|
|
|
649
668
|
prNumbers,
|
|
650
669
|
storyPaths,
|
|
651
670
|
closingIssues: [...closingIssues],
|
|
652
|
-
sweep
|
|
671
|
+
sweep,
|
|
653
672
|
commitSha: null,
|
|
654
673
|
branch: null,
|
|
655
674
|
prUrl: null,
|
|
@@ -688,7 +707,7 @@ export function finalizeCohort(args) {
|
|
|
688
707
|
prNumbers,
|
|
689
708
|
storyPaths,
|
|
690
709
|
closingIssues: [...closingIssues],
|
|
691
|
-
sweep
|
|
710
|
+
sweep,
|
|
692
711
|
commitSha,
|
|
693
712
|
branch,
|
|
694
713
|
prUrl,
|
|
@@ -747,6 +766,18 @@ function buildResponse(input) {
|
|
|
747
766
|
lines.push(` - ${oneLine(path)}`);
|
|
748
767
|
}
|
|
749
768
|
}
|
|
769
|
+
if (input.sweep !== null) {
|
|
770
|
+
const records = [...input.sweep.stories, ...input.sweep.parents];
|
|
771
|
+
if (records.length > 0 || input.sweep.errors.length > 0) {
|
|
772
|
+
lines.push(" Sweep:");
|
|
773
|
+
for (const rec of records) {
|
|
774
|
+
lines.push(` [${rec.ok ? "ok" : "FAILED"}] ${rec.action} ${rec.path} -- ${oneLine(rec.detail)}`);
|
|
775
|
+
}
|
|
776
|
+
for (const err of input.sweep.errors) {
|
|
777
|
+
lines.push(` - ${oneLine(err)}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
750
781
|
if (input.branch !== null) {
|
|
751
782
|
lines.push(` Branch: ${input.branch}`);
|
|
752
783
|
}
|