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,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Test failure-digest.sh
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
DIGEST_SCRIPT="$SCRIPT_DIR/../failure-digest.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
|
+
WORK=$(mktemp -d)
|
|
25
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
26
|
+
|
|
27
|
+
# Create a fake log with errors
|
|
28
|
+
cat > "$WORK/batch-1-attempt-1.log" << 'LOG'
|
|
29
|
+
Some setup output...
|
|
30
|
+
FAILED tests/test_auth.py::test_login - AssertionError: expected 200 got 401
|
|
31
|
+
FAILED tests/test_auth.py::test_signup - KeyError: 'email'
|
|
32
|
+
Traceback (most recent call last):
|
|
33
|
+
File "src/auth.py", line 42, in login
|
|
34
|
+
token = generate_token(user)
|
|
35
|
+
TypeError: generate_token() missing 1 required argument: 'secret'
|
|
36
|
+
3 failed, 10 passed in 5.2s
|
|
37
|
+
LOG
|
|
38
|
+
|
|
39
|
+
# --- Test: extracts failed test names ---
|
|
40
|
+
output=$(bash "$DIGEST_SCRIPT" "$WORK/batch-1-attempt-1.log")
|
|
41
|
+
echo "$output" | grep -q "test_login" && echo "PASS: found test_login" && TESTS=$((TESTS + 1)) || {
|
|
42
|
+
echo "FAIL: missing test_login"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
echo "$output" | grep -q "test_signup" && echo "PASS: found test_signup" && TESTS=$((TESTS + 1)) || {
|
|
46
|
+
echo "FAIL: missing test_signup"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# --- Test: extracts error types ---
|
|
50
|
+
echo "$output" | grep -q "TypeError" && echo "PASS: found TypeError" && TESTS=$((TESTS + 1)) || {
|
|
51
|
+
echo "FAIL: missing TypeError"; TESTS=$((TESTS + 1)); FAILURES=$((FAILURES + 1))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# --- Test: help flag ---
|
|
55
|
+
bash "$DIGEST_SCRIPT" --help >/dev/null 2>&1
|
|
56
|
+
TESTS=$((TESTS + 1))
|
|
57
|
+
echo "PASS: --help exits cleanly"
|
|
58
|
+
|
|
59
|
+
# === Summary ===
|
|
60
|
+
echo ""
|
|
61
|
+
echo "Results: $((TESTS - FAILURES))/$TESTS passed"
|
|
62
|
+
if [[ $FAILURES -gt 0 ]]; then
|
|
63
|
+
echo "FAILURES: $FAILURES"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
echo "ALL PASSED"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
|
|
6
|
+
FAILURES=0
|
|
7
|
+
TESTS=0
|
|
8
|
+
|
|
9
|
+
assert_eq() {
|
|
10
|
+
local desc="$1" expected="$2" actual="$3"
|
|
11
|
+
TESTS=$((TESTS + 1))
|
|
12
|
+
if [[ "$expected" != "$actual" ]]; then
|
|
13
|
+
echo "FAIL: $desc"
|
|
14
|
+
echo " expected: $expected"
|
|
15
|
+
echo " actual: $actual"
|
|
16
|
+
FAILURES=$((FAILURES + 1))
|
|
17
|
+
else
|
|
18
|
+
echo "PASS: $desc"
|
|
19
|
+
fi
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
assert_contains() {
|
|
23
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
24
|
+
TESTS=$((TESTS + 1))
|
|
25
|
+
if [[ "$haystack" == *"$needle"* ]]; then
|
|
26
|
+
echo "PASS: $desc"
|
|
27
|
+
else
|
|
28
|
+
echo "FAIL: $desc"
|
|
29
|
+
echo " expected to contain: $needle"
|
|
30
|
+
FAILURES=$((FAILURES + 1))
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
WORK=$(mktemp -d)
|
|
35
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
36
|
+
|
|
37
|
+
# Create test lesson files
|
|
38
|
+
mkdir -p "$WORK/lessons"
|
|
39
|
+
|
|
40
|
+
# Syntactic lesson (should be SKIPPED — grep handles these)
|
|
41
|
+
cat > "$WORK/lessons/0001-test.md" << 'LESSON'
|
|
42
|
+
---
|
|
43
|
+
id: 1
|
|
44
|
+
title: "Bare except"
|
|
45
|
+
severity: blocker
|
|
46
|
+
languages: [python]
|
|
47
|
+
category: silent-failures
|
|
48
|
+
pattern:
|
|
49
|
+
type: syntactic
|
|
50
|
+
regex: "^\\s*except\\s*:"
|
|
51
|
+
description: "bare except"
|
|
52
|
+
fix: "Use specific exception"
|
|
53
|
+
example:
|
|
54
|
+
bad: |
|
|
55
|
+
except:
|
|
56
|
+
pass
|
|
57
|
+
good: |
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(e)
|
|
60
|
+
---
|
|
61
|
+
LESSON
|
|
62
|
+
|
|
63
|
+
# Semantic lesson with supported language (should generate rule)
|
|
64
|
+
cat > "$WORK/lessons/0033-async.md" << 'LESSON'
|
|
65
|
+
---
|
|
66
|
+
id: 33
|
|
67
|
+
title: "Async iteration mutable"
|
|
68
|
+
severity: blocker
|
|
69
|
+
languages: [python]
|
|
70
|
+
category: async-traps
|
|
71
|
+
pattern:
|
|
72
|
+
type: semantic
|
|
73
|
+
description: "async loop iterates over mutable instance attribute"
|
|
74
|
+
fix: "Snapshot with list()"
|
|
75
|
+
example:
|
|
76
|
+
bad: |
|
|
77
|
+
async for item in self.connections:
|
|
78
|
+
await item.send(data)
|
|
79
|
+
good: |
|
|
80
|
+
for item in list(self.connections):
|
|
81
|
+
await item.send(data)
|
|
82
|
+
---
|
|
83
|
+
LESSON
|
|
84
|
+
|
|
85
|
+
# Unsupported language lesson (should be skipped)
|
|
86
|
+
cat > "$WORK/lessons/0099-go.md" << 'LESSON'
|
|
87
|
+
---
|
|
88
|
+
id: 99
|
|
89
|
+
title: "Go error ignore"
|
|
90
|
+
severity: blocker
|
|
91
|
+
languages: [go]
|
|
92
|
+
category: silent-failures
|
|
93
|
+
pattern:
|
|
94
|
+
type: semantic
|
|
95
|
+
description: "ignoring error return value"
|
|
96
|
+
fix: "Handle the error"
|
|
97
|
+
example:
|
|
98
|
+
bad: |
|
|
99
|
+
result, _ := doThing()
|
|
100
|
+
good: |
|
|
101
|
+
result, err := doThing()
|
|
102
|
+
---
|
|
103
|
+
LESSON
|
|
104
|
+
|
|
105
|
+
# Test: generates pattern files from lessons
|
|
106
|
+
"$SCRIPT_DIR/../generate-ast-rules.sh" --lessons-dir "$WORK/lessons" --output-dir "$WORK/patterns"
|
|
107
|
+
|
|
108
|
+
# Syntactic lessons should NOT generate ast-grep rules (grep handles them)
|
|
109
|
+
assert_eq "generate-ast-rules: skips syntactic patterns" "false" \
|
|
110
|
+
"$(test -f "$WORK/patterns/0001-test.yml" && echo true || echo false)"
|
|
111
|
+
|
|
112
|
+
# Semantic lesson with supported language should generate a rule
|
|
113
|
+
assert_eq "generate-ast-rules: generates for semantic python lesson" "true" \
|
|
114
|
+
"$(test -f "$WORK/patterns/0033-async.yml" && echo true || echo false)"
|
|
115
|
+
|
|
116
|
+
# Unsupported language lesson should NOT generate
|
|
117
|
+
assert_eq "generate-ast-rules: skips unsupported language" "false" \
|
|
118
|
+
"$(test -f "$WORK/patterns/0099-go.yml" && echo true || echo false)"
|
|
119
|
+
|
|
120
|
+
# Generated rule should contain lesson metadata
|
|
121
|
+
if [[ -f "$WORK/patterns/0033-async.yml" ]]; then
|
|
122
|
+
rule_content=$(cat "$WORK/patterns/0033-async.yml")
|
|
123
|
+
assert_contains "generate-ast-rules: rule has id" "0033-async" "$rule_content"
|
|
124
|
+
assert_contains "generate-ast-rules: rule has message" "Async iteration mutable" "$rule_content"
|
|
125
|
+
assert_contains "generate-ast-rules: rule has language" "python" "$rule_content"
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Test: --list flag shows what would be generated
|
|
129
|
+
output=$("$SCRIPT_DIR/../generate-ast-rules.sh" --lessons-dir "$WORK/lessons" --list 2>&1)
|
|
130
|
+
assert_contains "generate-ast-rules: list shows lesson info" "lesson" "$output"
|
|
131
|
+
assert_contains "generate-ast-rules: list shows summary" "syntactic" "$output"
|
|
132
|
+
|
|
133
|
+
# Test: default output-dir falls back to scripts/patterns/ when --output-dir omitted
|
|
134
|
+
# Use an empty lessons dir so nothing is written to the production patterns directory
|
|
135
|
+
mkdir -p "$WORK/empty-lessons"
|
|
136
|
+
default_output=$("$SCRIPT_DIR/../generate-ast-rules.sh" --lessons-dir "$WORK/empty-lessons" 2>&1)
|
|
137
|
+
assert_contains "generate-ast-rules: default output-dir references scripts/patterns" "scripts/patterns" "$default_output"
|
|
138
|
+
|
|
139
|
+
echo ""
|
|
140
|
+
echo "Results: $((TESTS - FAILURES))/$TESTS passed"
|
|
141
|
+
if [[ $FAILURES -gt 0 ]]; then
|
|
142
|
+
echo "FAILURES: $FAILURES"
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
echo "ALL PASSED"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-helpers.sh — Shared test assertions for validator tests
|
|
3
|
+
# Source this file, don't execute it directly.
|
|
4
|
+
|
|
5
|
+
FAILURES=0
|
|
6
|
+
TESTS=0
|
|
7
|
+
|
|
8
|
+
pass() {
|
|
9
|
+
TESTS=$((TESTS + 1))
|
|
10
|
+
echo "PASS: $1"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fail() {
|
|
14
|
+
TESTS=$((TESTS + 1))
|
|
15
|
+
echo "FAIL: $1"
|
|
16
|
+
FAILURES=$((FAILURES + 1))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
assert_eq() {
|
|
20
|
+
local desc="$1" expected="$2" actual="$3"
|
|
21
|
+
TESTS=$((TESTS + 1))
|
|
22
|
+
if [[ "$expected" != "$actual" ]]; then
|
|
23
|
+
echo "FAIL: $desc"
|
|
24
|
+
echo " expected: $expected"
|
|
25
|
+
echo " actual: $actual"
|
|
26
|
+
FAILURES=$((FAILURES + 1))
|
|
27
|
+
else
|
|
28
|
+
echo "PASS: $desc"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
assert_exit() {
|
|
33
|
+
local desc="$1" expected_exit="$2"
|
|
34
|
+
shift 2
|
|
35
|
+
local actual_exit=0
|
|
36
|
+
"$@" >/dev/null 2>&1 || actual_exit=$?
|
|
37
|
+
TESTS=$((TESTS + 1))
|
|
38
|
+
if [[ "$expected_exit" != "$actual_exit" ]]; then
|
|
39
|
+
echo "FAIL: $desc"
|
|
40
|
+
echo " expected exit: $expected_exit"
|
|
41
|
+
echo " actual exit: $actual_exit"
|
|
42
|
+
FAILURES=$((FAILURES + 1))
|
|
43
|
+
else
|
|
44
|
+
echo "PASS: $desc"
|
|
45
|
+
fi
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
assert_contains() {
|
|
49
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
50
|
+
TESTS=$((TESTS + 1))
|
|
51
|
+
if echo "$haystack" | grep -qF "$needle"; then
|
|
52
|
+
echo "PASS: $desc"
|
|
53
|
+
else
|
|
54
|
+
echo "FAIL: $desc"
|
|
55
|
+
echo " expected to contain: $needle"
|
|
56
|
+
echo " in: $(echo "$haystack" | head -5)"
|
|
57
|
+
FAILURES=$((FAILURES + 1))
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
assert_not_contains() {
|
|
62
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
63
|
+
TESTS=$((TESTS + 1))
|
|
64
|
+
if echo "$haystack" | grep -qF "$needle"; then
|
|
65
|
+
echo "FAIL: $desc"
|
|
66
|
+
echo " should NOT contain: $needle"
|
|
67
|
+
FAILURES=$((FAILURES + 1))
|
|
68
|
+
else
|
|
69
|
+
echo "PASS: $desc"
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Call at end of test file
|
|
74
|
+
report_results() {
|
|
75
|
+
echo ""
|
|
76
|
+
echo "Results: $((TESTS - FAILURES))/$TESTS passed"
|
|
77
|
+
if [[ $FAILURES -gt 0 ]]; then
|
|
78
|
+
echo "FAILURES: $FAILURES"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
echo "ALL PASSED"
|
|
82
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Test scripts/init.sh — project bootstrapper
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
7
|
+
INIT_SCRIPT="$REPO_ROOT/scripts/init.sh"
|
|
8
|
+
|
|
9
|
+
source "$SCRIPT_DIR/test-helpers.sh"
|
|
10
|
+
|
|
11
|
+
# --- Setup temp project ---
|
|
12
|
+
WORK=$(mktemp -d)
|
|
13
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
14
|
+
cd "$WORK"
|
|
15
|
+
git init -q
|
|
16
|
+
|
|
17
|
+
# --- Test 1: init creates tasks/ directory ---
|
|
18
|
+
bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || true
|
|
19
|
+
assert_eq "init creates tasks/ directory" "true" "$([ -d "$WORK/tasks" ] && echo true || echo false)"
|
|
20
|
+
|
|
21
|
+
# --- Test 2: init creates progress.txt ---
|
|
22
|
+
assert_eq "init creates progress.txt" "true" "$([ -f "$WORK/progress.txt" ] && echo true || echo false)"
|
|
23
|
+
|
|
24
|
+
# --- Test 3: init creates logs/ directory ---
|
|
25
|
+
assert_eq "init creates logs/ directory" "true" "$([ -d "$WORK/logs" ] && echo true || echo false)"
|
|
26
|
+
|
|
27
|
+
# --- Test 4: init detects project type ---
|
|
28
|
+
output=$(bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || true)
|
|
29
|
+
assert_contains "init detects project type" "Detected:" "$output"
|
|
30
|
+
|
|
31
|
+
# --- Test 5: init with --quickstart copies quickstart plan ---
|
|
32
|
+
mkdir -p "$WORK/docs/plans"
|
|
33
|
+
bash "$INIT_SCRIPT" --project-root "$WORK" --quickstart 2>&1 || true
|
|
34
|
+
assert_eq "quickstart creates plan file" "true" "$([ -f "$WORK/docs/plans/quickstart.md" ] && echo true || echo false)"
|
|
35
|
+
|
|
36
|
+
# --- Test 6: init fails without --project-root ---
|
|
37
|
+
exit_code=0
|
|
38
|
+
bash "$INIT_SCRIPT" 2>/dev/null || exit_code=$?
|
|
39
|
+
assert_eq "init fails without --project-root" "1" "$exit_code"
|
|
40
|
+
|
|
41
|
+
# --- Test 7: init is idempotent ---
|
|
42
|
+
bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || true
|
|
43
|
+
exit_code=0
|
|
44
|
+
bash "$INIT_SCRIPT" --project-root "$WORK" 2>&1 || exit_code=$?
|
|
45
|
+
assert_eq "init is idempotent (exit 0 on re-run)" "0" "$exit_code"
|
|
46
|
+
|
|
47
|
+
report_results
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Test lesson-check.sh — anti-pattern detector
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
7
|
+
LESSON_CHECK="$SCRIPT_DIR/../lesson-check.sh"
|
|
8
|
+
|
|
9
|
+
FAILURES=0
|
|
10
|
+
TESTS=0
|
|
11
|
+
|
|
12
|
+
pass() {
|
|
13
|
+
TESTS=$((TESTS + 1))
|
|
14
|
+
echo "PASS: $1"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fail() {
|
|
18
|
+
TESTS=$((TESTS + 1))
|
|
19
|
+
echo "FAIL: $1"
|
|
20
|
+
FAILURES=$((FAILURES + 1))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# --- Setup temp workspace ---
|
|
24
|
+
WORK=$(mktemp -d)
|
|
25
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
26
|
+
|
|
27
|
+
# --- Test 1: Detects bare except in Python file (lesson 1, uses \s) ---
|
|
28
|
+
cat > "$WORK/bad.py" <<'PY'
|
|
29
|
+
try:
|
|
30
|
+
do_something()
|
|
31
|
+
except:
|
|
32
|
+
pass
|
|
33
|
+
PY
|
|
34
|
+
|
|
35
|
+
# PROJECT_CLAUDE_MD=/dev/null isolates from toolkit's scope tags (language:bash would filter out python lessons)
|
|
36
|
+
output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad.py" 2>&1 || true)
|
|
37
|
+
if echo "$output" | grep -q '\[lesson-1\]'; then
|
|
38
|
+
pass "Detects bare except in Python file (lesson 1, \\s ERE conversion)"
|
|
39
|
+
else
|
|
40
|
+
fail "Should detect bare except in Python file, got: $output"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# --- Test 2: Clean file passes ---
|
|
44
|
+
cat > "$WORK/good.py" <<'PY'
|
|
45
|
+
try:
|
|
46
|
+
do_something()
|
|
47
|
+
except ValueError:
|
|
48
|
+
pass
|
|
49
|
+
PY
|
|
50
|
+
|
|
51
|
+
output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/good.py" 2>&1 || true)
|
|
52
|
+
if echo "$output" | grep -q 'clean'; then
|
|
53
|
+
pass "Clean file reports clean"
|
|
54
|
+
else
|
|
55
|
+
fail "Should report clean for good file, got: $output"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# --- Test 3: PCRE shorthand \d works via ERE conversion (lesson 28, hardcoded IP) ---
|
|
59
|
+
cat > "$WORK/bad_ip.js" <<'JS'
|
|
60
|
+
const url = "http://192.168.1.1/api";
|
|
61
|
+
JS
|
|
62
|
+
|
|
63
|
+
output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad_ip.js" 2>&1 || true)
|
|
64
|
+
if echo "$output" | grep -q '\[lesson-28\]'; then
|
|
65
|
+
pass "PCRE \\d converted to ERE [0-9] detects hardcoded IPs"
|
|
66
|
+
else
|
|
67
|
+
fail "Should detect hardcoded IP via lesson 28, got: $output"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# --- Test 4: Language filtering — Python lesson skips .sh files ---
|
|
71
|
+
cat > "$WORK/not_python.sh" <<'SH'
|
|
72
|
+
except:
|
|
73
|
+
SH
|
|
74
|
+
|
|
75
|
+
output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/not_python.sh" 2>&1 || true)
|
|
76
|
+
if echo "$output" | grep -q '\[lesson-1\]'; then
|
|
77
|
+
fail "Python-only lesson 1 should not match .sh files"
|
|
78
|
+
else
|
|
79
|
+
pass "Python-only lesson correctly skips .sh files"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# --- Test 5: --help works ---
|
|
83
|
+
output=$(bash "$LESSON_CHECK" --help 2>&1 || true)
|
|
84
|
+
if echo "$output" | grep -q 'Usage:'; then
|
|
85
|
+
pass "--help shows usage"
|
|
86
|
+
else
|
|
87
|
+
fail "--help should show usage, got: $output"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# --- Test 6: No files to check (no args, no pipe, no git diff) ---
|
|
91
|
+
output=$(cd "$WORK" && bash "$LESSON_CHECK" 2>&1 || true)
|
|
92
|
+
if echo "$output" | grep -q 'no files to check'; then
|
|
93
|
+
pass "No files gracefully reports nothing to check"
|
|
94
|
+
else
|
|
95
|
+
fail "Should report no files to check, got: $output"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# --- Test 6b: stdin pipe detection — uses -p /dev/stdin not -t 0 (#34) ---
|
|
99
|
+
# This prevents hanging when stdin is a socket (e.g. systemd/cron).
|
|
100
|
+
# The fix: only read stdin when [[ -p /dev/stdin ]] (a named pipe), not
|
|
101
|
+
# whenever [[ ! -t 0 ]] (which includes sockets that never send EOF).
|
|
102
|
+
TESTS=$((TESTS + 1))
|
|
103
|
+
if grep -q '\-p /dev/stdin' "$LESSON_CHECK"; then
|
|
104
|
+
echo "PASS: lesson-check uses -p /dev/stdin (pipe-safe, not socket-blocking)"
|
|
105
|
+
else
|
|
106
|
+
echo "FAIL: lesson-check should use [[ -p /dev/stdin ]] not [[ ! -t 0 ]] for stdin detection (bug #34)"
|
|
107
|
+
FAILURES=$((FAILURES + 1))
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Verify the old ! -t 0 pattern is NOT present in executable code (it caused the socket hang).
|
|
111
|
+
# Filter comment-only lines before checking.
|
|
112
|
+
TESTS=$((TESTS + 1))
|
|
113
|
+
if grep -v '^\s*#' "$LESSON_CHECK" | grep -q '! -t 0'; then
|
|
114
|
+
echo "FAIL: lesson-check still uses '! -t 0' in executable code, which blocks on socket stdin (bug #34)"
|
|
115
|
+
FAILURES=$((FAILURES + 1))
|
|
116
|
+
else
|
|
117
|
+
echo "PASS: lesson-check does not use '! -t 0' in executable code (socket-safe)"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# --- Test 7: No grep -P in any script (portability) ---
|
|
121
|
+
TESTS=$((TESTS + 1))
|
|
122
|
+
# Scan for grep -P or grep -<flags>P usage in scripts/ (excluding comments)
|
|
123
|
+
offenders=$(grep -rn 'grep -[a-zA-Z]*P' "$REPO_ROOT/scripts/" \
|
|
124
|
+
--include='*.sh' \
|
|
125
|
+
| grep -v 'test-lesson-check.sh' \
|
|
126
|
+
| grep -v ':#' \
|
|
127
|
+
| grep -v ':.*# .*grep' \
|
|
128
|
+
|| true)
|
|
129
|
+
if [[ -z "$offenders" ]]; then
|
|
130
|
+
echo "PASS: No grep -P found in scripts/ (portability)"
|
|
131
|
+
else
|
|
132
|
+
echo "FAIL: grep -P found in scripts/ — not portable to macOS:"
|
|
133
|
+
echo "$offenders"
|
|
134
|
+
FAILURES=$((FAILURES + 1))
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# --- Test 8: All scripts use #!/usr/bin/env bash shebang ---
|
|
138
|
+
TESTS=$((TESTS + 1))
|
|
139
|
+
bad_shebangs=""
|
|
140
|
+
while IFS= read -r script; do
|
|
141
|
+
first_line=$(head -1 "$script")
|
|
142
|
+
if [[ "$first_line" == "#!/bin/bash" ]]; then
|
|
143
|
+
bad_shebangs+=" $script"$'\n'
|
|
144
|
+
fi
|
|
145
|
+
done < <(find "$REPO_ROOT/scripts" -name '*.sh' -type f)
|
|
146
|
+
if [[ -z "$bad_shebangs" ]]; then
|
|
147
|
+
echo "PASS: All scripts use #!/usr/bin/env bash"
|
|
148
|
+
else
|
|
149
|
+
echo "FAIL: Scripts with non-portable #!/bin/bash shebang:"
|
|
150
|
+
echo "$bad_shebangs"
|
|
151
|
+
FAILURES=$((FAILURES + 1))
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# --- Test 9: Scope field parsed from lesson YAML ---
|
|
155
|
+
# Create a lesson with scope: [language:python] and verify it's respected
|
|
156
|
+
cat > "$WORK/0999-scoped-lesson.md" <<'LESSON'
|
|
157
|
+
---
|
|
158
|
+
id: 999
|
|
159
|
+
title: "Test scoped lesson"
|
|
160
|
+
severity: should-fix
|
|
161
|
+
scope: [language:python]
|
|
162
|
+
languages: [python]
|
|
163
|
+
category: silent-failures
|
|
164
|
+
pattern:
|
|
165
|
+
type: syntactic
|
|
166
|
+
regex: "test_scope_marker"
|
|
167
|
+
description: "test marker"
|
|
168
|
+
fix: "test"
|
|
169
|
+
---
|
|
170
|
+
LESSON
|
|
171
|
+
|
|
172
|
+
# Create a CLAUDE.md with scope tags
|
|
173
|
+
cat > "$WORK/CLAUDE.md" <<'CMD'
|
|
174
|
+
# Test Project
|
|
175
|
+
|
|
176
|
+
## Scope Tags
|
|
177
|
+
language:python, framework:pytest
|
|
178
|
+
CMD
|
|
179
|
+
|
|
180
|
+
# Python file with the marker — should be detected (scope matches)
|
|
181
|
+
cat > "$WORK/scoped.py" <<'PY'
|
|
182
|
+
test_scope_marker = True
|
|
183
|
+
PY
|
|
184
|
+
|
|
185
|
+
output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" "$WORK/scoped.py" 2>&1 || true)
|
|
186
|
+
if echo "$output" | grep -q '\[lesson-999\]'; then
|
|
187
|
+
pass "Scoped lesson detected when project scope matches"
|
|
188
|
+
else
|
|
189
|
+
fail "Scoped lesson should detect violation when project scope matches, got: $output"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
# --- Test 10: Scoped lesson skipped when project scope doesn't match ---
|
|
193
|
+
cat > "$WORK/CLAUDE-noscope.md" <<'CMD'
|
|
194
|
+
# Different Project
|
|
195
|
+
|
|
196
|
+
## Scope Tags
|
|
197
|
+
domain:ha-aria
|
|
198
|
+
CMD
|
|
199
|
+
|
|
200
|
+
output=$(cd "$WORK" && LESSONS_DIR="$WORK" PROJECT_CLAUDE_MD="$WORK/CLAUDE-noscope.md" bash "$LESSON_CHECK" "$WORK/scoped.py" 2>&1 || true)
|
|
201
|
+
if echo "$output" | grep -q '\[lesson-999\]'; then
|
|
202
|
+
fail "Scoped lesson should be SKIPPED when project scope doesn't match"
|
|
203
|
+
else
|
|
204
|
+
pass "Scoped lesson correctly skipped for non-matching project scope"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# --- Test 11: Lesson without scope defaults to universal (backward compat) ---
|
|
208
|
+
# Use the real lesson 1 (no scope field) — should still work as before
|
|
209
|
+
# Isolate from repo's own CLAUDE.md by pointing PROJECT_CLAUDE_MD to /dev/null
|
|
210
|
+
output=$(PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad.py" 2>&1 || true)
|
|
211
|
+
if echo "$output" | grep -q '\[lesson-1\]'; then
|
|
212
|
+
pass "Lesson without scope: field defaults to universal (backward compatible)"
|
|
213
|
+
else
|
|
214
|
+
fail "Missing scope: should default to universal, got: $output"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# --- Test 12: --show-scope displays detected project scope ---
|
|
218
|
+
output=$(cd "$WORK" && bash "$LESSON_CHECK" --show-scope 2>&1 || true)
|
|
219
|
+
if echo "$output" | grep -q 'language:python'; then
|
|
220
|
+
pass "--show-scope displays detected project scope"
|
|
221
|
+
else
|
|
222
|
+
fail "--show-scope should display detected scope from CLAUDE.md, got: $output"
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# --- Test 13: --all-scopes bypasses scope filtering ---
|
|
226
|
+
# Use a lesson scoped to domain:ha-aria on a python project — should be skipped normally
|
|
227
|
+
cat > "$WORK/0998-ha-lesson.md" <<'LESSON'
|
|
228
|
+
---
|
|
229
|
+
id: 998
|
|
230
|
+
title: "HA-only test lesson"
|
|
231
|
+
severity: should-fix
|
|
232
|
+
scope: [domain:ha-aria]
|
|
233
|
+
languages: [python]
|
|
234
|
+
category: silent-failures
|
|
235
|
+
pattern:
|
|
236
|
+
type: syntactic
|
|
237
|
+
regex: "ha_scope_marker"
|
|
238
|
+
description: "test marker"
|
|
239
|
+
fix: "test"
|
|
240
|
+
---
|
|
241
|
+
LESSON
|
|
242
|
+
|
|
243
|
+
cat > "$WORK/ha_file.py" <<'PY'
|
|
244
|
+
ha_scope_marker = True
|
|
245
|
+
PY
|
|
246
|
+
|
|
247
|
+
# Without --all-scopes: lesson 998 should be skipped (project is python, not ha-aria)
|
|
248
|
+
output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" "$WORK/ha_file.py" 2>&1 || true)
|
|
249
|
+
if echo "$output" | grep -q '\[lesson-998\]'; then
|
|
250
|
+
fail "domain:ha-aria lesson should be skipped on a python-only project"
|
|
251
|
+
else
|
|
252
|
+
pass "domain:ha-aria lesson correctly skipped on non-matching project"
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# With --all-scopes: lesson 998 should fire
|
|
256
|
+
output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" --all-scopes "$WORK/ha_file.py" 2>&1 || true)
|
|
257
|
+
if echo "$output" | grep -q '\[lesson-998\]'; then
|
|
258
|
+
pass "--all-scopes bypasses scope filtering"
|
|
259
|
+
else
|
|
260
|
+
fail "--all-scopes should bypass scope filtering, got: $output"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# --- Test 14: --scope override replaces CLAUDE.md detection ---
|
|
264
|
+
output=$(cd "$WORK" && LESSONS_DIR="$WORK" bash "$LESSON_CHECK" --scope "domain:ha-aria" "$WORK/ha_file.py" 2>&1 || true)
|
|
265
|
+
if echo "$output" | grep -q '\[lesson-998\]'; then
|
|
266
|
+
pass "--scope override enables matching for specified scope"
|
|
267
|
+
else
|
|
268
|
+
fail "--scope override should enable domain:ha-aria matching, got: $output"
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
# --- Summary ---
|
|
272
|
+
echo ""
|
|
273
|
+
echo "lesson-check tests: $TESTS run, $((TESTS - FAILURES)) passed, $FAILURES failed"
|
|
274
|
+
|
|
275
|
+
if [[ $FAILURES -gt 0 ]]; then
|
|
276
|
+
exit 1
|
|
277
|
+
fi
|
|
278
|
+
exit 0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Test lesson-check.sh — project-local lesson loading (Tier 3)
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
7
|
+
LESSON_CHECK="$REPO_ROOT/scripts/lesson-check.sh"
|
|
8
|
+
|
|
9
|
+
source "$SCRIPT_DIR/test-helpers.sh"
|
|
10
|
+
|
|
11
|
+
# --- Setup: project with local lessons ---
|
|
12
|
+
WORK=$(mktemp -d)
|
|
13
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
14
|
+
|
|
15
|
+
# Create a project-local lesson
|
|
16
|
+
mkdir -p "$WORK/docs/lessons"
|
|
17
|
+
cat > "$WORK/docs/lessons/9901-local-test.md" <<'LESSON'
|
|
18
|
+
---
|
|
19
|
+
id: 9901
|
|
20
|
+
title: "Test local lesson"
|
|
21
|
+
severity: error
|
|
22
|
+
languages: [python]
|
|
23
|
+
scope: [universal]
|
|
24
|
+
category: testing
|
|
25
|
+
pattern:
|
|
26
|
+
type: syntactic
|
|
27
|
+
regex: "LOCALTEST_BAD_PATTERN"
|
|
28
|
+
fix: "Use LOCALTEST_GOOD_PATTERN instead"
|
|
29
|
+
positive_alternative: "LOCALTEST_GOOD_PATTERN"
|
|
30
|
+
---
|
|
31
|
+
LESSON
|
|
32
|
+
|
|
33
|
+
# Create a file that triggers the local lesson
|
|
34
|
+
cat > "$WORK/bad.py" <<'PY'
|
|
35
|
+
x = LOCALTEST_BAD_PATTERN
|
|
36
|
+
PY
|
|
37
|
+
|
|
38
|
+
# --- Test: project-local lesson is loaded ---
|
|
39
|
+
output=$(PROJECT_ROOT="$WORK" PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/bad.py" 2>&1 || true)
|
|
40
|
+
if echo "$output" | grep -q 'lesson-9901'; then
|
|
41
|
+
pass "Project-local lesson detected violation"
|
|
42
|
+
else
|
|
43
|
+
fail "Project-local lesson not loaded, got: $output"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# --- Test: clean file passes with local lessons ---
|
|
47
|
+
cat > "$WORK/good.py" <<'PY'
|
|
48
|
+
x = LOCALTEST_GOOD_PATTERN
|
|
49
|
+
PY
|
|
50
|
+
|
|
51
|
+
exit_code=0
|
|
52
|
+
PROJECT_ROOT="$WORK" PROJECT_CLAUDE_MD="/dev/null" bash "$LESSON_CHECK" "$WORK/good.py" 2>/dev/null || exit_code=$?
|
|
53
|
+
assert_eq "Clean file passes with local lessons" "0" "$exit_code"
|
|
54
|
+
|
|
55
|
+
report_results
|