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,968 @@
1
+ # Lesson Scope Metadata Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Add project-level scope filtering to lesson-check.sh so domain-specific lessons (HA, Telegram, etc.) only fire on relevant projects, reducing false positives as lesson count grows past 100.
6
+
7
+ **Architecture:** lesson-check.sh gains a `scope:` field in YAML frontmatter (demand side) and reads `## Scope Tags` from the project's CLAUDE.md (supply side). A new `scope_matches()` function gates lessons before the existing language/grep check. A new `scope-infer.sh` script handles bulk tagging of existing lessons.
8
+
9
+ **Tech Stack:** Bash, YAML frontmatter parsing, grep, jq (none — pure bash)
10
+
11
+ ---
12
+
13
+ ## Batch 1: Scope Parsing & Matching (Core Engine)
14
+
15
+ ### Task 1: Add scope parsing tests to test-lesson-check.sh
16
+
17
+ **Files:**
18
+ - Modify: `scripts/tests/test-lesson-check.sh`
19
+
20
+ **Step 1: Write failing tests for scope field parsing**
21
+
22
+ Add these tests after the existing Test 8 block (before the Summary section at line 153):
23
+
24
+ ```bash
25
+ # --- Test 9: Scope field parsed from lesson YAML ---
26
+ # Create a lesson with scope: [language:python] and verify it's respected
27
+ cat > "$WORK/scoped-lesson.md" <<'LESSON'
28
+ ---
29
+ id: 999
30
+ title: "Test scoped lesson"
31
+ severity: should-fix
32
+ scope: [language:python]
33
+ languages: [python]
34
+ category: silent-failures
35
+ pattern:
36
+ type: syntactic
37
+ regex: "test_scope_marker"
38
+ description: "test marker"
39
+ fix: "test"
40
+ ---
41
+ LESSON
42
+
43
+ # Create a CLAUDE.md with scope tags
44
+ cat > "$WORK/CLAUDE.md" <<'CMD'
45
+ # Test Project
46
+
47
+ ## Scope Tags
48
+ language:python, framework:pytest
49
+ CMD
50
+
51
+ # Python file with the marker — should be detected (scope matches)
52
+ cat > "$WORK/scoped.py" <<'PY'
53
+ test_scope_marker = True
54
+ PY
55
+
56
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" "$WORK/scoped.py" 2>&1 || true)
57
+ if echo "$output" | grep -q '\[lesson-999\]'; then
58
+ pass "Scoped lesson detected when project scope matches"
59
+ else
60
+ fail "Scoped lesson should detect violation when project scope matches, got: $output"
61
+ fi
62
+
63
+ # --- Test 10: Scoped lesson skipped when project scope doesn't match ---
64
+ cat > "$WORK/CLAUDE-noscope.md" <<'CMD'
65
+ # Different Project
66
+
67
+ ## Scope Tags
68
+ domain:ha-aria
69
+ CMD
70
+
71
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" PROJECT_CLAUDE_MD="$WORK/CLAUDE-noscope.md" bash "$LESSON_CHECK" "$WORK/scoped.py" 2>&1 || true)
72
+ if echo "$output" | grep -q '\[lesson-999\]'; then
73
+ fail "Scoped lesson should be SKIPPED when project scope doesn't match"
74
+ else
75
+ pass "Scoped lesson correctly skipped for non-matching project scope"
76
+ fi
77
+
78
+ # --- Test 11: Lesson without scope defaults to universal (backward compat) ---
79
+ # Use the real lesson 1 (no scope field) — should still work as before
80
+ output=$(bash "$LESSON_CHECK" "$WORK/bad.py" 2>&1 || true)
81
+ if echo "$output" | grep -q '\[lesson-1\]'; then
82
+ pass "Lesson without scope: field defaults to universal (backward compatible)"
83
+ else
84
+ fail "Missing scope: should default to universal, got: $output"
85
+ fi
86
+
87
+ # --- Test 12: --show-scope displays detected project scope ---
88
+ output=$(cd "$WORK" && bash "$LESSON_CHECK" --show-scope 2>&1 || true)
89
+ if echo "$output" | grep -q 'language:python'; then
90
+ pass "--show-scope displays detected project scope"
91
+ else
92
+ fail "--show-scope should display detected scope from CLAUDE.md, got: $output"
93
+ fi
94
+
95
+ # --- Test 13: --all-scopes bypasses scope filtering ---
96
+ # Use a lesson scoped to domain:ha-aria on a python project — should be skipped normally
97
+ cat > "$WORK/ha-lesson.md" <<'LESSON'
98
+ ---
99
+ id: 998
100
+ title: "HA-only test lesson"
101
+ severity: should-fix
102
+ scope: [domain:ha-aria]
103
+ languages: [python]
104
+ category: silent-failures
105
+ pattern:
106
+ type: syntactic
107
+ regex: "ha_scope_marker"
108
+ description: "test marker"
109
+ fix: "test"
110
+ ---
111
+ LESSON
112
+
113
+ cat > "$WORK/ha_file.py" <<'PY'
114
+ ha_scope_marker = True
115
+ PY
116
+
117
+ # Without --all-scopes: lesson 998 should be skipped (project is python, not ha-aria)
118
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" "$WORK/ha_file.py" 2>&1 || true)
119
+ if echo "$output" | grep -q '\[lesson-998\]'; then
120
+ fail "domain:ha-aria lesson should be skipped on a python-only project"
121
+ else
122
+ pass "domain:ha-aria lesson correctly skipped on non-matching project"
123
+ fi
124
+
125
+ # With --all-scopes: lesson 998 should fire
126
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" --all-scopes "$WORK/ha_file.py" 2>&1 || true)
127
+ if echo "$output" | grep -q '\[lesson-998\]'; then
128
+ pass "--all-scopes bypasses scope filtering"
129
+ else
130
+ fail "--all-scopes should bypass scope filtering, got: $output"
131
+ fi
132
+
133
+ # --- Test 14: --scope override replaces CLAUDE.md detection ---
134
+ output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" --scope "domain:ha-aria" "$WORK/ha_file.py" 2>&1 || true)
135
+ if echo "$output" | grep -q '\[lesson-998\]'; then
136
+ pass "--scope override enables matching for specified scope"
137
+ else
138
+ fail "--scope override should enable domain:ha-aria matching, got: $output"
139
+ fi
140
+ ```
141
+
142
+ **Step 2: Run tests to verify they fail**
143
+
144
+ Run: `bash scripts/tests/test-lesson-check.sh`
145
+ Expected: Tests 9-14 FAIL (scope features not yet implemented)
146
+
147
+ **Step 3: Commit test file**
148
+
149
+ ```bash
150
+ git add scripts/tests/test-lesson-check.sh
151
+ git commit -m "test: add scope filtering tests for lesson-check.sh (red)"
152
+ ```
153
+
154
+ ---
155
+
156
+ ### Task 2: Add scope parsing to parse_lesson()
157
+
158
+ **Files:**
159
+ - Modify: `scripts/lesson-check.sh:17-88` (parse_lesson function)
160
+
161
+ **Step 1: Add lesson_scope variable initialization and parsing**
162
+
163
+ In `parse_lesson()`, add `lesson_scope=""` after the existing `lesson_languages=""` (line 24), then add a parsing case in the top-level fields block (after the `languages:` elif on line 52):
164
+
165
+ ```bash
166
+ # Add after line 24:
167
+ lesson_scope=""
168
+
169
+ # Add after the languages elif block (after line 57):
170
+ elif [[ "$line" =~ ^scope:[[:space:]]+(.*) ]]; then
171
+ lesson_scope="${BASH_REMATCH[1]}"
172
+ lesson_scope="${lesson_scope//[\[\]]/}"
173
+ lesson_scope="${lesson_scope//,/ }"
174
+ lesson_scope="${lesson_scope## }"
175
+ lesson_scope="${lesson_scope%% }"
176
+ ```
177
+
178
+ The parsing follows the exact same pattern as `lesson_languages` — strip brackets, replace commas with spaces, trim.
179
+
180
+ **Default when missing:** After the `return 1` checks at lines 76-77, add:
181
+
182
+ ```bash
183
+ # Default scope to universal when omitted (backward compatible)
184
+ [[ -z "$lesson_scope" ]] && lesson_scope="universal"
185
+ ```
186
+
187
+ **Step 2: Run existing tests to verify no regression**
188
+
189
+ Run: `bash scripts/tests/test-lesson-check.sh`
190
+ Expected: Tests 1-8 still pass, tests 9-14 still fail (matching not implemented yet)
191
+
192
+ **Step 3: Commit**
193
+
194
+ ```bash
195
+ git add scripts/lesson-check.sh
196
+ git commit -m "feat: parse scope: field from lesson YAML frontmatter"
197
+ ```
198
+
199
+ ---
200
+
201
+ ### Task 3: Add detect_project_scope() and scope_matches()
202
+
203
+ **Files:**
204
+ - Modify: `scripts/lesson-check.sh` (add two new functions after file_matches_languages)
205
+
206
+ **Step 1: Add detect_project_scope()**
207
+
208
+ Insert after `file_matches_languages()` (after line 183):
209
+
210
+ ```bash
211
+ # ---------------------------------------------------------------------------
212
+ # detect_project_scope [claude_md_path]
213
+ # Reads ## Scope Tags from CLAUDE.md. Falls back to detect_project_type().
214
+ # Sets global: project_scope (space-separated tags)
215
+ # ---------------------------------------------------------------------------
216
+ detect_project_scope() {
217
+ local claude_md="${1:-}"
218
+ project_scope=""
219
+
220
+ # Try explicit path first, then search current directory upward
221
+ if [[ -z "$claude_md" ]]; then
222
+ claude_md="CLAUDE.md"
223
+ # Walk up to find CLAUDE.md (max 5 levels)
224
+ local search_dir="$PWD"
225
+ for _ in 1 2 3 4 5; do
226
+ if [[ -f "$search_dir/CLAUDE.md" ]]; then
227
+ claude_md="$search_dir/CLAUDE.md"
228
+ break
229
+ fi
230
+ search_dir="$(dirname "$search_dir")"
231
+ done
232
+ fi
233
+
234
+ # Parse ## Scope Tags section from CLAUDE.md
235
+ if [[ -f "$claude_md" ]]; then
236
+ local in_scope_section=false
237
+ local line
238
+ while IFS= read -r line; do
239
+ if [[ "$line" =~ ^##[[:space:]]+Scope[[:space:]]+Tags ]]; then
240
+ in_scope_section=true
241
+ continue
242
+ fi
243
+ if [[ "$in_scope_section" == true ]]; then
244
+ # Stop at next heading
245
+ if [[ "$line" =~ ^## ]]; then
246
+ break
247
+ fi
248
+ # Skip empty lines
249
+ [[ -z "${line// /}" ]] && continue
250
+ # Parse comma-separated tags
251
+ local tag
252
+ for tag in ${line//,/ }; do
253
+ tag="${tag## }"
254
+ tag="${tag%% }"
255
+ [[ -n "$tag" ]] && project_scope+="$tag "
256
+ done
257
+ fi
258
+ done < "$claude_md"
259
+ project_scope="${project_scope%% }"
260
+ fi
261
+
262
+ # Fallback: detect project type → language tag
263
+ if [[ -z "$project_scope" ]]; then
264
+ source "$SCRIPT_DIR/lib/common.sh" 2>/dev/null || true
265
+ if type detect_project_type &>/dev/null; then
266
+ local ptype
267
+ ptype=$(detect_project_type "$PWD")
268
+ case "$ptype" in
269
+ python) project_scope="language:python" ;;
270
+ node) project_scope="language:javascript" ;;
271
+ bash) project_scope="language:bash" ;;
272
+ *) project_scope="" ;;
273
+ esac
274
+ fi
275
+ fi
276
+
277
+ # If still empty, everything matches (universal behavior)
278
+ }
279
+
280
+ # ---------------------------------------------------------------------------
281
+ # scope_matches <lesson_scope> <project_scope>
282
+ # Returns 0 if lesson should run on this project, 1 if it should be skipped.
283
+ # A lesson matches if ANY of its scope tags intersects the project's scope set,
284
+ # or if the lesson scope includes "universal".
285
+ # ---------------------------------------------------------------------------
286
+ scope_matches() {
287
+ local l_scope="$1" # space-separated lesson scope tags
288
+ local p_scope="$2" # space-separated project scope tags
289
+
290
+ # universal matches everything
291
+ local tag
292
+ for tag in $l_scope; do
293
+ [[ "$tag" == "universal" ]] && return 0
294
+ done
295
+
296
+ # If project has no scope, everything matches (backward compat)
297
+ [[ -z "$p_scope" ]] && return 0
298
+
299
+ # Check intersection
300
+ local ltag ptag
301
+ for ltag in $l_scope; do
302
+ for ptag in $p_scope; do
303
+ [[ "$ltag" == "$ptag" ]] && return 0
304
+ done
305
+ done
306
+
307
+ return 1
308
+ }
309
+ ```
310
+
311
+ **Step 2: Run tests**
312
+
313
+ Run: `bash scripts/tests/test-lesson-check.sh`
314
+ Expected: Tests 1-8 pass, tests 9-14 still fail (gate not wired in main loop yet)
315
+
316
+ **Step 3: Commit**
317
+
318
+ ```bash
319
+ git add scripts/lesson-check.sh
320
+ git commit -m "feat: add detect_project_scope() and scope_matches() functions"
321
+ ```
322
+
323
+ ---
324
+
325
+ ### Task 4: Wire scope filtering into main loop and CLI flags
326
+
327
+ **Files:**
328
+ - Modify: `scripts/lesson-check.sh` (CLI parsing, main loop gate, --help)
329
+
330
+ **Step 1: Add CLI flag parsing**
331
+
332
+ Replace the existing `--help` check block (lines 118-121) with expanded flag parsing:
333
+
334
+ ```bash
335
+ # ---------------------------------------------------------------------------
336
+ # CLI flag parsing
337
+ # ---------------------------------------------------------------------------
338
+ ALL_SCOPES=false
339
+ SHOW_SCOPE=false
340
+ SCOPE_OVERRIDE=""
341
+
342
+ # Parse flags before file arguments
343
+ args=()
344
+ while [[ $# -gt 0 ]]; do
345
+ case "$1" in
346
+ --help|-h) build_help; exit 0 ;;
347
+ --all-scopes) ALL_SCOPES=true; shift ;;
348
+ --show-scope) SHOW_SCOPE=true; shift ;;
349
+ --scope) SCOPE_OVERRIDE="$2"; shift 2 ;;
350
+ *) args+=("$1"); shift ;;
351
+ esac
352
+ done
353
+ set -- "${args[@]+"${args[@]}"}"
354
+ ```
355
+
356
+ **Step 2: Add scope detection before main loop**
357
+
358
+ After the `existing_files` check (after line 160), add:
359
+
360
+ ```bash
361
+ # ---------------------------------------------------------------------------
362
+ # Detect project scope (unless --all-scopes)
363
+ # ---------------------------------------------------------------------------
364
+ project_scope=""
365
+ if [[ "$ALL_SCOPES" == false ]]; then
366
+ if [[ -n "$SCOPE_OVERRIDE" ]]; then
367
+ project_scope="${SCOPE_OVERRIDE//,/ }"
368
+ else
369
+ detect_project_scope "${PROJECT_CLAUDE_MD:-}"
370
+ fi
371
+ fi
372
+
373
+ if [[ "$SHOW_SCOPE" == true ]]; then
374
+ if [[ -n "$project_scope" ]]; then
375
+ echo "Detected project scope: $project_scope"
376
+ else
377
+ echo "No project scope detected (all lessons will apply)"
378
+ fi
379
+ exit 0
380
+ fi
381
+ ```
382
+
383
+ **Step 3: Add scope gate in main loop**
384
+
385
+ In the main loop (line 191: `parse_lesson "$lfile" || continue`), add the scope check right after:
386
+
387
+ ```bash
388
+ parse_lesson "$lfile" || continue
389
+
390
+ # Scope filtering: skip lessons that don't match this project
391
+ if [[ "$ALL_SCOPES" == false ]]; then
392
+ scope_matches "$lesson_scope" "$project_scope" || continue
393
+ fi
394
+ ```
395
+
396
+ **Step 4: Update build_help() to show scope**
397
+
398
+ In `build_help()`, update the checks_text line (line 101) to include scope:
399
+
400
+ ```bash
401
+ local scope_display="$lesson_scope"
402
+ checks_text+=" [lesson-${lesson_id}] ${lesson_title} (${lang_display}) [scope: ${scope_display}]"$'\n'
403
+ ```
404
+
405
+ And add scope flags to the usage text:
406
+
407
+ ```bash
408
+ cat <<USAGE
409
+ Usage: lesson-check.sh [OPTIONS] [file ...]
410
+ Check files for known anti-patterns from lessons learned.
411
+ Files can be passed as arguments or piped via stdin (one per line).
412
+ If neither, defaults to git diff --name-only in current directory.
413
+
414
+ Options:
415
+ --help, -h Show this help
416
+ --all-scopes Bypass scope filtering (check all lessons regardless of project)
417
+ --show-scope Display detected project scope and exit
418
+ --scope <tags> Override project scope (comma-separated, e.g. "language:python,domain:ha-aria")
419
+
420
+ Checks (syntactic only — loaded from ${LESSONS_DIR}):
421
+ ${checks_text}
422
+ Output: file:line: [lesson-N] description
423
+ Exit: 0 if clean, 1 if violations found
424
+ USAGE
425
+ ```
426
+
427
+ **Step 5: Run all tests**
428
+
429
+ Run: `bash scripts/tests/test-lesson-check.sh`
430
+ Expected: All tests 1-14 PASS
431
+
432
+ **Step 6: Commit**
433
+
434
+ ```bash
435
+ git add scripts/lesson-check.sh
436
+ git commit -m "feat: wire scope filtering into lesson-check main loop with CLI flags"
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Batch 2: Scope Inference Script & Template Update
442
+
443
+ ### Task 5: Write test-scope-infer.sh
444
+
445
+ **Files:**
446
+ - Create: `scripts/tests/test-scope-infer.sh`
447
+
448
+ **Step 1: Write test file**
449
+
450
+ ```bash
451
+ #!/usr/bin/env bash
452
+ set -euo pipefail
453
+
454
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
455
+ source "$SCRIPT_DIR/test-helpers.sh"
456
+
457
+ INFER="$SCRIPT_DIR/../scope-infer.sh"
458
+
459
+ # --- Test: --help exits 0 ---
460
+ assert_exit "--help exits 0" 0 "$INFER" --help
461
+
462
+ # --- Test: --dry-run shows proposed scope without modifying files ---
463
+ WORK=$(mktemp -d)
464
+ trap 'rm -rf "$WORK"' EXIT
465
+
466
+ # Create a lesson with no scope field, mentioning "HA" and "entity"
467
+ cat > "$WORK/0099-test-ha-lesson.md" <<'LESSON'
468
+ ---
469
+ id: 99
470
+ title: "HA entity resolution fails on restart"
471
+ severity: should-fix
472
+ languages: [python]
473
+ category: data-model
474
+ pattern:
475
+ type: semantic
476
+ description: "HA entity lookup returns stale area"
477
+ fix: "Refresh entity registry on restart"
478
+ ---
479
+
480
+ ## Observation
481
+ Home Assistant entity area resolution uses a cached registry.
482
+ LESSON
483
+
484
+ dry_output=$("$INFER" --dir "$WORK" --dry-run 2>&1 || true)
485
+ assert_contains "--dry-run mentions ha-aria" "domain:ha-aria" "$dry_output"
486
+
487
+ # Verify file was NOT modified (dry run)
488
+ TESTS=$((TESTS + 1))
489
+ if grep -q '^scope:' "$WORK/0099-test-ha-lesson.md" 2>/dev/null; then
490
+ echo "FAIL: --dry-run should not modify lesson files"
491
+ FAILURES=$((FAILURES + 1))
492
+ else
493
+ echo "PASS: --dry-run does not modify lesson files"
494
+ fi
495
+
496
+ # --- Test: --apply writes scope field to lesson ---
497
+ "$INFER" --dir "$WORK" --apply > /dev/null 2>&1 || true
498
+
499
+ TESTS=$((TESTS + 1))
500
+ if grep -q '^scope:' "$WORK/0099-test-ha-lesson.md" 2>/dev/null; then
501
+ echo "PASS: --apply writes scope field to lesson"
502
+ else
503
+ echo "FAIL: --apply should write scope field to lesson"
504
+ FAILURES=$((FAILURES + 1))
505
+ fi
506
+
507
+ # Verify inferred scope is correct
508
+ scope_line=$(grep '^scope:' "$WORK/0099-test-ha-lesson.md" 2>/dev/null || true)
509
+ assert_contains "--apply infers domain:ha-aria" "domain:ha-aria" "$scope_line"
510
+
511
+ # --- Test: Lesson with existing scope is not modified ---
512
+ cat > "$WORK/0098-already-scoped.md" <<'LESSON'
513
+ ---
514
+ id: 98
515
+ title: "Already scoped lesson"
516
+ severity: should-fix
517
+ scope: [language:python]
518
+ languages: [python]
519
+ category: silent-failures
520
+ pattern:
521
+ type: semantic
522
+ description: "test"
523
+ fix: "test"
524
+ ---
525
+ LESSON
526
+
527
+ apply_output=$("$INFER" --dir "$WORK" --apply 2>&1 || true)
528
+ scope_line=$(grep '^scope:' "$WORK/0098-already-scoped.md" 2>/dev/null || true)
529
+ assert_contains "existing scope preserved" "language:python" "$scope_line"
530
+
531
+ # --- Test: Python-only lesson with no domain signals → language:python ---
532
+ cat > "$WORK/0097-python-only.md" <<'LESSON'
533
+ ---
534
+ id: 97
535
+ title: "Generic Python anti-pattern"
536
+ severity: should-fix
537
+ languages: [python]
538
+ category: async-traps
539
+ pattern:
540
+ type: syntactic
541
+ regex: "some_pattern"
542
+ description: "test"
543
+ fix: "test"
544
+ ---
545
+
546
+ ## Observation
547
+ This is a generic Python lesson with no domain signals.
548
+ LESSON
549
+
550
+ "$INFER" --dir "$WORK" --apply > /dev/null 2>&1 || true
551
+ scope_line=$(grep '^scope:' "$WORK/0097-python-only.md" 2>/dev/null || true)
552
+ assert_contains "python-only → language:python" "language:python" "$scope_line"
553
+
554
+ # --- Test: No signals → universal ---
555
+ cat > "$WORK/0096-universal.md" <<'LESSON'
556
+ ---
557
+ id: 96
558
+ title: "Generic coding practice"
559
+ severity: nice-to-have
560
+ languages: [all]
561
+ category: test-anti-patterns
562
+ pattern:
563
+ type: syntactic
564
+ regex: "some_other_pattern"
565
+ description: "test"
566
+ fix: "test"
567
+ ---
568
+
569
+ ## Observation
570
+ This applies to all projects everywhere.
571
+ LESSON
572
+
573
+ "$INFER" --dir "$WORK" --apply > /dev/null 2>&1 || true
574
+ scope_line=$(grep '^scope:' "$WORK/0096-universal.md" 2>/dev/null || true)
575
+ assert_contains "no signals → universal" "universal" "$scope_line"
576
+
577
+ # --- Test: Summary output shows counts ---
578
+ WORK2=$(mktemp -d)
579
+ trap 'rm -rf "$WORK" "$WORK2"' EXIT
580
+
581
+ cat > "$WORK2/0001-test.md" <<'LESSON'
582
+ ---
583
+ id: 1
584
+ title: "Test lesson"
585
+ severity: should-fix
586
+ languages: [python]
587
+ category: silent-failures
588
+ pattern:
589
+ type: syntactic
590
+ regex: "test"
591
+ description: "test"
592
+ fix: "test"
593
+ ---
594
+ Generic content.
595
+ LESSON
596
+
597
+ summary_output=$("$INFER" --dir "$WORK2" --dry-run 2>&1 || true)
598
+ assert_contains "summary shows count" "Inferred scope for" "$summary_output"
599
+
600
+ report_results
601
+ ```
602
+
603
+ **Step 2: Run test to verify it fails**
604
+
605
+ Run: `bash scripts/tests/test-scope-infer.sh`
606
+ Expected: FAIL (script doesn't exist yet)
607
+
608
+ **Step 3: Commit**
609
+
610
+ ```bash
611
+ git add scripts/tests/test-scope-infer.sh
612
+ git commit -m "test: add scope-infer.sh tests (red)"
613
+ ```
614
+
615
+ ---
616
+
617
+ ### Task 6: Implement scope-infer.sh
618
+
619
+ **Files:**
620
+ - Create: `scripts/scope-infer.sh`
621
+
622
+ **Step 1: Write the script**
623
+
624
+ ```bash
625
+ #!/usr/bin/env bash
626
+ # scope-infer.sh — Infer scope tags for lessons missing them
627
+ # Reads lesson content and applies heuristics to propose scope tags.
628
+ set -euo pipefail
629
+
630
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
631
+
632
+ # Defaults
633
+ LESSONS_DIR="$SCRIPT_DIR/../docs/lessons"
634
+ DRY_RUN=true
635
+ APPLY=false
636
+
637
+ usage() {
638
+ cat <<USAGE
639
+ Usage: scope-infer.sh [--dir <lessons-dir>] [--dry-run] [--apply]
640
+
641
+ Infer scope tags for lesson files that don't have a scope: field.
642
+
643
+ Options:
644
+ --dir <path> Lessons directory (default: docs/lessons/)
645
+ --dry-run Show proposed scope without modifying files (default)
646
+ --apply Write scope field into lesson files
647
+ --help, -h Show this help
648
+ USAGE
649
+ }
650
+
651
+ while [[ $# -gt 0 ]]; do
652
+ case "$1" in
653
+ --dir) LESSONS_DIR="$2"; shift 2 ;;
654
+ --dry-run) DRY_RUN=true; APPLY=false; shift ;;
655
+ --apply) APPLY=true; DRY_RUN=false; shift ;;
656
+ --help|-h) usage; exit 0 ;;
657
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1 ;;
658
+ esac
659
+ done
660
+
661
+ # Counters
662
+ total=0
663
+ inferred=0
664
+ skipped=0
665
+ count_universal=0
666
+ count_language=0
667
+ count_domain=0
668
+ count_project=0
669
+
670
+ infer_scope() {
671
+ local file="$1"
672
+ local content
673
+ content=$(cat "$file")
674
+
675
+ # Domain signals (check title + body)
676
+ local title_and_body
677
+ title_and_body=$(echo "$content" | tr '[:upper:]' '[:lower:]')
678
+
679
+ # Domain: ha-aria
680
+ if echo "$title_and_body" | grep -qE '(home assistant|\\bha\\b|entity.*area|automation.*trigger|hass|ha-aria)'; then
681
+ echo "domain:ha-aria"
682
+ return
683
+ fi
684
+
685
+ # Domain: telegram
686
+ if echo "$title_and_body" | grep -qE '(telegram|bot.*poll|getupdates|chat_id|telegram-brief|telegram-capture)'; then
687
+ echo "domain:telegram"
688
+ return
689
+ fi
690
+
691
+ # Domain: notion
692
+ if echo "$title_and_body" | grep -qE '(\\bnotion\\b|notion.*sync|notion.*database|notion-tools|notion_api)'; then
693
+ echo "domain:notion"
694
+ return
695
+ fi
696
+
697
+ # Domain: ollama
698
+ if echo "$title_and_body" | grep -qE '(\\bollama\\b|ollama.*queue|local.*llm|ollama-queue)'; then
699
+ echo "domain:ollama"
700
+ return
701
+ fi
702
+
703
+ # Framework: systemd
704
+ if echo "$title_and_body" | grep -qE '(systemd|systemctl|\.service|\.timer|journalctl|envfile)'; then
705
+ echo "framework:systemd"
706
+ return
707
+ fi
708
+
709
+ # Framework: pytest
710
+ if echo "$title_and_body" | grep -qE '(\\bpytest\\b|conftest|fixture|parametrize)'; then
711
+ echo "framework:pytest"
712
+ return
713
+ fi
714
+
715
+ # Framework: preact/jsx
716
+ if echo "$title_and_body" | grep -qE '(\\bpreact\\b|\\bjsx\\b|esbuild.*jsx|jsx.*factory)'; then
717
+ echo "framework:preact"
718
+ return
719
+ fi
720
+
721
+ # Project-specific: autonomous-coding-toolkit
722
+ if echo "$title_and_body" | grep -qE '(run-plan|quality.gate|lesson-check|mab-run|batch.*audit|ralph.*loop|headless.*mode)'; then
723
+ echo "project:autonomous-coding-toolkit"
724
+ return
725
+ fi
726
+
727
+ # Language: check the languages field
728
+ local languages
729
+ languages=$(sed -n '/^---$/,/^---$/{ /^languages:/p; }' "$file" 2>/dev/null | head -1)
730
+ languages=$(echo "$languages" | sed 's/languages:[[:space:]]*//' | tr -d '[]' | tr ',' ' ' | xargs)
731
+
732
+ if [[ "$languages" == "python" ]]; then
733
+ echo "language:python"
734
+ return
735
+ elif [[ "$languages" == "shell" ]]; then
736
+ echo "language:bash"
737
+ return
738
+ elif [[ "$languages" == "javascript" || "$languages" == "typescript" ]]; then
739
+ echo "language:javascript"
740
+ return
741
+ fi
742
+
743
+ # No signals → universal
744
+ echo "universal"
745
+ }
746
+
747
+ for lesson_file in "$LESSONS_DIR"/[0-9]*.md; do
748
+ [[ -f "$lesson_file" ]] || continue
749
+ total=$((total + 1))
750
+
751
+ # Check if scope already present
752
+ if sed -n '/^---$/,/^---$/p' "$lesson_file" | grep -q '^scope:'; then
753
+ skipped=$((skipped + 1))
754
+ continue
755
+ fi
756
+
757
+ scope=$(infer_scope "$lesson_file")
758
+ inferred=$((inferred + 1))
759
+
760
+ # Count by type
761
+ case "$scope" in
762
+ universal) count_universal=$((count_universal + 1)) ;;
763
+ language:*) count_language=$((count_language + 1)) ;;
764
+ domain:*) count_domain=$((count_domain + 1)) ;;
765
+ project:*) count_project=$((count_project + 1)) ;;
766
+ framework:*) count_language=$((count_language + 1)) ;; # group with language
767
+ esac
768
+
769
+ basename_file=$(basename "$lesson_file")
770
+
771
+ if [[ "$APPLY" == true ]]; then
772
+ # Insert scope: [$scope] after the languages: line in YAML frontmatter
773
+ sed -i "/^languages:/a scope: [$scope]" "$lesson_file"
774
+ echo " APPLIED: $basename_file → scope: [$scope]"
775
+ else
776
+ echo " PROPOSED: $basename_file → scope: [$scope]"
777
+ fi
778
+ done
779
+
780
+ echo ""
781
+ echo "Inferred scope for $inferred lessons: $count_universal universal, $count_domain domain-specific, $count_language language/framework, $count_project project-specific"
782
+ echo "Skipped $skipped lessons (already have scope)"
783
+ echo "Total: $total lessons scanned"
784
+ ```
785
+
786
+ **Step 2: Make executable**
787
+
788
+ ```bash
789
+ chmod +x scripts/scope-infer.sh
790
+ ```
791
+
792
+ **Step 3: Run tests**
793
+
794
+ Run: `bash scripts/tests/test-scope-infer.sh`
795
+ Expected: ALL PASS
796
+
797
+ **Step 4: Commit**
798
+
799
+ ```bash
800
+ git add scripts/scope-infer.sh scripts/tests/test-scope-infer.sh
801
+ git commit -m "feat: add scope-infer.sh for bulk scope tagging of lessons"
802
+ ```
803
+
804
+ ---
805
+
806
+ ### Task 7: Update TEMPLATE.md with scope field
807
+
808
+ **Files:**
809
+ - Modify: `docs/lessons/TEMPLATE.md`
810
+
811
+ **Step 1: Add scope field to the template**
812
+
813
+ Insert the `scope:` field after `languages:` in the YAML block. The template should show it as optional with a comment:
814
+
815
+ ```yaml
816
+ languages: [<python|javascript|typescript|shell|all>]
817
+ scope: [<universal|language:X|framework:X|domain:X|project:X>] # optional, defaults to universal
818
+ ```
819
+
820
+ **Step 2: Add scope to the Field Guide**
821
+
822
+ Add a new section after Categories:
823
+
824
+ ```markdown
825
+ ### Scope (Project-Level Filtering)
826
+ Scope controls which projects a lesson applies to. Language filtering (`languages:`) picks files; scope filtering picks projects. Both are orthogonal.
827
+
828
+ | Tag Format | Example | Matches |
829
+ |------------|---------|---------|
830
+ | `universal` | `[universal]` | All projects (default) |
831
+ | `language:<lang>` | `[language:python]` | Projects with that language |
832
+ | `framework:<name>` | `[framework:pytest]` | Projects using that framework |
833
+ | `domain:<name>` | `[domain:ha-aria]` | Domain-specific projects |
834
+ | `project:<name>` | `[project:autonomous-coding-toolkit]` | Exact project match |
835
+
836
+ Default when omitted: `[universal]` — backward compatible.
837
+ ```
838
+
839
+ **Step 3: Run existing tests to verify no regression**
840
+
841
+ Run: `bash scripts/tests/run-all-tests.sh`
842
+ Expected: All tests pass
843
+
844
+ **Step 4: Commit**
845
+
846
+ ```bash
847
+ git add docs/lessons/TEMPLATE.md
848
+ git commit -m "docs: add scope field to lesson TEMPLATE.md"
849
+ ```
850
+
851
+ ---
852
+
853
+ ## Batch 3: Apply Scope Tags to Existing Lessons
854
+
855
+ ### Task 8: Run scope-infer.sh --dry-run and review
856
+
857
+ **Step 1: Run dry-run**
858
+
859
+ ```bash
860
+ bash scripts/scope-infer.sh --dir docs/lessons --dry-run
861
+ ```
862
+
863
+ Expected: See proposed scope for all 67 lessons. Review the output to verify heuristics are reasonable.
864
+
865
+ **Step 2: Fix any obvious misclassifications**
866
+
867
+ If a lesson is proposed with the wrong scope, either:
868
+ - Adjust the heuristics in `scope-infer.sh`, OR
869
+ - Plan to manually fix after --apply
870
+
871
+ **Step 3: No commit (review only)**
872
+
873
+ ---
874
+
875
+ ### Task 9: Apply scope tags to the first 10 lessons manually
876
+
877
+ Apply the scope assignments from the design doc for lessons 0001-0010. These were manually reviewed.
878
+
879
+ **Files:**
880
+ - Modify: `docs/lessons/0001-bare-exception-swallowing.md` through `docs/lessons/0010-local-outside-function-bash.md`
881
+
882
+ **Step 1: Add scope field to each lesson**
883
+
884
+ For each file, insert `scope: [<value>]` after the `languages:` line in the YAML frontmatter:
885
+
886
+ | File | Scope to add |
887
+ |------|-------------|
888
+ | `0001-bare-exception-swallowing.md` | `scope: [language:python]` |
889
+ | `0002-async-def-without-await.md` | `scope: [language:python]` |
890
+ | `0003-create-task-without-callback.md` | `scope: [language:python]` |
891
+ | `0004-hardcoded-test-counts.md` | `scope: [universal]` |
892
+ | `0005-sqlite-without-closing.md` | `scope: [language:python]` |
893
+ | `0006-venv-pip-path.md` | `scope: [language:python, framework:pytest]` |
894
+ | `0007-runner-state-self-rejection.md` | `scope: [project:autonomous-coding-toolkit]` |
895
+ | `0008-quality-gate-blind-spot.md` | `scope: [project:autonomous-coding-toolkit]` |
896
+ | `0009-parser-overcount-empty-batches.md` | `scope: [project:autonomous-coding-toolkit]` |
897
+ | `0010-local-outside-function-bash.md` | `scope: [language:bash]` |
898
+
899
+ **Step 2: Run scope-infer.sh --apply for remaining lessons**
900
+
901
+ ```bash
902
+ bash scripts/scope-infer.sh --dir docs/lessons --apply
903
+ ```
904
+
905
+ This will add scope to lessons 0011-0067 (the first 10 already have scope and will be skipped).
906
+
907
+ **Step 3: Run full test suite**
908
+
909
+ Run: `bash scripts/tests/run-all-tests.sh`
910
+ Expected: All tests pass (scope field is backward-compatible)
911
+
912
+ **Step 4: Commit**
913
+
914
+ ```bash
915
+ git add docs/lessons/
916
+ git commit -m "feat: add scope tags to all 67 toolkit lessons"
917
+ ```
918
+
919
+ ---
920
+
921
+ ### Task 10: Run full test suite and verify
922
+
923
+ **Step 1: Run all tests**
924
+
925
+ ```bash
926
+ bash scripts/tests/run-all-tests.sh
927
+ ```
928
+
929
+ Expected: All test files pass, including the new scope tests.
930
+
931
+ **Step 2: Test behavioral scenarios**
932
+
933
+ ```bash
934
+ # From the toolkit root:
935
+ cd /path/to/autonomous-coding-toolkit
936
+ bash scripts/lesson-check.sh --show-scope
937
+
938
+ # Should show the toolkit's scope tags from CLAUDE.md
939
+ # (If no ## Scope Tags section exists yet, it will fall back to language detection)
940
+
941
+ # Test with --all-scopes
942
+ bash scripts/lesson-check.sh --all-scopes scripts/lesson-check.sh
943
+ ```
944
+
945
+ **Step 3: Final commit if needed**
946
+
947
+ ```bash
948
+ git add -A
949
+ git commit -m "chore: Phase 5A scope metadata implementation complete"
950
+ ```
951
+
952
+ ---
953
+
954
+ ## Summary
955
+
956
+ | Batch | Tasks | What it delivers |
957
+ |-------|-------|-----------------|
958
+ | 1 | Tasks 1-4 | Core scope engine: parsing, matching, CLI flags, filtering gate |
959
+ | 2 | Tasks 5-7 | Scope inference script + template update |
960
+ | 3 | Tasks 8-10 | Apply scope tags to all 67 lessons + full verification |
961
+
962
+ **Out of scope for this plan (deferred):**
963
+ - Lesson-scanner agent updates (workspace lessons — separate plan)
964
+ - `/capture-lesson` skill integration (scope inference at creation time)
965
+ - Propagation tracking (`validated_in` field)
966
+ - Workspace lessons bulk tagging (76 files in ~/Documents/docs/lessons/)
967
+
968
+ These are listed in the design doc and can be planned as follow-on batches.