wingfoil 0.2.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 (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +334 -0
  3. package/dist/cli/error.d.ts +11 -0
  4. package/dist/cli/error.d.ts.map +1 -0
  5. package/dist/cli/error.js +27 -0
  6. package/dist/cli/error.js.map +1 -0
  7. package/dist/cli/exit.d.ts +19 -0
  8. package/dist/cli/exit.d.ts.map +1 -0
  9. package/dist/cli/exit.js +14 -0
  10. package/dist/cli/exit.js.map +1 -0
  11. package/dist/cli/index.d.ts +28 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +36 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/init-command.d.ts +34 -0
  16. package/dist/cli/init-command.d.ts.map +1 -0
  17. package/dist/cli/init-command.js +100 -0
  18. package/dist/cli/init-command.js.map +1 -0
  19. package/dist/cli/mcp-command.d.ts +24 -0
  20. package/dist/cli/mcp-command.d.ts.map +1 -0
  21. package/dist/cli/mcp-command.js +42 -0
  22. package/dist/cli/mcp-command.js.map +1 -0
  23. package/dist/cli/output.d.ts +13 -0
  24. package/dist/cli/output.d.ts.map +1 -0
  25. package/dist/cli/output.js +28 -0
  26. package/dist/cli/output.js.map +1 -0
  27. package/dist/cli/program.d.ts +12 -0
  28. package/dist/cli/program.d.ts.map +1 -0
  29. package/dist/cli/program.js +279 -0
  30. package/dist/cli/program.js.map +1 -0
  31. package/dist/cli/registrar.d.ts +69 -0
  32. package/dist/cli/registrar.d.ts.map +1 -0
  33. package/dist/cli/registrar.js +82 -0
  34. package/dist/cli/registrar.js.map +1 -0
  35. package/dist/cli.d.ts +3 -0
  36. package/dist/cli.d.ts.map +1 -0
  37. package/dist/cli.js +45 -0
  38. package/dist/cli.js.map +1 -0
  39. package/dist/core/approval-authority.d.ts +83 -0
  40. package/dist/core/approval-authority.d.ts.map +1 -0
  41. package/dist/core/approval-authority.js +112 -0
  42. package/dist/core/approval-authority.js.map +1 -0
  43. package/dist/core/builtin-asset.d.ts +71 -0
  44. package/dist/core/builtin-asset.d.ts.map +1 -0
  45. package/dist/core/builtin-asset.js +137 -0
  46. package/dist/core/builtin-asset.js.map +1 -0
  47. package/dist/core/builtin-integrity.d.ts +32 -0
  48. package/dist/core/builtin-integrity.d.ts.map +1 -0
  49. package/dist/core/builtin-integrity.js +148 -0
  50. package/dist/core/builtin-integrity.js.map +1 -0
  51. package/dist/core/confinement.d.ts +57 -0
  52. package/dist/core/confinement.d.ts.map +1 -0
  53. package/dist/core/confinement.js +108 -0
  54. package/dist/core/confinement.js.map +1 -0
  55. package/dist/core/context.d.ts +187 -0
  56. package/dist/core/context.d.ts.map +1 -0
  57. package/dist/core/context.js +192 -0
  58. package/dist/core/context.js.map +1 -0
  59. package/dist/core/directive-assign.d.ts +131 -0
  60. package/dist/core/directive-assign.d.ts.map +1 -0
  61. package/dist/core/directive-assign.js +352 -0
  62. package/dist/core/directive-assign.js.map +1 -0
  63. package/dist/core/directives-list.d.ts +80 -0
  64. package/dist/core/directives-list.d.ts.map +1 -0
  65. package/dist/core/directives-list.js +148 -0
  66. package/dist/core/directives-list.js.map +1 -0
  67. package/dist/core/exit-code.d.ts +85 -0
  68. package/dist/core/exit-code.d.ts.map +1 -0
  69. package/dist/core/exit-code.js +163 -0
  70. package/dist/core/exit-code.js.map +1 -0
  71. package/dist/core/git-identity.d.ts +32 -0
  72. package/dist/core/git-identity.d.ts.map +1 -0
  73. package/dist/core/git-identity.js +72 -0
  74. package/dist/core/git-identity.js.map +1 -0
  75. package/dist/core/index.d.ts +388 -0
  76. package/dist/core/index.d.ts.map +1 -0
  77. package/dist/core/index.js +1331 -0
  78. package/dist/core/index.js.map +1 -0
  79. package/dist/core/init.d.ts +78 -0
  80. package/dist/core/init.d.ts.map +1 -0
  81. package/dist/core/init.js +167 -0
  82. package/dist/core/init.js.map +1 -0
  83. package/dist/core/loaders.d.ts +174 -0
  84. package/dist/core/loaders.d.ts.map +1 -0
  85. package/dist/core/loaders.js +364 -0
  86. package/dist/core/loaders.js.map +1 -0
  87. package/dist/core/memory-add-type.d.ts +43 -0
  88. package/dist/core/memory-add-type.d.ts.map +1 -0
  89. package/dist/core/memory-add-type.js +151 -0
  90. package/dist/core/memory-add-type.js.map +1 -0
  91. package/dist/core/memory-transition.d.ts +111 -0
  92. package/dist/core/memory-transition.d.ts.map +1 -0
  93. package/dist/core/memory-transition.js +269 -0
  94. package/dist/core/memory-transition.js.map +1 -0
  95. package/dist/core/registry.d.ts +189 -0
  96. package/dist/core/registry.d.ts.map +1 -0
  97. package/dist/core/registry.js +66 -0
  98. package/dist/core/registry.js.map +1 -0
  99. package/dist/core/relevance.d.ts +86 -0
  100. package/dist/core/relevance.d.ts.map +1 -0
  101. package/dist/core/relevance.js +258 -0
  102. package/dist/core/relevance.js.map +1 -0
  103. package/dist/core/require-reason.d.ts +27 -0
  104. package/dist/core/require-reason.d.ts.map +1 -0
  105. package/dist/core/require-reason.js +95 -0
  106. package/dist/core/require-reason.js.map +1 -0
  107. package/dist/core/types.d.ts +45 -0
  108. package/dist/core/types.d.ts.map +1 -0
  109. package/dist/core/types.js +22 -0
  110. package/dist/core/types.js.map +1 -0
  111. package/dist/core/usage-error.d.ts +20 -0
  112. package/dist/core/usage-error.d.ts.map +1 -0
  113. package/dist/core/usage-error.js +29 -0
  114. package/dist/core/usage-error.js.map +1 -0
  115. package/dist/core/write-guard.d.ts +99 -0
  116. package/dist/core/write-guard.d.ts.map +1 -0
  117. package/dist/core/write-guard.js +247 -0
  118. package/dist/core/write-guard.js.map +1 -0
  119. package/dist/directives/create.d.ts +47 -0
  120. package/dist/directives/create.d.ts.map +1 -0
  121. package/dist/directives/create.js +82 -0
  122. package/dist/directives/create.js.map +1 -0
  123. package/dist/directives/index.d.ts +20 -0
  124. package/dist/directives/index.d.ts.map +1 -0
  125. package/dist/directives/index.js +32 -0
  126. package/dist/directives/index.js.map +1 -0
  127. package/dist/directives/roles-edit.d.ts +53 -0
  128. package/dist/directives/roles-edit.d.ts.map +1 -0
  129. package/dist/directives/roles-edit.js +286 -0
  130. package/dist/directives/roles-edit.js.map +1 -0
  131. package/dist/directives/schema.d.ts +65 -0
  132. package/dist/directives/schema.d.ts.map +1 -0
  133. package/dist/directives/schema.js +68 -0
  134. package/dist/directives/schema.js.map +1 -0
  135. package/dist/dna/edit.d.ts +61 -0
  136. package/dist/dna/edit.d.ts.map +1 -0
  137. package/dist/dna/edit.js +429 -0
  138. package/dist/dna/edit.js.map +1 -0
  139. package/dist/dna/index.d.ts +24 -0
  140. package/dist/dna/index.d.ts.map +1 -0
  141. package/dist/dna/index.js +59 -0
  142. package/dist/dna/index.js.map +1 -0
  143. package/dist/dna/mutate.d.ts +74 -0
  144. package/dist/dna/mutate.d.ts.map +1 -0
  145. package/dist/dna/mutate.js +318 -0
  146. package/dist/dna/mutate.js.map +1 -0
  147. package/dist/dna/path.d.ts +175 -0
  148. package/dist/dna/path.d.ts.map +1 -0
  149. package/dist/dna/path.js +289 -0
  150. package/dist/dna/path.js.map +1 -0
  151. package/dist/dna/roles.d.ts +57 -0
  152. package/dist/dna/roles.d.ts.map +1 -0
  153. package/dist/dna/roles.js +50 -0
  154. package/dist/dna/roles.js.map +1 -0
  155. package/dist/dna/schema.d.ts +172 -0
  156. package/dist/dna/schema.d.ts.map +1 -0
  157. package/dist/dna/schema.js +182 -0
  158. package/dist/dna/schema.js.map +1 -0
  159. package/dist/dna/set.d.ts +145 -0
  160. package/dist/dna/set.d.ts.map +1 -0
  161. package/dist/dna/set.js +428 -0
  162. package/dist/dna/set.js.map +1 -0
  163. package/dist/mcp/dna-resource.d.ts +16 -0
  164. package/dist/mcp/dna-resource.d.ts.map +1 -0
  165. package/dist/mcp/dna-resource.js +56 -0
  166. package/dist/mcp/dna-resource.js.map +1 -0
  167. package/dist/mcp/index.d.ts +49 -0
  168. package/dist/mcp/index.d.ts.map +1 -0
  169. package/dist/mcp/index.js +60 -0
  170. package/dist/mcp/index.js.map +1 -0
  171. package/dist/mcp/memory-resource.d.ts +17 -0
  172. package/dist/mcp/memory-resource.d.ts.map +1 -0
  173. package/dist/mcp/memory-resource.js +133 -0
  174. package/dist/mcp/memory-resource.js.map +1 -0
  175. package/dist/mcp/prompt.d.ts +48 -0
  176. package/dist/mcp/prompt.d.ts.map +1 -0
  177. package/dist/mcp/prompt.js +176 -0
  178. package/dist/mcp/prompt.js.map +1 -0
  179. package/dist/mcp/read-only.d.ts +42 -0
  180. package/dist/mcp/read-only.d.ts.map +1 -0
  181. package/dist/mcp/read-only.js +93 -0
  182. package/dist/mcp/read-only.js.map +1 -0
  183. package/dist/mcp/registrar.d.ts +54 -0
  184. package/dist/mcp/registrar.d.ts.map +1 -0
  185. package/dist/mcp/registrar.js +72 -0
  186. package/dist/mcp/registrar.js.map +1 -0
  187. package/dist/mcp/server.d.ts +58 -0
  188. package/dist/mcp/server.d.ts.map +1 -0
  189. package/dist/mcp/server.js +64 -0
  190. package/dist/mcp/server.js.map +1 -0
  191. package/dist/mcp/workflow-resource.d.ts +17 -0
  192. package/dist/mcp/workflow-resource.d.ts.map +1 -0
  193. package/dist/mcp/workflow-resource.js +63 -0
  194. package/dist/mcp/workflow-resource.js.map +1 -0
  195. package/dist/memory/add.d.ts +50 -0
  196. package/dist/memory/add.d.ts.map +1 -0
  197. package/dist/memory/add.js +131 -0
  198. package/dist/memory/add.js.map +1 -0
  199. package/dist/memory/audit.d.ts +177 -0
  200. package/dist/memory/audit.d.ts.map +1 -0
  201. package/dist/memory/audit.js +287 -0
  202. package/dist/memory/audit.js.map +1 -0
  203. package/dist/memory/commit-message.d.ts +146 -0
  204. package/dist/memory/commit-message.d.ts.map +1 -0
  205. package/dist/memory/commit-message.js +203 -0
  206. package/dist/memory/commit-message.js.map +1 -0
  207. package/dist/memory/entry.d.ts +56 -0
  208. package/dist/memory/entry.d.ts.map +1 -0
  209. package/dist/memory/entry.js +28 -0
  210. package/dist/memory/entry.js.map +1 -0
  211. package/dist/memory/frontmatter-edit.d.ts +68 -0
  212. package/dist/memory/frontmatter-edit.d.ts.map +1 -0
  213. package/dist/memory/frontmatter-edit.js +333 -0
  214. package/dist/memory/frontmatter-edit.js.map +1 -0
  215. package/dist/memory/git-log.d.ts +32 -0
  216. package/dist/memory/git-log.d.ts.map +1 -0
  217. package/dist/memory/git-log.js +100 -0
  218. package/dist/memory/git-log.js.map +1 -0
  219. package/dist/memory/history.d.ts +142 -0
  220. package/dist/memory/history.d.ts.map +1 -0
  221. package/dist/memory/history.js +250 -0
  222. package/dist/memory/history.js.map +1 -0
  223. package/dist/memory/index.d.ts +58 -0
  224. package/dist/memory/index.d.ts.map +1 -0
  225. package/dist/memory/index.js +107 -0
  226. package/dist/memory/index.js.map +1 -0
  227. package/dist/memory/query.d.ts +150 -0
  228. package/dist/memory/query.d.ts.map +1 -0
  229. package/dist/memory/query.js +307 -0
  230. package/dist/memory/query.js.map +1 -0
  231. package/dist/memory/reject.d.ts +11 -0
  232. package/dist/memory/reject.d.ts.map +1 -0
  233. package/dist/memory/reject.js +32 -0
  234. package/dist/memory/reject.js.map +1 -0
  235. package/dist/memory/schema.d.ts +104 -0
  236. package/dist/memory/schema.d.ts.map +1 -0
  237. package/dist/memory/schema.js +161 -0
  238. package/dist/memory/schema.js.map +1 -0
  239. package/dist/memory/state-machine.d.ts +166 -0
  240. package/dist/memory/state-machine.d.ts.map +1 -0
  241. package/dist/memory/state-machine.js +431 -0
  242. package/dist/memory/state-machine.js.map +1 -0
  243. package/dist/memory/submit.d.ts +11 -0
  244. package/dist/memory/submit.d.ts.map +1 -0
  245. package/dist/memory/submit.js +44 -0
  246. package/dist/memory/submit.js.map +1 -0
  247. package/dist/storage/builtin-directives.d.ts +54 -0
  248. package/dist/storage/builtin-directives.d.ts.map +1 -0
  249. package/dist/storage/builtin-directives.js +152 -0
  250. package/dist/storage/builtin-directives.js.map +1 -0
  251. package/dist/storage/commit.d.ts +92 -0
  252. package/dist/storage/commit.d.ts.map +1 -0
  253. package/dist/storage/commit.js +201 -0
  254. package/dist/storage/commit.js.map +1 -0
  255. package/dist/storage/confinement.d.ts +93 -0
  256. package/dist/storage/confinement.d.ts.map +1 -0
  257. package/dist/storage/confinement.js +164 -0
  258. package/dist/storage/confinement.js.map +1 -0
  259. package/dist/storage/document.d.ts +27 -0
  260. package/dist/storage/document.d.ts.map +1 -0
  261. package/dist/storage/document.js +52 -0
  262. package/dist/storage/document.js.map +1 -0
  263. package/dist/storage/errors.d.ts +36 -0
  264. package/dist/storage/errors.d.ts.map +1 -0
  265. package/dist/storage/errors.js +46 -0
  266. package/dist/storage/errors.js.map +1 -0
  267. package/dist/storage/frontmatter.d.ts +27 -0
  268. package/dist/storage/frontmatter.d.ts.map +1 -0
  269. package/dist/storage/frontmatter.js +37 -0
  270. package/dist/storage/frontmatter.js.map +1 -0
  271. package/dist/storage/git-root.d.ts +17 -0
  272. package/dist/storage/git-root.d.ts.map +1 -0
  273. package/dist/storage/git-root.js +49 -0
  274. package/dist/storage/git-root.js.map +1 -0
  275. package/dist/storage/index.d.ts +29 -0
  276. package/dist/storage/index.d.ts.map +1 -0
  277. package/dist/storage/index.js +66 -0
  278. package/dist/storage/index.js.map +1 -0
  279. package/dist/storage/init-state.d.ts +9 -0
  280. package/dist/storage/init-state.d.ts.map +1 -0
  281. package/dist/storage/init-state.js +24 -0
  282. package/dist/storage/init-state.js.map +1 -0
  283. package/dist/storage/layout.d.ts +51 -0
  284. package/dist/storage/layout.d.ts.map +1 -0
  285. package/dist/storage/layout.js +88 -0
  286. package/dist/storage/layout.js.map +1 -0
  287. package/dist/storage/memory-path.d.ts +72 -0
  288. package/dist/storage/memory-path.d.ts.map +1 -0
  289. package/dist/storage/memory-path.js +127 -0
  290. package/dist/storage/memory-path.js.map +1 -0
  291. package/dist/storage/snapshot.d.ts +16 -0
  292. package/dist/storage/snapshot.d.ts.map +1 -0
  293. package/dist/storage/snapshot.js +72 -0
  294. package/dist/storage/snapshot.js.map +1 -0
  295. package/dist/storage/templates.d.ts +88 -0
  296. package/dist/storage/templates.d.ts.map +1 -0
  297. package/dist/storage/templates.js +422 -0
  298. package/dist/storage/templates.js.map +1 -0
  299. package/dist/validation/error-mapper.d.ts +16 -0
  300. package/dist/validation/error-mapper.d.ts.map +1 -0
  301. package/dist/validation/error-mapper.js +18 -0
  302. package/dist/validation/error-mapper.js.map +1 -0
  303. package/dist/validation/errors.d.ts +69 -0
  304. package/dist/validation/errors.d.ts.map +1 -0
  305. package/dist/validation/errors.js +69 -0
  306. package/dist/validation/errors.js.map +1 -0
  307. package/dist/validation/id.d.ts +16 -0
  308. package/dist/validation/id.d.ts.map +1 -0
  309. package/dist/validation/id.js +177 -0
  310. package/dist/validation/id.js.map +1 -0
  311. package/dist/validation/index.d.ts +18 -0
  312. package/dist/validation/index.d.ts.map +1 -0
  313. package/dist/validation/index.js +37 -0
  314. package/dist/validation/index.js.map +1 -0
  315. package/dist/validation/secret-scan.d.ts +129 -0
  316. package/dist/validation/secret-scan.d.ts.map +1 -0
  317. package/dist/validation/secret-scan.js +367 -0
  318. package/dist/validation/secret-scan.js.map +1 -0
  319. package/dist/validation/two-pass.d.ts +24 -0
  320. package/dist/validation/two-pass.d.ts.map +1 -0
  321. package/dist/validation/two-pass.js +24 -0
  322. package/dist/validation/two-pass.js.map +1 -0
  323. package/dist/validation/warning.d.ts +31 -0
  324. package/dist/validation/warning.d.ts.map +1 -0
  325. package/dist/validation/warning.js +81 -0
  326. package/dist/validation/warning.js.map +1 -0
  327. package/dist/validation/yaml.d.ts +9 -0
  328. package/dist/validation/yaml.d.ts.map +1 -0
  329. package/dist/validation/yaml.js +30 -0
  330. package/dist/validation/yaml.js.map +1 -0
  331. package/dist/workflow/index.d.ts +10 -0
  332. package/dist/workflow/index.d.ts.map +1 -0
  333. package/dist/workflow/index.js +16 -0
  334. package/dist/workflow/index.js.map +1 -0
  335. package/dist/workflow/schema.d.ts +89 -0
  336. package/dist/workflow/schema.d.ts.map +1 -0
  337. package/dist/workflow/schema.js +73 -0
  338. package/dist/workflow/schema.js.map +1 -0
  339. package/package.json +78 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * True when `target` is **not strictly inside** `resolvedRoot` — the root itself (a mutating op's
3
+ * target is a file *within* the project, never the project), a climb above it, or an unrelated
4
+ * absolute path. Both arguments must already be absolute; nothing here touches the filesystem.
5
+ */
6
+ export declare function escapesRoot(resolvedRoot: string, target: string): boolean;
7
+ /** What {@link resolveRealPathInRoot} found out about a path. */
8
+ export interface RealPathResolution {
9
+ /** The textual resolution under the root — what a prefix check would have compared. */
10
+ readonly absolute: string;
11
+ /** The filesystem resolution: the target's real parent directory, plus the target's own name. */
12
+ readonly real: string;
13
+ /** Whether {@link real} is strictly inside the (itself real-resolved) root. */
14
+ readonly within: boolean;
15
+ }
16
+ /**
17
+ * Resolve `relativePath` under `root` **on the filesystem** and report whether the result is still
18
+ * inside the project. The root is real-resolved too, so a project that itself lives under a
19
+ * symlinked path (a `/tmp` that is a link to `/private/tmp`, a home directory reached through one)
20
+ * does not read as its own escape.
21
+ *
22
+ * **The target's parent is resolved; the target's own name is not** — and that asymmetry is the
23
+ * contract, not an oversight. A directive that is a **symlinked file** inside a real directory is
24
+ * removable and must stay so: `unlink` removes the link, the target survives, and git stages the
25
+ * link as an ordinary blob. It is reaching *through* a symlinked **directory** that leaves the
26
+ * project — that is the file git itself refuses to stage ("beyond a symbolic link") and the one
27
+ * whose deletion lands outside the root.
28
+ *
29
+ * **That reasoning is a deletion's, and this function has write callers too.** For `unlinkSync` it
30
+ * holds entire: resolving the leaf would turn a working removal into a refusal protecting nothing.
31
+ * `task-105` added two **write** consumers — `resolveConfinedMemoryPath` (`./memory-path.ts`) and
32
+ * `requireConfinedTarget` inside `commitMemoryTransition` (`../core/memory-transition.ts`) — and
33
+ * `writeFileSync` **follows** a symlinked leaf where `unlinkSync` acts on it, which put bytes
34
+ * outside the root on both of them (`bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
35
+ *
36
+ * That is answered **beside** this function rather than inside it, by `task-106`: the write paths
37
+ * ask {@link targetIsSymlink} as well, and refuse a symlinked target outright. This function is
38
+ * unchanged, so the delete path is unchanged with it — a wider boundary here would have red exactly
39
+ * the `bug-044` case the paragraph above exists to keep working. So the asymmetry is read per verb:
40
+ * resolve the parent everywhere, refuse a symlinked leaf where the syscall would follow it.
41
+ * `dl-086` carries the argument about which state a guard over a filesystem effect may read.
42
+ *
43
+ * @param root - The project root; need not be real-resolved by the caller.
44
+ * @param relativePath - A root-relative path (an absolute one is honoured as given, and then judged
45
+ * by the same boundary).
46
+ */
47
+ export declare function resolveRealPathInRoot(root: string, relativePath: string): RealPathResolution;
48
+ /**
49
+ * True when `target`'s **own name** is a symbolic link — the question a write must ask and a delete
50
+ * must not (task-106, `bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
51
+ *
52
+ * `writeFileSync` follows a symlinked leaf: the bytes land at the link's destination, not at the
53
+ * path the operation named, and {@link resolveRealPathInRoot} does not see it because it resolves
54
+ * the parent and keeps the basename. `unlinkSync` is the opposite — it acts on the link — so the
55
+ * same input is a defect for one verb and correct behaviour for the other (`bug-044`'s benign case).
56
+ * Hence a separate predicate, asked only where the syscall follows links, rather than a wider
57
+ * boundary that would decide both.
58
+ *
59
+ * **`lstat`, not `exists`.** `existsSync` follows links and is therefore **false** for a dangling
60
+ * one, which is how `requireAbsentTarget` (`../core/write-guard.ts`) read an occupied path as free
61
+ * and let `memory add` write through it and commit (`bug-120` D1). `lstatSync` inspects the link
62
+ * itself and answers `true`. Its `ENOENT` — nothing at the path at all, the ordinary case for a
63
+ * document about to be created — is the one failure absorbed here, along with any other `lstat`
64
+ * error: a path this cannot inspect is not a link it can report, and every caller has already
65
+ * decided confinement separately.
66
+ *
67
+ * **Not an adversarial defence, and the window is real.** Node's `fs` exposes no `O_NOFOLLOW` on a
68
+ * path-based API, so nothing closes the gap between this check and the write that follows it; only
69
+ * file-descriptor primitives (`openat`) would. `dl-086` names the same limit for the delete path.
70
+ * What this converts is a routine, self-inflicted loss — a store aliased into shared space, a
71
+ * document linked into a folder — into a refusal.
72
+ *
73
+ * @param target - An absolute path. Only its own name is inspected; links on the way to it are the
74
+ * confinement boundary's business ({@link resolveRealPathInRoot}), not this predicate's.
75
+ */
76
+ export declare function targetIsSymlink(target: string): boolean;
77
+ /**
78
+ * The refusal a write path returns for a symlinked target — one sentence, one place, so the
79
+ * `StorageError` `memory add` raises and the `CoreResult` the transition verbs return cannot drift
80
+ * apart on what the rule is (task-106).
81
+ *
82
+ * It names the path and says the target is a link, and it deliberately does **not** name where the
83
+ * link points: {@link targetIsSymlink} refuses without resolving, and a message quoting a
84
+ * destination would imply the guard had judged it. No child-process text goes anywhere near it
85
+ * (`bug-071`/`bug-093`).
86
+ *
87
+ * @param action - The verb for the first clause, e.g. `'write'` → `cannot write '<path>'`, the
88
+ * message shape `requireConfinedTarget` and `requireCustomAsset` already use.
89
+ * @param path - The path as the operation named it, root-relative or rendered — never re-spelled,
90
+ * so the operator reads back what they typed against.
91
+ */
92
+ export declare function symlinkTargetRefusal(action: string, path: string): string;
93
+ //# sourceMappingURL=confinement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confinement.d.ts","sourceRoot":"","sources":["../../src/storage/confinement.ts"],"names":[],"mappings":"AA8BA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGzE;AA2BD,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iGAAiG;IACjG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,kBAAkB,CAK5F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAMvD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMzE"}
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.escapesRoot = escapesRoot;
4
+ exports.resolveRealPathInRoot = resolveRealPathInRoot;
5
+ exports.targetIsSymlink = targetIsSymlink;
6
+ exports.symlinkTargetRefusal = symlinkTargetRefusal;
7
+ /**
8
+ * Storage confinement — **the one place that decides whether a path is inside the project root**
9
+ * (REQ-SEC-06, `task-017-storage-confinement`; extended to the filesystem by `task-102` for
10
+ * `bug-044-symlinked-directives-custom-escapes-confinement`).
11
+ *
12
+ * `resolveConfinedMemoryPath` (`./memory-path.ts`) shipped that boundary first, but only over a
13
+ * rendered Memory-path *pattern* and only **textually**: `resolve` + `relative`. Textual resolution
14
+ * answers a different question from the filesystem's. `.wingfoil/directives/custom/legacy-rule.md`
15
+ * has no traversal in it and every segment is inside the project — and when `custom` is a symlink to
16
+ * a directory elsewhere, the file it names is not in the project at all. `src/core/builtin-asset.ts`
17
+ * predicted exactly this when it refused to normalise `.`/`..` textually: "a symlinked `custom/`
18
+ * aliasing `built-in/` defeats any string-level normalisation".
19
+ *
20
+ * So the predicate lives here and `resolveConfinedMemoryPath` calls it, rather than a second
21
+ * boundary check being written next to it — two places deciding confinement is how a guarantee
22
+ * becomes a suggestion. {@link escapesRoot} is the decision; {@link resolveRealPathInRoot} is the
23
+ * filesystem-resolving entry point a caller uses when the path names a real file on disk.
24
+ *
25
+ * {@link targetIsSymlink} (task-106, `bug-120`) is a **different question** kept in the same module
26
+ * because it is asked in the same breath: not *where does this path lead* but *would the syscall
27
+ * land on this path at all*. It is asked only by callers whose syscall follows links — a write —
28
+ * and never by a delete, which acts on the link and must go on doing so.
29
+ *
30
+ * All three are deterministic (REQ-SYS-07): no wall clock, no randomness, no unordered iteration —
31
+ * only `path` arithmetic, `realpathSync` and `lstatSync`, whose answers are properties of the
32
+ * filesystem.
33
+ */
34
+ const node_fs_1 = require("node:fs");
35
+ const node_path_1 = require("node:path");
36
+ /**
37
+ * True when `target` is **not strictly inside** `resolvedRoot` — the root itself (a mutating op's
38
+ * target is a file *within* the project, never the project), a climb above it, or an unrelated
39
+ * absolute path. Both arguments must already be absolute; nothing here touches the filesystem.
40
+ */
41
+ function escapesRoot(resolvedRoot, target) {
42
+ const rel = (0, node_path_1.relative)(resolvedRoot, target);
43
+ return rel.length === 0 || rel === '..' || rel.startsWith(`..${node_path_1.sep}`) || (0, node_path_1.isAbsolute)(rel);
44
+ }
45
+ /**
46
+ * The real location of `directory`, following symlinks as far as the filesystem actually goes and
47
+ * keeping the not-yet-existing tail verbatim.
48
+ *
49
+ * `realpathSync` throws `ENOENT` for a path that does not exist, which would make a confinement
50
+ * check fail differently depending on whether the caller's target had been created yet — a guard
51
+ * must answer the same question either way. Walking up to the nearest existing ancestor is the
52
+ * resolution the filesystem itself would give once the missing directories were created.
53
+ */
54
+ function realpathOfDirectory(directory) {
55
+ const pending = [];
56
+ let current = (0, node_path_1.resolve)(directory);
57
+ for (;;) {
58
+ try {
59
+ return (0, node_path_1.join)((0, node_fs_1.realpathSync)(current), ...pending);
60
+ }
61
+ catch {
62
+ const parent = (0, node_path_1.dirname)(current);
63
+ // `dirname` is its own fixed point at the filesystem root: nothing above it left to resolve.
64
+ if (parent === current)
65
+ return (0, node_path_1.join)(current, ...pending);
66
+ pending.unshift((0, node_path_1.basename)(current));
67
+ current = parent;
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * Resolve `relativePath` under `root` **on the filesystem** and report whether the result is still
73
+ * inside the project. The root is real-resolved too, so a project that itself lives under a
74
+ * symlinked path (a `/tmp` that is a link to `/private/tmp`, a home directory reached through one)
75
+ * does not read as its own escape.
76
+ *
77
+ * **The target's parent is resolved; the target's own name is not** — and that asymmetry is the
78
+ * contract, not an oversight. A directive that is a **symlinked file** inside a real directory is
79
+ * removable and must stay so: `unlink` removes the link, the target survives, and git stages the
80
+ * link as an ordinary blob. It is reaching *through* a symlinked **directory** that leaves the
81
+ * project — that is the file git itself refuses to stage ("beyond a symbolic link") and the one
82
+ * whose deletion lands outside the root.
83
+ *
84
+ * **That reasoning is a deletion's, and this function has write callers too.** For `unlinkSync` it
85
+ * holds entire: resolving the leaf would turn a working removal into a refusal protecting nothing.
86
+ * `task-105` added two **write** consumers — `resolveConfinedMemoryPath` (`./memory-path.ts`) and
87
+ * `requireConfinedTarget` inside `commitMemoryTransition` (`../core/memory-transition.ts`) — and
88
+ * `writeFileSync` **follows** a symlinked leaf where `unlinkSync` acts on it, which put bytes
89
+ * outside the root on both of them (`bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
90
+ *
91
+ * That is answered **beside** this function rather than inside it, by `task-106`: the write paths
92
+ * ask {@link targetIsSymlink} as well, and refuse a symlinked target outright. This function is
93
+ * unchanged, so the delete path is unchanged with it — a wider boundary here would have red exactly
94
+ * the `bug-044` case the paragraph above exists to keep working. So the asymmetry is read per verb:
95
+ * resolve the parent everywhere, refuse a symlinked leaf where the syscall would follow it.
96
+ * `dl-086` carries the argument about which state a guard over a filesystem effect may read.
97
+ *
98
+ * @param root - The project root; need not be real-resolved by the caller.
99
+ * @param relativePath - A root-relative path (an absolute one is honoured as given, and then judged
100
+ * by the same boundary).
101
+ */
102
+ function resolveRealPathInRoot(root, relativePath) {
103
+ const realRoot = realpathOfDirectory(root);
104
+ const absolute = (0, node_path_1.resolve)(realRoot, relativePath);
105
+ const real = (0, node_path_1.join)(realpathOfDirectory((0, node_path_1.dirname)(absolute)), (0, node_path_1.basename)(absolute));
106
+ return { absolute, real, within: !escapesRoot(realRoot, real) };
107
+ }
108
+ /**
109
+ * True when `target`'s **own name** is a symbolic link — the question a write must ask and a delete
110
+ * must not (task-106, `bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
111
+ *
112
+ * `writeFileSync` follows a symlinked leaf: the bytes land at the link's destination, not at the
113
+ * path the operation named, and {@link resolveRealPathInRoot} does not see it because it resolves
114
+ * the parent and keeps the basename. `unlinkSync` is the opposite — it acts on the link — so the
115
+ * same input is a defect for one verb and correct behaviour for the other (`bug-044`'s benign case).
116
+ * Hence a separate predicate, asked only where the syscall follows links, rather than a wider
117
+ * boundary that would decide both.
118
+ *
119
+ * **`lstat`, not `exists`.** `existsSync` follows links and is therefore **false** for a dangling
120
+ * one, which is how `requireAbsentTarget` (`../core/write-guard.ts`) read an occupied path as free
121
+ * and let `memory add` write through it and commit (`bug-120` D1). `lstatSync` inspects the link
122
+ * itself and answers `true`. Its `ENOENT` — nothing at the path at all, the ordinary case for a
123
+ * document about to be created — is the one failure absorbed here, along with any other `lstat`
124
+ * error: a path this cannot inspect is not a link it can report, and every caller has already
125
+ * decided confinement separately.
126
+ *
127
+ * **Not an adversarial defence, and the window is real.** Node's `fs` exposes no `O_NOFOLLOW` on a
128
+ * path-based API, so nothing closes the gap between this check and the write that follows it; only
129
+ * file-descriptor primitives (`openat`) would. `dl-086` names the same limit for the delete path.
130
+ * What this converts is a routine, self-inflicted loss — a store aliased into shared space, a
131
+ * document linked into a folder — into a refusal.
132
+ *
133
+ * @param target - An absolute path. Only its own name is inspected; links on the way to it are the
134
+ * confinement boundary's business ({@link resolveRealPathInRoot}), not this predicate's.
135
+ */
136
+ function targetIsSymlink(target) {
137
+ try {
138
+ return (0, node_fs_1.lstatSync)(target).isSymbolicLink();
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ /**
145
+ * The refusal a write path returns for a symlinked target — one sentence, one place, so the
146
+ * `StorageError` `memory add` raises and the `CoreResult` the transition verbs return cannot drift
147
+ * apart on what the rule is (task-106).
148
+ *
149
+ * It names the path and says the target is a link, and it deliberately does **not** name where the
150
+ * link points: {@link targetIsSymlink} refuses without resolving, and a message quoting a
151
+ * destination would imply the guard had judged it. No child-process text goes anywhere near it
152
+ * (`bug-071`/`bug-093`).
153
+ *
154
+ * @param action - The verb for the first clause, e.g. `'write'` → `cannot write '<path>'`, the
155
+ * message shape `requireConfinedTarget` and `requireCustomAsset` already use.
156
+ * @param path - The path as the operation named it, root-relative or rendered — never re-spelled,
157
+ * so the operator reads back what they typed against.
158
+ */
159
+ function symlinkTargetRefusal(action, path) {
160
+ return (`cannot ${action} '${path}': the target is itself a symbolic link. A write follows a link where a delete acts ` +
161
+ 'on it, so the bytes would land wherever the link points — not at the path named here, and outside the project ' +
162
+ 'entirely when the link leaves it. Replace the link with a regular file, then retry.');
163
+ }
164
+ //# sourceMappingURL=confinement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confinement.js","sourceRoot":"","sources":["../../src/storage/confinement.ts"],"names":[],"mappings":";;AAmCA,kCAGC;AAoED,sDAKC;AA8BD,0CAMC;AAiBD,oDAMC;AA1KD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qCAAkD;AAClD,yCAAwF;AAExF;;;;GAIG;AACH,SAAgB,WAAW,CAAC,YAAoB,EAAE,MAAc;IAC9D,MAAM,GAAG,GAAG,IAAA,oBAAQ,EAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC3C,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,eAAG,EAAE,CAAC,IAAI,IAAA,sBAAU,EAAC,GAAG,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAAC,SAAiB;IAC5C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAG,IAAA,mBAAO,EAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,OAAO,IAAA,gBAAI,EAAC,IAAA,sBAAY,EAAC,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,MAAM,GAAG,IAAA,mBAAO,EAAC,OAAO,CAAC,CAAC;YAChC,6FAA6F;YAC7F,IAAI,MAAM,KAAK,OAAO;gBAAE,OAAO,IAAA,gBAAI,EAAC,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC;YACzD,OAAO,CAAC,OAAO,CAAC,IAAA,oBAAQ,EAAC,OAAO,CAAC,CAAC,CAAC;YACnC,OAAO,GAAG,MAAM,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,SAAgB,qBAAqB,CAAC,IAAY,EAAE,YAAoB;IACtE,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAA,mBAAO,EAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAA,gBAAI,EAAC,mBAAmB,CAAC,IAAA,mBAAO,EAAC,QAAQ,CAAC,CAAC,EAAE,IAAA,oBAAQ,EAAC,QAAQ,CAAC,CAAC,CAAC;IAC9E,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;AAClE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,SAAgB,eAAe,CAAC,MAAc;IAC5C,IAAI,CAAC;QACH,OAAO,IAAA,mBAAS,EAAC,MAAM,CAAC,CAAC,cAAc,EAAE,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,oBAAoB,CAAC,MAAc,EAAE,IAAY;IAC/D,OAAO,CACL,UAAU,MAAM,KAAK,IAAI,sFAAsF;QAC/G,gHAAgH;QAChH,qFAAqF,CACtF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ /** Read a document's full contents as UTF-8 text. */
2
+ export declare function readDocument(absolutePath: string): string;
3
+ /** Whether a document exists at `absolutePath`. */
4
+ export declare function documentExists(absolutePath: string): boolean;
5
+ /**
6
+ * Write `content` to `absolutePath`, creating any missing parent directories. No other file or
7
+ * directory is created or touched — the write is exactly this one path, nothing else.
8
+ */
9
+ export declare function writeDocument(absolutePath: string, content: string): void;
10
+ /**
11
+ * Delete the document at `absolutePath` — {@link writeDocument}'s exact counterpart, added for
12
+ * `directive remove` (P3.3, task-052-directive-remove), the first operation in the system that
13
+ * removes a file rather than writing one.
14
+ *
15
+ * Exactly this one path is unlinked: no parent directory is pruned (an emptied
16
+ * `directives/custom/` must keep existing — `spec-011-storage-layout`'s `built-in/` vs `custom/`
17
+ * split is what REQ-SEC-07 keys removability on, so the directory is structure, not content), and
18
+ * nothing is ever removed recursively. Staging and committing the deletion is the caller's job,
19
+ * exactly as it is for a write (`commitPaths`, ./commit) — this module only owns the bytes at an
20
+ * already-resolved absolute path.
21
+ *
22
+ * @throws Node's `ENOENT` when nothing is there. Callers resolve the target before calling (P3.3
23
+ * resolves a directive NAME to a file that `loadDirectives` just read), so a missing path is a
24
+ * programmer error rather than a domain outcome to swallow.
25
+ */
26
+ export declare function removeDocument(absolutePath: string): void;
27
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/storage/document.ts"],"names":[],"mappings":"AAWA,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAGzE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEzD"}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readDocument = readDocument;
4
+ exports.documentExists = documentExists;
5
+ exports.writeDocument = writeDocument;
6
+ exports.removeDocument = removeDocument;
7
+ /**
8
+ * Plain document read/write/remove (task-003-git-backed-sot, REQ-SYS-01). Every Memory/DNA/Directives/
9
+ * Workflow mutation is a regular file write with no side-channel state — no `.wingfoil/state/`
10
+ * index (REQ-SYS-03), no cache file that isn't itself just a copy of what's already on disk.
11
+ * `git add`/`git commit` of the result is the caller's job (dev-loop's commit conventions, CLAUDE.md
12
+ * §5.1) — this module only owns the bytes at an already-resolved absolute path (see
13
+ * `./memory-path`).
14
+ */
15
+ const fs_1 = require("fs");
16
+ const path_1 = require("path");
17
+ /** Read a document's full contents as UTF-8 text. */
18
+ function readDocument(absolutePath) {
19
+ return (0, fs_1.readFileSync)(absolutePath, 'utf-8');
20
+ }
21
+ /** Whether a document exists at `absolutePath`. */
22
+ function documentExists(absolutePath) {
23
+ return (0, fs_1.existsSync)(absolutePath);
24
+ }
25
+ /**
26
+ * Write `content` to `absolutePath`, creating any missing parent directories. No other file or
27
+ * directory is created or touched — the write is exactly this one path, nothing else.
28
+ */
29
+ function writeDocument(absolutePath, content) {
30
+ (0, fs_1.mkdirSync)((0, path_1.dirname)(absolutePath), { recursive: true });
31
+ (0, fs_1.writeFileSync)(absolutePath, content, 'utf-8');
32
+ }
33
+ /**
34
+ * Delete the document at `absolutePath` — {@link writeDocument}'s exact counterpart, added for
35
+ * `directive remove` (P3.3, task-052-directive-remove), the first operation in the system that
36
+ * removes a file rather than writing one.
37
+ *
38
+ * Exactly this one path is unlinked: no parent directory is pruned (an emptied
39
+ * `directives/custom/` must keep existing — `spec-011-storage-layout`'s `built-in/` vs `custom/`
40
+ * split is what REQ-SEC-07 keys removability on, so the directory is structure, not content), and
41
+ * nothing is ever removed recursively. Staging and committing the deletion is the caller's job,
42
+ * exactly as it is for a write (`commitPaths`, ./commit) — this module only owns the bytes at an
43
+ * already-resolved absolute path.
44
+ *
45
+ * @throws Node's `ENOENT` when nothing is there. Callers resolve the target before calling (P3.3
46
+ * resolves a directive NAME to a file that `loadDirectives` just read), so a missing path is a
47
+ * programmer error rather than a domain outcome to swallow.
48
+ */
49
+ function removeDocument(absolutePath) {
50
+ (0, fs_1.unlinkSync)(absolutePath);
51
+ }
52
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/storage/document.ts"],"names":[],"mappings":";;AAYA,oCAEC;AAGD,wCAEC;AAMD,sCAGC;AAkBD,wCAEC;AAhDD;;;;;;;GAOG;AACH,2BAAoF;AACpF,+BAA+B;AAE/B,qDAAqD;AACrD,SAAgB,YAAY,CAAC,YAAoB;IAC/C,OAAO,IAAA,iBAAY,EAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,mDAAmD;AACnD,SAAgB,cAAc,CAAC,YAAoB;IACjD,OAAO,IAAA,eAAU,EAAC,YAAY,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAC,YAAoB,EAAE,OAAe;IACjE,IAAA,cAAS,EAAC,IAAA,cAAO,EAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,IAAA,kBAAa,EAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,cAAc,CAAC,YAAoB;IACjD,IAAA,eAAU,EAAC,YAAY,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Shared storage-layer error type (task-003-git-backed-sot, REQ-SYS-01, spec-011-storage-layout).
3
+ *
4
+ * Mirrors the shape of {@link ../validation/errors.ValidationError} — a single typed `Error`
5
+ * subclass carrying a stable `E_*` code — but for storage/root/init-detection failures, which are
6
+ * process-environment problems (wrong cwd, not a git repo, an unresolved path placeholder), not
7
+ * data-validation problems. Kept separate from `src/validation` rather than reusing
8
+ * `ValidationError` because these failures never carry a `ValidationIssue[]`/`exitCode` pair: they
9
+ * are single, precise conditions raised before any document parsing happens.
10
+ */
11
+ export declare class StorageError extends Error {
12
+ readonly code: string;
13
+ constructor(code: string, message: string);
14
+ }
15
+ /** Raised by {@link resolveProjectRoot} when no ancestor of `cwd` contains a `.git` entry. */
16
+ export declare const E_NO_GIT_ROOT = "E_NO_GIT_ROOT";
17
+ /** Raised by {@link resolveProjectRoot} when the git root exists but isn't `cwd` itself. */
18
+ export declare const E_NOT_AT_GIT_ROOT = "E_NOT_AT_GIT_ROOT";
19
+ /** Raised by {@link renderMemoryPath} when a `{placeholder}` in a `path` pattern has no value. */
20
+ export declare const E_MISSING_PATH_VALUE = "E_MISSING_PATH_VALUE";
21
+ /**
22
+ * Raised by {@link resolveConfinedMemoryPath} when a resolved Memory-entry path would land outside the
23
+ * project root — e.g. a crafted `id`/value containing `../` traversal (REQ-SEC-06 storage confinement).
24
+ */
25
+ export declare const E_PATH_ESCAPES_ROOT = "E_PATH_ESCAPES_ROOT";
26
+ /**
27
+ * Raised by {@link resolveConfinedMemoryPath} when the target's **own name** is a symbolic link
28
+ * (task-106, `bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
29
+ *
30
+ * Distinct from {@link E_PATH_ESCAPES_ROOT} because it reports a different finding: that code says
31
+ * where the path leads, this one says a write would not land on the named path at all. The link is
32
+ * refused without being resolved, so where it points is deliberately not part of the answer — see
33
+ * `targetIsSymlink` (`./confinement.ts`).
34
+ */
35
+ export declare const E_TARGET_IS_SYMLINK = "E_TARGET_IS_SYMLINK";
36
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/storage/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAO1C;AAED,8FAA8F;AAC9F,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAE7C,4FAA4F;AAC5F,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD,kGAAkG;AAClG,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.E_TARGET_IS_SYMLINK = exports.E_PATH_ESCAPES_ROOT = exports.E_MISSING_PATH_VALUE = exports.E_NOT_AT_GIT_ROOT = exports.E_NO_GIT_ROOT = exports.StorageError = void 0;
4
+ /**
5
+ * Shared storage-layer error type (task-003-git-backed-sot, REQ-SYS-01, spec-011-storage-layout).
6
+ *
7
+ * Mirrors the shape of {@link ../validation/errors.ValidationError} — a single typed `Error`
8
+ * subclass carrying a stable `E_*` code — but for storage/root/init-detection failures, which are
9
+ * process-environment problems (wrong cwd, not a git repo, an unresolved path placeholder), not
10
+ * data-validation problems. Kept separate from `src/validation` rather than reusing
11
+ * `ValidationError` because these failures never carry a `ValidationIssue[]`/`exitCode` pair: they
12
+ * are single, precise conditions raised before any document parsing happens.
13
+ */
14
+ class StorageError extends Error {
15
+ code;
16
+ constructor(code, message) {
17
+ super(`${code}: ${message}`);
18
+ this.name = 'StorageError';
19
+ this.code = code;
20
+ // Restore the prototype chain so `instanceof StorageError` holds after transpilation.
21
+ Object.setPrototypeOf(this, StorageError.prototype);
22
+ }
23
+ }
24
+ exports.StorageError = StorageError;
25
+ /** Raised by {@link resolveProjectRoot} when no ancestor of `cwd` contains a `.git` entry. */
26
+ exports.E_NO_GIT_ROOT = 'E_NO_GIT_ROOT';
27
+ /** Raised by {@link resolveProjectRoot} when the git root exists but isn't `cwd` itself. */
28
+ exports.E_NOT_AT_GIT_ROOT = 'E_NOT_AT_GIT_ROOT';
29
+ /** Raised by {@link renderMemoryPath} when a `{placeholder}` in a `path` pattern has no value. */
30
+ exports.E_MISSING_PATH_VALUE = 'E_MISSING_PATH_VALUE';
31
+ /**
32
+ * Raised by {@link resolveConfinedMemoryPath} when a resolved Memory-entry path would land outside the
33
+ * project root — e.g. a crafted `id`/value containing `../` traversal (REQ-SEC-06 storage confinement).
34
+ */
35
+ exports.E_PATH_ESCAPES_ROOT = 'E_PATH_ESCAPES_ROOT';
36
+ /**
37
+ * Raised by {@link resolveConfinedMemoryPath} when the target's **own name** is a symbolic link
38
+ * (task-106, `bug-120-a-symlinked-document-leaf-is-followed-by-the-write`).
39
+ *
40
+ * Distinct from {@link E_PATH_ESCAPES_ROOT} because it reports a different finding: that code says
41
+ * where the path leads, this one says a write would not land on the named path at all. The link is
42
+ * refused without being resolved, so where it points is deliberately not part of the answer — see
43
+ * `targetIsSymlink` (`./confinement.ts`).
44
+ */
45
+ exports.E_TARGET_IS_SYMLINK = 'E_TARGET_IS_SYMLINK';
46
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/storage/errors.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACH,MAAa,YAAa,SAAQ,KAAK;IAC5B,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,sFAAsF;QACtF,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;CACF;AAVD,oCAUC;AAED,8FAA8F;AACjF,QAAA,aAAa,GAAG,eAAe,CAAC;AAE7C,4FAA4F;AAC/E,QAAA,iBAAiB,GAAG,mBAAmB,CAAC;AAErD,kGAAkG;AACrF,QAAA,oBAAoB,GAAG,sBAAsB,CAAC;AAE3D;;;GAGG;AACU,QAAA,mBAAmB,GAAG,qBAAqB,CAAC;AAEzD;;;;;;;;GAQG;AACU,QAAA,mBAAmB,GAAG,qBAAqB,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Frontmatter extraction (task-003-git-backed-sot). Memory element state is derived from each
3
+ * document's frontmatter, not any external index (CLAUDE.md §5, REQ-STATE-01/-02): this is the
4
+ * shared primitive for reading it, as raw YAML text. Parsing that text against a type's schema is
5
+ * the validation module's job (spec-009-validation-strategy, task-002), not this module's — keeping
6
+ * the two concerns separate lets the storage layer stay YAML-schema-agnostic.
7
+ */
8
+ /**
9
+ * Extract the raw YAML frontmatter block (the text between the first pair of `---` delimiters)
10
+ * from a Memory document's content. Returns `null` if the document has no frontmatter block at all
11
+ * (no leading `---` line).
12
+ */
13
+ export declare function extractFrontmatter(content: string): string | null;
14
+ /** The result of {@link splitFrontmatter}: the raw frontmatter text (or `null`) plus the body. */
15
+ export interface FrontmatterSplit {
16
+ readonly frontmatter: string | null;
17
+ readonly body: string;
18
+ }
19
+ /**
20
+ * Split a Memory document into its raw frontmatter text (see {@link extractFrontmatter}) and the
21
+ * body that follows it (task-008-dna-memory-query-latency: the query primitives need the body text
22
+ * for keyword matching, in the same read pass that already extracts frontmatter — no second parse).
23
+ * When there is no frontmatter block at all, `frontmatter` is `null` and `body` is the whole
24
+ * document, unchanged.
25
+ */
26
+ export declare function splitFrontmatter(content: string): FrontmatterSplit;
27
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/storage/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjE;AAED,kGAAkG;AAClG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAIlE"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * Frontmatter extraction (task-003-git-backed-sot). Memory element state is derived from each
4
+ * document's frontmatter, not any external index (CLAUDE.md §5, REQ-STATE-01/-02): this is the
5
+ * shared primitive for reading it, as raw YAML text. Parsing that text against a type's schema is
6
+ * the validation module's job (spec-009-validation-strategy, task-002), not this module's — keeping
7
+ * the two concerns separate lets the storage layer stay YAML-schema-agnostic.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.extractFrontmatter = extractFrontmatter;
11
+ exports.splitFrontmatter = splitFrontmatter;
12
+ // Opening delimiter must be the very first line of the document (no leading blank line); content is
13
+ // everything up to the next standalone "---" line; trailing newline after the closing delimiter is
14
+ // optional so a frontmatter-only document (no body) still matches.
15
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
16
+ /**
17
+ * Extract the raw YAML frontmatter block (the text between the first pair of `---` delimiters)
18
+ * from a Memory document's content. Returns `null` if the document has no frontmatter block at all
19
+ * (no leading `---` line).
20
+ */
21
+ function extractFrontmatter(content) {
22
+ return splitFrontmatter(content).frontmatter;
23
+ }
24
+ /**
25
+ * Split a Memory document into its raw frontmatter text (see {@link extractFrontmatter}) and the
26
+ * body that follows it (task-008-dna-memory-query-latency: the query primitives need the body text
27
+ * for keyword matching, in the same read pass that already extracts frontmatter — no second parse).
28
+ * When there is no frontmatter block at all, `frontmatter` is `null` and `body` is the whole
29
+ * document, unchanged.
30
+ */
31
+ function splitFrontmatter(content) {
32
+ const match = FRONTMATTER_RE.exec(content);
33
+ if (!match)
34
+ return { frontmatter: null, body: content };
35
+ return { frontmatter: match[1] ?? '', body: content.slice(match[0].length) };
36
+ }
37
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/storage/frontmatter.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAYH,gDAEC;AAeD,4CAIC;AA/BD,oGAAoG;AACpG,mGAAmG;AACnG,mEAAmE;AACnE,MAAM,cAAc,GAAG,mCAAmC,CAAC;AAE3D;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,OAAe;IAChD,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC;AAC/C,CAAC;AAQD;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACxD,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Walk up from `cwd` looking for a `.git` entry (spec-011: "no upward search for `.wingfoil/`
3
+ * itself, only for `.git`"). Returns the first ancestor directory (inclusive of `cwd`) that
4
+ * contains `.git`, or `null` if the filesystem root is reached without finding one. `cwd` is
5
+ * resolved first so relative paths and trailing separators don't affect the result (determinism).
6
+ */
7
+ export declare function findGitRoot(cwd: string): string | null;
8
+ /**
9
+ * Resolve the WingFoil project root: the git root, and only the git root. Per spec-011, `wingfoil`
10
+ * must be invoked at the exact repository root — no upward search for `.wingfoil/` and no running
11
+ * from a subdirectory of the root.
12
+ *
13
+ * @throws {@link StorageError} `E_NO_GIT_ROOT` if `cwd` is not inside a git repository at all, or
14
+ * `E_NOT_AT_GIT_ROOT` if a git root was found but it isn't `cwd` itself.
15
+ */
16
+ export declare function resolveProjectRoot(cwd: string): string;
17
+ //# sourceMappingURL=git-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-root.d.ts","sourceRoot":"","sources":["../../src/storage/git-root.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQtD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUtD"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findGitRoot = findGitRoot;
4
+ exports.resolveProjectRoot = resolveProjectRoot;
5
+ /**
6
+ * Git-root detection and project-root resolution (task-003-git-backed-sot, REQ-SYS-01,
7
+ * spec-011-storage-layout "Git-root detection algorithm"). Every other storage primitive assumes it
8
+ * is operating against a resolved project root — this is where that root comes from.
9
+ */
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const errors_1 = require("./errors");
13
+ /**
14
+ * Walk up from `cwd` looking for a `.git` entry (spec-011: "no upward search for `.wingfoil/`
15
+ * itself, only for `.git`"). Returns the first ancestor directory (inclusive of `cwd`) that
16
+ * contains `.git`, or `null` if the filesystem root is reached without finding one. `cwd` is
17
+ * resolved first so relative paths and trailing separators don't affect the result (determinism).
18
+ */
19
+ function findGitRoot(cwd) {
20
+ let dir = (0, path_1.resolve)(cwd);
21
+ for (;;) {
22
+ if ((0, fs_1.existsSync)((0, path_1.join)(dir, '.git')))
23
+ return dir;
24
+ const parent = (0, path_1.dirname)(dir);
25
+ if (parent === dir)
26
+ return null; // reached the filesystem root without finding .git
27
+ dir = parent;
28
+ }
29
+ }
30
+ /**
31
+ * Resolve the WingFoil project root: the git root, and only the git root. Per spec-011, `wingfoil`
32
+ * must be invoked at the exact repository root — no upward search for `.wingfoil/` and no running
33
+ * from a subdirectory of the root.
34
+ *
35
+ * @throws {@link StorageError} `E_NO_GIT_ROOT` if `cwd` is not inside a git repository at all, or
36
+ * `E_NOT_AT_GIT_ROOT` if a git root was found but it isn't `cwd` itself.
37
+ */
38
+ function resolveProjectRoot(cwd) {
39
+ const resolvedCwd = (0, path_1.resolve)(cwd);
40
+ const root = findGitRoot(resolvedCwd);
41
+ if (root === null) {
42
+ throw new errors_1.StorageError(errors_1.E_NO_GIT_ROOT, 'not inside a git repository');
43
+ }
44
+ if (root !== resolvedCwd) {
45
+ throw new errors_1.StorageError(errors_1.E_NOT_AT_GIT_ROOT, 'run wingfoil from the project root');
46
+ }
47
+ return root;
48
+ }
49
+ //# sourceMappingURL=git-root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-root.js","sourceRoot":"","sources":["../../src/storage/git-root.ts"],"names":[],"mappings":";;AAgBA,kCAQC;AAUD,gDAUC;AA5CD;;;;GAIG;AACH,2BAAgC;AAChC,+BAA8C;AAE9C,qCAA0E;AAE1E;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,GAAW;IACrC,IAAI,GAAG,GAAG,IAAA,cAAO,EAAC,GAAG,CAAC,CAAC;IACvB,SAAS,CAAC;QACR,IAAI,IAAA,eAAU,EAAC,IAAA,WAAI,EAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAA,cAAO,EAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,mDAAmD;QACpF,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAAC,GAAW;IAC5C,MAAM,WAAW,GAAG,IAAA,cAAO,EAAC,GAAG,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACtC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAY,CAAC,sBAAa,EAAE,6BAA6B,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,MAAM,IAAI,qBAAY,CAAC,0BAAiB,EAAE,oCAAoC,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `storage` module — git-backed file storage (YAML + Markdown); single source of truth
3
+ * (REQ-SYS-01, task-003-git-backed-sot). Single entry point for: resolving the WingFoil project
4
+ * root, detecting initialization state, rendering a Memory type's `path` pattern
5
+ * (spec-001-memory-yaml-schema) against concrete values, reading/writing document bytes, and
6
+ * computing the whole-project state snapshot the REQ-SYS-01 fit criterion is stated in terms of.
7
+ */
8
+ export declare const MODULE_NAME: "storage";
9
+ export { StorageError, E_NO_GIT_ROOT, E_NOT_AT_GIT_ROOT, E_MISSING_PATH_VALUE } from './errors';
10
+ export { findGitRoot, resolveProjectRoot } from './git-root';
11
+ export { detectInitState } from './init-state';
12
+ export type { InitState } from './init-state';
13
+ export { renderMemoryPath, resolveMemoryPath } from './memory-path';
14
+ export { escapesRoot, resolveRealPathInRoot } from './confinement';
15
+ export type { RealPathResolution } from './confinement';
16
+ export { extractFrontmatter, splitFrontmatter } from './frontmatter';
17
+ export type { FrontmatterSplit } from './frontmatter';
18
+ export { readDocument, documentExists, removeDocument, writeDocument } from './document';
19
+ export { changedPathsBetween, commitParent, commitPaths, EMPTY_TREE_SHA, listPathsAtRev, pathPorcelainStatus, readPathAtRev } from './commit';
20
+ export type { CommitOptions } from './commit';
21
+ export { initStorage, scaffoldFiles, WINGFOIL_DIR, INIT_COMMIT_MESSAGE } from './layout';
22
+ export type { ScaffoldFile } from './layout';
23
+ export { TEMPLATES, TEMPLATE_NAMES, DEFAULT_TEMPLATE, resolveTemplate, templateScaffold, initProjectCommitMessage, builtinTemplateSources, BUILTIN_DIRECTIVES_DIR, BUILTIN_WORKFLOWS_DIR, } from './templates';
24
+ export type { TemplateDefinition, BuiltinTemplateKind, BuiltinTemplateSource } from './templates';
25
+ export { BUILTIN_DIRECTIVE_TEMPLATES, BUILTIN_DIRECTIVE_IDS, builtinDirectiveMd } from './builtin-directives';
26
+ export type { BuiltinDirectiveTemplate } from './builtin-directives';
27
+ export { computeStateSnapshot, serializeSnapshot } from './snapshot';
28
+ export type { SnapshotEntry } from './snapshot';
29
+ //# sourceMappingURL=index.d.ts.map