@sechroom/cli 2026.8.3 → 2026.8.4

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 (2) hide show
  1. package/dist/index.js +145 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1072,14 +1072,19 @@ async function fetchSetup(cfg, namespaceSlug) {
1072
1072
  "/operator-surface/setup",
1073
1073
  hasQuery ? { params: { query } } : {}
1074
1074
  );
1075
- if (error) throw new Error(`GET /operator-surface/setup failed: ${JSON.stringify(error)}`);
1075
+ if (error)
1076
+ throw new Error(
1077
+ `GET /operator-surface/setup failed: ${JSON.stringify(error)}`
1078
+ );
1076
1079
  return data;
1077
1080
  }
1078
1081
  async function listNamespaces(cfg) {
1079
1082
  const client = await makeClient(cfg);
1080
1083
  const { data } = await client.GET("/mcp-aggregator/namespaces", {});
1081
1084
  const rows = data ?? [];
1082
- return rows.filter((r) => typeof r.slug === "string").map((r) => ({ slug: r.slug, displayName: r.displayName ?? r.slug }));
1085
+ return rows.filter(
1086
+ (r) => typeof r.slug === "string"
1087
+ ).map((r) => ({ slug: r.slug, displayName: r.displayName ?? r.slug }));
1083
1088
  }
1084
1089
  function findSurface(setup, surfaceKey) {
1085
1090
  return setup.surfaces.find((s) => s.surfaceKey === surfaceKey);
@@ -1095,10 +1100,15 @@ function sectionSnippet(section) {
1095
1100
  }
1096
1101
  return null;
1097
1102
  }
