domain-knowledge-kit 0.2.15 → 0.2.19

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 (210) hide show
  1. package/README.md +4 -0
  2. package/dist/cli.js +24 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/features/agent/commands/init.d.ts +90 -1
  5. package/dist/features/agent/commands/init.d.ts.map +1 -1
  6. package/dist/features/agent/commands/init.js +328 -32
  7. package/dist/features/agent/commands/init.js.map +1 -1
  8. package/dist/features/agent/commands/prime.d.ts +11 -0
  9. package/dist/features/agent/commands/prime.d.ts.map +1 -1
  10. package/dist/features/agent/commands/prime.js +105 -8
  11. package/dist/features/agent/commands/prime.js.map +1 -1
  12. package/dist/features/agent/commands/update.d.ts +27 -0
  13. package/dist/features/agent/commands/update.d.ts.map +1 -0
  14. package/dist/features/agent/commands/update.js +316 -0
  15. package/dist/features/agent/commands/update.js.map +1 -0
  16. package/dist/features/agent/dkk-artifacts.d.ts +76 -0
  17. package/dist/features/agent/dkk-artifacts.d.ts.map +1 -0
  18. package/dist/features/agent/dkk-artifacts.js +328 -0
  19. package/dist/features/agent/dkk-artifacts.js.map +1 -0
  20. package/dist/features/agent/install-mode.d.ts +34 -0
  21. package/dist/features/agent/install-mode.d.ts.map +1 -0
  22. package/dist/features/agent/install-mode.js +78 -0
  23. package/dist/features/agent/install-mode.js.map +1 -0
  24. package/dist/features/agent/mcp-register.d.ts +20 -0
  25. package/dist/features/agent/mcp-register.d.ts.map +1 -0
  26. package/dist/features/agent/mcp-register.js +116 -0
  27. package/dist/features/agent/mcp-register.js.map +1 -0
  28. package/dist/features/agent/settings-prune.d.ts +29 -0
  29. package/dist/features/agent/settings-prune.d.ts.map +1 -0
  30. package/dist/features/agent/settings-prune.js +70 -0
  31. package/dist/features/agent/settings-prune.js.map +1 -0
  32. package/dist/features/agent/tests/settings-prune.test.d.ts +2 -0
  33. package/dist/features/agent/tests/settings-prune.test.d.ts.map +1 -0
  34. package/dist/features/agent/tests/settings-prune.test.js +118 -0
  35. package/dist/features/agent/tests/settings-prune.test.js.map +1 -0
  36. package/dist/features/federation/commands/consumers.d.ts +40 -0
  37. package/dist/features/federation/commands/consumers.d.ts.map +1 -0
  38. package/dist/features/federation/commands/consumers.js +126 -0
  39. package/dist/features/federation/commands/consumers.js.map +1 -0
  40. package/dist/features/federation/commands/peers-add.d.ts +14 -0
  41. package/dist/features/federation/commands/peers-add.d.ts.map +1 -0
  42. package/dist/features/federation/commands/peers-add.js +79 -0
  43. package/dist/features/federation/commands/peers-add.js.map +1 -0
  44. package/dist/features/federation/commands/peers-list.d.ts +8 -0
  45. package/dist/features/federation/commands/peers-list.d.ts.map +1 -0
  46. package/dist/features/federation/commands/peers-list.js +51 -0
  47. package/dist/features/federation/commands/peers-list.js.map +1 -0
  48. package/dist/features/federation/commands/peers-status.d.ts +8 -0
  49. package/dist/features/federation/commands/peers-status.d.ts.map +1 -0
  50. package/dist/features/federation/commands/peers-status.js +78 -0
  51. package/dist/features/federation/commands/peers-status.js.map +1 -0
  52. package/dist/features/federation/commands/pull.d.ts +18 -0
  53. package/dist/features/federation/commands/pull.d.ts.map +1 -0
  54. package/dist/features/federation/commands/pull.js +153 -0
  55. package/dist/features/federation/commands/pull.js.map +1 -0
  56. package/dist/features/federation/git-fetcher.d.ts +45 -0
  57. package/dist/features/federation/git-fetcher.d.ts.map +1 -0
  58. package/dist/features/federation/git-fetcher.js +70 -0
  59. package/dist/features/federation/git-fetcher.js.map +1 -0
  60. package/dist/features/federation/loader.d.ts +60 -0
  61. package/dist/features/federation/loader.d.ts.map +1 -0
  62. package/dist/features/federation/loader.js +193 -0
  63. package/dist/features/federation/loader.js.map +1 -0
  64. package/dist/features/federation/lock.d.ts +12 -0
  65. package/dist/features/federation/lock.d.ts.map +1 -0
  66. package/dist/features/federation/lock.js +48 -0
  67. package/dist/features/federation/lock.js.map +1 -0
  68. package/dist/features/federation/tests/git-fetcher.test.d.ts +2 -0
  69. package/dist/features/federation/tests/git-fetcher.test.d.ts.map +1 -0
  70. package/dist/features/federation/tests/git-fetcher.test.js +167 -0
  71. package/dist/features/federation/tests/git-fetcher.test.js.map +1 -0
  72. package/dist/features/federation/tests/loader.test.d.ts +2 -0
  73. package/dist/features/federation/tests/loader.test.d.ts.map +1 -0
  74. package/dist/features/federation/tests/loader.test.js +144 -0
  75. package/dist/features/federation/tests/loader.test.js.map +1 -0
  76. package/dist/features/federation/tests/phase5.test.d.ts +2 -0
  77. package/dist/features/federation/tests/phase5.test.d.ts.map +1 -0
  78. package/dist/features/federation/tests/phase5.test.js +137 -0
  79. package/dist/features/federation/tests/phase5.test.js.map +1 -0
  80. package/dist/features/federation/tests/schema-load.test.d.ts +2 -0
  81. package/dist/features/federation/tests/schema-load.test.d.ts.map +1 -0
  82. package/dist/features/federation/tests/schema-load.test.js +97 -0
  83. package/dist/features/federation/tests/schema-load.test.js.map +1 -0
  84. package/dist/features/federation/tests/validator.test.d.ts +2 -0
  85. package/dist/features/federation/tests/validator.test.d.ts.map +1 -0
  86. package/dist/features/federation/tests/validator.test.js +319 -0
  87. package/dist/features/federation/tests/validator.test.js.map +1 -0
  88. package/dist/features/mcp/commands/serve.d.ts +10 -0
  89. package/dist/features/mcp/commands/serve.d.ts.map +1 -0
  90. package/dist/features/mcp/commands/serve.js +12 -0
  91. package/dist/features/mcp/commands/serve.js.map +1 -0
  92. package/dist/features/mcp/server.d.ts +15 -0
  93. package/dist/features/mcp/server.d.ts.map +1 -0
  94. package/dist/features/mcp/server.js +438 -0
  95. package/dist/features/mcp/server.js.map +1 -0
  96. package/dist/features/pipeline/commands/validate.d.ts.map +1 -1
  97. package/dist/features/pipeline/commands/validate.js +7 -0
  98. package/dist/features/pipeline/commands/validate.js.map +1 -1
  99. package/dist/features/pipeline/indexer.d.ts +28 -2
  100. package/dist/features/pipeline/indexer.d.ts.map +1 -1
  101. package/dist/features/pipeline/indexer.js +82 -27
  102. package/dist/features/pipeline/indexer.js.map +1 -1
  103. package/dist/features/pipeline/validator.d.ts +10 -0
  104. package/dist/features/pipeline/validator.d.ts.map +1 -1
  105. package/dist/features/pipeline/validator.js +274 -27
  106. package/dist/features/pipeline/validator.js.map +1 -1
  107. package/dist/features/query/commands/list.d.ts +10 -0
  108. package/dist/features/query/commands/list.d.ts.map +1 -1
  109. package/dist/features/query/commands/list.js +1 -1
  110. package/dist/features/query/commands/list.js.map +1 -1
  111. package/dist/features/query/commands/locate.d.ts +1 -0
  112. package/dist/features/query/commands/locate.d.ts.map +1 -1
  113. package/dist/features/query/commands/locate.js +1 -1
  114. package/dist/features/query/commands/locate.js.map +1 -1
  115. package/dist/features/query/commands/search.d.ts.map +1 -1
  116. package/dist/features/query/commands/search.js +2 -0
  117. package/dist/features/query/commands/search.js.map +1 -1
  118. package/dist/features/query/commands/show.d.ts +15 -0
  119. package/dist/features/query/commands/show.d.ts.map +1 -1
  120. package/dist/features/query/commands/show.js +116 -58
  121. package/dist/features/query/commands/show.js.map +1 -1
  122. package/dist/features/query/commands/story.d.ts +70 -0
  123. package/dist/features/query/commands/story.d.ts.map +1 -1
  124. package/dist/features/query/commands/story.js +2 -2
  125. package/dist/features/query/commands/story.js.map +1 -1
  126. package/dist/features/query/commands/summary.d.ts +3 -0
  127. package/dist/features/query/commands/summary.d.ts.map +1 -1
  128. package/dist/features/query/commands/summary.js +1 -1
  129. package/dist/features/query/commands/summary.js.map +1 -1
  130. package/dist/features/query/searcher.d.ts +18 -1
  131. package/dist/features/query/searcher.d.ts.map +1 -1
  132. package/dist/features/query/searcher.js +11 -2
  133. package/dist/features/query/searcher.js.map +1 -1
  134. package/dist/features/scaffold/commands/new-domain.d.ts +22 -0
  135. package/dist/features/scaffold/commands/new-domain.d.ts.map +1 -1
  136. package/dist/features/scaffold/commands/new-domain.js +44 -28
  137. package/dist/features/scaffold/commands/new-domain.js.map +1 -1
  138. package/dist/features/scaffold/commands/service-init.d.ts +12 -0
  139. package/dist/features/scaffold/commands/service-init.d.ts.map +1 -0
  140. package/dist/features/scaffold/commands/service-init.js +69 -0
  141. package/dist/features/scaffold/commands/service-init.js.map +1 -0
  142. package/dist/shared/graph.d.ts +8 -0
  143. package/dist/shared/graph.d.ts.map +1 -1
  144. package/dist/shared/graph.js +180 -112
  145. package/dist/shared/graph.js.map +1 -1
  146. package/dist/shared/index.d.ts +4 -1
  147. package/dist/shared/index.d.ts.map +1 -1
  148. package/dist/shared/index.js +6 -1
  149. package/dist/shared/index.js.map +1 -1
  150. package/dist/shared/loader.d.ts +22 -0
  151. package/dist/shared/loader.d.ts.map +1 -1
  152. package/dist/shared/loader.js +31 -1
  153. package/dist/shared/loader.js.map +1 -1
  154. package/dist/shared/paths.d.ts +59 -7
  155. package/dist/shared/paths.d.ts.map +1 -1
  156. package/dist/shared/paths.js +93 -11
  157. package/dist/shared/paths.js.map +1 -1
  158. package/dist/shared/refs.d.ts +96 -0
  159. package/dist/shared/refs.d.ts.map +1 -0
  160. package/dist/shared/refs.js +182 -0
  161. package/dist/shared/refs.js.map +1 -0
  162. package/dist/shared/service-id.d.ts +11 -0
  163. package/dist/shared/service-id.d.ts.map +1 -0
  164. package/dist/shared/service-id.js +64 -0
  165. package/dist/shared/service-id.js.map +1 -0
  166. package/dist/shared/tests/paths.test.d.ts +2 -0
  167. package/dist/shared/tests/paths.test.d.ts.map +1 -0
  168. package/dist/shared/tests/paths.test.js +111 -0
  169. package/dist/shared/tests/paths.test.js.map +1 -0
  170. package/dist/shared/tests/refs.test.d.ts +2 -0
  171. package/dist/shared/tests/refs.test.d.ts.map +1 -0
  172. package/dist/shared/tests/refs.test.js +104 -0
  173. package/dist/shared/tests/refs.test.js.map +1 -0
  174. package/dist/shared/types/domain.d.ts +14 -0
  175. package/dist/shared/types/domain.d.ts.map +1 -1
  176. package/dist/shared/types/federation.d.ts +60 -0
  177. package/dist/shared/types/federation.d.ts.map +1 -0
  178. package/dist/shared/types/federation.js +12 -0
  179. package/dist/shared/types/federation.js.map +1 -0
  180. package/dist/version.d.ts +4 -0
  181. package/dist/version.d.ts.map +1 -0
  182. package/dist/version.js +15 -0
  183. package/dist/version.js.map +1 -0
  184. package/package.json +8 -5
  185. package/tools/dkk/claude/agents/dkk-domain-reviewer.md +69 -0
  186. package/tools/dkk/claude/commands/dkk-adr.md +11 -0
  187. package/tools/dkk/claude/commands/dkk-impact.md +34 -0
  188. package/tools/dkk/claude/commands/dkk-implement.md +12 -0
  189. package/tools/dkk/claude/commands/dkk-prime.md +6 -0
  190. package/tools/dkk/claude/commands/dkk-review.md +12 -0
  191. package/tools/dkk/claude/commands/dkk-story.md +12 -0
  192. package/tools/dkk/claude/hooks/post-edit-validate.mjs +68 -0
  193. package/tools/dkk/claude/hooks/pre-edit-block-generated.mjs +39 -0
  194. package/tools/dkk/claude/hooks/session-start-prime.mjs +20 -0
  195. package/tools/dkk/claude/hooks/stop-validate.mjs +67 -0
  196. package/tools/dkk/claude/settings.json +62 -0
  197. package/tools/dkk/claude/skills/dkk-adr-author/SKILL.md +54 -0
  198. package/tools/dkk/claude/skills/dkk-flow-implementer/SKILL.md +51 -0
  199. package/tools/dkk/claude/skills/dkk-story-analyst/SKILL.md +108 -0
  200. package/tools/dkk/schema/actors.schema.json +1 -1
  201. package/tools/dkk/schema/adr-frontmatter.schema.json +4 -4
  202. package/tools/dkk/schema/aggregate.schema.json +1 -1
  203. package/tools/dkk/schema/command.schema.json +1 -1
  204. package/tools/dkk/schema/event.schema.json +1 -1
  205. package/tools/dkk/schema/federation.schema.json +71 -0
  206. package/tools/dkk/schema/glossary.schema.json +1 -1
  207. package/tools/dkk/schema/index.schema.json +2 -2
  208. package/tools/dkk/schema/policy.schema.json +1 -1
  209. package/tools/dkk/schema/read-model.schema.json +1 -1
  210. package/tools/dkk/schema/service.schema.json +30 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Register the DKK MCP server with Claude Code, with a fallback for
