@jamesaphoenix/tx-core 0.4.5 → 0.5.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 (204) hide show
  1. package/README.md +154 -299
  2. package/dist/db.d.ts +4 -9
  3. package/dist/db.d.ts.map +1 -1
  4. package/dist/db.js +6 -80
  5. package/dist/db.js.map +1 -1
  6. package/dist/errors.d.ts +67 -10
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +44 -10
  9. package/dist/errors.js.map +1 -1
  10. package/dist/index.d.ts +12 -7
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +23 -29
  13. package/dist/index.js.map +1 -1
  14. package/dist/layer.d.ts +32 -10
  15. package/dist/layer.d.ts.map +1 -1
  16. package/dist/layer.js +126 -47
  17. package/dist/layer.js.map +1 -1
  18. package/dist/mappers/anchor.d.ts +28 -0
  19. package/dist/mappers/anchor.d.ts.map +1 -0
  20. package/dist/mappers/anchor.js +105 -0
  21. package/dist/mappers/anchor.js.map +1 -0
  22. package/dist/mappers/candidate.d.ts +25 -0
  23. package/dist/mappers/candidate.d.ts.map +1 -0
  24. package/dist/mappers/candidate.js +83 -0
  25. package/dist/mappers/candidate.js.map +1 -0
  26. package/dist/mappers/doc.d.ts +2 -4
  27. package/dist/mappers/doc.d.ts.map +1 -1
  28. package/dist/mappers/doc.js +7 -4
  29. package/dist/mappers/doc.js.map +1 -1
  30. package/dist/mappers/edge.d.ts +19 -0
  31. package/dist/mappers/edge.d.ts.map +1 -0
  32. package/dist/mappers/edge.js +81 -0
  33. package/dist/mappers/edge.js.map +1 -0
  34. package/dist/mappers/index.d.ts +7 -3
  35. package/dist/mappers/index.d.ts.map +1 -1
  36. package/dist/mappers/index.js +14 -6
  37. package/dist/mappers/index.js.map +1 -1
  38. package/dist/mappers/message.d.ts +15 -0
  39. package/dist/mappers/message.d.ts.map +1 -0
  40. package/dist/mappers/message.js +58 -0
  41. package/dist/mappers/message.js.map +1 -0
  42. package/dist/repo/anchor-repo.d.ts +52 -0
  43. package/dist/repo/anchor-repo.d.ts.map +1 -0
  44. package/dist/repo/anchor-repo.js +245 -0
  45. package/dist/repo/anchor-repo.js.map +1 -0
  46. package/dist/repo/candidate-repo.d.ts +16 -0
  47. package/dist/repo/candidate-repo.d.ts.map +1 -0
  48. package/dist/repo/candidate-repo.js +164 -0
  49. package/dist/repo/candidate-repo.js.map +1 -0
  50. package/dist/repo/compaction-repo.d.ts +41 -0
  51. package/dist/repo/compaction-repo.d.ts.map +1 -0
  52. package/dist/repo/compaction-repo.js +84 -0
  53. package/dist/repo/compaction-repo.js.map +1 -0
  54. package/dist/repo/doc-repo.d.ts +68 -51
  55. package/dist/repo/doc-repo.d.ts.map +1 -1
  56. package/dist/repo/doc-repo.js +120 -54
  57. package/dist/repo/doc-repo.js.map +1 -1
  58. package/dist/repo/edge-repo.d.ts +26 -0
  59. package/dist/repo/edge-repo.d.ts.map +1 -0
  60. package/dist/repo/edge-repo.js +258 -0
  61. package/dist/repo/edge-repo.js.map +1 -0
  62. package/dist/repo/index.d.ts +8 -3
  63. package/dist/repo/index.d.ts.map +1 -1
  64. package/dist/repo/index.js +7 -2
  65. package/dist/repo/index.js.map +1 -1
  66. package/dist/repo/message-repo.d.ts +55 -0
  67. package/dist/repo/message-repo.d.ts.map +1 -0
  68. package/dist/repo/message-repo.js +132 -0
  69. package/dist/repo/message-repo.js.map +1 -0
  70. package/dist/services/agent-service.d.ts +18 -23
  71. package/dist/services/agent-service.d.ts.map +1 -1
  72. package/dist/services/agent-service.js +9 -0
  73. package/dist/services/agent-service.js.map +1 -1
  74. package/dist/services/anchor-service.d.ts +147 -0
  75. package/dist/services/anchor-service.d.ts.map +1 -0
  76. package/dist/services/anchor-service.js +540 -0
  77. package/dist/services/anchor-service.js.map +1 -0
  78. package/dist/services/anchor-verification.d.ts +102 -0
  79. package/dist/services/anchor-verification.d.ts.map +1 -0
  80. package/dist/services/anchor-verification.js +817 -0
  81. package/dist/services/anchor-verification.js.map +1 -0
  82. package/dist/services/ast-grep-service.d.ts +58 -0
  83. package/dist/services/ast-grep-service.d.ts.map +1 -0
  84. package/dist/services/ast-grep-service.js +427 -0
  85. package/dist/services/ast-grep-service.js.map +1 -0
  86. package/dist/services/attempt-service.d.ts.map +1 -1
  87. package/dist/services/attempt-service.js +4 -1
  88. package/dist/services/attempt-service.js.map +1 -1
  89. package/dist/services/auto-sync-service.d.ts.map +1 -1
  90. package/dist/services/auto-sync-service.js +7 -7
  91. package/dist/services/auto-sync-service.js.map +1 -1
  92. package/dist/services/candidate-extractor-service.d.ts +44 -0
  93. package/dist/services/candidate-extractor-service.d.ts.map +1 -0
  94. package/dist/services/candidate-extractor-service.js +175 -0
  95. package/dist/services/candidate-extractor-service.js.map +1 -0
  96. package/dist/services/claim-service.d.ts.map +1 -1
  97. package/dist/services/claim-service.js +0 -8
  98. package/dist/services/claim-service.js.map +1 -1
  99. package/dist/services/compaction-service.d.ts +105 -0
  100. package/dist/services/compaction-service.d.ts.map +1 -0
  101. package/dist/services/compaction-service.js +281 -0
  102. package/dist/services/compaction-service.js.map +1 -0
  103. package/dist/services/cycle-scan-service.d.ts +1 -5
  104. package/dist/services/cycle-scan-service.d.ts.map +1 -1
  105. package/dist/services/cycle-scan-service.js +49 -19
  106. package/dist/services/cycle-scan-service.js.map +1 -1
  107. package/dist/services/daemon-service.d.ts +2 -8
  108. package/dist/services/daemon-service.d.ts.map +1 -1
  109. package/dist/services/daemon-service.js +21 -35
  110. package/dist/services/daemon-service.js.map +1 -1
  111. package/dist/services/doc-service.d.ts +25 -32
  112. package/dist/services/doc-service.d.ts.map +1 -1
  113. package/dist/services/doc-service.js +206 -190
  114. package/dist/services/doc-service.js.map +1 -1
  115. package/dist/services/edge-service.d.ts +78 -0
  116. package/dist/services/edge-service.d.ts.map +1 -0
  117. package/dist/services/edge-service.js +158 -0
  118. package/dist/services/edge-service.js.map +1 -0
  119. package/dist/services/embedding-service.d.ts +3 -3
  120. package/dist/services/embedding-service.d.ts.map +1 -1
  121. package/dist/services/embedding-service.js +8 -14
  122. package/dist/services/embedding-service.js.map +1 -1
  123. package/dist/services/feedback-tracker.d.ts +64 -0
  124. package/dist/services/feedback-tracker.d.ts.map +1 -0
  125. package/dist/services/feedback-tracker.js +110 -0
  126. package/dist/services/feedback-tracker.js.map +1 -0
  127. package/dist/services/file-watcher-service.d.ts.map +1 -1
  128. package/dist/services/file-watcher-service.js +1 -3
  129. package/dist/services/file-watcher-service.js.map +1 -1
  130. package/dist/services/graph-expansion.d.ts +158 -0
  131. package/dist/services/graph-expansion.d.ts.map +1 -0
  132. package/dist/services/graph-expansion.js +487 -0
  133. package/dist/services/graph-expansion.js.map +1 -0
  134. package/dist/services/index.d.ts +19 -8
  135. package/dist/services/index.d.ts.map +1 -1
  136. package/dist/services/index.js +18 -7
  137. package/dist/services/index.js.map +1 -1
  138. package/dist/services/learning-service.d.ts.map +1 -1
  139. package/dist/services/learning-service.js +22 -14
  140. package/dist/services/learning-service.js.map +1 -1
  141. package/dist/services/llm-service.d.ts +60 -38
  142. package/dist/services/llm-service.d.ts.map +1 -1
  143. package/dist/services/llm-service.js +201 -113
  144. package/dist/services/llm-service.js.map +1 -1
  145. package/dist/services/message-service.d.ts +57 -0
  146. package/dist/services/message-service.d.ts.map +1 -0
  147. package/dist/services/message-service.js +78 -0
  148. package/dist/services/message-service.js.map +1 -0
  149. package/dist/services/orchestrator-service.d.ts.map +1 -1
  150. package/dist/services/orchestrator-service.js +19 -20
  151. package/dist/services/orchestrator-service.js.map +1 -1
  152. package/dist/services/promotion-service.d.ts +67 -0
  153. package/dist/services/promotion-service.d.ts.map +1 -0
  154. package/dist/services/promotion-service.js +151 -0
  155. package/dist/services/promotion-service.js.map +1 -0
  156. package/dist/services/query-expansion-service.d.ts +7 -22
  157. package/dist/services/query-expansion-service.d.ts.map +1 -1
  158. package/dist/services/query-expansion-service.js +41 -75
  159. package/dist/services/query-expansion-service.js.map +1 -1
  160. package/dist/services/reranker-service.d.ts +1 -1
  161. package/dist/services/reranker-service.d.ts.map +1 -1
  162. package/dist/services/reranker-service.js +8 -10
  163. package/dist/services/reranker-service.js.map +1 -1
  164. package/dist/services/retriever-service.d.ts +8 -5
  165. package/dist/services/retriever-service.d.ts.map +1 -1
  166. package/dist/services/retriever-service.js +150 -15
  167. package/dist/services/retriever-service.js.map +1 -1
  168. package/dist/services/swarm-verification.d.ts +104 -0
  169. package/dist/services/swarm-verification.d.ts.map +1 -0
  170. package/dist/services/swarm-verification.js +406 -0
  171. package/dist/services/swarm-verification.js.map +1 -0
  172. package/dist/services/sync-service.d.ts.map +1 -1
  173. package/dist/services/sync-service.js +8 -9
  174. package/dist/services/sync-service.js.map +1 -1
  175. package/dist/services/task-service.d.ts.map +1 -1
  176. package/dist/services/task-service.js +3 -8
  177. package/dist/services/task-service.js.map +1 -1
  178. package/dist/services/tracing-service.js +6 -6
  179. package/dist/services/tracing-service.js.map +1 -1
  180. package/dist/services/transcript-adapter.d.ts.map +1 -1
  181. package/dist/services/transcript-adapter.js +1 -1
  182. package/dist/services/transcript-adapter.js.map +1 -1
  183. package/dist/services/worker-process.d.ts.map +1 -1
  184. package/dist/services/worker-process.js +8 -30
  185. package/dist/services/worker-process.js.map +1 -1
  186. package/dist/utils/doc-hash.d.ts +0 -4
  187. package/dist/utils/doc-hash.d.ts.map +1 -1
  188. package/dist/utils/doc-hash.js.map +1 -1
  189. package/dist/utils/doc-renderer.d.ts +31 -26
  190. package/dist/utils/doc-renderer.d.ts.map +1 -1
  191. package/dist/utils/doc-renderer.js +0 -7
  192. package/dist/utils/doc-renderer.js.map +1 -1
  193. package/dist/utils/llm-json.d.ts +17 -0
  194. package/dist/utils/llm-json.d.ts.map +1 -0
  195. package/dist/utils/llm-json.js +51 -0
  196. package/dist/utils/llm-json.js.map +1 -0
  197. package/dist/utils/toml-config.d.ts +10 -16
  198. package/dist/utils/toml-config.d.ts.map +1 -1
  199. package/dist/utils/toml-config.js +3 -1
  200. package/dist/utils/toml-config.js.map +1 -1
  201. package/dist/worker/run-worker.d.ts.map +1 -1
  202. package/dist/worker/run-worker.js +2 -7
  203. package/dist/worker/run-worker.js.map +1 -1
  204. package/package.json +9 -8
