@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
|
@@ -1,21 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
cpSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
} from "node:fs";
|
|
2
13
|
import path from "node:path";
|
|
3
14
|
import { safeResourceWrite } from "../../core/safe-resource-write.js";
|
|
4
15
|
import { clioConfigDir } from "../../core/xdg.js";
|
|
5
16
|
import { evaluateClioCompatibility } from "./compatibility.js";
|
|
6
17
|
import { isRecord, loadManifestFromRoot, trimString } from "./discovery.js";
|
|
18
|
+
import { extensionContentDigest, extensionContentDigestWithCapture } from "./integrity.js";
|
|
7
19
|
import type {
|
|
20
|
+
ExtensionDiagnostic,
|
|
8
21
|
ExtensionInstallOptions,
|
|
9
22
|
ExtensionInstallResult,
|
|
10
23
|
ExtensionListOptions,
|
|
11
24
|
ExtensionMutationResult,
|
|
12
25
|
ExtensionScope,
|
|
13
26
|
ExtensionState,
|
|
27
|
+
ExtensionStateUpgradeReport,
|
|
14
28
|
InstalledExtension,
|
|
15
29
|
} from "./types.js";
|
|
16
30
|
|
|
17
31
|
const DEFAULT_STATE: ExtensionState = { version: 1, disabled: [], installed: {} };
|
|
18
32
|
|
|
33
|
+
type StateReadResult =
|
|
34
|
+
| { status: "absent"; state: ExtensionState }
|
|
35
|
+
| { status: "valid"; state: ExtensionState }
|
|
36
|
+
| { status: "corrupt"; state: ExtensionState; message: string };
|
|
37
|
+
|
|
38
|
+
export interface InstalledExtensionRecord {
|
|
39
|
+
entry: InstalledExtension;
|
|
40
|
+
/** Exact file bytes captured by the stable tree-digest read. */
|
|
41
|
+
captured?: ReadonlyMap<string, Buffer>;
|
|
42
|
+
}
|
|
43
|
+
|
|
19
44
|
export function extensionBaseDir(scope: ExtensionScope, cwd = process.cwd()): string {
|
|
20
45
|
return scope === "user"
|
|
21
46
|
? path.join(clioConfigDir(), "extensions")
|
|
@@ -26,32 +51,61 @@ function statePath(scope: ExtensionScope, cwd = process.cwd()): string {
|
|
|
26
51
|
return path.join(extensionBaseDir(scope, cwd), "state.json");
|
|
27
52
|
}
|
|
28
53
|
|
|
54
|
+
function recoveryPath(filePath: string, label: string): string {
|
|
55
|
+
return `${filePath}.${label}-${process.pid}-${Date.now()}.bak`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function packageRecoveryPath(root: string, label: string): string {
|
|
59
|
+
return path.join(path.dirname(root), `.${path.basename(root)}.${label}-${process.pid}-${Date.now()}.bak`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function preserveFile(filePath: string, label: string): string | undefined {
|
|
63
|
+
if (!existsSync(filePath)) return undefined;
|
|
64
|
+
const backupPath = recoveryPath(filePath, label);
|
|
65
|
+
safeResourceWrite(backupPath, readFileSync(filePath));
|
|
66
|
+
return backupPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
export function scopeRank(scope: ExtensionScope): number {
|
|
30
70
|
return scope === "project" ? 2 : 1;
|
|
31
71
|
}
|
|
32
72
|
|
|
33
|
-
function readState(scope: ExtensionScope, cwd = process.cwd()):
|
|
73
|
+
function readState(scope: ExtensionScope, cwd = process.cwd()): StateReadResult {
|
|
34
74
|
const filePath = statePath(scope, cwd);
|
|
35
|
-
if (!existsSync(filePath)) return structuredClone(DEFAULT_STATE);
|
|
75
|
+
if (!existsSync(filePath)) return { status: "absent", state: structuredClone(DEFAULT_STATE) };
|
|
36
76
|
try {
|
|
37
77
|
const parsed = JSON.parse(readFileSync(filePath, "utf8")) as unknown;
|
|
38
|
-
if (!isRecord(parsed) || parsed.version !== 1)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
if (!isRecord(parsed) || parsed.version !== 1) throw new Error("state must be a version 1 object");
|
|
79
|
+
if (!Array.isArray(parsed.disabled) || !parsed.disabled.every((entry) => typeof entry === "string")) {
|
|
80
|
+
throw new Error("state.disabled must be an array of strings");
|
|
81
|
+
}
|
|
82
|
+
if (!isRecord(parsed.installed)) throw new Error("state.installed must be an object");
|
|
83
|
+
const installed: ExtensionState["installed"] = {};
|
|
84
|
+
for (const [id, raw] of Object.entries(parsed.installed)) {
|
|
85
|
+
if (!isRecord(raw)) throw new Error(`state.installed.${id} must be an object`);
|
|
86
|
+
const installedAt = trimString(raw.installedAt);
|
|
87
|
+
if (!installedAt) throw new Error(`state.installed.${id}.installedAt must be a non-empty string`);
|
|
88
|
+
const source = raw.source === undefined ? undefined : trimString(raw.source);
|
|
89
|
+
if (raw.source !== undefined && !source) {
|
|
90
|
+
throw new Error(`state.installed.${id}.source must be a non-empty string`);
|
|
91
|
+
}
|
|
92
|
+
const contentDigest = raw.contentDigest === undefined ? undefined : trimString(raw.contentDigest);
|
|
93
|
+
if (raw.contentDigest !== undefined && (!contentDigest || !/^[a-f0-9]{64}$/u.test(contentDigest))) {
|
|
94
|
+
throw new Error(`state.installed.${id}.contentDigest must be a SHA-256 digest`);
|
|
95
|
+
}
|
|
96
|
+
installed[id] = {
|
|
97
|
+
installedAt,
|
|
98
|
+
...(source ? { source } : {}),
|
|
99
|
+
...(contentDigest ? { contentDigest } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { status: "valid", state: { version: 1, disabled: [...parsed.disabled], installed } };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
status: "corrupt",
|
|
106
|
+
state: structuredClone(DEFAULT_STATE),
|
|
107
|
+
message: error instanceof Error ? error.message : String(error),
|
|
108
|
+
};
|
|
55
109
|
}
|
|
56
110
|
}
|
|
57
111
|
|
|
@@ -60,68 +114,225 @@ function writeState(scope: ExtensionScope, state: ExtensionState, cwd = process.
|
|
|
60
114
|
safeResourceWrite(filePath, `${JSON.stringify(state, null, 2)}\n`, { encoding: "utf8" });
|
|
61
115
|
}
|
|
62
116
|
|
|
63
|
-
function installedFromRoot(
|
|
117
|
+
function installedFromRoot(
|
|
118
|
+
root: string,
|
|
119
|
+
scope: ExtensionScope,
|
|
120
|
+
stateResult: StateReadResult,
|
|
121
|
+
cwd: string,
|
|
122
|
+
fallbackId: string,
|
|
123
|
+
): InstalledExtensionRecord | null {
|
|
64
124
|
const candidate = loadManifestFromRoot(root);
|
|
65
125
|
const manifest = candidate.manifest;
|
|
66
|
-
|
|
67
|
-
const
|
|
126
|
+
const diagnostics = [...candidate.diagnostics];
|
|
127
|
+
const id = manifest?.id ?? fallbackId;
|
|
128
|
+
const provenance = stateResult.state.installed[id];
|
|
129
|
+
const expectedDigest = provenance?.contentDigest;
|
|
130
|
+
let observedDigest: string | undefined;
|
|
131
|
+
let captured: ReadonlyMap<string, Buffer> | undefined;
|
|
132
|
+
let contentVerified = false;
|
|
133
|
+
if (stateResult.status === "corrupt") {
|
|
134
|
+
diagnostics.push({
|
|
135
|
+
type: "error",
|
|
136
|
+
message: `extension install state is corrupt: ${stateResult.message}; reinstall with --force or remove the package to preserve the corrupt bytes and recover`,
|
|
137
|
+
path: statePath(scope, cwd),
|
|
138
|
+
});
|
|
139
|
+
} else if (stateResult.status === "absent") {
|
|
140
|
+
diagnostics.push({
|
|
141
|
+
type: "error",
|
|
142
|
+
message: "extension install state is absent; installed content cannot be verified; reinstall with --force",
|
|
143
|
+
path: statePath(scope, cwd),
|
|
144
|
+
});
|
|
145
|
+
} else if (!provenance) {
|
|
146
|
+
diagnostics.push({
|
|
147
|
+
type: "error",
|
|
148
|
+
message: `extension ${id} is not recorded in install state; reinstall with --force`,
|
|
149
|
+
path: statePath(scope, cwd),
|
|
150
|
+
});
|
|
151
|
+
} else if (!expectedDigest) {
|
|
152
|
+
diagnostics.push({
|
|
153
|
+
type: "error",
|
|
154
|
+
message: `extension ${id} install provenance has no content digest; run clio-coder upgrade, then reinstall with --force if it remains unverifiable`,
|
|
155
|
+
path: statePath(scope, cwd),
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
try {
|
|
159
|
+
const manifestName = candidate.manifestPath ? path.basename(candidate.manifestPath) : undefined;
|
|
160
|
+
const digestResult = extensionContentDigestWithCapture(root, {
|
|
161
|
+
capture: [...(manifestName ? [manifestName] : []), "hooks.yaml"],
|
|
162
|
+
});
|
|
163
|
+
observedDigest = digestResult.digest;
|
|
164
|
+
contentVerified = observedDigest === expectedDigest;
|
|
165
|
+
if (contentVerified) captured = digestResult.captured;
|
|
166
|
+
if (!contentVerified) {
|
|
167
|
+
diagnostics.push({
|
|
168
|
+
type: "error",
|
|
169
|
+
message: `installed extension content drift detected (expected ${expectedDigest}, observed ${observedDigest})`,
|
|
170
|
+
path: root,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
diagnostics.push({
|
|
175
|
+
type: "error",
|
|
176
|
+
message: `installed extension content could not be verified: ${error instanceof Error ? error.message : String(error)}`,
|
|
177
|
+
path: root,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const clioRange = manifest?.compatibility?.clio;
|
|
68
182
|
const compatible = clioRange === undefined || evaluateClioCompatibility(clioRange).satisfied;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
183
|
+
const manifestName = candidate.manifestPath ? path.basename(candidate.manifestPath) : undefined;
|
|
184
|
+
const manifestBytes = manifestName ? captured?.get(manifestName) : undefined;
|
|
185
|
+
const extensionProvenance =
|
|
186
|
+
contentVerified && expectedDigest && manifestBytes
|
|
187
|
+
? {
|
|
188
|
+
id,
|
|
189
|
+
scope,
|
|
190
|
+
...(provenance?.source ? { sourcePath: provenance.source } : {}),
|
|
191
|
+
canonicalRoot: realpathSync(root),
|
|
192
|
+
manifestDigest: createHash("sha256").update(manifestBytes).digest("hex"),
|
|
193
|
+
contentDigest: expectedDigest,
|
|
194
|
+
}
|
|
195
|
+
: undefined;
|
|
196
|
+
const entry: InstalledExtension = {
|
|
197
|
+
id,
|
|
198
|
+
name: manifest?.name ?? id,
|
|
199
|
+
version: manifest?.version ?? "unknown",
|
|
200
|
+
description: manifest?.description ?? "Installed extension has an invalid manifest.",
|
|
74
201
|
scope,
|
|
75
202
|
rootPath: root,
|
|
76
|
-
manifestPath: candidate.manifestPath,
|
|
77
|
-
enabled: !state.disabled.includes(
|
|
203
|
+
manifestPath: candidate.manifestPath ?? root,
|
|
204
|
+
enabled: !stateResult.state.disabled.includes(id),
|
|
205
|
+
valid: manifest !== undefined && candidate.valid && contentVerified,
|
|
78
206
|
compatible,
|
|
79
207
|
effective: false,
|
|
80
|
-
|
|
81
|
-
|
|
208
|
+
loadable: false,
|
|
209
|
+
...(extensionProvenance ? { provenance: extensionProvenance } : {}),
|
|
210
|
+
...(observedDigest ? { observedContentDigest: observedDigest } : {}),
|
|
211
|
+
resources: manifest?.resources ?? {},
|
|
212
|
+
diagnostics,
|
|
82
213
|
};
|
|
214
|
+
return { entry, ...(extensionProvenance && captured ? { captured } : {}) };
|
|
83
215
|
}
|
|
84
216
|
|
|
85
|
-
function listScope(scope: ExtensionScope, cwd = process.cwd()):
|
|
217
|
+
function listScope(scope: ExtensionScope, cwd = process.cwd()): InstalledExtensionRecord[] {
|
|
86
218
|
const base = extensionBaseDir(scope, cwd);
|
|
87
219
|
if (!existsSync(base)) return [];
|
|
88
220
|
const state = readState(scope, cwd);
|
|
89
|
-
const out:
|
|
221
|
+
const out: InstalledExtensionRecord[] = [];
|
|
90
222
|
for (const entry of readdirSync(base, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
91
|
-
if (!entry.isDirectory()) continue;
|
|
223
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
92
224
|
const root = path.join(base, entry.name);
|
|
93
|
-
const installed = installedFromRoot(root, scope, state);
|
|
225
|
+
const installed = installedFromRoot(root, scope, state, cwd, entry.name);
|
|
94
226
|
if (installed) out.push(installed);
|
|
95
227
|
}
|
|
96
228
|
return out;
|
|
97
229
|
}
|
|
98
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Add integrity digests to released v1 install records only after the installed
|
|
233
|
+
* tree validates and remains byte-identical through the atomic state rewrite.
|
|
234
|
+
* Refused records remain unchanged and therefore fail closed at load time.
|
|
235
|
+
*/
|
|
236
|
+
export function upgradeLegacyExtensionInstallState(
|
|
237
|
+
cwd = process.cwd(),
|
|
238
|
+
scopes: ReadonlyArray<ExtensionScope> = ["user", "project"],
|
|
239
|
+
): ExtensionStateUpgradeReport[] {
|
|
240
|
+
const reports: ExtensionStateUpgradeReport[] = [];
|
|
241
|
+
for (const scope of scopes) {
|
|
242
|
+
const filePath = statePath(scope, cwd);
|
|
243
|
+
const report: ExtensionStateUpgradeReport = { scope, statePath: filePath, upgraded: [], refused: [] };
|
|
244
|
+
const stateResult = readState(scope, cwd);
|
|
245
|
+
if (stateResult.status === "absent") {
|
|
246
|
+
reports.push(report);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (stateResult.status === "corrupt") {
|
|
250
|
+
report.refused.push({ id: "<state>", reason: `install state is corrupt: ${stateResult.message}` });
|
|
251
|
+
reports.push(report);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const next = structuredClone(stateResult.state);
|
|
255
|
+
const verified = new Map<string, { root: string; digest: string }>();
|
|
256
|
+
for (const [id, record] of Object.entries(next.installed)) {
|
|
257
|
+
if (record.contentDigest) continue;
|
|
258
|
+
const root = path.join(extensionBaseDir(scope, cwd), id);
|
|
259
|
+
try {
|
|
260
|
+
if (!lstatSync(root).isDirectory()) throw new Error("installed path is not a directory");
|
|
261
|
+
const candidate = loadManifestFromRoot(root);
|
|
262
|
+
if (!candidate.valid || !candidate.manifest) {
|
|
263
|
+
throw new Error(candidate.diagnostics.map((diagnostic) => diagnostic.message).join("; ") || "manifest is invalid");
|
|
264
|
+
}
|
|
265
|
+
if (candidate.manifest.id !== id) {
|
|
266
|
+
throw new Error(`manifest id ${candidate.manifest.id} does not match install record ${id}`);
|
|
267
|
+
}
|
|
268
|
+
const digest = extensionContentDigest(root);
|
|
269
|
+
record.contentDigest = digest;
|
|
270
|
+
verified.set(id, { root, digest });
|
|
271
|
+
report.upgraded.push(id);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
report.refused.push({ id, reason: error instanceof Error ? error.message : String(error) });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (report.upgraded.length > 0) {
|
|
277
|
+
const backupPath = `${filePath}.pre-digest.bak`;
|
|
278
|
+
safeResourceWrite(filePath, `${JSON.stringify(next, null, 2)}\n`, {
|
|
279
|
+
encoding: "utf8",
|
|
280
|
+
backup: { path: backupPath },
|
|
281
|
+
beforeRename: () => {
|
|
282
|
+
for (const [id, expected] of verified) {
|
|
283
|
+
const candidate = loadManifestFromRoot(expected.root);
|
|
284
|
+
if (!candidate.valid || candidate.manifest?.id !== id) {
|
|
285
|
+
throw new Error(`extension ${id} became invalid during digest migration`);
|
|
286
|
+
}
|
|
287
|
+
const observed = extensionContentDigest(expected.root);
|
|
288
|
+
if (observed !== expected.digest) throw new Error(`extension ${id} changed during digest migration`);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
report.backupPath = backupPath;
|
|
293
|
+
}
|
|
294
|
+
reports.push(report);
|
|
295
|
+
}
|
|
296
|
+
return reports;
|
|
297
|
+
}
|
|
298
|
+
|
|
99
299
|
export function listInstalledExtensions(cwd = process.cwd(), options: ExtensionListOptions = {}): InstalledExtension[] {
|
|
300
|
+
return listInstalledExtensionRecords(cwd, options).map((record) => record.entry);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function listInstalledExtensionRecords(
|
|
304
|
+
cwd = process.cwd(),
|
|
305
|
+
options: ExtensionListOptions = {},
|
|
306
|
+
): InstalledExtensionRecord[] {
|
|
100
307
|
const scopes: ExtensionScope[] = options.scope ? [options.scope] : ["user", "project"];
|
|
101
|
-
const
|
|
102
|
-
const byId = new Map<string,
|
|
103
|
-
for (const
|
|
308
|
+
const records = scopes.flatMap((scope) => listScope(scope, cwd));
|
|
309
|
+
const byId = new Map<string, InstalledExtensionRecord[]>();
|
|
310
|
+
for (const record of records) {
|
|
311
|
+
const entry = record.entry;
|
|
104
312
|
const list = byId.get(entry.id) ?? [];
|
|
105
|
-
list.push(
|
|
313
|
+
list.push(record);
|
|
106
314
|
byId.set(entry.id, list);
|
|
107
315
|
}
|
|
108
316
|
for (const group of byId.values()) {
|
|
109
317
|
const winner = group
|
|
110
|
-
.filter((
|
|
111
|
-
.sort((a, b) => scopeRank(a.scope) - scopeRank(b.scope))
|
|
318
|
+
.filter((record) => record.entry.valid && record.entry.compatible)
|
|
319
|
+
.sort((a, b) => scopeRank(a.entry.scope) - scopeRank(b.entry.scope))
|
|
112
320
|
.at(-1);
|
|
113
|
-
for (const
|
|
114
|
-
entry
|
|
115
|
-
|
|
321
|
+
for (const record of group) {
|
|
322
|
+
const entry = record.entry;
|
|
323
|
+
entry.effective = record === winner;
|
|
324
|
+
entry.loadable = entry.valid && entry.compatible && entry.enabled && entry.effective;
|
|
325
|
+
if (entry.valid && entry.compatible && !entry.effective && winner) entry.overriddenBy = winner.entry.scope;
|
|
116
326
|
}
|
|
117
327
|
}
|
|
118
|
-
//
|
|
119
|
-
// its
|
|
120
|
-
const all =
|
|
328
|
+
// Invalid and incompatible packages remain visible by default so the load
|
|
329
|
+
// refusal and its diagnostic cannot disappear with the resources it suppresses.
|
|
330
|
+
const all =
|
|
331
|
+
options.all === true ? records : records.filter(({ entry }) => entry.effective || !entry.valid || !entry.compatible);
|
|
121
332
|
return all.sort((a, b) => {
|
|
122
|
-
const id = a.id.localeCompare(b.id);
|
|
333
|
+
const id = a.entry.id.localeCompare(b.entry.id);
|
|
123
334
|
if (id !== 0) return id;
|
|
124
|
-
return scopeRank(a.scope) - scopeRank(b.scope);
|
|
335
|
+
return scopeRank(a.entry.scope) - scopeRank(b.entry.scope);
|
|
125
336
|
});
|
|
126
337
|
}
|
|
127
338
|
|
|
@@ -144,7 +355,11 @@ export function installExtension(sourcePath: string, options: ExtensionInstallOp
|
|
|
144
355
|
if (!options.force) {
|
|
145
356
|
return {
|
|
146
357
|
diagnostics: [
|
|
147
|
-
{
|
|
358
|
+
{
|
|
359
|
+
type: "error",
|
|
360
|
+
message: `extension ${candidate.manifest.id} is already installed; retry with --force to replace it`,
|
|
361
|
+
path: targetRoot,
|
|
362
|
+
},
|
|
148
363
|
],
|
|
149
364
|
};
|
|
150
365
|
}
|
|
@@ -154,9 +369,24 @@ export function installExtension(sourcePath: string, options: ExtensionInstallOp
|
|
|
154
369
|
const backupRoot = path.join(parent, `.${candidate.manifest.id}.backup-${process.pid}-${Date.now()}`);
|
|
155
370
|
let installedReplacement = false;
|
|
156
371
|
let movedExisting = false;
|
|
157
|
-
const
|
|
372
|
+
const stateResult = readState(scope, cwd);
|
|
373
|
+
if (stateResult.status === "corrupt" && !options.force) {
|
|
374
|
+
return {
|
|
375
|
+
diagnostics: [
|
|
376
|
+
{
|
|
377
|
+
type: "error",
|
|
378
|
+
message: `extension install state is corrupt: ${stateResult.message}; retry with --force to back it up and reinstall safely`,
|
|
379
|
+
path: statePath(scope, cwd),
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const state = stateResult.status === "corrupt" ? structuredClone(DEFAULT_STATE) : stateResult.state;
|
|
385
|
+
let stateBackup: string | undefined;
|
|
386
|
+
let packageBackup: string | undefined;
|
|
158
387
|
try {
|
|
159
388
|
mkdirSync(parent, { recursive: true });
|
|
389
|
+
if (stateResult.status === "corrupt") stateBackup = preserveFile(statePath(scope, cwd), "corrupt");
|
|
160
390
|
rmSync(stagingRoot, { recursive: true, force: true });
|
|
161
391
|
rmSync(backupRoot, { recursive: true, force: true });
|
|
162
392
|
cpSync(source, stagingRoot, {
|
|
@@ -167,23 +397,40 @@ export function installExtension(sourcePath: string, options: ExtensionInstallOp
|
|
|
167
397
|
// template), so exclude only a bookkeeping file at the package root.
|
|
168
398
|
filter: (src) => path.relative(source, src) !== "state.json",
|
|
169
399
|
});
|
|
400
|
+
const stagedCandidate = loadManifestFromRoot(stagingRoot);
|
|
401
|
+
if (!stagedCandidate.valid || !stagedCandidate.manifest) {
|
|
402
|
+
const reasons = stagedCandidate.diagnostics.map((diagnostic) => diagnostic.message).join("; ");
|
|
403
|
+
throw new Error(`staged extension content is invalid${reasons ? `: ${reasons}` : ""}`);
|
|
404
|
+
}
|
|
405
|
+
if (stagedCandidate.manifest.id !== candidate.manifest.id) {
|
|
406
|
+
throw new Error(`staged extension id changed from ${candidate.manifest.id} to ${stagedCandidate.manifest.id}`);
|
|
407
|
+
}
|
|
408
|
+
const contentDigest = extensionContentDigest(stagingRoot);
|
|
170
409
|
if (existsSync(targetRoot)) {
|
|
171
410
|
renameSync(targetRoot, backupRoot);
|
|
172
411
|
movedExisting = true;
|
|
412
|
+
packageBackup = backupRoot;
|
|
173
413
|
}
|
|
174
414
|
renameSync(stagingRoot, targetRoot);
|
|
175
415
|
installedReplacement = true;
|
|
176
|
-
state.installed[candidate.manifest.id] = {
|
|
416
|
+
state.installed[candidate.manifest.id] = {
|
|
417
|
+
installedAt: new Date().toISOString(),
|
|
418
|
+
source,
|
|
419
|
+
contentDigest,
|
|
420
|
+
};
|
|
177
421
|
state.disabled = state.disabled.filter((entry) => entry !== candidate.manifest?.id);
|
|
178
422
|
writeState(scope, state, cwd);
|
|
179
|
-
rmSync(backupRoot, { recursive: true, force: true });
|
|
180
423
|
} catch (error) {
|
|
181
424
|
rmSync(stagingRoot, { recursive: true, force: true });
|
|
182
425
|
if (installedReplacement) rmSync(targetRoot, { recursive: true, force: true });
|
|
183
426
|
if (movedExisting && existsSync(backupRoot) && !existsSync(targetRoot)) {
|
|
184
427
|
renameSync(backupRoot, targetRoot);
|
|
428
|
+
packageBackup = undefined;
|
|
185
429
|
}
|
|
186
430
|
return {
|
|
431
|
+
...(stateBackup || packageBackup
|
|
432
|
+
? { recovery: { ...(stateBackup ? { stateBackup } : {}), ...(packageBackup ? { packageBackup } : {}) } }
|
|
433
|
+
: {}),
|
|
187
434
|
diagnostics: [
|
|
188
435
|
{
|
|
189
436
|
type: "error",
|
|
@@ -193,10 +440,35 @@ export function installExtension(sourcePath: string, options: ExtensionInstallOp
|
|
|
193
440
|
],
|
|
194
441
|
};
|
|
195
442
|
}
|
|
443
|
+
const diagnostics = [...candidate.diagnostics];
|
|
444
|
+
if (movedExisting && stateResult.status !== "valid") {
|
|
445
|
+
diagnostics.push({
|
|
446
|
+
type: "warning",
|
|
447
|
+
message: "previous unverifiable extension bytes were preserved during forced reinstall",
|
|
448
|
+
path: backupRoot,
|
|
449
|
+
});
|
|
450
|
+
} else {
|
|
451
|
+
try {
|
|
452
|
+
rmSync(backupRoot, { recursive: true, force: true });
|
|
453
|
+
packageBackup = undefined;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
diagnostics.push({
|
|
456
|
+
type: "warning",
|
|
457
|
+
message: `extension installed, but its backup could not be removed: ${error instanceof Error ? error.message : String(error)}`,
|
|
458
|
+
path: backupRoot,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (stateBackup) {
|
|
463
|
+
diagnostics.push({ type: "warning", message: "corrupt extension install state was preserved", path: stateBackup });
|
|
464
|
+
}
|
|
196
465
|
const installed = findInstalled(candidate.manifest.id, cwd, scope);
|
|
197
466
|
return {
|
|
198
467
|
...(installed ? { extension: installed } : {}),
|
|
199
|
-
|
|
468
|
+
...(stateBackup || packageBackup
|
|
469
|
+
? { recovery: { ...(stateBackup ? { stateBackup } : {}), ...(packageBackup ? { packageBackup } : {}) } }
|
|
470
|
+
: {}),
|
|
471
|
+
diagnostics,
|
|
200
472
|
};
|
|
201
473
|
}
|
|
202
474
|
|
|
@@ -206,7 +478,22 @@ function mutateEnabled(id: string, enabled: boolean, options: ExtensionListOptio
|
|
|
206
478
|
if (!target) {
|
|
207
479
|
return { diagnostics: [{ type: "error", message: `extension ${id} is not installed` }] };
|
|
208
480
|
}
|
|
209
|
-
const
|
|
481
|
+
const stateResult = readState(target.scope, cwd);
|
|
482
|
+
if (stateResult.status !== "valid") {
|
|
483
|
+
return {
|
|
484
|
+
diagnostics: [
|
|
485
|
+
{
|
|
486
|
+
type: "error",
|
|
487
|
+
message:
|
|
488
|
+
stateResult.status === "corrupt"
|
|
489
|
+
? `extension install state is corrupt: ${stateResult.message}`
|
|
490
|
+
: "extension install state is absent",
|
|
491
|
+
path: statePath(target.scope, cwd),
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const state = stateResult.state;
|
|
210
497
|
if (enabled) state.disabled = state.disabled.filter((entry) => entry !== id);
|
|
211
498
|
else if (!state.disabled.includes(id)) state.disabled.push(id);
|
|
212
499
|
writeState(target.scope, state, cwd);
|
|
@@ -228,8 +515,49 @@ export function removeExtension(id: string, options: ExtensionListOptions = {}):
|
|
|
228
515
|
if (!target) {
|
|
229
516
|
return { diagnostics: [{ type: "error", message: `extension ${id} is not installed` }] };
|
|
230
517
|
}
|
|
518
|
+
const stateResult = readState(target.scope, cwd);
|
|
519
|
+
if (stateResult.status !== "valid") {
|
|
520
|
+
const filePath = statePath(target.scope, cwd);
|
|
521
|
+
const packageBackup = packageRecoveryPath(target.rootPath, "removed-unverifiable");
|
|
522
|
+
let stateBackup: string | undefined;
|
|
523
|
+
try {
|
|
524
|
+
if (stateResult.status === "corrupt") stateBackup = preserveFile(filePath, "corrupt");
|
|
525
|
+
renameSync(target.rootPath, packageBackup);
|
|
526
|
+
try {
|
|
527
|
+
writeState(target.scope, structuredClone(DEFAULT_STATE), cwd);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
renameSync(packageBackup, target.rootPath);
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
const diagnostics: ExtensionDiagnostic[] = [
|
|
533
|
+
{
|
|
534
|
+
type: "warning",
|
|
535
|
+
message: "unverifiable extension bytes were preserved while removing the package from the load path",
|
|
536
|
+
path: packageBackup,
|
|
537
|
+
},
|
|
538
|
+
];
|
|
539
|
+
if (stateBackup) {
|
|
540
|
+
diagnostics.push({ type: "warning", message: "corrupt extension install state was preserved", path: stateBackup });
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
removed: { id, scope: target.scope, path: target.rootPath },
|
|
544
|
+
recovery: { ...(stateBackup ? { stateBackup } : {}), packageBackup },
|
|
545
|
+
diagnostics,
|
|
546
|
+
};
|
|
547
|
+
} catch (error) {
|
|
548
|
+
return {
|
|
549
|
+
diagnostics: [
|
|
550
|
+
{
|
|
551
|
+
type: "error",
|
|
552
|
+
message: `extension ${id} could not be removed safely: ${error instanceof Error ? error.message : String(error)}`,
|
|
553
|
+
path: target.rootPath,
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const state = stateResult.state;
|
|
231
560
|
rmSync(target.rootPath, { recursive: true, force: true });
|
|
232
|
-
const state = readState(target.scope, cwd);
|
|
233
561
|
Reflect.deleteProperty(state.installed, id);
|
|
234
562
|
state.disabled = state.disabled.filter((entry) => entry !== id);
|
|
235
563
|
writeState(target.scope, state, cwd);
|