@tyroneross/build-loop 0.30.3
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/.agents/plugins/marketplace.json +10 -0
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.codex-plugin/plugin.json +41 -0
- package/.cursor/rules/build-loop-surface.mdc +22 -0
- package/AGENTS.md +498 -0
- package/LICENSE +202 -0
- package/README.md +403 -0
- package/agents/alignment-checker.md +124 -0
- package/agents/api-assessor.md +136 -0
- package/agents/architecture-scout.md +185 -0
- package/agents/assessment-orchestrator.md +163 -0
- package/agents/build-orchestrator.md +198 -0
- package/agents/database-assessor.md +133 -0
- package/agents/design-contract-specialist.md +239 -0
- package/agents/fact-checker.md +69 -0
- package/agents/fix-critique.md +161 -0
- package/agents/frontend-assessor.md +136 -0
- package/agents/implementer.md +185 -0
- package/agents/independent-auditor.md +143 -0
- package/agents/mock-scanner.md +78 -0
- package/agents/optimize-runner.md +112 -0
- package/agents/overfitting-reviewer.md +96 -0
- package/agents/performance-assessor.md +159 -0
- package/agents/plan-critic.md +85 -0
- package/agents/promotion-reviewer.md +121 -0
- package/agents/recurring-pattern-detector.md +248 -0
- package/agents/retrospective-synthesizer.md +109 -0
- package/agents/root-cause-investigator.md +285 -0
- package/agents/scope-auditor.md +121 -0
- package/agents/security-reviewer.md +151 -0
- package/agents/self-improvement-architect.md +125 -0
- package/agents/synthesis-critic.md +99 -0
- package/agents/transcript-pattern-miner.md +165 -0
- package/agents/ui-validator.md +193 -0
- package/codex-skills/build-loop/SKILL.md +20 -0
- package/codex-skills/debug-loop/SKILL.md +18 -0
- package/codex-skills/handoff/SKILL.md +22 -0
- package/codex-skills/knowledge/SKILL.md +21 -0
- package/codex-skills/optimize/SKILL.md +18 -0
- package/codex-skills/research/SKILL.md +18 -0
- package/commands/agent-rally-point.md +301 -0
- package/commands/assess.md +41 -0
- package/commands/debug.md +20 -0
- package/commands/debugger-detail.md +34 -0
- package/commands/debugger-scan.md +15 -0
- package/commands/debugger-status.md +20 -0
- package/commands/debugger.md +32 -0
- package/commands/handoff.md +113 -0
- package/commands/knowledge-review.md +16 -0
- package/commands/optimize.md +31 -0
- package/commands/plan-verify.md +39 -0
- package/commands/promote-experiment.md +129 -0
- package/commands/research.md +22 -0
- package/commands/run.md +12 -0
- package/commands/self-review.md +51 -0
- package/commands/start-prd.md +57 -0
- package/commands/test.md +34 -0
- package/dist/src/assessment-orchestrator.d.ts +78 -0
- package/dist/src/assessment-orchestrator.d.ts.map +1 -0
- package/dist/src/assessment-orchestrator.js +304 -0
- package/dist/src/assessment-orchestrator.js.map +1 -0
- package/dist/src/audit-miner.d.ts +21 -0
- package/dist/src/audit-miner.d.ts.map +1 -0
- package/dist/src/audit-miner.js +421 -0
- package/dist/src/audit-miner.js.map +1 -0
- package/dist/src/batch-operations.d.ts +30 -0
- package/dist/src/batch-operations.d.ts.map +1 -0
- package/dist/src/batch-operations.js +339 -0
- package/dist/src/batch-operations.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +176 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-engine.d.ts +45 -0
- package/dist/src/context-engine.d.ts.map +1 -0
- package/dist/src/context-engine.js +135 -0
- package/dist/src/context-engine.js.map +1 -0
- package/dist/src/debug-wrapper.d.ts +58 -0
- package/dist/src/debug-wrapper.d.ts.map +1 -0
- package/dist/src/debug-wrapper.js +285 -0
- package/dist/src/debug-wrapper.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +149 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interactive-verifier.d.ts +26 -0
- package/dist/src/interactive-verifier.d.ts.map +1 -0
- package/dist/src/interactive-verifier.js +435 -0
- package/dist/src/interactive-verifier.js.map +1 -0
- package/dist/src/lessons.d.ts +59 -0
- package/dist/src/lessons.d.ts.map +1 -0
- package/dist/src/lessons.js +172 -0
- package/dist/src/lessons.js.map +1 -0
- package/dist/src/log-reader.d.ts +39 -0
- package/dist/src/log-reader.d.ts.map +1 -0
- package/dist/src/log-reader.js +291 -0
- package/dist/src/log-reader.js.map +1 -0
- package/dist/src/logger.d.ts +59 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +328 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/parallel-retrieval.d.ts +52 -0
- package/dist/src/parallel-retrieval.d.ts.map +1 -0
- package/dist/src/parallel-retrieval.js +309 -0
- package/dist/src/parallel-retrieval.js.map +1 -0
- package/dist/src/pattern-extractor.d.ts +30 -0
- package/dist/src/pattern-extractor.d.ts.map +1 -0
- package/dist/src/pattern-extractor.js +386 -0
- package/dist/src/pattern-extractor.js.map +1 -0
- package/dist/src/result-aggregator.d.ts +44 -0
- package/dist/src/result-aggregator.d.ts.map +1 -0
- package/dist/src/result-aggregator.js +315 -0
- package/dist/src/result-aggregator.js.map +1 -0
- package/dist/src/retrieval.d.ts +139 -0
- package/dist/src/retrieval.d.ts.map +1 -0
- package/dist/src/retrieval.js +724 -0
- package/dist/src/retrieval.js.map +1 -0
- package/dist/src/setup/auto-setup.d.ts +15 -0
- package/dist/src/setup/auto-setup.d.ts.map +1 -0
- package/dist/src/setup/auto-setup.js +157 -0
- package/dist/src/setup/auto-setup.js.map +1 -0
- package/dist/src/setup/configure-hooks.d.ts +8 -0
- package/dist/src/setup/configure-hooks.d.ts.map +1 -0
- package/dist/src/setup/configure-hooks.js +16 -0
- package/dist/src/setup/configure-hooks.js.map +1 -0
- package/dist/src/setup/create-slash-commands.d.ts +8 -0
- package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
- package/dist/src/setup/create-slash-commands.js +101 -0
- package/dist/src/setup/create-slash-commands.js.map +1 -0
- package/dist/src/setup/index.d.ts +8 -0
- package/dist/src/setup/index.d.ts.map +1 -0
- package/dist/src/setup/index.js +17 -0
- package/dist/src/setup/index.js.map +1 -0
- package/dist/src/setup/inject-claude-md.d.ts +10 -0
- package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
- package/dist/src/setup/inject-claude-md.js +150 -0
- package/dist/src/setup/inject-claude-md.js.map +1 -0
- package/dist/src/setup/uninstall.d.ts +22 -0
- package/dist/src/setup/uninstall.d.ts.map +1 -0
- package/dist/src/setup/uninstall.js +235 -0
- package/dist/src/setup/uninstall.js.map +1 -0
- package/dist/src/storage.d.ts +192 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storage.js +1037 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/string-similarity.d.ts +8 -0
- package/dist/src/string-similarity.d.ts.map +1 -0
- package/dist/src/string-similarity.js +57 -0
- package/dist/src/string-similarity.js.map +1 -0
- package/dist/src/traces/adapters/browser.d.ts +69 -0
- package/dist/src/traces/adapters/browser.d.ts.map +1 -0
- package/dist/src/traces/adapters/browser.js +280 -0
- package/dist/src/traces/adapters/browser.js.map +1 -0
- package/dist/src/traces/adapters/index.d.ts +23 -0
- package/dist/src/traces/adapters/index.d.ts.map +1 -0
- package/dist/src/traces/adapters/index.js +67 -0
- package/dist/src/traces/adapters/index.js.map +1 -0
- package/dist/src/traces/adapters/langchain.d.ts +42 -0
- package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
- package/dist/src/traces/adapters/langchain.js +151 -0
- package/dist/src/traces/adapters/langchain.js.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.js +164 -0
- package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
- package/dist/src/traces/adapters/sentry.d.ts +58 -0
- package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
- package/dist/src/traces/adapters/sentry.js +212 -0
- package/dist/src/traces/adapters/sentry.js.map +1 -0
- package/dist/src/traces/index.d.ts +10 -0
- package/dist/src/traces/index.d.ts.map +1 -0
- package/dist/src/traces/index.js +45 -0
- package/dist/src/traces/index.js.map +1 -0
- package/dist/src/traces/storage.d.ts +78 -0
- package/dist/src/traces/storage.d.ts.map +1 -0
- package/dist/src/traces/storage.js +399 -0
- package/dist/src/traces/storage.js.map +1 -0
- package/dist/src/traces/summarizer.d.ts +83 -0
- package/dist/src/traces/summarizer.d.ts.map +1 -0
- package/dist/src/traces/summarizer.js +247 -0
- package/dist/src/traces/summarizer.js.map +1 -0
- package/dist/src/traces/types.d.ts +365 -0
- package/dist/src/traces/types.d.ts.map +1 -0
- package/dist/src/traces/types.js +14 -0
- package/dist/src/traces/types.js.map +1 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +27 -0
- package/dist/src/types.js.map +1 -0
- package/docs/agent-surface-policy.md +45 -0
- package/docs/memory-setup.md +226 -0
- package/hooks/_arch_scan_bg.py +88 -0
- package/hooks/_plugin_drift_check_bg.py +85 -0
- package/hooks/_session_start_lib.sh +42 -0
- package/hooks/git/pre-push +141 -0
- package/hooks/hooks.json +126 -0
- package/hooks/post-commit +30 -0
- package/hooks/pre-commit +29 -0
- package/hooks/pre-edit-apppulse.sh +6 -0
- package/hooks/pre-edit-architecture.sh +106 -0
- package/hooks/pre-edit-rally-point.sh +92 -0
- package/hooks/session-start-apppulse.sh +6 -0
- package/hooks/session-start-architecture.sh +10 -0
- package/hooks/session-start-git-hooks.sh +12 -0
- package/hooks/session-start-plugin-drift.sh +27 -0
- package/hooks/session-start-rally-point.sh +44 -0
- package/hooks/session-start-retrieval.sh +24 -0
- package/hooks/session-start-worktree-gc.sh +105 -0
- package/hooks/test_apppulse_hooks.sh +6 -0
- package/hooks/test_rally_point_hooks.sh +388 -0
- package/hooks/test_sec008_hooks.sh +66 -0
- package/package.json +111 -0
- package/scripts/README.md +301 -0
- package/scripts/_db_url.py +61 -0
- package/scripts/_paths.py +445 -0
- package/scripts/blm.py +162 -0
- package/scripts/blm_api.py +263 -0
- package/scripts/check_cache_sync.py +490 -0
- package/scripts/embed_backend.py +475 -0
- package/scripts/install_memory.py +556 -0
- package/scripts/lessons_index/__init__.py +77 -0
- package/scripts/lessons_index/__main__.py +135 -0
- package/scripts/lessons_index/ingest.py +288 -0
- package/scripts/lessons_index/query.py +285 -0
- package/scripts/lessons_index/schema.py +114 -0
- package/scripts/memory_context/__init__.py +610 -0
- package/scripts/memory_graph/__init__.py +393 -0
- package/scripts/project_resolver.py +172 -0
- package/scripts/prune_codex_plugin_cache.py +23 -0
- package/scripts/prune_plugin_cache.py +359 -0
- package/scripts/semantic_index/__init__.py +482 -0
- package/scripts/semantic_index/_bench_hybrid.py +175 -0
- package/scripts/semantic_index/backfill.py +263 -0
- package/scripts/semantic_index/hybrid.py +174 -0
- package/scripts/semantic_index/test_backfill.py +292 -0
- package/scripts/semantic_index/test_hybrid.py +541 -0
- package/scripts/sync_navgator_lessons.py +666 -0
- package/scripts/sync_plugin_cache.py +388 -0
- package/skills/agent-rally-point/SKILL.md +165 -0
- package/skills/agent-rally-watcher/SKILL.md +83 -0
- package/skills/api-registry-bridge/SKILL.md +59 -0
- package/skills/architecture/dead/SKILL.md +56 -0
- package/skills/architecture/impact/SKILL.md +88 -0
- package/skills/architecture/review/SKILL.md +227 -0
- package/skills/architecture/rules/SKILL.md +72 -0
- package/skills/architecture/scan/SKILL.md +79 -0
- package/skills/architecture/trace/SKILL.md +65 -0
- package/skills/attribution-standard/SKILL.md +122 -0
- package/skills/authentication/SKILL.md +81 -0
- package/skills/authentication/references/better-auth-magic-link.md +135 -0
- package/skills/authentication/references/better-auth-setup.md +224 -0
- package/skills/authentication/references/google-calendar-sync.md +278 -0
- package/skills/authentication/references/google-cloud-console.md +159 -0
- package/skills/authentication/references/google-geocoding-directions.md +126 -0
- package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
- package/skills/authentication/references/google-maps.md +162 -0
- package/skills/authentication/references/google-oauth-setup.md +343 -0
- package/skills/authentication/references/google-places.md +173 -0
- package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
- package/skills/authentication/references/resend-email.md +227 -0
- package/skills/authentication/references/resend-otp-magic-link.md +153 -0
- package/skills/authentication/references/supabase-auth.md +172 -0
- package/skills/auto-decision-capture/SKILL.md +419 -0
- package/skills/build-loop/SKILL.md +516 -0
- package/skills/build-loop/detect-plugins.mjs +82 -0
- package/skills/build-loop/eval-guide.md +65 -0
- package/skills/build-loop/fallbacks.md +549 -0
- package/skills/build-loop/phases/fact-check.md +42 -0
- package/skills/build-loop/phases/ui-validation.md +267 -0
- package/skills/build-loop/references/apple-native-planning.md +439 -0
- package/skills/build-loop/references/capability-routing.md +181 -0
- package/skills/build-loop/references/codex-subagents.md +98 -0
- package/skills/build-loop/references/coordination.md +161 -0
- package/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/skills/build-loop/references/independent-auditor.md +72 -0
- package/skills/build-loop/references/intent-capability-pack.md +170 -0
- package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/skills/build-loop/references/leadership.md +72 -0
- package/skills/build-loop/references/memory.md +284 -0
- package/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/skills/build-loop/references/output-style.md +129 -0
- package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/skills/build-loop/references/phase-1-assess.md +218 -0
- package/skills/build-loop/references/phase-2-plan.md +77 -0
- package/skills/build-loop/references/phase-3-execute.md +47 -0
- package/skills/build-loop/references/phase-4-review.md +310 -0
- package/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/skills/build-loop/references/phase-6-learn.md +56 -0
- package/skills/build-loop/references/recent-design-structures.md +274 -0
- package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/skills/build-loop/references/self-review.md +232 -0
- package/skills/build-loop/references/ui-io-contract.md +116 -0
- package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/skills/build-loop/templates/backlog-item.md +35 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
- package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/skills/building-with-deepagents/SKILL.md +195 -0
- package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
- package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
- package/skills/capabilities/SKILL.md +91 -0
- package/skills/debug-loop/SKILL.md +278 -0
- package/skills/debug-loop/references/convergence-rules.md +145 -0
- package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
- package/skills/debugging/assess/SKILL.md +111 -0
- package/skills/debugging/debug-loop/SKILL.md +231 -0
- package/skills/debugging/memory/SKILL.md +147 -0
- package/skills/debugging/store/SKILL.md +153 -0
- package/skills/debugging-memory/SKILL.md +440 -0
- package/skills/debugging-memory/examples/incident-example.json +67 -0
- package/skills/debugging-memory/references/incident-documentation.md +280 -0
- package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
- package/skills/debugging-memory/references/pattern-extraction.md +163 -0
- package/skills/debugging-memory/references/subagent-integration.md +312 -0
- package/skills/defenseclaw-bridge/SKILL.md +156 -0
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
- package/skills/defenseclaw-bridge/references/output-format.md +108 -0
- package/skills/handoff/SKILL.md +94 -0
- package/skills/ibr-bridge/SKILL.md +68 -0
- package/skills/knowledge/SKILL.md +127 -0
- package/skills/knowledge/references/recall-integration.md +83 -0
- package/skills/knowledge/templates/madr-minimal.md +56 -0
- package/skills/knowledge-review/SKILL.md +112 -0
- package/skills/logging-tracer/SKILL.md +202 -0
- package/skills/logging-tracer/references/log-analysis.md +128 -0
- package/skills/logging-tracer/references/stack-templates.md +274 -0
- package/skills/logging-tracer-bridge/SKILL.md +70 -0
- package/skills/mcp-builder/SKILL.md +278 -0
- package/skills/mcp-builder/references/mcp-security.md +106 -0
- package/skills/model-tiering/SKILL.md +216 -0
- package/skills/native-ax-driver/SKILL.md +220 -0
- package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
- package/skills/native-ax-driver/scripts/native_driver.py +495 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
- package/skills/optimize/SKILL.md +202 -0
- package/skills/optimize/profiles.md +61 -0
- package/skills/plan-verify/SKILL.md +115 -0
- package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
- package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
- package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
- package/skills/plugin-builder/SKILL.md +329 -0
- package/skills/plugin-builder/references/authoritative-sources.md +104 -0
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
- package/skills/plugin-builder/references/components-guide.md +221 -0
- package/skills/plugin-builder/references/distribution.md +317 -0
- package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
- package/skills/plugin-builder/references/hooks-reference.md +210 -0
- package/skills/plugin-builder/references/manifest-schema.md +111 -0
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
- package/skills/plugin-tests/SKILL.md +94 -0
- package/skills/prd-bridge/SKILL.md +135 -0
- package/skills/research/SKILL.md +80 -0
- package/skills/security-methodology/SKILL.md +105 -0
- package/skills/security-methodology/references/cross-source-matrix.md +77 -0
- package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
- package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
- package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
- package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
- package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
- package/skills/self-improve/SKILL.md +298 -0
- package/skills/spec-writing/SKILL.md +548 -0
- package/skills/spec-writing/scripts/check_checklist.py +478 -0
- package/skills/sync-skills/SKILL.md +132 -0
- package/skills/telemetry/SKILL.md +66 -0
- package/skills/ui-design/SKILL.md +75 -0
- package/skills/ui-design/references/design-patterns-multi.md +165 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
- package/skills/ui-design/references/evidence-capture-policy.md +55 -0
- package/skills/ui-design/references/ui-guidance-sources.md +330 -0
- package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
- package/skills/ui-design/references/universal-design-principles.md +419 -0
- package/templates/memory/MEMORY.md.template +55 -0
- package/templates/memory/constitution.md.template +64 -0
- package/templates/memory/manifest.json +47 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
"""Sync a local plugin source repo into installed host caches.
|
|
5
|
+
|
|
6
|
+
Default behavior is intentionally conservative: materialize committed HEAD with
|
|
7
|
+
``git archive`` and sync that clean snapshot into the target cache. This keeps
|
|
8
|
+
unrelated dirty worktree edits out of Codex and Claude runtime caches.
|
|
9
|
+
|
|
10
|
+
Use ``--dirty`` only for explicit temporary runtime testing. Use ``--file`` to
|
|
11
|
+
copy a targeted slice without deleting the rest of the cache.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import shutil
|
|
21
|
+
import stat
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import tarfile
|
|
25
|
+
import tempfile
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
HERE = Path(__file__).resolve().parent
|
|
30
|
+
if str(HERE) not in sys.path:
|
|
31
|
+
sys.path.insert(0, str(HERE))
|
|
32
|
+
|
|
33
|
+
import check_cache_sync as cache_sync # type: ignore # noqa: E402
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
EXCLUDE_DIRS = {".git", "node_modules", "__pycache__", ".venv", ".build-loop"}
|
|
37
|
+
HOOK_NAMES = ("post-commit", "post-merge", "post-checkout", "post-rewrite")
|
|
38
|
+
HOOK_MARKER = "# --- BEGIN build-loop plugin-cache-sync ---"
|
|
39
|
+
HOOK_MARKER_END = "# --- END build-loop plugin-cache-sync ---"
|
|
40
|
+
HOOK_SEGMENT_RE = re.compile(
|
|
41
|
+
rf"{re.escape(HOOK_MARKER)}.*?{re.escape(HOOK_MARKER_END)}\n?",
|
|
42
|
+
re.DOTALL,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SyncError(RuntimeError):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_git(source: Path, args: list[str]) -> str:
|
|
51
|
+
proc = subprocess.run(
|
|
52
|
+
["git", *args],
|
|
53
|
+
cwd=str(source),
|
|
54
|
+
stdout=subprocess.PIPE,
|
|
55
|
+
stderr=subprocess.PIPE,
|
|
56
|
+
text=True,
|
|
57
|
+
check=False,
|
|
58
|
+
)
|
|
59
|
+
if proc.returncode != 0:
|
|
60
|
+
raise SyncError(f"git {' '.join(args)} failed: {proc.stderr.strip()}")
|
|
61
|
+
return proc.stdout.strip()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def require_git_head(source: Path) -> None:
|
|
65
|
+
inside = run_git(source, ["rev-parse", "--is-inside-work-tree"])
|
|
66
|
+
if inside != "true":
|
|
67
|
+
raise SyncError(f"{source} is not inside a git worktree; use --dirty for non-git sources")
|
|
68
|
+
run_git(source, ["rev-parse", "--verify", "HEAD"])
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def safe_extract_tar(tar_bytes: bytes, target: Path) -> None:
|
|
72
|
+
target_resolved = target.resolve()
|
|
73
|
+
with tarfile.open(fileobj=io.BytesIO(tar_bytes), mode="r:") as archive:
|
|
74
|
+
for member in archive.getmembers():
|
|
75
|
+
member_target = (target / member.name).resolve()
|
|
76
|
+
if target_resolved != member_target and target_resolved not in member_target.parents:
|
|
77
|
+
raise SyncError(f"unsafe archive member path: {member.name}")
|
|
78
|
+
archive.extractall(target)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def materialize_head(source: Path) -> tempfile.TemporaryDirectory[str]:
|
|
82
|
+
require_git_head(source)
|
|
83
|
+
proc = subprocess.run(
|
|
84
|
+
["git", "archive", "--format=tar", "HEAD"],
|
|
85
|
+
cwd=str(source),
|
|
86
|
+
stdout=subprocess.PIPE,
|
|
87
|
+
stderr=subprocess.PIPE,
|
|
88
|
+
check=False,
|
|
89
|
+
)
|
|
90
|
+
if proc.returncode != 0:
|
|
91
|
+
raise SyncError(f"git archive HEAD failed: {proc.stderr.decode(errors='ignore').strip()}")
|
|
92
|
+
tmp = tempfile.TemporaryDirectory(prefix="build-loop-plugin-head-")
|
|
93
|
+
safe_extract_tar(proc.stdout, Path(tmp.name))
|
|
94
|
+
return tmp
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def ignore_dirty_tree(_dir: str, names: list[str]) -> set[str]:
|
|
98
|
+
return {name for name in names if name in EXCLUDE_DIRS}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def copy_tree_to_temp(source: Path, target_parent: Path) -> Path:
|
|
102
|
+
target_parent.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
tmp = Path(tempfile.mkdtemp(prefix=".build-loop-sync-", dir=str(target_parent)))
|
|
104
|
+
shutil.rmtree(tmp)
|
|
105
|
+
shutil.copytree(source, tmp, ignore=ignore_dirty_tree)
|
|
106
|
+
return tmp
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def sync_full_tree(source: Path, cache: Path, dry_run: bool) -> int:
|
|
110
|
+
files = [p for p in source.rglob("*") if p.is_file() and not any(part in EXCLUDE_DIRS for part in p.parts)]
|
|
111
|
+
if dry_run:
|
|
112
|
+
return len(files)
|
|
113
|
+
if cache.resolve() == source.resolve() or cache.resolve() in source.resolve().parents:
|
|
114
|
+
raise SyncError(f"refusing to sync into source path: {cache}")
|
|
115
|
+
parent = cache.parent
|
|
116
|
+
tmp = copy_tree_to_temp(source, parent)
|
|
117
|
+
if cache.exists():
|
|
118
|
+
shutil.rmtree(cache)
|
|
119
|
+
os.replace(tmp, cache)
|
|
120
|
+
return len(files)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def sync_targeted_files(source: Path, cache: Path, files: list[str], dry_run: bool) -> tuple[int, list[str]]:
|
|
124
|
+
missing: list[str] = []
|
|
125
|
+
copied = 0
|
|
126
|
+
for raw in files:
|
|
127
|
+
rel = raw.strip().lstrip("/")
|
|
128
|
+
if not rel or rel.startswith("../") or "/../" in rel:
|
|
129
|
+
missing.append(raw)
|
|
130
|
+
continue
|
|
131
|
+
src = source / rel
|
|
132
|
+
if not src.is_file():
|
|
133
|
+
missing.append(rel)
|
|
134
|
+
continue
|
|
135
|
+
copied += 1
|
|
136
|
+
if dry_run:
|
|
137
|
+
continue
|
|
138
|
+
dst = cache / rel
|
|
139
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
shutil.copy2(src, dst)
|
|
141
|
+
return copied, missing
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def host_list(host: str) -> list[str]:
|
|
145
|
+
return ["claude", "codex"] if host == "all" else [host]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def load_host_manifest(source: Path, host: str, explicit: bool) -> dict[str, Any] | None:
|
|
149
|
+
manifest_dir = ".codex-plugin" if host == "codex" else ".claude-plugin"
|
|
150
|
+
if not (source / manifest_dir / "plugin.json").exists():
|
|
151
|
+
if explicit:
|
|
152
|
+
raise SyncError(f"missing {manifest_dir}/plugin.json under {source}")
|
|
153
|
+
return None
|
|
154
|
+
return cache_sync.load_manifest(source, host)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def resolve_cache(
|
|
158
|
+
*,
|
|
159
|
+
source: Path,
|
|
160
|
+
host: str,
|
|
161
|
+
manifest: dict[str, Any],
|
|
162
|
+
args: argparse.Namespace,
|
|
163
|
+
) -> tuple[Path, list[Path]]:
|
|
164
|
+
if host == "claude" and args.claude_cache:
|
|
165
|
+
return Path(args.claude_cache).expanduser().resolve(), []
|
|
166
|
+
if host == "codex" and args.codex_cache:
|
|
167
|
+
return Path(args.codex_cache).expanduser().resolve(), []
|
|
168
|
+
if args.cache:
|
|
169
|
+
if args.host == "all":
|
|
170
|
+
raise SyncError("--cache can only be used with --host claude or --host codex")
|
|
171
|
+
return Path(args.cache).expanduser().resolve(), []
|
|
172
|
+
name = manifest.get("name")
|
|
173
|
+
version = manifest.get("version")
|
|
174
|
+
if not name or not version:
|
|
175
|
+
raise SyncError(f"{host} plugin.json missing name or version")
|
|
176
|
+
return cache_sync.default_cache(source, host, str(name), str(version), args.marketplace)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def refs_for_host(source: Path, host: str, manifest: dict[str, Any], files: list[str]) -> set[str]:
|
|
180
|
+
if files:
|
|
181
|
+
return {item.strip().lstrip("/") for item in files if item.strip()}
|
|
182
|
+
if host == "codex":
|
|
183
|
+
return cache_sync.find_codex_surfaces(source, manifest)
|
|
184
|
+
refs = cache_sync.find_references(source)
|
|
185
|
+
for extra in (".claude-plugin/plugin.json", "hooks/hooks.json"):
|
|
186
|
+
if (source / extra).exists():
|
|
187
|
+
refs.add(extra)
|
|
188
|
+
return refs
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def verify_host(source: Path, host: str, manifest: dict[str, Any], cache: Path, files: list[str]) -> list[dict[str, Any]]:
|
|
192
|
+
return cache_sync.check_sync(source, cache, refs_for_host(source, host, manifest, files))
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def sync_one_host(
|
|
196
|
+
*,
|
|
197
|
+
host: str,
|
|
198
|
+
explicit_host: bool,
|
|
199
|
+
source_for_sync: Path,
|
|
200
|
+
source_for_manifest: Path,
|
|
201
|
+
args: argparse.Namespace,
|
|
202
|
+
) -> dict[str, Any]:
|
|
203
|
+
manifest = load_host_manifest(source_for_manifest, host, explicit_host)
|
|
204
|
+
if manifest is None:
|
|
205
|
+
return {"host": host, "action": "skipped", "reason": "missing_manifest"}
|
|
206
|
+
cache, stale_versions = resolve_cache(source=source_for_manifest, host=host, manifest=manifest, args=args)
|
|
207
|
+
if args.files:
|
|
208
|
+
copied, missing = sync_targeted_files(source_for_sync, cache, args.files, args.dry_run)
|
|
209
|
+
action = "dry_run" if args.dry_run else "targeted_sync"
|
|
210
|
+
else:
|
|
211
|
+
copied = sync_full_tree(source_for_sync, cache, args.dry_run)
|
|
212
|
+
missing = []
|
|
213
|
+
action = "dry_run" if args.dry_run else "full_sync"
|
|
214
|
+
|
|
215
|
+
diffs: list[dict[str, Any]] = []
|
|
216
|
+
if not args.no_verify and not args.dry_run:
|
|
217
|
+
diffs = verify_host(source_for_sync, host, manifest, cache, args.files)
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
"host": host,
|
|
221
|
+
"action": action,
|
|
222
|
+
"plugin": manifest.get("name"),
|
|
223
|
+
"version": manifest.get("version"),
|
|
224
|
+
"cache": str(cache),
|
|
225
|
+
"files_copied": copied,
|
|
226
|
+
"missing_files": missing,
|
|
227
|
+
"stale_versions": [str(path) for path in stale_versions],
|
|
228
|
+
"verification_diffs": diffs,
|
|
229
|
+
"ok": not missing and not diffs,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def repo_toplevel(source: Path) -> Path:
|
|
234
|
+
try:
|
|
235
|
+
return Path(run_git(source, ["rev-parse", "--show-toplevel"])).resolve()
|
|
236
|
+
except SyncError as exc:
|
|
237
|
+
raise SyncError(f"cannot install hooks outside a git repo: {exc}") from exc
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def hook_segment(script_path: Path, host: str) -> str:
|
|
241
|
+
return f'''{HOOK_MARKER}
|
|
242
|
+
BUILD_LOOP_PLUGIN_CACHE_SYNC_TOPLEVEL="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
243
|
+
if [ -n "$BUILD_LOOP_PLUGIN_CACHE_SYNC_TOPLEVEL" ] && [ "${{BUILD_LOOP_PLUGIN_CACHE_SYNC_DISABLE:-}}" != "1" ]; then
|
|
244
|
+
python3 {str(script_path)!r} --source "$BUILD_LOOP_PLUGIN_CACHE_SYNC_TOPLEVEL" --host {host} --quiet >/dev/null 2>&1 || true
|
|
245
|
+
fi
|
|
246
|
+
{HOOK_MARKER_END}
|
|
247
|
+
'''
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def append_before_terminal_exit(script: str, segment: str) -> str:
|
|
251
|
+
lines = script.rstrip().splitlines()
|
|
252
|
+
for idx in range(len(lines) - 1, -1, -1):
|
|
253
|
+
if lines[idx].strip() == "exit 0":
|
|
254
|
+
before = "\n".join(lines[:idx]).rstrip()
|
|
255
|
+
after = "\n".join(lines[idx:]).rstrip()
|
|
256
|
+
return f"{before}\n\n{segment.rstrip()}\n{after}\n"
|
|
257
|
+
return f"{script.rstrip()}\n\n{segment.rstrip()}\n"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def install_hook(hooks_dir: Path, name: str, segment: str) -> str:
|
|
261
|
+
hook = hooks_dir / name
|
|
262
|
+
existing = hook.read_text(encoding="utf-8") if hook.exists() else "#!/bin/sh\n"
|
|
263
|
+
if not existing.startswith("#!"):
|
|
264
|
+
existing = "#!/bin/sh\n" + existing
|
|
265
|
+
cleaned = HOOK_SEGMENT_RE.sub("", existing)
|
|
266
|
+
hook.write_text(append_before_terminal_exit(cleaned, segment), encoding="utf-8")
|
|
267
|
+
mode = hook.stat().st_mode
|
|
268
|
+
hook.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
269
|
+
return str(hook)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def install_git_hooks(source: Path, host: str) -> dict[str, Any]:
|
|
273
|
+
top = repo_toplevel(source)
|
|
274
|
+
hooks_dir = top / ".git" / "hooks"
|
|
275
|
+
if not hooks_dir.is_dir():
|
|
276
|
+
raise SyncError(f"hooks directory not found: {hooks_dir}")
|
|
277
|
+
segment = hook_segment(Path(__file__).resolve(), host)
|
|
278
|
+
installed = [install_hook(hooks_dir, name, segment) for name in HOOK_NAMES]
|
|
279
|
+
return {"action": "installed_hooks", "hooks": installed, "host": host, "source": str(top)}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
283
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
284
|
+
parser.add_argument("--host", choices=["claude", "codex", "all"], default="all")
|
|
285
|
+
parser.add_argument("--source", default=".", help="Plugin source repo root.")
|
|
286
|
+
parser.add_argument("--cache", default=None, help="Cache dir for a single explicit host.")
|
|
287
|
+
parser.add_argument("--claude-cache", default=None, help="Claude cache dir for --host all or --host claude.")
|
|
288
|
+
parser.add_argument("--codex-cache", default=None, help="Codex cache dir for --host all or --host codex.")
|
|
289
|
+
parser.add_argument("--marketplace", default=None, help="Marketplace name override.")
|
|
290
|
+
parser.add_argument("--dirty", action="store_true", help="Sync the dirty working tree instead of committed HEAD.")
|
|
291
|
+
parser.add_argument("--file", action="append", dest="files", default=[], help="Target one relative file. Repeatable.")
|
|
292
|
+
parser.add_argument("--dry-run", action="store_true")
|
|
293
|
+
parser.add_argument("--no-verify", action="store_true", help="Skip post-sync cache verification.")
|
|
294
|
+
parser.add_argument("--json", action="store_true")
|
|
295
|
+
parser.add_argument("--quiet", action="store_true")
|
|
296
|
+
parser.add_argument(
|
|
297
|
+
"--install-git-hooks",
|
|
298
|
+
action="store_true",
|
|
299
|
+
help="Install idempotent post-commit/merge/checkout/rewrite sync hooks and exit.",
|
|
300
|
+
)
|
|
301
|
+
return parser.parse_args(argv)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def print_human(results: list[dict[str, Any]], source_mode: str, materialized_source: Path) -> None:
|
|
305
|
+
for result in results:
|
|
306
|
+
host = result.get("host")
|
|
307
|
+
action = result.get("action")
|
|
308
|
+
if action == "skipped":
|
|
309
|
+
print(f"- {host}: skipped ({result.get('reason')})")
|
|
310
|
+
continue
|
|
311
|
+
ok = "OK" if result.get("ok", True) else "WARN"
|
|
312
|
+
print(f"- {host}: {ok} {action} {result.get('plugin')}@{result.get('version')}")
|
|
313
|
+
if result.get("cache"):
|
|
314
|
+
print(f" cache: {result['cache']}")
|
|
315
|
+
print(f" source_mode: {source_mode}")
|
|
316
|
+
print(f" verified_source: {materialized_source}")
|
|
317
|
+
print(f" files_copied: {result.get('files_copied', 0)}")
|
|
318
|
+
for path in result.get("missing_files") or []:
|
|
319
|
+
print(f" missing: {path}")
|
|
320
|
+
diffs = result.get("verification_diffs") or []
|
|
321
|
+
if diffs:
|
|
322
|
+
print(f" verification_diffs: {len(diffs)}")
|
|
323
|
+
for diff in diffs[:20]:
|
|
324
|
+
print(f" [{diff.get('status')}] {diff.get('path')}")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def main(argv: list[str] | None = None) -> int:
|
|
328
|
+
args = parse_args(argv)
|
|
329
|
+
source = Path(args.source).expanduser().resolve()
|
|
330
|
+
if not source.is_dir():
|
|
331
|
+
print(f"error: source is not a directory: {source}", file=sys.stderr)
|
|
332
|
+
return 2
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
if args.install_git_hooks:
|
|
336
|
+
installed = install_git_hooks(source, args.host)
|
|
337
|
+
if args.json:
|
|
338
|
+
print(json.dumps(installed, indent=2, sort_keys=True))
|
|
339
|
+
elif not args.quiet:
|
|
340
|
+
print(f"installed plugin cache sync hooks for {args.host}:")
|
|
341
|
+
for hook in installed["hooks"]:
|
|
342
|
+
print(f" {hook}")
|
|
343
|
+
return 0
|
|
344
|
+
|
|
345
|
+
tmp: tempfile.TemporaryDirectory[str] | None = None
|
|
346
|
+
if args.dirty:
|
|
347
|
+
source_for_sync = source
|
|
348
|
+
source_mode = "dirty"
|
|
349
|
+
else:
|
|
350
|
+
tmp = materialize_head(source)
|
|
351
|
+
source_for_sync = Path(tmp.name)
|
|
352
|
+
source_mode = "head"
|
|
353
|
+
|
|
354
|
+
explicit_host = args.host != "all"
|
|
355
|
+
results = [
|
|
356
|
+
sync_one_host(
|
|
357
|
+
host=host,
|
|
358
|
+
explicit_host=explicit_host,
|
|
359
|
+
source_for_sync=source_for_sync,
|
|
360
|
+
source_for_manifest=source_for_sync,
|
|
361
|
+
args=args,
|
|
362
|
+
)
|
|
363
|
+
for host in host_list(args.host)
|
|
364
|
+
]
|
|
365
|
+
payload = {
|
|
366
|
+
"ok": all(result.get("ok", True) for result in results),
|
|
367
|
+
"source": str(source),
|
|
368
|
+
"source_mode": source_mode,
|
|
369
|
+
"materialized_source": str(source_for_sync),
|
|
370
|
+
"results": results,
|
|
371
|
+
}
|
|
372
|
+
if args.json:
|
|
373
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
374
|
+
elif not args.quiet:
|
|
375
|
+
print_human(results, source_mode, source_for_sync)
|
|
376
|
+
if tmp is not None:
|
|
377
|
+
tmp.cleanup()
|
|
378
|
+
return 0 if payload["ok"] else 1
|
|
379
|
+
except SyncError as exc:
|
|
380
|
+
if args.json:
|
|
381
|
+
print(json.dumps({"ok": False, "error": str(exc)}, indent=2, sort_keys=True))
|
|
382
|
+
else:
|
|
383
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
384
|
+
return 2
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
if __name__ == "__main__":
|
|
388
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-rally-point
|
|
3
|
+
description: "Use when coordinating build-loop with peer coding agents, checking Rally Point presence/inbox state, posting handoffs or feedback, validating the embedded Rally Point boundary, or changing the future agent-rally-point spin-out surface."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
8
|
+
|
|
9
|
+
# Agent Rally Point
|
|
10
|
+
|
|
11
|
+
Build-loop ships a native embedded `agent-rally-point` capability while the
|
|
12
|
+
standalone repo matures. Treat it as a mini-plugin inside build-loop: keep the
|
|
13
|
+
substrate logic, docs, tests, and thin build-loop adapters grouped so spin-out
|
|
14
|
+
is mechanical later.
|
|
15
|
+
|
|
16
|
+
## Native Surface
|
|
17
|
+
|
|
18
|
+
| Purpose | Path |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Substrate package | `${CLAUDE_PLUGIN_ROOT}/scripts/rally_point/` |
|
|
21
|
+
| Host-neutral CLI | `${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py` |
|
|
22
|
+
| Slash command | `${CLAUDE_PLUGIN_ROOT}/commands/agent-rally-point.md` |
|
|
23
|
+
| Hook adapters | `${CLAUDE_PLUGIN_ROOT}/hooks/session-start-rally-point.sh`, `${CLAUDE_PLUGIN_ROOT}/hooks/pre-edit-rally-point.sh` |
|
|
24
|
+
| Build-loop adapters | `${CLAUDE_PLUGIN_ROOT}/scripts/coordination_status.py`, `${CLAUDE_PLUGIN_ROOT}/scripts/coordination_rally.py`, `${CLAUDE_PLUGIN_ROOT}/scripts/coordination_bootstrap.py` |
|
|
25
|
+
| Boundary manifest | `${CLAUDE_PLUGIN_ROOT}/scripts/rally_point/plugin_boundary.json` |
|
|
26
|
+
|
|
27
|
+
## Required Orchestrator Contract
|
|
28
|
+
|
|
29
|
+
At the Phase 1 preamble, before the first Rally Point write, the orchestrator
|
|
30
|
+
must generate or resume the durable run identity:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from scripts.rally_point.build_loop_id import generate_or_resume
|
|
34
|
+
|
|
35
|
+
execution = generate_or_resume(
|
|
36
|
+
workdir=Path.cwd(),
|
|
37
|
+
tool="<tool-id>",
|
|
38
|
+
session_id="<session-id>",
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`session_id` is the ephemeral host session. `run_id` is caller-chosen legacy
|
|
43
|
+
provenance. `build_loop_id` is the durable human-visible run identity and is
|
|
44
|
+
surfaced on Rally Point records as top-level `build_loop_id` and
|
|
45
|
+
`build_loop_run_label`.
|
|
46
|
+
|
|
47
|
+
## Operating Rules
|
|
48
|
+
|
|
49
|
+
- Resolve the channel through `scripts/rally_point/discovery_bridge.py` or
|
|
50
|
+
`scripts/agent_rally.py where`; do not hardcode legacy channel roots.
|
|
51
|
+
- Write cross-session events through `scripts/rally_point/post.py::post()`;
|
|
52
|
+
do not append directly to `changes.jsonl`.
|
|
53
|
+
- Keep `build_loop_id` separate from `producer_metadata`.
|
|
54
|
+
- Keep substrate files independent from build-loop memory/orchestration
|
|
55
|
+
internals. If a build-loop-specific integration is needed, put it in a thin
|
|
56
|
+
adapter named in `plugin_boundary.json`.
|
|
57
|
+
- Use stable tool ids: `claude_code`, `codex`, `cursor`, `gemini`, or another
|
|
58
|
+
lowercase id agreed by the adapter.
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py status --workdir "$PWD" --session-id "$SESSION_ID" --json
|
|
64
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py heartbeat --workdir "$PWD" --session-id "$SESSION_ID" --task-ref "$TASK_REF" --progress "still working" --json
|
|
65
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py ack-inbox --workdir "$PWD" --session-id "$SESSION_ID" --tool "$TOOL_ID" --json
|
|
66
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py boundary --repo "${CLAUDE_PLUGIN_ROOT}" --check --json
|
|
67
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/rally_point/boundary.py --repo "${CLAUDE_PLUGIN_ROOT}" --check --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use `/agent-rally-point status` for the same sensor from Claude Code.
|
|
71
|
+
Status/watch envelopes include `inbox_latest_messages`, a compact doorbell
|
|
72
|
+
preview for the newest unread direct/broadcast inbox records. Counts are
|
|
73
|
+
session-ack aware; read `inbox/<tool>.jsonl` or `inbox/all.jsonl` before acting
|
|
74
|
+
on a full message, then run `ack-inbox` after handling it. Ack cursors live
|
|
75
|
+
under `inbox/.acks/` and never rewrite the append-only inbox payloads.
|
|
76
|
+
|
|
77
|
+
## Task Heartbeat — still on the claimed task
|
|
78
|
+
|
|
79
|
+
Presence is process liveness: it tells peers this session can still write to
|
|
80
|
+
the channel. Task heartbeat is work liveness: it tells peers whether the
|
|
81
|
+
session is still on the claimed task, what changed since the last check-in,
|
|
82
|
+
what evidence exists, and when the next check-in is due.
|
|
83
|
+
|
|
84
|
+
For long-running tasks, write a heartbeat at task start and then at least every
|
|
85
|
+
10 minutes:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py heartbeat \
|
|
89
|
+
--workdir "$PWD" \
|
|
90
|
+
--session-id "$SESSION_ID" \
|
|
91
|
+
--tool "$TOOL_ID" \
|
|
92
|
+
--model "$MODEL" \
|
|
93
|
+
--run-id "$RUN_ID" \
|
|
94
|
+
--task-ref "$TASK_REF" \
|
|
95
|
+
--status running \
|
|
96
|
+
--progress "short update since last check-in" \
|
|
97
|
+
--evidence "files/tests/commit-or-handoff-ids" \
|
|
98
|
+
--json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Then pass the same `--task-ref` to `status` or `watch`. Health can be
|
|
102
|
+
`current`, `stale_check_in`, `wrong_task`, `missing`, `drift_risk`, `blocked`,
|
|
103
|
+
or `needs_attention`. `blocked` and `needs_attention` make status report
|
|
104
|
+
`blocked`; stale, missing, wrong-task, and drift-risk states report `warn`.
|
|
105
|
+
|
|
106
|
+
## Roster — who is running, where, doing what, with how many subagents
|
|
107
|
+
|
|
108
|
+
`roster` answers the cross-channel "who's live right now" question in one
|
|
109
|
+
command. It walks **every** `<apps_root>/*/sessions/*.json` (all repos at
|
|
110
|
+
once), keeps sessions whose `last_seen` is within the stale window, and
|
|
111
|
+
builds a parent/child tree from each record's `parent` link plus the
|
|
112
|
+
self-reported `spawned` fan-out.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py roster # live tree, all channels
|
|
116
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py roster --app ptyd # one channel
|
|
117
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py roster --json # structured (array of agent objects w/ children + spawned)
|
|
118
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py roster --stale-secs 300 --all # widen window, keep stale
|
|
119
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py roster --watch 5 # re-render every 5s
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Each row: `session_id · app · host:cwd · tool/model · task · last-seen(age) ·
|
|
123
|
+
subagents (Σtotal by-type + live nested count)`. Children that posted their
|
|
124
|
+
own presence nest under the parent; subagents that did not post presence are
|
|
125
|
+
reflected by the parent's `spawned` totals.
|
|
126
|
+
|
|
127
|
+
### Convention — populate the roster on every presence write
|
|
128
|
+
|
|
129
|
+
So the roster is useful, agents **must** enrich their `presence` calls. These
|
|
130
|
+
fields are additive and backward-compatible (existing callers keep working):
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally.py presence \
|
|
134
|
+
--session-id "$SESSION_ID" --tool claude_code --model opus \
|
|
135
|
+
--cwd "$PWD" \
|
|
136
|
+
--task "what this agent is actually doing right now" \
|
|
137
|
+
--parent "$SPAWNING_SESSION_ID" \
|
|
138
|
+
--spawned "coder:2,workflow:21,independent-auditor:1"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- `--cwd` / `--pid` / `--host` — where it runs (default cwd / this pid / this host).
|
|
142
|
+
- `--task` — fuller free text (falls back to `--phase` for display).
|
|
143
|
+
- `--parent <session_id>` — links a subagent to its spawning agent; omit for top-level.
|
|
144
|
+
- `--spawned <type:count,…>` — the fan-out an agent self-reports.
|
|
145
|
+
- Every `presence` call rewrites `last_seen`; re-post periodically so the
|
|
146
|
+
agent stays in the live window (default 120s). Presence is not task
|
|
147
|
+
heartbeat; use `agent_rally.py heartbeat` for long-running task check-ins.
|
|
148
|
+
|
|
149
|
+
Top-level orchestrators should post `--spawned` reflecting their dispatched
|
|
150
|
+
subagents; each dispatched subagent that can post should set `--parent` to the
|
|
151
|
+
orchestrator's `session_id`.
|
|
152
|
+
|
|
153
|
+
## Validation
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
uv run pytest scripts/test_build_loop_id.py scripts/rally_point/test_boundary.py scripts/rally_point/test_orchestrator_contract.py scripts/test_agent_rally_roster.py scripts/test_agent_rally_status.py
|
|
157
|
+
python3 scripts/agent_rally.py boundary --repo "$PWD" --check --json
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Spin-Out Rule
|
|
161
|
+
|
|
162
|
+
When extracting to the standalone plugin, copy the namespaced substrate,
|
|
163
|
+
skill, command, hooks, tests, and docs named in `plugin_boundary.json`.
|
|
164
|
+
Build-loop should then keep only the thin adapters that call the standalone
|
|
165
|
+
package.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-rally-watcher
|
|
3
|
+
description: "Use when listening for Rally Point changes, wiring coordination watchers, debugging watch-loop behavior, or changing the future agent-rally-watcher spin-out surface."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com> | SPDX-License-Identifier: Apache-2.0 -->
|
|
8
|
+
|
|
9
|
+
# Agent Rally Watcher
|
|
10
|
+
|
|
11
|
+
Build-loop ships a native embedded `agent-rally-watcher` capability while the
|
|
12
|
+
standalone watcher repo matures. Treat it as a mini-plugin inside build-loop:
|
|
13
|
+
watcher behavior lives in one namespace, and build-loop keeps compatibility
|
|
14
|
+
entrypoints thin.
|
|
15
|
+
|
|
16
|
+
## Native Surface
|
|
17
|
+
|
|
18
|
+
| Purpose | Path |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Watcher package | `${CLAUDE_PLUGIN_ROOT}/scripts/agent_rally_watcher/` |
|
|
21
|
+
| Compatibility CLI | `${CLAUDE_PLUGIN_ROOT}/scripts/coordination_watch.py` |
|
|
22
|
+
| Status dependency | `${CLAUDE_PLUGIN_ROOT}/scripts/coordination_status.py` |
|
|
23
|
+
| Manual command | `${CLAUDE_PLUGIN_ROOT}/commands/agent-rally-point.md` `watch` subcommand |
|
|
24
|
+
| Boundary manifest | `${CLAUDE_PLUGIN_ROOT}/scripts/rally_point/plugin_boundary.json` |
|
|
25
|
+
|
|
26
|
+
## Operating Rules
|
|
27
|
+
|
|
28
|
+
- Keep watch-loop logic under `scripts/agent_rally_watcher/`.
|
|
29
|
+
- Keep `scripts/coordination_watch.py` as a compatibility wrapper.
|
|
30
|
+
- Watchers emit compact JSONL transition events; they do not make decisions,
|
|
31
|
+
mutate code, stage files, commit, or resolve verdicts.
|
|
32
|
+
- Prefer short interactive watchers for active work. Durable always-on
|
|
33
|
+
watchers belong in launchd/systemd packaging, not ad hoc shell backgrounding.
|
|
34
|
+
- Stop watchers started by the current run before final closeout.
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/coordination_watch.py \
|
|
40
|
+
--workdir "$PWD" \
|
|
41
|
+
--session-id "$SESSION_ID" \
|
|
42
|
+
--tool "$TOOL_ID" \
|
|
43
|
+
--task-ref "$TASK_REF" \
|
|
44
|
+
--interval 5 \
|
|
45
|
+
--jsonl \
|
|
46
|
+
--baseline-current
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For one-shot validation:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/coordination_watch.py \
|
|
53
|
+
--workdir "$PWD" \
|
|
54
|
+
--session-id watcher-smoke \
|
|
55
|
+
--tool codex \
|
|
56
|
+
--iterations 1 \
|
|
57
|
+
--jsonl
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Watcher transition events include `inbox_latest_messages`, a compact
|
|
61
|
+
doorbell preview of the newest addressed/broadcast inbox records. It is
|
|
62
|
+
additive to the raw unread counts; read the inbox before acting on the full
|
|
63
|
+
message.
|
|
64
|
+
|
|
65
|
+
Watcher transition events also include `task_heartbeat`. Pass `--task-ref`
|
|
66
|
+
when a long-running task has an expected claim/run/pillar id; the watcher will
|
|
67
|
+
wake on current/stale/wrong-task/blocked heartbeat changes without requiring
|
|
68
|
+
the peer to send an inbox message.
|
|
69
|
+
|
|
70
|
+
## Validation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv run pytest scripts/test_coordination_status.py scripts/rally_point/test_session_probe.py
|
|
74
|
+
python3 scripts/coordination_watch.py --workdir "$PWD" --session-id watcher-smoke --tool codex --iterations 1 --jsonl
|
|
75
|
+
python3 scripts/agent_rally.py boundary --repo "$PWD" --check --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Spin-Out Rule
|
|
79
|
+
|
|
80
|
+
When extracting to the standalone watcher plugin, copy
|
|
81
|
+
`scripts/agent_rally_watcher/`, this skill, watcher tests, and the
|
|
82
|
+
compatibility contract named in `plugin_boundary.json`. Build-loop should then
|
|
83
|
+
keep `coordination_watch.py` as a thin adapter to the standalone package.
|