@skill-map/cli 0.47.0 → 0.48.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.
Files changed (34) hide show
  1. package/README.md +3 -3
  2. package/dist/cli/tutorial/sm-master/SKILL.md +3 -2
  3. package/dist/cli/tutorial/sm-tutorial/SKILL.md +2 -2
  4. package/dist/cli.js +441 -108
  5. package/dist/index.js +7 -5
  6. package/dist/kernel/index.d.ts +23 -28
  7. package/dist/kernel/index.js +7 -5
  8. package/dist/ui/{chunk-UV3QRBRR.js → chunk-2GTH7ZLV.js} +1 -1
  9. package/dist/ui/chunk-5K5WASS7.js +315 -0
  10. package/dist/ui/{chunk-WPUUCIS3.js → chunk-AVGEDQNI.js} +1 -1
  11. package/dist/ui/chunk-AWNZZYAU.js +1 -0
  12. package/dist/ui/{chunk-7K36273M.js → chunk-BKU4RCQK.js} +1 -1
  13. package/dist/ui/{chunk-RT7E4S5B.js → chunk-FSJSSLYD.js} +1 -1
  14. package/dist/ui/{chunk-CRWK2NFZ.js → chunk-IDZ7ZQXM.js} +1 -1
  15. package/dist/ui/chunk-K2LHWJKO.js +2190 -0
  16. package/dist/ui/chunk-L56QU7EF.js +2 -0
  17. package/dist/ui/chunk-NPK64R5H.js +123 -0
  18. package/dist/ui/chunk-OBYZDEVO.js +1 -0
  19. package/dist/ui/{chunk-UIUGLD7F.js → chunk-Q747VBQL.js} +3 -3
  20. package/dist/ui/{chunk-3HLMBEDX.js → chunk-QNFHGPFR.js} +1 -1
  21. package/dist/ui/chunk-Y6VK27P4.js +1 -0
  22. package/dist/ui/{chunk-CO2ZOUSD.js → chunk-YQ7ZKAFY.js} +1 -1
  23. package/dist/ui/index.html +2 -2
  24. package/dist/ui/{main-55GYZX6C.js → main-KXXADOQV.js} +3 -3
  25. package/dist/ui/{styles-HI4A6IWA.css → styles-HWRPHKTJ.css} +1 -1
  26. package/package.json +5 -3
  27. package/dist/ui/chunk-3AKR33GE.js +0 -1
  28. package/dist/ui/chunk-EPBUSS3I.js +0 -2
  29. package/dist/ui/chunk-K365TVPA.js +0 -1
  30. package/dist/ui/chunk-PO2VZMOB.js +0 -123
  31. package/dist/ui/chunk-VNA3TMIO.js +0 -1
  32. package/dist/ui/chunk-XWU3YFSM.js +0 -315
  33. package/dist/ui/chunk-YOF6HQCQ.js +0 -2190
  34. package/dist/ui/chunk-ZZJ7XWDX.js +0 -1
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // cli/entry.ts
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="926243f9-3707-54bc-9aa6-c68955118161")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a68e2b18-f1ae-5a4d-8cd7-72ddbf9688ab")}catch(e){}}();
4
4
  import { existsSync as existsSync33 } from "fs";
5
5
  import { Builtins, Cli as Cli2 } from "clipanion";
6
6
 
