@shrkcrft/cli 0.1.0-alpha.7 → 0.1.0-alpha.8

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 (41) hide show
  1. package/dist/commands/command-catalog.d.ts +7 -3
  2. package/dist/commands/command-catalog.d.ts.map +1 -1
  3. package/dist/commands/command-catalog.js +112 -33
  4. package/dist/commands/commands.command.d.ts.map +1 -1
  5. package/dist/commands/commands.command.js +4 -4
  6. package/dist/commands/constructs.command.d.ts.map +1 -1
  7. package/dist/commands/constructs.command.js +5 -22
  8. package/dist/commands/diff-check.command.d.ts +30 -0
  9. package/dist/commands/diff-check.command.d.ts.map +1 -0
  10. package/dist/commands/diff-check.command.js +210 -0
  11. package/dist/commands/doctor.command.d.ts.map +1 -1
  12. package/dist/commands/doctor.command.js +34 -2
  13. package/dist/commands/export.command.d.ts.map +1 -1
  14. package/dist/commands/export.command.js +76 -3
  15. package/dist/commands/help.command.d.ts +4 -3
  16. package/dist/commands/help.command.d.ts.map +1 -1
  17. package/dist/commands/help.command.js +74 -16
  18. package/dist/commands/helper.command.js +1 -1
  19. package/dist/commands/import.command.d.ts.map +1 -1
  20. package/dist/commands/import.command.js +121 -5
  21. package/dist/commands/init.command.d.ts.map +1 -1
  22. package/dist/commands/init.command.js +151 -7
  23. package/dist/commands/packs-new.d.ts +1 -1
  24. package/dist/commands/packs-new.d.ts.map +1 -1
  25. package/dist/commands/packs-new.js +2 -26
  26. package/dist/commands/profiles.command.js +4 -4
  27. package/dist/commands/release.command.js +13 -13
  28. package/dist/commands/search.command.js +1 -1
  29. package/dist/commands/task-context.command.js +0 -16
  30. package/dist/export/claude-commands-export.d.ts +60 -0
  31. package/dist/export/claude-commands-export.d.ts.map +1 -0
  32. package/dist/export/claude-commands-export.js +276 -0
  33. package/dist/export/export-formats.d.ts +1 -1
  34. package/dist/export/export-formats.d.ts.map +1 -1
  35. package/dist/export/export-formats.js +139 -12
  36. package/dist/main.d.ts.map +1 -1
  37. package/dist/main.js +104 -11
  38. package/package.json +20 -20
  39. package/dist/commands/plugin.command.d.ts +0 -11
  40. package/dist/commands/plugin.command.d.ts.map +0 -1
  41. package/dist/commands/plugin.command.js +0 -394
@@ -8,7 +8,6 @@ const VALID_KINDS = new Set([
8
8
  'framework',
9
9
  'architecture',
10
10
  'enterprise',
11
- 'platform-adopter',
12
11
  ]);
13
12
  /** Pure: compute the file set to write. No IO. */
14
13
  export function planPackScaffold(input) {
@@ -101,12 +100,6 @@ export function planPackScaffold(input) {
101
100
  body: renderBoundariesAsset(),
102
101
  });
103
102
  }
104
- if (input.kind === 'platform-adopter' || input.withExamples) {
105
- files.push({
106
- relativePath: 'src/assets/contracts/plugin-contract.example.ts',
107
- body: renderPluginContractExample(),
108
- });
109
- }
110
103
  if (input.kind === 'enterprise') {
111
104
  files.push({
112
105
  relativePath: 'docs/review-workflow.md',
@@ -232,12 +225,6 @@ function renderKnowledgeAsset(input) {
232
225
  ['framework.overview', 'Framework overview', 'High', 'What this framework is for.'],
233
226
  ]);
234
227
  }
235
- if (input.kind === 'platform-adopter') {
236
- return knowledgeBody([
237
- ['platform.policy', 'Policy capability', 'Medium', 'Describe the policy/capability model your platform exposes.'],
238
- ['platform.adapter', 'Adapter contract', 'Medium', 'Describe the adapter contract your platform expects.'],
239
- ]);
240
- }
241
228
  return knowledgeBody([
242
229
  ['pack.overview', 'Pack overview', 'Medium', 'Short overview of what this pack contributes.'],
243
230
  ]);
@@ -350,17 +337,6 @@ function renderBoundariesAsset() {
350
337
  ``,
351
338
  ].join('\n');
352
339
  }
