@iowarp/clio-coder 0.4.3 → 0.4.4
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 +63 -0
- package/README.md +1 -1
- package/dist/{acp-H2NGRPWO.js → acp-WNAYYF4F.js} +4 -5
- package/dist/{agents-TL5LLUQP.js → agents-3OKXHLOI.js} +33 -31
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-E5SW4HMS.js → auth-VKNNMGPU.js} +10 -11
- package/dist/{builtins-IA7V7FUC.js → builtins-WGALA46I.js} +4 -4
- package/dist/{chunk-F5JHEYZM.js → chunk-23L32XTI.js} +10 -7
- package/dist/{chunk-DWUOQKRU.js → chunk-25QBEXRS.js} +2 -2
- package/dist/{chunk-LLDJM5XK.js → chunk-26QSH3EJ.js} +2 -2
- package/dist/{chunk-N2Z7HLVY.js → chunk-2ASED4PZ.js} +10 -10
- package/dist/{chunk-MCEPRMZW.js → chunk-2CU2H6KE.js} +2 -2
- package/dist/chunk-2DSOYNFC.js +108 -0
- package/dist/{chunk-LDJG7DW3.js → chunk-2ZSONWVL.js} +4 -4
- package/dist/{chunk-GI7YYQ3F.js → chunk-36CT5VVL.js} +324 -41
- package/dist/{chunk-4UVU7BJ5.js → chunk-3GY4F45V.js} +2 -2
- package/dist/{chunk-PUVDKJ2Y.js → chunk-3ODX73FK.js} +3 -5
- package/dist/{chunk-IUE3Y34X.js → chunk-3UNOLWNZ.js} +2 -2
- package/dist/{chunk-6PTFB5VS.js → chunk-3UUXNFEX.js} +8 -4
- package/dist/{chunk-2APPQIER.js → chunk-4M6Z5QVF.js} +4 -4
- package/dist/{chunk-K6BSR66V.js → chunk-4NSRCOYP.js} +4 -1
- package/dist/{chunk-JGRC33J2.js → chunk-54X7T7DK.js} +12 -3
- package/dist/{chunk-YQWYVTMC.js → chunk-5636DCO5.js} +4 -4
- package/dist/{chunk-7E7I3WLS.js → chunk-57XXR6DR.js} +23 -22
- package/dist/{chunk-7ZYNNDKC.js → chunk-5TUB6SLS.js} +2 -2
- package/dist/{chunk-VW6DOEDG.js → chunk-6OSVSQL5.js} +11 -2
- package/dist/{chunk-L47TF46W.js → chunk-6PAZTBPA.js} +14 -2
- package/dist/{chunk-XIVNBFZS.js → chunk-6Q3CYFD3.js} +34 -16
- package/dist/{chunk-RSJ25QSL.js → chunk-6QOTUPRG.js} +155 -36
- package/dist/{chunk-JKKCYP3C.js → chunk-6UINWWS6.js} +10 -4
- package/dist/{chunk-WBKFA554.js → chunk-72YIHOZQ.js} +2 -2
- package/dist/{chunk-PAJQJ7BS.js → chunk-75W7L2E2.js} +246 -697
- package/dist/{chunk-RRNP2ANY.js → chunk-7UGL4MB5.js} +4 -4
- package/dist/{chunk-EKCHAPYA.js → chunk-AUPNRN7C.js} +2 -2
- package/dist/{chunk-ZDN3Y73Y.js → chunk-BJVFZO5U.js} +6 -48
- package/dist/{chunk-IWT4SF4R.js → chunk-CUSRQKPU.js} +64 -2
- package/dist/{chunk-SKHCAU7K.js → chunk-DQOVN6KV.js} +3 -2
- package/dist/{chunk-ZWPRK62N.js → chunk-DT3LWJOB.js} +4 -4
- package/dist/chunk-DXKJURES.js +671 -0
- package/dist/{chunk-BEPZRGGU.js → chunk-EL24TAU4.js} +2 -2
- package/dist/{chunk-OML5D5V5.js → chunk-ELWDPP3Y.js} +5 -5
- package/dist/{chunk-NDINPTJ4.js → chunk-ELZVTCGV.js} +5 -4
- package/dist/chunk-EXLD33WO.js +381 -0
- package/dist/{chunk-IJNZMHLA.js → chunk-FEAXX7B6.js} +2 -2
- package/dist/{chunk-TM6LQDI3.js → chunk-FFUPXJC4.js} +74 -323
- package/dist/chunk-GX5WYQO4.js +59 -0
- package/dist/chunk-I2DWJ4GM.js +390 -0
- package/dist/{chunk-TXOTCRLG.js → chunk-I5FWO7L5.js} +5 -5
- package/dist/chunk-IRXAATOX.js +539 -0
- package/dist/chunk-IXIY2H4R.js +44 -0
- package/dist/{chunk-KK4JZPBQ.js → chunk-IZXGRF7P.js} +76 -10
- package/dist/{chunk-5TSRNF4G.js → chunk-JCI2ROMZ.js} +164 -6
- package/dist/{chunk-INY6HTFL.js → chunk-JEIYHLOR.js} +2 -2
- package/dist/{chunk-MUW2BDDH.js → chunk-JQLNNIKT.js} +2 -2
- package/dist/{chunk-XPWWI35G.js → chunk-JSD46VO2.js} +19 -8
- package/dist/{chunk-VA5FNYMT.js → chunk-JT2RFCC5.js} +39 -2
- package/dist/{chunk-V2ANDPVT.js → chunk-K6T2ZAMZ.js} +168 -6
- package/dist/{chunk-B4OAX3SI.js → chunk-KFV5L5SK.js} +13 -6
- package/dist/{chunk-JEQ3XTHC.js → chunk-LLXSDWXS.js} +2 -2
- package/dist/{chunk-E3TPLWFX.js → chunk-LTIKRKFL.js} +3 -3
- package/dist/{chunk-NIQJ66N4.js → chunk-N56KALIC.js} +17 -17
- package/dist/{chunk-JDAY6FIL.js → chunk-NAI6ZFCY.js} +13 -9
- package/dist/{chunk-AF4YM7Z4.js → chunk-NRO2BJRH.js} +2490 -2182
- package/dist/{chunk-WRBAGUNF.js → chunk-NXIMQY5W.js} +2 -2
- package/dist/{chunk-I7ZPNEJM.js → chunk-NXYCB2VD.js} +5 -5
- package/dist/chunk-ODGTEFFI.js +50 -0
- package/dist/{chunk-2VIKGWFZ.js → chunk-OEJSLEPW.js} +2 -2
- package/dist/{chunk-KKOJXO6R.js → chunk-OMQNJVKW.js} +4 -2
- package/dist/{chunk-54CBCGIR.js → chunk-Q4WO54TA.js} +132 -77
- package/dist/{chunk-B4VEBZKF.js → chunk-QUFRYSWI.js} +12 -6
- package/dist/{chunk-42FMPA75.js → chunk-QZWQA4DE.js} +2 -2
- package/dist/{chunk-HEQY7ZFI.js → chunk-RAY4OVGZ.js} +2 -2
- package/dist/{chunk-G76U63X4.js → chunk-RQCKCSRL.js} +7 -7
- package/dist/{chunk-64I3JVYM.js → chunk-RXTN6AKH.js} +2 -2
- package/dist/{chunk-UPZU6GE4.js → chunk-RZDWV63N.js} +3 -3
- package/dist/{chunk-2UH2KFUP.js → chunk-S6PYF2XF.js} +2 -2
- package/dist/{chunk-JSC3U7TI.js → chunk-TOIVGRUX.js} +2 -2
- package/dist/{chunk-7DRAWPTZ.js → chunk-TQAHXW6Y.js} +1 -1
- package/dist/{chunk-JIEGK6UF.js → chunk-U6TMQNSI.js} +48 -4
- package/dist/{chunk-AX2THNSA.js → chunk-UEPWCCTY.js} +4 -4
- package/dist/{chunk-QWGDJJYJ.js → chunk-USR47QNF.js} +4 -4
- package/dist/{chunk-Y3CBHOR6.js → chunk-V6HJFQZE.js} +2 -2
- package/dist/chunk-V76WTFTW.js +318 -0
- package/dist/{chunk-NMJXSHBJ.js → chunk-W54I7H25.js} +2 -2
- package/dist/{chunk-2UG5F4C5.js → chunk-XULDXHTN.js} +24 -12
- package/dist/chunk-XXYSBZIQ.js +283 -0
- package/dist/{chunk-MWUZBSAQ.js → chunk-Y55JBDO5.js} +331 -51
- package/dist/{chunk-W6RRQCPQ.js → chunk-YD5GIKET.js} +4 -4
- package/dist/{chunk-CE5AX47J.js → chunk-YECAMM3D.js} +2 -2
- package/dist/{chunk-WCXUNS7U.js → chunk-YNFKXPEC.js} +6 -6
- package/dist/cli/index.js +36 -35
- package/dist/{clio-CMMK4KRR.js → clio-QLICPCF5.js} +2 -2
- package/dist/{code-nav-MDZNQS33.js → code-nav-IJR2DBPR.js} +6 -6
- package/dist/{components-UCUQ4QXW.js → components-2TGAI2RC.js} +3 -4
- package/dist/{config-SVM5P5YI.js → config-IUA6OYNS.js} +52 -47
- package/dist/{configure-LE3IK2TJ.js → configure-VEPX4NMX.js} +14 -15
- package/dist/{context-VNCR7KAG.js → context-2DKHWH2T.js} +23 -23
- package/dist/{context-2OHRKS42.js → context-4MPR7WKB.js} +48 -42
- package/dist/{context-E3VC7RX5.js → context-BOYF5EJM.js} +11 -11
- package/dist/{context-clear-BW4O37TG.js → context-clear-S4ZJCQUX.js} +48 -42
- package/dist/{context-working-set-VDS25HXZ.js → context-working-set-3I3FYX6Y.js} +8 -8
- package/dist/detail-A7JAVSIG.js +98 -0
- package/dist/{dispatch-runner-5AHT53RF.js → dispatch-runner-RJ5I2F2O.js} +71 -58
- package/dist/{docs-PD3EXDKU.js → docs-SPOV3BAN.js} +3 -5
- package/dist/{doctor-WNNVO6FY.js → doctor-DKICC2SN.js} +54 -31
- package/dist/{eval-7G7SGAYO.js → eval-OQOQUDHK.js} +47 -56
- package/dist/{evidence-VD6736FQ.js → evidence-4DQ25GUQ.js} +53 -150
- package/dist/evidence-4F5USFKH.js +208 -0
- package/dist/{evolve-AL3NGVRL.js → evolve-GSS52E5J.js} +44 -41
- package/dist/{extensions-MOVJ32NM.js → extensions-G7MFLYHT.js} +4 -5
- package/dist/{fleet-QZHUMAGI.js → fleet-Q37YHHAQ.js} +74 -68
- package/dist/{fleet-commands-BAYT5FJZ.js → fleet-commands-G7E4N7SM.js} +14 -11
- package/dist/{fleet-decisions-IREVMRU4.js → fleet-decisions-O7M6QBA2.js} +6 -6
- package/dist/{fleet-graph-YCTT3HTI.js → fleet-graph-TOUBW6OW.js} +13 -13
- package/dist/{fleet-inspect-QVJTDAVB.js → fleet-inspect-SW33JJNI.js} +43 -39
- package/dist/{fleet-preflight-25QAFPK4.js → fleet-preflight-CV2655TW.js} +2 -3
- package/dist/{fleet-validate-5O57AAJ7.js → fleet-validate-KESZX2YH.js} +16 -16
- package/dist/{fleet-verify-CPH2W2T6.js → fleet-verify-UQPMTVE3.js} +42 -38
- package/dist/{fleet-view-SWBR3VGQ.js → fleet-view-MN2VG4MR.js} +43 -39
- package/dist/{init-J477LKZH.js → init-PXEXQSBF.js} +60 -54
- package/dist/{interop-3FCM6XLG.js → interop-ZG5T62U3.js} +6 -7
- package/dist/inventory-C26CFDRR.js +101 -0
- package/dist/{library-QUQEIUG6.js → library-B2W4N74O.js} +16 -17
- package/dist/{memory-SGGSEP65.js → memory-YCANYS5A.js} +45 -42
- package/dist/{models-HEKUAXXK.js → models-GERTU3YI.js} +20 -22
- package/dist/{monitor-HKU57TYQ.js → monitor-CPNIUULB.js} +50 -44
- package/dist/{orchestrator-VDFAEFAI.js → orchestrator-J4BSH4WQ.js} +491 -1182
- package/dist/{panes-DN2SSFOH.js → panes-BOHAEGYC.js} +3 -3
- package/dist/{panes-TALGNPZT.js → panes-NXSLDQZ2.js} +5 -6
- package/dist/{paths-NBMFAIEZ.js → paths-VSUWNC22.js} +3 -4
- package/dist/{reset-EAJFFJVB.js → reset-TNWTB5LU.js} +5 -6
- package/dist/{resources-OVKSEFVE.js → resources-4PXNMD5G.js} +14 -14
- package/dist/{run-7DP7ZF2J.js → run-D6XJ34CN.js} +79 -83
- package/dist/{share-WML67FT3.js → share-2NWMJJEE.js} +14 -15
- package/dist/{skills-SG662R2K.js → skills-KR7WON5G.js} +18 -19
- package/dist/{skills-eval-VVZEUU46.js → skills-eval-O2ZNOLDS.js} +49 -46
- package/dist/{skills-inventory-I2E23GET.js → skills-inventory-ZZOUBK7O.js} +14 -14
- package/dist/{slash-commands-S7MBJDQK.js → slash-commands-ZXPJD64J.js} +32 -23
- package/dist/{steer-2LQOMCPB.js → steer-XA25PSCS.js} +3 -3
- package/dist/{support-CC2UJBJ6.js → support-7EMVWYG2.js} +2 -2
- package/dist/{targets-4QC3HIEW.js → targets-OMH2XCSN.js} +25 -27
- package/dist/tasks-IPAGMEIX.js +36 -0
- package/dist/{terminal-lease-TUHIJ6Y2.js → terminal-lease-C2J3JYRE.js} +4 -4
- package/dist/{tools-TFGJICCU.js → tools-EFFEAIDP.js} +5 -6
- package/dist/{uninstall-5PEVOE5B.js → uninstall-HALS6BLF.js} +7 -8
- package/dist/{upgrade-M4WXY6KN.js → upgrade-MS72RJEP.js} +14 -11
- package/dist/{usage-N7ZNVLEM.js → usage-NHG6MCJM.js} +60 -51
- package/dist/{verifiers-DJTP4XX6.js → verifiers-7AUNVXDY.js} +150 -17
- package/dist/{verify-RWE4PPEK.js → verify-FWYGPKMR.js} +12 -10
- package/dist/{web-fetch-MPARV2K7.js → web-fetch-V4FKSDAV.js} +4 -4
- package/dist/{wiki-generate-C7IQOXSP.js → wiki-generate-743CIGJW.js} +63 -56
- package/dist/{with-panes-4GCGSL7J.js → with-panes-BDQEWBRT.js} +2 -2
- package/dist/worker/entry.js +36 -33
- package/docs/README.md +3 -2
- package/docs/architecture/acp.md +17 -0
- package/docs/architecture/artifact-versions.md +1 -1
- package/docs/architecture/dispatch-typed-intent.md +1 -1
- package/docs/architecture/evidence-and-memory.md +1 -1
- package/docs/architecture/observability.md +7 -1
- package/docs/architecture/prompt-envelope-and-tools.md +15 -5
- package/docs/architecture/safety-model.md +10 -17
- package/docs/architecture/tui-design.md +1 -1
- package/docs/guide/built-in-agents.md +8 -8
- package/docs/guide/commands-and-modes.md +17 -2
- package/docs/guide/configuration-and-targets.md +3 -1
- package/docs/guide/configuration-reference.md +10 -5
- package/docs/guide/environment-variables.md +2 -2
- package/docs/guide/tool-usage.md +78 -3
- package/docs/history/config-knobs-audit.md +2 -2
- package/docs/process/development-pipeline.md +6 -1
- package/docs/process/git-commit-provenance.md +15 -0
- package/docs/process/release-cut-checklist.md +207 -0
- package/docs/process/scientific-validation.md +18 -17
- package/evals/behavioral-machinery-support.ts +1 -0
- package/evals/behavioral-machinery.yaml +1 -1
- package/package.json +1 -1
- package/skills/coding/coding-standards/SKILL.md +14 -4
- package/skills/context/context-handoff/SKILL.md +3 -3
- package/skills/context/context-prime/SKILL.md +3 -3
- package/skills/planning/architecture/SKILL.md +5 -5
- package/skills/planning/backlog/SKILL.md +4 -4
- package/skills/planning/prd/SKILL.md +4 -3
- package/skills/planning/product-intent/SKILL.md +8 -8
- package/skills/planning/tech-spec/SKILL.md +4 -4
- package/skills/registry.yaml +32 -32
- package/skills/research/arxiv-literature/SKILL.md +3 -3
- package/skills/research/experiment-protocol/SKILL.md +3 -3
- package/skills/research/scientific-debugging/SKILL.md +3 -2
- package/skills/research/scientific-modernization/SKILL.md +3 -3
- package/skills/skill-marketplace.json +16 -16
- package/skills/workflow/cut-it/SKILL.md +3 -4
- package/skills/workflow/design-council/SKILL.md +5 -10
- package/skills/workflow/grill-me/SKILL.md +14 -15
- package/skills/workflow/workflow-distiller/SKILL.md +4 -4
- package/src/cli/args.ts +0 -8
- package/src/cli/configure.ts +2 -1
- package/src/cli/doctor-state-size.ts +1 -12
- package/src/cli/doctor-validation-contract.ts +28 -0
- package/src/cli/doctor.ts +5 -0
- package/src/cli/evidence-detail.ts +1 -75
- package/src/cli/evidence-inventory.ts +1 -167
- package/src/cli/index.ts +2 -0
- package/src/cli/run.ts +0 -2
- package/src/cli/targets.ts +1 -1
- package/src/cli/tasks.ts +84 -0
- package/src/cli/upgrade.ts +10 -5
- package/src/cli/usage.ts +6 -0
- package/src/cli/verifiers.ts +147 -1
- package/src/cli/wiki-generate.ts +1 -0
- package/src/core/commit-attribution.ts +41 -1
- package/src/core/git-commit-attribution.ts +46 -3
- package/src/core/run-overrides.ts +0 -5
- package/src/core/skill-activation.ts +3 -0
- package/src/core/tool-names.ts +5 -2
- package/src/domains/agents/builtins/architect.md +1 -1
- package/src/domains/agents/builtins/coder.md +1 -1
- package/src/domains/agents/builtins/documenter.md +1 -1
- package/src/domains/agents/builtins/git-master.md +1 -1
- package/src/domains/agents/builtins/provenance.md +7 -7
- package/src/domains/agents/builtins/tester.md +1 -1
- package/src/domains/agents/builtins/verifier.md +2 -2
- package/src/domains/agents/builtins/wiki-writer.md +4 -3
- package/src/domains/context/extension.ts +31 -7
- package/src/domains/context/refresh.ts +3 -0
- package/src/domains/context/wiki/frontmatter.ts +5 -2
- package/src/domains/context/wiki/generate.ts +6 -0
- package/src/domains/context/wiki/prompts.ts +43 -0
- package/src/domains/dispatch/active-route-planner.ts +4 -0
- package/src/domains/dispatch/code-step.ts +11 -4
- package/src/domains/dispatch/contract.ts +31 -7
- package/src/domains/dispatch/execution-scheduler.ts +2 -0
- package/src/domains/dispatch/extension.ts +50 -27
- package/src/domains/dispatch/fleet-commit-attribution.ts +5 -0
- package/src/domains/dispatch/fleet-run.ts +1 -0
- package/src/domains/dispatch/host-verification.ts +114 -13
- package/src/domains/dispatch/intent.ts +28 -18
- package/src/domains/dispatch/orphan-recovery.ts +2 -0
- package/src/domains/dispatch/receipt-integrity.ts +4 -0
- package/src/domains/dispatch/reservation-store.ts +5 -3
- package/src/domains/dispatch/state.ts +15 -2
- package/src/domains/dispatch/types.ts +17 -2
- package/src/domains/eval/runners/clio-run.ts +12 -9
- package/src/domains/eval/runners/context-index.ts +2 -7
- package/src/domains/eval/runners/context-init.ts +3 -6
- package/src/domains/eval/runners/external-command.ts +29 -11
- package/src/domains/evidence/build.ts +102 -15
- package/src/domains/evidence/detail.ts +69 -0
- package/src/domains/evidence/eval.ts +13 -1
- package/src/domains/evidence/finish-contract-map.ts +5 -1
- package/src/domains/evidence/inventory.ts +167 -0
- package/src/domains/evidence/store.ts +16 -0
- package/src/domains/evidence/types.ts +12 -0
- package/src/domains/extensions/resources.ts +7 -0
- package/src/domains/middleware/marketplace-offer.ts +20 -1
- package/src/domains/middleware/runtime.ts +7 -3
- package/src/domains/mux/detect.ts +3 -6
- package/src/domains/observability/accountability.ts +15 -1
- package/src/domains/observability/contract.ts +52 -7
- package/src/domains/observability/evidence-index.ts +10 -0
- package/src/domains/observability/extension.ts +9 -5
- package/src/domains/observability/projection.ts +394 -45
- package/src/{interactive → domains/observability}/worker-progress.ts +3 -3
- package/src/domains/prompts/fragments/operating/contract.md +2 -0
- package/src/domains/prompts/fragments/wiki/page.md +8 -0
- package/src/domains/providers/model-discovery.ts +1 -4
- package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +16 -14
- package/src/domains/providers/runtimes/claude/claude-code.ts +9 -0
- package/src/domains/providers/support.ts +1 -1
- package/src/domains/providers/types/runtime-descriptor.ts +1 -1
- package/src/domains/safety/action-classifier.ts +7 -0
- package/src/domains/safety/default-path-policy.ts +2 -0
- package/src/domains/safety/finish-contract-registration.ts +29 -14
- package/src/domains/safety/finish-contract.ts +252 -40
- package/src/domains/safety/index.ts +20 -1
- package/src/domains/safety/policy-engine.ts +26 -6
- package/src/domains/safety/rigor.ts +53 -39
- package/src/domains/safety/validation-contract.ts +388 -0
- package/src/domains/session/archive-readers.ts +10 -1
- package/src/domains/session/decision-board.ts +101 -2
- package/src/domains/session/entries.ts +44 -7
- package/src/domains/session/extension.ts +4 -4
- package/src/domains/session/handoff.ts +2 -1
- package/src/domains/session/manager.ts +2 -3
- package/src/domains/session/task-board.ts +14 -1
- package/src/domains/session/tree/fork.ts +1 -2
- package/src/domains/session/tree/navigator.ts +1 -1
- package/src/domains/user-tasks/acceptance.ts +56 -0
- package/src/domains/user-tasks/active-acceptance.ts +40 -0
- package/src/domains/user-tasks/store.ts +34 -3
- package/src/engine/acp/adapter.ts +24 -6
- package/src/engine/acp/server.ts +21 -4
- package/src/engine/acp/transport.ts +53 -8
- package/src/engine/acp/types.ts +4 -0
- package/src/engine/apis/ollama-native.ts +15 -0
- package/src/engine/claude/subprocess-runtime.ts +107 -60
- package/src/engine/external-subprocess.ts +12 -4
- package/src/entry/orchestrator.ts +11 -2
- package/src/interactive/dispatch-board.ts +46 -717
- package/src/interactive/fleet-run-preview.ts +2 -1
- package/src/interactive/interactive-application.ts +3 -2
- package/src/interactive/interactive-presentation.ts +55 -12
- package/src/interactive/interactive-slash-runtime.ts +2 -1
- package/src/interactive/oracle.ts +5 -2
- package/src/interactive/overlays/fleet-run-approval.ts +3 -2
- package/src/interactive/overlays/message-picker.ts +2 -2
- package/src/interactive/overlays/settings.ts +2 -2
- package/src/interactive/overlays/tree-selector.ts +2 -2
- package/src/interactive/renderers/branch-summary.ts +1 -1
- package/src/interactive/slash-autocomplete.ts +4 -6
- package/src/interactive/slash-commands.ts +25 -39
- package/src/interactive/slash-spec.ts +28 -0
- package/src/interactive/view/artifacts.ts +2 -0
- package/src/interactive/worker-stream.ts +7 -3
- package/src/tools/bootstrap.ts +4 -0
- package/src/tools/builtin-tool-catalog.ts +31 -0
- package/src/tools/compete-worktrees.ts +7 -1
- package/src/tools/core-bootstrap.ts +16 -0
- package/src/tools/decide.ts +136 -0
- package/src/tools/dispatch-admission.ts +21 -0
- package/src/tools/dispatch-plan.ts +5 -2
- package/src/tools/dispatch-runner.ts +15 -1
- package/src/tools/dispatch-types.ts +6 -0
- package/src/tools/evidence.ts +96 -0
- package/src/tools/limitation.ts +76 -0
- package/src/tools/policy.ts +9 -0
- package/src/tools/presentation.ts +3 -0
- package/src/tools/registry.ts +1 -1
- package/src/tools/result-shaping.ts +17 -5
- package/src/tools/task-worktree.ts +13 -3
- package/src/tools/tasks.ts +10 -1
- package/src/tools/verify/authoring.ts +170 -83
- package/src/tools/verify/catalog.ts +122 -5
- package/src/tools/verify/index.ts +2 -1
- package/src/tools/verify/numeric.ts +298 -0
- package/src/tools/verify/perf.ts +143 -0
- package/src/tools/verify/scripts.ts +229 -2
- package/dist/chunk-7DICMOS6.js +0 -313
- package/dist/chunk-RVG5JXAL.js +0 -41
- package/dist/chunk-T56WDKA5.js +0 -183
- package/dist/chunk-VPKWYKEY.js +0 -169
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
3
|
+
import type { DecisionBoardStore } from "../domains/session/decision-board.js";
|
|
4
|
+
import { decisionRef } from "../domains/session/entries.js";
|
|
5
|
+
import type { ToolResult, ToolSpec } from "./registry.js";
|
|
6
|
+
import { byteLength } from "./truncate-utf8.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The model's own design decisions, recorded on the session decision board
|
|
10
|
+
* beside the operator's `ask_user` answers. A `decide` call appends one
|
|
11
|
+
* `decisionLedger` entry with `origin: "agent"` and one `DecisionRecord` with
|
|
12
|
+
* `source: "agent"`, the alternatives the model rejected, and why. Dispatch
|
|
13
|
+
* seals every active decision's ref onto the run envelope and receipt, and
|
|
14
|
+
* the commit seams turn the same refs into `Clio-Decision:` trailers, so a
|
|
15
|
+
* receipt or a commit names the choices it was made under.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const DECIDE_CAPS = Object.freeze({
|
|
19
|
+
keyBytes: 64,
|
|
20
|
+
valueBytes: 512,
|
|
21
|
+
alternatives: 6,
|
|
22
|
+
alternativeBytes: 256,
|
|
23
|
+
rationaleBytes: 1024,
|
|
24
|
+
labelBytes: 128,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const KEBAB_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
28
|
+
|
|
29
|
+
export interface DecideToolDeps {
|
|
30
|
+
decisionBoard?: DecisionBoardStore;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function boundedText(value: unknown, field: string, cap: number): string | Error {
|
|
34
|
+
if (typeof value !== "string") return new Error(`decide: ${field} must be a string`);
|
|
35
|
+
const trimmed = value.trim();
|
|
36
|
+
if (trimmed.length === 0) return new Error(`decide: ${field} is required`);
|
|
37
|
+
if (byteLength(trimmed) > cap) return new Error(`decide: ${field} exceeds the ${cap}-byte cap`);
|
|
38
|
+
return trimmed;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function boundedAlternatives(value: unknown): string[] | Error {
|
|
42
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
43
|
+
return new Error("decide: alternatives must list at least one option you rejected");
|
|
44
|
+
}
|
|
45
|
+
if (value.length > DECIDE_CAPS.alternatives) {
|
|
46
|
+
return new Error(`decide: alternatives exceeds the ${DECIDE_CAPS.alternatives}-entry cap`);
|
|
47
|
+
}
|
|
48
|
+
const alternatives: string[] = [];
|
|
49
|
+
for (const [index, entry] of value.entries()) {
|
|
50
|
+
const text = boundedText(entry, `alternatives[${index}]`, DECIDE_CAPS.alternativeBytes);
|
|
51
|
+
if (text instanceof Error) return text;
|
|
52
|
+
alternatives.push(text);
|
|
53
|
+
}
|
|
54
|
+
return alternatives;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createDecideTool(deps: DecideToolDeps = {}): ToolSpec {
|
|
58
|
+
return {
|
|
59
|
+
name: ToolNames.Decide,
|
|
60
|
+
description:
|
|
61
|
+
"Record a design decision you made between two or more viable options, with the alternatives you rejected and why. Call it before implementing the choice; a later call with the same key supersedes the earlier record.",
|
|
62
|
+
parameters: Type.Object({
|
|
63
|
+
key: Type.String({
|
|
64
|
+
description: `Stable kebab-case name for the decision, e.g. "cache-key-shape" (at most ${DECIDE_CAPS.keyBytes} bytes).`,
|
|
65
|
+
}),
|
|
66
|
+
value: Type.String({ description: `The option you chose (at most ${DECIDE_CAPS.valueBytes} bytes).` }),
|
|
67
|
+
alternatives: Type.Array(Type.String(), {
|
|
68
|
+
description: `Options you considered and rejected, 1 to ${DECIDE_CAPS.alternatives} entries.`,
|
|
69
|
+
}),
|
|
70
|
+
rationale: Type.String({
|
|
71
|
+
description: `Why the chosen option won over the alternatives (at most ${DECIDE_CAPS.rationaleBytes} bytes).`,
|
|
72
|
+
}),
|
|
73
|
+
label: Type.Optional(Type.String({ description: "Short human-readable title for the decision." })),
|
|
74
|
+
}),
|
|
75
|
+
// Read class on purpose: the only effect is one session-ledger append,
|
|
76
|
+
// so it must stay available at every autonomy level without a prompt.
|
|
77
|
+
baseActionClass: "read",
|
|
78
|
+
executionMode: "sequential",
|
|
79
|
+
async run(args): Promise<ToolResult> {
|
|
80
|
+
const key = boundedText(args.key, "key", DECIDE_CAPS.keyBytes);
|
|
81
|
+
if (key instanceof Error) return { kind: "error", message: key.message };
|
|
82
|
+
if (!KEBAB_KEY.test(key)) {
|
|
83
|
+
return { kind: "error", message: "decide: key must be kebab-case (lowercase letters, digits, single hyphens)" };
|
|
84
|
+
}
|
|
85
|
+
const value = boundedText(args.value, "value", DECIDE_CAPS.valueBytes);
|
|
86
|
+
if (value instanceof Error) return { kind: "error", message: value.message };
|
|
87
|
+
const alternatives = boundedAlternatives(args.alternatives);
|
|
88
|
+
if (alternatives instanceof Error) return { kind: "error", message: alternatives.message };
|
|
89
|
+
const rationale = boundedText(args.rationale, "rationale", DECIDE_CAPS.rationaleBytes);
|
|
90
|
+
if (rationale instanceof Error) return { kind: "error", message: rationale.message };
|
|
91
|
+
let label: string | undefined;
|
|
92
|
+
if (args.label !== undefined && args.label !== null) {
|
|
93
|
+
const bounded = boundedText(args.label, "label", DECIDE_CAPS.labelBytes);
|
|
94
|
+
if (bounded instanceof Error) return { kind: "error", message: bounded.message };
|
|
95
|
+
label = bounded;
|
|
96
|
+
}
|
|
97
|
+
if (deps.decisionBoard === undefined) {
|
|
98
|
+
return { kind: "error", message: "decide: no session decision board is available in this context" };
|
|
99
|
+
}
|
|
100
|
+
let outcome: ReturnType<DecisionBoardStore["recordAgentDecision"]>;
|
|
101
|
+
try {
|
|
102
|
+
outcome = deps.decisionBoard.recordAgentDecision({
|
|
103
|
+
key,
|
|
104
|
+
value,
|
|
105
|
+
alternatives,
|
|
106
|
+
rationale,
|
|
107
|
+
...(label !== undefined ? { label } : {}),
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return { kind: "error", message: error instanceof Error ? error.message : String(error) };
|
|
111
|
+
}
|
|
112
|
+
const ref = decisionRef(outcome.entry.interviewId, key);
|
|
113
|
+
const supersededNote =
|
|
114
|
+
outcome.superseded === null
|
|
115
|
+
? ""
|
|
116
|
+
: ` (supersedes ${decisionRef(outcome.superseded.interviewId, outcome.superseded.key)})`;
|
|
117
|
+
return {
|
|
118
|
+
kind: "ok",
|
|
119
|
+
output: `decision recorded: ${key} = ${value} [${ref}]${supersededNote}\n`,
|
|
120
|
+
details: {
|
|
121
|
+
decision: {
|
|
122
|
+
ref,
|
|
123
|
+
key,
|
|
124
|
+
value,
|
|
125
|
+
alternatives,
|
|
126
|
+
rationale,
|
|
127
|
+
...(label !== undefined ? { label } : {}),
|
|
128
|
+
...(outcome.superseded !== null
|
|
129
|
+
? { superseded: decisionRef(outcome.superseded.interviewId, outcome.superseded.key) }
|
|
130
|
+
: {}),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
@@ -246,6 +246,27 @@ export function createDispatchAdmissionController(deps: DispatchToolDeps): Dispa
|
|
|
246
246
|
argv: [...check.command],
|
|
247
247
|
cwd: resolve(process.cwd(), check.cwd),
|
|
248
248
|
timeoutMs: entry.timeoutMs,
|
|
249
|
+
// A judged check seals its kind and absolute file paths here, so
|
|
250
|
+
// host verification judges against what the catalog declared at
|
|
251
|
+
// admission rather than whatever the file says at run time.
|
|
252
|
+
...(check.kind !== "command" ? { kind: check.kind } : {}),
|
|
253
|
+
...(check.numeric !== undefined
|
|
254
|
+
? {
|
|
255
|
+
numeric: {
|
|
256
|
+
reference: resolve(process.cwd(), check.numeric.reference),
|
|
257
|
+
tolerance: { ...check.numeric.tolerance },
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
: {}),
|
|
261
|
+
...(check.perf !== undefined
|
|
262
|
+
? {
|
|
263
|
+
perf: {
|
|
264
|
+
...(check.perf.budget !== undefined ? { budget: structuredClone(check.perf.budget) } : {}),
|
|
265
|
+
...(check.perf.baseline !== undefined ? { baseline: resolve(process.cwd(), check.perf.baseline) } : {}),
|
|
266
|
+
...(check.perf.tolerance !== undefined ? { tolerance: { ...check.perf.tolerance } } : {}),
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
: {}),
|
|
249
270
|
};
|
|
250
271
|
}),
|
|
251
272
|
};
|
|
@@ -487,7 +487,7 @@ function renderPlanText(
|
|
|
487
487
|
lines.push(...renderLegacyPathScope(task, cwd));
|
|
488
488
|
for (const check of task.resolvedVerification ?? []) {
|
|
489
489
|
lines.push(
|
|
490
|
-
` verification check=${safeField(check.check)} argv=${JSON.stringify(check.argv)} cwd=${JSON.stringify(check.cwd)} timeout_ms=${check.timeoutMs}`,
|
|
490
|
+
` verification check=${safeField(check.check)}${check.kind !== undefined ? ` kind=${check.kind}` : ""} argv=${JSON.stringify(check.argv)} cwd=${JSON.stringify(check.cwd)} timeout_ms=${check.timeoutMs}`,
|
|
491
491
|
);
|
|
492
492
|
}
|
|
493
493
|
}
|
|
@@ -616,7 +616,10 @@ function isResolvedTask(value: unknown): value is ResolvedDispatchPlanArtifact["
|
|
|
616
616
|
Array.isArray(check.argv) &&
|
|
617
617
|
check.argv.every((arg) => typeof arg === "string") &&
|
|
618
618
|
typeof check.cwd === "string" &&
|
|
619
|
-
typeof check.timeoutMs === "number"
|
|
619
|
+
typeof check.timeoutMs === "number" &&
|
|
620
|
+
(check.kind === undefined || check.kind === "numeric-compare" || check.kind === "perf-budget") &&
|
|
621
|
+
(check.numeric === undefined || (isRecord(check.numeric) && typeof check.numeric.reference === "string")) &&
|
|
622
|
+
(check.perf === undefined || isRecord(check.perf)),
|
|
620
623
|
))
|
|
621
624
|
)
|
|
622
625
|
return false;
|
|
@@ -50,6 +50,7 @@ import { extractRunProvenance, provenanceCompactSuffix } from "../domains/eviden
|
|
|
50
50
|
import { summarizeTrustStatus } from "../domains/evidence/trust-projection.js";
|
|
51
51
|
import { adaptRunReceiptTrustStatus } from "../domains/evidence/trust-status.js";
|
|
52
52
|
import type { AutonomyLevel } from "../domains/safety/autonomy.js";
|
|
53
|
+
import { activeDecisionRefs } from "../domains/session/decision-board.js";
|
|
53
54
|
import {
|
|
54
55
|
type CandidateWorktree,
|
|
55
56
|
type CompeteGroupOwnership,
|
|
@@ -802,6 +803,7 @@ async function runReviewGated(
|
|
|
802
803
|
...(review.target !== undefined ? { target: review.target } : {}),
|
|
803
804
|
...(base.plan !== undefined ? { plan: base.plan } : {}),
|
|
804
805
|
...(base.parentToolCallId !== undefined ? { parentToolCallId: base.parentToolCallId } : {}),
|
|
806
|
+
...(base.decisionRefs !== undefined ? { decisionRefs: base.decisionRefs } : {}),
|
|
805
807
|
...(base.reservation !== undefined
|
|
806
808
|
? { reservation: { ownerId: base.reservation.ownerId, memberId: base.reservation.memberId } }
|
|
807
809
|
: {}),
|
|
@@ -1334,7 +1336,11 @@ async function runCompete(
|
|
|
1334
1336
|
const receipt = candidateRuns[index]?.receipt;
|
|
1335
1337
|
const failed = receipt !== undefined && isPipelineStepFailure(receipt);
|
|
1336
1338
|
if (failed) return "builder failed";
|
|
1337
|
-
|
|
1339
|
+
// The receipt records the candidate's effective authority after recipe
|
|
1340
|
+
// and invocation narrowing, independently of the coordinator's autonomy.
|
|
1341
|
+
if (receipt?.autonomyEnforcement?.autonomy !== "read-only") {
|
|
1342
|
+
commitCandidateWork(worktree, `clio-coder compete ${group} candidate ${worktree.index}`);
|
|
1343
|
+
}
|
|
1338
1344
|
return candidateDiffStat(root, worktree.branch);
|
|
1339
1345
|
});
|
|
1340
1346
|
if (candidateRuns.every((run) => isPipelineStepFailure(run.receipt))) {
|
|
@@ -1367,6 +1373,7 @@ async function runCompete(
|
|
|
1367
1373
|
...(compete.judge?.node !== undefined ? { node: compete.judge.node } : {}),
|
|
1368
1374
|
...(base.plan !== undefined ? { plan: base.plan } : {}),
|
|
1369
1375
|
...(base.parentToolCallId !== undefined ? { parentToolCallId: base.parentToolCallId } : {}),
|
|
1376
|
+
...(base.decisionRefs !== undefined ? { decisionRefs: base.decisionRefs } : {}),
|
|
1370
1377
|
...(base.reservation !== undefined
|
|
1371
1378
|
? { reservation: { ownerId: base.reservation.ownerId, memberId: base.reservation.memberId } }
|
|
1372
1379
|
: {}),
|
|
@@ -2010,6 +2017,7 @@ async function runCouncil(
|
|
|
2010
2017
|
...(council.judge?.node ? { node: council.judge.node } : {}),
|
|
2011
2018
|
...(base.plan ? { plan: base.plan } : {}),
|
|
2012
2019
|
...(base.parentToolCallId ? { parentToolCallId: base.parentToolCallId } : {}),
|
|
2020
|
+
...(base.decisionRefs !== undefined ? { decisionRefs: base.decisionRefs } : {}),
|
|
2013
2021
|
};
|
|
2014
2022
|
const pinned = council.resolvedTasks?.find((task) => task.role === "synthesis");
|
|
2015
2023
|
const judgeRun = await runMember(
|
|
@@ -2189,6 +2197,12 @@ export async function runDispatchTool(
|
|
|
2189
2197
|
parentToolCallId === undefined || parentToolCallId.length === 0
|
|
2190
2198
|
? structuredClone(snapshot.requests)
|
|
2191
2199
|
: snapshot.requests.map((request) => ({ ...structuredClone(request), parentToolCallId }));
|
|
2200
|
+
// Seal the parent board's active decisions onto every request on the same
|
|
2201
|
+
// terms as the parent id: from the live board here, never from model
|
|
2202
|
+
// arguments (the validation projection strips the field). Derived reviewer,
|
|
2203
|
+
// judge, and synthesis requests copy it from their base explicitly.
|
|
2204
|
+
const decisionRefs = activeDecisionRefs(deps.getDecisionBoard?.() ?? []);
|
|
2205
|
+
if (decisionRefs.length > 0) requests = requests.map((request) => ({ ...request, decisionRefs }));
|
|
2192
2206
|
if (planView.planScale && deps.dispatch.preview !== undefined && resolvedPlan === null) {
|
|
2193
2207
|
return { kind: "error", message: "dispatch: resolved plan is missing after admission" };
|
|
2194
2208
|
}
|
|
@@ -3,6 +3,7 @@ import type { AgentSpec } from "../domains/agents/spec.js";
|
|
|
3
3
|
import type { DispatchContract, DispatchRequest } from "../domains/dispatch/contract.js";
|
|
4
4
|
import type { AgentRoleFactsResolver } from "../domains/dispatch/execution-role.js";
|
|
5
5
|
import type { AutonomyLevel } from "../domains/safety/autonomy.js";
|
|
6
|
+
import type { DecisionLedgerEntry } from "../domains/session/entries.js";
|
|
6
7
|
import type {
|
|
7
8
|
CandidateWorktree,
|
|
8
9
|
CompeteGroupOwnership,
|
|
@@ -108,4 +109,9 @@ export interface DispatchToolDeps {
|
|
|
108
109
|
getWorkerRosters?: () => Readonly<Record<string, { members: ReadonlyArray<DispatchCouncilMember> }>>;
|
|
109
110
|
/** Which optional schema blocks this session advertises; absent means every block. */
|
|
110
111
|
getSchemaComposition?: () => DispatchSchemaComposition;
|
|
112
|
+
/**
|
|
113
|
+
* Live decision board snapshot. Every request the tool builds carries the
|
|
114
|
+
* active decisions' refs, sealed here and never from model arguments.
|
|
115
|
+
*/
|
|
116
|
+
getDecisionBoard?: () => ReadonlyArray<DecisionLedgerEntry>;
|
|
111
117
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { assertSafeId } from "../core/safe-id.js";
|
|
3
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
4
|
+
import { clioDataDir, clioStateDir } from "../core/xdg.js";
|
|
5
|
+
import { StringEnum } from "../engine/ai.js";
|
|
6
|
+
import type { ToolResult, ToolSpec } from "./registry.js";
|
|
7
|
+
import { truncateUtf8 } from "./truncate-utf8.js";
|
|
8
|
+
|
|
9
|
+
export const EVIDENCE_TOOL_MAX_BYTES = 16_384;
|
|
10
|
+
|
|
11
|
+
/** Keep truncated results valid JSON and never hide an unbounded copy in details. */
|
|
12
|
+
function boundedResult(value: unknown): ToolResult {
|
|
13
|
+
const serialized = JSON.stringify(value, null, 2);
|
|
14
|
+
if (Buffer.byteLength(serialized, "utf8") <= EVIDENCE_TOOL_MAX_BYTES) {
|
|
15
|
+
return { kind: "ok", output: serialized, details: { truncated: false } };
|
|
16
|
+
}
|
|
17
|
+
const hint = "Inspect one run to narrow the output; this preview is incomplete JSON text.";
|
|
18
|
+
let preview = truncateUtf8(serialized, EVIDENCE_TOOL_MAX_BYTES, "");
|
|
19
|
+
let output = JSON.stringify({ truncated: true, hint, preview });
|
|
20
|
+
while (Buffer.byteLength(output, "utf8") > EVIDENCE_TOOL_MAX_BYTES) {
|
|
21
|
+
const excess = Buffer.byteLength(output, "utf8") - EVIDENCE_TOOL_MAX_BYTES;
|
|
22
|
+
preview = truncateUtf8(preview, Math.max(0, Buffer.byteLength(preview, "utf8") - excess), "");
|
|
23
|
+
output = JSON.stringify({ truncated: true, hint, preview });
|
|
24
|
+
}
|
|
25
|
+
return { kind: "ok", output, details: { truncated: true } };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const evidenceTool: ToolSpec = {
|
|
29
|
+
name: ToolNames.Evidence,
|
|
30
|
+
description:
|
|
31
|
+
"Read canonical evidence as JSON. List recent bundles, inspect a bundle's overview, trust axes and tier, gate decisions, and findings, or build/read evidence for a run.",
|
|
32
|
+
parameters: Type.Object({
|
|
33
|
+
mode: StringEnum(["list", "inspect", "run"]),
|
|
34
|
+
id: Type.Optional(Type.String({ description: "Evidence bundle id for inspect." })),
|
|
35
|
+
runId: Type.Optional(Type.String({ description: "Run id for run." })),
|
|
36
|
+
}),
|
|
37
|
+
baseActionClass: "read",
|
|
38
|
+
// Run mode may materialize a derived bundle in Clio's data directory.
|
|
39
|
+
executionMode: "sequential",
|
|
40
|
+
async run(args): Promise<ToolResult> {
|
|
41
|
+
const mode = args.mode;
|
|
42
|
+
if (mode !== "list" && mode !== "inspect" && mode !== "run") {
|
|
43
|
+
return { kind: "error", message: "evidence: mode must be list, inspect, or run" };
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const dataDir = clioDataDir();
|
|
47
|
+
if (mode === "list") {
|
|
48
|
+
const { evidenceInventorySnapshot } = await import("../domains/evidence/inventory.js");
|
|
49
|
+
return boundedResult(await evidenceInventorySnapshot(Date.now, dataDir));
|
|
50
|
+
}
|
|
51
|
+
const id = mode === "inspect" ? args.id : args.runId;
|
|
52
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
53
|
+
return { kind: "error", message: `evidence: ${mode} requires ${mode === "inspect" ? "id" : "runId"}` };
|
|
54
|
+
}
|
|
55
|
+
assertSafeId(id, mode === "inspect" ? "evidence" : "run");
|
|
56
|
+
const { buildEvidence, EvidenceNotFoundError, inspectEvidence, loadEvidenceGateDecisions } = await import(
|
|
57
|
+
"../domains/evidence/index.js"
|
|
58
|
+
);
|
|
59
|
+
const { evidenceDetailSnapshot } = await import("../domains/evidence/detail.js");
|
|
60
|
+
let evidenceId = mode === "run" ? `run-${id}` : id;
|
|
61
|
+
let inspected: Awaited<ReturnType<typeof inspectEvidence>>;
|
|
62
|
+
try {
|
|
63
|
+
inspected = await inspectEvidence(dataDir, evidenceId);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (mode !== "run" || !(error instanceof EvidenceNotFoundError)) throw error;
|
|
66
|
+
try {
|
|
67
|
+
evidenceId = (await buildEvidence({ dataDir, stateDir: clioStateDir(), runId: id })).evidenceId;
|
|
68
|
+
} catch (buildError) {
|
|
69
|
+
if (
|
|
70
|
+
buildError instanceof Error &&
|
|
71
|
+
(buildError.message === "run ledger not found" || buildError.message === `run not found: ${id}`)
|
|
72
|
+
)
|
|
73
|
+
throw new EvidenceNotFoundError(evidenceId);
|
|
74
|
+
throw buildError;
|
|
75
|
+
}
|
|
76
|
+
inspected = await inspectEvidence(dataDir, evidenceId);
|
|
77
|
+
}
|
|
78
|
+
return boundedResult({
|
|
79
|
+
...inspected,
|
|
80
|
+
...(await evidenceDetailSnapshot(evidenceId, Date.now, dataDir)),
|
|
81
|
+
gateDecisions: await loadEvidenceGateDecisions(dataDir, evidenceId),
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const absent = error instanceof Error && error.name === "EvidenceNotFoundError";
|
|
85
|
+
return {
|
|
86
|
+
kind: "error",
|
|
87
|
+
message: truncateUtf8(
|
|
88
|
+
`evidence: ${error instanceof Error ? error.message : String(error)}`,
|
|
89
|
+
EVIDENCE_TOOL_MAX_BYTES - 3,
|
|
90
|
+
"...",
|
|
91
|
+
),
|
|
92
|
+
details: { code: absent ? "artifact_absent" : "evidence_error", artifactAbsent: absent },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
3
|
+
import { StringEnum } from "../engine/ai.js";
|
|
4
|
+
import type { ToolResult, ToolSpec } from "./registry.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The typed limitation signal the finish contract reads. Calling the tool
|
|
8
|
+
* records a `limitation` tool_call and tool_result pair in the session ledger,
|
|
9
|
+
* and that successful receipt is what `assessFinishContract` accepts in place
|
|
10
|
+
* of validation evidence. The tool is pure: it touches no filesystem and runs
|
|
11
|
+
* no shell, so the receipt itself is its whole effect.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const LIMITATION_REASONS = ["no-runner", "blocked", "out-of-scope", "environment", "other"] as const;
|
|
15
|
+
|
|
16
|
+
export type LimitationReason = (typeof LIMITATION_REASONS)[number];
|
|
17
|
+
|
|
18
|
+
export interface LimitationRecord {
|
|
19
|
+
scope: string;
|
|
20
|
+
reason: LimitationReason;
|
|
21
|
+
paths: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isLimitationReason(value: unknown): value is LimitationReason {
|
|
25
|
+
return typeof value === "string" && (LIMITATION_REASONS as ReadonlyArray<string>).includes(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function trimString(value: unknown): string | null {
|
|
29
|
+
if (typeof value !== "string") return null;
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizePaths(value: unknown): string[] | null {
|
|
35
|
+
if (value === undefined || value === null) return [];
|
|
36
|
+
if (!Array.isArray(value)) return null;
|
|
37
|
+
const paths: string[] = [];
|
|
38
|
+
for (const item of value) {
|
|
39
|
+
const path = trimString(item);
|
|
40
|
+
if (path === null) return null;
|
|
41
|
+
if (!paths.includes(path)) paths.push(path);
|
|
42
|
+
}
|
|
43
|
+
return paths;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const limitationTool: ToolSpec = {
|
|
47
|
+
name: ToolNames.Limitation,
|
|
48
|
+
description:
|
|
49
|
+
"Record what this turn could not verify and why. Call it once, before your final reply, when you changed files but could not run validation; the receipt stands in for validation evidence at the finish gate.",
|
|
50
|
+
parameters: Type.Object({
|
|
51
|
+
scope: Type.String({ description: "What could not be verified, in one sentence." }),
|
|
52
|
+
reason: StringEnum(LIMITATION_REASONS, {
|
|
53
|
+
description:
|
|
54
|
+
"no-runner: no test or build runner is available; blocked: a safety gate or denial stopped the check; out-of-scope: verification was excluded by the task; environment: the environment cannot run the check; other.",
|
|
55
|
+
}),
|
|
56
|
+
paths: Type.Optional(Type.Array(Type.String(), { description: "Repository-relative paths left unverified." })),
|
|
57
|
+
}),
|
|
58
|
+
baseActionClass: "read",
|
|
59
|
+
executionMode: "parallel",
|
|
60
|
+
async run(args): Promise<ToolResult> {
|
|
61
|
+
const scope = trimString(args.scope);
|
|
62
|
+
if (scope === null) return { kind: "error", message: "limitation: scope is required" };
|
|
63
|
+
if (!isLimitationReason(args.reason)) {
|
|
64
|
+
return { kind: "error", message: `limitation: reason must be one of ${LIMITATION_REASONS.join(", ")}` };
|
|
65
|
+
}
|
|
66
|
+
const paths = normalizePaths(args.paths);
|
|
67
|
+
if (paths === null) return { kind: "error", message: "limitation: paths must be an array of non-empty strings" };
|
|
68
|
+
const limitation: LimitationRecord = { scope, reason: args.reason, paths };
|
|
69
|
+
const pathSuffix = paths.length > 0 ? ` paths=${paths.join(",")}` : "";
|
|
70
|
+
return {
|
|
71
|
+
kind: "ok",
|
|
72
|
+
output: `limitation recorded: ${scope} (reason=${limitation.reason}${pathSuffix})\n`,
|
|
73
|
+
details: { limitation },
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
package/src/tools/policy.ts
CHANGED
|
@@ -21,6 +21,7 @@ interface PlaneExpectation {
|
|
|
21
21
|
|
|
22
22
|
/** One row per registered tool: plane, action class, and concurrency rule. */
|
|
23
23
|
export const TOOL_PLANES: Readonly<Record<BuiltinToolName, PlaneExpectation>> = {
|
|
24
|
+
[ToolNames.Evidence]: { plane: "observe", actionClass: "read", executionMode: "sequential" },
|
|
24
25
|
[ToolNames.Read]: { plane: "observe", actionClass: "read", executionMode: "parallel" },
|
|
25
26
|
[ToolNames.Grep]: { plane: "observe", actionClass: "read", executionMode: "parallel" },
|
|
26
27
|
[ToolNames.Find]: { plane: "observe", actionClass: "read", executionMode: "parallel" },
|
|
@@ -53,6 +54,14 @@ export const TOOL_PLANES: Readonly<Record<BuiltinToolName, PlaneExpectation>> =
|
|
|
53
54
|
// panes. It mutates no workspace, so it stays read class; sequential because
|
|
54
55
|
// two pane operations in one batch would race the same pane registry.
|
|
55
56
|
[ToolNames.Panes]: { plane: "orchestrate", actionClass: "read", executionMode: "sequential" },
|
|
57
|
+
// limitation records a typed receipt in the session ledger and nothing
|
|
58
|
+
// else: no filesystem, no shell. Read class so it never trips a safety
|
|
59
|
+
// gate; parallel because the call is pure and idempotent.
|
|
60
|
+
[ToolNames.Limitation]: { plane: "orchestrate", actionClass: "read", executionMode: "parallel" },
|
|
61
|
+
// decide appends one decision-board entry to the session ledger and
|
|
62
|
+
// nothing else. Read class so it never trips a safety gate; sequential so
|
|
63
|
+
// two decisions in one batch never race the supersede lookup.
|
|
64
|
+
[ToolNames.Decide]: { plane: "orchestrate", actionClass: "read", executionMode: "sequential" },
|
|
56
65
|
[ToolNames.WebFetch]: { plane: "retrieve", actionClass: "read", executionMode: "parallel" },
|
|
57
66
|
[ToolNames.AskUser]: { plane: "interact", actionClass: "read", executionMode: "sequential" },
|
|
58
67
|
[ToolNames.Artifact]: { plane: "artifact", actionClass: "write", executionMode: "sequential" },
|
|
@@ -50,6 +50,7 @@ const FOLDED_WITH_DIFF: ToolPresentationPolicy = {
|
|
|
50
50
|
* tools, folds the same way.
|
|
51
51
|
*/
|
|
52
52
|
export const TOOL_PRESENTATION: Readonly<Record<string, ToolPresentationPolicy>> = {
|
|
53
|
+
[ToolNames.Evidence]: FOLDED,
|
|
53
54
|
[ToolNames.Read]: FOLDED,
|
|
54
55
|
[ToolNames.Grep]: FOLDED,
|
|
55
56
|
[ToolNames.Find]: FOLDED,
|
|
@@ -68,6 +69,8 @@ export const TOOL_PRESENTATION: Readonly<Record<string, ToolPresentationPolicy>>
|
|
|
68
69
|
[ToolNames.Tasks]: FOLDED,
|
|
69
70
|
[ToolNames.Ledger]: FOLDED,
|
|
70
71
|
[ToolNames.Panes]: FOLDED,
|
|
72
|
+
[ToolNames.Limitation]: FOLDED,
|
|
73
|
+
[ToolNames.Decide]: FOLDED,
|
|
71
74
|
[ToolNames.WebFetch]: FOLDED,
|
|
72
75
|
[ToolNames.AskUser]: FOLDED,
|
|
73
76
|
[ToolNames.Artifact]: FOLDED,
|
package/src/tools/registry.ts
CHANGED
|
@@ -1099,7 +1099,7 @@ function skillSurfaceBlockedVerdict(
|
|
|
1099
1099
|
const reason =
|
|
1100
1100
|
violation.disallowedBy.length > 0
|
|
1101
1101
|
? `${tool} is disallowed by the active skill(s) ${violation.disallowedBy.join(", ")} (disallowed-tools). ${lifetime} Work within the skill workflow; if it genuinely needs this step, use ask_user when available or state the blocker in your reply.`
|
|
1102
|
-
: `${tool} is outside the tool surface declared by the active skill(s) ${violation.skills.join(", ")}. Tools are narrowed to: ${(violation.mergedAllowedTools ?? []).join(", ")} (plus context and ask_user). ${lifetime} Work within the skill workflow; if it genuinely needs this step, use ask_user when available or state the blocker in your reply.`;
|
|
1102
|
+
: `${tool} is outside the tool surface declared by the active skill(s) ${violation.skills.join(", ")}. Tools are narrowed to: ${(violation.mergedAllowedTools ?? []).join(", ")} (plus context and ask_user). The limitation receipt is also available unless explicitly disallowed. ${lifetime} Work within the skill workflow; if it genuinely needs this step, use ask_user when available or state the blocker in your reply.`;
|
|
1103
1103
|
const blocked: SafetyDecision = {
|
|
1104
1104
|
kind: "block",
|
|
1105
1105
|
classification: decision.classification,
|
|
@@ -127,11 +127,17 @@ function offloadBody(text: string, bytes: number, maxBytes: number): string {
|
|
|
127
127
|
return `${truncateUtf8(text, Math.max(0, prefixBudget), "")}${notice}`;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
export interface ToolOffloadSweepResult {
|
|
131
|
+
removed: number;
|
|
132
|
+
skipped: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
/** Remove regular offload files older than the boot retention cap. */
|
|
131
|
-
export function sweepExpiredToolOffloads(now = Date.now()):
|
|
136
|
+
export function sweepExpiredToolOffloads(now = Date.now()): ToolOffloadSweepResult {
|
|
132
137
|
const root = join(clioStateDir(), "scratch");
|
|
133
138
|
const cutoff = now - TOOL_OFFLOAD_MAX_AGE_MS;
|
|
134
139
|
let removed = 0;
|
|
140
|
+
let skipped = 0;
|
|
135
141
|
try {
|
|
136
142
|
for (const session of readdirSync(root, { withFileTypes: true })) {
|
|
137
143
|
if (!session.isDirectory()) continue;
|
|
@@ -143,14 +149,20 @@ export function sweepExpiredToolOffloads(now = Date.now()): number {
|
|
|
143
149
|
if (statSync(path).mtimeMs >= cutoff) continue;
|
|
144
150
|
rmSync(path, { force: true });
|
|
145
151
|
removed += 1;
|
|
146
|
-
} catch {
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") skipped += 1;
|
|
154
|
+
}
|
|
147
155
|
}
|
|
148
156
|
try {
|
|
149
157
|
if (readdirSync(sessionDir).length === 0) rmdirSync(sessionDir);
|
|
150
|
-
} catch {
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") skipped += 1;
|
|
160
|
+
}
|
|
151
161
|
}
|
|
152
|
-
} catch {
|
|
153
|
-
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") skipped += 1;
|
|
164
|
+
}
|
|
165
|
+
return { removed, skipped };
|
|
154
166
|
}
|
|
155
167
|
|
|
156
168
|
/**
|
|
@@ -57,9 +57,19 @@ export function isCanonicalWorktreePathInside(parent: string, candidate: string)
|
|
|
57
57
|
return rel.length > 0 && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export function commitWorktreePath(
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
export function commitWorktreePath(
|
|
61
|
+
path: string,
|
|
62
|
+
identity: string,
|
|
63
|
+
message: string,
|
|
64
|
+
excludedPaths: ReadonlyArray<string> = [],
|
|
65
|
+
): boolean {
|
|
66
|
+
// Runtime state can already be staged by a worker. Reset only these exact
|
|
67
|
+
// index entries, leaving their working files and every authored path intact.
|
|
68
|
+
if (excludedPaths.length > 0) {
|
|
69
|
+
git(path, ["reset", "-q", "HEAD", "--", ...excludedPaths.map((entry) => `:(top,literal)${entry}`)]);
|
|
70
|
+
}
|
|
71
|
+
git(path, ["add", "-A", "--", ".", ...excludedPaths.map((entry) => `:(top,exclude,literal)${entry}`)]);
|
|
72
|
+
if (git(path, ["diff", "--cached", "--name-only", "-z"]).length === 0) return false;
|
|
63
73
|
git(path, [
|
|
64
74
|
"-c",
|
|
65
75
|
`user.name=${identity}`,
|
package/src/tools/tasks.ts
CHANGED
|
@@ -60,6 +60,10 @@ function renderTaskBoardText(board: TaskBoardSnapshot, userTasks: ReadonlyArray<
|
|
|
60
60
|
if (task.status === "blocked" && task.reason) line += ` — blocked: ${task.reason}`;
|
|
61
61
|
if (task.status === "cancelled" && task.reason) line += ` — dropped: ${task.reason}`;
|
|
62
62
|
lines.push(line);
|
|
63
|
+
const outputs = userTasks.find((item) => item.id === task.userTaskId)?.acceptance?.expectedOutputs;
|
|
64
|
+
if (outputs?.length) lines.push(` expected outputs: ${outputs.join(", ")}`);
|
|
65
|
+
for (const item of task.requiredValidationEvidence ?? [])
|
|
66
|
+
lines.push(` acceptance: ${item.command} (${item.status}; ${item.notes})`);
|
|
63
67
|
}
|
|
64
68
|
if (counts.open > 0 && counts.active === 0) {
|
|
65
69
|
lines.push(`next: start a task with action="start" before working it`);
|
|
@@ -210,7 +214,12 @@ export function createTasksTool(deps: TasksToolDeps): ToolSpec {
|
|
|
210
214
|
if (userTask.status !== "open" && userTask.status !== "handed") {
|
|
211
215
|
return { kind: "error", message: `tasks: operator task ${id} is ${userTask.status}; it cannot be picked` };
|
|
212
216
|
}
|
|
213
|
-
const result = deps.board.apply({
|
|
217
|
+
const result = deps.board.apply({
|
|
218
|
+
op: "pick",
|
|
219
|
+
title: userTask.title,
|
|
220
|
+
userTaskId: userTask.id,
|
|
221
|
+
...(userTask.acceptance ? { verification: userTask.acceptance.verification } : {}),
|
|
222
|
+
});
|
|
214
223
|
if (!result.ok) return { kind: "error", message: `tasks: ${result.message}` };
|
|
215
224
|
const picked = result.board.tasks.find((task) => task.userTaskId === userTask.id && task.status === "pending");
|
|
216
225
|
if (!picked) return { kind: "error", message: `tasks: durable pickup for ${id} did not produce a board row` };
|