@@ -6,14 +6,14 @@
6
6
  *
7
7
  * YAML content lives on disk (.tx/docs/); DB stores metadata + links only.
8
8
  */
9
- import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, } from "node:fs";
10
10
  import { resolve, dirname, join } from "node:path";
11
11
  import { Context, Effect, Layer } from "effect";
12
12
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
13
13
  import { DocRepository } from "../repo/doc-repo.js";
14
14
  import { ValidationError, DocNotFoundError, DocLockedError, InvalidDocYamlError, InvariantNotFoundError, } from "../errors.js";
15
15
  import { computeDocHash } from "../utils/doc-hash.js";
16
- import { renderDocToMarkdown, renderIndexToMarkdown } from "../utils/doc-renderer.js";
16
+ import { renderDocToMarkdown, renderIndexToMarkdown, } from "../utils/doc-renderer.js";
17
17
  import { readTxConfig } from "../utils/toml-config.js";
18
18
  import { DOC_KINDS, INVARIANT_ENFORCEMENT_TYPES, } from "@jamesaphoenix/tx-types";
19
19
  // Local string arrays for .includes() (avoids readonly cast)
@@ -38,12 +38,16 @@ const kindSubdir = (kind) => {
38
38
  /** Resolve the YAML file path for a doc. */
39
39
  const resolveYamlPath = (docsPath, kind, name) => {
40
40
  const sub = kindSubdir(kind);
41
- return sub ? resolve(docsPath, sub, `${name}.yml`) : resolve(docsPath, `${name}.yml`);
41
+ return sub
42
+ ? resolve(docsPath, sub, `${name}.yml`)
43
+ : resolve(docsPath, `${name}.yml`);
42
44
  };
43
45
  /** Resolve the MD file path for a doc. */
44
46
  const resolveMdPath = (docsPath, kind, name) => {
45
47
  const sub = kindSubdir(kind);
46
- return sub ? resolve(docsPath, sub, `${name}.md`) : resolve(docsPath, `${name}.md`);
48
+ return sub
49
+ ? resolve(docsPath, sub, `${name}.md`)
50
+ : resolve(docsPath, `${name}.md`);
47
51
  };
48
52
  /** Validate YAML content and return parsed object. */
49
53
  const validateYaml = (name, content) => {
@@ -52,10 +56,16 @@ const validateYaml = (name, content) => {
52
56
  parsed = parseYaml(content);
53
57
  }
54
58
  catch (e) {
55
- throw new InvalidDocYamlError({ name, reason: `YAML parse error: ${String(e)}` });
59
+ throw new InvalidDocYamlError({
60
+ name,
61
+ reason: `YAML parse error: ${String(e)}`,
62
+ });
56
63
  }
57
64
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
58
- throw new InvalidDocYamlError({ name, reason: "YAML must be an object (not array or scalar)" });
65
+ throw new InvalidDocYamlError({
66
+ name,
67
+ reason: "YAML must be an object (not array or scalar)",
68
+ });
59
69
  }
60
70
  return parsed;
61
71
  };
