@event4u/agent-config 6.0.0 → 6.1.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 (378) hide show
  1. package/.claude-plugin/marketplace.json +5 -5
  2. package/CHANGELOG.md +167 -440
  3. package/README.md +3 -3
  4. package/dist/agent-src/commands/agent-handoff.md +5 -4
  5. package/dist/agent-src/commands/agent-status.md +1 -0
  6. package/dist/agent-src/commands/agents/audit.md +1 -0
  7. package/dist/agent-src/commands/agents/init.md +3 -0
  8. package/dist/agent-src/commands/agents/optimize.md +1 -0
  9. package/dist/agent-src/commands/agents/user/accept.md +1 -0
  10. package/dist/agent-src/commands/agents/user/init.md +1 -0
  11. package/dist/agent-src/commands/agents/user/review.md +1 -0
  12. package/dist/agent-src/commands/agents/user/show.md +1 -0
  13. package/dist/agent-src/commands/agents/user/update.md +1 -0
  14. package/dist/agent-src/commands/agents/user.md +1 -0
  15. package/dist/agent-src/commands/agents.md +1 -0
  16. package/dist/agent-src/commands/analytics/prune.md +3 -2
  17. package/dist/agent-src/commands/analytics/show.md +3 -2
  18. package/dist/agent-src/commands/analytics.md +3 -2
  19. package/dist/agent-src/commands/analyze-reference-repo.md +1 -0
  20. package/dist/agent-src/commands/bug-fix.md +1 -0
  21. package/dist/agent-src/commands/bug-investigate.md +1 -0
  22. package/dist/agent-src/commands/challenge-me/vision.md +3 -2
  23. package/dist/agent-src/commands/challenge-me/with-docs.md +3 -2
  24. package/dist/agent-src/commands/challenge-me.md +3 -2
  25. package/dist/agent-src/commands/chat-history/import.md +9 -9
  26. package/dist/agent-src/commands/chat-history.md +32 -30
  27. package/dist/agent-src/commands/check-current-md.md +1 -0
  28. package/dist/agent-src/commands/commit/in-chunks.md +1 -0
  29. package/dist/agent-src/commands/commit.md +1 -0
  30. package/dist/agent-src/commands/condense.md +1 -0
  31. package/dist/agent-src/commands/context/create.md +1 -0
  32. package/dist/agent-src/commands/context/refactor.md +1 -0
  33. package/dist/agent-src/commands/context.md +1 -0
  34. package/dist/agent-src/commands/cost-report.md +5 -4
  35. package/dist/agent-src/commands/council/analysis.md +3 -2
  36. package/dist/agent-src/commands/council/debate.md +5 -4
  37. package/dist/agent-src/commands/council/default.md +3 -2
  38. package/dist/agent-src/commands/council/design.md +3 -2
  39. package/dist/agent-src/commands/council/optimize.md +3 -2
  40. package/dist/agent-src/commands/council/pr.md +3 -2
  41. package/dist/agent-src/commands/council.md +4 -3
  42. package/dist/agent-src/commands/e2e-heal.md +1 -0
  43. package/dist/agent-src/commands/e2e-plan.md +1 -0
  44. package/dist/agent-src/commands/estimate-ticket.md +1 -0
  45. package/dist/agent-src/commands/feature/dev.md +1 -0
  46. package/dist/agent-src/commands/feature/explore.md +1 -0
  47. package/dist/agent-src/commands/feature/plan.md +6 -6
  48. package/dist/agent-src/commands/feature/refactor.md +1 -0
  49. package/dist/agent-src/commands/feature/roadmap.md +1 -0
  50. package/dist/agent-src/commands/feature.md +1 -0
  51. package/dist/agent-src/commands/fix/ci.md +1 -0
  52. package/dist/agent-src/commands/fix/portability.md +1 -0
  53. package/dist/agent-src/commands/fix/pr-comments.md +147 -15
  54. package/dist/agent-src/commands/fix/refs.md +1 -0
  55. package/dist/agent-src/commands/fix/seeder.md +1 -0
  56. package/dist/agent-src/commands/fix.md +8 -8
  57. package/dist/agent-src/commands/ghostwriter/delete.md +1 -0
  58. package/dist/agent-src/commands/ghostwriter/fetch.md +1 -0
  59. package/dist/agent-src/commands/ghostwriter/list.md +1 -0
  60. package/dist/agent-src/commands/ghostwriter/show.md +1 -0
  61. package/dist/agent-src/commands/ghostwriter/write.md +1 -0
  62. package/dist/agent-src/commands/ghostwriter.md +1 -0
  63. package/dist/agent-src/commands/grill-me.md +3 -2
  64. package/dist/agent-src/commands/image/analyse.md +1 -0
  65. package/dist/agent-src/commands/image/create.md +1 -0
  66. package/dist/agent-src/commands/image/verify.md +1 -0
  67. package/dist/agent-src/commands/image.md +1 -0
  68. package/dist/agent-src/commands/implement-ticket.md +1 -0
  69. package/dist/agent-src/commands/jira-ticket.md +1 -0
  70. package/dist/agent-src/commands/judge/on-diff.md +1 -0
  71. package/dist/agent-src/commands/judge/solo.md +1 -0
  72. package/dist/agent-src/commands/judge/steps.md +1 -0
  73. package/dist/agent-src/commands/judge.md +1 -0
  74. package/dist/agent-src/commands/knowledge/cross-repo.md +1 -0
  75. package/dist/agent-src/commands/knowledge/forget.md +1 -0
  76. package/dist/agent-src/commands/knowledge/ingest.md +1 -0
  77. package/dist/agent-src/commands/knowledge/list.md +1 -0
  78. package/dist/agent-src/commands/knowledge.md +1 -0
  79. package/dist/agent-src/commands/memory/add.md +8 -6
  80. package/dist/agent-src/commands/memory/learn-low-impact.md +3 -2
  81. package/dist/agent-src/commands/memory/load.md +7 -7
  82. package/dist/agent-src/commands/memory/mine-session.md +39 -12
  83. package/dist/agent-src/commands/memory/promote.md +3 -2
  84. package/dist/agent-src/commands/memory/propose.md +7 -6
  85. package/dist/agent-src/commands/memory.md +3 -2
  86. package/dist/agent-src/commands/mode.md +1 -0
  87. package/dist/agent-src/commands/module/create.md +1 -0
  88. package/dist/agent-src/commands/module/explore.md +1 -0
  89. package/dist/agent-src/commands/module.md +1 -0
  90. package/dist/agent-src/commands/optimize/agents-dir.md +1 -0
  91. package/dist/agent-src/commands/optimize/augmentignore.md +1 -0
  92. package/dist/agent-src/commands/optimize/rtk.md +1 -0
  93. package/dist/agent-src/commands/optimize/skills.md +1 -0
  94. package/dist/agent-src/commands/optimize-prompt.md +1 -0
  95. package/dist/agent-src/commands/optimize.md +1 -0
  96. package/dist/agent-src/commands/orchestrate.md +1 -0
  97. package/dist/agent-src/commands/override/create.md +1 -0
  98. package/dist/agent-src/commands/override/manage.md +1 -0
  99. package/dist/agent-src/commands/override.md +1 -0
  100. package/dist/agent-src/commands/package-reset.md +1 -0
  101. package/dist/agent-src/commands/package-test.md +1 -0
  102. package/dist/agent-src/commands/post-as/ghostwriter.md +1 -0
  103. package/dist/agent-src/commands/post-as/me.md +1 -0
  104. package/dist/agent-src/commands/post-as.md +1 -0
  105. package/dist/agent-src/commands/pr/create/description-only.md +1 -0
  106. package/dist/agent-src/commands/pr/create.md +25 -0
  107. package/dist/agent-src/commands/prediction-pool.md +1 -0
  108. package/dist/agent-src/commands/prepare-for-review.md +1 -0
  109. package/dist/agent-src/commands/profile/activate.md +1 -0
  110. package/dist/agent-src/commands/profile/deactivate.md +1 -0
  111. package/dist/agent-src/commands/profile/show.md +1 -0
  112. package/dist/agent-src/commands/profile.md +1 -0
  113. package/dist/agent-src/commands/project-analyze.md +1 -0
  114. package/dist/agent-src/commands/project-health.md +1 -0
  115. package/dist/agent-src/commands/quality-fix.md +1 -0
  116. package/dist/agent-src/commands/refine-ticket.md +1 -0
  117. package/dist/agent-src/commands/research/deep.md +1 -0
  118. package/dist/agent-src/commands/research/report.md +1 -0
  119. package/dist/agent-src/commands/research.md +1 -0
  120. package/dist/agent-src/commands/review-changes.md +1 -0
  121. package/dist/agent-src/commands/review-routing.md +1 -0
  122. package/dist/agent-src/commands/roadmap/ai-council.md +1 -0
  123. package/dist/agent-src/commands/roadmap/create.md +1 -0
  124. package/dist/agent-src/commands/roadmap/process-full.md +1 -0
  125. package/dist/agent-src/commands/roadmap/process-phase.md +1 -0
  126. package/dist/agent-src/commands/roadmap/process-step.md +1 -0
  127. package/dist/agent-src/commands/roadmap.md +1 -0
  128. package/dist/agent-src/commands/rule-compliance-audit.md +1 -0
  129. package/dist/agent-src/commands/security-audit-config.md +84 -0
  130. package/dist/agent-src/commands/set-cost-profile.md +1 -0
  131. package/dist/agent-src/commands/skill/preview.md +1 -0
  132. package/dist/agent-src/commands/skill.md +1 -0
  133. package/dist/agent-src/commands/skills/discover.md +1 -0
  134. package/dist/agent-src/commands/skills.md +1 -0
  135. package/dist/agent-src/commands/sync-agent-settings.md +1 -0
  136. package/dist/agent-src/commands/sync-gitignore/fix.md +1 -0
  137. package/dist/agent-src/commands/sync-gitignore.md +1 -0
  138. package/dist/agent-src/commands/tests/create.md +1 -0
  139. package/dist/agent-src/commands/tests/execute.md +1 -0
  140. package/dist/agent-src/commands/tests.md +1 -0
  141. package/dist/agent-src/commands/threat-model.md +1 -0
  142. package/dist/agent-src/commands/update-form-request-messages.md +1 -0
  143. package/dist/agent-src/commands/upstream-contribute.md +1 -0
  144. package/dist/agent-src/commands/video/from-script.md +1 -0
  145. package/dist/agent-src/commands/video/from-song.md +1 -0
  146. package/dist/agent-src/commands/video/scene.md +1 -0
  147. package/dist/agent-src/commands/video/stitch.md +1 -0
  148. package/dist/agent-src/commands/video/storyboard.md +1 -0
  149. package/dist/agent-src/commands/video.md +1 -0
  150. package/dist/agent-src/commands/work.md +1 -0
  151. package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
  152. package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
  153. package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +2 -2
  154. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -6
  155. package/dist/agent-src/contexts/contracts/consumer-agents-md-guide.md +2 -2
  156. package/dist/agent-src/contexts/execution/rdp-gate.md +75 -0
  157. package/dist/agent-src/contexts/subagent-configuration.md +1 -0
  158. package/dist/agent-src/personas/advisors/contrarian.md +1 -1
  159. package/dist/agent-src/personas/advisors/executor.md +1 -1
  160. package/dist/agent-src/personas/advisors/expansionist.md +1 -1
  161. package/dist/agent-src/personas/advisors/first-principles.md +1 -1
  162. package/dist/agent-src/personas/advisors/outsider.md +1 -1
  163. package/dist/agent-src/rules/autonomous-execution.md +12 -0
  164. package/dist/agent-src/rules/external-reference-deep-dive.md +1 -1
  165. package/dist/agent-src/rules/git-history-discipline.md +47 -1
  166. package/dist/agent-src/rules/improve-before-implement.md +12 -0
  167. package/dist/agent-src/rules/lethal-trifecta-guard.md +80 -0
  168. package/dist/agent-src/rules/no-pr-progress-comments.md +3 -4
  169. package/dist/agent-src/rules/notes-first-reasoning.md +71 -0
  170. package/dist/agent-src/rules/roadmap-progress-sync.md +48 -31
  171. package/dist/agent-src/rules/security-sensitive-stop.md +14 -1
  172. package/dist/agent-src/rules/source-confidentiality.md +97 -0
  173. package/dist/agent-src/rules/think-before-action.md +9 -1
  174. package/dist/agent-src/rules/untrusted-input-defense.md +76 -0
  175. package/dist/agent-src/scripts/archive_completed_roadmaps.py +171 -0
  176. package/dist/agent-src/skills/adversarial-review/SKILL.md +14 -0
  177. package/dist/agent-src/skills/agent-security-review/SKILL.md +113 -0
  178. package/dist/agent-src/skills/agent-security-review/evals/triggers.json +51 -0
  179. package/dist/agent-src/skills/ai-council/SKILL.md +3 -3
  180. package/dist/agent-src/skills/async-python-patterns/SKILL.md +1 -1
  181. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +12 -11
  182. package/dist/agent-src/skills/command-routing/SKILL.md +1 -1
  183. package/dist/agent-src/skills/complexity-first-planning/SKILL.md +96 -0
  184. package/dist/agent-src/skills/complexity-first-planning/evals/triggers.json +16 -0
  185. package/dist/agent-src/skills/copilot-config/SKILL.md +3 -4
  186. package/dist/agent-src/skills/defense-in-depth/SKILL.md +1 -1
  187. package/dist/agent-src/skills/developer-like-execution/SKILL.md +5 -4
  188. package/dist/agent-src/skills/error-handling-patterns/SKILL.md +1 -1
  189. package/dist/agent-src/skills/feature-planning/SKILL.md +2 -2
  190. package/dist/agent-src/skills/mcp-builder/SKILL.md +1 -1
  191. package/dist/agent-src/skills/memory-consolidation/SKILL.md +63 -17
  192. package/dist/agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -1
  193. package/dist/agent-src/skills/readme-writing-package/SKILL.md +1 -1
  194. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +119 -0
  195. package/dist/agent-src/skills/reasoning-orchestrator/evals/triggers.json +16 -0
  196. package/dist/agent-src/skills/receiving-code-review/SKILL.md +6 -6
  197. package/dist/agent-src/skills/refine-prompt/SKILL.md +1 -1
  198. package/dist/agent-src/skills/refine-ticket/SKILL.md +1 -1
  199. package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
  200. package/dist/agent-src/skills/secrets-management/SKILL.md +1 -1
  201. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +10 -3
  202. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
  203. package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +1 -1
  204. package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
  205. package/dist/agent-src/templates/agents/.gitattributes.fragment +0 -1
  206. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +4 -4
  207. package/dist/agent-src/templates/scripts/check_memory.py +1 -2
  208. package/dist/agent-src/templates/scripts/check_memory_proposal.py +1 -1
  209. package/dist/agent-src/templates/scripts/memory_lookup.py +148 -289
  210. package/dist/agent-src/templates/scripts/memory_report.py +132 -2
  211. package/dist/agent-src/templates/scripts/memory_signal.py +7 -9
  212. package/dist/agent-src/templates/scripts/memory_status.py +25 -206
  213. package/dist/agent-src/templates/scripts/work_engine/directives/backend/memory.py +6 -6
  214. package/dist/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +3 -3
  215. package/dist/agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +0 -1
  216. package/dist/cli/agent-config.js +31 -300
  217. package/dist/cli/agent-config.js.map +1 -1
  218. package/dist/cli/commands/commands.js +10 -5
  219. package/dist/cli/commands/commands.js.map +1 -1
  220. package/dist/cli/discovery/loadManifest.js.map +1 -1
  221. package/dist/cli/main.js +309 -0
  222. package/dist/cli/main.js.map +1 -0
  223. package/dist/discovery/deprecation-report.md +1 -1
  224. package/dist/discovery/discovery-manifest.json +645 -342
  225. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  226. package/dist/discovery/discovery-manifest.summary.md +8 -5
  227. package/dist/discovery/orphan-report.md +1 -1
  228. package/dist/discovery/packs.json +149 -37
  229. package/dist/discovery/trust-report.md +3 -3
  230. package/dist/discovery/workspaces.json +61 -36
  231. package/dist/mcp/registry-manifest.json +4 -4
  232. package/dist/router.json +1 -1
  233. package/dist/server/routes/wizard.js +4 -3
  234. package/dist/server/routes/wizard.js.map +1 -1
  235. package/dist/server/schemas/settings.js +18 -0
  236. package/dist/server/schemas/settings.js.map +1 -1
  237. package/docs/MIGRATION.md +1 -1
  238. package/docs/adrs/cost/0001-hard-stop-hook.md +5 -5
  239. package/docs/adrs/memory/0001-consumer-side-snapshot.md +15 -7
  240. package/docs/adrs/memory/README.md +6 -5
  241. package/docs/adrs/router/0001-three-tier-routing.md +2 -2
  242. package/docs/adrs/schema/0001-json-schema-frontmatter.md +2 -2
  243. package/docs/adrs/smoke/0001-per-tier-smoke-scripts.md +5 -5
  244. package/docs/adrs/telegraph/0001-default-off-until-bench.md +3 -3
  245. package/docs/architecture.md +9 -9
  246. package/docs/archive/CHANGELOG-pre-2.2.0.md +30 -30
  247. package/docs/archive/CHANGELOG-pre-2.25.0.md +1 -1
  248. package/docs/archive/CHANGELOG-pre-4.5.0.md +1 -1
  249. package/docs/archive/CHANGELOG-pre-6.0.0.md +473 -0
  250. package/docs/benchmark.md +54 -53
  251. package/docs/benchmarks.md +2 -2
  252. package/docs/case-studies/{frontend-design-vs-ui-ux-pro-max.md → frontend-design-positioning.md} +4 -4
  253. package/docs/catalog.md +20 -13
  254. package/docs/command-flows.md +90 -92
  255. package/docs/contracts/adr-layout.md +2 -3
  256. package/docs/contracts/adr-level-6-productization.md +1 -1
  257. package/docs/contracts/ai-council-config.md +42 -7
  258. package/docs/contracts/command-clusters.md +1 -1
  259. package/docs/contracts/cost-enforcement.md +1 -1
  260. package/docs/contracts/cost-summary-schema.md +1 -1
  261. package/docs/contracts/daily-workspace.md +1 -0
  262. package/docs/contracts/discovery-manifest.schema.json +4 -2
  263. package/docs/contracts/explain-modes.md +1 -1
  264. package/docs/contracts/implement-ticket-flow.md +6 -7
  265. package/docs/contracts/mcp-tool-inventory.md +10 -10
  266. package/docs/contracts/measurement-baseline.md +1 -1
  267. package/docs/contracts/memory-visibility-v1.md +1 -5
  268. package/docs/contracts/namespace.md +1 -1
  269. package/docs/contracts/persona-schema.md +1 -1
  270. package/docs/contracts/rule-interactions.md +1 -1
  271. package/docs/contracts/smoke-contracts.md +1 -1
  272. package/docs/contracts/universal-skills.md +0 -1
  273. package/docs/contracts/workspace-boundary.md +84 -0
  274. package/docs/customization.md +3 -3
  275. package/docs/decisions/ADR-009-event4u-namespace.md +1 -1
  276. package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +1 -1
  277. package/docs/decisions/ADR-026-explain-mode-translation.md +1 -1
  278. package/docs/decisions/ADR-088-no-external-runtime-federation.md +26 -27
  279. package/docs/decisions/ADR-090-visibility-command-frontmatter-field.md +95 -0
  280. package/docs/decisions/ADR-091-split-meta-capability-packs.md +113 -0
  281. package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +93 -0
  282. package/docs/decisions/ADR-093-ai-council-config-user-global.md +111 -0
  283. package/docs/decisions/ADR-094-agent-memory-layer-removal.md +94 -0
  284. package/docs/decisions/ADR-095-workspace-boundary-contract.md +108 -0
  285. package/docs/decisions/INDEX.md +6 -0
  286. package/docs/development.md +5 -7
  287. package/docs/getting-started.md +4 -4
  288. package/docs/guidelines/agent-infra/5w2h-analysis.md +1 -1
  289. package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
  290. package/docs/guidelines/agent-infra/corpus-grounding-authoring.md +1 -1
  291. package/docs/guidelines/agent-infra/critical-thinking.md +1 -1
  292. package/docs/guidelines/agent-infra/engineering-memory-data-format.md +1 -5
  293. package/docs/guidelines/agent-infra/first-principles.md +1 -1
  294. package/docs/guidelines/agent-infra/frontier-reasoning-operating-profile.md +164 -0
  295. package/docs/guidelines/agent-infra/inversion-thinking.md +1 -1
  296. package/docs/guidelines/agent-infra/ios-simulator-guide.md +9 -14
  297. package/docs/guidelines/agent-infra/mcp-request-signing.md +19 -22
  298. package/docs/guidelines/agent-infra/memory-access.md +25 -31
  299. package/docs/guidelines/agent-infra/mental-models.md +1 -1
  300. package/docs/guidelines/agent-infra/model-recommendation.md +29 -0
  301. package/docs/guidelines/agent-infra/scqa-framework.md +3 -3
  302. package/docs/guidelines/agent-infra/security-lint-containment.md +81 -0
  303. package/docs/guidelines/agent-infra/six-hats.md +1 -1
  304. package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
  305. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +72 -0
  306. package/docs/installation.md +1 -1
  307. package/docs/mcp.md +2 -2
  308. package/docs/parity/{bench-ruflo.json → bench-external.json} +10 -10
  309. package/docs/parity/{ruflo.md → external-runtime.md} +9 -9
  310. package/docs/quality.md +3 -3
  311. package/docs/safety.md +3 -3
  312. package/docs/skills-catalog.md +4 -1
  313. package/llms.txt +3 -0
  314. package/package.json +1 -1
  315. package/src/config/agent-settings.template.yml +65 -3
  316. package/src/config/discovery/packs.yml +29 -0
  317. package/src/config/discovery/workspaces.yml +3 -1
  318. package/src/config/gitignore-block.txt +6 -0
  319. package/src/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
  320. package/src/scripts/_cli/cmd_doctor.py +99 -13
  321. package/src/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
  322. package/src/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
  323. package/src/scripts/_lib/bench_ab_scoring_v2.py +227 -0
  324. package/src/scripts/_lib/global_deploy_inventory.py +39 -9
  325. package/src/scripts/_lib/link_crypto.py +206 -0
  326. package/src/scripts/_lib/security_lint.py +228 -0
  327. package/src/scripts/ai_council/clients.py +2 -2
  328. package/src/scripts/ai_council/config.py +55 -0
  329. package/src/scripts/audit_adr_coverage.py +0 -2
  330. package/src/scripts/audit_command_surface.py +18 -5
  331. package/src/scripts/audit_mcp_tools.py +2 -2
  332. package/src/scripts/audit_skill_descriptions.py +2 -2
  333. package/src/scripts/bench_ab_clone.py +62 -12
  334. package/src/scripts/bench_ab_task_runner.py +475 -30
  335. package/src/scripts/bench_ab_v2_run.py +247 -0
  336. package/src/scripts/bench_ab_v2_stats.py +347 -0
  337. package/src/scripts/bench_run.py +1 -1
  338. package/src/scripts/build_discovery_manifest.py +10 -0
  339. package/src/scripts/check_bite_sized_granularity.py +1 -2
  340. package/src/scripts/check_memory.py +49 -63
  341. package/src/scripts/check_memory_proposal.py +1 -1
  342. package/src/scripts/check_no_external_sources.py +101 -0
  343. package/src/scripts/check_references.py +2 -0
  344. package/src/scripts/cost_by_conversation.py +1 -1
  345. package/src/scripts/council_cli.py +28 -14
  346. package/src/scripts/external_sources_denylist.json +91 -0
  347. package/src/scripts/hook_manifest.yaml +14 -6
  348. package/src/scripts/injection_scan_hook.py +145 -0
  349. package/src/scripts/install-hooks.sh +11 -0
  350. package/src/scripts/install.py +88 -13
  351. package/src/scripts/lint_agent_security.py +112 -0
  352. package/src/scripts/lint_bench_ab.py +5 -4
  353. package/src/scripts/lint_command_tiers.py +63 -22
  354. package/src/scripts/lint_discovery_vocabulary.py +2 -0
  355. package/src/scripts/lint_empty_roadmaps.py +80 -0
  356. package/src/scripts/lint_hidden_unicode.py +132 -0
  357. package/src/scripts/lint_instruction_smuggling.py +107 -0
  358. package/src/scripts/lint_marketplace.py +1 -1
  359. package/src/scripts/lint_mcp_config_security.py +124 -0
  360. package/src/scripts/lint_skill_frontmatter_safety.py +144 -0
  361. package/src/scripts/lint_workspace_boundary.py +122 -0
  362. package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -3
  363. package/src/scripts/mcp_server/tools.py +8 -32
  364. package/src/scripts/memory_lookup.py +27 -296
  365. package/src/scripts/memory_report.py +1 -23
  366. package/src/scripts/memory_signal.py +6 -53
  367. package/src/scripts/memory_status.py +25 -206
  368. package/src/scripts/mine_session.py +118 -41
  369. package/src/scripts/pack_dependency_allowlist.json +2 -2
  370. package/src/scripts/render_benchmark_md.py +141 -52
  371. package/src/scripts/schemas/command.schema.json +6 -1
  372. package/src/scripts/security_audit_config.py +153 -0
  373. package/dist/agent-src/commands/chat-history/learn.md +0 -184
  374. package/dist/agent-src/commands/chat-history/show.md +0 -113
  375. package/dist/agent-src/commands/fix/pr-bot-comments.md +0 -157
  376. package/dist/agent-src/commands/fix/pr-developer-comments.md +0 -163
  377. package/dist/agent-src/templates/agents/memory/architecture-decisions.example.yml +0 -95
  378. package/docs/contracts/agent-memory-contract.md +0 -159
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """Hard-Gate linter: no empty roadmap files under ``agents/roadmaps/``.
3
+
4
+ A roadmap ``.md`` that is 0 bytes (or only whitespace) is never valid — it
5
+ carries no goal, no phases, no content. Empty roadmaps have been introduced
6
+ twice by an external ``chore: add uncomitted roadmaps`` auto-commit that
7
+ staged 0-byte placeholders (2026-06-13: ``road-to-6.0.0-final-readiness.md``
8
+ and ``road-to-reaping-catches-pre-inventory-orphans.md``). The local
9
+ pre-commit dashboard check did not catch them — the dashboard generator
10
+ silently skips empty files — and the commits bypassed it anyway. This linter
11
+ is the authoritative backstop: it fails CI (and the pre-commit hook) so an
12
+ empty roadmap can never reach ``main`` again, regardless of how it was staged.
13
+
14
+ Scope: every ``*.md`` under ``agents/roadmaps/`` (active, ``archive/``,
15
+ ``skipped/``, ``stubs/``, ``later/``) — empty is invalid everywhere. The
16
+ ``.gitkeep`` placeholders are not ``.md`` and are ignored.
17
+
18
+ Cap: ≤ 120 LOC, stdlib only. Hooked into ``task ci`` / ``task ci-fast`` via
19
+ ``task lint-empty-roadmaps`` and into the pre-commit hook.
20
+
21
+ Exit codes: 0 = clean, 1 = at least one empty roadmap found.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import sys
26
+ from pathlib import Path
27
+
28
+ QUIET = "--quiet" in sys.argv
29
+
30
+ ROADMAP_DIR = Path("agents/roadmaps")
31
+
32
+
33
+ def _repo_root() -> Path:
34
+ """Walk up from CWD until a dir containing ``agents/roadmaps`` is found."""
35
+ here = Path.cwd()
36
+ for candidate in (here, *here.parents):
37
+ if (candidate / ROADMAP_DIR).is_dir():
38
+ return candidate
39
+ return here
40
+
41
+
42
+ def find_empty_roadmaps(root: Path) -> list[Path]:
43
+ base = root / ROADMAP_DIR
44
+ if not base.is_dir():
45
+ return []
46
+ empties: list[Path] = []
47
+ for md in sorted(base.rglob("*.md")):
48
+ try:
49
+ text = md.read_text(encoding="utf-8")
50
+ except (OSError, UnicodeDecodeError):
51
+ # Unreadable / binary -> not an empty-text file; leave to other gates.
52
+ continue
53
+ if text.strip() == "":
54
+ empties.append(md.relative_to(root))
55
+ return empties
56
+
57
+
58
+ def main() -> int:
59
+ root = _repo_root()
60
+ empties = find_empty_roadmaps(root)
61
+
62
+ if not empties:
63
+ if not QUIET:
64
+ print("✅ lint-empty-roadmaps: no empty roadmap files.")
65
+ return 0
66
+
67
+ print("❌ lint-empty-roadmaps: empty (0-byte / whitespace-only) roadmap file(s):")
68
+ for rel in empties:
69
+ print(f" {rel}")
70
+ print()
71
+ print(" A roadmap with no content is invalid. Either:")
72
+ print(" • restore the intended content, or")
73
+ print(" • delete the file (if its content lives in agents/roadmaps/archive/).")
74
+ print(" Empty roadmap stubs are usually an artefact of an auto-commit that")
75
+ print(" staged a 0-byte placeholder — remove it; do not commit it.")
76
+ return 1
77
+
78
+
79
+ if __name__ == "__main__":
80
+ raise SystemExit(main())
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+ """P1.1 — hidden-Unicode / smuggling-codepoint linter (road-to-security-pillar.md).
3
+
4
+ Detects the invisible-character class used by the "rules-file backdoor" attack:
5
+ instructions a human reviewer cannot see but the model reads. The codepoint set
6
+ covers bidi controls (Trojan Source), zero-width / format chars, the Unicode Tag
7
+ block, variation-selector runs, Private Use Area, and stray C0/C1 controls.
8
+
9
+ Scope: every `.md` under src/{skills,rules,agent-src,domains} + frontmatter.
10
+ Containment: a real teaching doc never needs the *actual* invisible char (it
11
+ writes ``U+200B`` as text), so this linter scans even inside ordinary code
12
+ fences; only a ```security-example fence or a `security-lint: allow
13
+ hidden-unicode` pragma exempts a file/region.
14
+
15
+ Exit 0 clean, 1 on any blocking finding. ``--fix`` writes an NFKC-normalised,
16
+ zero-width-stripped sibling ``<file>.sanitized`` for human review (never
17
+ auto-applied).
18
+
19
+ Usage: python3 src/scripts/lint_hidden_unicode.py [--json] [--fix]
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import sys
25
+ import unicodedata
26
+ from pathlib import Path
27
+
28
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
29
+ from _lib import security_lint as sl # noqa: E402
30
+
31
+ CHECK = "hidden-unicode"
32
+
33
+ # (name, predicate over a single codepoint) — ordered by specificity.
34
+ _BIDI = {0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0x2066, 0x2067, 0x2068, 0x2069,
35
+ 0x200E, 0x200F, 0x061C}
36
+ _ZERO_WIDTH = {0x200B, 0x200C, 0x200D, 0x2060, 0xFEFF, 0x00AD}
37
+ _DEPRECATED = {0x206A, 0x206B, 0x206C, 0x206D, 0x206E, 0x206F, 0xFFF9, 0xFFFA, 0xFFFB}
38
+
39
+
40
+ def _classify(cp: int) -> str | None:
41
+ if cp in _BIDI:
42
+ return "bidi-control"
43
+ if cp in _ZERO_WIDTH:
44
+ return "zero-width"
45
+ if 0xE0000 <= cp <= 0xE007F:
46
+ return "unicode-tag"
47
+ if cp in _DEPRECATED:
48
+ return "deprecated-format"
49
+ if 0xE000 <= cp <= 0xF8FF or 0xF0000 <= cp <= 0xFFFFD or 0x100000 <= cp <= 0x10FFFD:
50
+ return "private-use-area"
51
+ # C0/C1 controls except tab/newline/CR
52
+ if (0x00 <= cp <= 0x1F or 0x7F <= cp <= 0x9F) and cp not in (0x09, 0x0A, 0x0D):
53
+ return "control-char"
54
+ return None
55
+
56
+
57
+ # Variation selectors flagged only in runs of >=3 on one line (steganography).
58
+ # Restricted to the SUPPLEMENTARY block (U+E0100–E01EF): the standard selectors
59
+ # U+FE00–FE0F are legitimate emoji/text presentation (e.g. ❌️ ✅️ ⚠️) and runs
60
+ # of them are normal, so they are NOT a steganography signal.
61
+ _VS = set(range(0xE0100, 0xE01F0))
62
+
63
+
64
+ def _scan(sf: sl.ScannedFile) -> list[sl.Finding]:
65
+ if sf.pragma_allows(CHECK):
66
+ return []
67
+ out: list[sl.Finding] = []
68
+ for lineno, text in sf.iter_lines(skip_example_fence=True):
69
+ vs_run = 0
70
+ for ch in text:
71
+ cp = ord(ch)
72
+ if cp in _VS:
73
+ vs_run += 1
74
+ continue
75
+ kind = _classify(cp)
76
+ if kind:
77
+ name = unicodedata.name(ch, "<unnamed>")
78
+ out.append(sl.Finding(
79
+ path=sf.rel, line=lineno, check=CHECK, severity="HIGH",
80
+ message=f"{kind} U+{cp:04X} ({name})",
81
+ weight=sf.weight,
82
+ ))
83
+ if vs_run >= 3:
84
+ out.append(sl.Finding(
85
+ path=sf.rel, line=lineno, check=CHECK, severity="HIGH",
86
+ message=f"variation-selector run x{vs_run} (steganography signature)",
87
+ weight=sf.weight,
88
+ ))
89
+ return out
90
+
91
+
92
+ def _sanitize(path: Path) -> Path:
93
+ raw = path.read_text(encoding="utf-8", errors="surrogatepass")
94
+ cleaned = "".join(
95
+ ch for ch in raw
96
+ if _classify(ord(ch)) is None and ord(ch) not in _VS
97
+ )
98
+ cleaned = unicodedata.normalize("NFKC", cleaned)
99
+ out = path.with_suffix(path.suffix + ".sanitized")
100
+ out.write_text(cleaned, encoding="utf-8")
101
+ return out
102
+
103
+
104
+ def main() -> int:
105
+ ap = argparse.ArgumentParser(description=__doc__, epilog=sl.GUIDELINE_EPILOG)
106
+ ap.add_argument("--json", action="store_true")
107
+ ap.add_argument("--fix", action="store_true",
108
+ help="write a sanitised sibling for each flagged file (review only)")
109
+ args = ap.parse_args()
110
+
111
+ findings: list[sl.Finding] = []
112
+ flagged: set[Path] = set()
113
+ for sf in sl.iter_corpus():
114
+ hits = _scan(sf)
115
+ findings.extend(hits)
116
+ if hits:
117
+ flagged.add(sf.path)
118
+
119
+ if args.fix:
120
+ for p in sorted(flagged):
121
+ print(f" fixed → {_sanitize(p).relative_to(sl.ROOT)}")
122
+
123
+ if args.json:
124
+ import json
125
+ print(json.dumps([f.__dict__ for f in findings], indent=2))
126
+ return 1 if any(f.is_fail for f in findings) else 0
127
+
128
+ return sl.report(findings, check_label="hidden-unicode")
129
+
130
+
131
+ if __name__ == "__main__":
132
+ raise SystemExit(main())
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ """P1.2 — instruction-smuggling / suppression-phrase linter (road-to-security-pillar.md).
3
+
4
+ Detects instructions smuggled into rule/skill/command bodies (and MCP tool
5
+ descriptions) that try to steer or silence the agent — the prose half of the
6
+ "rules-file backdoor" + MCP "tool-poisoning" attack classes.
7
+
8
+ Precision over recall (false-positive containment, P1.5): the corpus
9
+ *legitimately* quotes attack strings as teaching material, so this linter only
10
+ treats a pattern as a **blocking (HIGH)** smuggle when it appears as live
11
+ **prose** — outside any code fence and outside inline `backtick` spans, where a
12
+ teaching doc would put a quoted example. Shell / secret-path signatures are
13
+ common in legitimate security docs, so they are **MED warnings**, never blocking.
14
+
15
+ - HIGH (fail): disclosure-suppression imperative ("do not mention/tell the
16
+ user", "without explaining") or an imperative injection tag (`<IMPORTANT>`,
17
+ "ignore previous instructions", "you are now …") in live prose.
18
+ - MED (warn): pipe-to-shell, reverse-shell, or secret-path read in live prose.
19
+
20
+ Scope: every `.md` under src/{skills,rules,agent-src,domains}. Exempt: a
21
+ ```security-example fence or a `security-lint: allow instruction-smuggling` pragma.
22
+
23
+ Usage: python3 src/scripts/lint_instruction_smuggling.py [--json]
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import re
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
33
+ from _lib import security_lint as sl # noqa: E402
34
+
35
+ CHECK = "instruction-smuggling"
36
+
37
+ _INLINE_CODE = re.compile(r"`[^`]*`")
38
+
39
+ # HIGH — disclosure suppression as an imperative addressed to the agent.
40
+ _SUPPRESS = re.compile(
41
+ r"\b(do not|don'?t|never)\s+(mention|tell|inform|disclose|reveal|notify)\b"
42
+ r"[^.]{0,40}\b(the )?(user|human|reviewer|operator|owner)\b"
43
+ r"|without (telling|informing|notifying|explaining to) (the )?(user|them|anyone)",
44
+ re.IGNORECASE,
45
+ )
46
+ # HIGH — imperative injection / role-takeover tags and phrases.
47
+ _INJECT = re.compile(
48
+ r"<\s*(important|system|admin|secret|critical)\s*>"
49
+ r"|ignore (all |the )?(previous|prior|above) (instructions|prompts|rules)"
50
+ r"|disregard (all |the )?(previous|prior|above)"
51
+ r"|you are now (a|an|the)\b"
52
+ r"|new system prompt",
53
+ re.IGNORECASE,
54
+ )
55
+
56
+ # MED — execution / exfil signatures (common in legit security docs → warn only).
57
+ _MED = [
58
+ (re.compile(r"\bcurl\b[^\n|]*\|\s*(ba|z|fi)?sh\b", re.IGNORECASE), "pipe-to-shell (curl|sh)"),
59
+ (re.compile(r"\bwget\b[^\n|]*\|\s*(ba|z|fi)?sh\b", re.IGNORECASE), "pipe-to-shell (wget|sh)"),
60
+ (re.compile(r"\b(socat|nc)\b[^\n]*\b(exec|-e)\b|/dev/tcp/", re.IGNORECASE), "reverse-shell signature"),
61
+ (re.compile(r"(~/\.ssh/id_[rd]sa|/etc/shadow|\.aws/credentials)"), "secret-path read"),
62
+ ]
63
+
64
+
65
+ def _strip_inline_code(text: str) -> str:
66
+ """Blank out inline `code` spans so quoted examples don't trip prose checks."""
67
+ return _INLINE_CODE.sub(lambda m: " " * len(m.group(0)), text)
68
+
69
+
70
+ def _scan(sf: sl.ScannedFile) -> list[sl.Finding]:
71
+ if sf.pragma_allows(CHECK):
72
+ return []
73
+ out: list[sl.Finding] = []
74
+ # prose = lines outside ANY fence; inline-code spans blanked.
75
+ for lineno, text in sf.iter_lines(skip_example_fence=True, skip_any_fence=True):
76
+ prose = _strip_inline_code(text)
77
+ if _SUPPRESS.search(prose):
78
+ out.append(sl.Finding(sf.rel, lineno, CHECK, "HIGH",
79
+ "disclosure-suppression imperative in prose", sf.weight))
80
+ if _INJECT.search(prose):
81
+ out.append(sl.Finding(sf.rel, lineno, CHECK, "HIGH",
82
+ "injection / role-takeover phrase in prose", sf.weight))
83
+ for rx, label in _MED:
84
+ if rx.search(prose):
85
+ out.append(sl.Finding(sf.rel, lineno, CHECK, "MED",
86
+ f"{label} in prose (verify intent)", sf.weight))
87
+ return out
88
+
89
+
90
+ def main() -> int:
91
+ ap = argparse.ArgumentParser(description=__doc__, epilog=sl.GUIDELINE_EPILOG)
92
+ ap.add_argument("--json", action="store_true")
93
+ args = ap.parse_args()
94
+
95
+ findings: list[sl.Finding] = []
96
+ for sf in sl.iter_corpus():
97
+ findings.extend(_scan(sf))
98
+
99
+ if args.json:
100
+ import json
101
+ print(json.dumps([f.__dict__ for f in findings], indent=2))
102
+ return 1 if any(f.is_fail for f in findings) else 0
103
+ return sl.report(findings, check_label="instruction-smuggling")
104
+
105
+
106
+ if __name__ == "__main__":
107
+ raise SystemExit(main())
@@ -3,7 +3,7 @@
3
3
  Lint .claude-plugin/marketplace.json for the event4u/agent-config package.
