@abelxiaoxing/cadence 1.8.4 → 2.0.1

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.
Files changed (202) hide show
  1. package/README.md +324 -117
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -18
  4. package/agents/diagnosis-worker.md +14 -14
  5. package/agents/implementation-worker.md +16 -17
  6. package/config/workflow-task.example.json +7 -0
  7. package/docs/remote-access.md +60 -0
  8. package/docs/reuse-and-distribution.md +44 -0
  9. package/docs/service-management.md +108 -0
  10. package/licenses/bundled-web.txt +6245 -0
  11. package/licenses/pi-web-ui-MIT.txt +21 -0
  12. package/licenses/pi-web-ui-reuse.md +22 -0
  13. package/package.json +92 -20
  14. package/src/index.ts +21 -1802
  15. package/src/native-build.ts +6 -0
  16. package/src/native-client.ts +103 -0
  17. package/src/native-export.ts +58 -0
  18. package/src/native-history.ts +359 -0
  19. package/src/operator-cli.mjs +453 -1937
  20. package/src/plan-proposal.ts +87 -0
  21. package/src/sdk-worker.mjs +549 -0
  22. package/src/service/contracts/active.ts +82 -0
  23. package/src/service/contracts/budget.ts +148 -0
  24. package/src/service/contracts/build.ts +67 -0
  25. package/src/service/contracts/capture.ts +48 -0
  26. package/src/service/contracts/core.ts +33 -0
  27. package/src/service/contracts/current.ts +261 -0
  28. package/src/service/contracts/domain.ts +68 -0
  29. package/src/service/contracts/history.ts +2245 -0
  30. package/src/service/contracts/models.ts +139 -0
  31. package/src/service/coordination.ts +862 -0
  32. package/src/service/private-files.ts +204 -0
  33. package/src/service-cli.mjs +5592 -0
  34. package/src/service-client.ts +747 -0
  35. package/src/service-contract.ts +245 -0
  36. package/src/service-host.mjs +22382 -0
  37. package/src/service-http-client.ts +425 -0
  38. package/src/service-wire.ts +372 -0
  39. package/src/task-shell.ts +1045 -0
  40. package/src/task-store.ts +479 -0
  41. package/src/task-types.ts +184 -0
  42. package/src/verification-diagnostics.ts +29 -72
  43. package/src/worker-capture.mjs +1197 -0
  44. package/src/worker-public.mjs +1753 -0
  45. package/src/worker-redaction.mjs +608 -0
  46. package/src/worker-tools.mjs +202 -0
  47. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  50. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  56. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  62. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  65. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  68. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  71. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  74. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  77. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  80. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  89. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  92. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  95. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  98. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  101. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  104. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  107. package/web/dist/assets/cadence.css +2 -0
  108. package/web/dist/assets/cadence.js +7 -0
  109. package/web/dist/assets/markdown.js +296 -0
  110. package/web/dist/assets/react.js +9 -0
  111. package/web/dist/assets/rolldown-runtime.js +1 -0
  112. package/web/dist/assets/timeline-client.js +41 -0
  113. package/web/dist/assets/vis-timeline.css +1274 -0
  114. package/web/dist/assets/vis-timeline.js +32 -0
  115. package/web/dist/index.html +18 -0
  116. package/web/dist/manifest.json +505 -0
  117. package/config/plan-draft.example.json +0 -125
  118. package/config/plan-draft.multiple-tasks.example.json +0 -375
  119. package/config/plan-draft.quick.example.json +0 -78
  120. package/prompts/abel-design.md +0 -382
  121. package/prompts/abel-diagnose.md +0 -71
  122. package/prompts/abel-implement.md +0 -284
  123. package/prompts/abel-init.md +0 -52
  124. package/src/activation.ts +0 -44
  125. package/src/activity-contracts.ts +0 -82
  126. package/src/agent-registry.ts +0 -53
  127. package/src/apply-transaction.ts +0 -1437
  128. package/src/artifact-store.ts +0 -213
  129. package/src/build-windows-job.mjs +0 -95
  130. package/src/candidate-artifact.ts +0 -143
  131. package/src/candidate-context.ts +0 -289
  132. package/src/candidate-patch.ts +0 -271
  133. package/src/candidate-workspace.ts +0 -80
  134. package/src/canonical.ts +0 -16
  135. package/src/change-contract-schema.ts +0 -191
  136. package/src/change-contract.ts +0 -324
  137. package/src/change-verification.ts +0 -1617
  138. package/src/contracts.ts +0 -2466
  139. package/src/control-contracts.ts +0 -382
  140. package/src/delivery-compiler.ts +0 -1805
  141. package/src/delivery-revision.ts +0 -42
  142. package/src/design-control.ts +0 -1299
  143. package/src/design-diagnostics.ts +0 -181
  144. package/src/design-journal.ts +0 -1773
  145. package/src/directory-sync.ts +0 -36
  146. package/src/durable-contracts.ts +0 -227
  147. package/src/durable-workflow.ts +0 -822
  148. package/src/evidence-draft.ts +0 -122
  149. package/src/execution-profile.ts +0 -64
  150. package/src/execution-retention.ts +0 -77
  151. package/src/file-snapshot.ts +0 -163
  152. package/src/implement-continuation.ts +0 -559
  153. package/src/implement-graph.ts +0 -690
  154. package/src/implement-plan.ts +0 -72
  155. package/src/install-config.d.mts +0 -6
  156. package/src/isolation-backend.ts +0 -353
  157. package/src/model-source.ts +0 -22
  158. package/src/openspec-cli.ts +0 -494
  159. package/src/operator-cli-entry.mjs +0 -24
  160. package/src/operator-tools.ts +0 -309
  161. package/src/package-candidate-process.ts +0 -206
  162. package/src/package-candidate.ts +0 -24
  163. package/src/package-delivery.ts +0 -486
  164. package/src/package-state.ts +0 -320
  165. package/src/package-verification.ts +0 -635
  166. package/src/package-workflow.ts +0 -305
  167. package/src/packet-runtime.ts +0 -459
  168. package/src/phase-execution.ts +0 -2163
  169. package/src/pi-adapter.ts +0 -19
  170. package/src/plan-draft-summary.ts +0 -527
  171. package/src/plan-draft.ts +0 -502
  172. package/src/remove-path.ts +0 -32
  173. package/src/run-state.ts +0 -420
  174. package/src/run-store.ts +0 -1179
  175. package/src/safe-path.ts +0 -77
  176. package/src/scoped-grep-worker.mjs +0 -23
  177. package/src/scoped-tools.ts +0 -775
  178. package/src/single-task-draft.ts +0 -142
  179. package/src/sqlite-schema.ts +0 -171
  180. package/src/state-root.ts +0 -215
  181. package/src/storage-schema.ts +0 -360
  182. package/src/subagent-activity.ts +0 -1417
  183. package/src/subagent-process.ts +0 -476
  184. package/src/task-ledger.ts +0 -1338
  185. package/src/transport-budget.ts +0 -50
  186. package/src/verification-capability.ts +0 -1191
  187. package/src/verification-environment-io.ts +0 -389
  188. package/src/verification-environment.ts +0 -162
  189. package/src/verification-identity.ts +0 -77
  190. package/src/verification-scan.ts +0 -64
  191. package/src/windows-job-backend.ts +0 -419
  192. package/src/windows-job.c +0 -149
  193. package/src/workflow-engine.ts +0 -28
  194. package/src/workflow-policy.ts +0 -1670
  195. package/src/workflow-recovery-policy.ts +0 -97
  196. package/src/workflow-scheduling.ts +0 -105
  197. package/src/workflow-state-machine.ts +0 -4341
  198. package/src/workflow-status.ts +0 -576
  199. package/src/workspace-io-entry.mjs +0 -76
  200. package/src/workspace-io-worker.mjs +0 -1312
  201. package/src/workspace-io.ts +0 -89
  202. package/src/workspace-store.ts +0 -634
