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,96 @@
1
+ #!/usr/bin/env bash
2
+ # generate-ast-rules.sh — Generate ast-grep rules from lesson YAML frontmatter
3
+ #
4
+ # Reads lesson files with pattern.type: semantic and supported languages,
5
+ # generates ast-grep YAML rule files in the output directory.
6
+ # Syntactic patterns are skipped (grep handles them via lesson-check.sh).
7
+ #
8
+ # Usage: generate-ast-rules.sh --lessons-dir <dir> [--output-dir <dir>] [--list]
9
+ set -euo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
+ LESSONS_DIR=""
13
+ OUTPUT_DIR=""
14
+ LIST_ONLY=false
15
+
16
+ while [[ $# -gt 0 ]]; do
17
+ case "$1" in
18
+ --lessons-dir) LESSONS_DIR="$2"; shift 2 ;;
19
+ --output-dir) OUTPUT_DIR="$2"; shift 2 ;;
20
+ --list) LIST_ONLY=true; shift ;;
21
+ -h|--help)
22
+ echo "Usage: generate-ast-rules.sh --lessons-dir <dir> [--output-dir <dir>] [--list]"
23
+ exit 0 ;;
24
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
25
+ esac
26
+ done
27
+
28
+ if [[ -z "$LESSONS_DIR" ]]; then
29
+ echo "ERROR: --lessons-dir required" >&2
30
+ exit 1
31
+ fi
32
+
33
+ # Default output directory to scripts/patterns/ (where existing patterns live)
34
+ if [[ -z "$OUTPUT_DIR" ]]; then
35
+ OUTPUT_DIR="$SCRIPT_DIR/patterns"
36
+ fi
37
+
38
+ generated=0
39
+ skipped_syntactic=0
40
+ skipped_unconvertible=0
41
+
42
+ for lesson_file in "$LESSONS_DIR"/*.md; do
43
+ [[ -f "$lesson_file" ]] || continue
44
+ base=$(basename "$lesson_file")
45
+ [[ "$base" == "TEMPLATE.md" || "$base" == "SUMMARY.md" || "$base" == "FRAMEWORK.md" ]] && continue
46
+
47
+ # Extract frontmatter fields via sed
48
+ local_id=$(sed -n '/^---$/,/^---$/{/^id:/s/^id: *//p}' "$lesson_file" | head -1)
49
+ local_type=$(sed -n '/^---$/,/^---$/{/^ type:/s/^ type: *//p}' "$lesson_file" | head -1)
50
+ local_title=$(sed -n '/^---$/,/^---$/{/^title:/s/^title: *"*//p}' "$lesson_file" | head -1 | sed 's/"$//')
51
+ local_langs=$(sed -n '/^---$/,/^---$/{/^languages:/s/^languages: *//p}' "$lesson_file" | head -1)
52
+
53
+ # Skip syntactic patterns (grep handles these)
54
+ if [[ "$local_type" == "syntactic" ]]; then
55
+ skipped_syntactic=$((skipped_syntactic + 1))
56
+ continue
57
+ fi
58
+
59
+ # Only generate for languages ast-grep supports
60
+ if [[ "$local_langs" != *"python"* && "$local_langs" != *"javascript"* && "$local_langs" != *"typescript"* ]]; then
61
+ skipped_unconvertible=$((skipped_unconvertible + 1))
62
+ continue
63
+ fi
64
+
65
+ local_basename=$(basename "$lesson_file" .md)
66
+
67
+ if [[ "$LIST_ONLY" == true ]]; then
68
+ echo " Would generate: $local_basename.yml (lesson $local_id: $local_title)"
69
+ generated=$((generated + 1))
70
+ continue
71
+ fi
72
+
73
+ mkdir -p "$OUTPUT_DIR"
74
+
75
+ # Determine primary language
76
+ local_lang=$(echo "$local_langs" | sed 's/\[//;s/\]//;s/,.*//;s/ //g')
77
+
78
+ # Generate ast-grep rule YAML
79
+ cat > "$OUTPUT_DIR/$local_basename.yml" << RULE
80
+ id: $local_basename
81
+ message: "$local_title"
82
+ severity: warning
83
+ language: $local_lang
84
+ note: "Auto-generated from lesson $local_id. See docs/lessons/$local_basename.md"
85
+ RULE
86
+
87
+ generated=$((generated + 1))
88
+ done
89
+
90
+ if [[ "$LIST_ONLY" == true ]]; then
91
+ echo ""
92
+ echo "Summary: $generated convertible, $skipped_syntactic syntactic (grep), $skipped_unconvertible unsupported language"
93
+ else
94
+ echo "Generated $generated ast-grep rules in $OUTPUT_DIR"
95
+ echo "Skipped: $skipped_syntactic syntactic (grep handles), $skipped_unconvertible unsupported language"
96
+ fi
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # init.sh — Bootstrap a project for use with the Autonomous Coding Toolkit
3
+ #
4
+ # Usage: init.sh --project-root <dir> [--quickstart]
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
8
+ TOOLKIT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9
+ source "$SCRIPT_DIR/lib/common.sh"
10
+
11
+ PROJECT_ROOT=""
12
+ QUICKSTART=false
13
+
14
+ usage() {
15
+ cat <<'USAGE'
16
+ Usage: init.sh --project-root <dir> [--quickstart]
17
+
18
+ Bootstrap a project for the Autonomous Coding Toolkit.
19
+
20
+ Creates:
21
+ tasks/ — PRD and acceptance criteria
22
+ logs/ — Telemetry, routing decisions, failure patterns
23
+ progress.txt — Append-only discovery log
24
+
25
+ Options:
26
+ --project-root <dir> Project directory to initialize (required)
27
+ --quickstart Copy quickstart plan + run quality gate
28
+ --help, -h Show this help
29
+ USAGE
30
+ exit 0
31
+ }
32
+
33
+ while [[ $# -gt 0 ]]; do
34
+ case "$1" in
35
+ --project-root) PROJECT_ROOT="${2:-}"; shift 2 ;;
36
+ --quickstart) QUICKSTART=true; shift ;;
37
+ --help|-h) usage ;;
38
+ *) echo "init: unknown option: $1" >&2; exit 1 ;;
39
+ esac
40
+ done
41
+
42
+ if [[ -z "$PROJECT_ROOT" ]]; then
43
+ echo "init: --project-root is required" >&2
44
+ exit 1
45
+ fi
46
+
47
+ PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
48
+
49
+ echo "Autonomous Coding Toolkit — Project Init"
50
+ echo "========================================="
51
+ echo ""
52
+
53
+ # Detect project type
54
+ project_type=$(detect_project_type "$PROJECT_ROOT")
55
+ echo "Detected: $project_type project"
56
+
57
+ # Create directories
58
+ mkdir -p "$PROJECT_ROOT/tasks"
59
+ mkdir -p "$PROJECT_ROOT/logs"
60
+ mkdir -p "$PROJECT_ROOT/docs/plans"
61
+ echo "Created: tasks/, logs/, docs/plans/"
62
+
63
+ # Create progress.txt if missing
64
+ if [[ ! -f "$PROJECT_ROOT/progress.txt" ]]; then
65
+ echo "# Progress — $(basename "$PROJECT_ROOT")" > "$PROJECT_ROOT/progress.txt"
66
+ echo "# Append-only discovery log. Read at start of each batch." >> "$PROJECT_ROOT/progress.txt"
67
+ echo "" >> "$PROJECT_ROOT/progress.txt"
68
+ echo "Created: progress.txt"
69
+ else
70
+ echo "Exists: progress.txt (skipped)"
71
+ fi
72
+
73
+ # Detect language for scope tags
74
+ scope_lang=""
75
+ case "$project_type" in
76
+ python) scope_lang="language:python" ;;
77
+ node) scope_lang="language:javascript" ;;
78
+ bash) scope_lang="language:bash" ;;
79
+ *) scope_lang="" ;;
80
+ esac
81
+
82
+ # Print next steps
83
+ echo ""
84
+ echo "--- Next Steps ---"
85
+ echo ""
86
+ echo "1. Quality gate: act gate --project-root \"$PROJECT_ROOT\""
87
+ echo "2. Run a plan: act plan docs/plans/your-plan.md"
88
+
89
+ if [[ -n "$scope_lang" ]]; then
90
+ echo ""
91
+ echo "Recommended: Add to your CLAUDE.md:"
92
+ echo " ## Scope Tags"
93
+ echo " $scope_lang"
94
+ fi
95
+
96
+ # Quickstart mode
97
+ if [[ "$QUICKSTART" == true ]]; then
98
+ echo ""
99
+ echo "--- Quickstart ---"
100
+ if [[ -f "$TOOLKIT_ROOT/examples/quickstart-plan.md" ]]; then
101
+ cp "$TOOLKIT_ROOT/examples/quickstart-plan.md" "$PROJECT_ROOT/docs/plans/quickstart.md"
102
+ echo "Copied: docs/plans/quickstart.md"
103
+ echo ""
104
+ echo "Run your first quality-gated execution:"
105
+ echo " act plan docs/plans/quickstart.md"
106
+ else
107
+ echo "WARNING: quickstart-plan.md not found in toolkit" >&2
108
+ fi
109
+ fi
110
+
111
+ echo ""
112
+ echo "Init complete."
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env bash
2
+ # lesson-check.sh — Syntactic anti-pattern detector from lessons learned
3
+ # Dynamically loads checks from docs/lessons/[0-9]*.md (syntactic pattern.type only).
4
+ # Exit 0 if clean, exit 1 with file:line: [lesson-N] format if violations found.
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
8
+ LESSONS_DIR="${LESSONS_DIR:-$SCRIPT_DIR/../docs/lessons}"
9
+
10
+ # Project-local lessons (Tier 3) — loaded alongside bundled lessons.
11
+ # Set PROJECT_ROOT to the project being checked for project-specific anti-patterns.
12
+ PROJECT_LESSONS_DIR=""
13
+ if [[ -n "${PROJECT_ROOT:-}" && -d "${PROJECT_ROOT}/docs/lessons" ]]; then
14
+ _canonical_bundled="$(cd "$LESSONS_DIR" 2>/dev/null && pwd)"
15
+ _canonical_project="$(cd "${PROJECT_ROOT}/docs/lessons" 2>/dev/null && pwd)"
16
+ if [[ "$_canonical_project" != "$_canonical_bundled" ]]; then
17
+ PROJECT_LESSONS_DIR="${PROJECT_ROOT}/docs/lessons"
18
+ fi
19
+ fi
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # parse_lesson <file>
23
+ # shellcheck disable=SC2034 # lesson_severity, lesson_scope parsed for future filtering
24
+ # Sets: lesson_id, lesson_title, lesson_severity, pattern_type, pattern_regex,
25
+ # lesson_languages (space-separated list), lesson_scope (space-separated tags)
26
+ # Returns 1 if the lesson cannot be parsed or has no syntactic pattern.
27
+ # ---------------------------------------------------------------------------
28
+ parse_lesson() {
29
+ local file="$1"
30
+ lesson_id=""
31
+ lesson_title=""
32
+ lesson_severity=""
33
+ pattern_type=""
34
+ pattern_regex=""
35
+ lesson_languages=""
36
+ lesson_scope=""
37
+
38
+ # Parse YAML frontmatter with sed + read (no eval — safe with special chars).
39
+ # Extract text between first two --- delimiters, then parse key: value lines.
40
+ local in_pattern=false
41
+ local line
42
+ while IFS= read -r line; do
43
+ # Detect entry/exit of pattern: block
44
+ if [[ "$line" =~ ^pattern: ]]; then
45
+ in_pattern=true
46
+ continue
47
+ fi
48
+ if [[ "$in_pattern" == true && "$line" =~ ^[^[:space:]] && ! "$line" =~ ^pattern: ]]; then
49
+ in_pattern=false
50
+ fi
51
+
52
+ if [[ "$in_pattern" == false ]]; then
53
+ # Top-level fields
54
+ if [[ "$line" =~ ^id:[[:space:]]+(.*) ]]; then
55
+ lesson_id="${BASH_REMATCH[1]}"
56
+ elif [[ "$line" =~ ^title:[[:space:]]+(.*) ]]; then
57
+ lesson_title="${BASH_REMATCH[1]}"
58
+ lesson_title="${lesson_title#\"}"
59
+ lesson_title="${lesson_title%\"}"
60
+ lesson_title="${lesson_title#\'}"
61
+ lesson_title="${lesson_title%\'}"
62
+ elif [[ "$line" =~ ^severity:[[:space:]]+(.*) ]]; then
63
+ lesson_severity="${BASH_REMATCH[1]}"
64
+ elif [[ "$line" =~ ^languages:[[:space:]]+(.*) ]]; then
65
+ lesson_languages="${BASH_REMATCH[1]}"
66
+ lesson_languages="${lesson_languages//[\[\]]/}"
67
+ lesson_languages="${lesson_languages//,/ }"
68
+ lesson_languages="${lesson_languages## }"
69
+ lesson_languages="${lesson_languages%% }"
70
+ elif [[ "$line" =~ ^scope:[[:space:]]+(.*) ]]; then
71
+ lesson_scope="${BASH_REMATCH[1]}"
72
+ lesson_scope="${lesson_scope//[\[\]]/}"
73
+ lesson_scope="${lesson_scope//,/ }"
74
+ lesson_scope="${lesson_scope## }"
75
+ lesson_scope="${lesson_scope%% }"
76
+ fi
77
+ else
78
+ # Nested pattern: fields (indented)
79
+ if [[ "$line" =~ ^[[:space:]]+type:[[:space:]]+(.*) ]]; then
80
+ pattern_type="${BASH_REMATCH[1]}"
81
+ elif [[ "$line" =~ ^[[:space:]]+regex:[[:space:]]+(.*) ]]; then
82
+ pattern_regex="${BASH_REMATCH[1]}"
83
+ pattern_regex="${pattern_regex#\"}"
84
+ pattern_regex="${pattern_regex%\"}"
85
+ pattern_regex="${pattern_regex#\'}"
86
+ pattern_regex="${pattern_regex%\'}"
87
+ fi
88
+ fi
89
+ done < <(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$file" 2>/dev/null)
90
+
91
+ # Unescape YAML double-escaped backslashes: \\s → \s, \\d → \d, etc.
92
+ pattern_regex="${pattern_regex//\\\\/\\}"
93
+
94
+ [[ -z "$pattern_type" || "$pattern_type" != "syntactic" ]] && return 1
95
+ [[ -z "$pattern_regex" ]] && return 1
96
+
97
+ # Default scope to universal when omitted (backward compatible)
98
+ [[ -z "$lesson_scope" ]] && lesson_scope="universal"
99
+
100
+ # Convert PCRE shorthand classes to POSIX ERE equivalents for grep -E portability.
101
+ # This lets lesson authors use \s, \d, \w, \b in regex: fields while keeping
102
+ # the scanner portable (grep -P is unavailable on macOS).
103
+ pattern_regex="${pattern_regex//\\d/[0-9]}"
104
+ pattern_regex="${pattern_regex//\\s/[[:space:]]}"
105
+ pattern_regex="${pattern_regex//\\w/[_[:alnum:]]}"
106
+ pattern_regex="${pattern_regex//\\b/\\b}" # no-op: \b passes through unchanged; GNU grep -E supports \b as word boundary (not BSD/macOS)
107
+
108
+ return 0
109
+ }
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # Build the --help text dynamically from lesson files
113
+ # ---------------------------------------------------------------------------
114
+ build_help() {
115
+ local checks_text=""
116
+ local lfile
117
+ for lfile in "$LESSONS_DIR"/[0-9]*.md; do
118
+ [[ -f "$lfile" ]] || continue
119
+ if parse_lesson "$lfile"; then
120
+ local lang_display="$lesson_languages"
121
+ [[ "$lang_display" == "all" ]] && lang_display="all files"
122
+ local scope_display="$lesson_scope"
123
+ checks_text+=" [lesson-${lesson_id}] ${lesson_title} (${lang_display}) [scope: ${scope_display}]"$'\n'
124
+ fi
125
+ done
126
+
127
+ cat <<USAGE
128
+ Usage: lesson-check.sh [OPTIONS] [file ...]
129
+ Check files for known anti-patterns from lessons learned.
130
+ Files can be passed as arguments or piped via stdin (one per line).
131
+ If neither, defaults to git diff --name-only in current directory.
132
+
133
+ Options:
134
+ --help, -h Show this help
135
+ --all-scopes Bypass scope filtering (check all lessons regardless of project)
136
+ --show-scope Display detected project scope and exit
137
+ --scope <tags> Override project scope (comma-separated, e.g. "language:python,domain:ha-aria")
138
+
139
+ Checks (syntactic only — loaded from ${LESSONS_DIR}):
140
+ ${checks_text}
141
+ Output: file:line: [lesson-N] description
142
+ Exit: 0 if clean, 1 if violations found
143
+ USAGE
144
+ }
145
+
146
+ # ---------------------------------------------------------------------------
147
+ # detect_project_scope [claude_md_path]
148
+ # Reads ## Scope Tags from CLAUDE.md. Falls back to detect_project_type().
149
+ # Sets global: project_scope (space-separated tags)
150
+ # ---------------------------------------------------------------------------
151
+ detect_project_scope() {
152
+ local claude_md="${1:-}"
153
+ project_scope=""
154
+
155
+ # Try explicit path first, then search current directory upward
156
+ if [[ -z "$claude_md" ]]; then
157
+ claude_md="CLAUDE.md"
158
+ # Walk up to find CLAUDE.md (max 5 levels)
159
+ local search_dir="$PWD"
160
+ for _ in 1 2 3 4 5; do
161
+ if [[ -f "$search_dir/CLAUDE.md" ]]; then
162
+ claude_md="$search_dir/CLAUDE.md"
163
+ break
164
+ fi
165
+ search_dir="$(dirname "$search_dir")"
166
+ done
167
+ fi
168
+
169
+ # Parse ## Scope Tags section from CLAUDE.md
170
+ if [[ -f "$claude_md" ]]; then
171
+ local in_scope_section=false
172
+ local line
173
+ while IFS= read -r line; do
174
+ if [[ "$line" =~ ^##[[:space:]]+Scope[[:space:]]+Tags ]]; then
175
+ in_scope_section=true
176
+ continue
177
+ fi
178
+ if [[ "$in_scope_section" == true ]]; then
179
+ # Stop at next heading
180
+ if [[ "$line" =~ ^## ]]; then
181
+ break
182
+ fi
183
+ # Skip empty lines
184
+ [[ -z "${line// /}" ]] && continue
185
+ # Parse comma-separated tags
186
+ local tag
187
+ for tag in ${line//,/ }; do
188
+ tag="${tag## }"
189
+ tag="${tag%% }"
190
+ [[ -n "$tag" ]] && project_scope+="$tag "
191
+ done
192
+ fi
193
+ done < "$claude_md"
194
+ project_scope="${project_scope%% }"
195
+ fi
196
+
197
+ # Fallback: detect project type → language tag
198
+ if [[ -z "$project_scope" ]]; then
199
+ source "$SCRIPT_DIR/lib/common.sh" 2>/dev/null || true
200
+ if type detect_project_type &>/dev/null; then
201
+ local ptype
202
+ ptype=$(detect_project_type "$PWD")
203
+ case "$ptype" in
204
+ python) project_scope="language:python" ;;
205
+ node) project_scope="language:javascript" ;;
206
+ bash) project_scope="language:bash" ;;
207
+ *) project_scope="" ;;
208
+ esac
209
+ fi
210
+ fi
211
+
212
+ # If still empty, everything matches (universal behavior)
213
+ }
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # scope_matches <lesson_scope> <project_scope>
217
+ # Returns 0 if lesson should run on this project, 1 if it should be skipped.
218
+ # A lesson matches if ANY of its scope tags intersects the project's scope set,
219
+ # or if the lesson scope includes "universal".
220
+ # ---------------------------------------------------------------------------
221
+ scope_matches() {
222
+ local l_scope="$1" # space-separated lesson scope tags
223
+ local p_scope="$2" # space-separated project scope tags
224
+
225
+ # universal matches everything
226
+ local tag
227
+ for tag in $l_scope; do
228
+ [[ "$tag" == "universal" ]] && return 0
229
+ done
230
+
231
+ # If project has no scope, everything matches (backward compat)
232
+ [[ -z "$p_scope" ]] && return 0
233
+
234
+ # Check intersection
235
+ local ltag ptag
236
+ for ltag in $l_scope; do
237
+ for ptag in $p_scope; do
238
+ [[ "$ltag" == "$ptag" ]] && return 0
239
+ done
240
+ done
241
+
242
+ return 1
243
+ }
244
+
245
+ # ---------------------------------------------------------------------------
246
+ # CLI flag parsing
247
+ # ---------------------------------------------------------------------------
248
+ ALL_SCOPES=false
249
+ SHOW_SCOPE=false
250
+ SCOPE_OVERRIDE=""
251
+
252
+ # Parse flags before file arguments
253
+ args=()
254
+ while [[ $# -gt 0 ]]; do
255
+ case "$1" in
256
+ --help|-h) build_help; exit 0 ;;
257
+ --all-scopes) ALL_SCOPES=true; shift ;;
258
+ --show-scope) SHOW_SCOPE=true; shift ;;
259
+ --scope)
260
+ [[ -z "${2:-}" ]] && { echo "lesson-check: --scope requires an argument" >&2; exit 1; }
261
+ SCOPE_OVERRIDE="$2"; shift 2 ;;
262
+ *) args+=("$1"); shift ;;
263
+ esac
264
+ done
265
+ set -- "${args[@]+"${args[@]}"}"
266
+
267
+ # Handle --show-scope early (no files needed)
268
+ if [[ "$SHOW_SCOPE" == true ]]; then
269
+ project_scope=""
270
+ if [[ -n "$SCOPE_OVERRIDE" ]]; then
271
+ project_scope="${SCOPE_OVERRIDE//,/ }"
272
+ else
273
+ detect_project_scope "${PROJECT_CLAUDE_MD:-}"
274
+ fi
275
+ if [[ -n "$project_scope" ]]; then
276
+ echo "Detected project scope: $project_scope"
277
+ else
278
+ echo "No project scope detected (all lessons will apply)"
279
+ fi
280
+ exit 0
281
+ fi
282
+
283
+ violations=0
284
+
285
+ # ---------------------------------------------------------------------------
286
+ # Gather file list: args → stdin pipe → git diff fallback
287
+ # ---------------------------------------------------------------------------
288
+ files=()
289
+ if [[ $# -gt 0 ]]; then
290
+ files=("$@")
291
+ elif [[ -p /dev/stdin ]]; then
292
+ # stdin is a named pipe (shell pipe) — safe to read without blocking.
293
+ # Using [[ -p /dev/stdin ]] instead of [[ ! -t 0 ]] avoids hanging when
294
+ # stdin is a socket (e.g. systemd/cron), which satisfies ! -t 0 but
295
+ # never sends EOF (#34). A socket is not a pipe, so -p /dev/stdin is false
296
+ # and we fall through to the git diff fallback instead of blocking forever.
297
+ while IFS= read -r f; do
298
+ [[ -n "$f" ]] && files+=("$f")
299
+ done
300
+ else
301
+ while IFS= read -r f; do
302
+ [[ -n "$f" ]] && files+=("$f")
303
+ done < <(git diff --name-only 2>/dev/null || true)
304
+ fi
305
+
306
+ if [[ ${#files[@]} -eq 0 ]]; then
307
+ echo "lesson-check: no files to check" >&2
308
+ exit 0
309
+ fi
310
+
311
+ # Pre-filter: only keep files that actually exist on disk
312
+ existing_files=()
313
+ for f in "${files[@]}"; do
314
+ [[ -f "$f" ]] && existing_files+=("$f")
315
+ done
316
+
317
+ if [[ ${#existing_files[@]} -eq 0 ]]; then
318
+ echo "lesson-check: no files to check" >&2
319
+ exit 0
320
+ fi
321
+
322
+ # ---------------------------------------------------------------------------
323
+ # Detect project scope (unless --all-scopes)
324
+ # ---------------------------------------------------------------------------
325
+ project_scope=""
326
+ if [[ "$ALL_SCOPES" == false ]]; then
327
+ if [[ -n "$SCOPE_OVERRIDE" ]]; then
328
+ project_scope="${SCOPE_OVERRIDE//,/ }"
329
+ else
330
+ detect_project_scope "${PROJECT_CLAUDE_MD:-}"
331
+ fi
332
+ fi
333
+
334
+ # ---------------------------------------------------------------------------
335
+ # Language → file extension mapping
336
+ # ---------------------------------------------------------------------------
337
+ # Returns 1 (mismatch) if the file doesn't match the lesson's languages.
338
+ file_matches_languages() {
339
+ local filepath="$1"
340
+ local languages="$2" # space-separated
341
+
342
+ # "all" matches everything
343
+ [[ "$languages" == "all" ]] && return 0
344
+
345
+ local lang
346
+ for lang in $languages; do
347
+ case "$lang" in
348
+ python) [[ "$filepath" == *.py ]] && return 0 ;;
349
+ javascript) [[ "$filepath" == *.js ]] && return 0 ;;
350
+ typescript) [[ "$filepath" == *.ts ]] && return 0 ;;
351
+ shell) [[ "$filepath" == *.sh ]] && return 0 ;;
352
+ esac
353
+ done
354
+ return 1
355
+ }
356
+
357
+ # ---------------------------------------------------------------------------
358
+ # Main loop: iterate lesson files, run syntactic checks
359
+ # ---------------------------------------------------------------------------
360
+ lfile=""
361
+ for lfile in "$LESSONS_DIR"/[0-9]*.md; do
362
+ [[ -f "$lfile" ]] || continue
363
+ parse_lesson "$lfile" || continue
364
+
365
+ # Scope filtering: skip lessons that don't match this project
366
+ if [[ "$ALL_SCOPES" == false ]]; then
367
+ scope_matches "$lesson_scope" "$project_scope" || continue
368
+ fi
369
+
370
+ # Build list of target files that match this lesson's languages
371
+ target_files=()
372
+ local_f=""
373
+ for local_f in "${existing_files[@]}"; do
374
+ file_matches_languages "$local_f" "$lesson_languages" && target_files+=("$local_f")
375
+ done
376
+ [[ ${#target_files[@]} -eq 0 ]] && continue
377
+
378
+ # Run grep against matching files; format output as file:line: [lesson-N] title
379
+ local_id="$lesson_id"
380
+ local_title="$lesson_title"
381
+ while IFS=: read -r matched_file lineno _rest; do
382
+ [[ -z "$matched_file" ]] && continue
383
+ echo "${matched_file}:${lineno}: [lesson-${local_id}] ${local_title}"
384
+ ((violations++)) || true
385
+ done < <(grep -EHn "$pattern_regex" "${target_files[@]}" 2>/dev/null || true)
386
+ done
387
+
388
+ # Load project-local lessons (Tier 3)
389
+ if [[ -n "$PROJECT_LESSONS_DIR" ]]; then
390
+ for lfile in "$PROJECT_LESSONS_DIR"/[0-9]*.md; do
391
+ [[ -f "$lfile" ]] || continue
392
+ parse_lesson "$lfile" || continue
393
+
394
+ # Scope filtering: skip lessons that don't match this project
395
+ if [[ "$ALL_SCOPES" == false ]]; then
396
+ scope_matches "$lesson_scope" "$project_scope" || continue
397
+ fi
398
+
399
+ # Build list of target files that match this lesson's languages
400
+ target_files=()
401
+ local_f=""
402
+ for local_f in "${existing_files[@]}"; do
403
+ file_matches_languages "$local_f" "$lesson_languages" && target_files+=("$local_f")
404
+ done
405
+ [[ ${#target_files[@]} -eq 0 ]] && continue
406
+
407
+ # Run grep against matching files; format output as file:line: [lesson-N] title
408
+ local_id="$lesson_id"
409
+ local_title="$lesson_title"
410
+ while IFS=: read -r matched_file lineno _rest; do
411
+ [[ -z "$matched_file" ]] && continue
412
+ echo "${matched_file}:${lineno}: [lesson-${local_id}] ${local_title}"
413
+ ((violations++)) || true
414
+ done < <(grep -EHn "$pattern_regex" "${target_files[@]}" 2>/dev/null || true)
415
+ done
416
+ fi
417
+
418
+ # ---------------------------------------------------------------------------
419
+ # Summary and exit
420
+ # ---------------------------------------------------------------------------
421
+ if [[ $violations -gt 0 ]]; then
422
+ echo ""
423
+ echo "lesson-check: $violations violation(s) found"
424
+ exit 1
425
+ else
426
+ echo "lesson-check: clean"
427
+ exit 0
428
+ fi