@@ -63,7 +73,10 @@ const validateYaml = (name, content) => {
63
73
  const validateKind = (name, parsed, expectedKind) => {
64
74
  const yamlKind = parsed.kind;
65
75
  if (yamlKind && typeof yamlKind === "string" && yamlKind !== expectedKind) {
66
- throw new InvalidDocYamlError({ name, reason: `YAML kind '${yamlKind}' does not match expected kind '${expectedKind}'` });
76
+ throw new InvalidDocYamlError({
77
+ name,
78
+ reason: `YAML kind '${yamlKind}' does not match expected kind '${expectedKind}'`,
79
+ });
67
80
  }
68
81
  };
69
82
  export class DocService extends Context.Tag("DocService")() {
@@ -94,36 +107,174 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
94
107
  writeFileSync(mdPath, md, "utf8");
95
108
  return mdPath;
96
109
  };
110
+ /** Generate index.yml and index.md from all docs in DB. */
111
+ function generateIndexEffect(docsPath) {
112
+ return Effect.gen(function* () {
113
+ const allDocs = yield* docRepo.findAll();
114
+ const allLinks = yield* docRepo.getAllLinks();
115
+ const overviewDoc = allDocs.find((d) => d.kind === "overview");
116
+ const prds = allDocs
117
+ .filter((d) => d.kind === "prd")
118
+ .map((d) => ({ name: d.name, title: d.title, status: d.status }));
119
+ const designDocs = allDocs
120
+ .filter((d) => d.kind === "design")
121
+ .map((d) => {
122
+ const implLink = allLinks.find((l) => l.toDocId === d.id && l.linkType === "prd_to_design");
123
+ const implDoc = implLink
124
+ ? allDocs.find((dd) => dd.id === implLink.fromDocId)
125
+ : undefined;
126
+ return {
127
+ name: d.name,
128
+ title: d.title,
129
+ status: d.status,
130
+ implements: implDoc?.name,
131
+ };
132
+ });
133
+ const links = allLinks.map((l) => {
134
+ const from = allDocs.find((d) => d.id === l.fromDocId);
135
+ const to = allDocs.find((d) => d.id === l.toDocId);
136
+ return {
137
+ from: from?.name ?? String(l.fromDocId),
138
+ to: to?.name ?? String(l.toDocId),
139
+ type: l.linkType,
140
+ };
141
+ });
142
+ // Invariant summary
143
+ const allInvariants = yield* docRepo.findInvariants();
144
+ const activeInvariants = allInvariants.filter((i) => i.status === "active");
145
+ const byEnforcement = {};
146
+ const bySubsystem = {};
147
+ for (const inv of activeInvariants) {
148
+ byEnforcement[inv.enforcement] =
149
+ (byEnforcement[inv.enforcement] ?? 0) + 1;
150
+ const sub = inv.subsystem ?? "system";
151
+ bySubsystem[sub] = (bySubsystem[sub] ?? 0) + 1;
152
+ }
153
+ const indexData = {
154
+ overview: overviewDoc?.name,
155
+ prds,
156
+ design_docs: designDocs,
157
+ links,
158
+ invariant_summary: activeInvariants.length > 0
159
+ ? {
160
+ total: activeInvariants.length,
161
+ by_enforcement: byEnforcement,
162
+ by_subsystem: bySubsystem,
163
+ }
164
+ : undefined,
165
+ };
166
+ // Write index.yml
167
+ const indexYamlObj = {
168
+ generated: true,
169
+ generated_at: new Date().toISOString(),
170
+ };
171
+ if (indexData.overview) {
172
+ indexYamlObj.overview = indexData.overview;
173
+ }
174
+ if (prds.length > 0) {
175
+ indexYamlObj.prds = prds.map((p) => ({
176
+ name: p.name,
177
+ title: p.title,
178
+ status: p.status,
179
+ }));
180
+ }
181
+ if (designDocs.length > 0) {
182
+ indexYamlObj.design_docs = designDocs.map((dd) => {
183
+ const entry = {
184
+ name: dd.name,
185
+ title: dd.title,
186
+ status: dd.status,
187
+ };
188
+ if (dd.implements)
189
+ entry.implements = dd.implements;
190
+ return entry;
191
+ });
192
+ }
193
+ const indexYamlPath = resolve(docsPath, "index.yml");
194
+ ensureDir(indexYamlPath);
195
+ writeFileSync(indexYamlPath, stringifyYaml(indexYamlObj), "utf8");
196
+ // Write index.md
197
+ const indexMd = renderIndexToMarkdown(indexData);
198
+ const indexMdPath = resolve(docsPath, "index.md");
199
+ writeFileSync(indexMdPath, indexMd, "utf8");
200
+ });
201
+ }
202
+ /** Sync invariants from a single doc's YAML into DB. */
203
+ function syncInvariantsForDoc(doc) {
204
+ return Effect.gen(function* () {
205
+ const docsPath = getDocsPath();
206
+ const yamlPath = resolveYamlPath(docsPath, doc.kind, doc.name);
207
+ if (!existsSync(yamlPath)) {
208
+ return [];
209
+ }
210
+ const yamlContent = readFileSync(yamlPath, "utf8");
211
+ const parsed = validateYaml(doc.name, yamlContent);
212
+ const invariantsRaw = parsed.invariants;
213
+ if (!Array.isArray(invariantsRaw) || invariantsRaw.length === 0) {
214
+ yield* docRepo.deprecateInvariantsNotIn(doc.id, []);
215
+ return [];
216
+ }
217
+ const synced = [];
218
+ const activeIds = [];
219
+ for (const raw of invariantsRaw) {
220
+ if (typeof raw !== "object" || raw === null)
221
+ continue;
222
+ const inv = raw;
223
+ const id = typeof inv.id === "string" ? inv.id : null;
224
+ const rule = typeof inv.rule === "string" ? inv.rule : null;
225
+ const enforcement = typeof inv.enforcement === "string" ? inv.enforcement : null;
226
+ if (!id || !rule || !enforcement)
227
+ continue;
228
+ if (!enforcementStrings.includes(enforcement))
229
+ continue;
230
+ const input = {
231
+ id,
232
+ rule,
233
+ enforcement,
234
+ docId: doc.id,
235
+ subsystem: typeof inv.subsystem === "string"
236
+ ? inv.subsystem
237
+ : inv.subsystem === null
238
+ ? null
239
+ : undefined,
240
+ testRef: typeof inv.test_ref === "string" ? inv.test_ref : undefined,
241
+ lintRule: typeof inv.lint_rule === "string" ? inv.lint_rule : undefined,
242
+ promptRef: typeof inv.prompt_ref === "string" ? inv.prompt_ref : undefined,
243
+ };
244
+ const result = yield* docRepo.upsertInvariant(input);
245
+ synced.push(result);
246
+ activeIds.push(id);
247
+ }
248
+ yield* docRepo.deprecateInvariantsNotIn(doc.id, activeIds);
249
+ return synced;
250
+ });
251
+ }
97
252
  return {
98
253
  create: (input) => Effect.gen(function* () {
99
254
  const { kind, name, title, yamlContent, metadata } = input;
100
- // Validate kind
101
255
  if (!docKindStrings.includes(kind)) {
102
256
  return yield* Effect.fail(new ValidationError({ reason: `Invalid doc kind: ${kind}` }));
103
257
  }
104
- // Validate name (no spaces, lowercase with dashes)
105
258
  if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)) {
106
- return yield* Effect.fail(new ValidationError({ reason: `Invalid doc name: ${name}. Use alphanumeric with dashes/dots.` }));
259
+ return yield* Effect.fail(new ValidationError({
260
+ reason: `Invalid doc name: ${name}. Use alphanumeric with dashes/dots.`,
261
+ }));
107
262
  }
108
- // Validate YAML
109
263
  const parsed = validateYaml(name, yamlContent);
110
264
  validateKind(name, parsed, kind);
111
- // Check uniqueness
112
265
  const existing = yield* docRepo.findByName(name);
113
266
  if (existing) {
114
- return yield* Effect.fail(new ValidationError({ reason: `Doc '${name}' already exists (v${existing.version})` }));
267
+ return yield* Effect.fail(new ValidationError({
268
+ reason: `Doc '${name}' already exists (v${existing.version})`,
269
+ }));
115
270
  }
116
- // Compute hash and write file
117
271
  const hash = computeDocHash(yamlContent);
118
272
  const docsPath = getDocsPath();
119
273
  const filePath = resolveYamlPath(docsPath, kind, name);
120
274
  ensureDir(filePath);
121
275
  writeFileSync(filePath, yamlContent, "utf8");
122
- // Relative path for DB storage
123
- const relPath = kind === "overview"
124
- ? `${name}.yml`
125
- : join(kind, `${name}.yml`);
126
- const insertInput = {
276
+ const relPath = kind === "overview" ? `${name}.yml` : join(kind, `${name}.yml`);
277
+ const doc = yield* docRepo.insert({
127
278
  hash,
128
279
  kind,
129
280
  name,
@@ -132,13 +283,13 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
132
283
  filePath: relPath,
133
284
  parentDocId: null,
134
285
  metadata: metadata ? JSON.stringify(metadata) : undefined,
135
- };
136
- const doc = yield* docRepo.insert(insertInput);
137
- // Auto-render on create
286
+ });
138
287
  try {
139
288
  renderSingleDoc(doc, docsPath);
140
289
  }
141
- catch { /* non-fatal */ }
290
+ catch {
291
+ /* non-fatal */
292
+ }
142
293
  yield* generateIndexEffect(docsPath);
143
294
  return doc;
144
295
  }),
