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,1378 @@
1
+ # Full-Coverage Hardening Pass Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Audit all code for quality issues, fix findings, and add comprehensive tests to close every coverage gap in the toolkit.
6
+
7
+ **Architecture:** Two-phase approach — Phase 1 audits with shellcheck + schema validation + smoke tests, Phase 2 writes permanent test suites. Test files follow the existing bash framework (`assert_exit`/`assert_output_contains` helpers). The test runner at `scripts/tests/run-all-tests.sh` discovers files matching `test-*.sh` glob.
8
+
9
+ **Tech Stack:** bash, shellcheck 0.9.0, grep -P (PCRE), jq, awk
10
+
11
+ **Quality Gates:** `bash scripts/tests/run-all-tests.sh` — must maintain 109+ existing tests passing and add 60+ new tests.
12
+
13
+ ---
14
+
15
+ ## Batch 1: Static Analysis — Shellcheck + Lesson Scanner
16
+
17
+ ### Task 1: Run shellcheck against all scripts and catalog findings
18
+
19
+ **Files:**
20
+ - Read: `scripts/*.sh`, `scripts/lib/*.sh`, `hooks/stop-hook.sh`
21
+ - Create: `docs/plans/audit-findings.md`
22
+
23
+ **Step 1: Run shellcheck against all scripts**
24
+
25
+ ```bash
26
+ shellcheck -s bash -f gcc scripts/run-plan.sh scripts/lesson-check.sh scripts/quality-gate.sh scripts/setup-ralph-loop.sh scripts/auto-compound.sh scripts/entropy-audit.sh scripts/batch-audit.sh scripts/batch-test.sh scripts/lib/*.sh hooks/stop-hook.sh 2>&1 | tee /tmp/shellcheck-findings.txt
27
+ echo "Exit code: $?"
28
+ ```
29
+
30
+ Expected: Some warnings (SC2086 word splitting, SC2155 declare/assign, etc.)
31
+
32
+ **Step 2: Catalog all findings**
33
+
34
+ Create `docs/plans/audit-findings.md` with:
35
+ - Each finding: file:line, shellcheck code, severity, description
36
+ - Group by severity (error, warning, info)
37
+ - For each finding, add a disposition: FIX or SUPPRESS (with justification)
38
+
39
+ **Step 3: Commit findings**
40
+
41
+ ```bash
42
+ git add docs/plans/audit-findings.md
43
+ git commit -m "docs: add shellcheck audit findings"
44
+ ```
45
+
46
+ ### Task 2: Run lesson-scanner agent against toolkit's own codebase
47
+
48
+ **Files:**
49
+ - Read: `docs/lessons/*.md` (lesson definitions)
50
+ - Modify: `docs/plans/audit-findings.md` (append lesson scanner results)
51
+
52
+ **Step 1: Run lesson-check.sh against all shell scripts**
53
+
54
+ ```bash
55
+ scripts/lesson-check.sh scripts/*.sh scripts/lib/*.sh hooks/stop-hook.sh
56
+ ```
57
+
58
+ Expected: Clean (these are bash, lessons target python/js). Record result.
59
+
60
+ **Step 2: Run lesson-check.sh against any Python/JS in examples**
61
+
62
+ ```bash
63
+ find . -name "*.py" -o -name "*.js" -o -name "*.ts" | grep -v node_modules | grep -v .venv | xargs scripts/lesson-check.sh 2>/dev/null || echo "No matching files"
64
+ ```
65
+
66
+ **Step 3: Append results to audit-findings.md and commit**
67
+
68
+ ```bash
69
+ git add docs/plans/audit-findings.md
70
+ git commit -m "docs: add lesson scanner results to audit findings"
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Batch 2: Schema Validation
76
+
77
+ ### Task 3: Validate all lesson file YAML frontmatter
78
+
79
+ **Files:**
80
+ - Read: `docs/lessons/0001-*.md` through `docs/lessons/0006-*.md`
81
+ - Read: `docs/lessons/TEMPLATE.md` (schema reference)
82
+ - Modify: `docs/plans/audit-findings.md`
83
+
84
+ **Step 1: For each lesson file, verify required YAML fields exist**
85
+
86
+ Required fields per TEMPLATE.md:
87
+ - `id` — integer
88
+ - `title` — non-empty string
89
+ - `severity` — one of: blocker, should-fix, nice-to-have
90
+ - `languages` — array like `[python, javascript]` or `all`
91
+ - `category` — one of: async-traps, resource-lifecycle, silent-failures, integration-boundaries, test-anti-patterns, performance
92
+ - `pattern.type` — one of: syntactic, semantic
93
+ - `pattern.regex` — non-empty string (required if type=syntactic)
94
+ - `pattern.description` — non-empty string
95
+ - `fix` — non-empty string
96
+ - `example.bad` — non-empty
97
+ - `example.good` — non-empty
98
+
99
+ For each file, extract frontmatter and verify each field. Use awk:
100
+
101
+ ```bash
102
+ for f in docs/lessons/0*.md; do
103
+ echo "=== $f ==="
104
+ # Extract and display all top-level fields
105
+ awk '/^---$/{c++; if(c==2) exit} c==1 && !/^---$/{print}' "$f"
106
+ echo ""
107
+ done
108
+ ```
109
+
110
+ **Step 2: Validate regex patterns are valid grep -P**
111
+
112
+ For each syntactic lesson, test the regex compiles:
113
+
114
+ ```bash
115
+ for f in docs/lessons/0*.md; do
116
+ regex=$(awk 'BEGIN{ip=0} /^---$/{c++; if(c==2) exit} c==1 && /^pattern:/{ip=1;next} ip && /^[^[:space:]]/{ip=0} ip && /^[[:space:]]+regex:/{sub(/^[[:space:]]+regex:[[:space:]]+/,""); gsub(/^["'"'"']|["'"'"']$/,""); print}' "$f")
117
+ if [[ -n "$regex" ]]; then
118
+ # Unescape double backslashes (YAML stores \\s, we need \s)
119
+ regex="${regex//\\\\/\\}"
120
+ if echo "" | grep -P "$regex" >/dev/null 2>&1 || [[ $? -le 1 ]]; then
121
+ echo "PASS: $(basename "$f") regex compiles: $regex"
122
+ else
123
+ echo "FAIL: $(basename "$f") invalid regex: $regex"
124
+ fi
125
+ fi
126
+ done
127
+ ```
128
+
129
+ **Step 3: Append validation results to audit-findings.md and commit**
130
+
131
+ ```bash
132
+ git add docs/plans/audit-findings.md
133
+ git commit -m "docs: add lesson schema validation to audit findings"
134
+ ```
135
+
136
+ ### Task 4: Validate plugin manifests, hooks.json, skill frontmatters, and command files
137
+
138
+ **Files:**
139
+ - Read: `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`
140
+ - Read: `hooks/hooks.json`
141
+ - Read: `skills/*/SKILL.md` (all 15)
142
+ - Read: `commands/*.md` (all 6)
143
+ - Modify: `docs/plans/audit-findings.md`
144
+
145
+ **Step 1: Validate JSON files with jq**
146
+
147
+ ```bash
148
+ jq . .claude-plugin/plugin.json >/dev/null && echo "PASS: plugin.json valid JSON"
149
+ jq . .claude-plugin/marketplace.json >/dev/null && echo "PASS: marketplace.json valid JSON"
150
+ jq . hooks/hooks.json >/dev/null && echo "PASS: hooks.json valid JSON"
151
+ ```
152
+
153
+ **Step 2: Check plugin.json has required fields**
154
+
155
+ ```bash
156
+ jq -e '.name and .description and .version and .author' .claude-plugin/plugin.json >/dev/null && echo "PASS: plugin.json has required fields"
157
+ ```
158
+
159
+ **Step 3: Check all 15 skills have YAML frontmatter with name, description, version**
160
+
161
+ ```bash
162
+ for f in skills/*/SKILL.md; do
163
+ name=$(awk '/^---$/{c++; if(c==2) exit} c==1 && /^name:/{sub(/^name:[[:space:]]+/,""); print}' "$f")
164
+ desc=$(awk '/^---$/{c++; if(c==2) exit} c==1 && /^description:/{sub(/^description:[[:space:]]+/,""); print}' "$f")
165
+ ver=$(awk '/^---$/{c++; if(c==2) exit} c==1 && /^version:/{sub(/^version:[[:space:]]+/,""); print}' "$f")
166
+ if [[ -n "$name" && -n "$desc" && -n "$ver" ]]; then
167
+ echo "PASS: $f (name=$name, version=$ver)"
168
+ else
169
+ echo "FAIL: $f missing: ${name:+}${name:-name }${desc:+}${desc:-desc }${ver:+}${ver:-version}"
170
+ fi
171
+ done
172
+ ```
173
+
174
+ **Step 4: Check all 6 commands have frontmatter with name and description**
175
+
176
+ ```bash
177
+ for f in commands/*.md; do
178
+ has_fm=$(awk '/^---$/{c++} c==2{print "yes"; exit}' "$f")
179
+ if [[ "$has_fm" == "yes" ]]; then
180
+ echo "PASS: $f has frontmatter"
181
+ else
182
+ echo "FAIL: $f missing frontmatter"
183
+ fi
184
+ done
185
+ ```
186
+
187
+ **Step 5: Append to audit-findings.md and commit**
188
+
189
+ ```bash
190
+ git add docs/plans/audit-findings.md
191
+ git commit -m "docs: add manifest and frontmatter validation to audit findings"
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Batch 3: Integration Smoke Tests
197
+
198
+ ### Task 5: Smoke test lesson-check.sh with known patterns
199
+
200
+ **Files:**
201
+ - Read: `scripts/lesson-check.sh`
202
+ - Read: `docs/lessons/0001-bare-exception-swallowing.md` (regex: `^\s*except\s*:`)
203
+ - Read: `docs/lessons/0006-venv-pip-path.md` (regex: `\.venv/bin/pip\b`)
204
+
205
+ **Step 1: Create test fixture files with known anti-patterns**
206
+
207
+ Create temporary files:
208
+
209
+ ```bash
210
+ TMPDIR=$(mktemp -d)
211
+
212
+ # File that should trigger lesson-1 (bare except)
213
+ cat > "$TMPDIR/bad_except.py" <<'PYEOF'
214
+ try:
215
+ do_something()
216
+ except:
217
+ pass
218
+ PYEOF
219
+
220
+ # File that should trigger lesson-6 (.venv/bin/pip)
221
+ cat > "$TMPDIR/bad_pip.sh" <<'SHEOF'
222
+ .venv/bin/pip install requests
223
+ SHEOF
224
+
225
+ # Clean file — no violations
226
+ cat > "$TMPDIR/clean.py" <<'PYEOF'
227
+ try:
228
+ do_something()
229
+ except Exception as e:
230
+ logger.error("Failed: %s", e)
231
+ PYEOF
232
+ ```
233
+
234
+ **Step 2: Run lesson-check against fixture files**
235
+
236
+ ```bash
237
+ # Should find violations
238
+ scripts/lesson-check.sh "$TMPDIR/bad_except.py" "$TMPDIR/bad_pip.sh"
239
+ echo "Exit code: $?"
240
+ # Expected: exit 1, output showing [lesson-1] and [lesson-6]
241
+
242
+ # Should be clean
243
+ scripts/lesson-check.sh "$TMPDIR/clean.py"
244
+ echo "Exit code: $?"
245
+ # Expected: exit 0, "lesson-check: clean"
246
+ ```
247
+
248
+ **Step 3: Clean up and record results**
249
+
250
+ ```bash
251
+ rm -rf "$TMPDIR"
252
+ ```
253
+
254
+ ### Task 6: Smoke test quality-gate.sh with a mock project
255
+
256
+ **Files:**
257
+ - Read: `scripts/quality-gate.sh`
258
+
259
+ **Step 1: Create mock project directory**
260
+
261
+ ```bash
262
+ MOCK_PROJECT=$(mktemp -d)
263
+ cd "$MOCK_PROJECT"
264
+ git init
265
+ echo "print('hello')" > main.py
266
+ git add main.py && git commit -m "init"
267
+ ```
268
+
269
+ **Step 2: Run quality-gate.sh against it**
270
+
271
+ ```bash
272
+ scripts/quality-gate.sh --project-root "$MOCK_PROJECT"
273
+ echo "Exit code: $?"
274
+ ```
275
+
276
+ Expected: exit 0 (no changed files, no test suite detected, memory OK)
277
+
278
+ **Step 3: Verify --help works**
279
+
280
+ ```bash
281
+ scripts/quality-gate.sh --help
282
+ echo "Exit code: $?"
283
+ ```
284
+
285
+ Expected: exit 0, help text shown
286
+
287
+ **Step 4: Clean up**
288
+
289
+ ```bash
290
+ rm -rf "$MOCK_PROJECT"
291
+ ```
292
+
293
+ ### Task 7: Smoke test setup-ralph-loop.sh and stop-hook.sh
294
+
295
+ **Files:**
296
+ - Read: `scripts/setup-ralph-loop.sh`
297
+ - Read: `hooks/stop-hook.sh`
298
+
299
+ **Step 1: Test setup-ralph-loop.sh creates valid state file**
300
+
301
+ ```bash
302
+ TMPDIR=$(mktemp -d)
303
+ cd "$TMPDIR"
304
+ bash /path/to/scripts/setup-ralph-loop.sh "Build a todo API" --max-iterations 10 --completion-promise "DONE"
305
+ # Verify state file
306
+ cat .claude/ralph-loop.local.md
307
+ # Should have: active: true, iteration: 1, max_iterations: 10, completion_promise: "DONE"
308
+ ```
309
+
310
+ **Step 2: Test setup-ralph-loop.sh --help**
311
+
312
+ ```bash
313
+ bash /path/to/scripts/setup-ralph-loop.sh --help
314
+ echo "Exit code: $?"
315
+ ```
316
+
317
+ Expected: exit 0, help text
318
+
319
+ **Step 3: Test setup-ralph-loop.sh with no args**
320
+
321
+ ```bash
322
+ bash /path/to/scripts/setup-ralph-loop.sh 2>&1
323
+ echo "Exit code: $?"
324
+ ```
325
+
326
+ Expected: exit 1, error "No prompt provided"
327
+
328
+ **Step 4: Test stop-hook.sh with no state file**
329
+
330
+ ```bash
331
+ cd "$TMPDIR"
332
+ rm -f .claude/ralph-loop.local.md
333
+ echo '{}' | bash /path/to/hooks/stop-hook.sh
334
+ echo "Exit code: $?"
335
+ ```
336
+
337
+ Expected: exit 0 (no state file = allow exit)
338
+
339
+ **Step 5: Clean up and record**
340
+
341
+ ```bash
342
+ rm -rf "$TMPDIR"
343
+ ```
344
+
345
+ **Step 6: Commit smoke test results to audit-findings.md**
346
+
347
+ ```bash
348
+ git add docs/plans/audit-findings.md
349
+ git commit -m "docs: add integration smoke test results to audit findings"
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Batch 4: Fix All Audit Findings
355
+
356
+ ### Task 8: Fix shellcheck issues
357
+
358
+ **Files:**
359
+ - Modify: Any scripts with shellcheck findings from Task 1
360
+
361
+ **Step 1: Read audit-findings.md for all FIX-disposition items**
362
+
363
+ **Step 2: For each FIX item, apply the fix**
364
+
365
+ Common shellcheck fixes:
366
+ - SC2086 (word splitting): Quote variables — `"$var"` instead of `$var`
367
+ - SC2155 (declare/assign): Split `local var=$(...)` into `local var; var=$(...)`
368
+ - SC2034 (unused variable): Remove or prefix with `_`
369
+ - SC2181 (check $?): Use `if command; then` instead of `command; if [[ $? ... ]]`
370
+
371
+ For SUPPRESS items: Add `# shellcheck disable=SCXXXX` with comment explaining why.
372
+
373
+ **Step 3: Re-run shellcheck to verify**
374
+
375
+ ```bash
376
+ shellcheck -s bash -f gcc scripts/run-plan.sh scripts/lesson-check.sh scripts/quality-gate.sh scripts/setup-ralph-loop.sh scripts/auto-compound.sh scripts/entropy-audit.sh scripts/batch-audit.sh scripts/batch-test.sh scripts/lib/*.sh hooks/stop-hook.sh
377
+ ```
378
+
379
+ Expected: 0 findings (all fixed or suppressed)
380
+
381
+ **Step 4: Run existing tests to verify no regressions**
382
+
383
+ ```bash
384
+ bash scripts/tests/run-all-tests.sh
385
+ ```
386
+
387
+ Expected: 109/109 pass
388
+
389
+ **Step 5: Commit**
390
+
391
+ ```bash
392
+ git add -A
393
+ git commit -m "fix: resolve shellcheck findings across all scripts"
394
+ ```
395
+
396
+ ### Task 9: Fix schema and smoke test findings
397
+
398
+ **Files:**
399
+ - Modify: Any files with schema or smoke test issues from Tasks 3-7
400
+
401
+ **Step 1: Fix any lesson file schema issues**
402
+
403
+ Check audit-findings.md for lesson file issues. Fix YAML frontmatter as needed.
404
+
405
+ **Step 2: Fix any manifest/frontmatter issues**
406
+
407
+ Fix plugin.json, marketplace.json, hooks.json, skill frontmatters, command frontmatters as needed.
408
+
409
+ **Step 3: Fix any bugs discovered during smoke tests**
410
+
411
+ **Step 4: Re-run all existing tests**
412
+
413
+ ```bash
414
+ bash scripts/tests/run-all-tests.sh
415
+ ```
416
+
417
+ Expected: 109/109 pass
418
+
419
+ **Step 5: Commit**
420
+
421
+ ```bash
422
+ git add -A
423
+ git commit -m "fix: resolve schema and smoke test findings"
424
+ ```
425
+
426
+ ---
427
+
428
+ ## Batch 5: lesson-check.sh Tests (**CRITICAL — A/B Competitive**)
429
+
430
+ ### Task 10: Create test-lesson-check.sh with parse_lesson unit tests
431
+
432
+ **Files:**
433
+ - Create: `scripts/tests/test-lesson-check.sh`
434
+ - Read: `scripts/lesson-check.sh` (source `parse_lesson` function)
435
+ - Read: `docs/lessons/0001-*.md` through `docs/lessons/0006-*.md`
436
+
437
+ **Step 1: Write the test file with helper functions and parse_lesson tests**
438
+
439
+ Create `scripts/tests/test-lesson-check.sh`:
440
+
441
+ ```bash
442
+ #!/usr/bin/env bash
443
+ # Test lesson-check.sh — parse_lesson(), regex matching, language filter, exit codes
444
+ set -euo pipefail
445
+
446
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
447
+ LESSON_CHECK="$SCRIPT_DIR/../lesson-check.sh"
448
+
449
+ # Source the lesson-check.sh to get parse_lesson and file_matches_languages
450
+ # We need to source it without running the main logic.
451
+ # Extract functions only by sourcing in a subshell context.
452
+ LESSONS_DIR="$SCRIPT_DIR/../../docs/lessons"
453
+
454
+ FAILURES=0
455
+ TESTS=0
456
+
457
+ assert_equals() {
458
+ local desc="$1" expected="$2" actual="$3"
459
+ TESTS=$((TESTS + 1))
460
+ if [[ "$actual" != "$expected" ]]; then
461
+ echo "FAIL: $desc"
462
+ echo " expected: $expected"
463
+ echo " actual: $actual"
464
+ FAILURES=$((FAILURES + 1))
465
+ else
466
+ echo "PASS: $desc"
467
+ fi
468
+ }
469
+
470
+ assert_exit() {
471
+ local desc="$1" expected_exit="$2"
472
+ shift 2
473
+ local actual_exit=0
474
+ local output
475
+ output=$("$@" 2>&1) || actual_exit=$?
476
+ TESTS=$((TESTS + 1))
477
+ if [[ "$actual_exit" -ne "$expected_exit" ]]; then
478
+ echo "FAIL: $desc"
479
+ echo " expected exit: $expected_exit"
480
+ echo " actual exit: $actual_exit"
481
+ echo " output: ${output:0:300}"
482
+ FAILURES=$((FAILURES + 1))
483
+ else
484
+ echo "PASS: $desc"
485
+ fi
486
+ }
487
+
488
+ assert_output_contains() {
489
+ local desc="$1" needle="$2"
490
+ shift 2
491
+ local output
492
+ output=$("$@" 2>&1) || true
493
+ TESTS=$((TESTS + 1))
494
+ if [[ "$output" != *"$needle"* ]]; then
495
+ echo "FAIL: $desc"
496
+ echo " expected to contain: $needle"
497
+ echo " in: ${output:0:300}"
498
+ FAILURES=$((FAILURES + 1))
499
+ else
500
+ echo "PASS: $desc"
501
+ fi
502
+ }
503
+
504
+ assert_output_not_contains() {
505
+ local desc="$1" needle="$2"
506
+ shift 2
507
+ local output
508
+ output=$("$@" 2>&1) || true
509
+ TESTS=$((TESTS + 1))
510
+ if [[ "$output" == *"$needle"* ]]; then
511
+ echo "FAIL: $desc"
512
+ echo " expected NOT to contain: $needle"
513
+ echo " in: ${output:0:300}"
514
+ FAILURES=$((FAILURES + 1))
515
+ else
516
+ echo "PASS: $desc"
517
+ fi
518
+ }
519
+
520
+ # --- Create test fixtures ---
521
+ FIXTURES=$(mktemp -d)
522
+ trap 'rm -rf "$FIXTURES"' EXIT
523
+
524
+ # Python file with bare except (triggers lesson-1)
525
+ cat > "$FIXTURES/bare_except.py" <<'EOF'
526
+ try:
527
+ do_something()
528
+ except:
529
+ pass
530
+ EOF
531
+
532
+ # Python file with clean except (should NOT trigger)
533
+ cat > "$FIXTURES/clean_except.py" <<'EOF'
534
+ try:
535
+ do_something()
536
+ except Exception as e:
537
+ logger.error("Failed: %s", e)
538
+ EOF
539
+
540
+ # Shell file with .venv/bin/pip (triggers lesson-6)
541
+ cat > "$FIXTURES/bad_pip.sh" <<'EOF'
542
+ .venv/bin/pip install requests
543
+ EOF
544
+
545
+ # Python file with sqlite3.connect (triggers lesson-5)
546
+ cat > "$FIXTURES/bad_sqlite.py" <<'EOF'
547
+ import sqlite3
548
+ conn = sqlite3.connect("test.db")
549
+ EOF
550
+
551
+ # Python file with hardcoded test count (triggers lesson-4)
552
+ cat > "$FIXTURES/bad_test_count.py" <<'EOF'
553
+ def test_items():
554
+ items = get_all()
555
+ assert len(items) == 42
556
+ EOF
557
+
558
+ # Clean shell file — no violations
559
+ cat > "$FIXTURES/clean.sh" <<'EOF'
560
+ #!/bin/bash
561
+ echo "hello world"
562
+ EOF
563
+
564
+ # Empty file
565
+ touch "$FIXTURES/empty.py"
566
+
567
+ # --- Tests: Exit codes ---
568
+ assert_exit "clean file exits 0" 0 "$LESSON_CHECK" "$FIXTURES/clean.sh"
569
+ assert_exit "bare except exits 1" 1 "$LESSON_CHECK" "$FIXTURES/bare_except.py"
570
+ assert_exit "no files exits 0" 0 "$LESSON_CHECK"
571
+ assert_exit "--help exits 0" 0 "$LESSON_CHECK" --help
572
+
573
+ # --- Tests: Detection accuracy ---
574
+ assert_output_contains "detects bare except" "[lesson-1]" "$LESSON_CHECK" "$FIXTURES/bare_except.py"
575
+ assert_output_not_contains "clean except not detected" "[lesson-1]" "$LESSON_CHECK" "$FIXTURES/clean_except.py"
576
+ assert_output_contains "detects .venv/bin/pip" "[lesson-6]" "$LESSON_CHECK" "$FIXTURES/bad_pip.sh"
577
+ assert_output_contains "detects sqlite3.connect" "[lesson-5]" "$LESSON_CHECK" "$FIXTURES/bad_sqlite.py"
578
+ assert_output_contains "detects hardcoded test count" "[lesson-4]" "$LESSON_CHECK" "$FIXTURES/bad_test_count.py"
579
+
580
+ # --- Tests: Language filtering ---
581
+ # lesson-1 is python-only — should NOT trigger on .sh files even if they contain "except:"
582
+ cat > "$FIXTURES/except_in_shell.sh" <<'EOF'
583
+ # This has except: in a comment
584
+ except:
585
+ EOF
586
+ assert_output_not_contains "python lesson skips .sh files" "[lesson-1]" "$LESSON_CHECK" "$FIXTURES/except_in_shell.sh"
587
+
588
+ # lesson-6 is shell-only — should NOT trigger on .py files
589
+ cat > "$FIXTURES/pip_in_python.py" <<'EOF'
590
+ # .venv/bin/pip is mentioned in a comment
591
+ path = ".venv/bin/pip"
592
+ EOF
593
+ assert_output_not_contains "shell lesson skips .py files" "[lesson-6]" "$LESSON_CHECK" "$FIXTURES/pip_in_python.py"
594
+
595
+ # --- Tests: Multiple files ---
596
+ assert_output_contains "multiple files: finds violation in first" "[lesson-1]" "$LESSON_CHECK" "$FIXTURES/bare_except.py" "$FIXTURES/clean.sh"
597
+ assert_exit "multiple files with violation exits 1" 1 "$LESSON_CHECK" "$FIXTURES/bare_except.py" "$FIXTURES/clean.sh"
598
+
599
+ # --- Tests: Stdin pipe mode ---
600
+ assert_output_contains "stdin pipe detects violation" "[lesson-1]" bash -c "echo '$FIXTURES/bare_except.py' | $LESSON_CHECK"
601
+
602
+ # --- Tests: --help shows dynamic lessons ---
603
+ assert_output_contains "--help shows lesson-1" "[lesson-1]" "$LESSON_CHECK" --help
604
+ assert_output_contains "--help shows lesson-6" "[lesson-6]" "$LESSON_CHECK" --help
605
+
606
+ # --- Tests: Empty and nonexistent files ---
607
+ assert_exit "empty file exits 0" 0 "$LESSON_CHECK" "$FIXTURES/empty.py"
608
+ assert_exit "nonexistent file exits 0" 0 "$LESSON_CHECK" "$FIXTURES/does_not_exist.py"
609
+
610
+ # --- Tests: Violation count ---
611
+ assert_output_contains "reports violation count" "violation(s) found" "$LESSON_CHECK" "$FIXTURES/bare_except.py"
612
+ assert_output_contains "clean reports clean" "lesson-check: clean" "$LESSON_CHECK" "$FIXTURES/clean.sh"
613
+
614
+ # --- Summary ---
615
+ echo ""
616
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
617
+ if [[ $FAILURES -gt 0 ]]; then
618
+ echo "FAILURES: $FAILURES"
619
+ exit 1
620
+ fi
621
+ echo "ALL PASSED"
622
+ ```
623
+
624
+ **Step 2: Run the test**
625
+
626
+ ```bash
627
+ bash scripts/tests/test-lesson-check.sh
628
+ ```
629
+
630
+ Expected: All tests pass. If any fail, fix lesson-check.sh or the test expectations.
631
+
632
+ **Step 3: Verify existing tests still pass**
633
+
634
+ ```bash
635
+ bash scripts/tests/run-all-tests.sh
636
+ ```
637
+
638
+ Expected: 109/109 still pass (new test file won't be picked up by `test-run-plan-*.sh` glob)
639
+
640
+ **Step 4: Update run-all-tests.sh to also discover test-lesson-check.sh and other new test files**
641
+
642
+ Modify `scripts/tests/run-all-tests.sh` line 13 — change the glob from `test-run-plan-*.sh` to `test-*.sh`:
643
+
644
+ ```bash
645
+ # Old:
646
+ mapfile -t TEST_FILES < <(find "$SCRIPT_DIR" -maxdepth 1 -name "test-run-plan-*.sh" -type f | sort)
647
+ # New:
648
+ mapfile -t TEST_FILES < <(find "$SCRIPT_DIR" -maxdepth 1 -name "test-*.sh" -type f | sort)
649
+ ```
650
+
651
+ **Step 5: Run updated test runner**
652
+
653
+ ```bash
654
+ bash scripts/tests/run-all-tests.sh
655
+ ```
656
+
657
+ Expected: 109 + new tests all pass (8 test files now)
658
+
659
+ **Step 6: Commit**
660
+
661
+ ```bash
662
+ git add scripts/tests/test-lesson-check.sh scripts/tests/run-all-tests.sh
663
+ git commit -m "test: add comprehensive tests for lesson-check.sh"
664
+ ```
665
+
666
+ ---
667
+
668
+ ## Batch 6: stop-hook.sh + setup-ralph-loop.sh Tests (**CRITICAL — A/B Competitive**)
669
+
670
+ ### Task 11: Create test-stop-hook.sh
671
+
672
+ **Files:**
673
+ - Create: `scripts/tests/test-stop-hook.sh`
674
+ - Read: `hooks/stop-hook.sh`
675
+
676
+ **Step 1: Write test-stop-hook.sh**
677
+
678
+ Create `scripts/tests/test-stop-hook.sh`:
679
+
680
+ ```bash
681
+ #!/usr/bin/env bash
682
+ # Test hooks/stop-hook.sh — state file parsing, iteration, completion promise, JSON output
683
+ set -euo pipefail
684
+
685
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
686
+ STOP_HOOK="$SCRIPT_DIR/../../hooks/stop-hook.sh"
687
+
688
+ FAILURES=0
689
+ TESTS=0
690
+
691
+ assert_exit() {
692
+ local desc="$1" expected_exit="$2"
693
+ shift 2
694
+ local actual_exit=0
695
+ local output
696
+ output=$("$@" 2>&1) || actual_exit=$?
697
+ TESTS=$((TESTS + 1))
698
+ if [[ "$actual_exit" -ne "$expected_exit" ]]; then
699
+ echo "FAIL: $desc"
700
+ echo " expected exit: $expected_exit"
701
+ echo " actual exit: $actual_exit"
702
+ echo " output: ${output:0:500}"
703
+ FAILURES=$((FAILURES + 1))
704
+ else
705
+ echo "PASS: $desc"
706
+ fi
707
+ }
708
+
709
+ assert_output_contains() {
710
+ local desc="$1" needle="$2"
711
+ shift 2
712
+ local output
713
+ output=$("$@" 2>&1) || true
714
+ TESTS=$((TESTS + 1))
715
+ if [[ "$output" != *"$needle"* ]]; then
716
+ echo "FAIL: $desc"
717
+ echo " expected to contain: $needle"
718
+ echo " in: ${output:0:500}"
719
+ FAILURES=$((FAILURES + 1))
720
+ else
721
+ echo "PASS: $desc"
722
+ fi
723
+ }
724
+
725
+ assert_json_field() {
726
+ local desc="$1" field="$2" expected="$3" json="$4"
727
+ local actual
728
+ actual=$(echo "$json" | jq -r "$field" 2>/dev/null || echo "PARSE_ERROR")
729
+ TESTS=$((TESTS + 1))
730
+ if [[ "$actual" != "$expected" ]]; then
731
+ echo "FAIL: $desc"
732
+ echo " field: $field"
733
+ echo " expected: $expected"
734
+ echo " actual: $actual"
735
+ FAILURES=$((FAILURES + 1))
736
+ else
737
+ echo "PASS: $desc"
738
+ fi
739
+ }
740
+
741
+ # --- Setup ---
742
+ WORK=$(mktemp -d)
743
+ trap 'rm -rf "$WORK"' EXIT
744
+ cd "$WORK"
745
+
746
+ # --- Test: No state file = allow exit ---
747
+ assert_exit "no state file exits 0" 0 bash -c "echo '{}' | bash '$STOP_HOOK'"
748
+
749
+ # --- Test: State file with max iterations reached ---
750
+ mkdir -p .claude
751
+ cat > .claude/ralph-loop.local.md <<'STATE'
752
+ ---
753
+ active: true
754
+ iteration: 10
755
+ max_iterations: 10
756
+ completion_promise: null
757
+ started_at: "2026-01-01T00:00:00Z"
758
+ ---
759
+
760
+ Build something
761
+ STATE
762
+
763
+ assert_exit "max iterations reached exits 0" 0 bash -c "echo '{}' | bash '$STOP_HOOK'"
764
+ # State file should be removed
765
+ TESTS=$((TESTS + 1))
766
+ if [[ -f .claude/ralph-loop.local.md ]]; then
767
+ echo "FAIL: state file should be removed at max iterations"
768
+ FAILURES=$((FAILURES + 1))
769
+ else
770
+ echo "PASS: state file removed at max iterations"
771
+ fi
772
+
773
+ # --- Test: Corrupted state file (non-numeric iteration) ---
774
+ mkdir -p .claude
775
+ cat > .claude/ralph-loop.local.md <<'STATE'
776
+ ---
777
+ active: true
778
+ iteration: abc
779
+ max_iterations: 10
780
+ completion_promise: null
781
+ started_at: "2026-01-01T00:00:00Z"
782
+ ---
783
+
784
+ Build something
785
+ STATE
786
+
787
+ assert_exit "corrupted iteration exits 0" 0 bash -c "echo '{}' | bash '$STOP_HOOK'"
788
+ assert_output_contains "corrupted iteration warns" "corrupted" bash -c "echo '{}' | bash '$STOP_HOOK'" || true
789
+
790
+ # --- Test: Active loop with transcript containing completion promise ---
791
+ mkdir -p .claude
792
+ cat > .claude/ralph-loop.local.md <<'STATE'
793
+ ---
794
+ active: true
795
+ iteration: 3
796
+ max_iterations: 0
797
+ completion_promise: "ALL_TESTS_PASS"
798
+ started_at: "2026-01-01T00:00:00Z"
799
+ ---
800
+
801
+ Build and test everything
802
+ STATE
803
+
804
+ # Create mock transcript with completion promise
805
+ TRANSCRIPT="$WORK/transcript.jsonl"
806
+ cat > "$TRANSCRIPT" <<TRANSCRIPT_EOF
807
+ {"role":"user","message":{"content":[{"type":"text","text":"start"}]}}
808
+ {"role":"assistant","message":{"content":[{"type":"text","text":"Done! <promise>ALL_TESTS_PASS</promise>"}]}}
809
+ TRANSCRIPT_EOF
810
+
811
+ HOOK_INPUT=$(jq -n --arg tp "$TRANSCRIPT" '{"transcript_path": $tp}')
812
+ OUTPUT=$(echo "$HOOK_INPUT" | bash "$STOP_HOOK" 2>&1) || true
813
+ TESTS=$((TESTS + 1))
814
+ if [[ "$OUTPUT" == *"Detected"* ]]; then
815
+ echo "PASS: completion promise detected"
816
+ else
817
+ echo "FAIL: completion promise not detected"
818
+ echo " output: ${OUTPUT:0:500}"
819
+ FAILURES=$((FAILURES + 1))
820
+ fi
821
+
822
+ # --- Test: Active loop WITHOUT completion promise in transcript → block and continue ---
823
+ mkdir -p .claude
824
+ cat > .claude/ralph-loop.local.md <<'STATE'
825
+ ---
826
+ active: true
827
+ iteration: 3
828
+ max_iterations: 0
829
+ completion_promise: "ALL_TESTS_PASS"
830
+ started_at: "2026-01-01T00:00:00Z"
831
+ ---
832
+
833
+ Build and test everything
834
+ STATE
835
+
836
+ TRANSCRIPT2="$WORK/transcript2.jsonl"
837
+ cat > "$TRANSCRIPT2" <<TRANSCRIPT_EOF
838
+ {"role":"user","message":{"content":[{"type":"text","text":"start"}]}}
839
+ {"role":"assistant","message":{"content":[{"type":"text","text":"Still working on it..."}]}}
840
+ TRANSCRIPT_EOF
841
+
842
+ HOOK_INPUT2=$(jq -n --arg tp "$TRANSCRIPT2" '{"transcript_path": $tp}')
843
+ OUTPUT2=$(echo "$HOOK_INPUT2" | bash "$STOP_HOOK" 2>&1) || true
844
+
845
+ # Should output JSON with "decision": "block"
846
+ assert_json_field "block decision when promise not found" ".decision" "block" "$OUTPUT2"
847
+ assert_json_field "reason contains prompt" ".reason" "Build and test everything" "$OUTPUT2"
848
+
849
+ # Iteration should be incremented
850
+ TESTS=$((TESTS + 1))
851
+ if [[ -f .claude/ralph-loop.local.md ]]; then
852
+ NEW_ITER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' .claude/ralph-loop.local.md | grep '^iteration:' | sed 's/iteration: *//')
853
+ if [[ "$NEW_ITER" == "4" ]]; then
854
+ echo "PASS: iteration incremented to 4"
855
+ else
856
+ echo "FAIL: iteration should be 4, got $NEW_ITER"
857
+ FAILURES=$((FAILURES + 1))
858
+ fi
859
+ else
860
+ echo "FAIL: state file should still exist"
861
+ FAILURES=$((FAILURES + 1))
862
+ fi
863
+
864
+ # --- Summary ---
865
+ echo ""
866
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
867
+ if [[ $FAILURES -gt 0 ]]; then
868
+ echo "FAILURES: $FAILURES"
869
+ exit 1
870
+ fi
871
+ echo "ALL PASSED"
872
+ ```
873
+
874
+ **Step 2: Run test**
875
+
876
+ ```bash
877
+ bash scripts/tests/test-stop-hook.sh
878
+ ```
879
+
880
+ Expected: All pass
881
+
882
+ **Step 3: Commit**
883
+
884
+ ```bash
885
+ git add scripts/tests/test-stop-hook.sh
886
+ git commit -m "test: add comprehensive tests for stop-hook.sh"
887
+ ```
888
+
889
+ ### Task 12: Create test-setup-ralph-loop.sh
890
+
891
+ **Files:**
892
+ - Create: `scripts/tests/test-setup-ralph-loop.sh`
893
+ - Read: `scripts/setup-ralph-loop.sh`
894
+
895
+ **Step 1: Write test-setup-ralph-loop.sh**
896
+
897
+ Create `scripts/tests/test-setup-ralph-loop.sh`:
898
+
899
+ ```bash
900
+ #!/usr/bin/env bash
901
+ # Test scripts/setup-ralph-loop.sh — state file creation, arg parsing, error handling
902
+ set -euo pipefail
903
+
904
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
905
+ SETUP_RALPH="$SCRIPT_DIR/../setup-ralph-loop.sh"
906
+
907
+ FAILURES=0
908
+ TESTS=0
909
+
910
+ assert_exit() {
911
+ local desc="$1" expected_exit="$2"
912
+ shift 2
913
+ local actual_exit=0
914
+ local output
915
+ output=$("$@" 2>&1) || actual_exit=$?
916
+ TESTS=$((TESTS + 1))
917
+ if [[ "$actual_exit" -ne "$expected_exit" ]]; then
918
+ echo "FAIL: $desc"
919
+ echo " expected exit: $expected_exit"
920
+ echo " actual exit: $actual_exit"
921
+ echo " output: ${output:0:300}"
922
+ FAILURES=$((FAILURES + 1))
923
+ else
924
+ echo "PASS: $desc"
925
+ fi
926
+ }
927
+
928
+ assert_output_contains() {
929
+ local desc="$1" needle="$2"
930
+ shift 2
931
+ local output
932
+ output=$("$@" 2>&1) || true
933
+ TESTS=$((TESTS + 1))
934
+ if [[ "$output" != *"$needle"* ]]; then
935
+ echo "FAIL: $desc"
936
+ echo " expected to contain: $needle"
937
+ echo " in: ${output:0:300}"
938
+ FAILURES=$((FAILURES + 1))
939
+ else
940
+ echo "PASS: $desc"
941
+ fi
942
+ }
943
+
944
+ assert_file_contains() {
945
+ local desc="$1" needle="$2" filepath="$3"
946
+ TESTS=$((TESTS + 1))
947
+ if [[ ! -f "$filepath" ]]; then
948
+ echo "FAIL: $desc (file not found: $filepath)"
949
+ FAILURES=$((FAILURES + 1))
950
+ elif grep -q "$needle" "$filepath"; then
951
+ echo "PASS: $desc"
952
+ else
953
+ echo "FAIL: $desc"
954
+ echo " expected file to contain: $needle"
955
+ FAILURES=$((FAILURES + 1))
956
+ fi
957
+ }
958
+
959
+ # --- Setup ---
960
+ WORK=$(mktemp -d)
961
+ trap 'rm -rf "$WORK"' EXIT
962
+
963
+ # --- Test: --help exits 0 ---
964
+ assert_exit "--help exits 0" 0 bash "$SETUP_RALPH" --help
965
+
966
+ # --- Test: No prompt exits 1 ---
967
+ assert_exit "no prompt exits 1" 1 bash -c "cd '$WORK' && bash '$SETUP_RALPH'"
968
+
969
+ # --- Test: Invalid --max-iterations exits 1 ---
970
+ assert_exit "non-numeric max-iterations exits 1" 1 bash -c "cd '$WORK' && bash '$SETUP_RALPH' Build something --max-iterations abc"
971
+
972
+ # --- Test: Basic prompt creates state file ---
973
+ rm -rf "$WORK/.claude"
974
+ (cd "$WORK" && bash "$SETUP_RALPH" "Build a todo API") >/dev/null 2>&1
975
+ TESTS=$((TESTS + 1))
976
+ if [[ -f "$WORK/.claude/ralph-loop.local.md" ]]; then
977
+ echo "PASS: state file created"
978
+ else
979
+ echo "FAIL: state file not created"
980
+ FAILURES=$((FAILURES + 1))
981
+ fi
982
+
983
+ # --- Test: State file has correct frontmatter ---
984
+ assert_file_contains "state has active: true" "active: true" "$WORK/.claude/ralph-loop.local.md"
985
+ assert_file_contains "state has iteration: 1" "iteration: 1" "$WORK/.claude/ralph-loop.local.md"
986
+ assert_file_contains "state has max_iterations: 0" "max_iterations: 0" "$WORK/.claude/ralph-loop.local.md"
987
+ assert_file_contains "state has prompt text" "Build a todo API" "$WORK/.claude/ralph-loop.local.md"
988
+
989
+ # --- Test: With --max-iterations and --completion-promise ---
990
+ rm -rf "$WORK/.claude"
991
+ (cd "$WORK" && bash "$SETUP_RALPH" "Fix the auth bug" --max-iterations 20 --completion-promise "DONE") >/dev/null 2>&1
992
+ assert_file_contains "max-iterations in state" "max_iterations: 20" "$WORK/.claude/ralph-loop.local.md"
993
+ assert_file_contains "completion-promise in state" 'completion_promise: "DONE"' "$WORK/.claude/ralph-loop.local.md"
994
+ assert_file_contains "prompt in state" "Fix the auth bug" "$WORK/.claude/ralph-loop.local.md"
995
+
996
+ # --- Test: Multi-word prompt without quotes ---
997
+ rm -rf "$WORK/.claude"
998
+ (cd "$WORK" && bash "$SETUP_RALPH" Build a todo API with tests) >/dev/null 2>&1
999
+ assert_file_contains "multi-word prompt joined" "Build a todo API with tests" "$WORK/.claude/ralph-loop.local.md"
1000
+
1001
+ # --- Test: Output contains activation message ---
1002
+ rm -rf "$WORK/.claude"
1003
+ assert_output_contains "output shows activated" "Ralph loop activated" bash -c "cd '$WORK' && bash '$SETUP_RALPH' Test prompt"
1004
+
1005
+ # --- Summary ---
1006
+ echo ""
1007
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
1008
+ if [[ $FAILURES -gt 0 ]]; then
1009
+ echo "FAILURES: $FAILURES"
1010
+ exit 1
1011
+ fi
1012
+ echo "ALL PASSED"
1013
+ ```
1014
+
1015
+ **Step 2: Run test**
1016
+
1017
+ ```bash
1018
+ bash scripts/tests/test-setup-ralph-loop.sh
1019
+ ```
1020
+
1021
+ Expected: All pass
1022
+
1023
+ **Step 3: Commit**
1024
+
1025
+ ```bash
1026
+ git add scripts/tests/test-setup-ralph-loop.sh
1027
+ git commit -m "test: add tests for setup-ralph-loop.sh"
1028
+ ```
1029
+
1030
+ ---
1031
+
1032
+ ## Batch 7: quality-gate.sh + Utility Tests
1033
+
1034
+ ### Task 13: Create test-quality-gate.sh for orchestration logic
1035
+
1036
+ **Files:**
1037
+ - Create: `scripts/tests/test-quality-gate.sh`
1038
+ - Read: `scripts/quality-gate.sh`
1039
+
1040
+ **Step 1: Write test-quality-gate.sh**
1041
+
1042
+ Create `scripts/tests/test-quality-gate.sh`:
1043
+
1044
+ ```bash
1045
+ #!/usr/bin/env bash
1046
+ # Test scripts/quality-gate.sh — CLI args, test runner detection, exit codes
1047
+ set -euo pipefail
1048
+
1049
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1050
+ QUALITY_GATE="$SCRIPT_DIR/../quality-gate.sh"
1051
+
1052
+ FAILURES=0
1053
+ TESTS=0
1054
+
1055
+ assert_exit() {
1056
+ local desc="$1" expected_exit="$2"
1057
+ shift 2
1058
+ local actual_exit=0
1059
+ local output
1060
+ output=$("$@" 2>&1) || actual_exit=$?
1061
+ TESTS=$((TESTS + 1))
1062
+ if [[ "$actual_exit" -ne "$expected_exit" ]]; then
1063
+ echo "FAIL: $desc"
1064
+ echo " expected exit: $expected_exit"
1065
+ echo " actual exit: $actual_exit"
1066
+ echo " output: ${output:0:300}"
1067
+ FAILURES=$((FAILURES + 1))
1068
+ else
1069
+ echo "PASS: $desc"
1070
+ fi
1071
+ }
1072
+
1073
+ assert_output_contains() {
1074
+ local desc="$1" needle="$2"
1075
+ shift 2
1076
+ local output
1077
+ output=$("$@" 2>&1) || true
1078
+ TESTS=$((TESTS + 1))
1079
+ if [[ "$output" != *"$needle"* ]]; then
1080
+ echo "FAIL: $desc"
1081
+ echo " expected to contain: $needle"
1082
+ echo " in: ${output:0:300}"
1083
+ FAILURES=$((FAILURES + 1))
1084
+ else
1085
+ echo "PASS: $desc"
1086
+ fi
1087
+ }
1088
+
1089
+ # --- Test: --help exits 0 ---
1090
+ assert_exit "--help exits 0" 0 "$QUALITY_GATE" --help
1091
+
1092
+ # --- Test: No --project-root exits 1 ---
1093
+ assert_exit "no project-root exits 1" 1 "$QUALITY_GATE"
1094
+
1095
+ # --- Test: Nonexistent directory exits 1 ---
1096
+ assert_exit "nonexistent dir exits 1" 1 "$QUALITY_GATE" --project-root /tmp/nonexistent-dir-$$
1097
+
1098
+ # --- Test: Unknown option exits 1 ---
1099
+ assert_exit "unknown option exits 1" 1 "$QUALITY_GATE" --unknown-flag
1100
+
1101
+ # --- Test: Clean git repo with no test suite passes ---
1102
+ MOCK=$(mktemp -d)
1103
+ trap 'rm -rf "$MOCK"' EXIT
1104
+ cd "$MOCK"
1105
+ git init -q
1106
+ echo "hello" > file.txt
1107
+ git add file.txt && git commit -q -m "init"
1108
+ cd - >/dev/null
1109
+
1110
+ assert_exit "clean repo passes" 0 "$QUALITY_GATE" --project-root "$MOCK"
1111
+ assert_output_contains "skips test suite when none detected" "No test suite detected" "$QUALITY_GATE" --project-root "$MOCK"
1112
+ assert_output_contains "shows ALL PASSED" "ALL PASSED" "$QUALITY_GATE" --project-root "$MOCK"
1113
+ assert_output_contains "shows Memory" "Memory" "$QUALITY_GATE" --project-root "$MOCK"
1114
+
1115
+ # --- Test: help text mentions required options ---
1116
+ assert_output_contains "help mentions --project-root" "--project-root" "$QUALITY_GATE" --help
1117
+ assert_output_contains "help mentions lesson check" "Lesson check" "$QUALITY_GATE" --help
1118
+
1119
+ # --- Summary ---
1120
+ echo ""
1121
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
1122
+ if [[ $FAILURES -gt 0 ]]; then
1123
+ echo "FAILURES: $FAILURES"
1124
+ exit 1
1125
+ fi
1126
+ echo "ALL PASSED"
1127
+ ```
1128
+
1129
+ **Step 2: Run test**
1130
+
1131
+ ```bash
1132
+ bash scripts/tests/test-quality-gate.sh
1133
+ ```
1134
+
1135
+ **Step 3: Commit**
1136
+
1137
+ ```bash
1138
+ git add scripts/tests/test-quality-gate.sh
1139
+ git commit -m "test: add orchestration tests for quality-gate.sh"
1140
+ ```
1141
+
1142
+ ---
1143
+
1144
+ ## Batch 8: Low-Risk Validation Tests + Integration Wiring
1145
+
1146
+ ### Task 14: Create test-lesson-schema.sh (reusable lesson file validator)
1147
+
1148
+ **Files:**
1149
+ - Create: `scripts/tests/test-lesson-schema.sh`
1150
+
1151
+ **Step 1: Write test-lesson-schema.sh**
1152
+
1153
+ Create `scripts/tests/test-lesson-schema.sh`:
1154
+
1155
+ ```bash
1156
+ #!/usr/bin/env bash
1157
+ # Test that all lesson files in docs/lessons/ have valid YAML frontmatter
1158
+ set -euo pipefail
1159
+
1160
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1161
+ LESSONS_DIR="$SCRIPT_DIR/../../docs/lessons"
1162
+
1163
+ FAILURES=0
1164
+ TESTS=0
1165
+
1166
+ assert_true() {
1167
+ local desc="$1" condition="$2"
1168
+ TESTS=$((TESTS + 1))
1169
+ if eval "$condition"; then
1170
+ echo "PASS: $desc"
1171
+ else
1172
+ echo "FAIL: $desc"
1173
+ FAILURES=$((FAILURES + 1))
1174
+ fi
1175
+ }
1176
+
1177
+ VALID_SEVERITIES="blocker should-fix nice-to-have"
1178
+ VALID_TYPES="syntactic semantic"
1179
+ VALID_CATEGORIES="async-traps resource-lifecycle silent-failures integration-boundaries test-anti-patterns performance"
1180
+
1181
+ for f in "$LESSONS_DIR"/[0-9]*.md; do
1182
+ [[ -f "$f" ]] || continue
1183
+ base=$(basename "$f")
1184
+
1185
+ # Extract frontmatter
1186
+ fm=$(awk '/^---$/{c++; if(c==2) exit} c==1 && !/^---$/{print}' "$f")
1187
+
1188
+ # Required top-level fields
1189
+ id=$(echo "$fm" | grep '^id:' | sed 's/^id:[[:space:]]*//')
1190
+ title=$(echo "$fm" | grep '^title:' | sed 's/^title:[[:space:]]*//' | sed 's/^["'"'"']//;s/["'"'"']$//')
1191
+ severity=$(echo "$fm" | grep '^severity:' | sed 's/^severity:[[:space:]]*//')
1192
+ languages=$(echo "$fm" | grep '^languages:' | sed 's/^languages:[[:space:]]*//')
1193
+ category=$(echo "$fm" | grep '^category:' | sed 's/^category:[[:space:]]*//')
1194
+ fix=$(echo "$fm" | grep '^fix:' | sed 's/^fix:[[:space:]]*//')
1195
+
1196
+ # Nested pattern fields
1197
+ ptype=$(echo "$fm" | awk '/^pattern:/{ip=1;next} ip && /^[^[:space:]]/{ip=0} ip && /^[[:space:]]+type:/{sub(/^[[:space:]]+type:[[:space:]]+/,""); print}')
1198
+
1199
+ assert_true "$base: has id" '[[ -n "$id" ]]'
1200
+ assert_true "$base: id is numeric" '[[ "$id" =~ ^[0-9]+$ ]]'
1201
+ assert_true "$base: has title" '[[ -n "$title" ]]'
1202
+ assert_true "$base: has severity" '[[ -n "$severity" ]]'
1203
+ assert_true "$base: severity is valid" '[[ "$VALID_SEVERITIES" == *"$severity"* ]]'
1204
+ assert_true "$base: has languages" '[[ -n "$languages" ]]'
1205
+ assert_true "$base: has category" '[[ -n "$category" ]]'
1206
+ assert_true "$base: category is valid" '[[ "$VALID_CATEGORIES" == *"$category"* ]]'
1207
+ assert_true "$base: has pattern.type" '[[ -n "$ptype" ]]'
1208
+ assert_true "$base: pattern.type is valid" '[[ "$VALID_TYPES" == *"$ptype"* ]]'
1209
+ assert_true "$base: has fix" '[[ -n "$fix" ]]'
1210
+
1211
+ # If syntactic, must have regex
1212
+ if [[ "$ptype" == "syntactic" ]]; then
1213
+ pregex=$(echo "$fm" | awk '/^pattern:/{ip=1;next} ip && /^[^[:space:]]/{ip=0} ip && /^[[:space:]]+regex:/{sub(/^[[:space:]]+regex:[[:space:]]+/,""); gsub(/^["'"'"']|["'"'"']$/,""); print}')
1214
+ assert_true "$base: syntactic has regex" '[[ -n "$pregex" ]]'
1215
+
1216
+ # Test regex compiles with grep -P
1217
+ pregex_unesc="${pregex//\\\\/\\}"
1218
+ compile_ok=true
1219
+ echo "" | grep -P "$pregex_unesc" >/dev/null 2>&1 || { [[ $? -le 1 ]] || compile_ok=false; }
1220
+ assert_true "$base: regex compiles" '$compile_ok'
1221
+ fi
1222
+ done
1223
+
1224
+ # --- Summary ---
1225
+ echo ""
1226
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
1227
+ if [[ $FAILURES -gt 0 ]]; then
1228
+ echo "FAILURES: $FAILURES"
1229
+ exit 1
1230
+ fi
1231
+ echo "ALL PASSED"
1232
+ ```
1233
+
1234
+ **Step 2: Run test**
1235
+
1236
+ ```bash
1237
+ bash scripts/tests/test-lesson-schema.sh
1238
+ ```
1239
+
1240
+ **Step 3: Commit**
1241
+
1242
+ ```bash
1243
+ git add scripts/tests/test-lesson-schema.sh
1244
+ git commit -m "test: add lesson schema validation tests"
1245
+ ```
1246
+
1247
+ ### Task 15: Create test-plugin-manifests.sh (JSON + frontmatter validation)
1248
+
1249
+ **Files:**
1250
+ - Create: `scripts/tests/test-plugin-manifests.sh`
1251
+
1252
+ **Step 1: Write test-plugin-manifests.sh**
1253
+
1254
+ Create `scripts/tests/test-plugin-manifests.sh`:
1255
+
1256
+ ```bash
1257
+ #!/usr/bin/env bash
1258
+ # Test plugin manifests, hooks.json, skill frontmatters, and command frontmatters
1259
+ set -euo pipefail
1260
+
1261
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1262
+ REPO_ROOT="$SCRIPT_DIR/../.."
1263
+
1264
+ FAILURES=0
1265
+ TESTS=0
1266
+
1267
+ assert_true() {
1268
+ local desc="$1" condition="$2"
1269
+ TESTS=$((TESTS + 1))
1270
+ if eval "$condition"; then
1271
+ echo "PASS: $desc"
1272
+ else
1273
+ echo "FAIL: $desc"
1274
+ FAILURES=$((FAILURES + 1))
1275
+ fi
1276
+ }
1277
+
1278
+ # --- JSON validity ---
1279
+ assert_true "plugin.json is valid JSON" 'jq . "$REPO_ROOT/.claude-plugin/plugin.json" >/dev/null 2>&1'
1280
+ assert_true "marketplace.json is valid JSON" 'jq . "$REPO_ROOT/.claude-plugin/marketplace.json" >/dev/null 2>&1'
1281
+ assert_true "hooks.json is valid JSON" 'jq . "$REPO_ROOT/hooks/hooks.json" >/dev/null 2>&1'
1282
+
1283
+ # --- plugin.json required fields ---
1284
+ assert_true "plugin.json has name" 'jq -e ".name" "$REPO_ROOT/.claude-plugin/plugin.json" >/dev/null 2>&1'
1285
+ assert_true "plugin.json has description" 'jq -e ".description" "$REPO_ROOT/.claude-plugin/plugin.json" >/dev/null 2>&1'
1286
+ assert_true "plugin.json has version" 'jq -e ".version" "$REPO_ROOT/.claude-plugin/plugin.json" >/dev/null 2>&1'
1287
+ assert_true "plugin.json has author" 'jq -e ".author" "$REPO_ROOT/.claude-plugin/plugin.json" >/dev/null 2>&1'
1288
+
1289
+ # --- marketplace.json required fields ---
1290
+ assert_true "marketplace.json has \$schema" 'jq -e ".\"\\$schema\"" "$REPO_ROOT/.claude-plugin/marketplace.json" >/dev/null 2>&1'
1291
+ assert_true "marketplace.json has plugins" 'jq -e ".plugins" "$REPO_ROOT/.claude-plugin/marketplace.json" >/dev/null 2>&1'
1292
+
1293
+ # --- hooks.json structure ---
1294
+ assert_true "hooks.json has hooks.Stop" 'jq -e ".hooks.Stop" "$REPO_ROOT/hooks/hooks.json" >/dev/null 2>&1'
1295
+ assert_true "hooks.json references stop-hook.sh" 'jq -r ".hooks.Stop[0].hooks[0].command" "$REPO_ROOT/hooks/hooks.json" | grep -q "stop-hook.sh"'
1296
+
1297
+ # --- Skill frontmatters ---
1298
+ for f in "$REPO_ROOT"/skills/*/SKILL.md; do
1299
+ base=$(basename "$(dirname "$f")")
1300
+ has_name=$(awk '/^---$/{c++; if(c==2) exit} c==1 && /^name:/{print "yes"}' "$f")
1301
+ has_desc=$(awk '/^---$/{c++; if(c==2) exit} c==1 && /^description:/{print "yes"}' "$f")
1302
+ has_ver=$(awk '/^---$/{c++; if(c==2) exit} c==1 && /^version:/{print "yes"}' "$f")
1303
+ assert_true "skill $base has name" '[[ "$has_name" == "yes" ]]'
1304
+ assert_true "skill $base has description" '[[ "$has_desc" == "yes" ]]'
1305
+ assert_true "skill $base has version" '[[ "$has_ver" == "yes" ]]'
1306
+ done
1307
+
1308
+ # --- Command frontmatters ---
1309
+ for f in "$REPO_ROOT"/commands/*.md; do
1310
+ base=$(basename "$f")
1311
+ has_fm=$(awk '/^---$/{c++} c==2{print "yes"; exit}' "$f")
1312
+ assert_true "command $base has frontmatter" '[[ "$has_fm" == "yes" ]]'
1313
+ done
1314
+
1315
+ # --- Summary ---
1316
+ echo ""
1317
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
1318
+ if [[ $FAILURES -gt 0 ]]; then
1319
+ echo "FAILURES: $FAILURES"
1320
+ exit 1
1321
+ fi
1322
+ echo "ALL PASSED"
1323
+ ```
1324
+
1325
+ **Step 2: Run test**
1326
+
1327
+ ```bash
1328
+ bash scripts/tests/test-plugin-manifests.sh
1329
+ ```
1330
+
1331
+ **Step 3: Commit**
1332
+
1333
+ ```bash
1334
+ git add scripts/tests/test-plugin-manifests.sh
1335
+ git commit -m "test: add plugin manifest and frontmatter validation tests"
1336
+ ```
1337
+
1338
+ ### Task 16: Final integration verification
1339
+
1340
+ **Files:**
1341
+ - Read: all test files
1342
+
1343
+ **Step 1: Run full test suite**
1344
+
1345
+ ```bash
1346
+ bash scripts/tests/run-all-tests.sh
1347
+ ```
1348
+
1349
+ Expected: All tests pass across all test files (old + new)
1350
+
1351
+ **Step 2: Count total tests**
1352
+
1353
+ ```bash
1354
+ bash scripts/tests/run-all-tests.sh 2>&1 | tail -5
1355
+ ```
1356
+
1357
+ Expected: Total should be 170+ (109 existing + 60+ new)
1358
+
1359
+ **Step 3: Run shellcheck one final time**
1360
+
1361
+ ```bash
1362
+ shellcheck -s bash scripts/tests/test-*.sh
1363
+ ```
1364
+
1365
+ Expected: Clean (our test files should also pass shellcheck)
1366
+
1367
+ **Step 4: Commit any final fixes**
1368
+
1369
+ ```bash
1370
+ git add -A
1371
+ git commit -m "test: final integration verification — all tests pass"
1372
+ ```
1373
+
1374
+ **Step 5: Push to remote**
1375
+
1376
+ ```bash
1377
+ git push origin main
1378
+ ```