@momentiq/dark-factory-cli 1.1.0 → 2.1.0
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 +86 -2
- package/dist/adapters/critic-result-schema.d.ts +14 -14
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/static-schema-lint.d.ts +68 -0
- package/dist/adapters/static-schema-lint.d.ts.map +1 -0
- package/dist/adapters/static-schema-lint.js +813 -0
- package/dist/adapters/static-schema-lint.js.map +1 -0
- package/dist/cli.d.ts +17 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +313 -41
- package/dist/cli.js.map +1 -1
- package/dist/commands/findings.d.ts +6 -0
- package/dist/commands/findings.d.ts.map +1 -0
- package/dist/commands/findings.js +196 -0
- package/dist/commands/findings.js.map +1 -0
- package/dist/commands/onboard.d.ts +6 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +111 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/skills.d.ts +6 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +248 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/cycle-doc-validator/validate_cycle_doc.py +131 -60
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +107 -0
- package/dist/doctor.js.map +1 -1
- package/dist/evidence/docker-build.d.ts +6 -0
- package/dist/evidence/docker-build.d.ts.map +1 -0
- package/dist/evidence/docker-build.js +199 -0
- package/dist/evidence/docker-build.js.map +1 -0
- package/dist/evidence/index.d.ts +1 -0
- package/dist/evidence/index.d.ts.map +1 -1
- package/dist/evidence/index.js +6 -0
- package/dist/evidence/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/doctor.d.ts.map +1 -1
- package/dist/mcp/tools/doctor.js +5 -0
- package/dist/mcp/tools/doctor.js.map +1 -1
- package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
- package/dist/mcp/tools/review-bypass.js +21 -0
- package/dist/mcp/tools/review-bypass.js.map +1 -1
- package/dist/mcp/tools/skills-install.d.ts +6 -0
- package/dist/mcp/tools/skills-install.d.ts.map +1 -0
- package/dist/mcp/tools/skills-install.js +260 -0
- package/dist/mcp/tools/skills-install.js.map +1 -0
- package/dist/mcp/tools/stats-gate.d.ts.map +1 -1
- package/dist/mcp/tools/stats-gate.js +63 -15
- package/dist/mcp/tools/stats-gate.js.map +1 -1
- package/dist/onboard/analyze.d.ts +7 -0
- package/dist/onboard/analyze.d.ts.map +1 -0
- package/dist/onboard/analyze.js +86 -0
- package/dist/onboard/analyze.js.map +1 -0
- package/dist/onboard/analyzer.d.ts +15 -0
- package/dist/onboard/analyzer.d.ts.map +1 -0
- package/dist/onboard/analyzer.js +52 -0
- package/dist/onboard/analyzer.js.map +1 -0
- package/dist/onboard/analyzers/ci.d.ts +3 -0
- package/dist/onboard/analyzers/ci.d.ts.map +1 -0
- package/dist/onboard/analyzers/ci.js +112 -0
- package/dist/onboard/analyzers/ci.js.map +1 -0
- package/dist/onboard/analyzers/docs.d.ts +3 -0
- package/dist/onboard/analyzers/docs.d.ts.map +1 -0
- package/dist/onboard/analyzers/docs.js +151 -0
- package/dist/onboard/analyzers/docs.js.map +1 -0
- package/dist/onboard/analyzers/git.d.ts +3 -0
- package/dist/onboard/analyzers/git.d.ts.map +1 -0
- package/dist/onboard/analyzers/git.js +87 -0
- package/dist/onboard/analyzers/git.js.map +1 -0
- package/dist/onboard/analyzers/lockfile.d.ts +3 -0
- package/dist/onboard/analyzers/lockfile.d.ts.map +1 -0
- package/dist/onboard/analyzers/lockfile.js +285 -0
- package/dist/onboard/analyzers/lockfile.js.map +1 -0
- package/dist/onboard/analyzers/manifest.d.ts +3 -0
- package/dist/onboard/analyzers/manifest.d.ts.map +1 -0
- package/dist/onboard/analyzers/manifest.js +228 -0
- package/dist/onboard/analyzers/manifest.js.map +1 -0
- package/dist/onboard/analyzers/tree.d.ts +3 -0
- package/dist/onboard/analyzers/tree.d.ts.map +1 -0
- package/dist/onboard/analyzers/tree.js +196 -0
- package/dist/onboard/analyzers/tree.js.map +1 -0
- package/dist/onboard/fixtures/replay-git-history.d.ts +20 -0
- package/dist/onboard/fixtures/replay-git-history.d.ts.map +1 -0
- package/dist/onboard/fixtures/replay-git-history.js +91 -0
- package/dist/onboard/fixtures/replay-git-history.js.map +1 -0
- package/dist/onboard/schema.d.ts +545 -0
- package/dist/onboard/schema.d.ts.map +1 -0
- package/dist/onboard/schema.js +142 -0
- package/dist/onboard/schema.js.map +1 -0
- package/dist/policy/gate.d.ts +1 -1
- package/dist/policy/gate.d.ts.map +1 -1
- package/dist/policy/gate.js +96 -12
- package/dist/policy/gate.js.map +1 -1
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +105 -1
- package/dist/prompt.js.map +1 -1
- package/dist/report.d.ts +106 -3
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +282 -10
- package/dist/report.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +86 -2
- package/dist/runner.js.map +1 -1
- package/dist/self-consistency.d.ts +144 -0
- package/dist/self-consistency.d.ts.map +1 -0
- package/dist/self-consistency.js +368 -0
- package/dist/self-consistency.js.map +1 -0
- package/dist/skills/config.d.ts +176 -0
- package/dist/skills/config.d.ts.map +1 -0
- package/dist/skills/config.js +251 -0
- package/dist/skills/config.js.map +1 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +8 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/install.d.ts +62 -0
- package/dist/skills/install.d.ts.map +1 -0
- package/dist/skills/install.js +315 -0
- package/dist/skills/install.js.map +1 -0
- package/dist/skills/template.d.ts +42 -0
- package/dist/skills/template.d.ts.map +1 -0
- package/dist/skills/template.js +95 -0
- package/dist/skills/template.js.map +1 -0
- package/dist/trusted-surface/rebind.d.ts.map +1 -1
- package/dist/trusted-surface/rebind.js +14 -1
- package/dist/trusted-surface/rebind.js.map +1 -1
- package/package.json +36 -2
- package/skills/README.md +89 -0
- package/skills/chief-engineer-blitz/SKILL.md.tmpl +443 -0
- package/skills/chief-engineer-blitz/skill.json +53 -0
- package/skills/chief-engineer-review/SKILL.md.tmpl +184 -0
- package/skills/chief-engineer-review/references/review-examples.md.tmpl +130 -0
- package/skills/chief-engineer-review/skill.json +67 -0
- package/skills/chief-engineer-review/templates/code-review-prompt.md.tmpl +111 -0
- package/skills/chief-engineer-review/templates/escalation-prompt.md.tmpl +56 -0
- package/skills/chief-engineer-review/templates/plan-review-prompt.md.tmpl +74 -0
- package/skills/skill-schema.json +73 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// `darkfactory.yaml` — unified consumer config loader.
|
|
2
|
+
//
|
|
3
|
+
// The single config surface for all `df`-installed assets (skills today;
|
|
4
|
+
// future hooks/agents tomorrow). Lives at the consumer repo root as a
|
|
5
|
+
// plain YAML file. Avoids fragmenting per-asset config across multiple
|
|
6
|
+
// files (e.g. `.dark-factory/skills.json`, `.agent-review/extras.json`).
|
|
7
|
+
//
|
|
8
|
+
// Shape (all keys optional — every consumer of the config supplies
|
|
9
|
+
// defaults):
|
|
10
|
+
//
|
|
11
|
+
// repo:
|
|
12
|
+
// displayName: "Dark Factory Platform"
|
|
13
|
+
// slug: "dark-factory-platform"
|
|
14
|
+
// ownerRepo: "momentiq-ai/dark-factory-platform"
|
|
15
|
+
// docs:
|
|
16
|
+
// manifesto: "docs/PRINCIPLES.md"
|
|
17
|
+
// adrDir: "docs/ADR"
|
|
18
|
+
// cycleDocsDir: "docs/roadmap/cycles"
|
|
19
|
+
// rfcDir: "docs/rfcs"
|
|
20
|
+
// prdDir: "docs/prds"
|
|
21
|
+
// agents:
|
|
22
|
+
// chiefEngineer: ".claude/agents/chief-engineer.md"
|
|
23
|
+
// qualityGates:
|
|
24
|
+
// - "make quality-gates"
|
|
25
|
+
// qualityGatesExtras:
|
|
26
|
+
// apiTypes: "make generate-api-types"
|
|
27
|
+
// worktreeRoot: ".claude/worktrees"
|
|
28
|
+
// agentCommitterOrg: "momentiq"
|
|
29
|
+
// skills:
|
|
30
|
+
// chief-engineer-review:
|
|
31
|
+
// enabled: true
|
|
32
|
+
// chief-engineer-blitz:
|
|
33
|
+
// enabled: true
|
|
34
|
+
//
|
|
35
|
+
// Backed by the existing `yaml` workspace dep (already required by other
|
|
36
|
+
// CLI surfaces); zod validates the shape so a malformed config fails closed
|
|
37
|
+
// at load time with a useful error.
|
|
38
|
+
import { execFileSync } from "node:child_process";
|
|
39
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
40
|
+
import { resolve } from "node:path";
|
|
41
|
+
import { parse as parseYaml } from "yaml";
|
|
42
|
+
import { z } from "zod";
|
|
43
|
+
export const CONFIG_FILENAME = "darkfactory.yaml";
|
|
44
|
+
const repoSchema = z
|
|
45
|
+
.object({
|
|
46
|
+
displayName: z.string().optional(),
|
|
47
|
+
slug: z.string().optional(),
|
|
48
|
+
ownerRepo: z.string().optional(),
|
|
49
|
+
})
|
|
50
|
+
.partial()
|
|
51
|
+
.optional();
|
|
52
|
+
const docsSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
manifesto: z.string().optional(),
|
|
55
|
+
adrDir: z.string().optional(),
|
|
56
|
+
cycleDocsDir: z.string().optional(),
|
|
57
|
+
rfcDir: z.string().optional(),
|
|
58
|
+
prdDir: z.string().optional(),
|
|
59
|
+
})
|
|
60
|
+
.partial()
|
|
61
|
+
.optional();
|
|
62
|
+
const agentsSchema = z
|
|
63
|
+
.object({
|
|
64
|
+
chiefEngineer: z.string().optional(),
|
|
65
|
+
})
|
|
66
|
+
.partial()
|
|
67
|
+
.optional();
|
|
68
|
+
const skillEntrySchema = z
|
|
69
|
+
.object({
|
|
70
|
+
enabled: z.boolean().optional(),
|
|
71
|
+
})
|
|
72
|
+
.partial();
|
|
73
|
+
export const DarkFactoryConfigSchema = z
|
|
74
|
+
.object({
|
|
75
|
+
repo: repoSchema,
|
|
76
|
+
docs: docsSchema,
|
|
77
|
+
agents: agentsSchema,
|
|
78
|
+
qualityGates: z.array(z.string()).optional(),
|
|
79
|
+
qualityGatesExtras: z
|
|
80
|
+
.object({
|
|
81
|
+
apiTypes: z.string().optional(),
|
|
82
|
+
})
|
|
83
|
+
.partial()
|
|
84
|
+
.optional(),
|
|
85
|
+
worktreeRoot: z.string().optional(),
|
|
86
|
+
agentCommitterOrg: z.string().optional(),
|
|
87
|
+
skills: z.record(skillEntrySchema).optional(),
|
|
88
|
+
})
|
|
89
|
+
.strict();
|
|
90
|
+
/**
|
|
91
|
+
* Locate + parse `darkfactory.yaml` at `repoRoot`. Returns a defaulted
|
|
92
|
+
* empty config when the file does not exist (so callers can render skills
|
|
93
|
+
* against pure manifest defaults without special-casing the missing file).
|
|
94
|
+
* Throws on parse / schema errors — those are explicit consumer mistakes
|
|
95
|
+
* the install should fail closed on.
|
|
96
|
+
*/
|
|
97
|
+
export function loadDarkFactoryConfig(repoRoot) {
|
|
98
|
+
const configPath = resolve(repoRoot, CONFIG_FILENAME);
|
|
99
|
+
if (!existsSync(configPath)) {
|
|
100
|
+
return {
|
|
101
|
+
config: DarkFactoryConfigSchema.parse({}),
|
|
102
|
+
configPath,
|
|
103
|
+
isDefault: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const raw = readFileSync(configPath, "utf8");
|
|
107
|
+
let parsed;
|
|
108
|
+
try {
|
|
109
|
+
parsed = parseYaml(raw);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
throw new Error(`${CONFIG_FILENAME}: YAML parse error — ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
// Empty file → empty object (yaml parser returns null for a file
|
|
115
|
+
// containing only comments / whitespace; treat that as the empty config).
|
|
116
|
+
const candidate = parsed === null || parsed === undefined ? {} : parsed;
|
|
117
|
+
const result = DarkFactoryConfigSchema.safeParse(candidate);
|
|
118
|
+
if (!result.success) {
|
|
119
|
+
const issues = result.error.issues
|
|
120
|
+
.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`)
|
|
121
|
+
.join("\n");
|
|
122
|
+
throw new Error(`${CONFIG_FILENAME}: schema validation failed:\n${issues}`);
|
|
123
|
+
}
|
|
124
|
+
return { config: result.data, configPath, isDefault: false };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse a git remote URL into `<owner>/<repo>`. Handles both the SSH
|
|
128
|
+
* shorthand (`git@github.com:owner/repo[.git]`) and the HTTPS form
|
|
129
|
+
* (`https://github.com/owner/repo[.git]`). Returns null when neither
|
|
130
|
+
* shape matches — we don't want to populate `OWNER_REPO` from a URL we
|
|
131
|
+
* couldn't confidently parse.
|
|
132
|
+
*/
|
|
133
|
+
export function parseGitRemoteOwnerRepo(remoteUrl) {
|
|
134
|
+
const trimmed = remoteUrl.trim();
|
|
135
|
+
// SSH: git@host:owner/repo[.git]
|
|
136
|
+
const ssh = trimmed.match(/^[^@]+@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
137
|
+
if (ssh)
|
|
138
|
+
return `${ssh[1]}/${ssh[2]}`;
|
|
139
|
+
// HTTPS/HTTP: https://host[:port]/owner/repo[.git]
|
|
140
|
+
const https = trimmed.match(/^https?:\/\/[^/]+\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
141
|
+
if (https)
|
|
142
|
+
return `${https[1]}/${https[2]}`;
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Best-effort inference of `<owner>/<repo>` from the consumer's
|
|
147
|
+
* `origin` git remote. Returns null when the repo is not a git checkout,
|
|
148
|
+
* has no origin, or origin has an unparseable URL. Never throws — the
|
|
149
|
+
* caller treats null as "no inference available, fall back to manifest
|
|
150
|
+
* default".
|
|
151
|
+
*/
|
|
152
|
+
export function inferGitOriginOwnerRepo(repoRoot) {
|
|
153
|
+
let url;
|
|
154
|
+
try {
|
|
155
|
+
url = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
156
|
+
cwd: repoRoot,
|
|
157
|
+
encoding: "utf8",
|
|
158
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
159
|
+
}).trim();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if (url.length === 0)
|
|
165
|
+
return null;
|
|
166
|
+
return parseGitRemoteOwnerRepo(url);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the install-time variable overrides for one skill, given the
|
|
170
|
+
* loaded consumer config. The output is the `overrides` arg to
|
|
171
|
+
* `renderTemplateBody` — a map from variable name to value (scalar string)
|
|
172
|
+
* or values (string[] for kind:"list").
|
|
173
|
+
*
|
|
174
|
+
* Precedence for `OWNER_REPO` / `REPO_SLUG`:
|
|
175
|
+
* 1. yaml `repo.ownerRepo` / `repo.slug` (explicit; wins)
|
|
176
|
+
* 2. git remote inference from `repoRoot` (when supplied)
|
|
177
|
+
* 3. manifest default (renderer falls back when this map omits the key)
|
|
178
|
+
*
|
|
179
|
+
* Variable→config-key mapping is hard-coded here because there is exactly
|
|
180
|
+
* one consumer-config schema. A generic mapping system is not worth the
|
|
181
|
+
* indirection at this size.
|
|
182
|
+
*/
|
|
183
|
+
export function resolveSkillOverrides(options) {
|
|
184
|
+
const { config, repoRoot } = options;
|
|
185
|
+
const overrides = {};
|
|
186
|
+
if (config.repo?.displayName !== undefined) {
|
|
187
|
+
overrides.REPO_NAME = config.repo.displayName;
|
|
188
|
+
}
|
|
189
|
+
if (config.repo?.slug !== undefined) {
|
|
190
|
+
overrides.REPO_SLUG = config.repo.slug;
|
|
191
|
+
}
|
|
192
|
+
if (config.repo?.ownerRepo !== undefined) {
|
|
193
|
+
overrides.OWNER_REPO = config.repo.ownerRepo;
|
|
194
|
+
}
|
|
195
|
+
if ((overrides.OWNER_REPO === undefined || overrides.REPO_SLUG === undefined) &&
|
|
196
|
+
repoRoot !== undefined) {
|
|
197
|
+
const inferred = inferGitOriginOwnerRepo(repoRoot);
|
|
198
|
+
if (inferred !== null) {
|
|
199
|
+
const slashIndex = inferred.indexOf("/");
|
|
200
|
+
if (overrides.OWNER_REPO === undefined) {
|
|
201
|
+
overrides.OWNER_REPO = inferred;
|
|
202
|
+
}
|
|
203
|
+
if (overrides.REPO_SLUG === undefined && slashIndex > 0) {
|
|
204
|
+
overrides.REPO_SLUG = inferred.slice(slashIndex + 1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (config.docs?.manifesto !== undefined) {
|
|
209
|
+
overrides.MANIFESTO_PATH = config.docs.manifesto;
|
|
210
|
+
}
|
|
211
|
+
if (config.docs?.adrDir !== undefined) {
|
|
212
|
+
overrides.ADR_DIR = config.docs.adrDir;
|
|
213
|
+
}
|
|
214
|
+
if (config.docs?.cycleDocsDir !== undefined) {
|
|
215
|
+
overrides.CYCLE_DOCS_DIR = config.docs.cycleDocsDir;
|
|
216
|
+
}
|
|
217
|
+
if (config.docs?.rfcDir !== undefined) {
|
|
218
|
+
overrides.RFC_DIR = config.docs.rfcDir;
|
|
219
|
+
}
|
|
220
|
+
if (config.docs?.prdDir !== undefined) {
|
|
221
|
+
overrides.PRD_DIR = config.docs.prdDir;
|
|
222
|
+
}
|
|
223
|
+
if (config.agents?.chiefEngineer !== undefined) {
|
|
224
|
+
overrides.CE_AGENT_PATH = config.agents.chiefEngineer;
|
|
225
|
+
}
|
|
226
|
+
if (config.qualityGates !== undefined && config.qualityGates.length > 0) {
|
|
227
|
+
overrides.QUALITY_GATE_TARGETS = config.qualityGates;
|
|
228
|
+
}
|
|
229
|
+
if (config.qualityGatesExtras?.apiTypes !== undefined) {
|
|
230
|
+
overrides.API_TYPES_TARGET = config.qualityGatesExtras.apiTypes;
|
|
231
|
+
}
|
|
232
|
+
if (config.worktreeRoot !== undefined) {
|
|
233
|
+
overrides.WORKTREE_ROOT = config.worktreeRoot;
|
|
234
|
+
}
|
|
235
|
+
if (config.agentCommitterOrg !== undefined) {
|
|
236
|
+
overrides.AGENT_COMMITTER_ORG = config.agentCommitterOrg;
|
|
237
|
+
}
|
|
238
|
+
return overrides;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Returns the list of skills the consumer has marked `enabled: true` in
|
|
242
|
+
* `darkfactory.yaml#skills`. Used by `df skills install --all`.
|
|
243
|
+
*/
|
|
244
|
+
export function enabledSkillNames(config) {
|
|
245
|
+
if (!config.skills)
|
|
246
|
+
return [];
|
|
247
|
+
return Object.entries(config.skills)
|
|
248
|
+
.filter(([, entry]) => entry.enabled === true)
|
|
249
|
+
.map(([name]) => name);
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/skills/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { installSkill, listBundledSkills, resolveSkillsRoot, KNOWN_SKILLS, type InstallOptions, type InstallResult, type InstalledFile, type ListedSkill, } from "./install.js";
|
|
2
|
+
export { loadDarkFactoryConfig, resolveSkillOverrides, enabledSkillNames, inferGitOriginOwnerRepo, parseGitRemoteOwnerRepo, DarkFactoryConfigSchema, CONFIG_FILENAME, type DarkFactoryConfig, type LoadedDarkFactoryConfig, type ResolveSkillOverridesOptions, } from "./config.js";
|
|
3
|
+
export { renderTemplateBody, extractReferencedVariables, type SkillManifest, type SkillVariableDef, type RenderResult, type RenderTemplateOptions, type VariableOverride, } from "./template.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/skills/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,GAClC,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Public exports for the `skills` module — consumed by:
|
|
2
|
+
// - `src/cli.ts` (the `df skills install/list` CLI surface)
|
|
3
|
+
// - `src/mcp/tools/skills-install.ts` (the parallel MCP tool surface)
|
|
4
|
+
// - `src/index.ts` (the library export for downstream consumers)
|
|
5
|
+
export { installSkill, listBundledSkills, resolveSkillsRoot, KNOWN_SKILLS, } from "./install.js";
|
|
6
|
+
export { loadDarkFactoryConfig, resolveSkillOverrides, enabledSkillNames, inferGitOriginOwnerRepo, parseGitRemoteOwnerRepo, DarkFactoryConfigSchema, CONFIG_FILENAME, } from "./config.js";
|
|
7
|
+
export { renderTemplateBody, extractReferencedVariables, } from "./template.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/skills/index.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,8DAA8D;AAC9D,wEAAwE;AACxE,mEAAmE;AAEnE,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GAKb,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,GAIhB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,EAClB,0BAA0B,GAM3B,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Names of the skills bundled with this CLI. Adding a new skill = (1) drop
|
|
3
|
+
* the source-of-truth dir under `skills/`, (2) append the name here.
|
|
4
|
+
*/
|
|
5
|
+
export declare const KNOWN_SKILLS: ReadonlyArray<string>;
|
|
6
|
+
export interface InstallOptions {
|
|
7
|
+
/** Consumer repo root. `darkfactory.yaml` is loaded from here. */
|
|
8
|
+
readonly cwd: string;
|
|
9
|
+
/** Skill name (must be in KNOWN_SKILLS). */
|
|
10
|
+
readonly skillName: string;
|
|
11
|
+
/**
|
|
12
|
+
* Override location of the target install dir. Defaults to
|
|
13
|
+
* `<cwd>/.claude/skills/<skillName>/`. Tests override to a temp dir.
|
|
14
|
+
*/
|
|
15
|
+
readonly targetDir?: string;
|
|
16
|
+
/** Force-overwrite hand-edited rendered files. */
|
|
17
|
+
readonly force?: boolean;
|
|
18
|
+
/** Override the bundled-skills source dir (tests). */
|
|
19
|
+
readonly _sourceRoot?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface InstalledFile {
|
|
22
|
+
readonly relTarget: string;
|
|
23
|
+
readonly absoluteTarget: string;
|
|
24
|
+
readonly action: "created" | "updated" | "unchanged" | "skipped";
|
|
25
|
+
/** Present when action = "skipped" — explains why. */
|
|
26
|
+
readonly reason?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface InstallResult {
|
|
29
|
+
readonly skillName: string;
|
|
30
|
+
readonly manifestVersion: string;
|
|
31
|
+
readonly configPath: string;
|
|
32
|
+
readonly configIsDefault: boolean;
|
|
33
|
+
readonly resolvedVariables: ReadonlyArray<{
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly value: string;
|
|
36
|
+
}>;
|
|
37
|
+
readonly files: ReadonlyArray<InstalledFile>;
|
|
38
|
+
}
|
|
39
|
+
export interface ListedSkill {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly version: string;
|
|
42
|
+
readonly summary: string;
|
|
43
|
+
readonly originatingRepo?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the absolute path of the bundled-skills source-of-truth root.
|
|
47
|
+
* In dev (running from src/), the path is `<package>/skills/`. In a
|
|
48
|
+
* published install (running from dist/), the path is the same — the
|
|
49
|
+
* `files` array in package.json carries the `skills/` dir verbatim and
|
|
50
|
+
* `dist/skills/index.js`'s ../../skills resolution lands on it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveSkillsRoot(): string;
|
|
53
|
+
/**
|
|
54
|
+
* List every bundled skill (one-entry per dir under the source root that
|
|
55
|
+
* has a valid skill.json). Used by `df skills list` + `df_skills_list`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function listBundledSkills(sourceRoot?: string): ListedSkill[];
|
|
58
|
+
/**
|
|
59
|
+
* Install one skill into the consumer's `.claude/skills/<name>/`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function installSkill(options: InstallOptions): InstallResult;
|
|
62
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/skills/install.ts"],"names":[],"mappings":"AA8CA;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,MAAM,CAG9C,CAAC;AAUF,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IACjE,sDAAsD;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7F,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAK1C;AA8CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE,CA2BpE;AA6FD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CA0HnE"}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// `df skills install <name>` — install a bundled skill into the consumer's
|
|
2
|
+
// `.claude/skills/<name>/`, rendering all template files against the
|
|
3
|
+
// consumer's `darkfactory.yaml`.
|
|
4
|
+
//
|
|
5
|
+
// Idempotency contract:
|
|
6
|
+
// - Re-running `df skills install <name>` overwrites the rendered files
|
|
7
|
+
// (no merge — the rendered file is a generated artifact).
|
|
8
|
+
// - If the rendered file would change AND it has been hand-edited since
|
|
9
|
+
// the last install (detected via a top-of-file GENERATED header marker),
|
|
10
|
+
// install refuses to overwrite without `--force`.
|
|
11
|
+
// - The generated header carries a hash of the manifest version + the
|
|
12
|
+
// resolved variable set, so a no-op re-run is detectable.
|
|
13
|
+
//
|
|
14
|
+
// Discovery:
|
|
15
|
+
// - The bundled skill source-of-truth lives at `packages/cli/skills/<name>/`
|
|
16
|
+
// in this repo; published, it lives at the equivalent location inside the
|
|
17
|
+
// installed npm package.
|
|
18
|
+
// - `resolveSkillSourceDir()` finds the dir relative to this module's
|
|
19
|
+
// compiled location (works for both src/ in dev and dist/ in production).
|
|
20
|
+
import { createHash, } from "node:crypto";
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, } from "node:fs";
|
|
22
|
+
import { dirname, join, resolve } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { loadDarkFactoryConfig, resolveSkillOverrides, } from "./config.js";
|
|
25
|
+
import { extractReferencedVariables, renderTemplateBody, } from "./template.js";
|
|
26
|
+
/**
|
|
27
|
+
* Names of the skills bundled with this CLI. Adding a new skill = (1) drop
|
|
28
|
+
* the source-of-truth dir under `skills/`, (2) append the name here.
|
|
29
|
+
*/
|
|
30
|
+
export const KNOWN_SKILLS = [
|
|
31
|
+
"chief-engineer-review",
|
|
32
|
+
"chief-engineer-blitz",
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* The HTML-comment marker emitted at the top of every rendered file
|
|
36
|
+
* (after frontmatter for SKILL.md, at the very top for other files). Used
|
|
37
|
+
* to detect hand-edits at re-install time so we can refuse to overwrite
|
|
38
|
+
* unless `--force` is passed.
|
|
39
|
+
*/
|
|
40
|
+
const GENERATED_MARKER = "GENERATED by `df skills install";
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the absolute path of the bundled-skills source-of-truth root.
|
|
43
|
+
* In dev (running from src/), the path is `<package>/skills/`. In a
|
|
44
|
+
* published install (running from dist/), the path is the same — the
|
|
45
|
+
* `files` array in package.json carries the `skills/` dir verbatim and
|
|
46
|
+
* `dist/skills/index.js`'s ../../skills resolution lands on it.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveSkillsRoot() {
|
|
49
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
50
|
+
// src/skills/install.ts → ../../skills (two up: skills → src → package root)
|
|
51
|
+
// dist/skills/install.js → ../../skills (same depth)
|
|
52
|
+
return resolve(here, "..", "..", "skills");
|
|
53
|
+
}
|
|
54
|
+
function readManifest(skillName, sourceRoot) {
|
|
55
|
+
const manifestPath = join(sourceRoot, skillName, "skill.json");
|
|
56
|
+
if (!existsSync(manifestPath)) {
|
|
57
|
+
throw new Error(`df skills install: unknown skill "${skillName}" — no manifest at ${manifestPath}. Known skills: ${KNOWN_SKILLS.join(", ")}.`);
|
|
58
|
+
}
|
|
59
|
+
const raw = readFileSync(manifestPath, "utf8");
|
|
60
|
+
let parsed;
|
|
61
|
+
try {
|
|
62
|
+
parsed = JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new Error(`df skills install: skill.json for "${skillName}" is not valid JSON — ${err.message}`);
|
|
66
|
+
}
|
|
67
|
+
// Light structural check — full schema is in skills/skill-schema.json.
|
|
68
|
+
// We rely on the renderer's per-variable check (catches typos) and the
|
|
69
|
+
// file-write loop (catches missing files); a heavyweight zod parse is
|
|
70
|
+
// overkill for a directory we author + ship ourselves.
|
|
71
|
+
const m = parsed;
|
|
72
|
+
if (typeof m.name !== "string" || typeof m.version !== "string") {
|
|
73
|
+
throw new Error(`df skills install: skill.json for "${skillName}" missing required fields (name, version).`);
|
|
74
|
+
}
|
|
75
|
+
if (m.name !== skillName) {
|
|
76
|
+
throw new Error(`df skills install: skill.json#name "${String(m.name)}" does not match directory name "${skillName}".`);
|
|
77
|
+
}
|
|
78
|
+
if (!Array.isArray(m.files) || m.files.length === 0) {
|
|
79
|
+
throw new Error(`df skills install: skill.json#files for "${skillName}" must be a non-empty array.`);
|
|
80
|
+
}
|
|
81
|
+
if (typeof m.variables !== "object" || m.variables === null) {
|
|
82
|
+
throw new Error(`df skills install: skill.json#variables for "${skillName}" must be an object.`);
|
|
83
|
+
}
|
|
84
|
+
return parsed;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* List every bundled skill (one-entry per dir under the source root that
|
|
88
|
+
* has a valid skill.json). Used by `df skills list` + `df_skills_list`.
|
|
89
|
+
*/
|
|
90
|
+
export function listBundledSkills(sourceRoot) {
|
|
91
|
+
const root = sourceRoot ?? resolveSkillsRoot();
|
|
92
|
+
if (!existsSync(root))
|
|
93
|
+
return [];
|
|
94
|
+
const out = [];
|
|
95
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
96
|
+
if (!entry.isDirectory())
|
|
97
|
+
continue;
|
|
98
|
+
const name = entry.name;
|
|
99
|
+
const manifestPath = join(root, name, "skill.json");
|
|
100
|
+
if (!existsSync(manifestPath))
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
const m = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
104
|
+
const item = {
|
|
105
|
+
name: m.name,
|
|
106
|
+
version: m.version,
|
|
107
|
+
summary: m.summary,
|
|
108
|
+
...(m.originatingRepo !== undefined
|
|
109
|
+
? { originatingRepo: m.originatingRepo }
|
|
110
|
+
: {}),
|
|
111
|
+
};
|
|
112
|
+
out.push(item);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Skip malformed manifests so a single bad dir doesn't break the list.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Stable order — alphabetical by name, regardless of FS readdir order.
|
|
119
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Read an existing rendered file, if it exists, and return the marker line
|
|
124
|
+
* embedded by a previous install (if any) so we can detect hand-edits.
|
|
125
|
+
*/
|
|
126
|
+
function detectExistingMarker(absPath) {
|
|
127
|
+
if (!existsSync(absPath))
|
|
128
|
+
return { present: false, hash: null };
|
|
129
|
+
const body = readFileSync(absPath, "utf8");
|
|
130
|
+
// Marker shape: `# install-hash: <64-hex>` on a line by itself, after the
|
|
131
|
+
// GENERATED prefix line. We match the hash specifically; if the hash isn't
|
|
132
|
+
// present (or doesn't match the freshly-computed one), we treat the file
|
|
133
|
+
// as hand-edited.
|
|
134
|
+
const match = body.match(/install-hash:\s*([0-9a-f]{64})/);
|
|
135
|
+
if (match)
|
|
136
|
+
return { present: true, hash: match[1] ?? null };
|
|
137
|
+
// Marker comment present but no hash → also treat as old format / hand-edited.
|
|
138
|
+
if (body.includes(GENERATED_MARKER))
|
|
139
|
+
return { present: true, hash: null };
|
|
140
|
+
return { present: false, hash: null };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Compute a stable hash of (manifest.version + resolved-variables) so a
|
|
144
|
+
* re-render with identical inputs produces an identical hash. Embedded in
|
|
145
|
+
* the GENERATED header so subsequent installs can skip a no-op rewrite.
|
|
146
|
+
*/
|
|
147
|
+
function computeInstallHash(manifest, resolved) {
|
|
148
|
+
const h = createHash("sha256");
|
|
149
|
+
h.update(manifest.name);
|
|
150
|
+
h.update("\n");
|
|
151
|
+
h.update(manifest.version);
|
|
152
|
+
h.update("\n");
|
|
153
|
+
// Sort for determinism — renderTemplateBody returns in template-encounter
|
|
154
|
+
// order but the hash should not depend on which template referenced the
|
|
155
|
+
// variable first.
|
|
156
|
+
const sorted = [...resolved].sort((a, b) => a.name.localeCompare(b.name));
|
|
157
|
+
for (const entry of sorted) {
|
|
158
|
+
h.update(entry.name);
|
|
159
|
+
h.update("=");
|
|
160
|
+
h.update(entry.value);
|
|
161
|
+
h.update("\n");
|
|
162
|
+
}
|
|
163
|
+
return h.digest("hex");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Inject the GENERATED marker (with install-hash) into a rendered body.
|
|
167
|
+
* For .md files with YAML frontmatter, the marker goes immediately after
|
|
168
|
+
* the closing `---`. For other files, it goes at the top.
|
|
169
|
+
*/
|
|
170
|
+
function injectInstallMarker(body, skillName, installHash) {
|
|
171
|
+
const marker = [
|
|
172
|
+
`<!--`,
|
|
173
|
+
` ${GENERATED_MARKER} ${skillName}\`. Do not hand-edit.`,
|
|
174
|
+
` install-hash: ${installHash}`,
|
|
175
|
+
` Re-render with: df skills install ${skillName}`,
|
|
176
|
+
` Or edit darkfactory.yaml + the upstream template, then re-install.`,
|
|
177
|
+
`-->`,
|
|
178
|
+
``,
|
|
179
|
+
].join("\n");
|
|
180
|
+
// Detect YAML frontmatter (--- ... ---) at file start.
|
|
181
|
+
if (body.startsWith("---\n")) {
|
|
182
|
+
const closing = body.indexOf("\n---", 4);
|
|
183
|
+
if (closing !== -1) {
|
|
184
|
+
const afterFm = closing + "\n---".length;
|
|
185
|
+
const head = body.slice(0, afterFm);
|
|
186
|
+
const tail = body.slice(afterFm);
|
|
187
|
+
// Ensure exactly one blank line between frontmatter and marker.
|
|
188
|
+
const tailTrim = tail.replace(/^\n+/, "");
|
|
189
|
+
return `${head}\n\n${marker}${tailTrim}`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return `${marker}${body}`;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Strip a previously-injected marker block from a body so we can compare
|
|
196
|
+
* the freshly-rendered body to the on-disk body modulo the marker (which
|
|
197
|
+
* carries a hash that changes per-install).
|
|
198
|
+
*/
|
|
199
|
+
function stripInstallMarker(body) {
|
|
200
|
+
// The marker block is a `<!-- … -->` whose first content line starts with
|
|
201
|
+
// the GENERATED prefix. Conservative match — only the first such block.
|
|
202
|
+
const re = /<!--\s*\n\s+GENERATED by `df skills install [^]*?-->\n?/;
|
|
203
|
+
return body.replace(re, "");
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Install one skill into the consumer's `.claude/skills/<name>/`.
|
|
207
|
+
*/
|
|
208
|
+
export function installSkill(options) {
|
|
209
|
+
const sourceRoot = options._sourceRoot ?? resolveSkillsRoot();
|
|
210
|
+
const skillSourceDir = join(sourceRoot, options.skillName);
|
|
211
|
+
if (!existsSync(skillSourceDir) || !statSync(skillSourceDir).isDirectory()) {
|
|
212
|
+
throw new Error(`df skills install: unknown skill "${options.skillName}". Known skills: ${KNOWN_SKILLS.join(", ")}.`);
|
|
213
|
+
}
|
|
214
|
+
const manifest = readManifest(options.skillName, sourceRoot);
|
|
215
|
+
// Load consumer config + resolve overrides.
|
|
216
|
+
const loaded = loadDarkFactoryConfig(options.cwd);
|
|
217
|
+
const config = loaded.config;
|
|
218
|
+
const overrides = resolveSkillOverrides({ config, repoRoot: options.cwd });
|
|
219
|
+
const targetDir = options.targetDir ??
|
|
220
|
+
resolve(options.cwd, ".claude", "skills", options.skillName);
|
|
221
|
+
// First pass: render all files in-memory to compute the install hash
|
|
222
|
+
// from the full resolved-variable set across templates (so the hash
|
|
223
|
+
// changes when ANY variable resolves differently, not just one).
|
|
224
|
+
const renderedNoMarker = [];
|
|
225
|
+
const allVars = new Map();
|
|
226
|
+
for (const fileSpec of manifest.files) {
|
|
227
|
+
const templatePath = join(skillSourceDir, fileSpec.template);
|
|
228
|
+
if (!existsSync(templatePath)) {
|
|
229
|
+
throw new Error(`df skills install: template file missing at ${templatePath} (skill: ${manifest.name}).`);
|
|
230
|
+
}
|
|
231
|
+
const raw = readFileSync(templatePath, "utf8");
|
|
232
|
+
// Lint: every variable referenced must be declared.
|
|
233
|
+
for (const varName of extractReferencedVariables(raw)) {
|
|
234
|
+
if (!(varName in manifest.variables)) {
|
|
235
|
+
throw new Error(`df skills install: template ${fileSpec.template} references undeclared variable "{{${varName}}}" — add to skill.json#variables for skill "${manifest.name}".`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const rendered = renderTemplateBody(raw, { manifest, overrides });
|
|
239
|
+
for (const entry of rendered.substituted) {
|
|
240
|
+
allVars.set(entry.name, entry.value);
|
|
241
|
+
}
|
|
242
|
+
renderedNoMarker.push({
|
|
243
|
+
target: fileSpec.target,
|
|
244
|
+
body: rendered.body,
|
|
245
|
+
substituted: rendered.substituted,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const resolvedVariables = Array.from(allVars, ([name, value]) => ({ name, value }));
|
|
249
|
+
const installHash = computeInstallHash(manifest, resolvedVariables);
|
|
250
|
+
// Second pass: inject marker + write to disk.
|
|
251
|
+
const files = [];
|
|
252
|
+
for (const r of renderedNoMarker) {
|
|
253
|
+
const absTarget = resolve(targetDir, r.target);
|
|
254
|
+
const finalBody = injectInstallMarker(r.body, manifest.name, installHash);
|
|
255
|
+
const existing = detectExistingMarker(absTarget);
|
|
256
|
+
const exists = existsSync(absTarget);
|
|
257
|
+
if (exists && existing.present === false) {
|
|
258
|
+
// File exists but has no marker → hand-edited or pre-template version.
|
|
259
|
+
if (!options.force) {
|
|
260
|
+
files.push({
|
|
261
|
+
relTarget: r.target,
|
|
262
|
+
absoluteTarget: absTarget,
|
|
263
|
+
action: "skipped",
|
|
264
|
+
reason: "file exists without GENERATED marker (hand-edited or pre-template). Re-run with --force to overwrite.",
|
|
265
|
+
});
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (exists && existing.present && existing.hash === installHash) {
|
|
270
|
+
// Hash matches → same manifest version + same resolved vars. Compare
|
|
271
|
+
// the body (modulo the marker) to detect a hand-edit the hash cannot
|
|
272
|
+
// catch: a user can edit the prose without touching the marker, and
|
|
273
|
+
// both `manifest.version` + the resolved-variable set stay identical,
|
|
274
|
+
// so the recomputed `installHash` still matches. Without this guard
|
|
275
|
+
// the next install silently treats the modified body as "unchanged"
|
|
276
|
+
// and the consumer drifts from the source-of-truth template.
|
|
277
|
+
const currentBody = readFileSync(absTarget, "utf8");
|
|
278
|
+
const currentWithoutMarker = stripInstallMarker(currentBody).trim();
|
|
279
|
+
const newWithoutMarker = stripInstallMarker(finalBody).trim();
|
|
280
|
+
if (currentWithoutMarker === newWithoutMarker) {
|
|
281
|
+
files.push({
|
|
282
|
+
relTarget: r.target,
|
|
283
|
+
absoluteTarget: absTarget,
|
|
284
|
+
action: "unchanged",
|
|
285
|
+
});
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!options.force) {
|
|
289
|
+
files.push({
|
|
290
|
+
relTarget: r.target,
|
|
291
|
+
absoluteTarget: absTarget,
|
|
292
|
+
action: "skipped",
|
|
293
|
+
reason: "rendered file body has been hand-edited (install-hash matches but content differs from the upstream template). Re-run with --force to overwrite.",
|
|
294
|
+
});
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
mkdirSync(dirname(absTarget), { recursive: true });
|
|
299
|
+
writeFileSync(absTarget, finalBody, "utf8");
|
|
300
|
+
files.push({
|
|
301
|
+
relTarget: r.target,
|
|
302
|
+
absoluteTarget: absTarget,
|
|
303
|
+
action: exists ? "updated" : "created",
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
skillName: manifest.name,
|
|
308
|
+
manifestVersion: manifest.version,
|
|
309
|
+
configPath: loaded.configPath,
|
|
310
|
+
configIsDefault: loaded.isDefault,
|
|
311
|
+
resolvedVariables,
|
|
312
|
+
files,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=install.js.map
|