3
+ * environments where the `claude` CLI is unavailable.
4
+ *
5
+ * Order of attempts:
6
+ *
7
+ * 1. If a `dkk` MCP server is already present in the project's `.mcp.json`,
8
+ * skip — we don't want to overwrite a user's customised entry.
9
+ * 2. If `which claude` succeeds, run `claude mcp add dkk -- dkk mcp`.
10
+ * This writes into the user's Claude config (`~/.claude.json` or
11
+ * equivalent), which is the canonical place for project-agnostic
12
+ * Claude Code config.
13
+ * 3. Fallback: merge `{ "mcpServers": { "dkk": { "command": "dkk", "args": ["mcp"] } } }`
14
+ * into the project's `.mcp.json`, preserving any other servers
15
+ * already registered there.
16
+ *
17
+ * Failures are reported but never throw — `dkk update` continues even if
18
+ * MCP registration can't be completed automatically.
19
+ */
20
+ import { execFileSync, spawnSync } from "node:child_process";
21
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ const SERVER_NAME = "dkk";
24
+ /**
25
+ * Detect existing registration → register via `claude mcp add` → fall back
26
+ * to writing `.mcp.json`. Returns a structured outcome rather than logging
27
+ * so callers can format the result as part of a larger summary.
28
+ */
29
+ export function ensureMcpRegistered(root) {
30
+ if (isRegisteredInProject(root)) {
31
+ return { status: "already-registered", via: "project" };
32
+ }
33
+ if (isRegisteredInClaudeCli()) {
34
+ return { status: "already-registered", via: "claude-cli" };
35
+ }
36
+ if (hasClaudeCli()) {
37
+ const result = spawnSync("claude", ["mcp", "add", SERVER_NAME, "--", "dkk", "mcp"], {
38
+ stdio: ["ignore", "pipe", "pipe"],
39
+ encoding: "utf-8",
40
+ timeout: 20_000,
41
+ });
42
+ if (result.status === 0) {
43
+ return { status: "registered", via: "claude-cli" };
44
+ }
45
+ // Fall through to .mcp.json if `claude mcp add` failed for some reason;
46
+ // we'd rather have a working entry than fail loudly here.
47
+ }
48
+ try {
49
+ writeToMcpJson(root);
50
+ return { status: "registered", via: "mcp-json" };
51
+ }
52
+ catch (err) {
53
+ const msg = err instanceof Error ? err.message : String(err);
54
+ return { status: "failed", reason: msg };
55
+ }
56
+ }
57
+ function isRegisteredInProject(root) {
58
+ const path = join(root, ".mcp.json");
59
+ if (!existsSync(path))
60
+ return false;
61
+ try {
62
+ const config = JSON.parse(readFileSync(path, "utf-8"));
63
+ return Boolean(config.mcpServers && SERVER_NAME in config.mcpServers);
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ function hasClaudeCli() {
70
+ try {
71
+ execFileSync("which", ["claude"], {
72
+ stdio: ["ignore", "pipe", "ignore"],
73
+ timeout: 5_000,
74
+ });
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ function isRegisteredInClaudeCli() {
82
+ if (!hasClaudeCli())
83
+ return false;
84
+ try {
85
+ const out = execFileSync("claude", ["mcp", "list"], {
86
+ stdio: ["ignore", "pipe", "ignore"],
87
+ encoding: "utf-8",
88
+ timeout: 10_000,
89
+ });
90
+ // `claude mcp list` prints one server per line; match a token-bounded
91
+ // `dkk` rather than a substring so we don't false-positive on
92
+ // names like `dkk-extended`.
93
+ return new RegExp(`(^|\\s|:)${SERVER_NAME}(\\s|:|$)`, "m").test(out);
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
99
+ function writeToMcpJson(root) {
100
+ const path = join(root, ".mcp.json");
101
+ let config = {};
102
+ if (existsSync(path)) {
103
+ try {
104
+ config = JSON.parse(readFileSync(path, "utf-8"));
105
+ }
106
+ catch {
107
+ // Corrupt .mcp.json — refuse to clobber it.
108
+ throw new Error(`.mcp.json exists but is not valid JSON; cannot merge dkk entry`);
109
+ }
110
+ }
111
+ if (!config.mcpServers)
112
+ config.mcpServers = {};
113
+ config.mcpServers[SERVER_NAME] = { command: "dkk", args: ["mcp"] };
114
+ writeFileSync(path, JSON.stringify(config, null, 2) + "\n", "utf-8");
115
+ }
116
+ //# sourceMappingURL=mcp-register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-register.js","sourceRoot":"","sources":["../../../src/features/agent/mcp-register.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Prune DKK-owned entries from a Claude `settings.json` payload.
3
+ *
4
+ * Used by `dkk update` to clear stale entries (e.g. hooks for scripts that
5
+ * have been renamed or removed) before re-applying the additive merge with
6
+ * the new template. User-authored entries are preserved untouched.
7
+ *
8
+ * Decision boundaries (see [[dkk-artifacts]] for the predicate source of
9
+ * truth):
10
+ *
11
+ * - A `permissions.allow` entry is DKK-owned iff it appears verbatim in
12
+ * the bundled template's allow list. Exact string equality is the right
13
+ * bar because the template's allow patterns are stable identifiers.
14
+ * - A `hooks.<event>` sub-entry is DKK-owned iff **every** `hooks[].command`
15
+ * inside it resolves via {@link extractHookBasename} to a DKK basename.
16
+ * Mixed entries (some DKK, some user-authored) are left intact and
17
+ * reported as warnings — pruning them partially would mutate
18
+ * user-authored data.
19
+ */
20
+ import { type ClaudeSettings } from "./commands/init.js";
21
+ export interface PruneResult {
22
+ pruned: ClaudeSettings;
23
+ /** Human-readable summary of what was removed. */
24
+ removed: string[];
25
+ /** Hook entries that contained a mix of DKK + user-owned commands; left intact. */
26
+ mixedHookWarnings: string[];
27
+ }
28
+ export declare function pruneDkkEntries(settings: ClaudeSettings, dkkAllow: ReadonlySet<string>, dkkHookBasenames: ReadonlySet<string>): PruneResult;
29
+ //# sourceMappingURL=settings-prune.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-prune.d.ts","sourceRoot":"","sources":["../../../src/features/agent/settings-prune.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,kDAAkD;IAClD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mFAAmF;IACnF,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAC7B,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,GACpC,WAAW,CAmDb"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Prune DKK-owned entries from a Claude `settings.json` payload.
3
+ *
4
+ * Used by `dkk update` to clear stale entries (e.g. hooks for scripts that
5
+ * have been renamed or removed) before re-applying the additive merge with
6
+ * the new template. User-authored entries are preserved untouched.
7
+ *
8
+ * Decision boundaries (see [[dkk-artifacts]] for the predicate source of
9
+ * truth):
10
+ *
11
+ * - A `permissions.allow` entry is DKK-owned iff it appears verbatim in
12
+ * the bundled template's allow list. Exact string equality is the right
13
+ * bar because the template's allow patterns are stable identifiers.
14
+ * - A `hooks.<event>` sub-entry is DKK-owned iff **every** `hooks[].command`
15
+ * inside it resolves via {@link extractHookBasename} to a DKK basename.
16
+ * Mixed entries (some DKK, some user-authored) are left intact and
17
+ * reported as warnings — pruning them partially would mutate
18
+ * user-authored data.
19
+ */
20
+ import { extractHookBasename } from "./commands/init.js";
21
+ export function pruneDkkEntries(settings, dkkAllow, dkkHookBasenames) {
22
+ // Deep-clone so callers can compare before/after without surprise.
23
+ const pruned = JSON.parse(JSON.stringify(settings));
24
+ const removed = [];
25
+ const mixedHookWarnings = [];
26
+ // permissions.allow — drop exact-string matches.
27
+ if (Array.isArray(pruned.permissions?.allow)) {
28
+ const before = pruned.permissions.allow;
29
+ const kept = [];
30
+ for (const entry of before) {
31
+ if (dkkAllow.has(entry)) {
32
+ removed.push(`permissions.allow: ${entry}`);
33
+ }
34
+ else {
35
+ kept.push(entry);
36
+ }
37
+ }
38
+ pruned.permissions.allow = kept;
39
+ }
40
+ // hooks.* — drop entries whose every command resolves to a DKK basename.
41
+ if (pruned.hooks && typeof pruned.hooks === "object") {
42
+ for (const [event, entries] of Object.entries(pruned.hooks)) {
43
+ if (!Array.isArray(entries))
44
+ continue;
45
+ const kept = [];
46
+ for (const entry of entries) {
47
+ const commands = entry.hooks ?? [];
48
+ if (commands.length === 0) {
49
+ kept.push(entry);
50
+ continue;
51
+ }
52
+ const basenames = commands.map((h) => extractHookBasename(h.command));
53
+ const dkkOwned = basenames.filter((b) => b !== null && dkkHookBasenames.has(b));
54
+ const allDkk = basenames.every((b) => b !== null && dkkHookBasenames.has(b));
55
+ const someDkk = dkkOwned.length > 0;
56
+ if (allDkk) {
57
+ removed.push(`hooks.${event}: ${basenames.filter(Boolean).join(", ")}`);
58
+ continue;
59
+ }
60
+ if (someDkk) {
61
+ mixedHookWarnings.push(`hooks.${event}: mixed DKK/user commands left intact (DKK: ${dkkOwned.join(", ")})`);
62
+ }
63
+ kept.push(entry);
64
+ }
65
+ pruned.hooks[event] = kept;
66
+ }
67
+ }
68
+ return { pruned, removed, mixedHookWarnings };
69
+ }
70
+ //# sourceMappingURL=settings-prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-prune.js","sourceRoot":"","sources":["../../../src/features/agent/settings-prune.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=settings-prune.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-prune.test.d.ts","sourceRoot":"","sources":["../../../../src/features/agent/tests/settings-prune.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Tests for the settings.json prune logic used by `dkk update`.
3
+ *
4
+ * Covers the three scenarios the update flow cares about:
5
+ * 1. DKK-only `permissions.allow` entries are removed; user entries stay.
6
+ * 2. Hook entries whose every command is DKK-owned are removed; mixed
7
+ * entries (DKK + user) are kept with a warning.
8
+ * 3. Empty settings are handled without throwing.
9
+ */
10
+ import { pruneDkkEntries } from "../settings-prune.js";
11
+ let passed = 0;
12
+ let failed = 0;
13
+ function assert(label, condition, detail) {
14
+ if (condition) {
15
+ console.log(` OK: ${label}`);
16
+ passed++;
17
+ }
18
+ else {
19
+ console.error(`FAIL: ${label}${detail ? ` — ${detail}` : ""}`);
20
+ failed++;
21
+ }
22
+ }
23
+ // Canonical DKK identifiers (subset — enough to exercise the prune logic).
24
+ const DKK_ALLOW = new Set([
25
+ "Bash(dkk list:*)",
26
+ "Bash(dkk show:*)",
27
+ "Bash(dkk validate:*)",
28
+ ]);
29
+ const DKK_HOOKS = new Set([
30
+ "session-start-prime.mjs",
31
+ "post-edit-validate.mjs",
32
+ ]);
33
+ console.log("\n=== prune: removes only DKK-owned permissions.allow entries ===");
34
+ {
35
+ const input = {
36
+ permissions: {
37
+ allow: [
38
+ "Bash(dkk list:*)", // DKK-owned
39
+ "Bash(dkk show:*)", // DKK-owned
40
+ "Bash(myproject build:*)", // user-authored
41
+ "Bash(custom thing:*)", // user-authored
42
+ ],
43
+ },
44
+ };
45
+ const { pruned, removed } = pruneDkkEntries(input, DKK_ALLOW, DKK_HOOKS);
46
+ assert("user entries preserved", pruned.permissions?.allow?.includes("Bash(myproject build:*)") === true &&
47
+ pruned.permissions?.allow?.includes("Bash(custom thing:*)") === true);
48
+ assert("DKK entries removed from permissions.allow", pruned.permissions?.allow?.includes("Bash(dkk list:*)") === false &&
49
+ pruned.permissions?.allow?.includes("Bash(dkk show:*)") === false);
50
+ assert("removed list reports DKK entries", removed.length === 2);
51
+ }
52
+ console.log("\n=== prune: removes hook entries whose every command is DKK ===");
53
+ {
54
+ const input = {
55
+ hooks: {
56
+ SessionStart: [
57
+ {
58
+ // DKK-only entry — should be dropped.
59
+ hooks: [
60
+ { type: "command", command: 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start-prime.mjs"' },
61
+ ],
62
+ },
63
+ {
64
+ // User-only entry — should be preserved.
65
+ hooks: [{ type: "command", command: "echo user-hook" }],
66
+ },
67
+ ],
68
+ },
69
+ };
70
+ const { pruned } = pruneDkkEntries(input, DKK_ALLOW, DKK_HOOKS);
71
+ const remaining = pruned.hooks?.SessionStart ?? [];
72
+ assert("DKK-only hook entry removed", remaining.length === 1);
73
+ assert("user hook entry preserved", remaining[0]?.hooks?.[0]?.command === "echo user-hook");
74
+ }
75
+ console.log("\n=== prune: leaves mixed hook entries alone with a warning ===");
76
+ {
77
+ const input = {
78
+ hooks: {
79
+ SessionStart: [
80
+ {
81
+ hooks: [
82
+ { type: "command", command: 'node "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start-prime.mjs"' },
83
+ { type: "command", command: "echo also-user-hook" },
84
+ ],
85
+ },
86
+ ],
87
+ },
88
+ };
89
+ const { pruned, mixedHookWarnings } = pruneDkkEntries(input, DKK_ALLOW, DKK_HOOKS);
90
+ assert("mixed entry preserved verbatim", pruned.hooks?.SessionStart?.length === 1);
91
+ assert("mixed warning emitted", mixedHookWarnings.length === 1);
92
+ assert("mixed warning names the DKK hook", mixedHookWarnings[0].includes("session-start-prime.mjs"));
93
+ }
94
+ console.log("\n=== prune: tolerates empty / missing fields ===");
95
+ {
96
+ const empty = {};
97
+ const { pruned, removed, mixedHookWarnings } = pruneDkkEntries(empty, DKK_ALLOW, DKK_HOOKS);
98
+ assert("empty settings → no removed entries", removed.length === 0);
99
+ assert("empty settings → no mixed warnings", mixedHookWarnings.length === 0);
100
+ // Deep clone returns an object that's structurally equivalent (also empty).
101
+ assert("empty settings round-trip", JSON.stringify(pruned) === "{}");
102
+ }
103
+ console.log("\n=== prune: drops empty allow list entries cleanly ===");
104
+ {
105
+ const input = {
106
+ permissions: {
107
+ allow: ["Bash(dkk list:*)", "Bash(dkk show:*)", "Bash(dkk validate:*)"],
108
+ },
109
+ };
110
+ const { pruned, removed } = pruneDkkEntries(input, DKK_ALLOW, DKK_HOOKS);
111
+ assert("all DKK entries removed", pruned.permissions?.allow?.length === 0);
112
+ assert("removed list has 3 entries", removed.length === 3);
113
+ }
114
+ // ── Summary ───────────────────────────────────────────────────────────
115
+ console.log(`\n${passed} passed, ${failed} failed`);
116
+ if (failed > 0)
117
+ process.exit(1);
118
+ //# sourceMappingURL=settings-prune.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-prune.test.js","sourceRoot":"","sources":["../../../../src/features/agent/tests/settings-prune.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `dkk consumers <id>` — reverse lookup across federated peers.
3
+ *
4
+ * Given a local item id (e.g. `ordering.OrderPlaced`), walk every
5
+ * loaded peer model and report references back to that item. This is
6
+ * the producer-side answer to "who breaks if I rename this?".
7
+ *
8
+ * The walk inspects the ref-bearing fields that the validator already
9
+ * understands: events.raised_by, commands.handled_by / actor,
10
+ * aggregates.handles / emits, policies.when / then, read_models.subscribes_to
11
+ * / used_by, ADR domain_refs / superseded_by, and flow step refs.
12
+ *
13
+ * Both fully-qualified peer refs (`<localService>:<ctx>.<Name>`) and
14
+ * the same-service bare form (when a peer happens to have a local
15
+ * service named the same as ours — rare) are matched.
16
+ */
17
+ import type { Command as Cmd } from "commander";
18
+ import type { DomainModel } from "../../../shared/types/domain.js";
19
+ import "../loader.js";
20
+ interface ConsumerHit {
21
+ /** Peer service that references the queried item. */
22
+ service: string;
23
+ /** Reference path describing where in the peer model the match was found. */
24
+ source: string;
25
+ /** The relation kind (e.g. "when.events", "subscribes_to", "domain_refs"). */
26
+ relation: string;
27
+ /** The raw ref string as written in the peer's YAML. */
28
+ ref: string;
29
+ }
30
+ export declare function registerConsumers(program: Cmd): void;
31
+ /**
32
+ * Walk every loaded peer model and collect references back to the
33
+ * queried item. Matches both the fully-qualified peer form
34
+ * (`<localService>:<ctx>.<Name>`) and bare names — the latter handles
35
+ * the case where a peer references the same item via shorthand
36
+ * inside its own walk (uncommon, but possible).
37
+ */
38
+ export declare function findConsumers(model: DomainModel, rawId: string, localService: string | undefined): ConsumerHit[];
39
+ export {};
40
+ //# sourceMappingURL=consumers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumers.d.ts","sourceRoot":"","sources":["../../../../src/features/federation/commands/consumers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAGnE,OAAO,cAAc,CAAC;AAEtB,UAAU,WAAW;IACnB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;CACb;AAQD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI,CAkCpD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,WAAW,EAAE,CAmCf"}
@@ -0,0 +1,126 @@
1
+ import { loadDomainModel } from "../../../shared/loader.js";
2
+ import { parseRef } from "../../../shared/refs.js";
3
+ // Side-effect import: registers the peer-hydration hook with the
4
+ // shared loader so `loadDomainModel` populates `model.peers`.
5
+ import "../loader.js";
6
+ export function registerConsumers(program) {
7
+ program
8
+ .command("consumers <id>")
9
+ .description("List peers that reference this local item (reverse lookup across federation)")
10
+ .option("-r, --root <path>", "Override repository root")
11
+ .option("--json", "Output as JSON")
12
+ .option("--minify", "Minify JSON output")
13
+ .action((id, opts) => {
14
+ const model = loadDomainModel({ root: opts.root });
15
+ const localService = model.service?.name;
16
+ const hits = findConsumers(model, id, localService);
17
+ if (opts.json) {
18
+ console.log(JSON.stringify({ item: id, service: localService ?? null, consumers: hits }, null, opts.minify ? 0 : 2));
19
+ return;
20
+ }
21
+ if (hits.length === 0) {
22
+ console.log(`No peer consumers of "${id}" found in ${model.peers?.size ?? 0} federated peer(s).`);
23
+ return;
24
+ }
25
+ console.log(`${hits.length} reference(s) to "${id}":`);
26
+ for (const h of hits) {
27
+ console.log(` ${h.service} ${h.source} [${h.relation}] → ${h.ref}`);
28
+ }
29
+ });
30
+ }
31
+ /**
32
+ * Walk every loaded peer model and collect references back to the
33
+ * queried item. Matches both the fully-qualified peer form
34
+ * (`<localService>:<ctx>.<Name>`) and bare names — the latter handles
35
+ * the case where a peer references the same item via shorthand
36
+ * inside its own walk (uncommon, but possible).
37
+ */
38
+ export function findConsumers(model, rawId, localService) {
39
+ const hits = [];
40
+ if (!model.peers || model.peers.size === 0)
41
+ return hits;
42
+ const parsed = parseRef(rawId);
43
+ if (!parsed)
44
+ return hits;
45
+ // The forms a peer might use to reference our local item:
46
+ // - Full federated: `<localService>:<ctx>.<Name>` (or `:<ItemName>` shorthand)
47
+ // - Bare: only valid when peer happens to share our service name (rare)
48
+ // We pre-compute the candidate strings to match.
49
+ const matches = new Set();
50
+ if (parsed.kind === "item") {
51
+ if (localService) {
52
+ matches.add(`${localService}:${parsed.context}.${parsed.name}`);
53
+ matches.add(`${localService}:${parsed.name}`);
54
+ }
55
+ matches.add(`${parsed.context}.${parsed.name}`);
56
+ }
57
+ else if (parsed.kind === "adr") {
58
+ if (localService)
59
+ matches.add(`${localService}:${parsed.id}`);
60
+ matches.add(parsed.id);
61
+ }
62
+ else if (parsed.kind === "actor") {
63
+ if (localService)
64
+ matches.add(`${localService}:actor.${parsed.name}`);
65
+ matches.add(`actor.${parsed.name}`);
66
+ }
67
+ for (const [peerName, peerModel] of model.peers) {
68
+ walkRefs(peerModel, peerName, (relation, source, ref) => {
69
+ if (matches.has(ref)) {
70
+ hits.push({ service: peerName, source, relation, ref });
71
+ }
72
+ });
73
+ }
74
+ return hits;
75
+ }
76
+ /**
77
+ * Visit every ref-bearing field in a model, invoking `visit` with
78
+ * (relation, source-path, raw-ref) per occurrence.
79
+ *
80
+ * Intentionally narrow — covers the validator's lookup sites so the
81
+ * reverse view stays consistent with the forward view.
82
+ */
83
+ function walkRefs(model, modelName, visit) {
84
+ for (const [ctxName, ctx] of model.contexts) {
85
+ for (const e of ctx.events ?? []) {
86
+ if (e.raised_by)
87
+ visit("raised_by", `${modelName}:${ctxName}.${e.name}`, e.raised_by);
88
+ }
89
+ for (const c of ctx.commands ?? []) {
90
+ if (c.handled_by)
91
+ visit("handled_by", `${modelName}:${ctxName}.${c.name}`, c.handled_by);
92
+ if (c.actor)
93
+ visit("actor", `${modelName}:${ctxName}.${c.name}`, c.actor);
94
+ }
95
+ for (const a of ctx.aggregates ?? []) {
96
+ for (const h of a.handles?.commands ?? [])
97
+ visit("handles.commands", `${modelName}:${ctxName}.${a.name}`, h);
98
+ for (const ev of a.emits?.events ?? [])
99
+ visit("emits.events", `${modelName}:${ctxName}.${a.name}`, ev);
100
+ }
101
+ for (const p of ctx.policies ?? []) {
102
+ for (const t of p.when?.events ?? [])
103
+ visit("when.events", `${modelName}:${ctxName}.${p.name}`, t);
104
+ for (const t of p.then?.commands ?? [])
105
+ visit("then.commands", `${modelName}:${ctxName}.${p.name}`, t);
106
+ }
107
+ for (const r of ctx.read_models ?? []) {
108
+ for (const s of r.subscribes_to ?? [])
109
+ visit("subscribes_to", `${modelName}:${ctxName}.${r.name}`, s);
110
+ for (const u of r.used_by ?? [])
111
+ visit("used_by", `${modelName}:${ctxName}.${r.name}`, u);
112
+ }
113
+ }
114
+ for (const [adrId, adr] of model.adrs) {
115
+ for (const ref of adr.domain_refs ?? [])
116
+ visit("domain_refs", `${modelName}:${adrId}`, ref);
117
+ if (adr.superseded_by)
118
+ visit("superseded_by", `${modelName}:${adrId}`, adr.superseded_by);
119
+ }
120
+ for (const flow of model.index.flows ?? []) {
121
+ for (const step of flow.steps) {
122
+ visit("flow.steps.ref", `${modelName}:flow.${flow.name}`, step.ref);
123
+ }
124
+ }
125
+ }
126
+ //# sourceMappingURL=consumers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumers.js","sourceRoot":"","sources":["../../../../src/features/federation/commands/consumers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `dkk peers add <name>` — append a peer to .dkk/federation.yml.
3
+ *
4
+ * Two source forms (Phase 2 ships local; --git lands in Phase 3 but
5
+ * is parsed here so the CLI surface is forward-compatible):
6
+ * dkk peers add <name> --local <path>
7
+ * dkk peers add <name> --git <url> --branch <branch> [--git-path <subpath>]
8
+ *
9
+ * Idempotent: re-adding an existing peer with the same source is a
10
+ * no-op; replacing requires --force.
11
+ */
12
+ import type { Command as Cmd } from "commander";
13
+ export declare function registerPeersAdd(parent: Cmd): void;
14
+ //# sourceMappingURL=peers-add.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peers-add.d.ts","sourceRoot":"","sources":["../../../../src/features/federation/commands/peers-add.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,WAAW,CAAC;AA0BhD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAgFlD"}
@@ -0,0 +1,79 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { federationFile } from "../../../shared/paths.js";
4
+ import { parseYaml, stringifyYaml } from "../../../shared/yaml.js";
5
+ function isValidKebab(name) {
6
+ return /^[a-z][a-z0-9-]*$/.test(name);
7
+ }
8
+ export function registerPeersAdd(parent) {
9
+ parent
10
+ .command("add <name>")
11
+ .description("Register a peer service in .dkk/federation.yml")
12
+ .option("--local <path>", "Local filesystem path to the peer's repository root")
13
+ .option("--git <url>", "Git URL of the peer repository")
14
+ .option("--branch <branch>", "Branch to track for git sources", "main")
15
+ .option("--git-path <subpath>", "Sub-path inside the peer repo where .dkk/ lives")
16
+ .option("-r, --root <path>", "Override repository root")
17
+ .option("--force", "Replace an existing entry for this peer")
18
+ .option("--json", "Output as JSON")
19
+ .option("--minify", "Minify JSON output")
20
+ .action((name, opts) => {
21
+ if (!isValidKebab(name)) {
22
+ console.error(`Error: Peer name "${name}" must be kebab-case.`);
23
+ process.exit(1);
24
+ }
25
+ if (!opts.local && !opts.git) {
26
+ console.error("Error: must specify either --local <path> or --git <url>.");
27
+ process.exit(1);
28
+ }
29
+ if (opts.local && opts.git) {
30
+ console.error("Error: --local and --git are mutually exclusive.");
31
+ process.exit(1);
32
+ }
33
+ let source;
34
+ if (opts.local) {
35
+ source = { type: "local", path: opts.local };
36
+ }
37
+ else {
38
+ const branch = opts.branch ?? "main";
39
+ source = { type: "git", url: opts.git, branch };
40
+ if (opts.gitPath)
41
+ source.path = opts.gitPath;
42
+ }
43
+ const path = federationFile(opts.root);
44
+ let manifest;
45
+ if (existsSync(path)) {
46
+ manifest = parseYaml(readFileSync(path, "utf-8"));
47
+ if (!Array.isArray(manifest.peers))
48
+ manifest.peers = [];
49
+ }
50
+ else {
51
+ manifest = { peers: [] };
52
+ mkdirSync(dirname(path), { recursive: true });
53
+ }
54
+ const existingIdx = manifest.peers.findIndex((p) => p.name === name);
55
+ if (existingIdx >= 0 && !opts.force) {
56
+ console.error(`Error: peer "${name}" already exists. Use --force to replace.`);
57
+ process.exit(1);
58
+ }
59
+ const entry = { name, source };
60
+ if (existingIdx >= 0) {
61
+ manifest.peers[existingIdx] = entry;
62
+ }
63
+ else {
64
+ manifest.peers.push(entry);
65
+ }
66
+ const header = "# Federation manifest — peer services to load alongside this repo.\n";
67
+ writeFileSync(path, header + stringifyYaml(manifest), "utf-8");
68
+ if (opts.json) {
69
+ console.log(JSON.stringify({ path, peer: entry, replaced: existingIdx >= 0 }, null, opts.minify ? 0 : 2));
70
+ return;
71
+ }
72
+ const action = existingIdx >= 0 ? "Replaced" : "Added";
73
+ const summary = source.type === "local"
74
+ ? `local: ${source.path}`
75
+ : `git: ${source.url} @ ${source.branch}`;
76
+ console.log(`${action} peer "${name}" (${summary}) in ${path}`);
77
+ });
78
+ }
79
+ //# sourceMappingURL=peers-add.js.map