@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
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import { ToolNames } from "../../core/tool-names.js";
|
|
2
3
|
import { isProjectVerifierCheckId, isVerificationScriptName } from "../../core/verification-scripts.js";
|
|
4
|
+
import { type DeclaredCheckSourceRef, PROJECT_VERIFIER_CATALOG_RELATIVE_PATH } from "../../tools/verify/catalog.js";
|
|
5
|
+
import type { UserTaskAcceptance } from "../user-tasks/acceptance.js";
|
|
3
6
|
import {
|
|
4
7
|
detectValidationCommand,
|
|
5
8
|
extractCommandDeleteTargets,
|
|
6
9
|
extractCommandWriteTargets,
|
|
7
10
|
toolMutationPaths,
|
|
8
11
|
} from "./protected-artifacts.js";
|
|
12
|
+
import type { Rigor } from "./rigor.js";
|
|
9
13
|
|
|
10
14
|
export const FINISH_CONTRACT_ADVISORY_MESSAGE =
|
|
11
|
-
"[Clio Coder] finish-contract advisory: you changed files this turn without recording validation evidence or
|
|
15
|
+
"[Clio Coder] finish-contract advisory: you changed files this turn without recording validation evidence or a limitation receipt. Run a verification command or call limitation with the scope and reason.";
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* The recent-window cap (entries since the last user message). Exported so a
|
|
@@ -17,11 +21,24 @@ export const FINISH_CONTRACT_ADVISORY_MESSAGE =
|
|
|
17
21
|
*/
|
|
18
22
|
export const DEFAULT_RECENT_ENTRY_LIMIT = 80;
|
|
19
23
|
|
|
20
|
-
export type FinishContractEvidenceKind =
|
|
24
|
+
export type FinishContractEvidenceKind =
|
|
25
|
+
| "validation_command"
|
|
26
|
+
| "protected_artifact"
|
|
27
|
+
| "dispatch_receipt"
|
|
28
|
+
| "limitation";
|
|
29
|
+
|
|
30
|
+
interface ValidationExecutionEvidence {
|
|
31
|
+
source?: DeclaredCheckSourceRef;
|
|
32
|
+
cwd?: string;
|
|
33
|
+
argv?: ReadonlyArray<string>;
|
|
34
|
+
durationMs?: number;
|
|
35
|
+
}
|
|
21
36
|
|
|
22
|
-
export interface FinishContractEvidence {
|
|
37
|
+
export interface FinishContractEvidence extends ValidationExecutionEvidence {
|
|
23
38
|
kind: FinishContractEvidenceKind;
|
|
24
39
|
summary: string;
|
|
40
|
+
check?: string;
|
|
41
|
+
paths?: ReadonlyArray<string>;
|
|
25
42
|
turnId?: string;
|
|
26
43
|
}
|
|
27
44
|
|
|
@@ -48,16 +65,22 @@ export type FinishContractAssessment =
|
|
|
48
65
|
};
|
|
49
66
|
|
|
50
67
|
export interface FinishContractInput {
|
|
51
|
-
assistantText: string;
|
|
52
68
|
sessionEntries?: ReadonlyArray<unknown>;
|
|
53
69
|
assistantTurnId?: string | null;
|
|
54
70
|
recentEntryLimit?: number;
|
|
71
|
+
rigor?: Rigor;
|
|
72
|
+
activeAcceptance?: UserTaskAcceptance;
|
|
73
|
+
/** Owning session workspace, supplied by the finish registration. */
|
|
74
|
+
workspaceRoot?: string;
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
interface ToolCallEvidenceCandidate {
|
|
58
78
|
turnId?: string;
|
|
59
79
|
toolCallId: string;
|
|
60
80
|
command: string;
|
|
81
|
+
check?: string;
|
|
82
|
+
paths?: string[];
|
|
83
|
+
execution?: ValidationExecutionEvidence;
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
interface MutationCandidate {
|
|
@@ -65,32 +88,26 @@ interface MutationCandidate {
|
|
|
65
88
|
paths: string[];
|
|
66
89
|
}
|
|
67
90
|
|
|
68
|
-
const LIMITATION_PATTERNS: ReadonlyArray<RegExp> = [
|
|
69
|
-
/\b(?:blocked by|blocker|blockers|unable to|not able to|could not|couldn't|cannot|can't)\b/i,
|
|
70
|
-
/\b(?:did not|didn't|have not|haven't|has not|hasn't|was not able|wasn't able)\b/i,
|
|
71
|
-
/\b(?:not complete|incomplete)\b/i,
|
|
72
|
-
/\b(?:not|un)(?:\s|-)?(?:validated|verified|tested)\b/i,
|
|
73
|
-
/^\s*Tests\s*:\s*(?:not run|not executed|not available|failed|blocked|skipped)\b/im,
|
|
74
|
-
/^\s*Known gaps?\s*:\s*(?!\s*(?:none|no\b|n\/a|not applicable)\b).+/im,
|
|
75
|
-
/\bremaining(?:\s+\w+){0,3}\s+(?:work|issue|issues|gap|gaps|blocker|blockers)\b/i,
|
|
76
|
-
];
|
|
77
|
-
|
|
78
91
|
/**
|
|
79
92
|
* Action-scoped completion contract. The engine keys off what the turn actually
|
|
80
93
|
* DID, not how the prompt was phrased: it engages only when the recent window
|
|
81
94
|
* mutated workspace state and then settled (turn_end) without recording
|
|
82
|
-
* validation evidence or
|
|
95
|
+
* validation evidence or a limitation receipt. The turn settling is itself
|
|
83
96
|
* the completion signal, so the model never has to type "done" to be gated, and
|
|
84
97
|
* a work request phrased as a question cannot bypass the gate.
|
|
85
98
|
*
|
|
86
99
|
* Decision order (pure function of ledger receipts):
|
|
87
100
|
* 1. no mutating receipt in the window -> ok/no_mutation
|
|
88
101
|
* 2. validation evidence present -> ok/validation_evidence
|
|
89
|
-
* 3.
|
|
102
|
+
* 3. successful `limitation` receipt present -> ok/explicit_limitation
|
|
90
103
|
* 4. otherwise -> engage/unvalidated_mutation
|
|
104
|
+
*
|
|
105
|
+
* The assistant's prose never enters the decision. A limitation counts only
|
|
106
|
+
* as a `limitation` tool_call paired with a non-error tool_result inside the
|
|
107
|
+
* same window the mutation scan uses, so a run cannot satisfy the contract by
|
|
108
|
+
* wording alone and a real limitation is never missed for its phrasing.
|
|
91
109
|
*/
|
|
92
110
|
export function assessFinishContract(input: FinishContractInput): FinishContractAssessment {
|
|
93
|
-
const assistantText = input.assistantText.trim();
|
|
94
111
|
const sessionEntries = input.sessionEntries ?? [];
|
|
95
112
|
const assistantTurnId = input.assistantTurnId ?? null;
|
|
96
113
|
const window = recentEntries(sessionEntries, assistantTurnId, input.recentEntryLimit ?? DEFAULT_RECENT_ENTRY_LIMIT);
|
|
@@ -100,13 +117,39 @@ export function assessFinishContract(input: FinishContractInput): FinishContract
|
|
|
100
117
|
return { kind: "ok", reason: "no_mutation", evidence: [], mutatedPaths };
|
|
101
118
|
}
|
|
102
119
|
|
|
103
|
-
const
|
|
120
|
+
const acceptanceChecks = new Set(input.activeAcceptance?.verification.map((item) => item.check) ?? []);
|
|
121
|
+
const evidence = collectValidationEvidence(window, acceptanceChecks, input.workspaceRoot);
|
|
122
|
+
const limitations = collectLimitationEvidence(window);
|
|
123
|
+
const required = input.rigor === "high" ? (input.activeAcceptance?.verification ?? []) : [];
|
|
124
|
+
if (required.length > 0) {
|
|
125
|
+
const passes = new Set(
|
|
126
|
+
required.filter((check) => evidence.some((item) => acceptanceCheckPassed(item, check, input.workspaceRoot))),
|
|
127
|
+
);
|
|
128
|
+
const limited = new Set(limitations.flatMap((item) => item.paths ?? []));
|
|
129
|
+
const missing = [
|
|
130
|
+
...new Set(required.filter((check) => !passes.has(check) && !limited.has(check.check)).map((item) => item.check)),
|
|
131
|
+
];
|
|
132
|
+
if (missing.length > 0)
|
|
133
|
+
return {
|
|
134
|
+
kind: "engage",
|
|
135
|
+
reason: "unvalidated_mutation",
|
|
136
|
+
message: `[Clio Coder] high-rigor finish gate: operator acceptance still requires passing validation for: ${missing.join(", ")}. Run each named check or include its exact id in limitation.paths.`,
|
|
137
|
+
evidence: [...evidence, ...limitations],
|
|
138
|
+
mutatedPaths,
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
kind: "ok",
|
|
142
|
+
reason: passes.size === required.length ? "validation_evidence" : "explicit_limitation",
|
|
143
|
+
evidence: [...evidence, ...limitations],
|
|
144
|
+
mutatedPaths,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
104
147
|
if (evidence.length > 0) {
|
|
105
148
|
return { kind: "ok", reason: "validation_evidence", evidence, mutatedPaths };
|
|
106
149
|
}
|
|
107
150
|
|
|
108
|
-
if (
|
|
109
|
-
return { kind: "ok", reason: "explicit_limitation", evidence:
|
|
151
|
+
if (limitations.length > 0) {
|
|
152
|
+
return { kind: "ok", reason: "explicit_limitation", evidence: limitations, mutatedPaths };
|
|
110
153
|
}
|
|
111
154
|
|
|
112
155
|
return {
|
|
@@ -118,16 +161,80 @@ export function assessFinishContract(input: FinishContractInput): FinishContract
|
|
|
118
161
|
};
|
|
119
162
|
}
|
|
120
163
|
|
|
164
|
+
function acceptanceCheckPassed(
|
|
165
|
+
evidence: FinishContractEvidence,
|
|
166
|
+
required: UserTaskAcceptance["verification"][number],
|
|
167
|
+
workspaceRoot: string | undefined,
|
|
168
|
+
): boolean {
|
|
169
|
+
if (evidence.kind !== "validation_command" || evidence.check !== required.check || !workspaceRoot || !evidence.source)
|
|
170
|
+
return false;
|
|
171
|
+
if (evidence.durationMs !== undefined && evidence.durationMs > required.timeoutMs) return false;
|
|
172
|
+
const root = resolve(workspaceRoot);
|
|
173
|
+
if (evidence.source.kind === "project-catalog")
|
|
174
|
+
return evidence.source.path === resolve(root, PROJECT_VERIFIER_CATALOG_RELATIVE_PATH);
|
|
175
|
+
return (
|
|
176
|
+
evidence.source.path === resolve(root, "package.json") &&
|
|
177
|
+
evidence.cwd === root &&
|
|
178
|
+
validationCheckId(evidence.argv?.join(" ") ?? "") === required.check
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
121
182
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
183
|
+
* Successful `limitation` receipts over the recent window. A receipt is a
|
|
184
|
+
* `limitation` tool_call whose tool_result in the same window is not an
|
|
185
|
+
* error, judged by the same `successfulToolResultId` rule the mutation and
|
|
186
|
+
* validation scans use. A call the tool rejected (bad reason, empty scope)
|
|
187
|
+
* leaves no receipt, so it cannot settle the contract.
|
|
126
188
|
*/
|
|
127
|
-
function
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
189
|
+
function collectLimitationEvidence(recent: ReadonlyArray<unknown>): FinishContractEvidence[] {
|
|
190
|
+
const evidence: FinishContractEvidence[] = [];
|
|
191
|
+
const calls = new Map<string, ToolCallEvidenceCandidate>();
|
|
192
|
+
const seen = new Set<string>();
|
|
193
|
+
|
|
194
|
+
for (const entry of recent) {
|
|
195
|
+
const call = limitationToolCall(entry);
|
|
196
|
+
if (call !== null) {
|
|
197
|
+
calls.set(call.toolCallId, call);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const resultId = successfulToolResultId(entry);
|
|
201
|
+
if (resultId === null) continue;
|
|
202
|
+
const candidate = calls.get(resultId);
|
|
203
|
+
if (candidate === undefined) continue;
|
|
204
|
+
const item: FinishContractEvidence = {
|
|
205
|
+
kind: "limitation",
|
|
206
|
+
summary: candidate.command,
|
|
207
|
+
...(candidate.paths ? { paths: candidate.paths } : {}),
|
|
208
|
+
};
|
|
209
|
+
if (candidate.turnId !== undefined) item.turnId = candidate.turnId;
|
|
210
|
+
pushEvidence(evidence, seen, item);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return evidence;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function limitationToolCall(entry: unknown): ToolCallEvidenceCandidate | null {
|
|
217
|
+
const record = asRecord(entry);
|
|
218
|
+
if (record?.kind !== "message" || record.role !== "tool_call") return null;
|
|
219
|
+
const payload = asRecord(record.payload);
|
|
220
|
+
if (payload === null) return null;
|
|
221
|
+
const toolName = stringFromFirst(payload, ["name", "toolName", "tool"]);
|
|
222
|
+
if (toolName !== ToolNames.Limitation) return null;
|
|
223
|
+
const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
|
|
224
|
+
if (toolCallId === null) return null;
|
|
225
|
+
const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
|
|
226
|
+
const scope = typeof args?.scope === "string" ? args.scope.trim() : "";
|
|
227
|
+
const reason = typeof args?.reason === "string" ? args.reason.trim() : "";
|
|
228
|
+
const candidate: ToolCallEvidenceCandidate = {
|
|
229
|
+
toolCallId,
|
|
230
|
+
paths: Array.isArray(args?.paths)
|
|
231
|
+
? args.paths.filter((item): item is string => typeof item === "string").map((item) => item.trim())
|
|
232
|
+
: [],
|
|
233
|
+
command: `limitation recorded: ${scope.length > 0 ? scope : "unspecified"} (reason=${reason.length > 0 ? reason : "unspecified"})`,
|
|
234
|
+
};
|
|
235
|
+
const turnId = turnIdOf(entry);
|
|
236
|
+
if (turnId !== null) candidate.turnId = turnId;
|
|
237
|
+
return candidate;
|
|
131
238
|
}
|
|
132
239
|
|
|
133
240
|
/**
|
|
@@ -226,7 +333,11 @@ function pushPath(paths: string[], seen: Set<string>, path: string): void {
|
|
|
226
333
|
* Inspection was removed because inspecting the repo is never a mutation, so it
|
|
227
334
|
* can no longer be on the path to engaging the contract.
|
|
228
335
|
*/
|
|
229
|
-
function collectValidationEvidence(
|
|
336
|
+
function collectValidationEvidence(
|
|
337
|
+
recent: ReadonlyArray<unknown>,
|
|
338
|
+
acceptanceChecks: ReadonlySet<string>,
|
|
339
|
+
workspaceRoot: string | undefined,
|
|
340
|
+
): FinishContractEvidence[] {
|
|
230
341
|
const evidence: FinishContractEvidence[] = [];
|
|
231
342
|
const toolCalls = new Map<string, ToolCallEvidenceCandidate>();
|
|
232
343
|
const dispatchCalls = new Map<string, ToolCallEvidenceCandidate>();
|
|
@@ -239,7 +350,7 @@ function collectValidationEvidence(recent: ReadonlyArray<unknown>): FinishContra
|
|
|
239
350
|
continue;
|
|
240
351
|
}
|
|
241
352
|
|
|
242
|
-
const call = bashValidationCall(entry);
|
|
353
|
+
const call = bashValidationCall(entry, acceptanceChecks, workspaceRoot);
|
|
243
354
|
if (call !== null) {
|
|
244
355
|
toolCalls.set(call.toolCallId, call);
|
|
245
356
|
continue;
|
|
@@ -267,19 +378,19 @@ function collectValidationEvidence(recent: ReadonlyArray<unknown>): FinishContra
|
|
|
267
378
|
}
|
|
268
379
|
const candidate = toolCalls.get(resultId);
|
|
269
380
|
if (candidate !== undefined) {
|
|
270
|
-
pushEvidence(evidence, seen, validationEvidence(candidate));
|
|
381
|
+
pushEvidence(evidence, seen, validationEvidence(candidate, entry));
|
|
271
382
|
}
|
|
272
383
|
continue;
|
|
273
384
|
}
|
|
274
385
|
|
|
275
|
-
const bashExecution = bashExecutionEvidence(entry);
|
|
386
|
+
const bashExecution = bashExecutionEvidence(entry, acceptanceChecks, workspaceRoot);
|
|
276
387
|
if (bashExecution !== null) pushEvidence(evidence, seen, bashExecution);
|
|
277
388
|
}
|
|
278
389
|
|
|
279
390
|
return evidence;
|
|
280
391
|
}
|
|
281
392
|
|
|
282
|
-
function recentEntries(
|
|
393
|
+
export function recentEntries(
|
|
283
394
|
entries: ReadonlyArray<unknown>,
|
|
284
395
|
assistantTurnId: string | null,
|
|
285
396
|
recentEntryLimit: number,
|
|
@@ -299,7 +410,11 @@ function recentEntries(
|
|
|
299
410
|
return entries.slice(startInclusive, endExclusive);
|
|
300
411
|
}
|
|
301
412
|
|
|
302
|
-
function bashValidationCall(
|
|
413
|
+
function bashValidationCall(
|
|
414
|
+
entry: unknown,
|
|
415
|
+
acceptanceChecks: ReadonlySet<string>,
|
|
416
|
+
workspaceRoot: string | undefined,
|
|
417
|
+
): ToolCallEvidenceCandidate | null {
|
|
303
418
|
const record = asRecord(entry);
|
|
304
419
|
if (record?.kind !== "message" || record.role !== "tool_call") return null;
|
|
305
420
|
const payload = asRecord(record.payload);
|
|
@@ -309,13 +424,15 @@ function bashValidationCall(entry: unknown): ToolCallEvidenceCandidate | null {
|
|
|
309
424
|
const args = asRecord(payload.args ?? payload.arguments ?? payload.input);
|
|
310
425
|
const command = typeof args?.command === "string" ? args.command : null;
|
|
311
426
|
if (command === null) return null;
|
|
312
|
-
const detected =
|
|
427
|
+
const detected = acceptanceValidationCommand(command, acceptanceChecks);
|
|
313
428
|
if (detected.kind !== "validation") return null;
|
|
314
429
|
const toolCallId = stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]) ?? turnIdOf(entry);
|
|
315
430
|
if (toolCallId === null) return null;
|
|
316
431
|
const candidate: ToolCallEvidenceCandidate = {
|
|
317
432
|
toolCallId,
|
|
318
433
|
command: detected.matched,
|
|
434
|
+
...validationCheckFields(detected.matched),
|
|
435
|
+
execution: packageCommandExecution(command, args?.cwd, workspaceRoot),
|
|
319
436
|
};
|
|
320
437
|
const turnId = turnIdOf(entry);
|
|
321
438
|
if (turnId !== null) candidate.turnId = turnId;
|
|
@@ -380,6 +497,7 @@ function validationToolCall(entry: unknown): ToolCallEvidenceCandidate | null {
|
|
|
380
497
|
const candidate: ToolCallEvidenceCandidate = {
|
|
381
498
|
toolCallId,
|
|
382
499
|
command: summary,
|
|
500
|
+
...validationCheckFields(summary),
|
|
383
501
|
};
|
|
384
502
|
const turnId = turnIdOf(entry);
|
|
385
503
|
if (turnId !== null) candidate.turnId = turnId;
|
|
@@ -414,7 +532,8 @@ function successfulToolResultId(entry: unknown): string | null {
|
|
|
414
532
|
if (payload.isError === true || payload.error === true) return null;
|
|
415
533
|
const result = asRecord(payload.result);
|
|
416
534
|
const details = asRecord(result?.details);
|
|
417
|
-
if (details?.kind === "error") return null;
|
|
535
|
+
if (result?.kind === "error" || details?.kind === "error") return null;
|
|
536
|
+
if (typeof details?.exitCode === "number" && details.exitCode !== 0) return null;
|
|
418
537
|
return stringFromFirst(payload, ["toolCallId", "tool_call_id", "id"]);
|
|
419
538
|
}
|
|
420
539
|
|
|
@@ -482,18 +601,24 @@ function dispatchReceiptAgentId(
|
|
|
482
601
|
return candidate?.command.match(/^agent=([^\s]+)/)?.[1] ?? "unknown";
|
|
483
602
|
}
|
|
484
603
|
|
|
485
|
-
function bashExecutionEvidence(
|
|
604
|
+
function bashExecutionEvidence(
|
|
605
|
+
entry: unknown,
|
|
606
|
+
acceptanceChecks: ReadonlySet<string>,
|
|
607
|
+
workspaceRoot: string | undefined,
|
|
608
|
+
): FinishContractEvidence | null {
|
|
486
609
|
const record = asRecord(entry);
|
|
487
610
|
if (record?.kind !== "bashExecution") return null;
|
|
488
611
|
if (typeof record.command !== "string") return null;
|
|
489
612
|
if (record.cancelled === true) return null;
|
|
490
613
|
if (record.exitCode !== 0) return null;
|
|
491
|
-
const detected =
|
|
614
|
+
const detected = acceptanceValidationCommand(record.command, acceptanceChecks);
|
|
492
615
|
if (detected.kind !== "validation") return null;
|
|
493
616
|
const contextMarker = record.excludeFromContext === true ? " [not sent to model]" : "";
|
|
494
617
|
const evidence: FinishContractEvidence = {
|
|
495
618
|
kind: "validation_command",
|
|
496
619
|
summary: `validation command passed: ${detected.matched}${contextMarker}`,
|
|
620
|
+
...validationCheckFields(detected.matched),
|
|
621
|
+
...packageCommandExecution(record.command, record.cwd, workspaceRoot),
|
|
497
622
|
};
|
|
498
623
|
const turnId = turnIdOf(entry);
|
|
499
624
|
if (turnId !== null) evidence.turnId = turnId;
|
|
@@ -515,17 +640,80 @@ function protectedArtifactEvidence(entry: unknown): FinishContractEvidence | nul
|
|
|
515
640
|
return evidence;
|
|
516
641
|
}
|
|
517
642
|
|
|
518
|
-
function validationEvidence(candidate: ToolCallEvidenceCandidate): FinishContractEvidence {
|
|
643
|
+
function validationEvidence(candidate: ToolCallEvidenceCandidate, entry: unknown): FinishContractEvidence {
|
|
519
644
|
const evidence: FinishContractEvidence = {
|
|
520
645
|
kind: "validation_command",
|
|
521
646
|
summary: `validation command passed: ${candidate.command}`,
|
|
647
|
+
...(candidate.check ? { check: candidate.check } : {}),
|
|
648
|
+
...(candidate.execution ?? declaredCheckExecution(entry, candidate.check)),
|
|
522
649
|
};
|
|
523
650
|
if (candidate.turnId !== undefined) evidence.turnId = candidate.turnId;
|
|
524
651
|
return evidence;
|
|
525
652
|
}
|
|
526
653
|
|
|
654
|
+
/** Preserve the verifier's execution facts only when they identify the declared check. */
|
|
655
|
+
function declaredCheckExecution(entry: unknown, check: string | undefined): ValidationExecutionEvidence {
|
|
656
|
+
const payload = asRecord(asRecord(entry)?.payload);
|
|
657
|
+
const details = asRecord(asRecord(payload?.result)?.details);
|
|
658
|
+
const source = asRecord(details?.source);
|
|
659
|
+
if (
|
|
660
|
+
!details ||
|
|
661
|
+
details.check !== check ||
|
|
662
|
+
details.exitCode !== 0 ||
|
|
663
|
+
details.aborted === true ||
|
|
664
|
+
details.timedOut === true ||
|
|
665
|
+
details.outputCapped === true ||
|
|
666
|
+
(source?.kind !== "package.json" && source?.kind !== "project-catalog") ||
|
|
667
|
+
typeof source.path !== "string" ||
|
|
668
|
+
typeof details.cwd !== "string" ||
|
|
669
|
+
!Array.isArray(details.argv) ||
|
|
670
|
+
!details.argv.every((arg): arg is string => typeof arg === "string")
|
|
671
|
+
)
|
|
672
|
+
return {};
|
|
673
|
+
if (source.kind === "project-catalog") {
|
|
674
|
+
const argv = details.argv;
|
|
675
|
+
if (
|
|
676
|
+
!Array.isArray(details.declaredCommand) ||
|
|
677
|
+
details.declaredCommand.length !== details.argv.length ||
|
|
678
|
+
!details.declaredCommand.every((arg, index) => arg === argv[index]) ||
|
|
679
|
+
typeof details.declaredCwd !== "string" ||
|
|
680
|
+
typeof details.declaredTimeoutMs !== "number" ||
|
|
681
|
+
typeof details.durationMs !== "number" ||
|
|
682
|
+
details.durationMs > details.declaredTimeoutMs ||
|
|
683
|
+
resolve(source.path, "../..", details.declaredCwd) !== details.cwd
|
|
684
|
+
)
|
|
685
|
+
return {};
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
source: { kind: source.kind, path: source.path },
|
|
689
|
+
cwd: details.cwd,
|
|
690
|
+
argv: details.argv,
|
|
691
|
+
...(typeof details.durationMs === "number" ? { durationMs: details.durationMs } : {}),
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Exact package commands retain their execution cwd; a matched substring proves no project identity. */
|
|
696
|
+
function packageCommandExecution(
|
|
697
|
+
command: string,
|
|
698
|
+
cwd: unknown,
|
|
699
|
+
workspaceRoot: string | undefined,
|
|
700
|
+
): ValidationExecutionEvidence {
|
|
701
|
+
if (
|
|
702
|
+
!workspaceRoot ||
|
|
703
|
+
!/^(?:npm|pnpm|yarn|bun) /u.test(command.trim()) ||
|
|
704
|
+
validationCheckId(command.trim()) === undefined
|
|
705
|
+
)
|
|
706
|
+
return {};
|
|
707
|
+
const executionCwd = resolve(workspaceRoot, typeof cwd === "string" ? cwd : ".");
|
|
708
|
+
return {
|
|
709
|
+
source: { kind: "package.json", path: resolve(executionCwd, "package.json") },
|
|
710
|
+
cwd: executionCwd,
|
|
711
|
+
argv: command.trim().split(/\s+/u),
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
527
715
|
function pushEvidence(evidence: FinishContractEvidence[], seen: Set<string>, item: FinishContractEvidence): void {
|
|
528
|
-
const key =
|
|
716
|
+
const key = JSON.stringify(item);
|
|
529
717
|
if (seen.has(key)) return;
|
|
530
718
|
seen.add(key);
|
|
531
719
|
evidence.push(item);
|
|
@@ -554,3 +742,27 @@ function stringFromFirst(record: Record<string, unknown>, keys: ReadonlyArray<st
|
|
|
554
742
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
555
743
|
return value !== null && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
|
556
744
|
}
|
|
745
|
+
|
|
746
|
+
/** Exact check ids, never substring matches against rendered evidence summaries. */
|
|
747
|
+
function validationCheckId(command: string): string | undefined {
|
|
748
|
+
return (
|
|
749
|
+
/^(?:npm|pnpm|yarn|bun) (?:run )?([a-z0-9][a-z0-9._:-]*)$/.exec(command)?.[1] ??
|
|
750
|
+
/^verify ([a-z0-9][a-z0-9._:-]*)$/.exec(command)?.[1]
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function validationCheckFields(command: string): { check?: string } {
|
|
755
|
+
const check = validationCheckId(command);
|
|
756
|
+
return check ? { check } : {};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Additional package scripts are admitted only by the operator's typed acceptance. */
|
|
760
|
+
function acceptanceValidationCommand(
|
|
761
|
+
command: string,
|
|
762
|
+
checks: ReadonlySet<string>,
|
|
763
|
+
): ReturnType<typeof detectValidationCommand> {
|
|
764
|
+
const exact = command.trim();
|
|
765
|
+
const check = /^npm run ([a-z0-9][a-z0-9._:-]*)$/.exec(exact)?.[1];
|
|
766
|
+
if (check && checks.has(check)) return { kind: "validation", matched: exact };
|
|
767
|
+
return detectValidationCommand(command);
|
|
768
|
+
}
|
|
@@ -42,4 +42,23 @@ export {
|
|
|
42
42
|
type PathPolicyKind,
|
|
43
43
|
type PathPolicyOperation,
|
|
44
44
|
} from "./path-policy.js";
|
|
45
|
-
export {
|
|
45
|
+
export {
|
|
46
|
+
parseRigorOverride,
|
|
47
|
+
type Rigor,
|
|
48
|
+
type RigorResolution,
|
|
49
|
+
type RigorSource,
|
|
50
|
+
resolveRigor,
|
|
51
|
+
rigorResolution,
|
|
52
|
+
} from "./rigor.js";
|
|
53
|
+
export {
|
|
54
|
+
describeValidationContract,
|
|
55
|
+
loadValidationContract,
|
|
56
|
+
parseValidationContractText,
|
|
57
|
+
VALIDATION_CONTRACT_CAPS,
|
|
58
|
+
VALIDATION_CONTRACT_MARKDOWN_PATH,
|
|
59
|
+
VALIDATION_CONTRACT_YAML_PATHS,
|
|
60
|
+
type ValidationContract,
|
|
61
|
+
type ValidationContractArtifact,
|
|
62
|
+
type ValidationContractLoadResult,
|
|
63
|
+
type ValidationContractRuntime,
|
|
64
|
+
} from "./validation-contract.js";
|
|
@@ -204,12 +204,15 @@ export function createSafetyPolicyEngine(options: SafetyPolicyEngineOptions = {}
|
|
|
204
204
|
const pathPolicyInput = projectPolicy.disableDefaultPathPolicy
|
|
205
205
|
? projectPolicy.pathPolicy
|
|
206
206
|
: mergePathPolicyInputs(DEFAULT_DAMAGE_CONTROL_PATH_POLICY, projectPolicy.pathPolicy);
|
|
207
|
-
// Clio's own secret store, by absolute path.
|
|
208
|
-
//
|
|
207
|
+
// Clio's own secret store and user skills, by absolute path.
|
|
208
|
+
// Config directory expansion has to happen here because
|
|
209
209
|
// the list cannot call config helpers at module scope.
|
|
210
210
|
const expandedDefaults = projectPolicy.disableDefaultPathPolicy
|
|
211
211
|
? pathPolicyInput
|
|
212
|
-
: mergePathPolicyInputs(pathPolicyInput, {
|
|
212
|
+
: mergePathPolicyInputs(pathPolicyInput, {
|
|
213
|
+
zeroAccessPaths: clioCredentialStorePaths(),
|
|
214
|
+
readOnlyPaths: clioSkillsRootPaths(),
|
|
215
|
+
});
|
|
213
216
|
const pathPolicy = compilePathPolicy(expandedDefaults, projectPolicyRoot);
|
|
214
217
|
// Bash-read scanning tests argument tokens against zero-access entries only:
|
|
215
218
|
// read-only paths stay readable from bash by design, secrets do not.
|
|
@@ -478,6 +481,15 @@ function clioCredentialStorePaths(): string[] {
|
|
|
478
481
|
}
|
|
479
482
|
}
|
|
480
483
|
|
|
484
|
+
/** Absolute path of Clio's user skills, when resolvable. */
|
|
485
|
+
function clioSkillsRootPaths(): string[] {
|
|
486
|
+
try {
|
|
487
|
+
return [path.join(clioConfigDir(), "skills")];
|
|
488
|
+
} catch {
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
481
493
|
/**
|
|
482
494
|
* The exact safe presence form: `grep -q` or `grep -sq` (either flag order)
|
|
483
495
|
* with a `^NAME=`-shaped pattern and a single file argument. Exit code only;
|
|
@@ -966,10 +978,16 @@ function cwdArg(args: Record<string, unknown> | undefined, fallback: string): st
|
|
|
966
978
|
}
|
|
967
979
|
|
|
968
980
|
/**
|
|
969
|
-
* Tools whose arguments
|
|
970
|
-
* Their damage-control scan is the destination path alone.
|
|
981
|
+
* Tools whose arguments carry file contents or task prose, not command lines.
|
|
982
|
+
* Their damage-control scan is the destination path alone, when present.
|
|
971
983
|
*/
|
|
972
|
-
const CONTENT_BEARING_TOOLS: ReadonlySet<string> = new Set([
|
|
984
|
+
const CONTENT_BEARING_TOOLS: ReadonlySet<string> = new Set([
|
|
985
|
+
ToolNames.Write,
|
|
986
|
+
ToolNames.Edit,
|
|
987
|
+
ToolNames.Artifact,
|
|
988
|
+
ToolNames.Dispatch,
|
|
989
|
+
ToolNames.Tasks,
|
|
990
|
+
]);
|
|
973
991
|
|
|
974
992
|
/**
|
|
975
993
|
* The text damage-control rules are matched against.
|
|
@@ -991,6 +1009,8 @@ const CONTENT_BEARING_TOOLS: ReadonlySet<string> = new Set([ToolNames.Write, Too
|
|
|
991
1009
|
* full. Only the destination path is scanned for a mutation tool, which keeps
|
|
992
1010
|
* any path-shaped rule working; where the file may land is the write tool's own
|
|
993
1011
|
* gate in `writePathClass`.
|
|
1012
|
+
* Dispatch and task-board prose likewise does not execute; worker commands
|
|
1013
|
+
* are scanned when the worker calls an execute-class tool.
|
|
994
1014
|
*/
|
|
995
1015
|
function damageControlScan(call: ClassifierCall): string {
|
|
996
1016
|
if (!CONTENT_BEARING_TOOLS.has(call.tool)) return serializeArgs(call.args);
|
|
@@ -5,44 +5,71 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - `normal`: the finish-contract advisory stays a soft `warn` reminder.
|
|
7
7
|
* - `high`: an unvalidated completion claim re-prompts the model to run
|
|
8
|
-
* validation or
|
|
8
|
+
* validation or record a limitation before the turn settles.
|
|
9
9
|
*
|
|
10
10
|
* Rigor resolves from a per-session / per-dispatch override (the `CLIO_CODER_RIGOR`
|
|
11
11
|
* env var today) layered over a repo-derived default. The repo-derived default
|
|
12
|
-
* is `high` when the workspace
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* is `high` only when the workspace's scientific-validation contract parses
|
|
13
|
+
* under the version-1 schema in `validation-contract.ts`. A contract that
|
|
14
|
+
* fails to parse is diagnosed and leaves rigor at `normal`, and a Markdown
|
|
15
|
+
* `VALIDATION.md` is advisory prose that never raises rigor on its own. This
|
|
16
|
+
* keeps the evidence bar derived from what the repo actually declares rather
|
|
17
|
+
* than from a filename or a global toggle.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
|
-
import {
|
|
19
|
-
import path from "node:path";
|
|
20
|
+
import { describeValidationContract, loadValidationContract } from "./validation-contract.js";
|
|
20
21
|
|
|
21
22
|
export type Rigor = "normal" | "high";
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
path
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
/** Why rigor settled where it did, so a caller can print the reason. */
|
|
25
|
+
export type RigorSource = "override" | "validation-contract" | "invalid-contract" | "markdown-advisory" | "none";
|
|
26
|
+
|
|
27
|
+
export interface RigorResolution {
|
|
28
|
+
rigor: Rigor;
|
|
29
|
+
source: RigorSource;
|
|
30
|
+
/** Present when a contract was found but could not raise rigor: the parse fault or the Markdown advisory note. */
|
|
31
|
+
diagnostic?: string;
|
|
32
|
+
/** The repository-relative contract path, when one was found. */
|
|
33
|
+
contractPath?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RigorOptions {
|
|
37
|
+
cwd?: string;
|
|
38
|
+
override?: Rigor | null;
|
|
39
|
+
}
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
|
-
* Resolve the effective rigor. An explicit override
|
|
39
|
-
* always wins; otherwise the repo-derived default keys
|
|
40
|
-
* validation contract at the workspace root.
|
|
42
|
+
* Resolve the effective rigor and the reason for it. An explicit override
|
|
43
|
+
* (`"high"` | `"normal"`) always wins; otherwise the repo-derived default keys
|
|
44
|
+
* off the parsed validation contract at the workspace root.
|
|
41
45
|
*/
|
|
42
|
-
export function
|
|
43
|
-
if (options.override === "high" || options.override === "normal")
|
|
46
|
+
export function rigorResolution(options: RigorOptions): RigorResolution {
|
|
47
|
+
if (options.override === "high" || options.override === "normal") {
|
|
48
|
+
return { rigor: options.override, source: "override" };
|
|
49
|
+
}
|
|
44
50
|
const cwd = options.cwd ?? process.cwd();
|
|
45
|
-
|
|
51
|
+
const loaded = loadValidationContract(cwd);
|
|
52
|
+
if (!loaded.ok) {
|
|
53
|
+
const resolution: RigorResolution = { rigor: "normal", source: "invalid-contract", contractPath: loaded.path };
|
|
54
|
+
const diagnostic = describeValidationContract(loaded);
|
|
55
|
+
if (diagnostic !== null) resolution.diagnostic = diagnostic;
|
|
56
|
+
return resolution;
|
|
57
|
+
}
|
|
58
|
+
if (loaded.contract !== null) {
|
|
59
|
+
return { rigor: "high", source: "validation-contract", contractPath: loaded.path };
|
|
60
|
+
}
|
|
61
|
+
if ("advisory" in loaded) {
|
|
62
|
+
const resolution: RigorResolution = { rigor: "normal", source: "markdown-advisory", contractPath: loaded.path };
|
|
63
|
+
const diagnostic = describeValidationContract(loaded);
|
|
64
|
+
if (diagnostic !== null) resolution.diagnostic = diagnostic;
|
|
65
|
+
return resolution;
|
|
66
|
+
}
|
|
67
|
+
return { rigor: "normal", source: "none" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The effective rigor alone; see {@link rigorResolution} for the reason. */
|
|
71
|
+
export function resolveRigor(options: RigorOptions): Rigor {
|
|
72
|
+
return rigorResolution(options).rigor;
|
|
46
73
|
}
|
|
47
74
|
|
|
48
75
|
/**
|
|
@@ -57,16 +84,3 @@ export function parseRigorOverride(value: string | null | undefined): Rigor | nu
|
|
|
57
84
|
if (normalized === "normal") return "normal";
|
|
58
85
|
return null;
|
|
59
86
|
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* True when the workspace declares a scientific-validation contract by the
|
|
63
|
-
* documented path convention. Pure and resilient: any filesystem error is
|
|
64
|
-
* swallowed and treated as "no contract".
|
|
65
|
-
*/
|
|
66
|
-
function hasValidationContract(cwd: string): boolean {
|
|
67
|
-
try {
|
|
68
|
-
return VALIDATION_CONTRACT_FILES.some((relative) => existsSync(path.join(cwd, relative)));
|
|
69
|
-
} catch {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
}
|