353
- function renderPluginContractExample() {
354
- return [
355
- `// Example: an adapter-style plugin contract.`,
356
- `// Replace with the actual interface your platform expects.`,
357
- `export interface IPluginCapability {`,
358
- ` id: string;`,
359
- ` invoke(input: unknown): Promise<unknown>;`,
360
- `}`,
361
- ``,
362
- ].join('\n');
363
- }
364
340
  function renderEnterpriseReviewDocs() {
365
341
  return [
366
342
  `# Review workflow`,
@@ -407,11 +383,11 @@ function renderDocsOverview(input, fullName) {
407
383
  export const packsNewCommand = {
408
384
  name: 'new',
409
385
  description: 'Scaffold a new SharkCraft pack package (rules / paths / templates / pipelines / presets / boundaries). Dry-run by default — pass --write to materialize. No install, no publish, no overwrite without --force.',
410
- usage: 'shrk [--cwd <dir>] packs new <name> [--scope @org] [--preset <id>] [--kind generic|framework|architecture|enterprise|platform-adopter] [--with-examples] [--write] [--force] [--json]',
386
+ usage: 'shrk [--cwd <dir>] packs new <name> [--scope @org] [--preset <id>] [--kind generic|framework|architecture|enterprise] [--with-examples] [--write] [--force] [--json]',
411
387
  async run(args) {
412
388
  const name = args.positional[0];
413
389
  if (!name) {
414
- process.stderr.write('Usage: shrk packs new <name> [--scope @org] [--preset <id>] [--kind generic|framework|architecture|enterprise|platform-adopter] [--with-examples] [--write]\n');
390
+ process.stderr.write('Usage: shrk packs new <name> [--scope @org] [--preset <id>] [--kind generic|framework|architecture|enterprise] [--with-examples] [--write]\n');
415
391
  return 2;
416
392
  }
417
393
  const cwd = resolveCwd(args);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `shrk profiles ...` — unified read-only surface for all pack-/local-
3
- * contributed profiles (plugin lifecycle, migration, and future kinds).
3
+ * contributed profiles (migration, and future kinds).
4
4
  */
5
5
  import { findProfile, inspectSharkcraft, listProfileIssues, listProfiles, ProfileKind, } from '@shrkcrft/inspector';
6
6
  import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
@@ -15,7 +15,7 @@ function parseKind(value) {
15
15
  }
16
16
  export const profilesListCommand = {
17
17
  name: 'list',
18
- description: 'List all registered profiles (plugin-lifecycle, migration, ...).',
18
+ description: 'List all registered profiles (migration, ...).',
19
19
  usage: 'shrk profiles list [--kind <kind>] [--json]',
20
20
  async run(args) {
21
21
  const cwd = resolveCwd(args);
@@ -28,7 +28,7 @@ export const profilesListCommand = {
28
28
  }
29
29
  process.stdout.write(header(`Profiles (${entries.length}${kind ? `, kind=${kind}` : ''})`));
30
30
  if (entries.length === 0) {
31
- process.stdout.write(' (none — contribute via packs: pluginLifecycleProfileFiles, migrationProfileFiles, etc.)\n');
31
+ process.stdout.write(' (none — contribute via packs: migrationProfileFiles, etc.)\n');
32
32
  return 0;
33
33
  }
34
34
  for (const e of entries) {
@@ -132,7 +132,7 @@ export const profilesSearchCommand = {
132
132
  };
133
133
  export const profilesCommand = {
134
134
  name: 'profiles',
135
- description: 'List / inspect pack-contributed profiles (plugin-lifecycle, migration, conventions, …).',
135
+ description: 'List / inspect pack-contributed profiles (migration, conventions, …).',
136
136
  usage: 'shrk profiles list|get|doctor|search ...',
137
137
  async run(args) {
138
138
  const sub = args.positional[0];
@@ -204,8 +204,8 @@ function resolveSmokeFlags(args) {
204
204
  const noAssertions = flagBool(args, 'no-assertions');
205
205
  return { assertionsEnabled: !noAssertions };
206
206
  }
207
- // Generic adopter target replaces the previous hardcoded project target.
208
- const BUILTIN_TARGET_IDS = ['sharkcraft', 'dogfood', 'synthetic', 'adopter'];
207
+ // Generic consumer target replaces the previous hardcoded project target.
208
+ const BUILTIN_TARGET_IDS = ['sharkcraft', 'dogfood', 'synthetic', 'consumer'];
209
209
  function resolveTargets(cwd, args, requested) {
210
210
  const want = (id) => requested.length === 0 || requested.includes(id);
211
211
  const out = [];
@@ -224,19 +224,19 @@ function resolveTargets(cwd, args, requested) {
224
224
  writeFileSync(nodePath.join(root, 'package.json'), JSON.stringify({ name: 'sharkcraft-synth', version: '0.0.0', type: 'module' }, null, 2), 'utf8');
225
225
  out.push({ id: 'synthetic', cwd: root, label: 'synthetic-fixture' });
226
226
  }
227
- if (want('adopter')) {
228
- const adopterRoot = flagString(args, 'adopter-root') ??
229
- process.env['SHARKCRAFT_ADOPTER_ROOT'] ??
227
+ if (want('consumer')) {
228
+ const consumerRoot = flagString(args, 'consumer-root') ??
229
+ process.env['SHARKCRAFT_CONSUMER_ROOT'] ??
230
230
  '';
231
- if (adopterRoot && existsSync(adopterRoot)) {
232
- out.push({ id: 'adopter', cwd: adopterRoot, label: `adopter:${adopterRoot}` });
231
+ if (consumerRoot && existsSync(consumerRoot)) {
232
+ out.push({ id: 'consumer', cwd: consumerRoot, label: `consumer:${consumerRoot}` });
233
233
  }
234
234
  else {
235
235
  out.push({
236
- id: 'adopter',
237
- cwd: adopterRoot || '(unset)',
238
- label: 'adopter target',
239
- warning: 'Adopter root not set (pass --adopter-root or set SHARKCRAFT_ADOPTER_ROOT) — skipped.',
236
+ id: 'consumer',
237
+ cwd: consumerRoot || '(unset)',
238
+ label: 'consumer target',
239
+ warning: 'Consumer root not set (pass --consumer-root or set SHARKCRAFT_CONSUMER_ROOT) — skipped.',
240
240
  });
241
241
  }
242
242
  }
@@ -292,7 +292,7 @@ async function runReleaseSmoke(args) {
292
292
  * examples/dogfood-target as the fixture.
293
293
  * - `synthetic` runs the scenarios that don't depend on a prepared source
294
294
  * (unconfigured-repo + pack-authoring).
295
- * - `adopter` runs the read-only scenarios that don't write outside the
295
+ * - `consumer` runs the read-only scenarios that don't write outside the
296
296
  * fixture (e.g. an external project that consumes a SharkCraft pack).
297
297
  */
298
298
  function isScenarioApplicableTo(scenario, target) {
@@ -304,7 +304,7 @@ function isScenarioApplicableTo(scenario, target) {
304
304
  if (target.id === 'dogfood') {
305
305
  return scenario === 'dev-workflow' || scenario === 'pr-review' || scenario === 'governance';
306
306
  }
307
- if (target.id === 'adopter') {
307
+ if (target.id === 'consumer') {
308
308
  return scenario === 'unconfigured-repo' || scenario === 'pack-authoring' || scenario === 'governance';
309
309
  }
310
310
  return true;
@@ -27,7 +27,7 @@ function parseSources(args) {
27
27
  }
28
28
  export const searchCommand = {
29
29
  name: 'search',
30
- description: 'Universal search across commands, MCP tools, knowledge, rules, paths, conventions, templates, helpers, playbooks, constructs, policies, decisions, scaffold patterns, contract templates, migration profiles, plugin lifecycle profiles, feedback rules, task routing hints, docs, recent reports. Default emits the 7-section unified output; pass --legacy for the flat output.',
30
+ description: 'Universal search across commands, MCP tools, knowledge, rules, paths, conventions, templates, helpers, playbooks, constructs, policies, decisions, scaffold patterns, contract templates, migration profiles, feedback rules, task routing hints, docs, recent reports. Default emits the 7-section unified output; pass --legacy for the flat output.',
31
31
  usage: 'shrk search <query> [--kind <kind>] [--source local|pack|...] [--limit N] [--explain] [--commands-only] [--actions-only] [--format text|markdown|json] [--legacy]',
32
32
  async run(args) {
33
33
  // Sub-dispatch for `shrk search tuning [list|doctor]`.
@@ -399,22 +399,6 @@ async function collectLikelyFilesV2(input) {
399
399
  }
400
400
  }
401
401
  }
402
- // Boost files that sit on registered plugin-lifecycle profile barrels.
403
- try {
404
- const { listPluginLifecycleProfiles } = await import('@shrkcrft/inspector');
405
- const profiles = await listPluginLifecycleProfiles(inspection);
406
- for (const entry of profiles) {
407
- for (const b of entry.profile.barrels ?? []) {
408
- for (const f of [...scoreByPath.keys()]) {
409
- if (f.includes(b.path))
410
- bump(f, 3, `lifecycle profile barrel: ${b.id}`);
411
- }
412
- }
413
- }
414
- }
415
- catch {
416
- // Profile registry unavailable — skip the boost.
417
- }
418
402
  // 10) Tests — files that look like they test the matched files.
419
403
  const tests = [];
420
404
  for (const f of [...scoreByPath.keys()]) {
@@ -0,0 +1,60 @@
1
+ /**
2
+ * `.claude/commands/` generator — emits per-project slash commands
3
+ * for Claude Code. Companion to `claude-skill` export, but with a
4
+ * different inversion semantics:
5
+ *
6
+ * - **claude-skill** loads rules into Claude's prompt automatically
7
+ * based on description match. Passive — Claude reads the rules.
8
+ * - **claude-commands** registers slash commands the USER invokes
9
+ * (`/new-service`, `/check-changes`). Active — the command IS the
10
+ * recipe; Claude follows it step-by-step.
11
+ *
12
+ * Generated commands fall into two buckets:
13
+ *
14
+ * **Static commands** (always present, project-agnostic recipes):
15
+ * - `/follow-shrk` — short reminder of the apply-gate flow.
16
+ * - `/check-changes` — runs `shrk check boundaries --changed-only`
17
+ * scoped to the current diff and reports back.
18
+ * - `/shrk-brief` — runs `shrk brief` and uses the output for the
19
+ * current task.
20
+ * - `/explain-file <path>` — per-file rules / paths / boundary
21
+ * lookup (pairs with `shrk advise <path>` from Phase 3).
22
+ *
23
+ * **Per-template commands** (one per id in `sharkcraft/templates.ts`):
24
+ * - `/new-<template-id>` — Claude runs `shrk gen <template-id>
25
+ * <name> --dry-run --save-plan ...`, reviews the plan, and
26
+ * applies via `shrk apply ... --verify-signature --validate`.
27
+ *
28
+ * Generated files are self-contained markdown — no `@shrkcrft/*`
29
+ * imports, no shell expansions. Each one is a complete "recipe in a
30
+ * file" that Claude Code reads when the user types the slash command.
31
+ */
32
+ import type { ISharkcraftInspection } from '@shrkcrft/inspector';
33
+ export interface IClaudeCommandFile {
34
+ /** Path relative to project root (e.g. `.claude/commands/new-service.md`). */
35
+ path: string;
36
+ /** Full markdown body, including YAML frontmatter. */
37
+ content: string;
38
+ /** The slash name users type (e.g. `new-service` → `/new-service`). */
39
+ slash: string;
40
+ /** Why this command was generated — surfaces in the dry-run summary. */
41
+ source: 'static' | 'template';
42
+ }
43
+ export interface IClaudeCommandsResult {
44
+ files: readonly IClaudeCommandFile[];
45
+ }
46
+ export interface IClaudeCommandsOptions {
47
+ /**
48
+ * Cap on the number of per-template commands to emit. Default 20.
49
+ * Templates are sorted by id; the first N are kept.
50
+ */
51
+ maxTemplateCommands?: number;
52
+ }
53
+ /**
54
+ * Build the full set of `.claude/commands/*.md` files for a project.
55
+ *
56
+ * Pure — caller writes the bytes. Same shape as the `synthesize-*`
57
+ * functions in this codebase: input inspection → output file list.
58
+ */
59
+ export declare function buildClaudeCommands(inspection: ISharkcraftInspection, options?: IClaudeCommandsOptions): IClaudeCommandsResult;
60
+ //# sourceMappingURL=claude-commands-export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-commands-export.d.ts","sourceRoot":"","sources":["../../src/export/claude-commands-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACtC;AA0MD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,qBAAqB,EACjC,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAsDvB"}
@@ -0,0 +1,276 @@
1
+ /**
2
+ * `.claude/commands/` generator — emits per-project slash commands
3
+ * for Claude Code. Companion to `claude-skill` export, but with a
4
+ * different inversion semantics:
5
+ *
6
+ * - **claude-skill** loads rules into Claude's prompt automatically
7
+ * based on description match. Passive — Claude reads the rules.
8
+ * - **claude-commands** registers slash commands the USER invokes
9
+ * (`/new-service`, `/check-changes`). Active — the command IS the
10
+ * recipe; Claude follows it step-by-step.
11
+ *
12
+ * Generated commands fall into two buckets:
13
+ *
14
+ * **Static commands** (always present, project-agnostic recipes):
15
+ * - `/follow-shrk` — short reminder of the apply-gate flow.
16
+ * - `/check-changes` — runs `shrk check boundaries --changed-only`
17
+ * scoped to the current diff and reports back.
18
+ * - `/shrk-brief` — runs `shrk brief` and uses the output for the
19
+ * current task.
20
+ * - `/explain-file <path>` — per-file rules / paths / boundary
21
+ * lookup (pairs with `shrk advise <path>` from Phase 3).
22
+ *
23
+ * **Per-template commands** (one per id in `sharkcraft/templates.ts`):
24
+ * - `/new-<template-id>` — Claude runs `shrk gen <template-id>
25
+ * <name> --dry-run --save-plan ...`, reviews the plan, and
26
+ * applies via `shrk apply ... --verify-signature --validate`.
27
+ *
28
+ * Generated files are self-contained markdown — no `@shrkcrft/*`
29
+ * imports, no shell expansions. Each one is a complete "recipe in a
30
+ * file" that Claude Code reads when the user types the slash command.
31
+ */
32
+ // ─── Static commands (always emitted) ────────────────────────────────────────
33
+ const STATIC_FOLLOW_SHRK = `---
34
+ description: Reminder of the shrk apply-gate flow for this project. Use when generating or modifying code so the change passes the same boundary / validation gates the project CI uses.
35
+ ---
36
+
37
+ # Follow the shrk apply-gate flow
38
+
39
+ This repo uses [SharkCraft](https://github.com/shrkcrft/sharkcraft) as the
40
+ gate for AI-written code. Skip the gate and CI will fail with the same
41
+ errors anyway — save the round-trip.
42
+
43
+ ## The loop
44
+
45
+ 1. **Get focused context first.**
46
+ - Run: \`shrk task "<one-sentence task>"\` for a task packet (relevant rules + templates + verification commands).
47
+ - Or: \`shrk brief\` for the single-page project brief.
48
+
49
+ 2. **Scaffold via a template (not freehand).**
50
+ - List options: \`shrk templates list\`.
51
+ - Dry-run + save plan: \`shrk gen <template-id> <name> --dry-run --save-plan /tmp/plan.json\`.
52
+
53
+ 3. **Apply the plan through the CLI.**
54
+ - \`shrk apply /tmp/plan.json --verify-signature --validate\`.
55
+ - Never write files directly through MCP — MCP is read-only in this repo.
56
+
57
+ 4. **Verify before declaring done.**
58
+ - \`shrk check boundaries --changed-only\` — fails if the diff broke any layer rule.
59
+ - \`shrk check imports\` — fails on lazy requires / cross-package deep imports.
60
+ - Project verification commands (from \`shrk task\`'s \`actionHints.verificationCommands\`).
61
+
62
+ ## When this loop doesn't apply
63
+
64
+ - Tiny changes that don't touch source files (docs, comments).
65
+ - Read-only investigations.
66
+ - Anything where shrk would clearly be in the way.
67
+
68
+ In all other cases: **follow the loop**. The gates are short; the rework if you skip them is long.
69
+ `;
70
+ const STATIC_CHECK_CHANGES = `---
71
+ description: Run shrk's boundary + import-hygiene checks on the current git diff. Use after making any file change to confirm the change didn't violate the project's architecture rules before declaring the task done.
72
+ ---
73
+
74
+ # Check the current diff for boundary violations
75
+
76
+ Run the diff-scoped boundary check:
77
+
78
+ \`\`\`bash
79
+ shrk check boundaries --changed-only
80
+ \`\`\`
81
+
82
+ Run the import-hygiene check on the changed files:
83
+
84
+ \`\`\`bash
85
+ shrk check imports
86
+ \`\`\`
87
+
88
+ ## Interpreting the output
89
+
90
+ - **Exit 0, "0 violations":** safe to declare done.
91
+ - **Exit non-zero with violations listed:** fix each violation before continuing. Each violation has a \`suggestedFix\` line — apply it.
92
+ - **A violation on a file you didn't change:** that's a pre-existing violation in the repo. Ignore it; the \`--changed-only\` filter only fails on violations your diff introduced.
93
+
94
+ If the violations look like false positives, consult \`sharkcraft/boundaries.ts\` (the rule definitions) — don't disable them ad-hoc.
95
+ `;
96
+ const STATIC_SHRK_BRIEF = `---
97
+ description: Pull shrk's single-page project brief — focused rules, paths, verification commands. Use when starting work in this codebase so you have the project's actual conventions in context before writing any code.
98
+ ---
99
+
100
+ # Pull the shrk brief for this project
101
+
102
+ Run the project brief:
103
+
104
+ \`\`\`bash
105
+ shrk brief
106
+ \`\`\`
107
+
108
+ This returns a compact markdown brief covering:
109
+ - Project overview (name, frameworks, package manager).
110
+ - Top rules that apply to code generation in this repo.
111
+ - Path conventions (where different file types belong).
112
+ - Action hints (commands, MCP tools, verification commands).
113
+ - Forbidden actions (what NOT to do).
114
+
115
+ For a per-task version (only the rules + paths + templates relevant to one task):
116
+
117
+ \`\`\`bash
118
+ shrk task "<one-sentence task description>"
119
+ \`\`\`
120
+
121
+ Both are read-only — no files are touched. Use the output to shape your plan before making any changes.
122
+ `;
123
+ const STATIC_EXPLAIN_FILE = `---
124
+ description: Look up the rules, path conventions, and boundary rules that apply to a specific file in this codebase. Use before editing an unfamiliar file so you follow the project's per-area conventions instead of generic patterns.
125
+ ---
126
+
127
+ # Explain what applies to a file in this codebase
128
+
129
+ For a given file path (e.g. \`apps/users/src/profile.service.ts\`):
130
+
131
+ \`\`\`bash
132
+ shrk why <file-path>
133
+ \`\`\`
134
+
135
+ Returns:
136
+ - Which package / layer the file belongs to.
137
+ - Which path conventions apply (e.g. "services live in \`apps/<x>/src/services/\`").
138
+ - Which rules are scoped to this file's path.
139
+ - Which boundary rules constrain this file's imports.
140
+ - Cross-references to related knowledge entries.
141
+
142
+ Use this *before* editing. The output is the project's actual conventions for that area, not your guess based on the file's content.
143
+ `;
144
+ // ─── Per-template command generator ──────────────────────────────────────────
145
+ /**
146
+ * Slugify a template id into a slash-command name. Template ids
147
+ * conventionally look like `typescript.service`; the slash command is
148
+ * `new-typescript-service` (or `new-service` if the segment after the
149
+ * dot is unique and shorter).
150
+ */
151
+ function templateSlash(templateId) {
152
+ // Use the LAST dot-separated segment as the primary name —
153
+ // `typescript.service` → `new-service`. Falls back to the full id
154
+ // when there's no dot or the segment is too generic (single char).
155
+ const parts = templateId.split('.');
156
+ const tail = parts[parts.length - 1] ?? templateId;
157
+ const safeName = tail.length >= 3 ? tail : templateId.replace(/\./g, '-');
158
+ return `new-${safeName}`
159
+ .toLowerCase()
160
+ .replace(/[^a-z0-9-]+/g, '-')
161
+ .replace(/-+/g, '-')
162
+ .replace(/^-+|-+$/g, '');
163
+ }
164
+ function templateCommandBody(template) {
165
+ const displayName = template.name ?? template.id;
166
+ const description = template.description
167
+ ? template.description.replace(/\s+/g, ' ').trim()
168
+ : `Scaffold a new ${displayName} using the project's actual template (\`${template.id}\`). Follows the shrk plan → apply → validate flow.`;
169
+ return `---
170
+ description: ${JSON.stringify(description)}
171
+ ---
172
+
173
+ # /${templateSlash(template.id)} — scaffold ${displayName}
174
+
175
+ This command scaffolds a new ${displayName} using the project's actual template
176
+ defined in \`sharkcraft/templates.ts\`. The template encodes this repo's
177
+ conventions for path, naming, and structure — the result will match how
178
+ the rest of the codebase is organized, not generic patterns.
179
+
180
+ ## The flow
181
+
182
+ When the user invokes \`/${templateSlash(template.id)} <name>\`:
183
+
184
+ 1. **Generate a plan (no writes yet):**
185
+ \`\`\`bash
186
+ shrk gen ${template.id} <name> --dry-run --save-plan /tmp/${templateSlash(template.id)}.plan.json
187
+ \`\`\`
188
+
189
+ 2. **Read the plan back** from \`/tmp/${templateSlash(template.id)}.plan.json\` and show the user which files will be created.
190
+
191
+ 3. **Confirm.** Wait for the user to approve. If they want changes, adjust the plan or re-run \`shrk gen\` with different flags.
192
+
193
+ 4. **Apply through the validated CLI path:**
194
+ \`\`\`bash
195
+ shrk apply /tmp/${templateSlash(template.id)}.plan.json --verify-signature --validate
196
+ \`\`\`
197
+
198
+ 5. **Verify** the diff didn't break any boundary rules:
199
+ \`\`\`bash
200
+ shrk check boundaries --changed-only
201
+ \`\`\`
202
+
203
+ ## If the template doesn't fit
204
+
205
+ If the user's request doesn't match the \`${template.id}\` template
206
+ shape, fall back to:
207
+
208
+ - \`shrk templates list\` — see all available templates.
209
+ - \`shrk gen <other-template> <name> --dry-run\` — try a different template.
210
+ - Hand-author only as last resort, and run \`/check-changes\` after.
211
+
212
+ The whole point of using the template is consistency with the rest of
213
+ this codebase. Skipping it means the new code will look different from
214
+ what's already there.
215
+ `;
216
+ }
217
+ /**
218
+ * Build the full set of `.claude/commands/*.md` files for a project.
219
+ *
220
+ * Pure — caller writes the bytes. Same shape as the `synthesize-*`
221
+ * functions in this codebase: input inspection → output file list.
222
+ */
223
+ export function buildClaudeCommands(inspection, options = {}) {
224
+ const files = [];
225
+ files.push({
226
+ path: '.claude/commands/follow-shrk.md',
227
+ slash: 'follow-shrk',
228
+ source: 'static',
229
+ content: STATIC_FOLLOW_SHRK,
230
+ });
231
+ files.push({
232
+ path: '.claude/commands/check-changes.md',
233
+ slash: 'check-changes',
234
+ source: 'static',
235
+ content: STATIC_CHECK_CHANGES,
236
+ });
237
+ files.push({
238
+ path: '.claude/commands/shrk-brief.md',
239
+ slash: 'shrk-brief',
240
+ source: 'static',
241
+ content: STATIC_SHRK_BRIEF,
242
+ });
243
+ files.push({
244
+ path: '.claude/commands/explain-file.md',
245
+ slash: 'explain-file',
246
+ source: 'static',
247
+ content: STATIC_EXPLAIN_FILE,
248
+ });
249
+ // Per-template commands — bounded so a pack with 50 templates
250
+ // doesn't dump 50 slash commands into the user's palette. Sorted
251
+ // by id for deterministic emit order.
252
+ const cap = options.maxTemplateCommands ?? 20;
253
+ const templates = [...inspection.templates].sort((a, b) => a.id.localeCompare(b.id));
254
+ const seenSlash = new Set(files.map((f) => f.slash));
255
+ for (const t of templates) {
256
+ if (files.filter((f) => f.source === 'template').length >= cap)
257
+ break;
258
+ let slash = templateSlash(t.id);
259
+ if (seenSlash.has(slash)) {
260
+ // Two templates with the same tail (e.g. `ts.service` and
261
+ // `py.service` both → `new-service`) — fall back to the full id
262
+ // to disambiguate the second one.
263
+ slash = `new-${t.id.replace(/\./g, '-').toLowerCase()}`;
264
+ if (seenSlash.has(slash))
265
+ continue;
266
+ }
267
+ seenSlash.add(slash);
268
+ files.push({
269
+ path: `.claude/commands/${slash}.md`,
270
+ slash,
271
+ source: 'template',
272
+ content: templateCommandBody(t),
273
+ });
274
+ }
275
+ return { files };
276
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ISharkcraftInspection } from '@shrkcrft/inspector';
2
- export type ExportFormat = 'agents-md' | 'claude-md' | 'cursor-rules' | 'copilot-instructions';
2
+ export type ExportFormat = 'agents-md' | 'claude-md' | 'claude-skill' | 'cursor-rules' | 'copilot-instructions';
3
3
  export interface ExportOptions {
4
4
  format: ExportFormat;
5
5
  /** Optional task to scope the export. */
@@ -1 +1 @@
1
- {"version":3,"file":"export-formats.d.ts","sourceRoot":"","sources":["../../src/export/export-formats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAIjE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,cAAc,GACd,sBAAsB,CAAC;AAE3B,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqHD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,qBAAqB,EACjC,OAAO,EAAE,aAAa,GACrB,YAAY,CAsBd;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,YAAY,CAOnE;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,YAAY,EAKpD,CAAC"}
1
+ {"version":3,"file":"export-formats.d.ts","sourceRoot":"","sources":["../../src/export/export-formats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAIjE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,WAAW,GACX,cAAc,GACd,cAAc,GACd,sBAAsB,CAAC;AAE3B,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAqPD,wBAAgB,YAAY,CAC1B,UAAU,EAAE,qBAAqB,EACjC,OAAO,EAAE,aAAa,GACrB,YAAY,CAmCd;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,YAAY,CAQnE;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,YAAY,EAMpD,CAAC"}