@dogfood-lab/findings 1.10.0 → 1.11.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.
package/cli.js CHANGED
@@ -88,26 +88,76 @@ const ROOT = process.env.FINDINGS_REPO_ROOT
88
88
  ? resolve(process.env.FINDINGS_REPO_ROOT)
89
89
  : resolve(__dirname, '../..');
90
90
 
91
+ /**
92
+ * Parse argv into { command, positional, flags }.
93
+ *
94
+ * F-720be224: reject unrecognized `--*` tokens at parse time (ERROR [BAD_ARGS]
95
+ * → exit 2) instead of silently absorbing typos like `--writ` / `--repso` /
96
+ * `--acter` into the flags bag. Mirrors report/cli.js valueFlags+booleans and
97
+ * the sibling F-e0bcbc47 / F-418f507c seal in this domain. Known flags keep
98
+ * equals-form (`--actor=mike`) and boolean presence (`--write`).
99
+ */
91
100
  function parseArgs(argv) {
92
101
  const args = argv.slice(2);
93
102
  const command = args[0];
94
103
  const positional = [];
95
104
  const flags = {};
96
105
 
106
+ // Boolean flags take no value; value flags consume the next token (or accept
107
+ // `--flag=value`). Union of every flag this CLI documents / reads.
108
+ const booleans = new Set([
109
+ 'all', 'write', 'dry-run', 'json', 'include-fixtures', 'help',
110
+ ]);
111
+ const valueFlags = new Set([
112
+ 'repo', 'status', 'surface', 'issue-kind', 'transfer-scope',
113
+ 'grep', 'text', 'file', 'record', 'actor', 'reason', 'reject-reason',
114
+ 'notes', 'set', 'into', 'policy', 'execution-mode', 'mode',
115
+ ]);
116
+
97
117
  for (let i = 1; i < args.length; i++) {
98
118
  const arg = args[i];
99
- if (arg.startsWith('--')) {
100
- const eqIdx = arg.indexOf('=');
101
- if (eqIdx !== -1) {
102
- flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
103
- } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
104
- flags[arg.slice(2)] = args[++i];
105
- } else {
106
- flags[arg.slice(2)] = true;
107
- }
108
- } else {
119
+ if (!arg.startsWith('--')) {
109
120
  positional.push(arg);
121
+ continue;
122
+ }
123
+
124
+ let name;
125
+ let inlineValue;
126
+ const eqIdx = arg.indexOf('=');
127
+ if (eqIdx !== -1) {
128
+ name = arg.slice(2, eqIdx);
129
+ inlineValue = arg.slice(eqIdx + 1);
130
+ } else {
131
+ name = arg.slice(2);
132
+ }
133
+
134
+ if (!booleans.has(name) && !valueFlags.has(name)) {
135
+ // F-720be224 — typo'd flags must fail loud, not dry-run / unfilter /
136
+ // mis-attribute under a silent absorption.
137
+ const err = new Error(`unknown flag "--${name}"`);
138
+ err.code = 'BAD_ARGS';
139
+ err.hint = 'run `dogfood findings --help` for usage';
140
+ throw err;
141
+ }
142
+
143
+ if (booleans.has(name)) {
144
+ flags[name] = true;
145
+ continue;
146
+ }
147
+
148
+ if (inlineValue !== undefined) {
149
+ flags[name] = inlineValue;
150
+ continue;
151
+ }
152
+ const next = args[i + 1];
153
+ if (next === undefined || next.startsWith('--')) {
154
+ const err = new Error(`flag "--${name}" expects a value`);
155
+ err.code = 'BAD_ARGS';
156
+ err.hint = `Pass a value: --${name} <value>. Run \`dogfood findings --help\` for usage.`;
157
+ throw err;
110
158
  }
159
+ flags[name] = next;
160
+ i++;
111
161
  }
112
162
 
113
163
  return { command, positional, flags };
@@ -252,7 +302,8 @@ function handleArtifactReview(type, sub, positional, flags) {
252
302
 
253
303
  const id = positional[1];
254
304
  if (!id) {
255
- console.error(`Usage: dogfood findings ${type === 'doctrine' ? 'doctrine' : type + 's'} ${sub} <id> --actor <name> [--reason "..."]`);
305
+ // F-58316c9f Usage must match runtime: actor defaults to 'operator'.
306
+ console.error(`Usage: dogfood findings ${type === 'doctrine' ? 'doctrine' : type + 's'} ${sub} <id> [--actor <name>] (default: operator) [--reason "..."]`);
256
307
  process.exit(2);
257
308
  }
258
309
  const actor = flags.actor || 'operator';
@@ -289,14 +340,14 @@ Commands:
289
340
  derive Derive candidate findings from records
290
341
  explain <finding_id> Show derivation provenance for a finding
291
342
  rules List all derivation rules
292
- accept <id> Accept a finding (--actor, --reason)
293
- reject <id> Reject a finding (--actor, --reason, --reject-reason)
294
- review <id> Move finding to reviewed (--actor)
295
- edit <id> Edit finding fields (--actor, --set field=value).
343
+ accept <id> Accept a finding ([--actor <name>] default: operator, --reason)
344
+ reject <id> Reject a finding ([--actor <name>] default: operator, --reason, --reject-reason)
345
+ review <id> Move finding to reviewed ([--actor <name>] default: operator)
346
+ edit <id> Edit finding fields ([--actor <name>] default: operator, --set field=value).
296
347
  Editable fields: ${EDITABLE_FIELDS.join(', ')}.
297
- merge <ids...> Merge findings (--into <id>, --actor, --reason)
298
- reopen <id> Reopen a rejected/accepted finding (--actor, --reason)
299
- invalidate <id> Invalidate an accepted finding (--actor, --reason)
348
+ merge <ids...> Merge findings (--into <id>, [--actor <name>] default: operator, --reason)
349
+ reopen <id> Reopen a rejected/accepted finding ([--actor <name>] default: operator, --reason)
350
+ invalidate <id> Invalidate an accepted finding ([--actor <name>] default: operator, --reason)
300
351
  history <id> Show review history for a finding (--json)
301
352
  queue Show review queue (--json)
302
353
 
@@ -719,7 +770,8 @@ Structured output:
719
770
  if (['accept', 'reject', 'review', 'reopen', 'invalidate'].includes(command)) {
720
771
  const findingId = positional[0];
721
772
  if (!findingId) {
722
- console.error(`Usage: dogfood findings ${command} <finding_id> --actor <name> [--reason "..."]`);
773
+ // F-58316c9f — Usage must match runtime: actor defaults to 'operator'.
774
+ console.error(`Usage: dogfood findings ${command} <finding_id> [--actor <name>] (default: operator) [--reason "..."]`);
723
775
  process.exit(2);
724
776
  }
725
777
  const actor = flags.actor || 'operator';
@@ -745,7 +797,8 @@ Structured output:
745
797
  if (command === 'edit') {
746
798
  const findingId = positional[0];
747
799
  if (!findingId) {
748
- console.error('Usage: dogfood findings edit <finding_id> --actor <name> --set field=value [--set field=value]');
800
+ // F-58316c9f Usage must match runtime: actor defaults to 'operator'.
801
+ console.error('Usage: dogfood findings edit <finding_id> [--actor <name>] (default: operator) --set field=value [--set field=value]');
749
802
  console.error(`Editable fields: ${EDITABLE_FIELDS.join(', ')}`);
750
803
  process.exit(2);
751
804
  }
@@ -789,7 +842,8 @@ Structured output:
789
842
  const actor = flags.actor || 'operator';
790
843
  const reason = flags.reason;
791
844
  if (sourceIds.length < 2 || !canonicalId) {
792
- console.error('Usage: dogfood findings merge <id1> <id2> [<id3>...] --into <canonical_id> --actor <name> --reason "..."');
845
+ // F-58316c9f Usage must match runtime: actor defaults to 'operator'.
846
+ console.error('Usage: dogfood findings merge <id1> <id2> [<id3>...] --into <canonical_id> [--actor <name>] (default: operator) --reason "..."');
793
847
  process.exit(2);
794
848
  }
795
849
  const result = performMerge(ROOT, { sourceIds, canonicalId, actor, reason });
@@ -1331,12 +1385,16 @@ main().catch(err => {
1331
1385
  // dumping the raw Error/stack. The raw stack is a triage aid, not a default —
1332
1386
  // it stays behind DEBUG so a mis-rooted checkout or an un-caught throw gives
1333
1387
  // the operator `ERROR [<CODE>]:` + a next step, not a bare Node stack.
1388
+ // F-720be224: parseArgs attaches err.hint for BAD_ARGS so the Next line can
1389
+ // point straight at `dogfood findings --help` instead of the generic root hint.
1334
1390
  const code = err && err.code ? err.code : 'UNEXPECTED';
1335
1391
  const message = err && err.message ? err.message : String(err);
1336
1392
  console.error(`ERROR [${code}]: ${message}`);
1337
1393
  console.error(
1338
- ' Next: run from the testing-os repo root, or set FINDINGS_REPO_ROOT to it; ' +
1339
- 'run `dogfood findings --help` for usage. Re-run with DEBUG=1 for the stack.'
1394
+ ' Next: ' + (err && err.hint
1395
+ ? err.hint
1396
+ : 'run from the testing-os repo root, or set FINDINGS_REPO_ROOT to it; ' +
1397
+ 'run `dogfood findings --help` for usage. Re-run with DEBUG=1 for the stack.')
1340
1398
  );
1341
1399
  if (process.env.DEBUG && err && err.stack) {
1342
1400
  console.error(err.stack);
@@ -84,7 +84,13 @@ export function loadYamlFile(filePath) {
84
84
  return { data: null, error: `Read error: ${err.message}` };
85
85
  }
86
86
  try {
87
- const data = yaml.load(raw);
87
+ // F-998fb547: CORE_SCHEMA omits YAML-1.1 merge keys (`<<`). DEFAULT_SCHEMA
88
+ // resolves them via mergeMappings()'s O(depth) copy — a hostile merge-chain
89
+ // in findings/patterns/doctrine trees can stall derive/review/synthesis
90
+ // despite MAX_YAML_BYTES, because the cost is in merge resolution, not size.
91
+ // Mirrors packages/ingest/load-context.js parseUntrustedScenarioYaml
92
+ // (COORD-001); js-yaml stays on v4 (v5 held — Dependabot #50).
93
+ const data = yaml.load(raw, { schema: yaml.CORE_SCHEMA });
88
94
  return { data, error: null };
89
95
  } catch (err) {
90
96
  return { data: null, error: `YAML parse error: ${err.message}` };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogfood-lab/findings",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "description": "Finding contract spine for testing-os. Validates, reads, lists, and queries evidence-bound findings — the fourth contract alongside record, scenario, and policy.",
6
6
  "main": "index.js",