@hraness/wordcell 0.20.0-bootstrap.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 (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +749 -0
  3. package/dist/agent-context.js +35 -0
  4. package/dist/agent-guide-audit.js +18 -0
  5. package/dist/attachments.js +19 -0
  6. package/dist/authoring.js +33 -0
  7. package/dist/benchmark.js +19 -0
  8. package/dist/capture.js +49 -0
  9. package/dist/cli.js +57 -0
  10. package/dist/clip/acquire.js +57 -0
  11. package/dist/clip/args.js +21 -0
  12. package/dist/clip/bounded-byte-buffer.js +9 -0
  13. package/dist/clip/browser-profiles.js +17 -0
  14. package/dist/clip/bundle-reader.js +11 -0
  15. package/dist/clip/cli.js +27 -0
  16. package/dist/clip/cookies.js +21 -0
  17. package/dist/clip/defuddle-worker.js +84 -0
  18. package/dist/clip/doctor.js +32 -0
  19. package/dist/clip/jobs.js +784 -0
  20. package/dist/clip/network-proxy.js +9 -0
  21. package/dist/clip/network.js +28 -0
  22. package/dist/clip/persist.js +30 -0
  23. package/dist/clip/refresh.js +11 -0
  24. package/dist/clip/terminal.js +11 -0
  25. package/dist/evaluation-builder.js +12238 -0
  26. package/dist/evaluation-kb.js +26 -0
  27. package/dist/evaluation.js +35 -0
  28. package/dist/git.js +39 -0
  29. package/dist/graph.js +41 -0
  30. package/dist/index-054mb7d3.js +1974 -0
  31. package/dist/index-06c9ctr6.js +738 -0
  32. package/dist/index-180zbkkt.js +2480 -0
  33. package/dist/index-1gwbassd.js +743 -0
  34. package/dist/index-1xxnjn0d.js +171 -0
  35. package/dist/index-23z4zxgg.js +119 -0
  36. package/dist/index-2gv8y733.js +432 -0
  37. package/dist/index-3ehtt59c.js +199 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-48pz4jpc.js +392 -0
  40. package/dist/index-4j3tt0c3.js +234 -0
  41. package/dist/index-5n05se68.js +307 -0
  42. package/dist/index-5vwpzb5a.js +764 -0
  43. package/dist/index-6jcz0m1c.js +712 -0
  44. package/dist/index-6sw24nvv.js +1189 -0
  45. package/dist/index-8a08v6x8.js +56 -0
  46. package/dist/index-adx6khj5.js +488 -0
  47. package/dist/index-b88v3vtm.js +805 -0
  48. package/dist/index-bcknqxrq.js +1943 -0
  49. package/dist/index-bs0yf4pp.js +711 -0
  50. package/dist/index-by0pbant.js +827 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dfag79p7.js +370 -0
  53. package/dist/index-e4fagfjz.js +44 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-ekpwvbra.js +1070 -0
  56. package/dist/index-f984hw45.js +927 -0
  57. package/dist/index-gh719d91.js +63 -0
  58. package/dist/index-h170byqw.js +371 -0
  59. package/dist/index-hgve9rh2.js +2046 -0
  60. package/dist/index-hya40gb2.js +474 -0
  61. package/dist/index-j4zgmzjr.js +152 -0
  62. package/dist/index-m7et4rz4.js +966 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n05s3wsb.js +2728 -0
  65. package/dist/index-nd6nynv2.js +1162 -0
  66. package/dist/index-npg9z1a4.js +431 -0
  67. package/dist/index-pj501bh1.js +140 -0
  68. package/dist/index-rdd5f7jj.js +70 -0
  69. package/dist/index-sy0zety7.js +3630 -0
  70. package/dist/index-tkpxp48t.js +1166 -0
  71. package/dist/index-w2zc0vwa.js +571 -0
  72. package/dist/index-why54q5p.js +462 -0
  73. package/dist/index-x3fthpsc.js +585 -0
  74. package/dist/index-x6m57nfc.js +1041 -0
  75. package/dist/index.js +784 -0
  76. package/dist/kb-alias.js +55 -0
  77. package/dist/navigation.js +13 -0
  78. package/dist/pdf.js +85 -0
  79. package/dist/percolate.js +44 -0
  80. package/dist/portfolio.js +77 -0
  81. package/dist/query.js +29 -0
  82. package/dist/repository-memory.js +60 -0
  83. package/dist/sdk.js +36 -0
  84. package/dist/search-rules.js +34 -0
  85. package/dist/search.js +22 -0
  86. package/dist/semantic.js +39 -0
  87. package/dist/source-inbox.js +17 -0
  88. package/dist/untrusted-content.js +32 -0
  89. package/dist/url-intelligence.js +74 -0
  90. package/dist/workflow.js +23 -0
  91. package/dist/workflows/decision-context.js +20 -0
  92. package/dist/workflows/explain-change.js +8 -0
  93. package/dist/workflows/index.js +28 -0
  94. package/dist/workflows/plan-radar.js +10 -0
  95. package/package.json +421 -0
  96. package/skills/wordcell/AGENTS.md +22 -0
  97. package/skills/wordcell/SKILL.md +91 -0
  98. package/skills/wordcell/agents/openai.yaml +4 -0
  99. package/skills/wordcell/references/companion-skills.md +96 -0
  100. package/skills/wordcell/references/customize.md +123 -0
  101. package/skills/wordcell/references/pdf-review.md +51 -0
  102. package/skills/wordcell/references/percolate.md +176 -0
  103. package/skills/wordcell/references/plan-structure.md +105 -0
  104. package/skills/wordcell/references/plan.md +115 -0
  105. package/skills/wordcell/references/query.md +252 -0
  106. package/skills/wordcell/references/refresh.md +133 -0
  107. package/skills/wordcell/references/save-pdf.md +125 -0
  108. package/skills/wordcell/references/save-url.md +178 -0
  109. package/skills/wordcell/references/url-authentication.md +75 -0
  110. package/skills/wordcell/references/url-platforms.md +25 -0
  111. package/skills/wordcell/templates/companion-skill.template.md +57 -0
  112. package/src/agent-context.ts +1241 -0
  113. package/src/agent-guide-audit.ts +750 -0
  114. package/src/attachments.ts +723 -0
  115. package/src/authoring-model.ts +798 -0
  116. package/src/authoring-platform.ts +549 -0
  117. package/src/authoring-program.ts +278 -0
  118. package/src/authoring-runtime.ts +54 -0
  119. package/src/authoring.ts +75 -0
  120. package/src/benchmark.ts +362 -0
  121. package/src/capture.ts +68 -0
  122. package/src/cli-program.ts +3662 -0
  123. package/src/cli.ts +6 -0
  124. package/src/clip/acquire.ts +1273 -0
  125. package/src/clip/archive-today.ts +402 -0
  126. package/src/clip/args.ts +423 -0
  127. package/src/clip/assets.ts +262 -0
  128. package/src/clip/bounded-byte-buffer.ts +75 -0
  129. package/src/clip/browser-profiles.ts +372 -0
  130. package/src/clip/bundle-reader.ts +608 -0
  131. package/src/clip/capture.ts +1054 -0
  132. package/src/clip/cli.ts +181 -0
  133. package/src/clip/cookies.ts +512 -0
  134. package/src/clip/doctor.ts +1110 -0
  135. package/src/clip/extract.ts +868 -0
  136. package/src/clip/jobs.ts +959 -0
  137. package/src/clip/lib.ts +861 -0
  138. package/src/clip/media.ts +1474 -0
  139. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  140. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  141. package/src/clip/metadata-search-tool/runner.ts +685 -0
  142. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  143. package/src/clip/metadata-search.ts +687 -0
  144. package/src/clip/network-proxy.ts +627 -0
  145. package/src/clip/network.ts +950 -0
  146. package/src/clip/package-root.ts +54 -0
  147. package/src/clip/persist.ts +1472 -0
  148. package/src/clip/platforms.ts +1824 -0
  149. package/src/clip/refresh.ts +197 -0
  150. package/src/clip/structured.ts +432 -0
  151. package/src/clip/terminal.ts +197 -0
  152. package/src/clip/url-intelligence.ts +581 -0
  153. package/src/clip/url-metadata-backfill.ts +616 -0
  154. package/src/clip/url-metadata-cli.ts +251 -0
  155. package/src/clip/url-metadata.ts +1397 -0
  156. package/src/evaluation-analysis-v2.ts +1440 -0
  157. package/src/evaluation-builder.ts +2539 -0
  158. package/src/evaluation-corpus-authoring.ts +2052 -0
  159. package/src/evaluation-evidence.ts +1147 -0
  160. package/src/evaluation-execution-v2.ts +426 -0
  161. package/src/evaluation-implementation.ts +131 -0
  162. package/src/evaluation-kb-closure.ts +1430 -0
  163. package/src/evaluation-kb-runner-v2.ts +735 -0
  164. package/src/evaluation-kb-v2.ts +2567 -0
  165. package/src/evaluation-kb.ts +1018 -0
  166. package/src/evaluation-measurement-v2.ts +1462 -0
  167. package/src/evaluation-packing-v2.ts +452 -0
  168. package/src/evaluation-redaction.ts +28 -0
  169. package/src/evaluation-v2.ts +3857 -0
  170. package/src/evaluation.ts +1220 -0
  171. package/src/git.ts +1276 -0
  172. package/src/graph.ts +1494 -0
  173. package/src/index.ts +21 -0
  174. package/src/init.ts +123 -0
  175. package/src/kb-alias.ts +14 -0
  176. package/src/navigation.ts +277 -0
  177. package/src/note-lock.ts +529 -0
  178. package/src/oh-adoption.ts +444 -0
  179. package/src/pdf/args.ts +202 -0
  180. package/src/pdf/capture.ts +298 -0
  181. package/src/pdf/cli.ts +159 -0
  182. package/src/pdf/extract.ts +303 -0
  183. package/src/pdf/layout.ts +618 -0
  184. package/src/pdf/markdown.ts +154 -0
  185. package/src/pdf/model.ts +234 -0
  186. package/src/pdf/ocr.ts +190 -0
  187. package/src/pdf/persist.ts +314 -0
  188. package/src/pdf/source.ts +135 -0
  189. package/src/pdf/tools.ts +146 -0
  190. package/src/pdf.ts +92 -0
  191. package/src/percolate.ts +1948 -0
  192. package/src/portfolio-audit.ts +574 -0
  193. package/src/portfolio-identity.ts +162 -0
  194. package/src/portfolio-registry.ts +473 -0
  195. package/src/portfolio.ts +765 -0
  196. package/src/query.ts +646 -0
  197. package/src/repository-memory.ts +1150 -0
  198. package/src/sdk.ts +1452 -0
  199. package/src/search-rules.ts +712 -0
  200. package/src/search.ts +710 -0
  201. package/src/semantic-runtime.ts +1139 -0
  202. package/src/semantic.ts +2480 -0
  203. package/src/source-inbox.ts +232 -0
  204. package/src/untrusted-content.ts +401 -0
  205. package/src/url-intelligence.ts +104 -0
  206. package/src/vault.ts +636 -0
  207. package/src/workflow-model.ts +444 -0
  208. package/src/workflow-platform.ts +74 -0
  209. package/src/workflow-program.ts +183 -0
  210. package/src/workflow-runtime.ts +51 -0
  211. package/src/workflow.ts +23 -0
  212. package/src/workflows/decision-context.ts +81 -0
  213. package/src/workflows/explain-change.ts +73 -0
  214. package/src/workflows/index.ts +3 -0
  215. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,278 @@
1
+ import { Effect, Exit } from "effect";
2
+
3
+ import {
4
+ MAX_NOTE_BYTES, NoteRevisionConflictError, addRelationToParts, assertCompatibleCreate,
5
+ assertExpected, canonicalNoteId, canonicalRelationTarget, checkedExpectedRevision,
6
+ frontmatter, isErrno, normalizeRelationPredicate, noteResult, pathFor, relationsFromParts,
7
+ removeRelationFromParts, renderCreatedNote, renderFrontmatter, revisionFor,
8
+ sameQuarantinedSnapshot, sameSnapshot, withRecoveryPath,
9
+ } from "./authoring-model.js";
10
+ import type {
11
+ AuthoringDependencies, AuthoringInstallContext, AuthoringOptions, CreateNoteInput,
12
+ DirectoryIdentity, NoteAuthoringResult, NoteRevision, NoteSnapshot, RecoveryLocation, Vault,
13
+ } from "./authoring-model.js";
14
+ import type { NoteLock } from "./note-lock.js";
15
+ import { parseDocumentId } from "./portfolio-identity.js";
16
+ import { joinDirectorySyncs } from "./authoring-platform.js";
17
+ import type { AuthoringPlatform, TemporaryNoteHandle } from "./authoring-platform.js";
18
+ import {
19
+ AuthoringFailure, authoringCauseValue, authoringNative, authoringResource, authoringSync,
20
+ } from "./authoring-runtime.js";
21
+
22
+ const fail = (reason: unknown) => Effect.fail(new AuthoringFailure(reason));
23
+
24
+ type InstallState = {
25
+ readonly handle: TemporaryNoteHandle;
26
+ readonly path: string;
27
+ readonly relativePath: string;
28
+ readonly directory: string;
29
+ readonly expectedDirectory: DirectoryIdentity;
30
+ readonly temporaryPath: string;
31
+ readonly mode: number;
32
+ readonly bytes: Uint8Array;
33
+ closed: boolean;
34
+ identity: { readonly device: bigint; readonly inode: bigint } | null;
35
+ recovery: RecoveryLocation | null;
36
+ sourceQuarantined: boolean;
37
+ destinationInstalled: boolean;
38
+ };
39
+
40
+ function installBody(
41
+ platform: AuthoringPlatform, vault: Vault, id: string, expected: NoteSnapshot | null,
42
+ lock: NoteLock, dependencies: AuthoringDependencies, state: InstallState,
43
+ ) {
44
+ return Effect.gen(function*() {
45
+ const { handle, path, relativePath, directory, expectedDirectory, temporaryPath, mode, bytes } = state;
46
+ const created = yield* authoringNative(() => handle.stat({ bigint: true }));
47
+ if (!created.isFile() || created.nlink !== 1n) {
48
+ return yield* fail(new Error("the temporary note target is not a private regular file"));
49
+ }
50
+ const identity = { device: created.dev, inode: created.ino };
51
+ state.identity = identity;
52
+ yield* authoringNative(() => handle.chmod(mode));
53
+ yield* authoringNative(() => handle.writeFile(bytes));
54
+ yield* authoringNative(() => handle.sync());
55
+ const complete = yield* authoringNative(() => handle.stat({ bigint: true }));
56
+ if (!complete.isFile() || complete.nlink !== 1n || complete.dev !== identity.device || complete.ino !== identity.inode) {
57
+ return yield* fail(new Error("the temporary note target changed before installation"));
58
+ }
59
+ yield* authoringNative(() => handle.close());
60
+ state.closed = true;
61
+
62
+ yield* authoringNative(() => Promise.resolve(dependencies.beforeInstall?.({
63
+ operation: expected === null ? "create" : "replace", path, temporaryPath,
64
+ })));
65
+ yield* authoringNative(() => lock.assertOwned());
66
+ const current = yield* authoringNative(() => platform.readOptionalSnapshot(vault, id));
67
+ if ((expected === null && current !== null)
68
+ || (expected !== null && (current === null || !sameSnapshot(current, expected)))) {
69
+ return yield* fail(new NoteRevisionConflictError(relativePath, expected?.revision ?? null, current?.revision ?? null));
70
+ }
71
+ yield* authoringNative(() => platform.assertSafeParent(vault, path));
72
+ const temporary = yield* authoringNative(() => platform.statTemporary(temporaryPath));
73
+ if (!temporary.isFile() || temporary.isSymbolicLink() || temporary.nlink !== 1n
74
+ || temporary.dev !== identity.device || temporary.ino !== identity.inode) {
75
+ return yield* fail(new Error("the temporary note target changed before installation"));
76
+ }
77
+
78
+ if (expected === null) {
79
+ const context: AuthoringInstallContext = { operation: "create", path, temporaryPath };
80
+ yield* authoringNative(() => Promise.resolve(dependencies.beforeCommit?.(context)));
81
+ yield* authoringNative(() => lock.assertOwned());
82
+ yield* authoringNative(() => platform.assertSameDirectory(vault, path, expectedDirectory));
83
+ const installed = yield* authoringNative(() => platform.installTemporaryWithoutClobber(temporaryPath, path));
84
+ if (!installed) {
85
+ const actual = yield* authoringNative(() => platform.currentRevisionOrNull(vault, id));
86
+ return yield* fail(new NoteRevisionConflictError(relativePath, null, actual));
87
+ }
88
+ state.destinationInstalled = true;
89
+ yield* authoringNative(() => platform.fsyncDirectory(directory));
90
+ return yield* authoringSync(() => revisionFor(bytes));
91
+ }
92
+
93
+ const recovery = yield* authoringNative(() => platform.createRecoveryLocation(vault, path, dependencies));
94
+ state.recovery = recovery;
95
+ const context: Required<AuthoringInstallContext> = {
96
+ operation: "replace", path, temporaryPath, recoveryPath: recovery.path,
97
+ };
98
+ yield* authoringNative(() => Promise.resolve(dependencies.beforeCommit?.(context)));
99
+ yield* authoringNative(() => lock.assertOwned());
100
+ yield* authoringNative(() => platform.assertSameDirectory(vault, path, expectedDirectory));
101
+ yield* authoringNative(() => platform.assertRecoveryLocation(recovery));
102
+ const moved = yield* Effect.exit(authoringNative(() => platform.rename(path, recovery.path)));
103
+ if (Exit.isFailure(moved)) {
104
+ const reason = authoringCauseValue(moved.cause);
105
+ return yield* fail(isErrno(reason, "ENOENT")
106
+ ? new NoteRevisionConflictError(relativePath, expected.revision, null)
107
+ : reason);
108
+ }
109
+ state.sourceQuarantined = true;
110
+ // A first failure cannot release the lock while the sibling still owns a
111
+ // native directory handle. Promise.all's original first failure is retained.
112
+ yield* authoringNative(() => joinDirectorySyncs([directory, recovery.directory], (path) => platform.fsyncDirectory(path)));
113
+
114
+ const quarantined = yield* authoringNative(() => platform.readSnapshotAtPath(vault, recovery.path, recovery.relativePath));
115
+ if (!sameQuarantinedSnapshot(quarantined, expected)) {
116
+ return yield* fail(new NoteRevisionConflictError(relativePath, expected.revision, quarantined.revision));
117
+ }
118
+ yield* authoringNative(() => Promise.resolve(dependencies.afterSourceQuarantined?.(context)));
119
+ yield* authoringNative(() => lock.assertOwned());
120
+ yield* authoringNative(() => platform.assertSameDirectory(vault, path, expectedDirectory));
121
+ yield* authoringNative(() => platform.assertRecoveryLocation(recovery));
122
+ const stillQuarantined = yield* authoringNative(() => platform.readSnapshotAtPath(vault, recovery.path, recovery.relativePath));
123
+ if (!sameQuarantinedSnapshot(stillQuarantined, expected)) {
124
+ return yield* fail(new NoteRevisionConflictError(relativePath, expected.revision, stillQuarantined.revision));
125
+ }
126
+ const installed = yield* authoringNative(() => platform.installTemporaryWithoutClobber(temporaryPath, path));
127
+ if (!installed) {
128
+ const actual = yield* authoringNative(() => platform.currentRevisionOrNull(vault, id));
129
+ return yield* fail(new NoteRevisionConflictError(relativePath, expected.revision, actual));
130
+ }
131
+ state.destinationInstalled = true;
132
+ yield* authoringNative(() => platform.fsyncDirectory(directory));
133
+ yield* authoringNative(() => platform.unlink(recovery.path));
134
+ state.sourceQuarantined = false;
135
+ yield* authoringNative(() => platform.fsyncDirectory(recovery.directory));
136
+ yield* authoringNative(() => platform.removeRecoveryDirectory(recovery, directory));
137
+ state.recovery = null;
138
+ yield* authoringNative(() => platform.fsyncDirectory(directory));
139
+ return yield* authoringSync(() => revisionFor(bytes));
140
+ });
141
+ }
142
+
143
+ function recoverInstall(platform: AuthoringPlatform, vault: Vault, state: InstallState, error: unknown) {
144
+ return Effect.gen(function*() {
145
+ if (state.recovery !== null && state.sourceQuarantined && !state.destinationInstalled) {
146
+ const recovery = state.recovery;
147
+ const restored = yield* Effect.exit(authoringNative(() => platform.restoreQuarantinedSource(
148
+ vault, recovery, state.path, state.expectedDirectory,
149
+ )));
150
+ if (Exit.isFailure(restored)) {
151
+ return yield* fail(withRecoveryPath(new AggregateError(
152
+ [error, authoringCauseValue(restored.cause)],
153
+ "authoring failed and the prior source could not be restored",
154
+ ), state.relativePath, recovery.relativePath));
155
+ }
156
+ if (restored.value) {
157
+ state.sourceQuarantined = false;
158
+ state.recovery = null;
159
+ }
160
+ }
161
+ if (state.recovery !== null && state.sourceQuarantined) {
162
+ return yield* fail(withRecoveryPath(error, state.relativePath, state.recovery.relativePath));
163
+ }
164
+ if (state.recovery !== null) {
165
+ const recovery = state.recovery;
166
+ const cleaned = yield* Effect.exit(authoringNative(() => platform.removeRecoveryDirectory(recovery, state.directory)));
167
+ if (Exit.isSuccess(cleaned)) state.recovery = null;
168
+ else {
169
+ const cleanupError = authoringCauseValue(cleaned.cause);
170
+ if (!isErrno(cleanupError, "ENOENT")) {
171
+ return yield* fail(new AggregateError(
172
+ [error, cleanupError], "authoring failed and its empty recovery directory could not be removed",
173
+ ));
174
+ }
175
+ }
176
+ }
177
+ return yield* fail(error);
178
+ });
179
+ }
180
+
181
+ function installNote(
182
+ platform: AuthoringPlatform, vault: Vault, id: string, content: string,
183
+ expected: NoteSnapshot | null, lock: NoteLock, dependencies: AuthoringDependencies,
184
+ ): Effect.Effect<NoteRevision, AuthoringFailure> {
185
+ return Effect.gen(function*() {
186
+ const { path, relativePath } = yield* authoringSync(() => pathFor(vault, id));
187
+ yield* authoringNative(() => platform.assertSafeParent(vault, path));
188
+ const bytes = yield* authoringSync(() => new TextEncoder().encode(content));
189
+ if (bytes.byteLength > MAX_NOTE_BYTES) return yield* fail(new Error("the rendered note is too large for bounded authoring"));
190
+ const directory = yield* authoringSync(() => platform.directoryPath(path));
191
+ const expectedDirectory = yield* authoringNative(() => platform.directoryIdentity(vault, path));
192
+ const temporaryPath = yield* authoringSync(() => platform.temporaryPath(path, dependencies));
193
+ const mode = expected?.mode ?? 0o644;
194
+ return yield* authoringResource(
195
+ authoringNative(() => platform.openTemporary(temporaryPath, mode)).pipe(Effect.map((handle): InstallState => ({
196
+ handle, path, relativePath, directory, expectedDirectory, temporaryPath, mode, bytes,
197
+ closed: false, identity: null, recovery: null, sourceQuarantined: false, destinationInstalled: false,
198
+ }))),
199
+ (state) => Effect.gen(function*() {
200
+ const body = yield* Effect.exit(installBody(platform, vault, id, expected, lock, dependencies, state));
201
+ if (Exit.isSuccess(body)) return body.value;
202
+ return yield* recoverInstall(platform, vault, state, authoringCauseValue(body.cause));
203
+ }),
204
+ (state) => Effect.gen(function*() {
205
+ // Legacy fallback close is best effort. Temporary cleanup remains a
206
+ // required finally and may supersede the selected operation failure.
207
+ if (!state.closed) yield* Effect.exit(authoringNative(() => state.handle.close())).pipe(Effect.asVoid);
208
+ yield* authoringNative(() => platform.cleanupTemporary(temporaryPath, state.identity));
209
+ }),
210
+ );
211
+ });
212
+ }
213
+
214
+ export function createNoteProgram(
215
+ platform: AuthoringPlatform, root: string, input: CreateNoteInput, options: AuthoringOptions,
216
+ ): Effect.Effect<NoteAuthoringResult, AuthoringFailure> {
217
+ return Effect.gen(function*() {
218
+ const vault = yield* authoringNative(() => platform.resolveVault(root));
219
+ const id = yield* authoringSync(() => canonicalNoteId(input.id));
220
+ const requestedDocumentId = yield* authoringSync(() => input.documentId === undefined ? undefined : parseDocumentId(input.documentId));
221
+ const expected = yield* authoringSync(() => checkedExpectedRevision(options));
222
+ const dependencies = yield* authoringSync(() => platform.dependenciesFor(options.dependencies));
223
+ return yield* authoringResource(
224
+ authoringNative(() => platform.acquireNoteLock(vault.root, id, options.lock)),
225
+ (lock) => Effect.gen(function*() {
226
+ yield* authoringNative(() => platform.recoverInterruptedAuthoring(vault, id, lock));
227
+ const existing = yield* authoringNative(() => platform.readOptionalSnapshot(vault, id));
228
+ if (existing !== null) {
229
+ yield* authoringSync(() => assertExpected(existing, expected));
230
+ const compatible = yield* authoringSync(() => assertCompatibleCreate(existing, input, requestedDocumentId));
231
+ return yield* authoringSync(() => noteResult(existing, compatible.relations, false, compatible.documentId));
232
+ }
233
+ if (expected !== undefined) return yield* fail(new NoteRevisionConflictError(`${id}.md`, expected, null));
234
+ const documentId = yield* authoringSync(() => requestedDocumentId ?? parseDocumentId(dependencies.documentId()));
235
+ const content = yield* authoringSync(() => renderCreatedNote(input, documentId));
236
+ const revision = yield* installNote(platform, vault, id, content, null, lock, dependencies);
237
+ return { changed: true, path: `${id}.md`, revision, relations: [], documentId };
238
+ }),
239
+ (lock) => authoringNative(() => lock.release()),
240
+ );
241
+ });
242
+ }
243
+
244
+ export function editNoteRelationProgram(
245
+ platform: AuthoringPlatform, operation: "add" | "remove", root: string,
246
+ sourceIdInput: string, predicateInput: string, targetIdInput: string, options: AuthoringOptions,
247
+ ): Effect.Effect<NoteAuthoringResult, AuthoringFailure> {
248
+ return Effect.gen(function*() {
249
+ const vault = yield* authoringNative(() => platform.resolveVault(root));
250
+ const sourceId = yield* authoringSync(() => canonicalNoteId(sourceIdInput));
251
+ const targetId = yield* authoringSync(() => canonicalRelationTarget(targetIdInput));
252
+ const predicate = yield* authoringSync(() => normalizeRelationPredicate(predicateInput));
253
+ const expected = yield* authoringSync(() => checkedExpectedRevision(options));
254
+ const dependencies = yield* authoringSync(() => platform.dependenciesFor(options.dependencies));
255
+ return yield* authoringResource(
256
+ authoringNative(() => platform.acquireNoteLock(vault.root, sourceId, options.lock)),
257
+ (lock) => Effect.gen(function*() {
258
+ yield* authoringNative(() => platform.recoverInterruptedAuthoring(vault, sourceId, lock));
259
+ const source = yield* authoringNative(() => platform.readSnapshot(vault, sourceId));
260
+ yield* authoringSync(() => assertExpected(source, expected));
261
+ if (operation === "add" && !targetId.startsWith("kb://") && targetId !== sourceId) {
262
+ yield* authoringNative(() => platform.readSnapshot(vault, targetId)).pipe(Effect.asVoid);
263
+ }
264
+ const parts = yield* authoringSync(() => frontmatter(source.content, source.relativePath));
265
+ if (operation === "add") yield* authoringSync(() => relationsFromParts(parts, source.relativePath)).pipe(Effect.asVoid);
266
+ const changed = yield* authoringSync(() => operation === "add"
267
+ ? addRelationToParts(parts, source.relativePath, predicate, targetId)
268
+ : removeRelationFromParts(parts, source.relativePath, predicate, targetId, sourceId));
269
+ if (!changed) return yield* authoringSync(() => noteResult(source, relationsFromParts(parts, source.relativePath), false));
270
+ const content = yield* authoringSync(() => renderFrontmatter(parts));
271
+ const relations = yield* authoringSync(() => relationsFromParts(frontmatter(content, source.relativePath), source.relativePath));
272
+ const revision = yield* installNote(platform, vault, sourceId, content, source, lock, dependencies);
273
+ return { changed: true, path: source.relativePath, revision, relations };
274
+ }),
275
+ (lock) => authoringNative(() => lock.release()),
276
+ );
277
+ });
278
+ }
@@ -0,0 +1,54 @@
1
+ import { Cause, Effect, Exit, Option } from "effect";
2
+
3
+ /** A raw native or policy failure, including falsey values; never mutate its object. */
4
+ export class AuthoringFailure {
5
+ readonly reason: unknown;
6
+
7
+ constructor(reason: unknown) {
8
+ this.reason = reason;
9
+ }
10
+ }
11
+
12
+ export function authoringNative<A>(operation: () => PromiseLike<A>): Effect.Effect<A, AuthoringFailure> {
13
+ // Once a native operation is admitted, retain its actual settlement. Public
14
+ // authoring has no cancellation API and cannot abandon a late descriptor.
15
+ return Effect.uninterruptible(Effect.tryPromise({
16
+ try: () => Promise.resolve(operation()),
17
+ catch: (reason: unknown) => new AuthoringFailure(reason),
18
+ }));
19
+ }
20
+
21
+ export function authoringSync<A>(operation: () => A): Effect.Effect<A, AuthoringFailure> {
22
+ return Effect.try({ try: operation, catch: (reason: unknown) => new AuthoringFailure(reason) });
23
+ }
24
+
25
+ export function authoringCauseValue(cause: Cause.Cause<AuthoringFailure>): unknown {
26
+ const failure = Cause.failureOption(cause);
27
+ if (Option.isSome(failure)) return failure.value.reason;
28
+ const defect = Cause.dieOption(cause);
29
+ if (Option.isSome(defect)) return defect.value;
30
+ return cause;
31
+ }
32
+
33
+ /** The native finally law: this resource's cleanup supersedes its body Exit. */
34
+ export function authoringResource<A, B, R, R2, R3>(
35
+ acquire: Effect.Effect<A, AuthoringFailure, R>,
36
+ use: (resource: A) => Effect.Effect<B, AuthoringFailure, R2>,
37
+ release: (resource: A) => Effect.Effect<unknown, AuthoringFailure, R3>,
38
+ ): Effect.Effect<B, AuthoringFailure, R | R2 | R3> {
39
+ return Effect.uninterruptibleMask((restore) => Effect.gen(function*() {
40
+ const resource = yield* acquire;
41
+ const body = yield* Effect.exit(restore(use(resource)));
42
+ const cleanup = yield* Effect.exit(release(resource));
43
+ if (Exit.isFailure(cleanup)) return yield* Effect.failCause(cleanup.cause);
44
+ if (Exit.isFailure(body)) return yield* Effect.failCause(body.cause);
45
+ return body.value;
46
+ }));
47
+ }
48
+
49
+ /** One finite invocation for one real public Promise operation. */
50
+ export async function runAuthoring<A>(program: Effect.Effect<A, AuthoringFailure>): Promise<A> {
51
+ const exit = await Effect.runPromiseExit(program);
52
+ if (Exit.isSuccess(exit)) return exit.value;
53
+ throw authoringCauseValue(exit.cause);
54
+ }
@@ -0,0 +1,75 @@
1
+ import type {
2
+ AuthoringOptions, CreateConceptNoteInput, CreateNoteInput, NoteAuthoringResult,
3
+ NoteRelation, NoteRevision,
4
+ } from "./authoring-model.js";
5
+ import { canonicalNoteId, frontmatter, relationsFromParts } from "./authoring-model.js";
6
+ import {
7
+ assertNoInterruptedRecovery, nativeAuthoringPlatform, readSnapshot, resolveVault,
8
+ } from "./authoring-platform.js";
9
+ import { createNoteProgram, editNoteRelationProgram } from "./authoring-program.js";
10
+ import { runAuthoring } from "./authoring-runtime.js";
11
+
12
+ export {
13
+ InvalidCanonicalNoteIdError, NoteAlreadyExistsError, NoteRecoveryRequiredError,
14
+ NoteRevisionConflictError, canonicalNoteId, canonicalRelationTarget, normalizeRelationPredicate,
15
+ } from "./authoring-model.js";
16
+ export type {
17
+ AuthoringDependencies, AuthoringInstallContext, AuthoringOptions,
18
+ CreateConceptNoteInput, CreateNoteInput, NoteAuthoringResult, NoteRelation, NoteRevision,
19
+ } from "./authoring-model.js";
20
+
21
+ /**
22
+ * Read the content revision used by optimistic authoring operations.
23
+ *
24
+ * Revisions intentionally describe UTF-8 bytes, while installation also
25
+ * checks inode and timestamps to detect same-content replacement races.
26
+ */
27
+ export async function noteRevision(root: string, id: string): Promise<NoteRevision> {
28
+ const vault = await resolveVault(root);
29
+ const canonicalId = canonicalNoteId(id);
30
+ await assertNoInterruptedRecovery(vault, canonicalId);
31
+ return (await readSnapshot(vault, canonicalId)).revision;
32
+ }
33
+
34
+ /** List exact outbound relation declarations without taking an authoring lock. */
35
+ export async function listNoteRelations(root: string, sourceId: string): Promise<readonly NoteRelation[]> {
36
+ const vault = await resolveVault(root);
37
+ const canonicalId = canonicalNoteId(sourceId);
38
+ await assertNoInterruptedRecovery(vault, canonicalId);
39
+ const source = await readSnapshot(vault, canonicalId);
40
+ return relationsFromParts(frontmatter(source.content, source.relativePath), source.relativePath);
41
+ }
42
+
43
+ /**
44
+ * Create one ordinary Markdown note. Existing compatible notes are an
45
+ * idempotent success and are never rewritten.
46
+ *
47
+ * Parent directories must already exist as real in-vault directories. This
48
+ * keeps the operation's durable write set to exactly one note.
49
+ */
50
+ export async function createNote(
51
+ root: string, input: CreateNoteInput, options: AuthoringOptions = {},
52
+ ): Promise<NoteAuthoringResult> {
53
+ return runAuthoring(createNoteProgram(nativeAuthoringPlatform, root, input, options));
54
+ }
55
+
56
+ /** Create an ordinary `type: concept` Markdown note. */
57
+ export async function createConceptNote(
58
+ root: string, input: CreateConceptNoteInput, options: AuthoringOptions = {},
59
+ ): Promise<NoteAuthoringResult> {
60
+ return createNote(root, { ...input, type: "concept" }, options);
61
+ }
62
+
63
+ /** Add one exact outbound typed relation, idempotently. */
64
+ export async function addNoteRelation(
65
+ root: string, sourceId: string, predicate: string, targetId: string, options: AuthoringOptions = {},
66
+ ): Promise<NoteAuthoringResult> {
67
+ return runAuthoring(editNoteRelationProgram(nativeAuthoringPlatform, "add", root, sourceId, predicate, targetId, options));
68
+ }
69
+
70
+ /** Remove one exact outbound typed relation, idempotently. */
71
+ export async function removeNoteRelation(
72
+ root: string, sourceId: string, predicate: string, targetId: string, options: AuthoringOptions = {},
73
+ ): Promise<NoteAuthoringResult> {
74
+ return runAuthoring(editNoteRelationProgram(nativeAuthoringPlatform, "remove", root, sourceId, predicate, targetId, options));
75
+ }