@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
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Product-state hash for AC-pass bank reuse (#3387).
|
|
3
|
+
*
|
|
4
|
+
* Acceptance plus product file bytes. Lifecycle dirs (xbrief/vbrief/.deft)
|
|
5
|
+
* are excluded so scope:complete moves do not invalidate a green bank.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { existsSync, globSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
9
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
10
|
+
import { defaultGitRunner, gitHead } from "./git.js";
|
|
11
|
+
const EXCLUDED_DIR_NAMES = new Set([
|
|
12
|
+
".git",
|
|
13
|
+
".deft",
|
|
14
|
+
".deft-scratch",
|
|
15
|
+
".deft-cache",
|
|
16
|
+
"node_modules",
|
|
17
|
+
"dist",
|
|
18
|
+
"coverage",
|
|
19
|
+
".planning",
|
|
20
|
+
]);
|
|
21
|
+
const EXCLUDED_PATH_PREFIXES = ["xbrief/", "vbrief/", ".deft/", ".git/"];
|
|
22
|
+
function asRecord(value) {
|
|
23
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function stableJson(value) {
|
|
29
|
+
if (value === null || typeof value !== "object") {
|
|
30
|
+
return JSON.stringify(value);
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
return `[${value.map((item) => stableJson(item)).join(",")}]`;
|
|
34
|
+
}
|
|
35
|
+
const obj = value;
|
|
36
|
+
const keys = Object.keys(obj).sort();
|
|
37
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${stableJson(obj[key])}`).join(",")}}`;
|
|
38
|
+
}
|
|
39
|
+
function toPosix(rel) {
|
|
40
|
+
return rel.replace(/\\/g, "/");
|
|
41
|
+
}
|
|
42
|
+
function isExcludedRel(rel) {
|
|
43
|
+
const posix = toPosix(rel);
|
|
44
|
+
if (posix === "." || posix.length === 0)
|
|
45
|
+
return false;
|
|
46
|
+
const first = posix.split("/")[0] ?? "";
|
|
47
|
+
if (EXCLUDED_DIR_NAMES.has(first))
|
|
48
|
+
return true;
|
|
49
|
+
return EXCLUDED_PATH_PREFIXES.some((prefix) => posix === prefix.slice(0, -1) || posix.startsWith(prefix));
|
|
50
|
+
}
|
|
51
|
+
function hashBytes(buf) {
|
|
52
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
53
|
+
}
|
|
54
|
+
/** Join root (UTF-8) to a latin1 rel so invalid filename bytes are not re-encoded. */
|
|
55
|
+
function joinRootRelBytes(root, rel) {
|
|
56
|
+
const sepBuf = Buffer.from(sep);
|
|
57
|
+
const parts = [Buffer.from(root, "utf8")];
|
|
58
|
+
for (const seg of toPosix(rel).split("/")) {
|
|
59
|
+
if (seg.length === 0 || seg === ".")
|
|
60
|
+
continue;
|
|
61
|
+
parts.push(sepBuf, Buffer.from(seg, "latin1"));
|
|
62
|
+
}
|
|
63
|
+
return Buffer.concat(parts);
|
|
64
|
+
}
|
|
65
|
+
function readFileHash(root, rel) {
|
|
66
|
+
try {
|
|
67
|
+
return hashBytes(readFileSync(resolve(root, rel)));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
try {
|
|
71
|
+
return hashBytes(readFileSync(joinRootRelBytes(root, rel)));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return "missing";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Unwalkable gitlink (mode 160000): fingerprint the recorded SHA, not missing. */
|
|
79
|
+
function gitlinkFingerprint(root, rel, runGit) {
|
|
80
|
+
const staged = runGit(root, ["ls-files", "--stage", "--", rel]);
|
|
81
|
+
if (staged.code === 0) {
|
|
82
|
+
const line = staged.stdout.trim().split(/\r?\n/)[0] ?? "";
|
|
83
|
+
const match = line.match(/^160000\s+([0-9a-f]{40,64})\b/);
|
|
84
|
+
if (match?.[1])
|
|
85
|
+
return `gitlink:${match[1]}`;
|
|
86
|
+
}
|
|
87
|
+
const parsed = runGit(root, ["rev-parse", `HEAD:${rel.replace(/\\/g, "/")}`]);
|
|
88
|
+
if (parsed.code === 0 && /^[0-9a-f]{40,64}$/.test(parsed.stdout.trim())) {
|
|
89
|
+
return `gitlink:${parsed.stdout.trim()}`;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function hashProductRel(root, rel, runGit) {
|
|
94
|
+
const file = readFileHash(root, rel);
|
|
95
|
+
if (file !== "missing")
|
|
96
|
+
return file;
|
|
97
|
+
return gitlinkFingerprint(root, rel, runGit) ?? "missing";
|
|
98
|
+
}
|
|
99
|
+
function asPathBuf(path) {
|
|
100
|
+
return Buffer.isBuffer(path) ? path : Buffer.from(path, "utf8");
|
|
101
|
+
}
|
|
102
|
+
function joinNameBytes(dir, name) {
|
|
103
|
+
const dirBuf = asPathBuf(dir);
|
|
104
|
+
const sepBuf = Buffer.from(sep);
|
|
105
|
+
if (dirBuf.length === 0)
|
|
106
|
+
return name;
|
|
107
|
+
const last = dirBuf[dirBuf.length - 1];
|
|
108
|
+
if (last === sepBuf[0])
|
|
109
|
+
return Buffer.concat([dirBuf, name]);
|
|
110
|
+
return Buffer.concat([dirBuf, sepBuf, name]);
|
|
111
|
+
}
|
|
112
|
+
function relFromRootBytes(root, abs) {
|
|
113
|
+
const rootBuf = Buffer.from(root, "utf8");
|
|
114
|
+
const sepByte = Buffer.from(sep)[0] ?? 47;
|
|
115
|
+
if (abs.length >= rootBuf.length && abs.subarray(0, rootBuf.length).equals(rootBuf)) {
|
|
116
|
+
let rest = abs.subarray(rootBuf.length);
|
|
117
|
+
if (rest.length > 0 && rest[0] === sepByte)
|
|
118
|
+
rest = rest.subarray(1);
|
|
119
|
+
if (rest.length === 0)
|
|
120
|
+
return "";
|
|
121
|
+
const segs = [];
|
|
122
|
+
let start = 0;
|
|
123
|
+
for (let i = 0; i <= rest.length; i += 1) {
|
|
124
|
+
if (i === rest.length || rest[i] === sepByte) {
|
|
125
|
+
segs.push(decodeOctalPathBytes(rest.subarray(start, i)));
|
|
126
|
+
start = i + 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return segs.filter((seg) => seg.length > 0).join("/");
|
|
130
|
+
}
|
|
131
|
+
return toPosix(relative(root, abs.toString("utf8")));
|
|
132
|
+
}
|
|
133
|
+
function walkFiles(root, dir, out, seen = new Set()) {
|
|
134
|
+
const dirBuf = asPathBuf(dir);
|
|
135
|
+
let real;
|
|
136
|
+
try {
|
|
137
|
+
real = realpathSync(dirBuf);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (seen.has(real))
|
|
143
|
+
return;
|
|
144
|
+
seen.add(real);
|
|
145
|
+
let names;
|
|
146
|
+
try {
|
|
147
|
+
names = readdirSync(dirBuf, { encoding: "buffer" });
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const nameBuf of names) {
|
|
153
|
+
const nameLatin1 = nameBuf.toString("latin1");
|
|
154
|
+
const abs = joinNameBytes(dirBuf, nameBuf);
|
|
155
|
+
const rel = relFromRootBytes(root, abs);
|
|
156
|
+
if (isExcludedRel(rel))
|
|
157
|
+
continue;
|
|
158
|
+
let st;
|
|
159
|
+
try {
|
|
160
|
+
st = statSync(abs);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (st.isDirectory()) {
|
|
166
|
+
if (EXCLUDED_DIR_NAMES.has(nameLatin1))
|
|
167
|
+
continue;
|
|
168
|
+
walkFiles(root, abs, out, seen);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (st.isFile())
|
|
172
|
+
out.push(rel);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Dirty submodule dirs are not files; walk their contents instead of hashing missing. */
|
|
176
|
+
function expandIfDirectory(root, rel) {
|
|
177
|
+
const tryWalk = (dir) => {
|
|
178
|
+
try {
|
|
179
|
+
const st = statSync(dir);
|
|
180
|
+
if (!st.isDirectory())
|
|
181
|
+
return null;
|
|
182
|
+
const walked = [];
|
|
183
|
+
walkFiles(root, dir, walked);
|
|
184
|
+
return walked;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const walked = tryWalk(resolve(root, rel)) ?? tryWalk(joinRootRelBytes(root, rel));
|
|
191
|
+
if (walked === null)
|
|
192
|
+
return [rel];
|
|
193
|
+
return walked.length > 0 ? walked : [rel];
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Node `fs.globSync` omits leading-dot names and has no `{ dot }` option.
|
|
197
|
+
* Each `*` / `?` / `[` segment gets an ordinary and a hidden variant so a
|
|
198
|
+
* later hidden segment (for example app/.config.ts) or a class-selected
|
|
199
|
+
* hidden name (frontend/[ab]* -> .app.ts) stays in the digest. Keep **
|
|
200
|
+
* intact and add a recursive hidden-name pattern. Hidden directories
|
|
201
|
+
* under ** are collected by walking the prefix (readdir includes .dirs).
|
|
202
|
+
*/
|
|
203
|
+
function hiddenSegmentVariant(segment) {
|
|
204
|
+
if (segment === "**" || segment.startsWith("."))
|
|
205
|
+
return null;
|
|
206
|
+
if (segment.startsWith("*") || segment.startsWith("?") || segment.startsWith("[")) {
|
|
207
|
+
return `.${segment}`;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function globPatternsIncludingDotfiles(pattern) {
|
|
212
|
+
const patterns = new Set([pattern]);
|
|
213
|
+
let combos = [[]];
|
|
214
|
+
for (const segment of pattern.split("/")) {
|
|
215
|
+
const variants = [segment];
|
|
216
|
+
const hidden = hiddenSegmentVariant(segment);
|
|
217
|
+
if (hidden !== null)
|
|
218
|
+
variants.push(hidden);
|
|
219
|
+
const next = [];
|
|
220
|
+
for (const combo of combos) {
|
|
221
|
+
for (const variant of variants)
|
|
222
|
+
next.push([...combo, variant]);
|
|
223
|
+
}
|
|
224
|
+
combos = next;
|
|
225
|
+
}
|
|
226
|
+
for (const combo of combos)
|
|
227
|
+
patterns.add(combo.join("/"));
|
|
228
|
+
if (pattern.includes("**")) {
|
|
229
|
+
if (/\*\*(?:\/\*)?$/.test(pattern)) {
|
|
230
|
+
patterns.add(pattern.replace(/\*\*(?:\/\*)?$/, "**/.*"));
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const dottedTail = pattern.replace(/(^|\/)([^/]*)$/, (_match, slash, name) => name.startsWith(".") ? `${slash}${name}` : `${slash}.${name}`);
|
|
234
|
+
patterns.add(dottedTail);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return [...patterns];
|
|
238
|
+
}
|
|
239
|
+
function closeBraceIndex(pattern, open) {
|
|
240
|
+
let depth = 0;
|
|
241
|
+
for (let i = open; i < pattern.length; i += 1) {
|
|
242
|
+
if (pattern[i] === "{")
|
|
243
|
+
depth += 1;
|
|
244
|
+
else if (pattern[i] === "}") {
|
|
245
|
+
depth -= 1;
|
|
246
|
+
if (depth === 0)
|
|
247
|
+
return i;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return -1;
|
|
251
|
+
}
|
|
252
|
+
function closeParenIndex(pattern, open) {
|
|
253
|
+
let depth = 0;
|
|
254
|
+
for (let i = open; i < pattern.length; i += 1) {
|
|
255
|
+
if (pattern[i] === "(")
|
|
256
|
+
depth += 1;
|
|
257
|
+
else if (pattern[i] === ")") {
|
|
258
|
+
depth -= 1;
|
|
259
|
+
if (depth === 0)
|
|
260
|
+
return i;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return -1;
|
|
264
|
+
}
|
|
265
|
+
function splitExtglobAlts(inner) {
|
|
266
|
+
const alts = [];
|
|
267
|
+
let depth = 0;
|
|
268
|
+
let start = 0;
|
|
269
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
270
|
+
const ch = inner[i];
|
|
271
|
+
if (ch === "(" || ch === "{")
|
|
272
|
+
depth += 1;
|
|
273
|
+
else if (ch === ")" || ch === "}")
|
|
274
|
+
depth -= 1;
|
|
275
|
+
else if (ch === "|" && depth === 0) {
|
|
276
|
+
alts.push(inner.slice(start, i));
|
|
277
|
+
start = i + 1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
alts.push(inner.slice(start));
|
|
281
|
+
return alts;
|
|
282
|
+
}
|
|
283
|
+
function splitBraceAlts(inner) {
|
|
284
|
+
const alts = [];
|
|
285
|
+
let depth = 0;
|
|
286
|
+
let start = 0;
|
|
287
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
288
|
+
if (inner[i] === "{")
|
|
289
|
+
depth += 1;
|
|
290
|
+
else if (inner[i] === "}")
|
|
291
|
+
depth -= 1;
|
|
292
|
+
else if (inner[i] === "," && depth === 0) {
|
|
293
|
+
alts.push(inner.slice(start, i));
|
|
294
|
+
start = i + 1;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
alts.push(inner.slice(start));
|
|
298
|
+
return alts;
|
|
299
|
+
}
|
|
300
|
+
/** Match a posix relpath; ** includes hidden directory segments. */
|
|
301
|
+
function globToRegExpSource(pattern) {
|
|
302
|
+
let i = 0;
|
|
303
|
+
let out = "";
|
|
304
|
+
while (i < pattern.length) {
|
|
305
|
+
if (pattern.startsWith("**", i)) {
|
|
306
|
+
i += 2;
|
|
307
|
+
if (pattern[i] === "/") {
|
|
308
|
+
out += "(?:[^/]+/)*";
|
|
309
|
+
i += 1;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
out += ".*";
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const c = pattern[i];
|
|
317
|
+
if ((c === "@" || c === "*" || c === "+" || c === "?" || c === "!") && pattern[i + 1] === "(") {
|
|
318
|
+
const end = closeParenIndex(pattern, i + 1);
|
|
319
|
+
if (end !== -1) {
|
|
320
|
+
const alts = splitExtglobAlts(pattern.slice(i + 2, end));
|
|
321
|
+
const body = alts.map((alt) => globToRegExpSource(alt)).join("|");
|
|
322
|
+
if (c === "@")
|
|
323
|
+
out += `(?:${body})`;
|
|
324
|
+
else if (c === "*")
|
|
325
|
+
out += `(?:${body})*`;
|
|
326
|
+
else if (c === "+")
|
|
327
|
+
out += `(?:${body})+`;
|
|
328
|
+
else if (c === "?")
|
|
329
|
+
out += `(?:${body})?`;
|
|
330
|
+
else
|
|
331
|
+
out += `(?:(?!(?:${body})$)[^/]+)`;
|
|
332
|
+
i = end + 1;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (c === "*") {
|
|
337
|
+
out += "[^/]*";
|
|
338
|
+
i += 1;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (c === "?") {
|
|
342
|
+
out += "[^/]";
|
|
343
|
+
i += 1;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (c === "[") {
|
|
347
|
+
const end = pattern.indexOf("]", i + 1);
|
|
348
|
+
if (end === -1) {
|
|
349
|
+
out += "\\[";
|
|
350
|
+
i += 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const body = pattern.slice(i + 1, end);
|
|
354
|
+
// Glob [!a] is "not a"; JS [!a] is "!" or "a".
|
|
355
|
+
if (body.startsWith("!") && body.length > 1) {
|
|
356
|
+
out += `[^${body.slice(1)}/]`;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
out += pattern.slice(i, end + 1);
|
|
360
|
+
}
|
|
361
|
+
i = end + 1;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (c === "{") {
|
|
365
|
+
const end = closeBraceIndex(pattern, i);
|
|
366
|
+
if (end === -1) {
|
|
367
|
+
out += "\\{";
|
|
368
|
+
i += 1;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const alts = splitBraceAlts(pattern.slice(i + 1, end));
|
|
372
|
+
out += `(?:${alts.map((alt) => globToRegExpSource(alt)).join("|")})`;
|
|
373
|
+
i = end + 1;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if ("\\^$+()|.".includes(c))
|
|
377
|
+
out += `\\${c}`;
|
|
378
|
+
else
|
|
379
|
+
out += c;
|
|
380
|
+
i += 1;
|
|
381
|
+
}
|
|
382
|
+
return out;
|
|
383
|
+
}
|
|
384
|
+
function globToRegExp(pattern) {
|
|
385
|
+
return new RegExp(`^${globToRegExpSource(pattern)}$`);
|
|
386
|
+
}
|
|
387
|
+
function fileScopePaths(plan) {
|
|
388
|
+
const meta = asRecord(plan.metadata);
|
|
389
|
+
const swarm = asRecord(meta?.swarm);
|
|
390
|
+
const raw = swarm?.file_scope;
|
|
391
|
+
if (!Array.isArray(raw))
|
|
392
|
+
return [];
|
|
393
|
+
return raw.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
394
|
+
}
|
|
395
|
+
/** `*`/`?` plus class/brace/extglob forms so they are not hashed as a missing literal. */
|
|
396
|
+
function looksLikeGlob(rel) {
|
|
397
|
+
return /[*?[\]{}]/.test(rel) || /[@*+?!]\(/.test(rel);
|
|
398
|
+
}
|
|
399
|
+
/** Longest non-glob directory prefix before ** (wildcard mid-path walks from that dir). */
|
|
400
|
+
function literalDirPrefix(rel) {
|
|
401
|
+
const before = rel.includes("**") ? rel.slice(0, rel.indexOf("**")) : rel;
|
|
402
|
+
const literal = [];
|
|
403
|
+
for (const seg of before.replace(/\/$/, "").split("/")) {
|
|
404
|
+
if (seg.length === 0)
|
|
405
|
+
continue;
|
|
406
|
+
if (looksLikeGlob(seg))
|
|
407
|
+
break;
|
|
408
|
+
literal.push(seg);
|
|
409
|
+
}
|
|
410
|
+
return literal.join("/");
|
|
411
|
+
}
|
|
412
|
+
function expandPath(root, relOrGlob) {
|
|
413
|
+
const rel = toPosix(relOrGlob).replace(/^\.\//, "");
|
|
414
|
+
if (looksLikeGlob(rel)) {
|
|
415
|
+
try {
|
|
416
|
+
const out = [];
|
|
417
|
+
const seen = new Set();
|
|
418
|
+
for (const pattern of globPatternsIncludingDotfiles(rel)) {
|
|
419
|
+
for (const match of globSync(pattern, { cwd: root })) {
|
|
420
|
+
const posix = toPosix(relative(root, resolve(root, match)));
|
|
421
|
+
if (posix.length === 0 || isExcludedRel(posix) || seen.has(posix))
|
|
422
|
+
continue;
|
|
423
|
+
seen.add(posix);
|
|
424
|
+
const absMatch = resolve(root, posix);
|
|
425
|
+
let st;
|
|
426
|
+
try {
|
|
427
|
+
st = statSync(absMatch);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (st.isFile())
|
|
433
|
+
out.push(posix);
|
|
434
|
+
else if (st.isDirectory())
|
|
435
|
+
walkFiles(root, absMatch, out);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
{
|
|
439
|
+
const prefix = literalDirPrefix(rel);
|
|
440
|
+
const startDir = prefix.length === 0 ? root : resolve(root, prefix);
|
|
441
|
+
if (existsSync(startDir)) {
|
|
442
|
+
let st = null;
|
|
443
|
+
try {
|
|
444
|
+
st = statSync(startDir);
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
st = null;
|
|
448
|
+
}
|
|
449
|
+
if (st?.isDirectory()) {
|
|
450
|
+
const walked = [];
|
|
451
|
+
walkFiles(root, startDir, walked);
|
|
452
|
+
const re = globToRegExp(rel);
|
|
453
|
+
for (const file of walked) {
|
|
454
|
+
if (seen.has(file) || !re.test(file))
|
|
455
|
+
continue;
|
|
456
|
+
seen.add(file);
|
|
457
|
+
out.push(file);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const abs = resolve(root, rel);
|
|
469
|
+
if (!existsSync(abs))
|
|
470
|
+
return [rel];
|
|
471
|
+
try {
|
|
472
|
+
const st = statSync(abs);
|
|
473
|
+
if (st.isDirectory()) {
|
|
474
|
+
const out = [];
|
|
475
|
+
walkFiles(root, abs, out);
|
|
476
|
+
return out;
|
|
477
|
+
}
|
|
478
|
+
if (st.isFile())
|
|
479
|
+
return [toPosix(relative(root, abs))];
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
return [rel];
|
|
483
|
+
}
|
|
484
|
+
return [rel];
|
|
485
|
+
}
|
|
486
|
+
function takeOctalByte(body, index) {
|
|
487
|
+
const first = body[index];
|
|
488
|
+
if (first === undefined || first < "0" || first > "7")
|
|
489
|
+
return null;
|
|
490
|
+
let oct = first;
|
|
491
|
+
let i = index + 1;
|
|
492
|
+
while (oct.length < 3 && i < body.length) {
|
|
493
|
+
const digit = body[i];
|
|
494
|
+
if (digit === undefined || digit < "0" || digit > "7")
|
|
495
|
+
break;
|
|
496
|
+
oct += digit;
|
|
497
|
+
i += 1;
|
|
498
|
+
}
|
|
499
|
+
return { value: Number.parseInt(oct, 8), next: i };
|
|
500
|
+
}
|
|
501
|
+
/** Valid UTF-8 stays UTF-8; invalid bytes stay latin1 so the path is not U+FFFD. */
|
|
502
|
+
function decodeOctalPathBytes(bytes) {
|
|
503
|
+
const buf = Buffer.from(bytes);
|
|
504
|
+
const utf8 = buf.toString("utf8");
|
|
505
|
+
if (Buffer.from(utf8, "utf8").equals(buf))
|
|
506
|
+
return utf8;
|
|
507
|
+
return buf.toString("latin1");
|
|
508
|
+
}
|
|
509
|
+
function decodeCEscape(body, index) {
|
|
510
|
+
const next = body[index];
|
|
511
|
+
if (next === undefined)
|
|
512
|
+
return { ch: "\\", next: index };
|
|
513
|
+
if (next === "n")
|
|
514
|
+
return { ch: "\n", next: index + 1 };
|
|
515
|
+
if (next === "t")
|
|
516
|
+
return { ch: "\t", next: index + 1 };
|
|
517
|
+
if (next === "r")
|
|
518
|
+
return { ch: "\r", next: index + 1 };
|
|
519
|
+
if (next === '"' || next === "\\")
|
|
520
|
+
return { ch: next, next: index + 1 };
|
|
521
|
+
const oct = takeOctalByte(body, index);
|
|
522
|
+
if (oct !== null)
|
|
523
|
+
return { ch: String.fromCharCode(oct.value), next: oct.next };
|
|
524
|
+
return { ch: next, next: index + 1 };
|
|
525
|
+
}
|
|
526
|
+
/** One C-quoted or unquoted porcelain path; stop unquoted tokens at ` -> `. */
|
|
527
|
+
function takePorcelainPathToken(raw) {
|
|
528
|
+
const s = raw.trimStart();
|
|
529
|
+
if (s.startsWith('"')) {
|
|
530
|
+
let i = 1;
|
|
531
|
+
let out = "";
|
|
532
|
+
while (i < s.length) {
|
|
533
|
+
const c = s[i];
|
|
534
|
+
if (c === "\\") {
|
|
535
|
+
const bytes = [];
|
|
536
|
+
let j = i;
|
|
537
|
+
while (s[j] === "\\") {
|
|
538
|
+
const oct = takeOctalByte(s, j + 1);
|
|
539
|
+
if (oct === null)
|
|
540
|
+
break;
|
|
541
|
+
bytes.push(oct.value);
|
|
542
|
+
j = oct.next;
|
|
543
|
+
}
|
|
544
|
+
if (bytes.length > 0) {
|
|
545
|
+
out += decodeOctalPathBytes(bytes);
|
|
546
|
+
i = j;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
const decoded = decodeCEscape(s, i + 1);
|
|
550
|
+
out += decoded.ch;
|
|
551
|
+
i = decoded.next;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (c === '"')
|
|
555
|
+
return { value: out, rest: s.slice(i + 1) };
|
|
556
|
+
out += c;
|
|
557
|
+
i += 1;
|
|
558
|
+
}
|
|
559
|
+
return { value: out, rest: "" };
|
|
560
|
+
}
|
|
561
|
+
const arrow = s.indexOf(" -> ");
|
|
562
|
+
if (arrow === -1)
|
|
563
|
+
return { value: s.trimEnd(), rest: "" };
|
|
564
|
+
return { value: s.slice(0, arrow), rest: s.slice(arrow) };
|
|
565
|
+
}
|
|
566
|
+
function porcelainFallbackRel(line) {
|
|
567
|
+
if (line.length < 3)
|
|
568
|
+
return "";
|
|
569
|
+
const code = line.slice(0, 2);
|
|
570
|
+
const rest = line.slice(3);
|
|
571
|
+
const renamed = code.includes("R") || code.includes("C");
|
|
572
|
+
if (!renamed) {
|
|
573
|
+
if (rest.startsWith('"'))
|
|
574
|
+
return takePorcelainPathToken(rest).value;
|
|
575
|
+
return rest.trimEnd();
|
|
576
|
+
}
|
|
577
|
+
const first = takePorcelainPathToken(rest);
|
|
578
|
+
const after = first.rest.trimStart();
|
|
579
|
+
if (after.startsWith("->"))
|
|
580
|
+
return takePorcelainPathToken(after.slice(2)).value;
|
|
581
|
+
return first.value;
|
|
582
|
+
}
|
|
583
|
+
function parseZedPorcelain(stdout) {
|
|
584
|
+
const out = [];
|
|
585
|
+
const parts = stdout.split("\0").filter((part) => part.length > 0);
|
|
586
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
587
|
+
const rec = parts[i];
|
|
588
|
+
if (rec.length < 4)
|
|
589
|
+
continue;
|
|
590
|
+
const code = rec.slice(0, 2);
|
|
591
|
+
const pathPart = rec.slice(3);
|
|
592
|
+
const renamed = code.includes("R") || code.includes("C");
|
|
593
|
+
// -z rename/copy is `XY dest\0orig\0` — keep dest, skip orig.
|
|
594
|
+
if (renamed && i + 1 < parts.length) {
|
|
595
|
+
i += 1;
|
|
596
|
+
}
|
|
597
|
+
const rel = toPosix(decodeOctalPathBytes(Buffer.from(pathPart, "latin1")));
|
|
598
|
+
if (rel.length === 0 || isExcludedRel(rel))
|
|
599
|
+
continue;
|
|
600
|
+
out.push(rel);
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
603
|
+
}
|
|
604
|
+
function dirtyProductFiles(projectRoot, runGit) {
|
|
605
|
+
const zed = runGit(projectRoot, ["status", "--porcelain", "-u", "-z"]);
|
|
606
|
+
if (zed.code === 0) {
|
|
607
|
+
return { files: parseZedPorcelain(zed.stdout), ok: true };
|
|
608
|
+
}
|
|
609
|
+
const { code, stdout } = runGit(projectRoot, ["status", "--porcelain", "-u"]);
|
|
610
|
+
if (code !== 0)
|
|
611
|
+
return { files: [], ok: false };
|
|
612
|
+
const out = [];
|
|
613
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
614
|
+
if (line.trim().length === 0)
|
|
615
|
+
continue;
|
|
616
|
+
const rel = toPosix(porcelainFallbackRel(line));
|
|
617
|
+
if (rel.length === 0 || isExcludedRel(rel))
|
|
618
|
+
continue;
|
|
619
|
+
out.push(rel);
|
|
620
|
+
}
|
|
621
|
+
return { files: out, ok: true };
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Hash plan.acceptance plus product files. Missing/empty product surface
|
|
625
|
+
* is incomplete so callers fail closed to a full walk (#3387).
|
|
626
|
+
*/
|
|
627
|
+
export function hashProductState(input) {
|
|
628
|
+
const root = resolve(input.projectRoot);
|
|
629
|
+
const runGit = input.runGit ?? defaultGitRunner;
|
|
630
|
+
const files = new Set();
|
|
631
|
+
let statusOk = true;
|
|
632
|
+
if (input.productPaths !== undefined) {
|
|
633
|
+
for (const rel of input.productPaths) {
|
|
634
|
+
for (const expanded of expandPath(root, rel))
|
|
635
|
+
files.add(expanded);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
const scope = fileScopePaths(input.plan);
|
|
640
|
+
if (scope.length > 0) {
|
|
641
|
+
for (const entry of scope) {
|
|
642
|
+
for (const expanded of expandPath(root, entry))
|
|
643
|
+
files.add(expanded);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
const git = existsSync(join(root, ".git"));
|
|
648
|
+
if (git) {
|
|
649
|
+
const dirty = dirtyProductFiles(root, runGit);
|
|
650
|
+
statusOk = dirty.ok;
|
|
651
|
+
for (const rel of dirty.files) {
|
|
652
|
+
for (const expanded of expandIfDirectory(root, rel))
|
|
653
|
+
files.add(expanded);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
const walked = [];
|
|
658
|
+
walkFiles(root, root, walked);
|
|
659
|
+
for (const rel of walked)
|
|
660
|
+
files.add(rel);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const sorted = [...files].sort();
|
|
665
|
+
const fileHashes = {};
|
|
666
|
+
for (const rel of sorted) {
|
|
667
|
+
fileHashes[rel] = hashProductRel(root, rel, runGit);
|
|
668
|
+
}
|
|
669
|
+
const head = existsSync(join(root, ".git")) ? gitHead(root, runGit).head : null;
|
|
670
|
+
const specifiedSurface = input.productPaths !== undefined || fileScopePaths(input.plan).length > 0;
|
|
671
|
+
const hasSurface = specifiedSurface
|
|
672
|
+
? sorted.length > 0
|
|
673
|
+
: statusOk && (sorted.length > 0 || head !== null);
|
|
674
|
+
const digest = createHash("sha256")
|
|
675
|
+
.update(stableJson({
|
|
676
|
+
acceptance: input.plan.acceptance ?? null,
|
|
677
|
+
head,
|
|
678
|
+
files: fileHashes,
|
|
679
|
+
}))
|
|
680
|
+
.digest("hex");
|
|
681
|
+
return { digest, complete: hasSurface, files: sorted };
|
|
682
|
+
}
|
|
683
|
+
//# sourceMappingURL=product-state-hash.js.map
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* remaining hard blocker is printed once).
|
|
13
13
|
*/
|
|
14
14
|
import type { GitRunner } from "./git.js";
|
|
15
|
+
import { type ApplyOccupancyInput, type OccupancyDecision } from "./occupancy.js";
|
|
15
16
|
import { type SessionStartOptions, type SessionStartResult } from "./session-start.js";
|
|
16
17
|
import { type InspectSessionRitualOptions, type RitualRunner, type VerifyResult, type VerifySessionRitualOptions } from "./verify-session-ritual.js";
|
|
17
18
|
export declare const SESSION_READY_FAST_PATH = "fast";
|
|
@@ -45,6 +46,7 @@ export interface SessionReadyOptions {
|
|
|
45
46
|
readonly inspectRitual?: (projectRoot: string, options: InspectSessionRitualOptions) => VerifyResult;
|
|
46
47
|
readonly verifyRitual?: (projectRoot: string, options: VerifySessionRitualOptions) => VerifyResult;
|
|
47
48
|
readonly runStart?: (projectRoot: string, options: SessionStartOptions) => SessionStartResult;
|
|
49
|
+
readonly applyOccupancy?: (projectRoot: string, input: ApplyOccupancyInput) => OccupancyDecision;
|
|
48
50
|
readonly fetchAll?: CacheFetchAllSeam;
|
|
49
51
|
readonly inferRepo?: (projectRoot: string) => string | null;
|
|
50
52
|
/** Skip cache recovery even when cache_fresh failed (tests). */
|