@@ -157,29 +308,25 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
157
308
  if (doc.status === "locked") {
158
309
  return yield* Effect.fail(new DocLockedError({ name, version: doc.version }));
159
310
  }
160
- // Validate YAML
161
311
  const parsed = validateYaml(name, yamlContent);
162
312
  validateKind(name, parsed, doc.kind);
163
- // Compute new hash
164
313
  const hash = computeDocHash(yamlContent);
165
- // Write file
166
314
  const docsPath = getDocsPath();
167
315
  const filePath = resolveYamlPath(docsPath, doc.kind, name);
168
316
  ensureDir(filePath);
169
317
  writeFileSync(filePath, yamlContent, "utf8");
170
- // Update title if changed in YAML
171
318
  const title = typeof parsed.title === "string" ? parsed.title : doc.title;
172
319
  yield* docRepo.update(doc.id, { hash, title });
173
- // Re-fetch to return updated doc
174
320
  const updated = yield* docRepo.findById(doc.id);
175
321
  if (!updated) {
176
322
  return yield* Effect.fail(new DocNotFoundError({ name }));
177
323
  }
178
- // Auto-render on update
179
324
  try {
180
325
  renderSingleDoc(updated, docsPath);
181
326
  }
182
- catch { /* non-fatal */ }
327
+ catch {
328
+ /* non-fatal */
329
+ }
183
330
  yield* generateIndexEffect(docsPath);
