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.
- package/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +13 -0
- package/LICENSE +21 -0
- package/Makefile +21 -0
- package/README.md +140 -0
- package/SECURITY.md +28 -0
- package/agents/bash-expert.md +113 -0
- package/agents/dependency-auditor.md +138 -0
- package/agents/integration-tester.md +120 -0
- package/agents/lesson-scanner.md +149 -0
- package/agents/python-expert.md +179 -0
- package/agents/service-monitor.md +141 -0
- package/agents/shell-expert.md +147 -0
- package/benchmarks/runner.sh +147 -0
- package/benchmarks/tasks/01-rest-endpoint/rubric.sh +29 -0
- package/benchmarks/tasks/01-rest-endpoint/task.md +17 -0
- package/benchmarks/tasks/02-refactor-module/task.md +8 -0
- package/benchmarks/tasks/03-fix-integration-bug/task.md +8 -0
- package/benchmarks/tasks/04-add-test-coverage/task.md +8 -0
- package/benchmarks/tasks/05-multi-file-feature/task.md +8 -0
- package/bin/act.js +238 -0
- package/commands/autocode.md +6 -0
- package/commands/cancel-ralph.md +18 -0
- package/commands/code-factory.md +53 -0
- package/commands/create-prd.md +55 -0
- package/commands/ralph-loop.md +18 -0
- package/commands/run-plan.md +117 -0
- package/commands/submit-lesson.md +122 -0
- package/docs/ARCHITECTURE.md +630 -0
- package/docs/CONTRIBUTING.md +125 -0
- package/docs/lessons/0001-bare-exception-swallowing.md +34 -0
- package/docs/lessons/0002-async-def-without-await.md +28 -0
- package/docs/lessons/0003-create-task-without-callback.md +28 -0
- package/docs/lessons/0004-hardcoded-test-counts.md +28 -0
- package/docs/lessons/0005-sqlite-without-closing.md +33 -0
- package/docs/lessons/0006-venv-pip-path.md +27 -0
- package/docs/lessons/0007-runner-state-self-rejection.md +35 -0
- package/docs/lessons/0008-quality-gate-blind-spot.md +33 -0
- package/docs/lessons/0009-parser-overcount-empty-batches.md +36 -0
- package/docs/lessons/0010-local-outside-function-bash.md +33 -0
- package/docs/lessons/0011-batch-tests-for-unimplemented-code.md +36 -0
- package/docs/lessons/0012-api-markdown-unescaped-chars.md +33 -0
- package/docs/lessons/0013-export-prefix-env-parsing.md +33 -0
- package/docs/lessons/0014-decorator-registry-import-side-effect.md +43 -0
- package/docs/lessons/0015-frontend-backend-schema-drift.md +43 -0
- package/docs/lessons/0016-event-driven-cold-start-seeding.md +44 -0
- package/docs/lessons/0017-copy-paste-logic-diverges.md +43 -0
- package/docs/lessons/0018-layer-passes-pipeline-broken.md +45 -0
- package/docs/lessons/0019-systemd-envfile-ignores-export.md +41 -0
- package/docs/lessons/0020-persist-state-incrementally.md +44 -0
- package/docs/lessons/0021-dual-axis-testing.md +48 -0
- package/docs/lessons/0022-jsx-factory-shadowing.md +43 -0
- package/docs/lessons/0023-static-analysis-spiral.md +51 -0
- package/docs/lessons/0024-shared-pipeline-implementation.md +55 -0
- package/docs/lessons/0025-defense-in-depth-all-entry-points.md +65 -0
- package/docs/lessons/0026-linter-no-rules-false-enforcement.md +54 -0
- package/docs/lessons/0027-jsx-silent-prop-drop.md +64 -0
- package/docs/lessons/0028-no-infrastructure-in-client-code.md +49 -0
- package/docs/lessons/0029-never-write-secrets-to-files.md +61 -0
- package/docs/lessons/0030-cache-merge-not-replace.md +62 -0
- package/docs/lessons/0031-verify-units-at-boundaries.md +66 -0
- package/docs/lessons/0032-module-lifecycle-subscribe-unsubscribe.md +89 -0
- package/docs/lessons/0033-async-iteration-mutable-snapshot.md +72 -0
- package/docs/lessons/0034-caller-missing-await-silent-discard.md +65 -0
- package/docs/lessons/0035-duplicate-registration-silent-overwrite.md +85 -0
- package/docs/lessons/0036-websocket-dirty-disconnect.md +33 -0
- package/docs/lessons/0037-parallel-agents-worktree-corruption.md +31 -0
- package/docs/lessons/0038-subscribe-no-stored-ref.md +36 -0
- package/docs/lessons/0039-fallback-or-default-hides-bugs.md +34 -0
- package/docs/lessons/0040-event-firehose-filter-first.md +36 -0
- package/docs/lessons/0041-ambiguous-base-dir-path-nesting.md +32 -0
- package/docs/lessons/0042-spec-compliance-insufficient.md +36 -0
- package/docs/lessons/0043-exact-count-extensible-collections.md +32 -0
- package/docs/lessons/0044-relative-file-deps-worktree.md +39 -0
- package/docs/lessons/0045-iterative-design-improvement.md +33 -0
- package/docs/lessons/0046-plan-assertion-math-bugs.md +38 -0
- package/docs/lessons/0047-pytest-single-threaded-default.md +37 -0
- package/docs/lessons/0048-integration-wiring-batch.md +40 -0
- package/docs/lessons/0049-ab-verification.md +41 -0
- package/docs/lessons/0050-editing-sourced-files-during-execution.md +33 -0
- package/docs/lessons/0051-infrastructure-fixes-cant-self-heal.md +30 -0
- package/docs/lessons/0052-uncommitted-changes-poison-quality-gates.md +31 -0
- package/docs/lessons/0053-jq-compact-flag-inconsistency.md +31 -0
- package/docs/lessons/0054-parser-matches-inside-code-blocks.md +30 -0
- package/docs/lessons/0055-agents-compensate-for-garbled-prompts.md +31 -0
- package/docs/lessons/0056-grep-count-exit-code-on-zero.md +42 -0
- package/docs/lessons/0057-new-artifacts-break-git-clean-gates.md +42 -0
- package/docs/lessons/0058-dead-config-keys-never-consumed.md +49 -0
- package/docs/lessons/0059-contract-test-shared-structures.md +53 -0
- package/docs/lessons/0060-set-e-silent-death-in-runners.md +53 -0
- package/docs/lessons/0061-context-injection-dirty-state.md +50 -0
- package/docs/lessons/0062-sibling-bug-neighborhood-scan.md +29 -0
- package/docs/lessons/0063-one-flag-two-lifetimes.md +31 -0
- package/docs/lessons/0064-test-passes-wrong-reason.md +31 -0
- package/docs/lessons/0065-pipefail-grep-count-double-output.md +39 -0
- package/docs/lessons/0066-local-keyword-outside-function.md +37 -0
- package/docs/lessons/0067-stdin-hang-non-interactive-shell.md +36 -0
- package/docs/lessons/0068-agent-builds-wrong-thing-correctly.md +31 -0
- package/docs/lessons/0069-plan-quality-dominates-execution.md +30 -0
- package/docs/lessons/0070-spec-echo-back-prevents-drift.md +31 -0
- package/docs/lessons/0071-positive-instructions-outperform-negative.md +30 -0
- package/docs/lessons/0072-lost-in-the-middle-context-placement.md +30 -0
- package/docs/lessons/0073-unscoped-lessons-cause-false-positives.md +30 -0
- package/docs/lessons/0074-stale-context-injection-wrong-batch.md +32 -0
- package/docs/lessons/0075-research-artifacts-must-persist.md +32 -0
- package/docs/lessons/0076-wrong-decomposition-contaminates-downstream.md +30 -0
- package/docs/lessons/0077-cherry-pick-merges-need-manual-resolution.md +30 -0
- package/docs/lessons/0078-static-review-without-live-test.md +30 -0
- package/docs/lessons/0079-integration-wiring-batch-required.md +32 -0
- package/docs/lessons/FRAMEWORK.md +161 -0
- package/docs/lessons/SUMMARY.md +201 -0
- package/docs/lessons/TEMPLATE.md +85 -0
- package/docs/plans/2026-02-21-code-factory-v2-design.md +204 -0
- package/docs/plans/2026-02-21-code-factory-v2-implementation-plan.md +2189 -0
- package/docs/plans/2026-02-21-code-factory-v2-phase4-design.md +537 -0
- package/docs/plans/2026-02-21-code-factory-v2-phase4-implementation-plan.md +2012 -0
- package/docs/plans/2026-02-21-hardening-pass-design.md +108 -0
- package/docs/plans/2026-02-21-hardening-pass-plan.md +1378 -0
- package/docs/plans/2026-02-21-mab-research-report.md +406 -0
- package/docs/plans/2026-02-21-marketplace-restructure-design.md +240 -0
- package/docs/plans/2026-02-21-marketplace-restructure-plan.md +832 -0
- package/docs/plans/2026-02-21-phase4-completion-plan.md +697 -0
- package/docs/plans/2026-02-21-validator-suite-design.md +148 -0
- package/docs/plans/2026-02-21-validator-suite-plan.md +540 -0
- package/docs/plans/2026-02-22-mab-research-round2.md +556 -0
- package/docs/plans/2026-02-22-mab-run-design.md +462 -0
- package/docs/plans/2026-02-22-mab-run-plan.md +2046 -0
- package/docs/plans/2026-02-22-operations-design-methodology-research.md +681 -0
- package/docs/plans/2026-02-22-research-agent-failure-taxonomy.md +532 -0
- package/docs/plans/2026-02-22-research-code-guideline-policies.md +886 -0
- package/docs/plans/2026-02-22-research-codebase-audit-refactoring.md +908 -0
- package/docs/plans/2026-02-22-research-coding-standards-documentation.md +541 -0
- package/docs/plans/2026-02-22-research-competitive-landscape.md +687 -0
- package/docs/plans/2026-02-22-research-comprehensive-testing.md +1076 -0
- package/docs/plans/2026-02-22-research-context-utilization.md +459 -0
- package/docs/plans/2026-02-22-research-cost-quality-tradeoff.md +548 -0
- package/docs/plans/2026-02-22-research-lesson-transferability.md +508 -0
- package/docs/plans/2026-02-22-research-multi-agent-coordination.md +312 -0
- package/docs/plans/2026-02-22-research-phase-integration.md +602 -0
- package/docs/plans/2026-02-22-research-plan-quality.md +428 -0
- package/docs/plans/2026-02-22-research-prompt-engineering.md +558 -0
- package/docs/plans/2026-02-22-research-unconventional-perspectives.md +528 -0
- package/docs/plans/2026-02-22-research-user-adoption.md +638 -0
- package/docs/plans/2026-02-22-research-verification-effectiveness.md +433 -0
- package/docs/plans/2026-02-23-agent-suite-design.md +299 -0
- package/docs/plans/2026-02-23-agent-suite-plan.md +578 -0
- package/docs/plans/2026-02-23-phase3-cost-infrastructure-design.md +148 -0
- package/docs/plans/2026-02-23-phase3-cost-infrastructure-plan.md +1062 -0
- package/docs/plans/2026-02-23-research-bash-expert-agent.md +543 -0
- package/docs/plans/2026-02-23-research-dependency-auditor-agent.md +564 -0
- package/docs/plans/2026-02-23-research-improving-existing-agents.md +503 -0
- package/docs/plans/2026-02-23-research-integration-tester-agent.md +454 -0
- package/docs/plans/2026-02-23-research-python-expert-agent.md +429 -0
- package/docs/plans/2026-02-23-research-service-monitor-agent.md +425 -0
- package/docs/plans/2026-02-23-research-shell-expert-agent.md +533 -0
- package/docs/plans/2026-02-23-roadmap-to-completion.md +530 -0
- package/docs/plans/2026-02-24-headless-module-split-design.md +98 -0
- package/docs/plans/2026-02-24-headless-module-split.md +443 -0
- package/docs/plans/2026-02-24-lesson-scope-metadata-design.md +228 -0
- package/docs/plans/2026-02-24-lesson-scope-metadata-plan.md +968 -0
- package/docs/plans/2026-02-24-npm-packaging-design.md +841 -0
- package/docs/plans/2026-02-24-npm-packaging-plan.md +1965 -0
- package/docs/plans/audit-findings.md +186 -0
- package/docs/telegram-notification-format.md +98 -0
- package/examples/example-plan.md +51 -0
- package/examples/example-prd.json +72 -0
- package/examples/example-roadmap.md +33 -0
- package/examples/quickstart-plan.md +63 -0
- package/hooks/hooks.json +26 -0
- package/hooks/setup-symlinks.sh +48 -0
- package/hooks/stop-hook.sh +135 -0
- package/package.json +47 -0
- package/policies/bash.md +71 -0
- package/policies/python.md +71 -0
- package/policies/testing.md +61 -0
- package/policies/universal.md +60 -0
- package/scripts/analyze-report.sh +97 -0
- package/scripts/architecture-map.sh +145 -0
- package/scripts/auto-compound.sh +273 -0
- package/scripts/batch-audit.sh +42 -0
- package/scripts/batch-test.sh +101 -0
- package/scripts/entropy-audit.sh +221 -0
- package/scripts/failure-digest.sh +51 -0
- package/scripts/generate-ast-rules.sh +96 -0
- package/scripts/init.sh +112 -0
- package/scripts/lesson-check.sh +428 -0
- package/scripts/lib/common.sh +61 -0
- package/scripts/lib/cost-tracking.sh +153 -0
- package/scripts/lib/ollama.sh +60 -0
- package/scripts/lib/progress-writer.sh +128 -0
- package/scripts/lib/run-plan-context.sh +215 -0
- package/scripts/lib/run-plan-echo-back.sh +231 -0
- package/scripts/lib/run-plan-headless.sh +396 -0
- package/scripts/lib/run-plan-notify.sh +57 -0
- package/scripts/lib/run-plan-parser.sh +81 -0
- package/scripts/lib/run-plan-prompt.sh +215 -0
- package/scripts/lib/run-plan-quality-gate.sh +132 -0
- package/scripts/lib/run-plan-routing.sh +315 -0
- package/scripts/lib/run-plan-sampling.sh +170 -0
- package/scripts/lib/run-plan-scoring.sh +146 -0
- package/scripts/lib/run-plan-state.sh +142 -0
- package/scripts/lib/run-plan-team.sh +199 -0
- package/scripts/lib/telegram.sh +54 -0
- package/scripts/lib/thompson-sampling.sh +176 -0
- package/scripts/license-check.sh +74 -0
- package/scripts/mab-run.sh +575 -0
- package/scripts/module-size-check.sh +146 -0
- package/scripts/patterns/async-no-await.yml +5 -0
- package/scripts/patterns/bare-except.yml +6 -0
- package/scripts/patterns/empty-catch.yml +6 -0
- package/scripts/patterns/hardcoded-localhost.yml +9 -0
- package/scripts/patterns/retry-loop-no-backoff.yml +12 -0
- package/scripts/pipeline-status.sh +197 -0
- package/scripts/policy-check.sh +226 -0
- package/scripts/prior-art-search.sh +133 -0
- package/scripts/promote-mab-lessons.sh +126 -0
- package/scripts/prompts/agent-a-superpowers.md +29 -0
- package/scripts/prompts/agent-b-ralph.md +29 -0
- package/scripts/prompts/judge-agent.md +61 -0
- package/scripts/prompts/planner-agent.md +44 -0
- package/scripts/pull-community-lessons.sh +90 -0
- package/scripts/quality-gate.sh +266 -0
- package/scripts/research-gate.sh +90 -0
- package/scripts/run-plan.sh +329 -0
- package/scripts/scope-infer.sh +159 -0
- package/scripts/setup-ralph-loop.sh +155 -0
- package/scripts/telemetry.sh +230 -0
- package/scripts/tests/run-all-tests.sh +52 -0
- package/scripts/tests/test-act-cli.sh +46 -0
- package/scripts/tests/test-agents-md.sh +87 -0
- package/scripts/tests/test-analyze-report.sh +114 -0
- package/scripts/tests/test-architecture-map.sh +89 -0
- package/scripts/tests/test-auto-compound.sh +169 -0
- package/scripts/tests/test-batch-test.sh +65 -0
- package/scripts/tests/test-benchmark-runner.sh +25 -0
- package/scripts/tests/test-common.sh +168 -0
- package/scripts/tests/test-cost-tracking.sh +158 -0
- package/scripts/tests/test-echo-back.sh +180 -0
- package/scripts/tests/test-entropy-audit.sh +146 -0
- package/scripts/tests/test-failure-digest.sh +66 -0
- package/scripts/tests/test-generate-ast-rules.sh +145 -0
- package/scripts/tests/test-helpers.sh +82 -0
- package/scripts/tests/test-init.sh +47 -0
- package/scripts/tests/test-lesson-check.sh +278 -0
- package/scripts/tests/test-lesson-local.sh +55 -0
- package/scripts/tests/test-license-check.sh +109 -0
- package/scripts/tests/test-mab-run.sh +182 -0
- package/scripts/tests/test-ollama-lib.sh +49 -0
- package/scripts/tests/test-ollama.sh +60 -0
- package/scripts/tests/test-pipeline-status.sh +198 -0
- package/scripts/tests/test-policy-check.sh +124 -0
- package/scripts/tests/test-prior-art-search.sh +96 -0
- package/scripts/tests/test-progress-writer.sh +140 -0
- package/scripts/tests/test-promote-mab-lessons.sh +110 -0
- package/scripts/tests/test-pull-community-lessons.sh +149 -0
- package/scripts/tests/test-quality-gate.sh +241 -0
- package/scripts/tests/test-research-gate.sh +132 -0
- package/scripts/tests/test-run-plan-cli.sh +86 -0
- package/scripts/tests/test-run-plan-context.sh +305 -0
- package/scripts/tests/test-run-plan-e2e.sh +153 -0
- package/scripts/tests/test-run-plan-headless.sh +424 -0
- package/scripts/tests/test-run-plan-notify.sh +124 -0
- package/scripts/tests/test-run-plan-parser.sh +217 -0
- package/scripts/tests/test-run-plan-prompt.sh +254 -0
- package/scripts/tests/test-run-plan-quality-gate.sh +222 -0
- package/scripts/tests/test-run-plan-routing.sh +178 -0
- package/scripts/tests/test-run-plan-scoring.sh +148 -0
- package/scripts/tests/test-run-plan-state.sh +261 -0
- package/scripts/tests/test-run-plan-team.sh +157 -0
- package/scripts/tests/test-scope-infer.sh +150 -0
- package/scripts/tests/test-setup-ralph-loop.sh +63 -0
- package/scripts/tests/test-telegram-env.sh +38 -0
- package/scripts/tests/test-telegram.sh +121 -0
- package/scripts/tests/test-telemetry.sh +46 -0
- package/scripts/tests/test-thompson-sampling.sh +139 -0
- package/scripts/tests/test-validate-all.sh +60 -0
- package/scripts/tests/test-validate-commands.sh +89 -0
- package/scripts/tests/test-validate-hooks.sh +98 -0
- package/scripts/tests/test-validate-lessons.sh +150 -0
- package/scripts/tests/test-validate-plan-quality.sh +235 -0
- package/scripts/tests/test-validate-plans.sh +187 -0
- package/scripts/tests/test-validate-plugin.sh +106 -0
- package/scripts/tests/test-validate-prd.sh +184 -0
- package/scripts/tests/test-validate-skills.sh +134 -0
- package/scripts/validate-all.sh +57 -0
- package/scripts/validate-commands.sh +67 -0
- package/scripts/validate-hooks.sh +89 -0
- package/scripts/validate-lessons.sh +98 -0
- package/scripts/validate-plan-quality.sh +369 -0
- package/scripts/validate-plans.sh +120 -0
- package/scripts/validate-plugin.sh +86 -0
- package/scripts/validate-policies.sh +42 -0
- package/scripts/validate-prd.sh +118 -0
- package/scripts/validate-skills.sh +96 -0
- package/skills/autocode/SKILL.md +285 -0
- package/skills/autocode/ab-verification.md +51 -0
- package/skills/autocode/code-quality-standards.md +37 -0
- package/skills/autocode/competitive-mode.md +364 -0
- package/skills/brainstorming/SKILL.md +97 -0
- package/skills/capture-lesson/SKILL.md +187 -0
- package/skills/check-lessons/SKILL.md +116 -0
- package/skills/dispatching-parallel-agents/SKILL.md +110 -0
- package/skills/executing-plans/SKILL.md +85 -0
- package/skills/finishing-a-development-branch/SKILL.md +201 -0
- package/skills/receiving-code-review/SKILL.md +72 -0
- package/skills/requesting-code-review/SKILL.md +59 -0
- package/skills/requesting-code-review/code-reviewer.md +82 -0
- package/skills/research/SKILL.md +145 -0
- package/skills/roadmap/SKILL.md +115 -0
- package/skills/subagent-driven-development/SKILL.md +98 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +18 -0
- package/skills/subagent-driven-development/implementer-prompt.md +73 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +57 -0
- package/skills/systematic-debugging/SKILL.md +134 -0
- package/skills/systematic-debugging/condition-based-waiting.md +64 -0
- package/skills/systematic-debugging/defense-in-depth.md +32 -0
- package/skills/systematic-debugging/root-cause-tracing.md +55 -0
- package/skills/test-driven-development/SKILL.md +167 -0
- package/skills/using-git-worktrees/SKILL.md +219 -0
- package/skills/using-superpowers/SKILL.md +54 -0
- package/skills/verification-before-completion/SKILL.md +140 -0
- package/skills/verify/SKILL.md +82 -0
- package/skills/writing-plans/SKILL.md +128 -0
- 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
|