dsh-compound-engineering 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +160 -2
- package/README.zh.md +161 -0
- package/cordis.patch.yml +6 -0
- package/lib/frontmatter.js +167 -0
- package/lib/index.js +46 -0
- package/lib/provider.js +201 -0
- package/package.json +62 -4
- package/skills/CODING_STANDARDS.md +68 -0
- package/skills/ce-babysit-pr/SKILL.md +59 -0
- package/skills/ce-babysit-pr/references/branch-currency.md +22 -0
- package/skills/ce-babysit-pr/references/envelope.md +44 -0
- package/skills/ce-babysit-pr/references/pipeline.md +15 -0
- package/skills/ce-babysit-pr/references/report.md +20 -0
- package/skills/ce-babysit-pr/references/settle.md +37 -0
- package/skills/ce-babysit-pr/references/setup.md +33 -0
- package/skills/ce-babysit-pr/references/stack-commands.md +39 -0
- package/skills/ce-babysit-pr/references/stack.md +43 -0
- package/skills/ce-babysit-pr/references/tick.md +102 -0
- package/skills/ce-babysit-pr/references/watch-loop.md +222 -0
- package/skills/ce-babysit-pr/scripts/pr-snapshot +3656 -0
- package/skills/ce-bakeoff/SKILL.md +48 -0
- package/skills/ce-bakeoff/references/candidates.md +37 -0
- package/skills/ce-bakeoff/references/judging.md +19 -0
- package/skills/ce-bakeoff/references/output.md +11 -0
- package/skills/ce-bakeoff/references/verification.md +15 -0
- package/skills/ce-brainstorm/SKILL.md +69 -0
- package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
- package/skills/ce-brainstorm/references/approaches.md +57 -0
- package/skills/ce-brainstorm/references/bakeoff.md +9 -0
- package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
- package/skills/ce-brainstorm/references/brainstorm-sections.md +458 -0
- package/skills/ce-brainstorm/references/dialogue.md +89 -0
- package/skills/ce-brainstorm/references/handoff.md +187 -0
- package/skills/ce-brainstorm/references/html-rendering.md +638 -0
- package/skills/ce-brainstorm/references/interaction-rules.md +33 -0
- package/skills/ce-brainstorm/references/markdown-rendering.md +242 -0
- package/skills/ce-brainstorm/references/model-tiers.md +13 -0
- package/skills/ce-brainstorm/references/output-mode.md +26 -0
- package/skills/ce-brainstorm/references/phase-0.md +104 -0
- package/skills/ce-brainstorm/references/plan-write.md +32 -0
- package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
- package/skills/ce-brainstorm/references/reasoning-elevation.md +124 -0
- package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
- package/skills/ce-brainstorm/references/synthesis-summary.md +149 -0
- package/skills/ce-brainstorm/references/universal-brainstorming.md +72 -0
- package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
- package/skills/ce-brainstorm/references/visual-probes.md +164 -0
- package/skills/ce-brainstorm/scripts/elevation-dispatch.sh +288 -0
- package/skills/ce-brainstorm/scripts/light-webserver.js +1275 -0
- package/skills/ce-brainstorm/scripts/packs-resolve.py +737 -0
- package/skills/ce-brainstorm/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-code-review/SKILL.md +40 -0
- package/skills/ce-code-review/references/action-class-rubric.md +67 -0
- package/skills/ce-code-review/references/cross-model-recovery.md +38 -0
- package/skills/ce-code-review/references/cross-model-review.md +191 -0
- package/skills/ce-code-review/references/depth-paths.md +32 -0
- package/skills/ce-code-review/references/diff-scope.md +59 -0
- package/skills/ce-code-review/references/dispatch-reviewers.md +135 -0
- package/skills/ce-code-review/references/findings-schema.json +125 -0
- package/skills/ce-code-review/references/finish-input.md +118 -0
- package/skills/ce-code-review/references/finish-review.md +202 -0
- package/skills/ce-code-review/references/intent-and-plan.md +73 -0
- package/skills/ce-code-review/references/modes-and-output.md +156 -0
- package/skills/ce-code-review/references/persona-catalog.md +66 -0
- package/skills/ce-code-review/references/personas/adversarial-reviewer.md +111 -0
- package/skills/ce-code-review/references/personas/agent-native-reviewer.md +173 -0
- package/skills/ce-code-review/references/personas/api-contract-reviewer.md +44 -0
- package/skills/ce-code-review/references/personas/correctness-reviewer.md +48 -0
- package/skills/ce-code-review/references/personas/data-migration-reviewer.md +111 -0
- package/skills/ce-code-review/references/personas/deployment-verification-agent.md +157 -0
- package/skills/ce-code-review/references/personas/julik-frontend-races-reviewer.md +45 -0
- package/skills/ce-code-review/references/personas/learnings-researcher.md +260 -0
- package/skills/ce-code-review/references/personas/maintainability-reviewer.md +85 -0
- package/skills/ce-code-review/references/personas/performance-reviewer.md +45 -0
- package/skills/ce-code-review/references/personas/previous-comments-reviewer.md +59 -0
- package/skills/ce-code-review/references/personas/project-standards-reviewer.md +77 -0
- package/skills/ce-code-review/references/personas/reliability-reviewer.md +49 -0
- package/skills/ce-code-review/references/personas/security-reviewer.md +50 -0
- package/skills/ce-code-review/references/personas/swift-ios-reviewer.md +99 -0
- package/skills/ce-code-review/references/personas/testing-reviewer.md +51 -0
- package/skills/ce-code-review/references/review-output-template.md +172 -0
- package/skills/ce-code-review/references/scope.md +176 -0
- package/skills/ce-code-review/references/select-and-route.md +93 -0
- package/skills/ce-code-review/references/subagent-template.md +206 -0
- package/skills/ce-code-review/references/validator-batch-template.md +67 -0
- package/skills/ce-code-review/scripts/cross-model-adversarial-review.sh +1275 -0
- package/skills/ce-code-review/scripts/findings-mechanics.py +274 -0
- package/skills/ce-code-review/scripts/packs-resolve.py +737 -0
- package/skills/ce-code-review/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-code-review/scripts/review-scope.py +419 -0
- package/skills/ce-code-review/scripts/run-log.py +280 -0
- package/skills/ce-commit/SKILL.md +60 -0
- package/skills/ce-commit-push-pr/SKILL.md +69 -0
- package/skills/ce-commit-push-pr/references/apply-and-handoff.md +79 -0
- package/skills/ce-commit-push-pr/references/branch-creation.md +47 -0
- package/skills/ce-commit-push-pr/references/commit-and-push.md +26 -0
- package/skills/ce-commit-push-pr/references/compose.md +29 -0
- package/skills/ce-commit-push-pr/references/context.md +42 -0
- package/skills/ce-commit-push-pr/references/gh-stack-cli.md +83 -0
- package/skills/ce-commit-push-pr/references/pr-description-writing.md +208 -0
- package/skills/ce-commit-push-pr/references/stack-submit.md +81 -0
- package/skills/ce-compound/SKILL.md +79 -0
- package/skills/ce-compound/assets/resolution-template.md +96 -0
- package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
- package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
- package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
- package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
- package/skills/ce-compound/references/agents/session-historian.md +82 -0
- package/skills/ce-compound/references/assembly.md +127 -0
- package/skills/ce-compound/references/concepts-vocabulary.md +98 -0
- package/skills/ce-compound/references/enhancement.md +42 -0
- package/skills/ce-compound/references/grounding-validation.md +89 -0
- package/skills/ce-compound/references/lightweight.md +56 -0
- package/skills/ce-compound/references/modes.md +13 -0
- package/skills/ce-compound/references/refresh-and-discoverability.md +96 -0
- package/skills/ce-compound/references/report.md +115 -0
- package/skills/ce-compound/references/research.md +144 -0
- package/skills/ce-compound/references/schema.yaml +236 -0
- package/skills/ce-compound/references/session-history.md +73 -0
- package/skills/ce-compound/references/yaml-schema.md +130 -0
- package/skills/ce-compound/scripts/packs-resolve.py +737 -0
- package/skills/ce-compound/scripts/session-history/__pycache__/dsh_session.cpython-314.pyc +0 -0
- package/skills/ce-compound/scripts/session-history/discover-sessions.sh +290 -0
- package/skills/ce-compound/scripts/session-history/discover_dsh.py +123 -0
- package/skills/ce-compound/scripts/session-history/dsh_session.py +225 -0
- package/skills/ce-compound/scripts/session-history/extract-errors.py +178 -0
- package/skills/ce-compound/scripts/session-history/extract-metadata.py +301 -0
- package/skills/ce-compound/scripts/session-history/extract-skeleton.py +346 -0
- package/skills/ce-compound/scripts/validate-doc-claims.py +497 -0
- package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-compound-refresh/SKILL.md +88 -0
- package/skills/ce-compound-refresh/assets/resolution-template.md +96 -0
- package/skills/ce-compound-refresh/references/classify.md +42 -0
- package/skills/ce-compound-refresh/references/commit.md +7 -0
- package/skills/ce-compound-refresh/references/concepts-vocabulary.md +116 -0
- package/skills/ce-compound-refresh/references/discoverability.md +15 -0
- package/skills/ce-compound-refresh/references/investigate.md +23 -0
- package/skills/ce-compound-refresh/references/modes.md +27 -0
- package/skills/ce-compound-refresh/references/per-action-flows.md +142 -0
- package/skills/ce-compound-refresh/references/report.md +23 -0
- package/skills/ce-compound-refresh/references/schema.yaml +236 -0
- package/skills/ce-compound-refresh/references/scope.md +18 -0
- package/skills/ce-compound-refresh/references/worth-audit.md +35 -0
- package/skills/ce-compound-refresh/references/yaml-schema.md +130 -0
- package/skills/ce-compound-refresh/scripts/validate-doc-claims.py +497 -0
- package/skills/ce-compound-refresh/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-debug/SKILL.md +122 -0
- package/skills/ce-debug/references/anti-patterns.md +91 -0
- package/skills/ce-debug/references/defense-in-depth.md +39 -0
- package/skills/ce-debug/references/fix.md +21 -0
- package/skills/ce-debug/references/investigate.md +114 -0
- package/skills/ce-debug/references/investigation-techniques.md +384 -0
- package/skills/ce-debug/references/pipeline-mode.md +78 -0
- package/skills/ce-debug/references/post-fix-handoff.md +60 -0
- package/skills/ce-debug/references/return-to-caller.md +57 -0
- package/skills/ce-doc-review/SKILL.md +75 -0
- package/skills/ce-doc-review/references/bulk-preview.md +146 -0
- package/skills/ce-doc-review/references/cross-model-review.md +207 -0
- package/skills/ce-doc-review/references/decision-primer.md +47 -0
- package/skills/ce-doc-review/references/dispatch.md +56 -0
- package/skills/ce-doc-review/references/document-intake.md +44 -0
- package/skills/ce-doc-review/references/findings-schema.json +85 -0
- package/skills/ce-doc-review/references/modes.md +20 -0
- package/skills/ce-doc-review/references/open-questions-defer.md +158 -0
- package/skills/ce-doc-review/references/persona-selection.md +41 -0
- package/skills/ce-doc-review/references/personas/adversarial-document-reviewer.md +109 -0
- package/skills/ce-doc-review/references/personas/coherence-reviewer.md +69 -0
- package/skills/ce-doc-review/references/personas/design-lens-reviewer.md +48 -0
- package/skills/ce-doc-review/references/personas/feasibility-reviewer.md +50 -0
- package/skills/ce-doc-review/references/personas/product-lens-reviewer.md +85 -0
- package/skills/ce-doc-review/references/personas/scope-guardian-reviewer.md +83 -0
- package/skills/ce-doc-review/references/personas/security-lens-reviewer.md +41 -0
- package/skills/ce-doc-review/references/personas/whole-doc-reviewer.md +25 -0
- package/skills/ce-doc-review/references/rendering-floor.md +175 -0
- package/skills/ce-doc-review/references/review-output-template.md +123 -0
- package/skills/ce-doc-review/references/subagent-template.md +188 -0
- package/skills/ce-doc-review/references/synthesis-and-presentation.md +350 -0
- package/skills/ce-doc-review/references/walkthrough.md +347 -0
- package/skills/ce-doc-review/scripts/cross-model-doc-review.sh +1270 -0
- package/skills/ce-doc-review/scripts/packs-resolve.py +737 -0
- package/skills/ce-doc-review/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-dogfood/SKILL.md +66 -0
- package/skills/ce-dogfood/agents/openai.yaml +2 -0
- package/skills/ce-dogfood/references/dogfood-report-template.md +97 -0
- package/skills/ce-dogfood/references/phases.md +167 -0
- package/skills/ce-dogfood/references/test-matrix-taxonomy.md +62 -0
- package/skills/ce-dogfood/scripts/packs-resolve.py +737 -0
- package/skills/ce-explain/SKILL.md +81 -0
- package/skills/ce-explain/references/agents/behavior-trace-scout.md +19 -0
- package/skills/ce-explain/references/agents/work-recap-scout.md +23 -0
- package/skills/ce-explain/references/check-in.md +18 -0
- package/skills/ce-explain/references/destinations.md +35 -0
- package/skills/ce-explain/references/explainer-html.md +29 -0
- package/skills/ce-explain/references/explainer-markdown.md +24 -0
- package/skills/ce-explain/references/intake.md +50 -0
- package/skills/ce-explain/references/orchestration.md +46 -0
- package/skills/ce-handoff/SKILL.md +54 -0
- package/skills/ce-handoff/references/create.md +88 -0
- package/skills/ce-handoff/references/resume.md +46 -0
- package/skills/ce-ideate/SKILL.md +75 -0
- package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +166 -0
- package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
- package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
- package/skills/ce-ideate/references/decomposition.md +39 -0
- package/skills/ce-ideate/references/divergent-ideation.md +112 -0
- package/skills/ce-ideate/references/grounding.md +138 -0
- package/skills/ce-ideate/references/html-rendering.md +638 -0
- package/skills/ce-ideate/references/ideation-sections.md +191 -0
- package/skills/ce-ideate/references/issue-intelligence.md +44 -0
- package/skills/ce-ideate/references/markdown-rendering.md +242 -0
- package/skills/ce-ideate/references/output-mode.md +34 -0
- package/skills/ce-ideate/references/post-ideation-workflow.md +169 -0
- package/skills/ce-ideate/references/scope-gates.md +148 -0
- package/skills/ce-ideate/references/universal-ideation.md +119 -0
- package/skills/ce-ideate/references/user-research-artifacts.md +28 -0
- package/skills/ce-ideate/references/web-research-cache.md +56 -0
- package/skills/ce-noslop/SKILL.md +45 -0
- package/skills/ce-noslop/references/patterns.md +61 -0
- package/skills/ce-noslop/references/terminology.md +3 -0
- package/skills/ce-optimize/SKILL.md +64 -0
- package/skills/ce-optimize/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-optimize/references/agents/repo-research-analyst.md +258 -0
- package/skills/ce-optimize/references/example-expensive-benchmark-spec.yaml +88 -0
- package/skills/ce-optimize/references/example-hard-spec.yaml +64 -0
- package/skills/ce-optimize/references/example-judge-spec.yaml +83 -0
- package/skills/ce-optimize/references/example-text-target-spec.yaml +80 -0
- package/skills/ce-optimize/references/experiment-log-schema.yaml +563 -0
- package/skills/ce-optimize/references/experiment-prompt-template.md +143 -0
- package/skills/ce-optimize/references/judge-prompt-template.md +117 -0
- package/skills/ce-optimize/references/loop.md +286 -0
- package/skills/ce-optimize/references/measurement.md +168 -0
- package/skills/ce-optimize/references/optimize-spec-schema.yaml +570 -0
- package/skills/ce-optimize/references/persistence.md +93 -0
- package/skills/ce-optimize/references/spec.md +107 -0
- package/skills/ce-optimize/references/text-targets.md +51 -0
- package/skills/ce-optimize/references/usage-guide.md +147 -0
- package/skills/ce-optimize/references/wrap-up.md +54 -0
- package/skills/ce-optimize/scripts/decide.mjs +676 -0
- package/skills/ce-optimize/scripts/experiment-worktree.sh +330 -0
- package/skills/ce-optimize/scripts/measure.sh +140 -0
- package/skills/ce-optimize/scripts/parallel-probe.sh +134 -0
- package/skills/ce-plan/SKILL.md +60 -0
- package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +66 -0
- package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
- package/skills/ce-plan/references/agents/best-practices-researcher.md +118 -0
- package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-plan/references/agents/framework-docs-researcher.md +97 -0
- package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
- package/skills/ce-plan/references/agents/learnings-researcher.md +268 -0
- package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
- package/skills/ce-plan/references/agents/repo-research-analyst.md +262 -0
- package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
- package/skills/ce-plan/references/agents/slack-researcher.md +131 -0
- package/skills/ce-plan/references/agents/spec-flow-analyzer.md +84 -0
- package/skills/ce-plan/references/agents/web-researcher.md +125 -0
- package/skills/ce-plan/references/approach-altitude.md +55 -0
- package/skills/ce-plan/references/bakeoff.md +9 -0
- package/skills/ce-plan/references/deepening-workflow.md +263 -0
- package/skills/ce-plan/references/final-review.md +134 -0
- package/skills/ce-plan/references/html-rendering.md +638 -0
- package/skills/ce-plan/references/intake.md +158 -0
- package/skills/ce-plan/references/markdown-rendering.md +242 -0
- package/skills/ce-plan/references/output-contracts.md +29 -0
- package/skills/ce-plan/references/output-mode.md +45 -0
- package/skills/ce-plan/references/plan-handoff.md +116 -0
- package/skills/ce-plan/references/plan-sections.md +489 -0
- package/skills/ce-plan/references/reasoning-elevation.md +124 -0
- package/skills/ce-plan/references/research.md +211 -0
- package/skills/ce-plan/references/resume.md +61 -0
- package/skills/ce-plan/references/settled-decisions.md +45 -0
- package/skills/ce-plan/references/structure.md +207 -0
- package/skills/ce-plan/references/synthesis-summary.md +260 -0
- package/skills/ce-plan/references/universal-planning.md +170 -0
- package/skills/ce-plan/scripts/elevation-dispatch.sh +288 -0
- package/skills/ce-plan/scripts/packs-resolve.py +737 -0
- package/skills/ce-plan/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-polish/SKILL.md +22 -0
- package/skills/ce-polish/agents/openai.yaml +2 -0
- package/skills/ce-polish/references/dev-server-astro.md +58 -0
- package/skills/ce-polish/references/dev-server-detection.md +40 -0
- package/skills/ce-polish/references/dev-server-next.md +61 -0
- package/skills/ce-polish/references/dev-server-nuxt.md +58 -0
- package/skills/ce-polish/references/dev-server-procfile.md +59 -0
- package/skills/ce-polish/references/dev-server-rails.md +50 -0
- package/skills/ce-polish/references/dev-server-remix.md +58 -0
- package/skills/ce-polish/references/dev-server-sveltekit.md +58 -0
- package/skills/ce-polish/references/dev-server-vite.md +48 -0
- package/skills/ce-polish/references/install-riffrec.md +35 -0
- package/skills/ce-polish/references/launch-json-schema.md +177 -0
- package/skills/ce-polish/references/live-loop.md +98 -0
- package/skills/ce-polish/references/live-remote.md +35 -0
- package/skills/ce-polish/references/live-start.md +93 -0
- package/skills/ce-polish/references/live-stream-contract.md +253 -0
- package/skills/ce-polish/references/run.md +67 -0
- package/skills/ce-polish/scripts/detect-project-type.sh +277 -0
- package/skills/ce-polish/scripts/detect-riffrec.sh +225 -0
- package/skills/ce-polish/scripts/live-endpoint.js +2841 -0
- package/skills/ce-polish/scripts/read-launch-json.sh +117 -0
- package/skills/ce-polish/scripts/resolve-package-manager.sh +95 -0
- package/skills/ce-polish/scripts/resolve-port.sh +307 -0
- package/skills/ce-pov/SKILL.md +64 -0
- package/skills/ce-pov/references/agents/external-evidence-researcher.md +26 -0
- package/skills/ce-pov/references/agents/pov-peer.md +43 -0
- package/skills/ce-pov/references/agents/precedent-activity-scout.md +21 -0
- package/skills/ce-pov/references/agents/project-grounding-scout.md +32 -0
- package/skills/ce-pov/references/boundaries.md +36 -0
- package/skills/ce-pov/references/cross-model-panel.md +596 -0
- package/skills/ce-pov/references/followup.md +9 -0
- package/skills/ce-pov/references/grounding.md +47 -0
- package/skills/ce-pov/references/intake.md +61 -0
- package/skills/ce-pov/references/invocation.md +47 -0
- package/skills/ce-pov/references/method.md +68 -0
- package/skills/ce-pov/references/pov-schema.json +56 -0
- package/skills/ce-pov/references/report.md +7 -0
- package/skills/ce-pov/scripts/cross-model-pov.sh +1110 -0
- package/skills/ce-pov/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-product-pulse/SKILL.md +81 -0
- package/skills/ce-product-pulse/agents/openai.yaml +2 -0
- package/skills/ce-product-pulse/references/config.md +20 -0
- package/skills/ce-product-pulse/references/interview.md +260 -0
- package/skills/ce-product-pulse/references/report-template.md +89 -0
- package/skills/ce-product-pulse/references/run.md +46 -0
- package/skills/ce-product-pulse/references/setup.md +43 -0
- package/skills/ce-promote/SKILL.md +90 -0
- package/skills/ce-promote/agents/openai.yaml +2 -0
- package/skills/ce-promote/references/spiral-cli.md +149 -0
- package/skills/ce-proof/SKILL.md +65 -0
- package/skills/ce-proof/references/api.md +176 -0
- package/skills/ce-proof/references/workflows.md +124 -0
- package/skills/ce-prototype/SKILL.md +59 -0
- package/skills/ce-prototype/assets/annotate.css +206 -0
- package/skills/ce-prototype/assets/annotate.js +837 -0
- package/skills/ce-prototype/references/annotation-loop.md +32 -0
- package/skills/ce-prototype/references/build.md +39 -0
- package/skills/ce-prototype/references/craft-floor.md +43 -0
- package/skills/ce-prototype/references/preview.md +118 -0
- package/skills/ce-prototype/references/scoping.md +38 -0
- package/skills/ce-prototype/references/write-back.md +37 -0
- package/skills/ce-prototype/scripts/light-webserver.js +1275 -0
- package/skills/ce-resolve-pr-feedback/SKILL.md +60 -0
- package/skills/ce-resolve-pr-feedback/references/agents/pr-comment-resolver.md +59 -0
- package/skills/ce-resolve-pr-feedback/references/evaluation-rubric.md +141 -0
- package/skills/ce-resolve-pr-feedback/references/full-mode.md +336 -0
- package/skills/ce-resolve-pr-feedback/references/pipeline-mode.md +23 -0
- package/skills/ce-resolve-pr-feedback/references/resume.md +60 -0
- package/skills/ce-resolve-pr-feedback/references/return-to-caller.md +111 -0
- package/skills/ce-resolve-pr-feedback/references/targeted-mode.md +48 -0
- package/skills/ce-resolve-pr-feedback/scripts/get-pr-comments +176 -0
- package/skills/ce-resolve-pr-feedback/scripts/get-thread-for-comment +84 -0
- package/skills/ce-resolve-pr-feedback/scripts/pending-feedback.py +287 -0
- package/skills/ce-resolve-pr-feedback/scripts/reply-to-pr-thread +57 -0
- package/skills/ce-resolve-pr-feedback/scripts/resolve-pr-thread +23 -0
- package/skills/ce-retune/SKILL.md +42 -0
- package/skills/ce-retune/agents/openai.yaml +2 -0
- package/skills/ce-retune/references/baseline-mining.md +122 -0
- package/skills/ce-retune/references/corpus-audit.md +128 -0
- package/skills/ce-retune/references/cut-passes.md +129 -0
- package/skills/ce-retune/references/halt-taxonomy.md +123 -0
- package/skills/ce-retune/references/noise-floor.md +131 -0
- package/skills/ce-retune/references/workflow-shapes.md +73 -0
- package/skills/ce-riffrec-feedback-analysis/SKILL.md +33 -0
- package/skills/ce-riffrec-feedback-analysis/references/analyzer.md +18 -0
- package/skills/ce-riffrec-feedback-analysis/references/compound-engineering-feedback-format.md +116 -0
- package/skills/ce-riffrec-feedback-analysis/references/extensive-analysis.md +98 -0
- package/skills/ce-riffrec-feedback-analysis/references/install-riffrec.md +27 -0
- package/skills/ce-riffrec-feedback-analysis/references/quick-bug-report.md +38 -0
- package/skills/ce-riffrec-feedback-analysis/scripts/analyze_riffrec_zip.py +1306 -0
- package/skills/ce-setup/SKILL.md +103 -0
- package/skills/ce-setup/agents/openai.yaml +2 -0
- package/skills/ce-setup/assets/compounding-directive.md +11 -0
- package/skills/ce-setup/assets/noslop-directive.md +7 -0
- package/skills/ce-setup/assets/pack-rule-template.md +11 -0
- package/skills/ce-setup/references/acpx-cache-warm.md +14 -0
- package/skills/ce-setup/references/config-template.yaml +210 -0
- package/skills/ce-setup/references/legacy-codex-tool-map.md +15 -0
- package/skills/ce-setup/references/pack-scaffold.md +45 -0
- package/skills/ce-setup/references/repo-fixes.md +121 -0
- package/skills/ce-setup/scripts/check-health +929 -0
- package/skills/ce-setup/scripts/packs-resolve.py +737 -0
- package/skills/ce-simplify-code/SKILL.md +76 -0
- package/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
- package/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
- package/skills/ce-simplify-code/references/personas/comment-reviewer.md +24 -0
- package/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
- package/skills/ce-simplify-code/references/personas/test-value-reviewer.md +23 -0
- package/skills/ce-simplify-code/references/reviewer-dispatch.md +11 -0
- package/skills/ce-simplify-code/references/standalone-report.md +25 -0
- package/skills/ce-strategy/SKILL.md +61 -0
- package/skills/ce-strategy/references/grounding.md +22 -0
- package/skills/ce-strategy/references/interview.md +179 -0
- package/skills/ce-strategy/references/strategy-template.md +94 -0
- package/skills/ce-strategy/references/update-run.md +33 -0
- package/skills/ce-sweep/SKILL.md +93 -0
- package/skills/ce-sweep/agents/openai.yaml +2 -0
- package/skills/ce-sweep/references/agents/media-analyzer.md +54 -0
- package/skills/ce-sweep/references/interview.md +208 -0
- package/skills/ce-sweep/references/model-tiers.md +13 -0
- package/skills/ce-sweep/references/plan-template.md +62 -0
- package/skills/ce-sweep/references/run.md +126 -0
- package/skills/ce-sweep/references/sources/email.md +57 -0
- package/skills/ce-sweep/references/sources/github-issues.md +59 -0
- package/skills/ce-sweep/references/sources/slack.md +61 -0
- package/skills/ce-sweep/references/state-schema.md +157 -0
- package/skills/ce-sweep/references/subagent-template.md +60 -0
- package/skills/ce-sweep/scripts/analyze_riffrec_zip.py +1306 -0
- package/skills/ce-sweep/scripts/sweep-state.py +793 -0
- package/skills/ce-test-browser/SKILL.md +50 -0
- package/skills/ce-test-browser/references/agent-browser-driver.md +47 -0
- package/skills/ce-test-browser/references/pipeline-orchestration.md +45 -0
- package/skills/ce-test-browser/references/route-and-report.md +129 -0
- package/skills/ce-test-browser/scripts/resolve-port.sh +41 -0
- package/skills/ce-test-xcode/SKILL.md +26 -0
- package/skills/ce-test-xcode/agents/openai.yaml +2 -0
- package/skills/ce-test-xcode/references/setup-and-build.md +32 -0
- package/skills/ce-test-xcode/references/test-and-report.md +64 -0
- package/skills/ce-work/SKILL.md +62 -0
- package/skills/ce-work/references/agents/figma-design-sync.md +27 -0
- package/skills/ce-work/references/agents/implementation-worker.md +17 -0
- package/skills/ce-work/references/cross-model-execution.md +191 -0
- package/skills/ce-work/references/execution-engines.md +64 -0
- package/skills/ce-work/references/execution-strategy.md +73 -0
- package/skills/ce-work/references/implementation-loop.md +107 -0
- package/skills/ce-work/references/implementation-result-schema.json +52 -0
- package/skills/ce-work/references/input-triage.md +50 -0
- package/skills/ce-work/references/non-code-execution.md +23 -0
- package/skills/ce-work/references/return-to-caller.md +33 -0
- package/skills/ce-work/references/review-findings-followup.md +82 -0
- package/skills/ce-work/references/shipping-workflow.md +89 -0
- package/skills/ce-work/references/tracker-defer.md +150 -0
- package/skills/ce-work/references/work-intake.md +51 -0
- package/skills/ce-work/references/workspace-setup.md +27 -0
- package/skills/ce-work/scripts/__pycache__/unit_workspace_jobs.cpython-314.pyc +0 -0
- package/skills/ce-work/scripts/__pycache__/unit_workspace_state.cpython-314.pyc +0 -0
- package/skills/ce-work/scripts/cross-model-work.sh +1385 -0
- package/skills/ce-work/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-work/scripts/unit-workspace.py +215 -0
- package/skills/ce-work/scripts/unit_workspace_ignored.py +93 -0
- package/skills/ce-work/scripts/unit_workspace_integration.py +677 -0
- package/skills/ce-work/scripts/unit_workspace_jobs.py +1216 -0
- package/skills/ce-work/scripts/unit_workspace_lifecycle.py +981 -0
- package/skills/ce-work/scripts/unit_workspace_state.py +1379 -0
- package/skills/ce-work/scripts/unit_workspace_transaction.py +612 -0
- package/skills/ce-worktree/SKILL.md +54 -0
- package/skills/lfg/SKILL.md +59 -0
- package/skills/lfg/references/debug-return.md +25 -0
- package/skills/lfg/references/intake.md +30 -0
- package/skills/lfg/references/next-work-handoff.md +96 -0
- package/skills/lfg/references/plan-brief.md +36 -0
- package/skills/lfg/references/review-followup.md +78 -0
- package/skills/lfg/references/shipping.md +65 -0
- package/skills/lfg/references/stage-routing.md +49 -0
- package/skills/lfg/references/task-visibility.md +11 -0
- package/skills/lfg/references/tracker-defer.md +150 -0
- package/skills/lfg/references/work-return.md +27 -0
- package/skills/wtf/SKILL.md +33 -0
- package/skills/wtf/agents/openai.yaml +2 -0
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Resolve the Compound Packs declared in this repo's CE config into pack roots.
|
|
3
|
+
|
|
4
|
+
Reads the `packs:` list from `<repo-root>/.compound-engineering/config.yaml`
|
|
5
|
+
and `config.local.yaml` (both layers concatenate; local adds, never replaces),
|
|
6
|
+
validates each entry, resolves path and git sources, enumerates the packs each
|
|
7
|
+
source publishes, applies selection, and prints one JSON object to stdout:
|
|
8
|
+
|
|
9
|
+
{"roots": [{"id": "...", "dir": "/abs/path", "nested_rule_shaped": 0}],
|
|
10
|
+
"warnings": [...], "errors": [...], "entries": <number of parsed packs entries>}
|
|
11
|
+
|
|
12
|
+
`nested_rule_shaped` counts the rule-shaped `.md` files one level below the
|
|
13
|
+
pack's top level. Discovery never reads them (subdirectories are storage), so
|
|
14
|
+
the count is informational and is reported by the health check, not warned.
|
|
15
|
+
|
|
16
|
+
Exit 0 whenever resolution ran (per-entry failures are data in `errors` /
|
|
17
|
+
`warnings`); non-zero only when the resolver itself cannot run. Consumers treat
|
|
18
|
+
`errors` as loud per-entry configuration problems and `warnings` as degraded
|
|
19
|
+
availability (e.g. an unreachable git source skipped per the warn-and-continue
|
|
20
|
+
contract).
|
|
21
|
+
|
|
22
|
+
`--declared-only` answers the config-only question without touching git or the
|
|
23
|
+
cache: it parses both config layers, shape-checks each entry, and prints
|
|
24
|
+
|
|
25
|
+
{"declared": <bool|null>, "entries": <n>, "errors": [...]}
|
|
26
|
+
|
|
27
|
+
where `declared` is true when any entry parsed or the `packs:` block is
|
|
28
|
+
malformed (a broken declaration is still a declaration the consumer should
|
|
29
|
+
surface), false when neither config layer names packs, and null when no
|
|
30
|
+
repository -- and so no config -- could be located.
|
|
31
|
+
|
|
32
|
+
Entry shape (documented subset -- anything else under `packs:` is a loud error):
|
|
33
|
+
|
|
34
|
+
packs:
|
|
35
|
+
- source: packs/local-rules # repo-relative path
|
|
36
|
+
- source: ~/packs/kk-style # ~ or absolute path
|
|
37
|
+
- source: https://github.com/o/r # git URL: ref required
|
|
38
|
+
ref: v1.2.0 # tag, sha, or branch
|
|
39
|
+
path: packs # optional subfolder (git only)
|
|
40
|
+
pack: [rails, inertia] # one id, a list, or omit = all
|
|
41
|
+
id: rails-core # rename (single-pack entries)
|
|
42
|
+
- source: https://github.com/o/r/tree/main/packs # tree-URL sugar
|
|
43
|
+
|
|
44
|
+
Git sources cache under `<scratch-root>/ce-packs/<sha256(url\\nref)>` with an
|
|
45
|
+
atomic temp-clone-then-rename, so a keyed path's existence proves a complete
|
|
46
|
+
clone. All git subprocesses run non-interactively (GIT_TERMINAL_PROMPT=0, ssh
|
|
47
|
+
BatchMode, bounded timeout): missing credentials degrade to a warning, never a
|
|
48
|
+
hang. A missing `git` binary degrades git sources only (each warns and is
|
|
49
|
+
skipped); path sources still resolve, with the repository located by walking up
|
|
50
|
+
from the working directory to the nearest `.git` entry. Environment overrides:
|
|
51
|
+
CE_PACKS_CACHE_ROOT (cache base for tests), CE_PACKS_GIT_TIMEOUT (seconds,
|
|
52
|
+
default 60).
|
|
53
|
+
|
|
54
|
+
A published pack is a directory a consumer lists and reads itself, so nothing
|
|
55
|
+
in it may link outside its source: a pack whose tree holds such a link is not
|
|
56
|
+
published (a loud per-entry error), never trimmed a file at a time.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from __future__ import annotations
|
|
60
|
+
|
|
61
|
+
import argparse
|
|
62
|
+
import functools
|
|
63
|
+
import hashlib
|
|
64
|
+
import json
|
|
65
|
+
import os
|
|
66
|
+
import re
|
|
67
|
+
import shutil
|
|
68
|
+
import stat
|
|
69
|
+
import subprocess
|
|
70
|
+
import sys
|
|
71
|
+
import tempfile
|
|
72
|
+
|
|
73
|
+
IS_WINDOWS = os.name == "nt"
|
|
74
|
+
_uid_getter = getattr(os, "geteuid", None) or getattr(os, "getuid", None)
|
|
75
|
+
_EFFECTIVE_UID = _uid_getter() if _uid_getter is not None else None
|
|
76
|
+
GIT_TIMEOUT = float(os.environ.get("CE_PACKS_GIT_TIMEOUT") or 60)
|
|
77
|
+
|
|
78
|
+
CONFIG_FILES = ("config.yaml", "config.local.yaml")
|
|
79
|
+
KNOWN_KEYS = {"source", "ref", "path", "pack", "id"}
|
|
80
|
+
SCALAR_KEYS = ("source", "ref", "path", "id") # every known key but the list-valued `pack`
|
|
81
|
+
_TREE_URL_RE = re.compile(
|
|
82
|
+
r"^(?P<base>https?://github\.com/[^/\s]+/[^/\s]+?)(?:\.git)?/tree/(?P<ref>[^/\s]+)(?:/(?P<path>[^\s]*))?/?$"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _is_git_url(source: str) -> bool:
|
|
87
|
+
return bool(
|
|
88
|
+
re.match(r"^(https?|ssh|git|file)://", source) or re.match(r"^[\w.-]+@[\w.-]+:", source)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _within(path: str, parent: str) -> bool:
|
|
93
|
+
"""True when `path` is `parent` or lies below it (both already realpath'd)."""
|
|
94
|
+
return path == parent or path.startswith(parent + os.sep)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# --- scratch root (peer-job-runner shape: probe /tmp, fall back to TMPDIR) ---
|
|
98
|
+
|
|
99
|
+
def _owned_dir(path: str) -> bool:
|
|
100
|
+
"""Directory, not a symlink, owned by the effective uid (POSIX)."""
|
|
101
|
+
try:
|
|
102
|
+
st = os.lstat(path)
|
|
103
|
+
except OSError:
|
|
104
|
+
return False
|
|
105
|
+
if not stat.S_ISDIR(st.st_mode):
|
|
106
|
+
return False
|
|
107
|
+
if _EFFECTIVE_UID is not None and st.st_uid != _EFFECTIVE_UID:
|
|
108
|
+
return False
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _private_root_usable(path: str) -> bool:
|
|
113
|
+
"""Create or repair `path` as a private (0700) root owned by this user."""
|
|
114
|
+
try:
|
|
115
|
+
os.mkdir(path, 0o700)
|
|
116
|
+
except FileExistsError:
|
|
117
|
+
pass
|
|
118
|
+
except OSError:
|
|
119
|
+
return False
|
|
120
|
+
if not IS_WINDOWS:
|
|
121
|
+
if not _owned_dir(path):
|
|
122
|
+
return False
|
|
123
|
+
try:
|
|
124
|
+
os.chmod(path, 0o700)
|
|
125
|
+
except OSError:
|
|
126
|
+
return False
|
|
127
|
+
return os.path.isdir(path) and os.access(path, os.W_OK)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _trusted_checkout(path: str) -> bool:
|
|
131
|
+
"""A real directory (never a symlink) that this user owns; ownership is POSIX-only."""
|
|
132
|
+
if os.path.islink(path) or not os.path.isdir(path):
|
|
133
|
+
return False
|
|
134
|
+
return IS_WINDOWS or _owned_dir(path)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@functools.lru_cache(maxsize=None)
|
|
138
|
+
def cache_base() -> str | None:
|
|
139
|
+
configured = os.environ.get("CE_PACKS_CACHE_ROOT")
|
|
140
|
+
if configured:
|
|
141
|
+
root = os.path.abspath(configured)
|
|
142
|
+
os.makedirs(root, exist_ok=True)
|
|
143
|
+
return root
|
|
144
|
+
if IS_WINDOWS:
|
|
145
|
+
base = os.environ.get("LOCALAPPDATA") or tempfile.gettempdir()
|
|
146
|
+
root = os.path.join(base, "compound-engineering-packs")
|
|
147
|
+
return root if _private_root_usable(root) else None
|
|
148
|
+
if _EFFECTIVE_UID is None:
|
|
149
|
+
return None
|
|
150
|
+
for base in ("/tmp", os.environ.get("TMPDIR") or "/tmp"):
|
|
151
|
+
root = os.path.join(base, f"compound-engineering-{_EFFECTIVE_UID}")
|
|
152
|
+
if _private_root_usable(root):
|
|
153
|
+
packs = os.path.join(root, "ce-packs")
|
|
154
|
+
if _private_root_usable(packs):
|
|
155
|
+
return packs
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# --- minimal YAML reader for the documented packs: subset --------------------
|
|
160
|
+
|
|
161
|
+
def _strip_comment(line: str) -> str:
|
|
162
|
+
"""Drop a trailing comment (a # preceded by whitespace, outside quotes).
|
|
163
|
+
|
|
164
|
+
A quote toggles quoted state only when it opens a value (start of line or
|
|
165
|
+
after `: `/`- `/`[`/`,`) or closes one it opened -- a mid-word apostrophe
|
|
166
|
+
(``it's``) is ordinary content and must not absorb a later comment.
|
|
167
|
+
"""
|
|
168
|
+
out, quote = [], ""
|
|
169
|
+
for i, ch in enumerate(line):
|
|
170
|
+
prev = line[i - 1] if i else " "
|
|
171
|
+
if quote:
|
|
172
|
+
if ch == quote:
|
|
173
|
+
quote = ""
|
|
174
|
+
elif ch in "'\"" and prev in " \t[,:":
|
|
175
|
+
quote = ch
|
|
176
|
+
elif ch == "#" and prev in " \t":
|
|
177
|
+
break
|
|
178
|
+
out.append(ch)
|
|
179
|
+
return "".join(out).rstrip()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _scalar(raw: str):
|
|
183
|
+
raw = raw.strip()
|
|
184
|
+
if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in "'\"":
|
|
185
|
+
return raw[1:-1]
|
|
186
|
+
if raw.lower() in ("true", "false"):
|
|
187
|
+
return raw.lower() == "true"
|
|
188
|
+
return raw
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _parse_value(raw: str):
|
|
192
|
+
raw = raw.strip()
|
|
193
|
+
if raw.startswith("[") and raw.endswith("]"):
|
|
194
|
+
inner = raw[1:-1].strip()
|
|
195
|
+
return [] if not inner else [_scalar(part) for part in inner.split(",")]
|
|
196
|
+
return _scalar(raw)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def parse_packs_block(path: str, errors: list) -> list:
|
|
200
|
+
"""Return the entry dicts under this file's top-level `packs:` key."""
|
|
201
|
+
if not os.path.isfile(path):
|
|
202
|
+
return []
|
|
203
|
+
with open(path, encoding="utf-8-sig", errors="replace") as fh:
|
|
204
|
+
lines = fh.read().splitlines()
|
|
205
|
+
entries, in_packs, current, pending_list_key = [], False, None, None
|
|
206
|
+
for lineno, raw in enumerate(lines, 1):
|
|
207
|
+
line = _strip_comment(raw)
|
|
208
|
+
if not line.strip():
|
|
209
|
+
continue
|
|
210
|
+
indent = len(line) - len(line.lstrip())
|
|
211
|
+
if indent == 0 and not (in_packs and line.lstrip().startswith("-")):
|
|
212
|
+
# A new top-level key ends the packs block; a zero-indent list item
|
|
213
|
+
# (`- source: ...`) is still part of it -- YAML allows both styles.
|
|
214
|
+
key, sep, rest = line.partition(":")
|
|
215
|
+
in_packs = bool(sep) and key.strip() == "packs" and rest.strip() in ("", "[]")
|
|
216
|
+
if sep and key.strip() == "packs" and not in_packs:
|
|
217
|
+
# `packs: <inline value>` is malformed, not absent: say so rather
|
|
218
|
+
# than letting a flow list or a bare path declare nothing.
|
|
219
|
+
errors.append(
|
|
220
|
+
f"{os.path.basename(path)}:{lineno}: `packs:` must be a block list of"
|
|
221
|
+
f" `- source: ...` entries (got `{line.strip()}`)"
|
|
222
|
+
)
|
|
223
|
+
current, pending_list_key = None, None
|
|
224
|
+
continue
|
|
225
|
+
if not in_packs:
|
|
226
|
+
continue
|
|
227
|
+
stripped = line.strip()
|
|
228
|
+
loc = f"{os.path.basename(path)}:{lineno}"
|
|
229
|
+
if stripped.startswith("- ") or stripped == "-":
|
|
230
|
+
body = stripped[1:].strip()
|
|
231
|
+
if pending_list_key and current is not None and ":" not in body:
|
|
232
|
+
current[pending_list_key].append(_scalar(body))
|
|
233
|
+
continue
|
|
234
|
+
current, pending_list_key = {"_origin": os.path.basename(path), "_line": lineno}, None
|
|
235
|
+
entries.append(current)
|
|
236
|
+
if body:
|
|
237
|
+
if ":" not in body:
|
|
238
|
+
errors.append(f"{loc}: unrecognized packs entry `{stripped}` -- expected `key: value`")
|
|
239
|
+
continue
|
|
240
|
+
key, _, val = body.partition(":")
|
|
241
|
+
_set_key(current, key.strip(), val, loc, errors)
|
|
242
|
+
continue
|
|
243
|
+
if current is None:
|
|
244
|
+
errors.append(f"{loc}: unrecognized line under packs: `{stripped}` -- expected a `- source: ...` entry")
|
|
245
|
+
continue
|
|
246
|
+
if ":" not in stripped:
|
|
247
|
+
errors.append(f"{loc}: unrecognized line under packs: `{stripped}` -- expected `key: value`")
|
|
248
|
+
continue
|
|
249
|
+
key, _, val = stripped.partition(":")
|
|
250
|
+
key = key.strip()
|
|
251
|
+
if val.strip() == "" and key == "pack":
|
|
252
|
+
current[key] = []
|
|
253
|
+
pending_list_key = key
|
|
254
|
+
continue
|
|
255
|
+
pending_list_key = None
|
|
256
|
+
_set_key(current, key, val, loc, errors)
|
|
257
|
+
return entries
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _set_key(entry: dict, key: str, raw_val: str, loc: str, errors: list) -> None:
|
|
261
|
+
if key not in KNOWN_KEYS:
|
|
262
|
+
errors.append(f"{loc}: unknown packs entry key `{key}:` -- accepted keys: {', '.join(sorted(KNOWN_KEYS))}")
|
|
263
|
+
return
|
|
264
|
+
entry[key] = _parse_value(raw_val)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# --- git ---------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
@functools.lru_cache(maxsize=None)
|
|
270
|
+
def _git_env() -> dict:
|
|
271
|
+
env = dict(os.environ)
|
|
272
|
+
env["GIT_TERMINAL_PROMPT"] = "0"
|
|
273
|
+
env["GIT_ASKPASS"] = env.get("GIT_ASKPASS") or "true"
|
|
274
|
+
ssh = env.get("GIT_SSH_COMMAND") or "ssh"
|
|
275
|
+
if "BatchMode" not in ssh:
|
|
276
|
+
env["GIT_SSH_COMMAND"] = ssh + " -o BatchMode=yes"
|
|
277
|
+
return env
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _run_git(args: list, cwd: str | None = None):
|
|
281
|
+
return subprocess.run(
|
|
282
|
+
["git", *args], cwd=cwd, env=_git_env(), timeout=GIT_TIMEOUT,
|
|
283
|
+
capture_output=True, text=True,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def resolve_git_source(url: str, ref: str, warnings: list, label: str) -> str | None:
|
|
288
|
+
"""Return the cached checkout dir for url@ref, cloning on miss. None = warn+skip."""
|
|
289
|
+
if shutil.which("git") is None:
|
|
290
|
+
warnings.append(f"{label}: git binary not found; source skipped")
|
|
291
|
+
return None
|
|
292
|
+
base = cache_base()
|
|
293
|
+
if base is None:
|
|
294
|
+
warnings.append(f"{label}: no writable cache root for git sources; source skipped")
|
|
295
|
+
return None
|
|
296
|
+
key = hashlib.sha256(f"{url}\n{ref}".encode()).hexdigest()
|
|
297
|
+
dest = os.path.join(base, key)
|
|
298
|
+
if os.path.lexists(dest):
|
|
299
|
+
if _trusted_checkout(dest):
|
|
300
|
+
return dest
|
|
301
|
+
warnings.append(f"{label}: cached checkout {dest} is a symlink or not owned by this user; refetching")
|
|
302
|
+
# rmtree refuses to follow a symlink (and ignores a plain file); unlink
|
|
303
|
+
# those explicitly so a planted link is removed, never its target.
|
|
304
|
+
shutil.rmtree(dest, ignore_errors=True)
|
|
305
|
+
if os.path.islink(dest) or os.path.isfile(dest):
|
|
306
|
+
try:
|
|
307
|
+
os.unlink(dest)
|
|
308
|
+
except OSError:
|
|
309
|
+
pass
|
|
310
|
+
if os.path.lexists(dest):
|
|
311
|
+
warnings.append(f"{label}: cannot replace untrusted cached checkout {dest}; source skipped")
|
|
312
|
+
return None
|
|
313
|
+
tmp = tempfile.mkdtemp(prefix=f"{key}.part-", dir=base)
|
|
314
|
+
try:
|
|
315
|
+
try:
|
|
316
|
+
proc = _run_git(["clone", "--quiet", "--depth", "1", "--no-recurse-submodules",
|
|
317
|
+
"--branch", ref, "--end-of-options", url, tmp])
|
|
318
|
+
except subprocess.TimeoutExpired:
|
|
319
|
+
warnings.append(f"{label}: git clone timed out after {int(GIT_TIMEOUT)}s; source skipped")
|
|
320
|
+
return None
|
|
321
|
+
if proc.returncode != 0:
|
|
322
|
+
# tag/branch clone failed -- retry treating ref as a commit sha
|
|
323
|
+
try:
|
|
324
|
+
fetched = (
|
|
325
|
+
_run_git(["init", "--quiet", tmp]).returncode == 0
|
|
326
|
+
and _run_git(["fetch", "--quiet", "--depth", "1", "--end-of-options", url, ref], cwd=tmp).returncode == 0
|
|
327
|
+
and _run_git(["checkout", "--quiet", "FETCH_HEAD"], cwd=tmp).returncode == 0
|
|
328
|
+
)
|
|
329
|
+
if not fetched:
|
|
330
|
+
warnings.append(f"{label}: cannot fetch `{ref}` from {url}; source skipped")
|
|
331
|
+
return None
|
|
332
|
+
except subprocess.TimeoutExpired:
|
|
333
|
+
warnings.append(f"{label}: git fetch timed out after {int(GIT_TIMEOUT)}s; source skipped")
|
|
334
|
+
return None
|
|
335
|
+
if not os.path.lexists(dest):
|
|
336
|
+
try:
|
|
337
|
+
os.replace(tmp, dest)
|
|
338
|
+
except OSError:
|
|
339
|
+
# Expected when another resolver published the same key
|
|
340
|
+
# concurrently; anything else leaves no checkout to return.
|
|
341
|
+
if not os.path.lexists(dest):
|
|
342
|
+
warnings.append(f"{label}: could not publish the clone to {dest}; source skipped")
|
|
343
|
+
return None
|
|
344
|
+
if _trusted_checkout(dest):
|
|
345
|
+
return dest
|
|
346
|
+
warnings.append(
|
|
347
|
+
f"{label}: cached checkout {dest} appeared during the clone and is a symlink or not owned by this user; source skipped"
|
|
348
|
+
)
|
|
349
|
+
return None
|
|
350
|
+
finally:
|
|
351
|
+
if os.path.isdir(tmp):
|
|
352
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# --- pack enumeration --------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
_FRONTMATTER_KEYS = ("title:", "applies_when:")
|
|
358
|
+
_FRONTMATTER_CAP = 64 * 1024 # frontmatter must close within this many characters
|
|
359
|
+
# A pack's README is its description, never a rule, whatever frontmatter it
|
|
360
|
+
# carries: it is not published, not reported as skipped, and not counted.
|
|
361
|
+
_README = "readme.md"
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@functools.lru_cache(maxsize=None)
|
|
365
|
+
def _is_knowledge_file(path: str) -> bool:
|
|
366
|
+
"""True when the file opens with a `---` frontmatter block, closed by a `---`
|
|
367
|
+
line within the cap, that carries `title:` and `applies_when:`. A UTF-8 BOM
|
|
368
|
+
is not part of the content."""
|
|
369
|
+
try:
|
|
370
|
+
with open(path, encoding="utf-8-sig", errors="replace") as fh:
|
|
371
|
+
lines = fh.read(_FRONTMATTER_CAP).splitlines()
|
|
372
|
+
except OSError:
|
|
373
|
+
return False
|
|
374
|
+
if not lines or lines[0].strip() != "---":
|
|
375
|
+
return False
|
|
376
|
+
for end, line in enumerate(lines[1:], 1):
|
|
377
|
+
if line.strip() == "---":
|
|
378
|
+
fm = "\n".join(lines[1:end])
|
|
379
|
+
break
|
|
380
|
+
else:
|
|
381
|
+
return False
|
|
382
|
+
return all(re.search(rf"^\s*{re.escape(k)}", fm, re.MULTILINE) for k in _FRONTMATTER_KEYS)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _contained_md_files(directory: str, boundary: str, escaped: list) -> list:
|
|
386
|
+
"""Paths of the `.md` entries directly under `directory`, minus its README,
|
|
387
|
+
whose real path stays within `boundary` (a realpath). An entry that links
|
|
388
|
+
outside it is appended to `escaped` and never opened, so a pack cannot read
|
|
389
|
+
files off the user's machine."""
|
|
390
|
+
try:
|
|
391
|
+
names = sorted(os.listdir(directory))
|
|
392
|
+
except OSError:
|
|
393
|
+
return []
|
|
394
|
+
files = []
|
|
395
|
+
for name in names:
|
|
396
|
+
if not name.endswith(".md") or name.lower() == _README:
|
|
397
|
+
continue
|
|
398
|
+
child = os.path.join(directory, name)
|
|
399
|
+
if _within(os.path.realpath(child), boundary):
|
|
400
|
+
files.append(child)
|
|
401
|
+
else:
|
|
402
|
+
escaped.append(child)
|
|
403
|
+
return files
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _contained_child_dirs(directory: str, boundary: str, escaped: list) -> list:
|
|
407
|
+
"""`(name, path)` of the non-hidden directories directly under `directory`
|
|
408
|
+
whose real path stays within `boundary`; one that links outside it is
|
|
409
|
+
appended to `escaped` and never entered."""
|
|
410
|
+
try:
|
|
411
|
+
names = sorted(os.listdir(directory))
|
|
412
|
+
except OSError:
|
|
413
|
+
return []
|
|
414
|
+
dirs = []
|
|
415
|
+
for name in names:
|
|
416
|
+
child = os.path.join(directory, name)
|
|
417
|
+
if name.startswith(".") or not os.path.isdir(child):
|
|
418
|
+
continue
|
|
419
|
+
if _within(os.path.realpath(child), boundary):
|
|
420
|
+
dirs.append((name, child))
|
|
421
|
+
else:
|
|
422
|
+
escaped.append(child)
|
|
423
|
+
return dirs
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _has_knowledge_files(directory: str, boundary: str, escaped: list) -> bool:
|
|
427
|
+
return any(_is_knowledge_file(f) for f in _contained_md_files(directory, boundary, escaped))
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _escaping_links(root: str, boundary: str) -> list:
|
|
431
|
+
"""Symlinks anywhere under `root` (walked without following links) whose real
|
|
432
|
+
path leaves `boundary`, sorted. Only a link can leave: every other entry sits
|
|
433
|
+
under `root`, which is already inside the boundary."""
|
|
434
|
+
leaks = []
|
|
435
|
+
for dirpath, dirnames, filenames in os.walk(root, followlinks=False):
|
|
436
|
+
for name in dirnames + filenames:
|
|
437
|
+
child = os.path.join(dirpath, name)
|
|
438
|
+
if os.path.islink(child) and not _within(os.path.realpath(child), boundary):
|
|
439
|
+
leaks.append(child)
|
|
440
|
+
return sorted(leaks)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def enumerate_packs(source_root: str, boundary: str, escaped: list, self_name: str | None = None) -> dict:
|
|
444
|
+
"""Map published pack id -> dir. Immediate children only; self = single pack.
|
|
445
|
+
|
|
446
|
+
`boundary` is the realpath every child directory and rule file must stay
|
|
447
|
+
within (the git checkout, the repository, or the source root itself); a
|
|
448
|
+
child that links outside it is recorded in `escaped` and skipped. Symlinks
|
|
449
|
+
that stay inside the boundary are ordinary content.
|
|
450
|
+
"""
|
|
451
|
+
if _has_knowledge_files(source_root, boundary, escaped):
|
|
452
|
+
name = self_name or os.path.basename(os.path.abspath(source_root))
|
|
453
|
+
return {name: source_root}
|
|
454
|
+
return {
|
|
455
|
+
name: child
|
|
456
|
+
for name, child in _contained_child_dirs(source_root, boundary, escaped)
|
|
457
|
+
if _has_knowledge_files(child, boundary, escaped)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def nested_rule_shaped(pack_dir: str, boundary: str) -> tuple:
|
|
462
|
+
"""`(total, subdirs)`: how many rule-shaped files sit in the immediate
|
|
463
|
+
subdirectories of `pack_dir`, and which subdirectories hold them. Scans one
|
|
464
|
+
level down only, inside `boundary`, so the pack's data is never walked.
|
|
465
|
+
Subdirectories are storage, so these files are counted, never published."""
|
|
466
|
+
hits, total = [], 0
|
|
467
|
+
for name, child in _contained_child_dirs(pack_dir, boundary, []):
|
|
468
|
+
count = sum(1 for f in _contained_md_files(child, boundary, []) if _is_knowledge_file(f))
|
|
469
|
+
if count:
|
|
470
|
+
hits.append(name)
|
|
471
|
+
total += count
|
|
472
|
+
return total, hits
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def nested_rules_warning(pack_id: str, pack_dir: str, boundary: str) -> str | None:
|
|
476
|
+
"""The warning for a pack directory with no rule at its top level whose
|
|
477
|
+
subdirectories hold rule-shaped files: it registers, yet nothing can ever be
|
|
478
|
+
discovered. None when the subdirectories hold none. The caller establishes
|
|
479
|
+
that the top level is empty of rules; a pack with top-level rules keeps its
|
|
480
|
+
nested files as storage and gets `nested_rule_shaped` instead."""
|
|
481
|
+
total, hits = nested_rule_shaped(pack_dir, boundary)
|
|
482
|
+
if not hits:
|
|
483
|
+
return None
|
|
484
|
+
where = ", ".join(f"`{name}/`" for name in hits)
|
|
485
|
+
return (
|
|
486
|
+
f"pack `{pack_id}` has {total} rule-shaped file(s) under {where} that discovery"
|
|
487
|
+
" never reads -- move rules to the pack's top level (see https://everyinc.github.io/compound-engineering-plugin/guides/packs/, Pack layout)"
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
# --- entry resolution --------------------------------------------------------
|
|
492
|
+
|
|
493
|
+
def _entry_label(entry: dict) -> str:
|
|
494
|
+
return f"{entry.get('_origin', 'config')}:{entry.get('_line', '?')}"
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _entry_shape_ok(entry: dict, label: str, errors: list) -> bool:
|
|
498
|
+
"""I/O-free checks on one parsed entry: `source:` present, and the scalar
|
|
499
|
+
keys actually scalar. False after appending the error."""
|
|
500
|
+
for key in SCALAR_KEYS:
|
|
501
|
+
val = entry.get(key)
|
|
502
|
+
if val is not None and not isinstance(val, str):
|
|
503
|
+
errors.append(f"{label}: `{key}:` must be a single string (got {val!r})")
|
|
504
|
+
return False
|
|
505
|
+
if not entry.get("source"):
|
|
506
|
+
errors.append(f"{label}: entry has no `source:`")
|
|
507
|
+
return False
|
|
508
|
+
return True
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def resolve_entry(entry: dict, repo_root: str, roots: list, warnings: list, errors: list) -> None:
|
|
512
|
+
label = _entry_label(entry)
|
|
513
|
+
if not _entry_shape_ok(entry, label, errors):
|
|
514
|
+
return
|
|
515
|
+
source, ref, sub_path = entry["source"], entry.get("ref"), entry.get("path")
|
|
516
|
+
|
|
517
|
+
tree = _TREE_URL_RE.match(source)
|
|
518
|
+
if tree:
|
|
519
|
+
t_ref, t_path = tree.group("ref"), tree.group("path") or ""
|
|
520
|
+
if isinstance(ref, str) and ref != t_ref:
|
|
521
|
+
errors.append(f"{label}: tree URL pins ref `{t_ref}` but entry says `ref: {ref}` -- remove one")
|
|
522
|
+
return
|
|
523
|
+
if isinstance(sub_path, str) and sub_path.strip("/") != t_path.strip("/"):
|
|
524
|
+
errors.append(f"{label}: tree URL path `{t_path}` conflicts with `path: {sub_path}` -- remove one")
|
|
525
|
+
return
|
|
526
|
+
source, ref, sub_path = tree.group("base"), t_ref, t_path or None
|
|
527
|
+
|
|
528
|
+
if _is_git_url(source):
|
|
529
|
+
if not isinstance(ref, str) or not ref:
|
|
530
|
+
errors.append(f"{label}: git source `{source}` requires `ref:` (tag, sha, or branch)")
|
|
531
|
+
return
|
|
532
|
+
if ref.startswith("-") or source.startswith("-"):
|
|
533
|
+
errors.append(f"{label}: git source/ref may not begin with `-`")
|
|
534
|
+
return
|
|
535
|
+
checkout = resolve_git_source(source, ref, warnings, label)
|
|
536
|
+
if checkout is None:
|
|
537
|
+
if tree:
|
|
538
|
+
warnings.append(
|
|
539
|
+
f"{label}: if the branch name contains `/`, tree-URL parsing splits it wrong -- use explicit `ref:` and `path:` fields"
|
|
540
|
+
)
|
|
541
|
+
return
|
|
542
|
+
git_meta = {"url": source, "ref": ref}
|
|
543
|
+
source_root = os.path.join(checkout, sub_path) if sub_path else checkout
|
|
544
|
+
real_root, real_checkout = os.path.realpath(source_root), os.path.realpath(checkout)
|
|
545
|
+
if not _within(real_root, real_checkout):
|
|
546
|
+
errors.append(f"{label}: path `{sub_path}` escapes the source checkout")
|
|
547
|
+
return
|
|
548
|
+
source_root, boundary = real_root, real_checkout
|
|
549
|
+
if not os.path.isdir(source_root):
|
|
550
|
+
errors.append(f"{label}: path `{sub_path}` does not exist in {source}@{ref}")
|
|
551
|
+
return
|
|
552
|
+
else:
|
|
553
|
+
git_meta = None
|
|
554
|
+
if ref is not None:
|
|
555
|
+
errors.append(f"{label}: `ref:` is only valid on git sources; path sources are read live")
|
|
556
|
+
return
|
|
557
|
+
if sub_path is not None:
|
|
558
|
+
errors.append(f"{label}: `path:` is only valid on git sources; point `source:` at the directory instead")
|
|
559
|
+
return
|
|
560
|
+
expanded = os.path.expanduser(source)
|
|
561
|
+
if os.path.isabs(expanded):
|
|
562
|
+
source_root = os.path.realpath(expanded)
|
|
563
|
+
boundary = source_root
|
|
564
|
+
else:
|
|
565
|
+
source_root = os.path.realpath(os.path.join(repo_root, expanded))
|
|
566
|
+
repo_real = os.path.realpath(repo_root)
|
|
567
|
+
if not _within(source_root, repo_real) or _within(source_root, os.path.join(repo_real, ".git")):
|
|
568
|
+
errors.append(f"{label}: repo-relative source `{source}` resolves outside the repository")
|
|
569
|
+
return
|
|
570
|
+
boundary = repo_real
|
|
571
|
+
if not os.path.isdir(source_root):
|
|
572
|
+
errors.append(f"{label}: source directory `{source}` does not exist")
|
|
573
|
+
return
|
|
574
|
+
|
|
575
|
+
if git_meta:
|
|
576
|
+
# Display name for a single-pack git source: the path: subfolder's
|
|
577
|
+
# basename, else the URL's last path segment (never the cache key).
|
|
578
|
+
tail = (sub_path or source).rstrip("/").rsplit("/", 1)[-1]
|
|
579
|
+
self_name = re.sub(r"\.git$", "", tail.split(":")[-1]) or None
|
|
580
|
+
else:
|
|
581
|
+
self_name = None
|
|
582
|
+
escaped = []
|
|
583
|
+
published = enumerate_packs(source_root, boundary, escaped, self_name)
|
|
584
|
+
for link in escaped:
|
|
585
|
+
warnings.append(
|
|
586
|
+
f"{label}: skipped `{os.path.relpath(link, source_root)}` in `{source}` -- it links outside the source"
|
|
587
|
+
)
|
|
588
|
+
if not published:
|
|
589
|
+
warnings.append(f"{label}: source `{source}` publishes no packs (no directories with valid knowledge files)")
|
|
590
|
+
# Each child directory is a would-be pack with no top-level rule (one
|
|
591
|
+
# with a rule would have published); say when its rules sit one level
|
|
592
|
+
# too deep, so the author learns why nothing published.
|
|
593
|
+
for name, child in _contained_child_dirs(source_root, boundary, []):
|
|
594
|
+
nested = nested_rules_warning(name, child, boundary)
|
|
595
|
+
if nested:
|
|
596
|
+
warnings.append(f"{label}: {nested}")
|
|
597
|
+
return
|
|
598
|
+
|
|
599
|
+
selection = entry.get("pack")
|
|
600
|
+
if selection is None:
|
|
601
|
+
selected = dict(published)
|
|
602
|
+
else:
|
|
603
|
+
wanted = selection if isinstance(selection, list) else [selection]
|
|
604
|
+
if not wanted:
|
|
605
|
+
warnings.append(f"{label}: `pack:` lists no ids; nothing installed from `{source}`")
|
|
606
|
+
return
|
|
607
|
+
missing = [w for w in wanted if w not in published]
|
|
608
|
+
if missing:
|
|
609
|
+
errors.append(
|
|
610
|
+
f"{label}: pack id(s) {', '.join(map(str, missing))} not published by `{source}`"
|
|
611
|
+
f" -- available: {', '.join(sorted(published)) or 'none'}"
|
|
612
|
+
)
|
|
613
|
+
return
|
|
614
|
+
selected = {w: published[w] for w in wanted}
|
|
615
|
+
|
|
616
|
+
override = entry.get("id")
|
|
617
|
+
if override is not None:
|
|
618
|
+
if len(selected) != 1:
|
|
619
|
+
errors.append(f"{label}: `id:` override requires the entry to install exactly one pack")
|
|
620
|
+
return
|
|
621
|
+
selected = {str(override): next(iter(selected.values()))}
|
|
622
|
+
|
|
623
|
+
for pack_id, pack_dir in selected.items():
|
|
624
|
+
# Consumers list the pack directory themselves, so a link that leaves
|
|
625
|
+
# the source anywhere inside it would let pack content read files off
|
|
626
|
+
# the user's machine: refuse the whole pack. (Top-level escapes were
|
|
627
|
+
# already named as skipped during enumeration.)
|
|
628
|
+
leaks = _escaping_links(pack_dir, boundary)
|
|
629
|
+
if leaks:
|
|
630
|
+
names = ", ".join(f"`{os.path.relpath(p, pack_dir)}`" for p in leaks)
|
|
631
|
+
errors.append(f"{label}: pack `{pack_id}` not published -- {names} link(s) outside the source")
|
|
632
|
+
continue
|
|
633
|
+
for child in _contained_md_files(pack_dir, boundary, []):
|
|
634
|
+
name = os.path.basename(child)
|
|
635
|
+
if os.path.isfile(child) and not _is_knowledge_file(child):
|
|
636
|
+
warnings.append(
|
|
637
|
+
f"{label}: skipped pack file `{pack_id}/{name}` (missing `title`/`applies_when` frontmatter)"
|
|
638
|
+
)
|
|
639
|
+
# A published pack has a rule at its top level, so rule-shaped files in
|
|
640
|
+
# its subdirectories are storage: counted for the health report, not warned.
|
|
641
|
+
nested_total, _ = nested_rule_shaped(pack_dir, boundary)
|
|
642
|
+
root = {"id": pack_id, "dir": pack_dir, "nested_rule_shaped": nested_total, "_label": label}
|
|
643
|
+
if git_meta:
|
|
644
|
+
root.update(git_meta)
|
|
645
|
+
roots.append(root)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def _emit(declared_only: bool, entries: list, roots: list, warnings: list, errors: list,
|
|
649
|
+
declared: bool | None = None) -> int:
|
|
650
|
+
if declared_only:
|
|
651
|
+
print(json.dumps({"declared": declared, "entries": len(entries), "errors": errors}))
|
|
652
|
+
else:
|
|
653
|
+
print(json.dumps({
|
|
654
|
+
"roots": [{k: v for k, v in r.items() if not k.startswith("_")} for r in roots],
|
|
655
|
+
"warnings": warnings,
|
|
656
|
+
"errors": errors,
|
|
657
|
+
"entries": len(entries),
|
|
658
|
+
}))
|
|
659
|
+
return 0
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _repo_root() -> str | None:
|
|
663
|
+
"""The enclosing checkout's top level: git's answer when git is on PATH,
|
|
664
|
+
otherwise the nearest ancestor of the working directory holding a `.git`
|
|
665
|
+
entry (a directory, or the file a worktree or submodule leaves). None when
|
|
666
|
+
the working directory is not inside a checkout."""
|
|
667
|
+
if shutil.which("git") is not None:
|
|
668
|
+
proc = subprocess.run(["git", "rev-parse", "--show-toplevel"],
|
|
669
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
|
|
670
|
+
return proc.stdout.strip() if proc.returncode == 0 else None
|
|
671
|
+
current = os.getcwd()
|
|
672
|
+
while True:
|
|
673
|
+
if os.path.lexists(os.path.join(current, ".git")):
|
|
674
|
+
return current
|
|
675
|
+
parent = os.path.dirname(current)
|
|
676
|
+
if parent == current:
|
|
677
|
+
return None
|
|
678
|
+
current = parent
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _main(argv: list) -> int:
|
|
682
|
+
parser = argparse.ArgumentParser(description="Resolve the Compound Packs declared in CE config.")
|
|
683
|
+
parser.add_argument(
|
|
684
|
+
"--declared-only", action="store_true",
|
|
685
|
+
help="parse and shape-check the packs: entries only; no git or cache work",
|
|
686
|
+
)
|
|
687
|
+
args = parser.parse_args(argv)
|
|
688
|
+
warnings, errors, roots, entries = [], [], [], []
|
|
689
|
+
|
|
690
|
+
repo_root = _repo_root()
|
|
691
|
+
if repo_root is None:
|
|
692
|
+
warnings.append("not inside a git repository; no CE config to read")
|
|
693
|
+
return _emit(args.declared_only, entries, roots, warnings, errors)
|
|
694
|
+
cfg_dir = os.path.join(repo_root, ".compound-engineering")
|
|
695
|
+
for name in CONFIG_FILES:
|
|
696
|
+
entries.extend(parse_packs_block(os.path.join(cfg_dir, name), errors))
|
|
697
|
+
|
|
698
|
+
if args.declared_only:
|
|
699
|
+
for entry in entries:
|
|
700
|
+
_entry_shape_ok(entry, _entry_label(entry), errors)
|
|
701
|
+
return _emit(True, entries, roots, warnings, errors, declared=bool(entries or errors))
|
|
702
|
+
|
|
703
|
+
for entry in entries:
|
|
704
|
+
try:
|
|
705
|
+
resolve_entry(entry, repo_root, roots, warnings, errors)
|
|
706
|
+
except Exception as exc: # one entry's surprise is that entry's error, not everyone's
|
|
707
|
+
errors.append(f"{_entry_label(entry)}: unexpected error resolving entry: {exc}")
|
|
708
|
+
|
|
709
|
+
# First declaration wins: config.yaml entries precede config.local.yaml in
|
|
710
|
+
# CONFIG_FILES, so a personal pack can never displace the team's.
|
|
711
|
+
by_id = {}
|
|
712
|
+
final = []
|
|
713
|
+
for root in roots:
|
|
714
|
+
prev = by_id.get(root["id"])
|
|
715
|
+
if prev is not None:
|
|
716
|
+
errors.append(
|
|
717
|
+
f"duplicate pack id `{root['id']}`: {root['_label']} ignored, {prev['_label']} kept"
|
|
718
|
+
" -- rename one with `id:`"
|
|
719
|
+
)
|
|
720
|
+
continue
|
|
721
|
+
by_id[root["id"]] = root
|
|
722
|
+
final.append(root)
|
|
723
|
+
return _emit(False, entries, final, warnings, errors)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def main() -> int:
|
|
727
|
+
try:
|
|
728
|
+
return _main(sys.argv[1:])
|
|
729
|
+
except Exception as exc: # never a traceback: consumers need valid JSON
|
|
730
|
+
print(json.dumps({
|
|
731
|
+
"roots": [], "warnings": [], "errors": [f"packs resolver failed unexpectedly: {exc}"], "entries": 0,
|
|
732
|
+
}))
|
|
733
|
+
return 0
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
if __name__ == "__main__":
|
|
737
|
+
sys.exit(main())
|