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,61 @@
1
+ #!/usr/bin/env bash
2
+ # common.sh — Shared utility functions for Code Factory scripts
3
+ #
4
+ # Source this in any script: source "$SCRIPT_DIR/lib/common.sh"
5
+ #
6
+ # Functions:
7
+ # detect_project_type <dir> -> "python"|"node"|"make"|"bash"|"unknown"
8
+ # strip_json_fences -> stdin filter: remove ```json wrappers
9
+ # check_memory_available <threshold_gb> -> exit 0 if available >= threshold, 1 if low, 2 if unknown
10
+ # require_command <cmd> [install_hint] -> exit 1 with message if cmd not found
11
+
12
+ detect_project_type() {
13
+ local dir="$1"
14
+ if [[ -f "$dir/pyproject.toml" || -f "$dir/setup.py" || -f "$dir/pytest.ini" ]]; then
15
+ echo "python"
16
+ elif [[ -f "$dir/package.json" ]]; then
17
+ echo "node"
18
+ elif [[ -f "$dir/Makefile" ]]; then
19
+ echo "make"
20
+ elif [[ -x "$dir/scripts/tests/run-all-tests.sh" ]] || compgen -G "$dir/scripts/tests/test-*.sh" >/dev/null 2>&1; then
21
+ echo "bash"
22
+ else
23
+ echo "unknown"
24
+ fi
25
+ }
26
+
27
+ strip_json_fences() {
28
+ sed '/^```json$/d; /^```$/d'
29
+ }
30
+
31
+ check_memory_available() {
32
+ local threshold_gb="${1:-4}"
33
+ local threshold_mb=$((threshold_gb * 1024))
34
+ local available_mb
35
+ available_mb=$(free -m 2>/dev/null | awk '/Mem:/{print $7}')
36
+ if [[ -z "$available_mb" ]]; then
37
+ # free command unavailable or produced no output — return -1 (unknown)
38
+ echo "WARNING: Cannot determine available memory (free command unavailable)" >&2
39
+ return 2
40
+ fi
41
+ if [[ "$available_mb" -ge "$threshold_mb" ]]; then
42
+ return 0
43
+ else
44
+ local available_display
45
+ available_display=$(awk "BEGIN {printf \"%.1f\", $available_mb / 1024}")
46
+ echo "WARNING: Low memory (${available_display}G available, need ${threshold_gb}G)" >&2
47
+ return 1
48
+ fi
49
+ }
50
+
51
+ require_command() {
52
+ local cmd="$1"
53
+ local hint="${2:-}"
54
+ if ! command -v "$cmd" >/dev/null 2>&1; then
55
+ echo "ERROR: Required command not found: $cmd" >&2
56
+ if [[ -n "$hint" ]]; then
57
+ echo " Install with: $hint" >&2
58
+ fi
59
+ return 1
60
+ fi
61
+ }
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env bash
2
+ # cost-tracking.sh — Per-batch cost tracking via Claude CLI JSONL session files
3
+ #
4
+ # Claude CLI stores session data in JSONL files at:
5
+ # ~/.claude/projects/<project>/<session-id>.jsonl
6
+ # The last line with type "summary" contains token counts and cost.
7
+ #
8
+ # Functions:
9
+ # find_session_jsonl <session_id> <claude_dir> -> path to JSONL file (empty if not found)
10
+ # extract_session_cost <session_id> <claude_dir> -> JSON: {input_tokens, output_tokens, cache_read_tokens, estimated_cost_usd, model, tracking_status}
11
+ # record_batch_cost <worktree> <batch_num> <session_id> [claude_dir] -> updates .run-plan-state.json
12
+ # check_budget <worktree> <max_budget_usd> -> exits 0 if under, 1 if over
13
+ # get_total_cost <worktree> -> prints total_cost_usd from state
14
+
15
+ find_session_jsonl() {
16
+ local session_id="$1" claude_dir="$2"
17
+ local found=""
18
+ # Search all project directories for the session JSONL
19
+ while IFS= read -r -d '' f; do
20
+ found="$f"
21
+ break
22
+ done < <(find "$claude_dir" -name "${session_id}.jsonl" -print0 2>/dev/null)
23
+ echo "$found"
24
+ }
25
+
26
+ extract_session_cost() {
27
+ local session_id="$1" claude_dir="$2"
28
+ local jsonl_path
29
+ jsonl_path=$(find_session_jsonl "$session_id" "$claude_dir")
30
+
31
+ if [[ -z "$jsonl_path" || ! -f "$jsonl_path" ]]; then
32
+ # Fix #39: tracking_status field distinguishes "broken tracking" from "true $0 cost"
33
+ # Fix #36: use jq --arg to safely interpolate session_id (no JSON injection)
34
+ echo "WARNING: cost-tracking: no JSONL file found for session $session_id" >&2
35
+ jq -n --arg sid "$session_id" \
36
+ '{input_tokens:0,output_tokens:0,cache_read_tokens:0,estimated_cost_usd:0,model:"unknown",session_id:$sid,tracking_status:"missing_file"}'
37
+ return 0
38
+ fi
39
+
40
+ # Fix #35: || true prevents grep exit-1 from killing set -e callers when no summary line exists
41
+ local summary
42
+ summary=$(grep '"type":"summary"' "$jsonl_path" | tail -1 || true)
43
+
44
+ if [[ -n "$summary" ]]; then
45
+ # Fix #36: use jq --arg for session_id to prevent JSON injection
46
+ # Fix #39: tracking_status:"found" confirms real data was retrieved
47
+ echo "$summary" | jq -c --arg sid "$session_id" '{
48
+ input_tokens: (.inputTokens // 0),
49
+ output_tokens: (.outputTokens // 0),
50
+ cache_read_tokens: (.cacheReadTokens // 0),
51
+ estimated_cost_usd: (.costUSD // 0),
52
+ model: (.model // "unknown"),
53
+ session_id: $sid,
54
+ tracking_status: "found"
55
+ }'
56
+ else
57
+ # Fix #36: use jq --arg for session_id to prevent JSON injection
58
+ # Fix #39: tracking_status:"no_summary" distinguishes from a real zero-cost session
59
+ echo "WARNING: cost-tracking: JSONL file exists but has no summary line for session $session_id" >&2
60
+ jq -n --arg sid "$session_id" \
61
+ '{input_tokens:0,output_tokens:0,cache_read_tokens:0,estimated_cost_usd:0,model:"unknown",session_id:$sid,tracking_status:"no_summary"}'
62
+ fi
63
+ }
64
+
65
+ record_batch_cost() {
66
+ local worktree="$1" batch_num="$2" session_id="$3"
67
+ local claude_dir="${4:-$HOME/.claude}"
68
+ local sf="$worktree/.run-plan-state.json"
69
+
70
+ if [[ ! -f "$sf" ]]; then
71
+ echo "WARNING: No state file at $sf" >&2
72
+ return 1
73
+ fi
74
+
75
+ local cost_json
76
+ cost_json=$(extract_session_cost "$session_id" "$claude_dir")
77
+
78
+ local tmp
79
+ tmp=$(mktemp)
80
+ # Fix #37: trap ensures temp file is cleaned up even if jq fails
81
+ trap 'rm -f "$tmp"' RETURN
82
+
83
+ # Fix #41: (... | add) // 0 handles empty .costs object (add on [] returns null, not 0)
84
+ jq --arg batch "$batch_num" --argjson cost "$cost_json" '
85
+ .costs //= {} |
86
+ .costs[$batch] = $cost |
87
+ .total_cost_usd = (([.costs[].estimated_cost_usd] | add) // 0)
88
+ ' "$sf" > "$tmp" && mv "$tmp" "$sf"
89
+ }
90
+
91
+ check_budget() {
92
+ local worktree="$1" max_budget="$2"
93
+ local sf="$worktree/.run-plan-state.json"
94
+
95
+ if [[ ! -f "$sf" ]]; then
96
+ return 0 # No state = no cost = under budget
97
+ fi
98
+
99
+ local total
100
+ total=$(jq -r '.total_cost_usd // 0' "$sf" 2>/dev/null) || total=""
101
+
102
+ # Fix #63: validate jq output is numeric — corrupted state must not bypass budget
103
+ if [[ -z "$total" ]] || ! [[ "$total" =~ ^[0-9]*\.?[0-9]+$ ]]; then
104
+ echo "WARNING: cost-tracking: corrupted total_cost_usd='$total' in state file — treating as budget exceeded" >&2
105
+ return 1
106
+ fi
107
+
108
+ # Fix #69: validate max_budget is numeric — prevent awk injection via CLI args
109
+ if ! [[ "$max_budget" =~ ^[0-9]*\.?[0-9]+$ ]]; then
110
+ echo "ERROR: cost-tracking: invalid max_budget='$max_budget'" >&2
111
+ return 1
112
+ fi
113
+
114
+ # Fix #40: check for bc; fall back to awk for float comparison if missing
115
+ if ! command -v bc >/dev/null 2>&1; then
116
+ echo "WARNING: cost-tracking: bc not found, using awk for budget comparison" >&2
117
+ # Safe: both values validated as numeric above
118
+ if awk "BEGIN {exit !(${total} > ${max_budget})}" 2>/dev/null; then
119
+ echo "BUDGET EXCEEDED: \$${total} spent of \$${max_budget} limit" >&2
120
+ return 1
121
+ fi
122
+ return 0
123
+ fi
124
+
125
+ # Compare using bc (bash can't do float comparison natively)
126
+ if (( $(echo "$total > $max_budget" | bc -l) )); then
127
+ echo "BUDGET EXCEEDED: \$${total} spent of \$${max_budget} limit" >&2
128
+ return 1
129
+ fi
130
+ return 0
131
+ }
132
+
133
+ get_total_cost() {
134
+ local worktree="$1"
135
+ local sf="$worktree/.run-plan-state.json"
136
+
137
+ if [[ ! -f "$sf" ]]; then
138
+ echo "0"
139
+ return 0
140
+ fi
141
+
142
+ local val
143
+ val=$(jq -r '.total_cost_usd // 0' "$sf" 2>/dev/null) || val=""
144
+
145
+ # Fix #63: validate output is numeric — don't silently return "0" on corrupted state
146
+ if [[ -n "$val" ]] && [[ "$val" =~ ^[0-9]*\.?[0-9]+$ ]]; then
147
+ echo "$val"
148
+ else
149
+ echo "WARNING: cost-tracking: corrupted total_cost_usd='$val' in $sf" >&2
150
+ echo "error"
151
+ return 1
152
+ fi
153
+ }
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+ # ollama.sh — Shared Ollama API interaction for Code Factory scripts
3
+ #
4
+ # Requires: common.sh sourced first (for strip_json_fences)
5
+ #
6
+ # Functions:
7
+ # ollama_build_payload <model> <prompt> -> JSON payload string
8
+ # ollama_parse_response -> stdin filter: extract .response from Ollama JSON
9
+ # ollama_extract_json -> stdin filter: parse response, strip fences, validate JSON
10
+ # ollama_query <model> <prompt> -> full query: build payload, call API, return response text
11
+ # ollama_query_json <model> <prompt> -> full query + JSON extraction
12
+
13
+ OLLAMA_DIRECT_URL="${OLLAMA_DIRECT_URL:-http://localhost:11434}"
14
+ OLLAMA_QUEUE_URL="${OLLAMA_QUEUE_URL:-http://localhost:7683}"
15
+
16
+ ollama_build_payload() {
17
+ local model="$1" prompt="$2"
18
+ jq -n --arg model "$model" --arg prompt "$prompt" \
19
+ '{model: $model, prompt: $prompt, stream: false}'
20
+ }
21
+
22
+ ollama_parse_response() {
23
+ jq -r '.response // empty'
24
+ }
25
+
26
+ ollama_extract_json() {
27
+ local text
28
+ text=$(cat)
29
+ # Strip fences
30
+ text=$(echo "$text" | strip_json_fences)
31
+ # Validate JSON
32
+ if echo "$text" | jq . >/dev/null 2>&1; then
33
+ echo "$text"
34
+ else
35
+ echo "WARNING: ollama_extract_json: invalid JSON in response" >&2
36
+ echo ""
37
+ fi
38
+ }
39
+
40
+ ollama_query() {
41
+ local model="$1" prompt="$2"
42
+ local payload api_url response
43
+
44
+ payload=$(ollama_build_payload "$model" "$prompt")
45
+
46
+ # Prefer queue if available
47
+ if curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 --max-time 10 "$OLLAMA_QUEUE_URL/health" 2>/dev/null | grep -q "200"; then
48
+ api_url="$OLLAMA_QUEUE_URL/api/generate"
49
+ else
50
+ api_url="$OLLAMA_DIRECT_URL/api/generate"
51
+ fi
52
+
53
+ response=$(curl -s "$api_url" -d "$payload" --connect-timeout 10 --max-time 300)
54
+ echo "$response" | ollama_parse_response
55
+ }
56
+
57
+ ollama_query_json() {
58
+ local model="$1" prompt="$2"
59
+ ollama_query "$model" "$prompt" | ollama_extract_json
60
+ }
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bash
2
+ # progress-writer.sh — Structured progress.txt writer/reader
3
+ #
4
+ # Provides structured batch progress tracking with machine-readable sections.
5
+ # Format:
6
+ # ## Batch N: <title> (YYYY-MM-DDTHH:MM:SSZ)
7
+ # ### Files Modified
8
+ # - path/to/file (created|modified|deleted)
9
+ # ### Decisions
10
+ # - decision: rationale
11
+ # ### Issues Encountered
12
+ # - issue → resolution
13
+ # ### State
14
+ # - Tests: N passing
15
+ # - Duration: Ns
16
+ # - Cost: $N.NN
17
+ #
18
+ # Functions:
19
+ # write_batch_progress <worktree> <batch_num> <title>
20
+ # append_progress_section <worktree> <section> <content>
21
+ # read_batch_progress <worktree> <batch_num>
22
+ #
23
+ # NOTE: write_batch_progress and append_progress_section are called by
24
+ # run-plan-headless.sh at batch start and after quality gate passes.
25
+
26
+ # Write a batch header with timestamp to progress.txt
27
+ # Usage: write_batch_progress <worktree> <batch_num> <title>
28
+ # Returns: 0 on success, 1 on I/O error
29
+ write_batch_progress() {
30
+ local worktree="$1" batch_num="$2" title="$3"
31
+ local progress_file="$worktree/progress.txt"
32
+ local timestamp
33
+ timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
34
+
35
+ # Ensure trailing newline before new batch header (if file exists and is non-empty)
36
+ if [[ -s "$progress_file" ]]; then
37
+ # Add blank line separator between batches
38
+ echo "" >> "$progress_file" || {
39
+ echo "ERROR: Failed to write to $progress_file" >&2
40
+ return 1
41
+ }
42
+ fi
43
+
44
+ echo "## Batch ${batch_num}: ${title} (${timestamp})" >> "$progress_file" || {
45
+ echo "ERROR: Failed to write batch header to $progress_file" >&2
46
+ return 1
47
+ }
48
+ }
49
+
50
+ # Append a named section under the most recent batch header
51
+ # Usage: append_progress_section <worktree> <section> <content>
52
+ # section: "Files Modified", "Decisions", "Issues Encountered", "State"
53
+ # Returns: 0 on success, 1 on I/O error
54
+ append_progress_section() {
55
+ local worktree="$1" section="$2" content="$3"
56
+ local progress_file="$worktree/progress.txt"
57
+
58
+ echo "### ${section}" >> "$progress_file" || {
59
+ echo "ERROR: Failed to write section header to $progress_file" >&2
60
+ return 1
61
+ }
62
+ echo "$content" >> "$progress_file" || {
63
+ echo "ERROR: Failed to write section content to $progress_file" >&2
64
+ return 1
65
+ }
66
+ }
67
+
68
+ # Extract a single batch's content from progress.txt using awk
69
+ # Returns everything from "## Batch N: <title> (timestamp)" up to (but not
70
+ # including) the next "## Batch" header or EOF.
71
+ #
72
+ # The pattern requires the timestamp in parens at end to avoid false matches
73
+ # when progress content itself mentions "## Batch N:" in notes.
74
+ #
75
+ # Exit codes:
76
+ # 0 — batch found (content printed, may be empty if batch has no body)
77
+ # 1 — batch not found in file
78
+ # 2 — progress.txt file does not exist
79
+ #
80
+ # Usage: read_batch_progress <worktree> <batch_num>
81
+ read_batch_progress() {
82
+ local worktree="$1" batch_num="$2"
83
+ local progress_file="$worktree/progress.txt"
84
+
85
+ # Validate batch_num is a positive integer
86
+ if [[ ! "$batch_num" =~ ^[0-9]+$ ]]; then
87
+ echo "ERROR: batch_num must be a positive integer, got: '$batch_num'" >&2
88
+ return 1
89
+ fi
90
+
91
+ if [[ ! -f "$progress_file" ]]; then
92
+ return 2
93
+ fi
94
+
95
+ # Use the timestamp-anchored pattern to avoid false matches on content.
96
+ # Header format: ## Batch N: <title> (YYYY-MM-DDTHH:MM:SSZ)
97
+ # The awk sets found=1 when the target header is matched, then prints
98
+ # subsequent lines until another timestamped batch header is encountered.
99
+ local found
100
+ found=$(awk -v batch="$batch_num" '
101
+ /^## Batch [0-9]+: .+ \([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\)$/ {
102
+ # Extract batch number: field 3, strip trailing colon
103
+ n = $3
104
+ sub(/:$/, "", n)
105
+ if (n == batch) {
106
+ printing = 1
107
+ print
108
+ next
109
+ } else if (printing) {
110
+ exit
111
+ }
112
+ }
113
+ printing { print }
114
+ ' "$progress_file")
115
+
116
+ if [[ -z "$found" ]]; then
117
+ # Check whether the batch header exists at all (empty body vs not found)
118
+ if grep -qE "^## Batch ${batch_num}: .+ \([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\)$" "$progress_file" 2>/dev/null; then
119
+ # Batch exists but has no body content — return 0, print nothing
120
+ return 0
121
+ else
122
+ return 1
123
+ fi
124
+ fi
125
+
126
+ echo "$found"
127
+ return 0
128
+ }
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env bash
2
+ # run-plan-context.sh — Per-batch context assembler for run-plan
3
+ #
4
+ # Assembles relevant context for a batch agent within a token budget.
5
+ # Reads: state file, progress.txt, git log, context_refs, failure patterns.
6
+ # Outputs: markdown section for CLAUDE.md injection.
7
+ #
8
+ # Functions:
9
+ # generate_batch_context <plan_file> <batch_num> <worktree> -> markdown string
10
+ # record_failure_pattern <worktree> <batch_title> <failure_type> <winning_fix>
11
+
12
+ TOKEN_BUDGET_CHARS=10000 # ~2500 tokens
13
+
14
+ generate_batch_context() {
15
+ local plan_file="$1" batch_num="$2" worktree="$3"
16
+ local context=""
17
+ local chars_used=0
18
+
19
+ context+="## Run-Plan: Batch $batch_num"$'\n\n'
20
+
21
+ # 1. Directives from state (highest priority)
22
+ local state_file="$worktree/.run-plan-state.json"
23
+ if [[ -f "$state_file" ]]; then
24
+ local prev_test_count
25
+ prev_test_count=$(jq -r '[.batches[].test_count // 0] | max' "$state_file" 2>/dev/null || echo "0")
26
+ if [[ "$prev_test_count" -gt 0 ]]; then
27
+ context+="**Directive:** tests must stay above $prev_test_count (current high water mark)"$'\n\n'
28
+ fi
29
+
30
+ # Prior batch summary (most recent 2 batches only)
31
+ local start_batch=$(( batch_num - 2 ))
32
+ [[ $start_batch -lt 1 ]] && start_batch=1
33
+ for ((b = start_batch; b < batch_num; b++)); do
34
+ local passed duration tests
35
+ passed=$(jq -r ".batches[\"$b\"].passed // false" "$state_file" 2>/dev/null)
36
+ tests=$(jq -r ".batches[\"$b\"].test_count // 0" "$state_file" 2>/dev/null)
37
+ duration=$(jq -r ".batches[\"$b\"].duration // 0" "$state_file" 2>/dev/null)
38
+ if [[ "$passed" == "true" ]]; then
39
+ context+="Batch $b: PASSED ($tests tests, ${duration}s)"$'\n'
40
+ fi
41
+ done
42
+ context+=$'\n'
43
+ fi
44
+
45
+ # 2. Failure patterns (cross-run learning)
46
+ local patterns_file="$worktree/logs/failure-patterns.json"
47
+ if [[ -f "$patterns_file" ]]; then
48
+ local batch_title
49
+ batch_title=$(get_batch_title "$plan_file" "$batch_num" 2>/dev/null || echo "")
50
+ local title_lower
51
+ title_lower=$(echo "$batch_title" | tr '[:upper:]' '[:lower:]')
52
+
53
+ # Match failure patterns by batch title keywords
54
+ local matches
55
+ matches=$(jq -r --arg title "$title_lower" \
56
+ '.[] | select(.batch_title_pattern as $p | $title | contains($p)) | "WARNING: Previously failed with \(.failure_type) (\(.frequency)x). Fix that worked: \(.winning_fix)"' \
57
+ "$patterns_file" 2>/dev/null || true)
58
+ if [[ -n "$matches" ]]; then
59
+ context+="### Known Failure Patterns"$'\n'
60
+ context+="$matches"$'\n\n'
61
+ fi
62
+ fi
63
+
64
+ chars_used=${#context}
65
+
66
+ # 2.5. MAB lessons (from competing agent runs)
67
+ local mab_lessons_file="$worktree/logs/mab-lessons.json"
68
+ if [[ -f "$mab_lessons_file" ]]; then
69
+ local mab_count
70
+ mab_count=$(jq 'length' "$mab_lessons_file" 2>/dev/null || echo "0")
71
+ if [[ "$mab_count" -gt 0 ]]; then
72
+ local mab_section="### MAB Lessons"$'\n'
73
+ mab_section+=$(jq -r '
74
+ sort_by(-.occurrences // 0) | .[0:5] | .[] |
75
+ "- **\(.pattern)** (\(.context // "general")): \(.recommendation // .winner // "")"
76
+ ' "$mab_lessons_file" 2>/dev/null || true)
77
+ mab_section+=$'\n\n'
78
+ if [[ $((chars_used + ${#mab_section})) -lt $TOKEN_BUDGET_CHARS ]]; then
79
+ context+="$mab_section"
80
+ chars_used=$((chars_used + ${#mab_section}))
81
+ fi
82
+ fi
83
+ fi
84
+
85
+ # 2.6. Research warnings (from tasks/research-*.json)
86
+ local research_files
87
+ research_files=$(find "$worktree/tasks" -name 'research-*.json' 2>/dev/null || true)
88
+ if [[ -n "$research_files" ]]; then
89
+ local research_section=""
90
+ while IFS= read -r rfile; do
91
+ [[ -z "$rfile" ]] && continue
92
+ local warnings
93
+ warnings=$(jq -r '.warnings[]? // empty' "$rfile" 2>/dev/null || true)
94
+ if [[ -n "$warnings" ]]; then
95
+ local feature_name
96
+ feature_name=$(jq -r '.feature // "unknown"' "$rfile" 2>/dev/null || echo "unknown")
97
+ research_section+="### Research Warnings ($feature_name)"$'\n'
98
+ while IFS= read -r w; do
99
+ research_section+="- $w"$'\n'
100
+ done <<< "$warnings"
101
+ research_section+=$'\n'
102
+ fi
103
+ done <<< "$research_files"
104
+ if [[ -n "$research_section" ]] && [[ $((chars_used + ${#research_section})) -lt $TOKEN_BUDGET_CHARS ]]; then
105
+ context+="$research_section"
106
+ chars_used=$((chars_used + ${#research_section}))
107
+ fi
108
+ fi
109
+
110
+ # 3. Context refs file contents (if budget allows)
111
+ local refs
112
+ refs=$(get_batch_context_refs "$plan_file" "$batch_num" 2>/dev/null || true)
113
+ if [[ -n "$refs" ]]; then
114
+ local refs_section="### Referenced Files"$'\n'
115
+ while IFS= read -r ref_file; do
116
+ # Trim whitespace using parameter expansion (xargs word-splits on spaces — #60)
117
+ ref_file="${ref_file#"${ref_file%%[![:space:]]*}"}"
118
+ ref_file="${ref_file%"${ref_file##*[![:space:]]}"}"
119
+ [[ -z "$ref_file" ]] && continue
120
+ local full_path="$worktree/$ref_file"
121
+ if [[ -f "$full_path" ]]; then
122
+ local file_content
123
+ file_content=$(head -50 "$full_path" 2>/dev/null || true)
124
+ local addition
125
+ addition=$'\n'"**$ref_file:**"$'\n'"$file_content"$'\n'
126
+ if [[ $(( chars_used + ${#refs_section} + ${#addition} )) -lt $TOKEN_BUDGET_CHARS ]]; then
127
+ refs_section+="$addition"
128
+ fi
129
+ fi
130
+ done <<< "$refs"
131
+ context+="$refs_section"$'\n'
132
+ fi
133
+
134
+ chars_used=${#context}
135
+
136
+ # 4. Git log (if budget allows)
137
+ if [[ $(( chars_used + 500 )) -lt $TOKEN_BUDGET_CHARS ]]; then
138
+ local git_log
139
+ git_log=$(git -C "$worktree" log --oneline -5 2>/dev/null || true)
140
+ if [[ -n "$git_log" ]]; then
141
+ context+="### Recent Commits"$'\n'
142
+ context+="$git_log"$'\n\n'
143
+ fi
144
+ fi
145
+
146
+ chars_used=${#context}
147
+
148
+ # 5. Progress.txt (if budget allows — structured read for last 2 batches)
149
+ # NOTE: No tail fallback — injecting lines from an unknown batch contaminates
150
+ # context with wrong-batch data. If structured read returns empty, no progress
151
+ # exists for the requested batches (#54).
152
+ if [[ $(( chars_used + 500 )) -lt $TOKEN_BUDGET_CHARS ]]; then
153
+ local progress_file="$worktree/progress.txt"
154
+ if [[ -f "$progress_file" ]]; then
155
+ local progress=""
156
+ if type read_batch_progress &>/dev/null; then
157
+ # Structured read: last 2 batches
158
+ local pb_start=$(( batch_num - 2 ))
159
+ [[ $pb_start -lt 1 ]] && pb_start=1
160
+ for ((pb = pb_start; pb < batch_num; pb++)); do
161
+ local pb_content
162
+ pb_content=$(read_batch_progress "$worktree" "$pb" 2>/dev/null || true)
163
+ if [[ -n "$pb_content" ]]; then
164
+ progress+="$pb_content"$'\n'
165
+ fi
166
+ done
167
+ fi
168
+ # No fallback to tail: structured read returning empty means no progress
169
+ # for the requested batches (not a failure condition).
170
+ if [[ -n "$progress" ]]; then
171
+ context+="### Progress Notes"$'\n'
172
+ context+="$progress"$'\n\n'
173
+ fi
174
+ fi
175
+ fi
176
+
177
+ echo "$context"
178
+ }
179
+
180
+ record_failure_pattern() {
181
+ local worktree="$1" batch_title="$2" failure_type="$3" winning_fix="$4"
182
+ local patterns_file="$worktree/logs/failure-patterns.json"
183
+ local title_lower
184
+ title_lower=$(echo "$batch_title" | tr '[:upper:]' '[:lower:]')
185
+
186
+ mkdir -p "$(dirname "$patterns_file")"
187
+
188
+ if [[ ! -f "$patterns_file" ]]; then
189
+ echo "[]" > "$patterns_file"
190
+ fi
191
+
192
+ # Check if pattern already exists
193
+ local existing
194
+ existing=$(jq -r --arg t "$title_lower" --arg f "$failure_type" \
195
+ '[.[] | select(.batch_title_pattern == $t and .failure_type == $f)] | length' \
196
+ "$patterns_file" 2>/dev/null || echo "0")
197
+
198
+ if [[ "$existing" -gt 0 ]]; then
199
+ # Increment frequency — trap ensures temp file cleanup even if jq fails (#58)
200
+ local tmp
201
+ tmp=$(mktemp)
202
+ trap 'rm -f "$tmp"' RETURN
203
+ jq --arg t "$title_lower" --arg f "$failure_type" \
204
+ '[.[] | if .batch_title_pattern == $t and .failure_type == $f then .frequency += 1 else . end]' \
205
+ "$patterns_file" > "$tmp" && mv "$tmp" "$patterns_file"
206
+ else
207
+ # Add new pattern — trap ensures temp file cleanup even if jq fails (#58)
208
+ local tmp
209
+ tmp=$(mktemp)
210
+ trap 'rm -f "$tmp"' RETURN
211
+ jq --arg t "$title_lower" --arg f "$failure_type" --arg w "$winning_fix" \
212
+ '. += [{"batch_title_pattern": $t, "failure_type": $f, "frequency": 1, "winning_fix": $w}]' \
213
+ "$patterns_file" > "$tmp" && mv "$tmp" "$patterns_file"
214
+ fi
215
+ }