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