domain-knowledge-kit 0.2.10 → 0.2.12

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 (89) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +41 -10
  3. package/dist/cli.js.map +1 -1
  4. package/dist/features/agent/commands/init.d.ts.map +1 -1
  5. package/dist/features/agent/commands/init.js +46 -8
  6. package/dist/features/agent/commands/init.js.map +1 -1
  7. package/dist/features/agent/commands/prime.d.ts.map +1 -1
  8. package/dist/features/agent/commands/prime.js +67 -16
  9. package/dist/features/agent/commands/prime.js.map +1 -1
  10. package/dist/features/audit/commands/stats.d.ts.map +1 -1
  11. package/dist/features/audit/commands/stats.js +24 -1
  12. package/dist/features/audit/commands/stats.js.map +1 -1
  13. package/dist/features/pipeline/commands/render.d.ts.map +1 -1
  14. package/dist/features/pipeline/commands/render.js +3 -2
  15. package/dist/features/pipeline/commands/render.js.map +1 -1
  16. package/dist/features/pipeline/renderer.d.ts.map +1 -1
  17. package/dist/features/pipeline/renderer.js +4 -0
  18. package/dist/features/pipeline/renderer.js.map +1 -1
  19. package/dist/features/pipeline/tests/validator.test.js +16 -0
  20. package/dist/features/pipeline/tests/validator.test.js.map +1 -1
  21. package/dist/features/pipeline/validator.d.ts.map +1 -1
  22. package/dist/features/pipeline/validator.js +22 -14
  23. package/dist/features/pipeline/validator.js.map +1 -1
  24. package/dist/features/query/commands/graph.d.ts +6 -0
  25. package/dist/features/query/commands/graph.d.ts.map +1 -1
  26. package/dist/features/query/commands/graph.js +223 -53
  27. package/dist/features/query/commands/graph.js.map +1 -1
  28. package/dist/features/query/commands/list.d.ts.map +1 -1
  29. package/dist/features/query/commands/list.js +5 -2
  30. package/dist/features/query/commands/list.js.map +1 -1
  31. package/dist/features/query/commands/locate.d.ts +3 -0
  32. package/dist/features/query/commands/locate.d.ts.map +1 -0
  33. package/dist/features/query/commands/locate.js +96 -0
  34. package/dist/features/query/commands/locate.js.map +1 -0
  35. package/dist/features/query/commands/related.d.ts.map +1 -1
  36. package/dist/features/query/commands/related.js +4 -3
  37. package/dist/features/query/commands/related.js.map +1 -1
  38. package/dist/features/query/commands/search.d.ts.map +1 -1
  39. package/dist/features/query/commands/search.js +3 -2
  40. package/dist/features/query/commands/search.js.map +1 -1
  41. package/dist/features/query/commands/show.d.ts.map +1 -1
  42. package/dist/features/query/commands/show.js +4 -3
  43. package/dist/features/query/commands/show.js.map +1 -1
  44. package/dist/features/query/commands/summary.d.ts.map +1 -1
  45. package/dist/features/query/commands/summary.js +61 -5
  46. package/dist/features/query/commands/summary.js.map +1 -1
  47. package/dist/features/refactor/commands/move.d.ts +14 -0
  48. package/dist/features/refactor/commands/move.d.ts.map +1 -0
  49. package/dist/features/refactor/commands/move.js +166 -0
  50. package/dist/features/refactor/commands/move.js.map +1 -0
  51. package/dist/features/refactor/commands/rename.d.ts.map +1 -1
  52. package/dist/features/refactor/commands/rename.js +329 -131
  53. package/dist/features/refactor/commands/rename.js.map +1 -1
  54. package/dist/features/refactor/commands/rm.d.ts.map +1 -1
  55. package/dist/features/refactor/commands/rm.js +349 -76
  56. package/dist/features/refactor/commands/rm.js.map +1 -1
  57. package/dist/features/refactor/refactor-helpers.d.ts +117 -0
  58. package/dist/features/refactor/refactor-helpers.d.ts.map +1 -0
  59. package/dist/features/refactor/refactor-helpers.js +653 -0
  60. package/dist/features/refactor/refactor-helpers.js.map +1 -0
  61. package/dist/features/scaffold/commands/add-item.d.ts +5 -13
  62. package/dist/features/scaffold/commands/add-item.d.ts.map +1 -1
  63. package/dist/features/scaffold/commands/add-item.js +75 -95
  64. package/dist/features/scaffold/commands/add-item.js.map +1 -1
  65. package/dist/features/scaffold/commands/new-adr.d.ts.map +1 -1
  66. package/dist/features/scaffold/commands/new-adr.js +0 -9
  67. package/dist/features/scaffold/commands/new-adr.js.map +1 -1
  68. package/dist/features/scaffold/commands/new-domain.d.ts.map +1 -1
  69. package/dist/features/scaffold/commands/new-domain.js +8 -3
  70. package/dist/features/scaffold/commands/new-domain.js.map +1 -1
  71. package/dist/shared/graph.d.ts.map +1 -1
  72. package/dist/shared/graph.js +5 -0
  73. package/dist/shared/graph.js.map +1 -1
  74. package/dist/shared/similarity.d.ts +3 -0
  75. package/dist/shared/similarity.d.ts.map +1 -0
  76. package/dist/shared/similarity.js +52 -0
  77. package/dist/shared/similarity.js.map +1 -0
  78. package/dist/shared/tests/graph.test.js +8 -0
  79. package/dist/shared/tests/graph.test.js.map +1 -1
  80. package/package.json +3 -2
  81. package/tools/dkk/templates/index.md.hbs +4 -0
  82. package/dist/features/adr/commands/adr-related.d.ts +0 -14
  83. package/dist/features/adr/commands/adr-related.d.ts.map +0 -1
  84. package/dist/features/adr/commands/adr-related.js +0 -146
  85. package/dist/features/adr/commands/adr-related.js.map +0 -1
  86. package/dist/features/adr/commands/adr-show.d.ts +0 -10
  87. package/dist/features/adr/commands/adr-show.d.ts.map +0 -1
  88. package/dist/features/adr/commands/adr-show.js +0 -30
  89. package/dist/features/adr/commands/adr-show.js.map +0 -1