4
4
 
5
5
  Validates the Claude Code Plugin Marketplace manifest against the canonical
6
- shape used by anthropics/skills:
6
+ manifest shape:
7
7
 
8
8
  - Required top-level fields: name, owner, metadata, plugins
9
9
  - owner must have name + email
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env python3
2
+ """P1.3 — MCP-config security linter (road-to-security-pillar.md). OWASP ASI04.
3
+
4
+ Scans shipped MCP configuration — named config files (``*.mcp.json``,
5
+ ``mcp.json``, ``claude_desktop_config.json*``) and fenced ```json blocks that
6
+ declare ``mcpServers`` — for the supply-chain smells behind MCP tool-poisoning
7
+ and rug-pull attacks.
8
+
9
+ - HIGH (fail): a **real inline secret value** in a shipped config (an actual
10
+ key, not the bare prefix used as documentation). Secrets belong in
11
+ ``${env:VAR}``.
12
+ - MED (warn): ``npx -y`` auto-install, unpinned server version, ``autoApprove``
13
+ / ``enableAllProjectMcpServers``, ``0.0.0.0`` binding, shell metacharacters in
14
+ args, omnibus scopes (``*`` / ``all`` / ``full-access``), ``*_BASE_URL`` in a
15
+ project-scoped env. These are smells, not leaks — templates legitimately show
16
+ them, so they warn (and weight 0.25x in example/template files per P1.5).
17
+
18
+ Usage: python3 src/scripts/lint_mcp_config_security.py [--json]
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import re
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
28
+ from _lib import security_lint as sl # noqa: E402
29
+
30
+ CHECK = "mcp-config-security"
31
+
32
+ _NAME_HINTS = re.compile(r"(^|/)(\.mcp\.json|mcp\.json|claude_desktop_config\.json)")
33
+
34
+ # Real secret VALUES (prefix + enough key chars to be a live credential).
35
+ _SECRET = re.compile(
36
+ r"sk-ant-[A-Za-z0-9_\-]{20,}"
37
+ r"|sk-proj-[A-Za-z0-9_\-]{20,}"
38
+ r"|AKIA[0-9A-Z]{16}"
39
+ r"|AIza[0-9A-Za-z_\-]{35}"
40
+ r"|ghp_[0-9A-Za-z]{36}"
41
+ r"|eyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}"
42
+ )
43
+
44
+ # Line-level smells (match on a single line).
45
+ _MED = [
46
+ (re.compile(r"\bautoApprove\b|\benableAllProjectMcpServers\b", re.IGNORECASE), "auto-approve / auto-enable bypasses consent"),
47
+ (re.compile(r"0\.0\.0\.0"), "0.0.0.0 bind (exposed beyond localhost)"),
48
+ (re.compile(r'"[^"]*_BASE_URL"\s*:'), "*_BASE_URL in config (request-redirect / token-exfil vector)"),
49
+ (re.compile(r'"(scopes?|permissions?)"\s*:\s*(\[[^\]]*"(\*|all|full-access)"|"(\*|all|full-access)")', re.IGNORECASE), "omnibus scope (* / all / full-access)"),
50
+ (re.compile(r'"args"\s*:\s*\[[^\]]*(&&|\|\||;|`)'), "shell metacharacters in args"),
51
+ ]
52
+ # Chunk-level smells (span multiple lines in pretty-printed JSON).
53
+ _NPX = re.compile(r'"command"\s*:\s*"(npx|uvx)"', re.IGNORECASE)
54
+ _NPX_YES = re.compile(r'"\s*(-y|--yes)\s*"')
55
+
56
+
57
+ def _candidate_chunks(sf: sl.ScannedFile):
58
+ """Yield (start_lineno, [lines]) for MCP-config regions in this file."""
59
+ if _NAME_HINTS.search(sf.rel):
60
+ yield 1, list(enumerate(sf.lines, start=1))
61
+ return
62
+ if sf.path.suffix != ".md":
63
+ return
64
+ # fenced ```json / ```jsonc blocks that mention mcpServers / command
65
+ in_block, start, buf = False, 0, []
66
+ for i, text in enumerate(sf.lines, start=1):
67
+ st = text.strip()
68
+ if not in_block and re.match(r"`{3,}(json[c5]?|jsonc)\b", st):
69
+ in_block, start, buf = True, i, []
70
+ continue
71
+ if in_block and re.match(r"`{3,}\s*$", st):
72
+ joined = "\n".join(t for _, t in buf)
73
+ if "mcpServers" in joined or '"command"' in joined:
74
+ yield start, buf
75
+ in_block, buf = False, []
76
+ continue
77
+ if in_block:
78
+ buf.append((i, text))
79
+
80
+
81
+ def _scan(sf: sl.ScannedFile):
82
+ if sf.pragma_allows(CHECK):
83
+ return []
84
+ out = []
85
+ for start, numbered in _candidate_chunks(sf):
86
+ for lineno, text in numbered:
87
+ if _SECRET.search(text):
88
+ out.append(sl.Finding(sf.rel, lineno, CHECK, "HIGH",
89
+ "inline secret value in MCP config — use ${env:VAR}",
90
+ sf.weight))
91
+ for rx, label in _MED:
92
+ if rx.search(text):
93
+ out.append(sl.Finding(sf.rel, lineno, CHECK, "MED", label, sf.weight))
94
+ # chunk-level: npx/uvx auto-install spans command + args lines
95
+ npx_line = next((ln for ln, t in numbered if _NPX.search(t)), start)
96
+ chunk = "\n".join(t for _, t in numbered)
97
+ if _NPX.search(chunk) and _NPX_YES.search(chunk):
98
+ out.append(sl.Finding(sf.rel, npx_line, CHECK, "MED",
99
+ "npx/uvx -y auto-install (supply-chain risk; pin + pre-install)",
100
+ sf.weight))
101
+ return out
102
+
103
+
104
+ def main() -> int:
105
+ ap = argparse.ArgumentParser(description=__doc__, epilog=sl.GUIDELINE_EPILOG)
106
+ ap.add_argument("--json", action="store_true")
107
+ args = ap.parse_args()
108
+
109
+ findings = []
110
+ # scan .md (fenced examples) under the default roots PLUS named MCP configs
111
+ # under src/templates (where the shipped claude_desktop_config template lives).
112
+ roots = (*sl.DEFAULT_SCAN_ROOTS, "src/templates")
113
+ for sf in sl.iter_corpus(roots=roots, exts=(".md", ".json", ".template")):
114
+ findings.extend(_scan(sf))
115
+
116
+ if args.json:
117
+ import json
118
+ print(json.dumps([f.__dict__ for f in findings], indent=2))
119
+ return 1 if any(f.is_fail for f in findings) else 0
120
+ return sl.report(findings, check_label="mcp-config-security")
121
+
122
+
123
+ if __name__ == "__main__":
124
+ raise SystemExit(main())
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ """P1.4 — dangerous-frontmatter linter (road-to-security-pillar.md).
3
+
4
+ Enforces the execution-safety contract from the ``runtime-safety`` rule at the
5
+ frontmatter layer, and flags the consumer-format consent-bypass headers
6
+ (``permissionMode: bypassPermissions``, wildcard ``allowed-tools``) that the
7
+ "skill supply-chain" attack class abuses.
8
+
9
+ Checks (skill / command / persona / agent frontmatter under src/):
10
+
11
+ - HIGH: ``execution.type: automated`` but the runtime-safety floor is not met —
12
+ ``handler`` is ``none``/missing, ``safety_mode`` is not ``strict``, or no
13
+ ``allowed_tools`` key is declared.
14
+ - HIGH: ``allowed_tools`` (or consumer ``allowed-tools``) grants a wildcard —
15
+ ``*``, ``Bash(*)``, or bare ``Bash`` — an over-broad tool grant.
16
+ - HIGH: ``permissionMode: bypassPermissions`` (consent bypass).
17
+
18
+ Reconciles with ``validate_frontmatter.py`` (schema fill) by checking only
19
+ *safety semantics*, never re-reporting shape/required-key errors.
20
+
21
+ Usage: python3 src/scripts/lint_skill_frontmatter_safety.py [--json]
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import re
27
+ import sys
28
+ from pathlib import Path
29
+
30
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
31
+ from _lib import security_lint as sl # noqa: E402
32
+
33
+ CHECK = "dangerous-frontmatter"
34
+
35
+ # Always over-broad: a star or Bash(*) wildcard grant.
36
+ _WILDCARD_TOOL = re.compile(r"(^|[\s,\[\"'])(\*|Bash\(\*\))(\s|,|\]|\"|'|$)")
37
+ # Bare `Bash` (full shell) — over-broad only on a NON-execution skill.
38
+ _BARE_BASH = re.compile(r"(^|[\s,\[\"'])Bash(\s|,|\]|\"|'|$)")
39
+
40
+
41
+ def _frontmatter(sf: sl.ScannedFile) -> tuple[list[tuple[int, str]], int] | None:
42
+ """Return [(lineno, text)] of the frontmatter body + its end line, or None."""
43
+ if not sf.lines or sf.lines[0].strip() != "---":
44
+ return None
45
+ body = []
46
+ for i in range(1, len(sf.lines)):
47
+ if sf.lines[i].strip() == "---":
48
+ return body, i + 1
49
+ body.append((i + 1, sf.lines[i]))
50
+ return None
51
+
52
+
53
+ def _exec_block(body: list[tuple[int, str]]) -> dict[str, tuple[int, str]]:
54
+ """Extract execution.* sub-keys as {key: (lineno, value)} (one-level block)."""
55
+ out: dict[str, tuple[int, str]] = {}
56
+ in_exec = False
57
+ base_indent = 0
58
+ for lineno, text in body:
59
+ if re.match(r"^execution:\s*$", text):
60
+ in_exec = True
61
+ base_indent = len(text) - len(text.lstrip())
62
+ continue
63
+ if in_exec:
64
+ indent = len(text) - len(text.lstrip())
65
+ if text.strip() and indent <= base_indent:
66
+ in_exec = False
67
+ continue
68
+ m = re.match(r"^\s+([\w-]+):\s*(.*)$", text)
69
+ if m:
70
+ out[m.group(1)] = (lineno, m.group(2).strip())
71
+ return out
72
+
73
+
74
+ def _scan(sf: sl.ScannedFile) -> list[sl.Finding]:
75
+ if sf.pragma_allows(CHECK):
76
+ return []
77
+ fm = _frontmatter(sf)
78
+ if not fm:
79
+ return []
80
+ body, _end = fm
81
+ out: list[sl.Finding] = []
82
+
83
+ ex = _exec_block(body)
84
+ handler = ex.get("handler", (0, ""))[1].strip("'\"")
85
+ is_execution_skill = bool(ex) and handler not in ("", "none")
86
+
87
+ # consumer consent-bypass header + wildcard `allowed-tools` (hyphen = Claude
88
+ # format; the underscore source key is covered by the execution block below).
89
+ for lineno, text in body:
90
+ if re.match(r"\s*permissionMode:\s*['\"]?bypassPermissions", text):
91
+ out.append(sl.Finding(sf.rel, lineno, CHECK, "HIGH",
92
+ "permissionMode: bypassPermissions (consent bypass)", sf.weight))
93
+ m = re.match(r"\s*allowed-tools:\s*(.+)$", text)
94
+ if m and (_WILDCARD_TOOL.search(m.group(1))
95
+ or (_BARE_BASH.search(m.group(1)) and not is_execution_skill)):
96
+ out.append(sl.Finding(sf.rel, lineno, CHECK, "HIGH",
97
+ "wildcard / bare-Bash tool grant (over-broad)", sf.weight))
98
+
99
+ if not ex:
100
+ return out
101
+ etype = ex.get("type", (0, ""))[1].strip("'\"")
102
+ safety = ex.get("safety_mode", (0, ""))[1].strip("'\"")
103
+ exec_line = ex.get("type", ex.get("handler", (0, "")))[0]
104
+
105
+ if etype == "automated":
106
+ if handler in ("", "none"):
107
+ out.append(sl.Finding(sf.rel, exec_line, CHECK, "HIGH",
108
+ "automated execution with handler none/missing (runtime-safety)", sf.weight))
109
+ if safety != "strict":
110
+ out.append(sl.Finding(sf.rel, exec_line, CHECK, "HIGH",
111
+ "automated execution without safety_mode: strict (runtime-safety)", sf.weight))
112
+ if "allowed_tools" not in ex:
113
+ out.append(sl.Finding(sf.rel, exec_line, CHECK, "HIGH",
114
+ "automated execution without an explicit allowed_tools declaration", sf.weight))
115
+
116
+ at_line, at_val = ex.get("allowed_tools", (0, ""))
117
+ if at_val and _WILDCARD_TOOL.search(at_val):
118
+ out.append(sl.Finding(sf.rel, at_line, CHECK, "HIGH",
119
+ "execution.allowed_tools wildcard (* / Bash(*)) grant", sf.weight))
120
+ elif at_val and _BARE_BASH.search(at_val) and not is_execution_skill:
121
+ out.append(sl.Finding(sf.rel, at_line, CHECK, "HIGH",
122
+ "bare Bash grant on a non-execution skill (handler none/missing)", sf.weight))
123
+ return out
124
+
125
+
126
+ def main() -> int:
127
+ ap = argparse.ArgumentParser(description=__doc__, epilog=sl.GUIDELINE_EPILOG)
128
+ ap.add_argument("--json", action="store_true")
129
+ args = ap.parse_args()
130
+
131
+ findings: list[sl.Finding] = []
132
+ roots = ("src/skills", "src/agent-src", "src/domains")
133
+ for sf in sl.iter_corpus(roots=roots, exts=(".md",)):
134
+ findings.extend(_scan(sf))
135
+
136
+ if args.json:
137
+ import json
138
+ print(json.dumps([f.__dict__ for f in findings], indent=2))
139
+ return 1 if any(f.is_fail for f in findings) else 0
140
+ return sl.report(findings, check_label="dangerous-frontmatter")
141
+
142
+
143
+ if __name__ == "__main__":
144
+ raise SystemExit(main())