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,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# generate-ast-rules.sh — Generate ast-grep rules from lesson YAML frontmatter
|
|
3
|
+
#
|
|
4
|
+
# Reads lesson files with pattern.type: semantic and supported languages,
|
|
5
|
+
# generates ast-grep YAML rule files in the output directory.
|
|
6
|
+
# Syntactic patterns are skipped (grep handles them via lesson-check.sh).
|
|
7
|
+
#
|
|
8
|
+
# Usage: generate-ast-rules.sh --lessons-dir <dir> [--output-dir <dir>] [--list]
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
LESSONS_DIR=""
|
|
13
|
+
OUTPUT_DIR=""
|
|
14
|
+
LIST_ONLY=false
|
|
15
|
+
|
|
16
|
+
while [[ $# -gt 0 ]]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--lessons-dir) LESSONS_DIR="$2"; shift 2 ;;
|
|
19
|
+
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
|
|
20
|
+
--list) LIST_ONLY=true; shift ;;
|
|
21
|
+
-h|--help)
|
|
22
|
+
echo "Usage: generate-ast-rules.sh --lessons-dir <dir> [--output-dir <dir>] [--list]"
|
|
23
|
+
exit 0 ;;
|
|
24
|
+
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
if [[ -z "$LESSONS_DIR" ]]; then
|
|
29
|
+
echo "ERROR: --lessons-dir required" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Default output directory to scripts/patterns/ (where existing patterns live)
|
|
34
|
+
if [[ -z "$OUTPUT_DIR" ]]; then
|
|
35
|
+
OUTPUT_DIR="$SCRIPT_DIR/patterns"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
generated=0
|
|
39
|
+
skipped_syntactic=0
|
|
40
|
+
skipped_unconvertible=0
|
|
41
|
+
|
|
42
|
+
for lesson_file in "$LESSONS_DIR"/*.md; do
|
|
43
|
+
[[ -f "$lesson_file" ]] || continue
|
|
44
|
+
base=$(basename "$lesson_file")
|
|
45
|
+
[[ "$base" == "TEMPLATE.md" || "$base" == "SUMMARY.md" || "$base" == "FRAMEWORK.md" ]] && continue
|
|
46
|
+
|
|
47
|
+
# Extract frontmatter fields via sed
|
|
48
|
+
local_id=$(sed -n '/^---$/,/^---$/{/^id:/s/^id: *//p}' "$lesson_file" | head -1)
|
|
49
|
+
local_type=$(sed -n '/^---$/,/^---$/{/^ type:/s/^ type: *//p}' "$lesson_file" | head -1)
|
|
50
|
+
local_title=$(sed -n '/^---$/,/^---$/{/^title:/s/^title: *"*//p}' "$lesson_file" | head -1 | sed 's/"$//')
|
|
51
|
+
local_langs=$(sed -n '/^---$/,/^---$/{/^languages:/s/^languages: *//p}' "$lesson_file" | head -1)
|
|
52
|
+
|
|
53
|
+
# Skip syntactic patterns (grep handles these)
|
|
54
|
+
if [[ "$local_type" == "syntactic" ]]; then
|
|
55
|
+
skipped_syntactic=$((skipped_syntactic + 1))
|
|
56
|
+
continue
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Only generate for languages ast-grep supports
|
|
60
|
+
if [[ "$local_langs" != *"python"* && "$local_langs" != *"javascript"* && "$local_langs" != *"typescript"* ]]; then
|
|
61
|
+
skipped_unconvertible=$((skipped_unconvertible + 1))
|
|
62
|
+
continue
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
local_basename=$(basename "$lesson_file" .md)
|
|
66
|
+
|
|
67
|
+
if [[ "$LIST_ONLY" == true ]]; then
|
|
68
|
+
echo " Would generate: $local_basename.yml (lesson $local_id: $local_title)"
|
|
69
|
+
generated=$((generated + 1))
|
|
70
|
+
continue
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
mkdir -p "$OUTPUT_DIR"
|
|
74
|
+
|
|
75
|
+
# Determine primary language
|
|
76
|
+
local_lang=$(echo "$local_langs" | sed 's/\[//;s/\]//;s/,.*//;s/ //g')
|
|
77
|
+
|
|
78
|
+
# Generate ast-grep rule YAML
|
|
79
|
+
cat > "$OUTPUT_DIR/$local_basename.yml" << RULE
|
|
80
|
+
id: $local_basename
|
|
81
|
+
message: "$local_title"
|
|
82
|
+
severity: warning
|
|
83
|
+
language: $local_lang
|
|
84
|
+
note: "Auto-generated from lesson $local_id. See docs/lessons/$local_basename.md"
|
|
85
|
+
RULE
|
|
86
|
+
|
|
87
|
+
generated=$((generated + 1))
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
if [[ "$LIST_ONLY" == true ]]; then
|
|
91
|
+
echo ""
|
|
92
|
+
echo "Summary: $generated convertible, $skipped_syntactic syntactic (grep), $skipped_unconvertible unsupported language"
|
|
93
|
+
else
|
|
94
|
+
echo "Generated $generated ast-grep rules in $OUTPUT_DIR"
|
|
95
|
+
echo "Skipped: $skipped_syntactic syntactic (grep handles), $skipped_unconvertible unsupported language"
|
|
96
|
+
fi
|
package/scripts/init.sh
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# init.sh — Bootstrap a project for use with the Autonomous Coding Toolkit
|
|
3
|
+
#
|
|
4
|
+
# Usage: init.sh --project-root <dir> [--quickstart]
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
|
8
|
+
TOOLKIT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/lib/common.sh"
|
|
10
|
+
|
|
11
|
+
PROJECT_ROOT=""
|
|
12
|
+
QUICKSTART=false
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat <<'USAGE'
|
|
16
|
+
Usage: init.sh --project-root <dir> [--quickstart]
|
|
17
|
+
|
|
18
|
+
Bootstrap a project for the Autonomous Coding Toolkit.
|
|
19
|
+
|
|
20
|
+
Creates:
|
|
21
|
+
tasks/ — PRD and acceptance criteria
|
|
22
|
+
logs/ — Telemetry, routing decisions, failure patterns
|
|
23
|
+
progress.txt — Append-only discovery log
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--project-root <dir> Project directory to initialize (required)
|
|
27
|
+
--quickstart Copy quickstart plan + run quality gate
|
|
28
|
+
--help, -h Show this help
|
|
29
|
+
USAGE
|
|
30
|
+
exit 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case "$1" in
|
|
35
|
+
--project-root) PROJECT_ROOT="${2:-}"; shift 2 ;;
|
|
36
|
+
--quickstart) QUICKSTART=true; shift ;;
|
|
37
|
+
--help|-h) usage ;;
|
|
38
|
+
*) echo "init: unknown option: $1" >&2; exit 1 ;;
|
|
39
|
+
esac
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
43
|
+
echo "init: --project-root is required" >&2
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
|
|
48
|
+
|
|
49
|
+
echo "Autonomous Coding Toolkit — Project Init"
|
|
50
|
+
echo "========================================="
|
|
51
|
+
echo ""
|
|
52
|
+
|
|
53
|
+
# Detect project type
|
|
54
|
+
project_type=$(detect_project_type "$PROJECT_ROOT")
|
|
55
|
+
echo "Detected: $project_type project"
|
|
56
|
+
|
|
57
|
+
# Create directories
|
|
58
|
+
mkdir -p "$PROJECT_ROOT/tasks"
|
|
59
|
+
mkdir -p "$PROJECT_ROOT/logs"
|
|
60
|
+
mkdir -p "$PROJECT_ROOT/docs/plans"
|
|
61
|
+
echo "Created: tasks/, logs/, docs/plans/"
|
|
62
|
+
|
|
63
|
+
# Create progress.txt if missing
|
|
64
|
+
if [[ ! -f "$PROJECT_ROOT/progress.txt" ]]; then
|
|
65
|
+
echo "# Progress — $(basename "$PROJECT_ROOT")" > "$PROJECT_ROOT/progress.txt"
|
|
66
|
+
echo "# Append-only discovery log. Read at start of each batch." >> "$PROJECT_ROOT/progress.txt"
|
|
67
|
+
echo "" >> "$PROJECT_ROOT/progress.txt"
|
|
68
|
+
echo "Created: progress.txt"
|
|
69
|
+
else
|
|
70
|
+
echo "Exists: progress.txt (skipped)"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Detect language for scope tags
|
|
74
|
+
scope_lang=""
|
|
75
|
+
case "$project_type" in
|
|
76
|
+
python) scope_lang="language:python" ;;
|
|
77
|
+
node) scope_lang="language:javascript" ;;
|
|
78
|
+
bash) scope_lang="language:bash" ;;
|
|
79
|
+
*) scope_lang="" ;;
|
|
80
|
+
esac
|
|
81
|
+
|
|
82
|
+
# Print next steps
|
|
83
|
+
echo ""
|
|
84
|
+
echo "--- Next Steps ---"
|
|
85
|
+
echo ""
|
|
86
|
+
echo "1. Quality gate: act gate --project-root \"$PROJECT_ROOT\""
|
|
87
|
+
echo "2. Run a plan: act plan docs/plans/your-plan.md"
|
|
88
|
+
|
|
89
|
+
if [[ -n "$scope_lang" ]]; then
|
|
90
|
+
echo ""
|
|
91
|
+
echo "Recommended: Add to your CLAUDE.md:"
|
|
92
|
+
echo " ## Scope Tags"
|
|
93
|
+
echo " $scope_lang"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Quickstart mode
|
|
97
|
+
if [[ "$QUICKSTART" == true ]]; then
|
|
98
|
+
echo ""
|
|
99
|
+
echo "--- Quickstart ---"
|
|
100
|
+
if [[ -f "$TOOLKIT_ROOT/examples/quickstart-plan.md" ]]; then
|
|
101
|
+
cp "$TOOLKIT_ROOT/examples/quickstart-plan.md" "$PROJECT_ROOT/docs/plans/quickstart.md"
|
|
102
|
+
echo "Copied: docs/plans/quickstart.md"
|
|
103
|
+
echo ""
|
|
104
|
+
echo "Run your first quality-gated execution:"
|
|
105
|
+
echo " act plan docs/plans/quickstart.md"
|
|
106
|
+
else
|
|
107
|
+
echo "WARNING: quickstart-plan.md not found in toolkit" >&2
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo ""
|
|
112
|
+
echo "Init complete."
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# lesson-check.sh — Syntactic anti-pattern detector from lessons learned
|
|
3
|
+
# Dynamically loads checks from docs/lessons/[0-9]*.md (syntactic pattern.type only).
|
|
4
|
+
# Exit 0 if clean, exit 1 with file:line: [lesson-N] format if violations found.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
|
8
|
+
LESSONS_DIR="${LESSONS_DIR:-$SCRIPT_DIR/../docs/lessons}"
|
|
9
|
+
|
|
10
|
+
# Project-local lessons (Tier 3) — loaded alongside bundled lessons.
|
|
11
|
+
# Set PROJECT_ROOT to the project being checked for project-specific anti-patterns.
|
|
12
|
+
PROJECT_LESSONS_DIR=""
|
|
13
|
+
if [[ -n "${PROJECT_ROOT:-}" && -d "${PROJECT_ROOT}/docs/lessons" ]]; then
|
|
14
|
+
_canonical_bundled="$(cd "$LESSONS_DIR" 2>/dev/null && pwd)"
|
|
15
|
+
_canonical_project="$(cd "${PROJECT_ROOT}/docs/lessons" 2>/dev/null && pwd)"
|
|
16
|
+
if [[ "$_canonical_project" != "$_canonical_bundled" ]]; then
|
|
17
|
+
PROJECT_LESSONS_DIR="${PROJECT_ROOT}/docs/lessons"
|
|
18
|
+
fi
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# parse_lesson <file>
|
|
23
|
+
# shellcheck disable=SC2034 # lesson_severity, lesson_scope parsed for future filtering
|
|
24
|
+
# Sets: lesson_id, lesson_title, lesson_severity, pattern_type, pattern_regex,
|
|
25
|
+
# lesson_languages (space-separated list), lesson_scope (space-separated tags)
|
|
26
|
+
# Returns 1 if the lesson cannot be parsed or has no syntactic pattern.
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
parse_lesson() {
|
|
29
|
+
local file="$1"
|
|
30
|
+
lesson_id=""
|
|
31
|
+
lesson_title=""
|
|
32
|
+
lesson_severity=""
|
|
33
|
+
pattern_type=""
|
|
34
|
+
pattern_regex=""
|
|
35
|
+
lesson_languages=""
|
|
36
|
+
lesson_scope=""
|
|
37
|
+
|
|
38
|
+
# Parse YAML frontmatter with sed + read (no eval — safe with special chars).
|
|
39
|
+
# Extract text between first two --- delimiters, then parse key: value lines.
|
|
40
|
+
local in_pattern=false
|
|
41
|
+
local line
|
|
42
|
+
while IFS= read -r line; do
|
|
43
|
+
# Detect entry/exit of pattern: block
|
|
44
|
+
if [[ "$line" =~ ^pattern: ]]; then
|
|
45
|
+
in_pattern=true
|
|
46
|
+
continue
|
|
47
|
+
fi
|
|
48
|
+
if [[ "$in_pattern" == true && "$line" =~ ^[^[:space:]] && ! "$line" =~ ^pattern: ]]; then
|
|
49
|
+
in_pattern=false
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [[ "$in_pattern" == false ]]; then
|
|
53
|
+
# Top-level fields
|
|
54
|
+
if [[ "$line" =~ ^id:[[:space:]]+(.*) ]]; then
|
|
55
|
+
lesson_id="${BASH_REMATCH[1]}"
|
|
56
|
+
elif [[ "$line" =~ ^title:[[:space:]]+(.*) ]]; then
|
|
57
|
+
lesson_title="${BASH_REMATCH[1]}"
|
|
58
|
+
lesson_title="${lesson_title#\"}"
|
|
59
|
+
lesson_title="${lesson_title%\"}"
|
|
60
|
+
lesson_title="${lesson_title#\'}"
|
|
61
|
+
lesson_title="${lesson_title%\'}"
|
|
62
|
+
elif [[ "$line" =~ ^severity:[[:space:]]+(.*) ]]; then
|
|
63
|
+
lesson_severity="${BASH_REMATCH[1]}"
|
|
64
|
+
elif [[ "$line" =~ ^languages:[[:space:]]+(.*) ]]; then
|
|
65
|
+
lesson_languages="${BASH_REMATCH[1]}"
|
|
66
|
+
lesson_languages="${lesson_languages//[\[\]]/}"
|
|
67
|
+
lesson_languages="${lesson_languages//,/ }"
|
|
68
|
+
lesson_languages="${lesson_languages## }"
|
|
69
|
+
lesson_languages="${lesson_languages%% }"
|
|
70
|
+
elif [[ "$line" =~ ^scope:[[:space:]]+(.*) ]]; then
|
|
71
|
+
lesson_scope="${BASH_REMATCH[1]}"
|
|
72
|
+
lesson_scope="${lesson_scope//[\[\]]/}"
|
|
73
|
+
lesson_scope="${lesson_scope//,/ }"
|
|
74
|
+
lesson_scope="${lesson_scope## }"
|
|
75
|
+
lesson_scope="${lesson_scope%% }"
|
|
76
|
+
fi
|
|
77
|
+
else
|
|
78
|
+
# Nested pattern: fields (indented)
|
|
79
|
+
if [[ "$line" =~ ^[[:space:]]+type:[[:space:]]+(.*) ]]; then
|
|
80
|
+
pattern_type="${BASH_REMATCH[1]}"
|
|
81
|
+
elif [[ "$line" =~ ^[[:space:]]+regex:[[:space:]]+(.*) ]]; then
|
|
82
|
+
pattern_regex="${BASH_REMATCH[1]}"
|
|
83
|
+
pattern_regex="${pattern_regex#\"}"
|
|
84
|
+
pattern_regex="${pattern_regex%\"}"
|
|
85
|
+
pattern_regex="${pattern_regex#\'}"
|
|
86
|
+
pattern_regex="${pattern_regex%\'}"
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
done < <(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$file" 2>/dev/null)
|
|
90
|
+
|
|
91
|
+
# Unescape YAML double-escaped backslashes: \\s → \s, \\d → \d, etc.
|
|
92
|
+
pattern_regex="${pattern_regex//\\\\/\\}"
|
|
93
|
+
|
|
94
|
+
[[ -z "$pattern_type" || "$pattern_type" != "syntactic" ]] && return 1
|
|
95
|
+
[[ -z "$pattern_regex" ]] && return 1
|
|
96
|
+
|
|
97
|
+
# Default scope to universal when omitted (backward compatible)
|
|
98
|
+
[[ -z "$lesson_scope" ]] && lesson_scope="universal"
|
|
99
|
+
|
|
100
|
+
# Convert PCRE shorthand classes to POSIX ERE equivalents for grep -E portability.
|
|
101
|
+
# This lets lesson authors use \s, \d, \w, \b in regex: fields while keeping
|
|
102
|
+
# the scanner portable (grep -P is unavailable on macOS).
|
|
103
|
+
pattern_regex="${pattern_regex//\\d/[0-9]}"
|
|
104
|
+
pattern_regex="${pattern_regex//\\s/[[:space:]]}"
|
|
105
|
+
pattern_regex="${pattern_regex//\\w/[_[:alnum:]]}"
|
|
106
|
+
pattern_regex="${pattern_regex//\\b/\\b}" # no-op: \b passes through unchanged; GNU grep -E supports \b as word boundary (not BSD/macOS)
|
|
107
|
+
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Build the --help text dynamically from lesson files
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
build_help() {
|
|
115
|
+
local checks_text=""
|
|
116
|
+
local lfile
|
|
117
|
+
for lfile in "$LESSONS_DIR"/[0-9]*.md; do
|
|
118
|
+
[[ -f "$lfile" ]] || continue
|
|
119
|
+
if parse_lesson "$lfile"; then
|
|
120
|
+
local lang_display="$lesson_languages"
|
|
121
|
+
[[ "$lang_display" == "all" ]] && lang_display="all files"
|
|
122
|
+
local scope_display="$lesson_scope"
|
|
123
|
+
checks_text+=" [lesson-${lesson_id}] ${lesson_title} (${lang_display}) [scope: ${scope_display}]"$'\n'
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
cat <<USAGE
|
|
128
|
+
Usage: lesson-check.sh [OPTIONS] [file ...]
|
|
129
|
+
Check files for known anti-patterns from lessons learned.
|
|
130
|
+
Files can be passed as arguments or piped via stdin (one per line).
|
|
131
|
+
If neither, defaults to git diff --name-only in current directory.
|
|
132
|
+
|
|
133
|
+
Options:
|
|
134
|
+
--help, -h Show this help
|
|
135
|
+
--all-scopes Bypass scope filtering (check all lessons regardless of project)
|
|
136
|
+
--show-scope Display detected project scope and exit
|
|
137
|
+
--scope <tags> Override project scope (comma-separated, e.g. "language:python,domain:ha-aria")
|
|
138
|
+
|
|
139
|
+
Checks (syntactic only — loaded from ${LESSONS_DIR}):
|
|
140
|
+
${checks_text}
|
|
141
|
+
Output: file:line: [lesson-N] description
|
|
142
|
+
Exit: 0 if clean, 1 if violations found
|
|
143
|
+
USAGE
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
# detect_project_scope [claude_md_path]
|
|
148
|
+
# Reads ## Scope Tags from CLAUDE.md. Falls back to detect_project_type().
|
|
149
|
+
# Sets global: project_scope (space-separated tags)
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
detect_project_scope() {
|
|
152
|
+
local claude_md="${1:-}"
|
|
153
|
+
project_scope=""
|
|
154
|
+
|
|
155
|
+
# Try explicit path first, then search current directory upward
|
|
156
|
+
if [[ -z "$claude_md" ]]; then
|
|
157
|
+
claude_md="CLAUDE.md"
|
|
158
|
+
# Walk up to find CLAUDE.md (max 5 levels)
|
|
159
|
+
local search_dir="$PWD"
|
|
160
|
+
for _ in 1 2 3 4 5; do
|
|
161
|
+
if [[ -f "$search_dir/CLAUDE.md" ]]; then
|
|
162
|
+
claude_md="$search_dir/CLAUDE.md"
|
|
163
|
+
break
|
|
164
|
+
fi
|
|
165
|
+
search_dir="$(dirname "$search_dir")"
|
|
166
|
+
done
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Parse ## Scope Tags section from CLAUDE.md
|
|
170
|
+
if [[ -f "$claude_md" ]]; then
|
|
171
|
+
local in_scope_section=false
|
|
172
|
+
local line
|
|
173
|
+
while IFS= read -r line; do
|
|
174
|
+
if [[ "$line" =~ ^##[[:space:]]+Scope[[:space:]]+Tags ]]; then
|
|
175
|
+
in_scope_section=true
|
|
176
|
+
continue
|
|
177
|
+
fi
|
|
178
|
+
if [[ "$in_scope_section" == true ]]; then
|
|
179
|
+
# Stop at next heading
|
|
180
|
+
if [[ "$line" =~ ^## ]]; then
|
|
181
|
+
break
|
|
182
|
+
fi
|
|
183
|
+
# Skip empty lines
|
|
184
|
+
[[ -z "${line// /}" ]] && continue
|
|
185
|
+
# Parse comma-separated tags
|
|
186
|
+
local tag
|
|
187
|
+
for tag in ${line//,/ }; do
|
|
188
|
+
tag="${tag## }"
|
|
189
|
+
tag="${tag%% }"
|
|
190
|
+
[[ -n "$tag" ]] && project_scope+="$tag "
|
|
191
|
+
done
|
|
192
|
+
fi
|
|
193
|
+
done < "$claude_md"
|
|
194
|
+
project_scope="${project_scope%% }"
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Fallback: detect project type → language tag
|
|
198
|
+
if [[ -z "$project_scope" ]]; then
|
|
199
|
+
source "$SCRIPT_DIR/lib/common.sh" 2>/dev/null || true
|
|
200
|
+
if type detect_project_type &>/dev/null; then
|
|
201
|
+
local ptype
|
|
202
|
+
ptype=$(detect_project_type "$PWD")
|
|
203
|
+
case "$ptype" in
|
|
204
|
+
python) project_scope="language:python" ;;
|
|
205
|
+
node) project_scope="language:javascript" ;;
|
|
206
|
+
bash) project_scope="language:bash" ;;
|
|
207
|
+
*) project_scope="" ;;
|
|
208
|
+
esac
|
|
209
|
+
fi
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# If still empty, everything matches (universal behavior)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
# scope_matches <lesson_scope> <project_scope>
|
|
217
|
+
# Returns 0 if lesson should run on this project, 1 if it should be skipped.
|
|
218
|
+
# A lesson matches if ANY of its scope tags intersects the project's scope set,
|
|
219
|
+
# or if the lesson scope includes "universal".
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
scope_matches() {
|
|
222
|
+
local l_scope="$1" # space-separated lesson scope tags
|
|
223
|
+
local p_scope="$2" # space-separated project scope tags
|
|
224
|
+
|
|
225
|
+
# universal matches everything
|
|
226
|
+
local tag
|
|
227
|
+
for tag in $l_scope; do
|
|
228
|
+
[[ "$tag" == "universal" ]] && return 0
|
|
229
|
+
done
|
|
230
|
+
|
|
231
|
+
# If project has no scope, everything matches (backward compat)
|
|
232
|
+
[[ -z "$p_scope" ]] && return 0
|
|
233
|
+
|
|
234
|
+
# Check intersection
|
|
235
|
+
local ltag ptag
|
|
236
|
+
for ltag in $l_scope; do
|
|
237
|
+
for ptag in $p_scope; do
|
|
238
|
+
[[ "$ltag" == "$ptag" ]] && return 0
|
|
239
|
+
done
|
|
240
|
+
done
|
|
241
|
+
|
|
242
|
+
return 1
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# CLI flag parsing
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
ALL_SCOPES=false
|
|
249
|
+
SHOW_SCOPE=false
|
|
250
|
+
SCOPE_OVERRIDE=""
|
|
251
|
+
|
|
252
|
+
# Parse flags before file arguments
|
|
253
|
+
args=()
|
|
254
|
+
while [[ $# -gt 0 ]]; do
|
|
255
|
+
case "$1" in
|
|
256
|
+
--help|-h) build_help; exit 0 ;;
|
|
257
|
+
--all-scopes) ALL_SCOPES=true; shift ;;
|
|
258
|
+
--show-scope) SHOW_SCOPE=true; shift ;;
|
|
259
|
+
--scope)
|
|
260
|
+
[[ -z "${2:-}" ]] && { echo "lesson-check: --scope requires an argument" >&2; exit 1; }
|
|
261
|
+
SCOPE_OVERRIDE="$2"; shift 2 ;;
|
|
262
|
+
*) args+=("$1"); shift ;;
|
|
263
|
+
esac
|
|
264
|
+
done
|
|
265
|
+
set -- "${args[@]+"${args[@]}"}"
|
|
266
|
+
|
|
267
|
+
# Handle --show-scope early (no files needed)
|
|
268
|
+
if [[ "$SHOW_SCOPE" == true ]]; then
|
|
269
|
+
project_scope=""
|
|
270
|
+
if [[ -n "$SCOPE_OVERRIDE" ]]; then
|
|
271
|
+
project_scope="${SCOPE_OVERRIDE//,/ }"
|
|
272
|
+
else
|
|
273
|
+
detect_project_scope "${PROJECT_CLAUDE_MD:-}"
|
|
274
|
+
fi
|
|
275
|
+
if [[ -n "$project_scope" ]]; then
|
|
276
|
+
echo "Detected project scope: $project_scope"
|
|
277
|
+
else
|
|
278
|
+
echo "No project scope detected (all lessons will apply)"
|
|
279
|
+
fi
|
|
280
|
+
exit 0
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
violations=0
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# Gather file list: args → stdin pipe → git diff fallback
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
files=()
|
|
289
|
+
if [[ $# -gt 0 ]]; then
|
|
290
|
+
files=("$@")
|
|
291
|
+
elif [[ -p /dev/stdin ]]; then
|
|
292
|
+
# stdin is a named pipe (shell pipe) — safe to read without blocking.
|
|
293
|
+
# Using [[ -p /dev/stdin ]] instead of [[ ! -t 0 ]] avoids hanging when
|
|
294
|
+
# stdin is a socket (e.g. systemd/cron), which satisfies ! -t 0 but
|
|
295
|
+
# never sends EOF (#34). A socket is not a pipe, so -p /dev/stdin is false
|
|
296
|
+
# and we fall through to the git diff fallback instead of blocking forever.
|
|
297
|
+
while IFS= read -r f; do
|
|
298
|
+
[[ -n "$f" ]] && files+=("$f")
|
|
299
|
+
done
|
|
300
|
+
else
|
|
301
|
+
while IFS= read -r f; do
|
|
302
|
+
[[ -n "$f" ]] && files+=("$f")
|
|
303
|
+
done < <(git diff --name-only 2>/dev/null || true)
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
if [[ ${#files[@]} -eq 0 ]]; then
|
|
307
|
+
echo "lesson-check: no files to check" >&2
|
|
308
|
+
exit 0
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
# Pre-filter: only keep files that actually exist on disk
|
|
312
|
+
existing_files=()
|
|
313
|
+
for f in "${files[@]}"; do
|
|
314
|
+
[[ -f "$f" ]] && existing_files+=("$f")
|
|
315
|
+
done
|
|
316
|
+
|
|
317
|
+
if [[ ${#existing_files[@]} -eq 0 ]]; then
|
|
318
|
+
echo "lesson-check: no files to check" >&2
|
|
319
|
+
exit 0
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# ---------------------------------------------------------------------------
|
|
323
|
+
# Detect project scope (unless --all-scopes)
|
|
324
|
+
# ---------------------------------------------------------------------------
|
|
325
|
+
project_scope=""
|
|
326
|
+
if [[ "$ALL_SCOPES" == false ]]; then
|
|
327
|
+
if [[ -n "$SCOPE_OVERRIDE" ]]; then
|
|
328
|
+
project_scope="${SCOPE_OVERRIDE//,/ }"
|
|
329
|
+
else
|
|
330
|
+
detect_project_scope "${PROJECT_CLAUDE_MD:-}"
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
# Language → file extension mapping
|
|
336
|
+
# ---------------------------------------------------------------------------
|
|
337
|
+
# Returns 1 (mismatch) if the file doesn't match the lesson's languages.
|
|
338
|
+
file_matches_languages() {
|
|
339
|
+
local filepath="$1"
|
|
340
|
+
local languages="$2" # space-separated
|
|
341
|
+
|
|
342
|
+
# "all" matches everything
|
|
343
|
+
[[ "$languages" == "all" ]] && return 0
|
|
344
|
+
|
|
345
|
+
local lang
|
|
346
|
+
for lang in $languages; do
|
|
347
|
+
case "$lang" in
|
|
348
|
+
python) [[ "$filepath" == *.py ]] && return 0 ;;
|
|
349
|
+
javascript) [[ "$filepath" == *.js ]] && return 0 ;;
|
|
350
|
+
typescript) [[ "$filepath" == *.ts ]] && return 0 ;;
|
|
351
|
+
shell) [[ "$filepath" == *.sh ]] && return 0 ;;
|
|
352
|
+
esac
|
|
353
|
+
done
|
|
354
|
+
return 1
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# ---------------------------------------------------------------------------
|
|
358
|
+
# Main loop: iterate lesson files, run syntactic checks
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
lfile=""
|
|
361
|
+
for lfile in "$LESSONS_DIR"/[0-9]*.md; do
|
|
362
|
+
[[ -f "$lfile" ]] || continue
|
|
363
|
+
parse_lesson "$lfile" || continue
|
|
364
|
+
|
|
365
|
+
# Scope filtering: skip lessons that don't match this project
|
|
366
|
+
if [[ "$ALL_SCOPES" == false ]]; then
|
|
367
|
+
scope_matches "$lesson_scope" "$project_scope" || continue
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
# Build list of target files that match this lesson's languages
|
|
371
|
+
target_files=()
|
|
372
|
+
local_f=""
|
|
373
|
+
for local_f in "${existing_files[@]}"; do
|
|
374
|
+
file_matches_languages "$local_f" "$lesson_languages" && target_files+=("$local_f")
|
|
375
|
+
done
|
|
376
|
+
[[ ${#target_files[@]} -eq 0 ]] && continue
|
|
377
|
+
|
|
378
|
+
# Run grep against matching files; format output as file:line: [lesson-N] title
|
|
379
|
+
local_id="$lesson_id"
|
|
380
|
+
local_title="$lesson_title"
|
|
381
|
+
while IFS=: read -r matched_file lineno _rest; do
|
|
382
|
+
[[ -z "$matched_file" ]] && continue
|
|
383
|
+
echo "${matched_file}:${lineno}: [lesson-${local_id}] ${local_title}"
|
|
384
|
+
((violations++)) || true
|
|
385
|
+
done < <(grep -EHn "$pattern_regex" "${target_files[@]}" 2>/dev/null || true)
|
|
386
|
+
done
|
|
387
|
+
|
|
388
|
+
# Load project-local lessons (Tier 3)
|
|
389
|
+
if [[ -n "$PROJECT_LESSONS_DIR" ]]; then
|
|
390
|
+
for lfile in "$PROJECT_LESSONS_DIR"/[0-9]*.md; do
|
|
391
|
+
[[ -f "$lfile" ]] || continue
|
|
392
|
+
parse_lesson "$lfile" || continue
|
|
393
|
+
|
|
394
|
+
# Scope filtering: skip lessons that don't match this project
|
|
395
|
+
if [[ "$ALL_SCOPES" == false ]]; then
|
|
396
|
+
scope_matches "$lesson_scope" "$project_scope" || continue
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
# Build list of target files that match this lesson's languages
|
|
400
|
+
target_files=()
|
|
401
|
+
local_f=""
|
|
402
|
+
for local_f in "${existing_files[@]}"; do
|
|
403
|
+
file_matches_languages "$local_f" "$lesson_languages" && target_files+=("$local_f")
|
|
404
|
+
done
|
|
405
|
+
[[ ${#target_files[@]} -eq 0 ]] && continue
|
|
406
|
+
|
|
407
|
+
# Run grep against matching files; format output as file:line: [lesson-N] title
|
|
408
|
+
local_id="$lesson_id"
|
|
409
|
+
local_title="$lesson_title"
|
|
410
|
+
while IFS=: read -r matched_file lineno _rest; do
|
|
411
|
+
[[ -z "$matched_file" ]] && continue
|
|
412
|
+
echo "${matched_file}:${lineno}: [lesson-${local_id}] ${local_title}"
|
|
413
|
+
((violations++)) || true
|
|
414
|
+
done < <(grep -EHn "$pattern_regex" "${target_files[@]}" 2>/dev/null || true)
|
|
415
|
+
done
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
# ---------------------------------------------------------------------------
|
|
419
|
+
# Summary and exit
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
if [[ $violations -gt 0 ]]; then
|
|
422
|
+
echo ""
|
|
423
|
+
echo "lesson-check: $violations violation(s) found"
|
|
424
|
+
exit 1
|
|
425
|
+
else
|
|
426
|
+
echo "lesson-check: clean"
|
|
427
|
+
exit 0
|
|
428
|
+
fi
|