@@ -246,7 +246,7 @@ function bucketByKind(kind, instance, bag) {
246
246
  // package.json
247
247
  var package_default = {
248
248
  name: "@skill-map/cli",
249
- version: "0.47.0",
249
+ version: "0.48.0",
250
250
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
251
251
  license: "MIT",
252
252
  type: "module",
@@ -299,9 +299,11 @@ var package_default = {
299
299
  "lint:fix": "eslint . --fix",
300
300
  "build-built-ins": "node ../scripts/generate-built-ins.js",
301
301
  "built-ins:check": "node ../scripts/generate-built-ins.js --check",
302
+ "view-catalog": "node ../scripts/generate-view-catalog.js",
303
+ "view-catalog:check": "node ../scripts/generate-view-catalog.js --check",
302
304
  prebuild: "pnpm build-built-ins",
303
305
  validate: "pnpm validate:compile && pnpm validate:test",
304
- "validate:compile": "pnpm typecheck && pnpm lint && pnpm build && pnpm built-ins:check",
306
+ "validate:compile": "pnpm typecheck && pnpm lint && pnpm build && pnpm built-ins:check && pnpm view-catalog:check",
305
307
  "validate:test": "pnpm test:ci",
306
308
  pretest: "tsup",
307
309
  "pretest:coverage": "tsup",
@@ -1950,10 +1952,11 @@ var annotationStaleAnalyzer = {
1950
1952
  slot: "card.footer.right",
1951
1953
  icon: "pi-clock",
1952
1954
  emitWhenEmpty: true,
1953
- // First in the footer-right cluster: drift is the operator's
1954
- // entry point for "this node disagrees with its sidecar",
1955
- // followed by stability, then the severity counters.
1956
- priority: 10
1955
+ // Sits right after the stability badge and before the severity
1956
+ // counters: stability is the node's declared lifecycle state,
1957
+ // drift is "this node disagrees with its sidecar", then the
1958
+ // warn / error counts anchor the right edge.
1959
+ priority: 20
1957
1960
  }
1958
1961
  },
1959
1962
  evaluate(ctx) {
@@ -2589,22 +2592,23 @@ var nodeStabilityAnalyzer = {
2589
2592
  description: "Reports a node's stability stage (`experimental`, `deprecated`) on the card.",
2590
2593
  mode: "deterministic",
2591
2594
  ui: {
2592
- // Second in the footer-right cluster, after the drift chip and
2593
- // before the severity counters. Stability is a state badge, not a
2594
- // count, so its priority sits between the two semantic zones.
2595
+ // First in the footer-right cluster: stability is the node's
2596
+ // declared lifecycle state, so it leads, followed by the drift
2597
+ // chip and then the severity counters. It's a state badge, not a
2598
+ // count, so it stays left of the numeric zone.
2595
2599
  experimental: {
2596
2600
  slot: "card.footer.right",
2597
2601
  icon: "fa-solid fa-flask",
2598
2602
  label: "experimental",
2599
2603
  emitWhenEmpty: false,
2600
- priority: 20
2604
+ priority: 10
2601
2605
  },
2602
2606
  deprecated: {
2603
2607
  slot: "card.footer.right",
2604
2608
  icon: "pi-ban",
2605
2609
  label: "deprecated",
2606
2610
  emitWhenEmpty: false,
2607
- priority: 20
2611
+ priority: 10
2608
2612
  }
2609
2613
  },
2610
2614
  evaluate(ctx) {
@@ -3009,7 +3013,7 @@ import { dirname as dirname2, resolve as resolve4 } from "path";
3009
3013
  import { createRequire as createRequire2 } from "module";
3010
3014
  import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
3011
3015
 
3012
- // kernel/types/view-catalog.ts
3016
+ // kernel/types/view-catalog.generated.ts
3013
3017
  var ALL_SLOT_NAMES = [
3014
3018
  "card.title.right",
3015
3019
  "card.subtitle.left",
@@ -19999,6 +20003,12 @@ var PLUGINS_TEXTS = {
19999
20003
  */
20000
20004
  createInvalidId: "{{glyph}} Plugin id must be kebab-case lowercase (got: {{id}}).\n {{hint}}\n",
20001
20005
  createInvalidIdHint: "Use a-z, 0-9, and hyphens between segments (e.g. `my-plugin`, `kw-counter`).",
20006
+ /**
20007
+ * §3.1b two-line block. Rejected when the `<kind>` positional is not one
20008
+ * of the closed extension-kind catalog; the hint lists the valid kinds.
20009
+ */
20010
+ createInvalidKind: "{{glyph}} Unknown extension kind (got: {{kind}}).\n {{hint}}\n",
20011
+ createInvalidKindHint: "Use one of: {{kinds}}.",
20002
20012
  /**
20003
20013
  * §3.1b two-line block. Target directory exists and `--force` was not
20004
20014
  * passed; the hint surfaces the override flag.
@@ -20006,10 +20016,10 @@ var PLUGINS_TEXTS = {
20006
20016
  createRefuseOverwrite: "{{glyph}} Refusing to overwrite {{targetDir}}.\n {{hint}}\n",
20007
20017
  createRefuseOverwriteHint: "Pass --force to overwrite the existing directory.",
20008
20018
  /**
20009
- * Success block printed after scaffolding. Follows the no-em-dash rule
20010
- * across every line.
20019
+ * Success block printed after scaffolding. Kind-agnostic (the main stub
20020
+ * path is interpolated). Follows the no-em-dash rule across every line.
20011
20021
  */
20012
- createSuccess: "Created {{targetDir}}\nNext:\n - Edit {{pluginId}}/extractors/{{pluginId}}-extractor/index.js (the extract() body)\n - Run sm scan to see the contribution surface\n - sm plugins slots list: browse other slots\n",
20022
+ createSuccess: "Created {{targetDir}}\nNext:\n - Edit {{mainFile}}\n - Run sm plugins doctor to confirm it loads\n - sm plugins slots list: browse slots and input-types\n",
20013
20023
  // --- slots list verb -------------------------------------------------
20014
20024
  /** Section header for the view-slots catalogue. */
20015
20025
  slotsListHeaderViewSlots: " View slots ({{count}})\n",
@@ -21291,72 +21301,137 @@ function resolveBareToggle(id, catalogue) {
21291
21301
 
21292
21302
  // cli/commands/plugins/create.ts
21293
21303
  import { existsSync as existsSync25, mkdirSync as mkdirSync5, writeFileSync } from "fs";
21294
- import { join as join18, resolve as resolve33 } from "path";
21304
+ import { dirname as dirname17, join as join18, resolve as resolve33 } from "path";
21295
21305
  import { Command as Command26, Option as Option25 } from "clipanion";
21296
- var PluginsCreateCommand = class extends SmCommand {
21297
- static paths = [["plugins", "create"]];
21298
- static usage = Command26.Usage({
21299
- category: "Plugins",
21300
- description: "Scaffold a new plugin directory.",
21301
- details: "Emits plugin.json + extension stub + README. Pre-filled with one view contribution (slot `card.footer.left`) and one setting (`string-list`); edit to taste. Use `sm plugins slots list` to see other options."
21302
- });
21303
- pluginId = Option25.String({ required: true, name: "plugin-id" });
21304
- at = Option25.String("--at", { required: false });
21305
- force = Option25.Boolean("--force", false);
21306
- async run() {
21307
- const ansi = this.ansiFor("stderr");
21308
- const errGlyph = ansi.red("\u2715");
21309
- if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(this.pluginId)) {
21310
- this.printer.error(
21311
- tx(PLUGINS_TEXTS.createInvalidId, {
21312
- glyph: errGlyph,
21313
- id: sanitizeForTerminal(this.pluginId),
21314
- hint: ansi.dim(PLUGINS_TEXTS.createInvalidIdHint)
21315
- })
21316
- );
21317
- return ExitCode.Error;
21306
+
21307
+ // cli/commands/plugins/scaffold/action.ts
21308
+ function indexStub(extId) {
21309
+ return `/**
21310
+ * Generated by \`sm plugins create\`. Edit the invoke() body.
21311
+ *
21312
+ * Loader contract: default export object literal; \`kind\` (action) and id
21313
+ * (\`${extId}\`) come from the folder layout (structure-as-truth), so
21314
+ * neither is declared here. Every Action carries a sibling
21315
+ * \`report.schema.json\`; a deterministic Action must NOT carry a
21316
+ * \`prompt.md\` (that is the probabilistic surface).
21317
+ *
21318
+ * \`invoke(input, ctx)\` returns \`{ report, writes? }\`. The report must
21319
+ * match this folder's \`report.schema.json\` (which extends the shared
21320
+ * report-base: \`confidence\` + \`safety\`). \`writes\` is an optional
21321
+ * array of side-effect descriptors the kernel materialises after the
21322
+ * call returns (e.g. a sidecar write); keep \`invoke()\` itself pure.
21323
+ *
21324
+ * See: spec/plugin-author-guide.md, spec/schemas/extensions/action.schema.json
21325
+ */
21326
+ export default {
21327
+ version: '0.1.0',
21328
+ description: 'Demo deterministic action; returns a fixed report.',
21329
+ mode: 'deterministic',
21330
+
21331
+ invoke(input, ctx) {
21332
+ return {
21333
+ report: {
21334
+ ok: true,
21335
+ confidence: 1,
21336
+ safety: { injectionDetected: false, contentQuality: 'unknown' },
21337
+ },
21338
+ };
21339
+ },
21340
+ };
21341
+ `;
21342
+ }
21343
+ function reportSchema(pluginId, extId) {
21344
+ const schema = {
21345
+ $schema: "https://json-schema.org/draft/2020-12/schema",
21346
+ $id: `urn:skill-map:${pluginId}/${extId}/report`,
21347
+ title: "DemoReport",
21348
+ description: "Report returned by the demo action. Deterministic: confidence is fixed at 1 and no injection is detected.",
21349
+ type: "object",
21350
+ required: ["confidence", "safety"],
21351
+ additionalProperties: false,
21352
+ properties: {
21353
+ ok: { type: "boolean" },
21354
+ confidence: { type: "number", minimum: 0, maximum: 1 },
21355
+ safety: {
21356
+ type: "object",
21357
+ required: ["injectionDetected", "contentQuality"],
21358
+ additionalProperties: false,
21359
+ properties: {
21360
+ injectionDetected: { type: "boolean" },
21361
+ contentQuality: {
21362
+ type: "string",
21363
+ enum: ["high", "medium", "low", "unknown"]
21364
+ }
21365
+ }
21366
+ }
21318
21367
  }
21319
- const ctx = defaultRuntimeContext();
21320
- const baseDir = defaultProjectPluginsDir(ctx);
21321
- const targetDir = this.at ? resolve33(this.at) : join18(baseDir, this.pluginId);
21322
- if (existsSync25(targetDir) && !this.force) {
21323
- this.printer.error(
21324
- tx(PLUGINS_TEXTS.createRefuseOverwrite, {
21325
- glyph: errGlyph,
21326
- targetDir: sanitizeForTerminal(targetDir),
21327
- hint: ansi.dim(PLUGINS_TEXTS.createRefuseOverwriteHint)
21328
- })
21329
- );
21330
- return ExitCode.Error;
21368
+ };
21369
+ return JSON.stringify(schema, null, 2) + "\n";
21370
+ }
21371
+ var actionGenerator = {
21372
+ folder: "actions",
21373
+ files(pluginId) {
21374
+ const ext = `${pluginId}-action`;
21375
+ return [
21376
+ { relPath: `actions/${ext}/index.js`, contents: indexStub(ext) },
21377
+ { relPath: `actions/${ext}/report.schema.json`, contents: reportSchema(pluginId, ext) }
21378
+ ];
21379
+ }
21380
+ };
21381
+
21382
+ // cli/commands/plugins/scaffold/analyzer.ts
21383
+ function stub(extId) {
21384
+ return `/**
21385
+ * Generated by \`sm plugins create\`. Edit the evaluate() body.
21386
+ *
21387
+ * Loader contract: default export object literal; \`kind\` (analyzer) and
21388
+ * id (\`${extId}\`) come from the folder layout (structure-as-truth), so
21389
+ * neither is declared here. Re-declaring them is \`invalid-manifest\`.
21390
+ *
21391
+ * \`evaluate(ctx)\` runs once per scan and returns \`Issue[]\`. The kernel
21392
+ * stamps \`analyzerId\` from this extension's id and validates each issue
21393
+ * (\`severity\` must be 'error' | 'warn' | 'info'; off-spec issues drop
21394
+ * with an \`extension.error\` diagnostic). \`ctx.nodes\` / \`ctx.links\`
21395
+ * are the scanned graph; \`ctx.accumulatedIssues\` is the live issue
21396
+ * accumulator (read-only) for late-phase aggregate analyzers.
21397
+ *
21398
+ * See: spec/plugin-author-guide.md, spec/schemas/extensions/analyzer.schema.json
21399
+ */
21400
+ export default {
21401
+ version: '0.1.0',
21402
+ description: 'Flags nodes that have no inbound or outbound links.',
21403
+ mode: 'deterministic',
21404
+
21405
+ evaluate(ctx) {
21406
+ const issues = [];
21407
+ for (const node of ctx.nodes) {
21408
+ if (node.linksInCount === 0 && node.linksOutCount === 0) {
21409
+ issues.push({
21410
+ severity: 'info',
21411
+ nodeIds: [node.path],
21412
+ message: 'Node has no links in or out (isolated).',
21413
+ });
21414
+ }
21331
21415
  }
21332
- const extractorName = `${this.pluginId}-extractor`;
21333
- mkdirSync5(join18(targetDir, "extractors", extractorName), { recursive: true });
21334
- const specVersion = installedSpecVersion();
21335
- const manifest = {
21336
- version: "0.1.0",
21337
- specCompat: `^${specVersion}`,
21338
- catalogCompat: "^1.0.0",
21339
- description: "Generated by `sm plugins create`. Edit to taste."
21340
- };
21341
- writeFileSync(
21342
- join18(targetDir, "plugin.json"),
21343
- JSON.stringify(manifest, null, 2) + "\n"
21344
- );
21345
- writeFileSync(
21346
- join18(targetDir, "extractors", extractorName, "index.js"),
21347
- scaffolderExtractorStub(extractorName)
21348
- );
21349
- writeFileSync(join18(targetDir, "README.md"), scaffolderReadme(this.pluginId));
21350
- this.printer.data(
21351
- tx(PLUGINS_TEXTS.createSuccess, {
21352
- targetDir: sanitizeForTerminal(targetDir),
21353
- pluginId: this.pluginId
21354
- })
21355
- );
21356
- return ExitCode.Ok;
21416
+ return issues;
21417
+ },
21418
+ };
21419
+ `;
21420
+ }
21421
+ var analyzerGenerator = {
21422
+ folder: "analyzers",
21423
+ files(pluginId) {
21424
+ const ext = `${pluginId}-analyzer`;
21425
+ return [{ relPath: `analyzers/${ext}/index.js`, contents: stub(ext) }];
21357
21426
  }
21358
21427
  };
21359
- function scaffolderExtractorStub(extractorId) {
21428
+
21429
+ // cli/commands/plugins/scaffold/defaults.ts
21430
+ var DEFAULT_SLOT = "card.footer.left";
21431
+ var DEFAULT_INPUT_TYPE = "string-list";
21432
+
21433
+ // cli/commands/plugins/scaffold/extractor.ts
21434
+ function stub2(extId) {
21360
21435
  return `/**
21361
21436
  * Generated by \`sm plugins create\`. Edit the extract() body.
21362
21437
  *
@@ -21366,19 +21441,19 @@ function scaffolderExtractorStub(extractorId) {
21366
21441
  * a \`load-error\`.
21367
21442
  *
21368
21443
  * Folder convention: this file lives at
21369
- * \`extractors/${extractorId}/index.js\`. The folder layout is the
21370
- * source of truth (structure-as-truth): the loader derives \`kind\`
21371
- * (\`extractor\`) from the parent folder and the id (\`${extractorId}\`)
21372
- * from the leaf folder, and injects \`pluginId\` from the plugin, so none
21373
- * of them are declared here. Re-declaring \`kind\` / \`id\` is rejected as
21444
+ * \`extractors/${extId}/index.js\`. The folder layout is the source of
21445
+ * truth (structure-as-truth): the loader derives \`kind\`
21446
+ * (\`extractor\`) from the parent folder and the id (\`${extId}\`) from
21447
+ * the leaf folder, and injects \`pluginId\` from the plugin, so none of
21448
+ * them are declared here. Re-declaring \`kind\` / \`id\` is rejected as
21374
21449
  * \`invalid-manifest\`.
21375
21450
  *
21376
21451
  * Declared view contributions (\`ui\`):
21377
- * - 'count' \u2192 slot \`card.footer.left\` (renders as a chip
21378
- * in the left footer of the node card)
21452
+ * - 'count' \u2192 slot \`${DEFAULT_SLOT}\` (renders as a chip in the node
21453
+ * card footer)
21379
21454
  *
21380
21455
  * Declared settings (\`settings\`):
21381
- * - 'keywords' (string-list) \u2192 exposed as ctx.settings.keywords
21456
+ * - 'keywords' (${DEFAULT_INPUT_TYPE}) \u2192 exposed as ctx.settings.keywords
21382
21457
  *
21383
21458
  * See: spec/plugin-author-guide.md \xA7View contributions
21384
21459
  * spec/view-slots.md
@@ -21390,7 +21465,7 @@ export default {
21390
21465
 
21391
21466
  settings: {
21392
21467
  keywords: {
21393
- type: 'string-list',
21468
+ type: '${DEFAULT_INPUT_TYPE}',
21394
21469
  label: 'Keywords to track',
21395
21470
  description: 'Words counted across each scanned node body.',
21396
21471
  default: ['TODO', 'FIXME'],
@@ -21400,7 +21475,7 @@ export default {
21400
21475
 
21401
21476
  ui: {
21402
21477
  count: {
21403
- slot: 'card.footer.left',
21478
+ slot: '${DEFAULT_SLOT}',
21404
21479
  icon: '\u{1F50D}',
21405
21480
  label: 'kw',
21406
21481
  emitWhenEmpty: false,
@@ -21421,26 +21496,284 @@ export default {
21421
21496
  };
21422
21497
  `;
21423
21498
  }
21424
- function scaffolderReadme(pluginId) {
21499
+ var extractorGenerator = {
21500
+ folder: "extractors",
21501
+ files(pluginId) {
21502
+ const ext = `${pluginId}-extractor`;
21503
+ return [{ relPath: `extractors/${ext}/index.js`, contents: stub2(ext) }];
21504
+ }
21505
+ };
21506
+
21507
+ // cli/commands/plugins/scaffold/formatter.ts
21508
+ function stub3(extId) {
21509
+ return `/**
21510
+ * Generated by \`sm plugins create\`. Edit the format() body.
21511
+ *
21512
+ * Loader contract: default export object literal; \`kind\` (formatter) and
21513
+ * id (\`${extId}\`) come from the folder layout (structure-as-truth). The
21514
+ * format id used by \`sm graph --format ${extId}\` is the folder name; do
21515
+ * NOT declare \`formatId\` (derived, re-declaring it is invalid-manifest).
21516
+ *
21517
+ * \`format(ctx)\` returns a string. \`ctx.scanResult\` is the full
21518
+ * persisted scan (the canonical input); \`ctx.nodes\` / \`ctx.links\` /
21519
+ * \`ctx.issues\` are the same data split out for convenience. Output has
21520
+ * no trailing newline guarantee; the calling verb adds one if it needs.
21521
+ *
21522
+ * See: spec/plugin-author-guide.md, spec/schemas/extensions/formatter.schema.json
21523
+ */
21524
+ export default {
21525
+ version: '0.1.0',
21526
+ description: 'Renders the scan as a one-line node/link/issue tally.',
21527
+ contentType: 'text/plain',
21528
+
21529
+ format(ctx) {
21530
+ const nodes = ctx.nodes || [];
21531
+ const links = ctx.links || [];
21532
+ const issues = ctx.issues || [];
21533
+ return \`nodes=\${nodes.length} links=\${links.length} issues=\${issues.length}\`;
21534
+ },
21535
+ };
21536
+ `;
21537
+ }
21538
+ var formatterGenerator = {
21539
+ folder: "formatters",
21540
+ files(pluginId) {
21541
+ const ext = `${pluginId}-formatter`;
21542
+ return [{ relPath: `formatters/${ext}/index.js`, contents: stub3(ext) }];
21543
+ }
21544
+ };
21545
+
21546
+ // cli/commands/plugins/scaffold/hook.ts
21547
+ function stub4(extId) {
21548
+ return `/**
21549
+ * Generated by \`sm plugins create\`. Edit the on() body.
21550
+ *
21551
+ * Loader contract: default export object literal; \`kind\` (hook) and id
21552
+ * (\`${extId}\`) come from the folder layout (structure-as-truth). Hooks
21553
+ * are deterministic-only: to run LLM work, enqueue a probabilistic Action
21554
+ * via \`ctx.queue('<plugin>/<action>', payload)\` instead.
21555
+ *
21556
+ * \`triggers\` is the curated set this hook subscribes to (one of: boot,
21557
+ * scan.started, scan.completed, extractor.completed, analyzer.completed,
21558
+ * action.completed, job.spawning, job.completed, job.failed, shutdown).
21559
+ * \`on(ctx)\` reads the event off \`ctx.event\` (\`ctx.event.data\` is the
21560
+ * payload); the dispatcher catches errors so a throw never breaks the verb.
21561
+ *
21562
+ * See: spec/plugin-author-guide.md, spec/schemas/extensions/hook.schema.json
21563
+ */
21564
+ export default {
21565
+ version: '0.1.0',
21566
+ description: 'Demo hook; reacts once on boot.',
21567
+ triggers: ['boot'],
21568
+
21569
+ async on(ctx) {
21570
+ const data = (ctx.event && ctx.event.data) || {};
21571
+ // No-op demo. A real hook reacts here, e.g. inspect \`data\`, write a
21572
+ // log line, or enqueue an action with ctx.queue(...).
21573
+ void data;
21574
+ },
21575
+ };
21576
+ `;
21577
+ }
21578
+ var hookGenerator = {
21579
+ folder: "hooks",
21580
+ files(pluginId) {
21581
+ const ext = `${pluginId}-hook`;
21582
+ return [{ relPath: `hooks/${ext}/index.js`, contents: stub4(ext) }];
21583
+ }
21584
+ };
21585
+
21586
+ // cli/commands/plugins/scaffold/provider.ts
21587
+ function titleCase(id) {
21588
+ return id.split("-").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
21589
+ }
21590
+ function stub5(pluginId, extId) {
21591
+ const label = titleCase(pluginId);
21592
+ return `/**
21593
+ * Generated by \`sm plugins create\`. Skeleton provider: edit classify()
21594
+ * and add a kind under \`kinds/\` (see the TODO below).
21595
+ *
21596
+ * Loader contract: default export object literal; \`kind\` (provider) and
21597
+ * id (\`${extId}\`) come from the folder layout (structure-as-truth). Do
21598
+ * NOT declare a \`kinds\` map here, it is rejected as invalid-manifest;
21599
+ * kinds are discovered from \`kinds/<kindName>/\` folders at the PLUGIN
21600
+ * root.
21601
+ *
21602
+ * \`presentation\` (label + hex color) is required: the UI reads it for
21603
+ * the lens dropdown, the topbar chip, and the per-node provider chip.
21604
+ * \`read\` configures file discovery (defaults to \`.md\` +
21605
+ * frontmatter-yaml when omitted). \`classify(path, frontmatter)\` returns
21606
+ * the node kind for files this provider owns, or null to let the file
21607
+ * fall through to the markdown fallback.
21608
+ *
21609
+ * TODO: this skeleton classifies nothing (returns null). To emit nodes:
21610
+ * 1. Pick a kind name, e.g. 'note'.
21611
+ * 2. Create \`kinds/note/schema.json\` (frontmatter JSON Schema,
21612
+ * allOf + $ref to frontmatter/base.schema.json).
21613
+ * 3. Create \`kinds/note/kind.json\` ({ "ui": { "label": "...",
21614
+ * "color": "#......" } }).
21615
+ * 4. Return 'note' from classify() for the files you own.
21616
+ *
21617
+ * See: spec/plugin-author-guide.md, spec/schemas/extensions/provider.schema.json
21618
+ */
21619
+ export default {
21620
+ version: '0.1.0',
21621
+ description: 'Skeleton provider; classify() returns null until a kind is added.',
21622
+
21623
+ presentation: {
21624
+ label: '${label}',
21625
+ color: '#6b7280',
21626
+ },
21627
+
21628
+ read: { extensions: ['.md'], parser: 'frontmatter-yaml' },
21629
+
21630
+ classify(path, frontmatter) {
21631
+ // TODO: return a kind name (see header) for files this provider owns.
21632
+ void path;
21633
+ void frontmatter;
21634
+ return null;
21635
+ },
21636
+ };
21637
+ `;
21638
+ }
21639
+ var providerGenerator = {
21640
+ folder: "providers",
21641
+ files(pluginId) {
21642
+ const ext = `${pluginId}-provider`;
21643
+ return [{ relPath: `providers/${ext}/index.js`, contents: stub5(pluginId, ext) }];
21644
+ }
21645
+ };
21646
+
21647
+ // cli/commands/plugins/scaffold/readme.ts
21648
+ function scaffolderReadme(pluginId, kind, mainFileRel) {
21425
21649
  return `# ${pluginId}
21426
21650
 
21427
- Generated by \`sm plugins create\`. Edit \`extractors/${pluginId}-extractor/index.js\` to taste.
21651
+ Generated by \`sm plugins create ${kind} ${pluginId}\`. Edit \`${mainFileRel}\` to taste.
21428
21652
 
21429
21653
  ## Verbs
21430
21654
 
21431
21655
  - \`sm plugins show ${pluginId}\`: manifest + load status
21432
21656
  - \`sm plugins doctor\`: full plugin diagnostic
21433
- - \`sm scan\`: re-emit contributions
21657
+ - \`sm scan\`: re-emit contributions / re-run analysis
21434
21658
 
21435
21659
  ## Resources
21436
21660
 
21437
- - \`spec/plugin-author-guide.md\` \xA7View contributions
21438
- - \`spec/view-slots.md\`: the closed catalog of slots
21439
- - \`spec/input-types.md\`: the closed catalog of input-types for settings
21440
- - \`sm plugins slots list\`: browse the catalog from the CLI
21661
+ - \`spec/plugin-author-guide.md\`: authoring guide for every kind
21662
+ - \`spec/view-slots.md\`: the closed catalog of view slots
21663
+ - \`spec/input-types.md\`: the closed catalog of setting input-types
21664
+ - \`sm plugins slots list\`: browse the slot / input-type catalog from the CLI
21441
21665
  `;
21442
21666
  }
21443
21667
 
21668
+ // cli/commands/plugins/scaffold/index.ts
21669
+ var GENERATORS = {
21670
+ provider: providerGenerator,
21671
+ extractor: extractorGenerator,
21672
+ analyzer: analyzerGenerator,
21673
+ action: actionGenerator,
21674
+ formatter: formatterGenerator,
21675
+ hook: hookGenerator
21676
+ };
21677
+ function pluginManifest(specVersion) {
21678
+ return {
21679
+ version: "0.1.0",
21680
+ specCompat: `^${specVersion}`,
21681
+ catalogCompat: "^1.0.0",
21682
+ description: "Generated by `sm plugins create`. Edit to taste."
21683
+ };
21684
+ }
21685
+ function generateScaffold(kind, pluginId, specVersion) {
21686
+ const extFiles = GENERATORS[kind].files(pluginId);
21687
+ const main = extFiles[0];
21688
+ if (!main) {
21689
+ throw new Error(`scaffold generator for kind '${kind}' returned no files`);
21690
+ }
21691
+ return [
21692
+ {
21693
+ relPath: "plugin.json",
21694
+ contents: JSON.stringify(pluginManifest(specVersion), null, 2) + "\n"
21695
+ },
21696
+ ...extFiles,
21697
+ { relPath: "README.md", contents: scaffolderReadme(pluginId, kind, main.relPath) }
21698
+ ];
21699
+ }
21700
+
21701
+ // cli/commands/plugins/create.ts
21702
+ var PluginsCreateCommand = class extends SmCommand {
21703
+ static paths = [["plugins", "create"]];
21704
+ static usage = Command26.Usage({
21705
+ category: "Plugins",
21706
+ description: "Scaffold a new plugin directory.",
21707
+ details: "Emits plugin.json + a per-kind extension stub + README. `<kind>` is one of: provider, extractor, analyzer, action, formatter, hook. The extractor stub ships one view contribution (slot `card.footer.left`) and one setting (`string-list`); edit to taste. Use `sm plugins slots list` to browse the slot / input-type catalog.",
21708
+ examples: [
21709
+ ["Scaffold an extractor", "$0 plugins create extractor kw-counter"],
21710
+ ["Scaffold an analyzer", "$0 plugins create analyzer my-linter"],
21711
+ ["Scaffold a provider", "$0 plugins create provider my-vendor"]
21712
+ ]
21713
+ });
21714
+ // First positional: the extension kind (required). Declared before
21715
+ // `pluginId` so clipanion assigns it the first positional slot.
21716
+ kind = Option25.String({ required: true, name: "kind" });
21717
+ pluginId = Option25.String({ required: true, name: "plugin-id" });
21718
+ at = Option25.String("--at", { required: false });
21719
+ force = Option25.Boolean("--force", false);
21720
+ async run() {
21721
+ const ansi = this.ansiFor("stderr");
21722
+ const errGlyph = ansi.red("\u2715");
21723
+ if (!EXTENSION_KINDS.includes(this.kind)) {
21724
+ this.printer.error(
21725
+ tx(PLUGINS_TEXTS.createInvalidKind, {
21726
+ glyph: errGlyph,
21727
+ kind: sanitizeForTerminal(this.kind),
21728
+ hint: ansi.dim(
21729
+ tx(PLUGINS_TEXTS.createInvalidKindHint, { kinds: EXTENSION_KINDS.join(", ") })
21730
+ )
21731
+ })
21732
+ );
21733
+ return ExitCode.Error;
21734
+ }
21735
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(this.pluginId)) {
21736
+ this.printer.error(
21737
+ tx(PLUGINS_TEXTS.createInvalidId, {
21738
+ glyph: errGlyph,
21739
+ id: sanitizeForTerminal(this.pluginId),
21740
+ hint: ansi.dim(PLUGINS_TEXTS.createInvalidIdHint)
21741
+ })
21742
+ );
21743
+ return ExitCode.Error;
21744
+ }
21745
+ const kind = this.kind;
21746
+ const ctx = defaultRuntimeContext();
21747
+ const baseDir = defaultProjectPluginsDir(ctx);
21748
+ const targetDir = this.at ? resolve33(this.at) : join18(baseDir, this.pluginId);
21749
+ if (existsSync25(targetDir) && !this.force) {
21750
+ this.printer.error(
21751
+ tx(PLUGINS_TEXTS.createRefuseOverwrite, {
21752
+ glyph: errGlyph,
21753
+ targetDir: sanitizeForTerminal(targetDir),
21754
+ hint: ansi.dim(PLUGINS_TEXTS.createRefuseOverwriteHint)
21755
+ })
21756
+ );
21757
+ return ExitCode.Error;
21758
+ }
21759
+ const specVersion = installedSpecVersion();
21760
+ const files = generateScaffold(kind, this.pluginId, specVersion);
21761
+ for (const file of files) {
21762
+ const abs = join18(targetDir, file.relPath);
21763
+ mkdirSync5(dirname17(abs), { recursive: true });
21764
+ writeFileSync(abs, file.contents);
21765
+ }
21766
+ const mainFile = `${kind}s/${this.pluginId}-${kind}/index.js`;
21767
+ this.printer.data(
21768
+ tx(PLUGINS_TEXTS.createSuccess, {
21769
+ targetDir: sanitizeForTerminal(targetDir),
21770
+ mainFile
21771
+ })
21772
+ );
21773
+ return ExitCode.Ok;
21774
+ }
21775
+ };
21776
+
21444
21777
  // cli/commands/plugins/slots.ts
21445
21778
  import { Command as Command27 } from "clipanion";
21446
21779
 
@@ -22078,7 +22411,7 @@ var SCAN_TEXTS = {
22078
22411
  import { Command as Command30, Option as Option28 } from "clipanion";
22079
22412
 
22080
22413
  // core/watcher/runtime.ts
22081
- import { dirname as dirname17 } from "path";
22414
+ import { dirname as dirname18 } from "path";
22082
22415
 
22083
22416
  // core/runtime/fresh-resolver.ts
22084
22417
  async function buildFreshResolver(deps) {
@@ -22321,7 +22654,7 @@ function createWatcherRuntime(opts) {
22321
22654
  roots: [
22322
22655
  cwd,
22323
22656
  // parent of `.skillmapignore`
22324
- dirname17(settingsPath)
22657
+ dirname18(settingsPath)
22325
22658
  // parent of `.skill-map/settings.json`
22326
22659
  ],
22327
22660
  cwd,
@@ -26699,7 +27032,7 @@ function validateNoUi(noUi, uiDist) {
26699
27032
 
26700
27033
  // server/paths.ts
26701
27034
  import { existsSync as existsSync30, statSync as statSync10 } from "fs";
26702
- import { dirname as dirname18, isAbsolute as isAbsolute11, join as join20, resolve as resolve37 } from "path";
27035
+ import { dirname as dirname19, isAbsolute as isAbsolute11, join as join20, resolve as resolve37 } from "path";
26703
27036
  import { fileURLToPath as fileURLToPath6 } from "url";
26704
27037
  var DEFAULT_UI_REL = join20("ui", "dist", "ui", "browser");
26705
27038
  var PACKAGE_UI_REL = "ui";
@@ -26724,7 +27057,7 @@ function isUiBundleDir(path) {
26724
27057
  function resolvePackageBundledUi() {
26725
27058
  let here;
26726
27059
  try {
26727
- here = dirname18(fileURLToPath6(import.meta.url));
27060
+ here = dirname19(fileURLToPath6(import.meta.url));
26728
27061
  } catch {
26729
27062
  return null;
26730
27063
  }
@@ -26737,7 +27070,7 @@ function resolvePackageBundledUiFrom(here) {
26737
27070
  if (isUiBundleDir(candidate)) return candidate;
26738
27071
  const distHere = join20(current, "dist", PACKAGE_UI_REL);
26739
27072
  if (isUiBundleDir(distHere)) return distHere;
26740
- const parent = dirname18(current);
27073
+ const parent = dirname19(current);
26741
27074
  if (parent === current) return null;
26742
27075
  current = parent;
26743
27076
  }
@@ -26748,7 +27081,7 @@ function walkUpForUi(startDir) {
26748
27081
  for (let i = 0; i < 64; i++) {
26749
27082
  const candidate = join20(current, DEFAULT_UI_REL);
26750
27083
  if (isUiBundleDir(candidate)) return candidate;
26751
- const parent = dirname18(current);
27084
+ const parent = dirname19(current);
26752
27085
  if (parent === current) return null;
26753
27086
  current = parent;
26754
27087
  }
@@ -28587,7 +28920,7 @@ var STUB_COMMANDS = [
28587
28920
 
28588
28921
  // cli/commands/tutorial.ts
28589
28922
  import { cpSync as cpSync2, existsSync as existsSync32, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync2, statSync as statSync11 } from "fs";
28590
- import { dirname as dirname19, join as join21, resolve as resolve39 } from "path";
28923
+ import { dirname as dirname20, join as join21, resolve as resolve39 } from "path";
28591
28924
  import { createInterface as createInterface5 } from "readline";
28592
28925
  import { fileURLToPath as fileURLToPath7 } from "url";
28593
28926
  import { Command as Command37, Option as Option35 } from "clipanion";
@@ -28650,14 +28983,14 @@ var VARIANT_SPECS = {
28650
28983
  tutorial: {
28651
28984
  slug: "sm-tutorial",
28652
28985
  sourceDir: ".claude/skills/sm-tutorial",
28653
- triggerEn: "start the tutorial",
28654
- triggerEs: "arranquemos el tutorial"
28986
+ triggerEn: "run the tutorial",
28987
+ triggerEs: "ejecuta el tutorial"
28655
28988
  },
28656
28989
  master: {
28657
28990
  slug: "sm-master",
28658
28991
  sourceDir: ".claude/skills/sm-master",
28659
- triggerEn: "advanced tutorial",
28660
- triggerEs: "tutorial maestro"
28992
+ triggerEn: "run the master tutorial",
28993
+ triggerEs: "ejecuta el tutorial maestro"
28661
28994
  }
28662
28995
  };
28663
28996
  var TutorialCommand = class extends SmCommand {
@@ -28733,7 +29066,7 @@ var TutorialCommand = class extends SmCommand {
28733
29066
  }
28734
29067
  try {
28735
29068
  rmSync2(targetDir, { recursive: true, force: true });
28736
- mkdirSync6(dirname19(targetDir), { recursive: true });
29069
+ mkdirSync6(dirname20(targetDir), { recursive: true });
28737
29070
  cpSync2(sourceDir, targetDir, { recursive: true });
28738
29071
  } catch (err) {
28739
29072
  this.printer.error(
@@ -28920,7 +29253,7 @@ function resolveSkillSourceDir(variant) {
28920
29253
  const cached = cachedSourceDirs.get(variant);
28921
29254
  if (cached !== void 0) return cached;
28922
29255
  const spec = VARIANT_SPECS[variant];
28923
- const here = dirname19(fileURLToPath7(import.meta.url));
29256
+ const here = dirname20(fileURLToPath7(import.meta.url));
28924
29257
  const candidates = [
28925
29258
  // dev: src/cli/commands/ → repo-root .claude/skills/<slug>/
28926
29259
  resolve39(here, "../../..", spec.sourceDir),
@@ -29155,4 +29488,4 @@ function resolveBareDefault() {
29155
29488
  process.exit(ExitCode.Error);
29156
29489
  }
29157
29490
  //# sourceMappingURL=cli.js.map
29158
- //# debugId=926243f9-3707-54bc-9aa6-c68955118161
29491
+ //# debugId=a68e2b18-f1ae-5a4d-8cd7-72ddbf9688ab