1098
- function parseTagArtifactId(id) {
1103
+ function parseTagArtifactCandidates(id) {
1099
1104
  if (!id.startsWith("tag:")) return null;
1100
- const tags = id.slice("tag:".length).split(",").map((t) => t.trim()).filter((t) => t.length > 0);
1101
- return tags.length > 0 ? tags : null;
1105
+ const candidates = id.slice("tag:".length).split("|").map(
1106
+ (set) => set.split(",").map((t) => t.trim()).filter((t) => t.length > 0)
1107
+ ).filter((set) => set.length > 0);
1108
+ return candidates.length > 0 ? candidates : null;
1109
+ }
1110
+ function parseTagArtifactId(id) {
1111
+ return parseTagArtifactCandidates(id)?.[0] ?? null;
1102
1112
  }
1103
1113
  async function getPersonalWorkspaceId(cfg) {
1104
1114
  const client = await makeClient(cfg);
@@ -1107,40 +1117,102 @@ async function getPersonalWorkspaceId(cfg) {
1107
1117
  }
1108
1118
  async function fetchMemoryFields(cfg, id) {
1109
1119
  const client = await makeClient(cfg);
1110
- const { data } = await client.GET("/memories/{memoryId}", { params: { path: { memoryId: id } } });
1120
+ const { data } = await client.GET("/memories/{memoryId}", {
1121
+ params: { path: { memoryId: id } }
1122
+ });
1111
1123
  const env = data;
1112
1124
  const m = env?.item ?? env;
1113
1125
  if (!m) return null;
1114
1126
  const version = typeof m.currentVersion === "string" ? Number(m.currentVersion) : m.currentVersion;
1115
- return { text: m.text, title: m.title, tags: m.tags, version: Number.isFinite(version) ? version : void 0 };
1127
+ return {
1128
+ text: m.text,
1129
+ title: m.title,
1130
+ tags: m.tags,
1131
+ version: Number.isFinite(version) ? version : void 0
1132
+ };
1116
1133
  }
1117
- async function resolveInstruction(cfg, section, personalWorkspaceId) {
1118
- const client = await makeClient(cfg);
1119
- for (const artifact of section.artifacts) {
1120
- const tags = parseTagArtifactId(artifact.id);
1121
- if (!tags) continue;
1134
+ async function findTemplateByCandidates(client, candidates) {
1135
+ for (let i = 0; i < candidates.length; i++) {
1136
+ const tags = candidates[i];
1122
1137
  const { data } = await client.POST("/memories/search", {
1123
- body: { query: null, textQuery: null, semanticQuery: null, hybrid: false, limit: 1, includeArchived: false, includeSystem: false, tags }
1138
+ body: {
1139
+ query: null,
1140
+ textQuery: null,
1141
+ semanticQuery: null,
1142
+ hybrid: false,
1143
+ limit: 1,
1144
+ includeArchived: false,
1145
+ includeSystem: false,
1146
+ tags
1147
+ }
1124
1148
  });
1125
1149
  const hits = data?.items ?? [];
1126
1150
  if (hits.length === 0) continue;
1127
- const templateId = hits[0].id;
1151
+ return { id: hits[0].id, tags, via: i === 0 ? "preferred" : "fallback" };
1152
+ }
1153
+ return null;
1154
+ }
1155
+ function attemptedTagSets(section) {
1156
+ return section.artifacts.flatMap(
1157
+ (artifact) => parseTagArtifactCandidates(artifact.id) ?? []
1158
+ );
1159
+ }
1160
+ async function resolveInstruction(cfg, section, personalWorkspaceId) {
1161
+ const client = await makeClient(cfg);
1162
+ for (const artifact of section.artifacts) {
1163
+ const candidates = parseTagArtifactCandidates(artifact.id);
1164
+ if (!candidates) continue;
1165
+ const found = await findTemplateByCandidates(client, candidates);
1166
+ if (!found) continue;
1167
+ const templateId = found.id;
1128
1168
  const template = await fetchMemoryFields(cfg, templateId);
1129
- if (typeof template?.text !== "string" || template.text.length === 0) continue;
1130
- const templateTags = template.tags ?? tags;
1169
+ if (typeof template?.text !== "string" || template.text.length === 0)
1170
+ continue;
1171
+ const templateTags = template.tags ?? found.tags;
1131
1172
  if (personalWorkspaceId) {
1132
1173
  const { data: ovr } = await client.POST("/memories/search", {
1133
- body: { query: null, textQuery: null, semanticQuery: null, hybrid: false, limit: 1, includeArchived: false, includeSystem: false, tags: ["sechroom:role:override", `sechroom:template-ref:${templateId}`], owner: { type: "Workspace", id: personalWorkspaceId } }
1174
+ body: {
1175
+ query: null,
1176
+ textQuery: null,
1177
+ semanticQuery: null,
1178
+ hybrid: false,
1179
+ limit: 1,
1180
+ includeArchived: false,
1181
+ includeSystem: false,
1182
+ tags: [
1183
+ "sechroom:role:override",
1184
+ `sechroom:template-ref:${templateId}`
1185
+ ],
1186
+ owner: { type: "Workspace", id: personalWorkspaceId }
1187
+ }
1134
1188
  });
1135
1189
  const ovrHits = ovr?.items ?? [];
1136
1190
  if (ovrHits.length > 0) {
1137
1191
  const override = await fetchMemoryFields(cfg, ovrHits[0].id);
1138
1192
  if (typeof override?.text === "string" && override.text.length > 0) {
1139
- return { title: override.title ?? template.title ?? artifact.title, body: override.text, source: "override", templateId, templateTags, sourceRef: `${ovrHits[0].id}@v${override.version ?? 1}` };
1193
+ return {
1194
+ title: override.title ?? template.title ?? artifact.title,
1195
+ body: override.text,
1196
+ source: "override",
1197
+ templateId,
1198
+ templateTags,
1199
+ resolvedVia: found.via,
1200
+ resolvedTags: found.tags,
1201
+ sourceRef: `${ovrHits[0].id}@v${override.version ?? 1}`
1202
+ };
1140
1203
  }
1141
1204
  }
1142
1205
  }
1143
- return { title: template.title ?? artifact.title, body: template.text, source: "template", templateId, templateTags, sourceRef: `${templateId}@v${template.version ?? 1}` };
1206
+ return {
1207
+ title: template.title ?? artifact.title,
1208
+ body: template.text,
1209
+ source: "template",
1210
+ templateId,
1211
+ templateTags,
1212
+ resolvedVia: found.via,
1213
+ resolvedTags: found.tags,
1214
+ sourceRef: `${templateId}@v${template.version ?? 1}`
1215
+ };
1144
1216
  }
1145
1217
  return null;
1146
1218
  }
@@ -1152,8 +1224,10 @@ async function resolveWorkspaceConventions(cfg, section) {
1152
1224
  const mem = await fetchMemoryFields(cfg, artifact.id);
1153
1225
  if (typeof mem?.text === "string" && mem.text.trim().length > 0) {
1154
1226
  const ref = `${artifact.id}@v${mem.version ?? 1}`;
1155
- parts.push(`<!-- @sechroom/cli:section source=${ref} -->
1156
- ${mem.text.trim()}`);
1227
+ parts.push(
1228
+ `<!-- @sechroom/cli:section source=${ref} -->
1229
+ ${mem.text.trim()}`
1230
+ );
1157
1231
  refs.push(ref);
1158
1232
  }
1159
1233
  }
@@ -1165,7 +1239,10 @@ async function createOverride(cfg, template, personalWorkspaceId) {
1165
1239
  const overrideTags = template.templateTags.filter(
1166
1240
  (t) => t !== "sechroom:role:template" && !t.startsWith("sechroom:bundle:") && !t.startsWith("sechroom:template-ref:")
1167
1241
  );
1168
- overrideTags.push("sechroom:role:override", `sechroom:template-ref:${template.templateId}`);
1242
+ overrideTags.push(
1243
+ "sechroom:role:override",
1244
+ `sechroom:template-ref:${template.templateId}`
1245
+ );
1169
1246
  const { error } = await client.POST("/memories", {
1170
1247
  body: {
1171
1248
  text: template.body,
@@ -1179,7 +1256,8 @@ async function createOverride(cfg, template, personalWorkspaceId) {
1179
1256
  owner: { type: "Workspace", id: personalWorkspaceId }
1180
1257
  }
1181
1258
  });
1182
- if (error) throw new Error(`creating personal copy failed: ${JSON.stringify(error)}`);
1259
+ if (error)
1260
+ throw new Error(`creating personal copy failed: ${JSON.stringify(error)}`);
1183
1261
  }
1184
1262
 
1185
1263
  // src/setup/skill-resolution.ts
@@ -11702,7 +11780,15 @@ async function applyClient(cfg, setup, target, opts) {
11702
11780
  kind: "instruction",
11703
11781
  path: target.instruction.path,
11704
11782
  status: "skipped",
11705
- note: "no role template found in this tenant \u2014 install the SEM Starter bundle, then re-run `sechroom setup agent-files`"
11783
+ // Name the block even when nothing resolved. Without it the skip reports a
11784
+ // path and no identity, so a consumer cannot tell WHICH managed block went
11785
+ // missing — absent rather than loud, which is the failure this guards.
11786
+ block: "role-template",
11787
+ // GUARD — an unresolved template must NAME what it looked for. A bare
11788
+ // "not found" reads identically whether the bundle is absent or the memo
11789
+ // was retagged out of every candidate family, and the second case is a
11790
+ // silent loss of a template the installation previously had.
11791
+ note: "no role template resolved \u2014 tried " + attemptedTagSets(section).map((set) => `[${set.join(", ")}]`).join(" then ") + " \u2014 install the SEM Starter bundle, or check the template's target tag, then re-run `sechroom setup agent-files`"
11706
11792
  });
11707
11793
  } else {
11708
11794
  const action = applyBlock(
@@ -12049,8 +12135,17 @@ function buildCheckReport(result) {
12049
12135
  stub: 0
12050
12136
  };
12051
12137
  const blocks = [];
12138
+ const unresolved = [];
12052
12139
  for (const { client, actions } of result) {
12053
12140
  for (const action of actions) {
12141
+ if (action.kind === "instruction" && action.status === "skipped" && !action.eval) {
12142
+ unresolved.push({
12143
+ client,
12144
+ path: action.path,
12145
+ block: action.block ?? "unknown",
12146
+ note: action.note
12147
+ });
12148
+ }
12054
12149
  if (!action.eval) continue;
12055
12150
  counts[action.eval]++;
12056
12151
  blocks.push({
@@ -12064,7 +12159,8 @@ function buildCheckReport(result) {
12064
12159
  return {
12065
12160
  eval: counts,
12066
12161
  wouldChange: counts.stale + counts.drift + counts.absent,
12067
- blocks
12162
+ blocks,
12163
+ unresolved
12068
12164
  };
12069
12165
  }
12070
12166
  function reportCheckAndExit(result, json, refreshCommand, jsonContext = {}) {
@@ -12079,7 +12175,7 @@ function reportCheckAndExit(result, json, refreshCommand, jsonContext = {}) {
12079
12175
  },
12080
12176
  true
12081
12177
  );
12082
- } else if (report.wouldChange === 0) {
12178
+ } else if (report.wouldChange === 0 && report.unresolved.length === 0) {
12083
12179
  if (report.eval.stub) {
12084
12180
  const files = report.eval.stub === 1 ? "file" : "files";
12085
12181
  process.stdout.write(
@@ -12091,11 +12187,27 @@ function reportCheckAndExit(result, json, refreshCommand, jsonContext = {}) {
12091
12187
  }
12092
12188
  } else {
12093
12189
  const bits = [];
12094
- if (report.eval.stale) bits.push(`${report.eval.stale} out of date`);
12095
- if (report.eval.drift) bits.push(`${report.eval.drift} with local edits`);
12096
- if (report.eval.absent) bits.push(`${report.eval.absent} not yet written`);
12190
+ if (report.wouldChange > 0) {
12191
+ const changes = [];
12192
+ if (report.eval.stale) changes.push(`${report.eval.stale} out of date`);
12193
+ if (report.eval.drift)
12194
+ changes.push(`${report.eval.drift} with local edits`);
12195
+ if (report.eval.absent)
12196
+ changes.push(`${report.eval.absent} not yet written`);
12197
+ bits.push(
12198
+ `${report.wouldChange} instruction block(s) would change: ${changes.join(", ")}`
12199
+ );
12200
+ }
12201
+ if (report.unresolved.length > 0) {
12202
+ const details = report.unresolved.map(
12203
+ ({ client, path, block, note }) => `${client} ${block} at ${path}${note ? ` \u2014 ${note}` : ""}`
12204
+ ).join("; ");
12205
+ bits.push(
12206
+ `${report.unresolved.length} instruction template(s) skipped or unresolved: ${details}`
12207
+ );
12208
+ }
12097
12209
  process.stderr.write(
12098
- `\u26A0 ${report.wouldChange} instruction block(s) would change: ${bits.join(", ")}. Run ${style.cyan(refreshCommand)}.
12210
+ `\u26A0 ${bits.join("; ")}. Run ${style.cyan(refreshCommand)}.
12099
12211
  `
12100
12212
  );
12101
12213
  }
@@ -12172,7 +12284,7 @@ function registerInit(program2) {
12172
12284
  false
12173
12285
  ).option(
12174
12286
  "--check",
12175
- "report whether agent files would change and exit (0 = current/stub, 1 = stale/drift/absent); writes nothing",
12287
+ "report whether agent files would change and exit (0 = current/stub or unresolved warning, 1 = stale/drift/absent); writes nothing",
12176
12288
  false
12177
12289
  ).addHelpText(
12178
12290
  "after",
@@ -12315,7 +12427,7 @@ function registerSetup(program2, deps = {}) {
12315
12427
  false
12316
12428
  ).option(
12317
12429
  "--check",
12318
- "report whether anything would change and exit (0 = current/stub, 1 = stale/drift/absent); writes nothing",
12430
+ "report whether anything would change and exit (0 = current/stub or unresolved warning, 1 = stale/drift/absent); writes nothing",
12319
12431
  false
12320
12432
  ).addHelpText(
12321
12433
  "after",
@@ -13258,7 +13370,7 @@ function registerOnboard(program2) {
13258
13370
  false
13259
13371
  ).option(
13260
13372
  "--check",
13261
- "report whether anything would change and exit (0 = current/stub, 1 = stale/drift/absent); writes nothing",
13373
+ "report whether anything would change and exit (0 = current/stub or unresolved warning, 1 = stale/drift/absent); writes nothing",
13262
13374
  false
13263
13375
  ).option(
13264
13376
  "-y, --yes",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.8.3",
3
+ "version": "2026.8.4",
4
4
  "description": "Command-line interface for Sechroom — sign in, wire your AI tools, and work with your sechroom from the terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",