@@ -0,0 +1,653 @@
1
+ /**
2
+ * Shared helpers for refactor commands (rm, rename, move).
3
+ *
4
+ * Provides YAML-aware manipulation functions for domain items, actors,
5
+ * ADR frontmatter, flows, and context indexes.
6
+ */
7
+ import { existsSync, readFileSync, writeFileSync, readdirSync, } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { parseYaml, stringifyYaml, } from "../../shared/yaml.js";
10
+ import { contextsDir, indexFile, actorsFile as actorsFilePath, adrDir, } from "../../shared/paths.js";
11
+ // ── Constants ────────────────────────────────────────────────────────
12
+ export const KIND_TO_DIR = {
13
+ event: "events",
14
+ command: "commands",
15
+ aggregate: "aggregates",
16
+ policy: "policies",
17
+ read_model: "read-models",
18
+ };
19
+ /**
20
+ * Classify a user-supplied item id into its kind and component parts.
21
+ *
22
+ * Supported formats:
23
+ * actor.ActorName → actor
24
+ * flow.FlowName → flow
25
+ * adr-NNNN → adr
26
+ * context.ctxName → context
27
+ * ctxName → context (bare, no dot)
28
+ * ctx.ItemName → domain item
29
+ */
30
+ export function classifyId(id) {
31
+ if (id.startsWith("actor.")) {
32
+ return { kind: "actor", name: id.slice("actor.".length), raw: id };
33
+ }
34
+ if (id.startsWith("flow.")) {
35
+ return { kind: "flow", name: id.slice("flow.".length), raw: id };
36
+ }
37
+ if (/^adr-/.test(id)) {
38
+ return { kind: "adr", name: id, raw: id };
39
+ }
40
+ if (id.startsWith("context.")) {
41
+ return { kind: "context", name: id.slice("context.".length), raw: id };
42
+ }
43
+ if (!id.includes(".")) {
44
+ return { kind: "context", name: id, raw: id };
45
+ }
46
+ const dot = id.indexOf(".");
47
+ return {
48
+ kind: "domain",
49
+ name: id.slice(dot + 1),
50
+ ctx: id.slice(0, dot),
51
+ raw: id,
52
+ };
53
+ }
54
+ // ── Diff helper ──────────────────────────────────────────────────────
55
+ export function printDiff(filePath, oldContent, newContent) {
56
+ console.log(`\n--- a/${filePath}\n+++ b/${filePath}`);
57
+ const oLines = oldContent.split("\n");
58
+ const nLines = newContent.split("\n");
59
+ const maxLines = Math.max(oLines.length, nLines.length);
60
+ for (let i = 0; i < maxLines; i++) {
61
+ const o = oLines[i] ?? "";
62
+ const n = nLines[i] ?? "";
63
+ if (o !== n) {
64
+ if (o)
65
+ console.log(`- ${o}`);
66
+ if (n)
67
+ console.log(`+ ${n}`);
68
+ }
69
+ }
70
+ }
71
+ // ── File discovery ───────────────────────────────────────────────────
72
+ /**
73
+ * List all YAML item files under a context directory (including context.yml).
74
+ */
75
+ export function listContextFiles(ctxPath) {
76
+ const files = [];
77
+ const meta = join(ctxPath, "context.yml");
78
+ if (existsSync(meta))
79
+ files.push(meta);
80
+ for (const subDir of Object.values(KIND_TO_DIR)) {
81
+ const dirPath = join(ctxPath, subDir);
82
+ if (!existsSync(dirPath))
83
+ continue;
84
+ for (const f of readdirSync(dirPath)) {
85
+ if (f.endsWith(".yml") || f.endsWith(".yaml")) {
86
+ files.push(join(dirPath, f));
87
+ }
88
+ }
89
+ }
90
+ return files;
91
+ }
92
+ /**
93
+ * Collect all YAML item files across all contexts.
94
+ */
95
+ export function allDomainFiles(root) {
96
+ const files = [];
97
+ const base = contextsDir(root);
98
+ if (!existsSync(base))
99
+ return files;
100
+ for (const ent of readdirSync(base, { withFileTypes: true })) {
101
+ if (ent.name.startsWith(".") || !ent.isDirectory())
102
+ continue;
103
+ files.push(...listContextFiles(join(base, ent.name)));
104
+ }
105
+ return files;
106
+ }
107
+ /**
108
+ * Collect all ADR markdown files.
109
+ */
110
+ export function allAdrFiles(root) {
111
+ const dir = adrDir(root);
112
+ if (!existsSync(dir))
113
+ return [];
114
+ return readdirSync(dir)
115
+ .filter((f) => f.endsWith(".md") &&
116
+ f.toLowerCase() !== "readme.md" &&
117
+ !f.startsWith("."))
118
+ .sort()
119
+ .map((f) => join(dir, f));
120
+ }
121
+ // ── Regex-based file rewrite ─────────────────────────────────────────
122
+ /**
123
+ * Rewrite all occurrences of oldId → newId in a file using word-boundary
124
+ * regex. Returns true if any changes were made.
125
+ *
126
+ * Safe for scoped IDs like "ordering.OrderPlaced" and "adr-0001".
127
+ */
128
+ export function rewriteFile(filePath, oldId, newId, showDiff = false) {
129
+ if (!existsSync(filePath))
130
+ return false;
131
+ const content = readFileSync(filePath, "utf-8");
132
+ const escaped = oldId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
133
+ const regex = new RegExp(`(?<![\\w.-])${escaped}(?![\\w.-])`, "g");
134
+ if (!regex.test(content))
135
+ return false;
136
+ const newContent = content.replace(new RegExp(`(?<![\\w.-])${escaped}(?![\\w.-])`, "g"), newId);
137
+ writeFileSync(filePath, newContent, "utf-8");
138
+ if (showDiff)
139
+ printDiff(filePath, content, newContent);
140
+ return true;
141
+ }
142
+ // ── ADR frontmatter manipulation ─────────────────────────────────────
143
+ /**
144
+ * Update an ADR markdown file's YAML frontmatter in-place.
145
+ * The mutator `fn` receives the parsed frontmatter object.
146
+ * Returns true if any changes were made.
147
+ */
148
+ export function updateAdrFrontmatter(filePath, fn, showDiff = false) {
149
+ if (!existsSync(filePath))
150
+ return false;
151
+ const content = readFileSync(filePath, "utf-8");
152
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/u.exec(content);
153
+ if (!match)
154
+ return false;
155
+ const fm = parseYaml(match[1]);
156
+ if (!fn(fm))
157
+ return false;
158
+ const newFm = stringifyYaml(fm).trimEnd();
159
+ const afterFm = content.slice(match.index + match[0].length);
160
+ const newContent = `---\n${newFm}\n---${afterFm}`;
161
+ writeFileSync(filePath, newContent, "utf-8");
162
+ if (showDiff)
163
+ printDiff(filePath, content, newContent);
164
+ return true;
165
+ }
166
+ // ── Glossary (YAML-aware) ────────────────────────────────────────────
167
+ /**
168
+ * Remove a glossary entry (by term) from context.yml.
169
+ * Returns true if the entry was found and removed.
170
+ */
171
+ export function removeGlossaryEntry(ctxPath, term, showDiff = false) {
172
+ const metaPath = join(ctxPath, "context.yml");
173
+ if (!existsSync(metaPath))
174
+ return false;
175
+ const content = readFileSync(metaPath, "utf-8");
176
+ const meta = parseYaml(content);
177
+ if (!meta.glossary?.length)
178
+ return false;
179
+ const before = meta.glossary.length;
180
+ meta.glossary = meta.glossary.filter((e) => e.term !== term);
181
+ if (meta.glossary.length === before)
182
+ return false;
183
+ if (meta.glossary.length === 0)
184
+ delete meta.glossary;
185
+ const newContent = stringifyYaml(meta);
186
+ if (showDiff)
187
+ printDiff(metaPath, content, newContent);
188
+ writeFileSync(metaPath, newContent, "utf-8");
189
+ return true;
190
+ }
191
+ /**
192
+ * Rename a glossary term in context.yml.
193
+ * Returns true if the entry was found and renamed.
194
+ */
195
+ export function renameGlossaryEntry(ctxPath, oldTerm, newTerm, showDiff = false) {
196
+ const metaPath = join(ctxPath, "context.yml");
197
+ if (!existsSync(metaPath))
198
+ return false;
199
+ const content = readFileSync(metaPath, "utf-8");
200
+ const meta = parseYaml(content);
201
+ const entry = meta.glossary?.find((e) => e.term === oldTerm);
202
+ if (!entry)
203
+ return false;
204
+ entry.term = newTerm;
205
+ const newContent = stringifyYaml(meta);
206
+ if (showDiff)
207
+ printDiff(metaPath, content, newContent);
208
+ writeFileSync(metaPath, newContent, "utf-8");
209
+ return true;
210
+ }
211
+ // ── Actors ───────────────────────────────────────────────────────────
212
+ /**
213
+ * Remove an actor from actors.yml. Returns true if removed.
214
+ */
215
+ export function removeActorEntry(root, actorName, showDiff = false) {
216
+ const path = actorsFilePath(root);
217
+ if (!existsSync(path))
218
+ return false;
219
+ const content = readFileSync(path, "utf-8");
220
+ const data = parseYaml(content);
221
+ const before = data.actors?.length ?? 0;
222
+ data.actors = (data.actors ?? []).filter((a) => a.name !== actorName);
223
+ if (data.actors.length === before)
224
+ return false;
225
+ const newContent = stringifyYaml(data);
226
+ if (showDiff)
227
+ printDiff(path, content, newContent);
228
+ writeFileSync(path, newContent, "utf-8");
229
+ return true;
230
+ }
231
+ /**
232
+ * Rename an actor in actors.yml. Returns true if renamed.
233
+ */
234
+ export function renameActorEntry(root, oldName, newName, showDiff = false) {
235
+ const path = actorsFilePath(root);
236
+ if (!existsSync(path))
237
+ return false;
238
+ const content = readFileSync(path, "utf-8");
239
+ const data = parseYaml(content);
240
+ const entry = data.actors?.find((a) => a.name === oldName);
241
+ if (!entry)
242
+ return false;
243
+ entry.name = newName;
244
+ const newContent = stringifyYaml(data);
245
+ if (showDiff)
246
+ printDiff(path, content, newContent);
247
+ writeFileSync(path, newContent, "utf-8");
248
+ return true;
249
+ }
250
+ // ── Domain item field cleanup (YAML-aware) ────────────────────────────
251
+ /**
252
+ * Remove a reference to `targetName` from the appropriate YAML fields
253
+ * of a domain item file, based on the item's kind.
254
+ *
255
+ * `targetName` is the simple (unscoped) name used in within-context refs.
256
+ * `targetScopedId` is the full scoped id, used here only for actor removal
257
+ * from read_models where the actor name equals the `targetName`.
258
+ *
259
+ * Returns true if any changes were made.
260
+ */
261
+ export function cleanDomainItemRef(filePath, depKind, targetName, showDiff = false) {
262
+ if (!existsSync(filePath))
263
+ return false;
264
+ const content = readFileSync(filePath, "utf-8");
265
+ let changed = false;
266
+ if (depKind === "aggregate") {
267
+ const agg = parseYaml(content);
268
+ if (agg.handles?.commands?.includes(targetName)) {
269
+ agg.handles.commands = agg.handles.commands.filter((c) => c !== targetName);
270
+ if (agg.handles.commands.length === 0)
271
+ delete agg.handles;
272
+ changed = true;
273
+ }
274
+ if (agg.emits?.events?.includes(targetName)) {
275
+ agg.emits.events = agg.emits.events.filter((e) => e !== targetName);
276
+ if (agg.emits.events.length === 0)
277
+ delete agg.emits;
278
+ changed = true;
279
+ }
280
+ if (!changed)
281
+ return false;
282
+ const newContent = stringifyYaml(agg);
283
+ if (showDiff)
284
+ printDiff(filePath, content, newContent);
285
+ writeFileSync(filePath, newContent, "utf-8");
286
+ return true;
287
+ }
288
+ if (depKind === "event") {
289
+ const evt = parseYaml(content);
290
+ if (evt.raised_by !== targetName)
291
+ return false;
292
+ delete evt.raised_by;
293
+ const newContent = stringifyYaml(evt);
294
+ if (showDiff)
295
+ printDiff(filePath, content, newContent);
296
+ writeFileSync(filePath, newContent, "utf-8");
297
+ return true;
298
+ }
299
+ if (depKind === "command") {
300
+ const cmd = parseYaml(content);
301
+ if (cmd.handled_by === targetName) {
302
+ delete cmd.handled_by;
303
+ changed = true;
304
+ }
305
+ if (cmd.actor === targetName) {
306
+ delete cmd.actor;
307
+ changed = true;
308
+ }
309
+ if (!changed)
310
+ return false;
311
+ const newContent = stringifyYaml(cmd);
312
+ if (showDiff)
313
+ printDiff(filePath, content, newContent);
314
+ writeFileSync(filePath, newContent, "utf-8");
315
+ return true;
316
+ }
317
+ if (depKind === "policy") {
318
+ const pol = parseYaml(content);
319
+ if (pol.when?.events?.includes(targetName)) {
320
+ pol.when.events = pol.when.events.filter((e) => e !== targetName);
321
+ if (pol.when.events.length === 0)
322
+ delete pol.when;
323
+ changed = true;
324
+ }
325
+ if (pol.then?.commands?.includes(targetName)) {
326
+ pol.then.commands = pol.then.commands.filter((c) => c !== targetName);
327
+ if (pol.then.commands.length === 0)
328
+ delete pol.then;
329
+ changed = true;
330
+ }
331
+ if (!changed)
332
+ return false;
333
+ const newContent = stringifyYaml(pol);
334
+ if (showDiff)
335
+ printDiff(filePath, content, newContent);
336
+ writeFileSync(filePath, newContent, "utf-8");
337
+ return true;
338
+ }
339
+ if (depKind === "read_model") {
340
+ const rm = parseYaml(content);
341
+ if (rm.subscribes_to?.includes(targetName)) {
342
+ rm.subscribes_to = rm.subscribes_to.filter((e) => e !== targetName);
343
+ if (rm.subscribes_to.length === 0)
344
+ delete rm.subscribes_to;
345
+ changed = true;
346
+ }
347
+ if (rm.used_by?.includes(targetName)) {
348
+ rm.used_by = rm.used_by.filter((a) => a !== targetName);
349
+ if (rm.used_by.length === 0)
350
+ delete rm.used_by;
351
+ changed = true;
352
+ }
353
+ if (!changed)
354
+ return false;
355
+ const newContent = stringifyYaml(rm);
356
+ if (showDiff)
357
+ printDiff(filePath, content, newContent);
358
+ writeFileSync(filePath, newContent, "utf-8");
359
+ return true;
360
+ }
361
+ return false;
362
+ }
363
+ /**
364
+ * Update a reference to `oldName` → `newName` in a domain item's YAML fields.
365
+ * Returns true if any changes were made.
366
+ */
367
+ export function renameDomainItemRef(filePath, depKind, oldName, newName, showDiff = false) {
368
+ if (!existsSync(filePath))
369
+ return false;
370
+ const content = readFileSync(filePath, "utf-8");
371
+ let changed = false;
372
+ if (depKind === "aggregate") {
373
+ const agg = parseYaml(content);
374
+ if (agg.handles?.commands) {
375
+ const idx = agg.handles.commands.indexOf(oldName);
376
+ if (idx !== -1) {
377
+ agg.handles.commands[idx] = newName;
378
+ changed = true;
379
+ }
380
+ }
381
+ if (agg.emits?.events) {
382
+ const idx = agg.emits.events.indexOf(oldName);
383
+ if (idx !== -1) {
384
+ agg.emits.events[idx] = newName;
385
+ changed = true;
386
+ }
387
+ }
388
+ if (!changed)
389
+ return false;
390
+ const newContent = stringifyYaml(agg);
391
+ if (showDiff)
392
+ printDiff(filePath, content, newContent);
393
+ writeFileSync(filePath, newContent, "utf-8");
394
+ return true;
395
+ }
396
+ if (depKind === "event") {
397
+ const evt = parseYaml(content);
398
+ if (evt.raised_by !== oldName)
399
+ return false;
400
+ evt.raised_by = newName;
401
+ const newContent = stringifyYaml(evt);
402
+ if (showDiff)
403
+ printDiff(filePath, content, newContent);
404
+ writeFileSync(filePath, newContent, "utf-8");
405
+ return true;
406
+ }
407
+ if (depKind === "command") {
408
+ const cmd = parseYaml(content);
409
+ if (cmd.handled_by === oldName) {
410
+ cmd.handled_by = newName;
411
+ changed = true;
412
+ }
413
+ if (cmd.actor === oldName) {
414
+ cmd.actor = newName;
415
+ changed = true;
416
+ }
417
+ if (!changed)
418
+ return false;
419
+ const newContent = stringifyYaml(cmd);
420
+ if (showDiff)
421
+ printDiff(filePath, content, newContent);
422
+ writeFileSync(filePath, newContent, "utf-8");
423
+ return true;
424
+ }
425
+ if (depKind === "policy") {
426
+ const pol = parseYaml(content);
427
+ if (pol.when?.events) {
428
+ const idx = pol.when.events.indexOf(oldName);
429
+ if (idx !== -1) {
430
+ pol.when.events[idx] = newName;
431
+ changed = true;
432
+ }
433
+ }
434
+ if (pol.then?.commands) {
435
+ const idx = pol.then.commands.indexOf(oldName);
436
+ if (idx !== -1) {
437
+ pol.then.commands[idx] = newName;
438
+ changed = true;
439
+ }
440
+ }
441
+ if (!changed)
442
+ return false;
443
+ const newContent = stringifyYaml(pol);
444
+ if (showDiff)
445
+ printDiff(filePath, content, newContent);
446
+ writeFileSync(filePath, newContent, "utf-8");
447
+ return true;
448
+ }
449
+ if (depKind === "read_model") {
450
+ const rm = parseYaml(content);
451
+ if (rm.subscribes_to) {
452
+ const idx = rm.subscribes_to.indexOf(oldName);
453
+ if (idx !== -1) {
454
+ rm.subscribes_to[idx] = newName;
455
+ changed = true;
456
+ }
457
+ }
458
+ if (rm.used_by) {
459
+ const idx = rm.used_by.indexOf(oldName);
460
+ if (idx !== -1) {
461
+ rm.used_by[idx] = newName;
462
+ changed = true;
463
+ }
464
+ }
465
+ if (!changed)
466
+ return false;
467
+ const newContent = stringifyYaml(rm);
468
+ if (showDiff)
469
+ printDiff(filePath, content, newContent);
470
+ writeFileSync(filePath, newContent, "utf-8");
471
+ return true;
472
+ }
473
+ return false;
474
+ }
475
+ // ── Flow helpers ─────────────────────────────────────────────────────
476
+ /**
477
+ * Remove a flow from index.yml by name. Returns true if removed.
478
+ */
479
+ export function removeFlowFromIndex(root, flowName, showDiff = false) {
480
+ const path = indexFile(root);
481
+ if (!existsSync(path))
482
+ return false;
483
+ const content = readFileSync(path, "utf-8");
484
+ const data = parseYaml(content);
485
+ if (!data.flows?.length)
486
+ return false;
487
+ const before = data.flows.length;
488
+ data.flows = data.flows.filter((f) => f.name !== flowName);
489
+ if (data.flows.length === before)
490
+ return false;
491
+ const newContent = stringifyYaml(data);
492
+ if (showDiff)
493
+ printDiff(path, content, newContent);
494
+ writeFileSync(path, newContent, "utf-8");
495
+ return true;
496
+ }
497
+ /**
498
+ * Rename a flow in index.yml. Returns true if renamed.
499
+ */
500
+ export function renameFlowInIndex(root, oldName, newName, showDiff = false) {
501
+ const path = indexFile(root);
502
+ if (!existsSync(path))
503
+ return false;
504
+ const content = readFileSync(path, "utf-8");
505
+ const data = parseYaml(content);
506
+ const flow = data.flows?.find((f) => f.name === oldName);
507
+ if (!flow)
508
+ return false;
509
+ flow.name = newName;
510
+ const newContent = stringifyYaml(data);
511
+ if (showDiff)
512
+ printDiff(path, content, newContent);
513
+ writeFileSync(path, newContent, "utf-8");
514
+ return true;
515
+ }
516
+ /**
517
+ * Remove all flow steps that reference a given scoped domain item id.
518
+ * Flows that become empty (0 steps) are removed entirely.
519
+ * Returns the number of steps removed.
520
+ */
521
+ export function removeFlowStepsForItem(root, scopedId, showDiff = false) {
522
+ const path = indexFile(root);
523
+ if (!existsSync(path))
524
+ return 0;
525
+ const content = readFileSync(path, "utf-8");
526
+ const data = parseYaml(content);
527
+ if (!data.flows?.length)
528
+ return 0;
529
+ let removed = 0;
530
+ data.flows = data.flows
531
+ .map((flow) => {
532
+ const before = flow.steps.length;
533
+ flow.steps = flow.steps.filter((s) => s.ref !== scopedId);
534
+ removed += before - flow.steps.length;
535
+ return flow;
536
+ })
537
+ .filter((flow) => flow.steps.length > 0);
538
+ if (removed === 0)
539
+ return 0;
540
+ const newContent = stringifyYaml(data);
541
+ if (showDiff)
542
+ printDiff(path, content, newContent);
543
+ writeFileSync(path, newContent, "utf-8");
544
+ return removed;
545
+ }
546
+ /**
547
+ * Remove all flow steps that reference any item in a given context.
548
+ * Flows that become empty are removed entirely.
549
+ */
550
+ export function removeFlowStepsForContext(root, ctxName, showDiff = false) {
551
+ const path = indexFile(root);
552
+ if (!existsSync(path))
553
+ return 0;
554
+ const content = readFileSync(path, "utf-8");
555
+ const data = parseYaml(content);
556
+ if (!data.flows?.length)
557
+ return 0;
558
+ let removed = 0;
559
+ data.flows = data.flows
560
+ .map((flow) => {
561
+ const before = flow.steps.length;
562
+ flow.steps = flow.steps.filter((s) => !s.ref.startsWith(`${ctxName}.`));
563
+ removed += before - flow.steps.length;
564
+ return flow;
565
+ })
566
+ .filter((flow) => flow.steps.length > 0);
567
+ if (removed === 0)
568
+ return 0;
569
+ const newContent = stringifyYaml(data);
570
+ if (showDiff)
571
+ printDiff(path, content, newContent);
572
+ writeFileSync(path, newContent, "utf-8");
573
+ return removed;
574
+ }
575
+ /**
576
+ * Update flow steps that reference an old scoped id to use a new scoped id.
577
+ * Returns the number of steps updated.
578
+ */
579
+ export function updateFlowStepsForItem(root, oldScopedId, newScopedId, showDiff = false) {
580
+ const path = indexFile(root);
581
+ if (!existsSync(path))
582
+ return 0;
583
+ const content = readFileSync(path, "utf-8");
584
+ const data = parseYaml(content);
585
+ if (!data.flows?.length)
586
+ return 0;
587
+ let updated = 0;
588
+ for (const flow of data.flows) {
589
+ for (const step of flow.steps) {
590
+ if (step.ref === oldScopedId) {
591
+ step.ref = newScopedId;
592
+ updated++;
593
+ }
594
+ }
595
+ }
596
+ if (updated === 0)
597
+ return 0;
598
+ const newContent = stringifyYaml(data);
599
+ if (showDiff)
600
+ printDiff(path, content, newContent);
601
+ writeFileSync(path, newContent, "utf-8");
602
+ return updated;
603
+ }
604
+ // ── Context index helpers ─────────────────────────────────────────────
605
+ /**
606
+ * Remove a context entry from index.yml. Returns true if removed.
607
+ */
608
+ export function removeContextFromIndex(root, ctxName, showDiff = false) {
609
+ const path = indexFile(root);
610
+ if (!existsSync(path))
611
+ return false;
612
+ const content = readFileSync(path, "utf-8");
613
+ const data = parseYaml(content);
614
+ const before = data.contexts.length;
615
+ data.contexts = data.contexts.filter((c) => c.name !== ctxName);
616
+ if (data.contexts.length === before)
617
+ return false;
618
+ const newContent = stringifyYaml(data);
619
+ if (showDiff)
620
+ printDiff(path, content, newContent);
621
+ writeFileSync(path, newContent, "utf-8");
622
+ return true;
623
+ }
624
+ /**
625
+ * Rename a context entry in index.yml. Returns true if renamed.
626
+ */
627
+ export function renameContextInIndex(root, oldName, newName, showDiff = false) {
628
+ const path = indexFile(root);
629
+ if (!existsSync(path))
630
+ return false;
631
+ const content = readFileSync(path, "utf-8");
632
+ const data = parseYaml(content);
633
+ const entry = data.contexts.find((c) => c.name === oldName);
634
+ if (!entry)
635
+ return false;
636
+ entry.name = newName;
637
+ // Also update any flow step refs that use old context prefix
638
+ if (data.flows?.length) {
639
+ for (const flow of data.flows) {
640
+ for (const step of flow.steps) {
641
+ if (step.ref.startsWith(`${oldName}.`)) {
642
+ step.ref = (newName + step.ref.slice(oldName.length));
643
+ }
644
+ }
645
+ }
646
+ }
647
+ const newContent = stringifyYaml(data);
648
+ if (showDiff)
649
+ printDiff(path, content, newContent);
650
+ writeFileSync(path, newContent, "utf-8");
651
+ return true;
652
+ }
653
+ //# sourceMappingURL=refactor-helpers.js.map