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,124 @@
1
+ #!/usr/bin/env bash
2
+ # Tests for policy-check.sh
3
+
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ POLICY_CHECK="$SCRIPT_DIR/../policy-check.sh"
8
+ PASS=0 FAIL=0 TOTAL=0
9
+
10
+ assert() {
11
+ local desc="$1" expected="$2" actual="$3"
12
+ TOTAL=$((TOTAL + 1))
13
+ if [[ "$expected" == "$actual" ]]; then
14
+ echo "PASS: $desc"
15
+ PASS=$((PASS + 1))
16
+ else
17
+ echo "FAIL: $desc (expected=$expected, actual=$actual)"
18
+ FAIL=$((FAIL + 1))
19
+ fi
20
+ }
21
+
22
+ tmpdir=$(mktemp -d)
23
+ trap 'rm -rf "$tmpdir"' EXIT
24
+
25
+ # Test 1: --help exits 0
26
+ exit_code=0
27
+ output=$("$POLICY_CHECK" --help 2>&1) || exit_code=$?
28
+ assert "--help: exit 0" "0" "$exit_code"
29
+ echo "$output" | grep -q "USAGE"
30
+ assert "--help: shows usage" "0" "$?"
31
+
32
+ # Test 2: Missing project dir exits 1
33
+ exit_code=0
34
+ "$POLICY_CHECK" --project-root "$tmpdir/nonexistent" > /dev/null 2>&1 || exit_code=$?
35
+ assert "missing dir: exit 1" "1" "$exit_code"
36
+
37
+ # Test 3: Clean bash project (with strict mode)
38
+ mkdir -p "$tmpdir/clean-bash"
39
+ cat > "$tmpdir/clean-bash/example.sh" <<'SCRIPT'
40
+ #!/usr/bin/env bash
41
+ set -euo pipefail
42
+ echo "hello"
43
+ SCRIPT
44
+ exit_code=0
45
+ output=$("$POLICY_CHECK" --project-root "$tmpdir/clean-bash" 2>&1) || exit_code=$?
46
+ assert "clean bash: exit 0" "0" "$exit_code"
47
+ echo "$output" | grep -q "no violations"
48
+ assert "clean bash: clean output" "0" "$?"
49
+
50
+ # Test 4: Bash script missing strict mode (advisory)
51
+ mkdir -p "$tmpdir/bad-bash"
52
+ cat > "$tmpdir/bad-bash/bad.sh" <<'SCRIPT'
53
+ #!/usr/bin/env bash
54
+ echo "no strict mode"
55
+ SCRIPT
56
+ exit_code=0
57
+ output=$("$POLICY_CHECK" --project-root "$tmpdir/bad-bash" 2>&1) || exit_code=$?
58
+ assert "bad bash advisory: exit 0" "0" "$exit_code"
59
+ echo "$output" | grep -q "missing strict mode"
60
+ assert "bad bash advisory: shows violation" "0" "$?"
61
+
62
+ # Test 5: Bash script missing strict mode (strict mode)
63
+ exit_code=0
64
+ "$POLICY_CHECK" --project-root "$tmpdir/bad-bash" --strict > /dev/null 2>&1 || exit_code=$?
65
+ assert "bad bash strict: exit 1" "1" "$exit_code"
66
+
67
+ # Test 6: Python project with sqlite but no closing()
68
+ mkdir -p "$tmpdir/bad-python"
69
+ touch "$tmpdir/bad-python/requirements.txt"
70
+ cat > "$tmpdir/bad-python/db.py" <<'PYCODE'
71
+ import sqlite3
72
+ conn = sqlite3.connect("test.db")
73
+ cursor = conn.execute("SELECT 1")
74
+ conn.close()
75
+ PYCODE
76
+ exit_code=0
77
+ output=$("$POLICY_CHECK" --project-root "$tmpdir/bad-python" 2>&1) || exit_code=$?
78
+ assert "python sqlite no closing: exit 0 (advisory)" "0" "$exit_code"
79
+ echo "$output" | grep -q "closing"
80
+ assert "python sqlite: shows violation" "0" "$?"
81
+
82
+ # Test 7: Python project with closing() (clean)
83
+ mkdir -p "$tmpdir/good-python"
84
+ touch "$tmpdir/good-python/requirements.txt"
85
+ cat > "$tmpdir/good-python/db.py" <<'PYCODE'
86
+ import sqlite3
87
+ from contextlib import closing
88
+ with closing(sqlite3.connect("test.db")) as conn:
89
+ cursor = conn.execute("SELECT 1")
90
+ PYCODE
91
+ exit_code=0
92
+ output=$("$POLICY_CHECK" --project-root "$tmpdir/good-python" 2>&1) || exit_code=$?
93
+ assert "python sqlite with closing: exit 0" "0" "$exit_code"
94
+
95
+ # Test 8: Empty directory (no language detected)
96
+ mkdir -p "$tmpdir/empty"
97
+ exit_code=0
98
+ output=$("$POLICY_CHECK" --project-root "$tmpdir/empty" 2>&1) || exit_code=$?
99
+ assert "empty dir: exit 0" "0" "$exit_code"
100
+
101
+ # Test 9: Test file with hardcoded count
102
+ mkdir -p "$tmpdir/bad-tests"
103
+ cat > "$tmpdir/bad-tests/test-example.sh" <<'SCRIPT'
104
+ #!/usr/bin/env bash
105
+ set -euo pipefail
106
+ count=42
107
+ assert "test count" -eq 42
108
+ SCRIPT
109
+ exit_code=0
110
+ output=$("$POLICY_CHECK" --project-root "$tmpdir/bad-tests" 2>&1) || exit_code=$?
111
+ assert "hardcoded count: exit 0 (advisory)" "0" "$exit_code"
112
+
113
+ # Test 10: Strict mode with no violations
114
+ exit_code=0
115
+ output=$("$POLICY_CHECK" --project-root "$tmpdir/clean-bash" --strict 2>&1) || exit_code=$?
116
+ assert "strict clean: exit 0" "0" "$exit_code"
117
+
118
+ echo ""
119
+ echo "Results: $PASS/$TOTAL passed"
120
+ if [[ $FAIL -gt 0 ]]; then
121
+ echo "FAILURES: $FAIL"
122
+ exit 1
123
+ fi
124
+ echo "ALL PASSED"
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # Test prior-art-search.sh
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ SEARCH_SCRIPT="$SCRIPT_DIR/../prior-art-search.sh"
7
+
8
+ FAILURES=0
9
+ TESTS=0
10
+
11
+ assert_eq() {
12
+ local desc="$1" expected="$2" actual="$3"
13
+ TESTS=$((TESTS + 1))
14
+ if [[ "$expected" != "$actual" ]]; then
15
+ echo "FAIL: $desc"
16
+ echo " expected: $expected"
17
+ echo " actual: $actual"
18
+ FAILURES=$((FAILURES + 1))
19
+ else
20
+ echo "PASS: $desc"
21
+ fi
22
+ }
23
+
24
+ assert_exit() {
25
+ local desc="$1" expected_exit="$2"
26
+ shift 2
27
+ local actual_exit=0
28
+ "$@" >/dev/null 2>&1 || actual_exit=$?
29
+ TESTS=$((TESTS + 1))
30
+ if [[ "$expected_exit" != "$actual_exit" ]]; then
31
+ echo "FAIL: $desc"
32
+ echo " expected exit: $expected_exit"
33
+ echo " actual exit: $actual_exit"
34
+ FAILURES=$((FAILURES + 1))
35
+ else
36
+ echo "PASS: $desc"
37
+ fi
38
+ }
39
+
40
+ # --- Test: --help exits 0 ---
41
+ assert_exit "prior-art-search --help exits 0" 0 \
42
+ bash "$SEARCH_SCRIPT" --help
43
+
44
+ # --- Test: --dry-run produces output without calling gh ---
45
+ output=$(bash "$SEARCH_SCRIPT" --dry-run "implement webhook handler" 2>&1)
46
+ echo "$output" | grep -q "Search query:" && TESTS=$((TESTS + 1)) && echo "PASS: dry-run shows search query" || {
47
+ TESTS=$((TESTS + 1)); echo "FAIL: dry-run missing search query"; FAILURES=$((FAILURES + 1))
48
+ }
49
+
50
+ # --- Test: dry-run shows what would be searched ---
51
+ TESTS=$((TESTS + 1))
52
+ if echo "$output" | grep -q "Would search:"; then
53
+ echo "PASS: dry-run shows would-search list"
54
+ else
55
+ echo "FAIL: dry-run shows would-search list"
56
+ FAILURES=$((FAILURES + 1))
57
+ fi
58
+
59
+ # --- Test: --dry-run exits 0 ---
60
+ assert_exit "prior-art-search --dry-run exits 0" 0 \
61
+ bash "$SEARCH_SCRIPT" --dry-run "test query"
62
+
63
+ # --- Test: missing query shows usage ---
64
+ assert_exit "prior-art-search: no args exits 1" 1 \
65
+ bash "$SEARCH_SCRIPT"
66
+
67
+ # --- Test: --local-only flag accepted ---
68
+ assert_exit "prior-art-search --local-only --dry-run exits 0" 0 \
69
+ bash "$SEARCH_SCRIPT" --local-only --dry-run "test query"
70
+
71
+ # --- Test: --github-only flag accepted ---
72
+ assert_exit "prior-art-search --github-only --dry-run exits 0" 0 \
73
+ bash "$SEARCH_SCRIPT" --github-only --dry-run "test query"
74
+
75
+ # --- Test: unknown option exits 1 ---
76
+ assert_exit "prior-art-search: unknown option exits 1" 1 \
77
+ bash "$SEARCH_SCRIPT" --bogus-flag
78
+
79
+ # --- Test: dry-run includes ast-grep section ---
80
+ output=$(bash "$SEARCH_SCRIPT" --dry-run "error handling patterns" 2>&1)
81
+ TESTS=$((TESTS + 1))
82
+ if echo "$output" | grep -qi "ast-grep\|structural"; then
83
+ echo "PASS: prior-art: dry-run mentions ast-grep/structural search"
84
+ else
85
+ echo "FAIL: prior-art: dry-run should mention ast-grep/structural search"
86
+ FAILURES=$((FAILURES + 1))
87
+ fi
88
+
89
+ # === Summary ===
90
+ echo ""
91
+ echo "Results: $((TESTS - FAILURES))/$TESTS passed"
92
+ if [[ $FAILURES -gt 0 ]]; then
93
+ echo "FAILURES: $FAILURES"
94
+ exit 1
95
+ fi
96
+ echo "ALL PASSED"
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ source "$SCRIPT_DIR/test-helpers.sh"
6
+ source "$SCRIPT_DIR/../lib/progress-writer.sh"
7
+
8
+ WORK=$(mktemp -d)
9
+ trap 'rm -rf "$WORK"' EXIT
10
+
11
+ # === write_batch_progress tests ===
12
+
13
+ # Test: writes batch header with timestamp
14
+ write_batch_progress "$WORK" 1 "Foundation setup"
15
+ content=$(cat "$WORK/progress.txt")
16
+ assert_contains "write_batch_progress: creates progress.txt" "## Batch 1: Foundation setup" "$content"
17
+ assert_contains "write_batch_progress: includes timestamp" "T" "$content"
18
+
19
+ # Test: appending second batch header
20
+ write_batch_progress "$WORK" 2 "Add tests"
21
+ content=$(cat "$WORK/progress.txt")
22
+ assert_contains "write_batch_progress: batch 2 header present" "## Batch 2: Add tests" "$content"
23
+ assert_contains "write_batch_progress: batch 1 still present" "## Batch 1: Foundation setup" "$content"
24
+
25
+ # === append_progress_section tests ===
26
+
27
+ # Test: append Files Modified section
28
+ append_progress_section "$WORK" "Files Modified" "- scripts/lib/foo.sh (created)
29
+ - scripts/lib/bar.sh (modified)"
30
+ content=$(cat "$WORK/progress.txt")
31
+ assert_contains "append_progress_section: Files Modified header" "### Files Modified" "$content"
32
+ assert_contains "append_progress_section: file list content" "scripts/lib/foo.sh (created)" "$content"
33
+
34
+ # Test: append Decisions section
35
+ append_progress_section "$WORK" "Decisions" "- Used awk for parsing: simpler than sed for multi-line extraction"
36
+ content=$(cat "$WORK/progress.txt")
37
+ assert_contains "append_progress_section: Decisions header" "### Decisions" "$content"
38
+ assert_contains "append_progress_section: decision content" "Used awk for parsing" "$content"
39
+
40
+ # Test: append Issues Encountered section
41
+ append_progress_section "$WORK" "Issues Encountered" "- shellcheck warning → added quotes"
42
+ content=$(cat "$WORK/progress.txt")
43
+ assert_contains "append_progress_section: Issues header" "### Issues Encountered" "$content"
44
+
45
+ # Test: append State section
46
+ append_progress_section "$WORK" "State" "- Tests: 12 passing
47
+ - Duration: 45s
48
+ - Cost: \$0.03"
49
+ content=$(cat "$WORK/progress.txt")
50
+ assert_contains "append_progress_section: State header" "### State" "$content"
51
+ assert_contains "append_progress_section: test count" "Tests: 12 passing" "$content"
52
+
53
+ # === read_batch_progress tests ===
54
+
55
+ # Test: read batch 1
56
+ batch1=$(read_batch_progress "$WORK" 1)
57
+ assert_contains "read_batch_progress: returns batch 1 header" "## Batch 1: Foundation setup" "$batch1"
58
+ assert_not_contains "read_batch_progress: excludes batch 2" "## Batch 2" "$batch1"
59
+
60
+ # Test: read batch 2 — should include all sections we appended
61
+ batch2=$(read_batch_progress "$WORK" 2)
62
+ assert_contains "read_batch_progress: returns batch 2 header" "## Batch 2: Add tests" "$batch2"
63
+ assert_contains "read_batch_progress: includes Files Modified" "### Files Modified" "$batch2"
64
+ assert_contains "read_batch_progress: includes Decisions" "### Decisions" "$batch2"
65
+ assert_contains "read_batch_progress: includes State" "### State" "$batch2"
66
+ assert_not_contains "read_batch_progress: excludes batch 1" "## Batch 1" "$batch2"
67
+
68
+ # Test: read nonexistent batch — returns empty, exit 1
69
+ batch99=""
70
+ batch99_exit=0
71
+ batch99=$(read_batch_progress "$WORK" 99) || batch99_exit=$?
72
+ assert_eq "read_batch_progress: nonexistent batch returns exit 1" "1" "$batch99_exit"
73
+ assert_eq "read_batch_progress: nonexistent batch returns empty" "" "$batch99"
74
+
75
+ # Test: read from nonexistent worktree — returns empty, exit 2
76
+ batch_none=""
77
+ batch_none_exit=0
78
+ batch_none=$(read_batch_progress "/tmp/nonexistent-worktree-$$" 1) || batch_none_exit=$?
79
+ assert_eq "read_batch_progress: missing progress.txt returns exit 2" "2" "$batch_none_exit"
80
+ assert_eq "read_batch_progress: missing progress.txt returns empty" "" "$batch_none"
81
+
82
+ # Test: invalid batch_num — returns exit 1 with error message
83
+ invalid_exit=0
84
+ invalid_out=""
85
+ invalid_out=$(read_batch_progress "$WORK" "abc" 2>&1) || invalid_exit=$?
86
+ assert_eq "read_batch_progress: invalid batch_num exits 1" "1" "$invalid_exit"
87
+ assert_contains "read_batch_progress: invalid batch_num prints error" "batch_num must be a positive integer" "$invalid_out"
88
+
89
+ # === Round-trip test with fresh worktree ===
90
+
91
+ WORK2=$(mktemp -d)
92
+ trap 'rm -rf "$WORK2"' EXIT
93
+ write_batch_progress "$WORK2" 1 "Round trip test"
94
+ append_progress_section "$WORK2" "Files Modified" "- a.sh (created)"
95
+ append_progress_section "$WORK2" "State" "- Tests: 5 passing"
96
+
97
+ roundtrip=$(read_batch_progress "$WORK2" 1)
98
+ assert_contains "round-trip: header preserved" "## Batch 1: Round trip test" "$roundtrip"
99
+ assert_contains "round-trip: files preserved" "a.sh (created)" "$roundtrip"
100
+ assert_contains "round-trip: state preserved" "Tests: 5 passing" "$roundtrip"
101
+
102
+ # === Multi-batch isolation test ===
103
+
104
+ WORK3=$(mktemp -d)
105
+ trap 'rm -rf "$WORK3"' EXIT
106
+ write_batch_progress "$WORK3" 1 "First"
107
+ append_progress_section "$WORK3" "State" "- Tests: 3 passing"
108
+ write_batch_progress "$WORK3" 2 "Second"
109
+ append_progress_section "$WORK3" "State" "- Tests: 7 passing"
110
+ write_batch_progress "$WORK3" 3 "Third"
111
+ append_progress_section "$WORK3" "State" "- Tests: 12 passing"
112
+
113
+ b1=$(read_batch_progress "$WORK3" 1)
114
+ b2=$(read_batch_progress "$WORK3" 2)
115
+ b3=$(read_batch_progress "$WORK3" 3)
116
+
117
+ assert_contains "multi-batch: batch 1 has 3 tests" "Tests: 3 passing" "$b1"
118
+ assert_not_contains "multi-batch: batch 1 excludes batch 2 state" "Tests: 7 passing" "$b1"
119
+ assert_contains "multi-batch: batch 2 has 7 tests" "Tests: 7 passing" "$b2"
120
+ assert_not_contains "multi-batch: batch 2 excludes batch 3 state" "Tests: 12 passing" "$b2"
121
+ assert_contains "multi-batch: batch 3 has 12 tests" "Tests: 12 passing" "$b3"
122
+
123
+ # === False-match prevention: content that looks like a batch header ===
124
+ # Content mentioning "## Batch N:" should NOT stop extraction early
125
+ # because the timestamp anchoring prevents it (#55)
126
+
127
+ WORK4=$(mktemp -d)
128
+ trap 'rm -rf "$WORK4"' EXIT
129
+ write_batch_progress "$WORK4" 1 "Content test"
130
+ # Manually write content that looks like a batch header (without timestamp)
131
+ echo "## Batch 2: This is just a note, not a real header" >> "$WORK4/progress.txt"
132
+ echo "- some progress note" >> "$WORK4/progress.txt"
133
+ write_batch_progress "$WORK4" 2 "Real second batch"
134
+ append_progress_section "$WORK4" "State" "- Tests: 9 passing"
135
+
136
+ b1_content=$(read_batch_progress "$WORK4" 1)
137
+ assert_contains "false-match: batch 1 includes embedded note" "This is just a note" "$b1_content"
138
+ assert_not_contains "false-match: batch 1 stops at real batch 2 header" "Tests: 9 passing" "$b1_content"
139
+
140
+ report_results
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ source "$SCRIPT_DIR/test-helpers.sh"
6
+
7
+ PROMOTE="$SCRIPT_DIR/../promote-mab-lessons.sh"
8
+
9
+ # --- Setup ---
10
+ TEST_TMPDIR=$(mktemp -d)
11
+ trap 'rm -rf "$TEST_TMPDIR"' EXIT
12
+
13
+ mkdir -p "$TEST_TMPDIR/logs" "$TEST_TMPDIR/docs/lessons"
14
+
15
+ # --- Test: --help exits 0 ---
16
+ assert_exit "--help exits 0" 0 "$PROMOTE" --help
17
+
18
+ # --- Test: no promotions when all below threshold ---
19
+ cat > "$TEST_TMPDIR/logs/mab-lessons.json" <<'JSON'
20
+ [
21
+ {"pattern": "rare pattern", "context": "new-file", "winner": "superpowers", "occurrences": 1, "promoted": false},
22
+ {"pattern": "another rare", "context": "refactoring", "winner": "ralph", "occurrences": 2, "promoted": false}
23
+ ]
24
+ JSON
25
+
26
+ output=$("$PROMOTE" --worktree "$TEST_TMPDIR" --min-occurrences 3 2>&1) || true
27
+ lesson_count=$(find "$TEST_TMPDIR/docs/lessons" -name "*.md" -newer "$TEST_TMPDIR/logs/mab-lessons.json" 2>/dev/null | wc -l)
28
+ assert_eq "no promotions below threshold" "0" "$lesson_count"
29
+
30
+ # --- Test: promotes at 3+ occurrences ---
31
+ cat > "$TEST_TMPDIR/logs/mab-lessons.json" <<'JSON'
32
+ [
33
+ {"pattern": "check imports before tests", "context": "integration", "winner": "superpowers", "occurrences": 5, "promoted": false},
34
+ {"pattern": "rare pattern", "context": "new-file", "winner": "ralph", "occurrences": 1, "promoted": false}
35
+ ]
36
+ JSON
37
+
38
+ "$PROMOTE" --worktree "$TEST_TMPDIR" --min-occurrences 3 > /dev/null 2>&1 || true
39
+
40
+ # Check a lesson file was created
41
+ promoted_files=$(find "$TEST_TMPDIR/docs/lessons" -name "*.md" 2>/dev/null)
42
+ TESTS=$((TESTS + 1))
43
+ if [[ -n "$promoted_files" ]]; then
44
+ echo "PASS: promotes at 3+ occurrences"
45
+ else
46
+ echo "FAIL: no lesson file created for pattern with 5 occurrences"
47
+ FAILURES=$((FAILURES + 1))
48
+ fi
49
+
50
+ # --- Test: promoted file has YAML frontmatter ---
51
+ if [[ -n "$promoted_files" ]]; then
52
+ first_file=$(echo "$promoted_files" | head -1)
53
+ first_line=$(head -1 "$first_file")
54
+ assert_eq "promoted file starts with YAML frontmatter" "---" "$first_line"
55
+
56
+ file_content=$(cat "$first_file")
57
+ assert_contains "promoted file has pattern field" "pattern:" "$file_content"
58
+ fi
59
+
60
+ # --- Test: marks lessons as promoted in JSON ---
61
+ promoted_count=$(jq '[.[] | select(.promoted == true)] | length' "$TEST_TMPDIR/logs/mab-lessons.json" 2>/dev/null || echo "0")
62
+ assert_eq "promoted lesson marked in JSON" "1" "$promoted_count"
63
+
64
+ # --- Test: --dry-run creates no files ---
65
+ # Reset
66
+ rm -f "$TEST_TMPDIR/docs/lessons"/*.md
67
+ cat > "$TEST_TMPDIR/logs/mab-lessons.json" <<'JSON'
68
+ [
69
+ {"pattern": "should not create file", "context": "test-only", "winner": "ralph", "occurrences": 10, "promoted": false}
70
+ ]
71
+ JSON
72
+
73
+ "$PROMOTE" --worktree "$TEST_TMPDIR" --min-occurrences 3 --dry-run > /dev/null 2>&1 || true
74
+ dry_files=$(find "$TEST_TMPDIR/docs/lessons" -name "*.md" 2>/dev/null | wc -l)
75
+ assert_eq "--dry-run creates no files" "0" "$dry_files"
76
+
77
+ # Verify JSON not modified either
78
+ dry_promoted=$(jq '[.[] | select(.promoted == true)] | length' "$TEST_TMPDIR/logs/mab-lessons.json" 2>/dev/null || echo "0")
79
+ assert_eq "--dry-run does not mark as promoted" "0" "$dry_promoted"
80
+
81
+ # --- Test: idempotency — second promotion creates no new files ---
82
+ # Reset directory
83
+ rm -f "$TEST_TMPDIR/docs/lessons"/*.md
84
+
85
+ cat > "$TEST_TMPDIR/logs/mab-lessons.json" <<'JSON'
86
+ [
87
+ {"pattern": "idempotent test pattern", "context": "new-file", "winner": "superpowers", "occurrences": 5, "promoted": false}
88
+ ]
89
+ JSON
90
+
91
+ # First run — should create lesson file and mark as promoted
92
+ "$PROMOTE" --worktree "$TEST_TMPDIR" --min-occurrences 3 > /dev/null 2>&1 || true
93
+ first_count=$(find "$TEST_TMPDIR/docs/lessons" -name "*.md" 2>/dev/null | wc -l)
94
+ first_promoted=$(jq '[.[] | select(.promoted == true)] | length' "$TEST_TMPDIR/logs/mab-lessons.json" 2>/dev/null || echo "0")
95
+
96
+ TESTS=$((TESTS + 1))
97
+ if [[ "$first_count" -ge 1 && "$first_promoted" == "1" ]]; then
98
+ echo "PASS: idempotency: first run creates file and marks promoted"
99
+ else
100
+ echo "FAIL: idempotency: first run should create file ($first_count) and mark promoted ($first_promoted)"
101
+ FAILURES=$((FAILURES + 1))
102
+ fi
103
+
104
+ # Second run — promoted=true guard should prevent new files
105
+ "$PROMOTE" --worktree "$TEST_TMPDIR" --min-occurrences 3 > /dev/null 2>&1 || true
106
+ second_count=$(find "$TEST_TMPDIR/docs/lessons" -name "*.md" 2>/dev/null | wc -l)
107
+
108
+ assert_eq "idempotency: second run creates no additional files" "$first_count" "$second_count"
109
+
110
+ report_results
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ source "$SCRIPT_DIR/test-helpers.sh"
6
+
7
+ PULL="$SCRIPT_DIR/../pull-community-lessons.sh"
8
+
9
+ # --- Test: --help exits 0 ---
10
+ assert_exit "--help exits 0" 0 "$PULL" --help
11
+
12
+ # --- Test: missing upstream remote exits 1 gracefully ---
13
+ TEST_TMPDIR=$(mktemp -d)
14
+ trap 'rm -rf "$TEST_TMPDIR"' EXIT
15
+
16
+ cd "$TEST_TMPDIR" && git init -q && git commit --allow-empty -m "init" -q
17
+ pull_output=$("$PULL" --remote nonexistent 2>&1) || true
18
+ pull_exit=0
19
+ "$PULL" --remote nonexistent > /dev/null 2>&1 || pull_exit=$?
20
+ assert_eq "missing remote exits 1" "1" "$pull_exit"
21
+ assert_contains "missing remote error message" "not found" "$pull_output"
22
+
23
+ # --- Test: --dry-run without remote shows status ---
24
+ dry_output=$("$PULL" --remote nonexistent --dry-run 2>&1) || true
25
+ # Should still fail because remote doesn't exist (dry-run doesn't skip validation)
26
+ assert_contains "--dry-run mentions remote name" "nonexistent" "$dry_output"
27
+
28
+ # --- Test: happy path — copies new lessons from upstream ---
29
+ # Create an "upstream" bare repo with a lesson file
30
+ UPSTREAM_DIR=$(mktemp -d)
31
+ LOCAL_DIR=$(mktemp -d)
32
+ trap 'rm -rf "$TEST_TMPDIR" "$UPSTREAM_DIR" "$LOCAL_DIR"' EXIT
33
+
34
+ # Set up upstream repo with a lesson
35
+ git init -q --bare "$UPSTREAM_DIR/upstream.git"
36
+ CLONE_DIR=$(mktemp -d)
37
+ git clone -q "$UPSTREAM_DIR/upstream.git" "$CLONE_DIR/work"
38
+ cd "$CLONE_DIR/work"
39
+ git config user.email "test@test.com"
40
+ git config user.name "Test"
41
+ mkdir -p docs/lessons
42
+ cat > docs/lessons/0099-upstream-lesson.md << 'LESSON'
43
+ ---
44
+ title: Test upstream lesson
45
+ tier: lesson
46
+ scope: universal
47
+ ---
48
+ ## Key Takeaway
49
+ This came from upstream.
50
+ LESSON
51
+ git add docs/lessons/0099-upstream-lesson.md
52
+ git commit -q -m "add upstream lesson"
53
+ git push -q origin main 2>/dev/null
54
+ cd - > /dev/null
55
+
56
+ # Set up local repo with upstream remote pointing to bare repo
57
+ git clone -q "$UPSTREAM_DIR/upstream.git" "$LOCAL_DIR/local"
58
+ cd "$LOCAL_DIR/local"
59
+ git config user.email "test@test.com"
60
+ git config user.name "Test"
61
+ git remote add upstream "$UPSTREAM_DIR/upstream.git"
62
+
63
+ # Remove the lesson locally (simulate it being new)
64
+ rm -f docs/lessons/0099-upstream-lesson.md
65
+ git add -u && git commit -q -m "remove lesson locally" || true
66
+
67
+ # Run pull-community-lessons
68
+ pull_output=$("$PULL" --remote upstream 2>&1) || true
69
+
70
+ TESTS=$((TESTS + 1))
71
+ if [[ -f "docs/lessons/0099-upstream-lesson.md" ]]; then
72
+ echo "PASS: happy path: upstream lesson copied to local"
73
+ else
74
+ echo "FAIL: happy path: upstream lesson should be copied to local docs/lessons/"
75
+ echo " output: $pull_output"
76
+ FAILURES=$((FAILURES + 1))
77
+ fi
78
+
79
+ # Verify content
80
+ TESTS=$((TESTS + 1))
81
+ if grep -q "This came from upstream" "docs/lessons/0099-upstream-lesson.md" 2>/dev/null; then
82
+ echo "PASS: happy path: lesson content is correct"
83
+ else
84
+ echo "FAIL: happy path: lesson content should match upstream"
85
+ FAILURES=$((FAILURES + 1))
86
+ fi
87
+
88
+ cd - > /dev/null
89
+ rm -rf "$CLONE_DIR"
90
+
91
+ # --- Test: strategy-perf.json max() merge ---
92
+ # Local has (3W, 2L), upstream has (5W, 1L) → merged should be (5W, 2L) per max()
93
+ MERGE_DIR=$(mktemp -d)
94
+ trap 'rm -rf "$TEST_TMPDIR" "$UPSTREAM_DIR" "$LOCAL_DIR" "$MERGE_DIR"' EXIT
95
+
96
+ # Setup upstream with perf data
97
+ git init -q --bare "$MERGE_DIR/upstream.git"
98
+ MERGE_CLONE=$(mktemp -d)
99
+ git clone -q "$MERGE_DIR/upstream.git" "$MERGE_CLONE/work"
100
+ cd "$MERGE_CLONE/work"
101
+ git config user.email "test@test.com"
102
+ git config user.name "Test"
103
+ mkdir -p logs docs/lessons
104
+ cat > logs/strategy-perf.json << 'PERF'
105
+ {
106
+ "new-file": {"superpowers": {"wins": 5, "losses": 1}, "ralph": {"wins": 2, "losses": 3}},
107
+ "refactoring": {"superpowers": {"wins": 0, "losses": 0}, "ralph": {"wins": 0, "losses": 0}},
108
+ "integration": {"superpowers": {"wins": 0, "losses": 0}, "ralph": {"wins": 0, "losses": 0}},
109
+ "test-only": {"superpowers": {"wins": 0, "losses": 0}, "ralph": {"wins": 0, "losses": 0}},
110
+ "calibration_count": 0,
111
+ "calibration_complete": false
112
+ }
113
+ PERF
114
+ git add . && git commit -q -m "add perf data" && git push -q origin main 2>/dev/null
115
+ cd - > /dev/null
116
+
117
+ # Local repo with different perf data
118
+ git clone -q "$MERGE_DIR/upstream.git" "$MERGE_DIR/local"
119
+ cd "$MERGE_DIR/local"
120
+ git config user.email "test@test.com"
121
+ git config user.name "Test"
122
+ git remote add upstream "$MERGE_DIR/upstream.git"
123
+ mkdir -p logs
124
+ cat > logs/strategy-perf.json << 'PERF_LOCAL'
125
+ {
126
+ "new-file": {"superpowers": {"wins": 3, "losses": 2}, "ralph": {"wins": 4, "losses": 1}},
127
+ "refactoring": {"superpowers": {"wins": 0, "losses": 0}, "ralph": {"wins": 0, "losses": 0}},
128
+ "integration": {"superpowers": {"wins": 0, "losses": 0}, "ralph": {"wins": 0, "losses": 0}},
129
+ "test-only": {"superpowers": {"wins": 0, "losses": 0}, "ralph": {"wins": 0, "losses": 0}},
130
+ "calibration_count": 0,
131
+ "calibration_complete": false
132
+ }
133
+ PERF_LOCAL
134
+
135
+ "$PULL" --remote upstream > /dev/null 2>&1 || true
136
+
137
+ # Check max() merge: max(3,5)=5 for sp wins, max(2,1)=2 for sp losses
138
+ sp_wins=$(jq '."new-file".superpowers.wins' logs/strategy-perf.json 2>/dev/null)
139
+ sp_losses=$(jq '."new-file".superpowers.losses' logs/strategy-perf.json 2>/dev/null)
140
+ ralph_wins=$(jq '."new-file".ralph.wins' logs/strategy-perf.json 2>/dev/null)
141
+
142
+ assert_eq "max merge: superpowers wins = max(3,5) = 5" "5" "$sp_wins"
143
+ assert_eq "max merge: superpowers losses = max(2,1) = 2" "2" "$sp_losses"
144
+ assert_eq "max merge: ralph wins = max(4,2) = 4" "4" "$ralph_wins"
145
+
146
+ cd - > /dev/null
147
+ rm -rf "$MERGE_CLONE" "$MERGE_DIR"
148
+
149
+ report_results