dsh-aris-panel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/README_CN.md +87 -0
- package/dsh/checkout.patch.yml +38 -0
- package/dsh/client.js +634 -0
- package/dsh/cordis.patch.yml +44 -0
- package/dsh/index.mjs +76 -0
- package/dsh/run-status.mjs +182 -0
- package/dsh/scope-limits.mjs +50 -0
- package/dsh/workbench.mjs +291 -0
- package/mcp-servers/claude-review/README.md +93 -0
- package/mcp-servers/claude-review/run_with_claude_aws.sh +49 -0
- package/mcp-servers/claude-review/server.py +718 -0
- package/mcp-servers/codex-image2/README.md +65 -0
- package/mcp-servers/codex-image2/server.py +893 -0
- package/mcp-servers/feishu-bridge/requirements.txt +1 -0
- package/mcp-servers/feishu-bridge/server.py +240 -0
- package/mcp-servers/gemini-review/README.md +171 -0
- package/mcp-servers/gemini-review/server.py +1856 -0
- package/mcp-servers/llm-chat/requirements.txt +1 -0
- package/mcp-servers/llm-chat/server.py +664 -0
- package/mcp-servers/manual-review/README.md +133 -0
- package/mcp-servers/manual-review/server.py +910 -0
- package/mcp-servers/manual-review/ui.html +279 -0
- package/mcp-servers/minimax-chat/requirements.txt +1 -0
- package/mcp-servers/minimax-chat/server.py +381 -0
- package/package.json +51 -0
- package/skills/ablation-planner/SKILL.md +123 -0
- package/skills/alphaxiv/SKILL.md +196 -0
- package/skills/analyze-results/SKILL.md +46 -0
- package/skills/arxiv/SKILL.md +248 -0
- package/skills/auto-paper-improvement-loop/SKILL.md +651 -0
- package/skills/auto-review-loop/SKILL.md +1137 -0
- package/skills/auto-review-loop-llm/SKILL.md +259 -0
- package/skills/auto-review-loop-minimax/SKILL.md +302 -0
- package/skills/citation-audit/SKILL.md +502 -0
- package/skills/claims-drafting/SKILL.md +227 -0
- package/skills/comm-lit-review/SKILL.md +297 -0
- package/skills/deepxiv/SKILL.md +263 -0
- package/skills/dse-loop/SKILL.md +296 -0
- package/skills/embodiment-description/SKILL.md +129 -0
- package/skills/exa-search/SKILL.md +205 -0
- package/skills/experiment-audit/SKILL.md +311 -0
- package/skills/experiment-bridge/SKILL.md +376 -0
- package/skills/experiment-plan/SKILL.md +249 -0
- package/skills/experiment-queue/SKILL.md +431 -0
- package/skills/experiment-queue/scripts/build_manifest.py +142 -0
- package/skills/experiment-queue/scripts/queue_manager.py +433 -0
- package/skills/feishu-notify/SKILL.md +156 -0
- package/skills/figure-description/SKILL.md +138 -0
- package/skills/figure-spec/SKILL.md +262 -0
- package/skills/figure-spec/scripts/figure_renderer.py +799 -0
- package/skills/formula-derivation/SKILL.md +280 -0
- package/skills/gemini-search/SKILL.md +231 -0
- package/skills/grant-proposal/SKILL.md +698 -0
- package/skills/idea-creator/SKILL.md +542 -0
- package/skills/idea-discovery/SKILL.md +521 -0
- package/skills/idea-discovery-robot/SKILL.md +363 -0
- package/skills/integrity-forensics/SKILL.md +284 -0
- package/skills/interview-cheatsheet/SKILL.md +245 -0
- package/skills/invention-structuring/SKILL.md +188 -0
- package/skills/jurisdiction-format/SKILL.md +192 -0
- package/skills/kill-argument/SKILL.md +437 -0
- package/skills/mermaid-diagram/SKILL.md +419 -0
- package/skills/meta-apply/SKILL.md +141 -0
- package/skills/meta-optimize/SKILL.md +437 -0
- package/skills/monitor-experiment/SKILL.md +140 -0
- package/skills/novelty-check/SKILL.md +101 -0
- package/skills/openalex/SKILL.md +237 -0
- package/skills/overleaf-sync/SKILL.md +220 -0
- package/skills/paper-claim-audit/SKILL.md +348 -0
- package/skills/paper-compile/SKILL.md +266 -0
- package/skills/paper-figure/SKILL.md +312 -0
- package/skills/paper-illustration/SKILL.md +736 -0
- package/skills/paper-illustration-image2/SKILL.md +391 -0
- package/skills/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
- package/skills/paper-plan/SKILL.md +386 -0
- package/skills/paper-poster/SKILL.md +19 -0
- package/skills/paper-poster-html/DESIGN_FINAL.md +176 -0
- package/skills/paper-poster-html/IMPLEMENTATION_CONVENTIONS.md +161 -0
- package/skills/paper-poster-html/LICENSES/posterly-MIT.txt +21 -0
- package/skills/paper-poster-html/NOTICE.md +57 -0
- package/skills/paper-poster-html/SKILL.md +323 -0
- package/skills/paper-poster-html/scripts/_posterly/__init__.py +0 -0
- package/skills/paper-poster-html/scripts/_posterly/canvas.py +200 -0
- package/skills/paper-poster-html/scripts/_posterly/measure.py +588 -0
- package/skills/paper-poster-html/scripts/_posterly/polish.py +498 -0
- package/skills/paper-poster-html/scripts/_posterly/preflight.py +489 -0
- package/skills/paper-poster-html/scripts/_posterly/render.py +215 -0
- package/skills/paper-poster-html/scripts/_posterly/textutil.py +16 -0
- package/skills/paper-poster-html/scripts/_posterly/verify_final.py +171 -0
- package/skills/paper-poster-html/scripts/asset_check.py +897 -0
- package/skills/paper-poster-html/scripts/extract_pdf_figures.py +666 -0
- package/skills/paper-poster-html/scripts/poster_check.py +251 -0
- package/skills/paper-poster-html/scripts/preprocess_figures.py +238 -0
- package/skills/paper-poster-html/scripts/render_preview.py +217 -0
- package/skills/paper-poster-html/scripts/run_gates.py +556 -0
- package/skills/paper-poster-html/scripts/style_check.py +1324 -0
- package/skills/paper-poster-html/templates/COMPONENTS.md +462 -0
- package/skills/paper-poster-html/templates/README.md +170 -0
- package/skills/paper-poster-html/templates/landscape_4col.html +1032 -0
- package/skills/paper-poster-html/templates/landscape_hero.html +1046 -0
- package/skills/paper-poster-html/templates/portrait_2col.html +947 -0
- package/skills/paper-poster-html/templates/tokens/acl.json +9 -0
- package/skills/paper-poster-html/templates/tokens/cvpr.json +9 -0
- package/skills/paper-poster-html/templates/tokens/generic.json +9 -0
- package/skills/paper-poster-html/templates/tokens/iclr.json +9 -0
- package/skills/paper-poster-html/templates/tokens/icml.json +9 -0
- package/skills/paper-poster-html/templates/tokens/neurips.json +9 -0
- package/skills/paper-slides/SKILL.md +635 -0
- package/skills/paper-talk/SKILL.md +381 -0
- package/skills/paper-write/SKILL.md +604 -0
- package/skills/paper-write/templates/IEEEtran.bst +2409 -0
- package/skills/paper-write/templates/IEEEtran.cls +6347 -0
- package/skills/paper-write/templates/iclr2026.tex +84 -0
- package/skills/paper-write/templates/icml2025.tex +87 -0
- package/skills/paper-write/templates/ieee_conference.tex +89 -0
- package/skills/paper-write/templates/ieee_journal.tex +93 -0
- package/skills/paper-write/templates/math_commands.tex +48 -0
- package/skills/paper-write/templates/neurips2025.tex +80 -0
- package/skills/paper-writing/SKILL.md +916 -0
- package/skills/patent-novelty-check/SKILL.md +153 -0
- package/skills/patent-pipeline/SKILL.md +344 -0
- package/skills/patent-review/SKILL.md +203 -0
- package/skills/pixel-art/SKILL.md +137 -0
- package/skills/prior-art-search/SKILL.md +146 -0
- package/skills/proof-checker/SKILL.md +866 -0
- package/skills/proof-orchestrator/NOTICE.md +24 -0
- package/skills/proof-orchestrator/SKILL.md +254 -0
- package/skills/proof-orchestrator/references/audit-output-contract.md +126 -0
- package/skills/proof-orchestrator/references/deepseek-routing.md +74 -0
- package/skills/proof-orchestrator/references/dispatch-prompts.md +227 -0
- package/skills/proof-orchestrator/references/notation-audit.md +135 -0
- package/skills/proof-orchestrator/references/proof-audit-rubric.md +70 -0
- package/skills/proof-orchestrator/references/stress-tests.md +38 -0
- package/skills/proof-writer/SKILL.md +223 -0
- package/skills/qzcli/SKILL.md +324 -0
- package/skills/rebuttal/SKILL.md +376 -0
- package/skills/render-html/SKILL.md +316 -0
- package/skills/render-html/scripts/render_html.py +1006 -0
- package/skills/render-html/scripts/templates/academic.html +703 -0
- package/skills/render-html/scripts/templates/dashboard.html +333 -0
- package/skills/research-lit/SKILL.md +756 -0
- package/skills/research-pipeline/SKILL.md +384 -0
- package/skills/research-refine/SKILL.md +770 -0
- package/skills/research-refine-pipeline/SKILL.md +186 -0
- package/skills/research-review/SKILL.md +198 -0
- package/skills/research-wiki/SKILL.md +461 -0
- package/skills/resubmit-pipeline/SKILL.md +447 -0
- package/skills/result-to-claim/SKILL.md +311 -0
- package/skills/run-experiment/SKILL.md +313 -0
- package/skills/semantic-scholar/SKILL.md +236 -0
- package/skills/serverless-modal/SKILL.md +335 -0
- package/skills/shared-references/acceptance-gate.md +324 -0
- package/skills/shared-references/assurance-contract.md +248 -0
- package/skills/shared-references/capture-antipatterns.md +78 -0
- package/skills/shared-references/citation-discipline.md +583 -0
- package/skills/shared-references/compute-env-contract.md +163 -0
- package/skills/shared-references/effort-contract.md +183 -0
- package/skills/shared-references/evidence-precheck.md +65 -0
- package/skills/shared-references/experiment-integrity.md +49 -0
- package/skills/shared-references/external-cadence.md +326 -0
- package/skills/shared-references/fan-out-pattern.md +366 -0
- package/skills/shared-references/injection-hygiene.md +127 -0
- package/skills/shared-references/integration-contract.md +461 -0
- package/skills/shared-references/output-composition.md +93 -0
- package/skills/shared-references/output-language.md +45 -0
- package/skills/shared-references/output-manifest.md +49 -0
- package/skills/shared-references/output-versioning.md +111 -0
- package/skills/shared-references/patent-format-cn.md +199 -0
- package/skills/shared-references/patent-format-ep.md +173 -0
- package/skills/shared-references/patent-format-us.md +161 -0
- package/skills/shared-references/patent-writing-principles.md +197 -0
- package/skills/shared-references/prior-art-databases.md +141 -0
- package/skills/shared-references/resumable-runs.md +109 -0
- package/skills/shared-references/review-scope-limits.md +81 -0
- package/skills/shared-references/review-tracing.md +391 -0
- package/skills/shared-references/reviewer-independence.md +79 -0
- package/skills/shared-references/reviewer-routing.md +852 -0
- package/skills/shared-references/skill-governance.md +104 -0
- package/skills/shared-references/taste-calibration.md +85 -0
- package/skills/shared-references/venue-checklists.md +114 -0
- package/skills/shared-references/wiki-helper-resolution.md +134 -0
- package/skills/shared-references/writing-principles.md +525 -0
- package/skills/skills-codex/README.md +102 -0
- package/skills/skills-codex/README_CN.md +100 -0
- package/skills/skills-codex/ablation-planner/SKILL.md +126 -0
- package/skills/skills-codex/alphaxiv/SKILL.md +186 -0
- package/skills/skills-codex/analyze-results/SKILL.md +45 -0
- package/skills/skills-codex/arxiv/SKILL.md +210 -0
- package/skills/skills-codex/auto-paper-improvement-loop/SKILL.md +574 -0
- package/skills/skills-codex/auto-review-loop/SKILL.md +500 -0
- package/skills/skills-codex/auto-review-loop-llm/SKILL.md +247 -0
- package/skills/skills-codex/auto-review-loop-minimax/SKILL.md +290 -0
- package/skills/skills-codex/citation-audit/SKILL.md +504 -0
- package/skills/skills-codex/claims-drafting/SKILL.md +239 -0
- package/skills/skills-codex/comm-lit-review/SKILL.md +299 -0
- package/skills/skills-codex/comm-lit-review/references/domain-taxonomy.md +57 -0
- package/skills/skills-codex/comm-lit-review/references/output-template.md +37 -0
- package/skills/skills-codex/comm-lit-review/references/source-policy.md +99 -0
- package/skills/skills-codex/comm-lit-review/references/venue-tiering.md +112 -0
- package/skills/skills-codex/deepxiv/SKILL.md +142 -0
- package/skills/skills-codex/dse-loop/SKILL.md +285 -0
- package/skills/skills-codex/embodiment-description/SKILL.md +129 -0
- package/skills/skills-codex/exa-search/SKILL.md +192 -0
- package/skills/skills-codex/experiment-audit/SKILL.md +286 -0
- package/skills/skills-codex/experiment-bridge/SKILL.md +356 -0
- package/skills/skills-codex/experiment-plan/SKILL.md +249 -0
- package/skills/skills-codex/experiment-queue/SKILL.md +401 -0
- package/skills/skills-codex/feishu-notify/SKILL.md +155 -0
- package/skills/skills-codex/figure-description/SKILL.md +138 -0
- package/skills/skills-codex/figure-spec/SKILL.md +252 -0
- package/skills/skills-codex/formula-derivation/SKILL.md +280 -0
- package/skills/skills-codex/gemini-search/SKILL.md +205 -0
- package/skills/skills-codex/grant-proposal/SKILL.md +626 -0
- package/skills/skills-codex/idea-creator/SKILL.md +405 -0
- package/skills/skills-codex/idea-discovery/SKILL.md +475 -0
- package/skills/skills-codex/idea-discovery-robot/SKILL.md +362 -0
- package/skills/skills-codex/integrity-forensics/SKILL.md +106 -0
- package/skills/skills-codex/interview-cheatsheet/SKILL.md +245 -0
- package/skills/skills-codex/invention-structuring/SKILL.md +188 -0
- package/skills/skills-codex/jurisdiction-format/SKILL.md +192 -0
- package/skills/skills-codex/kill-argument/SKILL.md +403 -0
- package/skills/skills-codex/mermaid-diagram/SKILL.md +379 -0
- package/skills/skills-codex/meta-apply/SKILL.md +154 -0
- package/skills/skills-codex/meta-optimize/SKILL.md +348 -0
- package/skills/skills-codex/monitor-experiment/SKILL.md +98 -0
- package/skills/skills-codex/novelty-check/SKILL.md +89 -0
- package/skills/skills-codex/openalex/SKILL.md +228 -0
- package/skills/skills-codex/overleaf-sync/SKILL.md +220 -0
- package/skills/skills-codex/paper-claim-audit/SKILL.md +350 -0
- package/skills/skills-codex/paper-compile/SKILL.md +253 -0
- package/skills/skills-codex/paper-figure/SKILL.md +311 -0
- package/skills/skills-codex/paper-illustration/SKILL.md +690 -0
- package/skills/skills-codex/paper-illustration-image2/SKILL.md +383 -0
- package/skills/skills-codex/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
- package/skills/skills-codex/paper-plan/SKILL.md +278 -0
- package/skills/skills-codex/paper-poster/SKILL.md +19 -0
- package/skills/skills-codex/paper-poster-html/SKILL.md +377 -0
- package/skills/skills-codex/paper-slides/SKILL.md +571 -0
- package/skills/skills-codex/paper-talk/SKILL.md +381 -0
- package/skills/skills-codex/paper-write/SKILL.md +411 -0
- package/skills/skills-codex/paper-write/templates/IEEEtran.bst +2409 -0
- package/skills/skills-codex/paper-write/templates/IEEEtran.cls +6347 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.bst +1493 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.sty +315 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.tex +952 -0
- package/skills/skills-codex/paper-write/templates/acl.sty +312 -0
- package/skills/skills-codex/paper-write/templates/acl2026.tex +377 -0
- package/skills/skills-codex/paper-write/templates/acl_natbib.bst +1940 -0
- package/skills/skills-codex/paper-write/templates/acm.bst +3081 -0
- package/skills/skills-codex/paper-write/templates/acm_mm2026.tex +204 -0
- package/skills/skills-codex/paper-write/templates/acmart.cls +3520 -0
- package/skills/skills-codex/paper-write/templates/cvpr.bst +1448 -0
- package/skills/skills-codex/paper-write/templates/cvpr.sty +508 -0
- package/skills/skills-codex/paper-write/templates/cvpr2026.tex +63 -0
- package/skills/skills-codex/paper-write/templates/iclr2026.tex +84 -0
- package/skills/skills-codex/paper-write/templates/iclr2026_conference.bst +1440 -0
- package/skills/skills-codex/paper-write/templates/iclr2026_conference.sty +246 -0
- package/skills/skills-codex/paper-write/templates/icml2026.sty +767 -0
- package/skills/skills-codex/paper-write/templates/icml2026.tex +662 -0
- package/skills/skills-codex/paper-write/templates/ieee_conference.tex +89 -0
- package/skills/skills-codex/paper-write/templates/ieee_journal.tex +93 -0
- package/skills/skills-codex/paper-write/templates/math_commands.tex +48 -0
- package/skills/skills-codex/paper-write/templates/neurips2026.tex +493 -0
- package/skills/skills-codex/paper-write/templates/neurips_2026.sty +437 -0
- package/skills/skills-codex/paper-writing/SKILL.md +731 -0
- package/skills/skills-codex/patent-novelty-check/SKILL.md +153 -0
- package/skills/skills-codex/patent-pipeline/SKILL.md +344 -0
- package/skills/skills-codex/patent-review/SKILL.md +202 -0
- package/skills/skills-codex/pixel-art/SKILL.md +139 -0
- package/skills/skills-codex/prior-art-search/SKILL.md +146 -0
- package/skills/skills-codex/proof-checker/SKILL.md +554 -0
- package/skills/skills-codex/proof-orchestrator/SKILL.md +260 -0
- package/skills/skills-codex/proof-orchestrator/references/audit-output-contract.md +126 -0
- package/skills/skills-codex/proof-orchestrator/references/deepseek-routing.md +76 -0
- package/skills/skills-codex/proof-orchestrator/references/dispatch-prompts.md +227 -0
- package/skills/skills-codex/proof-orchestrator/references/notation-audit.md +135 -0
- package/skills/skills-codex/proof-orchestrator/references/proof-audit-rubric.md +70 -0
- package/skills/skills-codex/proof-orchestrator/references/stress-tests.md +38 -0
- package/skills/skills-codex/proof-writer/SKILL.md +222 -0
- package/skills/skills-codex/qzcli/SKILL.md +324 -0
- package/skills/skills-codex/rebuttal/SKILL.md +305 -0
- package/skills/skills-codex/render-html/SKILL.md +305 -0
- package/skills/skills-codex/render-html/scripts/__pycache__/render_html.cpython-314.pyc +0 -0
- package/skills/skills-codex/render-html/scripts/render_html.py +909 -0
- package/skills/skills-codex/render-html/scripts/templates/academic.html +342 -0
- package/skills/skills-codex/render-html/scripts/templates/dashboard.html +333 -0
- package/skills/skills-codex/research-lit/SKILL.md +464 -0
- package/skills/skills-codex/research-pipeline/SKILL.md +340 -0
- package/skills/skills-codex/research-refine/SKILL.md +721 -0
- package/skills/skills-codex/research-refine-pipeline/SKILL.md +186 -0
- package/skills/skills-codex/research-review/SKILL.md +135 -0
- package/skills/skills-codex/research-wiki/SKILL.md +421 -0
- package/skills/skills-codex/resubmit-pipeline/SKILL.md +444 -0
- package/skills/skills-codex/result-to-claim/SKILL.md +246 -0
- package/skills/skills-codex/run-experiment/SKILL.md +236 -0
- package/skills/skills-codex/semantic-scholar/SKILL.md +219 -0
- package/skills/skills-codex/serverless-modal/SKILL.md +335 -0
- package/skills/skills-codex/shared-references/acceptance-gate.md +336 -0
- package/skills/skills-codex/shared-references/assurance-contract.md +139 -0
- package/skills/skills-codex/shared-references/capture-antipatterns.md +84 -0
- package/skills/skills-codex/shared-references/citation-discipline.md +452 -0
- package/skills/skills-codex/shared-references/compute-env-contract.md +163 -0
- package/skills/skills-codex/shared-references/effort-contract.md +143 -0
- package/skills/skills-codex/shared-references/evidence-precheck.md +73 -0
- package/skills/skills-codex/shared-references/experiment-integrity.md +49 -0
- package/skills/skills-codex/shared-references/external-cadence.md +334 -0
- package/skills/skills-codex/shared-references/fan-out-pattern.md +375 -0
- package/skills/skills-codex/shared-references/injection-hygiene.md +132 -0
- package/skills/skills-codex/shared-references/integration-contract.md +372 -0
- package/skills/skills-codex/shared-references/output-composition.md +98 -0
- package/skills/skills-codex/shared-references/output-language.md +45 -0
- package/skills/skills-codex/shared-references/output-manifest.md +40 -0
- package/skills/skills-codex/shared-references/output-versioning.md +111 -0
- package/skills/skills-codex/shared-references/patent-format-cn.md +199 -0
- package/skills/skills-codex/shared-references/patent-format-ep.md +173 -0
- package/skills/skills-codex/shared-references/patent-format-us.md +161 -0
- package/skills/skills-codex/shared-references/patent-writing-principles.md +197 -0
- package/skills/skills-codex/shared-references/prior-art-databases.md +141 -0
- package/skills/skills-codex/shared-references/resumable-runs.md +125 -0
- package/skills/skills-codex/shared-references/review-scope-limits.md +81 -0
- package/skills/skills-codex/shared-references/review-tracing.md +144 -0
- package/skills/skills-codex/shared-references/reviewer-independence.md +66 -0
- package/skills/skills-codex/shared-references/reviewer-routing.md +128 -0
- package/skills/skills-codex/shared-references/skill-governance.md +119 -0
- package/skills/skills-codex/shared-references/taste-calibration.md +90 -0
- package/skills/skills-codex/shared-references/venue-checklists.md +73 -0
- package/skills/skills-codex/shared-references/wiki-helper-resolution.md +69 -0
- package/skills/skills-codex/shared-references/writing-principles.md +525 -0
- package/skills/skills-codex/slides-polish/SKILL.md +563 -0
- package/skills/skills-codex/specification-writing/SKILL.md +211 -0
- package/skills/skills-codex/system-profile/SKILL.md +103 -0
- package/skills/skills-codex/training-check/SKILL.md +83 -0
- package/skills/skills-codex/vast-gpu/SKILL.md +394 -0
- package/skills/skills-codex/web-debug-search/SKILL.md +334 -0
- package/skills/skills-codex/wiki-enrich/SKILL.md +255 -0
- package/skills/skills-codex/writing-systems-papers/SKILL.md +184 -0
- package/skills/skills-codex-claude-review/README.md +79 -0
- package/skills/skills-codex-claude-review/README_CN.md +78 -0
- package/skills/skills-codex-claude-review/auto-paper-improvement-loop/SKILL.md +581 -0
- package/skills/skills-codex-claude-review/auto-review-loop/SKILL.md +510 -0
- package/skills/skills-codex-claude-review/novelty-check/SKILL.md +102 -0
- package/skills/skills-codex-claude-review/paper-figure/SKILL.md +319 -0
- package/skills/skills-codex-claude-review/paper-plan/SKILL.md +287 -0
- package/skills/skills-codex-claude-review/paper-write/SKILL.md +420 -0
- package/skills/skills-codex-claude-review/research-refine/SKILL.md +732 -0
- package/skills/skills-codex-claude-review/research-review/SKILL.md +149 -0
- package/skills/skills-codex-gemini-review/README.md +176 -0
- package/skills/skills-codex-gemini-review/README_CN.md +175 -0
- package/skills/skills-codex-gemini-review/auto-paper-improvement-loop/SKILL.md +331 -0
- package/skills/skills-codex-gemini-review/auto-review-loop/SKILL.md +304 -0
- package/skills/skills-codex-gemini-review/grant-proposal/SKILL.md +630 -0
- package/skills/skills-codex-gemini-review/idea-creator/SKILL.md +263 -0
- package/skills/skills-codex-gemini-review/idea-discovery/SKILL.md +275 -0
- package/skills/skills-codex-gemini-review/idea-discovery-robot/SKILL.md +365 -0
- package/skills/skills-codex-gemini-review/novelty-check/SKILL.md +92 -0
- package/skills/skills-codex-gemini-review/paper-figure/SKILL.md +289 -0
- package/skills/skills-codex-gemini-review/paper-plan/SKILL.md +265 -0
- package/skills/skills-codex-gemini-review/paper-poster-html/SKILL.md +102 -0
- package/skills/skills-codex-gemini-review/paper-slides/SKILL.md +582 -0
- package/skills/skills-codex-gemini-review/paper-write/SKILL.md +346 -0
- package/skills/skills-codex-gemini-review/paper-writing/SKILL.md +312 -0
- package/skills/skills-codex-gemini-review/research-refine/SKILL.md +674 -0
- package/skills/skills-codex-gemini-review/research-review/SKILL.md +112 -0
- package/skills/slides-polish/SKILL.md +565 -0
- package/skills/specification-writing/SKILL.md +211 -0
- package/skills/system-profile/SKILL.md +103 -0
- package/skills/training-check/SKILL.md +132 -0
- package/skills/vast-gpu/SKILL.md +394 -0
- package/skills/web-debug-search/SKILL.md +334 -0
- package/skills/wiki-enrich/SKILL.md +257 -0
- package/skills/writing-systems-papers/SKILL.md +184 -0
- package/templates/CLAUDE_MD_TEMPLATE.md +29 -0
- package/templates/EXPERIMENT_LOG_TEMPLATE.md +47 -0
- package/templates/EXPERIMENT_PLAN_TEMPLATE.md +51 -0
- package/templates/EXPERIMENT_PLAN_TEMPLATE_CN.md +53 -0
- package/templates/FINDINGS_TEMPLATE.md +52 -0
- package/templates/IDEA_CANDIDATES_TEMPLATE.md +47 -0
- package/templates/IDEA_CANDIDATES_TEMPLATE_CN.md +47 -0
- package/templates/INVENTION_BRIEF_TEMPLATE.md +87 -0
- package/templates/MANIFEST_TEMPLATE.md +7 -0
- package/templates/NARRATIVE_REPORT_TEMPLATE.md +49 -0
- package/templates/PAPER_PLAN_TEMPLATE.md +47 -0
- package/templates/PATENT_CLAIMS_TEMPLATE.md +78 -0
- package/templates/PATENT_SPECIFICATION_TEMPLATE.md +68 -0
- package/templates/README.md +57 -0
- package/templates/RESEARCH_BRIEF_TEMPLATE.md +35 -0
- package/templates/RESEARCH_BRIEF_TEMPLATE_CN.md +41 -0
- package/templates/RESEARCH_CONTRACT_TEMPLATE.md +60 -0
- package/templates/claude-hooks/corpus_write_guard.json +16 -0
- package/templates/claude-hooks/corpus_write_guard.py +85 -0
- package/templates/claude-hooks/meta_logging.json +74 -0
- package/templates/gitignore-trace.txt +3 -0
- package/tools/__pycache__/check_skills_inventory.cpython-314.pyc +0 -0
- package/tools/arxiv_fetch.py +311 -0
- package/tools/capture_filter.py +126 -0
- package/tools/check_skills_inventory.py +273 -0
- package/tools/convert_skills_to_llm_chat.py +282 -0
- package/tools/copilot_native_evidence.py +818 -0
- package/tools/deepxiv_fetch.py +213 -0
- package/tools/evidence_check.py +212 -0
- package/tools/exa_search.py +425 -0
- package/tools/experiment_queue/README.md +118 -0
- package/tools/experiment_queue/build_manifest.py +44 -0
- package/tools/experiment_queue/queue_manager.py +44 -0
- package/tools/extract_paper_style.py +560 -0
- package/tools/figure_renderer.py +69 -0
- package/tools/forensics_gate.py +669 -0
- package/tools/generate_codex_claude_review_overrides.py +299 -0
- package/tools/idea_discovery_gate.py +256 -0
- package/tools/install_aris.ps1 +1372 -0
- package/tools/install_aris.sh +1370 -0
- package/tools/install_aris_codex.sh +1023 -0
- package/tools/install_aris_copilot.sh +1052 -0
- package/tools/iteration_log.py +143 -0
- package/tools/lint_skills_helpers.sh +84 -0
- package/tools/meta_opt/check_ready.sh +80 -0
- package/tools/meta_opt/log_event.sh +91 -0
- package/tools/meta_opt/trigger_eval.py +280 -0
- package/tools/meta_opt/trigger_evals.sample.json +28 -0
- package/tools/openalex_fetch.py +326 -0
- package/tools/overleaf_audit.sh +104 -0
- package/tools/overleaf_setup.sh +150 -0
- package/tools/paper_illustration_image2.py +62 -0
- package/tools/provenance.py +294 -0
- package/tools/research_wiki.py +1720 -0
- package/tools/review_gate.py +502 -0
- package/tools/run_state.py +399 -0
- package/tools/save_trace.sh +477 -0
- package/tools/semantic_scholar_fetch.py +438 -0
- package/tools/skill-groups.tsv +116 -0
- package/tools/skill_picker.py +238 -0
- package/tools/smart_update.ps1 +521 -0
- package/tools/smart_update.sh +591 -0
- package/tools/smart_update_codex.sh +419 -0
- package/tools/smart_update_copilot.sh +605 -0
- package/tools/threat_scan.py +222 -0
- package/tools/verify_paper_audits.sh +487 -0
- package/tools/verify_papers.py +613 -0
- package/tools/verify_wiki_coverage.sh +176 -0
- package/tools/watchdog.py +485 -0
|
@@ -0,0 +1,1370 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install_aris.sh — Project-local ARIS skill installation (flat per-skill symlinks).
|
|
3
|
+
#
|
|
4
|
+
# Each ARIS skill is symlinked into `<project>/.claude/skills/<skill-name>` so
|
|
5
|
+
# Claude Code's slash-command discovery (which only scans one level deep) finds it.
|
|
6
|
+
# A versioned manifest at `<project>/.aris/installed-skills.txt` tracks every
|
|
7
|
+
# entry this installer created — uninstall and reconcile read from the manifest
|
|
8
|
+
# and never touch user-owned skills with the same name.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# bash tools/install_aris.sh [project_path] [options]
|
|
12
|
+
#
|
|
13
|
+
# Actions (mutually exclusive, default: auto):
|
|
14
|
+
# default install if no manifest, else reconcile
|
|
15
|
+
# --reconcile explicit reconcile; refuse if no manifest
|
|
16
|
+
# --uninstall remove only entries in manifest; delete manifest
|
|
17
|
+
#
|
|
18
|
+
# Selection (catalog: tools/skill-groups.tsv):
|
|
19
|
+
# --groups A,B install only these skill groups (see --list-groups)
|
|
20
|
+
# --skills X,Y additionally install these skills (clears declined mark)
|
|
21
|
+
# --exclude X,Y never install these skills (recorded as declined)
|
|
22
|
+
# --all install every upstream skill (legacy default)
|
|
23
|
+
# --add-new reconcile: accept all upstream skills not yet installed
|
|
24
|
+
# --skip-new reconcile: skip new upstream skills without prompting
|
|
25
|
+
# --list-groups print the group catalog and exit
|
|
26
|
+
# With no selection flags: fresh install on a TTY opens a full-screen
|
|
27
|
+
# checkbox picker (Space toggles a skill / a whole group, Enter confirms;
|
|
28
|
+
# ARIS_NO_PICKER=1 or missing python3/curses falls back to per-group
|
|
29
|
+
# Y/n/e prompts); fresh install with --quiet/no TTY installs everything (old behavior).
|
|
30
|
+
# Reconcile keeps exactly what the manifest says is installed; NEW upstream
|
|
31
|
+
# skills need per-skill confirmation (declined ones are remembered in
|
|
32
|
+
# .aris/skills-declined.txt and never re-asked).
|
|
33
|
+
#
|
|
34
|
+
# Options:
|
|
35
|
+
# --aris-repo PATH override aris-repo discovery
|
|
36
|
+
# --dry-run show plan, no writes
|
|
37
|
+
# --quiet no prompts; abort on any condition that would prompt
|
|
38
|
+
# --no-doc skip CLAUDE.md update
|
|
39
|
+
# --adopt-existing NAME adopt a non-managed symlink that already points to
|
|
40
|
+
# the correct upstream target (repeatable)
|
|
41
|
+
# --replace-link NAME replace an upstream-internal symlink that points to
|
|
42
|
+
# a DIFFERENT entry than expected (repeatable)
|
|
43
|
+
# --platform PLATFORM force platform: codex or claude (default: auto-detect)
|
|
44
|
+
# --from-old trigger migration from legacy nested install
|
|
45
|
+
# (.claude/skills/aris/)
|
|
46
|
+
# --migrate-copy STRAT for legacy COPY install: STRAT = keep-user | prefer-upstream
|
|
47
|
+
# (default: refuse)
|
|
48
|
+
# --clear-stale-lock remove stale lock dir from a crashed prior run
|
|
49
|
+
# (host+PID metadata is verified before removal)
|
|
50
|
+
#
|
|
51
|
+
# Safety rules enforced:
|
|
52
|
+
# S1 Never delete a path that is not a symlink.
|
|
53
|
+
# S2 Never delete a symlink whose target is outside the configured aris-repo.
|
|
54
|
+
# S3 Never delete a symlink not listed in the manifest (except via --uninstall
|
|
55
|
+
# which only deletes manifest entries).
|
|
56
|
+
# S4 Never overwrite an existing path during CREATE — abort by default.
|
|
57
|
+
# S5 Manifest write is atomic (temp + rename in same dir).
|
|
58
|
+
# S6 Concurrent runs in same project serialize via mkdir lockdir.
|
|
59
|
+
# S7 Crash mid-apply leaves the previous manifest intact; rerun adopts.
|
|
60
|
+
# S8 Uninstall revalidates each managed symlink's target before removing.
|
|
61
|
+
# S9 If .aris/, .claude/, or .claude/skills/ is itself a symlink, abort.
|
|
62
|
+
# S10 Reject upstream entries that are symlinks to outside aris-repo.
|
|
63
|
+
# S11 Revalidate exact target match (lstat + readlink) before every mutation.
|
|
64
|
+
# S12 The optional `.aris/tools` symlink (added in #174) is the only managed
|
|
65
|
+
# artifact NOT tracked in the manifest. It is identified at uninstall
|
|
66
|
+
# time by exact target match against `<aris-repo>/tools`. Any other
|
|
67
|
+
# path or differently-targeted symlink at `.aris/tools` is left alone.
|
|
68
|
+
# S12 Temp files live in the same directory as the destination.
|
|
69
|
+
# S13 Skill names must match ^[A-Za-z0-9][A-Za-z0-9._-]*$ (slug regex).
|
|
70
|
+
# S14 Copilot agent links are removed only when listed in the dedicated
|
|
71
|
+
# installed-agent-profiles.txt ownership sidecar.
|
|
72
|
+
|
|
73
|
+
set -euo pipefail
|
|
74
|
+
|
|
75
|
+
# ─── Constants ────────────────────────────────────────────────────────────────
|
|
76
|
+
MANIFEST_VERSION="1"
|
|
77
|
+
MANIFEST_NAME="installed-skills.txt"
|
|
78
|
+
MANIFEST_PREV_NAME="installed-skills.txt.prev"
|
|
79
|
+
AGENT_MANIFEST_NAME="installed-agent-profiles.txt"
|
|
80
|
+
DECLINED_NAME="skills-declined.txt"
|
|
81
|
+
CATALOG_REL="tools/skill-groups.tsv"
|
|
82
|
+
GLOBAL_POINTER="$HOME/.aris/repo"
|
|
83
|
+
ARIS_DIR_NAME=".aris"
|
|
84
|
+
LOCK_DIR_NAME=".install.lock.d"
|
|
85
|
+
SKILLS_REL=".claude/skills"
|
|
86
|
+
DOC_FILE_NAME="CLAUDE.md"
|
|
87
|
+
BLOCK_BEGIN="<!-- ARIS:BEGIN -->"
|
|
88
|
+
BLOCK_END="<!-- ARIS:END -->"
|
|
89
|
+
SAFE_NAME_REGEX='^[A-Za-z0-9][A-Za-z0-9._-]*$'
|
|
90
|
+
SUPPORT_NAMES=("shared-references")
|
|
91
|
+
AGENT_PROFILES_SRC=".github/agents" # Copilot agent profiles deployed alongside skills (F5)
|
|
92
|
+
EXCLUDE_TOP_NAMES=("skills-codex" "skills-codex.bak") # not skills, not symlinked
|
|
93
|
+
|
|
94
|
+
# ─── Argument parsing ─────────────────────────────────────────────────────────
|
|
95
|
+
PROJECT_PATH=""
|
|
96
|
+
ARIS_REPO_OVERRIDE=""
|
|
97
|
+
ACTION="auto" # auto | reconcile | uninstall
|
|
98
|
+
DRY_RUN=false
|
|
99
|
+
QUIET=false
|
|
100
|
+
NO_DOC=false
|
|
101
|
+
FROM_OLD=false
|
|
102
|
+
MIGRATE_COPY="" # "" | keep-user | prefer-upstream
|
|
103
|
+
CLEAR_STALE_LOCK=false
|
|
104
|
+
ADOPT_NAMES=()
|
|
105
|
+
REPLACE_LINK_NAMES=()
|
|
106
|
+
SELECT_GROUPS="" # comma list from --groups
|
|
107
|
+
SELECT_SKILLS="" # comma list from --skills
|
|
108
|
+
EXCLUDE_SKILLS="" # comma list from --exclude
|
|
109
|
+
SELECT_ALL=false
|
|
110
|
+
NEW_POLICY="" # "" (prompt) | add | skip
|
|
111
|
+
LIST_GROUPS=false
|
|
112
|
+
|
|
113
|
+
usage() { sed -n '2,69p' "$0" | sed 's/^# \?//'; }
|
|
114
|
+
|
|
115
|
+
FORWARDED_ARGS=()
|
|
116
|
+
PLATFORM_OVERRIDE=""
|
|
117
|
+
DETECTED_PLATFORM=""
|
|
118
|
+
CLAUDE_ONLY_FLAGS_USED=()
|
|
119
|
+
CODEX_ONLY_FLAGS_SEEN=()
|
|
120
|
+
|
|
121
|
+
# ─── Platform auto-detection for Codex CLI (#180) ──────────────────────────────
|
|
122
|
+
# If the project has Codex markers (.agents/, AGENTS.md, .codex/config.toml)
|
|
123
|
+
# and NO Claude markers (.claude/, CLAUDE.md), delegate to install_aris_codex.sh.
|
|
124
|
+
auto_detect_platform() {
|
|
125
|
+
local proj="$1"
|
|
126
|
+
local has_strong_codex_markers=false
|
|
127
|
+
local has_codex_hint=false
|
|
128
|
+
local has_claude_markers=false
|
|
129
|
+
|
|
130
|
+
# Strong Codex markers. AGENTS.md by itself is only a hint because many Claude
|
|
131
|
+
# projects may carry repo-level agent instructions before .claude/ exists.
|
|
132
|
+
[[ -d "$proj/.agents" || -f "$proj/.codex/config.toml" ]] && has_strong_codex_markers=true
|
|
133
|
+
[[ -f "$proj/AGENTS.md" ]] && has_codex_hint=true
|
|
134
|
+
|
|
135
|
+
# Claude markers
|
|
136
|
+
[[ -d "$proj/.claude" || -f "$proj/CLAUDE.md" || -f "$proj/.claude/settings.json" ]] && has_claude_markers=true
|
|
137
|
+
|
|
138
|
+
if $has_strong_codex_markers && $has_claude_markers; then
|
|
139
|
+
warn "Both Claude and Codex markers found in $proj"
|
|
140
|
+
warn " Defaulting to claude; use --platform codex to override."
|
|
141
|
+
DETECTED_PLATFORM="claude"
|
|
142
|
+
elif $has_strong_codex_markers && ! $has_claude_markers; then
|
|
143
|
+
DETECTED_PLATFORM="codex"
|
|
144
|
+
elif $has_claude_markers; then
|
|
145
|
+
DETECTED_PLATFORM="claude"
|
|
146
|
+
elif $has_codex_hint; then
|
|
147
|
+
warn "AGENTS.md found without .agents/ or .codex/config.toml; defaulting to claude."
|
|
148
|
+
warn " Use --platform codex to delegate to install_aris_codex.sh."
|
|
149
|
+
fi
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
while [[ $# -gt 0 ]]; do
|
|
153
|
+
case "$1" in
|
|
154
|
+
--reconcile) FORWARDED_ARGS+=("$1"); ACTION="reconcile"; shift ;;
|
|
155
|
+
--uninstall) FORWARDED_ARGS+=("$1"); ACTION="uninstall"; shift ;;
|
|
156
|
+
--aris-repo) FORWARDED_ARGS+=("$1" "${2:?--aris-repo requires path}"); ARIS_REPO_OVERRIDE="$2"; shift 2 ;;
|
|
157
|
+
--dry-run) FORWARDED_ARGS+=("$1"); DRY_RUN=true; shift ;;
|
|
158
|
+
--quiet) FORWARDED_ARGS+=("$1"); QUIET=true; shift ;;
|
|
159
|
+
--no-doc) FORWARDED_ARGS+=("$1"); NO_DOC=true; shift ;;
|
|
160
|
+
--from-old) FORWARDED_ARGS+=("$1"); FROM_OLD=true; CLAUDE_ONLY_FLAGS_USED+=("--from-old"); shift ;;
|
|
161
|
+
--migrate-copy) FORWARDED_ARGS+=("$1" "${2:?--migrate-copy requires keep-user|prefer-upstream}"); MIGRATE_COPY="$2"; CLAUDE_ONLY_FLAGS_USED+=("--migrate-copy"); shift 2 ;;
|
|
162
|
+
--clear-stale-lock) FORWARDED_ARGS+=("$1"); CLEAR_STALE_LOCK=true; shift ;;
|
|
163
|
+
--adopt-existing) FORWARDED_ARGS+=("$1" "${2:?--adopt-existing requires NAME}"); ADOPT_NAMES+=("$2"); CLAUDE_ONLY_FLAGS_USED+=("--adopt-existing"); shift 2 ;;
|
|
164
|
+
--replace-link) FORWARDED_ARGS+=("$1" "${2:?--replace-link requires NAME}"); REPLACE_LINK_NAMES+=("$2"); shift 2 ;;
|
|
165
|
+
--groups) FORWARDED_ARGS+=("$1" "${2:?--groups requires A,B,...}"); SELECT_GROUPS="${SELECT_GROUPS:+$SELECT_GROUPS,}$2"; shift 2 ;;
|
|
166
|
+
--skills) FORWARDED_ARGS+=("$1" "${2:?--skills requires X,Y,...}"); SELECT_SKILLS="${SELECT_SKILLS:+$SELECT_SKILLS,}$2"; shift 2 ;;
|
|
167
|
+
--exclude) FORWARDED_ARGS+=("$1" "${2:?--exclude requires X,Y,...}"); EXCLUDE_SKILLS="${EXCLUDE_SKILLS:+$EXCLUDE_SKILLS,}$2"; shift 2 ;;
|
|
168
|
+
--all) FORWARDED_ARGS+=("$1"); SELECT_ALL=true; shift ;;
|
|
169
|
+
--add-new) FORWARDED_ARGS+=("$1"); NEW_POLICY="add"; shift ;;
|
|
170
|
+
--skip-new) FORWARDED_ARGS+=("$1"); NEW_POLICY="skip"; shift ;;
|
|
171
|
+
--list-groups) FORWARDED_ARGS+=("$1"); LIST_GROUPS=true; shift ;;
|
|
172
|
+
--platform)
|
|
173
|
+
PLATFORM_OVERRIDE="${2:?--platform requires codex|claude}"; shift 2 ;;
|
|
174
|
+
--force)
|
|
175
|
+
echo "Error: --force is removed. Use the granular flags:" >&2
|
|
176
|
+
echo " --adopt-existing NAME (for non-managed symlinks pointing to correct upstream)" >&2
|
|
177
|
+
echo " --replace-link NAME (for managed symlinks pointing to different upstream entry)" >&2
|
|
178
|
+
echo " Real files/directories are never overwritten — back up and remove them yourself." >&2
|
|
179
|
+
exit 2 ;;
|
|
180
|
+
-h|--help) usage; exit 0 ;;
|
|
181
|
+
# Codex-only flags: recognized by parent, forwarded during delegation
|
|
182
|
+
--with-claude-review-overlay) FORWARDED_ARGS+=("$1"); CODEX_ONLY_FLAGS_SEEN+=("$1"); shift ;;
|
|
183
|
+
--with-gemini-review-overlay) FORWARDED_ARGS+=("$1"); CODEX_ONLY_FLAGS_SEEN+=("$1"); shift ;;
|
|
184
|
+
--*) echo "Unknown option: $1" >&2; exit 2 ;;
|
|
185
|
+
*)
|
|
186
|
+
if [[ -z "$PROJECT_PATH" ]]; then PROJECT_PATH="$1"
|
|
187
|
+
else echo "Error: unexpected positional: $1" >&2; exit 2; fi
|
|
188
|
+
FORWARDED_ARGS+=("$1"); shift ;;
|
|
189
|
+
esac
|
|
190
|
+
done
|
|
191
|
+
|
|
192
|
+
if [[ -n "$MIGRATE_COPY" && "$MIGRATE_COPY" != "keep-user" && "$MIGRATE_COPY" != "prefer-upstream" ]]; then
|
|
193
|
+
echo "Error: --migrate-copy must be keep-user or prefer-upstream (got: $MIGRATE_COPY)" >&2; exit 2
|
|
194
|
+
fi
|
|
195
|
+
if [[ -n "$PLATFORM_OVERRIDE" && "$PLATFORM_OVERRIDE" != "codex" && "$PLATFORM_OVERRIDE" != "claude" ]]; then
|
|
196
|
+
echo "Error: --platform must be codex or claude (got: $PLATFORM_OVERRIDE)" >&2; exit 2
|
|
197
|
+
fi
|
|
198
|
+
if $SELECT_ALL && [[ -n "$SELECT_GROUPS$SELECT_SKILLS" ]]; then
|
|
199
|
+
echo "Error: --all cannot be combined with --groups/--skills (only --exclude)" >&2; exit 2
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
203
|
+
log() { $QUIET && return 0; echo "$@"; }
|
|
204
|
+
warn() { echo "warning: $*" >&2; }
|
|
205
|
+
die() { echo "error: $*" >&2; exit 1; }
|
|
206
|
+
prompt() { $QUIET && return 1; printf "%s " "$1" >&2; read -r REPLY; [[ "$REPLY" =~ ^[Yy]$ ]]; }
|
|
207
|
+
abs_path() { ( cd "$1" 2>/dev/null && pwd ) || return 1; }
|
|
208
|
+
|
|
209
|
+
is_safe_name() { [[ "$1" =~ $SAFE_NAME_REGEX ]]; }
|
|
210
|
+
|
|
211
|
+
# Read symlink target without following further (one hop)
|
|
212
|
+
read_link_target() {
|
|
213
|
+
if command -v greadlink >/dev/null 2>&1; then greadlink "$1"
|
|
214
|
+
else readlink "$1"; fi
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Resolve a path, following all symlinks, to a canonical absolute path
|
|
218
|
+
canonicalize() {
|
|
219
|
+
if command -v greadlink >/dev/null 2>&1; then greadlink -f "$1" 2>/dev/null || true
|
|
220
|
+
elif readlink -f "$1" 2>/dev/null; then :
|
|
221
|
+
else
|
|
222
|
+
# macOS fallback: cd + pwd
|
|
223
|
+
local d f
|
|
224
|
+
if [[ -d "$1" ]]; then ( cd "$1" && pwd )
|
|
225
|
+
else d="$(dirname "$1")"; f="$(basename "$1")"; ( cd "$d" 2>/dev/null && echo "$(pwd)/$f" )
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# True if $1 is a symlink (lstat-style; doesn't follow)
|
|
231
|
+
is_symlink() { [[ -L "$1" ]]; }
|
|
232
|
+
|
|
233
|
+
# Find aris-repo location
|
|
234
|
+
resolve_aris_repo() {
|
|
235
|
+
local p
|
|
236
|
+
if [[ -n "$ARIS_REPO_OVERRIDE" ]]; then
|
|
237
|
+
p="$(abs_path "$ARIS_REPO_OVERRIDE")" || die "--aris-repo path not found: $ARIS_REPO_OVERRIDE"
|
|
238
|
+
[[ -d "$p/skills" ]] || die "--aris-repo has no skills/ subdir: $p"
|
|
239
|
+
echo "$p"; return
|
|
240
|
+
fi
|
|
241
|
+
local script_dir parent
|
|
242
|
+
script_dir="$(cd "$(dirname "$0")" && pwd)"
|
|
243
|
+
parent="$(cd "$script_dir/.." && pwd)"
|
|
244
|
+
if [[ -d "$parent/skills" ]]; then echo "$parent"; return; fi
|
|
245
|
+
if [[ -n "${ARIS_REPO:-}" && -d "$ARIS_REPO/skills" ]]; then abs_path "$ARIS_REPO"; return; fi
|
|
246
|
+
for guess in \
|
|
247
|
+
"$HOME/Desktop/aris_repo" \
|
|
248
|
+
"$HOME/aris_repo" \
|
|
249
|
+
"$HOME/.aris" \
|
|
250
|
+
"$HOME/Desktop/Auto-claude-code-research-in-sleep" \
|
|
251
|
+
"$HOME/.codex/Auto-claude-code-research-in-sleep" \
|
|
252
|
+
"$HOME/.claude/Auto-claude-code-research-in-sleep" ; do
|
|
253
|
+
[[ -d "$guess/skills" ]] && { abs_path "$guess"; return; }
|
|
254
|
+
done
|
|
255
|
+
die "cannot find ARIS repo. Use --aris-repo PATH or set ARIS_REPO env var."
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# Build the upstream inventory: array of "kind|name" entries
|
|
259
|
+
# Skills = top-level dirs in <aris-repo>/skills/ containing SKILL.md
|
|
260
|
+
# Support = explicitly listed support directories (shared-references)
|
|
261
|
+
# Rejects: anything in EXCLUDE_TOP_NAMES, names failing slug regex, symlinks to outside aris-repo (S10)
|
|
262
|
+
build_upstream_inventory() {
|
|
263
|
+
local repo="$1"
|
|
264
|
+
local skills_dir="$repo/skills"
|
|
265
|
+
local entries=() name kind src
|
|
266
|
+
# skills (with SKILL.md)
|
|
267
|
+
for d in "$skills_dir"/*/; do
|
|
268
|
+
name="$(basename "$d")"
|
|
269
|
+
is_safe_name "$name" || { warn "skipping unsafe upstream name: $name"; continue; }
|
|
270
|
+
# exclude listed
|
|
271
|
+
for ex in "${EXCLUDE_TOP_NAMES[@]}"; do [[ "$name" == "$ex" ]] && continue 2; done
|
|
272
|
+
# support entries handled separately
|
|
273
|
+
local is_support=false
|
|
274
|
+
for s in "${SUPPORT_NAMES[@]}"; do [[ "$name" == "$s" ]] && { is_support=true; break; }; done
|
|
275
|
+
if $is_support; then continue; fi
|
|
276
|
+
if [[ ! -f "$d/SKILL.md" ]]; then continue; fi
|
|
277
|
+
# S10: source must not be a symlink leading outside the repo
|
|
278
|
+
src="$skills_dir/$name"
|
|
279
|
+
if is_symlink "$src"; then
|
|
280
|
+
local resolved; resolved="$(canonicalize "$src")"
|
|
281
|
+
[[ "$resolved" == "$repo"/* ]] || { warn "skipping upstream symlink leading outside repo: $name -> $resolved"; continue; }
|
|
282
|
+
fi
|
|
283
|
+
entries+=("skill|$name")
|
|
284
|
+
done
|
|
285
|
+
# support directories (existing only)
|
|
286
|
+
for s in "${SUPPORT_NAMES[@]}"; do
|
|
287
|
+
if [[ -d "$skills_dir/$s" ]]; then entries+=("support|$s"); fi
|
|
288
|
+
done
|
|
289
|
+
# bash 3.2: empty-array "${ARR[@]}" trips `set -u`; an upstream with no valid
|
|
290
|
+
# entries should yield empty output so the caller's own diagnostic fires.
|
|
291
|
+
if [[ ${#entries[@]} -gt 0 ]]; then
|
|
292
|
+
printf "%s\n" "${entries[@]}"
|
|
293
|
+
fi
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# ─── Selective install (#366) ─────────────────────────────────────────────────
|
|
297
|
+
# Catalog = tools/skill-groups.tsv in the aris-repo. Two record types:
|
|
298
|
+
# group\t<id>\t<display>\t<description>
|
|
299
|
+
# skill\t<name>\t<group-id>\t<requires: comma list or "-">
|
|
300
|
+
# Selection state lives in two project files:
|
|
301
|
+
# .aris/installed-skills.txt — what IS installed (existing manifest)
|
|
302
|
+
# .aris/skills-declined.txt — skills the user explicitly said no to;
|
|
303
|
+
# never re-prompted on reconcile.
|
|
304
|
+
# "Skipped" (via --skip-new / --quiet) is NOT declined — those skills are
|
|
305
|
+
# asked about again on the next interactive reconcile.
|
|
306
|
+
|
|
307
|
+
catalog_ok() { [[ -n "${CATALOG_PATH:-}" && -f "$CATALOG_PATH" ]]; }
|
|
308
|
+
catalog_groups() { awk -F'\t' '$1=="group"{print $2 "\t" $3 "\t" $4}' "$CATALOG_PATH"; }
|
|
309
|
+
catalog_group_of() { awk -F'\t' -v s="$1" '$1=="skill" && $2==s {print $3; exit}' "$CATALOG_PATH"; }
|
|
310
|
+
catalog_desc_of() { awk -F'\t' -v s="$1" '$1=="skill" && $2==s {print (NF>=5?$5:""); exit}' "$CATALOG_PATH"; }
|
|
311
|
+
catalog_group_display() { awk -F'\t' -v g="$1" '$1=="group" && $2==g {print $3; exit}' "$CATALOG_PATH"; }
|
|
312
|
+
catalog_requires() { awk -F'\t' -v s="$1" '$1=="skill" && $2==s && $4!="-" {print $4; exit}' "$CATALOG_PATH" | tr ',' '\n'; }
|
|
313
|
+
catalog_skills_in_group() { awk -F'\t' -v g="$1" '$1=="skill" && $3==g {print $2}' "$CATALOG_PATH"; }
|
|
314
|
+
catalog_has_skill() { awk -F'\t' -v s="$1" '$1=="skill" && $2==s {found=1; exit} END{exit !found}' "$CATALOG_PATH"; }
|
|
315
|
+
|
|
316
|
+
in_file() { grep -qxF "$1" "$2" 2>/dev/null; }
|
|
317
|
+
upstream_has_skill() { grep -q "^skill|$1\$" "$2"; }
|
|
318
|
+
|
|
319
|
+
print_group_catalog() {
|
|
320
|
+
catalog_ok || die "skill catalog not found: ${CATALOG_PATH:-<unset>}"
|
|
321
|
+
echo "Skill groups (from $CATALOG_PATH):"
|
|
322
|
+
local gid display desc n
|
|
323
|
+
while IFS=$'\t' read -r gid display desc; do
|
|
324
|
+
n=$(catalog_skills_in_group "$gid" | wc -l | tr -d ' ')
|
|
325
|
+
printf "\n %-14s %s — %s [%s skills]\n" "$gid" "$display" "$desc" "$n"
|
|
326
|
+
awk -F'\t' -v g="$gid" '$1=="skill" && $3==g {printf " %-28s %s\n", $2, (NF>=5?$5:"")}' "$CATALOG_PATH"
|
|
327
|
+
done < <(catalog_groups)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
load_declined() { # $1 = out file
|
|
331
|
+
: > "$1"
|
|
332
|
+
[[ -f "$DECLINED_PATH" ]] || return 0
|
|
333
|
+
grep -v '^[[:space:]]*$' "$DECLINED_PATH" >> "$1" || true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
# Final declined set = (old declined ∪ new declines ∪ excludes ∪ fresh
|
|
337
|
+
# unselected) minus everything selected. S5-style atomic write, same dir.
|
|
338
|
+
save_declined() { # $1 = candidates file, $2 = selected file
|
|
339
|
+
$DRY_RUN && return 0
|
|
340
|
+
mkdir -p "$PROJECT_ARIS_DIR"
|
|
341
|
+
local tmp="$DECLINED_PATH.tmp.$$"
|
|
342
|
+
sort -u "$1" | grep -v '^$' | grep -vxF -f "$2" > "$tmp" || true
|
|
343
|
+
if [[ -s "$tmp" || -f "$DECLINED_PATH" ]]; then
|
|
344
|
+
mv -f "$tmp" "$DECLINED_PATH"
|
|
345
|
+
else
|
|
346
|
+
rm -f "$tmp"
|
|
347
|
+
fi
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
# Auto-include hard pipeline deps (catalog `requires` column, transitively).
|
|
351
|
+
# A dep excluded via --exclude this run is never auto-added — warn instead.
|
|
352
|
+
expand_deps() { # $1 = selected file, $2 = excludes file, $3 = upstream file
|
|
353
|
+
catalog_ok || return 0
|
|
354
|
+
local changed=1 name dep
|
|
355
|
+
while (( changed )); do
|
|
356
|
+
changed=0
|
|
357
|
+
for name in $(cat "$1"); do
|
|
358
|
+
for dep in $(catalog_requires "$name"); do
|
|
359
|
+
in_file "$dep" "$1" && continue
|
|
360
|
+
if in_file "$dep" "$2"; then
|
|
361
|
+
warn "'$name' requires '$dep' but it is excluded — that pipeline phase will break"
|
|
362
|
+
continue
|
|
363
|
+
fi
|
|
364
|
+
upstream_has_skill "$dep" "$3" || continue
|
|
365
|
+
echo "$dep" >> "$1"
|
|
366
|
+
log " ↳ auto-including '$dep' (required by '$name')"
|
|
367
|
+
changed=1
|
|
368
|
+
done
|
|
369
|
+
done
|
|
370
|
+
done
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# Interactive selection (fresh install on a TTY, no selection flags).
|
|
374
|
+
# Preferred UI: full-screen checkbox picker (tools/skill_picker.py, curses) —
|
|
375
|
+
# Space toggles a skill / a whole group, Enter confirms, q aborts. Falls back
|
|
376
|
+
# to the classic per-group Y/n/e prompts when python3/curses/tty are
|
|
377
|
+
# unavailable or ARIS_NO_PICKER=1 is set.
|
|
378
|
+
interactive_select() { # $1 = upstream file, $2 = out (selected) file
|
|
379
|
+
if [[ "${ARIS_NO_PICKER:-0}" != "1" ]] && command -v python3 >/dev/null 2>&1 \
|
|
380
|
+
&& [[ -f "$ARIS_REPO/tools/skill_picker.py" ]]; then
|
|
381
|
+
local avail_f rc=0
|
|
382
|
+
avail_f="$(mktemp -t aris-avail.XXXX)"
|
|
383
|
+
grep '^skill|' "$1" | cut -d'|' -f2 > "$avail_f" || true
|
|
384
|
+
python3 "$ARIS_REPO/tools/skill_picker.py" \
|
|
385
|
+
--catalog "$CATALOG_PATH" --available "$avail_f" --out "$2" \
|
|
386
|
+
</dev/tty >/dev/tty || rc=$?
|
|
387
|
+
rm -f "$avail_f"
|
|
388
|
+
if [[ $rc -eq 0 ]]; then return 0
|
|
389
|
+
elif [[ $rc -eq 1 ]]; then die "interactive selection aborted"
|
|
390
|
+
fi
|
|
391
|
+
# rc=2 (or unexpected): picker unusable — fall through to prompts.
|
|
392
|
+
warn "checkbox picker unavailable — using per-group prompts"
|
|
393
|
+
fi
|
|
394
|
+
log ""
|
|
395
|
+
log "Interactive skill selection — per group: Y=install all, n=skip, e=pick per skill."
|
|
396
|
+
local gid display desc n reply r2 name glist
|
|
397
|
+
glist="$(mktemp -t aris-glist.XXXX)"
|
|
398
|
+
while IFS=$'\t' read -r gid display desc; do
|
|
399
|
+
catalog_skills_in_group "$gid" | while read -r name; do
|
|
400
|
+
upstream_has_skill "$name" "$1" && echo "$name"
|
|
401
|
+
done > "$glist" || true
|
|
402
|
+
n=$(wc -l < "$glist" | tr -d ' ')
|
|
403
|
+
(( n == 0 )) && continue
|
|
404
|
+
printf "\n%s (%s) — %s\n" "$display" "$gid" "$desc" >&2
|
|
405
|
+
sed 's/^/ /' "$glist" >&2
|
|
406
|
+
printf "Install group '%s' (%s skills)? [Y/n/e] " "$gid" "$n" >&2
|
|
407
|
+
read -r reply </dev/tty
|
|
408
|
+
case "$reply" in
|
|
409
|
+
[nN]*) : ;;
|
|
410
|
+
[eE]*)
|
|
411
|
+
while read -r name; do
|
|
412
|
+
printf " install %-30s [Y/n] " "$name" >&2
|
|
413
|
+
read -r r2 </dev/tty
|
|
414
|
+
[[ "$r2" =~ ^[nN] ]] || echo "$name" >> "$2"
|
|
415
|
+
done < "$glist"
|
|
416
|
+
;;
|
|
417
|
+
*) cat "$glist" >> "$2" ;;
|
|
418
|
+
esac
|
|
419
|
+
done < <(catalog_groups)
|
|
420
|
+
# Upstream skills the catalog doesn't know yet (catalog drift): never drop silently.
|
|
421
|
+
local ungrouped; ungrouped="$(mktemp -t aris-ungrouped.XXXX)"
|
|
422
|
+
grep '^skill|' "$1" | cut -d'|' -f2 | while read -r name; do
|
|
423
|
+
catalog_has_skill "$name" || echo "$name"
|
|
424
|
+
done > "$ungrouped" || true
|
|
425
|
+
if [[ -s "$ungrouped" ]]; then
|
|
426
|
+
printf "\nSkills not in the catalog yet:\n" >&2
|
|
427
|
+
sed 's/^/ /' "$ungrouped" >&2
|
|
428
|
+
while read -r name; do
|
|
429
|
+
printf " install %-30s [Y/n] " "$name" >&2
|
|
430
|
+
read -r r2 </dev/tty
|
|
431
|
+
[[ "$r2" =~ ^[nN] ]] || echo "$name" >> "$2"
|
|
432
|
+
done < "$ungrouped"
|
|
433
|
+
fi
|
|
434
|
+
rm -f "$glist" "$ungrouped"
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
# Validate --groups/--skills names against catalog + upstream.
|
|
438
|
+
validate_selection_flags() { # $1 = upstream file
|
|
439
|
+
local g s
|
|
440
|
+
if [[ -n "$SELECT_GROUPS" ]]; then
|
|
441
|
+
catalog_ok || die "--groups needs the catalog at $CATALOG_PATH (update your aris-repo clone)"
|
|
442
|
+
for g in $(echo "$SELECT_GROUPS" | tr ',' ' '); do
|
|
443
|
+
catalog_groups | cut -f1 | grep -qxF "$g" \
|
|
444
|
+
|| die "unknown group '$g' — run with --list-groups to see valid ids"
|
|
445
|
+
done
|
|
446
|
+
fi
|
|
447
|
+
for s in $(echo "$SELECT_SKILLS" | tr ',' ' '); do
|
|
448
|
+
upstream_has_skill "$s" "$1" || die "unknown skill '$s' (not an upstream skill)"
|
|
449
|
+
done
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
# Build the selected-skill set (one name per line in $3).
|
|
453
|
+
build_selection() { # $1 = upstream file, $2 = declined-candidates out file, $3 = selected out file
|
|
454
|
+
local upstream="$1" declined_out="$2" out="$3"
|
|
455
|
+
: > "$out"
|
|
456
|
+
load_declined "$declined_out"
|
|
457
|
+
|
|
458
|
+
local excl; excl="$(mktemp -t aris-excl.XXXX)"
|
|
459
|
+
echo "$EXCLUDE_SKILLS" | tr ',' '\n' | grep -v '^$' > "$excl" || true
|
|
460
|
+
cat "$excl" >> "$declined_out"
|
|
461
|
+
|
|
462
|
+
validate_selection_flags "$upstream"
|
|
463
|
+
|
|
464
|
+
local has_selection_flags=false
|
|
465
|
+
[[ -n "$SELECT_GROUPS$SELECT_SKILLS" ]] && has_selection_flags=true
|
|
466
|
+
local fresh=true
|
|
467
|
+
[[ -f "$MANIFEST_PATH" ]] && fresh=false
|
|
468
|
+
|
|
469
|
+
local name g subset_choice=false
|
|
470
|
+
if $fresh; then
|
|
471
|
+
if $SELECT_ALL || { ! $has_selection_flags && { $QUIET || [[ ! -t 0 ]]; }; }; then
|
|
472
|
+
grep '^skill|' "$upstream" | cut -d'|' -f2 > "$out"
|
|
473
|
+
elif $has_selection_flags; then
|
|
474
|
+
subset_choice=true
|
|
475
|
+
for g in $(echo "$SELECT_GROUPS" | tr ',' ' '); do
|
|
476
|
+
catalog_skills_in_group "$g" | while read -r name; do
|
|
477
|
+
upstream_has_skill "$name" "$upstream" && echo "$name"
|
|
478
|
+
done >> "$out" || true
|
|
479
|
+
done
|
|
480
|
+
echo "$SELECT_SKILLS" | tr ',' '\n' | grep -v '^$' >> "$out" || true
|
|
481
|
+
elif catalog_ok; then
|
|
482
|
+
subset_choice=true
|
|
483
|
+
interactive_select "$upstream" "$out"
|
|
484
|
+
else
|
|
485
|
+
warn "catalog missing at $CATALOG_PATH — falling back to full install"
|
|
486
|
+
grep '^skill|' "$upstream" | cut -d'|' -f2 > "$out"
|
|
487
|
+
fi
|
|
488
|
+
# Explicit subset choice ⇒ remember the rest as declined (won't re-ask).
|
|
489
|
+
if $subset_choice; then
|
|
490
|
+
grep '^skill|' "$upstream" | cut -d'|' -f2 | grep -vxF -f "$out" >> "$declined_out" 2>/dev/null || true
|
|
491
|
+
fi
|
|
492
|
+
else
|
|
493
|
+
# Reconcile: installed set = manifest ∩ upstream (auto-detected).
|
|
494
|
+
manifest_names "$MANIFEST_DATA" | while read -r name; do
|
|
495
|
+
[[ "$(manifest_kind_of "$MANIFEST_DATA" "$name")" == "skill" ]] || continue
|
|
496
|
+
upstream_has_skill "$name" "$upstream" && echo "$name"
|
|
497
|
+
done >> "$out" || true
|
|
498
|
+
# Flag-based additions re-enable previously declined skills.
|
|
499
|
+
for g in $(echo "$SELECT_GROUPS" | tr ',' ' '); do
|
|
500
|
+
catalog_skills_in_group "$g" | while read -r name; do
|
|
501
|
+
upstream_has_skill "$name" "$upstream" && echo "$name"
|
|
502
|
+
done >> "$out" || true
|
|
503
|
+
done
|
|
504
|
+
echo "$SELECT_SKILLS" | tr ',' '\n' | grep -v '^$' >> "$out" || true
|
|
505
|
+
# NEW upstream skills: not installed, not declined, not just selected.
|
|
506
|
+
local new_file; new_file="$(mktemp -t aris-new.XXXX)"
|
|
507
|
+
grep '^skill|' "$upstream" | cut -d'|' -f2 | while read -r name; do
|
|
508
|
+
in_file "$name" "$out" && continue
|
|
509
|
+
in_file "$name" "$declined_out" && continue
|
|
510
|
+
echo "$name"
|
|
511
|
+
done > "$new_file" || true
|
|
512
|
+
if [[ -s "$new_file" ]]; then
|
|
513
|
+
if $SELECT_ALL || [[ "$NEW_POLICY" == "add" ]]; then
|
|
514
|
+
cat "$new_file" >> "$out"
|
|
515
|
+
log "→ adding $(wc -l < "$new_file" | tr -d ' ') new upstream skill(s) (--all/--add-new)"
|
|
516
|
+
elif [[ "$NEW_POLICY" == "skip" ]] || $QUIET || [[ ! -t 0 ]]; then
|
|
517
|
+
# warn (not log): must stay visible under --quiet — silently
|
|
518
|
+
# missing new skills is exactly the failure mode #366 fixes.
|
|
519
|
+
warn "new upstream skills NOT installed: $(paste -sd, - < "$new_file")"
|
|
520
|
+
warn " (rerun interactively to be asked, or pass --add-new / --skills NAME)"
|
|
521
|
+
else
|
|
522
|
+
log ""
|
|
523
|
+
log "New skills appeared upstream since your last install:"
|
|
524
|
+
local reply grp sdesc
|
|
525
|
+
while read -r name; do
|
|
526
|
+
grp="$(catalog_group_of "$name")"
|
|
527
|
+
sdesc="$(catalog_desc_of "$name")"
|
|
528
|
+
printf " install new skill %-30s (group: %s)%s [y/N] " "$name" "${grp:-?}" "${sdesc:+ — $sdesc}" >&2
|
|
529
|
+
read -r reply </dev/tty
|
|
530
|
+
if [[ "$reply" =~ ^[yY] ]]; then echo "$name" >> "$out"
|
|
531
|
+
else echo "$name" >> "$declined_out"
|
|
532
|
+
fi
|
|
533
|
+
done < "$new_file"
|
|
534
|
+
fi
|
|
535
|
+
fi
|
|
536
|
+
rm -f "$new_file"
|
|
537
|
+
fi
|
|
538
|
+
|
|
539
|
+
# Excludes beat every other source (manifest, groups, deps, new skills):
|
|
540
|
+
# prune before dep expansion so an excluded pipeline doesn't drag deps in,
|
|
541
|
+
# and expand_deps itself refuses to re-add excluded names.
|
|
542
|
+
local pruned
|
|
543
|
+
if [[ -s "$excl" ]]; then
|
|
544
|
+
pruned="$(mktemp -t aris-pruned.XXXX)"
|
|
545
|
+
grep -vxF -f "$excl" "$out" > "$pruned" || true
|
|
546
|
+
mv -f "$pruned" "$out"
|
|
547
|
+
fi
|
|
548
|
+
expand_deps "$out" "$excl" "$upstream"
|
|
549
|
+
sort -u -o "$out" "$out"
|
|
550
|
+
rm -f "$excl"
|
|
551
|
+
[[ -s "$out" ]] || die "selection is empty — nothing to install (use --all or --groups/--skills)"
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
# Keep support entries + selected skills only.
|
|
555
|
+
filter_upstream_by_selection() { # $1 = upstream file, $2 = selected file, $3 = out
|
|
556
|
+
awk -F'|' -v sel="$2" '
|
|
557
|
+
BEGIN { while ((getline line < sel) > 0) picked[line]=1 }
|
|
558
|
+
$1=="support" { print; next }
|
|
559
|
+
$1=="skill" && picked[$2] { print }
|
|
560
|
+
' "$1" > "$3"
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
# Layer-4 helper resolution (#366): a global pointer file lets globally/copy-
|
|
564
|
+
# installed skills find $ARIS_REPO/tools without a per-project install.
|
|
565
|
+
ensure_global_pointer() {
|
|
566
|
+
$DRY_RUN && return 0
|
|
567
|
+
mkdir -p "$(dirname "$GLOBAL_POINTER")" 2>/dev/null || { warn "cannot create $(dirname "$GLOBAL_POINTER") — skipping global pointer"; return 0; }
|
|
568
|
+
local cur=""
|
|
569
|
+
[[ -f "$GLOBAL_POINTER" ]] && cur="$(cat "$GLOBAL_POINTER" 2>/dev/null || true)"
|
|
570
|
+
[[ "$cur" == "$ARIS_REPO" ]] && return 0
|
|
571
|
+
printf '%s\n' "$ARIS_REPO" > "$GLOBAL_POINTER.tmp.$$" && mv -f "$GLOBAL_POINTER.tmp.$$" "$GLOBAL_POINTER"
|
|
572
|
+
log " + global pointer $GLOBAL_POINTER -> $ARIS_REPO"
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
# Parse manifest into a global associative-style array via temp file lookup
|
|
576
|
+
# We store the parsed content in $MANIFEST_DATA_FILE (one entry per line) for grep lookup
|
|
577
|
+
load_manifest() {
|
|
578
|
+
local path="$1" out="$2"
|
|
579
|
+
: > "$out"
|
|
580
|
+
[[ -f "$path" ]] || return 0
|
|
581
|
+
# Validate version header
|
|
582
|
+
local ver; ver="$(awk -F'\t' '$1=="version"{print $2}' "$path" | head -1)"
|
|
583
|
+
[[ "$ver" == "$MANIFEST_VERSION" ]] || die "manifest version mismatch (file: $ver, expected: $MANIFEST_VERSION)"
|
|
584
|
+
# Body lines: kind\tname\tsource_rel\ttarget_rel\tmode
|
|
585
|
+
awk -F'\t' '
|
|
586
|
+
BEGIN { in_body=0 }
|
|
587
|
+
/^kind\tname\tsource_rel\ttarget_rel\tmode$/ { in_body=1; next }
|
|
588
|
+
in_body && NF==5 { print }
|
|
589
|
+
' "$path" > "$out"
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
manifest_lookup_target() {
|
|
593
|
+
# echo target_rel for given name from $1=manifest_data_file $2=name
|
|
594
|
+
awk -F'\t' -v n="$2" '$2==n {print $4; exit}' "$1"
|
|
595
|
+
}
|
|
596
|
+
manifest_lookup_source() {
|
|
597
|
+
awk -F'\t' -v n="$2" '$2==n {print $3; exit}' "$1"
|
|
598
|
+
}
|
|
599
|
+
manifest_names() { awk -F'\t' '{print $2}' "$1"; }
|
|
600
|
+
manifest_kind_of() {
|
|
601
|
+
awk -F'\t' -v n="$2" '$2==n {print $1; exit}' "$1"
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
# ─── Resolve project path & aris-repo ─────────────────────────────────────────
|
|
605
|
+
PROJECT_PATH="${PROJECT_PATH:-$(pwd)}"
|
|
606
|
+
[[ -d "$PROJECT_PATH" ]] || die "project path does not exist: $PROJECT_PATH"
|
|
607
|
+
PROJECT_PATH="$(abs_path "$PROJECT_PATH")"
|
|
608
|
+
|
|
609
|
+
# ─── Platform auto-detect + delegation (before resolve_aris_repo) ─────────────
|
|
610
|
+
# Must happen before resolve_aris_repo because the Codex installer resolves its
|
|
611
|
+
# own repo path (looking for skills/skills-codex instead of just skills/).
|
|
612
|
+
# Skip auto-detect (and its marker warnings) when --platform is explicit — the
|
|
613
|
+
# override wins anyway, so detecting would only print a misleading "defaulting to …".
|
|
614
|
+
[[ -n "$PLATFORM_OVERRIDE" ]] || auto_detect_platform "$PROJECT_PATH"
|
|
615
|
+
PLATFORM="${PLATFORM_OVERRIDE:-$DETECTED_PLATFORM}"
|
|
616
|
+
if [[ "$PLATFORM" == "codex" ]]; then
|
|
617
|
+
# Validate: claude-only flags are incompatible with codex platform
|
|
618
|
+
if [[ ${#CLAUDE_ONLY_FLAGS_USED[@]} -gt 0 ]]; then
|
|
619
|
+
die "Claude-only flags incompatible with codex platform: ${CLAUDE_ONLY_FLAGS_USED[*]}"
|
|
620
|
+
fi
|
|
621
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
622
|
+
CODEX_INSTALLER="$SCRIPT_DIR/install_aris_codex.sh"
|
|
623
|
+
[[ -f "$CODEX_INSTALLER" ]] || die "Codex installer not found: $CODEX_INSTALLER"
|
|
624
|
+
log ""
|
|
625
|
+
if [[ -n "$PLATFORM_OVERRIDE" ]]; then
|
|
626
|
+
log "Codex CLI platform selected — delegating to install_aris_codex.sh"
|
|
627
|
+
else
|
|
628
|
+
log "Codex CLI platform detected — delegating to install_aris_codex.sh"
|
|
629
|
+
log " (override with --platform claude)"
|
|
630
|
+
fi
|
|
631
|
+
log ""
|
|
632
|
+
if [[ ${#FORWARDED_ARGS[@]} -gt 0 ]]; then
|
|
633
|
+
exec bash "$CODEX_INSTALLER" "${FORWARDED_ARGS[@]}"
|
|
634
|
+
fi
|
|
635
|
+
exec bash "$CODEX_INSTALLER"
|
|
636
|
+
fi
|
|
637
|
+
# Validate: codex-only flags are incompatible with claude platform
|
|
638
|
+
if [[ ${#CODEX_ONLY_FLAGS_SEEN[@]} -gt 0 ]]; then
|
|
639
|
+
die "Codex-only flags incompatible with claude platform: ${CODEX_ONLY_FLAGS_SEEN[*]}"
|
|
640
|
+
fi
|
|
641
|
+
|
|
642
|
+
ARIS_REPO="$(resolve_aris_repo)"
|
|
643
|
+
SKILLS_DIR_ABS="$ARIS_REPO/skills"
|
|
644
|
+
PROJECT_SKILLS_DIR="$PROJECT_PATH/$SKILLS_REL"
|
|
645
|
+
PROJECT_ARIS_DIR="$PROJECT_PATH/$ARIS_DIR_NAME"
|
|
646
|
+
MANIFEST_PATH="$PROJECT_ARIS_DIR/$MANIFEST_NAME"
|
|
647
|
+
MANIFEST_PREV="$PROJECT_ARIS_DIR/$MANIFEST_PREV_NAME"
|
|
648
|
+
AGENT_MANIFEST_PATH="$PROJECT_ARIS_DIR/$AGENT_MANIFEST_NAME"
|
|
649
|
+
LOCK_DIR="$PROJECT_ARIS_DIR/$LOCK_DIR_NAME"
|
|
650
|
+
DOC_FILE="$PROJECT_PATH/$DOC_FILE_NAME"
|
|
651
|
+
CATALOG_PATH="$ARIS_REPO/$CATALOG_REL"
|
|
652
|
+
DECLINED_PATH="$PROJECT_ARIS_DIR/$DECLINED_NAME"
|
|
653
|
+
|
|
654
|
+
if $LIST_GROUPS; then
|
|
655
|
+
print_group_catalog
|
|
656
|
+
exit 0
|
|
657
|
+
fi
|
|
658
|
+
|
|
659
|
+
# ─── S9: refuse if .aris / .claude / .claude/skills is itself a symlink ───────
|
|
660
|
+
# (.aris and .claude/skills may not exist yet — only check if present.)
|
|
661
|
+
check_no_symlinked_parents() {
|
|
662
|
+
local p
|
|
663
|
+
for p in "$PROJECT_ARIS_DIR" "$PROJECT_PATH/.claude" "$PROJECT_SKILLS_DIR" "$PROJECT_PATH/.github" "$PROJECT_PATH/.github/agents"; do
|
|
664
|
+
if is_symlink "$p"; then
|
|
665
|
+
die "S9: $p is a symlink — refusing to install (would mutate symlink target)"
|
|
666
|
+
fi
|
|
667
|
+
done
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
# ─── Lock acquisition (mkdir-based, portable) ─────────────────────────────────
|
|
671
|
+
write_lock_metadata() {
|
|
672
|
+
# Two files: owner.json for human inspection, owner.pid for reliable parsing
|
|
673
|
+
cat > "$LOCK_DIR/owner.json" <<EOF
|
|
674
|
+
{"host":"$(hostname)","pid":$$,"started_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)","tool":"install_aris.sh"}
|
|
675
|
+
EOF
|
|
676
|
+
echo "$$" > "$LOCK_DIR/owner.pid"
|
|
677
|
+
echo "$(hostname)" > "$LOCK_DIR/owner.host"
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
acquire_lock() {
|
|
681
|
+
mkdir -p "$PROJECT_ARIS_DIR"
|
|
682
|
+
if mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
683
|
+
write_lock_metadata
|
|
684
|
+
trap release_lock EXIT INT TERM
|
|
685
|
+
return 0
|
|
686
|
+
fi
|
|
687
|
+
if $CLEAR_STALE_LOCK; then
|
|
688
|
+
local owner=""
|
|
689
|
+
[[ -f "$LOCK_DIR/owner.json" ]] && owner="$(cat "$LOCK_DIR/owner.json")"
|
|
690
|
+
warn "removing stale lock: $LOCK_DIR (was: $owner)"
|
|
691
|
+
rm -rf "$LOCK_DIR"
|
|
692
|
+
mkdir "$LOCK_DIR" || die "still cannot acquire lock after stale clear"
|
|
693
|
+
write_lock_metadata
|
|
694
|
+
trap release_lock EXIT INT TERM
|
|
695
|
+
return 0
|
|
696
|
+
fi
|
|
697
|
+
local owner=""
|
|
698
|
+
[[ -f "$LOCK_DIR/owner.json" ]] && owner="$(cat "$LOCK_DIR/owner.json")"
|
|
699
|
+
die "another install_aris.sh is running in this project (lock: $LOCK_DIR)
|
|
700
|
+
owner: $owner
|
|
701
|
+
if you are sure no install is in progress, rerun with --clear-stale-lock"
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
release_lock() {
|
|
705
|
+
[[ -d "$LOCK_DIR" ]] || return 0
|
|
706
|
+
if [[ -f "$LOCK_DIR/owner.pid" ]]; then
|
|
707
|
+
local pid; pid="$(cat "$LOCK_DIR/owner.pid" 2>/dev/null || echo "")"
|
|
708
|
+
local host; host="$(cat "$LOCK_DIR/owner.host" 2>/dev/null || echo "")"
|
|
709
|
+
if [[ "$pid" == "$$" && "$host" == "$(hostname)" ]]; then
|
|
710
|
+
rm -rf "$LOCK_DIR"
|
|
711
|
+
fi
|
|
712
|
+
fi
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
# ─── Migration detection ──────────────────────────────────────────────────────
|
|
716
|
+
LEGACY_NESTED="$PROJECT_SKILLS_DIR/aris"
|
|
717
|
+
|
|
718
|
+
detect_legacy() {
|
|
719
|
+
if [[ ! -e "$LEGACY_NESTED" && ! -L "$LEGACY_NESTED" ]]; then echo "none"; return; fi
|
|
720
|
+
if is_symlink "$LEGACY_NESTED"; then
|
|
721
|
+
local tgt; tgt="$(read_link_target "$LEGACY_NESTED")"
|
|
722
|
+
if [[ "$tgt" == "$SKILLS_DIR_ABS" || "$tgt" == "$SKILLS_DIR_ABS/" ]]; then
|
|
723
|
+
echo "symlink_to_repo"
|
|
724
|
+
else
|
|
725
|
+
echo "symlink_to_other"
|
|
726
|
+
fi
|
|
727
|
+
elif [[ -d "$LEGACY_NESTED" ]]; then
|
|
728
|
+
echo "real_dir"
|
|
729
|
+
else
|
|
730
|
+
echo "real_file"
|
|
731
|
+
fi
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
migrate_legacy() {
|
|
735
|
+
local kind; kind="$(detect_legacy)"
|
|
736
|
+
case "$kind" in
|
|
737
|
+
none)
|
|
738
|
+
return 0 ;;
|
|
739
|
+
symlink_to_repo)
|
|
740
|
+
log "→ migrating legacy nested symlink: removing $LEGACY_NESTED"
|
|
741
|
+
$DRY_RUN || rm -f "$LEGACY_NESTED"
|
|
742
|
+
return 0 ;;
|
|
743
|
+
symlink_to_other)
|
|
744
|
+
die "S2: legacy $LEGACY_NESTED is a symlink to OUTSIDE the repo — refusing to touch.
|
|
745
|
+
investigate manually before re-running."
|
|
746
|
+
;;
|
|
747
|
+
real_file)
|
|
748
|
+
die "$LEGACY_NESTED is a regular file (unexpected). Move/delete it manually." ;;
|
|
749
|
+
real_dir)
|
|
750
|
+
if [[ -z "$MIGRATE_COPY" ]]; then
|
|
751
|
+
die "legacy nested COPY install detected at $LEGACY_NESTED.
|
|
752
|
+
This may contain user edits. Choose explicitly:
|
|
753
|
+
--migrate-copy keep-user (keep nested copy intact, install flat alongside;
|
|
754
|
+
old copy becomes inert for Claude discovery)
|
|
755
|
+
--migrate-copy prefer-upstream (archive nested copy to .aris/legacy-copy-backup-<ts>/
|
|
756
|
+
AFTER new flat install is verified, then flatten)"
|
|
757
|
+
fi
|
|
758
|
+
# actual handling deferred until after apply (for prefer-upstream)
|
|
759
|
+
return 0 ;;
|
|
760
|
+
esac
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
archive_legacy_copy() {
|
|
764
|
+
local ts; ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
765
|
+
local archive="$PROJECT_ARIS_DIR/legacy-copy-backup-$ts"
|
|
766
|
+
log "→ archiving legacy nested copy to: $archive"
|
|
767
|
+
$DRY_RUN || mv "$LEGACY_NESTED" "$archive"
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
# ─── Plan computation ─────────────────────────────────────────────────────────
|
|
771
|
+
# Plan is written to a temp file, one line per action: ACTION|kind|name|extra
|
|
772
|
+
# Actions: CREATE | UPDATE_TARGET | REUSE | REMOVE | ADOPT | CONFLICT
|
|
773
|
+
compute_plan() {
|
|
774
|
+
local upstream_file="$1" manifest_data="$2" out="$3"
|
|
775
|
+
: > "$out"
|
|
776
|
+
local target_path src expected_target current_target line kind name
|
|
777
|
+
# Iterate upstream entries
|
|
778
|
+
while IFS='|' read -r kind name; do
|
|
779
|
+
[[ -z "$name" ]] && continue
|
|
780
|
+
target_path="$PROJECT_SKILLS_DIR/$name"
|
|
781
|
+
expected_target="$SKILLS_DIR_ABS/$name"
|
|
782
|
+
if [[ -L "$target_path" ]]; then
|
|
783
|
+
current_target="$(read_link_target "$target_path")"
|
|
784
|
+
# Convert relative readlink to absolute (relative to symlink's dir)
|
|
785
|
+
if [[ "$current_target" != /* ]]; then
|
|
786
|
+
current_target="$(canonicalize "$PROJECT_SKILLS_DIR/$current_target")"
|
|
787
|
+
fi
|
|
788
|
+
local in_manifest=false
|
|
789
|
+
if [[ -n "$(manifest_lookup_target "$manifest_data" "$name")" ]]; then in_manifest=true; fi
|
|
790
|
+
if [[ "$current_target" == "$expected_target" ]]; then
|
|
791
|
+
if $in_manifest; then echo "REUSE|$kind|$name|" >> "$out"
|
|
792
|
+
else echo "ADOPT|$kind|$name|" >> "$out"
|
|
793
|
+
fi
|
|
794
|
+
else
|
|
795
|
+
# symlink exists but points elsewhere
|
|
796
|
+
if $in_manifest; then
|
|
797
|
+
# managed symlink with stale target — can update with --replace-link or auto if safely inside repo
|
|
798
|
+
echo "UPDATE_TARGET|$kind|$name|$current_target" >> "$out"
|
|
799
|
+
else
|
|
800
|
+
# foreign symlink (user's own?) — conflict
|
|
801
|
+
echo "CONFLICT|$kind|$name|symlink_to:$current_target" >> "$out"
|
|
802
|
+
fi
|
|
803
|
+
fi
|
|
804
|
+
elif [[ -e "$target_path" ]]; then
|
|
805
|
+
# real file/dir
|
|
806
|
+
echo "CONFLICT|$kind|$name|real_path" >> "$out"
|
|
807
|
+
else
|
|
808
|
+
echo "CREATE|$kind|$name|" >> "$out"
|
|
809
|
+
fi
|
|
810
|
+
done < "$upstream_file"
|
|
811
|
+
# Manifest entries no longer in upstream → REMOVE
|
|
812
|
+
while IFS=$'\t' read -r mkind mname msrc mtarget mmode; do
|
|
813
|
+
[[ -z "$mname" ]] && continue
|
|
814
|
+
# is name in upstream?
|
|
815
|
+
if grep -q "^[^|]*|$mname$" "$upstream_file"; then continue; fi
|
|
816
|
+
echo "REMOVE|$mkind|$mname|" >> "$out"
|
|
817
|
+
done < "$manifest_data"
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
# ─── Plan rendering ───────────────────────────────────────────────────────────
|
|
821
|
+
print_plan() {
|
|
822
|
+
local plan="$1"
|
|
823
|
+
local n_create n_update n_reuse n_remove n_adopt n_conflict
|
|
824
|
+
n_create=$(grep -c '^CREATE|' "$plan" || true)
|
|
825
|
+
n_update=$(grep -c '^UPDATE_TARGET|' "$plan" || true)
|
|
826
|
+
n_reuse=$(grep -c '^REUSE|' "$plan" || true)
|
|
827
|
+
n_remove=$(grep -c '^REMOVE|' "$plan" || true)
|
|
828
|
+
n_adopt=$(grep -c '^ADOPT|' "$plan" || true)
|
|
829
|
+
n_conflict=$(grep -c '^CONFLICT|' "$plan" || true)
|
|
830
|
+
log ""
|
|
831
|
+
log "Plan summary:"
|
|
832
|
+
log " CREATE: $n_create (new flat symlinks to add)"
|
|
833
|
+
log " ADOPT: $n_adopt (orphan symlinks already pointing to correct target)"
|
|
834
|
+
log " UPDATE_TARGET: $n_update (managed symlinks with stale target)"
|
|
835
|
+
log " REUSE: $n_reuse (already correct, no-op)"
|
|
836
|
+
log " REMOVE: $n_remove (in old manifest, no longer upstream)"
|
|
837
|
+
log " CONFLICT: $n_conflict (must be resolved before apply)"
|
|
838
|
+
if (( n_conflict > 0 )); then
|
|
839
|
+
log ""
|
|
840
|
+
log "Conflicts (need user action):"
|
|
841
|
+
grep '^CONFLICT|' "$plan" | while IFS='|' read -r _ kind name extra; do
|
|
842
|
+
log " - $name ($kind): $extra"
|
|
843
|
+
done
|
|
844
|
+
fi
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
# ─── Apply ────────────────────────────────────────────────────────────────────
|
|
848
|
+
# Order (safer than mine, per codex round 3):
|
|
849
|
+
# 1. Write new manifest temp (in-place dir for atomic rename)
|
|
850
|
+
# 2. Apply mutations with revalidation (lstat + readlink) right before each
|
|
851
|
+
# 3. Copy current manifest to .prev.tmp, rename to .prev
|
|
852
|
+
# 4. Rename new manifest temp over current manifest (LAST commit point)
|
|
853
|
+
# 5. CLAUDE.md update (best-effort, compare-and-swap, never fails install)
|
|
854
|
+
|
|
855
|
+
write_manifest_tmp() {
|
|
856
|
+
local plan="$1" out="$2"
|
|
857
|
+
{
|
|
858
|
+
printf "version\t%s\n" "$MANIFEST_VERSION"
|
|
859
|
+
printf "repo_root\t%s\n" "$ARIS_REPO"
|
|
860
|
+
printf "project_root\t%s\n" "$PROJECT_PATH"
|
|
861
|
+
printf "generated\t%s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
862
|
+
printf "kind\tname\tsource_rel\ttarget_rel\tmode\n"
|
|
863
|
+
# New manifest = REUSE + ADOPT + CREATE + UPDATE_TARGET (i.e., everything that will exist as a managed symlink after apply)
|
|
864
|
+
awk -F'|' '$1=="REUSE"||$1=="ADOPT"||$1=="CREATE"||$1=="UPDATE_TARGET"{print $0}' "$plan" \
|
|
865
|
+
| while IFS='|' read -r action kind name _; do
|
|
866
|
+
printf "%s\t%s\tskills/%s\t%s/%s\tsymlink\n" "$kind" "$name" "$name" "$SKILLS_REL" "$name"
|
|
867
|
+
done
|
|
868
|
+
} > "$out"
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
# Verify current symlink state matches our expectation immediately before mutating
|
|
872
|
+
revalidate_symlink_target() {
|
|
873
|
+
local path="$1" expected="$2"
|
|
874
|
+
is_symlink "$path" || return 1
|
|
875
|
+
local cur; cur="$(read_link_target "$path")"
|
|
876
|
+
[[ "$cur" != /* ]] && cur="$(canonicalize "$(dirname "$path")/$cur")"
|
|
877
|
+
[[ "$cur" == "$expected" ]]
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
apply_plan() {
|
|
881
|
+
local plan="$1" manifest_tmp="$2"
|
|
882
|
+
mkdir -p "$PROJECT_SKILLS_DIR"
|
|
883
|
+
local action kind name extra target_path expected_target
|
|
884
|
+
while IFS='|' read -r action kind name extra; do
|
|
885
|
+
[[ -z "$name" ]] && continue
|
|
886
|
+
target_path="$PROJECT_SKILLS_DIR/$name"
|
|
887
|
+
expected_target="$SKILLS_DIR_ABS/$name"
|
|
888
|
+
case "$action" in
|
|
889
|
+
REUSE|ADOPT)
|
|
890
|
+
: # nothing to do; manifest will record it
|
|
891
|
+
;;
|
|
892
|
+
CREATE)
|
|
893
|
+
# S4: must not exist
|
|
894
|
+
if [[ -e "$target_path" || -L "$target_path" ]]; then
|
|
895
|
+
die "S4 violation: $target_path appeared between plan and apply"
|
|
896
|
+
fi
|
|
897
|
+
if $DRY_RUN; then log " (dry-run) ln -s $expected_target $target_path"
|
|
898
|
+
else ln -s "$expected_target" "$target_path"; log " + $name"
|
|
899
|
+
fi
|
|
900
|
+
;;
|
|
901
|
+
UPDATE_TARGET)
|
|
902
|
+
# S11: revalidate current target equals what plan saw
|
|
903
|
+
local plan_saw_target; plan_saw_target="$(read_link_target "$target_path" 2>/dev/null || echo "")"
|
|
904
|
+
[[ "$plan_saw_target" != /* && -n "$plan_saw_target" ]] && plan_saw_target="$(canonicalize "$(dirname "$target_path")/$plan_saw_target")"
|
|
905
|
+
if [[ "$plan_saw_target" != "$extra" ]]; then
|
|
906
|
+
warn "S11: $target_path target changed since plan ($plan_saw_target vs $extra) — skipping"
|
|
907
|
+
continue
|
|
908
|
+
fi
|
|
909
|
+
# S2: stale target must point inside aris-repo
|
|
910
|
+
if [[ "$plan_saw_target" != "$ARIS_REPO"/* ]]; then
|
|
911
|
+
warn "S2: refusing to replace symlink pointing outside aris-repo: $target_path -> $plan_saw_target"
|
|
912
|
+
continue
|
|
913
|
+
fi
|
|
914
|
+
if $DRY_RUN; then log " (dry-run) update target: $target_path -> $expected_target"
|
|
915
|
+
else
|
|
916
|
+
rm -f "$target_path"
|
|
917
|
+
ln -s "$expected_target" "$target_path"
|
|
918
|
+
log " ↻ $name"
|
|
919
|
+
fi
|
|
920
|
+
;;
|
|
921
|
+
REMOVE)
|
|
922
|
+
# S1: must be a symlink
|
|
923
|
+
is_symlink "$target_path" || { warn "S1: $target_path is not a symlink, refusing to remove"; continue; }
|
|
924
|
+
# S2: target must be inside aris-repo
|
|
925
|
+
local cur; cur="$(read_link_target "$target_path")"
|
|
926
|
+
[[ "$cur" != /* ]] && cur="$(canonicalize "$(dirname "$target_path")/$cur")"
|
|
927
|
+
[[ "$cur" == "$ARIS_REPO"/* ]] || { warn "S2: $target_path target $cur outside aris-repo, refusing"; continue; }
|
|
928
|
+
if $DRY_RUN; then log " (dry-run) rm $target_path"
|
|
929
|
+
else rm -f "$target_path"; log " - $name"
|
|
930
|
+
fi
|
|
931
|
+
;;
|
|
932
|
+
CONFLICT)
|
|
933
|
+
die "BUG: CONFLICT $name reached apply phase"
|
|
934
|
+
;;
|
|
935
|
+
esac
|
|
936
|
+
done < "$plan"
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
# Phase 0 (#174): ensure project-local `.aris/tools` symlink exists, pointing
|
|
940
|
+
# to the canonical aris-repo `tools/` dir. Pure-additive: existing users who
|
|
941
|
+
# don't rerun the installer never see this. The symlink is currently inert
|
|
942
|
+
# (no SKILL.md references it); it sets up future #177 path-rewrites.
|
|
943
|
+
#
|
|
944
|
+
# Idempotent. If `.aris/tools` already exists with a different target (or as
|
|
945
|
+
# a real file/dir), warn and leave it alone — never replace user content.
|
|
946
|
+
# Membership in the "managed" set is determined by exact target match against
|
|
947
|
+
# `$ARIS_REPO/tools`, not via the manifest, so we don't need to bump the
|
|
948
|
+
# manifest format for this single-link addition (per #174 non-goals).
|
|
949
|
+
ensure_tools_symlink() {
|
|
950
|
+
local link_path="$PROJECT_ARIS_DIR/tools"
|
|
951
|
+
local expected_target="$ARIS_REPO/tools"
|
|
952
|
+
|
|
953
|
+
if is_symlink "$link_path"; then
|
|
954
|
+
local cur; cur="$(read_link_target "$link_path")"
|
|
955
|
+
[[ "$cur" != /* ]] && cur="$(canonicalize "$(dirname "$link_path")/$cur")"
|
|
956
|
+
if [[ "$cur" == "$expected_target" ]]; then
|
|
957
|
+
return 0
|
|
958
|
+
fi
|
|
959
|
+
warn ".aris/tools already exists with different target ($cur); leaving alone (#174)"
|
|
960
|
+
return 0
|
|
961
|
+
fi
|
|
962
|
+
|
|
963
|
+
if [[ -e "$link_path" ]]; then
|
|
964
|
+
warn ".aris/tools already exists as a non-symlink path; leaving alone (#174)"
|
|
965
|
+
return 0
|
|
966
|
+
fi
|
|
967
|
+
|
|
968
|
+
if $DRY_RUN; then
|
|
969
|
+
log " (dry-run) ln -s $expected_target $link_path"
|
|
970
|
+
else
|
|
971
|
+
ln -s "$expected_target" "$link_path"
|
|
972
|
+
log " + .aris/tools -> tools/ (Phase 0, #174)"
|
|
973
|
+
fi
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
# Counterpart for uninstall: only remove `.aris/tools` if it is exactly the
|
|
977
|
+
# managed symlink (target == $ARIS_REPO/tools). User-created directories /
|
|
978
|
+
# files / different symlinks are untouched.
|
|
979
|
+
remove_tools_symlink() {
|
|
980
|
+
local link_path="$PROJECT_ARIS_DIR/tools"
|
|
981
|
+
local expected_target="$ARIS_REPO/tools"
|
|
982
|
+
|
|
983
|
+
is_symlink "$link_path" || return 0
|
|
984
|
+
local cur; cur="$(read_link_target "$link_path")"
|
|
985
|
+
[[ "$cur" != /* ]] && cur="$(canonicalize "$(dirname "$link_path")/$cur")"
|
|
986
|
+
if [[ "$cur" != "$expected_target" ]]; then
|
|
987
|
+
return 0
|
|
988
|
+
fi
|
|
989
|
+
|
|
990
|
+
if $DRY_RUN; then
|
|
991
|
+
log " (dry-run) rm $link_path"
|
|
992
|
+
else
|
|
993
|
+
rm -f "$link_path"
|
|
994
|
+
log " - .aris/tools (managed symlink)"
|
|
995
|
+
fi
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
# F5: Deploy Copilot agent profiles from <aris-repo>/.github/agents/ to
|
|
999
|
+
# <project>/.github/agents/. Each .agent.md file is symlinked individually;
|
|
1000
|
+
# user-created files/dirs/symlinks at the target path are left alone.
|
|
1001
|
+
# Pure-additive — existing users who don't rerun the installer never see
|
|
1002
|
+
# this. Idempotent across re-runs.
|
|
1003
|
+
record_managed_agent_profile() {
|
|
1004
|
+
local name="$1" tmp="$AGENT_MANIFEST_PATH.tmp.$$" unsorted="$AGENT_MANIFEST_PATH.unsorted.$$"
|
|
1005
|
+
mkdir -p "$PROJECT_ARIS_DIR"
|
|
1006
|
+
if [[ -L "$AGENT_MANIFEST_PATH" ]]; then
|
|
1007
|
+
warn "$AGENT_MANIFEST_PATH is a symlink; refusing to record agent ownership"
|
|
1008
|
+
return 1
|
|
1009
|
+
fi
|
|
1010
|
+
if [[ -f "$AGENT_MANIFEST_PATH" ]]; then
|
|
1011
|
+
cp "$AGENT_MANIFEST_PATH" "$unsorted" || { rm -f "$unsorted" "$tmp"; return 1; }
|
|
1012
|
+
else
|
|
1013
|
+
: > "$unsorted" || return 1
|
|
1014
|
+
fi
|
|
1015
|
+
printf '%s\n' "$name" >> "$unsorted" || { rm -f "$unsorted" "$tmp"; return 1; }
|
|
1016
|
+
sort -u "$unsorted" > "$tmp" || { rm -f "$unsorted" "$tmp"; return 1; }
|
|
1017
|
+
rm -f "$unsorted"
|
|
1018
|
+
mv -f "$tmp" "$AGENT_MANIFEST_PATH"
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
ensure_agent_profiles() {
|
|
1022
|
+
local src_dir="$ARIS_REPO/$AGENT_PROFILES_SRC"
|
|
1023
|
+
[[ -d "$src_dir" ]] || return 0 # no profiles to deploy
|
|
1024
|
+
[[ ! -L "$src_dir" ]] || { warn "skipping symlinked upstream agents directory: $src_dir"; return 0; }
|
|
1025
|
+
local target_dir="$PROJECT_PATH/.github/agents"
|
|
1026
|
+
local deployed=0 name src target
|
|
1027
|
+
|
|
1028
|
+
for src in "$src_dir"/*.agent.md; do
|
|
1029
|
+
[[ -f "$src" ]] || continue
|
|
1030
|
+
# Resolve symlink and verify it's within the expected directory
|
|
1031
|
+
local resolved; resolved="$(canonicalize "$src")"
|
|
1032
|
+
local src_canon; src_canon="$(canonicalize "$src_dir")"
|
|
1033
|
+
[[ "$resolved" == "$src_canon"/* ]] || { warn "skipping external symlink: $src -> $resolved"; continue; }
|
|
1034
|
+
name="$(basename "$src")"
|
|
1035
|
+
target="$target_dir/$name"
|
|
1036
|
+
|
|
1037
|
+
if is_symlink "$target"; then
|
|
1038
|
+
local cur; cur="$(read_link_target "$target")"
|
|
1039
|
+
[[ "$cur" != /* ]] && cur="$(canonicalize "$(dirname "$target")/$cur")"
|
|
1040
|
+
if [[ "$cur" == "$src" ]]; then
|
|
1041
|
+
continue # already correct
|
|
1042
|
+
fi
|
|
1043
|
+
warn ".github/agents/$name already exists with different target ($cur); leaving alone"
|
|
1044
|
+
continue
|
|
1045
|
+
fi
|
|
1046
|
+
|
|
1047
|
+
if [[ -e "$target" ]]; then
|
|
1048
|
+
warn ".github/agents/$name already exists as a non-symlink path; leaving alone"
|
|
1049
|
+
continue
|
|
1050
|
+
fi
|
|
1051
|
+
|
|
1052
|
+
if $DRY_RUN; then
|
|
1053
|
+
log " (dry-run) ln -s $src $target"
|
|
1054
|
+
else
|
|
1055
|
+
mkdir -p "$target_dir"
|
|
1056
|
+
ln -s "$src" "$target"
|
|
1057
|
+
if ! record_managed_agent_profile "$name"; then
|
|
1058
|
+
rm -f "$target"
|
|
1059
|
+
die "could not record ownership for .github/agents/$name"
|
|
1060
|
+
fi
|
|
1061
|
+
deployed=$((deployed + 1))
|
|
1062
|
+
fi
|
|
1063
|
+
done
|
|
1064
|
+
|
|
1065
|
+
if ! $DRY_RUN && (( deployed > 0 )); then
|
|
1066
|
+
log " + .github/agents/ ($deployed copilot profile(s) deployed)"
|
|
1067
|
+
fi
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
# Counterpart for uninstall: only remove agent profile symlinks explicitly
|
|
1071
|
+
# recorded when this installer created them. An identical pre-existing link is
|
|
1072
|
+
# user-owned and must survive uninstall.
|
|
1073
|
+
remove_agent_profiles() {
|
|
1074
|
+
local target_dir="$PROJECT_PATH/.github/agents"
|
|
1075
|
+
[[ -d "$target_dir" || -L "$target_dir" ]] || return 0
|
|
1076
|
+
[[ -f "$AGENT_MANIFEST_PATH" ]] || return 0
|
|
1077
|
+
[[ ! -L "$AGENT_MANIFEST_PATH" ]] || { warn "$AGENT_MANIFEST_PATH is a symlink; refusing agent cleanup"; return 0; }
|
|
1078
|
+
local src_dir="$ARIS_REPO/$AGENT_PROFILES_SRC"
|
|
1079
|
+
local name target removed=0
|
|
1080
|
+
|
|
1081
|
+
while IFS= read -r name; do
|
|
1082
|
+
[[ "$name" =~ $SAFE_NAME_REGEX && "$name" == *.agent.md ]] || { warn "invalid agent manifest entry: $name"; continue; }
|
|
1083
|
+
target="$target_dir/$name"
|
|
1084
|
+
[[ -L "$target" ]] || continue
|
|
1085
|
+
local cur; cur="$(read_link_target "$target")"
|
|
1086
|
+
[[ "$cur" != /* ]] && cur="$(canonicalize "$(dirname "$target")/$cur")"
|
|
1087
|
+
# Revalidate the exact source target before mutation.
|
|
1088
|
+
if [[ "$cur" == "$src_dir/$name" ]]; then
|
|
1089
|
+
if $DRY_RUN; then
|
|
1090
|
+
log " (dry-run) rm $target"
|
|
1091
|
+
else
|
|
1092
|
+
rm -f "$target"
|
|
1093
|
+
removed=$((removed + 1))
|
|
1094
|
+
fi
|
|
1095
|
+
fi
|
|
1096
|
+
done < "$AGENT_MANIFEST_PATH"
|
|
1097
|
+
|
|
1098
|
+
if ! $DRY_RUN && (( removed > 0 )); then
|
|
1099
|
+
log " - .github/agents/ ($removed copilot profile(s) removed)"
|
|
1100
|
+
# Remove directory if empty after cleanup
|
|
1101
|
+
rmdir "$target_dir" 2>/dev/null || true
|
|
1102
|
+
fi
|
|
1103
|
+
if ! $DRY_RUN; then
|
|
1104
|
+
rm -f "$AGENT_MANIFEST_PATH"
|
|
1105
|
+
fi
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
commit_manifest() {
|
|
1109
|
+
local manifest_tmp="$1"
|
|
1110
|
+
if $DRY_RUN; then log " (dry-run) would commit manifest"; return; fi
|
|
1111
|
+
# Backup current manifest if exists
|
|
1112
|
+
if [[ -f "$MANIFEST_PATH" ]]; then
|
|
1113
|
+
cp -p "$MANIFEST_PATH" "$MANIFEST_PREV.tmp"
|
|
1114
|
+
mv -f "$MANIFEST_PREV.tmp" "$MANIFEST_PREV"
|
|
1115
|
+
fi
|
|
1116
|
+
# Atomic rename from temp (same dir → atomic on same FS)
|
|
1117
|
+
mv -f "$manifest_tmp" "$MANIFEST_PATH"
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
# ─── CLAUDE.md best-effort update (compare-and-swap) ──────────────────────────
|
|
1121
|
+
update_claude_doc() {
|
|
1122
|
+
local installed_names_file="$1"
|
|
1123
|
+
[[ -f "$DOC_FILE" ]] || { log " (skip CLAUDE.md: file not present)"; return 0; }
|
|
1124
|
+
if $NO_DOC; then return 0; fi
|
|
1125
|
+
|
|
1126
|
+
local original new_block tmp
|
|
1127
|
+
original="$(cat "$DOC_FILE")"
|
|
1128
|
+
# Build new block
|
|
1129
|
+
local count; count="$(wc -l < "$installed_names_file" | tr -d ' ')"
|
|
1130
|
+
new_block="$BLOCK_BEGIN
|
|
1131
|
+
## ARIS Skill Scope
|
|
1132
|
+
ARIS skills installed in this project: $count entries.
|
|
1133
|
+
Manifest: \`$ARIS_DIR_NAME/$MANIFEST_NAME\` (lists every skill ARIS installed and its upstream target).
|
|
1134
|
+
For ARIS workflows, prefer the project-local skills under \`$SKILLS_REL/\` over global skills.
|
|
1135
|
+
Do not modify or delete files inside any skill that is a symlink (symlinks point into \`$ARIS_REPO\`).
|
|
1136
|
+
Update with: \`bash $ARIS_REPO/tools/install_aris.sh\` (re-runnable; reconciles new/removed skills).
|
|
1137
|
+
$BLOCK_END"
|
|
1138
|
+
|
|
1139
|
+
# Compute new content
|
|
1140
|
+
local new_content
|
|
1141
|
+
if printf '%s' "$original" | grep -qF "$BLOCK_BEGIN"; then
|
|
1142
|
+
new_content="$(python3 - "$DOC_FILE" "$BLOCK_BEGIN" "$BLOCK_END" "$new_block" <<'PYEOF'
|
|
1143
|
+
import re, sys, pathlib
|
|
1144
|
+
path, begin, end, body = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
1145
|
+
text = pathlib.Path(path).read_text()
|
|
1146
|
+
pattern = re.compile(re.escape(begin) + r".*?" + re.escape(end), re.DOTALL)
|
|
1147
|
+
matches = pattern.findall(text)
|
|
1148
|
+
if len(matches) > 1:
|
|
1149
|
+
sys.stderr.write("ARIS:WARN multiple ARIS blocks found in CLAUDE.md; skipping update\n")
|
|
1150
|
+
sys.stdout.write(text)
|
|
1151
|
+
else:
|
|
1152
|
+
sys.stdout.write(pattern.sub(body, text))
|
|
1153
|
+
PYEOF
|
|
1154
|
+
)" || { warn "CLAUDE.md update failed (best-effort, continuing)"; return 0; }
|
|
1155
|
+
else
|
|
1156
|
+
new_content="$original"
|
|
1157
|
+
[[ -n "$original" ]] && new_content="${new_content}"$'\n'
|
|
1158
|
+
new_content="${new_content}${new_block}"$'\n'
|
|
1159
|
+
fi
|
|
1160
|
+
|
|
1161
|
+
# Compare-and-swap: re-read file, only commit if unchanged from snapshot
|
|
1162
|
+
if $DRY_RUN; then log " (dry-run) would update CLAUDE.md ARIS block"; return 0; fi
|
|
1163
|
+
tmp="$DOC_FILE.aris-tmp.$$"
|
|
1164
|
+
printf '%s' "$new_content" > "$tmp"
|
|
1165
|
+
local current; current="$(cat "$DOC_FILE")"
|
|
1166
|
+
if [[ "$current" != "$original" ]]; then
|
|
1167
|
+
rm -f "$tmp"
|
|
1168
|
+
warn "CLAUDE.md changed during install — skipping doc update (rerun to retry)"
|
|
1169
|
+
return 0
|
|
1170
|
+
fi
|
|
1171
|
+
mv -f "$tmp" "$DOC_FILE"
|
|
1172
|
+
log " ✓ updated CLAUDE.md (ARIS managed block)"
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
# ─── Uninstall ────────────────────────────────────────────────────────────────
|
|
1176
|
+
do_uninstall() {
|
|
1177
|
+
[[ -f "$MANIFEST_PATH" ]] || die "no manifest at $MANIFEST_PATH; nothing to uninstall"
|
|
1178
|
+
local manifest_data; manifest_data="$(mktemp -t aris-manifest.XXXX)"
|
|
1179
|
+
load_manifest "$MANIFEST_PATH" "$manifest_data"
|
|
1180
|
+
log ""
|
|
1181
|
+
log "Uninstall plan:"
|
|
1182
|
+
while IFS=$'\t' read -r kind name src target mode; do
|
|
1183
|
+
[[ -z "$name" ]] && continue
|
|
1184
|
+
log " - $name ($kind)"
|
|
1185
|
+
done < "$manifest_data"
|
|
1186
|
+
if ! $DRY_RUN && ! $QUIET; then
|
|
1187
|
+
prompt "Proceed?" || { log "aborted"; exit 0; }
|
|
1188
|
+
fi
|
|
1189
|
+
while IFS=$'\t' read -r kind name src target mode; do
|
|
1190
|
+
[[ -z "$name" ]] && continue
|
|
1191
|
+
local target_path="$PROJECT_PATH/$target"
|
|
1192
|
+
local expected="$SKILLS_DIR_ABS/$name"
|
|
1193
|
+
# S1: must be symlink
|
|
1194
|
+
is_symlink "$target_path" || { warn "S1: $target_path not a symlink, skipping"; continue; }
|
|
1195
|
+
# S8 + S11: revalidate target
|
|
1196
|
+
local cur; cur="$(read_link_target "$target_path")"
|
|
1197
|
+
[[ "$cur" != /* ]] && cur="$(canonicalize "$(dirname "$target_path")/$cur")"
|
|
1198
|
+
if [[ "$cur" != "$expected" ]]; then
|
|
1199
|
+
warn "S8: $target_path target $cur != expected $expected, skipping"
|
|
1200
|
+
continue
|
|
1201
|
+
fi
|
|
1202
|
+
if $DRY_RUN; then log " (dry-run) rm $target_path"
|
|
1203
|
+
else rm -f "$target_path"; log " - removed $name"
|
|
1204
|
+
fi
|
|
1205
|
+
done < "$manifest_data"
|
|
1206
|
+
rm -f "$manifest_data"
|
|
1207
|
+
# #174 Phase 0: best-effort cleanup of `.aris/tools` symlink, only if it
|
|
1208
|
+
# is exactly the managed symlink. Anything else (user-created dir, custom
|
|
1209
|
+
# symlink target) is left alone.
|
|
1210
|
+
remove_tools_symlink
|
|
1211
|
+
# F5: best-effort cleanup of managed agent profile symlinks.
|
|
1212
|
+
remove_agent_profiles
|
|
1213
|
+
if ! $DRY_RUN; then
|
|
1214
|
+
# Keep .prev for forensics, remove current manifest
|
|
1215
|
+
[[ -f "$MANIFEST_PATH" ]] && mv -f "$MANIFEST_PATH" "$MANIFEST_PREV"
|
|
1216
|
+
log " ✓ uninstalled (manifest preserved as $MANIFEST_PREV)"
|
|
1217
|
+
fi
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
# ─── Main flow ────────────────────────────────────────────────────────────────
|
|
1221
|
+
log ""
|
|
1222
|
+
log "ARIS Project Install"
|
|
1223
|
+
log " Project: $PROJECT_PATH"
|
|
1224
|
+
log " ARIS repo: $ARIS_REPO"
|
|
1225
|
+
log " Action: $ACTION$($DRY_RUN && echo ' (dry-run)')"
|
|
1226
|
+
log ""
|
|
1227
|
+
|
|
1228
|
+
check_no_symlinked_parents
|
|
1229
|
+
acquire_lock
|
|
1230
|
+
|
|
1231
|
+
if [[ "$ACTION" == "uninstall" ]]; then
|
|
1232
|
+
do_uninstall
|
|
1233
|
+
exit 0
|
|
1234
|
+
fi
|
|
1235
|
+
|
|
1236
|
+
# Migration check (only for install/reconcile)
|
|
1237
|
+
LEGACY_KIND="$(detect_legacy)"
|
|
1238
|
+
if [[ "$LEGACY_KIND" != "none" ]]; then
|
|
1239
|
+
if ! $FROM_OLD; then
|
|
1240
|
+
log "Legacy nested install detected: $LEGACY_NESTED ($LEGACY_KIND)"
|
|
1241
|
+
log "→ to migrate, rerun with --from-old"
|
|
1242
|
+
log " for COPY-style legacy installs, also pass --migrate-copy keep-user|prefer-upstream"
|
|
1243
|
+
exit 1
|
|
1244
|
+
fi
|
|
1245
|
+
migrate_legacy
|
|
1246
|
+
fi
|
|
1247
|
+
|
|
1248
|
+
# If --reconcile but no manifest, fail
|
|
1249
|
+
if [[ "$ACTION" == "reconcile" && ! -f "$MANIFEST_PATH" ]]; then
|
|
1250
|
+
die "--reconcile requires existing manifest; none found at $MANIFEST_PATH"
|
|
1251
|
+
fi
|
|
1252
|
+
|
|
1253
|
+
# Build inventories
|
|
1254
|
+
UPSTREAM_FILE="$(mktemp -t aris-upstream.XXXX)"
|
|
1255
|
+
build_upstream_inventory "$ARIS_REPO" > "$UPSTREAM_FILE"
|
|
1256
|
+
[[ -s "$UPSTREAM_FILE" ]] || die "upstream inventory empty (broken aris-repo?)"
|
|
1257
|
+
|
|
1258
|
+
MANIFEST_DATA="$(mktemp -t aris-manifest.XXXX)"
|
|
1259
|
+
load_manifest "$MANIFEST_PATH" "$MANIFEST_DATA"
|
|
1260
|
+
|
|
1261
|
+
# Selective install (#366): build the selected set, then plan against it.
|
|
1262
|
+
SELECTED_FILE="$(mktemp -t aris-selected.XXXX)"
|
|
1263
|
+
DECLINED_CANDIDATES="$(mktemp -t aris-declined.XXXX)"
|
|
1264
|
+
build_selection "$UPSTREAM_FILE" "$DECLINED_CANDIDATES" "$SELECTED_FILE"
|
|
1265
|
+
SELECTED_UPSTREAM="$(mktemp -t aris-upstream-sel.XXXX)"
|
|
1266
|
+
filter_upstream_by_selection "$UPSTREAM_FILE" "$SELECTED_FILE" "$SELECTED_UPSTREAM"
|
|
1267
|
+
N_SELECTED=$(grep -c '^skill|' "$SELECTED_UPSTREAM" || true)
|
|
1268
|
+
N_UPSTREAM=$(grep -c '^skill|' "$UPSTREAM_FILE" || true)
|
|
1269
|
+
log ""
|
|
1270
|
+
log "Selection: $N_SELECTED of $N_UPSTREAM upstream skills"
|
|
1271
|
+
|
|
1272
|
+
PLAN_FILE="$(mktemp -t aris-plan.XXXX)"
|
|
1273
|
+
compute_plan "$SELECTED_UPSTREAM" "$MANIFEST_DATA" "$PLAN_FILE"
|
|
1274
|
+
print_plan "$PLAN_FILE"
|
|
1275
|
+
|
|
1276
|
+
# Conflict resolution
|
|
1277
|
+
N_CONFLICT=$(grep -c '^CONFLICT|' "$PLAN_FILE" || true)
|
|
1278
|
+
if (( N_CONFLICT > 0 )); then
|
|
1279
|
+
# Check if any can be auto-resolved by --adopt-existing (where current target == expected)
|
|
1280
|
+
# (Already handled by ADOPT classification — anything still in CONFLICT is a real conflict.)
|
|
1281
|
+
# Apply --replace-link allowlist for symlink-to-other-repo-entry conflicts.
|
|
1282
|
+
# awk (string compare on field 3 = name) instead of sed: plan lines are
|
|
1283
|
+
# pipe-delimited, and only symlink conflicts may convert — a real_path
|
|
1284
|
+
# CONFLICT must never become UPDATE_TARGET. Converting also strips the
|
|
1285
|
+
# symlink_to: prefix so field 4 matches the shape of native UPDATE_TARGET rows.
|
|
1286
|
+
if [[ ${#REPLACE_LINK_NAMES[@]} -gt 0 ]]; then
|
|
1287
|
+
for n in "${REPLACE_LINK_NAMES[@]}"; do
|
|
1288
|
+
PLAN_TMP="${PLAN_FILE}.tmp"
|
|
1289
|
+
awk -F'|' -v OFS='|' -v n="$n" \
|
|
1290
|
+
'$1=="CONFLICT" && $3==n && $4 ~ /^symlink_to:/ { sub(/^symlink_to:/, "", $4); $1="UPDATE_TARGET" } { print }' \
|
|
1291
|
+
"$PLAN_FILE" > "$PLAN_TMP" && mv "$PLAN_TMP" "$PLAN_FILE"
|
|
1292
|
+
done
|
|
1293
|
+
N_CONFLICT=$(grep -c '^CONFLICT|' "$PLAN_FILE" || true)
|
|
1294
|
+
fi
|
|
1295
|
+
if (( N_CONFLICT > 0 )); then
|
|
1296
|
+
log ""
|
|
1297
|
+
log "Aborting due to $N_CONFLICT unresolved conflicts."
|
|
1298
|
+
log "Resolve options per name:"
|
|
1299
|
+
log " - back up & remove the conflicting path manually, then rerun"
|
|
1300
|
+
log " - if it's a foreign symlink that should be replaced: --replace-link NAME"
|
|
1301
|
+
exit 1
|
|
1302
|
+
fi
|
|
1303
|
+
fi
|
|
1304
|
+
|
|
1305
|
+
if $DRY_RUN; then
|
|
1306
|
+
# #174 preview: print the planned `.aris/tools` symlink action (function
|
|
1307
|
+
# is idempotent + DRY_RUN-aware, so it just logs in this mode)
|
|
1308
|
+
ensure_tools_symlink
|
|
1309
|
+
# F5 preview: print planned agent profile symlinks.
|
|
1310
|
+
ensure_agent_profiles
|
|
1311
|
+
log ""
|
|
1312
|
+
log "(dry-run) no changes made"
|
|
1313
|
+
exit 0
|
|
1314
|
+
fi
|
|
1315
|
+
|
|
1316
|
+
# Confirm if any mutations
|
|
1317
|
+
N_CHANGES=$(awk -F'|' '$1=="CREATE"||$1=="UPDATE_TARGET"||$1=="REMOVE"' "$PLAN_FILE" | wc -l | tr -d ' ')
|
|
1318
|
+
if (( N_CHANGES > 0 )) && ! $QUIET; then
|
|
1319
|
+
prompt "Apply these $N_CHANGES changes?" || { log "aborted"; exit 0; }
|
|
1320
|
+
fi
|
|
1321
|
+
|
|
1322
|
+
# Apply
|
|
1323
|
+
MANIFEST_TMP="$MANIFEST_PATH.tmp.$$" # S12: same dir as destination
|
|
1324
|
+
mkdir -p "$PROJECT_ARIS_DIR"
|
|
1325
|
+
write_manifest_tmp "$PLAN_FILE" "$MANIFEST_TMP"
|
|
1326
|
+
log ""
|
|
1327
|
+
log "Applying:"
|
|
1328
|
+
apply_plan "$PLAN_FILE" "$MANIFEST_TMP"
|
|
1329
|
+
commit_manifest "$MANIFEST_TMP"
|
|
1330
|
+
|
|
1331
|
+
# #174 Phase 0: ensure project-local .aris/tools symlink (purely additive).
|
|
1332
|
+
# Runs after manifest commit so a failure here doesn't roll back skill links.
|
|
1333
|
+
ensure_tools_symlink
|
|
1334
|
+
# F5: deploy Copilot agent profiles from <aris-repo>/.github/agents/ (purely additive).
|
|
1335
|
+
ensure_agent_profiles
|
|
1336
|
+
|
|
1337
|
+
|
|
1338
|
+
# #366: persist declined skills + global repo pointer (both best-effort,
|
|
1339
|
+
# after manifest commit for the same reason as above).
|
|
1340
|
+
save_declined "$DECLINED_CANDIDATES" "$SELECTED_FILE"
|
|
1341
|
+
ensure_global_pointer
|
|
1342
|
+
|
|
1343
|
+
# Handle prefer-upstream legacy archive AFTER successful apply
|
|
1344
|
+
if [[ "$LEGACY_KIND" == "real_dir" && "$MIGRATE_COPY" == "prefer-upstream" ]]; then
|
|
1345
|
+
archive_legacy_copy
|
|
1346
|
+
fi
|
|
1347
|
+
|
|
1348
|
+
# CLAUDE.md best-effort
|
|
1349
|
+
INSTALLED_NAMES="$(mktemp -t aris-names.XXXX)"
|
|
1350
|
+
awk -F'|' '$1=="REUSE"||$1=="ADOPT"||$1=="CREATE"||$1=="UPDATE_TARGET"{print $3}' "$PLAN_FILE" > "$INSTALLED_NAMES"
|
|
1351
|
+
update_claude_doc "$INSTALLED_NAMES"
|
|
1352
|
+
rm -f "$INSTALLED_NAMES"
|
|
1353
|
+
|
|
1354
|
+
# Verify
|
|
1355
|
+
if ! $DRY_RUN; then
|
|
1356
|
+
BAD=0
|
|
1357
|
+
while IFS=$'\t' read -r v_kind v_name v_src v_target v_mode; do
|
|
1358
|
+
[[ -z "$v_name" ]] && continue
|
|
1359
|
+
VTARGET="$PROJECT_PATH/$v_target"
|
|
1360
|
+
if ! is_symlink "$VTARGET"; then warn "verify: $VTARGET missing"; BAD=$((BAD+1)); fi
|
|
1361
|
+
done < <(awk -F'\t' '
|
|
1362
|
+
BEGIN { in_body=0 }
|
|
1363
|
+
/^kind\tname\tsource_rel\ttarget_rel\tmode$/ { in_body=1; next }
|
|
1364
|
+
in_body && NF==5 { print }
|
|
1365
|
+
' "$MANIFEST_PATH")
|
|
1366
|
+
(( BAD == 0 )) && log "" && log "✓ Install complete. $N_CHANGES changes applied."
|
|
1367
|
+
fi
|
|
1368
|
+
|
|
1369
|
+
# Cleanup
|
|
1370
|
+
rm -f "$UPSTREAM_FILE" "$MANIFEST_DATA" "$PLAN_FILE" "$SELECTED_FILE" "$SELECTED_UPSTREAM" "$DECLINED_CANDIDATES"
|