memento-mori-jester 0.1.50 → 0.1.52

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/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to Memento Mori Jester are tracked here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.1.52
8
+
9
+ - Added `npm run fixtures:check`, a local fixture authoring validator for duplicate IDs, weak metadata, unsafe-looking content, duplicate content, and explicit expected/absent rule intent.
10
+ - Wired fixture authoring validation into `npm test` so fixture quality is checked alongside the review-engine expectations.
11
+ - Updated maintainer, fixture, release, and production-readiness docs to make fixture validation part of the support-to-coverage workflow.
12
+
13
+ ## 0.1.51
14
+
15
+ - Added `docs/MAINTAINER_TRIAGE.md` with a repeatable flow for triaging bugs, false positives, security-sensitive reports, and fixture candidates.
16
+ - Updated fixture docs so useful noisy-rule reports can become small redacted fixture cases instead of one-off anecdotes.
17
+ - Expanded the production readiness guard so maintainer triage and fixture-conversion guidance stay present in future releases.
18
+
7
19
  ## 0.1.50
8
20
 
9
21
  - Added `SECURITY.md` with vulnerability reporting guidance, supported-version expectations, scope, and redacted diagnostic guidance.
package/README.md CHANGED
@@ -431,6 +431,7 @@ More setup examples:
431
431
  - [Review Fixtures](examples/fixtures)
432
432
  - [Framework CI Examples](examples/ci)
433
433
  - [Security Policy](SECURITY.md)
434
+ - [Maintainer Triage](docs/MAINTAINER_TRIAGE.md)
434
435
  - [Changelog](CHANGELOG.md)
435
436
  - [Roadmap](ROADMAP.md)
436
437
  - [Trusted npm Publishing](docs/TRUSTED_PUBLISHING.md)
@@ -498,6 +499,9 @@ When filing a bug, include redacted `jester doctor --json` output. The GitHub is
498
499
 
499
500
  Use the false-positive template for noisy cautions or blocks. Include `jester summary` and `jester tune <rule-id> --json` output when possible so rule changes can be backed by evidence.
500
501
 
502
+ Maintainers can use [docs/MAINTAINER_TRIAGE.md](docs/MAINTAINER_TRIAGE.md) to turn useful false-positive reports into redacted fixtures.
503
+ Run `npm run fixtures:check` before merging fixture changes; it catches duplicate IDs, missing rule metadata, weak descriptions, unsafe-looking content, and duplicate content.
504
+
501
505
  For vulnerabilities, private code exposure, or credential-handling concerns, follow [SECURITY.md](SECURITY.md) instead of opening a public issue with sensitive details.
502
506
 
503
507
  ## Publishing
package/ROADMAP.md CHANGED
@@ -6,6 +6,8 @@ Memento Mori Jester is usable today as a CLI, MCP server, GitHub Action, and git
6
6
 
7
7
  ## Recently Shipped
8
8
 
9
+ - Fixture authoring validator in v0.1.52 for duplicate IDs, missing expected/absent rule intent, weak metadata, unsafe-looking content, and duplicate content.
10
+ - Maintainer triage guide in v0.1.51 for turning useful false-positive reports into redacted fixture coverage.
9
11
  - Security policy and GitHub issue templates in v0.1.50 for bug reports, false positives, feature requests, and vulnerability intake.
10
12
  - Support-focused `doctor --json` diagnostics in v0.1.49 for package, config, hook, MCP, and GitHub Action state.
11
13
  - Production readiness checklist and static guard in v0.1.48 for package, workflow, docs, release, and support drift.
@@ -40,7 +42,7 @@ Memento Mori Jester is usable today as a CLI, MCP server, GitHub Action, and git
40
42
  ## Product Ideas
41
43
 
42
44
  - Add more framework-specific false-positive examples from real reports so tuning guidance keeps getting sharper.
43
- - Add a lightweight maintainer triage guide for turning noisy-rule reports into fixtures.
45
+ - Add a fixture report generator that shows which rules, presets, and review kinds still need better pass-case coverage.
44
46
 
