@skill-map/cli 0.59.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 (43) 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 +160 -76
  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-COXOWJFC.js +3 -0
  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-BCEJCDZB.js +0 -3
  35. package/dist/ui/chunk-DPLNM4UD.js +0 -2
  36. package/dist/ui/chunk-G3ZVF4TM.js +0 -1
  37. package/dist/ui/chunk-H6O2DYVT.js +0 -1110
  38. package/dist/ui/chunk-KJZQIUZA.js +0 -917
  39. package/dist/ui/chunk-Q2A6FWC7.js +0 -4
  40. package/dist/ui/chunk-QVNZN5F7.js +0 -1843
  41. package/dist/ui/chunk-SXXFDP6V.js +0 -1
  42. package/dist/ui/chunk-YHJL5LP3.js +0 -913
  43. package/dist/ui/main-PHRAKC4A.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]="88ff98f5-2b47-5849-9567-922fc880f5bc")}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.59.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",
@@ -2657,14 +2657,14 @@ var experimental = {
2657
2657
  slot: "card.footer.right",
2658
2658
  icon: "fa-solid fa-flask",
2659
2659
  label: "experimental",
2660
- emitWhenEmpty: false,
2660
+ emitWhenEmpty: true,
2661
2661
  priority: 10
2662
2662
  };
2663
2663
  var deprecated = {
2664
2664
  slot: "card.footer.right",
2665
2665
  icon: "pi-ban",
2666
2666
  label: "deprecated",
2667
- emitWhenEmpty: false,
2667
+ emitWhenEmpty: true,
2668
2668
  priority: 10
2669
2669
  };
