@iowarp/clio-coder 0.4.1 → 0.4.2
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/CHANGELOG.md +92 -0
- package/CONTRIBUTING.md +59 -36
- package/README.md +404 -472
- package/SECURITY.md +2 -1
- package/dist/{acp-ZILU3AUO.js → acp-TMDQZDIG.js} +7 -7
- package/dist/{agents-HYWGBGQR.js → agents-5N5NG3XG.js} +28 -28
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-N3QT7CBO.js → auth-Z5CCBXKQ.js} +8 -9
- package/dist/{builtins-UJLMOVOV.js → builtins-K6TNDT24.js} +4 -4
- package/dist/{chunk-GVQJ5CCZ.js → chunk-2HFQNRV3.js} +7 -7
- package/dist/{chunk-QMXC4JB7.js → chunk-2NHR3NAY.js} +163 -1401
- package/dist/chunk-2X4RYJTJ.js +39 -0
- package/dist/{chunk-Y45G3AXC.js → chunk-2Z2IKEXI.js} +6 -10
- package/dist/{chunk-EIMVLWB3.js → chunk-34BHNEE3.js} +7 -3
- package/dist/{chunk-GIZNH63R.js → chunk-35MSIRKH.js} +9 -4
- package/dist/chunk-3EBYEESD.js +314 -0
- package/dist/{chunk-CTJ4RNAA.js → chunk-3F7VUY77.js} +2 -2
- package/dist/{chunk-AP73CFDC.js → chunk-3KIPBMUA.js} +2 -2
- package/dist/{chunk-J5LZHVIT.js → chunk-3M6DQK6S.js} +113 -35
- package/dist/{chunk-VEGN6WIQ.js → chunk-462T4EGZ.js} +2 -2
- package/dist/{chunk-AFKWHWXF.js → chunk-4JDLP6ZS.js} +33 -16
- package/dist/{chunk-6FN3E6KX.js → chunk-4O6MANBS.js} +2 -2
- package/dist/{chunk-AKB4GYDL.js → chunk-54ODD65L.js} +5 -5
- package/dist/{chunk-BBTJOK6Y.js → chunk-5KW52TEP.js} +3 -3
- package/dist/{chunk-6CCS4G3W.js → chunk-5PFYMY2V.js} +2 -2
- package/dist/chunk-77QIVUZB.js +1334 -0
- package/dist/{chunk-7OBGU7UB.js → chunk-7BHIY2MW.js} +7 -13
- package/dist/{chunk-3QSOM6PA.js → chunk-AZ4WMN4W.js} +2 -2
- package/dist/{chunk-6NJQITNH.js → chunk-B74PXLU7.js} +6 -3
- package/dist/{chunk-R23Z6K6I.js → chunk-B7HM5Z7T.js} +15 -15
- package/dist/{chunk-R32CLGZ6.js → chunk-BO7Y52RY.js} +81 -20
- package/dist/{chunk-UEDMSP56.js → chunk-BYMNWQ7O.js} +123 -148
- package/dist/{chunk-ZJLUDYFY.js → chunk-CRFOIAX3.js} +4 -4
- package/dist/{chunk-2NM363SV.js → chunk-CYZW7JHJ.js} +7 -7
- package/dist/{chunk-6HMJX2VU.js → chunk-DYHAXKHD.js} +38 -10
- package/dist/{chunk-THYWACCR.js → chunk-DZAW46HP.js} +3 -3
- package/dist/{chunk-FYUN5KZ3.js → chunk-DZEK6CJN.js} +17 -17
- package/dist/{chunk-3I5NY75V.js → chunk-E7GT7O5N.js} +5 -5
- package/dist/{chunk-VKFQTNDV.js → chunk-F2I26BDK.js} +4 -4
- package/dist/{chunk-HLW2MRKE.js → chunk-F4EKGO4N.js} +3 -1
- package/dist/{chunk-IXJT6DCX.js → chunk-FVDGR2ZL.js} +3 -3
- package/dist/{chunk-TZSKNMZG.js → chunk-GTUD2WMY.js} +2 -1
- package/dist/{chunk-7EPLI7VL.js → chunk-HIICAHCJ.js} +2 -2
- package/dist/{chunk-E67WX76H.js → chunk-HKMD33FO.js} +29 -80
- package/dist/chunk-HLAFFSEK.js +360 -0
- package/dist/{chunk-UAPGZHYC.js → chunk-I64IFBLB.js} +9 -2
- package/dist/{chunk-XKA2ICR3.js → chunk-I66ZTYNP.js} +440 -175
- package/dist/{chunk-7PWAODYW.js → chunk-I7XBWTYH.js} +2 -2
- package/dist/{chunk-PVAMAVBB.js → chunk-IDNA72AH.js} +102 -2
- package/dist/{chunk-GCSMB2KY.js → chunk-IKOZFYBN.js} +1 -1
- package/dist/{chunk-2VG7KLYV.js → chunk-IKSLQ4XV.js} +5460 -3241
- package/dist/{chunk-QKIFBZKT.js → chunk-IMXMHHMQ.js} +166 -25
- package/dist/{chunk-74YWRRU5.js → chunk-JBCS7CRR.js} +2 -2
- package/dist/{chunk-BDPT6GTK.js → chunk-JWJGP5DQ.js} +2 -2
- package/dist/{chunk-K6BF4U2H.js → chunk-KKOJXO6R.js} +62 -14
- package/dist/chunk-KPXDY6QF.js +47 -0
- package/dist/{chunk-ABLSQ6JX.js → chunk-LJID3DYZ.js} +7 -1
- package/dist/{chunk-VKRH2TCS.js → chunk-M2DAX4F6.js} +2 -2
- package/dist/{chunk-6I5ILFOF.js → chunk-M2WXEHER.js} +2 -2
- package/dist/{chunk-YPI3QQCF.js → chunk-MCEPRMZW.js} +2 -4
- package/dist/{chunk-N5UK64DP.js → chunk-MCMZMDAC.js} +2 -2
- package/dist/{chunk-Y4CAGMM6.js → chunk-MNJGS2IN.js} +5 -6
- package/dist/{chunk-TVHHYFHE.js → chunk-NEDJ26B5.js} +2 -2
- package/dist/{chunk-U2WB7TZS.js → chunk-NMJXSHBJ.js} +97 -85
- package/dist/{chunk-HUAS7ITX.js → chunk-O3YUNJZ2.js} +13 -21
- package/dist/{chunk-MA3H6DM5.js → chunk-P75RZCJW.js} +25 -3
- package/dist/{chunk-IG7BCQBA.js → chunk-PGF63K6I.js} +2 -2
- package/dist/chunk-PJX3WQUQ.js +42 -0
- package/dist/{chunk-6DWBAZ5U.js → chunk-Q4XWMHX6.js} +4 -6
- package/dist/{chunk-OJTRZGR3.js → chunk-QQLGQY2A.js} +8 -8
- package/dist/{chunk-J4HBWF6Y.js → chunk-RLYRBIYQ.js} +115 -20
- package/dist/{chunk-NLFAQR7Z.js → chunk-S66XZJOF.js} +3 -23
- package/dist/{chunk-C537JADH.js → chunk-SSEYRH53.js} +6 -7
- package/dist/chunk-SZAA6XDG.js +30 -0
- package/dist/{chunk-MOPSG2X7.js → chunk-TPEQIQIE.js} +6 -6
- package/dist/{chunk-JA5QWE4Z.js → chunk-UBRFI4HS.js} +1879 -1650
- package/dist/{chunk-BTGG6BG2.js → chunk-UH347SHR.js} +154 -15
- package/dist/{chunk-5YHDIDBP.js → chunk-UH632ZYL.js} +2 -2
- package/dist/{chunk-BWW4HLO4.js → chunk-UXCU4E3T.js} +8 -6
- package/dist/{chunk-6VC4OV3Z.js → chunk-VIA6RFQZ.js} +3 -11
- package/dist/{chunk-ZAZB4JMW.js → chunk-VKPAQYEB.js} +27 -8
- package/dist/{chunk-UXN6JT4W.js → chunk-W4YEMFBX.js} +2 -2
- package/dist/{chunk-TD3PGPQA.js → chunk-W6NIE6OW.js} +2 -2
- package/dist/{chunk-TVH4ONAM.js → chunk-X7IARSHT.js} +3 -3
- package/dist/{chunk-PJJ6MY27.js → chunk-XE3PCIXH.js} +3 -3
- package/dist/{chunk-FEFIFZTL.js → chunk-XGDPUNND.js} +2 -2
- package/dist/{chunk-SCYB3HA4.js → chunk-XOXV5GKE.js} +51 -16
- package/dist/{chunk-QTFGO774.js → chunk-XQRY4DTA.js} +24 -11
- package/dist/{chunk-BJGUKIG4.js → chunk-YJISEZKC.js} +2 -2
- package/dist/{chunk-GPPB3JBE.js → chunk-ZGNYYXQ6.js} +2 -2
- package/dist/{chunk-SINK3QR6.js → chunk-ZNT2M6TG.js} +7 -7
- package/dist/{chunk-7RY5VZPH.js → chunk-ZW4HH5JJ.js} +6 -6
- package/dist/cli/index.js +33 -32
- package/dist/{clio-IT3G3VQH.js → clio-7VB377CC.js} +7 -7
- package/dist/{code-nav-RK6S7F6E.js → code-nav-YVLCYA7V.js} +85 -17
- package/dist/{config-3QZRWZJF.js → config-4HVOS65E.js} +88 -43
- package/dist/{configure-FL7Y3KJF.js → configure-PIWO7B24.js} +10 -10
- package/dist/{context-5HE7ODYK.js → context-IYEHL3WQ.js} +33 -31
- package/dist/{context-XNHL75JV.js → context-KQYIWPWT.js} +47 -34
- package/dist/{context-KYQFRVDC.js → context-N6ZE3LGJ.js} +11 -11
- package/dist/{context-clear-N545L53A.js → context-clear-G4OGZJDS.js} +33 -31
- package/dist/{context-working-set-QHKXSV2F.js → context-working-set-BWLF6LJP.js} +7 -7
- package/dist/{dispatch-runner-RGIE5PCT.js → dispatch-runner-2QQAITS3.js} +38 -38
- package/dist/{docs-5NAF6AU7.js → docs-PD3EXDKU.js} +21 -20
- package/dist/{doctor-ZGPEGHIP.js → doctor-LHBD36VU.js} +23 -22
- package/dist/{eval-GXLL44RD.js → eval-C45FYRJ6.js} +21 -20
- package/dist/{eval-inventory-HBWSWQOK.js → eval-inventory-6DEJPLBF.js} +2 -2
- package/dist/{evidence-HWLBRH3Q.js → evidence-6SHONYAF.js} +30 -28
- package/dist/{evolve-FTZBMNVW.js → evolve-KRKMV72X.js} +30 -28
- package/dist/{extensions-VHRBEID7.js → extensions-KPZ2UHBB.js} +5 -3
- package/dist/{fleet-CKZHJWZJ.js → fleet-IVTCKDHT.js} +62 -61
- package/dist/{fleet-commands-EXDXBMV6.js → fleet-commands-EDWL3IT7.js} +5 -5
- package/dist/{fleet-decisions-OTHB6KRL.js → fleet-decisions-YP3YEFGK.js} +4 -4
- package/dist/{fleet-graph-YTEZUCUT.js → fleet-graph-ZFWKHY2M.js} +16 -14
- package/dist/{fleet-inspect-SS6YMDCK.js → fleet-inspect-FVUNCBML.js} +31 -29
- package/dist/{fleet-preflight-PBY4VYOM.js → fleet-preflight-UN5XED4R.js} +2 -2
- package/dist/{fleet-validate-KMEM5L3S.js → fleet-validate-XOWC4HSX.js} +17 -15
- package/dist/{fleet-verify-QD5M7E7Q.js → fleet-verify-UN3SODEL.js} +30 -28
- package/dist/{fleet-view-WAMJYNDT.js → fleet-view-TWHJKCN6.js} +31 -29
- package/dist/{init-5XQRBOFV.js → init-T2QORQ3Y.js} +50 -49
- package/dist/{interop-34TVO25M.js → interop-IN5I2A66.js} +5 -5
- package/dist/{library-3QY6KF57.js → library-LSCATDLZ.js} +15 -13
- package/dist/{memory-L4UTIIIW.js → memory-HYOKAGGJ.js} +31 -29
- package/dist/{models-ZVX3QOWE.js → models-2GPMFYCM.js} +22 -21
- package/dist/{monitor-CEKVSYTS.js → monitor-E4ASVUJH.js} +34 -32
- package/dist/{orchestrator-77BAP6BC.js → orchestrator-DDMPR3PY.js} +984 -583
- package/dist/{panes-7STHOAUJ.js → panes-E3RUXOW5.js} +4 -4
- package/dist/{panes-SHAUIRXY.js → panes-IXKLOKA2.js} +23 -8
- package/dist/{reset-EOLM7GVE.js → reset-OAQP3W4O.js} +4 -4
- package/dist/{resources-74GKTLSF.js → resources-OTRSN34L.js} +15 -13
- package/dist/{run-HBAUJNNZ.js → run-5DEYH5QK.js} +60 -59
- package/dist/{share-G3APVLVP.js → share-IHWTLO3M.js} +19 -15
- package/dist/{skills-35HHUKCR.js → skills-IYMXMKW4.js} +17 -15
- package/dist/{skills-eval-QN4HSHDC.js → skills-eval-DROHSJAR.js} +36 -36
- package/dist/{skills-inventory-J357J34F.js → skills-inventory-D7X4L4ZX.js} +15 -13
- package/dist/{slash-commands-JZZCQA32.js → slash-commands-QBM7UZ3B.js} +21 -18
- package/dist/{steer-XAVHJM22.js → steer-Z5DO23FJ.js} +2 -2
- package/dist/{targets-DSM6CY3M.js → targets-P2FUC4IL.js} +25 -28
- package/dist/{terminal-lease-JOPFUVEM.js → terminal-lease-YREJ3JX2.js} +5 -5
- package/dist/{tools-MKNWVPBH.js → tools-5B7RO6MV.js} +4 -4
- package/dist/{trace-ECQ7TIYZ.js → trace-YMGMUM6A.js} +55 -7
- package/dist/{upgrade-H7TOM7YL.js → upgrade-PXK3S2YM.js} +11 -9
- package/dist/{usage-X52N3IDJ.js → usage-ME5MPXGX.js} +36 -34
- package/dist/{verifiers-EJTVVSMA.js → verifiers-BVZ7IWOO.js} +5 -5
- package/dist/{verify-YJL6XET2.js → verify-5K7ZKQFC.js} +4 -4
- package/dist/{web-fetch-MPIFL3LL.js → web-fetch-MPARV2K7.js} +2 -2
- package/dist/{wiki-generate-4NDZTQ4B.js → wiki-generate-F5W5QTYY.js} +48 -47
- package/dist/{with-panes-OBOBFIIR.js → with-panes-BYOJCLAM.js} +51 -255
- package/dist/worker/entry.js +45 -30
- package/docs/README.md +176 -81
- package/docs/{acp.md → architecture/acp.md} +36 -20
- package/docs/{alcf-provider.md → architecture/alcf-provider.md} +8 -5
- package/docs/{architecture.md → architecture/architecture.md} +43 -22
- package/docs/{artifact-placement.md → architecture/artifact-placement.md} +26 -23
- package/docs/architecture/artifact-versions.md +90 -0
- package/docs/{capacity-and-scheduling.md → architecture/capacity-and-scheduling.md} +26 -13
- package/docs/{context-engine.md → architecture/context-engine.md} +25 -25
- package/docs/{context-working-set.md → architecture/context-working-set.md} +13 -10
- package/docs/{dispatch-architecture-rationale.md → architecture/dispatch-architecture-rationale.md} +12 -9
- package/docs/{dispatch-typed-intent.md → architecture/dispatch-typed-intent.md} +68 -46
- package/docs/{evidence-and-memory.md → architecture/evidence-and-memory.md} +23 -16
- package/docs/{middleware-and-components.md → architecture/middleware-and-components.md} +11 -5
- package/docs/{model-catalog.md → architecture/model-catalog.md} +40 -17
- package/docs/{observability.md → architecture/observability.md} +26 -13
- package/docs/{pi-boundary.md → architecture/pi-boundary.md} +24 -11
- package/docs/{prompt-envelope-and-tools.md → architecture/prompt-envelope-and-tools.md} +55 -20
- package/docs/{provider-adapter-cookbook.md → architecture/provider-adapter-cookbook.md} +35 -24
- package/docs/{safety-model.md → architecture/safety-model.md} +20 -15
- package/docs/{session-lifecycle.md → architecture/session-lifecycle.md} +8 -5
- package/docs/architecture/time-conventions.md +125 -0
- package/docs/{trace-store.md → architecture/trace-store.md} +13 -5
- package/docs/{tui-design.md → architecture/tui-design.md} +13 -13
- package/docs/{worker-dispatch-mechanics.md → architecture/worker-dispatch-mechanics.md} +27 -30
- package/docs/{built-in-agents.md → guide/built-in-agents.md} +50 -34
- package/docs/{commands-and-modes.md → guide/commands-and-modes.md} +65 -60
- package/docs/{configuration-and-targets.md → guide/configuration-and-targets.md} +227 -289
- package/docs/guide/configuration-reference.md +1158 -0
- package/docs/{environment-variables.md → guide/environment-variables.md} +31 -28
- package/docs/{exit-codes-and-output.md → guide/exit-codes-and-output.md} +6 -3
- package/docs/{extensions-and-sharing.md → guide/extensions-and-sharing.md} +41 -14
- package/docs/{fleet-dispatch.md → guide/fleet-dispatch.md} +39 -43
- package/docs/{glossary.md → guide/glossary.md} +14 -11
- package/docs/{installation-and-lifecycle.md → guide/installation-and-lifecycle.md} +44 -13
- package/docs/guide/panes-and-files.md +290 -0
- package/docs/{proactive-memory.md → guide/proactive-memory.md} +79 -66
- package/docs/{resource-library.md → guide/resource-library.md} +13 -4
- package/docs/{skills-marketplace.md → guide/skills-marketplace.md} +7 -3
- package/docs/{tool-usage.md → guide/tool-usage.md} +87 -23
- package/docs/{troubleshooting.md → guide/troubleshooting.md} +9 -4
- package/docs/{config-knobs-audit.md → history/config-knobs-audit.md} +11 -11
- package/docs/{release-cut-checklist.md → history/release-cut-checklist.md} +29 -2
- package/docs/{development-pipeline.md → process/development-pipeline.md} +24 -26
- package/docs/process/documentation-coverage.md +100 -0
- package/docs/process/documentation-guide.md +187 -0
- package/docs/{eval-runner.md → process/eval-runner.md} +41 -50
- package/docs/{evals-internal.md → process/evals-internal.md} +10 -10
- package/docs/{evolution.md → process/evolution.md} +2 -2
- package/docs/{fleet-demo-runbook.md → process/fleet-demo-runbook.md} +11 -7
- package/docs/{git-commit-provenance.md → process/git-commit-provenance.md} +11 -4
- package/docs/{performance-methodology.md → process/performance-methodology.md} +87 -69
- package/docs/{scientific-validation.md → process/scientific-validation.md} +4 -4
- package/evals/README.md +2 -2
- package/package.json +9 -7
- package/skills/README.md +46 -37
- package/skills/coding/ast-grep/SKILL.md +2 -2
- package/skills/coding/coding-standards/SKILL.md +2 -2
- package/skills/coding/prototype/SKILL.md +2 -2
- package/skills/coding/tdd/SKILL.md +2 -2
- package/skills/context/context-handoff/SKILL.md +2 -2
- package/skills/context/context-prime/SKILL.md +2 -2
- package/skills/git/file-ticket/SKILL.md +2 -2
- package/skills/git/fix-issue/SKILL.md +3 -3
- package/skills/git/resolve-merge-conflicts/SKILL.md +2 -2
- package/skills/git/ship/SKILL.md +2 -2
- package/skills/git/worktree-create/SKILL.md +2 -2
- package/skills/git/worktree-merge/SKILL.md +2 -2
- package/skills/meta/clio-coder-dev/SKILL.md +9 -5
- package/skills/meta/clio-coder-dev/evals.md +3 -2
- package/skills/meta/clio-coder-test/SKILL.md +102 -95
- package/skills/meta/clio-coder-test/evals.md +9 -4
- package/skills/meta/clio-coder-test/references/harness.md +100 -124
- package/skills/meta/clio-coder-test/references/test-map.md +77 -50
- package/skills/meta/credentials/SKILL.md +2 -2
- package/skills/meta/find-skills/SKILL.md +2 -2
- package/skills/meta/herdr/SKILL.md +2 -2
- package/skills/meta/skill-craft/SKILL.md +22 -16
- package/skills/planning/architecture/SKILL.md +2 -2
- package/skills/planning/backlog/SKILL.md +2 -2
- package/skills/planning/prd/SKILL.md +2 -2
- package/skills/planning/product-intent/SKILL.md +2 -2
- package/skills/planning/tech-spec/SKILL.md +2 -2
- package/skills/registry.yaml +62 -62
- package/skills/research/arxiv-literature/SKILL.md +2 -2
- package/skills/research/experiment-protocol/SKILL.md +2 -2
- package/skills/research/scientific-debugging/SKILL.md +2 -2
- package/skills/research/scientific-modernization/SKILL.md +2 -2
- package/skills/skill-marketplace.json +62 -62
- package/skills/workflow/cut-it/SKILL.md +2 -2
- package/skills/workflow/design-council/SKILL.md +2 -2
- package/skills/workflow/grill-me/SKILL.md +2 -2
- package/skills/workflow/workflow-distiller/SKILL.md +2 -2
- package/src/cli/args.ts +2 -2
- package/src/cli/bootstrap-generate.ts +1 -1
- package/src/cli/config-inspect.ts +65 -12
- package/src/cli/configure.ts +0 -4
- package/src/cli/docs.ts +22 -14
- package/src/cli/doctor-naming.ts +5 -5
- package/src/cli/doctor-toolchain.ts +3 -3
- package/src/cli/eval.ts +1 -2
- package/src/cli/extensions.ts +2 -1
- package/src/cli/fleet.ts +1 -1
- package/src/cli/index.ts +2 -1
- package/src/cli/internal-dispatch.ts +3 -4
- package/src/cli/panes.ts +19 -5
- package/src/cli/run.ts +2 -2
- package/src/cli/share.ts +5 -1
- package/src/cli/skills-eval.ts +3 -3
- package/src/cli/targets.ts +2 -6
- package/src/cli/trace.ts +55 -4
- package/src/cli/wiki-generate.ts +1 -1
- package/src/core/artifact-paths.ts +1 -1
- package/src/core/bash-exec.ts +131 -86
- package/src/core/bus-events.ts +51 -6
- package/src/core/config.ts +5 -1
- package/src/core/defaults.ts +7 -4
- package/src/core/dispatch-outcome.ts +16 -0
- package/src/core/guardrails.ts +10 -49
- package/src/core/prompt-hint.ts +9 -0
- package/src/domains/agents/builtins/architect.md +2 -3
- package/src/domains/agents/builtins/coder.md +3 -2
- package/src/domains/agents/builtins/debugger.md +2 -2
- package/src/domains/agents/builtins/documenter.md +2 -2
- package/src/domains/agents/builtins/git-master.md +1 -1
- package/src/domains/agents/builtins/oracle.md +1 -1
- package/src/domains/agents/builtins/provenance.md +1 -1
- package/src/domains/agents/builtins/researcher.md +1 -1
- package/src/domains/agents/builtins/scout.md +1 -1
- package/src/domains/agents/builtins/tester.md +2 -2
- package/src/domains/agents/builtins/verifier.md +2 -2
- package/src/domains/agents/builtins/wiki-writer.md +1 -1
- package/src/domains/agents/catalog.ts +12 -14
- package/src/domains/agents/contract.ts +2 -0
- package/src/domains/agents/extension.ts +23 -1
- package/src/domains/config/keybindings.ts +8 -0
- package/src/domains/context/extension.ts +0 -3
- package/src/domains/context/working-set/path-index.ts +1 -0
- package/src/domains/dispatch/capability-match.ts +10 -0
- package/src/domains/dispatch/extension.ts +105 -22
- package/src/domains/dispatch/host-verification.ts +435 -39
- package/src/domains/dispatch/intent-requirements.ts +10 -0
- package/src/domains/dispatch/intent.ts +18 -1
- package/src/domains/dispatch/path-scope.ts +235 -24
- package/src/domains/dispatch/run-event-journal.ts +4 -15
- package/src/domains/dispatch/state.ts +2 -3
- package/src/domains/dispatch/transport.ts +45 -21
- package/src/domains/dispatch/types.ts +55 -3
- package/src/domains/eval/artifacts/store.ts +5 -0
- package/src/domains/eval/store.ts +8 -1
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/extensions/contract.ts +15 -1
- package/src/domains/extensions/discovery.ts +238 -41
- package/src/domains/extensions/extension.ts +105 -6
- package/src/domains/extensions/index.ts +24 -0
- package/src/domains/extensions/integrity.ts +189 -0
- package/src/domains/extensions/manager.ts +17 -1
- package/src/domains/extensions/resource-path.ts +27 -0
- package/src/domains/extensions/resources.ts +18 -38
- package/src/domains/extensions/snapshot-store.ts +39 -0
- package/src/domains/extensions/snapshot.ts +180 -0
- package/src/domains/extensions/state.ts +385 -57
- package/src/domains/extensions/types.ts +118 -1
- package/src/domains/lifecycle/migrations/2026-09-01-extension-install-digests.ts +27 -0
- package/src/domains/lifecycle/migrations/index.ts +2 -0
- package/src/domains/lifecycle/naming-resources.ts +19 -4
- package/src/domains/lifecycle/naming-yazi.ts +10 -5
- package/src/domains/middleware/contract.ts +26 -0
- package/src/domains/middleware/extension.ts +24 -24
- package/src/domains/middleware/hook-receipts.ts +27 -4
- package/src/domains/middleware/hooks-io.ts +65 -32
- package/src/domains/middleware/hooks.ts +64 -0
- package/src/domains/middleware/index.ts +28 -4
- package/src/domains/middleware/registrations.ts +326 -0
- package/src/domains/middleware/runtime.ts +28 -0
- package/src/domains/middleware/snapshot.ts +20 -7
- package/src/domains/mux/contract.ts +38 -0
- package/src/domains/mux/detect.ts +6 -13
- package/src/domains/mux/index.ts +1 -1
- package/src/domains/mux/operations.ts +44 -5
- package/src/domains/mux/yazi/assets/yazi.toml +2 -2
- package/src/domains/mux/yazi/session.ts +53 -4
- package/src/domains/mux/yazi/theme.ts +117 -17
- package/src/domains/observability/contract.ts +10 -11
- package/src/domains/observability/extension.ts +11 -3
- package/src/domains/observability/projection.ts +14 -90
- package/src/domains/observability/trace-store.ts +43 -7
- package/src/domains/prompts/compiler.ts +73 -53
- package/src/domains/prompts/contract.ts +15 -3
- package/src/domains/prompts/extension.ts +97 -9
- package/src/domains/prompts/fragments/identity/clio-worker.md +1 -3
- package/src/domains/prompts/fragments/identity/clio.md +6 -12
- package/src/domains/prompts/fragments/identity/docs-routing.md +1 -2
- package/src/domains/prompts/fragments/identity/self-awareness.md +3 -11
- package/src/domains/prompts/fragments/operating/contract.md +7 -15
- package/src/domains/prompts/fragments/operating/delegation.md +32 -34
- package/src/domains/prompts/fragments/operating/skills.md +10 -24
- package/src/domains/prompts/fragments/operating/worker.md +1 -8
- package/src/domains/providers/index.ts +1 -1
- package/src/domains/providers/model-runtime-capabilities.ts +85 -21
- package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +669 -104
- package/src/domains/providers/runtime-resolution.ts +31 -0
- package/src/domains/providers/runtimes/common/probe-helpers.ts +7 -2
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +9 -1
- package/src/domains/providers/types/cost-provenance.ts +19 -0
- package/src/domains/providers/types/local-model-quirks.ts +85 -37
- package/src/domains/resources/skills/loader.ts +16 -19
- package/src/domains/safety/call-target.ts +1 -1
- package/src/domains/safety/loop-detector.ts +7 -4
- package/src/domains/session/task-board.ts +10 -9
- package/src/domains/share/archive.ts +164 -7
- package/src/engine/acp/server.ts +62 -9
- package/src/engine/apis/llamacpp-residency.ts +3 -4
- package/src/engine/apis/lmstudio.ts +3 -3
- package/src/engine/apis/ollama-native.ts +6 -6
- package/src/engine/apis/openai-completions.ts +28 -25
- package/src/engine/apis/output-budget.ts +8 -18
- package/src/engine/apis/residency.ts +8 -27
- package/src/engine/gemma-channel-filter.ts +19 -0
- package/src/engine/loop-guard.ts +92 -12
- package/src/engine/worker-runtime.ts +40 -11
- package/src/engine/worker-tools.ts +3 -1
- package/src/entry/extension-hook-sources.ts +28 -0
- package/src/entry/extension-reload.ts +309 -0
- package/src/entry/orchestrator.ts +59 -35
- package/src/interactive/application-controller.ts +2 -1
- package/src/interactive/bus-notices.ts +8 -1
- package/src/interactive/chat-loop-messages.ts +3 -13
- package/src/interactive/chat-loop.ts +10 -1
- package/src/interactive/chat-panel.ts +36 -13
- package/src/interactive/chat-renderer.ts +71 -7
- package/src/interactive/dispatch-board.ts +6 -11
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/interactive-application.ts +39 -4
- package/src/interactive/interactive-input-runtime.ts +4 -0
- package/src/interactive/interactive-presentation.ts +2 -2
- package/src/interactive/interactive-slash-runtime.ts +2 -0
- package/src/interactive/overlays/extensions.ts +9 -1
- package/src/interactive/overlays/help-reference.ts +13 -0
- package/src/interactive/overlays/settings.ts +27 -16
- package/src/interactive/panes-runtime.ts +111 -35
- package/src/interactive/prompt-cache-identity.ts +88 -0
- package/src/interactive/slash-commands.ts +129 -14
- package/src/interactive/stream-pacing-policy.ts +0 -23
- package/src/interactive/turn-context.ts +30 -15
- package/src/interactive/yazi-bridge.ts +60 -6
- package/src/tools/agent-tools.ts +30 -1
- package/src/tools/artifact.ts +2 -2
- package/src/tools/ask-user.ts +3 -3
- package/src/tools/bash.ts +1 -1
- package/src/tools/bootstrap.ts +4 -0
- package/src/tools/builtin-tool-catalog.ts +52 -22
- package/src/tools/codewiki/code-nav-surface.ts +6 -0
- package/src/tools/codewiki/code-nav.ts +99 -13
- package/src/tools/context/docs-engine.ts +20 -7
- package/src/tools/context/index.ts +29 -12
- package/src/tools/core-bootstrap.ts +28 -6
- package/src/tools/credential-present.ts +1 -2
- package/src/tools/dispatch-arguments.ts +5 -1
- package/src/tools/dispatch-plan.ts +48 -4
- package/src/tools/dispatch-run-events.ts +1 -1
- package/src/tools/dispatch-schema.ts +338 -0
- package/src/tools/dispatch-types.ts +3 -0
- package/src/tools/dispatch.ts +9 -254
- package/src/tools/ledger.ts +3 -5
- package/src/tools/monitor-surface.ts +5 -13
- package/src/tools/observation.ts +4 -5
- package/src/tools/panes-surface.ts +4 -11
- package/src/tools/panes.ts +4 -2
- package/src/tools/policy.ts +15 -2
- package/src/tools/read.ts +5 -6
- package/src/tools/registry.ts +30 -7
- package/src/tools/result-shaping.ts +18 -14
- package/src/tools/steer-surface.ts +1 -1
- package/src/tools/tasks.ts +1 -1
- package/src/tools/truncate.ts +6 -5
- package/src/tools/verify/surface.ts +6 -12
- package/src/tools/web-fetch-surface.ts +1 -3
- package/dist/chunk-5QIAJV2D.js +0 -48
- package/dist/chunk-JZWT5J3Y.js +0 -814
- package/dist/chunk-K7VKOLQQ.js +0 -15
- package/dist/chunk-PMZCIOCJ.js +0 -25
- package/dist/chunk-SUW5DORT.js +0 -819
- package/dist/chunk-UOV2BYIW.js +0 -107
- package/dist/chunk-WR6U3OVP.js +0 -45
- package/docs/artifact-versions.md +0 -67
- package/docs/documentation-coverage.md +0 -46
- package/docs/documentation-guide.md +0 -167
- package/docs/time-conventions.md +0 -101
|
@@ -0,0 +1,1334 @@
|
|
|
1
|
+
import { createRequire as __clioCreateRequire } from "node:module"; const require = __clioCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
getVersionInfo
|
|
4
|
+
} from "./chunk-XPLRXC72.js";
|
|
5
|
+
import {
|
|
6
|
+
require_dist
|
|
7
|
+
} from "./chunk-APJ265NV.js";
|
|
8
|
+
import {
|
|
9
|
+
clioConfigDir
|
|
10
|
+
} from "./chunk-BYP5D4HI.js";
|
|
11
|
+
import {
|
|
12
|
+
safeResourceWrite
|
|
13
|
+
} from "./chunk-WEPFGWHJ.js";
|
|
14
|
+
import {
|
|
15
|
+
__toESM,
|
|
16
|
+
init_esm_shims
|
|
17
|
+
} from "./chunk-3R73A4XB.js";
|
|
18
|
+
|
|
19
|
+
// src/domains/extensions/discovery.ts
|
|
20
|
+
init_esm_shims();
|
|
21
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
22
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
|
|
25
|
+
// src/domains/extensions/compatibility.ts
|
|
26
|
+
init_esm_shims();
|
|
27
|
+
var MAX_RANGE_CHARS = 256;
|
|
28
|
+
var NUMERIC = "(?:0|[1-9]\\d*)";
|
|
29
|
+
var IDENTIFIER = "[0-9A-Za-z-]+";
|
|
30
|
+
var VERSION_RE = new RegExp(
|
|
31
|
+
`^v?(${NUMERIC})\\.(${NUMERIC})\\.(${NUMERIC})(?:-(${IDENTIFIER}(?:\\.${IDENTIFIER})*))?(?:\\+${IDENTIFIER}(?:\\.${IDENTIFIER})*)?$`
|
|
32
|
+
);
|
|
33
|
+
var PARTIAL_RE = new RegExp(
|
|
34
|
+
`^v?(${NUMERIC}|[xX*])(?:\\.(${NUMERIC}|[xX*]))?(?:\\.(${NUMERIC}|[xX*]))?(?:-(${IDENTIFIER}(?:\\.${IDENTIFIER})*))?(?:\\+${IDENTIFIER}(?:\\.${IDENTIFIER})*)?$`
|
|
35
|
+
);
|
|
36
|
+
function prerelease(value) {
|
|
37
|
+
if (value === void 0) return [];
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const identifier of value.split(".")) {
|
|
40
|
+
if (identifier.length === 0) return null;
|
|
41
|
+
if (/^\d+$/u.test(identifier)) {
|
|
42
|
+
if (identifier.length > 1 && identifier.startsWith("0")) return null;
|
|
43
|
+
const parsed = Number(identifier);
|
|
44
|
+
if (!Number.isSafeInteger(parsed)) return null;
|
|
45
|
+
out.push(parsed);
|
|
46
|
+
} else {
|
|
47
|
+
out.push(identifier);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function parseSemVer(value) {
|
|
53
|
+
const match = VERSION_RE.exec(value);
|
|
54
|
+
if (match === null) return null;
|
|
55
|
+
const parsedPrerelease = prerelease(match[4]);
|
|
56
|
+
if (parsedPrerelease === null) return null;
|
|
57
|
+
const major = Number(match[1]);
|
|
58
|
+
const minor = Number(match[2]);
|
|
59
|
+
const patch = Number(match[3]);
|
|
60
|
+
if (![major, minor, patch].every(Number.isSafeInteger)) return null;
|
|
61
|
+
return { major, minor, patch, prerelease: parsedPrerelease };
|
|
62
|
+
}
|
|
63
|
+
function parsePart(value) {
|
|
64
|
+
if (value === void 0 || value === "x" || value === "X" || value === "*") return null;
|
|
65
|
+
const parsed = Number(value);
|
|
66
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
67
|
+
}
|
|
68
|
+
function parsePartial(value) {
|
|
69
|
+
const match = PARTIAL_RE.exec(value);
|
|
70
|
+
if (match === null) return null;
|
|
71
|
+
for (const part of match.slice(1, 4)) {
|
|
72
|
+
if (part === void 0 || part === "x" || part === "X" || part === "*") continue;
|
|
73
|
+
if (!Number.isSafeInteger(Number(part))) return null;
|
|
74
|
+
}
|
|
75
|
+
const major = parsePart(match[1]);
|
|
76
|
+
const minor = parsePart(match[2]);
|
|
77
|
+
const patch = parsePart(match[3]);
|
|
78
|
+
if (major === null && (match[2] !== void 0 || match[3] !== void 0) || minor === null && match[3] !== void 0) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const parsedPrerelease = prerelease(match[4]);
|
|
82
|
+
if (parsedPrerelease === null) return null;
|
|
83
|
+
if (parsedPrerelease.length > 0 && (major === null || minor === null || patch === null)) return null;
|
|
84
|
+
return { major, minor, patch, prerelease: parsedPrerelease };
|
|
85
|
+
}
|
|
86
|
+
function compareIdentifiers(left, right) {
|
|
87
|
+
if (left === right) return 0;
|
|
88
|
+
if (typeof left === "number" && typeof right === "number") return left < right ? -1 : 1;
|
|
89
|
+
if (typeof left === "number") return -1;
|
|
90
|
+
if (typeof right === "number") return 1;
|
|
91
|
+
return left < right ? -1 : 1;
|
|
92
|
+
}
|
|
93
|
+
function compare(left, right) {
|
|
94
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
95
|
+
if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1;
|
|
96
|
+
}
|
|
97
|
+
if (left.prerelease.length === 0 || right.prerelease.length === 0) {
|
|
98
|
+
if (left.prerelease.length === right.prerelease.length) return 0;
|
|
99
|
+
return left.prerelease.length === 0 ? 1 : -1;
|
|
100
|
+
}
|
|
101
|
+
const length = Math.max(left.prerelease.length, right.prerelease.length);
|
|
102
|
+
for (let index = 0; index < length; index += 1) {
|
|
103
|
+
const leftPart = left.prerelease[index];
|
|
104
|
+
const rightPart = right.prerelease[index];
|
|
105
|
+
if (leftPart === void 0 || rightPart === void 0) {
|
|
106
|
+
if (leftPart === rightPart) return 0;
|
|
107
|
+
return leftPart === void 0 ? -1 : 1;
|
|
108
|
+
}
|
|
109
|
+
const order = compareIdentifiers(leftPart, rightPart);
|
|
110
|
+
if (order !== 0) return order;
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
function floor(partial) {
|
|
115
|
+
return {
|
|
116
|
+
major: partial.major ?? 0,
|
|
117
|
+
minor: partial.minor ?? 0,
|
|
118
|
+
patch: partial.patch ?? 0,
|
|
119
|
+
prerelease: partial.prerelease
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function nextWildcard(partial) {
|
|
123
|
+
if (partial.major === null) return null;
|
|
124
|
+
if (partial.minor === null) return { major: partial.major + 1, minor: 0, patch: 0, prerelease: [] };
|
|
125
|
+
if (partial.patch === null) return { major: partial.major, minor: partial.minor + 1, patch: 0, prerelease: [] };
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function lowerBound(version, inclusive) {
|
|
129
|
+
return (candidate) => inclusive ? compare(candidate, version) >= 0 : compare(candidate, version) > 0;
|
|
130
|
+
}
|
|
131
|
+
function upperBound(version, inclusive) {
|
|
132
|
+
return (candidate) => inclusive ? compare(candidate, version) <= 0 : compare(candidate, version) < 0;
|
|
133
|
+
}
|
|
134
|
+
function core(version) {
|
|
135
|
+
return `${version.major}.${version.minor}.${version.patch}`;
|
|
136
|
+
}
|
|
137
|
+
function predicatesForToken(token) {
|
|
138
|
+
const match = /^(<=|>=|<|>|=|~|\^)?(.+)$/u.exec(token);
|
|
139
|
+
if (match === null) return null;
|
|
140
|
+
const operator = match[1] ?? "";
|
|
141
|
+
const partial = parsePartial(match[2] ?? "");
|
|
142
|
+
if (partial === null) return null;
|
|
143
|
+
const base = floor(partial);
|
|
144
|
+
const wildcardUpper = nextWildcard(partial);
|
|
145
|
+
const prereleaseCore = partial.prerelease.length > 0 ? core(base) : null;
|
|
146
|
+
if (operator === "" || operator === "=") {
|
|
147
|
+
if (partial.major === null) return { predicates: [], prereleaseCore };
|
|
148
|
+
if (wildcardUpper !== null) {
|
|
149
|
+
return { predicates: [lowerBound(base, true), upperBound(wildcardUpper, false)], prereleaseCore };
|
|
150
|
+
}
|
|
151
|
+
return { predicates: [(candidate) => compare(candidate, base) === 0], prereleaseCore };
|
|
152
|
+
}
|
|
153
|
+
if (operator === ">=") return { predicates: [lowerBound(base, true)], prereleaseCore };
|
|
154
|
+
if (operator === "<") return { predicates: [upperBound(base, false)], prereleaseCore };
|
|
155
|
+
if (operator === ">") {
|
|
156
|
+
return {
|
|
157
|
+
predicates: [wildcardUpper === null ? lowerBound(base, false) : lowerBound(wildcardUpper, true)],
|
|
158
|
+
prereleaseCore
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (operator === "<=") {
|
|
162
|
+
return {
|
|
163
|
+
predicates: [wildcardUpper === null ? upperBound(base, true) : upperBound(wildcardUpper, false)],
|
|
164
|
+
prereleaseCore
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (partial.major === null) return { predicates: [], prereleaseCore };
|
|
168
|
+
if (operator === "~") {
|
|
169
|
+
const upper2 = partial.minor === null ? { major: partial.major + 1, minor: 0, patch: 0, prerelease: [] } : { major: partial.major, minor: partial.minor + 1, patch: 0, prerelease: [] };
|
|
170
|
+
return { predicates: [lowerBound(base, true), upperBound(upper2, false)], prereleaseCore };
|
|
171
|
+
}
|
|
172
|
+
const upper = partial.major > 0 ? { major: partial.major + 1, minor: 0, patch: 0, prerelease: [] } : partial.minor === null ? { major: 1, minor: 0, patch: 0, prerelease: [] } : (partial.minor ?? 0) > 0 ? { major: 0, minor: (partial.minor ?? 0) + 1, patch: 0, prerelease: [] } : partial.patch === null ? { major: 0, minor: 1, patch: 0, prerelease: [] } : { major: 0, minor: 0, patch: (partial.patch ?? 0) + 1, prerelease: [] };
|
|
173
|
+
return { predicates: [lowerBound(base, true), upperBound(upper, false)], prereleaseCore };
|
|
174
|
+
}
|
|
175
|
+
function predicatesForSet(raw) {
|
|
176
|
+
const hyphen = /^(\S+)\s+-\s+(\S+)$/u.exec(raw);
|
|
177
|
+
if (hyphen !== null) {
|
|
178
|
+
const left = parsePartial(hyphen[1] ?? "");
|
|
179
|
+
const right = parsePartial(hyphen[2] ?? "");
|
|
180
|
+
if (left === null || right === null || left.major === null || right.major === null) return null;
|
|
181
|
+
const rightUpper = nextWildcard(right);
|
|
182
|
+
return {
|
|
183
|
+
predicates: [
|
|
184
|
+
lowerBound(floor(left), true),
|
|
185
|
+
rightUpper === null ? upperBound(floor(right), true) : upperBound(rightUpper, false)
|
|
186
|
+
],
|
|
187
|
+
prereleaseCores: new Set(
|
|
188
|
+
[
|
|
189
|
+
left.prerelease.length > 0 ? core(floor(left)) : null,
|
|
190
|
+
right.prerelease.length > 0 ? core(floor(right)) : null
|
|
191
|
+
].filter((entry) => entry !== null)
|
|
192
|
+
)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const normalized = raw.replace(/(<=|>=|<|>|=|~|\^)\s+/gu, "$1");
|
|
196
|
+
const tokens = normalized.split(/\s+/u).filter((entry) => entry.length > 0);
|
|
197
|
+
if (tokens.length === 0) return null;
|
|
198
|
+
const predicates = [];
|
|
199
|
+
const prereleaseCores = /* @__PURE__ */ new Set();
|
|
200
|
+
for (const token of tokens) {
|
|
201
|
+
const parsed = predicatesForToken(token);
|
|
202
|
+
if (parsed === null) return null;
|
|
203
|
+
predicates.push(...parsed.predicates);
|
|
204
|
+
if (parsed.prereleaseCore !== null) prereleaseCores.add(parsed.prereleaseCore);
|
|
205
|
+
}
|
|
206
|
+
return { predicates, prereleaseCores };
|
|
207
|
+
}
|
|
208
|
+
function parseRange(range) {
|
|
209
|
+
const trimmed = range.trim();
|
|
210
|
+
if (trimmed.length === 0 || trimmed.length > MAX_RANGE_CHARS) return null;
|
|
211
|
+
const sets = trimmed.split("||");
|
|
212
|
+
if (sets.some((set) => set.trim().length === 0)) return null;
|
|
213
|
+
const parsed = sets.map((set) => predicatesForSet(set.trim()));
|
|
214
|
+
return parsed.every((set) => set !== null) ? parsed : null;
|
|
215
|
+
}
|
|
216
|
+
function isValidSemVerRange(range) {
|
|
217
|
+
return parseRange(range) !== null;
|
|
218
|
+
}
|
|
219
|
+
function satisfiesSemVerRange(version, range) {
|
|
220
|
+
const candidate = parseSemVer(version);
|
|
221
|
+
const sets = parseRange(range);
|
|
222
|
+
if (candidate === null || sets === null) return false;
|
|
223
|
+
return sets.some((set) => {
|
|
224
|
+
if (candidate.prerelease.length > 0 && !set.prereleaseCores.has(core(candidate))) return false;
|
|
225
|
+
return set.predicates.every((predicate) => predicate(candidate));
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function evaluateClioCompatibility(range, runningVersion = getVersionInfo().clio) {
|
|
229
|
+
const rangeValid = isValidSemVerRange(range);
|
|
230
|
+
return {
|
|
231
|
+
rangeValid,
|
|
232
|
+
satisfied: rangeValid && satisfiesSemVerRange(runningVersion, range),
|
|
233
|
+
runningVersion
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/domains/extensions/discovery.ts
|
|
238
|
+
var MANIFEST_NAMES = ["clio-coder-extension.yaml", "clio-coder-extension.yml", "clio-coder-extension.json"];
|
|
239
|
+
var MANIFEST_KEYS = /* @__PURE__ */ new Set([
|
|
240
|
+
"manifestVersion",
|
|
241
|
+
"id",
|
|
242
|
+
"name",
|
|
243
|
+
"version",
|
|
244
|
+
"description",
|
|
245
|
+
"resources",
|
|
246
|
+
"tools",
|
|
247
|
+
"settings",
|
|
248
|
+
"compatibility"
|
|
249
|
+
]);
|
|
250
|
+
var RESOURCE_KEYS = /* @__PURE__ */ new Set(["skills", "prompts", "agents", "fleets", "themes"]);
|
|
251
|
+
var COMPATIBILITY_KEYS = /* @__PURE__ */ new Set(["clio"]);
|
|
252
|
+
function compareNames(a, b) {
|
|
253
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
254
|
+
}
|
|
255
|
+
function isRecord(value) {
|
|
256
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
257
|
+
}
|
|
258
|
+
function trimString(value) {
|
|
259
|
+
if (typeof value !== "string") return void 0;
|
|
260
|
+
const trimmed = value.trim();
|
|
261
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
262
|
+
}
|
|
263
|
+
function validateId(id) {
|
|
264
|
+
if (id.length > 80) return "id exceeds 80 characters";
|
|
265
|
+
if (!/^[a-z0-9][a-z0-9._-]*[a-z0-9]$/.test(id)) {
|
|
266
|
+
return "id must use lowercase letters, numbers, dots, underscores, or hyphens and start/end alphanumeric";
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
function readJsonOrYaml(filePath) {
|
|
271
|
+
const raw = readFileSync(filePath, "utf8");
|
|
272
|
+
if (filePath.endsWith(".json")) return JSON.parse(raw);
|
|
273
|
+
return (0, import_yaml.parse)(raw);
|
|
274
|
+
}
|
|
275
|
+
function rejectUnknownKeys(value, allowed, label, manifestPath, diagnostics) {
|
|
276
|
+
for (const key of Object.keys(value).sort()) {
|
|
277
|
+
if (!allowed.has(key)) {
|
|
278
|
+
diagnostics.push({ type: "error", message: `unknown ${label} key '${key}'`, path: manifestPath });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function normalizeResources(value, manifestPath, diagnostics) {
|
|
283
|
+
if (value === void 0) return {};
|
|
284
|
+
if (!isRecord(value)) {
|
|
285
|
+
diagnostics.push({ type: "error", message: "resources must be an object", path: manifestPath });
|
|
286
|
+
return {};
|
|
287
|
+
}
|
|
288
|
+
rejectUnknownKeys(value, RESOURCE_KEYS, "resources", manifestPath, diagnostics);
|
|
289
|
+
const out = {};
|
|
290
|
+
for (const kind of RESOURCE_KEYS) {
|
|
291
|
+
if (value[kind] === void 0) continue;
|
|
292
|
+
const resourcePath = trimString(value[kind]);
|
|
293
|
+
if (!resourcePath) {
|
|
294
|
+
diagnostics.push({
|
|
295
|
+
type: "error",
|
|
296
|
+
message: `resources.${kind} must be a non-empty string`,
|
|
297
|
+
path: manifestPath
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
out[kind] = resourcePath;
|
|
302
|
+
}
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
function contained(root, candidate) {
|
|
306
|
+
const relative = path.relative(root, candidate);
|
|
307
|
+
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
308
|
+
}
|
|
309
|
+
function validateResourceTree(root, resourcePath, kind, diagnostics) {
|
|
310
|
+
if (path.isAbsolute(resourcePath)) {
|
|
311
|
+
diagnostics.push({ type: "error", message: `resources.${kind} must be relative`, path: resourcePath });
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const resolvedRoot = path.resolve(root);
|
|
315
|
+
const resolved = path.resolve(resolvedRoot, resourcePath);
|
|
316
|
+
if (!contained(resolvedRoot, resolved) || resolved === resolvedRoot) {
|
|
317
|
+
diagnostics.push({ type: "error", message: `resources.${kind} escapes the extension root`, path: resourcePath });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
let canonicalRoot;
|
|
321
|
+
let canonicalResource;
|
|
322
|
+
try {
|
|
323
|
+
canonicalRoot = realpathSync(resolvedRoot);
|
|
324
|
+
canonicalResource = realpathSync(resolved);
|
|
325
|
+
if (!statSync(resolved).isDirectory()) {
|
|
326
|
+
diagnostics.push({ type: "error", message: `resources.${kind} is not a directory`, path: resolved });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
} catch (error) {
|
|
330
|
+
diagnostics.push({
|
|
331
|
+
type: "error",
|
|
332
|
+
message: `resources.${kind} cannot be resolved: ${error instanceof Error ? error.message : String(error)}`,
|
|
333
|
+
path: resolved
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (!contained(canonicalRoot, canonicalResource)) {
|
|
338
|
+
diagnostics.push({ type: "error", message: `resources.${kind} escapes through a symbolic link`, path: resolved });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const pending = [resolved];
|
|
342
|
+
while (pending.length > 0) {
|
|
343
|
+
const directory = pending.pop();
|
|
344
|
+
if (!directory) continue;
|
|
345
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort(compareNames)) {
|
|
346
|
+
const candidate = path.join(directory, entry.name);
|
|
347
|
+
if (entry.isSymbolicLink()) {
|
|
348
|
+
try {
|
|
349
|
+
const canonicalTarget = realpathSync(candidate);
|
|
350
|
+
if (!contained(canonicalRoot, canonicalTarget)) {
|
|
351
|
+
diagnostics.push({
|
|
352
|
+
type: "error",
|
|
353
|
+
message: `resources.${kind} contains an escaping symbolic link`,
|
|
354
|
+
path: candidate
|
|
355
|
+
});
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const targetStat = statSync(candidate);
|
|
359
|
+
if (!targetStat.isDirectory() && !targetStat.isFile()) {
|
|
360
|
+
diagnostics.push({
|
|
361
|
+
type: "error",
|
|
362
|
+
message: `resources.${kind} contains a symbolic link to an unsupported filesystem entry`,
|
|
363
|
+
path: candidate
|
|
364
|
+
});
|
|
365
|
+
} else if (targetStat.isFile() && targetStat.nlink !== 1) {
|
|
366
|
+
diagnostics.push({
|
|
367
|
+
type: "error",
|
|
368
|
+
message: `resources.${kind} contains a symbolic link to a hard-linked file`,
|
|
369
|
+
path: candidate
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
diagnostics.push({
|
|
374
|
+
type: "error",
|
|
375
|
+
message: `resources.${kind} contains an unresolved symbolic link: ${error instanceof Error ? error.message : String(error)}`,
|
|
376
|
+
path: candidate
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
} else if (entry.isDirectory()) {
|
|
380
|
+
pending.push(candidate);
|
|
381
|
+
} else if (entry.isFile()) {
|
|
382
|
+
try {
|
|
383
|
+
if (lstatSync(candidate).nlink !== 1) {
|
|
384
|
+
diagnostics.push({
|
|
385
|
+
type: "error",
|
|
386
|
+
message: `resources.${kind} contains a hard-linked file`,
|
|
387
|
+
path: candidate
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
diagnostics.push({
|
|
392
|
+
type: "error",
|
|
393
|
+
message: `resources.${kind} entry could not be inspected: ${error instanceof Error ? error.message : String(error)}`,
|
|
394
|
+
path: candidate
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
diagnostics.push({
|
|
399
|
+
type: "error",
|
|
400
|
+
message: `resources.${kind} contains an unsupported filesystem entry`,
|
|
401
|
+
path: candidate
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function validateResourceRoots(root, resources, diagnostics) {
|
|
408
|
+
for (const [kind, resourcePath] of Object.entries(resources)) {
|
|
409
|
+
if (resourcePath) validateResourceTree(root, resourcePath, kind, diagnostics);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function stringArray(value, field, manifestPath, diagnostics) {
|
|
413
|
+
if (value === void 0) return void 0;
|
|
414
|
+
if (!Array.isArray(value)) {
|
|
415
|
+
diagnostics.push({ type: "error", message: `${field} must be an array of non-empty strings`, path: manifestPath });
|
|
416
|
+
return void 0;
|
|
417
|
+
}
|
|
418
|
+
const out = [];
|
|
419
|
+
const seen = /* @__PURE__ */ new Set();
|
|
420
|
+
for (const entry of value) {
|
|
421
|
+
const parsed = trimString(entry);
|
|
422
|
+
if (!parsed) {
|
|
423
|
+
diagnostics.push({ type: "error", message: `${field} must contain only non-empty strings`, path: manifestPath });
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (seen.has(parsed)) {
|
|
427
|
+
diagnostics.push({ type: "error", message: `${field} contains duplicate entry '${parsed}'`, path: manifestPath });
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
seen.add(parsed);
|
|
431
|
+
out.push(parsed);
|
|
432
|
+
}
|
|
433
|
+
return out.length > 0 ? out : void 0;
|
|
434
|
+
}
|
|
435
|
+
function parseExtensionManifest(value, manifestPath) {
|
|
436
|
+
const diagnostics = [];
|
|
437
|
+
if (!isRecord(value)) {
|
|
438
|
+
return { diagnostics: [{ type: "error", message: "extension manifest must be an object", path: manifestPath }] };
|
|
439
|
+
}
|
|
440
|
+
rejectUnknownKeys(value, MANIFEST_KEYS, "manifest", manifestPath, diagnostics);
|
|
441
|
+
if (value.manifestVersion !== 1) {
|
|
442
|
+
diagnostics.push({ type: "error", message: "manifestVersion must be 1", path: manifestPath });
|
|
443
|
+
}
|
|
444
|
+
const id = trimString(value.id);
|
|
445
|
+
const name = trimString(value.name) ?? id;
|
|
446
|
+
const version = trimString(value.version);
|
|
447
|
+
const description = trimString(value.description);
|
|
448
|
+
if (!id) diagnostics.push({ type: "error", message: "id is required", path: manifestPath });
|
|
449
|
+
else {
|
|
450
|
+
const idError = validateId(id);
|
|
451
|
+
if (idError) diagnostics.push({ type: "error", message: idError, path: manifestPath });
|
|
452
|
+
}
|
|
453
|
+
if (!version) diagnostics.push({ type: "error", message: "version is required", path: manifestPath });
|
|
454
|
+
if (!description) diagnostics.push({ type: "error", message: "description is required", path: manifestPath });
|
|
455
|
+
const resources = normalizeResources(value.resources, manifestPath, diagnostics);
|
|
456
|
+
const tools = stringArray(value.tools, "tools", manifestPath, diagnostics);
|
|
457
|
+
const settings = stringArray(value.settings, "settings", manifestPath, diagnostics);
|
|
458
|
+
let compatibility;
|
|
459
|
+
if (value.compatibility !== void 0) {
|
|
460
|
+
if (!isRecord(value.compatibility)) {
|
|
461
|
+
diagnostics.push({ type: "error", message: "compatibility must be an object", path: manifestPath });
|
|
462
|
+
} else {
|
|
463
|
+
rejectUnknownKeys(value.compatibility, COMPATIBILITY_KEYS, "compatibility", manifestPath, diagnostics);
|
|
464
|
+
if (!("clio" in value.compatibility)) {
|
|
465
|
+
compatibility = {};
|
|
466
|
+
} else {
|
|
467
|
+
const range = trimString(value.compatibility.clio);
|
|
468
|
+
if (range === void 0) {
|
|
469
|
+
diagnostics.push({
|
|
470
|
+
type: "error",
|
|
471
|
+
message: "compatibility.clio must be a non-empty semver range",
|
|
472
|
+
path: manifestPath
|
|
473
|
+
});
|
|
474
|
+
} else {
|
|
475
|
+
const evaluation = evaluateClioCompatibility(range);
|
|
476
|
+
if (!evaluation.rangeValid) {
|
|
477
|
+
diagnostics.push({
|
|
478
|
+
type: "error",
|
|
479
|
+
message: `extension ${id ?? "<unknown>"} declares malformed compatibility.clio range '${range}'`,
|
|
480
|
+
path: manifestPath
|
|
481
|
+
});
|
|
482
|
+
} else {
|
|
483
|
+
compatibility = { clio: range };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (!id || !name || !version || !description || diagnostics.some((diag) => diag.type === "error")) {
|
|
490
|
+
return { diagnostics };
|
|
491
|
+
}
|
|
492
|
+
const manifest = {
|
|
493
|
+
manifestVersion: 1,
|
|
494
|
+
id,
|
|
495
|
+
name,
|
|
496
|
+
version,
|
|
497
|
+
description,
|
|
498
|
+
resources
|
|
499
|
+
};
|
|
500
|
+
if (tools) manifest.tools = tools;
|
|
501
|
+
if (settings) manifest.settings = settings;
|
|
502
|
+
if (compatibility && Object.keys(compatibility).length > 0) manifest.compatibility = compatibility;
|
|
503
|
+
const clioRange = manifest.compatibility?.clio;
|
|
504
|
+
if (clioRange !== void 0) {
|
|
505
|
+
const evaluation = evaluateClioCompatibility(clioRange);
|
|
506
|
+
if (!evaluation.satisfied) {
|
|
507
|
+
diagnostics.push({
|
|
508
|
+
type: "error",
|
|
509
|
+
message: `extension ${manifest.id} requires Clio '${clioRange}', but running Clio version is '${evaluation.runningVersion}'`,
|
|
510
|
+
path: manifestPath
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return { manifest, diagnostics };
|
|
515
|
+
}
|
|
516
|
+
function findExtensionManifestPath(root) {
|
|
517
|
+
for (const name of MANIFEST_NAMES) {
|
|
518
|
+
const candidate = path.join(root, name);
|
|
519
|
+
try {
|
|
520
|
+
if (statSync(candidate).isFile()) return candidate;
|
|
521
|
+
} catch {
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
function loadManifestFromRoot(root) {
|
|
527
|
+
const manifestPath = findExtensionManifestPath(root);
|
|
528
|
+
if (!manifestPath) {
|
|
529
|
+
return {
|
|
530
|
+
path: root,
|
|
531
|
+
valid: false,
|
|
532
|
+
diagnostics: [{ type: "error", message: "extension manifest not found", path: root }]
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const parsed = parseExtensionManifest(readJsonOrYaml(manifestPath), manifestPath);
|
|
537
|
+
if (parsed.manifest) validateResourceRoots(root, parsed.manifest.resources, parsed.diagnostics);
|
|
538
|
+
return {
|
|
539
|
+
path: root,
|
|
540
|
+
manifestPath,
|
|
541
|
+
...parsed.manifest ? { manifest: parsed.manifest } : {},
|
|
542
|
+
valid: parsed.manifest !== void 0 && !parsed.diagnostics.some((diag) => diag.type === "error"),
|
|
543
|
+
diagnostics: parsed.diagnostics
|
|
544
|
+
};
|
|
545
|
+
} catch (err) {
|
|
546
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
547
|
+
return {
|
|
548
|
+
path: root,
|
|
549
|
+
manifestPath,
|
|
550
|
+
valid: false,
|
|
551
|
+
diagnostics: [{ type: "error", message: `extension manifest could not be read: ${reason}`, path: manifestPath }]
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function discoverExtensionPackages(root) {
|
|
556
|
+
const full = path.resolve(root);
|
|
557
|
+
if (!existsSync(full)) {
|
|
558
|
+
return [{ path: full, valid: false, diagnostics: [{ type: "error", message: "path does not exist", path: full }] }];
|
|
559
|
+
}
|
|
560
|
+
let stat;
|
|
561
|
+
try {
|
|
562
|
+
stat = statSync(full);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
return [
|
|
565
|
+
{
|
|
566
|
+
path: full,
|
|
567
|
+
valid: false,
|
|
568
|
+
diagnostics: [
|
|
569
|
+
{
|
|
570
|
+
type: "error",
|
|
571
|
+
message: `extension path could not be inspected: ${error instanceof Error ? error.message : String(error)}`,
|
|
572
|
+
path: full
|
|
573
|
+
}
|
|
574
|
+
]
|
|
575
|
+
}
|
|
576
|
+
];
|
|
577
|
+
}
|
|
578
|
+
if (!stat.isDirectory()) {
|
|
579
|
+
return [
|
|
580
|
+
{
|
|
581
|
+
path: full,
|
|
582
|
+
valid: false,
|
|
583
|
+
diagnostics: [{ type: "error", message: "extension path is not a directory", path: full }]
|
|
584
|
+
}
|
|
585
|
+
];
|
|
586
|
+
}
|
|
587
|
+
let canonicalFull;
|
|
588
|
+
try {
|
|
589
|
+
canonicalFull = realpathSync(full);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
return [
|
|
592
|
+
{
|
|
593
|
+
path: full,
|
|
594
|
+
valid: false,
|
|
595
|
+
diagnostics: [
|
|
596
|
+
{
|
|
597
|
+
type: "error",
|
|
598
|
+
message: `extension path could not be canonicalized: ${error instanceof Error ? error.message : String(error)}`,
|
|
599
|
+
path: full
|
|
600
|
+
}
|
|
601
|
+
]
|
|
602
|
+
}
|
|
603
|
+
];
|
|
604
|
+
}
|
|
605
|
+
const direct = loadManifestFromRoot(canonicalFull);
|
|
606
|
+
if (direct.valid || direct.manifestPath) return [direct];
|
|
607
|
+
const candidates = [];
|
|
608
|
+
const discoveryFailures = [];
|
|
609
|
+
const spellingsByCanonicalRoot = /* @__PURE__ */ new Map();
|
|
610
|
+
let entries;
|
|
611
|
+
try {
|
|
612
|
+
entries = readdirSync(canonicalFull, { withFileTypes: true, encoding: "utf8" });
|
|
613
|
+
} catch (error) {
|
|
614
|
+
return [
|
|
615
|
+
{
|
|
616
|
+
path: canonicalFull,
|
|
617
|
+
valid: false,
|
|
618
|
+
diagnostics: [
|
|
619
|
+
{
|
|
620
|
+
type: "error",
|
|
621
|
+
message: `extension directory could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
622
|
+
path: canonicalFull
|
|
623
|
+
}
|
|
624
|
+
]
|
|
625
|
+
}
|
|
626
|
+
];
|
|
627
|
+
}
|
|
628
|
+
for (const entry of entries.sort(compareNames)) {
|
|
629
|
+
if (entry.name.startsWith(".")) continue;
|
|
630
|
+
const child = path.join(canonicalFull, entry.name);
|
|
631
|
+
try {
|
|
632
|
+
if (!entry.isDirectory() && !(entry.isSymbolicLink() && statSync(child).isDirectory())) continue;
|
|
633
|
+
const canonicalChild = realpathSync(child);
|
|
634
|
+
const spellings = spellingsByCanonicalRoot.get(canonicalChild) ?? [];
|
|
635
|
+
spellings.push(child);
|
|
636
|
+
spellingsByCanonicalRoot.set(canonicalChild, spellings);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
discoveryFailures.push({
|
|
639
|
+
path: child,
|
|
640
|
+
valid: false,
|
|
641
|
+
diagnostics: [
|
|
642
|
+
{
|
|
643
|
+
type: "error",
|
|
644
|
+
message: `extension child could not be canonicalized: ${error instanceof Error ? error.message : String(error)}`,
|
|
645
|
+
path: child
|
|
646
|
+
}
|
|
647
|
+
]
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
for (const canonicalChild of [...spellingsByCanonicalRoot.keys()].sort()) {
|
|
652
|
+
const loaded = loadManifestFromRoot(canonicalChild);
|
|
653
|
+
const spellings = spellingsByCanonicalRoot.get(canonicalChild) ?? [];
|
|
654
|
+
if (spellings.length > 1) {
|
|
655
|
+
loaded.diagnostics.push({
|
|
656
|
+
type: "warning",
|
|
657
|
+
message: `duplicate canonical extension root loaded once; discovered as ${spellings.sort().join(", ")}`,
|
|
658
|
+
path: canonicalChild
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
if (loaded.valid || loaded.manifestPath) candidates.push(loaded);
|
|
662
|
+
}
|
|
663
|
+
const discovered = [...candidates, ...discoveryFailures].sort(
|
|
664
|
+
(a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0
|
|
665
|
+
);
|
|
666
|
+
if (discovered.length === 0) discovered.push(direct);
|
|
667
|
+
const ids = /* @__PURE__ */ new Map();
|
|
668
|
+
for (const candidate of discovered) {
|
|
669
|
+
const id = candidate.manifest?.id;
|
|
670
|
+
if (!id) continue;
|
|
671
|
+
const group = ids.get(id) ?? [];
|
|
672
|
+
group.push(candidate);
|
|
673
|
+
ids.set(id, group);
|
|
674
|
+
}
|
|
675
|
+
for (const [id, group] of ids) {
|
|
676
|
+
if (group.length < 2) continue;
|
|
677
|
+
for (const candidate of group) {
|
|
678
|
+
candidate.valid = false;
|
|
679
|
+
candidate.diagnostics.push({
|
|
680
|
+
type: "error",
|
|
681
|
+
message: `duplicate extension id ${id}`,
|
|
682
|
+
path: candidate.manifestPath ?? candidate.path
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return discovered;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/domains/extensions/state.ts
|
|
690
|
+
init_esm_shims();
|
|
691
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
692
|
+
import {
|
|
693
|
+
cpSync,
|
|
694
|
+
existsSync as existsSync2,
|
|
695
|
+
lstatSync as lstatSync3,
|
|
696
|
+
mkdirSync,
|
|
697
|
+
readdirSync as readdirSync3,
|
|
698
|
+
readFileSync as readFileSync3,
|
|
699
|
+
realpathSync as realpathSync3,
|
|
700
|
+
renameSync,
|
|
701
|
+
rmSync
|
|
702
|
+
} from "node:fs";
|
|
703
|
+
import path3 from "node:path";
|
|
704
|
+
|
|
705
|
+
// src/domains/extensions/integrity.ts
|
|
706
|
+
init_esm_shims();
|
|
707
|
+
import { createHash } from "node:crypto";
|
|
708
|
+
import {
|
|
709
|
+
closeSync,
|
|
710
|
+
constants,
|
|
711
|
+
fstatSync,
|
|
712
|
+
lstatSync as lstatSync2,
|
|
713
|
+
openSync,
|
|
714
|
+
readdirSync as readdirSync2,
|
|
715
|
+
readFileSync as readFileSync2,
|
|
716
|
+
readlinkSync,
|
|
717
|
+
realpathSync as realpathSync2
|
|
718
|
+
} from "node:fs";
|
|
719
|
+
import path2 from "node:path";
|
|
720
|
+
function contained2(root, candidate) {
|
|
721
|
+
const relative = path2.relative(root, candidate);
|
|
722
|
+
return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
|
|
723
|
+
}
|
|
724
|
+
function digestFrame(hash, kind, relativePath, payload) {
|
|
725
|
+
const header = Buffer.from(
|
|
726
|
+
`${kind}\0${relativePath.replaceAll(path2.sep, "/")}\0${payload?.byteLength ?? 0}\0`,
|
|
727
|
+
"utf8"
|
|
728
|
+
);
|
|
729
|
+
hash.update(header);
|
|
730
|
+
if (payload) hash.update(payload);
|
|
731
|
+
hash.update("\0");
|
|
732
|
+
}
|
|
733
|
+
function sameFileState(left, right) {
|
|
734
|
+
return left.dev === right.dev && left.ino === right.ino && left.mode === right.mode && left.nlink === right.nlink && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
|
|
735
|
+
}
|
|
736
|
+
function readStableRegularFile(filePath, inspected) {
|
|
737
|
+
const fd = openSync(filePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
738
|
+
try {
|
|
739
|
+
const opened = fstatSync(fd);
|
|
740
|
+
if (!opened.isFile() || opened.nlink !== 1 || !sameFileState(inspected, opened)) {
|
|
741
|
+
throw new Error(`extension file changed while being opened: ${filePath}`);
|
|
742
|
+
}
|
|
743
|
+
const payload = readFileSync2(fd);
|
|
744
|
+
if (!sameFileState(opened, fstatSync(fd))) {
|
|
745
|
+
throw new Error(`extension file changed while being hashed: ${filePath}`);
|
|
746
|
+
}
|
|
747
|
+
return payload;
|
|
748
|
+
} finally {
|
|
749
|
+
closeSync(fd);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
function readStableContainedLink(linkPath, inspected, canonicalRoot) {
|
|
753
|
+
const canonicalTarget = realpathSync2(linkPath);
|
|
754
|
+
if (!contained2(canonicalRoot, canonicalTarget)) {
|
|
755
|
+
throw new Error(`symbolic link escapes the extension root: ${linkPath}`);
|
|
756
|
+
}
|
|
757
|
+
const target = readlinkSync(linkPath);
|
|
758
|
+
const after = lstatSync2(linkPath);
|
|
759
|
+
if (!after.isSymbolicLink() || !sameFileState(inspected, after)) {
|
|
760
|
+
throw new Error(`symbolic link changed while being hashed: ${linkPath}`);
|
|
761
|
+
}
|
|
762
|
+
const canonicalAfter = realpathSync2(linkPath);
|
|
763
|
+
if (canonicalAfter !== canonicalTarget || !contained2(canonicalRoot, canonicalAfter)) {
|
|
764
|
+
throw new Error(`symbolic link changed or escapes the extension root: ${linkPath}`);
|
|
765
|
+
}
|
|
766
|
+
return Buffer.from(target, "utf8");
|
|
767
|
+
}
|
|
768
|
+
function assertStableDirectory(directoryPath, inspected, canonicalDirectory, canonicalRoot, expectedEntries) {
|
|
769
|
+
const after = lstatSync2(directoryPath);
|
|
770
|
+
if (!after.isDirectory() || !sameFileState(inspected, after)) {
|
|
771
|
+
throw new Error(`extension directory changed while being hashed: ${directoryPath}`);
|
|
772
|
+
}
|
|
773
|
+
const canonicalAfter = realpathSync2(directoryPath);
|
|
774
|
+
if (canonicalAfter !== canonicalDirectory || !contained2(canonicalRoot, canonicalAfter)) {
|
|
775
|
+
throw new Error(`extension directory changed or escapes the extension root: ${directoryPath}`);
|
|
776
|
+
}
|
|
777
|
+
if (expectedEntries !== void 0) {
|
|
778
|
+
const currentEntries = readdirSync2(directoryPath).sort();
|
|
779
|
+
if (currentEntries.length !== expectedEntries.length || currentEntries.some((entry, index) => entry !== expectedEntries[index])) {
|
|
780
|
+
throw new Error(`extension directory entries changed while being hashed: ${directoryPath}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function readStableDirectory(directoryPath, inspected, canonicalRoot) {
|
|
785
|
+
const canonicalDirectory = realpathSync2(directoryPath);
|
|
786
|
+
if (!contained2(canonicalRoot, canonicalDirectory)) {
|
|
787
|
+
throw new Error(`extension directory escapes the extension root: ${directoryPath}`);
|
|
788
|
+
}
|
|
789
|
+
const entries = readdirSync2(directoryPath).sort();
|
|
790
|
+
assertStableDirectory(directoryPath, inspected, canonicalDirectory, canonicalRoot);
|
|
791
|
+
return { canonicalDirectory, entries };
|
|
792
|
+
}
|
|
793
|
+
function extensionContentDigestWithCapture(root, options = {}) {
|
|
794
|
+
const resolvedRoot = path2.resolve(root);
|
|
795
|
+
const rootStat = lstatSync2(resolvedRoot);
|
|
796
|
+
if (rootStat.isSymbolicLink()) {
|
|
797
|
+
throw new Error(`extension root must be a directory, not a symbolic link: ${resolvedRoot}`);
|
|
798
|
+
}
|
|
799
|
+
if (!rootStat.isDirectory()) {
|
|
800
|
+
throw new Error(`extension root must be a directory: ${resolvedRoot}`);
|
|
801
|
+
}
|
|
802
|
+
const canonicalRoot = realpathSync2(resolvedRoot);
|
|
803
|
+
const canonicalizedRootStat = lstatSync2(resolvedRoot);
|
|
804
|
+
if (!canonicalizedRootStat.isDirectory() || !sameFileState(rootStat, canonicalizedRootStat)) {
|
|
805
|
+
throw new Error(`extension root changed while being canonicalized: ${resolvedRoot}`);
|
|
806
|
+
}
|
|
807
|
+
const hash = createHash("sha256");
|
|
808
|
+
const requested = new Set((options.capture ?? []).map((entry) => entry.replaceAll(path2.sep, "/")));
|
|
809
|
+
const captured = /* @__PURE__ */ new Map();
|
|
810
|
+
const visit = (absolutePath, relativePath) => {
|
|
811
|
+
const stat = lstatSync2(absolutePath);
|
|
812
|
+
if (stat.isSymbolicLink()) {
|
|
813
|
+
if (relativePath === "") {
|
|
814
|
+
throw new Error(`extension root changed to a symbolic link while being hashed: ${absolutePath}`);
|
|
815
|
+
}
|
|
816
|
+
digestFrame(hash, "link", relativePath, readStableContainedLink(absolutePath, stat, canonicalRoot));
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
if (stat.isDirectory()) {
|
|
820
|
+
digestFrame(hash, "directory", relativePath);
|
|
821
|
+
const { canonicalDirectory, entries } = readStableDirectory(absolutePath, stat, canonicalRoot);
|
|
822
|
+
for (const entry of entries) {
|
|
823
|
+
visit(path2.join(absolutePath, entry), relativePath ? path2.join(relativePath, entry) : entry);
|
|
824
|
+
}
|
|
825
|
+
assertStableDirectory(absolutePath, stat, canonicalDirectory, canonicalRoot, entries);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (stat.isFile()) {
|
|
829
|
+
if (stat.nlink !== 1) {
|
|
830
|
+
throw new Error(`hard-linked file is unsupported in an extension: ${absolutePath}`);
|
|
831
|
+
}
|
|
832
|
+
const payload = readStableRegularFile(absolutePath, stat);
|
|
833
|
+
digestFrame(hash, "file", relativePath, payload);
|
|
834
|
+
const key = relativePath.replaceAll(path2.sep, "/");
|
|
835
|
+
if (requested.has(key)) captured.set(key, payload);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
throw new Error(`unsupported filesystem entry in extension: ${absolutePath}`);
|
|
839
|
+
};
|
|
840
|
+
visit(resolvedRoot, "");
|
|
841
|
+
return { digest: hash.digest("hex"), captured };
|
|
842
|
+
}
|
|
843
|
+
function extensionContentDigest(root) {
|
|
844
|
+
return extensionContentDigestWithCapture(root).digest;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/domains/extensions/state.ts
|
|
848
|
+
var DEFAULT_STATE = { version: 1, disabled: [], installed: {} };
|
|
849
|
+
function extensionBaseDir(scope, cwd = process.cwd()) {
|
|
850
|
+
return scope === "user" ? path3.join(clioConfigDir(), "extensions") : path3.join(path3.resolve(cwd), ".clio-coder", "extensions");
|
|
851
|
+
}
|
|
852
|
+
function statePath(scope, cwd = process.cwd()) {
|
|
853
|
+
return path3.join(extensionBaseDir(scope, cwd), "state.json");
|
|
854
|
+
}
|
|
855
|
+
function recoveryPath(filePath, label) {
|
|
856
|
+
return `${filePath}.${label}-${process.pid}-${Date.now()}.bak`;
|
|
857
|
+
}
|
|
858
|
+
function packageRecoveryPath(root, label) {
|
|
859
|
+
return path3.join(path3.dirname(root), `.${path3.basename(root)}.${label}-${process.pid}-${Date.now()}.bak`);
|
|
860
|
+
}
|
|
861
|
+
function preserveFile(filePath, label) {
|
|
862
|
+
if (!existsSync2(filePath)) return void 0;
|
|
863
|
+
const backupPath = recoveryPath(filePath, label);
|
|
864
|
+
safeResourceWrite(backupPath, readFileSync3(filePath));
|
|
865
|
+
return backupPath;
|
|
866
|
+
}
|
|
867
|
+
function scopeRank(scope) {
|
|
868
|
+
return scope === "project" ? 2 : 1;
|
|
869
|
+
}
|
|
870
|
+
function readState(scope, cwd = process.cwd()) {
|
|
871
|
+
const filePath = statePath(scope, cwd);
|
|
872
|
+
if (!existsSync2(filePath)) return { status: "absent", state: structuredClone(DEFAULT_STATE) };
|
|
873
|
+
try {
|
|
874
|
+
const parsed = JSON.parse(readFileSync3(filePath, "utf8"));
|
|
875
|
+
if (!isRecord(parsed) || parsed.version !== 1) throw new Error("state must be a version 1 object");
|
|
876
|
+
if (!Array.isArray(parsed.disabled) || !parsed.disabled.every((entry) => typeof entry === "string")) {
|
|
877
|
+
throw new Error("state.disabled must be an array of strings");
|
|
878
|
+
}
|
|
879
|
+
if (!isRecord(parsed.installed)) throw new Error("state.installed must be an object");
|
|
880
|
+
const installed = {};
|
|
881
|
+
for (const [id, raw] of Object.entries(parsed.installed)) {
|
|
882
|
+
if (!isRecord(raw)) throw new Error(`state.installed.${id} must be an object`);
|
|
883
|
+
const installedAt = trimString(raw.installedAt);
|
|
884
|
+
if (!installedAt) throw new Error(`state.installed.${id}.installedAt must be a non-empty string`);
|
|
885
|
+
const source = raw.source === void 0 ? void 0 : trimString(raw.source);
|
|
886
|
+
if (raw.source !== void 0 && !source) {
|
|
887
|
+
throw new Error(`state.installed.${id}.source must be a non-empty string`);
|
|
888
|
+
}
|
|
889
|
+
const contentDigest = raw.contentDigest === void 0 ? void 0 : trimString(raw.contentDigest);
|
|
890
|
+
if (raw.contentDigest !== void 0 && (!contentDigest || !/^[a-f0-9]{64}$/u.test(contentDigest))) {
|
|
891
|
+
throw new Error(`state.installed.${id}.contentDigest must be a SHA-256 digest`);
|
|
892
|
+
}
|
|
893
|
+
installed[id] = {
|
|
894
|
+
installedAt,
|
|
895
|
+
...source ? { source } : {},
|
|
896
|
+
...contentDigest ? { contentDigest } : {}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
return { status: "valid", state: { version: 1, disabled: [...parsed.disabled], installed } };
|
|
900
|
+
} catch (error) {
|
|
901
|
+
return {
|
|
902
|
+
status: "corrupt",
|
|
903
|
+
state: structuredClone(DEFAULT_STATE),
|
|
904
|
+
message: error instanceof Error ? error.message : String(error)
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function writeState(scope, state, cwd = process.cwd()) {
|
|
909
|
+
const filePath = statePath(scope, cwd);
|
|
910
|
+
safeResourceWrite(filePath, `${JSON.stringify(state, null, 2)}
|
|
911
|
+
`, { encoding: "utf8" });
|
|
912
|
+
}
|
|
913
|
+
function installedFromRoot(root, scope, stateResult, cwd, fallbackId) {
|
|
914
|
+
const candidate = loadManifestFromRoot(root);
|
|
915
|
+
const manifest = candidate.manifest;
|
|
916
|
+
const diagnostics = [...candidate.diagnostics];
|
|
917
|
+
const id = manifest?.id ?? fallbackId;
|
|
918
|
+
const provenance = stateResult.state.installed[id];
|
|
919
|
+
const expectedDigest = provenance?.contentDigest;
|
|
920
|
+
let observedDigest;
|
|
921
|
+
let captured;
|
|
922
|
+
let contentVerified = false;
|
|
923
|
+
if (stateResult.status === "corrupt") {
|
|
924
|
+
diagnostics.push({
|
|
925
|
+
type: "error",
|
|
926
|
+
message: `extension install state is corrupt: ${stateResult.message}; reinstall with --force or remove the package to preserve the corrupt bytes and recover`,
|
|
927
|
+
path: statePath(scope, cwd)
|
|
928
|
+
});
|
|
929
|
+
} else if (stateResult.status === "absent") {
|
|
930
|
+
diagnostics.push({
|
|
931
|
+
type: "error",
|
|
932
|
+
message: "extension install state is absent; installed content cannot be verified; reinstall with --force",
|
|
933
|
+
path: statePath(scope, cwd)
|
|
934
|
+
});
|
|
935
|
+
} else if (!provenance) {
|
|
936
|
+
diagnostics.push({
|
|
937
|
+
type: "error",
|
|
938
|
+
message: `extension ${id} is not recorded in install state; reinstall with --force`,
|
|
939
|
+
path: statePath(scope, cwd)
|
|
940
|
+
});
|
|
941
|
+
} else if (!expectedDigest) {
|
|
942
|
+
diagnostics.push({
|
|
943
|
+
type: "error",
|
|
944
|
+
message: `extension ${id} install provenance has no content digest; run clio-coder upgrade, then reinstall with --force if it remains unverifiable`,
|
|
945
|
+
path: statePath(scope, cwd)
|
|
946
|
+
});
|
|
947
|
+
} else {
|
|
948
|
+
try {
|
|
949
|
+
const manifestName2 = candidate.manifestPath ? path3.basename(candidate.manifestPath) : void 0;
|
|
950
|
+
const digestResult = extensionContentDigestWithCapture(root, {
|
|
951
|
+
capture: [...manifestName2 ? [manifestName2] : [], "hooks.yaml"]
|
|
952
|
+
});
|
|
953
|
+
observedDigest = digestResult.digest;
|
|
954
|
+
contentVerified = observedDigest === expectedDigest;
|
|
955
|
+
if (contentVerified) captured = digestResult.captured;
|
|
956
|
+
if (!contentVerified) {
|
|
957
|
+
diagnostics.push({
|
|
958
|
+
type: "error",
|
|
959
|
+
message: `installed extension content drift detected (expected ${expectedDigest}, observed ${observedDigest})`,
|
|
960
|
+
path: root
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
} catch (error) {
|
|
964
|
+
diagnostics.push({
|
|
965
|
+
type: "error",
|
|
966
|
+
message: `installed extension content could not be verified: ${error instanceof Error ? error.message : String(error)}`,
|
|
967
|
+
path: root
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
const clioRange = manifest?.compatibility?.clio;
|
|
972
|
+
const compatible = clioRange === void 0 || evaluateClioCompatibility(clioRange).satisfied;
|
|
973
|
+
const manifestName = candidate.manifestPath ? path3.basename(candidate.manifestPath) : void 0;
|
|
974
|
+
const manifestBytes = manifestName ? captured?.get(manifestName) : void 0;
|
|
975
|
+
const extensionProvenance = contentVerified && expectedDigest && manifestBytes ? {
|
|
976
|
+
id,
|
|
977
|
+
scope,
|
|
978
|
+
...provenance?.source ? { sourcePath: provenance.source } : {},
|
|
979
|
+
canonicalRoot: realpathSync3(root),
|
|
980
|
+
manifestDigest: createHash2("sha256").update(manifestBytes).digest("hex"),
|
|
981
|
+
contentDigest: expectedDigest
|
|
982
|
+
} : void 0;
|
|
983
|
+
const entry = {
|
|
984
|
+
id,
|
|
985
|
+
name: manifest?.name ?? id,
|
|
986
|
+
version: manifest?.version ?? "unknown",
|
|
987
|
+
description: manifest?.description ?? "Installed extension has an invalid manifest.",
|
|
988
|
+
scope,
|
|
989
|
+
rootPath: root,
|
|
990
|
+
manifestPath: candidate.manifestPath ?? root,
|
|
991
|
+
enabled: !stateResult.state.disabled.includes(id),
|
|
992
|
+
valid: manifest !== void 0 && candidate.valid && contentVerified,
|
|
993
|
+
compatible,
|
|
994
|
+
effective: false,
|
|
995
|
+
loadable: false,
|
|
996
|
+
...extensionProvenance ? { provenance: extensionProvenance } : {},
|
|
997
|
+
...observedDigest ? { observedContentDigest: observedDigest } : {},
|
|
998
|
+
resources: manifest?.resources ?? {},
|
|
999
|
+
diagnostics
|
|
1000
|
+
};
|
|
1001
|
+
return { entry, ...extensionProvenance && captured ? { captured } : {} };
|
|
1002
|
+
}
|
|
1003
|
+
function listScope(scope, cwd = process.cwd()) {
|
|
1004
|
+
const base = extensionBaseDir(scope, cwd);
|
|
1005
|
+
if (!existsSync2(base)) return [];
|
|
1006
|
+
const state = readState(scope, cwd);
|
|
1007
|
+
const out = [];
|
|
1008
|
+
for (const entry of readdirSync3(base, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1009
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1010
|
+
const root = path3.join(base, entry.name);
|
|
1011
|
+
const installed = installedFromRoot(root, scope, state, cwd, entry.name);
|
|
1012
|
+
if (installed) out.push(installed);
|
|
1013
|
+
}
|
|
1014
|
+
return out;
|
|
1015
|
+
}
|
|
1016
|
+
function upgradeLegacyExtensionInstallState(cwd = process.cwd(), scopes = ["user", "project"]) {
|
|
1017
|
+
const reports = [];
|
|
1018
|
+
for (const scope of scopes) {
|
|
1019
|
+
const filePath = statePath(scope, cwd);
|
|
1020
|
+
const report = { scope, statePath: filePath, upgraded: [], refused: [] };
|
|
1021
|
+
const stateResult = readState(scope, cwd);
|
|
1022
|
+
if (stateResult.status === "absent") {
|
|
1023
|
+
reports.push(report);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (stateResult.status === "corrupt") {
|
|
1027
|
+
report.refused.push({ id: "<state>", reason: `install state is corrupt: ${stateResult.message}` });
|
|
1028
|
+
reports.push(report);
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const next = structuredClone(stateResult.state);
|
|
1032
|
+
const verified = /* @__PURE__ */ new Map();
|
|
1033
|
+
for (const [id, record] of Object.entries(next.installed)) {
|
|
1034
|
+
if (record.contentDigest) continue;
|
|
1035
|
+
const root = path3.join(extensionBaseDir(scope, cwd), id);
|
|
1036
|
+
try {
|
|
1037
|
+
if (!lstatSync3(root).isDirectory()) throw new Error("installed path is not a directory");
|
|
1038
|
+
const candidate = loadManifestFromRoot(root);
|
|
1039
|
+
if (!candidate.valid || !candidate.manifest) {
|
|
1040
|
+
throw new Error(candidate.diagnostics.map((diagnostic) => diagnostic.message).join("; ") || "manifest is invalid");
|
|
1041
|
+
}
|
|
1042
|
+
if (candidate.manifest.id !== id) {
|
|
1043
|
+
throw new Error(`manifest id ${candidate.manifest.id} does not match install record ${id}`);
|
|
1044
|
+
}
|
|
1045
|
+
const digest = extensionContentDigest(root);
|
|
1046
|
+
record.contentDigest = digest;
|
|
1047
|
+
verified.set(id, { root, digest });
|
|
1048
|
+
report.upgraded.push(id);
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
report.refused.push({ id, reason: error instanceof Error ? error.message : String(error) });
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (report.upgraded.length > 0) {
|
|
1054
|
+
const backupPath = `${filePath}.pre-digest.bak`;
|
|
1055
|
+
safeResourceWrite(filePath, `${JSON.stringify(next, null, 2)}
|
|
1056
|
+
`, {
|
|
1057
|
+
encoding: "utf8",
|
|
1058
|
+
backup: { path: backupPath },
|
|
1059
|
+
beforeRename: () => {
|
|
1060
|
+
for (const [id, expected] of verified) {
|
|
1061
|
+
const candidate = loadManifestFromRoot(expected.root);
|
|
1062
|
+
if (!candidate.valid || candidate.manifest?.id !== id) {
|
|
1063
|
+
throw new Error(`extension ${id} became invalid during digest migration`);
|
|
1064
|
+
}
|
|
1065
|
+
const observed = extensionContentDigest(expected.root);
|
|
1066
|
+
if (observed !== expected.digest) throw new Error(`extension ${id} changed during digest migration`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
report.backupPath = backupPath;
|
|
1071
|
+
}
|
|
1072
|
+
reports.push(report);
|
|
1073
|
+
}
|
|
1074
|
+
return reports;
|
|
1075
|
+
}
|
|
1076
|
+
function listInstalledExtensions(cwd = process.cwd(), options = {}) {
|
|
1077
|
+
return listInstalledExtensionRecords(cwd, options).map((record) => record.entry);
|
|
1078
|
+
}
|
|
1079
|
+
function listInstalledExtensionRecords(cwd = process.cwd(), options = {}) {
|
|
1080
|
+
const scopes = options.scope ? [options.scope] : ["user", "project"];
|
|
1081
|
+
const records = scopes.flatMap((scope) => listScope(scope, cwd));
|
|
1082
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1083
|
+
for (const record of records) {
|
|
1084
|
+
const entry = record.entry;
|
|
1085
|
+
const list = byId.get(entry.id) ?? [];
|
|
1086
|
+
list.push(record);
|
|
1087
|
+
byId.set(entry.id, list);
|
|
1088
|
+
}
|
|
1089
|
+
for (const group of byId.values()) {
|
|
1090
|
+
const winner = group.filter((record) => record.entry.valid && record.entry.compatible).sort((a, b) => scopeRank(a.entry.scope) - scopeRank(b.entry.scope)).at(-1);
|
|
1091
|
+
for (const record of group) {
|
|
1092
|
+
const entry = record.entry;
|
|
1093
|
+
entry.effective = record === winner;
|
|
1094
|
+
entry.loadable = entry.valid && entry.compatible && entry.enabled && entry.effective;
|
|
1095
|
+
if (entry.valid && entry.compatible && !entry.effective && winner) entry.overriddenBy = winner.entry.scope;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
const all = options.all === true ? records : records.filter(({ entry }) => entry.effective || !entry.valid || !entry.compatible);
|
|
1099
|
+
return all.sort((a, b) => {
|
|
1100
|
+
const id = a.entry.id.localeCompare(b.entry.id);
|
|
1101
|
+
if (id !== 0) return id;
|
|
1102
|
+
return scopeRank(a.entry.scope) - scopeRank(b.entry.scope);
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
function findInstalled(id, cwd, scope) {
|
|
1106
|
+
const entries = listInstalledExtensions(cwd, { ...scope ? { scope } : {}, all: true }).filter(
|
|
1107
|
+
(entry) => entry.id === id
|
|
1108
|
+
);
|
|
1109
|
+
if (entries.length === 0) return null;
|
|
1110
|
+
return [...entries].sort((a, b) => scopeRank(a.scope) - scopeRank(b.scope)).at(-1) ?? null;
|
|
1111
|
+
}
|
|
1112
|
+
function installExtension(sourcePath, options = {}) {
|
|
1113
|
+
const scope = options.scope ?? "user";
|
|
1114
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1115
|
+
const source = path3.resolve(sourcePath);
|
|
1116
|
+
const candidate = loadManifestFromRoot(source);
|
|
1117
|
+
if (!candidate.manifest || !candidate.valid) return { diagnostics: candidate.diagnostics };
|
|
1118
|
+
const targetRoot = path3.join(extensionBaseDir(scope, cwd), candidate.manifest.id);
|
|
1119
|
+
if (existsSync2(targetRoot)) {
|
|
1120
|
+
if (!options.force) {
|
|
1121
|
+
return {
|
|
1122
|
+
diagnostics: [
|
|
1123
|
+
{
|
|
1124
|
+
type: "error",
|
|
1125
|
+
message: `extension ${candidate.manifest.id} is already installed; retry with --force to replace it`,
|
|
1126
|
+
path: targetRoot
|
|
1127
|
+
}
|
|
1128
|
+
]
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
const parent = path3.dirname(targetRoot);
|
|
1133
|
+
const stagingRoot = path3.join(parent, `.${candidate.manifest.id}.install-${process.pid}-${Date.now()}`);
|
|
1134
|
+
const backupRoot = path3.join(parent, `.${candidate.manifest.id}.backup-${process.pid}-${Date.now()}`);
|
|
1135
|
+
let installedReplacement = false;
|
|
1136
|
+
let movedExisting = false;
|
|
1137
|
+
const stateResult = readState(scope, cwd);
|
|
1138
|
+
if (stateResult.status === "corrupt" && !options.force) {
|
|
1139
|
+
return {
|
|
1140
|
+
diagnostics: [
|
|
1141
|
+
{
|
|
1142
|
+
type: "error",
|
|
1143
|
+
message: `extension install state is corrupt: ${stateResult.message}; retry with --force to back it up and reinstall safely`,
|
|
1144
|
+
path: statePath(scope, cwd)
|
|
1145
|
+
}
|
|
1146
|
+
]
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
const state = stateResult.status === "corrupt" ? structuredClone(DEFAULT_STATE) : stateResult.state;
|
|
1150
|
+
let stateBackup;
|
|
1151
|
+
let packageBackup;
|
|
1152
|
+
try {
|
|
1153
|
+
mkdirSync(parent, { recursive: true });
|
|
1154
|
+
if (stateResult.status === "corrupt") stateBackup = preserveFile(statePath(scope, cwd), "corrupt");
|
|
1155
|
+
rmSync(stagingRoot, { recursive: true, force: true });
|
|
1156
|
+
rmSync(backupRoot, { recursive: true, force: true });
|
|
1157
|
+
cpSync(source, stagingRoot, {
|
|
1158
|
+
recursive: true,
|
|
1159
|
+
// `state.json` beside the installed extension directories belongs to
|
|
1160
|
+
// Clio's extension manager. An extension may legitimately ship nested
|
|
1161
|
+
// resources with the same basename (for example a project-state JSON
|
|
1162
|
+
// template), so exclude only a bookkeeping file at the package root.
|
|
1163
|
+
filter: (src) => path3.relative(source, src) !== "state.json"
|
|
1164
|
+
});
|
|
1165
|
+
const stagedCandidate = loadManifestFromRoot(stagingRoot);
|
|
1166
|
+
if (!stagedCandidate.valid || !stagedCandidate.manifest) {
|
|
1167
|
+
const reasons = stagedCandidate.diagnostics.map((diagnostic) => diagnostic.message).join("; ");
|
|
1168
|
+
throw new Error(`staged extension content is invalid${reasons ? `: ${reasons}` : ""}`);
|
|
1169
|
+
}
|
|
1170
|
+
if (stagedCandidate.manifest.id !== candidate.manifest.id) {
|
|
1171
|
+
throw new Error(`staged extension id changed from ${candidate.manifest.id} to ${stagedCandidate.manifest.id}`);
|
|
1172
|
+
}
|
|
1173
|
+
const contentDigest = extensionContentDigest(stagingRoot);
|
|
1174
|
+
if (existsSync2(targetRoot)) {
|
|
1175
|
+
renameSync(targetRoot, backupRoot);
|
|
1176
|
+
movedExisting = true;
|
|
1177
|
+
packageBackup = backupRoot;
|
|
1178
|
+
}
|
|
1179
|
+
renameSync(stagingRoot, targetRoot);
|
|
1180
|
+
installedReplacement = true;
|
|
1181
|
+
state.installed[candidate.manifest.id] = {
|
|
1182
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1183
|
+
source,
|
|
1184
|
+
contentDigest
|
|
1185
|
+
};
|
|
1186
|
+
state.disabled = state.disabled.filter((entry) => entry !== candidate.manifest?.id);
|
|
1187
|
+
writeState(scope, state, cwd);
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
rmSync(stagingRoot, { recursive: true, force: true });
|
|
1190
|
+
if (installedReplacement) rmSync(targetRoot, { recursive: true, force: true });
|
|
1191
|
+
if (movedExisting && existsSync2(backupRoot) && !existsSync2(targetRoot)) {
|
|
1192
|
+
renameSync(backupRoot, targetRoot);
|
|
1193
|
+
packageBackup = void 0;
|
|
1194
|
+
}
|
|
1195
|
+
return {
|
|
1196
|
+
...stateBackup || packageBackup ? { recovery: { ...stateBackup ? { stateBackup } : {}, ...packageBackup ? { packageBackup } : {} } } : {},
|
|
1197
|
+
diagnostics: [
|
|
1198
|
+
{
|
|
1199
|
+
type: "error",
|
|
1200
|
+
message: `extension ${candidate.manifest.id} install failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1201
|
+
path: targetRoot
|
|
1202
|
+
}
|
|
1203
|
+
]
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
const diagnostics = [...candidate.diagnostics];
|
|
1207
|
+
if (movedExisting && stateResult.status !== "valid") {
|
|
1208
|
+
diagnostics.push({
|
|
1209
|
+
type: "warning",
|
|
1210
|
+
message: "previous unverifiable extension bytes were preserved during forced reinstall",
|
|
1211
|
+
path: backupRoot
|
|
1212
|
+
});
|
|
1213
|
+
} else {
|
|
1214
|
+
try {
|
|
1215
|
+
rmSync(backupRoot, { recursive: true, force: true });
|
|
1216
|
+
packageBackup = void 0;
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
diagnostics.push({
|
|
1219
|
+
type: "warning",
|
|
1220
|
+
message: `extension installed, but its backup could not be removed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1221
|
+
path: backupRoot
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
if (stateBackup) {
|
|
1226
|
+
diagnostics.push({ type: "warning", message: "corrupt extension install state was preserved", path: stateBackup });
|
|
1227
|
+
}
|
|
1228
|
+
const installed = findInstalled(candidate.manifest.id, cwd, scope);
|
|
1229
|
+
return {
|
|
1230
|
+
...installed ? { extension: installed } : {},
|
|
1231
|
+
...stateBackup || packageBackup ? { recovery: { ...stateBackup ? { stateBackup } : {}, ...packageBackup ? { packageBackup } : {} } } : {},
|
|
1232
|
+
diagnostics
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
function mutateEnabled(id, enabled, options = {}) {
|
|
1236
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1237
|
+
const target = findInstalled(id, cwd, options.scope);
|
|
1238
|
+
if (!target) {
|
|
1239
|
+
return { diagnostics: [{ type: "error", message: `extension ${id} is not installed` }] };
|
|
1240
|
+
}
|
|
1241
|
+
const stateResult = readState(target.scope, cwd);
|
|
1242
|
+
if (stateResult.status !== "valid") {
|
|
1243
|
+
return {
|
|
1244
|
+
diagnostics: [
|
|
1245
|
+
{
|
|
1246
|
+
type: "error",
|
|
1247
|
+
message: stateResult.status === "corrupt" ? `extension install state is corrupt: ${stateResult.message}` : "extension install state is absent",
|
|
1248
|
+
path: statePath(target.scope, cwd)
|
|
1249
|
+
}
|
|
1250
|
+
]
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
const state = stateResult.state;
|
|
1254
|
+
if (enabled) state.disabled = state.disabled.filter((entry) => entry !== id);
|
|
1255
|
+
else if (!state.disabled.includes(id)) state.disabled.push(id);
|
|
1256
|
+
writeState(target.scope, state, cwd);
|
|
1257
|
+
const extension = findInstalled(id, cwd, target.scope) ?? void 0;
|
|
1258
|
+
return { ...extension ? { extension } : {}, diagnostics: [] };
|
|
1259
|
+
}
|
|
1260
|
+
function enableExtension(id, options = {}) {
|
|
1261
|
+
return mutateEnabled(id, true, options);
|
|
1262
|
+
}
|
|
1263
|
+
function disableExtension(id, options = {}) {
|
|
1264
|
+
return mutateEnabled(id, false, options);
|
|
1265
|
+
}
|
|
1266
|
+
function removeExtension(id, options = {}) {
|
|
1267
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1268
|
+
const target = findInstalled(id, cwd, options.scope);
|
|
1269
|
+
if (!target) {
|
|
1270
|
+
return { diagnostics: [{ type: "error", message: `extension ${id} is not installed` }] };
|
|
1271
|
+
}
|
|
1272
|
+
const stateResult = readState(target.scope, cwd);
|
|
1273
|
+
if (stateResult.status !== "valid") {
|
|
1274
|
+
const filePath = statePath(target.scope, cwd);
|
|
1275
|
+
const packageBackup = packageRecoveryPath(target.rootPath, "removed-unverifiable");
|
|
1276
|
+
let stateBackup;
|
|
1277
|
+
try {
|
|
1278
|
+
if (stateResult.status === "corrupt") stateBackup = preserveFile(filePath, "corrupt");
|
|
1279
|
+
renameSync(target.rootPath, packageBackup);
|
|
1280
|
+
try {
|
|
1281
|
+
writeState(target.scope, structuredClone(DEFAULT_STATE), cwd);
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
renameSync(packageBackup, target.rootPath);
|
|
1284
|
+
throw error;
|
|
1285
|
+
}
|
|
1286
|
+
const diagnostics = [
|
|
1287
|
+
{
|
|
1288
|
+
type: "warning",
|
|
1289
|
+
message: "unverifiable extension bytes were preserved while removing the package from the load path",
|
|
1290
|
+
path: packageBackup
|
|
1291
|
+
}
|
|
1292
|
+
];
|
|
1293
|
+
if (stateBackup) {
|
|
1294
|
+
diagnostics.push({ type: "warning", message: "corrupt extension install state was preserved", path: stateBackup });
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
removed: { id, scope: target.scope, path: target.rootPath },
|
|
1298
|
+
recovery: { ...stateBackup ? { stateBackup } : {}, packageBackup },
|
|
1299
|
+
diagnostics
|
|
1300
|
+
};
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
return {
|
|
1303
|
+
diagnostics: [
|
|
1304
|
+
{
|
|
1305
|
+
type: "error",
|
|
1306
|
+
message: `extension ${id} could not be removed safely: ${error instanceof Error ? error.message : String(error)}`,
|
|
1307
|
+
path: target.rootPath
|
|
1308
|
+
}
|
|
1309
|
+
]
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const state = stateResult.state;
|
|
1314
|
+
rmSync(target.rootPath, { recursive: true, force: true });
|
|
1315
|
+
Reflect.deleteProperty(state.installed, id);
|
|
1316
|
+
state.disabled = state.disabled.filter((entry) => entry !== id);
|
|
1317
|
+
writeState(target.scope, state, cwd);
|
|
1318
|
+
return { removed: { id, scope: target.scope, path: target.rootPath }, diagnostics: [] };
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
export {
|
|
1322
|
+
loadManifestFromRoot,
|
|
1323
|
+
discoverExtensionPackages,
|
|
1324
|
+
extensionContentDigest,
|
|
1325
|
+
extensionBaseDir,
|
|
1326
|
+
upgradeLegacyExtensionInstallState,
|
|
1327
|
+
listInstalledExtensions,
|
|
1328
|
+
listInstalledExtensionRecords,
|
|
1329
|
+
installExtension,
|
|
1330
|
+
enableExtension,
|
|
1331
|
+
disableExtension,
|
|
1332
|
+
removeExtension
|
|
1333
|
+
};
|
|
1334
|
+
//# sourceMappingURL=chunk-77QIVUZB.js.map
|