@iowarp/clio-coder 0.3.6 → 0.3.8
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 +59 -0
- package/README.md +22 -6
- package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
- package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
- package/dist/builtins-C6JMZVV6.js +17 -0
- package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
- package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
- package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
- package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
- package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
- package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
- package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
- package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
- package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
- package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
- package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
- package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
- package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
- package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
- package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
- package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
- package/dist/chunk-FBVTI2TJ.js +518 -0
- package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
- package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
- package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
- package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/chunk-H7IXIC72.js +103 -0
- package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
- package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
- package/dist/chunk-IIZWH4XA.js +172 -0
- package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
- package/dist/chunk-J3YUBZWY.js +382 -0
- package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
- package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
- package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/chunk-ME6CCNFO.js +108 -0
- package/dist/chunk-MXKJU4JB.js +1100 -0
- package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
- package/dist/chunk-NMPKI6XL.js +3006 -0
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
- package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
- package/dist/chunk-PT7HYKEM.js +165 -0
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
- package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
- package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
- package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
- package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
- package/dist/chunk-TTHACPOM.js +961 -0
- package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
- package/dist/chunk-VAWNZU7Z.js +242 -0
- package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
- package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
- package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
- package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
- package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
- package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
- package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
- package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
- package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
- package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
- package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
- package/dist/cli/index.js +33 -31
- package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
- package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
- package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
- package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
- package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
- package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
- package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
- package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
- package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
- package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
- package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
- package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
- package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
- package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
- package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
- package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
- package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
- package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
- package/dist/fleet-commands-UVHWM76J.js +70 -0
- package/dist/fleet-graph-6ULH7PES.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
- package/dist/fleet-validate-72PC4SLA.js +79 -0
- package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
- package/dist/library-CNTMPLRF.js +217 -0
- package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
- package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
- package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
- package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
- package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
- package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
- package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
- package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
- package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
- package/dist/support-MIETYA5E.js +38 -0
- package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
- package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
- package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
- package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
- package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
- package/dist/verifiers-YAWOJ3H2.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
- package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
- package/dist/worker/entry.js +67 -57
- package/docs/README.md +3 -2
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +11 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +83 -3
- package/docs/configuration-and-targets.md +86 -4
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +3 -3
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +74 -10
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +4 -1
- package/docs/extensions-and-sharing.md +6 -2
- package/docs/fleet-demo-runbook.md +2 -2
- package/docs/fleet-dispatch.md +228 -16
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +22 -2
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +2 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +56 -9
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +83 -65
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +37 -1
- package/docs/skills-marketplace.md +16 -3
- package/docs/tool-usage.md +14 -7
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +1 -2
- package/src/cli/argv.ts +5 -0
- package/src/cli/configure.ts +107 -23
- package/src/cli/doctor.ts +5 -1
- package/src/cli/evidence.ts +30 -25
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +111 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +173 -335
- package/src/cli/index.ts +3 -1
- package/src/cli/library.ts +190 -0
- package/src/cli/share.ts +13 -1
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +4 -1
- package/src/cli/usage.ts +111 -19
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +29 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +130 -0
- package/src/core/defaults.ts +81 -0
- package/src/core/path-boundary.ts +100 -0
- package/src/domains/agents/builtins/architect.md +1 -0
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +13 -1
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +304 -24
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +360 -15
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/capability-match.ts +1 -0
- package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
- package/src/domains/dispatch/contract.ts +48 -0
- package/src/domains/dispatch/delegation-plan.ts +164 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +12 -2
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +412 -74
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +791 -0
- package/src/domains/dispatch/gate-role-prompts.ts +47 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +41 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +235 -0
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +9 -4
- package/src/domains/dispatch/state.ts +36 -3
- package/src/domains/dispatch/types.ts +64 -12
- package/src/domains/dispatch/validation.ts +69 -6
- package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
- package/src/domains/dispatch/write-boundary.ts +201 -22
- package/src/domains/eval/metrics/evidence.ts +79 -2
- package/src/domains/eval/runners/clio-run.ts +12 -2
- package/src/domains/evidence/build.ts +69 -11
- package/src/domains/evidence/index.ts +21 -0
- package/src/domains/evidence/provenance.ts +46 -11
- package/src/domains/evidence/trust-projection.ts +274 -0
- package/src/domains/evidence/trust-status.ts +155 -18
- package/src/domains/evidence/types.ts +4 -0
- package/src/domains/extensions/discovery.ts +88 -1
- package/src/domains/extensions/resources.ts +20 -8
- package/src/domains/extensions/state.ts +6 -2
- package/src/domains/extensions/types.ts +4 -1
- package/src/domains/lifecycle/doctor.ts +140 -1
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +3 -1
- package/src/domains/observability/cost.ts +12 -1
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/observability/index.ts +10 -0
- package/src/domains/observability/out-of-turn-usage.ts +223 -0
- package/src/domains/prompts/contract.ts +3 -5
- package/src/domains/providers/extension.ts +30 -2
- package/src/domains/resources/common-loader.ts +3 -0
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/prompts/loader.ts +119 -14
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/safety/policy-engine.ts +5 -5
- package/src/domains/safety/run-effects.ts +64 -1
- package/src/domains/safety/scope.ts +7 -12
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/server.ts +4 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/entry/orchestrator.ts +37 -0
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop.ts +235 -1
- package/src/interactive/chat-renderer.ts +22 -0
- package/src/interactive/cost-overlay.ts +31 -3
- package/src/interactive/council-dispatch.ts +30 -0
- package/src/interactive/council-grid.ts +213 -0
- package/src/interactive/council.ts +99 -0
- package/src/interactive/dispatch-board.ts +311 -17
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +43 -1
- package/src/interactive/interactive-event-projection.ts +23 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
- package/src/interactive/overlay-general-openers.ts +190 -1
- package/src/interactive/overlay-key-routing.ts +17 -1
- package/src/interactive/overlay-lifecycle.ts +41 -1
- package/src/interactive/overlay-permission-lifecycle.ts +10 -0
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlays/fleet-run-approval.ts +208 -0
- package/src/interactive/overlays/handoff-review.ts +185 -0
- package/src/interactive/overlays/library-install-confirm.ts +151 -0
- package/src/interactive/overlays/list-overlay.ts +168 -2
- package/src/interactive/overlays/settings.ts +221 -30
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +439 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-middleware.ts +15 -1
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +15 -3
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +15 -0
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/dispatch-admission.ts +251 -18
- package/src/tools/dispatch-arguments.ts +84 -1
- package/src/tools/dispatch-plan.ts +165 -6
- package/src/tools/dispatch-runner.ts +364 -23
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +72 -2
- package/src/tools/monitor.ts +29 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +61 -1
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -14
- package/src/worker/spec-contract.ts +3 -1
- package/dist/chunk-HC4CLZ2Y.js +0 -68
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseCommandArgs, substituteArgs } from "../../../engine/prompt-templates.js";
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
COMPAT_RESOURCE_PRECEDENCE,
|
|
15
15
|
defaultScopedResourceRoots,
|
|
16
|
-
readRootEntries,
|
|
17
16
|
sourceInfoForRoot,
|
|
18
17
|
splitYamlFrontmatter,
|
|
19
18
|
stringField,
|
|
@@ -33,6 +32,8 @@ export interface PromptTemplate {
|
|
|
33
32
|
|
|
34
33
|
export interface PromptTemplateRoot {
|
|
35
34
|
path: string;
|
|
35
|
+
/** Present only for an installed extension resource root. */
|
|
36
|
+
rootPath?: string;
|
|
36
37
|
scope: ResourceScope;
|
|
37
38
|
source?: string;
|
|
38
39
|
precedence?: number;
|
|
@@ -139,6 +140,61 @@ function fallbackDescription(body: string): string {
|
|
|
139
140
|
return normalized.length > 60 ? `${normalized.slice(0, 57)}...` : normalized;
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
// Keep the suffix deliberately conservative. Prompt prose commonly places a
|
|
144
|
+
// comma, parenthesis, or full stop immediately after an @path; treating that
|
|
145
|
+
// punctuation as part of the filesystem reference makes a valid template look
|
|
146
|
+
// unresolved. Extension bundles use portable path components, so these are the
|
|
147
|
+
// only characters needed in a reference while still allowing `..` to be
|
|
148
|
+
// detected and rejected by the containment check below.
|
|
149
|
+
const EXTENSION_ROOT_REFERENCE = /\$\{extensionRoot\}(\/[A-Za-z0-9._~%+@/-]*)?/g;
|
|
150
|
+
const EXTENSION_ROOT_TOKEN = "$" + "{extensionRoot}";
|
|
151
|
+
|
|
152
|
+
function contained(root: string, candidate: string): boolean {
|
|
153
|
+
const relative = path.relative(root, candidate);
|
|
154
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Resolve only existing paths contained by the declaring extension package. */
|
|
158
|
+
function resolveExtensionReferences(
|
|
159
|
+
body: string,
|
|
160
|
+
root: PromptTemplateRoot,
|
|
161
|
+
filePath: string,
|
|
162
|
+
diagnostics: ResourceDiagnostic[],
|
|
163
|
+
): string | null {
|
|
164
|
+
if (root.rootPath === undefined || !body.includes(EXTENSION_ROOT_TOKEN)) return body;
|
|
165
|
+
let canonicalRoot: string;
|
|
166
|
+
try {
|
|
167
|
+
canonicalRoot = realpathSync(root.rootPath);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
diagnostics.push({
|
|
170
|
+
type: "warning",
|
|
171
|
+
message: `extension root could not be resolved: ${error instanceof Error ? error.message : String(error)}`,
|
|
172
|
+
path: filePath,
|
|
173
|
+
});
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
let invalid: string | null = null;
|
|
177
|
+
const resolved = body.replace(EXTENSION_ROOT_REFERENCE, (reference, suffix: string | undefined) => {
|
|
178
|
+
const candidate = path.resolve(canonicalRoot, `.${suffix ?? ""}`);
|
|
179
|
+
try {
|
|
180
|
+
const canonicalCandidate = realpathSync(candidate);
|
|
181
|
+
if (!contained(canonicalRoot, canonicalCandidate)) invalid = reference;
|
|
182
|
+
} catch {
|
|
183
|
+
invalid = reference;
|
|
184
|
+
}
|
|
185
|
+
return candidate;
|
|
186
|
+
});
|
|
187
|
+
if (invalid !== null) {
|
|
188
|
+
diagnostics.push({
|
|
189
|
+
type: "warning",
|
|
190
|
+
message: `prompt template has an unresolved or escaping extension reference: ${invalid}`,
|
|
191
|
+
path: filePath,
|
|
192
|
+
});
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return resolved;
|
|
196
|
+
}
|
|
197
|
+
|
|
142
198
|
function loadPromptFile(
|
|
143
199
|
filePath: string,
|
|
144
200
|
root: PromptTemplateRoot,
|
|
@@ -155,16 +211,28 @@ function loadPromptFile(
|
|
|
155
211
|
return null;
|
|
156
212
|
}
|
|
157
213
|
|
|
158
|
-
const
|
|
214
|
+
const relativePath = path.relative(root.path, filePath);
|
|
215
|
+
if (
|
|
216
|
+
relativePath === "" ||
|
|
217
|
+
relativePath === ".." ||
|
|
218
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
219
|
+
path.isAbsolute(relativePath)
|
|
220
|
+
) {
|
|
221
|
+
diagnostics.push({ type: "warning", message: "prompt template escaped its discovery root", path: filePath });
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const name = relativePath.slice(0, -ext.length).split(path.sep).join(":");
|
|
159
225
|
if (name.trim().length === 0) return null;
|
|
160
226
|
const { frontmatter, body } = splitOptionalFrontmatter(raw, filePath, diagnostics);
|
|
161
|
-
const
|
|
227
|
+
const resolvedBody = resolveExtensionReferences(body, root, filePath, diagnostics);
|
|
228
|
+
if (resolvedBody === null) return null;
|
|
229
|
+
const description = stringField(frontmatter, "description") ?? fallbackDescription(resolvedBody);
|
|
162
230
|
const argumentHint = stringField(frontmatter, "argument-hint") ?? stringField(frontmatter, "argumentHint");
|
|
163
231
|
const sourceInfo: ResourceSourceInfo = sourceInfoForRoot(root, filePath);
|
|
164
232
|
const template: PromptTemplate = {
|
|
165
233
|
name,
|
|
166
234
|
description,
|
|
167
|
-
content:
|
|
235
|
+
content: resolvedBody.trim(),
|
|
168
236
|
filePath,
|
|
169
237
|
sourceInfo,
|
|
170
238
|
trusted: root.trusted !== false,
|
|
@@ -178,10 +246,42 @@ function loadPromptRoot(
|
|
|
178
246
|
diagnostics: ResourceDiagnostic[],
|
|
179
247
|
): ResourceCandidate<PromptTemplate>[] {
|
|
180
248
|
const candidates: ResourceCandidate<PromptTemplate>[] = [];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
249
|
+
const pending = [root.path];
|
|
250
|
+
while (pending.length > 0) {
|
|
251
|
+
const directory = pending.pop();
|
|
252
|
+
if (!directory) continue;
|
|
253
|
+
let entries: import("node:fs").Dirent[];
|
|
254
|
+
try {
|
|
255
|
+
const stat = lstatSync(directory);
|
|
256
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
257
|
+
diagnostics.push({
|
|
258
|
+
type: "warning",
|
|
259
|
+
message: "prompt template root contains a non-directory or symbolic link",
|
|
260
|
+
path: directory,
|
|
261
|
+
});
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
entries = readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
267
|
+
diagnostics.push({
|
|
268
|
+
type: "warning",
|
|
269
|
+
message: `prompt template directory could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
270
|
+
path: directory,
|
|
271
|
+
});
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
for (const entry of [...entries].reverse()) {
|
|
275
|
+
const filePath = path.join(directory, entry.name);
|
|
276
|
+
if (entry.isSymbolicLink()) {
|
|
277
|
+
diagnostics.push({ type: "warning", message: "prompt template symbolic link was ignored", path: filePath });
|
|
278
|
+
} else if (entry.isDirectory()) {
|
|
279
|
+
pending.push(filePath);
|
|
280
|
+
} else if (entry.isFile()) {
|
|
281
|
+
const candidate = loadPromptFile(filePath, root, diagnostics);
|
|
282
|
+
if (candidate) candidates.push(candidate);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
185
285
|
}
|
|
186
286
|
return candidates;
|
|
187
287
|
}
|
|
@@ -210,13 +310,18 @@ export function loadPromptTemplates(input: LoadPromptTemplatesInput = {}): Promp
|
|
|
210
310
|
}
|
|
211
311
|
|
|
212
312
|
function parsePromptCommand(input: string): { name: string; rest: string } | null {
|
|
213
|
-
const
|
|
214
|
-
if (!
|
|
215
|
-
const withoutSlash =
|
|
313
|
+
const framed = input.trimStart();
|
|
314
|
+
if (!framed.startsWith("/")) return null;
|
|
315
|
+
const withoutSlash = framed.slice(1);
|
|
216
316
|
const separator = withoutSlash.search(/\s/);
|
|
217
317
|
const name = separator === -1 ? withoutSlash : withoutSlash.slice(0, separator);
|
|
218
318
|
if (name.length === 0 || name.includes("/")) return null;
|
|
219
|
-
|
|
319
|
+
// Leading whitespace before `/` and the first whitespace after the command
|
|
320
|
+
// are framing. A CRLF pair is one delimiter. Every subsequent byte belongs
|
|
321
|
+
// to the argument payload, including indentation, blank lines, and trailing
|
|
322
|
+
// whitespace, so raw `$ARGUMENTS` substitution can be exact at its boundary.
|
|
323
|
+
const delimiterWidth = separator !== -1 && withoutSlash.slice(separator, separator + 2) === "\r\n" ? 2 : 1;
|
|
324
|
+
const rest = separator === -1 ? "" : withoutSlash.slice(separator + delimiterWidth);
|
|
220
325
|
return { name, rest };
|
|
221
326
|
}
|
|
222
327
|
|
|
@@ -238,7 +343,7 @@ export function expandPromptTemplateInput(input: string, templates: PromptTempla
|
|
|
238
343
|
const args = parseCommandArgs(command.rest);
|
|
239
344
|
return {
|
|
240
345
|
expanded: true,
|
|
241
|
-
text: substituteArgs(template.content, args),
|
|
346
|
+
text: substituteArgs(template.content, args, command.rest),
|
|
242
347
|
args,
|
|
243
348
|
template,
|
|
244
349
|
diagnostics: templates.diagnostics,
|
|
@@ -35,6 +35,8 @@ import { loadSkills, type Skill } from "./loader.js";
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
export type MarketplaceSkillOrigin = "catalog" | "index";
|
|
38
|
+
export type LibraryEntryKind = "skill" | "agent" | "prompt" | "fleet";
|
|
39
|
+
export type LibraryRequirementRef = `${LibraryEntryKind}:${string}`;
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* The diagnostic that means "nothing is wrong, nothing is configured". Callers
|
|
@@ -44,6 +46,7 @@ export type MarketplaceSkillOrigin = "catalog" | "index";
|
|
|
44
46
|
export const MARKETPLACE_UNCONFIGURED = "no local skill marketplace catalog or index configured";
|
|
45
47
|
|
|
46
48
|
export interface MarketplaceSkill {
|
|
49
|
+
kind: LibraryEntryKind;
|
|
47
50
|
name: string;
|
|
48
51
|
description: string;
|
|
49
52
|
/** Local path or URL accepted by `clio-coder skills install`. */
|
|
@@ -53,6 +56,7 @@ export interface MarketplaceSkill {
|
|
|
53
56
|
/** Catalog grouping ("git", "research", ...); absent in a flat catalog. */
|
|
54
57
|
category?: string;
|
|
55
58
|
origin: MarketplaceSkillOrigin;
|
|
59
|
+
requires?: LibraryRequirementRef[];
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
export type MarketplaceStatus = "installed" | "installable" | "unavailable";
|
|
@@ -131,7 +135,7 @@ function indexAudit(value: unknown): MarketplaceSkill["audit"] {
|
|
|
131
135
|
return value === "pass" || value === "warn" || value === "fail" || value === "unknown" ? value : undefined;
|
|
132
136
|
}
|
|
133
137
|
|
|
134
|
-
function
|
|
138
|
+
function parseMarketplaceIndex(indexPath: string, diagnostics: string[] = []): MarketplaceSkill[] {
|
|
135
139
|
try {
|
|
136
140
|
const parsed = JSON.parse(readFileSync(indexPath, "utf8")) as unknown;
|
|
137
141
|
const rawSkills = Array.isArray(parsed)
|
|
@@ -139,7 +143,7 @@ function indexSkills(indexPath: string, diagnostics: string[]): MarketplaceSkill
|
|
|
139
143
|
: parsed && typeof parsed === "object" && Array.isArray((parsed as { skills?: unknown }).skills)
|
|
140
144
|
? (parsed as { skills: unknown[] }).skills
|
|
141
145
|
: [];
|
|
142
|
-
return rawSkills.filter(isIndexSkill).
|
|
146
|
+
return rawSkills.filter(isIndexSkill).flatMap((skill): MarketplaceSkill[] => {
|
|
143
147
|
const record = skill as unknown as Record<string, unknown>;
|
|
144
148
|
// Version, audit and category are published by `npm run skills:pin`.
|
|
145
149
|
// They are advisory display metadata; an index entry stays installable
|
|
@@ -147,15 +151,35 @@ function indexSkills(indexPath: string, diagnostics: string[]): MarketplaceSkill
|
|
|
147
151
|
const version = optionalString(record.version);
|
|
148
152
|
const audit = indexAudit(record.audit);
|
|
149
153
|
const category = optionalString(record.category);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
if (record.kind !== undefined && !["skill", "agent", "prompt", "fleet"].includes(String(record.kind))) {
|
|
155
|
+
diagnostics.push(`skill marketplace index entry has unsupported kind: ${skill.name}`);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
record.requires !== undefined &&
|
|
160
|
+
(!Array.isArray(record.requires) || record.requires.some((entry) => typeof entry !== "string"))
|
|
161
|
+
) {
|
|
162
|
+
diagnostics.push(`library_requirement_malformed: ${skill.name}`);
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const kind: LibraryEntryKind =
|
|
166
|
+
record.kind === "agent" || record.kind === "prompt" || record.kind === "fleet" ? record.kind : "skill";
|
|
167
|
+
const requires = Array.isArray(record.requires)
|
|
168
|
+
? record.requires.filter((entry): entry is LibraryRequirementRef => typeof entry === "string")
|
|
169
|
+
: undefined;
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
kind,
|
|
173
|
+
name: skill.name.trim(),
|
|
174
|
+
description: skill.description.trim(),
|
|
175
|
+
sourceUrl: skill.sourceUrl.trim(),
|
|
176
|
+
...(version ? { version } : {}),
|
|
177
|
+
...(audit ? { audit } : {}),
|
|
178
|
+
...(category ? { category } : {}),
|
|
179
|
+
...(requires ? { requires } : {}),
|
|
180
|
+
origin: "index" as const,
|
|
181
|
+
},
|
|
182
|
+
];
|
|
159
183
|
});
|
|
160
184
|
} catch (err) {
|
|
161
185
|
diagnostics.push(`skill marketplace index unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -226,6 +250,7 @@ function catalogEntry(skill: Skill, catalogDir: string): MarketplaceSkill {
|
|
|
226
250
|
...(skill.provenance?.audit ? { audit: skill.provenance.audit } : {}),
|
|
227
251
|
...(category ? { category } : {}),
|
|
228
252
|
origin: "catalog",
|
|
253
|
+
kind: "skill",
|
|
229
254
|
};
|
|
230
255
|
}
|
|
231
256
|
|
|
@@ -256,7 +281,7 @@ export function discoverMarketplaceSkills(options: DiscoverMarketplaceOptions =
|
|
|
256
281
|
options.indexPath === null ? null : (options.indexPath ?? process.env.CLIO_CODER_SKILL_MARKETPLACE_INDEX ?? null);
|
|
257
282
|
const resolvedIndexPath = indexPath ?? defaultIndexPath();
|
|
258
283
|
if (options.indexPath !== null && resolvedIndexPath && existsSync(resolvedIndexPath)) {
|
|
259
|
-
for (const skill of
|
|
284
|
+
for (const skill of parseMarketplaceIndex(resolvedIndexPath, diagnostics)) {
|
|
260
285
|
if (seen.has(skill.name)) continue;
|
|
261
286
|
seen.add(skill.name);
|
|
262
287
|
skills.push(skill);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { artifactDefaultPath } from "../../core/artifact-paths.js";
|
|
4
|
+
import { pathBoundaryCovers, resolvePathBoundary } from "../../core/path-boundary.js";
|
|
4
5
|
import { canonicalizeExistingPath } from "../../core/path-canonical.js";
|
|
5
6
|
import { ToolNames } from "../../core/tool-names.js";
|
|
6
7
|
import { clioConfigDir } from "../../core/xdg.js";
|
|
@@ -89,7 +90,8 @@ export interface SafetyPolicyEngineOptions {
|
|
|
89
90
|
rulePacks?: RulePacks;
|
|
90
91
|
projectPolicy?: LoadedProjectSafetyPolicy;
|
|
91
92
|
/**
|
|
92
|
-
* Absolute
|
|
93
|
+
* Absolute path boundaries a write-class tool call is confined to for this run.
|
|
94
|
+
* Exact files omit a trailing slash and subtrees retain one.
|
|
93
95
|
* When present and non-empty, a write/edit target outside every root is a
|
|
94
96
|
* final BLOCK (reason code "write-root"). Empty or absent disables the check.
|
|
95
97
|
* Enforced at the worker safety seam so both the native worker registry and
|
|
@@ -182,9 +184,7 @@ function evaluateWriteRoots(roots: ReadonlyArray<string>, writeRootCwd: string,
|
|
|
182
184
|
const target = writeRootTargetPath(call);
|
|
183
185
|
if (target === null) return null;
|
|
184
186
|
const resolved = path.resolve(writeRootCwd, target);
|
|
185
|
-
|
|
186
|
-
if (resolved === root || resolved.startsWith(`${root}${path.sep}`)) return null;
|
|
187
|
-
}
|
|
187
|
+
if (pathBoundaryCovers(roots, resolved)) return null;
|
|
188
188
|
return `write target '${target}' resolves to '${resolved}', which is outside the permitted write roots for this run: ${roots.join(", ")}`;
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -193,7 +193,7 @@ export function createSafetyPolicyEngine(options: SafetyPolicyEngineOptions = {}
|
|
|
193
193
|
// Write-root containment resolves lexically, so it keeps its own un-canonicalized
|
|
194
194
|
// cwd and roots to compare like against like (the design mandates no symlink chasing).
|
|
195
195
|
const writeRootCwd = path.resolve(options.cwd ?? process.cwd());
|
|
196
|
-
const writeRoots = (options.writeRoots ?? []).map((root) =>
|
|
196
|
+
const writeRoots = (options.writeRoots ?? []).map((root) => resolvePathBoundary(writeRootCwd, root));
|
|
197
197
|
const packs = options.rulePacks ?? getCachedDefaultRulePacks();
|
|
198
198
|
const projectPolicy = options.projectPolicy ?? loadProjectSafetyPolicy(cwd);
|
|
199
199
|
const projectPolicyRoot =
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { ToolNames } from "../../core/tool-names.js";
|
|
3
|
+
import { classify } from "./action-classifier.js";
|
|
3
4
|
import { typedValidationSummary } from "./finish-contract.js";
|
|
4
5
|
import {
|
|
5
6
|
detectValidationCommand,
|
|
@@ -25,6 +26,13 @@ import {
|
|
|
25
26
|
* bash command that writes through a script) leaves the path absent from the
|
|
26
27
|
* set and the validator falls back to the filesystem rather than to a failed
|
|
27
28
|
* conformance.
|
|
29
|
+
*
|
|
30
|
+
* `writeRecordComplete` is the other half of that sentence, and it exists for
|
|
31
|
+
* the reader that cannot fall back to the filesystem. The write boundary uses
|
|
32
|
+
* this record the other way round, to decide that a path the run did *not*
|
|
33
|
+
* write is somebody else's and must survive. Reading an under-reporting set as
|
|
34
|
+
* a closed list would clear a step of a write it really made, so the recorder
|
|
35
|
+
* says outright whether the set is closed.
|
|
28
36
|
*/
|
|
29
37
|
export interface RunEffects {
|
|
30
38
|
/** Absolute paths the run's successful tool calls aimed a mutation at. */
|
|
@@ -56,6 +64,18 @@ export interface RunEffects {
|
|
|
56
64
|
* wider set.
|
|
57
65
|
*/
|
|
58
66
|
verificationCommands: ReadonlySet<string>;
|
|
67
|
+
/**
|
|
68
|
+
* False once the run made a successful call to a tool that can mutate a path
|
|
69
|
+
* its own arguments do not name. After such a call `mutatedPaths` is a lower
|
|
70
|
+
* bound rather than the whole list, so a reader that would treat an absent
|
|
71
|
+
* path as proof the run did not write it has to stop treating it that way.
|
|
72
|
+
*
|
|
73
|
+
* The downgrade covers the whole run rather than the one call, because an
|
|
74
|
+
* opaque call's effect is unbounded. A shell command, a project script, or a
|
|
75
|
+
* spawned worker can write anywhere the process can reach, so once one has
|
|
76
|
+
* run there is no path left in the checkout that the record can clear.
|
|
77
|
+
*/
|
|
78
|
+
writeRecordComplete: boolean;
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
export interface RunEffectsRecorder {
|
|
@@ -70,6 +90,39 @@ interface PendingEffects {
|
|
|
70
90
|
paths: ReadonlyArray<string>;
|
|
71
91
|
validationCommand: string | null;
|
|
72
92
|
verificationCommand: string | null;
|
|
93
|
+
opaque: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Action classes whose successful calls write only paths their own arguments
|
|
98
|
+
* name. `read` covers the observe, retrieve, interact, and orchestrate-bookkeeping
|
|
99
|
+
* tools, none of which touch a source-workspace file: `git` is a closed
|
|
100
|
+
* status/diff/log surface, and `tasks`, `ledger`, and `context` write Clio's own
|
|
101
|
+
* state. `write` covers `write`, `edit`, and `artifact`, whose target
|
|
102
|
+
* `toolMutationPaths` resolves from the call itself, including the artifact
|
|
103
|
+
* tool's default path.
|
|
104
|
+
*/
|
|
105
|
+
const ENUMERABLE_ACTION_CLASSES: ReadonlySet<string> = new Set(["read", "write"]);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Whether a successful call to this tool can mutate a path its own recorded
|
|
109
|
+
* arguments do not name.
|
|
110
|
+
*
|
|
111
|
+
* Asked of the classifier rather than of a hand-written list, because
|
|
112
|
+
* `src/tools/policy.ts` asserts at bootstrap that every registered tool's
|
|
113
|
+
* `baseActionClass` equals what this classifier returns and what the plane
|
|
114
|
+
* table declares. A tool added to the execute or orchestrate-dispatch plane is
|
|
115
|
+
* therefore opaque the day it is registered, with no list to remember to update.
|
|
116
|
+
*
|
|
117
|
+
* The classifier is asked about the tool name alone. Bash's argument scan can
|
|
118
|
+
* escalate a single call to `system_modify` or `git_destructive`, and those are
|
|
119
|
+
* still bash; the question here is what the tool can do, not what one call was
|
|
120
|
+
* caught doing. An unregistered name (a dynamic or MCP tool) classifies as
|
|
121
|
+
* `unknown` and is opaque, which is the fail-closed answer for a tool whose
|
|
122
|
+
* arguments this process has no schema for.
|
|
123
|
+
*/
|
|
124
|
+
export function toolWritesOpaquely(toolName: string): boolean {
|
|
125
|
+
return !ENUMERABLE_ACTION_CLASSES.has(classify({ tool: toolName }).actionClass);
|
|
73
126
|
}
|
|
74
127
|
|
|
75
128
|
/**
|
|
@@ -157,18 +210,23 @@ export function createRunEffectsRecorder(cwd: string): RunEffectsRecorder {
|
|
|
157
210
|
const failedMutationPaths = new Set<string>();
|
|
158
211
|
const validationCommands = new Set<string>();
|
|
159
212
|
const verificationCommands = new Set<string>();
|
|
213
|
+
let opaqueWriteObserved = false;
|
|
160
214
|
return {
|
|
161
215
|
start(toolCallId, toolName, args) {
|
|
162
216
|
const paths = mutationTargets(toolName, args);
|
|
163
217
|
const validationCommand = validationCommandOf(toolName, args, "finish-contract");
|
|
164
218
|
const verificationCommand = validationCommandOf(toolName, args, "grounding");
|
|
165
|
-
|
|
219
|
+
const opaque = toolWritesOpaquely(toolName);
|
|
220
|
+
// An opaque call is tracked even when it exposed no path and no
|
|
221
|
+
// command, because the fact that it ran at all is the finding.
|
|
222
|
+
if (!opaque && paths.length === 0 && validationCommand === null && verificationCommand === null) return;
|
|
166
223
|
// A bash call may re-base its relative paths with its own cwd argument.
|
|
167
224
|
const base = typeof args?.cwd === "string" && args.cwd.length > 0 ? path.resolve(cwd, args.cwd) : cwd;
|
|
168
225
|
pending.set(toolCallId, {
|
|
169
226
|
paths: paths.map((target) => path.resolve(base, target)),
|
|
170
227
|
validationCommand,
|
|
171
228
|
verificationCommand,
|
|
229
|
+
opaque,
|
|
172
230
|
});
|
|
173
231
|
},
|
|
174
232
|
finish(toolCallId, failed) {
|
|
@@ -179,6 +237,10 @@ export function createRunEffectsRecorder(cwd: string): RunEffectsRecorder {
|
|
|
179
237
|
for (const target of effects.paths) failedMutationPaths.add(target);
|
|
180
238
|
return;
|
|
181
239
|
}
|
|
240
|
+
// Success-gated on purpose. A shell command the safety policy blocked,
|
|
241
|
+
// or one that came back an error, never reached the filesystem, so it
|
|
242
|
+
// leaves the record closed.
|
|
243
|
+
if (effects.opaque) opaqueWriteObserved = true;
|
|
182
244
|
for (const target of effects.paths) mutatedPaths.add(target);
|
|
183
245
|
if (effects.validationCommand !== null) validationCommands.add(effects.validationCommand);
|
|
184
246
|
if (effects.verificationCommand !== null) verificationCommands.add(effects.verificationCommand);
|
|
@@ -191,6 +253,7 @@ export function createRunEffectsRecorder(cwd: string): RunEffectsRecorder {
|
|
|
191
253
|
failedMutationPaths: new Set([...failedMutationPaths].filter((target) => !mutatedPaths.has(target))),
|
|
192
254
|
validationCommands: new Set(validationCommands),
|
|
193
255
|
verificationCommands: new Set(verificationCommands),
|
|
256
|
+
writeRecordComplete: !opaqueWriteObserved,
|
|
194
257
|
};
|
|
195
258
|
},
|
|
196
259
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { asDirectoryPathBoundary, pathBoundaryEntryCovers, resolvePathBoundary } from "../../core/path-boundary.js";
|
|
2
3
|
import type { ActionClass } from "./action-classifier.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -15,15 +16,7 @@ export interface ScopeSpec {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function normalizeRoot(root: string): string {
|
|
18
|
-
return
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function isUnder(child: string, parent: string): boolean {
|
|
22
|
-
const c = normalizeRoot(child);
|
|
23
|
-
const p = normalizeRoot(parent);
|
|
24
|
-
if (c === p) return true;
|
|
25
|
-
const rel = path.relative(p, c);
|
|
26
|
-
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
19
|
+
return resolvePathBoundary(process.cwd(), root);
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
export function isSubset(worker: ScopeSpec, orchestrator: ScopeSpec): boolean {
|
|
@@ -31,7 +24,9 @@ export function isSubset(worker: ScopeSpec, orchestrator: ScopeSpec): boolean {
|
|
|
31
24
|
if (!orchestrator.allowedActions.has(action)) return false;
|
|
32
25
|
}
|
|
33
26
|
for (const root of worker.allowedWriteRoots) {
|
|
34
|
-
const covered = orchestrator.allowedWriteRoots.some((outer) =>
|
|
27
|
+
const covered = orchestrator.allowedWriteRoots.some((outer) =>
|
|
28
|
+
pathBoundaryEntryCovers(normalizeRoot(outer), normalizeRoot(root)),
|
|
29
|
+
);
|
|
35
30
|
if (!covered) return false;
|
|
36
31
|
}
|
|
37
32
|
if (worker.allowNetwork && !orchestrator.allowNetwork) return false;
|
|
@@ -48,14 +43,14 @@ export const READONLY_SCOPE: ScopeSpec = {
|
|
|
48
43
|
|
|
49
44
|
export const WORKSPACE_SCOPE: ScopeSpec = {
|
|
50
45
|
allowedActions: new Set<ActionClass>(["read", "write", "execute", "dispatch"]),
|
|
51
|
-
allowedWriteRoots: [process.cwd()],
|
|
46
|
+
allowedWriteRoots: [asDirectoryPathBoundary(path.resolve(process.cwd()))],
|
|
52
47
|
allowNetwork: true,
|
|
53
48
|
allowDispatch: true,
|
|
54
49
|
};
|
|
55
50
|
|
|
56
51
|
export const CONFIRMED_SCOPE: ScopeSpec = {
|
|
57
52
|
allowedActions: new Set<ActionClass>(["read", "write", "execute", "dispatch", "system_modify"]),
|
|
58
|
-
allowedWriteRoots: [process.cwd()],
|
|
53
|
+
allowedWriteRoots: [asDirectoryPathBoundary(path.resolve(process.cwd()))],
|
|
59
54
|
allowNetwork: true,
|
|
60
55
|
allowDispatch: true,
|
|
61
56
|
};
|