@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 +81 -23
- package/lib/safe-yaml-load.js +7 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1339
|
-
|
|
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);
|
package/lib/safe-yaml-load.js
CHANGED
|
@@ -84,7 +84,13 @@ export function loadYamlFile(filePath) {
|
|
|
84
84
|
return { data: null, error: `Read error: ${err.message}` };
|
|
85
85
|
}
|
|
86
86
|
try {
|
|
87
|
-
|
|
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.
|
|
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",
|