@skill-map/cli 0.58.0 → 0.60.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 (42) hide show
  1. package/dist/cli/tutorial/sm-tutorial/SKILL.md +12 -5
  2. package/dist/cli/tutorial/sm-tutorial/references/fixtures.md +15 -4
  3. package/dist/cli/tutorial/sm-tutorial/references/part-cli.md +3 -3
  4. package/dist/cli/tutorial/sm-tutorial/references/part-connect-harness.md +19 -15
  5. package/dist/cli/tutorial/sm-tutorial/references/part-daily-loop.md +4 -2
  6. package/dist/cli/tutorial/sm-tutorial/references/part-fundamentals.md +59 -44
  7. package/dist/cli/tutorial/sm-tutorial/references/part-project-kickoff.md +18 -0
  8. package/dist/cli.js +169 -84
  9. package/dist/index.js +3 -3
  10. package/dist/kernel/index.d.ts +20 -0
  11. package/dist/kernel/index.js +3 -3
  12. package/dist/ui/chunk-4N3NRZEH.js +809 -0
  13. package/dist/ui/chunk-5SSKJ7AM.js +1 -0
  14. package/dist/ui/{chunk-WB7FKIBP.js → chunk-7VUEZZFJ.js} +1 -1
  15. package/dist/ui/{chunk-P2DAPRK7.js → chunk-7X3DZNG4.js} +1 -1
  16. package/dist/ui/chunk-AKKFFP7Y.js +1 -0
  17. package/dist/ui/{chunk-ZJCU4CXS.js → chunk-COXOWJFC.js} +2 -2
  18. package/dist/ui/{chunk-JA4Z74I3.js → chunk-FRUHVCND.js} +1 -1
  19. package/dist/ui/chunk-GDZRZU57.js +1843 -0
  20. package/dist/ui/{chunk-K2MAVAHG.js → chunk-GKQA75EF.js} +1 -1
  21. package/dist/ui/chunk-HQ6M2HXK.js +369 -0
  22. package/dist/ui/chunk-JRVAI7GI.js +2 -0
  23. package/dist/ui/chunk-JTCIY3SL.js +742 -0
  24. package/dist/ui/chunk-MBBJJEUX.js +917 -0
  25. package/dist/ui/{chunk-LCOYSPKE.js → chunk-MGWGV4VD.js} +1 -1
  26. package/dist/ui/chunk-N6MUHKWR.js +105 -0
  27. package/dist/ui/chunk-OGXBHDY4.js +1 -0
  28. package/dist/ui/{chunk-KHARMPTZ.js → chunk-OVVTCPBJ.js} +1 -1
  29. package/dist/ui/{chunk-UBQUCSQ4.js → chunk-Q4PXVDJA.js} +1 -1
  30. package/dist/ui/chunk-ZYPXVXYF.js +4 -0
  31. package/dist/ui/index.html +1 -1
  32. package/dist/ui/main-5MKN73KQ.js +4 -0
  33. package/package.json +2 -2
  34. package/dist/ui/chunk-DPLNM4UD.js +0 -2
  35. package/dist/ui/chunk-G3ZVF4TM.js +0 -1
  36. package/dist/ui/chunk-H6O2DYVT.js +0 -1110
  37. package/dist/ui/chunk-KJZQIUZA.js +0 -917
  38. package/dist/ui/chunk-Q2A6FWC7.js +0 -4
  39. package/dist/ui/chunk-QVNZN5F7.js +0 -1843
  40. package/dist/ui/chunk-SXXFDP6V.js +0 -1
  41. package/dist/ui/chunk-YHJL5LP3.js +0 -913
  42. package/dist/ui/main-KOFMNAVE.js +0 -4
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]="7e4fdbab-f037-5030-b67a-732bb7ee46fd")}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]="7e14428f-63d1-5e95-8756-98cea6d3c76e")}catch(e){}}();
4
4
  import { existsSync as existsSync33 } from "fs";
5
5
  import { Builtins, Cli as Cli2 } from "clipanion";
6
6
 
@@ -250,7 +250,7 @@ function bucketByKind(kind, instance, bag) {
250
250
  // package.json
251
251
  var package_default = {
252
252
  name: "@skill-map/cli",
253
- version: "0.58.0",
253
+ version: "0.60.0",
254
254
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
255
255
  license: "MIT",
256
256
  type: "module",
@@ -1812,10 +1812,6 @@ var annotationFieldUnknownAnalyzer = {
1812
1812
  const rootKeys = indexRootContributions(contributions);
1813
1813
  const knownPluginIds = collectPluginIds(contributions);
1814
1814
  const issues = [];
1815
- const perNode = /* @__PURE__ */ new Map();
1816
- const bump2 = (nodePath) => {
1817
- perNode.set(nodePath, (perNode.get(nodePath) ?? 0) + 1);
1818
- };
1819
1815
  for (const node of ctx.nodes) {
1820
1816
  const root = sidecarRoots.get(node.path);
1821
1817
  if (!root) continue;
@@ -1833,7 +1829,6 @@ var annotationFieldUnknownAnalyzer = {
1833
1829
  }),
1834
1830
  data: { surface: "annotations", key }
1835
1831
  });
1836
- bump2(node.path);
1837
1832
  }
