@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.
Files changed (185) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +144 -0
  3. package/README.md +5 -5
  4. package/dist/agent-src/commands/commit/in-chunks.md +8 -0
  5. package/dist/agent-src/commands/commit.md +17 -0
  6. package/dist/agent-src/commands/pr/create.md +4 -0
  7. package/dist/agent-src/contexts/override-system.md +75 -5
  8. package/dist/agent-src/rules/context-hygiene.md +2 -0
  9. package/dist/agent-src/rules/doc-screenshot-hygiene.md +97 -0
  10. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  11. package/dist/agent-src/rules/external-code-graph-interop.md +13 -3
  12. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
  13. package/dist/agent-src/rules/git-history-discipline.md +2 -0
  14. package/dist/agent-src/rules/language-and-tone.md +2 -0
  15. package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
  16. package/dist/agent-src/rules/media-governance-routing.md +2 -0
  17. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  18. package/dist/agent-src/rules/no-roadmap-references.md +3 -0
  19. package/dist/agent-src/rules/non-destructive-by-default.md +20 -0
  20. package/dist/agent-src/rules/onboarding-gate.md +2 -0
  21. package/dist/agent-src/rules/output-discipline.md +2 -0
  22. package/dist/agent-src/rules/persona-governance.md +2 -0
  23. package/dist/agent-src/rules/preservation-guard.md +3 -0
  24. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  25. package/dist/agent-src/rules/secret-vcs-guard.md +106 -0
  26. package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
  27. package/dist/agent-src/rules/skill-quality.md +2 -0
  28. package/dist/agent-src/rules/source-confidentiality.md +2 -0
  29. package/dist/agent-src/rules/source-of-truth.md +2 -0
  30. package/dist/agent-src/rules/telegraph-speak.md +2 -0
  31. package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
  32. package/dist/agent-src/rules/tool-safety.md +1 -0
  33. package/dist/agent-src/rules/verify-before-complete.md +2 -0
  34. package/dist/agent-src/skills/adr-create/SKILL.md +17 -1
  35. package/dist/agent-src/skills/code-intelligence/SKILL.md +94 -0
  36. package/dist/agent-src/skills/gated-reach/SKILL.md +185 -0
  37. package/dist/agent-src/skills/gated-reach/evals/triggers.json +72 -0
  38. package/dist/agent-src/skills/image-analyser/SKILL.md +14 -12
  39. package/dist/agent-src/skills/image-editing/SKILL.md +1 -0
  40. package/dist/agent-src/skills/readme-writing/SKILL.md +6 -1
  41. package/dist/agent-src/skills/screenshot-hygiene/SKILL.md +171 -0
  42. package/dist/agent-src/skills/secrets-management/SKILL.md +44 -1
  43. package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +29 -0
  44. package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md +29 -0
  45. package/dist/agent-src/skills/subagent-orchestration/prompts/do-and-judge.md +29 -0
  46. package/dist/agent-src/skills/subagent-orchestration/prompts/do-competitively.md +29 -0
  47. package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-parallel.md +29 -0
  48. package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +29 -0
  49. package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-worktrees.md +29 -0
  50. package/dist/agent-src/skills/subagent-orchestration/prompts/judge-with-debate.md +29 -0
  51. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +4 -0
  52. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  53. package/dist/agent-src/templates/github-workflows/secret-scan.yml +65 -0
  54. package/dist/cli/configRoot.js +112 -0
  55. package/dist/cli/configRoot.js.map +1 -0
  56. package/dist/cli/configRoot.test.js +97 -0
  57. package/dist/cli/configRoot.test.js.map +1 -0
  58. package/dist/cli/help.js +4 -1
  59. package/dist/cli/help.js.map +1 -1
  60. package/dist/cli/main.js +19 -2
  61. package/dist/cli/main.js.map +1 -1
  62. package/dist/cli/registry.js +1 -0
  63. package/dist/cli/registry.js.map +1 -1
  64. package/dist/discovery/deprecation-report.md +1 -1
  65. package/dist/discovery/discovery-manifest.json +161 -44
  66. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  67. package/dist/discovery/discovery-manifest.summary.md +4 -4
  68. package/dist/discovery/orphan-report.md +1 -1
  69. package/dist/discovery/packs.json +13 -8
  70. package/dist/discovery/trust-report.md +11 -11
  71. package/dist/discovery/workspaces.json +11 -6
  72. package/dist/install/install.mjs +16 -0
  73. package/dist/mcp/registry-manifest.json +3 -3
  74. package/dist/router.json +1 -1
  75. package/dist/server/app.js +10 -0
  76. package/dist/server/app.js.map +1 -1
  77. package/dist/server/routes/ping.js +18 -0
  78. package/dist/server/routes/ping.js.map +1 -1
  79. package/dist/server/schemas/settings.js +8 -0
  80. package/dist/server/schemas/settings.js.map +1 -1
  81. package/dist/server/serverInfo.js +6 -2
  82. package/dist/server/serverInfo.js.map +1 -1
  83. package/dist/server/token.js +11 -4
  84. package/dist/server/token.js.map +1 -1
  85. package/dist/server/writeRoot.js +15 -1
  86. package/dist/server/writeRoot.js.map +1 -1
  87. package/dist/shared/capabilities.js +33 -0
  88. package/dist/shared/capabilities.js.map +1 -0
  89. package/dist/shared/capabilities.test.js +39 -0
  90. package/dist/shared/capabilities.test.js.map +1 -0
  91. package/dist/ui/assets/{index-BiEZiZ31.js → index-CrmZ34Rg.js} +16 -16
  92. package/dist/ui/assets/index-CrmZ34Rg.js.map +1 -0
  93. package/dist/ui/index.html +14 -5
  94. package/docs/CLAIMS.md +62 -37
  95. package/docs/architecture.md +2 -2
  96. package/docs/benchmark.md +137 -0
  97. package/docs/catalog.md +8 -3
  98. package/docs/comparison.yaml +44 -2
  99. package/docs/contracts/local-server-ports.md +53 -0
  100. package/docs/contracts/no-runtime-boundary.md +1 -1
  101. package/docs/contracts/subagent-boundary.md +15 -0
  102. package/docs/customization.md +14 -3
  103. package/docs/decisions/ADR-016-installer-architecture.md +2 -0
  104. package/docs/decisions/ADR-088-no-external-runtime-federation.md +1 -1
  105. package/docs/decisions/ADR-094-agent-memory-layer-removal.md +1 -1
  106. package/docs/decisions/ADR-109-subagent-v1-contract.md +2 -0
  107. package/docs/decisions/ADR-110-discipline-profile-resolution-locus.md +2 -0
  108. package/docs/decisions/ADR-113-ci-native-release-label-trigger.md +11 -3
  109. package/docs/decisions/ADR-124-embedded-engine-doctrine.md +255 -0
  110. package/docs/decisions/ADR-125-doc-screenshot-anonymization.md +131 -0
  111. package/docs/decisions/ADR-126-internet-reach-operator-tooling.md +254 -0
  112. package/docs/decisions/ADR-127-enforcement-claims-must-resolve.md +150 -0
  113. package/docs/decisions/ADR-128-executable-evidence-and-measured-escalation.md +201 -0
  114. package/docs/decisions/INDEX.md +21 -0
  115. package/docs/decisions/engine-reclassification-2026-07.md +104 -0
  116. package/docs/distribution/awesome-list-submission.md +1 -1
  117. package/docs/distribution/registries.md +36 -27
  118. package/docs/enforcement-by-host.md +7 -0
  119. package/docs/featured-skills.md +2 -2
  120. package/docs/getting-started-by-role.md +1 -1
  121. package/docs/getting-started.md +1 -1
  122. package/docs/governance-advantage.md +1 -1
  123. package/docs/guides/gated-platform-reads.md +221 -0
  124. package/docs/positioning-evidence.md +1 -1
  125. package/docs/proof.md +92 -16
  126. package/docs/threat-model.md +1 -0
  127. package/package.json +5 -2
  128. package/src/config/agent-settings.template.yml +43 -0
  129. package/src/config/reach-channels.yml +217 -0
  130. package/src/config/reach-prescriptions-intake.yml +200 -0
  131. package/src/scripts/_dispatch.bash +15 -0
  132. package/src/scripts/_lib/exec_evidence.ts +251 -0
  133. package/src/scripts/_lib/fixtures/secret-detector/negatives.txt +10 -0
  134. package/src/scripts/_lib/fixtures/secret-detector/positives.txt +11 -0
  135. package/src/scripts/_lib/kernel_rules.ts +46 -0
  136. package/src/scripts/_lib/reddit_thread_parse.ts +598 -0
  137. package/src/scripts/_lib/risk_paths.ts +160 -0
  138. package/src/scripts/_lib/secret_detector.test.ts +103 -0
  139. package/src/scripts/_lib/secret_detector.ts +319 -0
  140. package/src/scripts/_lib/tool_probe.ts +359 -0
  141. package/src/scripts/adr/regenerate_index.ts +81 -6
  142. package/src/scripts/audit_adr_coverage.ts +33 -0
  143. package/src/scripts/build_proof.ts +162 -0
  144. package/src/scripts/check_adr_frontmatter.ts +195 -0
  145. package/src/scripts/check_artefact_count_messaging.ts +4 -0
  146. package/src/scripts/check_backstop_debt.ts +200 -0
  147. package/src/scripts/check_claims.ts +188 -8
  148. package/src/scripts/check_comparison.ts +12 -0
  149. package/src/scripts/check_enforcement_coverage.ts +592 -0
  150. package/src/scripts/check_kernel_rule_bundle.ts +16 -12
  151. package/src/scripts/check_reach_channels.ts +407 -0
  152. package/src/scripts/check_reach_staleness.ts +364 -0
  153. package/src/scripts/check_secret_leak.test.ts +47 -0
  154. package/src/scripts/check_secret_leak.ts +290 -0
  155. package/src/scripts/code_graph/build.ts +0 -0
  156. package/src/scripts/code_graph/cli.ts +207 -0
  157. package/src/scripts/code_graph/detect.ts +134 -0
  158. package/src/scripts/code_graph/extract.ts +386 -0
  159. package/src/scripts/code_graph/loader.ts +123 -0
  160. package/src/scripts/code_graph/query.ts +176 -0
  161. package/src/scripts/code_graph/sanitize.ts +30 -0
  162. package/src/scripts/code_graph/types.ts +83 -0
  163. package/src/scripts/code_graph/validate.ts +63 -0
  164. package/src/scripts/external_sources_denylist.json +3 -1
  165. package/src/scripts/generate_subagent_floor.ts +194 -0
  166. package/src/scripts/hook_manifest.yaml +13 -3
  167. package/src/scripts/hooks/code_graph_nudge_hook.ts +184 -0
  168. package/src/scripts/iron_law_sha.ts +3 -12
  169. package/src/scripts/lint_override_kernel_guard.ts +219 -0
  170. package/src/scripts/measure_rule_budget.ts +3 -12
  171. package/src/scripts/postinstall_gui.ts +129 -0
  172. package/src/scripts/reach_doctor.ts +1651 -0
  173. package/src/scripts/release.ts +24 -3
  174. package/src/scripts/risk_escalation_shadow.ts +160 -0
  175. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  176. package/src/scripts/schemas/reach-channels.schema.json +134 -0
  177. package/src/scripts/schemas/reach-doctor-payload.schema.json +220 -0
  178. package/src/scripts/schemas/rule.schema.json +10 -0
  179. package/src/scripts/skill_linter.ts +55 -0
  180. package/src/scripts/validate_reach_prescriptions.ts +751 -0
  181. package/src/server/schemas/settings.ts +16 -0
  182. package/src/shared/capabilities.test.ts +43 -0
  183. package/src/shared/capabilities.ts +75 -0
  184. package/src/templates/minimal/agents-overrides-readme.md +20 -0
  185. 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
+ }