@@ -1,122 +0,0 @@
1
- import { StringEnum } from "@earendil-works/pi-ai";
2
- import { Type } from "typebox";
3
- import { AGENTS_IMPACTS, RELATIVE_PATH_PATTERN } from "./contracts.ts";
4
-
5
- // The authoring boundary is smaller than the sealed EvidenceResult. Only
6
- // code-owned identity and explicitly advisory fields may be omitted.
7
- const advisoryStrings = () => Type.Optional(Type.Array(Type.String()));
8
- const relativePath = () =>
9
- Type.String({
10
- pattern: RELATIVE_PATH_PATTERN,
11
- minLength: 1,
12
- maxLength: 512,
13
- description:
14
- "Canonical workspace-relative path or module slug; no absolute paths, ./, .., backslashes, or trailing slash.",
15
- });
16
- const pathSet = (minItems = 0) =>
17
- Type.Array(relativePath(), { uniqueItems: true, minItems });
18
-
19
- export function evidenceDraftSchema(requestId: string, role: string) {
20
- const identity = {
21
- id: Type.Optional(
22
- Type.Literal(requestId, {
23
- description:
24
- "Omit: bound by the parent result adapter. If supplied, must match this request.",
25
- }),
26
- ),
27
- role: Type.Optional(Type.Literal(role)),
28
- kind: Type.Optional(Type.Literal("evidence")),
29
- };
30
- return role === "design-explorer"
31
- ? Type.Object(
32
- {
33
- ...identity,
34
- packet_id: Type.Optional(Type.Literal(requestId)),
35
- module_name: relativePath(),
36
- scope: pathSet(1),
37
- files_read: pathSet(),
38
- evidence: Type.Array(
39
- Type.Object(
40
- {
41
- claim: Type.String({ minLength: 1 }),
42
- path: relativePath(),
43
- line_start: Type.Integer({ minimum: 1 }),
44
- line_end: Type.Integer({
45
- minimum: 1,
46
- description:
47
- "Inclusive end line, greater than or equal to line_start.",
48
- }),
49
- },
50
- { additionalProperties: false },
51
- ),
52
- ),
53
- existing_structures: advisoryStrings(),
54
- existing_conventions: advisoryStrings(),
55
- constraints_discovered: Type.Array(Type.String()),
56
- open_questions: Type.Array(Type.String()),
57
- dependencies: advisoryStrings(),
58
- write_set_hints: Type.Optional(pathSet()),
59
- validation_hints: advisoryStrings(),
60
- agents_impact_hints: advisoryStrings(),
61
- risks: Type.Array(Type.String()),
62
- success_criteria_hints: advisoryStrings(),
63
- },
64
- { additionalProperties: false },
65
- )
66
- : Type.Object(
67
- {
68
- ...identity,
69
- conclusions: Type.Array(Type.String()),
70
- citations: Type.Array(
71
- Type.Object(
72
- { path: relativePath(), lines: Type.String() },
73
- { additionalProperties: false },
74
- ),
75
- ),
76
- constraints: Type.Array(Type.String()),
77
- dependencies: advisoryStrings(),
78
- risks: Type.Array(Type.String()),
79
- blockingQuestions: Type.Array(Type.String()),
80
- hints: Type.Optional(
81
- Type.Object(
82
- {
83
- writeSet: pathSet(),
84
- verification: Type.String(),
85
- agentsImpact: StringEnum(AGENTS_IMPACTS),
86
- },
87
- { additionalProperties: false },
88
- ),
89
- ),
90
- },
91
- { additionalProperties: false },
92
- );
93
- }
94
-
95
- export function completeEvidenceDraft(
96
- value: unknown,
97
- requestId: string,
98
- role: string,
99
- ): unknown {
100
- if (!value || typeof value !== "object" || Array.isArray(value)) return value;
101
- const draft = value as Record<string, unknown>;
102
- const defaults: Record<string, unknown> = {
103
- id: requestId,
104
- role,
105
- kind: "evidence",
106
- dependencies: [],
107
- ...(role === "design-explorer"
108
- ? {
109
- packet_id: requestId,
110
- existing_structures: [],
111
- existing_conventions: [],
112
- write_set_hints: [],
113
- validation_hints: [],
114
- agents_impact_hints: [],
115
- success_criteria_hints: [],
116
- }
117
- : { hints: { writeSet: [], verification: "", agentsImpact: "none" } }),
118
- };
119
- // Defaults mean "no advisory information supplied", never verified facts or
120
- // authority. Explicit wrong values (including null/undefined) survive validation.
121
- return { ...defaults, ...draft };
122
- }
@@ -1,64 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- export interface ExecutionProfile {
5
- mode: "isolated" | "local-trusted" | "host-trusted";
6
- timeoutMs: number;
7
- bwrapPath: string;
8
- inheritEnvironment: string[];
9
- windowsHelperPath?: string;
10
- }
11
-
12
- /** Operator configuration only. Unknown modes never silently disable isolation. */
13
- export function executionProfile(
14
- env: NodeJS.ProcessEnv = process.env,
15
- ): ExecutionProfile {
16
- const mode = env.ABEL_EXECUTION_MODE ?? "isolated";
17
- if (
18
- mode !== "isolated" &&
19
- mode !== "local-trusted" &&
20
- mode !== "host-trusted"
21
- )
22
- throw new Error("execution-mode-invalid");
23
- const timeoutMs = Number(env.ABEL_VERIFICATION_TIMEOUT_MS ?? 600_000);
24
- if (
25
- !Number.isSafeInteger(timeoutMs) ||
26
- timeoutMs < 1 ||
27
- timeoutMs > 86_400_000
28
- )
29
- throw new Error("verification-timeout-invalid");
30
- const inheritEnvironment = (env.ABEL_VERIFICATION_ENV ?? "")
31
- .split(",")
32
- .map((name) => name.trim())
33
- .filter(Boolean);
34
- if (
35
- inheritEnvironment.some((name) => !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name))
36
- )
37
- throw new Error("verification-environment-names-invalid");
38
- if (mode === "isolated" && inheritEnvironment.length)
39
- throw new Error("verification-environment-requires-trusted-mode");
40
- if (
41
- mode === "host-trusted" &&
42
- env.ABEL_WINDOWS_JOB_HELPER !== undefined &&
43
- (!env.ABEL_WINDOWS_JOB_HELPER ||
44
- env.ABEL_WINDOWS_JOB_HELPER.includes("\0") ||
45
- !(
46
- path.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER) ||
47
- path.win32.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER)
48
- ))
49
- )
50
- throw new Error("windows-job-helper-path-invalid");
51
- return {
52
- mode,
53
- timeoutMs,
54
- bwrapPath: env.ABEL_BWRAP_PATH ?? "/usr/bin/bwrap",
55
- inheritEnvironment,
56
- ...(mode === "host-trusted"
57
- ? {
58
- windowsHelperPath:
59
- env.ABEL_WINDOWS_JOB_HELPER ??
60
- fileURLToPath(new URL("./windows-job.exe", import.meta.url)),
61
- }
62
- : {}),
63
- };
64
- }
@@ -1,77 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import {
3
- closeSync,
4
- fsyncSync,
5
- lstatSync,
6
- openSync,
7
- readdirSync,
8
- unlinkSync,
9
- writeFileSync,
10
- } from "node:fs";
11
- import path from "node:path";
12
- import { syncDirectory } from "./directory-sync.ts";
13
-
14
- const prefix = ".cadence-execution-";
15
- const active = new Set<string>();
16
- const retained = new Map<string, readonly string[]>();
17
- /** Written before launching a host process. Absence of a settlement receipt
18
- * survives host loss; reopening must not infer ownership from a recycled PID. */
19
- export function retainExecution(ownerRoot: string, roots: readonly string[]) {
20
- assertExecutionsSettled(ownerRoot);
21
- const file = path.join(ownerRoot, `${prefix}${randomUUID()}.json`);
22
- const fd = openSync(file, "wx", 0o600);
23
- try {
24
- writeFileSync(
25
- fd,
26
- JSON.stringify({
27
- version: 1,
28
- roots: roots.map((root) => path.resolve(root)),
29
- }),
30
- );
31
- fsyncSync(fd);
32
- } finally {
33
- closeSync(fd);
34
- }
35
- syncDirectory(ownerRoot);
36
- active.add(file);
37
- retained.set(file, roots);
38
- return {
39
- settled() {
40
- unlinkSync(file);
41
- syncDirectory(ownerRoot);
42
- active.delete(file);
43
- retained.delete(file);
44
- },
45
- uncertain() {
46
- active.delete(file);
47
- },
48
- };
49
- }
50
- export function assertExecutionsSettled(
51
- ownerRoot: string,
52
- includeActive = false,
53
- ): void {
54
- if (!lstatSync(ownerRoot, { throwIfNoEntry: false })) return;
55
- if (
56
- readdirSync(ownerRoot).some(
57
- (name) =>
58
- name.startsWith(prefix) &&
59
- (includeActive || !active.has(path.join(ownerRoot, name))),
60
- )
61
- )
62
- throw new Error("isolation-termination-unconfirmed");
63
- }
64
- export function isExecutionRetained(root: string): boolean {
65
- const resolved = path.resolve(root);
66
- return [...retained.values()].some((roots) =>
67
- roots.some((candidate) => {
68
- const relative = path.relative(resolved, path.resolve(candidate));
69
- return (
70
- relative === "" ||
71
- (relative !== ".." &&
72
- !relative.startsWith(`..${path.sep}`) &&
73
- !path.isAbsolute(relative))
74
- );
75
- }),
76
- );
77
- }
@@ -1,163 +0,0 @@
1
- // File-level snapshots: content hashes for observed files, deterministic
2
- // recursive content manifests for observed directories, and explicit absent
3
- // markers for proposed new paths. Currency is decided per bound path; no
4
- // global workspace revision participates.
5
- import { createHash } from "node:crypto";
6
- import { lstatSync, readdirSync, readFileSync, readlinkSync } from "node:fs";
7
- import { join, resolve } from "node:path";
8
-
9
- import { observeSafePath } from "./safe-path.ts";
10
-
11
- export interface FileBound {
12
- kind: "file";
13
- sha256: string;
14
- bytes: number;
15
- }
16
-
17
- export interface DirBound {
18
- kind: "dir";
19
- manifest: string;
20
- }
21
-
22
- export interface AbsentBound {
23
- kind: "absent";
24
- absent: true;
25
- }
26
-
27
- export type Bound = Record<string, FileBound | DirBound | AbsentBound>;
28
-
29
- function sha256(bytes: Buffer): string {
30
- return createHash("sha256").update(bytes).digest("hex");
31
- }
32
-
33
- /** Hash bytes with the same identity used by file and workspace snapshots. */
34
- export function snapshotContentHash(bytes: Uint8Array): string {
35
- return createHash("sha256").update(bytes).digest("hex");
36
- }
37
-
38
- /** Snapshot one existing regular file under root. */
39
- export function snapshotFile(root: string, relPath: string): FileBound | null {
40
- if (observeSafePath(root, relPath).kind !== "file") return null;
41
- const abs = resolve(root, relPath);
42
- const bytes = readFileSync(abs);
43
- return { sha256: sha256(bytes), bytes: bytes.length, kind: "file" };
44
- }
45
-
46
- function updateManifest(
47
- hash: ReturnType<typeof createHash>,
48
- ...fields: Array<string | Buffer>
49
- ): void {
50
- for (const field of fields) {
51
- const bytes = typeof field === "string" ? Buffer.from(field) : field;
52
- hash.update(`${bytes.length}:`);
53
- hash.update(bytes);
54
- }
55
- }
56
-
57
- function recursiveDirectoryManifest(abs: string): string {
58
- const hash = createHash("sha256");
59
- updateManifest(hash, "cadence-directory-manifest");
60
- const visit = (directory: string, prefix: string): void => {
61
- const names = readdirSync(directory).sort();
62
- for (const name of names) {
63
- const relative = prefix ? `${prefix}/${name}` : name;
64
- const current = join(directory, name);
65
- const stat = lstatSync(current);
66
- if (stat.isDirectory()) {
67
- updateManifest(hash, "dir", relative, String(stat.mode & 0o777));
68
- visit(current, relative);
69
- } else if (stat.isFile()) {
70
- const bytes = readFileSync(current);
71
- updateManifest(
72
- hash,
73
- "file",
74
- relative,
75
- String(stat.mode & 0o777),
76
- String(bytes.length),
77
- sha256(bytes),
78
- );
79
- } else if (stat.isSymbolicLink()) {
80
- updateManifest(hash, "symlink", relative, readlinkSync(current));
81
- } else {
82
- updateManifest(
83
- hash,
84
- "special",
85
- relative,
86
- String(stat.mode),
87
- String(stat.size),
88
- );
89
- }
90
- }
91
- };
92
- visit(abs, "");
93
- return hash.digest("hex");
94
- }
95
-
96
- /** Deterministic recursive directory manifest bound to installed content. */
97
- export function snapshotDirManifest(
98
- root: string,
99
- relDir: string,
100
- ): DirBound | null {
101
- if (observeSafePath(root, relDir).kind !== "directory") return null;
102
- const abs = resolve(root, relDir);
103
- return { manifest: recursiveDirectoryManifest(abs), kind: "dir" };
104
- }
105
-
106
- /** Build a bound map for the given file paths, observed directories, and absent markers. */
107
- export function snapshotFiles(
108
- root: string,
109
- files: string[],
110
- opts: { absent?: string[] } = {},
111
- ): Bound {
112
- const bound: Bound = {};
113
- for (const p of [...new Set(files)]) {
114
- const entry = snapshotFile(root, p);
115
- if (entry) bound[p] = entry;
116
- else bound[p] = { kind: "absent", absent: true };
117
- }
118
- for (const p of opts.absent ?? []) {
119
- bound[p] = { kind: "absent", absent: true };
120
- }
121
- return bound;
122
- }
123
-
124
- /** Build a bound map for observed directories (deterministic manifests). */
125
- export function snapshotDirManifests(root: string, dirs: string[]): Bound {
126
- const bound: Bound = {};
127
- for (const p of [...new Set(dirs)]) {
128
- const entry = snapshotDirManifest(root, p);
129
- if (entry) bound[p] = entry;
130
- else bound[p] = { kind: "absent", absent: true };
131
- }
132
- return bound;
133
- }
134
-
135
- /** Recapture each bound path and report whether the bound state is current. */
136
- export function isCurrent(root: string, bound: Bound): boolean {
137
- for (const [rel, expected] of Object.entries(bound)) {
138
- if (expected.kind === "dir") {
139
- const now = snapshotDirManifest(root, rel);
140
- if (!now || now.manifest !== expected.manifest) return false;
141
- continue;
142
- }
143
- const observation = observeSafePath(root, rel);
144
- if (expected.kind === "file") {
145
- if (observation.kind !== "file") return false;
146
- const now = snapshotFile(root, rel);
147
- if (
148
- !now ||
149
- now.sha256 !== expected.sha256 ||
150
- now.bytes !== expected.bytes
151
- )
152
- return false;
153
- } else if (expected.absent && observation.kind !== "absent") {
154
- return false;
155
- }
156
- }
157
- return true;
158
- }
159
-
160
- /** Merge bound maps (read observations + write targets). */
161
- export function mergeBounds(...bounds: Bound[]): Bound {
162
- return Object.assign({}, ...bounds);
163
- }