@abelxiaoxing/cadence 1.8.3 → 2.0.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.
Files changed (208) hide show
  1. package/README.md +324 -111
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/agents/design-explorer.md +14 -26
  4. package/agents/diagnosis-worker.md +14 -25
  5. package/agents/implementation-worker.md +16 -35
  6. package/config/.env.example +2 -5
  7. package/config/workflow-task.example.json +7 -0
  8. package/docs/remote-access.md +60 -0
  9. package/docs/reuse-and-distribution.md +44 -0
  10. package/docs/service-management.md +108 -0
  11. package/licenses/bundled-web.txt +6245 -0
  12. package/licenses/pi-web-ui-MIT.txt +21 -0
  13. package/licenses/pi-web-ui-reuse.md +22 -0
  14. package/package.json +94 -21
  15. package/src/index.ts +22 -1807
  16. package/src/native-build.ts +6 -0
  17. package/src/native-client.ts +103 -0
  18. package/src/native-export.ts +58 -0
  19. package/src/native-history.ts +359 -0
  20. package/src/operator-cli.mjs +453 -1937
  21. package/src/plan-proposal.ts +87 -0
  22. package/src/sdk-worker.mjs +549 -0
  23. package/src/service/contracts/active.ts +82 -0
  24. package/src/service/contracts/budget.ts +148 -0
  25. package/src/service/contracts/build.ts +67 -0
  26. package/src/service/contracts/capture.ts +48 -0
  27. package/src/service/contracts/core.ts +33 -0
  28. package/src/service/contracts/current.ts +261 -0
  29. package/src/service/contracts/domain.ts +68 -0
  30. package/src/service/contracts/history.ts +2245 -0
  31. package/src/service/contracts/models.ts +139 -0
  32. package/src/service/coordination.ts +862 -0
  33. package/src/service/private-files.ts +204 -0
  34. package/src/service-cli.mjs +5592 -0
  35. package/src/service-client.ts +747 -0
  36. package/src/service-contract.ts +245 -0
  37. package/src/service-host.mjs +22382 -0
  38. package/src/service-http-client.ts +425 -0
  39. package/src/service-wire.ts +372 -0
  40. package/src/task-shell.ts +1045 -0
  41. package/src/task-store.ts +479 -0
  42. package/src/task-types.ts +184 -0
  43. package/src/verification-diagnostics.ts +29 -72
  44. package/src/worker-capture.mjs +1197 -0
  45. package/src/worker-public.mjs +1753 -0
  46. package/src/worker-redaction.mjs +608 -0
  47. package/src/worker-tools.mjs +202 -0
  48. package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
  49. package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
  50. package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
  51. package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
  52. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
  53. package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
  54. package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
  55. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
  56. package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
  57. package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
  58. package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
  59. package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
  60. package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
  61. package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
  62. package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
  63. package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
  64. package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
  65. package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
  66. package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
  67. package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
  68. package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
  69. package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
  70. package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
  71. package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
  72. package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
  73. package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
  74. package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
  75. package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
  76. package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
  77. package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
  78. package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
  79. package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
  80. package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
  81. package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
  82. package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
  83. package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
  84. package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
  85. package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
  86. package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
  87. package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
  88. package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
  89. package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
  90. package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
  91. package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
  92. package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
  93. package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
  94. package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
  95. package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
  97. package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
  98. package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
  99. package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
  100. package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
  101. package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
  102. package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
  103. package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
  104. package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
  105. package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
  106. package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
  107. package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
  108. package/web/dist/assets/cadence.css +2 -0
  109. package/web/dist/assets/cadence.js +7 -0
  110. package/web/dist/assets/markdown.js +296 -0
  111. package/web/dist/assets/react.js +9 -0
  112. package/web/dist/assets/rolldown-runtime.js +1 -0
  113. package/web/dist/assets/timeline-client.js +41 -0
  114. package/web/dist/assets/vis-timeline.css +1274 -0
  115. package/web/dist/assets/vis-timeline.js +32 -0
  116. package/web/dist/index.html +18 -0
  117. package/web/dist/manifest.json +505 -0
  118. package/config/plan-draft.example.json +0 -125
  119. package/config/plan-draft.multiple-tasks.example.json +0 -375
  120. package/config/plan-draft.quick.example.json +0 -78
  121. package/config/routes.example.json +0 -39
  122. package/prompts/abel-design.md +0 -382
  123. package/prompts/abel-diagnose.md +0 -71
  124. package/prompts/abel-implement.md +0 -284
  125. package/prompts/abel-init.md +0 -52
  126. package/src/activation.ts +0 -44
  127. package/src/activity-contracts.ts +0 -82
  128. package/src/agent-registry.ts +0 -53
  129. package/src/apply-transaction.ts +0 -1437
  130. package/src/artifact-store.ts +0 -213
  131. package/src/build-windows-job.mjs +0 -95
  132. package/src/candidate-artifact.ts +0 -143
  133. package/src/candidate-patch.ts +0 -271
  134. package/src/candidate-workspace.ts +0 -80
  135. package/src/canonical.ts +0 -16
  136. package/src/change-contract-schema.ts +0 -191
  137. package/src/change-contract.ts +0 -324
  138. package/src/change-verification.ts +0 -1617
  139. package/src/child-budget.ts +0 -58
  140. package/src/child-model.ts +0 -142
  141. package/src/child-session.ts +0 -640
  142. package/src/contracts.ts +0 -2476
  143. package/src/control-contracts.ts +0 -382
  144. package/src/delivery-compiler.ts +0 -1805
  145. package/src/delivery-revision.ts +0 -42
  146. package/src/design-control.ts +0 -1299
  147. package/src/design-diagnostics.ts +0 -181
  148. package/src/design-journal.ts +0 -1773
  149. package/src/directory-sync.ts +0 -36
  150. package/src/durable-contracts.ts +0 -231
  151. package/src/durable-workflow.ts +0 -978
  152. package/src/evidence-draft.ts +0 -122
  153. package/src/execution-profile.ts +0 -64
  154. package/src/execution-retention.ts +0 -77
  155. package/src/file-snapshot.ts +0 -163
  156. package/src/implement-continuation.ts +0 -559
  157. package/src/implement-graph.ts +0 -690
  158. package/src/implement-plan.ts +0 -72
  159. package/src/install-config.d.mts +0 -6
  160. package/src/isolation-backend.ts +0 -353
  161. package/src/model-source.ts +0 -22
  162. package/src/openspec-cli.ts +0 -494
  163. package/src/operator-cli-entry.mjs +0 -24
  164. package/src/operator-tools.ts +0 -309
  165. package/src/package-candidate.ts +0 -227
  166. package/src/package-delivery.ts +0 -486
  167. package/src/package-state.ts +0 -320
  168. package/src/package-verification.ts +0 -635
  169. package/src/package-workflow.ts +0 -342
  170. package/src/packet-runtime.ts +0 -570
  171. package/src/parent-provider.ts +0 -375
  172. package/src/phase-execution.ts +0 -2259
  173. package/src/pi-adapter.ts +0 -19
  174. package/src/plan-draft-summary.ts +0 -527
  175. package/src/plan-draft.ts +0 -502
  176. package/src/remove-path.ts +0 -32
  177. package/src/route-policy.ts +0 -545
  178. package/src/run-state.ts +0 -420
  179. package/src/run-store.ts +0 -1179
  180. package/src/safe-path.ts +0 -77
  181. package/src/scoped-grep-worker.mjs +0 -23
  182. package/src/scoped-tools.ts +0 -775
  183. package/src/single-task-draft.ts +0 -142
  184. package/src/sqlite-schema.ts +0 -171
  185. package/src/state-root.ts +0 -215
  186. package/src/storage-schema.ts +0 -360
  187. package/src/subagent-activity.ts +0 -1417
  188. package/src/submit-tool.ts +0 -950
  189. package/src/task-ledger.ts +0 -1338
  190. package/src/transport-budget.ts +0 -52
  191. package/src/verification-capability.ts +0 -1191
  192. package/src/verification-environment-io.ts +0 -389
  193. package/src/verification-environment.ts +0 -162
  194. package/src/verification-identity.ts +0 -77
  195. package/src/verification-scan.ts +0 -64
  196. package/src/windows-job-backend.ts +0 -419
  197. package/src/windows-job.c +0 -149
  198. package/src/worker-broker.ts +0 -806
  199. package/src/workflow-engine.ts +0 -28
  200. package/src/workflow-policy.ts +0 -1679
  201. package/src/workflow-recovery-policy.ts +0 -97
  202. package/src/workflow-scheduling.ts +0 -105
  203. package/src/workflow-state-machine.ts +0 -4355
  204. package/src/workflow-status.ts +0 -576
  205. package/src/workspace-io-entry.mjs +0 -76
  206. package/src/workspace-io-worker.mjs +0 -1312
  207. package/src/workspace-io.ts +0 -89
  208. 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 submit tool. 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
- }