184
331
  return updated;
185
332
  }),
@@ -189,7 +336,6 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
189
336
  return yield* Effect.fail(new DocNotFoundError({ name }));
190
337
  }
191
338
  if (doc.status === "locked") {
192
- // Already locked — return as-is
193
339
  return doc;
194
340
  }
195
341
  const lockedAt = new Date().toISOString();
@@ -198,12 +344,13 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
198
344
  if (!locked) {
199
345
  return yield* Effect.fail(new DocNotFoundError({ name }));
200
346
  }
201
- // Auto-render on lock (final version)
202
347
  const docsPath = getDocsPath();
203
348
  try {
204
349
  renderSingleDoc(locked, docsPath);
205
350
  }
206
- catch { /* non-fatal */ }
351
+ catch {
352
+ /* non-fatal */
353
+ }
207
354
  yield* generateIndexEffect(docsPath);
208
355
  return locked;
209
356
  }),
@@ -216,9 +363,7 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
216
363
  if (doc.status === "locked") {
217
364
  return yield* Effect.fail(new DocLockedError({ name, version: doc.version }));
218
365
  }
219
- // Delete from DB (CASCADE handles links + invariants)
220
366
  yield* docRepo.remove(doc.id);
221
- // Remove YAML + MD files from disk
222
367
  const docsPath = getDocsPath();
