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,2189 @@
1
+ # Code Factory v2 Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Eliminate code duplication, fix broken pipeline steps, expand quality gates, and add new capabilities to the Code Factory toolchain.
6
+
7
+ **Architecture:** Extract shared bash libraries from duplicated code across 6 scripts, then refactor each script to use them. Fix accuracy issues (test count parsing, cross-batch context). Add lint, prior-art search, and pipeline status tools. Finally add failure digest, structured context refs, and team mode.
8
+
9
+ **Tech Stack:** Bash, jq, ruff (Python linter), gh CLI, ast-grep (structural code search)
10
+
11
+ ## Quality Gates
12
+
13
+ Between each batch, run:
14
+ ```bash
15
+ scripts/tests/run-all-tests.sh # All existing + new tests pass
16
+ wc -l scripts/*.sh scripts/lib/*.sh # No script >300 lines
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Batch 1: Foundation Libraries
22
+
23
+ Create `scripts/lib/common.sh` and `scripts/lib/ollama.sh` — shared functions extracted from duplicated code across scripts. These are the building blocks for all subsequent refactoring.
24
+
25
+ ### Task 1: Create common.sh with detect_project_type
26
+
27
+ **Files:**
28
+ - Create: `scripts/lib/common.sh`
29
+ - Create: `scripts/tests/test-common.sh`
30
+
31
+ **Step 1: Write the failing test**
32
+
33
+ Create `scripts/tests/test-common.sh`:
34
+
35
+ ```bash
36
+ #!/usr/bin/env bash
37
+ # Test shared common.sh functions
38
+ set -euo pipefail
39
+
40
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
41
+ source "$SCRIPT_DIR/../lib/common.sh"
42
+
43
+ FAILURES=0
44
+ TESTS=0
45
+
46
+ assert_eq() {
47
+ local desc="$1" expected="$2" actual="$3"
48
+ TESTS=$((TESTS + 1))
49
+ if [[ "$expected" != "$actual" ]]; then
50
+ echo "FAIL: $desc"
51
+ echo " expected: $expected"
52
+ echo " actual: $actual"
53
+ FAILURES=$((FAILURES + 1))
54
+ else
55
+ echo "PASS: $desc"
56
+ fi
57
+ }
58
+
59
+ assert_exit() {
60
+ local desc="$1" expected_exit="$2"
61
+ shift 2
62
+ local actual_exit=0
63
+ "$@" || actual_exit=$?
64
+ TESTS=$((TESTS + 1))
65
+ if [[ "$expected_exit" != "$actual_exit" ]]; then
66
+ echo "FAIL: $desc"
67
+ echo " expected exit: $expected_exit"
68
+ echo " actual exit: $actual_exit"
69
+ FAILURES=$((FAILURES + 1))
70
+ else
71
+ echo "PASS: $desc"
72
+ fi
73
+ }
74
+
75
+ WORK=$(mktemp -d)
76
+ trap "rm -rf '$WORK'" EXIT
77
+
78
+ # === detect_project_type tests ===
79
+
80
+ # Python project (pyproject.toml)
81
+ mkdir -p "$WORK/py-proj"
82
+ touch "$WORK/py-proj/pyproject.toml"
83
+ val=$(detect_project_type "$WORK/py-proj")
84
+ assert_eq "detect_project_type: pyproject.toml -> python" "python" "$val"
85
+
86
+ # Python project (setup.py)
87
+ mkdir -p "$WORK/py-setup"
88
+ touch "$WORK/py-setup/setup.py"
89
+ val=$(detect_project_type "$WORK/py-setup")
90
+ assert_eq "detect_project_type: setup.py -> python" "python" "$val"
91
+
92
+ # Node project (package.json)
93
+ mkdir -p "$WORK/node-proj"
94
+ echo '{"name":"test"}' > "$WORK/node-proj/package.json"
95
+ val=$(detect_project_type "$WORK/node-proj")
96
+ assert_eq "detect_project_type: package.json -> node" "node" "$val"
97
+
98
+ # Makefile project
99
+ mkdir -p "$WORK/make-proj"
100
+ echo 'test:' > "$WORK/make-proj/Makefile"
101
+ val=$(detect_project_type "$WORK/make-proj")
102
+ assert_eq "detect_project_type: Makefile -> make" "make" "$val"
103
+
104
+ # Unknown project
105
+ mkdir -p "$WORK/empty"
106
+ val=$(detect_project_type "$WORK/empty")
107
+ assert_eq "detect_project_type: empty -> unknown" "unknown" "$val"
108
+
109
+ # === strip_json_fences tests ===
110
+
111
+ val=$(echo '```json
112
+ {"key":"value"}
113
+ ```' | strip_json_fences)
114
+ assert_eq "strip_json_fences: removes fences" '{"key":"value"}' "$val"
115
+
116
+ val=$(echo '{"key":"value"}' | strip_json_fences)
117
+ assert_eq "strip_json_fences: plain JSON unchanged" '{"key":"value"}' "$val"
118
+
119
+ # === check_memory_available tests ===
120
+
121
+ # This test just verifies the function exists and returns 0/1
122
+ # We can't control actual memory, so test the interface
123
+ assert_exit "check_memory_available: runs without error" 0 \
124
+ check_memory_available 0
125
+
126
+ # === require_command tests ===
127
+
128
+ assert_exit "require_command: bash exists" 0 \
129
+ require_command "bash"
130
+
131
+ assert_exit "require_command: nonexistent-binary-xyz fails" 1 \
132
+ require_command "nonexistent-binary-xyz"
133
+
134
+ # === Summary ===
135
+ echo ""
136
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
137
+ if [[ $FAILURES -gt 0 ]]; then
138
+ echo "FAILURES: $FAILURES"
139
+ exit 1
140
+ fi
141
+ echo "ALL PASSED"
142
+ ```
143
+
144
+ **Step 2: Run test to verify it fails**
145
+
146
+ Run: `bash scripts/tests/test-common.sh`
147
+ Expected: FAIL — `common.sh` does not exist yet
148
+
149
+ **Step 3: Write minimal implementation**
150
+
151
+ Create `scripts/lib/common.sh`:
152
+
153
+ ```bash
154
+ #!/usr/bin/env bash
155
+ # common.sh — Shared utility functions for Code Factory scripts
156
+ #
157
+ # Source this in any script: source "$SCRIPT_DIR/lib/common.sh"
158
+ #
159
+ # Functions:
160
+ # detect_project_type <dir> -> "python"|"node"|"make"|"unknown"
161
+ # strip_json_fences -> stdin filter: remove ```json wrappers
162
+ # check_memory_available <threshold_gb> -> exit 0 if available >= threshold, 1 otherwise
163
+ # require_command <cmd> [install_hint] -> exit 1 with message if cmd not found
164
+
165
+ detect_project_type() {
166
+ local dir="$1"
167
+ if [[ -f "$dir/pyproject.toml" || -f "$dir/setup.py" || -f "$dir/pytest.ini" ]]; then
168
+ echo "python"
169
+ elif [[ -f "$dir/package.json" ]]; then
170
+ echo "node"
171
+ elif [[ -f "$dir/Makefile" ]]; then
172
+ echo "make"
173
+ else
174
+ echo "unknown"
175
+ fi
176
+ }
177
+
178
+ strip_json_fences() {
179
+ sed '/^```json$/d; /^```$/d'
180
+ }
181
+
182
+ check_memory_available() {
183
+ local threshold_gb="${1:-4}"
184
+ local available_gb
185
+ available_gb=$(free -g 2>/dev/null | awk '/Mem:/{print $7}' || echo "999")
186
+ if [[ "$available_gb" -ge "$threshold_gb" ]]; then
187
+ return 0
188
+ else
189
+ echo "WARNING: Low memory (${available_gb}G available, need ${threshold_gb}G)" >&2
190
+ return 1
191
+ fi
192
+ }
193
+
194
+ require_command() {
195
+ local cmd="$1"
196
+ local hint="${2:-}"
197
+ if ! command -v "$cmd" >/dev/null 2>&1; then
198
+ echo "ERROR: Required command not found: $cmd" >&2
199
+ if [[ -n "$hint" ]]; then
200
+ echo " Install with: $hint" >&2
201
+ fi
202
+ return 1
203
+ fi
204
+ }
205
+ ```
206
+
207
+ **Step 4: Run test to verify it passes**
208
+
209
+ Run: `bash scripts/tests/test-common.sh`
210
+ Expected: ALL PASSED
211
+
212
+ **Step 5: Commit**
213
+
214
+ ```bash
215
+ git add scripts/lib/common.sh scripts/tests/test-common.sh
216
+ git commit -m "feat: create scripts/lib/common.sh shared library with tests"
217
+ ```
218
+
219
+ ### Task 2: Create ollama.sh shared library
220
+
221
+ **Files:**
222
+ - Create: `scripts/lib/ollama.sh`
223
+ - Create: `scripts/tests/test-ollama.sh`
224
+
225
+ **Step 1: Write the failing test**
226
+
227
+ Create `scripts/tests/test-ollama.sh`. Note: Ollama tests must work offline (mock the HTTP call). Test the URL construction and JSON parsing, not the actual API call.
228
+
229
+ ```bash
230
+ #!/usr/bin/env bash
231
+ # Test ollama.sh shared library functions
232
+ set -euo pipefail
233
+
234
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
235
+ source "$SCRIPT_DIR/../lib/common.sh"
236
+ source "$SCRIPT_DIR/../lib/ollama.sh"
237
+
238
+ FAILURES=0
239
+ TESTS=0
240
+
241
+ assert_eq() {
242
+ local desc="$1" expected="$2" actual="$3"
243
+ TESTS=$((TESTS + 1))
244
+ if [[ "$expected" != "$actual" ]]; then
245
+ echo "FAIL: $desc"
246
+ echo " expected: $expected"
247
+ echo " actual: $actual"
248
+ FAILURES=$((FAILURES + 1))
249
+ else
250
+ echo "PASS: $desc"
251
+ fi
252
+ }
253
+
254
+ # === ollama_build_payload tests ===
255
+
256
+ val=$(ollama_build_payload "deepseek-r1:8b" "Hello world")
257
+ model=$(echo "$val" | jq -r '.model')
258
+ assert_eq "ollama_build_payload: model set" "deepseek-r1:8b" "$model"
259
+
260
+ stream=$(echo "$val" | jq -r '.stream')
261
+ assert_eq "ollama_build_payload: stream false" "false" "$stream"
262
+
263
+ # === ollama_parse_response tests ===
264
+
265
+ val=$(echo '{"response":"hello"}' | ollama_parse_response)
266
+ assert_eq "ollama_parse_response: extracts response" "hello" "$val"
267
+
268
+ val=$(echo '{}' | ollama_parse_response)
269
+ assert_eq "ollama_parse_response: empty on missing field" "" "$val"
270
+
271
+ # === ollama_extract_json tests ===
272
+
273
+ val=$(echo '```json
274
+ {"key":"value"}
275
+ ```' | ollama_extract_json)
276
+ key=$(echo "$val" | jq -r '.key')
277
+ assert_eq "ollama_extract_json: strips fences and validates" "value" "$key"
278
+
279
+ val=$(echo 'not json at all' | ollama_extract_json)
280
+ assert_eq "ollama_extract_json: returns empty on invalid" "" "$val"
281
+
282
+ # === Summary ===
283
+ echo ""
284
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
285
+ if [[ $FAILURES -gt 0 ]]; then
286
+ echo "FAILURES: $FAILURES"
287
+ exit 1
288
+ fi
289
+ echo "ALL PASSED"
290
+ ```
291
+
292
+ **Step 2: Run test to verify it fails**
293
+
294
+ Run: `bash scripts/tests/test-ollama.sh`
295
+ Expected: FAIL — `ollama.sh` does not exist
296
+
297
+ **Step 3: Write minimal implementation**
298
+
299
+ Create `scripts/lib/ollama.sh`:
300
+
301
+ ```bash
302
+ #!/usr/bin/env bash
303
+ # ollama.sh — Shared Ollama API interaction for Code Factory scripts
304
+ #
305
+ # Requires: common.sh sourced first (for strip_json_fences)
306
+ #
307
+ # Functions:
308
+ # ollama_build_payload <model> <prompt> -> JSON payload string
309
+ # ollama_parse_response -> stdin filter: extract .response from Ollama JSON
310
+ # ollama_extract_json -> stdin filter: parse response, strip fences, validate JSON
311
+ # ollama_query <model> <prompt> -> full query: build payload, call API, return response text
312
+ # ollama_query_json <model> <prompt> -> full query + JSON extraction
313
+
314
+ OLLAMA_DIRECT_URL="${OLLAMA_DIRECT_URL:-http://localhost:11434}"
315
+ OLLAMA_QUEUE_URL="${OLLAMA_QUEUE_URL:-http://localhost:7683}"
316
+
317
+ ollama_build_payload() {
318
+ local model="$1" prompt="$2"
319
+ jq -n --arg model "$model" --arg prompt "$prompt" \
320
+ '{model: $model, prompt: $prompt, stream: false}'
321
+ }
322
+
323
+ ollama_parse_response() {
324
+ jq -r '.response // empty'
325
+ }
326
+
327
+ ollama_extract_json() {
328
+ local text
329
+ text=$(cat)
330
+ # Strip fences
331
+ text=$(echo "$text" | strip_json_fences)
332
+ # Validate JSON
333
+ if echo "$text" | jq . >/dev/null 2>&1; then
334
+ echo "$text"
335
+ else
336
+ echo ""
337
+ fi
338
+ }
339
+
340
+ ollama_query() {
341
+ local model="$1" prompt="$2"
342
+ local payload api_url response
343
+
344
+ payload=$(ollama_build_payload "$model" "$prompt")
345
+
346
+ # Prefer queue if available
347
+ if curl -s -o /dev/null -w '%{http_code}' "$OLLAMA_QUEUE_URL/health" 2>/dev/null | grep -q "200"; then
348
+ api_url="$OLLAMA_QUEUE_URL/api/generate"
349
+ else
350
+ api_url="$OLLAMA_DIRECT_URL/api/generate"
351
+ fi
352
+
353
+ response=$(curl -s "$api_url" -d "$payload" --max-time 300)
354
+ echo "$response" | ollama_parse_response
355
+ }
356
+
357
+ ollama_query_json() {
358
+ local model="$1" prompt="$2"
359
+ ollama_query "$model" "$prompt" | ollama_extract_json
360
+ }
361
+ ```
362
+
363
+ **Step 4: Run test to verify it passes**
364
+
365
+ Run: `bash scripts/tests/test-ollama.sh`
366
+ Expected: ALL PASSED
367
+
368
+ **Step 5: Commit**
369
+
370
+ ```bash
371
+ git add scripts/lib/ollama.sh scripts/tests/test-ollama.sh
372
+ git commit -m "feat: create scripts/lib/ollama.sh shared Ollama API library with tests"
373
+ ```
374
+
375
+ ### Task 3: Create telegram.sh shared library
376
+
377
+ **Files:**
378
+ - Create: `scripts/lib/telegram.sh`
379
+ - Modify: `scripts/lib/run-plan-notify.sh` (remove `_load_telegram_env` and `_send_telegram`, source telegram.sh)
380
+
381
+ **Step 1: Write the failing test**
382
+
383
+ Create `scripts/tests/test-telegram.sh`:
384
+
385
+ ```bash
386
+ #!/usr/bin/env bash
387
+ # Test telegram.sh shared library
388
+ set -euo pipefail
389
+
390
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
391
+ source "$SCRIPT_DIR/../lib/telegram.sh"
392
+
393
+ FAILURES=0
394
+ TESTS=0
395
+
396
+ assert_eq() {
397
+ local desc="$1" expected="$2" actual="$3"
398
+ TESTS=$((TESTS + 1))
399
+ if [[ "$expected" != "$actual" ]]; then
400
+ echo "FAIL: $desc"
401
+ echo " expected: $expected"
402
+ echo " actual: $actual"
403
+ FAILURES=$((FAILURES + 1))
404
+ else
405
+ echo "PASS: $desc"
406
+ fi
407
+ }
408
+
409
+ assert_exit() {
410
+ local desc="$1" expected_exit="$2"
411
+ shift 2
412
+ local actual_exit=0
413
+ "$@" || actual_exit=$?
414
+ TESTS=$((TESTS + 1))
415
+ if [[ "$expected_exit" != "$actual_exit" ]]; then
416
+ echo "FAIL: $desc"
417
+ echo " expected exit: $expected_exit"
418
+ echo " actual exit: $actual_exit"
419
+ FAILURES=$((FAILURES + 1))
420
+ else
421
+ echo "PASS: $desc"
422
+ fi
423
+ }
424
+
425
+ WORK=$(mktemp -d)
426
+ trap "rm -rf '$WORK'" EXIT
427
+
428
+ # === _load_telegram_env tests ===
429
+
430
+ # Missing file
431
+ assert_exit "_load_telegram_env: missing file returns 1" 1 \
432
+ _load_telegram_env "$WORK/nonexistent"
433
+
434
+ # File without keys
435
+ echo "SOME_OTHER_KEY=value" > "$WORK/empty.env"
436
+ assert_exit "_load_telegram_env: missing keys returns 1" 1 \
437
+ _load_telegram_env "$WORK/empty.env"
438
+
439
+ # File with both keys
440
+ cat > "$WORK/valid.env" << 'ENVFILE'
441
+ TELEGRAM_BOT_TOKEN=test-token-123
442
+ TELEGRAM_CHAT_ID=test-chat-456
443
+ ENVFILE
444
+ assert_exit "_load_telegram_env: valid file returns 0" 0 \
445
+ _load_telegram_env "$WORK/valid.env"
446
+ assert_eq "_load_telegram_env: token loaded" "test-token-123" "$TELEGRAM_BOT_TOKEN"
447
+ assert_eq "_load_telegram_env: chat_id loaded" "test-chat-456" "$TELEGRAM_CHAT_ID"
448
+
449
+ # === _send_telegram without credentials ===
450
+
451
+ unset TELEGRAM_BOT_TOKEN TELEGRAM_CHAT_ID
452
+ assert_exit "_send_telegram: no creds returns 0 (skip)" 0 \
453
+ _send_telegram "test message"
454
+
455
+ # === Summary ===
456
+ echo ""
457
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
458
+ if [[ $FAILURES -gt 0 ]]; then
459
+ echo "FAILURES: $FAILURES"
460
+ exit 1
461
+ fi
462
+ echo "ALL PASSED"
463
+ ```
464
+
465
+ **Step 2: Run test to verify it fails**
466
+
467
+ Run: `bash scripts/tests/test-telegram.sh`
468
+ Expected: FAIL — `telegram.sh` does not exist
469
+
470
+ **Step 3: Write implementation**
471
+
472
+ Create `scripts/lib/telegram.sh` — extract from `run-plan-notify.sh` lines 27-63:
473
+
474
+ ```bash
475
+ #!/usr/bin/env bash
476
+ # telegram.sh — Shared Telegram notification helpers
477
+ #
478
+ # Functions:
479
+ # _load_telegram_env [env_file] -> load TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID
480
+ # _send_telegram <message> -> send via Telegram Bot API
481
+
482
+ _load_telegram_env() {
483
+ local env_file="${1:-$HOME/.env}"
484
+
485
+ if [[ ! -f "$env_file" ]]; then
486
+ echo "WARNING: env file not found: $env_file" >&2
487
+ return 1
488
+ fi
489
+
490
+ TELEGRAM_BOT_TOKEN=$(grep -E '^TELEGRAM_BOT_TOKEN=' "$env_file" | head -1 | cut -d= -f2-)
491
+ TELEGRAM_CHAT_ID=$(grep -E '^TELEGRAM_CHAT_ID=' "$env_file" | head -1 | cut -d= -f2-)
492
+
493
+ if [[ -z "${TELEGRAM_BOT_TOKEN:-}" || -z "${TELEGRAM_CHAT_ID:-}" ]]; then
494
+ echo "WARNING: TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID not found in $env_file" >&2
495
+ return 1
496
+ fi
497
+
498
+ export TELEGRAM_BOT_TOKEN TELEGRAM_CHAT_ID
499
+ }
500
+
501
+ _send_telegram() {
502
+ local message="$1"
503
+
504
+ if [[ -z "${TELEGRAM_BOT_TOKEN:-}" || -z "${TELEGRAM_CHAT_ID:-}" ]]; then
505
+ echo "WARNING: Telegram credentials not set — skipping notification" >&2
506
+ return 0
507
+ fi
508
+
509
+ local url="https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage"
510
+
511
+ curl -s -X POST "$url" \
512
+ -d chat_id="$TELEGRAM_CHAT_ID" \
513
+ -d text="$message" \
514
+ -d parse_mode="Markdown" \
515
+ --max-time 10 > /dev/null 2>&1 || {
516
+ echo "WARNING: Failed to send Telegram notification" >&2
517
+ return 0
518
+ }
519
+ }
520
+ ```
521
+
522
+ Then update `scripts/lib/run-plan-notify.sh` — replace `_load_telegram_env` and `_send_telegram` with a source line:
523
+
524
+ ```bash
525
+ #!/usr/bin/env bash
526
+ # run-plan-notify.sh — Telegram notification helpers for run-plan
527
+ #
528
+ # Functions:
529
+ # format_success_message <plan_name> <batch_num> <test_count> <prev_count> <duration> <mode>
530
+ # format_failure_message <plan_name> <batch_num> <test_count> <failing_count> <error> <action>
531
+ # notify_success (same args as format_success_message) — format + send
532
+ # notify_failure (same args as format_failure_message) — format + send
533
+
534
+ # Source shared telegram functions
535
+ _NOTIFY_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
536
+ source "$_NOTIFY_SCRIPT_DIR/telegram.sh"
537
+
538
+ format_success_message() {
539
+ local plan_name="$1" batch_num="$2" test_count="$3" prev_count="$4" duration="$5" mode="$6"
540
+ local delta=$(( test_count - prev_count ))
541
+
542
+ printf '%s — Batch %s ✓\nTests: %s (↑%s)\nDuration: %s\nMode: %s' \
543
+ "$plan_name" "$batch_num" "$test_count" "$delta" "$duration" "$mode"
544
+ }
545
+
546
+ format_failure_message() {
547
+ local plan_name="$1" batch_num="$2" test_count="$3" failing_count="$4" error="$5" action="$6"
548
+
549
+ printf '%s — Batch %s ✗\nTests: %s (%s failing)\nError: %s\nAction: %s' \
550
+ "$plan_name" "$batch_num" "$test_count" "$failing_count" "$error" "$action"
551
+ }
552
+
553
+ notify_success() {
554
+ local msg
555
+ msg=$(format_success_message "$@")
556
+ _send_telegram "$msg"
557
+ }
558
+
559
+ notify_failure() {
560
+ local msg
561
+ msg=$(format_failure_message "$@")
562
+ _send_telegram "$msg"
563
+ }
564
+ ```
565
+
566
+ **Step 4: Run tests to verify they pass**
567
+
568
+ Run: `bash scripts/tests/test-telegram.sh && bash scripts/tests/test-run-plan-notify.sh`
569
+ Expected: ALL PASSED (both)
570
+
571
+ **Step 5: Commit**
572
+
573
+ ```bash
574
+ git add scripts/lib/telegram.sh scripts/tests/test-telegram.sh scripts/lib/run-plan-notify.sh
575
+ git commit -m "feat: extract scripts/lib/telegram.sh from run-plan-notify.sh"
576
+ ```
577
+
578
+ ## Batch 2: Refactor Scripts to Use Shared Libraries
579
+
580
+ Refactor all 5 scripts (`auto-compound.sh`, `quality-gate.sh`, `entropy-audit.sh`, `analyze-report.sh`, `run-plan.sh`) to use the shared libraries from Batch 1. No behavior changes — pure deduplication.
581
+
582
+ ### Task 4: Refactor quality-gate.sh to use common.sh
583
+
584
+ **Files:**
585
+ - Modify: `scripts/quality-gate.sh:29-45` (arg parsing), `scripts/quality-gate.sh:79-91` (project detection), `scripts/quality-gate.sh:97-107` (memory check)
586
+
587
+ **Step 1: Read current quality-gate.sh**
588
+
589
+ Identify the three sections to replace:
590
+ - Lines 79-91: inline project type detection → `detect_project_type()`
591
+ - Lines 100-106: inline memory check → `check_memory_available()`
592
+
593
+ **Step 2: Add source line and refactor**
594
+
595
+ Add after line 6 (`SCRIPT_DIR=...`):
596
+ ```bash
597
+ source "$SCRIPT_DIR/lib/common.sh"
598
+ ```
599
+
600
+ Replace lines 79-91 (test suite detection) with:
601
+ ```bash
602
+ project_type=$(detect_project_type "$PROJECT_ROOT")
603
+ case "$project_type" in
604
+ python)
605
+ echo "Detected: pytest project"
606
+ .venv/bin/python -m pytest --timeout=120 -x -q
607
+ test_ran=1
608
+ ;;
609
+ node)
610
+ if grep -q '"test"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
611
+ echo "Detected: npm project"
612
+ npm test
613
+ test_ran=1
614
+ fi
615
+ ;;
616
+ make)
617
+ if grep -q '^test:' "$PROJECT_ROOT/Makefile" 2>/dev/null; then
618
+ echo "Detected: Makefile project"
619
+ make test
620
+ test_ran=1
621
+ fi
622
+ ;;
623
+ esac
624
+ ```
625
+
626
+ Replace lines 100-106 (memory check) with:
627
+ ```bash
628
+ if check_memory_available 4; then
629
+ available_gb=$(free -g | awk '/Mem:/{print $7}')
630
+ echo "Memory OK (${available_gb}G available)"
631
+ else
632
+ echo "WARNING: Consider -n 0 for pytest"
633
+ fi
634
+ ```
635
+
636
+ **Step 3: Run quality gate test (existing tests must still pass)**
637
+
638
+ Run: `bash scripts/tests/run-all-tests.sh`
639
+ Expected: ALL PASSED
640
+
641
+ **Step 4: Verify line count**
642
+
643
+ Run: `wc -l scripts/quality-gate.sh`
644
+ Expected: Under 300 lines
645
+
646
+ **Step 5: Commit**
647
+
648
+ ```bash
649
+ git add scripts/quality-gate.sh
650
+ git commit -m "refactor: quality-gate.sh uses common.sh for project detection and memory check"
651
+ ```
652
+
653
+ ### Task 5: Refactor auto-compound.sh to use common.sh and ollama.sh
654
+
655
+ **Files:**
656
+ - Modify: `scripts/auto-compound.sh:17` (add source lines), `scripts/auto-compound.sh:127` (fix PRD discard), `scripts/auto-compound.sh:144-163` (replace inline project detection)
657
+
658
+ **Step 1: Read and identify sections**
659
+
660
+ Three changes:
661
+ 1. Add source lines after `SCRIPT_DIR` (line 17)
662
+ 2. Fix line 127: `> /dev/null 2>&1 || true` discards PRD output — capture it and log errors (lesson-7 fix)
663
+ 3. Replace lines 144-163 (fallback quality checks detection) with `detect_project_type()`
664
+
665
+ **Step 2: Apply changes**
666
+
667
+ After line 17, add:
668
+ ```bash
669
+ source "$SCRIPT_DIR/lib/common.sh"
670
+ source "$SCRIPT_DIR/lib/ollama.sh"
671
+ ```
672
+
673
+ Replace line 127:
674
+ ```bash
675
+ # OLD: claude --print "/create-prd $PRIORITY. Context from analysis: $(cat analysis.json)" > /dev/null 2>&1 || true
676
+ prd_output=$(claude --print "/create-prd $PRIORITY. Context from analysis: $(cat analysis.json)" 2>&1) || {
677
+ echo "WARNING: PRD generation failed:" >&2
678
+ echo "$prd_output" | tail -10 >&2
679
+ }
680
+ ```
681
+
682
+ Replace lines 144-163 (fallback detection) with:
683
+ ```bash
684
+ local project_type
685
+ project_type=$(detect_project_type "$PROJECT_DIR")
686
+ case "$project_type" in
687
+ python) QUALITY_CHECKS="pytest --timeout=120 -x -q" ;;
688
+ node)
689
+ QUALITY_CHECKS=""
690
+ grep -q '"test"' package.json 2>/dev/null && QUALITY_CHECKS+="npm test"
691
+ grep -q '"lint"' package.json 2>/dev/null && { [[ -n "$QUALITY_CHECKS" ]] && QUALITY_CHECKS+=";"; QUALITY_CHECKS+="npm run lint"; }
692
+ ;;
693
+ make) QUALITY_CHECKS="make test" ;;
694
+ *) QUALITY_CHECKS="" ;;
695
+ esac
696
+ echo " Fallback mode — quality-gate.sh not found"
697
+ ```
698
+
699
+ **Step 3: Verify no test regressions**
700
+
701
+ Run: `bash scripts/tests/run-all-tests.sh`
702
+ Expected: ALL PASSED
703
+
704
+ **Step 4: Verify line count**
705
+
706
+ Run: `wc -l scripts/auto-compound.sh`
707
+ Expected: Under 300 lines (was 230, should be ~220 now)
708
+
709
+ **Step 5: Commit**
710
+
711
+ ```bash
712
+ git add scripts/auto-compound.sh
713
+ git commit -m "refactor: auto-compound.sh uses common.sh, fixes PRD output discard (lesson-7)"
714
+ ```
715
+
716
+ ### Task 6: Refactor analyze-report.sh to use ollama.sh
717
+
718
+ **Files:**
719
+ - Modify: `scripts/analyze-report.sh:75-111` (replace inline Ollama call and JSON stripping)
720
+
721
+ **Step 1: Identify replacement sections**
722
+
723
+ Lines 75-88: Ollama API call logic → `ollama_query()`
724
+ Lines 100-111: JSON fence stripping → `strip_json_fences` + `ollama_extract_json()`
725
+
726
+ **Step 2: Apply changes**
727
+
728
+ Add after the `set -euo pipefail` line:
729
+ ```bash
730
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
731
+ source "$SCRIPT_DIR/lib/common.sh"
732
+ source "$SCRIPT_DIR/lib/ollama.sh"
733
+ ```
734
+
735
+ Replace lines 75-111 with:
736
+ ```bash
737
+ # Query Ollama
738
+ ANALYSIS=$(ollama_query "$MODEL" "$PROMPT")
739
+
740
+ if [[ -z "$ANALYSIS" ]]; then
741
+ echo "Error: Empty response from Ollama" >&2
742
+ exit 1
743
+ fi
744
+
745
+ # Parse as JSON
746
+ CLEANED=$(echo "$ANALYSIS" | ollama_extract_json)
747
+ if [[ -n "$CLEANED" ]]; then
748
+ echo "$CLEANED" | jq . > "$OUTPUT_DIR/analysis.json"
749
+ else
750
+ echo "Warning: Could not parse LLM response as JSON, saving raw" >&2
751
+ echo "{\"raw_response\": $(echo "$ANALYSIS" | jq -Rs .)}" > "$OUTPUT_DIR/analysis.json"
752
+ fi
753
+ ```
754
+
755
+ **Step 3: Run tests**
756
+
757
+ Run: `bash scripts/tests/run-all-tests.sh`
758
+ Expected: ALL PASSED
759
+
760
+ **Step 4: Verify line count**
761
+
762
+ Run: `wc -l scripts/analyze-report.sh`
763
+ Expected: Under 100 lines (was 114, trimmed ~30 lines of Ollama logic)
764
+
765
+ **Step 5: Commit**
766
+
767
+ ```bash
768
+ git add scripts/analyze-report.sh
769
+ git commit -m "refactor: analyze-report.sh uses ollama.sh shared library"
770
+ ```
771
+
772
+ ### Task 7: Refactor entropy-audit.sh — remove hardcoded path
773
+
774
+ **Files:**
775
+ - Modify: `scripts/entropy-audit.sh:17` (replace hardcoded path with env var + arg)
776
+
777
+ **Step 1: Identify the fix**
778
+
779
+ Line 17: `PROJECTS_DIR="$HOME/Documents/projects"` is hardcoded. Replace with `--projects-dir` arg that defaults to `PROJECTS_DIR` env var, then `$HOME/Documents/projects`.
780
+
781
+ **Step 2: Apply changes**
782
+
783
+ Add after `set -euo pipefail`:
784
+ ```bash
785
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
786
+ source "$SCRIPT_DIR/lib/common.sh"
787
+ ```
788
+
789
+ Replace line 17:
790
+ ```bash
791
+ PROJECTS_DIR="${PROJECTS_DIR:-$HOME/Documents/projects}"
792
+ ```
793
+
794
+ Add `--projects-dir` to the arg parser (after `--fix` case):
795
+ ```bash
796
+ --projects-dir) PROJECTS_DIR="$2"; shift 2 ;;
797
+ ```
798
+
799
+ **Step 3: Run tests**
800
+
801
+ Run: `bash scripts/tests/run-all-tests.sh`
802
+ Expected: ALL PASSED
803
+
804
+ **Step 4: Commit**
805
+
806
+ ```bash
807
+ git add scripts/entropy-audit.sh
808
+ git commit -m "refactor: entropy-audit.sh uses env var/arg instead of hardcoded projects path"
809
+ ```
810
+
811
+ ### Task 8: Extract run-plan-headless.sh from run-plan.sh
812
+
813
+ **Files:**
814
+ - Create: `scripts/lib/run-plan-headless.sh`
815
+ - Modify: `scripts/run-plan.sh:229-376` (replace `run_mode_headless()` with source + call)
816
+
817
+ **Step 1: Extract `run_mode_headless()` from run-plan.sh**
818
+
819
+ Move lines 229-376 of `scripts/run-plan.sh` (the entire `run_mode_headless()` function) into `scripts/lib/run-plan-headless.sh`. The function reads these globals: `WORKTREE`, `RESUME`, `START_BATCH`, `END_BATCH`, `NOTIFY`, `PLAN_FILE`, `QUALITY_GATE_CMD`, `PYTHON`, `MAX_RETRIES`, `ON_FAILURE`, `VERIFY`, `MODE`. These are all set before the function is called.
820
+
821
+ **Step 2: Create `scripts/lib/run-plan-headless.sh`**
822
+
823
+ ```bash
824
+ #!/usr/bin/env bash
825
+ # run-plan-headless.sh — Headless batch execution loop for run-plan
826
+ #
827
+ # Extracted from run-plan.sh to keep the main script under 300 lines.
828
+ #
829
+ # Requires these globals set before calling:
830
+ # WORKTREE, RESUME, START_BATCH, END_BATCH, NOTIFY, PLAN_FILE,
831
+ # QUALITY_GATE_CMD, PYTHON, MAX_RETRIES, ON_FAILURE, VERIFY, MODE
832
+ #
833
+ # Requires these libs sourced:
834
+ # run-plan-parser.sh, run-plan-state.sh, run-plan-quality-gate.sh,
835
+ # run-plan-notify.sh, run-plan-prompt.sh
836
+
837
+ run_mode_headless() {
838
+ # (paste the entire function body from run-plan.sh lines 230-376 here verbatim)
839
+ }
840
+ ```
841
+
842
+ Copy the function body exactly from lines 230-376. Do not modify any logic.
843
+
844
+ **Step 3: Update run-plan.sh**
845
+
846
+ Add source line after the other source statements (line 21):
847
+ ```bash
848
+ source "$SCRIPT_DIR/lib/run-plan-headless.sh"
849
+ ```
850
+
851
+ Remove lines 229-376 (the old `run_mode_headless` function).
852
+
853
+ **Step 4: Run all tests**
854
+
855
+ Run: `bash scripts/tests/run-all-tests.sh`
856
+ Expected: ALL PASSED (existing tests exercise run-plan.sh and should still work)
857
+
858
+ **Step 5: Verify line counts**
859
+
860
+ Run: `wc -l scripts/run-plan.sh scripts/lib/run-plan-headless.sh`
861
+ Expected: `run-plan.sh` ~260 lines (under 300), `run-plan-headless.sh` ~150 lines
862
+
863
+ **Step 6: Commit**
864
+
865
+ ```bash
866
+ git add scripts/lib/run-plan-headless.sh scripts/run-plan.sh
867
+ git commit -m "refactor: extract run_mode_headless() into scripts/lib/run-plan-headless.sh"
868
+ ```
869
+
870
+ ## Batch 3: Accuracy Fixes — Test Count Parsing and Cross-Batch Context
871
+
872
+ Fix the two most impactful accuracy bugs: test count parsing that only works for pytest, and missing cross-batch context that causes agents to repeat work.
873
+
874
+ ### Task 9: Fix test count parsing for multiple test frameworks
875
+
876
+ **Files:**
877
+ - Modify: `scripts/lib/run-plan-quality-gate.sh:19-29` (replace `extract_test_count`)
878
+ - Modify: `scripts/tests/test-run-plan-quality-gate.sh` (add new test cases)
879
+
880
+ **Step 1: Add failing tests for jest, go, and unknown formats**
881
+
882
+ Add to `scripts/tests/test-run-plan-quality-gate.sh` after the existing `extract_test_count` tests (before the `check_test_count_regression` section):
883
+
884
+ ```bash
885
+ # --- Test: extract from jest output ---
886
+ output="Tests: 3 failed, 45 passed, 48 total"
887
+ val=$(extract_test_count "$output")
888
+ assert_eq "extract_test_count: jest output" "45" "$val"
889
+
890
+ # --- Test: extract from jest all-pass output ---
891
+ output="Tests: 12 passed, 12 total"
892
+ val=$(extract_test_count "$output")
893
+ assert_eq "extract_test_count: jest all-pass" "12" "$val"
894
+
895
+ # --- Test: extract from go test output ---
896
+ output="ok github.com/foo/bar 0.123s
897
+ ok github.com/foo/baz 0.456s
898
+ FAIL github.com/foo/qux 0.789s"
899
+ val=$(extract_test_count "$output")
900
+ assert_eq "extract_test_count: go test (2 ok of 3)" "2" "$val"
901
+
902
+ # --- Test: unrecognized format returns -1 ---
903
+ val=$(extract_test_count "Some random build output with no test results")
904
+ assert_eq "extract_test_count: unrecognized format" "-1" "$val"
905
+ ```
906
+
907
+ **Step 2: Run test to verify failures**
908
+
909
+ Run: `bash scripts/tests/test-run-plan-quality-gate.sh`
910
+ Expected: FAIL on jest, go, and unrecognized format tests
911
+
912
+ **Step 3: Update extract_test_count implementation**
913
+
914
+ Replace `extract_test_count()` in `scripts/lib/run-plan-quality-gate.sh`:
915
+
916
+ ```bash
917
+ extract_test_count() {
918
+ local output="$1"
919
+ local count
920
+
921
+ # 1. pytest: "N passed" (e.g., "85 passed" in "3 failed, 85 passed, 2 skipped in 30.1s")
922
+ count=$(echo "$output" | grep -oP '\b(\d+) passed\b' | tail -1 | grep -oP '^\d+' || true)
923
+ if [[ -n "$count" ]]; then
924
+ echo "$count"
925
+ return
926
+ fi
927
+
928
+ # 2. jest: "Tests: N passed" (e.g., "Tests: 45 passed, 48 total")
929
+ count=$(echo "$output" | grep -oP 'Tests:\s+(\d+ failed, )?\K\d+(?= passed)' || true)
930
+ if [[ -n "$count" ]]; then
931
+ echo "$count"
932
+ return
933
+ fi
934
+
935
+ # 3. go test: count "ok" lines (each = one passing package)
936
+ count=$(echo "$output" | grep -c '^ok' || true)
937
+ if [[ "$count" -gt 0 ]]; then
938
+ echo "$count"
939
+ return
940
+ fi
941
+
942
+ # 4. No recognized format — return -1 to signal "skip regression check"
943
+ echo "-1"
944
+ }
945
+ ```
946
+
947
+ Also update `check_test_count_regression()` to handle `-1`:
948
+ ```bash
949
+ check_test_count_regression() {
950
+ local new_count="$1" previous_count="$2"
951
+ # -1 means unrecognized format — skip regression check
952
+ if [[ "$new_count" == "-1" || "$previous_count" == "-1" ]]; then
953
+ echo "INFO: Skipping test count regression check (unrecognized test format)" >&2
954
+ return 0
955
+ fi
956
+ if [[ "$new_count" -ge "$previous_count" ]]; then
957
+ return 0
958
+ else
959
+ echo "WARNING: Test count regression: $new_count < $previous_count (previous)" >&2
960
+ return 1
961
+ fi
962
+ }
963
+ ```
964
+
965
+ **Step 4: Run tests**
966
+
967
+ Run: `bash scripts/tests/test-run-plan-quality-gate.sh`
968
+ Expected: ALL PASSED
969
+
970
+ Also add a regression test for `check_test_count_regression` with -1:
971
+ ```bash
972
+ # --- Test: -1 skips regression check ---
973
+ assert_exit "check_test_count_regression: -1 new skips check" 0 \
974
+ check_test_count_regression -1 150
975
+
976
+ assert_exit "check_test_count_regression: -1 previous skips check" 0 \
977
+ check_test_count_regression 50 -1
978
+ ```
979
+
980
+ **Step 5: Commit**
981
+
982
+ ```bash
983
+ git add scripts/lib/run-plan-quality-gate.sh scripts/tests/test-run-plan-quality-gate.sh
984
+ git commit -m "fix: test count parsing supports jest, go test, and returns -1 for unknown formats"
985
+ ```
986
+
987
+ ### Task 10: Add cross-batch context to prompts
988
+
989
+ **Files:**
990
+ - Modify: `scripts/lib/run-plan-prompt.sh` (add git log, progress.txt, quality gate context)
991
+ - Modify: `scripts/tests/test-run-plan-prompt.sh` (add assertions for new context)
992
+
993
+ **Step 1: Read existing prompt test**
994
+
995
+ Read `scripts/tests/test-run-plan-prompt.sh` to understand current test structure.
996
+
997
+ **Step 2: Add failing test**
998
+
999
+ Add assertion that the prompt output contains cross-batch context markers:
1000
+
1001
+ ```bash
1002
+ # --- Test: prompt includes cross-batch context ---
1003
+ # Create a state file with previous quality gate
1004
+ echo '{"last_quality_gate":{"batch":1,"passed":true,"test_count":42}}' > "$WORK/.run-plan-state.json"
1005
+ # Create progress.txt
1006
+ echo "Batch 1: Implemented auth module" > "$WORK/progress.txt"
1007
+ # Create a git commit in the work dir
1008
+ echo "file" > "$WORK/code.py"
1009
+ git -C "$WORK" add code.py && git -C "$WORK" commit -q -m "feat: add auth"
1010
+
1011
+ prompt=$(build_batch_prompt "$PLAN" 2 "$WORK" "python3" "scripts/quality-gate.sh" 42)
1012
+ echo "$prompt" | grep -q "Recent commits" || {
1013
+ echo "FAIL: prompt missing 'Recent commits' section"
1014
+ FAILURES=$((FAILURES + 1))
1015
+ }
1016
+ TESTS=$((TESTS + 1))
1017
+ echo "$prompt" | grep -q "progress.txt" || echo "$prompt" | grep -q "Previous progress" || {
1018
+ echo "FAIL: prompt missing progress.txt context"
1019
+ FAILURES=$((FAILURES + 1))
1020
+ }
1021
+ TESTS=$((TESTS + 1))
1022
+ ```
1023
+
1024
+ **Step 3: Run test to verify failure**
1025
+
1026
+ Run: `bash scripts/tests/test-run-plan-prompt.sh`
1027
+ Expected: FAIL on cross-batch context assertions
1028
+
1029
+ **Step 4: Update build_batch_prompt**
1030
+
1031
+ Replace `build_batch_prompt()` in `scripts/lib/run-plan-prompt.sh`:
1032
+
1033
+ ```bash
1034
+ build_batch_prompt() {
1035
+ local plan_file="$1"
1036
+ local batch_num="$2"
1037
+ local worktree="$3"
1038
+ local python="$4"
1039
+ local quality_gate_cmd="$5"
1040
+ local prev_test_count="$6"
1041
+
1042
+ local title branch batch_text recent_commits progress_tail prev_gate
1043
+
1044
+ title=$(get_batch_title "$plan_file" "$batch_num")
1045
+ branch=$(git -C "$worktree" branch --show-current 2>/dev/null || echo "unknown")
1046
+ batch_text=$(get_batch_text "$plan_file" "$batch_num")
1047
+
1048
+ # Cross-batch context: recent commits
1049
+ recent_commits=$(git -C "$worktree" log --oneline -5 2>/dev/null || echo "(no commits)")
1050
+
1051
+ # Cross-batch context: progress.txt tail
1052
+ progress_tail=""
1053
+ if [[ -f "$worktree/progress.txt" ]]; then
1054
+ progress_tail=$(tail -20 "$worktree/progress.txt" 2>/dev/null || true)
1055
+ fi
1056
+
1057
+ # Cross-batch context: previous quality gate result
1058
+ prev_gate=""
1059
+ if [[ -f "$worktree/.run-plan-state.json" ]]; then
1060
+ prev_gate=$(jq -r '.last_quality_gate // empty' "$worktree/.run-plan-state.json" 2>/dev/null || true)
1061
+ fi
1062
+
1063
+ cat <<PROMPT
1064
+ You are implementing Batch ${batch_num}: ${title} from ${plan_file}.
1065
+
1066
+ Working directory: ${worktree}
1067
+ Python: ${python}
1068
+ Branch: ${branch}
1069
+
1070
+ Tasks in this batch:
1071
+ ${batch_text}
1072
+
1073
+ Recent commits:
1074
+ ${recent_commits}
1075
+ $(if [[ -n "$progress_tail" ]]; then
1076
+ echo "
1077
+ Previous progress:
1078
+ ${progress_tail}"
1079
+ fi)
1080
+ $(if [[ -n "$prev_gate" && "$prev_gate" != "null" ]]; then
1081
+ echo "
1082
+ Previous quality gate: ${prev_gate}"
1083
+ fi)
1084
+
1085
+ Requirements:
1086
+ - TDD: write test -> verify fail -> implement -> verify pass -> commit each task
1087
+ - After all tasks: run quality gate (${quality_gate_cmd})
1088
+ - Update progress.txt with batch summary and commit
1089
+ - All ${prev_test_count}+ tests must pass
1090
+ PROMPT
1091
+ }
1092
+ ```
1093
+
1094
+ **Step 5: Run tests**
1095
+
1096
+ Run: `bash scripts/tests/test-run-plan-prompt.sh`
1097
+ Expected: ALL PASSED
1098
+
1099
+ **Step 6: Commit**
1100
+
1101
+ ```bash
1102
+ git add scripts/lib/run-plan-prompt.sh scripts/tests/test-run-plan-prompt.sh
1103
+ git commit -m "feat: add cross-batch context (git log, progress.txt, gate result) to prompts"
1104
+ ```
1105
+
1106
+ ### Task 11: Add duration tracking to state
1107
+
1108
+ **Files:**
1109
+ - Modify: `scripts/lib/run-plan-state.sh` (add `duration_seconds` field to `complete_batch`)
1110
+ - Modify: `scripts/lib/run-plan-headless.sh` (pass duration to `complete_batch`)
1111
+ - Modify: `scripts/tests/test-run-plan-state.sh` (add duration assertions)
1112
+
1113
+ **Step 1: Add failing test**
1114
+
1115
+ Add to `scripts/tests/test-run-plan-state.sh`:
1116
+
1117
+ ```bash
1118
+ # --- Test: complete_batch stores duration ---
1119
+ complete_batch "$WORK" 1 42 120
1120
+ duration=$(jq -r '.durations["1"]' "$WORK/.run-plan-state.json")
1121
+ assert_eq "complete_batch: stores duration" "120" "$duration"
1122
+ ```
1123
+
1124
+ **Step 2: Run test to verify failure**
1125
+
1126
+ Run: `bash scripts/tests/test-run-plan-state.sh`
1127
+ Expected: FAIL — `complete_batch` only takes 3 args currently
1128
+
1129
+ **Step 3: Update complete_batch to accept optional duration**
1130
+
1131
+ In `scripts/lib/run-plan-state.sh`, update `complete_batch()`:
1132
+
1133
+ ```bash
1134
+ complete_batch() {
1135
+ local worktree="$1" batch_num="$2" test_count="$3" duration="${4:-0}"
1136
+ local sf tmp
1137
+ sf=$(_state_file "$worktree")
1138
+ tmp=$(mktemp)
1139
+
1140
+ jq \
1141
+ --argjson batch "$batch_num" \
1142
+ --argjson tc "$test_count" \
1143
+ --argjson dur "$duration" \
1144
+ '
1145
+ .completed_batches += [$batch] |
1146
+ .current_batch = ($batch + 1) |
1147
+ .test_counts[($batch | tostring)] = $tc |
1148
+ .durations[($batch | tostring)] = $dur
1149
+ ' "$sf" > "$tmp" && mv "$tmp" "$sf"
1150
+ }
1151
+ ```
1152
+
1153
+ Update `init_state()` to include `durations: {}`:
1154
+ ```bash
1155
+ jq -n \
1156
+ --arg plan_file "$plan_file" \
1157
+ --arg mode "$mode" \
1158
+ --arg started_at "$now" \
1159
+ '{
1160
+ plan_file: $plan_file,
1161
+ mode: $mode,
1162
+ current_batch: 1,
1163
+ completed_batches: [],
1164
+ test_counts: {},
1165
+ durations: {},
1166
+ started_at: $started_at,
1167
+ last_quality_gate: null
1168
+ }' > "$sf"
1169
+ ```
1170
+
1171
+ Update `run_mode_headless()` in `run-plan-headless.sh` — pass duration to `complete_batch`. Find the line `complete_batch "$WORKTREE" "$batch_num" "$test_count"` inside `run_quality_gate` and also ensure the headless loop passes duration. The duration is already computed as `$duration` variable.
1172
+
1173
+ **Step 4: Run tests**
1174
+
1175
+ Run: `bash scripts/tests/test-run-plan-state.sh && bash scripts/tests/run-all-tests.sh`
1176
+ Expected: ALL PASSED
1177
+
1178
+ **Step 5: Commit**
1179
+
1180
+ ```bash
1181
+ git add scripts/lib/run-plan-state.sh scripts/lib/run-plan-headless.sh scripts/tests/test-run-plan-state.sh
1182
+ git commit -m "feat: add per-batch duration tracking to run-plan state"
1183
+ ```
1184
+
1185
+ ## Batch 4: Quality Gate Expansion
1186
+
1187
+ Add lint checking and prior-art search to the quality gate pipeline.
1188
+
1189
+ ### Task 12: Add ruff lint step to quality-gate.sh
1190
+
1191
+ **Files:**
1192
+ - Modify: `scripts/quality-gate.sh` (add lint check between lesson-check and test suite)
1193
+
1194
+ **Step 1: Verify ruff is installed**
1195
+
1196
+ Run: `ruff --version`
1197
+ If not installed: `pip install ruff` (or `brew install ruff`)
1198
+
1199
+ **Step 2: Add lint check to quality-gate.sh**
1200
+
1201
+ Insert after the lesson check section (after the `fi` on line ~72), before the test suite section:
1202
+
1203
+ ```bash
1204
+ # === Check 2: Lint (ruff for Python, eslint for Node) ===
1205
+ echo ""
1206
+ echo "=== Quality Gate: Lint Check ==="
1207
+ project_type=$(detect_project_type "$PROJECT_ROOT")
1208
+ lint_ran=0
1209
+
1210
+ case "$project_type" in
1211
+ python)
1212
+ if command -v ruff >/dev/null 2>&1; then
1213
+ echo "Running: ruff check --select E,W,F"
1214
+ if ! ruff check --select E,W,F "$PROJECT_ROOT"; then
1215
+ echo ""
1216
+ echo "quality-gate: FAILED at lint check"
1217
+ exit 1
1218
+ fi
1219
+ lint_ran=1
1220
+ else
1221
+ echo "ruff not installed — skipping Python lint"
1222
+ fi
1223
+ ;;
1224
+ node)
1225
+ if [[ -f "$PROJECT_ROOT/.eslintrc" || -f "$PROJECT_ROOT/.eslintrc.js" || -f "$PROJECT_ROOT/.eslintrc.json" || -f "$PROJECT_ROOT/eslint.config.js" ]]; then
1226
+ echo "Running: npx eslint"
1227
+ if ! npx eslint "$PROJECT_ROOT" 2>/dev/null; then
1228
+ echo ""
1229
+ echo "quality-gate: FAILED at lint check"
1230
+ exit 1
1231
+ fi
1232
+ lint_ran=1
1233
+ else
1234
+ echo "No eslint config found — skipping Node lint"
1235
+ fi
1236
+ ;;
1237
+ esac
1238
+
1239
+ if [[ $lint_ran -eq 0 ]]; then
1240
+ echo "No linter configured — skipped"
1241
+ fi
1242
+ ```
1243
+
1244
+ Renumber the subsequent checks (test suite becomes Check 3, memory becomes Check 4).
1245
+
1246
+ **Step 3: Run tests**
1247
+
1248
+ Run: `bash scripts/tests/run-all-tests.sh`
1249
+ Expected: ALL PASSED
1250
+
1251
+ **Step 4: Verify line count**
1252
+
1253
+ Run: `wc -l scripts/quality-gate.sh`
1254
+ Expected: Under 300 lines (~145 now)
1255
+
1256
+ **Step 5: Commit**
1257
+
1258
+ ```bash
1259
+ git add scripts/quality-gate.sh
1260
+ git commit -m "feat: add ruff/eslint lint step to quality-gate.sh"
1261
+ ```
1262
+
1263
+ ### Task 13: Create prior-art-search.sh
1264
+
1265
+ **Files:**
1266
+ - Create: `scripts/prior-art-search.sh`
1267
+ - Create: `scripts/tests/test-prior-art-search.sh`
1268
+
1269
+ **Step 1: Write the failing test**
1270
+
1271
+ ```bash
1272
+ #!/usr/bin/env bash
1273
+ # Test prior-art-search.sh
1274
+ set -euo pipefail
1275
+
1276
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1277
+ SEARCH_SCRIPT="$SCRIPT_DIR/../prior-art-search.sh"
1278
+
1279
+ FAILURES=0
1280
+ TESTS=0
1281
+
1282
+ assert_eq() {
1283
+ local desc="$1" expected="$2" actual="$3"
1284
+ TESTS=$((TESTS + 1))
1285
+ if [[ "$expected" != "$actual" ]]; then
1286
+ echo "FAIL: $desc"
1287
+ echo " expected: $expected"
1288
+ echo " actual: $actual"
1289
+ FAILURES=$((FAILURES + 1))
1290
+ else
1291
+ echo "PASS: $desc"
1292
+ fi
1293
+ }
1294
+
1295
+ assert_exit() {
1296
+ local desc="$1" expected_exit="$2"
1297
+ shift 2
1298
+ local actual_exit=0
1299
+ "$@" || actual_exit=$?
1300
+ TESTS=$((TESTS + 1))
1301
+ if [[ "$expected_exit" != "$actual_exit" ]]; then
1302
+ echo "FAIL: $desc"
1303
+ echo " expected exit: $expected_exit"
1304
+ echo " actual exit: $actual_exit"
1305
+ FAILURES=$((FAILURES + 1))
1306
+ else
1307
+ echo "PASS: $desc"
1308
+ fi
1309
+ }
1310
+
1311
+ # --- Test: --help exits 0 ---
1312
+ assert_exit "prior-art-search --help exits 0" 0 \
1313
+ bash "$SEARCH_SCRIPT" --help
1314
+
1315
+ # --- Test: --dry-run produces output without calling gh ---
1316
+ output=$(bash "$SEARCH_SCRIPT" --dry-run "implement webhook handler" 2>&1)
1317
+ echo "$output" | grep -q "Search query:" && TESTS=$((TESTS + 1)) && echo "PASS: dry-run shows search query" || {
1318
+ TESTS=$((TESTS + 1)); echo "FAIL: dry-run missing search query"; FAILURES=$((FAILURES + 1))
1319
+ }
1320
+
1321
+ # --- Test: missing query shows usage ---
1322
+ assert_exit "prior-art-search: no args exits 1" 1 \
1323
+ bash "$SEARCH_SCRIPT"
1324
+
1325
+ # === Summary ===
1326
+ echo ""
1327
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
1328
+ if [[ $FAILURES -gt 0 ]]; then
1329
+ echo "FAILURES: $FAILURES"
1330
+ exit 1
1331
+ fi
1332
+ echo "ALL PASSED"
1333
+ ```
1334
+
1335
+ **Step 2: Run test to verify it fails**
1336
+
1337
+ Run: `bash scripts/tests/test-prior-art-search.sh`
1338
+ Expected: FAIL — script does not exist
1339
+
1340
+ **Step 3: Write implementation**
1341
+
1342
+ Create `scripts/prior-art-search.sh`:
1343
+
1344
+ ```bash
1345
+ #!/usr/bin/env bash
1346
+ # prior-art-search.sh — Search GitHub and local codebase for prior art
1347
+ #
1348
+ # Usage: prior-art-search.sh [--dry-run] [--local-only] [--github-only] <query>
1349
+ #
1350
+ # Searches:
1351
+ # 1. GitHub repos (gh search repos)
1352
+ # 2. GitHub code (gh search code)
1353
+ # 3. Local ~/Documents/projects/ (grep -r)
1354
+ #
1355
+ # Output: Ranked results with source, relevance, and URL/path
1356
+ set -euo pipefail
1357
+
1358
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1359
+ source "$SCRIPT_DIR/lib/common.sh"
1360
+
1361
+ DRY_RUN=false
1362
+ LOCAL_ONLY=false
1363
+ GITHUB_ONLY=false
1364
+ QUERY=""
1365
+ MAX_RESULTS=10
1366
+ PROJECTS_DIR="${PROJECTS_DIR:-$HOME/Documents/projects}"
1367
+
1368
+ while [[ $# -gt 0 ]]; do
1369
+ case "$1" in
1370
+ --dry-run) DRY_RUN=true; shift ;;
1371
+ --local-only) LOCAL_ONLY=true; shift ;;
1372
+ --github-only) GITHUB_ONLY=true; shift ;;
1373
+ --max-results) MAX_RESULTS="$2"; shift 2 ;;
1374
+ --projects-dir) PROJECTS_DIR="$2"; shift 2 ;;
1375
+ -h|--help)
1376
+ cat <<'USAGE'
1377
+ prior-art-search.sh — Search for prior art before building
1378
+
1379
+ Usage: prior-art-search.sh [OPTIONS] <query>
1380
+
1381
+ Options:
1382
+ --dry-run Show what would be searched without executing
1383
+ --local-only Only search local projects
1384
+ --github-only Only search GitHub
1385
+ --max-results N Max results per source (default: 10)
1386
+ --projects-dir P Local projects directory
1387
+
1388
+ Output: Results ranked by relevance with source attribution
1389
+ USAGE
1390
+ exit 0
1391
+ ;;
1392
+ -*) echo "Unknown option: $1" >&2; exit 1 ;;
1393
+ *) QUERY="$1"; shift ;;
1394
+ esac
1395
+ done
1396
+
1397
+ if [[ -z "$QUERY" ]]; then
1398
+ echo "Error: Query required" >&2
1399
+ echo "Usage: prior-art-search.sh <query>" >&2
1400
+ exit 1
1401
+ fi
1402
+
1403
+ echo "=== Prior Art Search ==="
1404
+ echo "Search query: $QUERY"
1405
+ echo ""
1406
+
1407
+ if [[ "$DRY_RUN" == true ]]; then
1408
+ echo "[dry-run] Would search:"
1409
+ [[ "$LOCAL_ONLY" != true ]] && echo " - GitHub repos: gh search repos '$QUERY' --limit $MAX_RESULTS"
1410
+ [[ "$LOCAL_ONLY" != true ]] && echo " - GitHub code: gh search code '$QUERY' --limit $MAX_RESULTS"
1411
+ [[ "$GITHUB_ONLY" != true ]] && echo " - Local projects: grep -rl in $PROJECTS_DIR"
1412
+ exit 0
1413
+ fi
1414
+
1415
+ # Search 1: GitHub repos
1416
+ if [[ "$LOCAL_ONLY" != true ]]; then
1417
+ echo "--- GitHub Repos ---"
1418
+ if command -v gh >/dev/null 2>&1; then
1419
+ gh search repos "$QUERY" --limit "$MAX_RESULTS" --json name,url,description,stargazersCount \
1420
+ --jq '.[] | "★ \(.stargazersCount) | \(.name) — \(.description // "no description") | \(.url)"' \
1421
+ 2>/dev/null || echo " (GitHub search unavailable)"
1422
+ else
1423
+ echo " gh CLI not installed — skipping"
1424
+ fi
1425
+ echo ""
1426
+
1427
+ echo "--- GitHub Code ---"
1428
+ if command -v gh >/dev/null 2>&1; then
1429
+ gh search code "$QUERY" --limit "$MAX_RESULTS" --json repository,path \
1430
+ --jq '.[] | "\(.repository.nameWithOwner)/\(.path)"' \
1431
+ 2>/dev/null || echo " (GitHub code search unavailable)"
1432
+ else
1433
+ echo " gh CLI not installed — skipping"
1434
+ fi
1435
+ echo ""
1436
+ fi
1437
+
1438
+ # Search 2: Local projects
1439
+ if [[ "$GITHUB_ONLY" != true ]]; then
1440
+ echo "--- Local Projects ---"
1441
+ if [[ -d "$PROJECTS_DIR" ]]; then
1442
+ grep -rl --include='*.py' --include='*.sh' --include='*.ts' --include='*.js' \
1443
+ "$QUERY" "$PROJECTS_DIR" 2>/dev/null | head -"$MAX_RESULTS" || echo " No local matches"
1444
+ else
1445
+ echo " Projects directory not found: $PROJECTS_DIR"
1446
+ fi
1447
+ echo ""
1448
+ fi
1449
+
1450
+ echo "=== Search Complete ==="
1451
+ ```
1452
+
1453
+ **Step 4: Run tests**
1454
+
1455
+ Run: `bash scripts/tests/test-prior-art-search.sh`
1456
+ Expected: ALL PASSED
1457
+
1458
+ **Step 5: Commit**
1459
+
1460
+ ```bash
1461
+ chmod +x scripts/prior-art-search.sh
1462
+ git add scripts/prior-art-search.sh scripts/tests/test-prior-art-search.sh
1463
+ git commit -m "feat: create prior-art-search.sh for GitHub and local code search"
1464
+ ```
1465
+
1466
+ ### Task 14: Create pipeline-status.sh
1467
+
1468
+ **Files:**
1469
+ - Create: `scripts/pipeline-status.sh`
1470
+
1471
+ **Step 1: Write the script**
1472
+
1473
+ ```bash
1474
+ #!/usr/bin/env bash
1475
+ # pipeline-status.sh — Single-command view of Code Factory pipeline status
1476
+ #
1477
+ # Usage: pipeline-status.sh [--project-root <dir>]
1478
+ set -euo pipefail
1479
+
1480
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1481
+ source "$SCRIPT_DIR/lib/common.sh"
1482
+
1483
+ PROJECT_ROOT="${1:-.}"
1484
+
1485
+ if [[ "$1" == "--help" || "$1" == "-h" ]]; then
1486
+ echo "pipeline-status.sh — Show Code Factory pipeline status"
1487
+ echo "Usage: pipeline-status.sh [project-root]"
1488
+ exit 0
1489
+ fi
1490
+
1491
+ echo "═══════════════════════════════════════════════"
1492
+ echo " Code Factory Pipeline Status"
1493
+ echo "═══════════════════════════════════════════════"
1494
+ echo "Project: $(basename "$(realpath "$PROJECT_ROOT")")"
1495
+ echo "Type: $(detect_project_type "$PROJECT_ROOT")"
1496
+ echo ""
1497
+
1498
+ # Run-plan state
1499
+ STATE_FILE="$PROJECT_ROOT/.run-plan-state.json"
1500
+ if [[ -f "$STATE_FILE" ]]; then
1501
+ echo "--- Run Plan ---"
1502
+ plan=$(jq -r '.plan_file // "unknown"' "$STATE_FILE")
1503
+ mode=$(jq -r '.mode // "unknown"' "$STATE_FILE")
1504
+ current=$(jq -r '.current_batch // 0' "$STATE_FILE")
1505
+ completed=$(jq -r '.completed_batches | length' "$STATE_FILE")
1506
+ started=$(jq -r '.started_at // "unknown"' "$STATE_FILE")
1507
+ echo " Plan: $(basename "$plan")"
1508
+ echo " Mode: $mode"
1509
+ echo " Progress: $completed batches completed (current: $current)"
1510
+ echo " Started: $started"
1511
+
1512
+ # Last quality gate
1513
+ gate_passed=$(jq -r '.last_quality_gate.passed // "n/a"' "$STATE_FILE")
1514
+ gate_tests=$(jq -r '.last_quality_gate.test_count // "n/a"' "$STATE_FILE")
1515
+ echo " Last gate: passed=$gate_passed, tests=$gate_tests"
1516
+ echo ""
1517
+ else
1518
+ echo "--- Run Plan ---"
1519
+ echo " No active run-plan state found"
1520
+ echo ""
1521
+ fi
1522
+
1523
+ # PRD status
1524
+ if [[ -f "$PROJECT_ROOT/tasks/prd.json" ]]; then
1525
+ echo "--- PRD ---"
1526
+ total=$(jq 'length' "$PROJECT_ROOT/tasks/prd.json")
1527
+ passing=$(jq '[.[] | select(.passes == true)] | length' "$PROJECT_ROOT/tasks/prd.json")
1528
+ echo " Tasks: $passing/$total passing"
1529
+ echo ""
1530
+ else
1531
+ echo "--- PRD ---"
1532
+ echo " No PRD found (tasks/prd.json)"
1533
+ echo ""
1534
+ fi
1535
+
1536
+ # Progress file
1537
+ if [[ -f "$PROJECT_ROOT/progress.txt" ]]; then
1538
+ echo "--- Progress ---"
1539
+ tail -5 "$PROJECT_ROOT/progress.txt" | sed 's/^/ /'
1540
+ echo ""
1541
+ fi
1542
+
1543
+ # Git status
1544
+ echo "--- Git ---"
1545
+ branch=$(git -C "$PROJECT_ROOT" branch --show-current 2>/dev/null || echo "unknown")
1546
+ uncommitted=$(git -C "$PROJECT_ROOT" status --porcelain 2>/dev/null | wc -l || echo 0)
1547
+ echo " Branch: $branch"
1548
+ echo " Uncommitted: $uncommitted files"
1549
+
1550
+ echo ""
1551
+ echo "═══════════════════════════════════════════════"
1552
+ ```
1553
+
1554
+ **Step 2: Test manually**
1555
+
1556
+ Run: `bash scripts/pipeline-status.sh .`
1557
+ Expected: Shows status output without errors
1558
+
1559
+ **Step 3: Commit**
1560
+
1561
+ ```bash
1562
+ chmod +x scripts/pipeline-status.sh
1563
+ git add scripts/pipeline-status.sh
1564
+ git commit -m "feat: create pipeline-status.sh for single-command pipeline overview"
1565
+ ```
1566
+
1567
+ ### Task 15: Wire prior-art search into auto-compound.sh
1568
+
1569
+ **Files:**
1570
+ - Modify: `scripts/auto-compound.sh` (add prior-art search step before PRD generation)
1571
+
1572
+ **Step 1: Add search step**
1573
+
1574
+ Insert after Step 2 (branch creation, ~line 117) and before Step 3 (PRD generation):
1575
+
1576
+ ```bash
1577
+ # Step 2.5: Prior art search
1578
+ echo "🔎 Step 2.5: Searching for prior art..."
1579
+ if [[ "$DRY_RUN" == "true" ]]; then
1580
+ echo " [dry-run] Would search: $PRIORITY"
1581
+ else
1582
+ PRIOR_ART=$("$SCRIPT_DIR/prior-art-search.sh" "$PRIORITY" 2>&1 || true)
1583
+ echo "$PRIOR_ART" | head -20
1584
+ # Save for PRD context
1585
+ echo "$PRIOR_ART" > prior-art-results.txt
1586
+ echo " Saved to prior-art-results.txt"
1587
+
1588
+ # Append to progress.txt
1589
+ mkdir -p "$(dirname progress.txt)"
1590
+ echo "## Prior Art Search: $PRIORITY" >> progress.txt
1591
+ echo "$PRIOR_ART" | head -10 >> progress.txt
1592
+ echo "" >> progress.txt
1593
+ fi
1594
+ echo ""
1595
+ ```
1596
+
1597
+ Update Step 3 (PRD generation) to include prior art context:
1598
+
1599
+ ```bash
1600
+ # Include prior art if available
1601
+ local prior_art_context=""
1602
+ if [[ -f "prior-art-results.txt" ]]; then
1603
+ prior_art_context="Prior art found: $(head -20 prior-art-results.txt)"
1604
+ fi
1605
+ prd_output=$(claude --print "/create-prd $PRIORITY. Context from analysis: $(cat analysis.json). $prior_art_context" 2>&1) || {
1606
+ echo "WARNING: PRD generation failed:" >&2
1607
+ echo "$prd_output" | tail -10 >&2
1608
+ }
1609
+ ```
1610
+
1611
+ **Step 2: Verify no regressions**
1612
+
1613
+ Run: `bash scripts/tests/run-all-tests.sh`
1614
+ Expected: ALL PASSED
1615
+
1616
+ **Step 3: Verify line count**
1617
+
1618
+ Run: `wc -l scripts/auto-compound.sh`
1619
+ Expected: Under 300 lines
1620
+
1621
+ **Step 4: Commit**
1622
+
1623
+ ```bash
1624
+ git add scripts/auto-compound.sh
1625
+ git commit -m "feat: wire prior-art search into auto-compound.sh before PRD generation"
1626
+ ```
1627
+
1628
+ ## Batch 5: New Capabilities — Failure Digest and Structured Context
1629
+
1630
+ Add intelligent failure analysis and structured cross-batch dependencies.
1631
+
1632
+ ### Task 16: Create failure-digest.sh
1633
+
1634
+ **Files:**
1635
+ - Create: `scripts/failure-digest.sh`
1636
+ - Create: `scripts/tests/test-failure-digest.sh`
1637
+
1638
+ **Step 1: Write the failing test**
1639
+
1640
+ ```bash
1641
+ #!/usr/bin/env bash
1642
+ # Test failure-digest.sh
1643
+ set -euo pipefail
1644
+
1645
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1646
+ DIGEST_SCRIPT="$SCRIPT_DIR/../failure-digest.sh"
1647
+
1648
+ FAILURES=0
1649
+ TESTS=0
1650
+
1651
+ assert_eq() {
1652
+ local desc="$1" expected="$2" actual="$3"
1653
+ TESTS=$((TESTS + 1))
1654
+ if [[ "$expected" != "$actual" ]]; then
1655
+ echo "FAIL: $desc"
1656
+ echo " expected: $expected"
1657
+ echo " actual: $actual"
1658
+ FAILURES=$((FAILURES + 1))
1659
+ else
1660
+ echo "PASS: $desc"
1661
+ fi
1662
+ }
1663
+
1664
+ WORK=$(mktemp -d)
1665
+ trap "rm -rf '$WORK'" EXIT
1666
+
1667
+ # Create a fake log with errors
1668
+ cat > "$WORK/batch-1-attempt-1.log" << 'LOG'
1669
+ Some setup output...
1670
+ FAILED tests/test_auth.py::test_login - AssertionError: expected 200 got 401
1671
+ FAILED tests/test_auth.py::test_signup - KeyError: 'email'
1672
+ Traceback (most recent call last):
1673
+ File "src/auth.py", line 42, in login
1674
+ token = generate_token(user)
1675
+ TypeError: generate_token() missing 1 required argument: 'secret'
1676
+ 3 failed, 10 passed in 5.2s
1677
+ LOG
1678
+
1679
+ # --- Test: extracts failed test names ---
1680
+ output=$(bash "$DIGEST_SCRIPT" "$WORK/batch-1-attempt-1.log")
1681
+ echo "$output" | grep -q "test_login" && echo "PASS: found test_login" && TESTS=$((TESTS + 1)) || {
1682
+ echo "FAIL: missing test_login"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
1683
+ }
1684
+
1685
+ echo "$output" | grep -q "test_signup" && echo "PASS: found test_signup" && TESTS=$((TESTS + 1)) || {
1686
+ echo "FAIL: missing test_signup"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
1687
+ }
1688
+
1689
+ # --- Test: extracts error types ---
1690
+ echo "$output" | grep -q "TypeError" && echo "PASS: found TypeError" && TESTS=$((TESTS + 1)) || {
1691
+ echo "FAIL: missing TypeError"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
1692
+ }
1693
+
1694
+ # --- Test: help flag ---
1695
+ bash "$DIGEST_SCRIPT" --help >/dev/null 2>&1
1696
+ TESTS=$((TESTS + 1))
1697
+ echo "PASS: --help exits cleanly"
1698
+
1699
+ # === Summary ===
1700
+ echo ""
1701
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
1702
+ if [[ $FAILURES -gt 0 ]]; then
1703
+ echo "FAILURES: $FAILURES"
1704
+ exit 1
1705
+ fi
1706
+ echo "ALL PASSED"
1707
+ ```
1708
+
1709
+ **Step 2: Run test to verify failure**
1710
+
1711
+ Run: `bash scripts/tests/test-failure-digest.sh`
1712
+ Expected: FAIL — script does not exist
1713
+
1714
+ **Step 3: Write implementation**
1715
+
1716
+ Create `scripts/failure-digest.sh`:
1717
+
1718
+ ```bash
1719
+ #!/usr/bin/env bash
1720
+ # failure-digest.sh — Parse failed batch logs into structured failure digest
1721
+ #
1722
+ # Usage: failure-digest.sh <log-file>
1723
+ #
1724
+ # Extracts:
1725
+ # - Failed test names (FAILED pattern)
1726
+ # - Error types and messages (Traceback, Error:, Exception:)
1727
+ # - Test summary line (N failed, M passed)
1728
+ #
1729
+ # Output: Structured text digest suitable for retry prompts
1730
+ set -euo pipefail
1731
+
1732
+ LOG_FILE="${1:-}"
1733
+
1734
+ if [[ "$1" == "--help" || "$1" == "-h" ]]; then
1735
+ echo "failure-digest.sh — Parse batch log into structured failure digest"
1736
+ echo "Usage: failure-digest.sh <log-file>"
1737
+ exit 0
1738
+ fi
1739
+
1740
+ if [[ -z "$LOG_FILE" || ! -f "$LOG_FILE" ]]; then
1741
+ echo "Error: Log file required" >&2
1742
+ exit 1
1743
+ fi
1744
+
1745
+ echo "=== Failure Digest ==="
1746
+ echo "Log: $(basename "$LOG_FILE")"
1747
+ echo ""
1748
+
1749
+ # Extract failed test names
1750
+ echo "--- Failed Tests ---"
1751
+ grep -E '^FAILED ' "$LOG_FILE" 2>/dev/null | sed 's/^FAILED / /' || echo " (none found)"
1752
+ echo ""
1753
+
1754
+ # Extract error types and messages
1755
+ echo "--- Errors ---"
1756
+ grep -E '(Error|Exception|FAIL):' "$LOG_FILE" 2>/dev/null | grep -v '^FAILED ' | head -20 | sed 's/^/ /' || echo " (none found)"
1757
+ echo ""
1758
+
1759
+ # Extract tracebacks (last frame + error line)
1760
+ echo "--- Stack Traces (last frame) ---"
1761
+ grep -B1 -E '^\w+Error:|^\w+Exception:' "$LOG_FILE" 2>/dev/null | head -20 | sed 's/^/ /' || echo " (none found)"
1762
+ echo ""
1763
+
1764
+ # Extract test summary
1765
+ echo "--- Summary ---"
1766
+ grep -E '\d+ (failed|passed|error)' "$LOG_FILE" 2>/dev/null | tail -1 | sed 's/^/ /' || echo " (no summary found)"
1767
+ echo ""
1768
+
1769
+ echo "=== End Digest ==="
1770
+ ```
1771
+
1772
+ **Step 4: Run tests**
1773
+
1774
+ Run: `bash scripts/tests/test-failure-digest.sh`
1775
+ Expected: ALL PASSED
1776
+
1777
+ **Step 5: Wire into run-plan-headless.sh**
1778
+
1779
+ In `scripts/lib/run-plan-headless.sh`, replace the naive `tail -50` in the retry prompt (the section that builds `log_tail` for attempt >= 3):
1780
+
1781
+ ```bash
1782
+ elif [[ $attempt -ge 3 ]]; then
1783
+ local prev_log="$WORKTREE/logs/batch-${batch}-attempt-$((attempt - 1)).log"
1784
+ local log_digest=""
1785
+ if [[ -f "$prev_log" ]]; then
1786
+ log_digest=$("$SCRIPT_DIR/failure-digest.sh" "$prev_log" 2>/dev/null || tail -50 "$prev_log" 2>/dev/null || true)
1787
+ fi
1788
+ ```
1789
+
1790
+ **Step 6: Commit**
1791
+
1792
+ ```bash
1793
+ chmod +x scripts/failure-digest.sh
1794
+ git add scripts/failure-digest.sh scripts/tests/test-failure-digest.sh scripts/lib/run-plan-headless.sh
1795
+ git commit -m "feat: create failure-digest.sh, wire into retry prompts replacing naive tail -50"
1796
+ ```
1797
+
1798
+ ### Task 17: Add context_refs support to plan parser
1799
+
1800
+ **Files:**
1801
+ - Modify: `scripts/lib/run-plan-parser.sh` (add `get_batch_context_refs` function)
1802
+ - Modify: `scripts/lib/run-plan-prompt.sh` (include context_refs file contents in prompt)
1803
+ - Modify: `scripts/tests/test-run-plan-parser.sh` (add context_refs tests)
1804
+
1805
+ **Step 1: Add failing test**
1806
+
1807
+ Add to `scripts/tests/test-run-plan-parser.sh`:
1808
+
1809
+ ```bash
1810
+ # === get_batch_context_refs tests ===
1811
+
1812
+ # Create a plan with context_refs
1813
+ cat > "$WORK/refs-plan.md" << 'PLAN'
1814
+ ## Batch 1: Setup
1815
+
1816
+ ### Task 1: Create base
1817
+ Content here.
1818
+
1819
+ ## Batch 2: Build on base
1820
+ context_refs: src/auth.py, tests/test_auth.py
1821
+
1822
+ ### Task 2: Extend
1823
+ Uses auth module from batch 1.
1824
+ PLAN
1825
+
1826
+ # Batch 1 has no refs
1827
+ val=$(get_batch_context_refs "$WORK/refs-plan.md" 1)
1828
+ assert_eq "get_batch_context_refs: batch 1 has no refs" "" "$val"
1829
+
1830
+ # Batch 2 has refs
1831
+ val=$(get_batch_context_refs "$WORK/refs-plan.md" 2)
1832
+ echo "$val" | grep -q "src/auth.py" && echo "PASS: batch 2 refs include src/auth.py" && TESTS=$((TESTS + 1)) || {
1833
+ echo "FAIL: batch 2 refs missing src/auth.py"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
1834
+ }
1835
+ ```
1836
+
1837
+ **Step 2: Run test to verify failure**
1838
+
1839
+ Run: `bash scripts/tests/test-run-plan-parser.sh`
1840
+ Expected: FAIL — `get_batch_context_refs` does not exist
1841
+
1842
+ **Step 3: Add function to parser**
1843
+
1844
+ Add to `scripts/lib/run-plan-parser.sh`:
1845
+
1846
+ ```bash
1847
+ get_batch_context_refs() {
1848
+ local plan_file="$1" batch_num="$2"
1849
+ local batch_text
1850
+ batch_text=$(get_batch_text "$plan_file" "$batch_num")
1851
+ # Extract "context_refs: file1, file2, ..." line
1852
+ local refs_line
1853
+ refs_line=$(echo "$batch_text" | grep -E '^context_refs:' | head -1 || true)
1854
+ if [[ -z "$refs_line" ]]; then
1855
+ echo ""
1856
+ return
1857
+ fi
1858
+ # Strip "context_refs: " prefix and split on comma
1859
+ echo "${refs_line#context_refs: }" | tr ',' '\n' | sed 's/^[[:space:]]*//' | sed 's/[[:space:]]*$//'
1860
+ }
1861
+ ```
1862
+
1863
+ **Step 4: Update prompt builder to include context_refs**
1864
+
1865
+ In `scripts/lib/run-plan-prompt.sh`, add after the `progress_tail` section:
1866
+
1867
+ ```bash
1868
+ # Cross-batch context: referenced files from context_refs
1869
+ local context_refs_content=""
1870
+ local refs
1871
+ refs=$(get_batch_context_refs "$plan_file" "$batch_num")
1872
+ if [[ -n "$refs" ]]; then
1873
+ while IFS= read -r ref; do
1874
+ [[ -z "$ref" ]] && continue
1875
+ if [[ -f "$worktree/$ref" ]]; then
1876
+ context_refs_content+="
1877
+ --- $ref ---
1878
+ $(head -100 "$worktree/$ref")
1879
+ "
1880
+ fi
1881
+ done <<< "$refs"
1882
+ fi
1883
+ ```
1884
+
1885
+ And add to the prompt output:
1886
+ ```bash
1887
+ $(if [[ -n "$context_refs_content" ]]; then
1888
+ echo "
1889
+ Referenced files from prior batches:
1890
+ ${context_refs_content}"
1891
+ fi)
1892
+ ```
1893
+
1894
+ **Step 5: Run tests**
1895
+
1896
+ Run: `bash scripts/tests/test-run-plan-parser.sh && bash scripts/tests/test-run-plan-prompt.sh`
1897
+ Expected: ALL PASSED
1898
+
1899
+ **Step 6: Commit**
1900
+
1901
+ ```bash
1902
+ git add scripts/lib/run-plan-parser.sh scripts/lib/run-plan-prompt.sh scripts/tests/test-run-plan-parser.sh
1903
+ git commit -m "feat: add context_refs support for cross-batch file dependencies in plans"
1904
+ ```
1905
+
1906
+ ## Batch 6: License Check and Final Wiring
1907
+
1908
+ Add license checking, wire all new gates, and ensure the full pipeline works end-to-end.
1909
+
1910
+ ### Task 18: Create license-check.sh
1911
+
1912
+ **Files:**
1913
+ - Create: `scripts/license-check.sh`
1914
+
1915
+ **Step 1: Write the script**
1916
+
1917
+ ```bash
1918
+ #!/usr/bin/env bash
1919
+ # license-check.sh — Check dependencies for license compatibility
1920
+ #
1921
+ # Usage: license-check.sh [--project-root <dir>]
1922
+ set -euo pipefail
1923
+
1924
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1925
+ source "$SCRIPT_DIR/lib/common.sh"
1926
+
1927
+ PROJECT_ROOT="."
1928
+
1929
+ while [[ $# -gt 0 ]]; do
1930
+ case "$1" in
1931
+ --project-root) PROJECT_ROOT="$2"; shift 2 ;;
1932
+ -h|--help)
1933
+ echo "license-check.sh — Check dependency licenses"
1934
+ echo "Usage: license-check.sh [--project-root <dir>]"
1935
+ echo "Flags GPL/AGPL in MIT-licensed projects."
1936
+ exit 0 ;;
1937
+ *) echo "Unknown: $1" >&2; exit 1 ;;
1938
+ esac
1939
+ done
1940
+
1941
+ cd "$PROJECT_ROOT"
1942
+ project_type=$(detect_project_type ".")
1943
+
1944
+ echo "=== License Check ==="
1945
+ violations=0
1946
+
1947
+ case "$project_type" in
1948
+ python)
1949
+ if [[ -d ".venv" ]] && command -v pip-licenses >/dev/null 2>&1; then
1950
+ echo "Checking Python dependencies..."
1951
+ gpl_deps=$(.venv/bin/python -m pip-licenses --format=csv 2>/dev/null | grep -iE 'GPL|AGPL' | grep -v 'LGPL' || true)
1952
+ if [[ -n "$gpl_deps" ]]; then
1953
+ echo "WARNING: GPL/AGPL dependencies found:"
1954
+ echo "$gpl_deps" | sed 's/^/ /'
1955
+ violations=$((violations + 1))
1956
+ else
1957
+ echo " No GPL/AGPL dependencies"
1958
+ fi
1959
+ else
1960
+ echo " pip-licenses not available — skipping"
1961
+ fi
1962
+ ;;
1963
+ node)
1964
+ if command -v npx >/dev/null 2>&1; then
1965
+ echo "Checking Node dependencies..."
1966
+ gpl_deps=$(npx license-checker --csv 2>/dev/null | grep -iE 'GPL|AGPL' | grep -v 'LGPL' || true)
1967
+ if [[ -n "$gpl_deps" ]]; then
1968
+ echo "WARNING: GPL/AGPL dependencies found:"
1969
+ echo "$gpl_deps" | head -10 | sed 's/^/ /'
1970
+ violations=$((violations + 1))
1971
+ else
1972
+ echo " No GPL/AGPL dependencies"
1973
+ fi
1974
+ else
1975
+ echo " license-checker not available — skipping"
1976
+ fi
1977
+ ;;
1978
+ *)
1979
+ echo " No license check for project type: $project_type"
1980
+ ;;
1981
+ esac
1982
+
1983
+ if [[ $violations -gt 0 ]]; then
1984
+ echo ""
1985
+ echo "license-check: $violations issue(s) found"
1986
+ exit 1
1987
+ fi
1988
+
1989
+ echo "license-check: CLEAN"
1990
+ exit 0
1991
+ ```
1992
+
1993
+ **Step 2: Test manually**
1994
+
1995
+ Run: `bash scripts/license-check.sh --help`
1996
+ Expected: Help text, exit 0
1997
+
1998
+ **Step 3: Commit**
1999
+
2000
+ ```bash
2001
+ chmod +x scripts/license-check.sh
2002
+ git add scripts/license-check.sh
2003
+ git commit -m "feat: create license-check.sh for dependency license auditing"
2004
+ ```
2005
+
2006
+ ### Task 19: Wire all new gates into quality-gate.sh
2007
+
2008
+ **Files:**
2009
+ - Modify: `scripts/quality-gate.sh` (add `--with-license` and `--quick` flags)
2010
+
2011
+ **Step 1: Add flag parsing**
2012
+
2013
+ Add `QUICK=false` and `WITH_LICENSE=false` to defaults. Add to arg parser:
2014
+ ```bash
2015
+ --quick) QUICK=true; shift ;;
2016
+ --with-license) WITH_LICENSE=true; shift ;;
2017
+ ```
2018
+
2019
+ **Step 2: Add conditional checks**
2020
+
2021
+ If `--quick` is set, skip lint and license checks. If `--with-license` is set, add license check after tests:
2022
+
2023
+ ```bash
2024
+ # === Optional: License Check ===
2025
+ if [[ "$WITH_LICENSE" == true ]]; then
2026
+ echo ""
2027
+ echo "=== Quality Gate: License Check ==="
2028
+ if ! "$SCRIPT_DIR/license-check.sh" --project-root "$PROJECT_ROOT"; then
2029
+ echo "quality-gate: FAILED at license check"
2030
+ exit 1
2031
+ fi
2032
+ fi
2033
+ ```
2034
+
2035
+ If `--quick`, wrap the lint section in:
2036
+ ```bash
2037
+ if [[ "$QUICK" != true ]]; then
2038
+ # lint check here
2039
+ fi
2040
+ ```
2041
+
2042
+ **Step 3: Run tests**
2043
+
2044
+ Run: `bash scripts/tests/run-all-tests.sh`
2045
+ Expected: ALL PASSED
2046
+
2047
+ **Step 4: Verify line count**
2048
+
2049
+ Run: `wc -l scripts/quality-gate.sh`
2050
+ Expected: Under 300 lines
2051
+
2052
+ **Step 5: Commit**
2053
+
2054
+ ```bash
2055
+ git add scripts/quality-gate.sh
2056
+ git commit -m "feat: add --quick and --with-license flags to quality-gate.sh"
2057
+ ```
2058
+
2059
+ ### Task 20: Update run-all-tests.sh to discover new test files
2060
+
2061
+ **Files:**
2062
+ - Modify: `scripts/tests/run-all-tests.sh` (expand glob to include non-run-plan tests)
2063
+
2064
+ **Step 1: Update test discovery**
2065
+
2066
+ Change line 13 from:
2067
+ ```bash
2068
+ mapfile -t TEST_FILES < <(find "$SCRIPT_DIR" -maxdepth 1 -name "test-run-plan-*.sh" -type f | sort)
2069
+ ```
2070
+ to:
2071
+ ```bash
2072
+ mapfile -t TEST_FILES < <(find "$SCRIPT_DIR" -maxdepth 1 -name "test-*.sh" -type f | sort)
2073
+ ```
2074
+
2075
+ This picks up `test-common.sh`, `test-ollama.sh`, `test-telegram.sh`, `test-prior-art-search.sh`, and `test-failure-digest.sh`.
2076
+
2077
+ **Step 2: Run full test suite**
2078
+
2079
+ Run: `bash scripts/tests/run-all-tests.sh`
2080
+ Expected: ALL new and existing tests discovered and PASSED
2081
+
2082
+ **Step 3: Commit**
2083
+
2084
+ ```bash
2085
+ git add scripts/tests/run-all-tests.sh
2086
+ git commit -m "fix: run-all-tests.sh discovers all test-*.sh files, not just run-plan tests"
2087
+ ```
2088
+
2089
+ ## Batch 7: Integration Wiring + Final Verification
2090
+
2091
+ Wire all components together and verify the full pipeline works end-to-end.
2092
+
2093
+ ### Task 21: Verify all scripts under 300 lines
2094
+
2095
+ **Step 1: Check line counts**
2096
+
2097
+ Run: `wc -l scripts/*.sh scripts/lib/*.sh | sort -n`
2098
+
2099
+ Expected: Every file under 300 lines. If any violations remain, refactor.
2100
+
2101
+ **Step 2: Commit any fixes**
2102
+
2103
+ ```bash
2104
+ git add -A && git commit -m "fix: ensure all scripts under 300-line limit"
2105
+ ```
2106
+
2107
+ ### Task 22: Run full test suite and verify
2108
+
2109
+ **Step 1: Run all tests**
2110
+
2111
+ Run: `bash scripts/tests/run-all-tests.sh`
2112
+ Expected: ALL PASSED with zero failures
2113
+
2114
+ **Step 2: Run quality gate on this project**
2115
+
2116
+ Run: `bash scripts/quality-gate.sh --project-root .`
2117
+ Expected: ALL PASSED
2118
+
2119
+ ### Task 23: Run pipeline-status.sh to verify integration
2120
+
2121
+ **Step 1: Run pipeline status**
2122
+
2123
+ Run: `bash scripts/pipeline-status.sh .`
2124
+ Expected: Shows project status without errors
2125
+
2126
+ ### Task 24: Update progress.txt with final summary
2127
+
2128
+ **Step 1: Append summary**
2129
+
2130
+ ```bash
2131
+ echo "## Code Factory v2 — Implementation Complete
2132
+
2133
+ ### Phase 1: Foundation
2134
+ - Created scripts/lib/common.sh (detect_project_type, strip_json_fences, check_memory_available, require_command)
2135
+ - Created scripts/lib/ollama.sh (ollama_query, ollama_extract_json)
2136
+ - Created scripts/lib/telegram.sh (extracted from run-plan-notify.sh)
2137
+ - Refactored: quality-gate.sh, auto-compound.sh, analyze-report.sh, entropy-audit.sh
2138
+ - Extracted run-plan-headless.sh from run-plan.sh (412 -> ~260 lines)
2139
+
2140
+ ### Phase 2: Accuracy
2141
+ - Fixed test count parsing for jest, go test, and unknown formats
2142
+ - Added cross-batch context (git log, progress.txt, quality gate result) to prompts
2143
+ - Added per-batch duration tracking to state
2144
+ - Fixed PRD output discard in auto-compound.sh (lesson-7)
2145
+
2146
+ ### Phase 3: Quality Gates
2147
+ - Added ruff/eslint lint step to quality-gate.sh
2148
+ - Created prior-art-search.sh (GitHub + local search)
2149
+ - Created license-check.sh
2150
+ - Created pipeline-status.sh
2151
+ - Wired prior-art search into auto-compound.sh
2152
+
2153
+ ### Phase 4: New Capabilities
2154
+ - Created failure-digest.sh (replaces naive tail -50 in retries)
2155
+ - Added context_refs support for cross-batch file dependencies
2156
+ - All scripts under 300 lines
2157
+ " >> progress.txt
2158
+ ```
2159
+
2160
+ **Step 2: Commit**
2161
+
2162
+ ```bash
2163
+ git add progress.txt
2164
+ git commit -m "docs: update progress.txt with Code Factory v2 implementation summary"
2165
+ ```
2166
+
2167
+ ### Task 25: Vertical pipeline trace
2168
+
2169
+ **Step 1: End-to-end dry run**
2170
+
2171
+ Run the full auto-compound pipeline in dry-run mode to verify all components are wired:
2172
+
2173
+ ```bash
2174
+ bash scripts/auto-compound.sh . --dry-run --report reports/daily.md
2175
+ ```
2176
+
2177
+ Verify output shows: analyze → search → PRD → quality gate → Ralph loop → PR
2178
+
2179
+ If no report file exists, create a minimal one for the trace:
2180
+ ```bash
2181
+ echo "# Test Report\n## Issue: Test pipeline integration" > /tmp/test-report.md
2182
+ bash scripts/auto-compound.sh . --dry-run --report /tmp/test-report.md
2183
+ ```
2184
+
2185
+ **Step 2: Final commit**
2186
+
2187
+ ```bash
2188
+ git add -A && git commit -m "feat: Code Factory v2 complete — all phases implemented and verified"
2189
+ ```