45
47
  ## Quality And Safety
46
48
 
package/docs/DEMO.md CHANGED
@@ -347,6 +347,8 @@ Preset packs:
347
347
 
348
348
  The fixture suite in `examples/fixtures/preset-review-cases.json` captures small real-usage examples with expected `pass`, `caution`, or `block` verdicts. These examples are run by `npm test`, so preset tuning changes stay visible.
349
349
 
350
+ Maintainers can use `docs/MAINTAINER_TRIAGE.md` to turn useful false-positive reports into redacted fixture cases.
351
+
350
352
  ## 14. Framework CI Examples
351
353
 
352
354
  The workflow examples in `examples/ci` show copy-paste GitHub Actions setups for Next.js, Vite React, Express API, FastAPI, Terraform/Kubernetes, and AI MCP repos. Each workflow uploads SARIF and writes the readable Jester job summary.
@@ -0,0 +1,107 @@
1
+ # Maintainer Triage
2
+
3
+ This guide turns support reports into repeatable maintenance work. The goal is simple: every useful bug or false-positive report should either become a clearer answer, a better fixture, or a focused code change.
4
+
5
+ ## First Response
6
+
7
+ Ask for redacted diagnostics when they are missing:
8
+
9
+ ```powershell
10
+ npx -y memento-mori-jester@latest doctor --json
11
+ ```
12
+
13
+ For noisy rule reports, also ask for:
14
+
15
+ ```powershell
16
+ npx -y memento-mori-jester@latest summary --kind <command|plan|diff|final> "<minimal input>"
17
+ npx -y memento-mori-jester@latest tune <rule-id> --json
18
+ ```
19
+
20
+ Do not ask users to paste secrets, private code, customer data, live credentials, complete CI logs, or unredacted SARIF. If the report involves credential exposure, command execution, unexpected network access, private code disclosure, package publishing, or MCP data exposure, route it through [SECURITY.md](../SECURITY.md).
21
+
22
+ ## Triage Labels
23
+
24
+ Use a small, boring label vocabulary:
25
+
26
+ - `bug`: behavior is broken or misleading.
27
+ - `false-positive`: Jester warned or blocked when the minimal example should probably pass.
28
+ - `rules`: rule matching, severity, fixture evidence, or tuning behavior.
29
+ - `docs`: documentation is unclear or missing.
30
+ - `enhancement`: a new command, preset, workflow, or larger product idea.
31
+ - `security`: only for public tracking with no sensitive details; private details belong in the security report flow.
32
+
33
+ ## False-Positive Decision Tree
34
+
35
+ 1. Confirm the minimal input reproduces on `latest` or local `main`.
36
+ 2. Identify the rule id from `summary` output.
37
+ 3. Run `jester tune <rule-id> --json` and inspect `fixtureEvidence`.
38
+ 4. Decide whether the current behavior is:
39
+ - expected and should be explained,
40
+ - noisy but acceptable with tuning guidance,
41
+ - a fixture gap,
42
+ - a rule bug,
43
+ - or a preset mismatch.
44
+
45
+ If the user has a safe example that should pass, prefer adding a pass fixture before loosening a rule. If the example should still caution but the wording is confusing, update the rule guidance or docs instead of changing matching behavior.
46
+
47
+ ## Converting Reports Into Fixtures
48
+
49
+ Add a fixture when the report is minimal, redacted, realistic, and captures a rule behavior worth preserving.
50
+
51
+ Good fixture candidates:
52
+
53
+ - use the smallest command, plan, diff, or final answer that reproduces the behavior,
54
+ - identify the preset or default config needed to reproduce it,
55
+ - include the expected verdict,
56
+ - include `expectedRuleIds` when the fixture should cover specific rules,
57
+ - include `absentRuleIds` when a quiet fixture protects against noisy matches,
58
+ - include `weight` when the case should strongly influence tuning confidence,
59
+ - set `edgeCase: true` when the example is useful but unusual.
60
+
61
+ Avoid fixtures that:
62
+
63
+ - contain secrets, private paths, private code, customer data, or identifiable logs,
64
+ - depend on network calls, local machine state, dates, or npm/GitHub availability,
65
+ - encode a one-off project preference as a global default,
66
+ - duplicate an existing fixture without adding a new rule, preset, kind, or edge case.
67
+
68
+ ## Fixture Workflow
69
+
70
+ 1. Add or edit a case in [examples/fixtures/preset-review-cases.json](../examples/fixtures/preset-review-cases.json).
71
+ 2. Keep fixture IDs stable and descriptive, for example `web-localstorage-token-pass-2`.
72
+ 3. Prefer deterministic content over full real-world excerpts.
73
+ 4. Run:
74
+
75
+ ```powershell
76
+ npm.cmd test
77
+ npm.cmd run fixtures:check
78
+ node .\dist\cli.js tune <rule-id>
79
+ node .\dist\cli.js tune <rule-id> --json
80
+ node .\dist\cli.js tune coverage
81
+ ```
82
+
83
+ 5. Fix any duplicate IDs, missing expected rule metadata, weak descriptions, unsafe content, or duplicate content reported by `fixtures:check`.
84
+ 6. Check whether support/confidence changed in the expected direction.
85
+ 7. If the fixture changes verdict behavior, mention the exact rule impact in `CHANGELOG.md`.
86
+
87
+ ## When To Change A Rule
88
+
89
+ Change rule logic only when fixtures show the current matcher is broadly wrong or too blunt. Keep the change small:
90
+
91
+ - preserve existing JSON output shapes unless the release explicitly changes them,
92
+ - add pass and caution/block fixtures around the boundary,
93
+ - update `jester rule <id>` guidance when the safe alternative or tuning advice changes,
94
+ - keep docs-only noise suppression conservative,
95
+ - never suppress project custom rules globally.
96
+
97
+ ## Closing Notes
98
+
99
+ Close with the command users can run next. Good closes include:
100
+
101
+ ```powershell
102
+ npx -y memento-mori-jester@latest tune <rule-id>
103
+ npx -y memento-mori-jester@latest config disable-rule <rule-id>
104
+ npx -y memento-mori-jester@latest config validate
105
+ ```
106
+
107
+ If the report produced a fixture, mention the fixture ID in the issue. That gives future maintainers a trail from user pain to test coverage.
@@ -51,6 +51,8 @@ This checklist defines what "production grade" means for Memento Mori Jester rig
51
51
  - `jester tune`, `jester tune coverage`, and the fixture suite give maintainers a way to inspect noisy rules before changing defaults.
