@shomra/agent 0.3.13 → 0.3.14

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/package.json +1 -1
  2. package/shomra.mjs +59 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shomra/agent",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "Shomra — adversarial assurance for AI agents, as a local-first CLI. Blocks dangerous tool-calls before they run, attacks your own guardrails to prove they hold, and gates AI artifacts in your editor and CI.",
5
5
  "type": "module",
6
6
  "bin": {
package/shomra.mjs CHANGED
@@ -219,7 +219,7 @@ const BOOLEAN_FLAGS = new Set([
219
219
  'apply', 'dry-run', 'global', 'local', 'trailer', 'evolve', 'report', 'init',
220
220
  'no-suppress', 'no-baseline', 'no-policy', 'no-index', 'adaptive',
221
221
  'fail-on-regression', 'fail-on-blocked', 'write', 'yes', 'stdin', 'quiet', 'help',
222
- 'check', 'checklist', 'pre-receive', 'uninstall',
222
+ 'check', 'checklist', 'pre-receive', 'uninstall', 'save',
223
223
  ]);
224
224
  // Flags that take a value (`--key value` or `--key=value`).
225
225
  const VALUE_FLAGS = new Set([
@@ -230,6 +230,8 @@ const VALUE_FLAGS = new Set([
230
230
  // `--fail-on <critical|high|medium>` lets CI gate below the default
231
231
  // (blocked-only) exit code — e.g. fail the build on a HIGH finding.
232
232
  'fail-on',
233
+ // `shomra design --save --subject KIND:id` — pin the analysis to a subject.
234
+ 'subject', 'title', 'note', 'actor',
233
235
  ]);
234
236
  const KNOWN_FLAGS = new Set([...BOOLEAN_FLAGS, ...VALUE_FLAGS]);
235
237
 
@@ -5067,7 +5069,7 @@ async function addPackage(flags, positional) {
5067
5069
 
5068
5070
  // ── shomra design: threat-model a system before it exists ───────────────────
5069
5071
  //
5070
- // shomra design <file|dir|-> [--checklist] [--json] [--strict]
5072
+ // shomra design <file|dir|-> [--save --subject KIND:id] [--checklist] [--json] [--strict]
5071
5073
  //
5072
5074
  // The leftmost surface Shomra has. Everything else needs an artifact; this reads
5073
5075
  // a DESCRIPTION — an RFC, a design doc, a Jira/Linear ticket, a PR body — and
@@ -5136,6 +5138,11 @@ async function cmdDesign(flags, positional) {
5136
5138
  }
5137
5139
  }
5138
5140
 
5141
+ // --save pins the analysis to the live capability manifest server-side, so the
5142
+ // dashboard and the CI gate can tell later that the system has moved past it.
5143
+ // Without it the analysis dies with the terminal it printed to.
5144
+ if (flags.save) await saveDesign(results, flags);
5145
+
5139
5146
  // CRITICAL = untrusted input reaching execution or a destructive action. That
5140
5147
  // is a hard fail even without --strict: it is the one shape where the attacker
5141
5148
  // picks the action, and no amount of care in the implementation recovers it.
@@ -5143,6 +5150,55 @@ async function cmdDesign(flags, positional) {
5143
5150
  else if (open.length && flags.strict) process.exitCode = 2;
5144
5151
  }
5145
5152
 
5153
+ // ── shomra design --save --subject KIND:id ──────────────────────────────────
5154
+ //
5155
+ // ⚠ ONE SUBJECT, ONE DOCUMENT. A directory of RFCs produces one analysis each,
5156
+ // and pinning several of them to the same subject would leave the subject
5157
+ // described by whichever happened to be written last. The command refuses
5158
+ // rather than picking.
5159
+ async function saveDesign(results, flags) {
5160
+ const { apiKey, url } = resolveSettings(loadConfig());
5161
+ const subject = String(flags.subject ?? '').trim();
5162
+ const m = /^(AGENT|PROJECT|DESIGN):(.+)$/i.exec(subject);
5163
+ if (!m) {
5164
+ console.error(red('✗') + ' --save needs ' + bold('--subject KIND:id') + dim(' (AGENT | PROJECT | DESIGN)'));
5165
+ console.error(dim(' e.g. ') + 'shomra design docs/rfc.md --save --subject DESIGN:inbox-agent');
5166
+ process.exit(EXIT_USAGE);
5167
+ }
5168
+ if (results.length !== 1) {
5169
+ console.error(red('✗') + ` --save takes one document; ${results.length} were modelled.`);
5170
+ console.error(dim(' Save each against its own subject.'));
5171
+ process.exit(EXIT_USAGE);
5172
+ }
5173
+ if (!apiKey || !url) exitNotConfigured();
5174
+
5175
+ const [, kind, id] = m;
5176
+ const r = results[0];
5177
+ process.stdout.write(dim(' Saving to the platform… '));
5178
+ try {
5179
+ const res = await api(url, apiKey, '/threat-models/agent-author', {
5180
+ subjectKind: kind.toUpperCase(),
5181
+ subjectId: id,
5182
+ title: flags.title ? String(flags.title) : r.name,
5183
+ analysis: r,
5184
+ note: flags.note ? String(flags.note) : `Modelled from ${r.name}.`,
5185
+ actor: flags.actor ? String(flags.actor) : undefined,
5186
+ });
5187
+ const v = res?.version;
5188
+ const c = res?.coverage;
5189
+ console.log(green('saved') + dim(` — v${v?.seq ?? '?'}, ${v?.reviewState ?? 'IN_REVIEW'}`));
5190
+ // ⚠ Saving is not approving. A version nobody else has signed off does not
5191
+ // clear the gate, and saying so here is the difference between a stored
5192
+ // document and a control.
5193
+ console.log(dim(' It is not approved yet — a threat model must be reviewed by someone other than its author.'));
5194
+ if (c?.headline) console.log(dim(' ') + c.headline);
5195
+ } catch (e) {
5196
+ console.log(red('failed'));
5197
+ console.error(dim(' ') + (e?.message || String(e)));
5198
+ process.exitCode = 1;
5199
+ }
5200
+ }
5201
+
5146
5202
  const DESIGN_DOC_RE = /\.(md|markdown|txt|rst|adoc)$/i;
5147
5203
  const DESIGN_MAX_DOCS = 50;
5148
5204
 
@@ -6606,7 +6662,7 @@ ${bold('COMMANDS')}
6606
6662
  ${cyan('doctor')} ${bold('Am I safe?')} Posture of this machine's AI setup ${dim('[--json]')}
6607
6663
 
6608
6664
  ${dim('Prevention — get in front of the model, not just behind it')}
6609
- ${cyan('design')} ${bold('Threat-model a system before it exists')} ${dim('<file|dir|-> [--checklist] [--strict] [--json]')}
6665
+ ${cyan('design')} ${bold('Threat-model a system before it exists')} ${dim('<file|dir|-> [--save --subject KIND:id] [--checklist] [--strict] [--json]')}
6610
6666
  ${dim('Reads an RFC / design doc / ticket and says whether it closes a path from')}
6611
6667
  ${dim('untrusted input to a consequence, plus what must be true before it ships.')}
6612
6668
  ${dim('Pipe a ticket straight in: ')}${bold('gh issue view 42 --json body -q .body | shomra design -')}