2670
2670
  var nodeStabilityAnalyzer = {
@@ -3665,6 +3665,10 @@ var nodeBumpAction = {
3665
3665
  // the drift analyzer that motivates it.
3666
3666
  stability: "experimental",
3667
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"],
3668
3672
  ui: { bumpButton },
3669
3673
  project(ctx) {
3670
3674
  for (const node of ctx.nodes) {
@@ -3765,6 +3769,10 @@ var nodeSetStabilityAction = {
3765
3769
  kind: "action",
3766
3770
  description: "Sets the lifecycle stage of the current node (writes `stability` to the sidecar).",
3767
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"],
3768
3776
  ui: { setStabilityButton },
3769
3777
  project(ctx) {
3770
3778
  for (const node of ctx.nodes) {
@@ -3827,33 +3835,18 @@ function invokeSetStability(input, ctx) {
3827
3835
  return { report, writes: [write] };
3828
3836
  }
3829
3837
 
3830
- // plugins/core/actions/node-set-tags/text.ts
3831
- var TAGS_TEXTS = {
3832
- /** Label of the inspector action button that edits the node's tags. */
3833
- editLabel: "Edit tags",
3834
- /** Prompt label for the string-list tags input. */
3835
- promptLabel: "Tags"
3836
- };
3837
-
3838
3838
  // plugins/core/actions/node-set-tags/index.ts
3839
3839
  var ID27 = "node-set-tags";
3840
- var setTagsButton = {
3841
- slot: "inspector.action.button",
3842
- priority: 15
3843
- };
3844
3840
  var nodeSetTagsAction = {
3845
3841
  id: ID27,
3846
3842
  pluginId: CORE_PLUGIN_ID,
3847
3843
  kind: "action",
3848
3844
  description: "Sets the taxonomy tags of the current node (writes `tags` to the sidecar; whole-array replace).",
3849
3845
  mode: "deterministic",
3850
- ui: { setTagsButton },
3851
- project(ctx) {
3852
- for (const node of ctx.nodes) {
3853
- if (node.virtual === true) continue;
3854
- emitSetTagsButton(ctx, node);
3855
- }
3856
- },
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"],
3857
3850
  // The runtime contract uses generic <TInput, TReport>; this narrows
3858
3851
  // both. The cast is the standard pattern for built-ins that want
3859
3852
  // typed local I/O while staying compatible with the open generic.
@@ -3862,29 +3855,21 @@ var nodeSetTagsAction = {
3862
3855
  return invokeSetTags(input, ctx);
3863
3856
  }
3864
3857
  };
3865
- function emitSetTagsButton(ctx, node) {
3866
- ctx.emitContribution(node.path, setTagsButton, {
3867
- actionId: "core/node-set-tags",
3868
- label: TAGS_TEXTS.editLabel,
3869
- icon: "pi-tags",
3870
- enabled: true,
3871
- prompt: {
3872
- inputType: "string-list",
3873
- paramKey: "tags",
3874
- label: TAGS_TEXTS.promptLabel,
3875
- defaultValue: currentTags(node)
3876
- }
3877
- });
3878
- }
3879
- function currentTags(node) {
3880
- const ann = node.sidecar?.annotations;
3881
- if (!ann || typeof ann !== "object" || Array.isArray(ann)) return [];
3882
- const value = ann["tags"];
3883
- if (!Array.isArray(value)) return [];
3884
- 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;
3885
3870
  }
3886
3871
  function invokeSetTags(input, ctx) {
3887
- const tags = Array.isArray(input.tags) ? input.tags : [];
3872
+ const tags = sanitizeTags(input.tags);
3888
3873
  const timestamp = ctx.now().toISOString();
3889
3874
  const write = {
3890
3875
  kind: "sidecar",
@@ -5307,6 +5292,7 @@ var CONFIG_LOADER_TEXTS = {
5307
5292
  var defaults_default = {
5308
5293
  schemaVersion: 1,
5309
5294
  allowEditSmFiles: false,
5295
+ allowSidecarWriters: true,
5310
5296
  tokenizer: "cl100k_base",
5311
5297
  roots: [],
5312
5298
  ignore: [],
@@ -5729,7 +5715,27 @@ var EConsentRequiredError = class extends Error {
5729
5715
  this.hintTarget = init.hintTarget;
5730
5716
  }
5731
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
+ }
5732
5737
  function ensureSidecarWritesAllowed(opts) {
5738
+ assertSidecarWritersAllowed(opts.cwd);
5733
5739
  const allowed = readConfigValue("allowEditSmFiles", {
5734
5740
  cwd: opts.cwd,
5735
5741
  default: false
@@ -8636,13 +8642,18 @@ function applyIssueFilters(query, filter) {
8636
8642
  const tokens = filter.analyzerIds;
8637
8643
  q = q.where(
8638
8644
  ({ eb, or }) => or(
8639
- tokens.flatMap((token) => [
8640
- eb("analyzerId", "=", token),
8641
- // `'%/' || ?` keeps the LIKE pattern's `%` literal in the
8642
- // template and binds `token` separately, no interpolation of
8643
- // user input into the SQL string.
8644
- eb("analyzerId", "like", `%/${token}`)
8645
- ])
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
+ })
8646
8657
  )
8647
8658
  );
8648
8659
  }
@@ -9120,6 +9131,8 @@ var BumpCommand = class extends SmCommand {
9120
9131
  const flagError = this.#validateFlagCombo(ansi);
9121
9132
  if (flagError !== null) return flagError;
9122
9133
  const ctx = defaultRuntimeContext();
9134
+ const policyError = this.#assertWritersAllowed(ansi, ctx.cwd);
9135
+ if (policyError !== null) return policyError;
9123
9136
  const dbPath = resolveDbPath({ db: this.db, ...ctx });
9124
9137
  const persisted = await tryWithSqlite(
9125
9138
  { databasePath: dbPath, autoBackup: false },
@@ -9140,6 +9153,22 @@ var BumpCommand = class extends SmCommand {
9140
9153
  () => this.pending ? this.#runPending(persisted.nodes, ctx.cwd, ansi) : this.#runSingle(persisted.nodes, ctx.cwd, ansi)
9141
9154
  );
9142
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
+ }
9143
9172
  /**
9144
9173
  * Three argument-validation guards, hoisted out of `run()` so the
9145
9174
  * lint complexity cap on `run()` is satisfied without an
@@ -9547,11 +9576,12 @@ import { Command as Command3, Option as Option3 } from "clipanion";
9547
9576
  function matchesAnalyzerFilter(analyzerId, filter) {
9548
9577
  if (filter.length === 0) return true;
9549
9578
  if (filter.includes(analyzerId)) return true;
9550
- const slashIdx = analyzerId.indexOf("/");
9551
- if (slashIdx >= 0) {
9552
- const short = analyzerId.slice(slashIdx + 1);
9553
- 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;
9554
9582
  }
9583
+ const argSlashIdx = analyzerId.indexOf("/");
9584
+ if (argSlashIdx >= 0 && filter.includes(analyzerId.slice(argSlashIdx + 1))) return true;
9555
9585
  return false;
9556
9586
  }
9557
9587
 
@@ -11126,6 +11156,7 @@ function filterBuiltInManifests(manifests, resolveEnabled) {
11126
11156
  function composeScanExtensions(opts) {
11127
11157
  const resolveEnabled = opts.resolveEnabled ?? opts.pluginRuntime.resolveEnabled;
11128
11158
  const resolveSettings = opts.resolveSettings;
11159
+ const forbidSidecarWriters = opts.forbidSidecarWriters === true;
11129
11160
  const providers = [];
11130
11161
  const extractors = [];
11131
11162
  const analyzers = [];
@@ -11135,7 +11166,8 @@ function composeScanExtensions(opts) {
11135
11166
  accumulateBuiltInScanExtensions(
11136
11167
  { providers, extractors, analyzers, hooks, actions },
11137
11168
  resolveEnabled,
11138
- resolveSettings
11169
+ resolveSettings,
11170
+ forbidSidecarWriters
11139
11171
  );
11140
11172
  }
11141
11173
  for (const ext of opts.pluginRuntime.extensions.providers) {
@@ -11151,7 +11183,9 @@ function composeScanExtensions(opts) {
11151
11183
  if (isPluginExtensionEnabled(ext, resolveEnabled)) hooks.push(withResolvedSettings(ext, resolveSettings));
11152
11184
  }
11153
11185
  for (const ext of opts.pluginRuntime.extensions.actions) {
11154
- if (isPluginExtensionEnabled(ext, resolveEnabled)) actions.push(ext);
11186
+ if (!isPluginExtensionEnabled(ext, resolveEnabled)) continue;
11187
+ if (forbidSidecarWriters && isSidecarWriterAction(ext)) continue;
11188
+ actions.push(ext);
11155
11189
  }
11156
11190
  const finalProviders = opts.killSwitches?.providers === true ? [] : providers;
11157
11191
  const finalExtractors = opts.killSwitches?.extractors === true ? [] : extractors;
@@ -11171,7 +11205,10 @@ function withResolvedSettings(ext, resolveSettings) {
11171
11205
  if (!resolveSettings) return ext;
11172
11206
  return { ...ext, resolvedSettings: resolveSettings(ext) };
11173
11207
  }
11174
- 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) {
11175
11212
  for (const plugin of builtInPlugins) {
11176
11213
  for (const ext of plugin.extensions) {
11177
11214
  if (!isBuiltInExtensionEnabled(plugin, ext, resolveEnabled)) continue;
@@ -11189,6 +11226,7 @@ function accumulateBuiltInScanExtensions(buckets, resolveEnabled, resolveSetting
11189
11226
  buckets.hooks.push(withResolvedSettings(ext, resolveSettings));
11190
11227
  break;
11191
11228
  case "action":
11229
+ if (forbidSidecarWriters && isSidecarWriterAction(ext)) break;
11192
11230
  buckets.actions.push(ext);
11193
11231
  break;
11194
11232
  case "formatter":
@@ -18431,7 +18469,8 @@ function registerExtensions(kernel, pluginRuntime, opts, cfg) {
18431
18469
  const composeOpts = {
18432
18470
  noBuiltIns: opts.noBuiltIns,
18433
18471
  pluginRuntime,
18434
- resolveSettings: buildSettingsResolver(cfg)
18472
+ resolveSettings: buildSettingsResolver(cfg),
18473
+ forbidSidecarWriters: cfg.allowSidecarWriters === false
18435
18474
  };
18436
18475
  if (opts.killSwitches) composeOpts.killSwitches = opts.killSwitches;
18437
18476
  if (opts.resolveEnabledOverride) composeOpts.resolveEnabled = opts.resolveEnabledOverride;
@@ -23644,7 +23683,8 @@ function createWatcherRuntime(opts) {
23644
23683
  noBuiltIns: opts.noBuiltIns,
23645
23684
  pluginRuntime,
23646
23685
  resolveEnabled: resolveEnabledOverride,
23647
- resolveSettings: buildSettingsResolver(cfg)
23686
+ resolveSettings: buildSettingsResolver(cfg),
23687
+ forbidSidecarWriters: cfg.allowSidecarWriters === false
23648
23688
  };
23649
23689
  if (opts.killSwitches) composeOpts.killSwitches = opts.killSwitches;
23650
23690
  const composed = composeScanExtensions(composeOpts);
@@ -24654,6 +24694,7 @@ var ScanCompareCommand = class extends SmCommand {
24654
24694
  noBuiltIns: false,
24655
24695
  pluginRuntime,
24656
24696
  resolveSettings: buildSettingsResolver(cfg),
24697
+ forbidSidecarWriters: cfg.allowSidecarWriters === false,
24657
24698
  killSwitches: readConformanceKillSwitches()
24658
24699
  });
24659
24700
  let current;
@@ -25120,8 +25161,13 @@ var SERVER_TEXTS = {
25120
25161
  // silently widen the scan surface.
25121
25162
  projectPrefsBodyNotJson: "Request body must be valid JSON.",
25122
25163
  projectPrefsBodyNotObject: "Request body must be a JSON object.",
25123
- projectPrefsBodyEmpty: "Request body must contain a `scan` block with `referencePaths`.",
25164
+ projectPrefsBodyEmpty: "Request body must contain `allowSidecarWriters` and/or a `scan` block with `referencePaths`.",
25124
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}}",
25125
25171
  projectPrefsScanNotObject: '`scan` must be an object (e.g. `{"scan": {"referencePaths": ["~/Documents"]}}`).',
25126
25172
  projectPrefsListNotArray: "`{{key}}` must be an array of strings.",
25127
25173
  projectPrefsListEntryNotString: "`{{key}}` entries must be strings.",
@@ -26951,6 +26997,10 @@ function registerProjectPreferencesRoute(app, deps) {
26951
26997
  function buildEnvelope3(deps) {
26952
26998
  const cwd = deps.runtimeContext.cwd;
26953
26999
  return {
27000
+ allowSidecarWriters: readConfigValue("allowSidecarWriters", {
27001
+ cwd,
27002
+ default: true
27003
+ }) ?? true,
26954
27004
  scan: {
26955
27005
  referencePaths: readConfigValue("scan.referencePaths", {
26956
27006
  cwd,
@@ -26960,9 +27010,15 @@ function buildEnvelope3(deps) {
26960
27010
  };
26961
27011
  }
26962
27012
  async function applyPatch3(deps, body) {
26963
- const writes = collectWrites(body);
26964
- if (writes.length === 0) return;
26965
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 };
26966
27022
  const missingPaths = collectMissingPaths(writes, cwd);
26967
27023
  if (missingPaths.length > 0) {
26968
27024
  throw new HTTPException12(400, {
@@ -26980,12 +27036,29 @@ async function applyPatch3(deps, body) {
26980
27036
  })
26981
27037
  });
26982
27038
  }
26983
- let scanSurfaceMutated = false;
27039
+ let mutated = false;
26984
27040
  for (const w of writes) {
26985
- 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
+ });
26986
27057
  }
26987
- if (scanSurfaceMutated) await maybeRestartWatcher2(deps);
26988
- deps.configService.reload();
27058
+ log.warn(
27059
+ tx(SERVER_TEXTS.projectPrefsSidecarWritersSet, { value: String(value) })
27060
+ );
27061
+ return true;
26989
27062
  }
26990
27063
  function collectWrites(body) {
26991
27064
  if (!body.scan) return [];
@@ -27093,9 +27166,10 @@ function isExistingDirectory(entry, cwd) {
27093
27166
  var PATCH_BODY_SCHEMA3 = {
27094
27167
  type: "object",
27095
27168
  additionalProperties: false,
27096
- required: ["scan"],
27169
+ anyOf: [{ required: ["allowSidecarWriters"] }, { required: ["scan"] }],
27097
27170
  properties: {
27098
27171
  confirm: { type: "boolean" },
27172
+ allowSidecarWriters: { type: "boolean" },
27099
27173
  scan: {
27100
27174
  type: "object",
27101
27175
  additionalProperties: false,
@@ -27114,10 +27188,11 @@ var parsePatchBody4 = makeBodyValidator(PATCH_BODY_SCHEMA3, {
27114
27188
  notObject: SERVER_TEXTS.projectPrefsBodyNotObject,
27115
27189
  invalid: SERVER_TEXTS.projectPrefsBodyEmpty,
27116
27190
  mapping: {
27117
- "/scan:required": SERVER_TEXTS.projectPrefsBodyEmpty,
27191
+ ":anyOf": SERVER_TEXTS.projectPrefsBodyEmpty,
27118
27192
  "/scan:minProperties": SERVER_TEXTS.projectPrefsBodyEmpty,
27119
27193
  "/scan:type:object": SERVER_TEXTS.projectPrefsScanNotObject,
27120
27194
  "/confirm:type:boolean": SERVER_TEXTS.projectPrefsConfirmNotBoolean,
27195
+ "/allowSidecarWriters:type:boolean": SERVER_TEXTS.projectPrefsSidecarWritersNotBoolean,
27121
27196
  "/scan/referencePaths:type:array": tx(SERVER_TEXTS.projectPrefsListNotArray, { key: "scan.referencePaths" }),
27122
27197
  "/scan/referencePaths/*:type:string": tx(SERVER_TEXTS.projectPrefsListEntryNotString, { key: "scan.referencePaths" }),
27123
27198
  "/scan/referencePaths/*:pattern": SERVER_TEXTS.projectPrefsEntryHasComma
@@ -27349,6 +27424,7 @@ async function materializeWrites(writes, body, cwd) {
27349
27424
  }
27350
27425
  } catch (err) {
27351
27426
  if (err instanceof EConsentRequiredError) throw err;
27427
+ if (err instanceof ESidecarWritersForbiddenError) throw err;
27352
27428
  throw new HTTPException15(500, { message: formatErrorMessage(err) });
27353
27429
  }
27354
27430
  }
@@ -28093,18 +28169,26 @@ function formatError2(err, c) {
28093
28169
  };
28094
28170
  return c.json(envelope, 400);
28095
28171
  }
28172
+ const sidecar = formatSidecarConsentError(err, c);
28173
+ if (sidecar) return sidecar;
28174
+ return formatInternalErrorFallThrough(err, c);
28175
+ }
28176
+ function formatSidecarConsentError(err, c) {
28096
28177
  if (err instanceof EConsentRequiredError) {
28097
28178
  const envelope = {
28098
28179
  ok: false,
28099
- error: {
28100
- code: "confirm-required",
28101
- message: err.message,
28102
- details: { key: err.key }
28103
- }
28180
+ error: { code: "confirm-required", message: err.message, details: { key: err.key } }
28104
28181
  };
28105
28182
  return c.json(envelope, 412);
28106
28183
  }
28107
- 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;
28108
28192
  }
28109
28193
  function formatConflict(err, c) {
28110
28194
  if (err instanceof ActionRefusedError) {
@@ -30974,4 +31058,4 @@ function resolveBareDefault() {
30974
31058
  process.exit(ExitCode.Error);
30975
31059
  }
30976
31060
  //# sourceMappingURL=cli.js.map
30977
- //# debugId=88ff98f5-2b47-5849-9567-922fc880f5bc
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]="76b71233-68c3-5deb-90fd-07f4d545740f")}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.59.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=76b71233-68c3-5deb-90fd-07f4d545740f
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]="9f668117-2fa7-5470-a1b8-9431144567d1")}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.59.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=9f668117-2fa7-5470-a1b8-9431144567d1
3848
+ //# debugId=694d5ef3-1132-5669-b493-3082eef46469