agentic-skill-mill 1.0.3

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 (71) hide show
  1. package/README.md +135 -0
  2. package/dist/cache/cache-manager.d.ts +27 -0
  3. package/dist/cache/cache-manager.d.ts.map +1 -0
  4. package/dist/cache/cache-manager.js +139 -0
  5. package/dist/cache/cache-manager.js.map +1 -0
  6. package/dist/cli/commands/check-exports.d.ts +6 -0
  7. package/dist/cli/commands/check-exports.d.ts.map +1 -0
  8. package/dist/cli/commands/check-exports.js +7 -0
  9. package/dist/cli/commands/check-exports.js.map +1 -0
  10. package/dist/cli/commands/list-project.d.ts +6 -0
  11. package/dist/cli/commands/list-project.d.ts.map +1 -0
  12. package/dist/cli/commands/list-project.js +7 -0
  13. package/dist/cli/commands/list-project.js.map +1 -0
  14. package/dist/cli/commands/scaffold.d.ts +6 -0
  15. package/dist/cli/commands/scaffold.d.ts.map +1 -0
  16. package/dist/cli/commands/scaffold.js +11 -0
  17. package/dist/cli/commands/scaffold.js.map +1 -0
  18. package/dist/cli/commands/validate-manifest.d.ts +6 -0
  19. package/dist/cli/commands/validate-manifest.d.ts.map +1 -0
  20. package/dist/cli/commands/validate-manifest.js +7 -0
  21. package/dist/cli/commands/validate-manifest.js.map +1 -0
  22. package/dist/cli/commands/validate-skill.d.ts +6 -0
  23. package/dist/cli/commands/validate-skill.d.ts.map +1 -0
  24. package/dist/cli/commands/validate-skill.js +8 -0
  25. package/dist/cli/commands/validate-skill.js.map +1 -0
  26. package/dist/cli/index.d.ts +3 -0
  27. package/dist/cli/index.d.ts.map +1 -0
  28. package/dist/cli/index.js +268 -0
  29. package/dist/cli/index.js.map +1 -0
  30. package/dist/cli/output-formatter.d.ts +6 -0
  31. package/dist/cli/output-formatter.d.ts.map +1 -0
  32. package/dist/cli/output-formatter.js +19 -0
  33. package/dist/cli/output-formatter.js.map +1 -0
  34. package/dist/core/check-exports.d.ts +21 -0
  35. package/dist/core/check-exports.d.ts.map +1 -0
  36. package/dist/core/check-exports.js +67 -0
  37. package/dist/core/check-exports.js.map +1 -0
  38. package/dist/core/list-project.d.ts +31 -0
  39. package/dist/core/list-project.d.ts.map +1 -0
  40. package/dist/core/list-project.js +118 -0
  41. package/dist/core/list-project.js.map +1 -0
  42. package/dist/core/scaffold.d.ts +24 -0
  43. package/dist/core/scaffold.d.ts.map +1 -0
  44. package/dist/core/scaffold.js +168 -0
  45. package/dist/core/scaffold.js.map +1 -0
  46. package/dist/core/validate-manifest.d.ts +21 -0
  47. package/dist/core/validate-manifest.d.ts.map +1 -0
  48. package/dist/core/validate-manifest.js +151 -0
  49. package/dist/core/validate-manifest.js.map +1 -0
  50. package/dist/core/validate-skill.d.ts +24 -0
  51. package/dist/core/validate-skill.d.ts.map +1 -0
  52. package/dist/core/validate-skill.js +149 -0
  53. package/dist/core/validate-skill.js.map +1 -0
  54. package/dist/errors/types.d.ts +19 -0
  55. package/dist/errors/types.d.ts.map +1 -0
  56. package/dist/errors/types.js +40 -0
  57. package/dist/errors/types.js.map +1 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +13 -0
  61. package/dist/index.js.map +1 -0
  62. package/package.json +56 -0
  63. package/skill/build/compile.mjs +385 -0
  64. package/skill/build/manifest.json +29 -0
  65. package/skill/fragments/meta/architecture-overview.md +37 -0
  66. package/skill/fragments/meta/cli-command-pattern.md +83 -0
  67. package/skill/fragments/meta/core-module-pattern.md +38 -0
  68. package/skill/fragments/meta/fragment-composition.md +40 -0
  69. package/skill/fragments/meta/rename-workflow.md +51 -0
  70. package/skill/fragments/meta/research-to-code.md +44 -0
  71. package/skill/skills/agentic-skill-mill/agentic-skill-mill.md +133 -0
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Core domain exports
2
+ export { validateManifest } from './core/validate-manifest.js';
3
+ export { validateSkill } from './core/validate-skill.js';
4
+ export { listProject } from './core/list-project.js';
5
+ export { scaffold } from './core/scaffold.js';
6
+ export { checkExports } from './core/check-exports.js';
7
+ // Cache exports
8
+ export { CacheManager } from './cache/cache-manager.js';
9
+ // Error exports
10
+ export { AppError, NotFoundError, CommandError, CacheError, ConfigError, } from './errors/types.js';
11
+ // CLI exports
12
+ export { OutputFormatter } from './cli/output-formatter.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sBAAsB;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/D,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGzD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGrD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,gBAAgB;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,gBAAgB;AAChB,OAAO,EACL,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAE3B,cAAc;AACd,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "agentic-skill-mill",
3
+ "version": "1.0.3",
4
+ "description": "Forge and refine agent skill projects -- fragment-composed skills compiled to 7 IDE targets with a companion CLI",
5
+ "type": "module",
6
+ "bin": {
7
+ "skillmill": "dist/cli/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "skill",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:coverage": "vitest run --coverage",
27
+ "lint": "eslint src",
28
+ "typecheck": "tsc --noEmit",
29
+ "setup": "bash install.sh",
30
+ "setup:all": "bash install.sh --all",
31
+ "install-skills": "bash install.sh --skills-only",
32
+ "uninstall-skills": "bash install.sh --uninstall",
33
+ "compile": "node skill/build/compile.mjs",
34
+ "compile:validate": "node skill/build/compile.mjs --validate",
35
+ "compile:watch": "node skill/build/compile.mjs --watch"
36
+ },
37
+ "dependencies": {
38
+ "chalk": "^5.3.0",
39
+ "commander": "^11.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.10.0",
43
+ "@typescript-eslint/eslint-plugin": "^8.58.0",
44
+ "@typescript-eslint/parser": "^8.58.0",
45
+ "@vitest/coverage-v8": "^4.1.0",
46
+ "eslint": "^8.57.0",
47
+ "typescript": "^5.3.0",
48
+ "vitest": "^4.1.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Skill compiler: resolves {{include:...}} markers, compiles to IDE-specific
5
+ * targets, and validates the output.
6
+ *
7
+ * This compiler is the core of the fragment-composition architecture.
8
+ * It is intentionally a single self-contained file with no runtime
9
+ * dependencies beyond Node.js builtins.
10
+ *
11
+ * Usage:
12
+ * node skill/build/compile.mjs # compile all
13
+ * node skill/build/compile.mjs --targets claude,cursor # specific targets
14
+ * node skill/build/compile.mjs --validate # validate only
15
+ * node skill/build/compile.mjs --watch # recompile on change
16
+ */
17
+
18
+ import { readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, statSync, existsSync, watch as fsWatch } from 'node:fs';
19
+ import { join, dirname, resolve, relative } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+ const SKILL_ROOT = resolve(__dirname, '..');
25
+ const PROJECT_ROOT = resolve(SKILL_ROOT, '..');
26
+ const FRAGMENTS_DIR = join(SKILL_ROOT, 'fragments');
27
+ const COMPILED_DIR = join(PROJECT_ROOT, 'compiled');
28
+ const MANIFEST_PATH = join(__dirname, 'manifest.json');
29
+
30
+ const MANAGED_BY = 'managed_by: agentic-skill-mill';
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // CLI
34
+ // ---------------------------------------------------------------------------
35
+
36
+ const args = process.argv.slice(2);
37
+ const validateOnly = args.includes('--validate');
38
+ const watchMode = args.includes('--watch');
39
+ const targetFlag = args.find(a => a.startsWith('--targets=')) || args[args.indexOf('--targets') + 1];
40
+ const requestedTargets = targetFlag ? targetFlag.replace('--targets=', '').split(',').map(t => t.trim()) : null;
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Load manifest
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const manifest = JSON.parse(readFileSync(MANIFEST_PATH, 'utf-8'));
47
+ const targets = requestedTargets || manifest.targets;
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Step 1: Resolve includes
51
+ // ---------------------------------------------------------------------------
52
+
53
+ function resolveIncludes(content, sourceFile) {
54
+ const includePattern = /\{\{include:([^}]+)\}\}/g;
55
+ const errors = [];
56
+
57
+ const resolved = content.replace(includePattern, (match, fragmentPath) => {
58
+ const fullPath = join(FRAGMENTS_DIR, fragmentPath.trim());
59
+ if (!existsSync(fullPath)) {
60
+ errors.push(`Missing fragment: ${fragmentPath} (referenced in ${sourceFile})`);
61
+ return match;
62
+ }
63
+ const fragmentContent = readFileSync(fullPath, 'utf-8').trimEnd();
64
+
65
+ if (/\{\{include:[^}]+\}\}/.test(fragmentContent)) {
66
+ errors.push(`Nested include in fragment: ${fragmentPath} (referenced in ${sourceFile}). Only one level of includes is supported.`);
67
+ return match;
68
+ }
69
+
70
+ return fragmentContent;
71
+ });
72
+
73
+ return { resolved, errors };
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Step 2: Frontmatter transforms
78
+ // ---------------------------------------------------------------------------
79
+
80
+ function extractFrontmatter(content) {
81
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
82
+ if (!match) return { frontmatter: {}, body: content };
83
+
84
+ const fm = {};
85
+ for (const line of match[1].split('\n')) {
86
+ const idx = line.indexOf(':');
87
+ if (idx === -1) continue;
88
+ const key = line.slice(0, idx).trim();
89
+ let val = line.slice(idx + 1).trim();
90
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
91
+ val = val.slice(1, -1);
92
+ }
93
+ fm[key] = val;
94
+ }
95
+
96
+ return { frontmatter: fm, body: match[2] };
97
+ }
98
+
99
+ function buildFrontmatter(fields) {
100
+ const lines = ['---'];
101
+ lines.push(MANAGED_BY);
102
+ for (const [k, v] of Object.entries(fields)) {
103
+ if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
104
+ lines.push(`${k}:`);
105
+ for (const [nk, nv] of Object.entries(v)) {
106
+ lines.push(` ${nk}: ${nv}`);
107
+ }
108
+ } else if (typeof v === 'string' && (v.includes(':') || v.includes('"') || v.includes("'"))) {
109
+ lines.push(`${k}: "${v.replace(/"/g, '\\"')}"`);
110
+ } else {
111
+ lines.push(`${k}: ${v}`);
112
+ }
113
+ }
114
+ lines.push('---');
115
+ return lines.join('\n');
116
+ }
117
+
118
+ function compileTarget(targetName, skillName, resolvedContent) {
119
+ const { frontmatter: fm, body } = extractFrontmatter(resolvedContent);
120
+ const desc = fm.description || fm.name || skillName;
121
+
122
+ function injectMarker(content) {
123
+ return content.replace(/^---\n/, `---\n${MANAGED_BY}\n`);
124
+ }
125
+
126
+ switch (targetName) {
127
+ case 'claude':
128
+ return injectMarker(resolvedContent);
129
+
130
+ case 'cursor-rules':
131
+ return buildFrontmatter({ description: `"${desc}"`, alwaysApply: 'false' }) + '\n' + body;
132
+
133
+ case 'cursor-skills':
134
+ return injectMarker(resolvedContent);
135
+
136
+ case 'windsurf-rules':
137
+ return buildFrontmatter({ trigger: 'manual', description: `"${desc}"` }) + '\n' + body;
138
+
139
+ case 'windsurf-skills':
140
+ return injectMarker(resolvedContent);
141
+
142
+ case 'opencode':
143
+ return buildFrontmatter({
144
+ mode: 'subagent',
145
+ description: `"${desc}"`,
146
+ permission: { bash: 'allow', edit: 'allow', webfetch: 'allow' }
147
+ }) + '\n' + body;
148
+
149
+ case 'codex':
150
+ return injectMarker(resolvedContent);
151
+
152
+ default:
153
+ throw new Error(`Unknown target: ${targetName}`);
154
+ }
155
+ }
156
+
157
+ function getOutputPath(targetName, skillName) {
158
+ switch (targetName) {
159
+ case 'claude':
160
+ return join(COMPILED_DIR, 'claude', skillName, 'SKILL.md');
161
+ case 'cursor-rules':
162
+ return join(COMPILED_DIR, 'cursor', 'rules', `${skillName}.mdc`);
163
+ case 'cursor-skills':
164
+ return join(COMPILED_DIR, 'cursor', 'skills', skillName, 'SKILL.md');
165
+ case 'windsurf-rules':
166
+ return join(COMPILED_DIR, 'windsurf', 'rules', `${skillName}.md`);
167
+ case 'windsurf-skills':
168
+ return join(COMPILED_DIR, 'windsurf', 'skills', skillName, 'SKILL.md');
169
+ case 'opencode':
170
+ return join(COMPILED_DIR, 'opencode', `${skillName}.md`);
171
+ case 'codex':
172
+ return join(COMPILED_DIR, 'codex', skillName, 'SKILL.md');
173
+ default:
174
+ throw new Error(`Unknown target: ${targetName}`);
175
+ }
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Step 3: Validation
180
+ // ---------------------------------------------------------------------------
181
+
182
+ function validateNaming(skillName) {
183
+ const errors = [];
184
+ if (skillName !== skillName.toLowerCase()) {
185
+ errors.push(`Skill name not lowercase: ${skillName}`);
186
+ }
187
+ if (/\s/.test(skillName)) {
188
+ errors.push(`Skill name contains spaces: ${skillName}`);
189
+ }
190
+ for (const suffix of manifest.naming_rules.forbidden_suffixes) {
191
+ if (skillName.endsWith(`-${suffix}`) || skillName === suffix) {
192
+ errors.push(`Skill name uses forbidden suffix '${suffix}': ${skillName}`);
193
+ }
194
+ }
195
+ return errors;
196
+ }
197
+
198
+ function validateResolved(content, skillName) {
199
+ const errors = [];
200
+ const unresolvedPattern = /\{\{include:[^}]+\}\}/g;
201
+ const matches = content.match(unresolvedPattern);
202
+ if (matches) {
203
+ for (const m of matches) {
204
+ errors.push(`Unresolved include in ${skillName}: ${m}`);
205
+ }
206
+ }
207
+ return errors;
208
+ }
209
+
210
+ function validateFrontmatter(content, skillName) {
211
+ const errors = [];
212
+ if (!content.startsWith('---\n')) {
213
+ errors.push(`Missing frontmatter in ${skillName}`);
214
+ }
215
+ return errors;
216
+ }
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Main
220
+ // ---------------------------------------------------------------------------
221
+
222
+ function main() {
223
+ const allErrors = [];
224
+ const stats = { skills: 0, targets: 0, fragments: 0 };
225
+
226
+ function countFiles(dir) {
227
+ let count = 0;
228
+ if (!existsSync(dir)) return 0;
229
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
230
+ if (entry.isDirectory()) count += countFiles(join(dir, entry.name));
231
+ else if (entry.name.endsWith('.md')) count++;
232
+ }
233
+ return count;
234
+ }
235
+ stats.fragments = countFiles(FRAGMENTS_DIR);
236
+
237
+ if (!validateOnly) {
238
+ if (existsSync(COMPILED_DIR)) {
239
+ rmSync(COMPILED_DIR, { recursive: true });
240
+ }
241
+ }
242
+
243
+ for (const [skillName, skillDef] of Object.entries(manifest.skills)) {
244
+ stats.skills++;
245
+
246
+ allErrors.push(...validateNaming(skillName));
247
+
248
+ const sourcePath = join(SKILL_ROOT, skillDef.source);
249
+ if (!existsSync(sourcePath)) {
250
+ allErrors.push(`Missing source: ${skillDef.source} (skill: ${skillName})`);
251
+ continue;
252
+ }
253
+ const source = readFileSync(sourcePath, 'utf-8');
254
+
255
+ const { resolved, errors: includeErrors } = resolveIncludes(source, skillDef.source);
256
+ allErrors.push(...includeErrors);
257
+
258
+ allErrors.push(...validateResolved(resolved, skillName));
259
+ allErrors.push(...validateFrontmatter(resolved, skillName));
260
+
261
+ if (validateOnly) continue;
262
+
263
+ for (const target of targets) {
264
+ stats.targets++;
265
+ const compiled = compileTarget(target, skillName, resolved);
266
+ const outPath = getOutputPath(target, skillName);
267
+
268
+ mkdirSync(dirname(outPath), { recursive: true });
269
+ writeFileSync(outPath, compiled);
270
+ }
271
+ }
272
+
273
+ // Cross-validate manifest fragment declarations against actual includes
274
+ for (const [skillName, skillDef] of Object.entries(manifest.skills)) {
275
+ const sourcePath = join(SKILL_ROOT, skillDef.source);
276
+ if (!existsSync(sourcePath)) continue;
277
+ const source = readFileSync(sourcePath, 'utf-8');
278
+ const includePattern = /\{\{include:([^}]+)\}\}/g;
279
+ const actualIncludes = new Set();
280
+ let match;
281
+ while ((match = includePattern.exec(source)) !== null) {
282
+ actualIncludes.add(match[1].trim());
283
+ }
284
+ const declared = new Set(skillDef.fragments);
285
+
286
+ for (const inc of actualIncludes) {
287
+ if (!declared.has(inc)) {
288
+ allErrors.push(`Undeclared fragment in ${skillName}: ${inc} (found in source but not in manifest.fragments)`);
289
+ }
290
+ }
291
+ for (const dec of declared) {
292
+ if (!actualIncludes.has(dec)) {
293
+ allErrors.push(`Unused declared fragment in ${skillName}: ${dec} (in manifest.fragments but not in source)`);
294
+ }
295
+ }
296
+ }
297
+
298
+ // Staleness check (validate-only mode)
299
+ if (validateOnly) {
300
+ for (const [skillName, skillDef] of Object.entries(manifest.skills)) {
301
+ const sourcePath = join(SKILL_ROOT, skillDef.source);
302
+ if (!existsSync(sourcePath)) continue;
303
+ const sourceMtime = statSync(sourcePath).mtimeMs;
304
+
305
+ let newestInput = sourceMtime;
306
+ for (const frag of skillDef.fragments) {
307
+ const fragPath = join(FRAGMENTS_DIR, frag);
308
+ if (existsSync(fragPath)) {
309
+ newestInput = Math.max(newestInput, statSync(fragPath).mtimeMs);
310
+ }
311
+ }
312
+ newestInput = Math.max(newestInput, statSync(MANIFEST_PATH).mtimeMs);
313
+
314
+ for (const target of manifest.targets) {
315
+ const outPath = getOutputPath(target, skillName);
316
+ if (!existsSync(outPath)) {
317
+ allErrors.push(`Missing compiled output: ${relative(PROJECT_ROOT, outPath)} (run npm run compile)`);
318
+ } else {
319
+ const outMtime = statSync(outPath).mtimeMs;
320
+ if (outMtime < newestInput) {
321
+ allErrors.push(`Stale compiled output: ${relative(PROJECT_ROOT, outPath)} is older than its source or fragments (run npm run compile)`);
322
+ }
323
+ }
324
+ }
325
+ }
326
+ }
327
+
328
+ if (allErrors.length > 0) {
329
+ console.error('\n==> Errors:');
330
+ for (const e of allErrors) {
331
+ console.error(` ERROR: ${e}`);
332
+ }
333
+ console.error(`\n${allErrors.length} error(s) found.`);
334
+ process.exit(1);
335
+ }
336
+
337
+ if (validateOnly) {
338
+ console.log(`==> Validation passed. ${stats.skills} skills, ${stats.fragments} fragments.`);
339
+ } else {
340
+ console.log(`==> Compiled ${stats.skills} skills to ${targets.length} targets (${stats.targets} files). ${stats.fragments} fragments resolved.`);
341
+ }
342
+ }
343
+
344
+ // ---------------------------------------------------------------------------
345
+ // Watch mode
346
+ // ---------------------------------------------------------------------------
347
+
348
+ function startWatch() {
349
+ console.log('==> Watch mode: compiling on change...');
350
+ console.log(` Watching: skill/fragments/, skill/skills/, skill/build/manifest.json`);
351
+ console.log(' Press Ctrl+C to stop.\n');
352
+
353
+ main();
354
+
355
+ let debounceTimer = null;
356
+ const recompile = (eventType, filename) => {
357
+ if (debounceTimer) clearTimeout(debounceTimer);
358
+ debounceTimer = setTimeout(() => {
359
+ console.log(`\n--- Change detected${filename ? `: ${filename}` : ''} ---`);
360
+ try {
361
+ main();
362
+ } catch (e) {
363
+ console.error(`Compile error: ${e.message}`);
364
+ }
365
+ }, 300);
366
+ };
367
+
368
+ const watchDirs = [
369
+ FRAGMENTS_DIR,
370
+ join(SKILL_ROOT, 'skills'),
371
+ ];
372
+
373
+ for (const dir of watchDirs) {
374
+ if (existsSync(dir)) {
375
+ fsWatch(dir, { recursive: true }, recompile);
376
+ }
377
+ }
378
+ fsWatch(MANIFEST_PATH, recompile);
379
+ }
380
+
381
+ if (watchMode) {
382
+ startWatch();
383
+ } else {
384
+ main();
385
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "targets": [
3
+ "claude",
4
+ "cursor-rules",
5
+ "cursor-skills",
6
+ "windsurf-rules",
7
+ "windsurf-skills",
8
+ "opencode",
9
+ "codex"
10
+ ],
11
+ "skills": {
12
+ "agentic-skill-mill": {
13
+ "source": "skills/agentic-skill-mill/agentic-skill-mill.md",
14
+ "fragments": [
15
+ "meta/architecture-overview.md",
16
+ "meta/research-to-code.md",
17
+ "meta/core-module-pattern.md",
18
+ "meta/cli-command-pattern.md",
19
+ "meta/fragment-composition.md",
20
+ "meta/rename-workflow.md"
21
+ ]
22
+ }
23
+ },
24
+ "naming_rules": {
25
+ "case": "lowercase",
26
+ "separator": "-",
27
+ "forbidden_suffixes": ["final", "new", "latest", "v2"]
28
+ }
29
+ }
@@ -0,0 +1,37 @@
1
+ The skill-system-template architecture has two halves that meet at the agent:
2
+
3
+ ```
4
+ Skills (what to do) CLI Companion (tools to do it with)
5
+ skill/skills/*.md src/cli/commands/*.ts
6
+ skill/fragments/*.md src/core/*.ts
7
+ | |
8
+ v v
9
+ compiled/ (7 IDE formats) dist/ (npm link -> global CLI)
10
+ | |
11
+ +---------> Agent <----------+
12
+ ```
13
+
14
+ **Skills** are step-by-step runbooks in markdown with YAML frontmatter. They tell the agent *what* to do. Skills reference the CLI by name so the agent can invoke structured commands.
15
+
16
+ **Fragments** are shared knowledge blocks included by multiple skills via include markers (`\{\{include:path\}\}`). Edit a fragment once, recompile, and every skill that includes it gets the update. Fragments have no frontmatter.
17
+
18
+ **The compiler** (`skill/build/compile.mjs`) resolves includes, applies IDE-specific frontmatter transforms, and writes to `compiled/` with one subdirectory per target. It validates naming rules, checks for unresolved includes, and cross-validates manifest declarations against actual includes in source files.
19
+
20
+ **The CLI companion** is a TypeScript package with:
21
+ - `src/core/` — Pure logic modules with typed interfaces
22
+ - `src/cli/commands/` — Thin command wrappers that delegate to core modules
23
+ - `src/cli/index.ts` — Commander.js entry point wiring all commands
24
+ - `src/cli/output-formatter.ts` — JSON, table, and key-value formatters
25
+ - `src/errors/types.ts` — Typed error hierarchy (AppError, NotFoundError, etc.)
26
+ - `src/cache/cache-manager.ts` — Two-tier cache (memory + disk) with TTL
27
+
28
+ **The installer** (`install.sh`) builds the CLI, compiles skills, and copies compiled outputs to IDE-specific directories (~/.claude/skills, ~/.cursor/rules, etc.) with marker-based stale file cleanup. The installer uses `set -e` for fail-fast behavior. Any function that uses an early-exit guard (`[[ -d ... ]] || return`, `[[ -z ... ]] && return`) **must** use `return 0`, never bare `return`. Bare `return` inherits the exit code of the last command, which for a failed conditional test is 1 -- and `set -e` treats that as a script-terminating failure with no error message.
29
+
30
+ ### Key files to modify when augmenting a project
31
+
32
+ | What | File(s) |
33
+ |------|---------|
34
+ | Add a CLI command | `src/core/<name>.ts`, `src/cli/commands/<name>.ts`, `src/cli/index.ts`, `src/index.ts` |
35
+ | Add a fragment | `skill/fragments/<category>/<name>.md`, `skill/build/manifest.json`, skill source |
36
+ | Add a skill | `skill/skills/<name>/<name>.md`, `skill/build/manifest.json`, `install.sh` SKILLS array |
37
+ | Rename the project | See the rename workflow |
@@ -0,0 +1,83 @@
1
+ ### Command wrapper pattern
2
+
3
+ Each CLI command lives in `src/cli/commands/<name>.ts` as a thin wrapper:
4
+
5
+ ```typescript
6
+ // src/cli/commands/<name>.ts
7
+ import { doThing, type ThingOptions, type ThingResult } from '../../core/<name>.js';
8
+
9
+ export interface ThingCommandOptions extends ThingOptions {
10
+ json: boolean;
11
+ }
12
+
13
+ export async function thingCommand(
14
+ options: ThingCommandOptions,
15
+ ): Promise<ThingResult> {
16
+ return doThing({
17
+ param: options.param,
18
+ });
19
+ }
20
+ ```
21
+
22
+ **Rules for command wrappers:**
23
+ - Import from core module using `.js` extension (ESM resolution)
24
+ - Extend the core options interface with `json: boolean`
25
+ - Delegate immediately to the core function -- no business logic in the wrapper
26
+ - Return the core result type -- formatting happens in `index.ts`
27
+
28
+ ### Wiring into the CLI entry point
29
+
30
+ Add the command in `src/cli/index.ts`:
31
+
32
+ ```typescript
33
+ import { thingCommand } from './commands/<name>.js';
34
+
35
+ program
36
+ .command('<name>')
37
+ .description('One-line description of what this does')
38
+ .argument('<required>', 'Description of required positional arg') // if needed
39
+ .requiredOption('--param <value>', 'Required option description') // if needed
40
+ .option('--json', 'Output as JSON', false)
41
+ .option('--optional <value>', 'Optional flag', 'default')
42
+ .action(async (positionalArg, options) => {
43
+ try {
44
+ const result = await thingCommand({
45
+ param: positionalArg,
46
+ json: options.json,
47
+ });
48
+
49
+ if (options.json) {
50
+ console.log(JSON.stringify(result, null, 2));
51
+ } else {
52
+ // Human-readable output using chalk
53
+ console.log();
54
+ console.log(chalk.bold('Title'));
55
+ for (const item of result.data) {
56
+ console.log(` ${item}`);
57
+ }
58
+ console.log();
59
+ }
60
+ } catch (error) {
61
+ handleError(error);
62
+ }
63
+ });
64
+ ```
65
+
66
+ **Rules for CLI wiring:**
67
+ - Every command supports `--json` for structured agent consumption
68
+ - Human-readable output uses chalk for color and formatting
69
+ - Errors funnel through the shared `handleError()` function
70
+ - Use `.argument()` for required positional args, `.requiredOption()` for mandatory flags
71
+ - Parse string options to their real types (parseInt, parseFloat) in the action handler
72
+ - Comma-separated list options get `.split(',').map(s => s.trim())` in the action handler
73
+
74
+ ### Updating exports
75
+
76
+ After adding a new core module, export its public API from `src/index.ts`:
77
+
78
+ ```typescript
79
+ export { doThing } from './core/<name>.js';
80
+ export type { ThingOptions, ThingResult } from './core/<name>.js';
81
+ ```
82
+
83
+ This allows the package to be consumed as a library, not just a CLI.
@@ -0,0 +1,38 @@
1
+ Every CLI command starts as a **core module** in `src/core/`. Core modules contain pure domain logic with no CLI concerns (no chalk, no console.log, no process.exit). This separation lets the logic be consumed as a library, tested independently, and composed by multiple commands.
2
+
3
+ ### Structure of a core module
4
+
5
+ ```typescript
6
+ // src/core/<name>.ts
7
+
8
+ /** Input options -- everything the caller needs to provide */
9
+ export interface <Name>Options {
10
+ requiredParam: string;
11
+ optionalParam?: number;
12
+ }
13
+
14
+ /** Output result -- everything the caller gets back */
15
+ export interface <Name>Result {
16
+ data: SomeType[];
17
+ warnings: string[];
18
+ }
19
+
20
+ /** Pure function: options in, result out. No side effects on stdout. */
21
+ export function <name>(options: <Name>Options): <Name>Result {
22
+ return { data: [], warnings: [] };
23
+ }
24
+ ```
25
+
26
+ ### Rules
27
+
28
+ - **Export typed interfaces** for both input and output so consumers get autocomplete and type safety
29
+ - **Return structured data**, never print to stdout -- the CLI wrapper decides how to format
30
+ - **Include a `warnings` array** in results when the function can detect non-fatal issues
31
+ - **Use `async` only when needed** (file I/O, network) -- synchronous logic stays synchronous
32
+ - **Throw typed errors** from `src/errors/types.ts` for unrecoverable failures
33
+ - **Keep modules focused** -- one concept per file. A pace calculator doesn't also validate outlines
34
+ - **Accept paths and options, not raw CLI strings** -- parsing belongs in the command wrapper
35
+
36
+ ### Shared parsers
37
+
38
+ When two or more commands need to parse the same input format, extract a shared parser module. The parser returns a structured type that both consumers work with.