52
52
  - GitHub issue templates collect bug reports, false-positive reports, and feature requests with the diagnostic context maintainers need.
53
53
  - `SECURITY.md` routes vulnerability reports away from public issues and asks for redacted diagnostics.
54
+ - `docs/MAINTAINER_TRIAGE.md` explains how to turn useful false-positive reports into fixture coverage before changing rule logic.
55
+ - `npm run fixtures:check` validates fixture IDs, metadata, unsafe-looking content, duplicate content, and explicit expected/absent rule intent.
54
56
  - npm publish has a manual workflow fallback, but the normal release path is tag-driven trusted publishing.
55
57
 
56
58
  ## Static Guard
@@ -63,6 +65,8 @@ This checklist defines what "production grade" means for Memento Mori Jester rig
63
65
  - onboarding docs mention the important adoption paths,
64
66
  - production readiness documentation covers package, GitHub Action, MCP, git hooks, docs, and support,
65
67
  - `SECURITY.md` and GitHub issue templates exist and ask for the right diagnostics.
68
+ - maintainer triage docs exist and link noisy-rule reports back to fixture coverage.
69
+ - fixture authoring checks are wired into `npm test`.
66
70
 
67
71
  `npm test` runs this check after the TypeScript build and unit tests.
68
72
 
package/docs/RELEASE.md CHANGED
@@ -8,6 +8,7 @@ This project publishes GitHub Releases and npm packages from `v*` tags.
8
8
  npm.cmd version 0.1.x --no-git-tag-version
