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,66 @@
1
+ #!/usr/bin/env bash
2
+ # Test failure-digest.sh
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ DIGEST_SCRIPT="$SCRIPT_DIR/../failure-digest.sh"
7
+
8
+ FAILURES=0
9
+ TESTS=0
10
+
11
+ assert_eq() {
12
+ local desc="$1" expected="$2" actual="$3"
13
+ TESTS=$((TESTS + 1))
14
+ if [[ "$expected" != "$actual" ]]; then
15
+ echo "FAIL: $desc"
16
+ echo " expected: $expected"
17
+ echo " actual: $actual"
18
+ FAILURES=$((FAILURES + 1))
19
+ else
20
+ echo "PASS: $desc"
21
+ fi
22
+ }
23
+
24
+ WORK=$(mktemp -d)
25
+ trap 'rm -rf "$WORK"' EXIT
26
+
27
+ # Create a fake log with errors
28
+ cat > "$WORK/batch-1-attempt-1.log" << 'LOG'
29
+ Some setup output...
30
+ FAILED tests/test_auth.py::test_login - AssertionError: expected 200 got 401
31
+ FAILED tests/test_auth.py::test_signup - KeyError: 'email'
32
+ Traceback (most recent call last):
33
+ File "src/auth.py", line 42, in login
34
+ token = generate_token(user)
35
+ TypeError: generate_token() missing 1 required argument: 'secret'
36
+ 3 failed, 10 passed in 5.2s
37
+ LOG
38
+
39
+ # --- Test: extracts failed test names ---
40
+ output=$(bash "$DIGEST_SCRIPT" "$WORK/batch-1-attempt-1.log")
41
+ echo "$output" | grep -q "test_login" && echo "PASS: found test_login" && TESTS=$((TESTS + 1)) || {
42
+ echo "FAIL: missing test_login"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
43
+ }
44
+
45
+ echo "$output" | grep -q "test_signup" && echo "PASS: found test_signup" && TESTS=$((TESTS + 1)) || {
46
+ echo "FAIL: missing test_signup"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
47
+ }
48
+
49
+ # --- Test: extracts error types ---
50
+ echo "$output" | grep -q "TypeError" && echo "PASS: found TypeError" && TESTS=$((TESTS + 1)) || {
51
+ echo "FAIL: missing TypeError"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
52
+ }
53
+
54
+ # --- Test: help flag ---
55
+ bash "$DIGEST_SCRIPT" --help >/dev/null 2>&1
56
+ TESTS=$((TESTS + 1))
57
+ echo "PASS: --help exits cleanly"
58
+
59
+ # === Summary ===
60
+ echo ""
61
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
62
+ if [[ $FAILURES -gt 0 ]]; then
63
+ echo "FAILURES: $FAILURES"
64
+ exit 1
65
+ fi
66
+ echo "ALL PASSED"
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ FAILURES=0
7
+ TESTS=0
8
+
9
+ assert_eq() {
10
+ local desc="$1" expected="$2" actual="$3"
11
+ TESTS=$((TESTS + 1))
12
+ if [[ "$expected" != "$actual" ]]; then
13
+ echo "FAIL: $desc"
14
+ echo " expected: $expected"
15
+ echo " actual: $actual"
16
+ FAILURES=$((FAILURES + 1))
17
+ else
18
+ echo "PASS: $desc"
19
+ fi
20
+ }
21
+
22
+ assert_contains() {
23
+ local desc="$1" needle="$2" haystack="$3"
24
+ TESTS=$((TESTS + 1))
25
+ if [[ "$haystack" == *"$needle"* ]]; then
26
+ echo "PASS: $desc"
27
+ else
28
+ echo "FAIL: $desc"
29
+ echo " expected to contain: $needle"
30
+ FAILURES=$((FAILURES + 1))
31
+ fi
32
+ }
33
+
34
+ WORK=$(mktemp -d)
35
+ trap 'rm -rf "$WORK"' EXIT
36
+
37
+ # Create test lesson files
38
+ mkdir -p "$WORK/lessons"
39
+
40
+ # Syntactic lesson (should be SKIPPED — grep handles these)
41
+ cat > "$WORK/lessons/0001-test.md" << 'LESSON'
42
+ ---
43
+ id: 1
44
+ title: "Bare except"
45
+ severity: blocker
46
+ languages: [python]
47
+ category: silent-failures
48
+ pattern:
49
+ type: syntactic
50
+ regex: "^\\s*except\\s*:"
51
+ description: "bare except"
52
+ fix: "Use specific exception"
53
+ example:
54
+ bad: |
55
+ except:
56
+ pass
57
+ good: |
58
+ except Exception as e:
59
+ logger.error(e)
60
+ ---
61
+ LESSON
62
+
63
+ # Semantic lesson with supported language (should generate rule)
64
+ cat > "$WORK/lessons/0033-async.md" << 'LESSON'
65
+ ---
66
+ id: 33
67
+ title: "Async iteration mutable"
68
+ severity: blocker
69
+ languages: [python]
70
+ category: async-traps
71
+ pattern:
72
+ type: semantic
73
+ description: "async loop iterates over mutable instance attribute"
74
+ fix: "Snapshot with list()"
75
+ example:
76
+ bad: |
77
+ async for item in self.connections:
78
+ await item.send(data)
79
+ good: |
80
+ for item in list(self.connections):
81
+ await item.send(data)
82
+ ---
83
+ LESSON
84
+
85
+ # Unsupported language lesson (should be skipped)
86
+ cat > "$WORK/lessons/0099-go.md" << 'LESSON'
87
+ ---
88
+ id: 99
89
+ title: "Go error ignore"
90
+ severity: blocker
91
+ languages: [go]
92
+ category: silent-failures
93
+ pattern:
94
+ type: semantic
95
+ description: "ignoring error return value"
96
+ fix: "Handle the error"
97
+ example:
98
+ bad: |
99
+ result, _ := doThing()
100
+ good: |
101
+ result, err := doThing()
102
+ ---
103
+ LESSON
104
+
105
+ # Test: generates pattern files from lessons
106
+ "$SCRIPT_DIR/../generate-ast-rules.sh" --lessons-dir "$WORK/lessons" --output-dir "$WORK/patterns"
107
+
108
+ # Syntactic lessons should NOT generate ast-grep rules (grep handles them)
109
+ assert_eq "generate-ast-rules: skips syntactic patterns" "false" \
110
+ "$(test -f "$WORK/patterns/0001-test.yml" && echo true || echo false)"
111
+
112
+ # Semantic lesson with supported language should generate a rule
113
+ assert_eq "generate-ast-rules: generates for semantic python lesson" "true" \
114
+ "$(test -f "$WORK/patterns/0033-async.yml" && echo true || echo false)"
115
+
116
+ # Unsupported language lesson should NOT generate
117
+ assert_eq "generate-ast-rules: skips unsupported language" "false" \
118
+ "$(test -f "$WORK/patterns/0099-go.yml" && echo true || echo false)"
119
+
120
+ # Generated rule should contain lesson metadata
121
+ if [[ -f "$WORK/patterns/0033-async.yml" ]]; then
122
+ rule_content=$(cat "$WORK/patterns/0033-async.yml")
123
+ assert_contains "generate-ast-rules: rule has id" "0033-async" "$rule_content"
124
+ assert_contains "generate-ast-rules: rule has message" "Async iteration mutable" "$rule_content"
125
+ assert_contains "generate-ast-rules: rule has language" "python" "$rule_content"
126
+ fi
127
+
128
+ # Test: --list flag shows what would be generated
129
+ output=$("$SCRIPT_DIR/../generate-ast-rules.sh" --lessons-dir "$WORK/lessons" --list 2>&1)
130
+ assert_contains "generate-ast-rules: list shows lesson info" "lesson" "$output"
131
+ assert_contains "generate-ast-rules: list shows summary" "syntactic" "$output"
132
+
133
+ # Test: default output-dir falls back to scripts/patterns/ when --output-dir omitted
134
+ # Use an empty lessons dir so nothing is written to the production patterns directory
135
+ mkdir -p "$WORK/empty-lessons"
136
+ default_output=$("$SCRIPT_DIR/../generate-ast-rules.sh" --lessons-dir "$WORK/empty-lessons" 2>&1)
137
+ assert_contains "generate-ast-rules: default output-dir references scripts/patterns" "scripts/patterns" "$default_output"
138
+
139
+ echo ""
140
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
141
+ if [[ $FAILURES -gt 0 ]]; then
142
+ echo "FAILURES: $FAILURES"
143
+ exit 1
144
+ fi
145
+ echo "ALL PASSED"
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bash
2
+ # test-helpers.sh — Shared test assertions for validator tests
3
+ # Source this file, don't execute it directly.
4
+
5
+ FAILURES=0
6
+ TESTS=0
7
+
8
+ pass() {
9
+ TESTS=$((TESTS + 1))
10
+ echo "PASS: $1"
11
+ }
12
+
13
+ fail() {
14
+ TESTS=$((TESTS + 1))
15
+ echo "FAIL: $1"
16
+ FAILURES=$((FAILURES + 1))
17
+ }
18
+
19
+ assert_eq() {
20
+ local desc="$1" expected="$2" actual="$3"
21
+ TESTS=$((TESTS + 1))
22
+ if [[ "$expected" != "$actual" ]]; then
23
+ echo "FAIL: $desc"
24
+ echo " expected: $expected"
25
+ echo " actual: $actual"
26
+ FAILURES=$((FAILURES + 1))
27
+ else
28
+ echo "PASS: $desc"
29
+ fi
30
+ }
31
+
32
+ assert_exit() {
33
+ local desc="$1" expected_exit="$2"
34
+ shift 2
35
+ local actual_exit=0
36
+ "$@" >/dev/null 2>&1 || actual_exit=$?
37
+ TESTS=$((TESTS + 1))
38
+ if [[ "$expected_exit" != "$actual_exit" ]]; then
39
+ echo "FAIL: $desc"
40
+ echo " expected exit: $expected_exit"
41
+ echo " actual exit: $actual_exit"
42
+ FAILURES=$((FAILURES + 1))
43
+ else
44
+ echo "PASS: $desc"
45
+ fi
46
+ }
47
+
48
+ assert_contains() {
49
+ local desc="$1" needle="$2" haystack="$3"
50
+ TESTS=$((TESTS + 1))
51
+ if echo "$haystack" | grep -qF "$needle"; then
52
+ echo "PASS: $desc"
53
+ else
54
+ echo "FAIL: $desc"
55
+ echo " expected to contain: $needle"
56
+ echo " in: $(echo "$haystack" | head -5)"
57
+ FAILURES=$((FAILURES + 1))
58
+ fi
59
+ }
60
+
61
+ assert_not_contains() {
62
+ local desc="$1" needle="$2" haystack="$3"
63
+ TESTS=$((TESTS + 1))
64
+ if echo "$haystack" | grep -qF "$needle"; then
65
+ echo "FAIL: $desc"
66
+ echo " should NOT contain: $needle"
67
+ FAILURES=$((FAILURES + 1))
68
+ else
69
+ echo "PASS: $desc"
70
+ fi
71
+ }
72
+
73
+ # Call at end of test file
74
+ report_results() {
75
+ echo ""
76
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
77
+ if [[ $FAILURES -gt 0 ]]; then
78
+ echo "FAILURES: $FAILURES"
79
+ exit 1
80
+ fi
81
+ echo "ALL PASSED"
82
+ }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # Test scripts/init.sh — project bootstrapper
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7
+ INIT_SCRIPT="$REPO_ROOT/scripts/init.sh"
8
+
9
+ source "$SCRIPT_DIR/test-helpers.sh"
10
+
11
+ # --- Setup temp project ---
12
+ WORK=$(mktemp -d)
13
+ trap 'rm -rf "$WORK"' EXIT
14
+ cd "$WORK"
15
+ git init -q
16
+
17
+ # --- Test 1: init creates tasks/ directory ---
18
+ bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || true
19
+ assert_eq "init creates tasks/ directory" "true" "$([ -d "$WORK/tasks" ] && echo true || echo false)"
20
+
21
+ # --- Test 2: init creates progress.txt ---
22
+ assert_eq "init creates progress.txt" "true" "$([ -f "$WORK/progress.txt" ] && echo true || echo false)"
23
+
24
+ # --- Test 3: init creates logs/ directory ---
25
+ assert_eq "init creates logs/ directory" "true" "$([ -d "$WORK/logs" ] && echo true || echo false)"
26
+
27
+ # --- Test 4: init detects project type ---
28
+ output=$(bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || true)
29
+ assert_contains "init detects project type" "Detected:" "$output"
30
+
31
+ # --- Test 5: init with --quickstart copies quickstart plan ---
32
+ mkdir -p "$WORK/docs/plans"
33
+ bash "$INIT_SCRIPT" --project-root "$WORK" --quickstart 2>&1 || true
34
+ assert_eq "quickstart creates plan file" "true" "$([ -f "$WORK/docs/plans/quickstart.md" ] && echo true || echo false)"
35
+
36
+ # --- Test 6: init fails without --project-root ---
37
+ exit_code=0
38
+ bash "$INIT_SCRIPT" 2>/dev/null || exit_code=$?
39
+ assert_eq "init fails without --project-root" "1" "$exit_code"
40
+
41
+ # --- Test 7: init is idempotent ---
42
+ bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || true
43
+ exit_code=0
44
+ bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || exit_code=$?
45
+ assert_eq "init is idempotent (exit 0 on re-run)" "0" "$exit_code"
46
+
47
+ report_results
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env bash
2
+ # Test lesson-check.sh — anti-pattern detector
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7
+ LESSON_CHECK="$SCRIPT_DIR/../lesson-check.sh"
8
+
9
+ FAILURES=0
10
+ TESTS=0
11
+
12
+ pass() {
13
+ TESTS=$((TESTS + 1))
14
+ echo "PASS: $1"
15
+ }
16
+
17
+ fail() {
18
+ TESTS=$((TESTS + 1))
19
+ echo "FAIL: $1"
20
+ FAILURES=$((FAILURES + 1))
21
+ }
22
+
23
+ # --- Setup temp workspace ---
24
+ WORK=$(mktemp -d)
25
+ trap 'rm -rf "$WORK"' EXIT
26
+
27
+ # --- Test 1: Detects bare except in Python file (lesson 1, uses \s) ---
28
+ cat > "$WORK/bad.py" <<'PY'
29
+ try:
30
+ do_something()
31
+ except:
32
+ pass
33
+ PY
34
+
35
+ # PROJECT_CLAUDE_MD=/dev/null isolates from toolkit's scope tags (language:bash would filter out python lessons)
36
+ output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad.py" 2>&1 || true)
37
+ if echo "$output" | grep -q '\[lesson-1\]'; then
38
+ pass "Detects bare except in Python file (lesson 1, \\s ERE conversion)"
39
+ else
40
+ fail "Should detect bare except in Python file, got: $output"
41
+ fi
42
+
43
+ # --- Test 2: Clean file passes ---
44
+ cat > "$WORK/good.py" <<'PY'
45
+ try:
46
+ do_something()
47
+ except ValueError:
48
+ pass
49
+ PY
50
+
51
+ output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/good.py" 2>&1 || true)
52
+ if echo "$output" | grep -q 'clean'; then
53
+ pass "Clean file reports clean"
54
+ else
55
+ fail "Should report clean for good file, got: $output"
56
+ fi
57
+
58
+ # --- Test 3: PCRE shorthand \d works via ERE conversion (lesson 28, hardcoded IP) ---
59
+ cat > "$WORK/bad_ip.js" <<'JS'
60
+ const url = "http://192.168.1.1/api";
61
+ JS
62
+
63
+ output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad_ip.js" 2>&1 || true)
64
+ if echo "$output" | grep -q '\[lesson-28\]'; then
65
+ pass "PCRE \\d converted to ERE [0-9] detects hardcoded IPs"
66
+ else
67
+ fail "Should detect hardcoded IP via lesson 28, got: $output"
68
+ fi
69
+
70
+ # --- Test 4: Language filtering — Python lesson skips .sh files ---
71
+ cat > "$WORK/not_python.sh" <<'SH'
72
+ except:
73
+ SH
74
+
75
+ output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/not_python.sh" 2>&1 || true)
76
+ if echo "$output" | grep -q '\[lesson-1\]'; then
77
+ fail "Python-only lesson 1 should not match .sh files"
78
+ else
79
+ pass "Python-only lesson correctly skips .sh files"
80
+ fi
81
+
82
+ # --- Test 5: --help works ---
83
+ output=$(bash "$LESSON_CHECK" --help 2>&1 || true)
84
+ if echo "$output" | grep -q 'Usage:'; then
85
+ pass "--help shows usage"
86
+ else
87
+ fail "--help should show usage, got: $output"
88
+ fi
89
+
90
+ # --- Test 6: No files to check (no args, no pipe, no git diff) ---
91
+ output=$(cd "$WORK" && bash "$LESSON_CHECK" 2>&1 || true)
92
+ if echo "$output" | grep -q 'no files to check'; then
93
+ pass "No files gracefully reports nothing to check"
94
+ else
95
+ fail "Should report no files to check, got: $output"
96
+ fi
97
+
98
+ # --- Test 6b: stdin pipe detection — uses -p /dev/stdin not -t 0 (#34) ---
99
+ # This prevents hanging when stdin is a socket (e.g. systemd/cron).
100
+ # The fix: only read stdin when [[ -p /dev/stdin ]] (a named pipe), not
101
+ # whenever [[ ! -t 0 ]] (which includes sockets that never send EOF).
102
+ TESTS=$((TESTS + 1))
103
+ if grep -q '\-p /dev/stdin' "$LESSON_CHECK"; then
104
+ echo "PASS: lesson-check uses -p /dev/stdin (pipe-safe, not socket-blocking)"
105
+ else
106
+ echo "FAIL: lesson-check should use [[ -p /dev/stdin ]] not [[ ! -t 0 ]] for stdin detection (bug #34)"
107
+ FAILURES=$((FAILURES + 1))
108
+ fi
109
+
110
+ # Verify the old ! -t 0 pattern is NOT present in executable code (it caused the socket hang).
111
+ # Filter comment-only lines before checking.
112
+ TESTS=$((TESTS + 1))
113
+ if grep -v '^\s*#' "$LESSON_CHECK" | grep -q '! -t 0'; then
114
+ echo "FAIL: lesson-check still uses '! -t 0' in executable code, which blocks on socket stdin (bug #34)"
115
+ FAILURES=$((FAILURES + 1))
116
+ else
117
+ echo "PASS: lesson-check does not use '! -t 0' in executable code (socket-safe)"
118
+ fi
119
+
120
+ # --- Test 7: No grep -P in any script (portability) ---
121
+ TESTS=$((TESTS + 1))
122
+ # Scan for grep -P or grep -<flags>P usage in scripts/ (excluding comments)
123
+ offenders=$(grep -rn 'grep -[a-zA-Z]*P' "$REPO_ROOT/scripts/" \
124
+ --include='*.sh' \
125
+ | grep -v 'test-lesson-check.sh' \
126
+ | grep -v ':#' \
127
+ | grep -v ':.*# .*grep' \
128
+ || true)
129
+ if [[ -z "$offenders" ]]; then
130
+ echo "PASS: No grep -P found in scripts/ (portability)"
131
+ else
132
+ echo "FAIL: grep -P found in scripts/ — not portable to macOS:"
133
+ echo "$offenders"
134
+ FAILURES=$((FAILURES + 1))
135
+ fi
136
+
137
+ # --- Test 8: All scripts use #!/usr/bin/env bash shebang ---
138
+ TESTS=$((TESTS + 1))
139
+ bad_shebangs=""
140
+ while IFS= read -r script; do
141
+ first_line=$(head -1 "$script")
142
+ if [[ "$first_line" == "#!/bin/bash" ]]; then
143
+ bad_shebangs+=" $script"$'\n'
144
+ fi
145
+ done < <(find "$REPO_ROOT/scripts" -name '*.sh' -type f)
146
+ if [[ -z "$bad_shebangs" ]]; then
147
+ echo "PASS: All scripts use #!/usr/bin/env bash"
148
+ else
149
+ echo "FAIL: Scripts with non-portable #!/bin/bash shebang:"
150
+ echo "$bad_shebangs"
151
+ FAILURES=$((FAILURES + 1))
152
+ fi
153
+
154
+ # --- Test 9: Scope field parsed from lesson YAML ---
155
+ # Create a lesson with scope: [language:python] and verify it's respected
156
+ cat > "$WORK/0999-scoped-lesson.md" <<'LESSON'
157
+ ---
158
+ id: 999
159
+ title: "Test scoped lesson"
160
+ severity: should-fix
161
+ scope: [language:python]
162
+ languages: [python]
163
+ category: silent-failures
164
+ pattern:
165
+ type: syntactic
166
+ regex: "test_scope_marker"
167
+ description: "test marker"
168
+ fix: "test"
169
+ ---
170
+ LESSON
171
+
172
+ # Create a CLAUDE.md with scope tags
173
+ cat > "$WORK/CLAUDE.md" <<'CMD'
174
+ # Test Project
175
+
176
+ ## Scope Tags
177
+ language:python, framework:pytest
178
+ CMD
179
+
180
+ # Python file with the marker — should be detected (scope matches)
181
+ cat > "$WORK/scoped.py" <<'PY'
182
+ test_scope_marker = True
183
+ PY
184
+
185
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" "$WORK/scoped.py" 2>&1 || true)
186
+ if echo "$output" | grep -q '\[lesson-999\]'; then
187
+ pass "Scoped lesson detected when project scope matches"
188
+ else
189
+ fail "Scoped lesson should detect violation when project scope matches, got: $output"
190
+ fi
191
+
192
+ # --- Test 10: Scoped lesson skipped when project scope doesn't match ---
193
+ cat > "$WORK/CLAUDE-noscope.md" <<'CMD'
194
+ # Different Project
195
+
196
+ ## Scope Tags
197
+ domain:ha-aria
198
+ CMD
199
+
200
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" PROJECT_CLAUDE_MD="$WORK/CLAUDE-noscope.md" bash "$LESSON_CHECK" "$WORK/scoped.py" 2>&1 || true)
201
+ if echo "$output" | grep -q '\[lesson-999\]'; then
202
+ fail "Scoped lesson should be SKIPPED when project scope doesn't match"
203
+ else
204
+ pass "Scoped lesson correctly skipped for non-matching project scope"
205
+ fi
206
+
207
+ # --- Test 11: Lesson without scope defaults to universal (backward compat) ---
208
+ # Use the real lesson 1 (no scope field) — should still work as before
209
+ # Isolate from repo's own CLAUDE.md by pointing PROJECT_CLAUDE_MD to /dev/null
210
+ output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad.py" 2>&1 || true)
211
+ if echo "$output" | grep -q '\[lesson-1\]'; then
212
+ pass "Lesson without scope: field defaults to universal (backward compatible)"
213
+ else
214
+ fail "Missing scope: should default to universal, got: $output"
215
+ fi
216
+
217
+ # --- Test 12: --show-scope displays detected project scope ---
218
+ output=$(cd "$WORK" && bash "$LESSON_CHECK" --show-scope 2>&1 || true)
219
+ if echo "$output" | grep -q 'language:python'; then
220
+ pass "--show-scope displays detected project scope"
221
+ else
222
+ fail "--show-scope should display detected scope from CLAUDE.md, got: $output"
223
+ fi
224
+
225
+ # --- Test 13: --all-scopes bypasses scope filtering ---
226
+ # Use a lesson scoped to domain:ha-aria on a python project — should be skipped normally
227
+ cat > "$WORK/0998-ha-lesson.md" <<'LESSON'
228
+ ---
229
+ id: 998
230
+ title: "HA-only test lesson"
231
+ severity: should-fix
232
+ scope: [domain:ha-aria]
233
+ languages: [python]
234
+ category: silent-failures
235
+ pattern:
236
+ type: syntactic
237
+ regex: "ha_scope_marker"
238
+ description: "test marker"
239
+ fix: "test"
240
+ ---
241
+ LESSON
242
+
243
+ cat > "$WORK/ha_file.py" <<'PY'
244
+ ha_scope_marker = True
245
+ PY
246
+
247
+ # Without --all-scopes: lesson 998 should be skipped (project is python, not ha-aria)
248
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" "$WORK/ha_file.py" 2>&1 || true)
249
+ if echo "$output" | grep -q '\[lesson-998\]'; then
250
+ fail "domain:ha-aria lesson should be skipped on a python-only project"
251
+ else
252
+ pass "domain:ha-aria lesson correctly skipped on non-matching project"
253
+ fi
254
+
255
+ # With --all-scopes: lesson 998 should fire
256
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" --all-scopes "$WORK/ha_file.py" 2>&1 || true)
257
+ if echo "$output" | grep -q '\[lesson-998\]'; then
258
+ pass "--all-scopes bypasses scope filtering"
259
+ else
260
+ fail "--all-scopes should bypass scope filtering, got: $output"
261
+ fi
262
+
263
+ # --- Test 14: --scope override replaces CLAUDE.md detection ---
264
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" --scope "domain:ha-aria" "$WORK/ha_file.py" 2>&1 || true)
265
+ if echo "$output" | grep -q '\[lesson-998\]'; then
266
+ pass "--scope override enables matching for specified scope"
267
+ else
268
+ fail "--scope override should enable domain:ha-aria matching, got: $output"
269
+ fi
270
+
271
+ # --- Summary ---
272
+ echo ""
273
+ echo "lesson-check tests: $TESTS run, $((TESTS - FAILURES)) passed, $FAILURES failed"
274
+
275
+ if [[ $FAILURES -gt 0 ]]; then
276
+ exit 1
277
+ fi
278
+ exit 0
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # Test lesson-check.sh — project-local lesson loading (Tier 3)
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7
+ LESSON_CHECK="$REPO_ROOT/scripts/lesson-check.sh"
8
+
9
+ source "$SCRIPT_DIR/test-helpers.sh"
10
+
11
+ # --- Setup: project with local lessons ---
12
+ WORK=$(mktemp -d)
13
+ trap 'rm -rf "$WORK"' EXIT
14
+
15
+ # Create a project-local lesson
16
+ mkdir -p "$WORK/docs/lessons"
17
+ cat > "$WORK/docs/lessons/9901-local-test.md" <<'LESSON'
18
+ ---
19
+ id: 9901
20
+ title: "Test local lesson"
21
+ severity: error
22
+ languages: [python]
23
+ scope: [universal]
24
+ category: testing
25
+ pattern:
26
+ type: syntactic
27
+ regex: "LOCALTEST_BAD_PATTERN"
28
+ fix: "Use LOCALTEST_GOOD_PATTERN instead"
29
+ positive_alternative: "LOCALTEST_GOOD_PATTERN"
30
+ ---
31
+ LESSON
32
+
33
+ # Create a file that triggers the local lesson
34
+ cat > "$WORK/bad.py" <<'PY'
35
+ x = LOCALTEST_BAD_PATTERN
36
+ PY
37
+
38
+ # --- Test: project-local lesson is loaded ---
39
+ output=$(PROJECT_ROOT="$WORK" PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad.py" 2>&1 || true)
40
+ if echo "$output" | grep -q 'lesson-9901'; then
41
+ pass "Project-local lesson detected violation"
42
+ else
43
+ fail "Project-local lesson not loaded, got: $output"
44
+ fi
45
+
46
+ # --- Test: clean file passes with local lessons ---
47
+ cat > "$WORK/good.py" <<'PY'
48
+ x = LOCALTEST_GOOD_PATTERN
49
+ PY
50
+
51
+ exit_code=0
52
+ PROJECT_ROOT="$WORK" PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/good.py" 2>/dev/null || exit_code=$?
53
+ assert_eq "Clean file passes with local lessons" "0" "$exit_code"
54
+
55
+ report_results