@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,359 @@
|
|
|
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
|
+
"""Prune stale plugin cache versions for Claude Code and Codex.
|
|
5
|
+
|
|
6
|
+
Both hosts keep versioned plugin cache directories:
|
|
7
|
+
|
|
8
|
+
~/.codex/plugins/cache/<marketplace>/<plugin>/<version>/
|
|
9
|
+
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/
|
|
10
|
+
|
|
11
|
+
Dry-run is the default. Pass ``--apply`` to delete stale verified directories.
|
|
12
|
+
The script only deletes directories whose host-specific plugin manifest confirms
|
|
13
|
+
the same plugin name.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Literal
|
|
24
|
+
|
|
25
|
+
Host = Literal["codex", "claude"]
|
|
26
|
+
|
|
27
|
+
# Env vars a live host sets to the plugin's active cache version dir
|
|
28
|
+
# (.../cache/<marketplace>/<plugin>/<version>). The version dir a running
|
|
29
|
+
# session is loaded from MUST never be pruned — deleting it removes the
|
|
30
|
+
# session's plugin root and every one of its hooks fails mid-run with
|
|
31
|
+
# "Plugin directory does not exist".
|
|
32
|
+
IN_USE_ENV_VARS: tuple[str, ...] = ("CLAUDE_PLUGIN_ROOT", "CODEX_PLUGIN_ROOT")
|
|
33
|
+
|
|
34
|
+
HOST_CONFIG: dict[Host, dict[str, str]] = {
|
|
35
|
+
"codex": {
|
|
36
|
+
"manifest": ".codex-plugin/plugin.json",
|
|
37
|
+
"cache": ".codex/plugins/cache",
|
|
38
|
+
},
|
|
39
|
+
"claude": {
|
|
40
|
+
"manifest": ".claude-plugin/plugin.json",
|
|
41
|
+
"cache": ".claude/plugins/cache",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_manifest(source: Path, host: Host) -> dict[str, Any]:
|
|
47
|
+
manifest_path = source / HOST_CONFIG[host]["manifest"]
|
|
48
|
+
if not manifest_path.is_file():
|
|
49
|
+
raise FileNotFoundError(f"missing {host} manifest: {manifest_path}")
|
|
50
|
+
data = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
51
|
+
if not data.get("name") or not data.get("version"):
|
|
52
|
+
raise ValueError(f"{manifest_path} must contain name and version")
|
|
53
|
+
return data
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def load_cached_manifest(version_dir: Path, host: Host) -> dict[str, Any] | None:
|
|
57
|
+
manifest_path = version_dir / HOST_CONFIG[host]["manifest"]
|
|
58
|
+
if not manifest_path.is_file():
|
|
59
|
+
return None
|
|
60
|
+
try:
|
|
61
|
+
return json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
62
|
+
except json.JSONDecodeError:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def default_cache_root(host: Host) -> Path:
|
|
67
|
+
return Path.home() / HOST_CONFIG[host]["cache"]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def iter_version_dirs(
|
|
71
|
+
*,
|
|
72
|
+
cache_root: Path,
|
|
73
|
+
plugin_name: str,
|
|
74
|
+
marketplace: str | None,
|
|
75
|
+
) -> list[Path]:
|
|
76
|
+
if marketplace:
|
|
77
|
+
plugin_root = cache_root / marketplace / plugin_name
|
|
78
|
+
if not plugin_root.is_dir():
|
|
79
|
+
return []
|
|
80
|
+
return sorted(p for p in plugin_root.iterdir() if p.is_dir())
|
|
81
|
+
|
|
82
|
+
if not cache_root.is_dir():
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
versions: list[Path] = []
|
|
86
|
+
for market_dir in sorted(p for p in cache_root.iterdir() if p.is_dir()):
|
|
87
|
+
plugin_root = market_dir / plugin_name
|
|
88
|
+
if not plugin_root.is_dir():
|
|
89
|
+
continue
|
|
90
|
+
versions.extend(p for p in plugin_root.iterdir() if p.is_dir())
|
|
91
|
+
return sorted(versions)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def detect_in_use_versions(*, plugin_name: str) -> set[str]:
|
|
95
|
+
"""Cache version dir name(s) a live host session is currently loaded from.
|
|
96
|
+
|
|
97
|
+
Read from the host plugin-root env vars. The path is intentionally NOT
|
|
98
|
+
resolved: an in-use version dir may itself be a symlink (e.g. a local-dev
|
|
99
|
+
override pointing at a working tree), and we must protect its *cache* name,
|
|
100
|
+
not the symlink target's name. Only a path whose parent is this plugin's
|
|
101
|
+
cache dir counts, so an unrelated env value can't over-protect.
|
|
102
|
+
"""
|
|
103
|
+
names: set[str] = set()
|
|
104
|
+
for var in IN_USE_ENV_VARS:
|
|
105
|
+
root = os.environ.get(var)
|
|
106
|
+
if not root:
|
|
107
|
+
continue
|
|
108
|
+
p = Path(root)
|
|
109
|
+
if p.parent.name == plugin_name:
|
|
110
|
+
names.add(p.name)
|
|
111
|
+
return names
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def installed_pinned_versions(*, plugin_name: str, host: Host) -> set[str]:
|
|
115
|
+
"""Cache version dir names the host has pinned for this plugin across ALL
|
|
116
|
+
scopes/projects, read from its installed_plugins.json. These belong to other
|
|
117
|
+
projects or concurrent sessions and must never be pruned — deleting a pinned
|
|
118
|
+
version breaks that install on next load. Complements detect_in_use_versions
|
|
119
|
+
(this process only) for safe automated/session-start pruning."""
|
|
120
|
+
registry = (Path.home() / HOST_CONFIG[host]["cache"]).parent / "installed_plugins.json"
|
|
121
|
+
names: set[str] = set()
|
|
122
|
+
try:
|
|
123
|
+
data = json.loads(registry.read_text(encoding="utf-8"))
|
|
124
|
+
except (OSError, json.JSONDecodeError):
|
|
125
|
+
return names
|
|
126
|
+
for key, entries in (data.get("plugins") or {}).items():
|
|
127
|
+
if key.split("@", 1)[0] != plugin_name:
|
|
128
|
+
continue
|
|
129
|
+
for entry in entries or []:
|
|
130
|
+
install_path = entry.get("installPath")
|
|
131
|
+
if install_path:
|
|
132
|
+
names.add(Path(install_path).name)
|
|
133
|
+
return names
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def classify_versions(
|
|
137
|
+
*,
|
|
138
|
+
version_dirs: list[Path],
|
|
139
|
+
host: Host,
|
|
140
|
+
plugin_name: str,
|
|
141
|
+
keep_version: str,
|
|
142
|
+
protected_names: frozenset[str] = frozenset(),
|
|
143
|
+
) -> tuple[list[Path], list[Path], list[Path]]:
|
|
144
|
+
keep: list[Path] = []
|
|
145
|
+
stale: list[Path] = []
|
|
146
|
+
skipped: list[Path] = []
|
|
147
|
+
|
|
148
|
+
for version_dir in version_dirs:
|
|
149
|
+
# In-use / explicitly-protected dirs are kept by NAME, before any manifest
|
|
150
|
+
# check — a live session's root may be a symlink whose manifest reads a
|
|
151
|
+
# different version, but deleting it still breaks that session's hooks.
|
|
152
|
+
if version_dir.name in protected_names:
|
|
153
|
+
keep.append(version_dir)
|
|
154
|
+
continue
|
|
155
|
+
cached_manifest = load_cached_manifest(version_dir, host)
|
|
156
|
+
if cached_manifest is None or cached_manifest.get("name") != plugin_name:
|
|
157
|
+
skipped.append(version_dir)
|
|
158
|
+
continue
|
|
159
|
+
if cached_manifest.get("version") == keep_version and version_dir.name == keep_version:
|
|
160
|
+
keep.append(version_dir)
|
|
161
|
+
continue
|
|
162
|
+
stale.append(version_dir)
|
|
163
|
+
|
|
164
|
+
return keep, stale, skipped
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def remove_cache_entry(path: Path) -> None:
|
|
168
|
+
if path.is_symlink():
|
|
169
|
+
path.unlink()
|
|
170
|
+
return
|
|
171
|
+
shutil.rmtree(path)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def prune_host(
|
|
175
|
+
*,
|
|
176
|
+
host: Host,
|
|
177
|
+
source: Path,
|
|
178
|
+
cache_root: Path,
|
|
179
|
+
marketplace: str | None,
|
|
180
|
+
plugin_override: str | None,
|
|
181
|
+
keep_version_override: str | None,
|
|
182
|
+
protect: list[str] | None,
|
|
183
|
+
detect_in_use: bool,
|
|
184
|
+
protect_installed: bool,
|
|
185
|
+
apply: bool,
|
|
186
|
+
) -> tuple[dict[str, Any], int]:
|
|
187
|
+
manifest = load_manifest(source, host)
|
|
188
|
+
plugin_name = plugin_override or manifest["name"]
|
|
189
|
+
keep_version = keep_version_override or manifest["version"]
|
|
190
|
+
protected_names: set[str] = set(protect or [])
|
|
191
|
+
if detect_in_use:
|
|
192
|
+
protected_names.update(detect_in_use_versions(plugin_name=plugin_name))
|
|
193
|
+
if protect_installed:
|
|
194
|
+
protected_names.update(installed_pinned_versions(plugin_name=plugin_name, host=host))
|
|
195
|
+
version_dirs = iter_version_dirs(
|
|
196
|
+
cache_root=cache_root,
|
|
197
|
+
plugin_name=plugin_name,
|
|
198
|
+
marketplace=marketplace,
|
|
199
|
+
)
|
|
200
|
+
keep, stale, skipped = classify_versions(
|
|
201
|
+
version_dirs=version_dirs,
|
|
202
|
+
host=host,
|
|
203
|
+
plugin_name=plugin_name,
|
|
204
|
+
keep_version=keep_version,
|
|
205
|
+
protected_names=frozenset(protected_names),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
deleted: list[Path] = []
|
|
209
|
+
errors: list[dict[str, str]] = []
|
|
210
|
+
if apply:
|
|
211
|
+
for path in stale:
|
|
212
|
+
try:
|
|
213
|
+
remove_cache_entry(path)
|
|
214
|
+
deleted.append(path)
|
|
215
|
+
except OSError as exc:
|
|
216
|
+
errors.append({"path": str(path), "error": str(exc)})
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
"host": host,
|
|
220
|
+
"plugin": plugin_name,
|
|
221
|
+
"keep_version": keep_version,
|
|
222
|
+
"protected": sorted(protected_names),
|
|
223
|
+
"cache_root": str(cache_root),
|
|
224
|
+
"marketplace": marketplace,
|
|
225
|
+
"dry_run": not apply,
|
|
226
|
+
"kept": [str(p) for p in keep],
|
|
227
|
+
"stale": [str(p) for p in stale],
|
|
228
|
+
"deleted": [str(p) for p in deleted],
|
|
229
|
+
"skipped_unverified": [str(p) for p in skipped],
|
|
230
|
+
"errors": errors,
|
|
231
|
+
}, 1 if errors else 0
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def parse_args() -> argparse.Namespace:
|
|
235
|
+
p = argparse.ArgumentParser(description="Prune stale plugin cache versions.")
|
|
236
|
+
p.add_argument("--source", default=".", help="Plugin source repo root. Defaults to cwd.")
|
|
237
|
+
p.add_argument("--host", choices=["codex", "claude", "all"], default="all")
|
|
238
|
+
p.add_argument("--plugin", default=None, help="Plugin name override. Defaults to host manifest name.")
|
|
239
|
+
p.add_argument("--keep-version", default=None, help="Version to keep. Defaults to host manifest version.")
|
|
240
|
+
p.add_argument(
|
|
241
|
+
"--protect",
|
|
242
|
+
action="append",
|
|
243
|
+
default=None,
|
|
244
|
+
metavar="VERSION",
|
|
245
|
+
help="Cache version dir name to never prune (repeatable). Use for a version "
|
|
246
|
+
"an active session is running but the manifest no longer points at.",
|
|
247
|
+
)
|
|
248
|
+
p.add_argument(
|
|
249
|
+
"--no-detect-in-use",
|
|
250
|
+
action="store_true",
|
|
251
|
+
help="Disable auto-protecting the version dir the running host session is "
|
|
252
|
+
"loaded from (read from CLAUDE_PLUGIN_ROOT / CODEX_PLUGIN_ROOT).",
|
|
253
|
+
)
|
|
254
|
+
p.add_argument(
|
|
255
|
+
"--protect-installed",
|
|
256
|
+
action="store_true",
|
|
257
|
+
help="Protect every cache version pinned in the host's installed_plugins.json "
|
|
258
|
+
"(all scopes/projects), not just this process's in-use dir. Use for "
|
|
259
|
+
"automated/session-start pruning so concurrent or other-project installs "
|
|
260
|
+
"are never deleted.",
|
|
261
|
+
)
|
|
262
|
+
p.add_argument("--marketplace", default=None, help="Optional marketplace name for the selected host(s).")
|
|
263
|
+
p.add_argument("--codex-marketplace", default=None, help="Optional Codex marketplace name.")
|
|
264
|
+
p.add_argument("--claude-marketplace", default=None, help="Optional Claude marketplace name.")
|
|
265
|
+
p.add_argument("--cache-root", default=None, help="Cache root override for a single --host run.")
|
|
266
|
+
p.add_argument("--codex-cache-root", default=None, help="Codex cache root override.")
|
|
267
|
+
p.add_argument("--claude-cache-root", default=None, help="Claude cache root override.")
|
|
268
|
+
p.add_argument("--apply", action="store_true", help="Delete stale directories. Default is dry-run.")
|
|
269
|
+
p.add_argument("--json", action="store_true", help="Emit JSON report.")
|
|
270
|
+
return p.parse_args()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def selected_hosts(host_arg: str) -> list[Host]:
|
|
274
|
+
if host_arg == "all":
|
|
275
|
+
return ["codex", "claude"]
|
|
276
|
+
return [host_arg] # type: ignore[list-item]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def cache_root_for(args: argparse.Namespace, host: Host) -> Path:
|
|
280
|
+
specific = getattr(args, f"{host}_cache_root")
|
|
281
|
+
if args.cache_root and len(selected_hosts(args.host)) > 1:
|
|
282
|
+
raise ValueError("--cache-root can only be used with --host codex or --host claude")
|
|
283
|
+
value = specific or args.cache_root
|
|
284
|
+
if value:
|
|
285
|
+
return Path(value).expanduser().resolve()
|
|
286
|
+
return default_cache_root(host).expanduser().resolve()
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def marketplace_for(args: argparse.Namespace, host: Host) -> str | None:
|
|
290
|
+
return getattr(args, f"{host}_marketplace") or args.marketplace
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def print_human(reports: list[dict[str, Any]]) -> None:
|
|
294
|
+
for index, report in enumerate(reports):
|
|
295
|
+
if index:
|
|
296
|
+
print()
|
|
297
|
+
mode = "deleted" if not report["dry_run"] else "would delete"
|
|
298
|
+
print(
|
|
299
|
+
f"{report['host']} cache prune for {report['plugin']}; "
|
|
300
|
+
f"keeping {report['keep_version']}"
|
|
301
|
+
)
|
|
302
|
+
print(f"cache root: {report['cache_root']}")
|
|
303
|
+
if report["marketplace"]:
|
|
304
|
+
print(f"marketplace: {report['marketplace']}")
|
|
305
|
+
protected_extra = [v for v in report.get("protected", []) if v != report["keep_version"]]
|
|
306
|
+
if protected_extra:
|
|
307
|
+
print(f"protected (in-use/explicit): {', '.join(protected_extra)}")
|
|
308
|
+
if not report["stale"]:
|
|
309
|
+
print("No stale cache versions found.")
|
|
310
|
+
else:
|
|
311
|
+
print(f"Stale cache versions ({mode}):")
|
|
312
|
+
for path in report["stale"]:
|
|
313
|
+
print(f" {path}")
|
|
314
|
+
if report["skipped_unverified"]:
|
|
315
|
+
print("Skipped unverified cache directories:")
|
|
316
|
+
for path in report["skipped_unverified"]:
|
|
317
|
+
print(f" {path}")
|
|
318
|
+
if report["errors"]:
|
|
319
|
+
print("Errors:", file=sys.stderr)
|
|
320
|
+
for error in report["errors"]:
|
|
321
|
+
print(f" {error['path']}: {error['error']}", file=sys.stderr)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def main() -> int:
|
|
325
|
+
args = parse_args()
|
|
326
|
+
source = Path(args.source).resolve()
|
|
327
|
+
|
|
328
|
+
reports: list[dict[str, Any]] = []
|
|
329
|
+
exit_code = 0
|
|
330
|
+
try:
|
|
331
|
+
for host in selected_hosts(args.host):
|
|
332
|
+
report, code = prune_host(
|
|
333
|
+
host=host,
|
|
334
|
+
source=source,
|
|
335
|
+
cache_root=cache_root_for(args, host),
|
|
336
|
+
marketplace=marketplace_for(args, host),
|
|
337
|
+
plugin_override=args.plugin,
|
|
338
|
+
keep_version_override=args.keep_version,
|
|
339
|
+
protect=args.protect,
|
|
340
|
+
detect_in_use=not args.no_detect_in_use,
|
|
341
|
+
protect_installed=args.protect_installed,
|
|
342
|
+
apply=args.apply,
|
|
343
|
+
)
|
|
344
|
+
reports.append(report)
|
|
345
|
+
exit_code = max(exit_code, code)
|
|
346
|
+
except (FileNotFoundError, ValueError, json.JSONDecodeError) as exc:
|
|
347
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
348
|
+
return 2
|
|
349
|
+
|
|
350
|
+
if args.json:
|
|
351
|
+
payload: dict[str, Any] = reports[0] if len(reports) == 1 else {"reports": reports}
|
|
352
|
+
print(json.dumps(payload, indent=2))
|
|
353
|
+
else:
|
|
354
|
+
print_human(reports)
|
|
355
|
+
return exit_code
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if __name__ == "__main__":
|
|
359
|
+
sys.exit(main())
|