9
9
  npm.cmd test
10
10
  npm.cmd run production:check
11
+ npm.cmd run fixtures:check
11
12
  npm.cmd run pack:dry
12
13
  git diff --check
13
14
  ```
@@ -17,7 +18,7 @@ Move the current changelog bullets into a matching version section and add `docs
17
18
  ## 2. Tag And Push
18
19
 
19
20
  ```powershell
20
- git add package.json package-lock.json CHANGELOG.md docs/RELEASE_NOTES_v0.1.x.md docs/PRODUCTION_READINESS.md SECURITY.md .github/ISSUE_TEMPLATE
21
+ git add package.json package-lock.json CHANGELOG.md docs/RELEASE_NOTES_v0.1.x.md docs/PRODUCTION_READINESS.md docs/MAINTAINER_TRIAGE.md SECURITY.md .github/ISSUE_TEMPLATE
21
22
  git commit -m "Release v0.1.x"
22
23
  git tag -a v0.1.x -m "Memento Mori Jester v0.1.x"
23
24
  git push origin main
@@ -0,0 +1,29 @@
1
+ # v0.1.51 Release Notes
2
+
3
+ This release adds a maintainer triage guide so support reports can feed back into better fixture coverage and rule tuning.
4
+
5
+ ## What Changed
6
+
7
+ - Added `docs/MAINTAINER_TRIAGE.md`.
8
+ - Documented the first-response diagnostic commands for bug and false-positive reports.
9
+ - Added a false-positive decision tree for deciding between explanation, tuning guidance, fixture coverage, rule fixes, and preset fixes.
10
+ - Updated `examples/fixtures/README.md` with guidance for converting safe reports into redacted fixture cases.
11
+ - Updated README, demo docs, roadmap, changelog, release docs, and production readiness docs.
12
+ - Expanded `npm run production:check` so maintainer triage and fixture-conversion guidance remain part of the release contract.
13
+
14
+ ## Behavior Notes
15
+
16
+ - No CLI, MCP, config, rule, playground, GitHub Action runtime, or release automation behavior changed.
17
+ - This is a support and maintenance release.
18
+
19
+ ## Release Validation
20
+
21
+ ```powershell
22
+ npm.cmd test
23
+ npm.cmd run production:check
24
+ npm.cmd run demo:svg:check
25
+ npm.cmd run pack:dry
26
+ git diff --check
27
+ node .\dist\cli.js doctor --json
28
+ git diff | node .\dist\cli.js diff --fail-on block --subject "v0.1.51 maintainer triage"
29
+ ```
@@ -0,0 +1,35 @@
1
+ # v0.1.52 Release Notes
2
+
3
+ This release adds a fixture authoring validator so noisy-rule reports become clean, deterministic fixture coverage instead of fragile JSON edits.
4
+
5
+ ## What Changed
6
+
7
+ - Added `scripts/check-fixtures.mjs`.
8
+ - Added `npm run fixtures:check`.
9
+ - Wired `fixtures:check` into `npm test`.
10
+ - The validator checks:
11
+ - duplicate fixture IDs,
12
+ - valid preset, kind, verdict, and rule-id metadata,
13
+ - missing `expectedRuleIds` / `absentRuleIds` intent,
14
+ - invalid `weight` / `edgeCase` values,
15
+ - duplicate content for the same preset and kind,
16
+ - unsafe-looking fixture content such as private keys, provider tokens, and absolute home-directory paths.
17
+ - Updated maintainer triage docs, fixture docs, release docs, roadmap, changelog, and production readiness checks.
18
+
19
+ ## Behavior Notes
20
+
21
+ - No CLI, MCP, config, rule, playground, GitHub Action runtime, or release automation behavior changed.
22
+ - Existing review fixture expectations remain unchanged.
23
+
24
+ ## Release Validation
25
+
26
+ ```powershell
27
+ npm.cmd test
28
+ npm.cmd run fixtures:check
29
+ npm.cmd run production:check
30
+ npm.cmd run demo:svg:check
31
+ npm.cmd run pack:dry
32
+ git diff --check
33
+ node .\dist\cli.js doctor --json
34
+ git diff | node .\dist\cli.js diff --fail-on block --subject "v0.1.52 fixture authoring validator"
35
+ ```
@@ -4,6 +4,8 @@ These fixtures are small, real-usage-shaped examples for preset tuning. They are
4
4
 
5
5
  The fixture file is [preset-review-cases.json](preset-review-cases.json).
6
6
 
7
+ Maintainer triage guidance lives in [docs/MAINTAINER_TRIAGE.md](../../docs/MAINTAINER_TRIAGE.md).
8
+
7
9
  ## What They Cover
8
10
 
9
11
  - Documentation-only diffs that should stay quiet.
@@ -17,6 +19,7 @@ The fixture file is [preset-review-cases.json](preset-review-cases.json).
17
19
 
18
20
  ```powershell