223
368
  const yamlPath = resolveYamlPath(docsPath, doc.kind, name);
224
369
  const mdPath = resolveMdPath(docsPath, doc.kind, name);
@@ -226,20 +371,22 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
226
371
  if (existsSync(yamlPath))
227
372
  unlinkSync(yamlPath);
228
373
  }
229
- catch { /* non-fatal */ }
374
+ catch {
375
+ /* non-fatal */
376
+ }
230
377
  try {
231
378
  if (existsSync(mdPath))
232
379
  unlinkSync(mdPath);
233
380
  }
234
- catch { /* non-fatal */ }
235
- // Regenerate index
381
+ catch {
382
+ /* non-fatal */
383
+ }
236
384
  yield* generateIndexEffect(docsPath);
237
385
  }),
238
386
  render: (name) => Effect.gen(function* () {
239
387
  const docsPath = getDocsPath();
240
388
  const rendered = [];
241
389
  if (name) {
242
- // Render single doc
243
390
  const doc = yield* docRepo.findByName(name);
244
391
  if (!doc) {
245
392
  return yield* Effect.fail(new DocNotFoundError({ name }));
@@ -247,18 +394,16 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
247
394
  rendered.push(renderSingleDoc(doc, docsPath));
248
395
  }
249
396
  else {
250
- // Render all docs
251
397
  const allDocs = yield* docRepo.findAll();
252
398
  for (const doc of allDocs) {
253
399
  try {
254
400
  rendered.push(renderSingleDoc(doc, docsPath));
255
401
  }
256
402
  catch {
257
- // Skip docs with missing YAML files
403
+ /* skip docs with missing YAML */
258
404
  }
259
405
  }
260
406
  }
261
- // Always regenerate index
262
407
  yield* generateIndexEffect(docsPath);
263
408
  return rendered;
264
409
  }),
@@ -268,13 +413,16 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
268
413
  return yield* Effect.fail(new DocNotFoundError({ name }));
269
414
  }
270
415
  if (doc.status !== "locked") {
271
- return yield* Effect.fail(new ValidationError({ reason: `Doc '${name}' must be locked before creating a new version` }));
416
+ return yield* Effect.fail(new ValidationError({
417
+ reason: `Doc '${name}' must be locked before creating a new version`,
418
+ }));
272
419
  }
273
- // Read existing YAML content
274
420
  const docsPath = getDocsPath();
275
421
  const yamlPath = resolveYamlPath(docsPath, doc.kind, name);
276
422
  if (!existsSync(yamlPath)) {
277
- return yield* Effect.fail(new ValidationError({ reason: `YAML file not found for '${name}'` }));
423
+ return yield* Effect.fail(new ValidationError({
424
+ reason: `YAML file not found for '${name}'`,
425
+ }));
278
426
  }
279
427
  const yamlContent = readFileSync(yamlPath, "utf8");
280
428
  const hash = computeDocHash(yamlContent);
@@ -282,7 +430,7 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
282
430
  const relPath = doc.kind === "overview"
283
431
  ? `${name}.yml`
284
432
  : join(doc.kind, `${name}.yml`);
285
- const insertInput = {
433
+ const newDoc = yield* docRepo.insert({
286
434
  hash,
287
435
  kind: doc.kind,
288
436
  name,
@@ -290,13 +438,13 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
290
438
  version: newVersion,
291
439
  filePath: relPath,
292
440
  parentDocId: doc.id,
293
- };
294
- const newDoc = yield* docRepo.insert(insertInput);
295
- // Auto-render on version create
441
+ });
296
442
  try {
297
443
  renderSingleDoc(newDoc, docsPath);
298
444
  }
