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.
- package/README.md +4 -0
- package/dist/cli.js +24 -1
- package/dist/cli.js.map +1 -1
- package/dist/features/agent/commands/init.d.ts +90 -1
- package/dist/features/agent/commands/init.d.ts.map +1 -1
- package/dist/features/agent/commands/init.js +328 -32
- package/dist/features/agent/commands/init.js.map +1 -1
- package/dist/features/agent/commands/prime.d.ts +11 -0
- package/dist/features/agent/commands/prime.d.ts.map +1 -1
- package/dist/features/agent/commands/prime.js +105 -8
- package/dist/features/agent/commands/prime.js.map +1 -1
- package/dist/features/agent/commands/update.d.ts +27 -0
- package/dist/features/agent/commands/update.d.ts.map +1 -0
- package/dist/features/agent/commands/update.js +316 -0
- package/dist/features/agent/commands/update.js.map +1 -0
- package/dist/features/agent/dkk-artifacts.d.ts +76 -0
- package/dist/features/agent/dkk-artifacts.d.ts.map +1 -0
- package/dist/features/agent/dkk-artifacts.js +328 -0
- package/dist/features/agent/dkk-artifacts.js.map +1 -0
- package/dist/features/agent/install-mode.d.ts +34 -0
- package/dist/features/agent/install-mode.d.ts.map +1 -0
- package/dist/features/agent/install-mode.js +78 -0
- package/dist/features/agent/install-mode.js.map +1 -0
- package/dist/features/agent/mcp-register.d.ts +20 -0
- package/dist/features/agent/mcp-register.d.ts.map +1 -0
- package/dist/features/agent/mcp-register.js +116 -0
- package/dist/features/agent/mcp-register.js.map +1 -0
- package/dist/features/agent/settings-prune.d.ts +29 -0
- package/dist/features/agent/settings-prune.d.ts.map +1 -0
- package/dist/features/agent/settings-prune.js +70 -0
- package/dist/features/agent/settings-prune.js.map +1 -0
- package/dist/features/agent/tests/settings-prune.test.d.ts +2 -0
- package/dist/features/agent/tests/settings-prune.test.d.ts.map +1 -0
- package/dist/features/agent/tests/settings-prune.test.js +118 -0
- package/dist/features/agent/tests/settings-prune.test.js.map +1 -0
- package/dist/features/federation/commands/consumers.d.ts +40 -0
- package/dist/features/federation/commands/consumers.d.ts.map +1 -0
- package/dist/features/federation/commands/consumers.js +126 -0
- package/dist/features/federation/commands/consumers.js.map +1 -0
- package/dist/features/federation/commands/peers-add.d.ts +14 -0
- package/dist/features/federation/commands/peers-add.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-add.js +79 -0
- package/dist/features/federation/commands/peers-add.js.map +1 -0
- package/dist/features/federation/commands/peers-list.d.ts +8 -0
- package/dist/features/federation/commands/peers-list.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-list.js +51 -0
- package/dist/features/federation/commands/peers-list.js.map +1 -0
- package/dist/features/federation/commands/peers-status.d.ts +8 -0
- package/dist/features/federation/commands/peers-status.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-status.js +78 -0
- package/dist/features/federation/commands/peers-status.js.map +1 -0
- package/dist/features/federation/commands/pull.d.ts +18 -0
- package/dist/features/federation/commands/pull.d.ts.map +1 -0
- package/dist/features/federation/commands/pull.js +153 -0
- package/dist/features/federation/commands/pull.js.map +1 -0
- package/dist/features/federation/git-fetcher.d.ts +45 -0
- package/dist/features/federation/git-fetcher.d.ts.map +1 -0
- package/dist/features/federation/git-fetcher.js +70 -0
- package/dist/features/federation/git-fetcher.js.map +1 -0
- package/dist/features/federation/loader.d.ts +60 -0
- package/dist/features/federation/loader.d.ts.map +1 -0
- package/dist/features/federation/loader.js +193 -0
- package/dist/features/federation/loader.js.map +1 -0
- package/dist/features/federation/lock.d.ts +12 -0
- package/dist/features/federation/lock.d.ts.map +1 -0
- package/dist/features/federation/lock.js +48 -0
- package/dist/features/federation/lock.js.map +1 -0
- package/dist/features/federation/tests/git-fetcher.test.d.ts +2 -0
- package/dist/features/federation/tests/git-fetcher.test.d.ts.map +1 -0
- package/dist/features/federation/tests/git-fetcher.test.js +167 -0
- package/dist/features/federation/tests/git-fetcher.test.js.map +1 -0
- package/dist/features/federation/tests/loader.test.d.ts +2 -0
- package/dist/features/federation/tests/loader.test.d.ts.map +1 -0
- package/dist/features/federation/tests/loader.test.js +144 -0
- package/dist/features/federation/tests/loader.test.js.map +1 -0
- package/dist/features/federation/tests/phase5.test.d.ts +2 -0
- package/dist/features/federation/tests/phase5.test.d.ts.map +1 -0
- package/dist/features/federation/tests/phase5.test.js +137 -0
- package/dist/features/federation/tests/phase5.test.js.map +1 -0
- package/dist/features/federation/tests/schema-load.test.d.ts +2 -0
- package/dist/features/federation/tests/schema-load.test.d.ts.map +1 -0
- package/dist/features/federation/tests/schema-load.test.js +97 -0
- package/dist/features/federation/tests/schema-load.test.js.map +1 -0
- package/dist/features/federation/tests/validator.test.d.ts +2 -0
- package/dist/features/federation/tests/validator.test.d.ts.map +1 -0
- package/dist/features/federation/tests/validator.test.js +319 -0
- package/dist/features/federation/tests/validator.test.js.map +1 -0
- package/dist/features/mcp/commands/serve.d.ts +10 -0
- package/dist/features/mcp/commands/serve.d.ts.map +1 -0
- package/dist/features/mcp/commands/serve.js +12 -0
- package/dist/features/mcp/commands/serve.js.map +1 -0
- package/dist/features/mcp/server.d.ts +15 -0
- package/dist/features/mcp/server.d.ts.map +1 -0
- package/dist/features/mcp/server.js +438 -0
- package/dist/features/mcp/server.js.map +1 -0
- package/dist/features/pipeline/commands/validate.d.ts.map +1 -1
- package/dist/features/pipeline/commands/validate.js +7 -0
- package/dist/features/pipeline/commands/validate.js.map +1 -1
- package/dist/features/pipeline/indexer.d.ts +28 -2
- package/dist/features/pipeline/indexer.d.ts.map +1 -1
- package/dist/features/pipeline/indexer.js +82 -27
- package/dist/features/pipeline/indexer.js.map +1 -1
- package/dist/features/pipeline/validator.d.ts +10 -0
- package/dist/features/pipeline/validator.d.ts.map +1 -1
- package/dist/features/pipeline/validator.js +274 -27
- package/dist/features/pipeline/validator.js.map +1 -1
- package/dist/features/query/commands/list.d.ts +10 -0
- package/dist/features/query/commands/list.d.ts.map +1 -1
- package/dist/features/query/commands/list.js +1 -1
- package/dist/features/query/commands/list.js.map +1 -1
- package/dist/features/query/commands/locate.d.ts +1 -0
- package/dist/features/query/commands/locate.d.ts.map +1 -1
- package/dist/features/query/commands/locate.js +1 -1
- package/dist/features/query/commands/locate.js.map +1 -1
- package/dist/features/query/commands/search.d.ts.map +1 -1
- package/dist/features/query/commands/search.js +2 -0
- package/dist/features/query/commands/search.js.map +1 -1
- package/dist/features/query/commands/show.d.ts +15 -0
- package/dist/features/query/commands/show.d.ts.map +1 -1
- package/dist/features/query/commands/show.js +116 -58
- package/dist/features/query/commands/show.js.map +1 -1
- package/dist/features/query/commands/story.d.ts +70 -0
- package/dist/features/query/commands/story.d.ts.map +1 -1
- package/dist/features/query/commands/story.js +2 -2
- package/dist/features/query/commands/story.js.map +1 -1
- package/dist/features/query/commands/summary.d.ts +3 -0
- package/dist/features/query/commands/summary.d.ts.map +1 -1
- package/dist/features/query/commands/summary.js +1 -1
- package/dist/features/query/commands/summary.js.map +1 -1
- package/dist/features/query/searcher.d.ts +18 -1
- package/dist/features/query/searcher.d.ts.map +1 -1
- package/dist/features/query/searcher.js +11 -2
- package/dist/features/query/searcher.js.map +1 -1
- package/dist/features/scaffold/commands/new-domain.d.ts +22 -0
- package/dist/features/scaffold/commands/new-domain.d.ts.map +1 -1
- package/dist/features/scaffold/commands/new-domain.js +44 -28
- package/dist/features/scaffold/commands/new-domain.js.map +1 -1
- package/dist/features/scaffold/commands/service-init.d.ts +12 -0
- package/dist/features/scaffold/commands/service-init.d.ts.map +1 -0
- package/dist/features/scaffold/commands/service-init.js +69 -0
- package/dist/features/scaffold/commands/service-init.js.map +1 -0
- package/dist/shared/graph.d.ts +8 -0
- package/dist/shared/graph.d.ts.map +1 -1
- package/dist/shared/graph.js +180 -112
- package/dist/shared/graph.js.map +1 -1
- package/dist/shared/index.d.ts +4 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +6 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/loader.d.ts +22 -0
- package/dist/shared/loader.d.ts.map +1 -1
- package/dist/shared/loader.js +31 -1
- package/dist/shared/loader.js.map +1 -1
- package/dist/shared/paths.d.ts +59 -7
- package/dist/shared/paths.d.ts.map +1 -1
- package/dist/shared/paths.js +93 -11
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/refs.d.ts +96 -0
- package/dist/shared/refs.d.ts.map +1 -0
- package/dist/shared/refs.js +182 -0
- package/dist/shared/refs.js.map +1 -0
- package/dist/shared/service-id.d.ts +11 -0
- package/dist/shared/service-id.d.ts.map +1 -0
- package/dist/shared/service-id.js +64 -0
- package/dist/shared/service-id.js.map +1 -0
- package/dist/shared/tests/paths.test.d.ts +2 -0
- package/dist/shared/tests/paths.test.d.ts.map +1 -0
- package/dist/shared/tests/paths.test.js +111 -0
- package/dist/shared/tests/paths.test.js.map +1 -0
- package/dist/shared/tests/refs.test.d.ts +2 -0
- package/dist/shared/tests/refs.test.d.ts.map +1 -0
- package/dist/shared/tests/refs.test.js +104 -0
- package/dist/shared/tests/refs.test.js.map +1 -0
- package/dist/shared/types/domain.d.ts +14 -0
- package/dist/shared/types/domain.d.ts.map +1 -1
- package/dist/shared/types/federation.d.ts +60 -0
- package/dist/shared/types/federation.d.ts.map +1 -0
- package/dist/shared/types/federation.js +12 -0
- package/dist/shared/types/federation.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +8 -5
- package/tools/dkk/claude/agents/dkk-domain-reviewer.md +69 -0
- package/tools/dkk/claude/commands/dkk-adr.md +11 -0
- package/tools/dkk/claude/commands/dkk-impact.md +34 -0
- package/tools/dkk/claude/commands/dkk-implement.md +12 -0
- package/tools/dkk/claude/commands/dkk-prime.md +6 -0
- package/tools/dkk/claude/commands/dkk-review.md +12 -0
- package/tools/dkk/claude/commands/dkk-story.md +12 -0
- package/tools/dkk/claude/hooks/post-edit-validate.mjs +68 -0
- package/tools/dkk/claude/hooks/pre-edit-block-generated.mjs +39 -0
- package/tools/dkk/claude/hooks/session-start-prime.mjs +20 -0
- package/tools/dkk/claude/hooks/stop-validate.mjs +67 -0
- package/tools/dkk/claude/settings.json +62 -0
- package/tools/dkk/claude/skills/dkk-adr-author/SKILL.md +54 -0
- package/tools/dkk/claude/skills/dkk-flow-implementer/SKILL.md +51 -0
- package/tools/dkk/claude/skills/dkk-story-analyst/SKILL.md +108 -0
- package/tools/dkk/schema/actors.schema.json +1 -1
- package/tools/dkk/schema/adr-frontmatter.schema.json +4 -4
- package/tools/dkk/schema/aggregate.schema.json +1 -1
- package/tools/dkk/schema/command.schema.json +1 -1
- package/tools/dkk/schema/event.schema.json +1 -1
- package/tools/dkk/schema/federation.schema.json +71 -0
- package/tools/dkk/schema/glossary.schema.json +1 -1
- package/tools/dkk/schema/index.schema.json +2 -2
- package/tools/dkk/schema/policy.schema.json +1 -1
- package/tools/dkk/schema/read-model.schema.json +1 -1
- 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 @@
|
|
|
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
|