1838
1833
  }
1839
1834
  }
@@ -1862,7 +1857,6 @@ var annotationFieldUnknownAnalyzer = {
1862
1857
  }),
1863
1858
  data: { surface: "plugin-namespace", pluginId: key, key: contribKey }
1864
1859
  });
1865
- bump2(node.path);
1866
1860
  }
1867
1861
  continue;
1868
1862
  }
@@ -1876,10 +1870,8 @@ var annotationFieldUnknownAnalyzer = {
1876
1870
  }),
1877
1871
  data: { surface: "root", key }
1878
1872
  });
1879
- bump2(node.path);
1880
1873
  }
1881
1874
  }
1882
- void perNode;
1883
1875
  return issues;
1884
1876
  }
1885
1877
  };
@@ -2015,6 +2007,10 @@ var annotationStaleAnalyzer = {
2015
2007
  pluginId: CORE_PLUGIN_ID,
2016
2008
  kind: "analyzer",
2017
2009
  description: "Marks sidecars (`.sm`) that are out of date with their `.md`.",
2010
+ // Ships experimental (disabled by default, Decision #128), gated as a
2011
+ // unit with the `core/node-bump` action that resolves the drift it
2012
+ // reports.
2013
+ stability: "experimental",
2018
2014
  mode: "deterministic",
2019
2015
  // The natural fix is to bump the node: refreshes the sidecar hashes,
2020
2016
  // increments `annotations.version`, and stamps the audit block. The
@@ -2661,14 +2657,14 @@ var experimental = {
2661
2657
  slot: "card.footer.right",
2662
2658
  icon: "fa-solid fa-flask",
2663
2659
  label: "experimental",
2664
- emitWhenEmpty: false,
2660
+ emitWhenEmpty: true,
2665
2661
  priority: 10
2666
2662
  };
2667
2663
  var deprecated = {
2668
2664
  slot: "card.footer.right",
2669
2665
  icon: "pi-ban",
2670
2666
  label: "deprecated",
2671
- emitWhenEmpty: false,
2667
+ emitWhenEmpty: true,
2672
2668
  priority: 10
2673
2669
  };
2674
2670
  var nodeStabilityAnalyzer = {
@@ -3663,7 +3659,16 @@ var nodeBumpAction = {
3663
3659
  pluginId: CORE_PLUGIN_ID,
3664
3660
  kind: "action",
3665
3661
  description: "Marks a node as updated: bumps `annotations.version`, refreshes sidecar hashes, and records the timestamp.",
3662
+ // Ships experimental (disabled by default, Decision #128), gated as a
3663
+ // unit with the companion `core/annotation-stale` analyzer: a disabled
3664
+ // action projects no Bump button, so the button never appears without
3665
+ // the drift analyzer that motivates it.
3666
+ stability: "experimental",
3666
3667
  mode: "deterministic",
3668
+ // Declares the sidecar-write capability: `invoke()` returns a
3669
+ // `{ kind: 'sidecar' }` write, so consumers (the `allowSidecarWriters`
3670
+ // policy) can gate this action without invoking it.
3671
+ writes: ["sidecar"],
3667
3672
  ui: { bumpButton },
3668
3673
  project(ctx) {
3669
3674
  for (const node of ctx.nodes) {
@@ -3764,6 +3769,10 @@ var nodeSetStabilityAction = {
3764
3769
  kind: "action",
3765
3770
  description: "Sets the lifecycle stage of the current node (writes `stability` to the sidecar).",
3766
3771
  mode: "deterministic",
3772
+ // Declares the sidecar-write capability: `invoke()` returns a
3773
+ // `{ kind: 'sidecar' }` write, so the `allowSidecarWriters` policy can
3774
+ // gate this action without invoking it.
3775
+ writes: ["sidecar"],
3767
3776
  ui: { setStabilityButton },
3768
3777
  project(ctx) {
3769
3778
  for (const node of ctx.nodes) {
@@ -3826,33 +3835,18 @@ function invokeSetStability(input, ctx) {
3826
3835
  return { report, writes: [write] };
3827
3836
  }
3828
3837
 
3829
- // plugins/core/actions/node-set-tags/text.ts
3830
- var TAGS_TEXTS = {
3831
- /** Label of the inspector action button that edits the node's tags. */
3832
- editLabel: "Edit tags",
3833
- /** Prompt label for the string-list tags input. */
3834
- promptLabel: "Tags"
3835
- };
3836
-
3837
3838
  // plugins/core/actions/node-set-tags/index.ts
3838
3839
  var ID27 = "node-set-tags";
3839
- var setTagsButton = {
3840
- slot: "inspector.action.button",
3841
- priority: 15
3842
- };
3843
3840
  var nodeSetTagsAction = {
3844
3841
  id: ID27,
3845
3842
  pluginId: CORE_PLUGIN_ID,
3846
3843
  kind: "action",
3847
3844
  description: "Sets the taxonomy tags of the current node (writes `tags` to the sidecar; whole-array replace).",
3848
3845
  mode: "deterministic",
3849
- ui: { setTagsButton },
3850
- project(ctx) {
3851
- for (const node of ctx.nodes) {
3852
- if (node.virtual === true) continue;
3853
- emitSetTagsButton(ctx, node);
3854
- }
3855
- },
3846
+ // Declares the sidecar-write capability: `invoke()` returns a
3847
+ // `{ kind: 'sidecar' }` write, so the `allowSidecarWriters` policy can
3848
+ // gate this action without invoking it.
3849
+ writes: ["sidecar"],
3856
3850
  // The runtime contract uses generic <TInput, TReport>; this narrows
3857
3851
  // both. The cast is the standard pattern for built-ins that want
3858
3852
  // typed local I/O while staying compatible with the open generic.
@@ -3861,29 +3855,21 @@ var nodeSetTagsAction = {
3861
3855
  return invokeSetTags(input, ctx);
3862
3856
  }
3863
3857
  };
3864
- function emitSetTagsButton(ctx, node) {
3865
- ctx.emitContribution(node.path, setTagsButton, {
3866
- actionId: "core/node-set-tags",
3867
- label: TAGS_TEXTS.editLabel,
3868
- icon: "pi-tags",
3869
- enabled: true,
3870
- prompt: {
3871
- inputType: "string-list",
3872
- paramKey: "tags",
3873
- label: TAGS_TEXTS.promptLabel,
3874
- defaultValue: currentTags(node)
3875
- }
3876
- });
3877
- }
3878
- function currentTags(node) {
3879
- const ann = node.sidecar?.annotations;
3880
- if (!ann || typeof ann !== "object" || Array.isArray(ann)) return [];
3881
- const value = ann["tags"];
3882
- if (!Array.isArray(value)) return [];
3883
- return value.filter((t) => typeof t === "string");
3858
+ function sanitizeTags(raw) {
3859
+ if (!Array.isArray(raw)) return [];
3860
+ const seen = /* @__PURE__ */ new Set();
3861
+ const out = [];
3862
+ for (const entry of raw) {
3863
+ if (typeof entry !== "string") continue;
3864
+ const tag = entry.trim();
3865
+ if (tag.length === 0 || seen.has(tag)) continue;
3866
+ seen.add(tag);
3867
+ out.push(tag);
3868
+ }
3869
+ return out;
3884
3870
  }
3885
3871
  function invokeSetTags(input, ctx) {
3886
- const tags = Array.isArray(input.tags) ? input.tags : [];
3872
+ const tags = sanitizeTags(input.tags);
3887
3873
  const timestamp = ctx.now().toISOString();
3888
3874
  const write = {
3889
3875
  kind: "sidecar",
@@ -5306,6 +5292,7 @@ var CONFIG_LOADER_TEXTS = {
5306
5292
  var defaults_default = {
5307
5293
  schemaVersion: 1,
5308
5294
  allowEditSmFiles: false,
5295
+ allowSidecarWriters: true,
5309
5296
  tokenizer: "cl100k_base",
5310
5297
  roots: [],
5311
5298
  ignore: [],
@@ -5728,7 +5715,27 @@ var EConsentRequiredError = class extends Error {
5728
5715
  this.hintTarget = init.hintTarget;
5729
5716
  }
5730
5717
  };
5718
+ var ESidecarWritersForbiddenError = class extends Error {
5719
+ key;
5720
+ constructor(init) {
5721
+ super(
5722
+ `Sidecar-writing extensions are disabled in this project ('${init.key}' is false in .skill-map/settings.json). This is a team-level project policy and cannot be overridden.`
5723
+ );
5724
+ this.name = "ESidecarWritersForbiddenError";
5725
+ this.key = init.key;
5726
+ }
5727
+ };
5728
+ function assertSidecarWritersAllowed(cwd) {
5729
+ const allowed = readConfigValue("allowSidecarWriters", {
5730
+ cwd,
5731
+ default: true
5732
+ });
5733
+ if (allowed === false) {
5734
+ throw new ESidecarWritersForbiddenError({ key: "allowSidecarWriters" });
5735
+ }
5736
+ }
5731
5737
  function ensureSidecarWritesAllowed(opts) {
5738
+ assertSidecarWritersAllowed(opts.cwd);
5732
5739
  const allowed = readConfigValue("allowEditSmFiles", {
5733
5740
  cwd: opts.cwd,
5734
5741
  default: false
@@ -8635,13 +8642,18 @@ function applyIssueFilters(query, filter) {
8635
8642
  const tokens = filter.analyzerIds;
8636
8643
  q = q.where(
8637
8644
  ({ eb, or }) => or(
8638
- tokens.flatMap((token) => [
8639
- eb("analyzerId", "=", token),
8640
- // `'%/' || ?` keeps the LIKE pattern's `%` literal in the
8641
- // template and binds `token` separately, no interpolation of
8642
- // user input into the SQL string.
8643
- eb("analyzerId", "like", `%/${token}`)
8644
- ])
8645
+ tokens.flatMap((token) => {
8646
+ const conds = [
8647
+ eb("analyzerId", "=", token),
8648
+ // `'%/' || ?` keeps the LIKE pattern's `%` literal in the
8649
+ // template and binds `token` separately, no interpolation of
8650
+ // user input into the SQL string.
8651
+ eb("analyzerId", "like", `%/${token}`)
8652
+ ];
8653
+ const slash = token.indexOf("/");
8654
+ if (slash >= 0) conds.push(eb("analyzerId", "=", token.slice(slash + 1)));
8655
+ return conds;
8656
+ })
8645
8657
  )
8646
8658
  );
8647
8659
  }
@@ -9119,6 +9131,8 @@ var BumpCommand = class extends SmCommand {
9119
9131
  const flagError = this.#validateFlagCombo(ansi);
9120
9132
  if (flagError !== null) return flagError;
9121
9133
  const ctx = defaultRuntimeContext();
9134
+ const policyError = this.#assertWritersAllowed(ansi, ctx.cwd);
9135
+ if (policyError !== null) return policyError;
9122
9136
  const dbPath = resolveDbPath({ db: this.db, ...ctx });
9123
9137
  const persisted = await tryWithSqlite(
9124
9138
  { databasePath: dbPath, autoBackup: false },
@@ -9139,6 +9153,22 @@ var BumpCommand = class extends SmCommand {
9139
9153
  () => this.pending ? this.#runPending(persisted.nodes, ctx.cwd, ansi) : this.#runSingle(persisted.nodes, ctx.cwd, ansi)
9140
9154
  );
9141
9155
  }
9156
+ /**
9157
+ * Fail-fast guard for the `allowSidecarWriters: false` project policy.
9158
+ * Returns `ExitCode.Error` (after printing the policy message) when
9159
+ * sidecar writers are forbidden, or `null` when writes may proceed.
9160
+ * Re-throws any non-policy error so unexpected failures still surface.
9161
+ */
9162
+ #assertWritersAllowed(ansi, cwd) {
9163
+ try {
9164
+ assertSidecarWritersAllowed(cwd);
9165
+ return null;
9166
+ } catch (err) {
9167
+ if (!(err instanceof ESidecarWritersForbiddenError)) throw err;
9168
+ this.printer.error(`${ansi.red("\u2715")} ${err.message}`);
9169
+ return ExitCode.Error;
9170
+ }
9171
+ }
9142
9172
  /**
9143
9173
  * Three argument-validation guards, hoisted out of `run()` so the
9144
9174
  * lint complexity cap on `run()` is satisfied without an
@@ -9546,11 +9576,12 @@ import { Command as Command3, Option as Option3 } from "clipanion";
9546
9576
  function matchesAnalyzerFilter(analyzerId, filter) {
9547
9577
  if (filter.length === 0) return true;
9548
9578
  if (filter.includes(analyzerId)) return true;
9549
- const slashIdx = analyzerId.indexOf("/");
9550
- if (slashIdx >= 0) {
9551
- const short = analyzerId.slice(slashIdx + 1);
9552
- if (filter.includes(short)) return true;
9579
+ for (const entry of filter) {
9580
+ const slashIdx = entry.indexOf("/");
9581
+ if (slashIdx >= 0 && entry.slice(slashIdx + 1) === analyzerId) return true;
9553
9582
  }
9583
+ const argSlashIdx = analyzerId.indexOf("/");
9584
+ if (argSlashIdx >= 0 && filter.includes(analyzerId.slice(argSlashIdx + 1))) return true;
9554
9585
  return false;
9555
9586
  }
9556
9587
 
@@ -11125,6 +11156,7 @@ function filterBuiltInManifests(manifests, resolveEnabled) {
11125
11156
  function composeScanExtensions(opts) {
11126
11157
  const resolveEnabled = opts.resolveEnabled ?? opts.pluginRuntime.resolveEnabled;
11127
11158
  const resolveSettings = opts.resolveSettings;
11159
+ const forbidSidecarWriters = opts.forbidSidecarWriters === true;
11128
11160
  const providers = [];
11129
11161
  const extractors = [];
11130
11162
  const analyzers = [];
@@ -11134,7 +11166,8 @@ function composeScanExtensions(opts) {
11134
11166
  accumulateBuiltInScanExtensions(
11135
11167
  { providers, extractors, analyzers, hooks, actions },
11136
11168
  resolveEnabled,
11137
- resolveSettings
11169
+ resolveSettings,
11170
+ forbidSidecarWriters
11138
11171
  );
11139
11172
  }
11140
11173
  for (const ext of opts.pluginRuntime.extensions.providers) {
@@ -11150,7 +11183,9 @@ function composeScanExtensions(opts) {
11150
11183
  if (isPluginExtensionEnabled(ext, resolveEnabled)) hooks.push(withResolvedSettings(ext, resolveSettings));
11151
11184
  }
11152
11185
  for (const ext of opts.pluginRuntime.extensions.actions) {
11153
- if (isPluginExtensionEnabled(ext, resolveEnabled)) actions.push(ext);
11186
+ if (!isPluginExtensionEnabled(ext, resolveEnabled)) continue;
11187
+ if (forbidSidecarWriters && isSidecarWriterAction(ext)) continue;
11188
+ actions.push(ext);
11154
11189
  }
11155
11190
  const finalProviders = opts.killSwitches?.providers === true ? [] : providers;
11156
11191
  const finalExtractors = opts.killSwitches?.extractors === true ? [] : extractors;
@@ -11170,7 +11205,10 @@ function withResolvedSettings(ext, resolveSettings) {
11170
11205
  if (!resolveSettings) return ext;
11171
11206
  return { ...ext, resolvedSettings: resolveSettings(ext) };
11172
11207
  }
11173
- function accumulateBuiltInScanExtensions(buckets, resolveEnabled, resolveSettings) {
11208
+ function isSidecarWriterAction(ext) {
11209
+ return ext.writes?.includes("sidecar") === true;
11210
+ }
11211
+ function accumulateBuiltInScanExtensions(buckets, resolveEnabled, resolveSettings, forbidSidecarWriters = false) {
11174
11212
  for (const plugin of builtInPlugins) {
11175
11213
  for (const ext of plugin.extensions) {
11176
11214
  if (!isBuiltInExtensionEnabled(plugin, ext, resolveEnabled)) continue;
@@ -11188,6 +11226,7 @@ function accumulateBuiltInScanExtensions(buckets, resolveEnabled, resolveSetting
11188
11226
  buckets.hooks.push(withResolvedSettings(ext, resolveSettings));
11189
11227
  break;
11190
11228
  case "action":
11229
+ if (forbidSidecarWriters && isSidecarWriterAction(ext)) break;
11191
11230
  buckets.actions.push(ext);
11192
11231
  break;
11193
11232
  case "formatter":
@@ -18430,7 +18469,8 @@ function registerExtensions(kernel, pluginRuntime, opts, cfg) {
18430
18469
  const composeOpts = {
18431
18470
  noBuiltIns: opts.noBuiltIns,
18432
18471
  pluginRuntime,
18433
- resolveSettings: buildSettingsResolver(cfg)
18472
+ resolveSettings: buildSettingsResolver(cfg),
18473
+ forbidSidecarWriters: cfg.allowSidecarWriters === false
18434
18474
  };
18435
18475
  if (opts.killSwitches) composeOpts.killSwitches = opts.killSwitches;
18436
18476
  if (opts.resolveEnabledOverride) composeOpts.resolveEnabled = opts.resolveEnabledOverride;
@@ -23643,7 +23683,8 @@ function createWatcherRuntime(opts) {
23643
23683
  noBuiltIns: opts.noBuiltIns,
23644
23684
  pluginRuntime,
23645
23685
  resolveEnabled: resolveEnabledOverride,
23646
- resolveSettings: buildSettingsResolver(cfg)
23686
+ resolveSettings: buildSettingsResolver(cfg),
23687
+ forbidSidecarWriters: cfg.allowSidecarWriters === false
23647
23688
  };
23648
23689
  if (opts.killSwitches) composeOpts.killSwitches = opts.killSwitches;
23649
23690
  const composed = composeScanExtensions(composeOpts);
@@ -24653,6 +24694,7 @@ var ScanCompareCommand = class extends SmCommand {
24653
24694
  noBuiltIns: false,
24654
24695
  pluginRuntime,
24655
24696
  resolveSettings: buildSettingsResolver(cfg),
24697
+ forbidSidecarWriters: cfg.allowSidecarWriters === false,
24656
24698
  killSwitches: readConformanceKillSwitches()
24657
24699
  });
24658
24700
  let current;
@@ -25119,8 +25161,13 @@ var SERVER_TEXTS = {
25119
25161
  // silently widen the scan surface.
25120
25162
  projectPrefsBodyNotJson: "Request body must be valid JSON.",
25121
25163
  projectPrefsBodyNotObject: "Request body must be a JSON object.",
25122
- projectPrefsBodyEmpty: "Request body must contain a `scan` block with `referencePaths`.",
25164
+ projectPrefsBodyEmpty: "Request body must contain `allowSidecarWriters` and/or a `scan` block with `referencePaths`.",
25123
25165
  projectPrefsConfirmNotBoolean: "`confirm` must be a boolean.",
25166
+ projectPrefsSidecarWritersNotBoolean: "`allowSidecarWriters` must be a boolean.",
25167
+ // Server-stderr advisory after `PATCH /api/project-preferences`
25168
+ // toggles the committed sidecar-writer policy. Lets the operator see
25169
+ // the team-shared change land without opening settings.json.
25170
+ projectPrefsSidecarWritersSet: "project-prefs: allowSidecarWriters = {{value}}",
25124
25171
  projectPrefsScanNotObject: '`scan` must be an object (e.g. `{"scan": {"referencePaths": ["~/Documents"]}}`).',
25125
25172
  projectPrefsListNotArray: "`{{key}}` must be an array of strings.",
25126
25173
  projectPrefsListEntryNotString: "`{{key}}` entries must be strings.",
@@ -26950,6 +26997,10 @@ function registerProjectPreferencesRoute(app, deps) {
26950
26997
  function buildEnvelope3(deps) {
26951
26998
  const cwd = deps.runtimeContext.cwd;
26952
26999
  return {
27000
+ allowSidecarWriters: readConfigValue("allowSidecarWriters", {
27001
+ cwd,
27002
+ default: true
27003
+ }) ?? true,
26953
27004
  scan: {
26954
27005
  referencePaths: readConfigValue("scan.referencePaths", {
26955
27006
  cwd,
@@ -26959,9 +27010,15 @@ function buildEnvelope3(deps) {
26959
27010
  };
26960
27011
  }
26961
27012
  async function applyPatch3(deps, body) {
26962
- const writes = collectWrites(body);
26963
- if (writes.length === 0) return;
26964
27013
  const cwd = deps.runtimeContext.cwd;
27014
+ const policyChanged = typeof body.allowSidecarWriters === "boolean" && writeSidecarWritersPolicy(body.allowSidecarWriters, cwd);
27015
+ const scan = applyScanWrites(body, cwd);
27016
+ if (policyChanged || scan.mutated) await maybeRestartWatcher2(deps);
27017
+ if (policyChanged || scan.attempted) deps.configService.reload();
27018
+ }
27019
+ function applyScanWrites(body, cwd) {
27020
+ const writes = collectWrites(body);
27021
+ if (writes.length === 0) return { attempted: false, mutated: false };
26965
27022
  const missingPaths = collectMissingPaths(writes, cwd);
26966
27023
  if (missingPaths.length > 0) {
26967
27024
  throw new HTTPException12(400, {
@@ -26979,12 +27036,29 @@ async function applyPatch3(deps, body) {
26979
27036
  })
26980
27037
  });
26981
27038
  }
26982
- let scanSurfaceMutated = false;
27039
+ let mutated = false;
26983
27040
  for (const w of writes) {
26984
- if (runWrite(w, cwd)) scanSurfaceMutated = true;
27041
+ if (runWrite(w, cwd)) mutated = true;
27042
+ }
27043
+ return { attempted: true, mutated };
27044
+ }
27045
+ function writeSidecarWritersPolicy(value, cwd) {
27046
+ const before = readConfigValue("allowSidecarWriters", { cwd, default: true }) ?? true;
27047
+ if (before === value) return false;
27048
+ try {
27049
+ writeConfigValue("allowSidecarWriters", value, { target: "project", cwd });
27050
+ } catch (err) {
27051
+ throw new HTTPException12(400, {
27052
+ message: tx(SERVER_TEXTS.projectPrefsPersistFailed, {
27053
+ key: "allowSidecarWriters",
27054
+ message: formatErrorMessage(err)
27055
+ })
27056
+ });
26985
27057
  }
26986
- if (scanSurfaceMutated) await maybeRestartWatcher2(deps);
26987
- deps.configService.reload();
27058
+ log.warn(
27059
+ tx(SERVER_TEXTS.projectPrefsSidecarWritersSet, { value: String(value) })
27060
+ );
27061
+ return true;
26988
27062
  }
26989
27063
  function collectWrites(body) {
26990
27064
  if (!body.scan) return [];
@@ -27092,9 +27166,10 @@ function isExistingDirectory(entry, cwd) {
27092
27166
  var PATCH_BODY_SCHEMA3 = {
27093
27167
  type: "object",
27094
27168
  additionalProperties: false,
27095
- required: ["scan"],
27169
+ anyOf: [{ required: ["allowSidecarWriters"] }, { required: ["scan"] }],
27096
27170
  properties: {
27097
27171
  confirm: { type: "boolean" },
27172
+ allowSidecarWriters: { type: "boolean" },
27098
27173
  scan: {
27099
27174
  type: "object",
27100
27175
  additionalProperties: false,
@@ -27113,10 +27188,11 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
27113
27188
  notObject: SERVER_TEXTS.projectPrefsBodyNotObject,
27114
27189
  invalid: SERVER_TEXTS.projectPrefsBodyEmpty,
27115
27190
  mapping: {
27116
- "/scan:required": SERVER_TEXTS.projectPrefsBodyEmpty,
27191
+ ":anyOf": SERVER_TEXTS.projectPrefsBodyEmpty,
27117
27192
  "/scan:minProperties": SERVER_TEXTS.projectPrefsBodyEmpty,
27118
27193
  "/scan:type:object": SERVER_TEXTS.projectPrefsScanNotObject,
27119
27194
  "/confirm:type:boolean": SERVER_TEXTS.projectPrefsConfirmNotBoolean,
27195
+ "/allowSidecarWriters:type:boolean": SERVER_TEXTS.projectPrefsSidecarWritersNotBoolean,
27120
27196
  "/scan/referencePaths:type:array": tx(SERVER_TEXTS.projectPrefsListNotArray, { key: "scan.referencePaths" }),
27121
27197
  "/scan/referencePaths/*:type:string": tx(SERVER_TEXTS.projectPrefsListEntryNotString, { key: "scan.referencePaths" }),
27122
27198
  "/scan/referencePaths/*:pattern": SERVER_TEXTS.projectPrefsEntryHasComma
@@ -27348,6 +27424,7 @@ async function materializeWrites(writes, body, cwd) {
27348
27424
  }
27349
27425
  } catch (err) {
27350
27426
  if (err instanceof EConsentRequiredError) throw err;
27427
+ if (err instanceof ESidecarWritersForbiddenError) throw err;
27351
27428
  throw new HTTPException15(500, { message: formatErrorMessage(err) });
27352
27429
  }
27353
27430
  }
@@ -28092,18 +28169,26 @@ function formatError2(err, c) {
28092
28169
  };
28093
28170
  return c.json(envelope, 400);
28094
28171
  }
28172
+ const sidecar = formatSidecarConsentError(err, c);
28173
+ if (sidecar) return sidecar;
28174
+ return formatInternalErrorFallThrough(err, c);
28175
+ }
28176
+ function formatSidecarConsentError(err, c) {
28095
28177
  if (err instanceof EConsentRequiredError) {
28096
28178
  const envelope = {
28097
28179
  ok: false,
28098
- error: {
28099
- code: "confirm-required",
28100
- message: err.message,
28101
- details: { key: err.key }
28102
- }
28180
+ error: { code: "confirm-required", message: err.message, details: { key: err.key } }
28103
28181
  };
28104
28182
  return c.json(envelope, 412);
28105
28183
  }
28106
- return formatInternalErrorFallThrough(err, c);
28184
+ if (err instanceof ESidecarWritersForbiddenError) {
28185
+ const envelope = {
28186
+ ok: false,
28187
+ error: { code: "sidecar-writers-forbidden", message: err.message, details: { key: err.key } }
28188
+ };
28189
+ return c.json(envelope, 403);
28190
+ }
28191
+ return null;
28107
28192
  }
28108
28193
  function formatConflict(err, c) {
28109
28194
  if (err instanceof ActionRefusedError) {
@@ -30973,4 +31058,4 @@ function resolveBareDefault() {
30973
31058
  process.exit(ExitCode.Error);
30974
31059
  }
30975
31060
  //# sourceMappingURL=cli.js.map
30976
- //# debugId=7e4fdbab-f037-5030-b67a-732bb7ee46fd
31061
+ //# debugId=7e14428f-63d1-5e95-8756-98cea6d3c76e
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // kernel/i18n/registry.texts.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]="4343b658-4e21-5fab-9600-420246ce199b")}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]="da133af4-c6e3-580c-95d3-203903628998")}catch(e){}}();
4
4
  var REGISTRY_TEXTS = {
5
5
  duplicateExtension: "Extension already registered: {{kind}}:{{qualifiedId}}",
6
6
  unknownKind: "Unknown extension kind: {{kind}}",
@@ -102,7 +102,7 @@ import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
102
102
  // package.json
103
103
  var package_default = {
104
104
  name: "@skill-map/cli",
105
- version: "0.58.0",
105
+ version: "0.60.0",
106
106
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
107
107
  license: "MIT",
108
108
  type: "module",
@@ -3845,4 +3845,4 @@ export {
3845
3845
  runScanWithRenames
3846
3846
  };
3847
3847
  //# sourceMappingURL=index.js.map
3848
- //# debugId=4343b658-4e21-5fab-9600-420246ce199b
3848
+ //# debugId=da133af4-c6e3-580c-95d3-203903628998
@@ -3268,6 +3268,13 @@ type TActionWrite = {
3268
3268
  path: string;
3269
3269
  changes: Record<string, unknown>;
3270
3270
  };
3271
+ /**
3272
+ * The discriminant kinds an Action may emit through `IActionResult.writes`.
3273
+ * Today the union has a single member (`'sidecar'`); the alias keeps the
3274
+ * manifest `writes` capability (`IAction.writes`) in lock-step with the
3275
+ * runtime write union so a new write kind only has to be added in one place.
3276
+ */
3277
+ type TActionWriteKind = TActionWrite['kind'];
3271
3278
  interface IActionResult<TReport = unknown> {
3272
3279
  report: TReport;
3273
3280
  writes?: TActionWrite[];
@@ -3353,6 +3360,19 @@ interface IAction extends IExtensionBase {
3353
3360
  * `expectedDurationSeconds` with the `prob*` prefix convention.
3354
3361
  */
3355
3362
  probExpectedDurationSeconds?: number;
3363
+ /**
3364
+ * Declared persistent-write capability. Mirrors the `kind`s this
3365
+ * Action's `invoke()` may return in `IActionResult.writes`. Today the
3366
+ * only kind is `'sidecar'` (the Action creates / modifies a `.sm`
3367
+ * annotation sidecar). An Action that returns a sidecar write MUST
3368
+ * declare `['sidecar']` here: the manifest declaration is what
3369
+ * consumers gate on WITHOUT invoking the action, so the
3370
+ * `allowSidecarWriters: false` project policy can drop every
3371
+ * sidecar-writer from the scan composer (its `inspector.action.button`
3372
+ * never projects) and the sidecar store can refuse the write. Absent =
3373
+ * the Action performs no persistent writes (read-only / report-only).
3374
+ */
3375
+ writes?: TActionWriteKind[];
3356
3376
  /**
3357
3377
  * Optional declarative filter; absent → applies to every node.
3358
3378
  */
@@ -1,6 +1,6 @@
1
1
  // kernel/i18n/registry.texts.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]="0597c2d7-10e5-56f2-81a2-8aef61ae4bed")}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]="694d5ef3-1132-5669-b493-3082eef46469")}catch(e){}}();
4
4
  var REGISTRY_TEXTS = {
5
5
  duplicateExtension: "Extension already registered: {{kind}}:{{qualifiedId}}",
6
6
  unknownKind: "Unknown extension kind: {{kind}}",
@@ -102,7 +102,7 @@ import { Tiktoken as Tiktoken2 } from "js-tiktoken/lite";
102
102
  // package.json
103
103
  var package_default = {
104
104
  name: "@skill-map/cli",
105
- version: "0.58.0",
105
+ version: "0.60.0",
106
106
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
107
107
  license: "MIT",
108
108
  type: "module",
@@ -3845,4 +3845,4 @@ export {
3845
3845
  runScanWithRenames
3846
3846
  };
3847
3847
  //# sourceMappingURL=index.js.map
3848
- //# debugId=0597c2d7-10e5-56f2-81a2-8aef61ae4bed
3848
+ //# debugId=694d5ef3-1132-5669-b493-3082eef46469