299
- catch { /* non-fatal */ }
445
+ catch {
446
+ /* non-fatal */
447
+ }
300
448
  yield* generateIndexEffect(docsPath);
301
449
  return newDoc;
302
450
  }),
@@ -309,11 +457,10 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
309
457
  if (!toDoc) {
310
458
  return yield* Effect.fail(new DocNotFoundError({ name: toName }));
311
459
  }
312
- // Determine link type
313
460
  const resolvedType = linkType ?? inferLinkType(fromDoc.kind, toDoc.kind);
314
461
  if (!resolvedType) {
315
462
  return yield* Effect.fail(new ValidationError({
316
- reason: `Cannot infer link type from ${fromDoc.kind} → ${toDoc.kind}. Provide explicit linkType.`
463
+ reason: `Cannot infer link type from ${fromDoc.kind} → ${toDoc.kind}. Provide explicit linkType.`,
317
464
  }));
318
465
  }
319
466
  return yield* docRepo.createLink(fromDoc.id, toDoc.id, resolvedType);
@@ -331,9 +478,10 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
331
478
  return yield* Effect.fail(new DocNotFoundError({ name: designName }));
332
479
  }
333
480
  if (parentDoc.kind !== "design") {
334
- return yield* Effect.fail(new ValidationError({ reason: `Patches can only be created on design docs, got '${parentDoc.kind}'` }));
481
+ return yield* Effect.fail(new ValidationError({
482
+ reason: `Patches can only be created on design docs, got '${parentDoc.kind}'`,
483
+ }));
335
484
  }
336
- // Create minimal patch YAML using a proper serializer to avoid injection
337
485
  const patchYaml = stringifyYaml({
338
486
  kind: "design",
339
487
  name: patchName,
@@ -358,13 +506,13 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
358
506
  filePath: relPath,
359
507
  parentDocId: null,
360
508
  });
361
- // Create design_patch link
362
509
  yield* docRepo.createLink(patchDoc.id, parentDoc.id, "design_patch");
363
- // Auto-render on patch create
364
510
  try {
365
511
  renderSingleDoc(patchDoc, docsPath);
366
512
  }
367
- catch { /* non-fatal */ }
513
+ catch {
514
+ /* non-fatal */
515
+ }
368
516
  yield* generateIndexEffect(docsPath);
369
517
  return patchDoc;
370
518
  }),
@@ -382,7 +530,6 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
382
530
  return yield* Effect.fail(new DocNotFoundError({ name }));
383
531
  }
384
532
  const warnings = [];
385
- // Check if YAML file hash matches DB hash
386
533
  const docsPath = getDocsPath();
387
534
  const yamlPath = resolveYamlPath(docsPath, doc.kind, name);
