@sechroom/cli 2026.7.10 → 2026.7.12

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 +78 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -633,7 +633,22 @@ async function runApi(label, fn) {
633
633
  return res.data;
634
634
  }
635
635
  function fail(error) {
636
- const msg = typeof error === "object" && error !== null && "title" in error ? String(error.title) : typeof error === "object" ? JSON.stringify(error) : String(error);
636
+ let msg;
637
+ if (typeof error === "object" && error !== null && "title" in error) {
638
+ const problem = error;
639
+ msg = String(problem.title);
640
+ if (problem.errors && typeof problem.errors === "object") {
641
+ const detail = Object.entries(problem.errors).flatMap(
642
+ ([field, msgs]) => (Array.isArray(msgs) ? msgs : [msgs]).map((m) => ` ${field}: ${String(m)}`)
643
+ );
644
+ if (detail.length > 0) msg += `
645
+ ${detail.join("\n")}`;
646
+ }
647
+ } else if (typeof error === "object" && error !== null) {
648
+ msg = JSON.stringify(error);
649
+ } else {
650
+ msg = String(error);
651
+ }
637
652
  process.stderr.write(`error: ${msg}
638
653
  `);
639
654
  process.exit(1);
@@ -2551,6 +2566,9 @@ function registerClose(program2) {
2551
2566
  ).option(
2552
2567
  "--no-status-flip",
2553
2568
  "Skip the status flip on a BARE task. (Managed runs never flip here \u2014 the run engine reflects the verdict onto the task on resume.)"
2569
+ ).option(
2570
+ "--to-version <n>",
2571
+ "Pin the Reference edge at this task version \u2014 the DISPATCHED version the executor worked against (D-continuity-2). Default: the task's current version (status flips are metadata edits that don't bump the version, so current == dispatched in the normal case; pass this when the task's content changed between dispatch and close)."
2554
2572
  ).option("--source <source>", "Source / lane stamp", "cli").addHelpText(
2555
2573
  "after",
2556
2574
  `
@@ -2584,6 +2602,7 @@ Examples:
2584
2602
  );
2585
2603
  const client = await makeClient(cfg);
2586
2604
  let matchTags;
2605
+ let dispatchedVersion;
2587
2606
  if (opts.decomposition) {
2588
2607
  const run = await runApi(
2589
2608
  "Reading run contract",
@@ -2601,6 +2620,8 @@ Examples:
2601
2620
  `run ${opts.decomposition} is awaiting task ${run.awaitingTaskId}, not ${opts.task} \u2014 refusing to mis-key the resume.`
2602
2621
  );
2603
2622
  matchTags = await_.matchTags;
2623
+ if (typeof await_.dispatchedTaskVersion === "number")
2624
+ dispatchedVersion = await_.dispatchedTaskVersion;
2604
2625
  } else {
2605
2626
  matchTags = [`wlp-task:${opts.task}`];
2606
2627
  }
@@ -2627,11 +2648,34 @@ Examples:
2627
2648
  }
2628
2649
  })
2629
2650
  );
2651
+ let toVersion;
2652
+ if (opts.toVersion !== void 0) {
2653
+ toVersion = Number(opts.toVersion);
2654
+ if (!Number.isInteger(toVersion) || toVersion < 1)
2655
+ fail(
2656
+ `--to-version must be an integer >= 1 \u2014 got '${opts.toVersion}'`
2657
+ );
2658
+ } else if (dispatchedVersion !== void 0) {
2659
+ toVersion = dispatchedVersion;
2660
+ } else {
2661
+ const task = await runApi(
2662
+ "Reading task version",
2663
+ async () => client.GET("/memories/{memoryId}", {
2664
+ params: { path: { memoryId: opts.task } }
2665
+ })
2666
+ );
2667
+ const v = task?.item?.currentVersion ?? task?.currentVersion;
2668
+ if (typeof v !== "number" || v < 1)
2669
+ fail(
2670
+ `could not read task ${opts.task} version to pin the Reference edge \u2014 pass --to-version <n>`
2671
+ );
2672
+ toVersion = v;
2673
+ }
2630
2674
  const edge = await runApi(
2631
2675
  "Wiring Reference edge",
2632
2676
  async () => client.POST("/memories/{memoryId}/relationships", {
2633
2677
  params: { path: { memoryId: closeout.id } },
2634
- body: { toMemoryId: opts.task, type: "Reference" }
2678
+ body: { toMemoryId: opts.task, type: "Reference", toVersion }
2635
2679
  })
2636
2680
  );
2637
2681
  let taskStatus;
@@ -2851,12 +2895,12 @@ Examples:
2851
2895
  $ sechroom decomposition reject sug_XXXX --reason "wrong shape"`
2852
2896
  );
2853
2897
  decomposition.command("decompose <briefId>").description(
2854
- "Decompose a governed brief into a candidate Task graph (POST /governed-briefs/{id}/decompose)"
2898
+ "Decompose a work brief into a candidate Task graph (POST /work-briefs/{id}/decompose)"
2855
2899
  ).action(async (briefId, _opts, cmd) => {
2856
2900
  const cfg = resolveConfig(cmd.optsWithGlobals());
2857
2901
  const data = await runApi("Queueing decomposition", async () => {
2858
2902
  const client = await makeClient(cfg);
2859
- return client.POST("/governed-briefs/{id}/decompose", {
2903
+ return client.POST("/work-briefs/{id}/decompose", {
2860
2904
  params: { path: { id: briefId } },
2861
2905
  body: { id: briefId }
2862
2906
  });
@@ -5065,24 +5109,45 @@ Examples:
5065
5109
  $ sechroom relationship suggestions --status Pending --memory mem_XXXX
5066
5110
  $ sechroom relationship suggestion accept rsg_XXXX`
5067
5111
  );
5068
- relationship.command("create <fromMemoryId> <toMemoryId>").description("Create a relationship (POST /memories/{memoryId}/relationships)").option("--type <type>", "Relationship type (Reference, Related, Parent, Child, Follows, \u2026)", "Reference").action(async (fromMemoryId, toMemoryId, opts, cmd) => {
5112
+ relationship.command("create <fromMemoryId> <toMemoryId>").description("Create a relationship (POST /memories/{memoryId}/relationships)").option("--type <type>", "Relationship type (Reference, Related, Parent, Child, Follows, \u2026)", "Reference").option(
5113
+ "--to-version <number>",
5114
+ "Version of the target memory to pin the edge to (defaults to the target's current version)"
5115
+ ).action(async (fromMemoryId, toMemoryId, opts, cmd) => {
5069
5116
  const cfg = resolveConfig(cmd.optsWithGlobals());
5070
- const data = await runApi("Creating relationship", async () => {
5071
- const client = await makeClient(cfg);
5072
- return client.POST("/memories/{memoryId}/relationships", {
5117
+ const client = await makeClient(cfg);
5118
+ let toVersion;
5119
+ if (opts.toVersion !== void 0) {
5120
+ toVersion = Number(opts.toVersion);
5121
+ if (!Number.isInteger(toVersion) || toVersion < 1) {
5122
+ fail(`--to-version must be a positive integer (got '${opts.toVersion}').`);
5123
+ }
5124
+ } else {
5125
+ const target = await runApi(
5126
+ "Resolving target version",
5127
+ async () => client.GET("/memories/{memoryId}", { params: { path: { memoryId: toMemoryId } } })
5128
+ );
5129
+ if (typeof target.item?.currentVersion !== "number") {
5130
+ fail(`Could not resolve the current version of ${toMemoryId}; pass --to-version explicitly.`);
5131
+ }
5132
+ toVersion = target.item.currentVersion;
5133
+ }
5134
+ const data = await runApi(
5135
+ "Creating relationship",
5136
+ async () => client.POST("/memories/{memoryId}/relationships", {
5073
5137
  params: { path: { memoryId: fromMemoryId } },
5074
5138
  body: {
5075
5139
  toMemoryId,
5076
- type: opts.type
5140
+ type: opts.type,
5141
+ toVersion
5077
5142
  }
5078
- });
5079
- });
5143
+ })
5144
+ );
5080
5145
  const inversePart = data.inverseId ? ` ${style.dim(`(inverse ${data.inverseId})`)}` : "";
5081
5146
  const view = resolveViewUrl(cfg.baseUrl, data.url);
5082
5147
  const urlPart = view ? ` ${style.dim("\u2192")} ${view}` : "";
5083
5148
  emitAction(
5084
- `created relationship ${style.bold(data.id)} ${style.dim(`${fromMemoryId} \u2192 ${toMemoryId}`)}${inversePart}${urlPart}`,
5085
- data,
5149
+ `created relationship ${style.bold(data.id)} ${style.dim(`${fromMemoryId} \u2192 ${toMemoryId} @v${toVersion}`)}${inversePart}${urlPart}`,
5150
+ { ...data, toVersion },
5086
5151
  cmd.optsWithGlobals().json
5087
5152
  );
5088
5153
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.7.10",
3
+ "version": "2026.7.12",
4
4
  "description": "Sechroom CLI — a thin, generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",