@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.
- package/dist/index.js +145 -33
- 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)
|
|
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(
|
|
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
|
|
1103
|
+
function parseTagArtifactCandidates(id) {
|
|
1099
1104
|
if (!id.startsWith("tag:")) return null;
|
|
1100
|
-
const
|
|
1101
|
-
|
|
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}", {
|
|
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 {
|
|
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
|
|
1118
|
-
|
|
1119
|
-
|
|
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: {
|
|
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
|
-
|
|
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)
|
|
1130
|
-
|
|
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: {
|
|
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 {
|
|
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 {
|
|
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(
|
|
1156
|
-
|
|
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(
|
|
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)
|
|
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
|
-
|
|
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.
|
|
12095
|
-
|
|
12096
|
-
|
|
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 ${
|
|
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