388
535
  if (existsSync(yamlPath)) {
@@ -395,7 +542,6 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
395
542
  else {
396
543
  warnings.push(`YAML file missing: ${yamlPath}`);
397
544
  }
398
- // Check tasks linked to this doc
399
545
  const taskLinks = yield* docRepo.getTaskLinksForDoc(doc.id);
400
546
  if (taskLinks.length === 0 && doc.kind === "design") {
401
547
  warnings.push(`Design doc '${name}' has no linked tasks`);
@@ -435,7 +581,6 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
435
581
  const allLinks = yield* docRepo.getAllLinks();
436
582
  const nodes = [];
437
583
  const edges = [];
438
- // Doc nodes
439
584
  for (const doc of allDocs) {
440
585
  nodes.push({
441
586
  id: `doc:${doc.id}`,
@@ -444,7 +589,6 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
444
589
  status: doc.status,
445
590
  });
446
591
  }
447
- // Doc-doc edges
448
592
  for (const link of allLinks) {
449
593
  edges.push({
450
594
  source: `doc:${link.fromDocId}`,
@@ -452,13 +596,11 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
452
596
  type: link.linkType,
453
597
  });
454
598
  }
455
- // Task-doc edges (collect task nodes from task_doc_links for each doc)
456
599
  for (const doc of allDocs) {
457
600
  const taskLinks = yield* docRepo.getTaskLinksForDoc(doc.id);
458
601
  for (const tl of taskLinks) {
459
- // Add task node if not already added
460
602
  const taskNodeId = `task:${tl.taskId}`;
461
- if (!nodes.some(n => n.id === taskNodeId)) {
603
+ if (!nodes.some((n) => n.id === taskNodeId)) {
462
604
  nodes.push({
463
605
  id: taskNodeId,
464
606
  label: tl.taskId,
@@ -475,131 +617,5 @@ export const DocServiceLive = Layer.effect(DocService, Effect.gen(function* () {
475
617
  return { nodes, edges };
476
618
  }),
477
619
  };
478
- /** Generate index.yml and index.md from all docs in DB. */
479
- function generateIndexEffect(docsPath) {
480
- return Effect.gen(function* () {
481
- const allDocs = yield* docRepo.findAll();
482
- const allLinks = yield* docRepo.getAllLinks();
483
- const overviewDoc = allDocs.find(d => d.kind === "overview");
484
- const prds = allDocs
485
- .filter(d => d.kind === "prd")
486
- .map(d => ({ name: d.name, title: d.title, status: d.status }));
487
- const designDocs = allDocs
488
- .filter(d => d.kind === "design")
489
- .map(d => {
490
- // Find the prd this design implements (via prd_to_design link)
491
- const implLink = allLinks.find(l => l.toDocId === d.id && l.linkType === "prd_to_design");
492
- const implDoc = implLink ? allDocs.find(dd => dd.id === implLink.fromDocId) : undefined;
493
- return {
494
- name: d.name,
495
- title: d.title,
496
- status: d.status,
497
- implements: implDoc?.name,
498
- };
499
- });
500
- const links = allLinks.map(l => {
501
- const from = allDocs.find(d => d.id === l.fromDocId);
502
- const to = allDocs.find(d => d.id === l.toDocId);
503
- return {
504
- from: from?.name ?? String(l.fromDocId),
505
- to: to?.name ?? String(l.toDocId),
506
- type: l.linkType,
507
- };
508
- });
509
- // Invariant summary
510
- const allInvariants = yield* docRepo.findInvariants();
511
- const activeInvariants = allInvariants.filter(i => i.status === "active");
512
- const byEnforcement = {};
513
- const bySubsystem = {};
514
- for (const inv of activeInvariants) {
515
- byEnforcement[inv.enforcement] = (byEnforcement[inv.enforcement] ?? 0) + 1;
516
- const sub = inv.subsystem ?? "system";
517
- bySubsystem[sub] = (bySubsystem[sub] ?? 0) + 1;
518
- }
519
- const indexData = {
520
- overview: overviewDoc?.name,
521
- prds,
522
- design_docs: designDocs,
523
- links,
524
- invariant_summary: activeInvariants.length > 0
525
- ? { total: activeInvariants.length, by_enforcement: byEnforcement, by_subsystem: bySubsystem }
526
- : undefined,
527
- };
528
- // Write index.yml using a proper YAML serializer to avoid injection
529
- const indexYamlObj = {
530
- generated: true,
531
- generated_at: new Date().toISOString(),
532
- };
533
- if (indexData.overview) {
534
- indexYamlObj.overview = indexData.overview;
535
- }
536
- if (prds.length > 0) {
537
- indexYamlObj.prds = prds.map(p => ({ name: p.name, title: p.title, status: p.status }));
538
- }
539
- if (designDocs.length > 0) {
540
- indexYamlObj.design_docs = designDocs.map(dd => {
541
- const entry = { name: dd.name, title: dd.title, status: dd.status };
542
- if (dd.implements)
543
- entry.implements = dd.implements;
544
- return entry;
545
- });
546
- }
547
- const indexYamlPath = resolve(docsPath, "index.yml");
548
- ensureDir(indexYamlPath);
549
- writeFileSync(indexYamlPath, stringifyYaml(indexYamlObj), "utf8");
550
- // Write index.md
551
- const indexMd = renderIndexToMarkdown(indexData);
552
- const indexMdPath = resolve(docsPath, "index.md");
553
- writeFileSync(indexMdPath, indexMd, "utf8");
554
- });
555
- }
556
- /** Sync invariants from a single doc's YAML into DB. */
557
- function syncInvariantsForDoc(doc) {
558
- return Effect.gen(function* () {
559
- const docsPath = getDocsPath();
560
- const yamlPath = resolveYamlPath(docsPath, doc.kind, doc.name);
561
- if (!existsSync(yamlPath)) {
562
- return [];
563
- }
564
- const yamlContent = readFileSync(yamlPath, "utf8");
565
- const parsed = validateYaml(doc.name, yamlContent);
566
- const invariantsRaw = parsed.invariants;
567
- if (!Array.isArray(invariantsRaw) || invariantsRaw.length === 0) {
568
- // Deprecate any existing invariants for this doc
569
- yield* docRepo.deprecateInvariantsNotIn(doc.id, []);
570
- return [];
571
- }
572
- const synced = [];
573
- const activeIds = [];
574
- for (const raw of invariantsRaw) {
575
- if (typeof raw !== "object" || raw === null)
576
- continue;
577
- const inv = raw;
578
- const id = typeof inv.id === "string" ? inv.id : null;
579
- const rule = typeof inv.rule === "string" ? inv.rule : null;
580
- const enforcement = typeof inv.enforcement === "string" ? inv.enforcement : null;
581
- if (!id || !rule || !enforcement)
582
- continue;
583
- if (!enforcementStrings.includes(enforcement))
584
- continue;
585
- const input = {
586
- id,
587
- rule,
588
- enforcement: enforcement,
589
- docId: doc.id,
590
- subsystem: typeof inv.subsystem === "string" ? inv.subsystem : (inv.subsystem === null ? null : undefined),
591
- testRef: typeof inv.test_ref === "string" ? inv.test_ref : undefined,
592
- lintRule: typeof inv.lint_rule === "string" ? inv.lint_rule : undefined,
593
- promptRef: typeof inv.prompt_ref === "string" ? inv.prompt_ref : undefined,
594
- };
595
- const result = yield* docRepo.upsertInvariant(input);
596
- synced.push(result);
597
- activeIds.push(id);
598
- }
599
- // Deprecate invariants no longer in YAML
600
- yield* docRepo.deprecateInvariantsNotIn(doc.id, activeIds);
601
- return synced;
602
- });
603
- }
604
620
  }));
605
621
  //# sourceMappingURL=doc-service.js.map