@event4u/agent-config 9.19.0 → 9.20.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +38 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/package.json +1 -1
- package/src/config/gate-coverage.yml +16 -0
- package/src/scripts/check_completion_review.ts +19 -4
- package/src/scripts/check_review_dispositions.ts +188 -0
- package/src/scripts/dispatch_r2_reviewer.ts +3 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Bootstrap shim for the event4u agent-config suite. This plugin ships ONLY the dispatcher hooks plus one install-pointer skill — ALL content (skills, rules, commands) is installed via `npx -y @event4u/agent-config init`, which writes the file projection AND registers the same hooks in ~/.claude/settings.json. DEPRECATED as a content channel; kept so the marketplace listing stays discoverable and existing installs keep working hooks.",
|
|
9
|
-
"version": "9.
|
|
9
|
+
"version": "9.20.0",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"agent-config",
|
|
12
12
|
"skills",
|
package/CHANGELOG.md
CHANGED
|
@@ -294,6 +294,44 @@ Entry-shape contract: [`docs/contracts/CHANGELOG-conventions.md`](docs/contracts
|
|
|
294
294
|
> that forces a new era split (`# Era: 9.19.x`, etc.) — see
|
|
295
295
|
> [`docs/contracts/CHANGELOG-conventions.md § Era splits`](docs/contracts/CHANGELOG-conventions.md).
|
|
296
296
|
|
|
297
|
+
## [9.20.0](https://github.com/event4u-app/agent-config/compare/9.19.0...9.20.0) (2026-08-04)
|
|
298
|
+
|
|
299
|
+
### Release highlights
|
|
300
|
+
|
|
301
|
+
<!-- Curated head: fill before merge, keep it under 10 lines, and leave `_none_` where it is genuinely the answer. The generated log below is unchanged. -->
|
|
302
|
+
- **Behaviour changes:** _none_
|
|
303
|
+
- **Default changes + migration:** _auto-derived, rewrite before merge:_ commits naming a default, migration or migrate in 14dc053, bb362a0.
|
|
304
|
+
- **Security and correctness:** _none_
|
|
305
|
+
- **Honest nulls:** _auto-derived, rewrite before merge:_ commits carrying an honest-null marker in ba77e6a.
|
|
306
|
+
- **Known limitations:** _none_
|
|
307
|
+
|
|
308
|
+
### Features
|
|
309
|
+
|
|
310
|
+
* **gates:** read the archived round records nothing was reading ([89eea71](https://github.com/event4u-app/agent-config/commit/89eea712b89048a032dfccf8a3bdd41f565adacc))
|
|
311
|
+
|
|
312
|
+
### Bug Fixes
|
|
313
|
+
|
|
314
|
+
* **gates:** stop the remediation text pointing at the closed hole ([2dc9e0d](https://github.com/event4u-app/agent-config/commit/2dc9e0d91262864237e50268d8ea54dcce761a62))
|
|
315
|
+
* **gates:** rows are live wherever they appear — close the fence fail-open ([ba77e6a](https://github.com/event4u-app/agent-config/commit/ba77e6a6f4cc5c31b9de4741ee30ccc41e126df7))
|
|
316
|
+
|
|
317
|
+
### Documentation
|
|
318
|
+
|
|
319
|
+
* **evidence:** close the blind-pass dispositions and correct a claim I made ([0398238](https://github.com/event4u-app/agent-config/commit/0398238d99f7451712e710204c86ab41f964de50))
|
|
320
|
+
* **contracts:** 2.7 is enforced now, and says what it still does not check ([d0b3650](https://github.com/event4u-app/agent-config/commit/d0b365035dc314183dcb0e353a09e3e2c5420284))
|
|
321
|
+
* **roadmap:** pin-then-migrate ordering; drop the index for a round-record check ([bb362a0](https://github.com/event4u-app/agent-config/commit/bb362a0b64a5e16a156d238c1acadded44f229aa))
|
|
322
|
+
* **roadmap:** record the council verdict on the fence grammar ([3e0deff](https://github.com/event4u-app/agent-config/commit/3e0deff6256050aa23fe952707ab88bf7bc9647e))
|
|
323
|
+
* **review:** give the blind-pass findings terminal statuses and a carrier ([667b81e](https://github.com/event4u-app/agent-config/commit/667b81e9fa44d892095a7b057a6407196aa429bf))
|
|
324
|
+
|
|
325
|
+
### Tests
|
|
326
|
+
|
|
327
|
+
* **gates:** pin every fence arrangement before the grammar migration ([14dc053](https://github.com/event4u-app/agent-config/commit/14dc053d51ac1e932ce4deeb50ffb2e07737bd84))
|
|
328
|
+
|
|
329
|
+
### CI
|
|
330
|
+
|
|
331
|
+
* **gates:** wire check_review_dispositions with a trippable floor ([9b03190](https://github.com/event4u-app/agent-config/commit/9b0319032494345fdcaad40ae79dbe3ea95f6fc8))
|
|
332
|
+
|
|
333
|
+
Tests: 10995 (+23 since 9.19.0)
|
|
334
|
+
|
|
297
335
|
## [9.19.0](https://github.com/event4u-app/agent-config/compare/9.18.1...9.19.0) (2026-08-04)
|
|
298
336
|
|
|
299
337
|
### Release highlights
|
|
@@ -39,7 +39,7 @@ schema_version: 1
|
|
|
39
39
|
# CI guard: a release bump of `package.json` must update this value
|
|
40
40
|
# in lockstep — see scripts/check_template_pin_drift.ts (road-to-
|
|
41
41
|
# portable-runtime-and-update-check P3.3).
|
|
42
|
-
agent_config_version: "9.
|
|
42
|
+
agent_config_version: "9.20.0"
|
|
43
43
|
|
|
44
44
|
# --- Project identity ---
|
|
45
45
|
project:
|
|
@@ -14402,7 +14402,7 @@
|
|
|
14402
14402
|
"reason": "scaffold template for ticket-bundle tickets (ADR-101); carries no runtime frontmatter — emit-tickets fills it in per ticket"
|
|
14403
14403
|
}
|
|
14404
14404
|
],
|
|
14405
|
-
"generated_at": "2026-08-
|
|
14405
|
+
"generated_at": "2026-08-04T21:41:44Z",
|
|
14406
14406
|
"packs": [
|
|
14407
14407
|
{
|
|
14408
14408
|
"artefact_count": 117,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
759c2227292efbd65092bc4a57aa783cb714db0e6d93a1a53f6f35ba940d32aa discovery-manifest.json
|
package/dist/mcp/server.json
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"transport": {
|
|
10
10
|
"type": "stdio"
|
|
11
11
|
},
|
|
12
|
-
"version": "9.
|
|
12
|
+
"version": "9.20.0"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"repository": {
|
|
16
16
|
"source": "github",
|
|
17
17
|
"url": "https://github.com/event4u-app/agent-config"
|
|
18
18
|
},
|
|
19
|
-
"version": "9.
|
|
19
|
+
"version": "9.20.0"
|
|
20
20
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.20.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -365,6 +365,22 @@ gates:
|
|
|
365
365
|
stays registered so the guard reports this gate rather than skipping it
|
|
366
366
|
silently.
|
|
367
367
|
|
|
368
|
+
- id: check_review_dispositions
|
|
369
|
+
argv: []
|
|
370
|
+
min_scanned: 7
|
|
371
|
+
corpus: "archived round records — `*-review.md` under agents/evidence/reviews/, excluding live `*.findings.md` (9 at baseline)"
|
|
372
|
+
status: enforced
|
|
373
|
+
note: >
|
|
374
|
+
Contract § 2.7. Unlike the R2 entry above, this floor is genuinely
|
|
375
|
+
trippable: the corpus is 9 frozen records and archived records are not
|
|
376
|
+
deleted, so a drop to 7 means the reviews root moved or the naming
|
|
377
|
+
convention drifted — not that the work went clean. That is precisely the
|
|
378
|
+
case rule 3 exists for. The gate reads the FROZEN records because R2
|
|
379
|
+
selects only artefacts in the CURRENT review scope, so a superseded round
|
|
380
|
+
renamed out of the `*.findings.md` glob is read by nothing at all — two
|
|
381
|
+
findings sat recorded as `open` in this repo's own corpus for a day
|
|
382
|
+
through exactly that gap.
|
|
383
|
+
|
|
368
384
|
# ---------------------------------------------------------------------------
|
|
369
385
|
# The five below close the gap this header named: they already called
|
|
370
386
|
# `_lib/scan_scope.assertScanned` with a real count and only had to PRINT it.
|
|
@@ -495,10 +495,18 @@ export function parseArtifact(text: string): ParsedArtifact {
|
|
|
495
495
|
// LABELLED closed fence is illustrative content. A stray fence (bare, or
|
|
496
496
|
// an unclosed opener) skips nothing (see scanFences) — the lines around
|
|
497
497
|
// it are parsed, and the artefact is reported.
|
|
498
|
-
|
|
498
|
+
const trimmed = raw.trim();
|
|
499
|
+
// §2.2 row-liveness rule: fences govern the marker / honest-null / skip
|
|
500
|
+
// lines, which a fenced illustration may legitimately quote — but NOT
|
|
501
|
+
// findings rows. Hiding a marker or an honest-null fails CLOSED (the
|
|
502
|
+
// artefact reads as absent and the gate blocks); hiding a row fails OPEN
|
|
503
|
+
// (the finding silently ceases to exist). So the one line type whose
|
|
504
|
+
// concealment is dangerous is the one fences may not conceal, and a row
|
|
505
|
+
// is live wherever it appears. Only its own Status cell can excuse it.
|
|
506
|
+
const isCandidateRow = trimmed.startsWith('|');
|
|
507
|
+
if (FENCE_RE.test(raw) || (fences.fenced.has(i) && !isCandidateRow)) {
|
|
499
508
|
continue;
|
|
500
509
|
}
|
|
501
|
-
const trimmed = raw.trim();
|
|
502
510
|
// Marker candidates must LOOK like the §2.1 HTML comment: prose that
|
|
503
511
|
// merely mentions the grammar (`the \`completion-review:\` marker …`,
|
|
504
512
|
// or a reviewer note quoting it in backticks — real artefacts in this
|
|
@@ -544,6 +552,12 @@ export function parseArtifact(text: string): ParsedArtifact {
|
|
|
544
552
|
if (first === '#' || second === 'severity') {
|
|
545
553
|
continue; // header row
|
|
546
554
|
}
|
|
555
|
+
// §2.2: `example` is the ONE illustrative marker. It is read from the
|
|
556
|
+
// Status cell of a six-cell row only — a short row cannot excuse
|
|
557
|
+
// itself, or `malformed-row` would become opt-out.
|
|
558
|
+
if (cells.length === 6 && (cells[4] ?? '').trim().toLowerCase() === 'example') {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
547
561
|
// EXACTLY six, per contract §2.2 — not `>= 6`. An over-long row is
|
|
548
562
|
// as malformed as a short one and the surplus is usually the tell:
|
|
549
563
|
// an unescaped `|` inside a cell (the shape §2.2 requires escaping)
|
|
@@ -797,8 +811,9 @@ function evaluate(input: EvalInput): Violation[] {
|
|
|
797
811
|
detail:
|
|
798
812
|
`${at}: stray \`\`\` fence — a bare fence delimits no region, and a labelled fence must ` +
|
|
799
813
|
'be closed. An artefact whose ``` fences do not resolve is malformed (contract §2.2). ' +
|
|
800
|
-
'
|
|
801
|
-
'
|
|
814
|
+
'Close the fence, or inline-escape an illustrative ```. NOTE: fences never hide a ' +
|
|
815
|
+
'findings row — a row is live wherever it appears, and the ONLY way to mark one ' +
|
|
816
|
+
'illustrative is the status `example` in its own Status cell.',
|
|
802
817
|
});
|
|
803
818
|
}
|
|
804
819
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Archived-review disposition check (contract §2.7 + §2.2).
|
|
4
|
+
*
|
|
5
|
+
* Gate R2 selects only artefacts relevant to the CURRENT review scope (§2.6),
|
|
6
|
+
* and superseded rounds are renamed out of the `*.findings.md` glob on purpose.
|
|
7
|
+
* The consequence nobody noticed until it bit: **no validator reads archived
|
|
8
|
+
* round records at all**, so a finding left `open` after it was fixed is caught
|
|
9
|
+
* by nothing. That is exactly how two fixed findings sat recorded as `open` for
|
|
10
|
+
* a day in this repo's own corpus.
|
|
11
|
+
*
|
|
12
|
+
* This closes that gap with the smallest mechanism that covers the observed
|
|
13
|
+
* failure: archiving a round asserts the round is CLOSED, so every row in an
|
|
14
|
+
* archived record must carry a terminal status and a non-empty Reason/Ref.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately NOT the disposition index with stable ids that was originally
|
|
17
|
+
* proposed. That design assumed dispositions live OUTSIDE the round records;
|
|
18
|
+
* measured across the corpus, they do not — records are terminal in place. An
|
|
19
|
+
* index would be a second artefact to keep in sync, and therefore a new drift
|
|
20
|
+
* source, guarding a failure mode that has not occurred. Trigger to revisit: a
|
|
21
|
+
* disposition that genuinely cannot be recorded in the round record itself.
|
|
22
|
+
*
|
|
23
|
+
* Scope: `*-review.md` under the reviews root, excluding `*.findings.md` (those
|
|
24
|
+
* are live artefacts and belong to `check_completion_review`).
|
|
25
|
+
*
|
|
26
|
+
* Reference SHAPE is deliberately NOT checked, and that was a measurement, not
|
|
27
|
+
* a preference. The first draft resolved a `fixed` ref via `rev-parse` and a
|
|
28
|
+
* `deferred` ref via a path probe. Run against the real corpus it produced 8
|
|
29
|
+
* blocks — every one of them a pre-existing archived record whose reference is
|
|
30
|
+
* prose describing the change, or a carrier named by bare slug. None of the 8
|
|
31
|
+
* was the failure this gate exists to catch. Archived records are frozen
|
|
32
|
+
* (§2.7), so a shape rule cannot be satisfied retroactively without editing
|
|
33
|
+
* them; a gate whose only output is unfixable blocks is the gate that gets
|
|
34
|
+
* switched off. Trigger to revisit: a review round where an unresolvable
|
|
35
|
+
* reference actually hid a disposition, on a record written AFTER this gate
|
|
36
|
+
* shipped and therefore fixable at source.
|
|
37
|
+
*
|
|
38
|
+
* Exit codes (contract §6): 0 = pass, 1 = policy violation (including a DEAD
|
|
39
|
+
* SCAN SCOPE — a gate that read nothing has not passed), 2 = internal error
|
|
40
|
+
* (the CALLER applies degraded advisory mode). `scanned:` is emitted on EVERY
|
|
41
|
+
* exit path, exit 2 included.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import * as fs from 'node:fs';
|
|
45
|
+
import * as path from 'node:path';
|
|
46
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
47
|
+
|
|
48
|
+
import { DeadScopeError, assertScanned } from './_lib/scan_scope.js';
|
|
49
|
+
import { parseArtifact } from './check_completion_review.js';
|
|
50
|
+
|
|
51
|
+
const REVIEWS_ROOT = 'agents/evidence/reviews';
|
|
52
|
+
const TERMINAL = new Set(['fixed', 'accepted-risk', 'deferred']);
|
|
53
|
+
|
|
54
|
+
export interface Violation {
|
|
55
|
+
kind: string;
|
|
56
|
+
file: string;
|
|
57
|
+
detail: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** An archived record: a `*-review.md` that is not a live `*.findings.md`. */
|
|
61
|
+
export function isArchivedRecord(rel: string): boolean {
|
|
62
|
+
const base = rel.replace(/\\/g, '/').split('/').pop() ?? '';
|
|
63
|
+
return base.endsWith('-review.md') && !base.endsWith('.findings.md');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface CheckOptions {
|
|
67
|
+
repo: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Contract §2.7: every row of an archived record is terminal, with its reference. */
|
|
71
|
+
export function checkRecord(rel: string, text: string, _opts: CheckOptions): Violation[] {
|
|
72
|
+
const violations: Violation[] = [];
|
|
73
|
+
const parsed = parseArtifact(text);
|
|
74
|
+
for (const row of parsed.rows) {
|
|
75
|
+
const where = `row ${row.index} (line ${String(row.line)})`;
|
|
76
|
+
if (row.status === 'open') {
|
|
77
|
+
violations.push({
|
|
78
|
+
kind: 'open-in-archived-record',
|
|
79
|
+
file: rel,
|
|
80
|
+
detail:
|
|
81
|
+
`${where}: status \`open\` in an ARCHIVED record. Renaming a round out of the ` +
|
|
82
|
+
'`*.findings.md` glob asserts it is closed, so every row must already be terminal ' +
|
|
83
|
+
'(`fixed` / `accepted-risk` / `deferred`) — contract §2.7.',
|
|
84
|
+
});
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!TERMINAL.has(row.status)) {
|
|
88
|
+
violations.push({
|
|
89
|
+
kind: 'bad-status',
|
|
90
|
+
file: rel,
|
|
91
|
+
detail: `${where}: unknown status \`${row.status}\` — expected fixed / accepted-risk / deferred.`,
|
|
92
|
+
});
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (row.reasonRef.trim() === '') {
|
|
96
|
+
violations.push({
|
|
97
|
+
kind: 'missing-reference',
|
|
98
|
+
file: rel,
|
|
99
|
+
detail: `${where}: status \`${row.status}\` requires a Reason/Ref, which is empty.`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return violations;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function main(argv?: readonly string[]): number {
|
|
107
|
+
const args = argv ?? process.argv.slice(2);
|
|
108
|
+
let repo = process.cwd();
|
|
109
|
+
let root = REVIEWS_ROOT;
|
|
110
|
+
let quiet = false;
|
|
111
|
+
for (let i = 0; i < args.length; i++) {
|
|
112
|
+
const a = args[i] as string;
|
|
113
|
+
if (a === '--repo') repo = args[++i] as string;
|
|
114
|
+
else if (a === '--reviews-root') root = args[++i] as string;
|
|
115
|
+
else if (a === '--quiet') quiet = true;
|
|
116
|
+
else if (a === '-h' || a === '--help') {
|
|
117
|
+
process.stdout.write('usage: check_review_dispositions [--repo PATH] [--reviews-root PATH] [--quiet]\n');
|
|
118
|
+
process.stdout.write('scanned: 0\n');
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const abs = path.resolve(repo, root);
|
|
124
|
+
let files: string[] = [];
|
|
125
|
+
try {
|
|
126
|
+
files = fs
|
|
127
|
+
.readdirSync(abs, { withFileTypes: true })
|
|
128
|
+
.filter((e) => e.isFile() && isArchivedRecord(e.name))
|
|
129
|
+
.map((e) => path.join(root, e.name))
|
|
130
|
+
.sort();
|
|
131
|
+
} catch {
|
|
132
|
+
// Absent root: report it as a DEAD SCOPE below rather than a clean pass.
|
|
133
|
+
files = [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
assertScanned({
|
|
138
|
+
gate: 'check_review_dispositions',
|
|
139
|
+
scanned: files.length,
|
|
140
|
+
units: 'archived review record(s)',
|
|
141
|
+
roots: [root],
|
|
142
|
+
});
|
|
143
|
+
} catch (exc) {
|
|
144
|
+
// A dead scan scope is a POLICY violation (exit 1), never an internal
|
|
145
|
+
// error: exit 2 is warn-and-allow at every call site, so a moved reviews
|
|
146
|
+
// root would silently degrade this gate to advisory.
|
|
147
|
+
process.stdout.write('scanned: 0\n');
|
|
148
|
+
process.stderr.write(`❌ ${exc instanceof DeadScopeError ? exc.message : String(exc)}\n`);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const violations: Violation[] = [];
|
|
153
|
+
let scanned = 0;
|
|
154
|
+
try {
|
|
155
|
+
for (const rel of files) {
|
|
156
|
+
const text = fs.readFileSync(path.resolve(repo, rel), 'utf-8');
|
|
157
|
+
scanned += 1;
|
|
158
|
+
violations.push(...checkRecord(rel, text, { repo }));
|
|
159
|
+
}
|
|
160
|
+
} catch (exc) {
|
|
161
|
+
process.stdout.write(`scanned: ${String(scanned)}\n`);
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
`❌ check_review_dispositions: internal error after ${String(scanned)} record(s): ` +
|
|
164
|
+
`${exc instanceof Error ? exc.message : String(exc)}\n`,
|
|
165
|
+
);
|
|
166
|
+
return 2;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (violations.length > 0) {
|
|
170
|
+
process.stdout.write(`❌ ${String(violations.length)} disposition violation(s):\n\n`);
|
|
171
|
+
for (const v of violations) {
|
|
172
|
+
process.stdout.write(` ${v.kind} — ${v.file}\n │ ${v.detail}\n`);
|
|
173
|
+
}
|
|
174
|
+
} else if (!quiet) {
|
|
175
|
+
process.stdout.write(`✅ Archived review records all terminal (${String(scanned)} record(s) scanned).\n`);
|
|
176
|
+
}
|
|
177
|
+
process.stdout.write(`scanned: ${String(scanned)}\n`);
|
|
178
|
+
return violations.length > 0 ? 1 : 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
182
|
+
function _isCliEntry(): boolean {
|
|
183
|
+
if (process.argv[1] === undefined) return false;
|
|
184
|
+
return import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href || process.argv[1] === _HERE;
|
|
185
|
+
}
|
|
186
|
+
if (_isCliEntry()) {
|
|
187
|
+
process.exit(main());
|
|
188
|
+
}
|
|
@@ -535,6 +535,9 @@ function reviewerPrompt(args: {
|
|
|
535
535
|
'- Severity ∈ {`critical`, `high`, `medium`, `low`}, rows sorted descending',
|
|
536
536
|
' by severity (ties keep authoring order).',
|
|
537
537
|
'- Initial status of every finding: `open`.',
|
|
538
|
+
'- A row is LIVE wherever it appears — a code fence around it changes',
|
|
539
|
+
' nothing. If you quote the template as an illustration, its Status cell',
|
|
540
|
+
' must be exactly `example`, or the gate reads it as a real finding.',
|
|
538
541
|
'- 0 findings → replace the table with exactly this honest-null line',
|
|
539
542
|
' (contract §2.3):',
|
|
540
543
|
'',
|