autonomous-coding-toolkit 1.0.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 (324) hide show
  1. package/.claude-plugin/marketplace.json +22 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/LICENSE +21 -0
  4. package/Makefile +21 -0
  5. package/README.md +140 -0
  6. package/SECURITY.md +28 -0
  7. package/agents/bash-expert.md +113 -0
  8. package/agents/dependency-auditor.md +138 -0
  9. package/agents/integration-tester.md +120 -0
  10. package/agents/lesson-scanner.md +149 -0
  11. package/agents/python-expert.md +179 -0
  12. package/agents/service-monitor.md +141 -0
  13. package/agents/shell-expert.md +147 -0
  14. package/benchmarks/runner.sh +147 -0
  15. package/benchmarks/tasks/01-rest-endpoint/rubric.sh +29 -0
  16. package/benchmarks/tasks/01-rest-endpoint/task.md +17 -0
  17. package/benchmarks/tasks/02-refactor-module/task.md +8 -0
  18. package/benchmarks/tasks/03-fix-integration-bug/task.md +8 -0
  19. package/benchmarks/tasks/04-add-test-coverage/task.md +8 -0
  20. package/benchmarks/tasks/05-multi-file-feature/task.md +8 -0
  21. package/bin/act.js +238 -0
  22. package/commands/autocode.md +6 -0
  23. package/commands/cancel-ralph.md +18 -0
  24. package/commands/code-factory.md +53 -0
  25. package/commands/create-prd.md +55 -0
  26. package/commands/ralph-loop.md +18 -0
  27. package/commands/run-plan.md +117 -0
  28. package/commands/submit-lesson.md +122 -0
  29. package/docs/ARCHITECTURE.md +630 -0
  30. package/docs/CONTRIBUTING.md +125 -0
  31. package/docs/lessons/0001-bare-exception-swallowing.md +34 -0
  32. package/docs/lessons/0002-async-def-without-await.md +28 -0
  33. package/docs/lessons/0003-create-task-without-callback.md +28 -0
  34. package/docs/lessons/0004-hardcoded-test-counts.md +28 -0
  35. package/docs/lessons/0005-sqlite-without-closing.md +33 -0
  36. package/docs/lessons/0006-venv-pip-path.md +27 -0
  37. package/docs/lessons/0007-runner-state-self-rejection.md +35 -0
  38. package/docs/lessons/0008-quality-gate-blind-spot.md +33 -0
  39. package/docs/lessons/0009-parser-overcount-empty-batches.md +36 -0
  40. package/docs/lessons/0010-local-outside-function-bash.md +33 -0
  41. package/docs/lessons/0011-batch-tests-for-unimplemented-code.md +36 -0
  42. package/docs/lessons/0012-api-markdown-unescaped-chars.md +33 -0
  43. package/docs/lessons/0013-export-prefix-env-parsing.md +33 -0
  44. package/docs/lessons/0014-decorator-registry-import-side-effect.md +43 -0
  45. package/docs/lessons/0015-frontend-backend-schema-drift.md +43 -0
  46. package/docs/lessons/0016-event-driven-cold-start-seeding.md +44 -0
  47. package/docs/lessons/0017-copy-paste-logic-diverges.md +43 -0
  48. package/docs/lessons/0018-layer-passes-pipeline-broken.md +45 -0
  49. package/docs/lessons/0019-systemd-envfile-ignores-export.md +41 -0
  50. package/docs/lessons/0020-persist-state-incrementally.md +44 -0
  51. package/docs/lessons/0021-dual-axis-testing.md +48 -0
  52. package/docs/lessons/0022-jsx-factory-shadowing.md +43 -0
  53. package/docs/lessons/0023-static-analysis-spiral.md +51 -0
  54. package/docs/lessons/0024-shared-pipeline-implementation.md +55 -0
  55. package/docs/lessons/0025-defense-in-depth-all-entry-points.md +65 -0
  56. package/docs/lessons/0026-linter-no-rules-false-enforcement.md +54 -0
  57. package/docs/lessons/0027-jsx-silent-prop-drop.md +64 -0
  58. package/docs/lessons/0028-no-infrastructure-in-client-code.md +49 -0
  59. package/docs/lessons/0029-never-write-secrets-to-files.md +61 -0
  60. package/docs/lessons/0030-cache-merge-not-replace.md +62 -0
  61. package/docs/lessons/0031-verify-units-at-boundaries.md +66 -0
  62. package/docs/lessons/0032-module-lifecycle-subscribe-unsubscribe.md +89 -0
  63. package/docs/lessons/0033-async-iteration-mutable-snapshot.md +72 -0
  64. package/docs/lessons/0034-caller-missing-await-silent-discard.md +65 -0
  65. package/docs/lessons/0035-duplicate-registration-silent-overwrite.md +85 -0
  66. package/docs/lessons/0036-websocket-dirty-disconnect.md +33 -0
  67. package/docs/lessons/0037-parallel-agents-worktree-corruption.md +31 -0
  68. package/docs/lessons/0038-subscribe-no-stored-ref.md +36 -0
  69. package/docs/lessons/0039-fallback-or-default-hides-bugs.md +34 -0
  70. package/docs/lessons/0040-event-firehose-filter-first.md +36 -0
  71. package/docs/lessons/0041-ambiguous-base-dir-path-nesting.md +32 -0
  72. package/docs/lessons/0042-spec-compliance-insufficient.md +36 -0
  73. package/docs/lessons/0043-exact-count-extensible-collections.md +32 -0
  74. package/docs/lessons/0044-relative-file-deps-worktree.md +39 -0
  75. package/docs/lessons/0045-iterative-design-improvement.md +33 -0
  76. package/docs/lessons/0046-plan-assertion-math-bugs.md +38 -0
  77. package/docs/lessons/0047-pytest-single-threaded-default.md +37 -0
  78. package/docs/lessons/0048-integration-wiring-batch.md +40 -0
  79. package/docs/lessons/0049-ab-verification.md +41 -0
  80. package/docs/lessons/0050-editing-sourced-files-during-execution.md +33 -0
  81. package/docs/lessons/0051-infrastructure-fixes-cant-self-heal.md +30 -0
  82. package/docs/lessons/0052-uncommitted-changes-poison-quality-gates.md +31 -0
  83. package/docs/lessons/0053-jq-compact-flag-inconsistency.md +31 -0
  84. package/docs/lessons/0054-parser-matches-inside-code-blocks.md +30 -0
  85. package/docs/lessons/0055-agents-compensate-for-garbled-prompts.md +31 -0
  86. package/docs/lessons/0056-grep-count-exit-code-on-zero.md +42 -0
  87. package/docs/lessons/0057-new-artifacts-break-git-clean-gates.md +42 -0
  88. package/docs/lessons/0058-dead-config-keys-never-consumed.md +49 -0
  89. package/docs/lessons/0059-contract-test-shared-structures.md +53 -0
  90. package/docs/lessons/0060-set-e-silent-death-in-runners.md +53 -0
  91. package/docs/lessons/0061-context-injection-dirty-state.md +50 -0
  92. package/docs/lessons/0062-sibling-bug-neighborhood-scan.md +29 -0
  93. package/docs/lessons/0063-one-flag-two-lifetimes.md +31 -0
  94. package/docs/lessons/0064-test-passes-wrong-reason.md +31 -0
  95. package/docs/lessons/0065-pipefail-grep-count-double-output.md +39 -0
  96. package/docs/lessons/0066-local-keyword-outside-function.md +37 -0
  97. package/docs/lessons/0067-stdin-hang-non-interactive-shell.md +36 -0
  98. package/docs/lessons/0068-agent-builds-wrong-thing-correctly.md +31 -0
  99. package/docs/lessons/0069-plan-quality-dominates-execution.md +30 -0
  100. package/docs/lessons/0070-spec-echo-back-prevents-drift.md +31 -0
  101. package/docs/lessons/0071-positive-instructions-outperform-negative.md +30 -0
  102. package/docs/lessons/0072-lost-in-the-middle-context-placement.md +30 -0
  103. package/docs/lessons/0073-unscoped-lessons-cause-false-positives.md +30 -0
  104. package/docs/lessons/0074-stale-context-injection-wrong-batch.md +32 -0
  105. package/docs/lessons/0075-research-artifacts-must-persist.md +32 -0
  106. package/docs/lessons/0076-wrong-decomposition-contaminates-downstream.md +30 -0
  107. package/docs/lessons/0077-cherry-pick-merges-need-manual-resolution.md +30 -0
  108. package/docs/lessons/0078-static-review-without-live-test.md +30 -0
  109. package/docs/lessons/0079-integration-wiring-batch-required.md +32 -0
  110. package/docs/lessons/FRAMEWORK.md +161 -0
  111. package/docs/lessons/SUMMARY.md +201 -0
  112. package/docs/lessons/TEMPLATE.md +85 -0
  113. package/docs/plans/2026-02-21-code-factory-v2-design.md +204 -0
  114. package/docs/plans/2026-02-21-code-factory-v2-implementation-plan.md +2189 -0
  115. package/docs/plans/2026-02-21-code-factory-v2-phase4-design.md +537 -0
  116. package/docs/plans/2026-02-21-code-factory-v2-phase4-implementation-plan.md +2012 -0
  117. package/docs/plans/2026-02-21-hardening-pass-design.md +108 -0
  118. package/docs/plans/2026-02-21-hardening-pass-plan.md +1378 -0
  119. package/docs/plans/2026-02-21-mab-research-report.md +406 -0
  120. package/docs/plans/2026-02-21-marketplace-restructure-design.md +240 -0
  121. package/docs/plans/2026-02-21-marketplace-restructure-plan.md +832 -0
  122. package/docs/plans/2026-02-21-phase4-completion-plan.md +697 -0
  123. package/docs/plans/2026-02-21-validator-suite-design.md +148 -0
  124. package/docs/plans/2026-02-21-validator-suite-plan.md +540 -0
  125. package/docs/plans/2026-02-22-mab-research-round2.md +556 -0
  126. package/docs/plans/2026-02-22-mab-run-design.md +462 -0
  127. package/docs/plans/2026-02-22-mab-run-plan.md +2046 -0
  128. package/docs/plans/2026-02-22-operations-design-methodology-research.md +681 -0
  129. package/docs/plans/2026-02-22-research-agent-failure-taxonomy.md +532 -0
  130. package/docs/plans/2026-02-22-research-code-guideline-policies.md +886 -0
  131. package/docs/plans/2026-02-22-research-codebase-audit-refactoring.md +908 -0
  132. package/docs/plans/2026-02-22-research-coding-standards-documentation.md +541 -0
  133. package/docs/plans/2026-02-22-research-competitive-landscape.md +687 -0
  134. package/docs/plans/2026-02-22-research-comprehensive-testing.md +1076 -0
  135. package/docs/plans/2026-02-22-research-context-utilization.md +459 -0
  136. package/docs/plans/2026-02-22-research-cost-quality-tradeoff.md +548 -0
  137. package/docs/plans/2026-02-22-research-lesson-transferability.md +508 -0
  138. package/docs/plans/2026-02-22-research-multi-agent-coordination.md +312 -0
  139. package/docs/plans/2026-02-22-research-phase-integration.md +602 -0
  140. package/docs/plans/2026-02-22-research-plan-quality.md +428 -0
  141. package/docs/plans/2026-02-22-research-prompt-engineering.md +558 -0
  142. package/docs/plans/2026-02-22-research-unconventional-perspectives.md +528 -0
  143. package/docs/plans/2026-02-22-research-user-adoption.md +638 -0
  144. package/docs/plans/2026-02-22-research-verification-effectiveness.md +433 -0
  145. package/docs/plans/2026-02-23-agent-suite-design.md +299 -0
  146. package/docs/plans/2026-02-23-agent-suite-plan.md +578 -0
  147. package/docs/plans/2026-02-23-phase3-cost-infrastructure-design.md +148 -0
  148. package/docs/plans/2026-02-23-phase3-cost-infrastructure-plan.md +1062 -0
  149. package/docs/plans/2026-02-23-research-bash-expert-agent.md +543 -0
  150. package/docs/plans/2026-02-23-research-dependency-auditor-agent.md +564 -0
  151. package/docs/plans/2026-02-23-research-improving-existing-agents.md +503 -0
  152. package/docs/plans/2026-02-23-research-integration-tester-agent.md +454 -0
  153. package/docs/plans/2026-02-23-research-python-expert-agent.md +429 -0
  154. package/docs/plans/2026-02-23-research-service-monitor-agent.md +425 -0
  155. package/docs/plans/2026-02-23-research-shell-expert-agent.md +533 -0
  156. package/docs/plans/2026-02-23-roadmap-to-completion.md +530 -0
  157. package/docs/plans/2026-02-24-headless-module-split-design.md +98 -0
  158. package/docs/plans/2026-02-24-headless-module-split.md +443 -0
  159. package/docs/plans/2026-02-24-lesson-scope-metadata-design.md +228 -0
  160. package/docs/plans/2026-02-24-lesson-scope-metadata-plan.md +968 -0
  161. package/docs/plans/2026-02-24-npm-packaging-design.md +841 -0
  162. package/docs/plans/2026-02-24-npm-packaging-plan.md +1965 -0
  163. package/docs/plans/audit-findings.md +186 -0
  164. package/docs/telegram-notification-format.md +98 -0
  165. package/examples/example-plan.md +51 -0
  166. package/examples/example-prd.json +72 -0
  167. package/examples/example-roadmap.md +33 -0
  168. package/examples/quickstart-plan.md +63 -0
  169. package/hooks/hooks.json +26 -0
  170. package/hooks/setup-symlinks.sh +48 -0
  171. package/hooks/stop-hook.sh +135 -0
  172. package/package.json +47 -0
  173. package/policies/bash.md +71 -0
  174. package/policies/python.md +71 -0
  175. package/policies/testing.md +61 -0
  176. package/policies/universal.md +60 -0
  177. package/scripts/analyze-report.sh +97 -0
  178. package/scripts/architecture-map.sh +145 -0
  179. package/scripts/auto-compound.sh +273 -0
  180. package/scripts/batch-audit.sh +42 -0
  181. package/scripts/batch-test.sh +101 -0
  182. package/scripts/entropy-audit.sh +221 -0
  183. package/scripts/failure-digest.sh +51 -0
  184. package/scripts/generate-ast-rules.sh +96 -0
  185. package/scripts/init.sh +112 -0
  186. package/scripts/lesson-check.sh +428 -0
  187. package/scripts/lib/common.sh +61 -0
  188. package/scripts/lib/cost-tracking.sh +153 -0
  189. package/scripts/lib/ollama.sh +60 -0
  190. package/scripts/lib/progress-writer.sh +128 -0
  191. package/scripts/lib/run-plan-context.sh +215 -0
  192. package/scripts/lib/run-plan-echo-back.sh +231 -0
  193. package/scripts/lib/run-plan-headless.sh +396 -0
  194. package/scripts/lib/run-plan-notify.sh +57 -0
  195. package/scripts/lib/run-plan-parser.sh +81 -0
  196. package/scripts/lib/run-plan-prompt.sh +215 -0
  197. package/scripts/lib/run-plan-quality-gate.sh +132 -0
  198. package/scripts/lib/run-plan-routing.sh +315 -0
  199. package/scripts/lib/run-plan-sampling.sh +170 -0
  200. package/scripts/lib/run-plan-scoring.sh +146 -0
  201. package/scripts/lib/run-plan-state.sh +142 -0
  202. package/scripts/lib/run-plan-team.sh +199 -0
  203. package/scripts/lib/telegram.sh +54 -0
  204. package/scripts/lib/thompson-sampling.sh +176 -0
  205. package/scripts/license-check.sh +74 -0
  206. package/scripts/mab-run.sh +575 -0
  207. package/scripts/module-size-check.sh +146 -0
  208. package/scripts/patterns/async-no-await.yml +5 -0
  209. package/scripts/patterns/bare-except.yml +6 -0
  210. package/scripts/patterns/empty-catch.yml +6 -0
  211. package/scripts/patterns/hardcoded-localhost.yml +9 -0
  212. package/scripts/patterns/retry-loop-no-backoff.yml +12 -0
  213. package/scripts/pipeline-status.sh +197 -0
  214. package/scripts/policy-check.sh +226 -0
  215. package/scripts/prior-art-search.sh +133 -0
  216. package/scripts/promote-mab-lessons.sh +126 -0
  217. package/scripts/prompts/agent-a-superpowers.md +29 -0
  218. package/scripts/prompts/agent-b-ralph.md +29 -0
  219. package/scripts/prompts/judge-agent.md +61 -0
  220. package/scripts/prompts/planner-agent.md +44 -0
  221. package/scripts/pull-community-lessons.sh +90 -0
  222. package/scripts/quality-gate.sh +266 -0
  223. package/scripts/research-gate.sh +90 -0
  224. package/scripts/run-plan.sh +329 -0
  225. package/scripts/scope-infer.sh +159 -0
  226. package/scripts/setup-ralph-loop.sh +155 -0
  227. package/scripts/telemetry.sh +230 -0
  228. package/scripts/tests/run-all-tests.sh +52 -0
  229. package/scripts/tests/test-act-cli.sh +46 -0
  230. package/scripts/tests/test-agents-md.sh +87 -0
  231. package/scripts/tests/test-analyze-report.sh +114 -0
  232. package/scripts/tests/test-architecture-map.sh +89 -0
  233. package/scripts/tests/test-auto-compound.sh +169 -0
  234. package/scripts/tests/test-batch-test.sh +65 -0
  235. package/scripts/tests/test-benchmark-runner.sh +25 -0
  236. package/scripts/tests/test-common.sh +168 -0
  237. package/scripts/tests/test-cost-tracking.sh +158 -0
  238. package/scripts/tests/test-echo-back.sh +180 -0
  239. package/scripts/tests/test-entropy-audit.sh +146 -0
  240. package/scripts/tests/test-failure-digest.sh +66 -0
  241. package/scripts/tests/test-generate-ast-rules.sh +145 -0
  242. package/scripts/tests/test-helpers.sh +82 -0
  243. package/scripts/tests/test-init.sh +47 -0
  244. package/scripts/tests/test-lesson-check.sh +278 -0
  245. package/scripts/tests/test-lesson-local.sh +55 -0
  246. package/scripts/tests/test-license-check.sh +109 -0
  247. package/scripts/tests/test-mab-run.sh +182 -0
  248. package/scripts/tests/test-ollama-lib.sh +49 -0
  249. package/scripts/tests/test-ollama.sh +60 -0
  250. package/scripts/tests/test-pipeline-status.sh +198 -0
  251. package/scripts/tests/test-policy-check.sh +124 -0
  252. package/scripts/tests/test-prior-art-search.sh +96 -0
  253. package/scripts/tests/test-progress-writer.sh +140 -0
  254. package/scripts/tests/test-promote-mab-lessons.sh +110 -0
  255. package/scripts/tests/test-pull-community-lessons.sh +149 -0
  256. package/scripts/tests/test-quality-gate.sh +241 -0
  257. package/scripts/tests/test-research-gate.sh +132 -0
  258. package/scripts/tests/test-run-plan-cli.sh +86 -0
  259. package/scripts/tests/test-run-plan-context.sh +305 -0
  260. package/scripts/tests/test-run-plan-e2e.sh +153 -0
  261. package/scripts/tests/test-run-plan-headless.sh +424 -0
  262. package/scripts/tests/test-run-plan-notify.sh +124 -0
  263. package/scripts/tests/test-run-plan-parser.sh +217 -0
  264. package/scripts/tests/test-run-plan-prompt.sh +254 -0
  265. package/scripts/tests/test-run-plan-quality-gate.sh +222 -0
  266. package/scripts/tests/test-run-plan-routing.sh +178 -0
  267. package/scripts/tests/test-run-plan-scoring.sh +148 -0
  268. package/scripts/tests/test-run-plan-state.sh +261 -0
  269. package/scripts/tests/test-run-plan-team.sh +157 -0
  270. package/scripts/tests/test-scope-infer.sh +150 -0
  271. package/scripts/tests/test-setup-ralph-loop.sh +63 -0
  272. package/scripts/tests/test-telegram-env.sh +38 -0
  273. package/scripts/tests/test-telegram.sh +121 -0
  274. package/scripts/tests/test-telemetry.sh +46 -0
  275. package/scripts/tests/test-thompson-sampling.sh +139 -0
  276. package/scripts/tests/test-validate-all.sh +60 -0
  277. package/scripts/tests/test-validate-commands.sh +89 -0
  278. package/scripts/tests/test-validate-hooks.sh +98 -0
  279. package/scripts/tests/test-validate-lessons.sh +150 -0
  280. package/scripts/tests/test-validate-plan-quality.sh +235 -0
  281. package/scripts/tests/test-validate-plans.sh +187 -0
  282. package/scripts/tests/test-validate-plugin.sh +106 -0
  283. package/scripts/tests/test-validate-prd.sh +184 -0
  284. package/scripts/tests/test-validate-skills.sh +134 -0
  285. package/scripts/validate-all.sh +57 -0
  286. package/scripts/validate-commands.sh +67 -0
  287. package/scripts/validate-hooks.sh +89 -0
  288. package/scripts/validate-lessons.sh +98 -0
  289. package/scripts/validate-plan-quality.sh +369 -0
  290. package/scripts/validate-plans.sh +120 -0
  291. package/scripts/validate-plugin.sh +86 -0
  292. package/scripts/validate-policies.sh +42 -0
  293. package/scripts/validate-prd.sh +118 -0
  294. package/scripts/validate-skills.sh +96 -0
  295. package/skills/autocode/SKILL.md +285 -0
  296. package/skills/autocode/ab-verification.md +51 -0
  297. package/skills/autocode/code-quality-standards.md +37 -0
  298. package/skills/autocode/competitive-mode.md +364 -0
  299. package/skills/brainstorming/SKILL.md +97 -0
  300. package/skills/capture-lesson/SKILL.md +187 -0
  301. package/skills/check-lessons/SKILL.md +116 -0
  302. package/skills/dispatching-parallel-agents/SKILL.md +110 -0
  303. package/skills/executing-plans/SKILL.md +85 -0
  304. package/skills/finishing-a-development-branch/SKILL.md +201 -0
  305. package/skills/receiving-code-review/SKILL.md +72 -0
  306. package/skills/requesting-code-review/SKILL.md +59 -0
  307. package/skills/requesting-code-review/code-reviewer.md +82 -0
  308. package/skills/research/SKILL.md +145 -0
  309. package/skills/roadmap/SKILL.md +115 -0
  310. package/skills/subagent-driven-development/SKILL.md +98 -0
  311. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +18 -0
  312. package/skills/subagent-driven-development/implementer-prompt.md +73 -0
  313. package/skills/subagent-driven-development/spec-reviewer-prompt.md +57 -0
  314. package/skills/systematic-debugging/SKILL.md +134 -0
  315. package/skills/systematic-debugging/condition-based-waiting.md +64 -0
  316. package/skills/systematic-debugging/defense-in-depth.md +32 -0
  317. package/skills/systematic-debugging/root-cause-tracing.md +55 -0
  318. package/skills/test-driven-development/SKILL.md +167 -0
  319. package/skills/using-git-worktrees/SKILL.md +219 -0
  320. package/skills/using-superpowers/SKILL.md +54 -0
  321. package/skills/verification-before-completion/SKILL.md +140 -0
  322. package/skills/verify/SKILL.md +82 -0
  323. package/skills/writing-plans/SKILL.md +128 -0
  324. package/skills/writing-skills/SKILL.md +93 -0
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bash
2
+ # Test validate-prd.sh
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "$SCRIPT_DIR/test-helpers.sh"
7
+
8
+ VALIDATOR="$SCRIPT_DIR/../validate-prd.sh"
9
+ WORK=$(mktemp -d)
10
+ trap 'rm -rf "$WORK"' EXIT
11
+
12
+ # Helper: create a PRD file
13
+ create_prd() {
14
+ local name="$1" content="$2"
15
+ mkdir -p "$WORK/tasks"
16
+ printf '%s\n' "$content" > "$WORK/tasks/$name"
17
+ }
18
+
19
+ # Helper: run validator against a temp PRD file
20
+ run_validator() {
21
+ local exit_code=0
22
+ PRD_FILE="$WORK/tasks/prd.json" bash "$VALIDATOR" "$@" 2>&1 || exit_code=$?
23
+ echo "EXIT:$exit_code"
24
+ }
25
+
26
+ # === Test: Valid PRD passes ===
27
+ create_prd "prd.json" '[
28
+ {
29
+ "id": 1,
30
+ "title": "First task",
31
+ "acceptance_criteria": ["test -f foo.txt"],
32
+ "passes": false,
33
+ "blocked_by": []
34
+ },
35
+ {
36
+ "id": 2,
37
+ "title": "Second task",
38
+ "acceptance_criteria": ["test -f bar.txt"],
39
+ "passes": false,
40
+ "blocked_by": [1]
41
+ }
42
+ ]'
43
+
44
+ output=$(run_validator)
45
+ assert_contains "valid PRD: PASS" "validate-prd: PASS" "$output"
46
+ assert_contains "valid PRD: exit 0" "EXIT:0" "$output"
47
+
48
+ # === Test: Invalid JSON fails ===
49
+ create_prd "prd.json" 'this is not json at all {'
50
+
51
+ output=$(run_validator)
52
+ assert_contains "invalid JSON: reports violation" "Invalid JSON" "$output"
53
+ assert_contains "invalid JSON: exit 1" "EXIT:1" "$output"
54
+
55
+ # === Test: Not an array fails ===
56
+ create_prd "prd.json" '{"id": 1, "title": "Not an array"}'
57
+
58
+ output=$(run_validator)
59
+ assert_contains "not array: reports violation" "must be a JSON array" "$output"
60
+ assert_contains "not array: exit 1" "EXIT:1" "$output"
61
+
62
+ # === Test: Missing id field fails ===
63
+ create_prd "prd.json" '[
64
+ {
65
+ "title": "No ID",
66
+ "acceptance_criteria": ["test -f foo.txt"],
67
+ "blocked_by": []
68
+ }
69
+ ]'
70
+
71
+ output=$(run_validator)
72
+ assert_contains "missing id: reports violation" "missing or non-numeric 'id'" "$output"
73
+ assert_contains "missing id: exit 1" "EXIT:1" "$output"
74
+
75
+ # === Test: Missing title field fails ===
76
+ create_prd "prd.json" '[
77
+ {
78
+ "id": 1,
79
+ "acceptance_criteria": ["test -f foo.txt"],
80
+ "blocked_by": []
81
+ }
82
+ ]'
83
+
84
+ output=$(run_validator)
85
+ assert_contains "missing title: reports violation" "missing or empty 'title'" "$output"
86
+ assert_contains "missing title: exit 1" "EXIT:1" "$output"
87
+
88
+ # === Test: Missing acceptance_criteria fails ===
89
+ create_prd "prd.json" '[
90
+ {
91
+ "id": 1,
92
+ "title": "No criteria",
93
+ "blocked_by": []
94
+ }
95
+ ]'
96
+
97
+ output=$(run_validator)
98
+ assert_contains "missing criteria: reports violation" "missing or empty 'acceptance_criteria'" "$output"
99
+ assert_contains "missing criteria: exit 1" "EXIT:1" "$output"
100
+
101
+ # === Test: Empty acceptance_criteria fails ===
102
+ create_prd "prd.json" '[
103
+ {
104
+ "id": 1,
105
+ "title": "Empty criteria",
106
+ "acceptance_criteria": [],
107
+ "blocked_by": []
108
+ }
109
+ ]'
110
+
111
+ output=$(run_validator)
112
+ assert_contains "empty criteria: reports violation" "missing or empty 'acceptance_criteria'" "$output"
113
+ assert_contains "empty criteria: exit 1" "EXIT:1" "$output"
114
+
115
+ # === Test: blocked_by references non-existent ID fails ===
116
+ create_prd "prd.json" '[
117
+ {
118
+ "id": 1,
119
+ "title": "First",
120
+ "acceptance_criteria": ["true"],
121
+ "blocked_by": []
122
+ },
123
+ {
124
+ "id": 2,
125
+ "title": "Second",
126
+ "acceptance_criteria": ["true"],
127
+ "blocked_by": [99]
128
+ }
129
+ ]'
130
+
131
+ output=$(run_validator)
132
+ assert_contains "bad blocked_by: reports violation" "references non-existent ID 99" "$output"
133
+ assert_contains "bad blocked_by: exit 1" "EXIT:1" "$output"
134
+
135
+ # === Test: Self-referencing blocked_by fails ===
136
+ create_prd "prd.json" '[
137
+ {
138
+ "id": 1,
139
+ "title": "Self-blocking",
140
+ "acceptance_criteria": ["true"],
141
+ "blocked_by": [1]
142
+ }
143
+ ]'
144
+
145
+ output=$(run_validator)
146
+ assert_contains "self-ref: reports violation" "blocks itself" "$output"
147
+ assert_contains "self-ref: exit 1" "EXIT:1" "$output"
148
+
149
+ # === Test: Single file argument ===
150
+ create_prd "custom.json" '[
151
+ {
152
+ "id": 1,
153
+ "title": "Custom file",
154
+ "acceptance_criteria": ["true"],
155
+ "blocked_by": []
156
+ }
157
+ ]'
158
+
159
+ exit_code=0
160
+ output=$(bash "$VALIDATOR" "$WORK/tasks/custom.json" 2>&1) || exit_code=$?
161
+ output="${output}
162
+ EXIT:${exit_code}"
163
+ assert_contains "single file arg: PASS" "validate-prd: PASS" "$output"
164
+ assert_contains "single file arg: exit 0" "EXIT:0" "$output"
165
+
166
+ # === Test: --warn exits 0 even with violations ===
167
+ create_prd "prd.json" 'not json'
168
+
169
+ output=$(run_validator --warn)
170
+ assert_contains "--warn: still reports violation" "Invalid JSON" "$output"
171
+ assert_contains "--warn: exits 0" "EXIT:0" "$output"
172
+
173
+ # === Test: --help exits 0 ===
174
+ output=$(run_validator --help)
175
+ assert_contains "--help: shows usage" "Usage:" "$output"
176
+ assert_contains "--help: exits 0" "EXIT:0" "$output"
177
+
178
+ # === Test: Missing PRD file fails ===
179
+ rm -f "$WORK/tasks/prd.json"
180
+ output=$(PRD_FILE="$WORK/tasks/nonexistent.json" bash "$VALIDATOR" 2>&1 || echo "EXIT:$?")
181
+ assert_contains "missing file: error message" "PRD file not found" "$output"
182
+ assert_contains "missing file: exit 1" "EXIT:1" "$output"
183
+
184
+ report_results
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env bash
2
+ # Test validate-skills.sh
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "$SCRIPT_DIR/test-helpers.sh"
7
+
8
+ VALIDATOR="$SCRIPT_DIR/../validate-skills.sh"
9
+ WORK=$(mktemp -d)
10
+ trap 'rm -rf "$WORK"' EXIT
11
+
12
+ # Helper: create a skill directory with SKILL.md
13
+ create_skill() {
14
+ local name="$1" content="$2"
15
+ mkdir -p "$WORK/skills/$name"
16
+ echo "$content" > "$WORK/skills/$name/SKILL.md"
17
+ }
18
+
19
+ # Helper: create a companion file in a skill directory
20
+ create_companion() {
21
+ local skill="$1" file="$2"
22
+ echo "# Companion" > "$WORK/skills/$skill/$file"
23
+ }
24
+
25
+ # Helper: run validator against temp skills dir
26
+ run_validator() {
27
+ local exit_code=0
28
+ SKILLS_DIR="$WORK/skills" bash "$VALIDATOR" "$@" 2>&1 || exit_code=$?
29
+ echo "EXIT:$exit_code"
30
+ }
31
+
32
+ # === Test: Valid skill passes ===
33
+ rm -rf "$WORK/skills"
34
+ create_skill "my-skill" '---
35
+ name: my-skill
36
+ description: "A test skill"
37
+ ---
38
+ Body text here.'
39
+
40
+ output=$(run_validator)
41
+ assert_contains "valid skill: PASS" "validate-skills: PASS" "$output"
42
+ assert_contains "valid skill: exit 0" "EXIT:0" "$output"
43
+
44
+ # === Test: Missing name field fails ===
45
+ rm -rf "$WORK/skills"
46
+ create_skill "no-name" '---
47
+ description: "Missing name"
48
+ ---
49
+ Body.'
50
+
51
+ output=$(run_validator)
52
+ assert_contains "missing name: reports violation" "Missing required field: name" "$output"
53
+ assert_contains "missing name: exit 1" "EXIT:1" "$output"
54
+
55
+ # === Test: Missing description field fails ===
56
+ rm -rf "$WORK/skills"
57
+ create_skill "no-desc" '---
58
+ name: no-desc
59
+ ---
60
+ Body.'
61
+
62
+ output=$(run_validator)
63
+ assert_contains "missing description: reports violation" "Missing required field: description" "$output"
64
+ assert_contains "missing description: exit 1" "EXIT:1" "$output"
65
+
66
+ # === Test: Name mismatch with directory fails ===
67
+ rm -rf "$WORK/skills"
68
+ create_skill "actual-dir" '---
69
+ name: wrong-name
70
+ description: "Name does not match directory"
71
+ ---
72
+ Body.'
73
+
74
+ output=$(run_validator)
75
+ assert_contains "name mismatch: reports violation" "name 'wrong-name' does not match directory 'actual-dir'" "$output"
76
+ assert_contains "name mismatch: exit 1" "EXIT:1" "$output"
77
+
78
+ # === Test: Referenced .md file missing fails ===
79
+ rm -rf "$WORK/skills"
80
+ create_skill "has-ref" '---
81
+ name: has-ref
82
+ description: "References a companion file"
83
+ ---
84
+ See details in companion-doc.md for more.'
85
+
86
+ output=$(run_validator)
87
+ assert_contains "missing ref: reports violation" "Referenced file not found: companion-doc.md" "$output"
88
+ assert_contains "missing ref: exit 1" "EXIT:1" "$output"
89
+
90
+ # === Test: Referenced .md file exists passes ===
91
+ rm -rf "$WORK/skills"
92
+ create_skill "has-ref-ok" '---
93
+ name: has-ref-ok
94
+ description: "References a companion file that exists"
95
+ ---
96
+ See details in companion-doc.md for more.'
97
+ create_companion "has-ref-ok" "companion-doc.md"
98
+
99
+ output=$(run_validator)
100
+ assert_contains "existing ref: PASS" "validate-skills: PASS" "$output"
101
+ assert_contains "existing ref: exit 0" "EXIT:0" "$output"
102
+
103
+ # === Test: Missing frontmatter start fails ===
104
+ rm -rf "$WORK/skills"
105
+ create_skill "no-front" 'name: no-front
106
+ description: "No frontmatter markers"'
107
+
108
+ output=$(run_validator)
109
+ assert_contains "missing ---: reports violation" "First line must be '---'" "$output"
110
+ assert_contains "missing ---: exit 1" "EXIT:1" "$output"
111
+
112
+ # === Test: --warn exits 0 even with violations ===
113
+ rm -rf "$WORK/skills"
114
+ create_skill "warn-test" '---
115
+ description: "Missing name"
116
+ ---
117
+ Body.'
118
+
119
+ output=$(run_validator --warn)
120
+ assert_contains "--warn: still reports violation" "Missing required field: name" "$output"
121
+ assert_contains "--warn: exits 0" "EXIT:0" "$output"
122
+
123
+ # === Test: --help exits 0 ===
124
+ output=$(run_validator --help)
125
+ assert_contains "--help: shows usage" "Usage:" "$output"
126
+ assert_contains "--help: exits 0" "EXIT:0" "$output"
127
+
128
+ # === Test: Missing skills directory fails ===
129
+ rm -rf "$WORK/skills"
130
+ output=$(SKILLS_DIR="$WORK/nonexistent" bash "$VALIDATOR" 2>&1 || echo "EXIT:$?")
131
+ assert_contains "missing dir: error message" "skills directory not found" "$output"
132
+ assert_contains "missing dir: exit 1" "EXIT:1" "$output"
133
+
134
+ report_results
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ # validate-all.sh — Run all repo-level validators and report summary
3
+ # Exit 0 if all pass, exit 1 if any fail. Use --warn to pass through to validators.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ PASS_ARGS=()
8
+
9
+ usage() {
10
+ echo "Usage: validate-all.sh [--warn] [--help]"
11
+ echo " Runs all repo-level validators (lessons, skills, commands, plugin, hooks, policies)"
12
+ echo " --warn Pass --warn to all validators (print violations but exit 0)"
13
+ exit 0
14
+ }
15
+
16
+ [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]] && usage
17
+ [[ "${1:-}" == "--warn" ]] && PASS_ARGS=("--warn")
18
+
19
+ validators=(
20
+ validate-lessons
21
+ validate-skills
22
+ validate-commands
23
+ validate-plugin
24
+ validate-hooks
25
+ validate-policies
26
+ )
27
+
28
+ total=${#validators[@]}
29
+ passed=0
30
+ failed_names=()
31
+
32
+ for name in "${validators[@]}"; do
33
+ script="$SCRIPT_DIR/${name}.sh"
34
+ if [[ ! -f "$script" ]]; then
35
+ echo " $name: SKIP (not found)"
36
+ continue
37
+ fi
38
+
39
+ exit_code=0
40
+ bash "$script" ${PASS_ARGS[@]+"${PASS_ARGS[@]}"} >/dev/null 2>&1 || exit_code=$?
41
+
42
+ if [[ $exit_code -eq 0 ]]; then
43
+ echo " $name: PASS"
44
+ ((passed++)) || true
45
+ else
46
+ echo " $name: FAIL"
47
+ failed_names+=("$name")
48
+ fi
49
+ done
50
+
51
+ echo ""
52
+ echo "$passed/$total validators passed"
53
+
54
+ if [[ ${#failed_names[@]} -gt 0 ]]; then
55
+ echo "Failed: ${failed_names[*]}"
56
+ exit 1
57
+ fi
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ # validate-commands.sh — Validate command file frontmatter
3
+ # Exit 0 if clean, exit 1 if violations found. Use --warn to print but exit 0.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ COMMANDS_DIR="${COMMANDS_DIR:-$SCRIPT_DIR/../commands}"
8
+ WARN_ONLY=false
9
+ violations=0
10
+
11
+ usage() {
12
+ echo "Usage: validate-commands.sh [--warn] [--help]"
13
+ echo " Validates all commands/*.md files"
14
+ echo " --warn Print violations but exit 0"
15
+ exit 0
16
+ }
17
+
18
+ report_violation() {
19
+ local file="$1" line="$2" msg="$3"
20
+ echo "${file}:${line}: ${msg}"
21
+ ((violations++)) || true
22
+ }
23
+
24
+ [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]] && usage
25
+ [[ "${1:-}" == "--warn" ]] && WARN_ONLY=true
26
+
27
+ if [[ ! -d "$COMMANDS_DIR" ]]; then
28
+ echo "validate-commands: commands directory not found: $COMMANDS_DIR" >&2
29
+ exit 1
30
+ fi
31
+
32
+ for cmd_file in "$COMMANDS_DIR"/*.md; do
33
+ [[ -f "$cmd_file" ]] || continue
34
+ fname="$(basename "$cmd_file")"
35
+
36
+ # Check 1: First line must be ---
37
+ first_line=$(head -1 "$cmd_file")
38
+ if [[ "$first_line" != "---" ]]; then
39
+ report_violation "$fname" 1 "First line must be '---', got '$first_line'"
40
+ continue
41
+ fi
42
+
43
+ # Check 2: Second --- delimiter must exist (frontmatter is closed)
44
+ closing_line=$(sed -n '2,$ { /^---$/= }' "$cmd_file" | head -1)
45
+ if [[ -z "$closing_line" ]]; then
46
+ report_violation "$fname" 0 "Frontmatter not closed (missing second '---')"
47
+ continue
48
+ fi
49
+
50
+ # Extract frontmatter (between first two --- lines)
51
+ frontmatter=$(sed -n '2,/^---$/{ /^---$/d; p; }' "$cmd_file")
52
+
53
+ # Check 3: Required fields
54
+ if ! echo "$frontmatter" | grep -q "^description:"; then
55
+ report_violation "$fname" 0 "Missing required field: description"
56
+ fi
57
+ done
58
+
59
+ if [[ $violations -gt 0 ]]; then
60
+ echo ""
61
+ echo "validate-commands: FAIL ($violations issues)"
62
+ [[ "$WARN_ONLY" == true ]] && exit 0
63
+ exit 1
64
+ else
65
+ echo "validate-commands: PASS"
66
+ exit 0
67
+ fi
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ # validate-hooks.sh — Validate hooks/hooks.json and referenced scripts
3
+ # Exit 0 if clean, exit 1 if violations found. Use --warn to print but exit 0.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ HOOKS_DIR="${HOOKS_DIR:-$SCRIPT_DIR/../hooks}"
8
+ TOOLKIT_ROOT="${TOOLKIT_ROOT:-$SCRIPT_DIR/..}"
9
+ WARN_ONLY=false
10
+ violations=0
11
+
12
+ usage() {
13
+ echo "Usage: validate-hooks.sh [--warn] [--help]"
14
+ echo " Validates hooks/hooks.json and referenced scripts"
15
+ echo " --warn Print violations but exit 0"
16
+ exit 0
17
+ }
18
+
19
+ report_violation() {
20
+ local file="$1" msg="$2"
21
+ echo "${file}: ${msg}"
22
+ ((violations++)) || true
23
+ }
24
+
25
+ [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]] && usage
26
+ [[ "${1:-}" == "--warn" ]] && WARN_ONLY=true
27
+
28
+ if [[ ! -d "$HOOKS_DIR" ]]; then
29
+ echo "validate-hooks: hooks directory not found: $HOOKS_DIR" >&2
30
+ exit 1
31
+ fi
32
+
33
+ hooks_file="$HOOKS_DIR/hooks.json"
34
+ if [[ ! -f "$hooks_file" ]]; then
35
+ report_violation "hooks.json" "hooks.json not found"
36
+ echo ""
37
+ echo "validate-hooks: FAIL ($violations issues)"
38
+ [[ "$WARN_ONLY" == true ]] && exit 0
39
+ exit 1
40
+ fi
41
+
42
+ # Validate JSON
43
+ if ! jq empty "$hooks_file" 2>/dev/null; then
44
+ report_violation "hooks.json" "hooks.json is not valid JSON"
45
+ echo ""
46
+ echo "validate-hooks: FAIL ($violations issues)"
47
+ [[ "$WARN_ONLY" == true ]] && exit 0
48
+ exit 1
49
+ fi
50
+
51
+ # Extract all command paths from hooks — walks the entire JSON tree for "command" keys
52
+ commands=$(jq -r '.. | objects | select(.type == "command") | .command' "$hooks_file" 2>/dev/null || true)
53
+
54
+ while IFS= read -r cmd; do
55
+ [[ -z "$cmd" ]] && continue
56
+ # Resolve ${CLAUDE_PLUGIN_ROOT} to toolkit root
57
+ resolved="${cmd//\$\{CLAUDE_PLUGIN_ROOT\}/$TOOLKIT_ROOT}"
58
+
59
+ # Extract the script path (last token that looks like a file path)
60
+ # Handles "bash /path/to/script.sh" and "/path/to/script.sh" alike
61
+ script_path="$resolved"
62
+ for token in $resolved; do
63
+ if [[ "$token" == /* || "$token" == \$* ]]; then
64
+ script_path="$token"
65
+ break
66
+ fi
67
+ done
68
+
69
+ # Skip if the resolved path is just a bare command (e.g., "bash" — not a file check target)
70
+ if [[ "$script_path" != */* ]]; then
71
+ continue
72
+ fi
73
+
74
+ if [[ ! -f "$script_path" ]]; then
75
+ report_violation "hooks.json" "script not found: $cmd (resolved: $script_path)"
76
+ elif [[ ! -x "$script_path" ]]; then
77
+ report_violation "hooks.json" "script not executable: $cmd (resolved: $script_path)"
78
+ fi
79
+ done <<< "$commands"
80
+
81
+ if [[ $violations -gt 0 ]]; then
82
+ echo ""
83
+ echo "validate-hooks: FAIL ($violations issues)"
84
+ [[ "$WARN_ONLY" == true ]] && exit 0
85
+ exit 1
86
+ else
87
+ echo "validate-hooks: PASS"
88
+ exit 0
89
+ fi
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bash
2
+ # validate-lessons.sh — Validate lesson file format and frontmatter
3
+ # Exit 0 if clean, exit 1 if violations found. Use --warn to print but exit 0.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ LESSONS_DIR="${LESSONS_DIR:-$SCRIPT_DIR/../docs/lessons}"
8
+ WARN_ONLY=false
9
+ violations=0
10
+
11
+ usage() {
12
+ echo "Usage: validate-lessons.sh [--warn] [--help]"
13
+ echo " Validates all lesson files in docs/lessons/"
14
+ echo " --warn Print violations but exit 0"
15
+ exit 0
16
+ }
17
+
18
+ report_violation() {
19
+ local file="$1" line="$2" msg="$3"
20
+ echo "${file}:${line}: ${msg}"
21
+ ((violations++)) || true
22
+ }
23
+
24
+ [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]] && usage
25
+ [[ "${1:-}" == "--warn" ]] && WARN_ONLY=true
26
+
27
+ if [[ ! -d "$LESSONS_DIR" ]]; then
28
+ echo "validate-lessons: lessons directory not found: $LESSONS_DIR" >&2
29
+ exit 1
30
+ fi
31
+
32
+ seen_ids=()
33
+
34
+ for lesson in "$LESSONS_DIR"/[0-9]*.md; do
35
+ [[ -f "$lesson" ]] || continue
36
+ fname="$(basename "$lesson")"
37
+
38
+ # Check 1: First line must be ---
39
+ first_line=$(head -1 "$lesson")
40
+ if [[ "$first_line" != "---" ]]; then
41
+ report_violation "$fname" 1 "First line must be '---', got '$first_line' (code block wrapping?)"
42
+ continue # Can't parse frontmatter if start is wrong
43
+ fi
44
+
45
+ # Extract frontmatter (between first two --- lines)
46
+ frontmatter=$(sed -n '2,/^---$/{ /^---$/d; p; }' "$lesson")
47
+
48
+ # Check 2: Required fields
49
+ for field in id title severity languages; do
50
+ if ! echo "$frontmatter" | grep -q "^${field}:"; then
51
+ report_violation "$fname" 0 "Missing required field: $field"
52
+ fi
53
+ done
54
+
55
+ # Check 3: pattern.type must exist
56
+ if ! echo "$frontmatter" | grep -q "type:"; then
57
+ report_violation "$fname" 0 "Missing pattern.type field"
58
+ fi
59
+
60
+ # Check 4: Extract and validate ID
61
+ lesson_id=$(echo "$frontmatter" | sed -n 's/^id:[[:space:]]*\(.*\)/\1/p' | tr -d ' "'"'"'')
62
+ if [[ -n "$lesson_id" ]]; then
63
+ # Check for duplicate IDs
64
+ for seen in "${seen_ids[@]+"${seen_ids[@]}"}"; do
65
+ if [[ "$seen" == "$lesson_id" ]]; then
66
+ report_violation "$fname" 0 "Duplicate lesson ID: $lesson_id"
67
+ fi
68
+ done
69
+ seen_ids+=("$lesson_id")
70
+ fi
71
+
72
+ # Check 5: Severity must be valid
73
+ severity=$(echo "$frontmatter" | sed -n 's/^severity:[[:space:]]*\(.*\)/\1/p' | tr -d ' ')
74
+ if [[ -n "$severity" ]]; then
75
+ case "$severity" in
76
+ blocker|should-fix|nice-to-have) ;;
77
+ *) report_violation "$fname" 0 "Invalid severity '$severity' (must be blocker|should-fix|nice-to-have)" ;;
78
+ esac
79
+ fi
80
+
81
+ # Check 6: Syntactic lessons must have regex
82
+ pattern_type=$(echo "$frontmatter" | grep "type:" | tail -1 | sed 's/.*type:[[:space:]]*//' | tr -d ' ')
83
+ if [[ "$pattern_type" == "syntactic" ]]; then
84
+ if ! echo "$frontmatter" | grep -q "regex:"; then
85
+ report_violation "$fname" 0 "Syntactic lesson missing regex field"
86
+ fi
87
+ fi
88
+ done
89
+
90
+ if [[ $violations -gt 0 ]]; then
91
+ echo ""
92
+ echo "validate-lessons: FAIL ($violations issues)"
93
+ [[ "$WARN_ONLY" == true ]] && exit 0
94
+ exit 1
95
+ else
96
+ echo "validate-lessons: PASS"
97
+ exit 0
98
+ fi