19
21
  npm.cmd test
22
+ npm.cmd run fixtures:check
20
23
  ```
21
24
 
22
25
  For one-off manual review, paste a fixture `content` value into:
@@ -24,3 +27,18 @@ For one-off manual review, paste a fixture `content` value into:
24
27
  ```powershell
25
28
  npx -y memento-mori-jester@latest playground
26
29
  ```
30
+
31
+ ## Adding A Fixture From A Report
32
+
33
+ Use the smallest redacted example that still reproduces the behavior. A good fixture records:
34
+
35
+ - the review `kind`,
36
+ - the preset or config needed to reproduce it,
37
+ - the expected verdict,
38
+ - the rule ids that should match in `expectedRuleIds`,
39
+ - noisy rules that must stay absent in `absentRuleIds`,
40
+ - and whether the case is an unusual `edgeCase`.
41
+
42
+ Do not add secrets, private code, customer data, complete logs, or machine-specific paths. If a false-positive report is safe but broad, add a passing fixture before loosening a rule.
43
+
44
+ `npm run fixtures:check` validates duplicate IDs, missing expected rule metadata, weak descriptions, unsafe-looking fixture content, and duplicate content before the fixture suite becomes tuning evidence.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memento-mori-jester",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "description": "A local court-jester sidecar for AI coding agents: review plans, commands, diffs, and final claims before they get too pleased with themselves.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -40,10 +40,11 @@
40
40
  "build": "tsc -p tsconfig.json",
41
41
  "start": "node dist/server.js",
42
42
  "start:mcp": "node dist/server.js",
43
- "test": "npm run build && node scripts/run-tests.mjs && npm run production:check",
43
+ "test": "npm run build && node scripts/run-tests.mjs && npm run fixtures:check && npm run production:check",
44
44
  "doctor": "node dist/cli.js doctor",
45
45
  "demo:svg": "node scripts/render-demo-svg.mjs",
46
46
  "demo:svg:check": "node scripts/render-demo-svg.mjs --check",
47
+ "fixtures:check": "node scripts/check-fixtures.mjs",
47
48
  "production:check": "node scripts/check-production-readiness.mjs",
48
49
  "pack:dry": "npm pack --dry-run",
49
50
  "prepare": "npm run build",
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ const root = process.cwd();
6
+ const fixturePath = "examples/fixtures/preset-review-cases.json";
7
+ const failures = [];
8
+
9
+ function read(path) {
10
+ return readFileSync(join(root, path), "utf8");
11
+ }
12
+
13
+ function readConstStringArray(path, constName) {
14
+ const source = read(path);
15
+ const match = source.match(new RegExp(`export const ${constName} = \\[([^\\]]+)\\] as const`));
16
+
17
+ if (!match) {
18
+ failures.push(`Could not read ${constName} from ${path}.`);
19
+ return [];
20
+ }
21
+
22
+ return [...match[1].matchAll(/"([^"]+)"/g)].map((entry) => entry[1]);
23
+ }
24
+
25
+ const allowedPresets = new Set(readConstStringArray("src/config.ts", "configPresetNames"));
26
+ const allowedKinds = new Set(readConstStringArray("src/types.ts", "reviewKinds"));
27
+ const allowedVerdicts = new Set(["pass", "caution", "block"]);
28
+ const unsafeContentPatterns = [
29
+ { name: "private key block", pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
30
+ { name: "OpenAI-looking secret key", pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/ },
31
+ { name: "Anthropic-looking secret key", pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/ },
32
+ { name: "GitHub-looking token", pattern: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/ },
33
+ { name: "AWS access key id", pattern: /\bAKIA[0-9A-Z]{16}\b/ },
34
+ { name: "Slack-looking token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/ },
35
+ { name: "absolute Unix home path", pattern: /(?:^|[\s"'`])\/(?:Users|home)\/[A-Za-z0-9._-]+/ },
36
+ { name: "absolute Windows user path", pattern: /[A-Za-z]:\\Users\\[A-Za-z0-9._-]+\\/ }
37
+ ];
38
+
39
+ let fixtures;
40
+ try {
41
+ fixtures = JSON.parse(read(fixturePath));
42
+ } catch (error) {
43
+ console.error(`Could not parse ${fixturePath}: ${error instanceof Error ? error.message : String(error)}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ if (!Array.isArray(fixtures)) {
48
+ console.error(`${fixturePath} should contain a JSON array.`);
49
+ process.exit(1);
50
+ }
51
+
52
+ const ids = new Set();
53
+ const contentKeys = new Map();
54
+
55
+ for (const [index, fixture] of fixtures.entries()) {
56
+ const label = typeof fixture?.id === "string" && fixture.id.length > 0
57
+ ? fixture.id
58
+ : `fixture[${index}]`;
59
+
60
+ if (!fixture || typeof fixture !== "object" || Array.isArray(fixture)) {
61
+ failures.push(`${label} should be an object.`);
62
+ continue;
63
+ }
64
+
65
+ checkString(fixture, "id", label);
66
+ checkString(fixture, "description", label, { minLength: 20 });
67
+ checkString(fixture, "content", label, { minLength: 3 });
68
+
69
+ if (typeof fixture.id === "string") {
70
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(fixture.id)) {
71
+ failures.push(`${label}.id should use stable kebab-case.`);
72
+ }
73
+
74
+ if (ids.has(fixture.id)) {
75
+ failures.push(`${label}.id is duplicated.`);
76
+ }
77
+ ids.add(fixture.id);
78
+ }
79
+
80
+ if (!allowedPresets.has(fixture.preset)) {
81
+ failures.push(`${label}.preset should be one of: ${[...allowedPresets].join(", ")}.`);
82
+ }
83
+
84
+ if (!allowedKinds.has(fixture.kind)) {
85
+ failures.push(`${label}.kind should be one of: ${[...allowedKinds].join(", ")}.`);
86
+ }
87
+
88
+ if (!allowedVerdicts.has(fixture.expectedVerdict)) {
89
+ failures.push(`${label}.expectedVerdict should be pass, caution, or block.`);
90
+ }
91
+
92
+ checkRuleIdArray(fixture, "expectedRuleIds", label, { required: true });
93
+ checkRuleIdArray(fixture, "absentRuleIds", label, { required: false });
94
+
95
+ const expectedRuleIds = Array.isArray(fixture.expectedRuleIds) ? fixture.expectedRuleIds : [];
96
+ const absentRuleIds = Array.isArray(fixture.absentRuleIds) ? fixture.absentRuleIds : [];
97
+ const expectedSet = new Set(expectedRuleIds);
98
+ for (const ruleId of absentRuleIds) {
99
+ if (expectedSet.has(ruleId)) {
100
+ failures.push(`${label} lists ${ruleId} in both expectedRuleIds and absentRuleIds.`);
101
+ }
102
+ }
103
+
104
+ if (fixture.expectedVerdict !== "pass" && expectedRuleIds.length === 0) {
105
+ failures.push(`${label} should include at least one expectedRuleIds entry for ${fixture.expectedVerdict} verdicts.`);
106
+ }
107
+
108
+ if (expectedRuleIds.length === 0 && absentRuleIds.length === 0) {
109
+ failures.push(`${label} should include expectedRuleIds or absentRuleIds so fixture intent is explicit.`);
110
+ }
111
+
112
+ if (fixture.weight !== undefined && (!Number.isInteger(fixture.weight) || fixture.weight < 1 || fixture.weight > 3)) {
113
+ failures.push(`${label}.weight should be an integer from 1 to 3.`);
114
+ }
115
+
116
+ if (fixture.edgeCase !== undefined && typeof fixture.edgeCase !== "boolean") {
117
+ failures.push(`${label}.edgeCase should be boolean when present.`);
118
+ }
119
+
120
+ if (typeof fixture.content === "string") {
121
+ for (const unsafe of unsafeContentPatterns) {
122
+ if (unsafe.pattern.test(fixture.content)) {
123
+ failures.push(`${label}.content appears to contain ${unsafe.name}; fixtures should use redacted placeholders.`);
124
+ }
125
+ }
126
+
127
+ const contentKey = `${fixture.preset}:${fixture.kind}:${fixture.content}`;
128
+ const previous = contentKeys.get(contentKey);
129
+ if (previous) {
130
+ failures.push(`${label}.content duplicates ${previous} for the same preset and kind.`);
131
+ } else {
132
+ contentKeys.set(contentKey, label);
133
+ }
134
+ }
135
+ }
136
+
137
+ if (failures.length > 0) {
138
+ console.error("Fixture authoring check failed:");
139
+ for (const failure of failures) {
140
+ console.error(`- ${failure}`);
141
+ }
142
+ process.exit(1);
143
+ }
144
+
145
+ process.stdout.write(`Fixture authoring check passed for ${fixtures.length} fixtures.\n`);
146
+
147
+ function checkString(fixture, field, label, options = {}) {
148
+ const value = fixture[field];
149
+ if (typeof value !== "string" || value.trim().length === 0) {
150
+ failures.push(`${label}.${field} should be a non-empty string.`);
151
+ return;
152
+ }
153
+
154
+ if (options.minLength && value.trim().length < options.minLength) {
155
+ failures.push(`${label}.${field} should be at least ${options.minLength} characters.`);
156
+ }
157
+ }
158
+
159
+ function checkRuleIdArray(fixture, field, label, options) {
160
+ const value = fixture[field];
161
+
162
+ if (value === undefined) {
163
+ if (options.required) {
164
+ failures.push(`${label}.${field} should be an array.`);
165
+ }
166
+ return;
167
+ }
168
+
169
+ if (!Array.isArray(value)) {
170
+ failures.push(`${label}.${field} should be an array.`);
171
+ return;
172
+ }
173
+
174
+ const seen = new Set();
175
+ for (const ruleId of value) {
176
+ if (typeof ruleId !== "string" || ruleId.length === 0) {
177
+ failures.push(`${label}.${field} should only contain non-empty strings.`);
178
+ continue;
179
+ }
180
+
181
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(ruleId)) {
182
+ failures.push(`${label}.${field} contains ${ruleId}; rule ids should use kebab-case.`);
183
+ }
184
+
185
+ if (seen.has(ruleId)) {
186
+ failures.push(`${label}.${field} repeats ${ruleId}.`);
187
+ }
188
+ seen.add(ruleId);
189
+ }
190
+ }
@@ -54,8 +54,10 @@ for (const path of [
54
54
  "docs/RELEASE.md",
55
55
  "docs/TRUSTED_PUBLISHING.md",
56
56
  "docs/PRODUCTION_READINESS.md",
57
+ "docs/MAINTAINER_TRIAGE.md",
57
58
  `docs/RELEASE_NOTES_${tag}.md`,
58
59
  "action.yml",
60
+ "scripts/check-fixtures.mjs",
59
61
  ".github/ISSUE_TEMPLATE/bug_report.yml",
60
62
  ".github/ISSUE_TEMPLATE/false_positive.yml",
61
63
  ".github/ISSUE_TEMPLATE/feature_request.yml",
@@ -81,6 +83,8 @@ requireText("README.md", /setup --agent codex/, "Codex setup onboarding");
81
83
  requireText("README.md", /github-action --write/, "GitHub Action onboarding");
82
84
  requireText("README.md", /SECURITY\.md/, "security policy link");
83
85
  requireText("README.md", /false-positive/i, "false-positive support guidance");
86
+ requireText("README.md", /MAINTAINER_TRIAGE\.md/, "maintainer triage guide link");
87
+ requireText("README.md", /fixtures:check/, "fixture authoring check guidance");
84
88
  requireText("README.md", /License: PolyForm Noncommercial/, "the noncommercial license badge");
85
89
  requireText("docs/PRODUCTION_READINESS.md", /npm package/i, "npm package readiness");
86
90
  requireText("docs/PRODUCTION_READINESS.md", /GitHub Action/i, "GitHub Action readiness");
@@ -90,7 +94,21 @@ requireText("docs/PRODUCTION_READINESS.md", /support/i, "support readiness");
90
94
  requireText("docs/PRODUCTION_READINESS.md", /doctor --json/, "doctor JSON support diagnostics");
91
95
  requireText("docs/PRODUCTION_READINESS.md", /SECURITY\.md/, "security policy readiness");
92
96
  requireText("docs/PRODUCTION_READINESS.md", /issue templates/i, "issue template readiness");
97
+ requireText("docs/PRODUCTION_READINESS.md", /MAINTAINER_TRIAGE\.md/, "maintainer triage readiness");
98
+ requireText("docs/PRODUCTION_READINESS.md", /fixtures:check/, "fixture authoring check readiness");
93
99
  requireText("docs/CLI.md", /jester doctor --json/, "doctor JSON CLI docs");
100
+ requireText("docs/MAINTAINER_TRIAGE.md", /doctor --json/, "doctor JSON triage prompt");
101
+ requireText("docs/MAINTAINER_TRIAGE.md", /tune <rule-id> --json/, "tune JSON triage prompt");
102
+ requireText("docs/MAINTAINER_TRIAGE.md", /preset-review-cases\.json/, "fixture suite link");
103
+ requireText("docs/MAINTAINER_TRIAGE.md", /expectedRuleIds/, "fixture expected rule guidance");
104
+ requireText("docs/MAINTAINER_TRIAGE.md", /absentRuleIds/, "fixture absent rule guidance");
105
+ requireText("examples/fixtures/README.md", /MAINTAINER_TRIAGE\.md/, "maintainer triage link");
106
+ requireText("examples/fixtures/README.md", /Adding A Fixture From A Report/, "fixture report conversion guidance");
107
+ requireText("examples/fixtures/README.md", /fixtures:check/, "fixture authoring check guidance");
108
+ requireText("scripts/check-fixtures.mjs", /duplicated/, "duplicate fixture id check");
109
+ requireText("scripts/check-fixtures.mjs", /unsafeContentPatterns/, "unsafe fixture content checks");
110
+ requireText("package.json", /"fixtures:check": "node scripts\/check-fixtures\.mjs"/, "fixture authoring check script");
111
+ requireText("package.json", /npm run fixtures:check/, "fixture authoring check in npm test");
94
112
  requireText("SECURITY.md", /doctor --json/, "doctor JSON redaction guidance");
95
113
  requireText("SECURITY.md", /security\/advisories\/new/, "private vulnerability report link");
96
114
  requireText(".github/ISSUE_TEMPLATE/bug_report.yml", /doctor --json/, "doctor JSON support prompt");