@event4u/agent-config 9.7.0 → 9.8.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 +144 -0
- package/README.md +5 -5
- package/dist/agent-src/commands/commit/in-chunks.md +8 -0
- package/dist/agent-src/commands/commit.md +17 -0
- package/dist/agent-src/commands/pr/create.md +4 -0
- package/dist/agent-src/contexts/override-system.md +75 -5
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +97 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +13 -3
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
- package/dist/agent-src/rules/git-history-discipline.md +2 -0
- package/dist/agent-src/rules/language-and-tone.md +2 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
- package/dist/agent-src/rules/media-governance-routing.md +2 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/no-roadmap-references.md +3 -0
- package/dist/agent-src/rules/non-destructive-by-default.md +20 -0
- package/dist/agent-src/rules/onboarding-gate.md +2 -0
- package/dist/agent-src/rules/output-discipline.md +2 -0
- package/dist/agent-src/rules/persona-governance.md +2 -0
- package/dist/agent-src/rules/preservation-guard.md +3 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +106 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +2 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/telegraph-speak.md +2 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/verify-before-complete.md +2 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +17 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +94 -0
- package/dist/agent-src/skills/gated-reach/SKILL.md +185 -0
- package/dist/agent-src/skills/gated-reach/evals/triggers.json +72 -0
- package/dist/agent-src/skills/image-analyser/SKILL.md +14 -12
- package/dist/agent-src/skills/image-editing/SKILL.md +1 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +6 -1
- package/dist/agent-src/skills/screenshot-hygiene/SKILL.md +171 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +44 -1
- package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-competitively.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-parallel.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-worktrees.md +29 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/judge-with-debate.md +29 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +4 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/github-workflows/secret-scan.yml +65 -0
- package/dist/cli/configRoot.js +112 -0
- package/dist/cli/configRoot.js.map +1 -0
- package/dist/cli/configRoot.test.js +97 -0
- package/dist/cli/configRoot.test.js.map +1 -0
- package/dist/cli/help.js +4 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +19 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +161 -44
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +13 -8
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +11 -6
- package/dist/install/install.mjs +16 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/server/app.js +10 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/routes/ping.js +18 -0
- package/dist/server/routes/ping.js.map +1 -1
- package/dist/server/schemas/settings.js +8 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/server/serverInfo.js +6 -2
- package/dist/server/serverInfo.js.map +1 -1
- package/dist/server/token.js +11 -4
- package/dist/server/token.js.map +1 -1
- package/dist/server/writeRoot.js +15 -1
- package/dist/server/writeRoot.js.map +1 -1
- package/dist/shared/capabilities.js +33 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/capabilities.test.js +39 -0
- package/dist/shared/capabilities.test.js.map +1 -0
- package/dist/ui/assets/{index-BiEZiZ31.js → index-CrmZ34Rg.js} +16 -16
- package/dist/ui/assets/index-CrmZ34Rg.js.map +1 -0
- package/dist/ui/index.html +14 -5
- package/docs/CLAIMS.md +62 -37
- package/docs/architecture.md +2 -2
- package/docs/benchmark.md +137 -0
- package/docs/catalog.md +8 -3
- package/docs/comparison.yaml +44 -2
- package/docs/contracts/local-server-ports.md +53 -0
- package/docs/contracts/no-runtime-boundary.md +1 -1
- package/docs/contracts/subagent-boundary.md +15 -0
- package/docs/customization.md +14 -3
- package/docs/decisions/ADR-016-installer-architecture.md +2 -0
- package/docs/decisions/ADR-088-no-external-runtime-federation.md +1 -1
- package/docs/decisions/ADR-094-agent-memory-layer-removal.md +1 -1
- package/docs/decisions/ADR-109-subagent-v1-contract.md +2 -0
- package/docs/decisions/ADR-110-discipline-profile-resolution-locus.md +2 -0
- package/docs/decisions/ADR-113-ci-native-release-label-trigger.md +11 -3
- package/docs/decisions/ADR-124-embedded-engine-doctrine.md +255 -0
- package/docs/decisions/ADR-125-doc-screenshot-anonymization.md +131 -0
- package/docs/decisions/ADR-126-internet-reach-operator-tooling.md +254 -0
- package/docs/decisions/ADR-127-enforcement-claims-must-resolve.md +150 -0
- package/docs/decisions/ADR-128-executable-evidence-and-measured-escalation.md +201 -0
- package/docs/decisions/INDEX.md +21 -0
- package/docs/decisions/engine-reclassification-2026-07.md +104 -0
- package/docs/distribution/awesome-list-submission.md +1 -1
- package/docs/distribution/registries.md +36 -27
- package/docs/enforcement-by-host.md +7 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/guides/gated-platform-reads.md +221 -0
- package/docs/positioning-evidence.md +1 -1
- package/docs/proof.md +92 -16
- package/docs/threat-model.md +1 -0
- package/package.json +5 -2
- package/src/config/agent-settings.template.yml +43 -0
- package/src/config/reach-channels.yml +217 -0
- package/src/config/reach-prescriptions-intake.yml +200 -0
- package/src/scripts/_dispatch.bash +15 -0
- package/src/scripts/_lib/exec_evidence.ts +251 -0
- package/src/scripts/_lib/fixtures/secret-detector/negatives.txt +10 -0
- package/src/scripts/_lib/fixtures/secret-detector/positives.txt +11 -0
- package/src/scripts/_lib/kernel_rules.ts +46 -0
- package/src/scripts/_lib/reddit_thread_parse.ts +598 -0
- package/src/scripts/_lib/risk_paths.ts +160 -0
- package/src/scripts/_lib/secret_detector.test.ts +103 -0
- package/src/scripts/_lib/secret_detector.ts +319 -0
- package/src/scripts/_lib/tool_probe.ts +359 -0
- package/src/scripts/adr/regenerate_index.ts +81 -6
- package/src/scripts/audit_adr_coverage.ts +33 -0
- package/src/scripts/build_proof.ts +162 -0
- package/src/scripts/check_adr_frontmatter.ts +195 -0
- package/src/scripts/check_artefact_count_messaging.ts +4 -0
- package/src/scripts/check_backstop_debt.ts +200 -0
- package/src/scripts/check_claims.ts +188 -8
- package/src/scripts/check_comparison.ts +12 -0
- package/src/scripts/check_enforcement_coverage.ts +592 -0
- package/src/scripts/check_kernel_rule_bundle.ts +16 -12
- package/src/scripts/check_reach_channels.ts +407 -0
- package/src/scripts/check_reach_staleness.ts +364 -0
- package/src/scripts/check_secret_leak.test.ts +47 -0
- package/src/scripts/check_secret_leak.ts +290 -0
- package/src/scripts/code_graph/build.ts +0 -0
- package/src/scripts/code_graph/cli.ts +207 -0
- package/src/scripts/code_graph/detect.ts +134 -0
- package/src/scripts/code_graph/extract.ts +386 -0
- package/src/scripts/code_graph/loader.ts +123 -0
- package/src/scripts/code_graph/query.ts +176 -0
- package/src/scripts/code_graph/sanitize.ts +30 -0
- package/src/scripts/code_graph/types.ts +83 -0
- package/src/scripts/code_graph/validate.ts +63 -0
- package/src/scripts/external_sources_denylist.json +3 -1
- package/src/scripts/generate_subagent_floor.ts +194 -0
- package/src/scripts/hook_manifest.yaml +13 -3
- package/src/scripts/hooks/code_graph_nudge_hook.ts +184 -0
- package/src/scripts/iron_law_sha.ts +3 -12
- package/src/scripts/lint_override_kernel_guard.ts +219 -0
- package/src/scripts/measure_rule_budget.ts +3 -12
- package/src/scripts/postinstall_gui.ts +129 -0
- package/src/scripts/reach_doctor.ts +1651 -0
- package/src/scripts/release.ts +24 -3
- package/src/scripts/risk_escalation_shadow.ts +160 -0
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/reach-channels.schema.json +134 -0
- package/src/scripts/schemas/reach-doctor-payload.schema.json +220 -0
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/scripts/skill_linter.ts +55 -0
- package/src/scripts/validate_reach_prescriptions.ts +751 -0
- package/src/server/schemas/settings.ts +16 -0
- package/src/shared/capabilities.test.ts +43 -0
- package/src/shared/capabilities.ts +75 -0
- package/src/templates/minimal/agents-overrides-readme.md +20 -0
- package/dist/ui/assets/index-BiEZiZ31.js.map +0 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Reach prescription intake record — committed supply-chain evidence
|
|
2
|
+
# (road-to-internet-reach Phase 3, Step 2).
|
|
3
|
+
#
|
|
4
|
+
# WHAT THIS FILE IS. The offline substitute for a network lookup at lint time.
|
|
5
|
+
# `src/scripts/validate_reach_prescriptions.ts` (gate:
|
|
6
|
+
# `task check-reach-prescriptions`) reads THIS file to answer "does the package
|
|
7
|
+
# this prescription names exist, is its pin the one we checked, and was it
|
|
8
|
+
# CVE-reviewed" — it never fetches a registry. That is a hard architectural
|
|
9
|
+
# constraint, not a convenience: the Class A boundary
|
|
10
|
+
# (docs/contracts/no-runtime-boundary.md, ADR-124) forbids network access in the
|
|
11
|
+
# build path, and a lint that phones a third party is a flaky lint.
|
|
12
|
+
#
|
|
13
|
+
# CONSEQUENCE. Every claim below is a HUMAN-RUN check frozen at `checked_on`.
|
|
14
|
+
# Freshness is a maintenance duty, not a guarantee this file can make about
|
|
15
|
+
# itself: re-run the recorded commands, update `pinned_version` +
|
|
16
|
+
# `verified_on`, and change the registry pin in the same commit.
|
|
17
|
+
#
|
|
18
|
+
# WHAT AN ENTRY IS FOR. `tools:` carries exactly one entry per EXTERNALLY
|
|
19
|
+
# INSTALLED third-party tool named by an install prescription in
|
|
20
|
+
# src/config/reach-channels.yml. A prescription whose backend has no entry
|
|
21
|
+
# here cannot ship — the validator fails closed on it.
|
|
22
|
+
#
|
|
23
|
+
# WHY `os_baseline:` IS A SEPARATE CLASS, NOT A THIN `tools:` ENTRY. `curl`,
|
|
24
|
+
# `node` and `jq` are not package installs this project prescribes: they ship
|
|
25
|
+
# with the OS or the developer's base image. Writing a fake intake entry for
|
|
26
|
+
# them — "existence verified: it is curl" — would launder an assumption into
|
|
27
|
+
# evidence and put a registry/CVE claim on this project's ledger that nobody
|
|
28
|
+
# actually checked per platform. They are declared as baselines instead, with
|
|
29
|
+
# the reason, and the validator exempts them from the `tools:` requirement
|
|
30
|
+
# while still enforcing the pinning rules on any string they do carry.
|
|
31
|
+
#
|
|
32
|
+
# EXECUTION HONESTY. `asdf` and `pipx` are ABSENT on the authoring machine
|
|
33
|
+
# (`which asdf` / `which pipx` → not found), so no install command below was
|
|
34
|
+
# ever executed here. That is the design: prescription-first, human-performed,
|
|
35
|
+
# no auto-install (roadmap `## Non-goals`). Existence was verified against the
|
|
36
|
+
# registries' own metadata APIs, which is what the pin depends on.
|
|
37
|
+
#
|
|
38
|
+
# ISO DATES MUST STAY QUOTED — unquoted, YAML parses them as timestamps and
|
|
39
|
+
# the validator's string check fails loudly.
|
|
40
|
+
schema_version: reach-prescriptions-intake-v1
|
|
41
|
+
|
|
42
|
+
# The date every `verification.command` below was last executed by a human.
|
|
43
|
+
checked_on: "2026-07-24"
|
|
44
|
+
|
|
45
|
+
tools:
|
|
46
|
+
# ── gh ────────────────────────────────────────────────────────────────────
|
|
47
|
+
# Backend of the `github` channel. Two package identities because no
|
|
48
|
+
# single registry offers an exactly-pinnable `gh` on every platform:
|
|
49
|
+
# Homebrew ships only an unversioned `gh` formula (a pin there would be a
|
|
50
|
+
# lie), so macOS/Linux go through asdf, and Windows through winget.
|
|
51
|
+
- backend: gh
|
|
52
|
+
tool: gh
|
|
53
|
+
pinned_version: "2.96.0"
|
|
54
|
+
packages:
|
|
55
|
+
- registry: asdf
|
|
56
|
+
package: github-cli
|
|
57
|
+
- registry: winget
|
|
58
|
+
package: GitHub.cli
|
|
59
|
+
intake:
|
|
60
|
+
existence_verified: >-
|
|
61
|
+
Three independent confirmations at the pin. (1) The binary itself
|
|
62
|
+
reports 2.96.0 on the authoring machine. (2) The asdf plugin index
|
|
63
|
+
contains plugins/github-cli (blob sha
|
|
64
|
+
b8ab100243bc01e361a61c50d11d4ab66b790c92). (3) The winget-pkgs manifest
|
|
65
|
+
tree lists exactly 2.96.0 under manifests/g/GitHub/cli. Not a
|
|
66
|
+
model-produced name: every identifier came out of a registry response.
|
|
67
|
+
pinned: >-
|
|
68
|
+
Exact, both platforms — `asdf install github-cli 2.96.0` and
|
|
69
|
+
`winget install --id GitHub.cli --version 2.96.0`. No range, no
|
|
70
|
+
`latest`, no branch, no archive URL. Homebrew was rejected as the
|
|
71
|
+
macOS/Linux path precisely because its unversioned `gh` formula admits
|
|
72
|
+
no exact pin at all — a pinned-looking Homebrew line would be fiction.
|
|
73
|
+
lockfile_note: >-
|
|
74
|
+
No project lockfile applies — gh is an external CLI a human installs,
|
|
75
|
+
never a dependency of this package (absent from package.json, and this
|
|
76
|
+
repo ships no .tool-versions). The lock equivalent is the exact pin in
|
|
77
|
+
this record plus asdf's own .tool-versions in the operator's
|
|
78
|
+
environment, if they use one. Nothing in this repo can pin it for them.
|
|
79
|
+
cve_note: >-
|
|
80
|
+
GitHub Advisory Database, ecosystem `go`, package
|
|
81
|
+
github.com/cli/cli/v2: 6 advisories, newest first_patched_version
|
|
82
|
+
2.93.0 (GHSA-8xvp-7hj6-mcj9, TUF-mirror auth header). 2.93.0 <= 2.96.0,
|
|
83
|
+
so no listed advisory affects the pin. Reviewed by advisory listing, not
|
|
84
|
+
by a scanner — osv-scanner and pip-audit are not installed here, and
|
|
85
|
+
neither covers a system CLI install anyway.
|
|
86
|
+
verified_on: "2026-07-24"
|
|
87
|
+
verification:
|
|
88
|
+
- command: gh --version
|
|
89
|
+
source: local PATH on the authoring machine
|
|
90
|
+
observed: "gh version 2.96.0 (2026-07-02)"
|
|
91
|
+
- command: gh api repos/asdf-vm/asdf-plugins/contents/plugins/github-cli --jq '.sha + " " + .path'
|
|
92
|
+
source: https://api.github.com/repos/asdf-vm/asdf-plugins
|
|
93
|
+
observed: "b8ab100243bc01e361a61c50d11d4ab66b790c92 plugins/github-cli"
|
|
94
|
+
- command: gh api repos/microsoft/winget-pkgs/contents/manifests/g/GitHub/cli --jq '[.[].name] | map(select(startswith("2.96")))'
|
|
95
|
+
source: https://api.github.com/repos/microsoft/winget-pkgs
|
|
96
|
+
observed: '["2.96.0"]'
|
|
97
|
+
- command: gh api "/advisories?ecosystem=go&affects=github.com%2Fcli%2Fcli%2Fv2&per_page=100" --jq '[.[].vulnerabilities[] | select(.package.name=="github.com/cli/cli/v2") | .first_patched_version] | unique | sort'
|
|
98
|
+
source: https://api.github.com/advisories
|
|
99
|
+
observed: '["2.62.0","2.63.0","2.63.1","2.67.0","2.92.0","2.93.0"]'
|
|
100
|
+
|
|
101
|
+
# ── yt-dlp ────────────────────────────────────────────────────────────────
|
|
102
|
+
# Backend of the `youtube` channel, and NOT installed here — the roadmap
|
|
103
|
+
# blocker `yt-dlp-not-installed` is open by design. An intake record is
|
|
104
|
+
# about whether the prescription is safe to hand a human, which is a
|
|
105
|
+
# different question from whether the tool is present locally; the probe
|
|
106
|
+
# layer owns the second one.
|
|
107
|
+
- backend: yt-dlp
|
|
108
|
+
tool: yt-dlp
|
|
109
|
+
pinned_version: "2026.7.4"
|
|
110
|
+
packages:
|
|
111
|
+
- registry: PyPI
|
|
112
|
+
package: yt-dlp
|
|
113
|
+
intake:
|
|
114
|
+
existence_verified: >-
|
|
115
|
+
PyPI JSON API reports .info.version 2026.7.4 for the project, and the
|
|
116
|
+
per-release endpoint /pypi/yt-dlp/2026.7.4/json resolves with 2 upload
|
|
117
|
+
files — so the exact pinned release exists, not merely the project name.
|
|
118
|
+
Typo-adjacency checked deliberately: `yt-dlp` is the hyphenated form,
|
|
119
|
+
the shape most prone to hallucinated variants (`ytdlp`, `yt_dlp`,
|
|
120
|
+
`youtube-dl`); the identifier used here is the one PyPI returned.
|
|
121
|
+
pinned: >-
|
|
122
|
+
Exact PyPI equality pin — `pipx install yt-dlp==2026.7.4` (Windows:
|
|
123
|
+
`py -m pipx install yt-dlp==2026.7.4`). yt-dlp publishes date-shaped
|
|
124
|
+
versions, so a `latest`-style install would silently roll forward
|
|
125
|
+
every few days; the schema rejects that form outright.
|
|
126
|
+
lockfile_note: >-
|
|
127
|
+
No project lockfile applies — an isolated pipx application, not a
|
|
128
|
+
dependency of this package. pipx records the exact spec in its own
|
|
129
|
+
venv metadata; the pin above is the only lock this repo controls.
|
|
130
|
+
cve_note: >-
|
|
131
|
+
GitHub Advisory Database, ecosystem `pip`, package yt-dlp: 11
|
|
132
|
+
advisories, newest first_patched_version 2026.6.9 (GHSA-69qj-pvh9-c5wg
|
|
133
|
+
`--exec` command injection, plus three siblings patched in the same
|
|
134
|
+
release). 2026.6.9 < 2026.7.4, so no listed advisory affects the pin.
|
|
135
|
+
Worth carrying forward: the tool's own history is dominated by
|
|
136
|
+
argument-injection and filename-sanitization classes, which is one more
|
|
137
|
+
reason its output is untrusted content, never instructions
|
|
138
|
+
(src/rules/untrusted-input-defense.md).
|
|
139
|
+
verified_on: "2026-07-24"
|
|
140
|
+
verification:
|
|
141
|
+
- command: curl -fsSL https://pypi.org/pypi/yt-dlp/json | jq -r '.info.version'
|
|
142
|
+
source: https://pypi.org/pypi/yt-dlp/json
|
|
143
|
+
observed: "2026.7.4"
|
|
144
|
+
- command: curl -fsSL https://pypi.org/pypi/yt-dlp/2026.7.4/json | jq -r '.info.version + " files=" + (.urls|length|tostring)'
|
|
145
|
+
source: https://pypi.org/pypi/yt-dlp/2026.7.4/json
|
|
146
|
+
observed: "2026.7.4 files=2"
|
|
147
|
+
- command: gh api "/advisories?ecosystem=pip&affects=yt-dlp&per_page=100" --jq '[.[].vulnerabilities[] | select(.package.name=="yt-dlp") | .first_patched_version] | unique | sort'
|
|
148
|
+
source: https://api.github.com/advisories
|
|
149
|
+
observed: '["2023.09.24","2023.11.14","2023.7.06","2024.04.09","2024.07.01","2024.07.07","2026.02.21","2026.6.9"]'
|
|
150
|
+
|
|
151
|
+
# ── OS baselines — declared non-installs ──────────────────────────────────
|
|
152
|
+
# Present because the registry names them as backends, NOT because this
|
|
153
|
+
# project prescribes installing them. Each entry states why a pin is not
|
|
154
|
+
# meaningful; the validator exempts these from the `tools:` requirement and
|
|
155
|
+
# still enforces "pinned or explicit os-baseline sentinel" on every string
|
|
156
|
+
# they carry.
|
|
157
|
+
os_baseline:
|
|
158
|
+
- tool: curl
|
|
159
|
+
os_baseline: true
|
|
160
|
+
rationale: >-
|
|
161
|
+
Ships with macOS (/usr/bin/curl) and Windows 10+ (System32\curl.exe),
|
|
162
|
+
and is a distro package on Linux whose version tracks the distro. There
|
|
163
|
+
is no cross-platform version this project could pin without prescribing
|
|
164
|
+
a source it does not control — so the registry carries `os-baseline:`
|
|
165
|
+
sentinels and no version, deliberately.
|
|
166
|
+
verification:
|
|
167
|
+
- command: curl --version | head -1
|
|
168
|
+
source: local PATH on the authoring machine
|
|
169
|
+
observed: "curl 8.7.1 (x86_64-apple-darwin24.0) libcurl/8.7.1"
|
|
170
|
+
|
|
171
|
+
- tool: node
|
|
172
|
+
os_baseline: true
|
|
173
|
+
rationale: >-
|
|
174
|
+
A Node runtime is this package's own execution baseline
|
|
175
|
+
(package.json engines >=20.11.0), so any environment that can run the
|
|
176
|
+
validator already has it. The one platform where a pin IS meaningful —
|
|
177
|
+
Homebrew's versioned major-line formula — carries a real pin
|
|
178
|
+
(`brew install node@22`), verified below; the remaining platforms track
|
|
179
|
+
their own LTS channel and stay unpinned behind an explicit sentinel.
|
|
180
|
+
verification:
|
|
181
|
+
- command: curl -fsSL https://formulae.brew.sh/api/formula/node@22.json | jq -r '.versions.stable'
|
|
182
|
+
source: https://formulae.brew.sh/api/formula/node@22.json
|
|
183
|
+
observed: "22.23.1 (versioned formula node@22 exists)"
|
|
184
|
+
- command: node --version
|
|
185
|
+
source: local PATH on the authoring machine
|
|
186
|
+
observed: "v25.9.0"
|
|
187
|
+
|
|
188
|
+
- tool: jq
|
|
189
|
+
os_baseline: true
|
|
190
|
+
rationale: >-
|
|
191
|
+
Named nowhere in the registry as a backend on purpose — JSON shaping
|
|
192
|
+
happens inside the operator's own recipe, downstream of retrieval, so jq
|
|
193
|
+
never satisfies a channel. Recorded here so its absence from `tools:` is
|
|
194
|
+
a documented decision rather than an omission: if jq ever becomes a
|
|
195
|
+
backend it inherits curl's `os-baseline:` treatment, not a fabricated
|
|
196
|
+
pin.
|
|
197
|
+
verification:
|
|
198
|
+
- command: jq --version
|
|
199
|
+
source: local PATH on the authoring machine
|
|
200
|
+
observed: "jq-1.7.1-apple"
|
|
@@ -205,6 +205,15 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
205
205
|
Usage: hooks:replay --platform <name> --event <event>
|
|
206
206
|
--payload <path|event-name> [--native-event <native>]
|
|
207
207
|
[--manifest <path>] [--json] [--dry-run]
|
|
208
|
+
reach:doctor Health report over the reach channel registry: per channel
|
|
209
|
+
the active backend, lifecycle, and the pinned install command
|
|
210
|
+
for this platform when a backend is missing/broken.
|
|
211
|
+
Read-only — no writes, no installs; NOT a router and NOT an
|
|
212
|
+
agent-facing recommendation.
|
|
213
|
+
Flags: --format json|table, --strict (CI), --channel <id>,
|
|
214
|
+
--registry <path>, --deep (⚠️ opt-in NETWORK: one real
|
|
215
|
+
read-only request per declared backend; never runs in CI,
|
|
216
|
+
writes nothing)
|
|
208
217
|
memory:lookup Retrieve memory entries (text or JSON envelope)
|
|
209
218
|
linked-projects:list List opted-in IDE-attached sibling repos (path · detected_via · large)
|
|
210
219
|
Flags: --all (show undecided too), --format json
|
|
@@ -636,6 +645,11 @@ cmd_hooks_replay() {
|
|
|
636
645
|
exec_hook "src/scripts/hooks/replay_hook" "$@"
|
|
637
646
|
}
|
|
638
647
|
|
|
648
|
+
# Read-only reach-channel health report. Never installs, never writes.
|
|
649
|
+
cmd_reach_doctor() {
|
|
650
|
+
exec_hook "src/scripts/reach_doctor" "$@"
|
|
651
|
+
}
|
|
652
|
+
|
|
639
653
|
cmd_chat_history_checkpoint() {
|
|
640
654
|
local script
|
|
641
655
|
script="$(resolve_script "src/scripts/chat_history.ts")" || return 1
|
|
@@ -1085,6 +1099,7 @@ main() {
|
|
|
1085
1099
|
hooks:status) cmd_hooks_status "$@" ;;
|
|
1086
1100
|
hooks:doctor) cmd_hooks_doctor "$@" ;;
|
|
1087
1101
|
hooks:replay) cmd_hooks_replay "$@" ;;
|
|
1102
|
+
reach:doctor) cmd_reach_doctor "$@" ;;
|
|
1088
1103
|
telemetry:record) cmd_telemetry_record "$@" ;;
|
|
1089
1104
|
telemetry:status) cmd_telemetry_status "$@" ;;
|
|
1090
1105
|
telemetry:report) cmd_telemetry_report "$@" ;;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `exec:` evidence — the ledger form that re-derives its own claim.
|
|
3
|
+
*
|
|
4
|
+
* The other three evidence forms are existence checks: a file is present, a
|
|
5
|
+
* substring is present, a URL carries a date. None of them can tell a live
|
|
6
|
+
* claim from a stale one. A ledger entry reading "the suite is green" whose
|
|
7
|
+
* pointer resolves to a report nobody regenerated stays `backed` forever.
|
|
8
|
+
*
|
|
9
|
+
* `exec:<command> -> 0` closes that: the command runs again and its exit code
|
|
10
|
+
* carries the verdict. Three properties make it safe enough to run in CI, and
|
|
11
|
+
* all three are the parts that get dropped when this is rebuilt from memory:
|
|
12
|
+
*
|
|
13
|
+
* 1. The allowlist is a set of **argv prefix tuples**, never a regex over a
|
|
14
|
+
* command string. A regex over shell text is the classic bypass; a tuple
|
|
15
|
+
* compared against parsed argv has nothing to hide behind.
|
|
16
|
+
* 2. Every argument *after* the matched prefix is re-checked — for shell
|
|
17
|
+
* metacharacters and for repo escape — including the right-hand side of
|
|
18
|
+
* `--flag=value`. Without that split, `--rootdir=/etc` clears an allowlist
|
|
19
|
+
* that only ever looked at the flag name.
|
|
20
|
+
* 3. Execution is `spawnSync` with an argv array and no shell, confined to the
|
|
21
|
+
* repo root, and gated to CI. Locally the caller reports UNVERIFIED rather
|
|
22
|
+
* than running anything — see `exec_allowed_here()`.
|
|
23
|
+
*
|
|
24
|
+
* Scope limit, stated rather than discovered later: this form only covers
|
|
25
|
+
* claims whose exit code *is* the verdict. A claim resting on a paid model run,
|
|
26
|
+
* a stochastic benchmark, or a prose contract cannot use it, and is recorded as
|
|
27
|
+
* unfalsifiable-by-machine instead of quietly omitted.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
|
|
33
|
+
/** Marker that opens the fourth evidence form. */
|
|
34
|
+
export const EXEC_PREFIX = 'exec:';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Allowlisted argv prefixes. Each entry is matched element-by-element against
|
|
38
|
+
* the parsed argv — `["skill_eval_coverage"]` admits
|
|
39
|
+
* `skill_eval_coverage --check` but not `skill_eval_coverage; rm -rf /`,
|
|
40
|
+
* because the latter never parses into that shape.
|
|
41
|
+
*
|
|
42
|
+
* All but one are this repo's own scripts, run through `scripts-run`. That is a
|
|
43
|
+
* deliberately narrower surface than a general test-runner allowlist: these are
|
|
44
|
+
* deterministic, model-free, and already run in CI for other reasons.
|
|
45
|
+
*/
|
|
46
|
+
export const ALLOWLIST_PREFIXES: readonly (readonly string[])[] = [
|
|
47
|
+
['skill_eval_coverage'],
|
|
48
|
+
['domain_soundness_status'],
|
|
49
|
+
['check_artefact_count_messaging'],
|
|
50
|
+
['check_enforcement_coverage'],
|
|
51
|
+
['check_token_regression'],
|
|
52
|
+
['lint_agent_security'],
|
|
53
|
+
['measure_lexical_ranking'],
|
|
54
|
+
['vitest', 'run'],
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/** Shell metacharacters plus the control bytes used to smuggle a second command. */
|
|
58
|
+
const UNSAFE_CHARS = new Set(['&', ';', '|', '>', '<', '`', '$', '\n', '\r', '\0']);
|
|
59
|
+
|
|
60
|
+
const WINDOWS_ABSOLUTE = /^[A-Za-z]:[\\/]/;
|
|
61
|
+
|
|
62
|
+
/** Commands that are not repo scripts and must not be routed through scripts-run. */
|
|
63
|
+
const NON_SCRIPT_PREFIXES = new Set(['vitest']);
|
|
64
|
+
|
|
65
|
+
export interface ExecPointer {
|
|
66
|
+
/** The command as written in the ledger, e.g. `skill_eval_coverage --check`. */
|
|
67
|
+
command: string;
|
|
68
|
+
/** The exit code the claim asserts. */
|
|
69
|
+
expected: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ExecOutcome {
|
|
73
|
+
/** `true` only when the command ran AND matched the asserted exit code. */
|
|
74
|
+
verified: boolean;
|
|
75
|
+
/** `true` when the command ran and disagreed — a real finding, not a skip. */
|
|
76
|
+
mismatch: boolean;
|
|
77
|
+
/** Human-readable verdict for the report. */
|
|
78
|
+
reason: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse `exec:<command> -> <code>`. Accepts `->` and the `→` the docs render.
|
|
83
|
+
* Returns null when `raw` is not an exec pointer at all, so callers can fall
|
|
84
|
+
* through to the path/URL forms.
|
|
85
|
+
*/
|
|
86
|
+
export function parse_exec_pointer(raw: string): ExecPointer | { error: string } | null {
|
|
87
|
+
const ev = raw.trim();
|
|
88
|
+
if (!ev.startsWith(EXEC_PREFIX)) return null;
|
|
89
|
+
|
|
90
|
+
const body = ev.slice(EXEC_PREFIX.length).trim();
|
|
91
|
+
const arrow = body.match(/\s*(?:->|→)\s*(\d+)\s*$/);
|
|
92
|
+
if (!arrow) return { error: 'exec pointer missing the `-> <exit-code>` suffix' };
|
|
93
|
+
|
|
94
|
+
const command = body.slice(0, arrow.index).trim();
|
|
95
|
+
if (!command) return { error: 'exec pointer has an empty command' };
|
|
96
|
+
|
|
97
|
+
return { command, expected: Number(arrow[1]) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Split a command into argv without invoking a shell. Deliberately minimal: no
|
|
102
|
+
* quote handling, no expansion, no substitution. Anything that would *need*
|
|
103
|
+
* those is rejected by `args_are_safe` anyway, so supporting them would only
|
|
104
|
+
* widen the surface.
|
|
105
|
+
*/
|
|
106
|
+
export function split_argv(command: string): string[] {
|
|
107
|
+
return command.split(/\s+/).filter((t) => t.length > 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Does argv start with an allowlisted prefix? */
|
|
111
|
+
export function command_is_allowlisted(argv: string[]): boolean {
|
|
112
|
+
return ALLOWLIST_PREFIXES.some(
|
|
113
|
+
(prefix) => argv.length >= prefix.length && prefix.every((tok, i) => argv[i] === tok),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Would this argument reach outside the repo? */
|
|
118
|
+
function targets_outside_repo(candidate: string): boolean {
|
|
119
|
+
if (candidate.startsWith('/')) return true;
|
|
120
|
+
if (WINDOWS_ABSOLUTE.test(candidate)) return true;
|
|
121
|
+
if (candidate === '..' || candidate.startsWith('../')) return true;
|
|
122
|
+
return candidate.includes('/../');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Re-check every argument after the allowlisted prefix.
|
|
127
|
+
*
|
|
128
|
+
* The `--flag=value` split is the part worth keeping: an allowlist that only
|
|
129
|
+
* inspects the token as written treats `--rootdir=/etc` as a flag and lets the
|
|
130
|
+
* path through. Here the right-hand side is extracted and checked on its own.
|
|
131
|
+
*/
|
|
132
|
+
export function args_are_safe(argv: string[]): boolean {
|
|
133
|
+
for (const arg of argv.slice(1)) {
|
|
134
|
+
for (const ch of arg) {
|
|
135
|
+
if (UNSAFE_CHARS.has(ch)) return false;
|
|
136
|
+
}
|
|
137
|
+
const eq = arg.indexOf('=');
|
|
138
|
+
const candidate = arg.startsWith('-') && eq !== -1 ? arg.slice(eq + 1) : arg;
|
|
139
|
+
if (candidate && targets_outside_repo(candidate)) return false;
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Static validation — everything checkable without running anything.
|
|
146
|
+
*
|
|
147
|
+
* Split out from `run_exec_evidence` on purpose: a malformed or non-allowlisted
|
|
148
|
+
* command is a defect in the *ledger*, so it must fail on a laptop too. Only
|
|
149
|
+
* the re-execution half is environment-dependent. Returns null when the pointer
|
|
150
|
+
* is well-formed and admissible, else the reason.
|
|
151
|
+
*/
|
|
152
|
+
export function exec_static_error(pointer: ExecPointer): string | null {
|
|
153
|
+
const argv = split_argv(pointer.command);
|
|
154
|
+
if (argv.length === 0) return 'exec pointer has an empty command';
|
|
155
|
+
if (!command_is_allowlisted(argv)) {
|
|
156
|
+
return `exec command \`${pointer.command}\` is not in the deterministic allowlist`;
|
|
157
|
+
}
|
|
158
|
+
if (!args_are_safe(argv)) {
|
|
159
|
+
return `exec command \`${pointer.command}\` carries a suspicious argument`;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* May this environment execute evidence commands?
|
|
166
|
+
*
|
|
167
|
+
* CI only. A consumer's checkout never re-runs a claim's command — the upstream
|
|
168
|
+
* package's ledger is not the consumer's business, and "runs in the repository
|
|
169
|
+
* root with full filesystem access" is a property to confine, not to trust.
|
|
170
|
+
*/
|
|
171
|
+
export function exec_allowed_here(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
172
|
+
if (env.AGENT_CONFIG_EXEC_EVIDENCE === '1') return true; // maintainer opt-in, one run
|
|
173
|
+
return env.CI === 'true' || env.CI === '1';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Build the real argv: repo scripts go through `scripts-run`, others run directly. */
|
|
177
|
+
function resolve_runner(argv: string[], repo: string): { file: string; args: string[] } {
|
|
178
|
+
const head = argv[0] as string;
|
|
179
|
+
if (NON_SCRIPT_PREFIXES.has(head)) {
|
|
180
|
+
return { file: path.join(repo, 'node_modules', '.bin', head), args: argv.slice(1) };
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
file: path.join(repo, 'scripts-run'),
|
|
184
|
+
args: [`src/scripts/${head}`, ...argv.slice(1)],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Run an allowlisted evidence command and compare its exit code to the claim.
|
|
190
|
+
*
|
|
191
|
+
* Never throws: every failure path returns an `ExecOutcome` so one bad pointer
|
|
192
|
+
* cannot take down the whole ledger check. `mismatch` distinguishes "ran and
|
|
193
|
+
* disagreed" (a finding) from "could not run" (a skip) — collapsing those two
|
|
194
|
+
* is how a verifier turns into a rubber stamp.
|
|
195
|
+
*/
|
|
196
|
+
export function run_exec_evidence(pointer: ExecPointer, repo: string, timeoutMs = 300_000): ExecOutcome {
|
|
197
|
+
const argv = split_argv(pointer.command);
|
|
198
|
+
|
|
199
|
+
if (argv.length === 0) {
|
|
200
|
+
return { verified: false, mismatch: false, reason: 'UNVERIFIED: empty command' };
|
|
201
|
+
}
|
|
202
|
+
if (!command_is_allowlisted(argv)) {
|
|
203
|
+
return {
|
|
204
|
+
verified: false,
|
|
205
|
+
mismatch: false,
|
|
206
|
+
reason: `UNVERIFIED: \`${pointer.command}\` is not in the deterministic allowlist`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (!args_are_safe(argv)) {
|
|
210
|
+
return {
|
|
211
|
+
verified: false,
|
|
212
|
+
mismatch: false,
|
|
213
|
+
reason: `UNVERIFIED: \`${pointer.command}\` carries a suspicious argument`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const { file, args } = resolve_runner(argv, repo);
|
|
218
|
+
const result = spawnSync(file, args, {
|
|
219
|
+
cwd: repo,
|
|
220
|
+
encoding: 'utf-8',
|
|
221
|
+
timeout: timeoutMs,
|
|
222
|
+
shell: false,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (result.error) {
|
|
226
|
+
return {
|
|
227
|
+
verified: false,
|
|
228
|
+
mismatch: false,
|
|
229
|
+
reason: `UNVERIFIED: could not execute \`${pointer.command}\` (${result.error.message})`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (result.status === null) {
|
|
233
|
+
return {
|
|
234
|
+
verified: false,
|
|
235
|
+
mismatch: false,
|
|
236
|
+
reason: `UNVERIFIED: \`${pointer.command}\` was killed (timeout or signal)`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (result.status !== pointer.expected) {
|
|
240
|
+
return {
|
|
241
|
+
verified: false,
|
|
242
|
+
mismatch: true,
|
|
243
|
+
reason: `evidence mismatch — claim asserts exit ${pointer.expected}, \`${pointer.command}\` exited ${result.status}`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
verified: true,
|
|
248
|
+
mismatch: false,
|
|
249
|
+
reason: `verified \`${pointer.command}\` (exit ${result.status})`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# secret-detector negative fixtures — all benign, must not flag high/medium.
|
|
2
|
+
api_key = "changeme"
|
|
3
|
+
api_key = "xxxxxxxxxxxxxxxx"
|
|
4
|
+
password = "your-password-here"
|
|
5
|
+
build commit d41d8cd98f00b204e9800998ecf8427e5f1a2b3c
|
|
6
|
+
request id 550e8400-e29b-41d4-a716-446655440000
|
|
7
|
+
bundle integrity 3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b
|
|
8
|
+
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAElFTkSuQmCC
|
|
9
|
+
API_KEY=your-key-here
|
|
10
|
+
aws_key = "AKIAZ4Q7WXYT2H9RVBKL" # secret-allow
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# secret-detector positive fixtures — every value below is FAKE, do not use.
|
|
2
|
+
# Line format: <kind-label>|<fake-secret>
|
|
3
|
+
aws|AKIA1234567890ABCDEF
|
|
4
|
+
github|ghp_0123456789abcdefghijklmnopqrstuvwxyz
|
|
5
|
+
stripe|sk_live_0123456789abcdefABCDEF
|
|
6
|
+
google|AIza0123456789abcdefghijklmnopqrstuvwxy
|
|
7
|
+
slack|xoxb-0123456789-abcdefABCDEF
|
|
8
|
+
pem|-----BEGIN RSA PRIVATE KEY-----
|
|
9
|
+
jwt|eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
|
10
|
+
dburl|postgres://dbuser:s3cr3tpass@db.internal:5432/app
|
|
11
|
+
assignment|api_key = "s3cr3tV4lu3Abc123"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The locked kernel set — one definition, imported everywhere.
|
|
3
|
+
*
|
|
4
|
+
* These nine rules are the Iron-Law floor (`docs/contracts/kernel-membership.md`
|
|
5
|
+
* § 4). Before this module the list existed three times — in
|
|
6
|
+
* `check_kernel_rule_bundle.ts`, `iron_law_sha.ts`, and `measure_rule_budget.ts`
|
|
7
|
+
* — two of them carrying a "kept in sync with …" comment, which is the standard
|
|
8
|
+
* way a list drifts: the comment records an intention, nothing enforces it.
|
|
9
|
+
*
|
|
10
|
+
* Membership is a governance decision, not a convenience. Adding or removing an
|
|
11
|
+
* entry changes what may never be overridden, what the prefix-stability snapshot
|
|
12
|
+
* covers, and what the one-kernel-rule-per-PR gate applies to. Change it here,
|
|
13
|
+
* once, and let every consumer follow.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Kernel rule ids, stem form (no extension), in stable sorted order. */
|
|
17
|
+
export const KERNEL_RULE_IDS: readonly string[] = [
|
|
18
|
+
'agent-authority',
|
|
19
|
+
'ask-when-uncertain',
|
|
20
|
+
'commit-policy',
|
|
21
|
+
'direct-answers',
|
|
22
|
+
'language-and-tone',
|
|
23
|
+
'no-cheap-questions',
|
|
24
|
+
'non-destructive-by-default',
|
|
25
|
+
'scope-control',
|
|
26
|
+
'verify-before-complete',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
/** Same set, `.md` filename form — what path-matching consumers compare against. */
|
|
30
|
+
export const KERNEL_RULE_FILENAMES: ReadonlySet<string> = new Set(
|
|
31
|
+
KERNEL_RULE_IDS.map((id) => `${id}.md`),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/** Set form of the stems, for membership tests that key on the id. */
|
|
35
|
+
export const KERNEL_RULE_ID_SET: ReadonlySet<string> = new Set(KERNEL_RULE_IDS);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* True when `name` names a kernel rule, in either stem or `.md` form.
|
|
39
|
+
*
|
|
40
|
+
* Accepts a bare id, a filename, or a path ending in either — callers hold the
|
|
41
|
+
* name in all three shapes and should not each re-derive the normalisation.
|
|
42
|
+
*/
|
|
43
|
+
export function is_kernel_rule(name: string): boolean {
|
|
44
|
+
const base = name.replace(/\\/g, '/').split('/').pop() ?? name;
|
|
45
|
+
return KERNEL_RULE_ID_SET.has(base.replace(/\.md$/, ''));
|
|
46
|
+
}
|