memento-mori-jester 0.1.82 → 0.1.84
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 +12 -0
- package/README.md +2 -1
- package/ROADMAP.md +4 -2
- package/docs/CLI.md +1 -1
- package/docs/DEMO.md +16 -1
- package/docs/FRAMEWORK_TUNING.md +9 -7
- package/docs/GETTING_STARTED.md +1 -1
- package/docs/PRODUCTION_READINESS.md +5 -1
- package/docs/RELEASE.md +3 -1
- package/docs/RELEASE_NOTES_v0.1.83.md +54 -0
- package/docs/RELEASE_NOTES_v0.1.84.md +50 -0
- package/examples/tuning/README.md +31 -0
- package/examples/tuning/framework-tuning-cookbook.json +96 -0
- package/package.json +4 -2
- package/scripts/check-framework-tuning.mjs +214 -0
- package/scripts/check-production-readiness.mjs +27 -0
- package/scripts/doctor-framework-tuning.mjs +263 -0
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.84
|
|
8
|
+
|
|
9
|
+
- Added `npm run framework:tuning:doctor`, a consumer-style smoke check that generates temporary preset configs and runs every cookbook `jester tune <rule-id> --json` command through the built CLI.
|
|
10
|
+
- Wired the tuning doctor into `npm test` and the production-readiness guard.
|
|
11
|
+
- Updated framework tuning docs, cookbook docs, release docs, demo docs, and README guidance for the new doctor check.
|
|
12
|
+
|
|
13
|
+
## 0.1.83
|
|
14
|
+
|
|
15
|
+
- Added `examples/tuning/framework-tuning-cookbook.json`, a small checked cookbook that maps framework-shaped noisy-rule reports to exact `jester tune <rule> --json` commands and fixture IDs.
|
|
16
|
+
- Added `examples/tuning/README.md` and linked it from README, CLI docs, getting-started docs, demo docs, and the framework tuning guide.
|
|
17
|
+
- Added `npm run framework:tuning:check` and wired it into `npm test` and production-readiness checks so cookbook recipes stay aligned with docs and fixtures.
|
|
18
|
+
|
|
7
19
|
## 0.1.82
|
|
8
20
|
|
|
9
21
|
- Added six real-world quiet-pass fixtures for FastAPI dependency injection, frozen `uv` syncs, docs-only Terraform and Helm guidance, redacted Gitleaks scans, and Next.js workspace linting.
|
package/README.md
CHANGED
|
@@ -315,7 +315,7 @@ jester tune coverage --json
|
|
|
315
315
|
|
|
316
316
|
`jester tune coverage` shows the fixture support and confidence signal for every rule, including suggested next actions such as adding coverage, reviewing surprise matches, checking quiet-pass boundaries, or leaving a healthy signal alone.
|
|
317
317
|
|
|
318
|
-
For stack-shaped noise, see [Framework Tuning Examples](docs/FRAMEWORK_TUNING.md). It maps common Next.js, Vite React, FastAPI, Terraform/Kubernetes, security-scan, and AI/MCP false-positive reports to the `jester tune <rule>` command and fixture IDs worth checking first.
|
|
318
|
+
For stack-shaped noise, see [Framework Tuning Examples](docs/FRAMEWORK_TUNING.md). It maps common Next.js, Vite React, FastAPI, Terraform/Kubernetes, security-scan, and AI/MCP false-positive reports to the `jester tune <rule>` command and fixture IDs worth checking first. The checked [framework tuning cookbook](examples/tuning) turns those rows into copy-paste recipes and a machine-readable JSON file, and `npm run framework:tuning:doctor` proves those recipes execute through the built CLI.
|
|
319
319
|
|
|
320
320
|
Disable a noisy rule by adding its id to `disabledRules` in `jester.config.json`:
|
|
321
321
|
|
|
@@ -429,6 +429,7 @@ More setup examples:
|
|
|
429
429
|
- [MCP Tool Reference](docs/MCP_TOOLS.md)
|
|
430
430
|
- [GitHub Actions](docs/GITHUB_ACTIONS.md)
|
|
431
431
|
- [Framework Tuning Examples](docs/FRAMEWORK_TUNING.md)
|
|
432
|
+
- [Framework Tuning Cookbook](examples/tuning)
|
|
432
433
|
- [Demo Script](docs/DEMO.md)
|
|
433
434
|
- [Promo Share Kit](promo)
|
|
434
435
|
- [Examples](examples)
|
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
|
+
- Framework tuning doctor in v0.1.84, proving cookbook recipes execute through the built CLI with generated preset configs before release.
|
|
10
|
+
- Framework tuning cookbook in v0.1.83, adding checked copy-paste recipes and a machine-readable JSON map for stack-shaped noisy-rule reports.
|
|
9
11
|
- Framework tuning examples and quiet-pass fixture curation in v0.1.82, adding six safe real-world examples plus a guide for framework-shaped noisy-rule reports.
|
|
10
12
|
- Repo-local landing page in v0.1.81, adding a static one-page share surface plus deterministic link checks.
|
|
11
13
|
- Social preview card in v0.1.80, adding a deterministic 1200x630 promo card plus generation and freshness checks.
|
|
@@ -71,8 +73,8 @@ Memento Mori Jester is usable today as a CLI, MCP server, GitHub Action, and git
|
|
|
71
73
|
|
|
72
74
|
## Product Ideas
|
|
73
75
|
|
|
74
|
-
- Collect real-world reports
|
|
75
|
-
- Add a small fixture issue template checklist that asks for the nearest framework tuning
|
|
76
|
+
- Collect real-world reports and fold the strongest redacted cases into more framework tuning cookbook recipes.
|
|
77
|
+
- Add a small fixture issue template checklist that asks for the nearest framework tuning cookbook recipe and redacted `jester tune <rule-id> --json` output.
|
|
76
78
|
- Add a hosted-page option or GitHub Pages instructions once the static page has settled.
|
|
77
79
|
|
|
78
80
|
## Quality And Safety
|
package/docs/CLI.md
CHANGED
|
@@ -158,7 +158,7 @@ Use `jester tune <id>` when the question is practical: should this noisy rule be
|
|
|
158
158
|
|
|
159
159
|
When filing a false-positive issue, include redacted `jester summary` output and `jester tune <rule-id> --json` output when possible.
|
|
160
160
|
|
|
161
|
-
For stack-shaped reports, see [Framework Tuning Examples](FRAMEWORK_TUNING.md). It points common Next.js, Vite React, FastAPI, Terraform/Kubernetes, security-scan, and AI/MCP noisy-rule reports at the relevant `jester tune <rule-id>` command and fixture IDs.
|
|
161
|
+
For stack-shaped reports, see [Framework Tuning Examples](FRAMEWORK_TUNING.md). It points common Next.js, Vite React, FastAPI, Terraform/Kubernetes, security-scan, and AI/MCP noisy-rule reports at the relevant `jester tune <rule-id>` command and fixture IDs. The checked [framework tuning cookbook](../examples/tuning) keeps copy-paste recipes and fixture IDs in one machine-readable place. Maintainers can run `npm run framework:tuning:doctor` to prove every cookbook tune command executes through the built CLI with temporary preset configs.
|
|
162
162
|
|
|
163
163
|
Use `jester tune coverage` when maintaining the rule set. It ranks every rule by fixture support and confidence, shows expected vs unexpected fixture weight, and suggests the next maintenance action for each rule.
|
|
164
164
|
|
package/docs/DEMO.md
CHANGED
|
@@ -373,6 +373,21 @@ Maintainers can run `npm run fixtures:report` to see coverage by verdict, kind,
|
|
|
373
373
|
|
|
374
374
|
Maintainers can use `docs/MAINTAINER_TRIAGE.md` to turn useful false-positive reports into redacted fixture cases.
|
|
375
375
|
|
|
376
|
-
## 14. Framework
|
|
376
|
+
## 14. Framework Tuning Cookbook
|
|
377
|
+
|
|
378
|
+
For real repos with stack-shaped noisy rules, use [docs/FRAMEWORK_TUNING.md](FRAMEWORK_TUNING.md) and the checked cookbook in [examples/tuning](../examples/tuning).
|
|
379
|
+
|
|
380
|
+
The cookbook maps recipe IDs such as `next-vite-public-config`, `terraform-kubernetes-docs-only`, and `ai-mcp-tooling` to the exact `jester tune <rule-id> --json` commands and fixture IDs worth comparing first.
|
|
381
|
+
|
|
382
|
+
Maintainers can run:
|
|
383
|
+
|
|
384
|
+
```powershell
|
|
385
|
+
npm.cmd run framework:tuning:doctor
|
|
386
|
+
npm.cmd run framework:tuning:check
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The doctor runs every cookbook tune command through the built CLI with temporary preset configs. The check validates [framework-tuning-cookbook.json](../examples/tuning/framework-tuning-cookbook.json) against this guide, the cookbook README, and `examples/fixtures/preset-review-cases.json`.
|
|
390
|
+
|
|
391
|
+
## 15. Framework CI Examples
|
|
377
392
|
|
|
378
393
|
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.
|
package/docs/FRAMEWORK_TUNING.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Use this when a rule is noisy in a real project and you want the smallest evidence-backed next step before muting it.
|
|
4
4
|
|
|
5
|
+
For copy-pasteable recipes, see [examples/tuning](../examples/tuning). The machine-readable cookbook is [framework-tuning-cookbook.json](../examples/tuning/framework-tuning-cookbook.json). `npm run framework:tuning:check` keeps it aligned with this guide and the fixture suite, while `npm run framework:tuning:doctor` runs the cookbook's tune commands through the built CLI with temporary preset configs.
|
|
6
|
+
|
|
5
7
|
Start with the rule that actually fired:
|
|
6
8
|
|
|
7
9
|
```powershell
|
|
@@ -11,13 +13,13 @@ jester tune <rule-id> --json
|
|
|
11
13
|
|
|
12
14
|
Then compare the output with the nearest fixture-backed examples below. If your case is closer to the safe examples than the risky examples, add a redacted fixture before changing a rule.
|
|
13
15
|
|
|
14
|
-
| Stack | Common noisy rule | Useful tune command | Safe fixture examples |
|
|
15
|
-
| --- | --- | --- | --- |
|
|
16
|
-
| Next.js / Vite React | Public but non-secret frontend names or harmless workspace commands | `jester tune custom-web-public-secret-name --json` or `jester tune custom-node-install-script-change --json` | `web-public-analytics-env-command-pass`, `node-next-lint-command-pass` |
|
|
17
|
-
| FastAPI / Python | Typed dependency injection, schema parsing, or locked dependency sync being confused with dynamic execution | `jester tune custom-python-eval-exec --json` or `jester tune custom-python-pickle-load --json` | `python-fastapi-dependency-diff-pass`, `python-pydantic-parse-diff-pass`, `python-uv-sync-frozen-command-pass` |
|
|
18
|
-
| Terraform / Kubernetes / Helm | Docs-only infrastructure guidance mentioning sensitive tool names | `jester tune risky-domain --json` or `jester tune configured-sensitive-domain-terraform --json` | `infra-terraform-plan-docs-pass`, `infra-helm-values-docs-pass`, `infra-kubectl-describe-command-pass` |
|
|
19
|
-
| Security scanning | Redacted scanner output or SBOM/report generation being confused with secret material | `jester tune secret-material --json` or `jester tune custom-insecure-tls-disabled --json` | `sec-gitleaks-redacted-command-pass`, `sec-sbom-command-pass`, `sec-vulnerability-report-docs-pass` |
|
|
20
|
-
| AI / MCP tools | Static allowlists, model checks, or public model names being confused with unsafe tool dispatch or provider keys | `jester tune custom-ai-user-controlled-tool-dispatch --json` or `jester tune custom-ai-public-provider-key --json` | `ai-tool-registry-allowlist-diff-pass`, `ai-model-regression-command-pass`, `ai-public-model-env-diff-pass` |
|
|
16
|
+
| Cookbook recipe | Stack | Common noisy rule | Useful tune command | Safe fixture examples |
|
|
17
|
+
| --- | --- | --- | --- | --- |
|
|
18
|
+
| `next-vite-public-config` | Next.js / Vite React | Public but non-secret frontend names or harmless workspace commands | `jester tune custom-web-public-secret-name --json` or `jester tune custom-node-install-script-change --json` | `web-public-analytics-env-command-pass`, `node-next-lint-command-pass` |
|
|
19
|
+
| `fastapi-python-execution-boundary` | FastAPI / Python | Typed dependency injection, schema parsing, or locked dependency sync being confused with dynamic execution | `jester tune custom-python-eval-exec --json` or `jester tune custom-python-pickle-load --json` | `python-fastapi-dependency-diff-pass`, `python-pydantic-parse-diff-pass`, `python-uv-sync-frozen-command-pass` |
|
|
20
|
+
| `terraform-kubernetes-docs-only` | Terraform / Kubernetes / Helm | Docs-only infrastructure guidance mentioning sensitive tool names | `jester tune risky-domain --json` or `jester tune configured-sensitive-domain-terraform --json` | `infra-terraform-plan-docs-pass`, `infra-helm-values-docs-pass`, `infra-kubectl-describe-command-pass` |
|
|
21
|
+
| `security-scan-reporting` | Security scanning | Redacted scanner output or SBOM/report generation being confused with secret material | `jester tune secret-material --json` or `jester tune custom-insecure-tls-disabled --json` | `sec-gitleaks-redacted-command-pass`, `sec-sbom-command-pass`, `sec-vulnerability-report-docs-pass` |
|
|
22
|
+
| `ai-mcp-tooling` | AI / MCP tools | Static allowlists, model checks, or public model names being confused with unsafe tool dispatch or provider keys | `jester tune custom-ai-user-controlled-tool-dispatch --json` or `jester tune custom-ai-public-provider-key --json` | `ai-tool-registry-allowlist-diff-pass`, `ai-model-regression-command-pass`, `ai-public-model-env-diff-pass` |
|
|
21
23
|
|
|
22
24
|
## What To Do With The Result
|
|
23
25
|
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -106,7 +106,7 @@ npx -y memento-mori-jester@latest bootstrap --preset node
|
|
|
106
106
|
|
|
107
107
|
Then tell them to open `MEMENTO_MORI.md`.
|
|
108
108
|
|
|
109
|
-
For copy-paste agent and hook examples, see [examples](../examples). For stack-specific config examples, see [preset example packs](../examples/presets) for Next.js, Vite React, Express API, FastAPI, Terraform/Kubernetes, and AI MCP repos. For copy-paste CI workflows, see [framework CI examples](../examples/ci). For concrete pass, caution, and block cases, see [review fixtures](../examples/fixtures). For stack-shaped noisy-rule reports, see [framework tuning examples](FRAMEWORK_TUNING.md).
|
|
109
|
+
For copy-paste agent and hook examples, see [examples](../examples). For stack-specific config examples, see [preset example packs](../examples/presets) for Next.js, Vite React, Express API, FastAPI, Terraform/Kubernetes, and AI MCP repos. For copy-paste CI workflows, see [framework CI examples](../examples/ci). For concrete pass, caution, and block cases, see [review fixtures](../examples/fixtures). For stack-shaped noisy-rule reports, see [framework tuning examples](FRAMEWORK_TUNING.md) and the checked [framework tuning cookbook](../examples/tuning).
|
|
110
110
|
|
|
111
111
|
## Need Help?
|
|
112
112
|
|
|
@@ -52,12 +52,14 @@ This checklist defines what "production grade" means for Memento Mori Jester rig
|
|
|
52
52
|
- Package metadata points bug reports at the GitHub issues page.
|
|
53
53
|
- `jester doctor --json`, `jester config validate`, and `jester rules` are the first troubleshooting commands.
|
|
54
54
|
- `jester tune`, `jester tune coverage`, and the fixture suite give maintainers a way to inspect noisy rules before changing defaults.
|
|
55
|
-
- [FRAMEWORK_TUNING.md](FRAMEWORK_TUNING.md) maps common stack-specific false-positive reports to the relevant `jester tune <rule-id>` evidence and fixture IDs.
|
|
55
|
+
- [FRAMEWORK_TUNING.md](FRAMEWORK_TUNING.md) maps common stack-specific false-positive reports to the relevant `jester tune <rule-id>` evidence and fixture IDs, while [examples/tuning](../examples/tuning) provides checked copy-paste recipes.
|
|
56
56
|
- GitHub issue templates collect bug reports, false-positive reports, and feature requests with the diagnostic context maintainers need.
|
|
57
57
|
- `SECURITY.md` routes vulnerability reports away from public issues and asks for redacted diagnostics.
|
|
58
58
|
- `docs/MAINTAINER_TRIAGE.md` explains how to turn useful false-positive reports into fixture coverage before changing rule logic.
|
|
59
59
|
- `npm run fixtures:check` validates fixture IDs, metadata, unsafe-looking content, duplicate content, and explicit expected/absent rule intent.
|
|
60
60
|
- `npm run fixtures:report` shows fixture coverage by rule, rule family, preset slice, kind, verdict, quiet-pass rule boundaries, and feasible pass-case gaps so maintainers can pick the next fixture target; `npm run fixtures:report -- --markdown` produces a paste-ready maintainer snapshot.
|
|
61
|
+
- `npm run framework:tuning:check` keeps the framework tuning guide, cookbook JSON, cookbook README, and fixture IDs aligned.
|
|
62
|
+
- `npm run framework:tuning:doctor` runs the cookbook tune commands through the built CLI with temporary preset configs, so package consumers do not inherit stale recipes.
|
|
61
63
|
- `npm run promo:card` regenerates the deterministic social preview card, and `npm run promo:check` verifies current repo-local promo assets against the current fixture evidence before maintainers post or refresh the demo.
|
|
62
64
|
- `npm run site:check` verifies the static landing page before maintainers post or host it.
|
|
63
65
|
- npm publish has a manual workflow fallback, but the normal release path is tag-driven trusted publishing.
|
|
@@ -75,6 +77,8 @@ This checklist defines what "production grade" means for Memento Mori Jester rig
|
|
|
75
77
|
- maintainer triage docs exist and link noisy-rule reports back to fixture coverage.
|
|
76
78
|
- fixture authoring checks are wired into `npm test`.
|
|
77
79
|
- fixture coverage reports are wired into `npm test`.
|
|
80
|
+
- framework tuning cookbook checks are wired into `npm test`.
|
|
81
|
+
- framework tuning cookbook doctor checks are wired into `npm test`.
|
|
78
82
|
- promo freshness checks are wired into `npm test`.
|
|
79
83
|
- site checks are wired into `npm test`.
|
|
80
84
|
|
package/docs/RELEASE.md
CHANGED
|
@@ -12,6 +12,8 @@ npm.cmd run fixtures:check
|
|
|
12
12
|
npm.cmd run fixtures:report
|
|
13
13
|
npm.cmd run fixtures:report -- --json
|
|
14
14
|
npm.cmd run fixtures:report -- --markdown
|
|
15
|
+
npm.cmd run framework:tuning:check
|
|
16
|
+
npm.cmd run framework:tuning:doctor
|
|
15
17
|
npm.cmd run promo:card:check
|
|
16
18
|
npm.cmd run promo:check
|
|
17
19
|
npm.cmd run site:check
|
|
@@ -24,7 +26,7 @@ Move the current changelog bullets into a matching version section and add `docs
|
|
|
24
26
|
## 2. Tag And Push
|
|
25
27
|
|
|
26
28
|
```powershell
|
|
27
|
-
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
|
|
29
|
+
git add package.json package-lock.json CHANGELOG.md docs/RELEASE_NOTES_v0.1.x.md docs/PRODUCTION_READINESS.md docs/MAINTAINER_TRIAGE.md docs/FRAMEWORK_TUNING.md examples/tuning scripts/check-framework-tuning.mjs scripts/doctor-framework-tuning.mjs SECURITY.md .github/ISSUE_TEMPLATE
|
|
28
30
|
git commit -m "Release v0.1.x"
|
|
29
31
|
git tag -a v0.1.x -m "Memento Mori Jester v0.1.x"
|
|
30
32
|
git push origin main
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Memento Mori Jester v0.1.83
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
This release makes the framework tuning guide easier to execute from real repos. It adds a tiny checked cookbook that maps common stack-shaped noisy-rule reports to exact `jester tune <rule-id> --json` commands and fixture IDs.
|
|
6
|
+
|
|
7
|
+
## What Changed
|
|
8
|
+
|
|
9
|
+
- Added `examples/tuning/framework-tuning-cookbook.json`.
|
|
10
|
+
- Added `examples/tuning/README.md` with copy-paste recipes for:
|
|
11
|
+
- Next.js / Vite React,
|
|
12
|
+
- FastAPI / Python,
|
|
13
|
+
- Terraform / Kubernetes / Helm,
|
|
14
|
+
- security scanning,
|
|
15
|
+
- AI / MCP tools.
|
|
16
|
+
- Added `npm run framework:tuning:check`.
|
|
17
|
+
- Wired the cookbook checker into `npm test` and production-readiness checks.
|
|
18
|
+
- Linked the cookbook from README, CLI docs, getting-started docs, demo docs, release docs, production-readiness docs, and `docs/FRAMEWORK_TUNING.md`.
|
|
19
|
+
|
|
20
|
+
## Public Interface
|
|
21
|
+
|
|
22
|
+
- No CLI command changes.
|
|
23
|
+
- No MCP tool changes.
|
|
24
|
+
- No config schema changes.
|
|
25
|
+
- No review rule, scoring, matching, or verdict behavior changes.
|
|
26
|
+
- No GitHub Action or release workflow changes.
|
|
27
|
+
- Fixture count remains `222`; this release adds checked cookbook assets, not new review cases.
|
|
28
|
+
|
|
29
|
+
## Release Validation
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
npm.cmd test
|
|
33
|
+
npm.cmd run demo:svg:check
|
|
34
|
+
npm.cmd run promo:card:check
|
|
35
|
+
npm.cmd run promo:check
|
|
36
|
+
npm.cmd run framework:tuning:check
|
|
37
|
+
npm.cmd run fixtures:report
|
|
38
|
+
npm.cmd run fixtures:report -- --json
|
|
39
|
+
npm.cmd run fixtures:report -- --markdown
|
|
40
|
+
npm.cmd run site:check
|
|
41
|
+
npm.cmd run pack:dry
|
|
42
|
+
git diff --check
|
|
43
|
+
node .\dist\cli.js tune coverage --no-config
|
|
44
|
+
git diff | node .\dist\cli.js diff --fail-on block --subject "v0.1.83 framework tuning cookbook"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Expected:
|
|
48
|
+
|
|
49
|
+
- `framework:tuning:check` passes for five recipes,
|
|
50
|
+
- fixture report still shows `Fixtures: 222`,
|
|
51
|
+
- no thin rule coverage,
|
|
52
|
+
- no preset/kind gaps,
|
|
53
|
+
- no rules without quiet-pass coverage,
|
|
54
|
+
- GitHub Release and npm Publish complete from the `v0.1.83` tag.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Memento Mori Jester v0.1.84
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
This release adds a consumer-style doctor for the framework tuning cookbook. The cookbook was already checked for doc and fixture alignment; now maintainers can prove the cookbook's tune commands execute through the built CLI with generated preset configs.
|
|
6
|
+
|
|
7
|
+
## What Changed
|
|
8
|
+
|
|
9
|
+
- Added `scripts/doctor-framework-tuning.mjs`.
|
|
10
|
+
- Added `npm run framework:tuning:doctor`.
|
|
11
|
+
- Wired the doctor into `npm test` and production-readiness checks.
|
|
12
|
+
- Updated README, CLI docs, demo docs, release docs, production-readiness docs, roadmap, changelog, and the tuning cookbook docs.
|
|
13
|
+
|
|
14
|
+
## Public Interface
|
|
15
|
+
|
|
16
|
+
- No CLI command changes.
|
|
17
|
+
- No MCP tool changes.
|
|
18
|
+
- No config schema changes.
|
|
19
|
+
- No review rule, scoring, matching, or verdict behavior changes.
|
|
20
|
+
- No GitHub Action or release workflow changes.
|
|
21
|
+
- New maintainer/package script: `npm run framework:tuning:doctor`.
|
|
22
|
+
|
|
23
|
+
## Release Validation
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
npm.cmd test
|
|
27
|
+
npm.cmd run demo:svg:check
|
|
28
|
+
npm.cmd run promo:card:check
|
|
29
|
+
npm.cmd run promo:check
|
|
30
|
+
npm.cmd run framework:tuning:check
|
|
31
|
+
npm.cmd run framework:tuning:doctor
|
|
32
|
+
npm.cmd run fixtures:report
|
|
33
|
+
npm.cmd run fixtures:report -- --json
|
|
34
|
+
npm.cmd run fixtures:report -- --markdown
|
|
35
|
+
npm.cmd run site:check
|
|
36
|
+
npm.cmd run pack:dry
|
|
37
|
+
git diff --check
|
|
38
|
+
node .\dist\cli.js tune coverage --no-config
|
|
39
|
+
git diff | node .\dist\cli.js diff --fail-on block --subject "v0.1.84 framework tuning doctor"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Expected:
|
|
43
|
+
|
|
44
|
+
- `framework:tuning:check` passes for five recipes,
|
|
45
|
+
- `framework:tuning:doctor` runs 10 executable tune commands,
|
|
46
|
+
- fixture report still shows `Fixtures: 222`,
|
|
47
|
+
- no thin rule coverage,
|
|
48
|
+
- no preset/kind gaps,
|
|
49
|
+
- no rules without quiet-pass coverage,
|
|
50
|
+
- GitHub Release and npm Publish complete from the `v0.1.84` tag.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Framework Tuning Cookbook
|
|
2
|
+
|
|
3
|
+
These small recipes pair the [Framework Tuning Examples](../../docs/FRAMEWORK_TUNING.md) guide with checked, fixture-backed commands. Use them when a real repo reports a noisy rule and you want the smallest evidence-backed next step before changing config.
|
|
4
|
+
|
|
5
|
+
The machine-readable source is [framework-tuning-cookbook.json](framework-tuning-cookbook.json). It is checked by `npm run framework:tuning:check`, so recipe commands and fixture IDs stay aligned with the public guide and fixture suite.
|
|
6
|
+
|
|
7
|
+
Run `npm run framework:tuning:doctor` after `npm run build` when you want a consumer-style smoke check. It generates temporary preset configs with the built CLI, runs every cookbook `jester tune <rule-id> --json` command, validates the JSON advice shape, and confirms each recipe's fixture IDs are present in the packaged fixture suite.
|
|
8
|
+
|
|
9
|
+
## Recipes
|
|
10
|
+
|
|
11
|
+
| Recipe | Stack | Preset | Tune commands | Fixture examples |
|
|
12
|
+
| --- | --- | --- | --- | --- |
|
|
13
|
+
| `next-vite-public-config` | Next.js / Vite React | `web` | `jester tune custom-web-public-secret-name --json`; `jester tune custom-node-install-script-change --json` | `web-public-analytics-env-command-pass`, `node-next-lint-command-pass` |
|
|
14
|
+
| `fastapi-python-execution-boundary` | FastAPI / Python | `python` | `jester tune custom-python-eval-exec --json`; `jester tune custom-python-pickle-load --json` | `python-fastapi-dependency-diff-pass`, `python-pydantic-parse-diff-pass`, `python-uv-sync-frozen-command-pass` |
|
|
15
|
+
| `terraform-kubernetes-docs-only` | Terraform / Kubernetes / Helm | `infra` | `jester tune risky-domain --json`; `jester tune configured-sensitive-domain-terraform --json` | `infra-terraform-plan-docs-pass`, `infra-helm-values-docs-pass`, `infra-kubectl-describe-command-pass` |
|
|
16
|
+
| `security-scan-reporting` | Security scanning | `security` | `jester tune secret-material --json`; `jester tune custom-insecure-tls-disabled --json` | `sec-gitleaks-redacted-command-pass`, `sec-sbom-command-pass`, `sec-vulnerability-report-docs-pass` |
|
|
17
|
+
| `ai-mcp-tooling` | AI / MCP tools | `ai` | `jester tune custom-ai-user-controlled-tool-dispatch --json`; `jester tune custom-ai-public-provider-key --json` | `ai-tool-registry-allowlist-diff-pass`, `ai-model-regression-command-pass`, `ai-public-model-env-diff-pass` |
|
|
18
|
+
|
|
19
|
+
## How To Use A Recipe
|
|
20
|
+
|
|
21
|
+
1. Run `jester summary --kind <command|plan|diff|final> "<redacted minimal input>"` and copy the rule id that fired.
|
|
22
|
+
2. Run the nearest recipe command, such as `jester tune custom-ai-user-controlled-tool-dispatch --json`.
|
|
23
|
+
3. Compare `fixtureEvidence.quietPassFixtures` and sample fixture descriptions with the local hit.
|
|
24
|
+
4. If the local case is safe but missing from the fixture suite, add a redacted pass fixture before loosening a rule.
|
|
25
|
+
5. If the local case includes a real secret, destructive command, broad permission, skipped eval, or user-controlled execution path, fix the change instead of muting the rule.
|
|
26
|
+
|
|
27
|
+
Maintainer smoke check:
|
|
28
|
+
|
|
29
|
+
```powershell
|
|
30
|
+
npm.cmd run framework:tuning:doctor
|
|
31
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "next-vite-public-config",
|
|
4
|
+
"stack": "Next.js / Vite React",
|
|
5
|
+
"preset": "web",
|
|
6
|
+
"when": "Public-but-non-secret frontend names or harmless workspace commands look similar to browser secret or install-script risks.",
|
|
7
|
+
"commands": [
|
|
8
|
+
"jester tune custom-web-public-secret-name --json",
|
|
9
|
+
"jester tune custom-node-install-script-change --json"
|
|
10
|
+
],
|
|
11
|
+
"fixtures": [
|
|
12
|
+
"web-public-analytics-env-command-pass",
|
|
13
|
+
"node-next-lint-command-pass"
|
|
14
|
+
],
|
|
15
|
+
"next": [
|
|
16
|
+
"Compare fixtureEvidence.quietPassFixtures with the redacted local hit.",
|
|
17
|
+
"Prefer a local disabledRules entry only after repeated harmless hits in the same repo."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "fastapi-python-execution-boundary",
|
|
22
|
+
"stack": "FastAPI / Python",
|
|
23
|
+
"preset": "python",
|
|
24
|
+
"when": "Typed dependency injection, schema parsing, or locked dependency sync is mistaken for dynamic execution or unsafe deserialization.",
|
|
25
|
+
"commands": [
|
|
26
|
+
"jester tune custom-python-eval-exec --json",
|
|
27
|
+
"jester tune custom-python-pickle-load --json"
|
|
28
|
+
],
|
|
29
|
+
"fixtures": [
|
|
30
|
+
"python-fastapi-dependency-diff-pass",
|
|
31
|
+
"python-pydantic-parse-diff-pass",
|
|
32
|
+
"python-uv-sync-frozen-command-pass"
|
|
33
|
+
],
|
|
34
|
+
"next": [
|
|
35
|
+
"Compare the local change with the safe FastAPI and Pydantic fixture descriptions first.",
|
|
36
|
+
"Add a redacted pass fixture before loosening Python execution or pickle-shaped rules."
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "terraform-kubernetes-docs-only",
|
|
41
|
+
"stack": "Terraform / Kubernetes / Helm",
|
|
42
|
+
"preset": "infra",
|
|
43
|
+
"when": "Docs-only infrastructure guidance mentions Terraform, Helm, or other sensitive infra words without changing deploy behavior.",
|
|
44
|
+
"commands": [
|
|
45
|
+
"jester tune risky-domain --json",
|
|
46
|
+
"jester tune configured-sensitive-domain-terraform --json"
|
|
47
|
+
],
|
|
48
|
+
"fixtures": [
|
|
49
|
+
"infra-terraform-plan-docs-pass",
|
|
50
|
+
"infra-helm-values-docs-pass",
|
|
51
|
+
"infra-kubectl-describe-command-pass"
|
|
52
|
+
],
|
|
53
|
+
"next": [
|
|
54
|
+
"Check that the local hit is documentation-only before muting broad sensitive-domain noise.",
|
|
55
|
+
"Keep destructive command, IAM widening, public exposure, and deploy-changing rules active."
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "security-scan-reporting",
|
|
60
|
+
"stack": "Security scanning",
|
|
61
|
+
"preset": "security",
|
|
62
|
+
"when": "Redacted scanner output, SBOM generation, or reporting docs look like exposed secret material or skipped verification.",
|
|
63
|
+
"commands": [
|
|
64
|
+
"jester tune secret-material --json",
|
|
65
|
+
"jester tune custom-insecure-tls-disabled --json"
|
|
66
|
+
],
|
|
67
|
+
"fixtures": [
|
|
68
|
+
"sec-gitleaks-redacted-command-pass",
|
|
69
|
+
"sec-sbom-command-pass",
|
|
70
|
+
"sec-vulnerability-report-docs-pass"
|
|
71
|
+
],
|
|
72
|
+
"next": [
|
|
73
|
+
"Verify the report uses placeholders or scanner-generated summaries rather than live tokens.",
|
|
74
|
+
"If a real credential appears, rotate it and treat the Jester hit as correct."
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "ai-mcp-tooling",
|
|
79
|
+
"stack": "AI / MCP tools",
|
|
80
|
+
"preset": "ai",
|
|
81
|
+
"when": "Static allowlists, model checks, or public model names are mistaken for unsafe tool dispatch, skipped evals, or provider keys.",
|
|
82
|
+
"commands": [
|
|
83
|
+
"jester tune custom-ai-user-controlled-tool-dispatch --json",
|
|
84
|
+
"jester tune custom-ai-public-provider-key --json"
|
|
85
|
+
],
|
|
86
|
+
"fixtures": [
|
|
87
|
+
"ai-tool-registry-allowlist-diff-pass",
|
|
88
|
+
"ai-model-regression-command-pass",
|
|
89
|
+
"ai-public-model-env-diff-pass"
|
|
90
|
+
],
|
|
91
|
+
"next": [
|
|
92
|
+
"Compare the local tool path with the allowlist fixture before changing an AI preset rule.",
|
|
93
|
+
"Keep concrete model-output execution and client-exposed provider-key hits treated as dangerous."
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memento-mori-jester",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.84",
|
|
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,12 +40,14 @@
|
|
|
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 fixtures:check && npm run fixtures:report && npm run promo:check && npm run site:check && npm run production:check",
|
|
43
|
+
"test": "npm run build && node scripts/run-tests.mjs && npm run fixtures:check && npm run fixtures:report && npm run framework:tuning:check && npm run framework:tuning:doctor && npm run promo:check && npm run site: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
47
|
"fixtures:check": "node scripts/check-fixtures.mjs",
|
|
48
48
|
"fixtures:report": "node scripts/report-fixtures.mjs",
|
|
49
|
+
"framework:tuning:check": "node scripts/check-framework-tuning.mjs",
|
|
50
|
+
"framework:tuning:doctor": "node scripts/doctor-framework-tuning.mjs",
|
|
49
51
|
"promo:card": "node scripts/render-social-card.mjs",
|
|
50
52
|
"promo:card:check": "node scripts/render-social-card.mjs --check",
|
|
51
53
|
"promo:check": "node scripts/check-promo-freshness.mjs",
|
|
@@ -0,0 +1,214 @@
|
|
|
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 cookbookPath = "examples/tuning/framework-tuning-cookbook.json";
|
|
7
|
+
const cookbookReadmePath = "examples/tuning/README.md";
|
|
8
|
+
const guidePath = "docs/FRAMEWORK_TUNING.md";
|
|
9
|
+
const fixturesPath = "examples/fixtures/preset-review-cases.json";
|
|
10
|
+
const failures = [];
|
|
11
|
+
|
|
12
|
+
const allowedPresets = new Set(["default", "node", "python", "web", "api", "infra", "ai", "security"]);
|
|
13
|
+
const unsafeContentPatterns = [
|
|
14
|
+
{ name: "private key block", pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
|
|
15
|
+
{ name: "OpenAI-looking secret key", pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/ },
|
|
16
|
+
{ name: "Anthropic-looking secret key", pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/ },
|
|
17
|
+
{ name: "GitHub-looking token", pattern: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/ },
|
|
18
|
+
{ name: "AWS access key id", pattern: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
19
|
+
{ name: "Slack-looking token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/ },
|
|
20
|
+
{ name: "absolute Unix home path", pattern: /(?:^|[\s"'`])\/(?:Users|home)\/[A-Za-z0-9._-]+/ },
|
|
21
|
+
{ name: "absolute Windows user path", pattern: /[A-Za-z]:\\Users\\[A-Za-z0-9._-]+\\/ }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function read(path) {
|
|
25
|
+
return readFileSync(join(root, path), "utf8");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readJson(path) {
|
|
29
|
+
return JSON.parse(read(path));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function checkString(value, label, options = {}) {
|
|
33
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
34
|
+
failures.push(`${label} should be a non-empty string.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (options.kebab && !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {
|
|
39
|
+
failures.push(`${label} should use stable kebab-case.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (options.minLength && value.trim().length < options.minLength) {
|
|
43
|
+
failures.push(`${label} should be at least ${options.minLength} characters.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function checkStringArray(value, label, options = {}) {
|
|
48
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
49
|
+
failures.push(`${label} should be a non-empty array.`);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
for (const [index, item] of value.entries()) {
|
|
55
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
56
|
+
failures.push(`${label}[${index}] should be a non-empty string.`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (seen.has(item)) {
|
|
61
|
+
failures.push(`${label} repeats ${item}.`);
|
|
62
|
+
}
|
|
63
|
+
seen.add(item);
|
|
64
|
+
|
|
65
|
+
if (options.pattern && !options.pattern.test(item)) {
|
|
66
|
+
failures.push(`${label}[${index}] should match ${options.description}. Saw ${item}.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function requireIncludes(content, file, value, description) {
|
|
74
|
+
if (!content.includes(value)) {
|
|
75
|
+
failures.push(`${file} should include ${description}: ${value}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let cookbook;
|
|
80
|
+
let fixtures;
|
|
81
|
+
try {
|
|
82
|
+
cookbook = readJson(cookbookPath);
|
|
83
|
+
fixtures = readJson(fixturesPath);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error(`Could not parse framework tuning inputs: ${error instanceof Error ? error.message : String(error)}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const cookbookRaw = read(cookbookPath);
|
|
90
|
+
const cookbookReadme = read(cookbookReadmePath);
|
|
91
|
+
const frameworkGuide = read(guidePath);
|
|
92
|
+
|
|
93
|
+
for (const [file, content] of [
|
|
94
|
+
[cookbookPath, cookbookRaw],
|
|
95
|
+
[cookbookReadmePath, cookbookReadme],
|
|
96
|
+
[guidePath, frameworkGuide]
|
|
97
|
+
]) {
|
|
98
|
+
for (const unsafe of unsafeContentPatterns) {
|
|
99
|
+
if (unsafe.pattern.test(content)) {
|
|
100
|
+
failures.push(`${file} appears to contain ${unsafe.name}; tuning examples should stay public and redacted.`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!Array.isArray(cookbook)) {
|
|
106
|
+
failures.push(`${cookbookPath} should contain a JSON array.`);
|
|
107
|
+
} else if (cookbook.length < 5) {
|
|
108
|
+
failures.push(`${cookbookPath} should contain at least five framework recipes.`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!Array.isArray(fixtures)) {
|
|
112
|
+
failures.push(`${fixturesPath} should contain a JSON array.`);
|
|
113
|
+
fixtures = [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const fixtureIds = new Set();
|
|
117
|
+
const fixtureRuleIds = new Set();
|
|
118
|
+
for (const fixture of fixtures) {
|
|
119
|
+
if (typeof fixture?.id === "string") {
|
|
120
|
+
fixtureIds.add(fixture.id);
|
|
121
|
+
}
|
|
122
|
+
for (const field of ["expectedRuleIds", "absentRuleIds"]) {
|
|
123
|
+
if (Array.isArray(fixture?.[field])) {
|
|
124
|
+
for (const ruleId of fixture[field]) {
|
|
125
|
+
if (typeof ruleId === "string") {
|
|
126
|
+
fixtureRuleIds.add(ruleId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
requireIncludes(frameworkGuide, guidePath, "framework-tuning-cookbook.json", "the cookbook JSON link");
|
|
134
|
+
requireIncludes(frameworkGuide, guidePath, "examples/tuning", "the tuning examples link");
|
|
135
|
+
requireIncludes(cookbookReadme, cookbookReadmePath, "framework-tuning-cookbook.json", "the cookbook JSON link");
|
|
136
|
+
requireIncludes(cookbookReadme, cookbookReadmePath, "npm run framework:tuning:check", "the checker command");
|
|
137
|
+
|
|
138
|
+
const ids = new Set();
|
|
139
|
+
const commandPattern = /^jester tune ([a-z0-9]+(?:-[a-z0-9]+)*) --json$/;
|
|
140
|
+
|
|
141
|
+
for (const [index, recipe] of (Array.isArray(cookbook) ? cookbook : []).entries()) {
|
|
142
|
+
const label = typeof recipe?.id === "string" ? recipe.id : `recipe[${index}]`;
|
|
143
|
+
|
|
144
|
+
if (!recipe || typeof recipe !== "object" || Array.isArray(recipe)) {
|
|
145
|
+
failures.push(`${label} should be an object.`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
checkString(recipe.id, `${label}.id`, { kebab: true });
|
|
150
|
+
checkString(recipe.stack, `${label}.stack`);
|
|
151
|
+
checkString(recipe.preset, `${label}.preset`);
|
|
152
|
+
checkString(recipe.when, `${label}.when`, { minLength: 40 });
|
|
153
|
+
|
|
154
|
+
if (typeof recipe.id === "string") {
|
|
155
|
+
if (ids.has(recipe.id)) {
|
|
156
|
+
failures.push(`${label}.id is duplicated.`);
|
|
157
|
+
}
|
|
158
|
+
ids.add(recipe.id);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!allowedPresets.has(recipe.preset)) {
|
|
162
|
+
failures.push(`${label}.preset should be one of: ${[...allowedPresets].join(", ")}.`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const commands = checkStringArray(recipe.commands, `${label}.commands`, {
|
|
166
|
+
pattern: commandPattern,
|
|
167
|
+
description: "`jester tune <rule-id> --json`"
|
|
168
|
+
});
|
|
169
|
+
const fixturesForRecipe = checkStringArray(recipe.fixtures, `${label}.fixtures`, {
|
|
170
|
+
pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
171
|
+
description: "a kebab-case fixture id"
|
|
172
|
+
});
|
|
173
|
+
checkStringArray(recipe.next, `${label}.next`);
|
|
174
|
+
|
|
175
|
+
for (const command of commands) {
|
|
176
|
+
const match = command.match(commandPattern);
|
|
177
|
+
const ruleId = match?.[1];
|
|
178
|
+
if (ruleId && !fixtureRuleIds.has(ruleId)) {
|
|
179
|
+
failures.push(`${label}.commands references ${ruleId}, but no fixture currently names that rule in expectedRuleIds or absentRuleIds.`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
requireIncludes(frameworkGuide, guidePath, command, `${label} command`);
|
|
183
|
+
requireIncludes(cookbookReadme, cookbookReadmePath, command, `${label} command`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const fixtureId of fixturesForRecipe) {
|
|
187
|
+
if (!fixtureIds.has(fixtureId)) {
|
|
188
|
+
failures.push(`${label}.fixtures references missing fixture ${fixtureId}.`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
requireIncludes(frameworkGuide, guidePath, fixtureId, `${label} fixture id`);
|
|
192
|
+
requireIncludes(cookbookReadme, cookbookReadmePath, fixtureId, `${label} fixture id`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (typeof recipe.id === "string") {
|
|
196
|
+
requireIncludes(frameworkGuide, guidePath, recipe.id, `${label} recipe id`);
|
|
197
|
+
requireIncludes(cookbookReadme, cookbookReadmePath, recipe.id, `${label} recipe id`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (typeof recipe.stack === "string") {
|
|
201
|
+
requireIncludes(frameworkGuide, guidePath, recipe.stack, `${label} stack`);
|
|
202
|
+
requireIncludes(cookbookReadme, cookbookReadmePath, recipe.stack, `${label} stack`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (failures.length > 0) {
|
|
207
|
+
console.error("Framework tuning check failed:");
|
|
208
|
+
for (const failure of failures) {
|
|
209
|
+
console.error(`- ${failure}`);
|
|
210
|
+
}
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(`Framework tuning check passed for ${Array.isArray(cookbook) ? cookbook.length : 0} recipes.`);
|
|
@@ -68,6 +68,8 @@ for (const path of [
|
|
|
68
68
|
"scripts/check-promo-freshness.mjs",
|
|
69
69
|
"scripts/render-social-card.mjs",
|
|
70
70
|
"scripts/check-site.mjs",
|
|
71
|
+
"scripts/check-framework-tuning.mjs",
|
|
72
|
+
"scripts/doctor-framework-tuning.mjs",
|
|
71
73
|
"scripts/check-fixtures.mjs",
|
|
72
74
|
"scripts/report-fixtures.mjs",
|
|
73
75
|
".github/ISSUE_TEMPLATE/bug_report.yml",
|
|
@@ -81,6 +83,8 @@ for (const path of [
|
|
|
81
83
|
"examples/github-code-scanning.yml",
|
|
82
84
|
"examples/ci/README.md",
|
|
83
85
|
"examples/presets/README.md",
|
|
86
|
+
"examples/tuning/README.md",
|
|
87
|
+
"examples/tuning/framework-tuning-cookbook.json",
|
|
84
88
|
"examples/fixtures/preset-review-cases.json",
|
|
85
89
|
"site/index.html"
|
|
86
90
|
]) {
|
|
@@ -101,6 +105,7 @@ requireText("README.md", /fixtures:check/, "fixture authoring check guidance");
|
|
|
101
105
|
requireText("README.md", /fixtures:report/, "fixture coverage report guidance");
|
|
102
106
|
requireText("README.md", /fixtures:report -- --markdown/, "Markdown fixture report guidance");
|
|
103
107
|
requireText("README.md", /FRAMEWORK_TUNING\.md/, "framework tuning guide link");
|
|
108
|
+
requireText("README.md", /examples\/tuning/, "framework tuning cookbook link");
|
|
104
109
|
requireText("README.md", /License: PolyForm Noncommercial/, "the noncommercial license badge");
|
|
105
110
|
requireText("docs/PRODUCTION_READINESS.md", /npm package/i, "npm package readiness");
|
|
106
111
|
requireText("docs/PRODUCTION_READINESS.md", /GitHub Action/i, "GitHub Action readiness");
|
|
@@ -114,14 +119,20 @@ requireText("docs/PRODUCTION_READINESS.md", /MAINTAINER_TRIAGE\.md/, "maintainer
|
|
|
114
119
|
requireText("docs/PRODUCTION_READINESS.md", /fixtures:check/, "fixture authoring check readiness");
|
|
115
120
|
requireText("docs/PRODUCTION_READINESS.md", /fixtures:report/, "fixture coverage report readiness");
|
|
116
121
|
requireText("docs/PRODUCTION_READINESS.md", /fixtures:report -- --markdown/, "Markdown fixture report readiness");
|
|
122
|
+
requireText("docs/PRODUCTION_READINESS.md", /framework:tuning:check/, "framework tuning cookbook readiness");
|
|
123
|
+
requireText("docs/PRODUCTION_READINESS.md", /framework:tuning:doctor/, "framework tuning cookbook doctor readiness");
|
|
117
124
|
requireText("docs/PRODUCTION_READINESS.md", /quiet-pass/, "quiet-pass fixture readiness");
|
|
118
125
|
requireText("docs/CLI.md", /jester doctor --json/, "doctor JSON CLI docs");
|
|
119
126
|
requireText("docs/CLI.md", /quiet-pass fixture/, "quiet-pass fixture CLI docs");
|
|
120
127
|
requireText("docs/CLI.md", /FRAMEWORK_TUNING\.md/, "framework tuning CLI link");
|
|
128
|
+
requireText("docs/CLI.md", /examples\/tuning/, "framework tuning cookbook CLI link");
|
|
129
|
+
requireText("docs/CLI.md", /framework:tuning:doctor/, "framework tuning doctor CLI docs");
|
|
121
130
|
requireText("docs/FRAMEWORK_TUNING.md", /Next\.js/, "Next.js framework tuning example");
|
|
122
131
|
requireText("docs/FRAMEWORK_TUNING.md", /FastAPI/, "FastAPI framework tuning example");
|
|
123
132
|
requireText("docs/FRAMEWORK_TUNING.md", /Terraform/, "Terraform framework tuning example");
|
|
124
133
|
requireText("docs/FRAMEWORK_TUNING.md", /jester tune <rule-id> --json/, "framework tuning command guidance");
|
|
134
|
+
requireText("docs/FRAMEWORK_TUNING.md", /framework-tuning-cookbook\.json/, "framework tuning cookbook JSON link");
|
|
135
|
+
requireText("docs/FRAMEWORK_TUNING.md", /framework:tuning:doctor/, "framework tuning doctor guidance");
|
|
125
136
|
requireText("docs/MAINTAINER_TRIAGE.md", /doctor --json/, "doctor JSON triage prompt");
|
|
126
137
|
requireText("docs/MAINTAINER_TRIAGE.md", /tune <rule-id> --json/, "tune JSON triage prompt");
|
|
127
138
|
requireText("docs/MAINTAINER_TRIAGE.md", /preset-review-cases\.json/, "fixture suite link");
|
|
@@ -132,6 +143,11 @@ requireText("examples/fixtures/README.md", /Adding A Fixture From A Report/, "fi
|
|
|
132
143
|
requireText("examples/fixtures/README.md", /fixtures:check/, "fixture authoring check guidance");
|
|
133
144
|
requireText("examples/fixtures/README.md", /fixtures:report/, "fixture coverage report guidance");
|
|
134
145
|
requireText("examples/fixtures/README.md", /fixtures:report -- --markdown/, "Markdown fixture report guidance");
|
|
146
|
+
requireText("examples/tuning/README.md", /framework-tuning-cookbook\.json/, "framework tuning cookbook JSON link");
|
|
147
|
+
requireText("examples/tuning/README.md", /framework:tuning:doctor/, "framework tuning doctor guidance");
|
|
148
|
+
requireText("examples/tuning/README.md", /jester tune <rule-id> --json|jester tune [a-z0-9-]+ --json/, "framework tuning command guidance");
|
|
149
|
+
requireText("examples/tuning/framework-tuning-cookbook.json", /next-vite-public-config/, "Next/Vite tuning recipe");
|
|
150
|
+
requireText("examples/tuning/framework-tuning-cookbook.json", /ai-mcp-tooling/, "AI/MCP tuning recipe");
|
|
135
151
|
requireText("scripts/check-fixtures.mjs", /duplicated/, "duplicate fixture id check");
|
|
136
152
|
requireText("scripts/check-fixtures.mjs", /unsafeContentPatterns/, "unsafe fixture content checks");
|
|
137
153
|
forbidText("scripts/check-fixtures.mjs", /src\/config\.ts|src\/types\.ts/, "source-only fixture validator dependencies");
|
|
@@ -141,14 +157,25 @@ requireText("scripts/report-fixtures.mjs", /quietPassRuleCoverage/, "quiet-pass
|
|
|
141
157
|
requireText("scripts/report-fixtures.mjs", /presetKindGaps/, "preset and kind gap report");
|
|
142
158
|
requireText("scripts/report-fixtures.mjs", /--markdown/, "Markdown fixture report output");
|
|
143
159
|
forbidText("scripts/report-fixtures.mjs", /src\/config\.ts|src\/types\.ts/, "source-only fixture report dependencies");
|
|
160
|
+
requireText("scripts/check-framework-tuning.mjs", /framework-tuning-cookbook\.json/, "framework tuning cookbook check");
|
|
161
|
+
requireText("scripts/check-framework-tuning.mjs", /preset-review-cases\.json/, "framework tuning fixture alignment");
|
|
162
|
+
requireText("scripts/check-framework-tuning.mjs", /unsafeContentPatterns/, "unsafe tuning content checks");
|
|
163
|
+
requireText("scripts/doctor-framework-tuning.mjs", /dist.*cli\.js|cliPath/, "built CLI doctor path");
|
|
164
|
+
requireText("scripts/doctor-framework-tuning.mjs", /config.*init|config", "init"/, "generated preset config doctor");
|
|
165
|
+
requireText("scripts/doctor-framework-tuning.mjs", /tune.*--json|tune", ruleId, "--json"/, "tune JSON doctor command");
|
|
166
|
+
forbidText("scripts/doctor-framework-tuning.mjs", /src\/config\.ts|src\/types\.ts/, "source-only framework tuning doctor dependencies");
|
|
144
167
|
requireText("package.json", /"fixtures:check": "node scripts\/check-fixtures\.mjs"/, "fixture authoring check script");
|
|
145
168
|
requireText("package.json", /"fixtures:report": "node scripts\/report-fixtures\.mjs"/, "fixture coverage report script");
|
|
169
|
+
requireText("package.json", /"framework:tuning:check": "node scripts\/check-framework-tuning\.mjs"/, "framework tuning cookbook check script");
|
|
170
|
+
requireText("package.json", /"framework:tuning:doctor": "node scripts\/doctor-framework-tuning\.mjs"/, "framework tuning cookbook doctor script");
|
|
146
171
|
requireText("package.json", /"promo:card": "node scripts\/render-social-card\.mjs"/, "social card render script");
|
|
147
172
|
requireText("package.json", /"promo:card:check": "node scripts\/render-social-card\.mjs --check"/, "social card stale check script");
|
|
148
173
|
requireText("package.json", /"promo:check": "node scripts\/check-promo-freshness\.mjs"/, "promo freshness check script");
|
|
149
174
|
requireText("package.json", /"site:check": "node scripts\/check-site\.mjs"/, "site check script");
|
|
150
175
|
requireText("package.json", /npm run fixtures:check/, "fixture authoring check in npm test");
|
|
151
176
|
requireText("package.json", /npm run fixtures:report/, "fixture coverage report in npm test");
|
|
177
|
+
requireText("package.json", /npm run framework:tuning:check/, "framework tuning cookbook check in npm test");
|
|
178
|
+
requireText("package.json", /npm run framework:tuning:doctor/, "framework tuning cookbook doctor in npm test");
|
|
152
179
|
requireText("package.json", /npm run promo:check/, "promo freshness check in npm test");
|
|
153
180
|
requireText("package.json", /npm run site:check/, "site check in npm test");
|
|
154
181
|
requireText("scripts/check-promo-freshness.mjs", /--require-package-version/, "optional strict package-version promo check");
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root = join(scriptDir, "..");
|
|
10
|
+
const cliPath = join(root, "dist", "cli.js");
|
|
11
|
+
const cookbookPath = join(root, "examples", "tuning", "framework-tuning-cookbook.json");
|
|
12
|
+
const fixturesPath = join(root, "examples", "fixtures", "preset-review-cases.json");
|
|
13
|
+
const failures = [];
|
|
14
|
+
const commandPattern = /^jester tune ([a-z0-9]+(?:-[a-z0-9]+)*) --json$/;
|
|
15
|
+
const presets = ["node", "python", "web", "api", "infra", "ai", "security"];
|
|
16
|
+
|
|
17
|
+
function readJson(path) {
|
|
18
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runCli(args, options = {}) {
|
|
22
|
+
const result = spawnSync(process.execPath, [cliPath, ...args], {
|
|
23
|
+
cwd: root,
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
maxBuffer: 10 * 1024 * 1024
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (result.error) {
|
|
29
|
+
throw result.error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (result.status !== 0) {
|
|
33
|
+
const detail = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
34
|
+
throw new Error(`jester ${args.join(" ")} failed${detail ? `:\n${detail}` : "."}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!options.json) {
|
|
38
|
+
return result.stdout;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(result.stdout);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(`jester ${args.join(" ")} did not return JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!existsSync(cliPath)) {
|
|
49
|
+
console.error("Framework tuning doctor failed:");
|
|
50
|
+
console.error(`- ${cliPath} is missing. Run npm run build first, or run this from an installed package.`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let cookbook;
|
|
55
|
+
let fixtures;
|
|
56
|
+
try {
|
|
57
|
+
cookbook = readJson(cookbookPath);
|
|
58
|
+
fixtures = readJson(fixturesPath);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`Could not parse framework tuning doctor inputs: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!Array.isArray(cookbook)) {
|
|
65
|
+
console.error(`Framework tuning doctor failed: ${cookbookPath} should contain a JSON array.`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!Array.isArray(fixtures)) {
|
|
70
|
+
console.error(`Framework tuning doctor failed: ${fixturesPath} should contain a JSON array.`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fixtureById = new Map(fixtures.map((fixture) => [fixture.id, fixture]));
|
|
75
|
+
const tmpRoot = mkdtempSync(join(tmpdir(), "jester-framework-tuning-"));
|
|
76
|
+
const configByPreset = new Map();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
for (const preset of presets) {
|
|
80
|
+
const configPath = join(tmpRoot, `${preset}.config.json`);
|
|
81
|
+
runCli(["config", "init", "--preset", preset, "--path", configPath, "--force"]);
|
|
82
|
+
runCli(["config", "validate", "--config", configPath]);
|
|
83
|
+
configByPreset.set(preset, configPath);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const noConfigCatalog = readRuleCatalog(["rules", "--json", "--no-config"]);
|
|
87
|
+
const presetCatalogs = new Map();
|
|
88
|
+
for (const [preset, configPath] of configByPreset.entries()) {
|
|
89
|
+
presetCatalogs.set(preset, readRuleCatalog(["rules", "--json", "--config", configPath]));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const recipeSummaries = [];
|
|
93
|
+
let commandCount = 0;
|
|
94
|
+
let fixtureReferenceCount = 0;
|
|
95
|
+
|
|
96
|
+
for (const recipe of cookbook) {
|
|
97
|
+
const label = typeof recipe?.id === "string" ? recipe.id : "unknown-recipe";
|
|
98
|
+
const commands = Array.isArray(recipe?.commands) ? recipe.commands : [];
|
|
99
|
+
const fixtureIds = Array.isArray(recipe?.fixtures) ? recipe.fixtures : [];
|
|
100
|
+
const recipeRuleIds = [];
|
|
101
|
+
|
|
102
|
+
for (const command of commands) {
|
|
103
|
+
const match = typeof command === "string" ? command.match(commandPattern) : null;
|
|
104
|
+
if (!match) {
|
|
105
|
+
failures.push(`${label} command should match "jester tune <rule-id> --json". Saw ${String(command)}.`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
recipeRuleIds.push(match[1]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const fixtureId of fixtureIds) {
|
|
113
|
+
const fixture = fixtureById.get(fixtureId);
|
|
114
|
+
if (!fixture) {
|
|
115
|
+
failures.push(`${label} references missing fixture ${fixtureId}.`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const refs = new Set([
|
|
120
|
+
...(Array.isArray(fixture.expectedRuleIds) ? fixture.expectedRuleIds : []),
|
|
121
|
+
...(Array.isArray(fixture.absentRuleIds) ? fixture.absentRuleIds : [])
|
|
122
|
+
]);
|
|
123
|
+
fixtureReferenceCount += refs.size;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const ruleId of recipeRuleIds) {
|
|
127
|
+
const relatedFixtures = fixtureIds.filter((fixtureId) => {
|
|
128
|
+
const fixture = fixtureById.get(fixtureId);
|
|
129
|
+
const refs = new Set([
|
|
130
|
+
...(Array.isArray(fixture?.expectedRuleIds) ? fixture.expectedRuleIds : []),
|
|
131
|
+
...(Array.isArray(fixture?.absentRuleIds) ? fixture.absentRuleIds : [])
|
|
132
|
+
]);
|
|
133
|
+
return refs.has(ruleId);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (relatedFixtures.length === 0) {
|
|
137
|
+
failures.push(`${label} command for ${ruleId} should have at least one referenced fixture in expectedRuleIds or absentRuleIds.`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const runContext = chooseRunContext(ruleId, recipe.preset, noConfigCatalog, presetCatalogs, configByPreset);
|
|
141
|
+
if (!runContext) {
|
|
142
|
+
failures.push(`${label} command for ${ruleId} did not resolve in built-in rules or generated preset configs.`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const args = ["tune", ruleId, "--json"];
|
|
147
|
+
if (runContext.configPath) {
|
|
148
|
+
args.push("--config", runContext.configPath);
|
|
149
|
+
} else {
|
|
150
|
+
args.push("--no-config");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let advice;
|
|
154
|
+
try {
|
|
155
|
+
advice = runCli(args, { json: true });
|
|
156
|
+
} catch (error) {
|
|
157
|
+
failures.push(`${label} command for ${ruleId} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
commandCount += 1;
|
|
162
|
+
validateTuneAdvice(label, ruleId, runContext.name, advice);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
recipeSummaries.push({
|
|
166
|
+
id: label,
|
|
167
|
+
commandCount: recipeRuleIds.length,
|
|
168
|
+
fixtureCount: fixtureIds.length
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (failures.length > 0) {
|
|
173
|
+
console.error("Framework tuning doctor failed:");
|
|
174
|
+
for (const failure of failures) {
|
|
175
|
+
console.error(`- ${failure}`);
|
|
176
|
+
}
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log("Framework tuning doctor");
|
|
181
|
+
console.log("");
|
|
182
|
+
for (const recipe of recipeSummaries) {
|
|
183
|
+
console.log(`PASS ${recipe.id}: ${recipe.commandCount} tune command(s), ${recipe.fixtureCount} fixture reference(s)`);
|
|
184
|
+
}
|
|
185
|
+
console.log("");
|
|
186
|
+
console.log(`Checked ${recipeSummaries.length} recipe(s), ${commandCount} executable tune command(s), and ${fixtureReferenceCount} fixture rule reference(s).`);
|
|
187
|
+
console.log("Generated temporary preset configs and removed them after validation.");
|
|
188
|
+
} finally {
|
|
189
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readRuleCatalog(args) {
|
|
193
|
+
const output = runCli(args, { json: true });
|
|
194
|
+
if (!Array.isArray(output.rules)) {
|
|
195
|
+
throw new Error(`jester ${args.join(" ")} did not return a rules array.`);
|
|
196
|
+
}
|
|
197
|
+
return new Set(output.rules.map((rule) => rule.id).filter((id) => typeof id === "string"));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function chooseRunContext(ruleId, preferredPreset, noConfigCatalog, presetCatalogs, configByPreset) {
|
|
201
|
+
if (typeof preferredPreset === "string" && presetCatalogs.get(preferredPreset)?.has(ruleId)) {
|
|
202
|
+
return {
|
|
203
|
+
name: preferredPreset,
|
|
204
|
+
configPath: configByPreset.get(preferredPreset)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const [preset, catalog] of presetCatalogs.entries()) {
|
|
209
|
+
if (catalog.has(ruleId)) {
|
|
210
|
+
return {
|
|
211
|
+
name: preset,
|
|
212
|
+
configPath: configByPreset.get(preset)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (noConfigCatalog.has(ruleId)) {
|
|
218
|
+
return {
|
|
219
|
+
name: "built-in",
|
|
220
|
+
configPath: null
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function validateTuneAdvice(recipeId, ruleId, contextName, advice) {
|
|
228
|
+
if (advice?.ruleId !== ruleId) {
|
|
229
|
+
failures.push(`${recipeId} expected tune JSON ruleId ${ruleId}; saw ${String(advice?.ruleId)}.`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (typeof advice?.title !== "string" || advice.title.length === 0) {
|
|
233
|
+
failures.push(`${recipeId} tune ${ruleId} should include a title.`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!Number.isInteger(advice?.severity)) {
|
|
237
|
+
failures.push(`${recipeId} tune ${ruleId} should include numeric severity.`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!Array.isArray(advice?.kinds) || advice.kinds.length === 0) {
|
|
241
|
+
failures.push(`${recipeId} tune ${ruleId} should include review kinds.`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (typeof advice?.recommendation !== "string" || advice.recommendation.length === 0) {
|
|
245
|
+
failures.push(`${recipeId} tune ${ruleId} should include a recommendation.`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!Array.isArray(advice?.checksBeforeMuting) || advice.checksBeforeMuting.length === 0) {
|
|
249
|
+
failures.push(`${recipeId} tune ${ruleId} should include checksBeforeMuting.`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (advice?.fixtureEvidence?.ruleId !== ruleId) {
|
|
253
|
+
failures.push(`${recipeId} tune ${ruleId} should include fixtureEvidence for the same rule.`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!advice?.commands?.inspect || !advice?.commands?.validate || !advice?.commands?.list) {
|
|
257
|
+
failures.push(`${recipeId} tune ${ruleId} should include inspect, validate, and list commands.`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (contextName !== "built-in" && advice?.configPath === null) {
|
|
261
|
+
failures.push(`${recipeId} tune ${ruleId} should report a generated config path when using the ${contextName} preset.`);
|
|
262
|
+
}
|
|
263
|
+
}
|