@deftai/directive-core 0.104.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 +526 -42
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- 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 -19
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +241 -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 +1 -0
- package/dist/index.js +2 -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 +38 -1
- package/dist/intake/clause-derivation.js +211 -9
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +35 -6
- 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 +4 -0
- package/dist/preflight/evaluate.js +35 -1
- package/dist/preflight/index.d.ts +4 -0
- 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 +4 -1
- package/dist/product-first-done-gate/empty-resolution.js +12 -1
- package/dist/product-first-done-gate/evaluate.d.ts +12 -0
- package/dist/product-first-done-gate/evaluate.js +254 -31
- package/dist/product-first-done-gate/index.d.ts +2 -0
- package/dist/product-first-done-gate/index.js +1 -0
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +16 -10
- package/dist/run-summary/emit.js +26 -15
- 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/types.d.ts +29 -4
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-evidence.d.ts +19 -7
- package/dist/scope/acceptance-evidence.js +38 -11
- package/dist/scope/delivery-evidence.d.ts +3 -3
- package/dist/scope/delivery-evidence.js +22 -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 +1 -1
- 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/git.js +19 -4
- package/dist/session/index.d.ts +4 -0
- package/dist/session/index.js +4 -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 +71 -7
- 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 +9 -3
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- 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/fake-trial.js +10 -0
- package/dist/telemetry-coverage/kinds.js +2 -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 +1 -1
- package/dist/vbrief-reconcile/index.js +1 -1
- 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/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/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. */
|
|
@@ -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
|
}
|
package/dist/swarm/launch.d.ts
CHANGED
|
@@ -37,6 +37,27 @@ export declare function enforceGates(resolved: readonly ResolvedStory[], project
|
|
|
37
37
|
story: ResolvedStory;
|
|
38
38
|
reason: string;
|
|
39
39
|
} | null;
|
|
40
|
+
export declare const SWARM_LAUNCH_MANIFEST_RELPATH: readonly [".deft", "swarm-launch-manifest.json"];
|
|
41
|
+
export declare const SWARM_LAUNCH_OCCUPANCY_DIR: readonly [".deft", "swarm-launch-occupancy"];
|
|
42
|
+
export declare function swarmLaunchManifestPath(projectRoot: string): string;
|
|
43
|
+
export interface LaunchOccupancyRecord {
|
|
44
|
+
readonly allocation_plan_id: string | null;
|
|
45
|
+
readonly occupancy_session_id: string;
|
|
46
|
+
readonly story_ids: readonly string[];
|
|
47
|
+
readonly cohort_key: string;
|
|
48
|
+
}
|
|
49
|
+
export interface LaunchOccupancyQuery {
|
|
50
|
+
readonly allocationPlanId?: string | null;
|
|
51
|
+
readonly storyIds?: readonly string[];
|
|
52
|
+
}
|
|
53
|
+
export type LaunchOccupancyLookupReason = "ok" | "missing" | "wrong-cohort";
|
|
54
|
+
export declare function occupancyCohortKey(allocationPlanId: string | null | undefined, storyIds?: readonly string[]): string;
|
|
55
|
+
export declare function launchOccupancyRecordRelpath(cohortKey: string): string[];
|
|
56
|
+
export declare function persistLaunchOccupancyRecord(projectRoot: string, record: LaunchOccupancyRecord): void;
|
|
57
|
+
export declare function resolveLaunchOccupancySessionId(projectRoot: string, query?: LaunchOccupancyQuery): {
|
|
58
|
+
sessionId: string;
|
|
59
|
+
reason: LaunchOccupancyLookupReason;
|
|
60
|
+
};
|
|
40
61
|
export declare function buildManifest(resolved: readonly ResolvedStory[], options: {
|
|
41
62
|
projectRoot: string;
|
|
42
63
|
group?: string | null;
|
|
@@ -53,6 +74,7 @@ export declare function buildManifest(resolved: readonly ResolvedStory[], option
|
|
|
53
74
|
modelSource?: string | null;
|
|
54
75
|
runtimeMode?: string | null;
|
|
55
76
|
githubAuthMode?: string | null;
|
|
77
|
+
occupancySessionId?: string | null;
|
|
56
78
|
}): Record<string, unknown>[];
|
|
57
79
|
export declare function orderCohort(resolved: readonly ResolvedStory[], _projectRoot: string): ResolvedStory[];
|
|
58
80
|
export interface LaunchArgs {
|