@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,189 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
constants,
|
|
5
|
+
fstatSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
readlinkSync,
|
|
11
|
+
realpathSync,
|
|
12
|
+
type Stats,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
export interface ExtensionContentDigestOptions {
|
|
17
|
+
/** Root-relative regular files whose exact hashed bytes should be returned. */
|
|
18
|
+
capture?: ReadonlyArray<string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ExtensionContentDigestResult {
|
|
22
|
+
digest: string;
|
|
23
|
+
captured: ReadonlyMap<string, Buffer>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function contained(root: string, candidate: string): boolean {
|
|
27
|
+
const relative = path.relative(root, candidate);
|
|
28
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function digestFrame(hash: ReturnType<typeof createHash>, kind: string, relativePath: string, payload?: Buffer): void {
|
|
32
|
+
const header = Buffer.from(
|
|
33
|
+
`${kind}\0${relativePath.replaceAll(path.sep, "/")}\0${payload?.byteLength ?? 0}\0`,
|
|
34
|
+
"utf8",
|
|
35
|
+
);
|
|
36
|
+
hash.update(header);
|
|
37
|
+
if (payload) hash.update(payload);
|
|
38
|
+
hash.update("\0");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sameFileState(left: Stats, right: Stats): boolean {
|
|
42
|
+
return (
|
|
43
|
+
left.dev === right.dev &&
|
|
44
|
+
left.ino === right.ino &&
|
|
45
|
+
left.mode === right.mode &&
|
|
46
|
+
left.nlink === right.nlink &&
|
|
47
|
+
left.size === right.size &&
|
|
48
|
+
left.mtimeMs === right.mtimeMs &&
|
|
49
|
+
left.ctimeMs === right.ctimeMs
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readStableRegularFile(filePath: string, inspected: Stats): Buffer {
|
|
54
|
+
const fd = openSync(filePath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
55
|
+
try {
|
|
56
|
+
const opened = fstatSync(fd);
|
|
57
|
+
if (!opened.isFile() || opened.nlink !== 1 || !sameFileState(inspected, opened)) {
|
|
58
|
+
throw new Error(`extension file changed while being opened: ${filePath}`);
|
|
59
|
+
}
|
|
60
|
+
const payload = readFileSync(fd);
|
|
61
|
+
if (!sameFileState(opened, fstatSync(fd))) {
|
|
62
|
+
throw new Error(`extension file changed while being hashed: ${filePath}`);
|
|
63
|
+
}
|
|
64
|
+
return payload;
|
|
65
|
+
} finally {
|
|
66
|
+
closeSync(fd);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function readStableContainedLink(linkPath: string, inspected: Stats, canonicalRoot: string): Buffer {
|
|
71
|
+
const canonicalTarget = realpathSync(linkPath);
|
|
72
|
+
if (!contained(canonicalRoot, canonicalTarget)) {
|
|
73
|
+
throw new Error(`symbolic link escapes the extension root: ${linkPath}`);
|
|
74
|
+
}
|
|
75
|
+
const target = readlinkSync(linkPath);
|
|
76
|
+
const after = lstatSync(linkPath);
|
|
77
|
+
if (!after.isSymbolicLink() || !sameFileState(inspected, after)) {
|
|
78
|
+
throw new Error(`symbolic link changed while being hashed: ${linkPath}`);
|
|
79
|
+
}
|
|
80
|
+
const canonicalAfter = realpathSync(linkPath);
|
|
81
|
+
if (canonicalAfter !== canonicalTarget || !contained(canonicalRoot, canonicalAfter)) {
|
|
82
|
+
throw new Error(`symbolic link changed or escapes the extension root: ${linkPath}`);
|
|
83
|
+
}
|
|
84
|
+
return Buffer.from(target, "utf8");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function assertStableDirectory(
|
|
88
|
+
directoryPath: string,
|
|
89
|
+
inspected: Stats,
|
|
90
|
+
canonicalDirectory: string,
|
|
91
|
+
canonicalRoot: string,
|
|
92
|
+
expectedEntries?: readonly string[],
|
|
93
|
+
): void {
|
|
94
|
+
const after = lstatSync(directoryPath);
|
|
95
|
+
if (!after.isDirectory() || !sameFileState(inspected, after)) {
|
|
96
|
+
throw new Error(`extension directory changed while being hashed: ${directoryPath}`);
|
|
97
|
+
}
|
|
98
|
+
const canonicalAfter = realpathSync(directoryPath);
|
|
99
|
+
if (canonicalAfter !== canonicalDirectory || !contained(canonicalRoot, canonicalAfter)) {
|
|
100
|
+
throw new Error(`extension directory changed or escapes the extension root: ${directoryPath}`);
|
|
101
|
+
}
|
|
102
|
+
if (expectedEntries !== undefined) {
|
|
103
|
+
const currentEntries = readdirSync(directoryPath).sort();
|
|
104
|
+
if (
|
|
105
|
+
currentEntries.length !== expectedEntries.length ||
|
|
106
|
+
currentEntries.some((entry, index) => entry !== expectedEntries[index])
|
|
107
|
+
) {
|
|
108
|
+
throw new Error(`extension directory entries changed while being hashed: ${directoryPath}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readStableDirectory(
|
|
114
|
+
directoryPath: string,
|
|
115
|
+
inspected: Stats,
|
|
116
|
+
canonicalRoot: string,
|
|
117
|
+
): { canonicalDirectory: string; entries: string[] } {
|
|
118
|
+
const canonicalDirectory = realpathSync(directoryPath);
|
|
119
|
+
if (!contained(canonicalRoot, canonicalDirectory)) {
|
|
120
|
+
throw new Error(`extension directory escapes the extension root: ${directoryPath}`);
|
|
121
|
+
}
|
|
122
|
+
const entries = readdirSync(directoryPath).sort();
|
|
123
|
+
assertStableDirectory(directoryPath, inspected, canonicalDirectory, canonicalRoot);
|
|
124
|
+
return { canonicalDirectory, entries };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Hash an extension tree in stable relative-path order. File names, node kinds,
|
|
129
|
+
* symlink targets, empty directories, and file bytes are all integrity-bound.
|
|
130
|
+
* Symlinks must resolve within the package so their content cannot drift outside
|
|
131
|
+
* the tree without changing the digest.
|
|
132
|
+
*/
|
|
133
|
+
export function extensionContentDigestWithCapture(
|
|
134
|
+
root: string,
|
|
135
|
+
options: ExtensionContentDigestOptions = {},
|
|
136
|
+
): ExtensionContentDigestResult {
|
|
137
|
+
const resolvedRoot = path.resolve(root);
|
|
138
|
+
const rootStat = lstatSync(resolvedRoot);
|
|
139
|
+
if (rootStat.isSymbolicLink()) {
|
|
140
|
+
throw new Error(`extension root must be a directory, not a symbolic link: ${resolvedRoot}`);
|
|
141
|
+
}
|
|
142
|
+
if (!rootStat.isDirectory()) {
|
|
143
|
+
throw new Error(`extension root must be a directory: ${resolvedRoot}`);
|
|
144
|
+
}
|
|
145
|
+
const canonicalRoot = realpathSync(resolvedRoot);
|
|
146
|
+
const canonicalizedRootStat = lstatSync(resolvedRoot);
|
|
147
|
+
if (!canonicalizedRootStat.isDirectory() || !sameFileState(rootStat, canonicalizedRootStat)) {
|
|
148
|
+
throw new Error(`extension root changed while being canonicalized: ${resolvedRoot}`);
|
|
149
|
+
}
|
|
150
|
+
const hash = createHash("sha256");
|
|
151
|
+
const requested = new Set((options.capture ?? []).map((entry) => entry.replaceAll(path.sep, "/")));
|
|
152
|
+
const captured = new Map<string, Buffer>();
|
|
153
|
+
const visit = (absolutePath: string, relativePath: string): void => {
|
|
154
|
+
const stat = lstatSync(absolutePath);
|
|
155
|
+
if (stat.isSymbolicLink()) {
|
|
156
|
+
if (relativePath === "") {
|
|
157
|
+
throw new Error(`extension root changed to a symbolic link while being hashed: ${absolutePath}`);
|
|
158
|
+
}
|
|
159
|
+
digestFrame(hash, "link", relativePath, readStableContainedLink(absolutePath, stat, canonicalRoot));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (stat.isDirectory()) {
|
|
163
|
+
digestFrame(hash, "directory", relativePath);
|
|
164
|
+
const { canonicalDirectory, entries } = readStableDirectory(absolutePath, stat, canonicalRoot);
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
visit(path.join(absolutePath, entry), relativePath ? path.join(relativePath, entry) : entry);
|
|
167
|
+
}
|
|
168
|
+
assertStableDirectory(absolutePath, stat, canonicalDirectory, canonicalRoot, entries);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (stat.isFile()) {
|
|
172
|
+
if (stat.nlink !== 1) {
|
|
173
|
+
throw new Error(`hard-linked file is unsupported in an extension: ${absolutePath}`);
|
|
174
|
+
}
|
|
175
|
+
const payload = readStableRegularFile(absolutePath, stat);
|
|
176
|
+
digestFrame(hash, "file", relativePath, payload);
|
|
177
|
+
const key = relativePath.replaceAll(path.sep, "/");
|
|
178
|
+
if (requested.has(key)) captured.set(key, payload);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
throw new Error(`unsupported filesystem entry in extension: ${absolutePath}`);
|
|
182
|
+
};
|
|
183
|
+
visit(resolvedRoot, "");
|
|
184
|
+
return { digest: hash.digest("hex"), captured };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function extensionContentDigest(root: string): string {
|
|
188
|
+
return extensionContentDigestWithCapture(root).digest;
|
|
189
|
+
}
|
|
@@ -4,27 +4,43 @@ export {
|
|
|
4
4
|
findExtensionManifestPath,
|
|
5
5
|
parseExtensionManifest,
|
|
6
6
|
} from "./discovery.js";
|
|
7
|
-
export { enabledExtensionResourceRoots, extensionResourcePath } from "./resources.js";
|
|
7
|
+
export { enabledExtensionResourceRoots, extensionResourcePath, extensionSnapshotFor } from "./resources.js";
|
|
8
|
+
export type { InstalledExtensionRecord } from "./state.js";
|
|
8
9
|
export {
|
|
9
10
|
disableExtension,
|
|
10
11
|
enableExtension,
|
|
11
12
|
extensionBaseDir,
|
|
12
13
|
installExtension,
|
|
14
|
+
listInstalledExtensionRecords,
|
|
13
15
|
listInstalledExtensions,
|
|
14
16
|
removeExtension,
|
|
17
|
+
upgradeLegacyExtensionInstallState,
|
|
15
18
|
} from "./state.js";
|
|
16
19
|
export type {
|
|
17
20
|
ClioExtensionManifest,
|
|
18
21
|
ExtensionCandidate,
|
|
19
22
|
ExtensionDiagnostic,
|
|
23
|
+
ExtensionHookSource,
|
|
20
24
|
ExtensionInstallOptions,
|
|
21
25
|
ExtensionInstallResult,
|
|
22
26
|
ExtensionListOptions,
|
|
23
27
|
ExtensionManifestResources,
|
|
24
28
|
ExtensionMutationResult,
|
|
29
|
+
ExtensionProvenance,
|
|
30
|
+
ExtensionReloadCandidate,
|
|
31
|
+
ExtensionReloadCommitted,
|
|
32
|
+
ExtensionReloadPrepareResult,
|
|
33
|
+
ExtensionReloadRejection,
|
|
34
|
+
ExtensionReloadRejectionReason,
|
|
35
|
+
ExtensionReloadResult,
|
|
25
36
|
ExtensionResourceKind,
|
|
26
37
|
ExtensionResourceRoot,
|
|
27
38
|
ExtensionScope,
|
|
39
|
+
ExtensionSnapshot,
|
|
40
|
+
ExtensionSnapshotDiagnostics,
|
|
28
41
|
ExtensionState,
|
|
42
|
+
ExtensionStateUpgradeReport,
|
|
29
43
|
InstalledExtension,
|
|
44
|
+
LoadableExtension,
|
|
30
45
|
} from "./types.js";
|
|
46
|
+
export { isLoadableExtension } from "./types.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { realpathSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function extensionResourcePath(rootPath: string, resourcePath: string): string | null {
|
|
5
|
+
const root = path.resolve(rootPath);
|
|
6
|
+
const full = path.resolve(root, resourcePath);
|
|
7
|
+
const relative = path.relative(root, full);
|
|
8
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const rootReal = realpathSync(root);
|
|
13
|
+
const fullReal = realpathSync(full);
|
|
14
|
+
const canonicalRelative = path.relative(rootReal, fullReal);
|
|
15
|
+
if (
|
|
16
|
+
canonicalRelative === ".." ||
|
|
17
|
+
canonicalRelative.startsWith(`..${path.sep}`) ||
|
|
18
|
+
path.isAbsolute(canonicalRelative)
|
|
19
|
+
) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (!statSync(fullReal).isDirectory()) return null;
|
|
23
|
+
return fullReal;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,50 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { buildExtensionSnapshot } from "./snapshot.js";
|
|
4
|
+
import { committedExtensionSnapshot } from "./snapshot-store.js";
|
|
5
|
+
import type { ExtensionResourceKind, ExtensionResourceRoot, ExtensionSnapshot } from "./types.js";
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
export { extensionResourcePath } from "./resource-path.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The committed snapshot when a store is bound for this cwd; otherwise an
|
|
11
|
+
* ephemeral generation-0 build that never touches the store. The ephemeral
|
|
12
|
+
* branch serves the CLI, `config inspect` with an explicit cwd, and any
|
|
13
|
+
* process that never booted the extensions bundle.
|
|
14
|
+
*/
|
|
15
|
+
export function extensionSnapshotFor(cwd = process.cwd()): ExtensionSnapshot {
|
|
16
|
+
const committed = committedExtensionSnapshot();
|
|
12
17
|
try {
|
|
13
|
-
|
|
14
|
-
const fullReal = realpathSync(full);
|
|
15
|
-
const canonicalRelative = path.relative(rootReal, fullReal);
|
|
16
|
-
if (
|
|
17
|
-
canonicalRelative === ".." ||
|
|
18
|
-
canonicalRelative.startsWith(`..${path.sep}`) ||
|
|
19
|
-
path.isAbsolute(canonicalRelative)
|
|
20
|
-
) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
if (!lstatSync(full).isDirectory()) return null;
|
|
24
|
-
return full;
|
|
18
|
+
if (committed !== null && committed.cwd === realpathSync(path.resolve(cwd))) return committed;
|
|
25
19
|
} catch {
|
|
26
|
-
|
|
20
|
+
// Fall through to an ephemeral build, which retains the original error semantics.
|
|
27
21
|
}
|
|
22
|
+
return buildExtensionSnapshot({ cwd, generation: 0 });
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
export function enabledExtensionResourceRoots(
|
|
31
26
|
kind: ExtensionResourceKind,
|
|
32
27
|
cwd = process.cwd(),
|
|
33
28
|
): ExtensionResourceRoot[] {
|
|
34
|
-
|
|
35
|
-
for (const entry of listInstalledExtensions(cwd)) {
|
|
36
|
-
if (!entry.enabled || !entry.compatible || !entry.effective) continue;
|
|
37
|
-
const rel = entry.resources[kind];
|
|
38
|
-
if (!rel) continue;
|
|
39
|
-
const full = extensionResourcePath(entry.rootPath, rel);
|
|
40
|
-
if (!full) continue;
|
|
41
|
-
roots.push({
|
|
42
|
-
id: entry.id,
|
|
43
|
-
scope: entry.scope,
|
|
44
|
-
path: full,
|
|
45
|
-
rootPath: entry.rootPath,
|
|
46
|
-
source: `extension:${entry.scope}:${entry.id}`,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
return roots;
|
|
29
|
+
return [...extensionSnapshotFor(cwd).resourceRoots[kind]];
|
|
50
30
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ExtensionSnapshot } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Holder for the committed snapshot reference plus the process-local
|
|
5
|
+
* generation counter. `publish` is assignment-only: validation that a
|
|
6
|
+
* snapshot is strictly newer belongs to the candidate that prepared it, on
|
|
7
|
+
* the same stack, immediately before publishing.
|
|
8
|
+
*/
|
|
9
|
+
export interface ExtensionSnapshotStore {
|
|
10
|
+
current(): ExtensionSnapshot | null;
|
|
11
|
+
publish(snapshot: ExtensionSnapshot): void;
|
|
12
|
+
/** Reserve and return the next process-local generation. Reservations are never reused. */
|
|
13
|
+
nextGeneration(): number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createExtensionSnapshotStore(): ExtensionSnapshotStore {
|
|
17
|
+
let committed: ExtensionSnapshot | null = null;
|
|
18
|
+
let reservedGeneration = 0;
|
|
19
|
+
return {
|
|
20
|
+
current: () => committed,
|
|
21
|
+
publish(snapshot) {
|
|
22
|
+
committed = snapshot;
|
|
23
|
+
},
|
|
24
|
+
nextGeneration() {
|
|
25
|
+
reservedGeneration += 1;
|
|
26
|
+
return reservedGeneration;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let boundStore: ExtensionSnapshotStore | null = null;
|
|
32
|
+
|
|
33
|
+
export function committedExtensionSnapshot(): ExtensionSnapshot | null {
|
|
34
|
+
return boundStore?.current() ?? null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function bindExtensionSnapshotStore(store: ExtensionSnapshotStore | null): void {
|
|
38
|
+
boundStore = store;
|
|
39
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { extensionResourcePath } from "./resource-path.js";
|
|
6
|
+
import { type InstalledExtensionRecord, listInstalledExtensionRecords } from "./state.js";
|
|
7
|
+
import {
|
|
8
|
+
type ExtensionDiagnostic,
|
|
9
|
+
type ExtensionHookSource,
|
|
10
|
+
type ExtensionResourceKind,
|
|
11
|
+
type ExtensionResourceRoot,
|
|
12
|
+
type ExtensionSnapshot,
|
|
13
|
+
type ExtensionSnapshotDiagnostics,
|
|
14
|
+
isLoadableExtension,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
export const EXTENSION_SNAPSHOT_DIAGNOSTIC_CAP = 200;
|
|
18
|
+
export const EXTENSION_SNAPSHOT_DIAGNOSTIC_PER_PACKAGE_CAP = 20;
|
|
19
|
+
export const EXTENSION_SNAPSHOT_DIAGNOSTIC_MESSAGE_CAP = 512;
|
|
20
|
+
|
|
21
|
+
const RESOURCE_KINDS: ReadonlyArray<ExtensionResourceKind> = ["skills", "prompts", "agents", "fleets", "themes"];
|
|
22
|
+
|
|
23
|
+
export interface BuildExtensionSnapshotInput {
|
|
24
|
+
cwd: string;
|
|
25
|
+
generation: number;
|
|
26
|
+
now?: () => Date;
|
|
27
|
+
listRecords?: (cwd?: string, options?: { all?: boolean }) => InstalledExtensionRecord[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function canonicalJson(value: unknown): string {
|
|
31
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
32
|
+
if (value !== null && typeof value === "object") {
|
|
33
|
+
const record = value as Record<string, unknown>;
|
|
34
|
+
return `{${Object.keys(record)
|
|
35
|
+
.sort()
|
|
36
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
|
37
|
+
.join(",")}}`;
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(value) ?? "null";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function deepFreeze<T>(value: T, seen = new Set<object>()): T {
|
|
43
|
+
if (value === null || typeof value !== "object" || seen.has(value as object)) return value;
|
|
44
|
+
seen.add(value as object);
|
|
45
|
+
for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child, seen);
|
|
46
|
+
return Object.freeze(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function boundedDiagnostics(records: ReadonlyArray<InstalledExtensionRecord>): ExtensionSnapshotDiagnostics {
|
|
50
|
+
const entries: Array<ExtensionDiagnostic & { extensionId?: string }> = [];
|
|
51
|
+
let truncated = 0;
|
|
52
|
+
for (const { entry } of records) {
|
|
53
|
+
let admittedForPackage = 0;
|
|
54
|
+
for (const diagnostic of entry.diagnostics) {
|
|
55
|
+
if (
|
|
56
|
+
admittedForPackage >= EXTENSION_SNAPSHOT_DIAGNOSTIC_PER_PACKAGE_CAP ||
|
|
57
|
+
entries.length >= EXTENSION_SNAPSHOT_DIAGNOSTIC_CAP
|
|
58
|
+
) {
|
|
59
|
+
truncated += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
entries.push({
|
|
63
|
+
type: diagnostic.type,
|
|
64
|
+
message: diagnostic.message.slice(0, EXTENSION_SNAPSHOT_DIAGNOSTIC_MESSAGE_CAP),
|
|
65
|
+
...(diagnostic.path ? { path: diagnostic.path } : {}),
|
|
66
|
+
extensionId: entry.id,
|
|
67
|
+
});
|
|
68
|
+
admittedForPackage += 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { entries, truncated };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function buildExtensionSnapshot(input: BuildExtensionSnapshotInput): ExtensionSnapshot {
|
|
75
|
+
const cwd = realpathSync(path.resolve(input.cwd));
|
|
76
|
+
const records = (input.listRecords ?? listInstalledExtensionRecords)(cwd, { all: true });
|
|
77
|
+
const resourceRoots: Record<ExtensionResourceKind, ExtensionResourceRoot[]> = {
|
|
78
|
+
skills: [],
|
|
79
|
+
prompts: [],
|
|
80
|
+
agents: [],
|
|
81
|
+
fleets: [],
|
|
82
|
+
themes: [],
|
|
83
|
+
};
|
|
84
|
+
const hookSources: ExtensionHookSource[] = [];
|
|
85
|
+
const digestPackages: unknown[] = [];
|
|
86
|
+
|
|
87
|
+
for (const record of records) {
|
|
88
|
+
const { entry } = record;
|
|
89
|
+
const packageRoots: Array<{ kind: ExtensionResourceKind; path: string }> = [];
|
|
90
|
+
let hooksDigest: string | undefined;
|
|
91
|
+
if (isLoadableExtension(entry)) {
|
|
92
|
+
for (const kind of RESOURCE_KINDS) {
|
|
93
|
+
const relative = entry.resources[kind];
|
|
94
|
+
if (!relative) continue;
|
|
95
|
+
const resolved = extensionResourcePath(entry.rootPath, relative);
|
|
96
|
+
if (!resolved) continue;
|
|
97
|
+
resourceRoots[kind].push({
|
|
98
|
+
id: entry.id,
|
|
99
|
+
scope: entry.scope,
|
|
100
|
+
path: resolved,
|
|
101
|
+
rootPath: entry.rootPath,
|
|
102
|
+
source: `extension:${entry.scope}:${entry.id}`,
|
|
103
|
+
provenance: entry.provenance,
|
|
104
|
+
generation: input.generation,
|
|
105
|
+
});
|
|
106
|
+
packageRoots.push({ kind, path: resolved });
|
|
107
|
+
}
|
|
108
|
+
const hookBytes = record.captured?.get("hooks.yaml");
|
|
109
|
+
if (hookBytes !== undefined) {
|
|
110
|
+
hooksDigest = createHash("sha256").update(hookBytes).digest("hex");
|
|
111
|
+
let declarations: unknown = [];
|
|
112
|
+
let parseError: string | undefined;
|
|
113
|
+
try {
|
|
114
|
+
declarations = parseYaml(hookBytes.toString("utf8")) ?? [];
|
|
115
|
+
} catch (error) {
|
|
116
|
+
parseError = error instanceof Error ? error.message : String(error);
|
|
117
|
+
}
|
|
118
|
+
hookSources.push({
|
|
119
|
+
provenance: entry.provenance,
|
|
120
|
+
declarationsDigest: hooksDigest,
|
|
121
|
+
declarations,
|
|
122
|
+
...(parseError ? { parseError } : {}),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
digestPackages.push({
|
|
127
|
+
id: entry.id,
|
|
128
|
+
scope: entry.scope,
|
|
129
|
+
loadable: entry.loadable,
|
|
130
|
+
...(entry.provenance ? { provenance: entry.provenance } : {}),
|
|
131
|
+
resourceRoots: packageRoots,
|
|
132
|
+
...(hooksDigest ? { hooksDigest } : {}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const snapshot: ExtensionSnapshot = {
|
|
137
|
+
version: 1,
|
|
138
|
+
generation: input.generation,
|
|
139
|
+
cwd,
|
|
140
|
+
builtAt: (input.now ?? (() => new Date()))().toISOString(),
|
|
141
|
+
digest: createHash("sha256").update(canonicalJson(digestPackages)).digest("hex"),
|
|
142
|
+
packages: records.map((record) => record.entry),
|
|
143
|
+
resourceRoots,
|
|
144
|
+
hookSources,
|
|
145
|
+
diagnostics: boundedDiagnostics(records),
|
|
146
|
+
};
|
|
147
|
+
return deepFreeze(snapshot);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function packageFingerprints(snapshot: ExtensionSnapshot): Map<string, string> {
|
|
151
|
+
const grouped = new Map<string, unknown[]>();
|
|
152
|
+
for (const entry of snapshot.packages) {
|
|
153
|
+
const list = grouped.get(entry.id) ?? [];
|
|
154
|
+
list.push({
|
|
155
|
+
scope: entry.scope,
|
|
156
|
+
loadable: entry.loadable,
|
|
157
|
+
provenance: entry.provenance ?? null,
|
|
158
|
+
resources: entry.resources,
|
|
159
|
+
});
|
|
160
|
+
grouped.set(entry.id, list);
|
|
161
|
+
}
|
|
162
|
+
return new Map(
|
|
163
|
+
[...grouped].map(([id, values]) => [id, createHash("sha256").update(canonicalJson(values)).digest("hex")]),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function diffExtensionSnapshots(
|
|
168
|
+
previous: ExtensionSnapshot | null,
|
|
169
|
+
next: ExtensionSnapshot,
|
|
170
|
+
): { changed: boolean; added: string[]; removed: string[]; modified: string[] } {
|
|
171
|
+
if (previous === null) {
|
|
172
|
+
return { changed: true, added: [...new Set(next.packages.map((entry) => entry.id))], removed: [], modified: [] };
|
|
173
|
+
}
|
|
174
|
+
const before = packageFingerprints(previous);
|
|
175
|
+
const after = packageFingerprints(next);
|
|
176
|
+
const added = [...after.keys()].filter((id) => !before.has(id));
|
|
177
|
+
const removed = [...before.keys()].filter((id) => !after.has(id));
|
|
178
|
+
const modified = [...after.keys()].filter((id) => before.has(id) && before.get(id) !== after.get(id));
|
|
179
|
+
return { changed: previous.digest !== next.digest, added, removed, modified };
|
|
180
|
+
}
|