@iowarp/clio-coder 0.3.7 → 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 +31 -0
- package/README.md +7 -2
- package/dist/{acp-SK4MD6MM.js → acp-U67UHUK2.js} +13 -13
- package/dist/{agents-2FN2K6ME.js → agents-YU6SGALZ.js} +35 -33
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-QIYZWM5I.js → auth-5ZPJOIVG.js} +32 -24
- package/dist/builtins-C6JMZVV6.js +17 -0
- package/dist/{chunk-YD734TPH.js → chunk-26LEYJZH.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
- package/dist/{chunk-WJHBC77E.js → chunk-2HFZQUHL.js} +5 -5
- package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
- package/dist/{chunk-EOOQZZDE.js → chunk-4SPRNWDE.js} +18 -18
- package/dist/{chunk-EBEFWSGL.js → chunk-5DHKRSMQ.js} +7 -7
- package/dist/{chunk-OBMAI2DP.js → chunk-5H3GB5BO.js} +11 -9
- package/dist/{chunk-JTSEDYVQ.js → chunk-5Q2VVUKB.js} +7 -7
- package/dist/{chunk-VQNODYQ4.js → chunk-7RFXX52T.js} +294 -3704
- package/dist/{chunk-XEGB6BCN.js → chunk-7RGZWPB6.js} +5 -5
- package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-CEYBNUGC.js → chunk-A3WNZD3P.js} +323 -27
- package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
- package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
- package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
- package/dist/{chunk-KCMKRQX4.js → chunk-E77JEWSD.js} +42 -49
- package/dist/{chunk-J7PIKKWC.js → chunk-EMYUUSFG.js} +7 -7
- package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
- package/dist/{chunk-UVDSQ6LW.js → chunk-FBVTI2TJ.js} +74 -28
- package/dist/{chunk-BMWK7ZIZ.js → chunk-FHJEP5SW.js} +13 -13
- package/dist/{chunk-FOT2FX5J.js → chunk-GN57SG4G.js} +7 -7
- package/dist/{chunk-SROCI7ZU.js → chunk-GPIEI3LY.js} +5 -5
- package/dist/{chunk-UFQ3F4FW.js → chunk-GU2UIAFZ.js} +4 -4
- package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
- package/dist/{chunk-VREKEFLL.js → chunk-HLE42MG7.js} +3 -3
- package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-LADCF22A.js → chunk-IGWKHNIQ.js} +113 -54
- package/dist/chunk-IIZWH4XA.js +172 -0
- package/dist/{chunk-UHXRNZ2J.js → chunk-IJ7RPIYJ.js} +4 -4
- package/dist/{chunk-M4AKACEO.js → chunk-J3YUBZWY.js} +2 -2
- package/dist/{chunk-AB4XIIVB.js → chunk-JOZYP4GM.js} +6 -6
- package/dist/{chunk-ZGH7FGS5.js → chunk-K4XHGFR5.js} +742 -12
- package/dist/{chunk-EELBMBT6.js → chunk-KTYTFRMB.js} +62 -5
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/{chunk-DQA7QLMD.js → chunk-ME6CCNFO.js} +9 -24
- package/dist/{chunk-KZ2H5X4G.js → chunk-MXKJU4JB.js} +88 -14
- package/dist/{chunk-DMD2AGVS.js → chunk-N22QMJKY.js} +20 -18
- package/dist/chunk-NMPKI6XL.js +3006 -0
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-5C77SEEY.js → chunk-P43ETTHK.js} +2 -2
- package/dist/{chunk-JNXPYBB4.js → chunk-PMDBGQSJ.js} +2 -2
- package/dist/chunk-PT7HYKEM.js +165 -0
- package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
- package/dist/{chunk-C4JBQ5SR.js → chunk-TANS5ZJS.js} +6 -6
- package/dist/{chunk-YTYFXUI3.js → chunk-TB5666IT.js} +9 -9
- package/dist/{chunk-UUANF5CR.js → chunk-TLQJPP24.js} +375 -687
- package/dist/{chunk-OB5HIGJY.js → chunk-TT36MB5S.js} +2 -1
- package/dist/{chunk-DJNLUABN.js → chunk-TTHACPOM.js} +135 -17
- package/dist/{chunk-6M7VS3J3.js → chunk-TYPGUK6W.js} +4 -4
- package/dist/{chunk-THKY7CD7.js → chunk-U6MBIEMB.js} +108 -24
- package/dist/{chunk-PD3MESLB.js → chunk-VAWNZU7Z.js} +4 -4
- package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
- package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
- package/dist/{chunk-MXI6J5JF.js → chunk-VWZOAB7K.js} +10 -10
- package/dist/{chunk-QCTRSGHQ.js → chunk-WLFILSD5.js} +36 -36
- package/dist/{chunk-5UJ6ECTS.js → chunk-WNIJTQQK.js} +6 -6
- package/dist/{chunk-4DWFMQDR.js → chunk-WSB3FPX7.js} +68 -20
- package/dist/{chunk-3HAPLH5M.js → chunk-WWCZ5F23.js} +112 -10
- package/dist/{chunk-X2KV5FXT.js → chunk-WXY7KU3G.js} +2 -2
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-DOOEX22V.js → chunk-XWSF374K.js} +4 -4
- package/dist/{chunk-6TUKSZVF.js → chunk-YS5VLNH5.js} +8 -8
- 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 +31 -31
- package/dist/{clio-WBVQEBKO.js → clio-QVTYJ57A.js} +8 -8
- package/dist/code-nav-FGGFIE7L.js +2 -2
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
- package/dist/{config-TRBL3RCF.js → config-LW5IJFQN.js} +58 -53
- package/dist/{configure-OLCVPHNM.js → configure-7XIZCOU4.js} +26 -21
- package/dist/{context-XEWE3MOJ.js → context-L3WL3X7K.js} +47 -41
- package/dist/{context-WFPKQSM6.js → context-N52ZA626.js} +22 -22
- package/dist/{context-MJIJ6GOX.js → context-Y6Y7QPR6.js} +12 -12
- package/dist/{context-clear-KNOS2JPB.js → context-clear-MBQRLSDQ.js} +47 -41
- package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
- package/dist/{context-working-set-EUXAZI6N.js → context-working-set-GS6DSO7F.js} +14 -14
- package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-22ZCNOM3.js} +83 -55
- package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
- package/dist/{doctor-RN4YKO2X.js → doctor-M7YEDGAE.js} +24 -20
- package/dist/{eval-RUBJVSNQ.js → eval-BEC2WHDA.js} +71 -19
- package/dist/{evidence-JZNBUOQZ.js → evidence-REJUMSKM.js} +62 -56
- package/dist/{evolve-FJVC4KKI.js → evolve-PY5ZBA5K.js} +41 -35
- package/dist/{extensions-IQL36S7K.js → extensions-HVKU65YU.js} +6 -6
- package/dist/{fleet-BDKYJFCP.js → fleet-7WZEWRFA.js} +72 -67
- package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-UVHWM76J.js} +10 -10
- package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-6ULH7PES.js} +13 -13
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
- package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-72PC4SLA.js} +17 -17
- package/dist/{init-LQUB5COQ.js → init-OG3TPGQG.js} +61 -56
- package/dist/{library-NJAHIGG4.js → library-CNTMPLRF.js} +18 -18
- package/dist/{memory-OG6HOYKM.js → memory-6IS7F275.js} +43 -37
- package/dist/{models-5ZG5XY7J.js → models-ENRJDA5W.js} +33 -28
- package/dist/{monitor-TJ7AMTGB.js → monitor-XLDVO7TN.js} +56 -38
- package/dist/{orchestrator-WZYB54DM.js → orchestrator-6KSPYRHA.js} +421 -303
- package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-PXQT45IY.js → reset-RZ4ER727.js} +10 -10
- package/dist/{run-FQ74YF62.js → run-Y2CNK5RU.js} +79 -74
- package/dist/{share-FW7SVCL3.js → share-A55GYP6Z.js} +16 -16
- package/dist/{skills-7E7IRB3R.js → skills-ALC5J6AT.js} +19 -19
- package/dist/{skills-eval-LI75W6OK.js → skills-eval-JPBEBYQU.js} +52 -47
- package/dist/support-MIETYA5E.js +38 -0
- package/dist/{targets-4CIFKCTW.js → targets-VGNXIR3S.js} +42 -33
- package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-WOBR64YA.js} +3 -3
- package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
- package/dist/{upgrade-K2HVIVMQ.js → upgrade-FUSUAGHR.js} +27 -25
- package/dist/{usage-GTZELZQX.js → usage-N4MKVHKD.js} +51 -46
- package/dist/{verifiers-RLAHT27O.js → verifiers-YAWOJ3H2.js} +13 -13
- package/dist/{verify-BX3BRKH5.js → verify-LTDHYBGY.js} +9 -9
- package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-6M7GHTBJ.js} +66 -61
- package/dist/worker/entry.js +68 -61
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/artifact-versions.md +6 -5
- package/docs/built-in-agents.md +1 -1
- package/docs/commands-and-modes.md +2 -2
- package/docs/configuration-and-targets.md +8 -3
- package/docs/context-engine.md +1 -1
- package/docs/documentation-guide.md +1 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +70 -6
- package/docs/evolution.md +1 -1
- package/docs/extensions-and-sharing.md +1 -1
- package/docs/fleet-dispatch.md +34 -9
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +2 -2
- 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 +41 -38
- package/docs/safety-model.md +1 -1
- package/docs/scientific-validation.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +1 -1
- package/docs/trace-store.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- 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-preflight.ts +2 -12
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +4 -1
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +25 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/path-boundary.ts +100 -0
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +30 -12
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +128 -17
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/contract.ts +15 -1
- package/src/domains/dispatch/delegation-plan.ts +2 -5
- package/src/domains/dispatch/execution-role.ts +9 -1
- package/src/domains/dispatch/extension.ts +156 -67
- package/src/domains/dispatch/fleet-run.ts +57 -3
- package/src/domains/dispatch/gate-role-prompts.ts +38 -0
- package/src/domains/dispatch/index.ts +3 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +84 -8
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +2 -1
- package/src/domains/dispatch/types.ts +14 -7
- package/src/domains/dispatch/validation.ts +6 -3
- 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 +145 -17
- 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/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/prompts/loader.ts +119 -14
- 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/engine/acp/server.ts +4 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/interactive/dispatch-board.ts +53 -3
- package/src/interactive/interactive-event-projection.ts +14 -0
- package/src/interactive/overlays/settings.ts +141 -47
- package/src/interactive/slash-commands.ts +7 -2
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +15 -3
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-stream.ts +8 -0
- package/src/tools/dispatch-admission.ts +12 -13
- package/src/tools/dispatch-arguments.ts +27 -0
- package/src/tools/dispatch-plan.ts +29 -0
- package/src/tools/dispatch-runner.ts +48 -13
- package/src/tools/monitor.ts +13 -0
- package/src/tools/worker-evidence.ts +19 -13
- package/src/worker/spec-contract.ts +2 -1
|
@@ -21,7 +21,7 @@ export type {
|
|
|
21
21
|
AttemptRef,
|
|
22
22
|
DispatchAssignment,
|
|
23
23
|
} from "./assignment.js";
|
|
24
|
-
export type { DurableAssignmentRecord } from "./assignment-store.js";
|
|
24
|
+
export type { AssignmentVerdictOwner, DurableAssignmentRecord } from "./assignment-store.js";
|
|
25
25
|
export type { CodeStepOutcome, CodeStepRecord, CodeStepRunInput } from "./code-step.js";
|
|
26
26
|
export {
|
|
27
27
|
canonicalCodeReport,
|
|
@@ -160,10 +160,12 @@ export {
|
|
|
160
160
|
} from "./validation.js";
|
|
161
161
|
export type {
|
|
162
162
|
WorkspaceSnapshot,
|
|
163
|
+
WriteBoundaryAttribution,
|
|
163
164
|
WriteBoundaryStatus,
|
|
164
165
|
WriteBoundaryVerdict,
|
|
165
166
|
} from "./write-boundary.js";
|
|
166
167
|
export {
|
|
168
|
+
assertWriteBoundaryVisibleToGit,
|
|
167
169
|
captureWorkspaceSnapshot,
|
|
168
170
|
diffWorkspace,
|
|
169
171
|
enforceWriteBoundary,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DispatchIntent } from "./intent.js";
|
|
2
|
+
|
|
3
|
+
/** Render intent fields that describe required results rather than observed facts. */
|
|
4
|
+
export function renderDispatchIntentRequirements(intent: DispatchIntent | undefined): string | null {
|
|
5
|
+
if (intent === undefined || (intent.expectedOutputs.length === 0 && intent.verification.length === 0)) return null;
|
|
6
|
+
const lines = [
|
|
7
|
+
"# Declared Result Requirements",
|
|
8
|
+
"These entries are requirements to satisfy and verify. They are not evidence that an output exists or a check passed.",
|
|
9
|
+
];
|
|
10
|
+
if (intent.expectedOutputs.length > 0) {
|
|
11
|
+
lines.push("", "Expected outputs:", ...intent.expectedOutputs.map((output) => `- ${output}`));
|
|
12
|
+
}
|
|
13
|
+
if (intent.verification.length > 0) {
|
|
14
|
+
lines.push(
|
|
15
|
+
"",
|
|
16
|
+
"Verification requirements:",
|
|
17
|
+
...intent.verification.map(
|
|
18
|
+
(entry) => `- ${entry.check} must pass within its declared timeout of ${entry.timeoutMs}ms`,
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Build the reviewer assignment from the same requirements the builder received. */
|
|
26
|
+
export function renderDispatchReviewerTask(
|
|
27
|
+
originalTask: string,
|
|
28
|
+
builderRunId: string,
|
|
29
|
+
cycle: number,
|
|
30
|
+
intent: DispatchIntent | undefined,
|
|
31
|
+
): string {
|
|
32
|
+
const requirements = renderDispatchIntentRequirements(intent);
|
|
33
|
+
return [
|
|
34
|
+
`Review the work of builder run ${builderRunId} (review cycle ${cycle}).`,
|
|
35
|
+
"The builder's final answer is provided as input data; verify it against the workspace, do not trust it blindly.",
|
|
36
|
+
"Original task the builder was given:",
|
|
37
|
+
originalTask,
|
|
38
|
+
...(requirements === null ? [] : [requirements]),
|
|
39
|
+
].join("\n\n");
|
|
40
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { normalizePathBoundaryEntry, PATH_BOUNDARY_MAX_ENTRIES, PathBoundaryError } from "../../core/path-boundary.js";
|
|
3
|
+
import { type DispatchPathProvenanceEntry, declaredIntentPathProvenance } from "./path-scope.js";
|
|
2
4
|
|
|
3
|
-
export const DISPATCH_INTENT_PATH_LIST_CAP =
|
|
5
|
+
export const DISPATCH_INTENT_PATH_LIST_CAP = PATH_BOUNDARY_MAX_ENTRIES;
|
|
4
6
|
export const DISPATCH_INTENT_PATH_ENTRY_BYTES_CAP = 512;
|
|
5
7
|
export const DISPATCH_INTENT_VERIFICATION_CAP = 8;
|
|
6
8
|
export const DISPATCH_INTENT_TIMEOUT_MIN_MS = 1_000;
|
|
@@ -11,10 +13,12 @@ export interface DispatchIntentVerification {
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface DispatchIntent {
|
|
14
|
-
version:
|
|
16
|
+
version: 2;
|
|
15
17
|
readRoots: string[];
|
|
16
18
|
writeRoots: string[];
|
|
17
19
|
relevantPaths: string[];
|
|
20
|
+
/** Integrity-sealed source and confidence for every policy-bearing path. */
|
|
21
|
+
pathProvenance: DispatchPathProvenanceEntry[];
|
|
18
22
|
expectedOutputs: string[];
|
|
19
23
|
verification: DispatchIntentVerification[];
|
|
20
24
|
}
|
|
@@ -42,7 +46,7 @@ function fail(reason: string, message: string): DispatchIntentNormalizationResul
|
|
|
42
46
|
return { ok: false, reason, message };
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
function
|
|
49
|
+
function checkedPathText(value: unknown, field: string, index: number): string | DispatchIntentNormalizationResult {
|
|
46
50
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
47
51
|
return fail("intent_path_malformed", `${field}[${index}] must be a non-empty repository-relative POSIX path`);
|
|
48
52
|
}
|
|
@@ -53,6 +57,42 @@ function normalizePathEntry(value: unknown, field: string, index: number): strin
|
|
|
53
57
|
`${field}[${index}] exceeds the ${DISPATCH_INTENT_PATH_ENTRY_BYTES_CAP}-byte cap`,
|
|
54
58
|
);
|
|
55
59
|
}
|
|
60
|
+
return trimmed;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeScopePathEntry(
|
|
64
|
+
value: unknown,
|
|
65
|
+
field: string,
|
|
66
|
+
index: number,
|
|
67
|
+
): string | DispatchIntentNormalizationResult {
|
|
68
|
+
const checked = checkedPathText(value, field, index);
|
|
69
|
+
if (typeof checked !== "string") return checked;
|
|
70
|
+
try {
|
|
71
|
+
return normalizePathBoundaryEntry(checked);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof PathBoundaryError) {
|
|
74
|
+
if (error.code === "absolute") {
|
|
75
|
+
return fail("intent_path_absolute", `${field}[${index}] must be a repository-relative POSIX path`);
|
|
76
|
+
}
|
|
77
|
+
if (error.code === "parent") {
|
|
78
|
+
return fail("intent_path_escapes_root", `${field}[${index}] escapes the repository root`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return fail(
|
|
82
|
+
"intent_path_malformed",
|
|
83
|
+
`${field}[${index}] does not follow the repository path boundary grammar: ${error instanceof Error ? error.message : String(error)}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeExpectedOutputEntry(
|
|
89
|
+
value: unknown,
|
|
90
|
+
field: string,
|
|
91
|
+
index: number,
|
|
92
|
+
): string | DispatchIntentNormalizationResult {
|
|
93
|
+
const checked = checkedPathText(value, field, index);
|
|
94
|
+
if (typeof checked !== "string") return checked;
|
|
95
|
+
const trimmed = checked;
|
|
56
96
|
if (trimmed.includes("\\") || path.posix.isAbsolute(trimmed) || path.win32.isAbsolute(trimmed)) {
|
|
57
97
|
return fail("intent_path_absolute", `${field}[${index}] must be a repository-relative POSIX path`);
|
|
58
98
|
}
|
|
@@ -66,7 +106,15 @@ function normalizePathEntry(value: unknown, field: string, index: number): strin
|
|
|
66
106
|
return normalized;
|
|
67
107
|
}
|
|
68
108
|
|
|
69
|
-
function normalizePathList(
|
|
109
|
+
function normalizePathList(
|
|
110
|
+
value: unknown,
|
|
111
|
+
field: string,
|
|
112
|
+
normalizeEntry: (
|
|
113
|
+
value: unknown,
|
|
114
|
+
field: string,
|
|
115
|
+
index: number,
|
|
116
|
+
) => string | DispatchIntentNormalizationResult = normalizeScopePathEntry,
|
|
117
|
+
): string[] | DispatchIntentNormalizationResult {
|
|
70
118
|
if (value === undefined) return [];
|
|
71
119
|
if (!Array.isArray(value)) return fail("intent_path_malformed", `${field} must be an array`);
|
|
72
120
|
if (value.length > DISPATCH_INTENT_PATH_LIST_CAP) {
|
|
@@ -74,7 +122,7 @@ function normalizePathList(value: unknown, field: string): string[] | DispatchIn
|
|
|
74
122
|
}
|
|
75
123
|
const normalized: string[] = [];
|
|
76
124
|
for (const [index, entry] of value.entries()) {
|
|
77
|
-
const result =
|
|
125
|
+
const result = normalizeEntry(entry, field, index);
|
|
78
126
|
if (typeof result !== "string") return result;
|
|
79
127
|
normalized.push(result);
|
|
80
128
|
}
|
|
@@ -82,10 +130,26 @@ function normalizePathList(value: unknown, field: string): string[] | DispatchIn
|
|
|
82
130
|
}
|
|
83
131
|
|
|
84
132
|
export function isDispatchIntent(value: unknown): value is DispatchIntent {
|
|
85
|
-
if (!isRecord(value) || value.version !==
|
|
133
|
+
if (!isRecord(value) || value.version !== 2) return false;
|
|
86
134
|
for (const field of ["readRoots", "writeRoots", "relevantPaths", "expectedOutputs"] as const) {
|
|
87
135
|
if (!Array.isArray(value[field]) || !value[field].every((entry) => typeof entry === "string")) return false;
|
|
88
136
|
}
|
|
137
|
+
const readRoots = value.readRoots as string[];
|
|
138
|
+
const writeRoots = value.writeRoots as string[];
|
|
139
|
+
const relevantPaths = value.relevantPaths as string[];
|
|
140
|
+
if (
|
|
141
|
+
!Array.isArray(value.pathProvenance) ||
|
|
142
|
+
JSON.stringify(value.pathProvenance) !==
|
|
143
|
+
JSON.stringify(
|
|
144
|
+
declaredIntentPathProvenance({
|
|
145
|
+
readRoots,
|
|
146
|
+
writeRoots,
|
|
147
|
+
relevantPaths,
|
|
148
|
+
}),
|
|
149
|
+
)
|
|
150
|
+
) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
89
153
|
return (
|
|
90
154
|
Array.isArray(value.verification) &&
|
|
91
155
|
value.verification.every(
|
|
@@ -115,7 +179,11 @@ export function normalizeDispatchIntent(
|
|
|
115
179
|
if (!Array.isArray(writeRoots)) return writeRoots;
|
|
116
180
|
const relevantPaths = normalizePathList(raw.relevant_paths, "intent.relevant_paths");
|
|
117
181
|
if (!Array.isArray(relevantPaths)) return relevantPaths;
|
|
118
|
-
const expectedOutputs = normalizePathList(
|
|
182
|
+
const expectedOutputs = normalizePathList(
|
|
183
|
+
raw.expected_outputs,
|
|
184
|
+
"intent.expected_outputs",
|
|
185
|
+
normalizeExpectedOutputEntry,
|
|
186
|
+
);
|
|
119
187
|
if (!Array.isArray(expectedOutputs)) return expectedOutputs;
|
|
120
188
|
const rawVerification = raw.verification ?? [];
|
|
121
189
|
if (!Array.isArray(rawVerification)) {
|
|
@@ -154,6 +222,14 @@ export function normalizeDispatchIntent(
|
|
|
154
222
|
}
|
|
155
223
|
return {
|
|
156
224
|
ok: true,
|
|
157
|
-
intent: {
|
|
225
|
+
intent: {
|
|
226
|
+
version: 2,
|
|
227
|
+
readRoots,
|
|
228
|
+
writeRoots,
|
|
229
|
+
relevantPaths,
|
|
230
|
+
pathProvenance: declaredIntentPathProvenance({ readRoots, writeRoots, relevantPaths }),
|
|
231
|
+
expectedOutputs,
|
|
232
|
+
verification,
|
|
233
|
+
},
|
|
158
234
|
};
|
|
159
235
|
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asDirectoryPathBoundary,
|
|
3
|
+
normalizePathBoundaryEntry,
|
|
4
|
+
PathBoundaryError,
|
|
5
|
+
pathBoundaryCovers,
|
|
6
|
+
resolvePathBoundary,
|
|
7
|
+
} from "../../core/path-boundary.js";
|
|
8
|
+
import type { DispatchRequest } from "./contract.js";
|
|
9
|
+
|
|
10
|
+
/** Path-like tokens retained for requests that do not carry typed intent. */
|
|
11
|
+
const DISPATCH_PATH_TOKEN_RE = /(?:[\w.-]+\/)+[\w.-]+|\b[\w-]+\.[A-Za-z0-9]{1,8}\b/g;
|
|
12
|
+
const HTTP_URL_RE = /\bhttps?:\/\/\S+/gu;
|
|
13
|
+
const POSIX_ABSOLUTE_TOKEN_RE = /(?:^|[\s("'])((?:\/[\w.~+-]+)+)/gu;
|
|
14
|
+
const WINDOWS_ABSOLUTE_TOKEN_RE = /(?:^|[\s("'])([A-Za-z]:[\\/][^\s"']+)/gu;
|
|
15
|
+
const BACKSLASH_PATH_RE = /\b[A-Za-z_.-][\w.-]*\\(?:(?:[\w.-]+\\)+[\w.-]+|[\w-]+\.[A-Za-z0-9]{1,8})\b/gu;
|
|
16
|
+
const DOUBLE_SEPARATOR_PATH_RE = /\b[\w.-]+\/\/[\w./-]+/gu;
|
|
17
|
+
|
|
18
|
+
export type DispatchPathProvenanceKind = "declared" | "derived" | "inferred";
|
|
19
|
+
export type DispatchPathConfidence = "certain" | "high" | "medium" | "low";
|
|
20
|
+
export type DispatchPathProvenanceSource =
|
|
21
|
+
| "intent.read_roots"
|
|
22
|
+
| "intent.write_roots"
|
|
23
|
+
| "intent.relevant_paths"
|
|
24
|
+
| "writeRoots"
|
|
25
|
+
| "task"
|
|
26
|
+
| "briefing";
|
|
27
|
+
export type DispatchPathProvenanceReason =
|
|
28
|
+
| "explicit_intent"
|
|
29
|
+
| "legacy_write_roots"
|
|
30
|
+
| "task_path_token"
|
|
31
|
+
| "briefing_path_token";
|
|
32
|
+
|
|
33
|
+
export interface DispatchPathProvenanceEvidence {
|
|
34
|
+
provenance: DispatchPathProvenanceKind;
|
|
35
|
+
source: DispatchPathProvenanceSource;
|
|
36
|
+
confidence: DispatchPathConfidence;
|
|
37
|
+
reason: DispatchPathProvenanceReason;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DispatchPathProvenanceEntry {
|
|
41
|
+
path: string;
|
|
42
|
+
evidence: DispatchPathProvenanceEvidence[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DispatchPathScopeProvenance {
|
|
46
|
+
version: 1;
|
|
47
|
+
mode: "declared" | "legacy-inferred";
|
|
48
|
+
workingContextPaths: DispatchPathProvenanceEntry[];
|
|
49
|
+
writeBoundaries: DispatchPathProvenanceEntry[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DispatchPathScope {
|
|
53
|
+
source: "declared" | "inferred";
|
|
54
|
+
workingContextPaths: ReadonlyArray<string>;
|
|
55
|
+
writeBoundaries: ReadonlyArray<string>;
|
|
56
|
+
inferredOnlyPaths: ReadonlyArray<string>;
|
|
57
|
+
provenance: DispatchPathScopeProvenance;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface DeclaredScopeReplacementNotice {
|
|
61
|
+
code: "typed_scope_replaced_inferred_paths";
|
|
62
|
+
level: "warning";
|
|
63
|
+
omittedPaths: ReadonlyArray<string>;
|
|
64
|
+
message: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface LegacyScopeInferenceNotice {
|
|
68
|
+
code: "legacy_scope_inferred" | "legacy_scope_empty";
|
|
69
|
+
level: "warning";
|
|
70
|
+
paths: ReadonlyArray<{
|
|
71
|
+
path: string;
|
|
72
|
+
policy: "working-context" | "write-boundary";
|
|
73
|
+
provenance: DispatchPathProvenanceKind;
|
|
74
|
+
source: DispatchPathProvenanceSource;
|
|
75
|
+
confidence: DispatchPathConfidence;
|
|
76
|
+
}>;
|
|
77
|
+
message: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type DispatchPathScopeInferenceErrorCode = "legacy_scope_path_absolute" | "legacy_scope_path_malformed";
|
|
81
|
+
|
|
82
|
+
export class DispatchPathScopeInferenceError extends Error {
|
|
83
|
+
readonly code: DispatchPathScopeInferenceErrorCode;
|
|
84
|
+
readonly source: "task" | "briefing";
|
|
85
|
+
readonly token: string;
|
|
86
|
+
|
|
87
|
+
constructor(code: DispatchPathScopeInferenceErrorCode, source: "task" | "briefing", token: string, detail: string) {
|
|
88
|
+
super(`${code}: ${source} contains ${detail} '${token}'; declare intent explicitly`);
|
|
89
|
+
this.name = "DispatchPathScopeInferenceError";
|
|
90
|
+
this.code = code;
|
|
91
|
+
this.source = source;
|
|
92
|
+
this.token = token;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const DECLARED_EVIDENCE = {
|
|
97
|
+
readRoots: {
|
|
98
|
+
provenance: "declared",
|
|
99
|
+
source: "intent.read_roots",
|
|
100
|
+
confidence: "certain",
|
|
101
|
+
reason: "explicit_intent",
|
|
102
|
+
},
|
|
103
|
+
writeRoots: {
|
|
104
|
+
provenance: "declared",
|
|
105
|
+
source: "intent.write_roots",
|
|
106
|
+
confidence: "certain",
|
|
107
|
+
reason: "explicit_intent",
|
|
108
|
+
},
|
|
109
|
+
relevantPaths: {
|
|
110
|
+
provenance: "declared",
|
|
111
|
+
source: "intent.relevant_paths",
|
|
112
|
+
confidence: "certain",
|
|
113
|
+
reason: "explicit_intent",
|
|
114
|
+
},
|
|
115
|
+
} as const satisfies Record<"readRoots" | "writeRoots" | "relevantPaths", DispatchPathProvenanceEvidence>;
|
|
116
|
+
|
|
117
|
+
function cloneEvidence(evidence: DispatchPathProvenanceEvidence): DispatchPathProvenanceEvidence {
|
|
118
|
+
return { ...evidence };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function addProvenance(
|
|
122
|
+
entries: Map<string, DispatchPathProvenanceEntry>,
|
|
123
|
+
path: string,
|
|
124
|
+
evidence: DispatchPathProvenanceEvidence,
|
|
125
|
+
): void {
|
|
126
|
+
const current = entries.get(path);
|
|
127
|
+
if (current === undefined) {
|
|
128
|
+
entries.set(path, { path, evidence: [cloneEvidence(evidence)] });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!current.evidence.some((entry) => entry.source === evidence.source)) {
|
|
132
|
+
current.evidence.push(cloneEvidence(evidence));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function sortedEntries(entries: Map<string, DispatchPathProvenanceEntry>): DispatchPathProvenanceEntry[] {
|
|
137
|
+
return [...entries.values()]
|
|
138
|
+
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0))
|
|
139
|
+
.map((entry) => ({ path: entry.path, evidence: entry.evidence.map(cloneEvidence) }));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Build the required field-level provenance for normalized declared intent. */
|
|
143
|
+
export function declaredIntentPathProvenance(input: {
|
|
144
|
+
readRoots: ReadonlyArray<string>;
|
|
145
|
+
writeRoots: ReadonlyArray<string>;
|
|
146
|
+
relevantPaths: ReadonlyArray<string>;
|
|
147
|
+
}): DispatchPathProvenanceEntry[] {
|
|
148
|
+
const entries = new Map<string, DispatchPathProvenanceEntry>();
|
|
149
|
+
for (const path of input.readRoots) addProvenance(entries, path, DECLARED_EVIDENCE.readRoots);
|
|
150
|
+
for (const path of input.writeRoots) addProvenance(entries, path, DECLARED_EVIDENCE.writeRoots);
|
|
151
|
+
for (const path of input.relevantPaths) addProvenance(entries, path, DECLARED_EVIDENCE.relevantPaths);
|
|
152
|
+
return sortedEntries(entries);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function inferredPaths(req: Pick<DispatchRequest, "task" | "briefing">): string[] {
|
|
156
|
+
const paths = new Set<string>();
|
|
157
|
+
const text = `${req.task}\n${req.briefing ?? ""}`;
|
|
158
|
+
for (const match of text.matchAll(DISPATCH_PATH_TOKEN_RE)) {
|
|
159
|
+
const token = inferredPathToken(match[0]);
|
|
160
|
+
if (token.startsWith("http://") || token.startsWith("https://")) continue;
|
|
161
|
+
paths.add(token);
|
|
162
|
+
}
|
|
163
|
+
return [...paths];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function inferredPathToken(token: string): string {
|
|
167
|
+
return token.replace(/\.+$/u, "");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function failMalformedText(text: string, source: "task" | "briefing"): void {
|
|
171
|
+
const withoutUrls = text.replace(HTTP_URL_RE, (url) => " ".repeat(url.length));
|
|
172
|
+
const absolute = POSIX_ABSOLUTE_TOKEN_RE.exec(withoutUrls)?.[1];
|
|
173
|
+
POSIX_ABSOLUTE_TOKEN_RE.lastIndex = 0;
|
|
174
|
+
if (absolute !== undefined) {
|
|
175
|
+
throw new DispatchPathScopeInferenceError("legacy_scope_path_absolute", source, absolute, "an absolute path token");
|
|
176
|
+
}
|
|
177
|
+
const windowsAbsolute = WINDOWS_ABSOLUTE_TOKEN_RE.exec(withoutUrls)?.[1];
|
|
178
|
+
WINDOWS_ABSOLUTE_TOKEN_RE.lastIndex = 0;
|
|
179
|
+
if (windowsAbsolute !== undefined) {
|
|
180
|
+
throw new DispatchPathScopeInferenceError(
|
|
181
|
+
"legacy_scope_path_absolute",
|
|
182
|
+
source,
|
|
183
|
+
windowsAbsolute,
|
|
184
|
+
"an absolute path token",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
for (const pattern of [BACKSLASH_PATH_RE, DOUBLE_SEPARATOR_PATH_RE]) {
|
|
188
|
+
const malformed = pattern.exec(withoutUrls)?.[0];
|
|
189
|
+
pattern.lastIndex = 0;
|
|
190
|
+
if (malformed !== undefined) {
|
|
191
|
+
throw new DispatchPathScopeInferenceError(
|
|
192
|
+
"legacy_scope_path_malformed",
|
|
193
|
+
source,
|
|
194
|
+
malformed,
|
|
195
|
+
"a malformed path token",
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function strictInferredPaths(
|
|
202
|
+
text: string,
|
|
203
|
+
source: "task" | "briefing",
|
|
204
|
+
): Array<{ path: string; evidence: DispatchPathProvenanceEvidence }> {
|
|
205
|
+
failMalformedText(text, source);
|
|
206
|
+
const withoutUrls = text.replace(HTTP_URL_RE, (url) => " ".repeat(url.length));
|
|
207
|
+
const evidence: DispatchPathProvenanceEvidence =
|
|
208
|
+
source === "task"
|
|
209
|
+
? { provenance: "inferred", source, confidence: "medium", reason: "task_path_token" }
|
|
210
|
+
: { provenance: "inferred", source, confidence: "low", reason: "briefing_path_token" };
|
|
211
|
+
const result: Array<{ path: string; evidence: DispatchPathProvenanceEvidence }> = [];
|
|
212
|
+
for (const match of withoutUrls.matchAll(DISPATCH_PATH_TOKEN_RE)) {
|
|
213
|
+
const token = inferredPathToken(match[0]);
|
|
214
|
+
try {
|
|
215
|
+
result.push({ path: normalizePathBoundaryEntry(token), evidence });
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const code =
|
|
218
|
+
error instanceof PathBoundaryError && error.code === "absolute"
|
|
219
|
+
? "legacy_scope_path_absolute"
|
|
220
|
+
: "legacy_scope_path_malformed";
|
|
221
|
+
throw new DispatchPathScopeInferenceError(
|
|
222
|
+
code,
|
|
223
|
+
source,
|
|
224
|
+
token,
|
|
225
|
+
code === "legacy_scope_path_absolute" ? "an absolute path token" : "a malformed path token",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function legacyPathScope(req: DispatchRequest, cwd: string): DispatchPathScope {
|
|
233
|
+
const workingEntries = new Map<string, DispatchPathProvenanceEntry>();
|
|
234
|
+
const writeEntries = new Map<string, DispatchPathProvenanceEntry>();
|
|
235
|
+
const writeEvidence: DispatchPathProvenanceEvidence = {
|
|
236
|
+
provenance: "derived",
|
|
237
|
+
source: "writeRoots",
|
|
238
|
+
confidence: "high",
|
|
239
|
+
reason: "legacy_write_roots",
|
|
240
|
+
};
|
|
241
|
+
for (const root of req.writeRoots ?? []) {
|
|
242
|
+
addProvenance(workingEntries, root, writeEvidence);
|
|
243
|
+
addProvenance(writeEntries, asDirectoryPathBoundary(resolvePathBoundary(cwd, root)), writeEvidence);
|
|
244
|
+
}
|
|
245
|
+
for (const candidate of strictInferredPaths(req.task, "task")) {
|
|
246
|
+
addProvenance(workingEntries, candidate.path, candidate.evidence);
|
|
247
|
+
}
|
|
248
|
+
if (req.briefing !== undefined) {
|
|
249
|
+
for (const candidate of strictInferredPaths(req.briefing, "briefing")) {
|
|
250
|
+
addProvenance(workingEntries, candidate.path, candidate.evidence);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const workingContext = [...workingEntries.values()].map((entry) => ({
|
|
254
|
+
path: entry.path,
|
|
255
|
+
evidence: entry.evidence.map(cloneEvidence),
|
|
256
|
+
}));
|
|
257
|
+
const writeBoundaries = [...writeEntries.values()].map((entry) => ({
|
|
258
|
+
path: entry.path,
|
|
259
|
+
evidence: entry.evidence.map(cloneEvidence),
|
|
260
|
+
}));
|
|
261
|
+
return {
|
|
262
|
+
source: "inferred",
|
|
263
|
+
workingContextPaths: workingContext.map((entry) => entry.path),
|
|
264
|
+
writeBoundaries: writeBoundaries.map((entry) => entry.path),
|
|
265
|
+
inferredOnlyPaths: [],
|
|
266
|
+
provenance: {
|
|
267
|
+
version: 1,
|
|
268
|
+
mode: "legacy-inferred",
|
|
269
|
+
workingContextPaths: workingContext,
|
|
270
|
+
writeBoundaries,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Resolve every policy-bearing path projection for one dispatch request. */
|
|
276
|
+
export function resolveDispatchPathScope(req: DispatchRequest): DispatchPathScope {
|
|
277
|
+
const cwd = req.cwd ?? process.cwd();
|
|
278
|
+
if (req.intent === undefined) return legacyPathScope(req, cwd);
|
|
279
|
+
|
|
280
|
+
const declaredProvenance = declaredIntentPathProvenance(req.intent);
|
|
281
|
+
const declaredPaths = declaredProvenance.map((entry) => entry.path);
|
|
282
|
+
const inferredOnlyPaths = inferredPaths(req).filter((candidate) => !pathBoundaryCovers(declaredPaths, candidate));
|
|
283
|
+
const writeEvidence = DECLARED_EVIDENCE.writeRoots;
|
|
284
|
+
const writeBoundaries = req.intent.writeRoots.map((root) => ({
|
|
285
|
+
path: resolvePathBoundary(cwd, root),
|
|
286
|
+
evidence: [cloneEvidence(writeEvidence)],
|
|
287
|
+
}));
|
|
288
|
+
return {
|
|
289
|
+
source: "declared",
|
|
290
|
+
workingContextPaths: declaredPaths,
|
|
291
|
+
writeBoundaries: writeBoundaries.map((entry) => entry.path),
|
|
292
|
+
inferredOnlyPaths,
|
|
293
|
+
provenance: {
|
|
294
|
+
version: 1,
|
|
295
|
+
mode: "declared",
|
|
296
|
+
workingContextPaths: declaredProvenance,
|
|
297
|
+
writeBoundaries,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Render the named diagnostic for the declared-scope replacement tradeoff. */
|
|
303
|
+
export function declaredScopeReplacementDiagnostic(scope: DispatchPathScope): string | null {
|
|
304
|
+
if (scope.source !== "declared" || scope.inferredOnlyPaths.length === 0) return null;
|
|
305
|
+
return `typed_scope_replaced_inferred_paths: typed intent omitted prose-inferred paths ${scope.inferredOnlyPaths.join(
|
|
306
|
+
", ",
|
|
307
|
+
)}; those paths did not select project rules or expand worker authority`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Render the interactive warning for the declared-scope replacement tradeoff. */
|
|
311
|
+
export function declaredScopeReplacementNotice(scope: DispatchPathScope): DeclaredScopeReplacementNotice | null {
|
|
312
|
+
if (scope.source !== "declared" || scope.inferredOnlyPaths.length === 0) return null;
|
|
313
|
+
const omittedPaths = [...scope.inferredOnlyPaths];
|
|
314
|
+
return {
|
|
315
|
+
code: "typed_scope_replaced_inferred_paths",
|
|
316
|
+
level: "warning",
|
|
317
|
+
omittedPaths,
|
|
318
|
+
message: `[dispatch scope] typed intent replaced prose path inference; omitted paths: ${omittedPaths.join(
|
|
319
|
+
", ",
|
|
320
|
+
)}. Those paths did not select project rules or expand worker authority.`,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function legacyNoticePaths(scope: DispatchPathScope): LegacyScopeInferenceNotice["paths"] {
|
|
325
|
+
const paths: Array<LegacyScopeInferenceNotice["paths"][number]> = [];
|
|
326
|
+
for (const [policy, entries] of [
|
|
327
|
+
["working-context", scope.provenance.workingContextPaths],
|
|
328
|
+
["write-boundary", scope.provenance.writeBoundaries],
|
|
329
|
+
] as const) {
|
|
330
|
+
for (const entry of entries) {
|
|
331
|
+
for (const evidence of entry.evidence) {
|
|
332
|
+
paths.push({
|
|
333
|
+
path: entry.path,
|
|
334
|
+
policy,
|
|
335
|
+
provenance: evidence.provenance,
|
|
336
|
+
source: evidence.source,
|
|
337
|
+
confidence: evidence.confidence,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return paths;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Render the operator-visible warning for a legacy scope resolution. */
|
|
346
|
+
export function legacyScopeInferenceNotice(scope: DispatchPathScope): LegacyScopeInferenceNotice | null {
|
|
347
|
+
if (scope.source !== "inferred") return null;
|
|
348
|
+
const paths = legacyNoticePaths(scope);
|
|
349
|
+
if (paths.length === 0) {
|
|
350
|
+
return {
|
|
351
|
+
code: "legacy_scope_empty",
|
|
352
|
+
level: "warning",
|
|
353
|
+
paths,
|
|
354
|
+
message:
|
|
355
|
+
"[dispatch scope] legacy dispatch has no declared intent and inferred no policy-bearing paths. Path-scoped rules received no inferred match input, and worker authority did not expand.",
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const rendered = paths
|
|
359
|
+
.map(
|
|
360
|
+
(entry) =>
|
|
361
|
+
`${entry.policy} ${entry.path} (provenance=${entry.provenance} source=${entry.source} confidence=${entry.confidence})`,
|
|
362
|
+
)
|
|
363
|
+
.join("; ");
|
|
364
|
+
return {
|
|
365
|
+
code: "legacy_scope_inferred",
|
|
366
|
+
level: "warning",
|
|
367
|
+
paths,
|
|
368
|
+
message: `[dispatch scope] legacy dispatch resolved policy-bearing scope without declared intent: ${rendered}. Review this scope before execution.`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
@@ -10,7 +10,7 @@ import type { RunEnvelope, RunReceipt, RunReceiptDraft, RunReceiptIntegrity, Run
|
|
|
10
10
|
* base, so there are no historical receipts to keep verifying: a receipt is
|
|
11
11
|
* either this version or it is not a receipt.
|
|
12
12
|
*/
|
|
13
|
-
export const RUN_RECEIPT_INTEGRITY_VERSION: RunReceiptIntegrity["version"] =
|
|
13
|
+
export const RUN_RECEIPT_INTEGRITY_VERSION: RunReceiptIntegrity["version"] = 20;
|
|
14
14
|
export type ReceiptIntegrityVersion = RunReceiptIntegrity["version"];
|
|
15
15
|
export type ReceiptIntegrityField = keyof RunReceiptDraft;
|
|
16
16
|
export const RUN_RECEIPT_INTEGRITY_ALGORITHM = "sha256";
|
|
@@ -77,6 +77,7 @@ export const RECEIPT_INTEGRITY_FIELD_COVERAGE = {
|
|
|
77
77
|
requestOrigin: true,
|
|
78
78
|
task: true,
|
|
79
79
|
intent: true,
|
|
80
|
+
pathScope: true,
|
|
80
81
|
// Historical receipts omit this field, so their receipt and ledger payloads remain unchanged.
|
|
81
82
|
budget: true,
|
|
82
83
|
targetId: true,
|
|
@@ -15,6 +15,7 @@ import type { CostProvenance, RuntimeTargetSnapshot } from "../providers/index.j
|
|
|
15
15
|
import type { RunToolBudgetEnvelope } from "./budget-envelope.js";
|
|
16
16
|
import type { ExecutionRole, GateTopologyRole } from "./execution-role.js";
|
|
17
17
|
import type { DispatchIntent } from "./intent.js";
|
|
18
|
+
import type { DispatchPathScopeProvenance } from "./path-scope.js";
|
|
18
19
|
import type { RouteDecisionV1 } from "./route-decision.js";
|
|
19
20
|
import type { RoutingIntent } from "./routing-intent.js";
|
|
20
21
|
|
|
@@ -125,12 +126,16 @@ export interface RunPersonaOverride {
|
|
|
125
126
|
|
|
126
127
|
/**
|
|
127
128
|
* Effective project-context decision for a run, recorded on every receipt so
|
|
128
|
-
* evidence can prove what a worker saw without rehashing. `tier
|
|
129
|
-
*
|
|
130
|
-
* pre-provenance receipt where the
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* (
|
|
129
|
+
* evidence can prove what a worker saw without rehashing. `tier` is the
|
|
130
|
+
* recipe's CLIO-CODER.md handbook policy and is written explicitly: `none`
|
|
131
|
+
* distinguishes "policy said none" from a pre-provenance receipt where the
|
|
132
|
+
* whole block is absent. `chars` and `contentHash` describe every structured
|
|
133
|
+
* message that was actually sent, whatever the tier, and `sections` names
|
|
134
|
+
* them: `workspace-root` (sent under both tiers, so a none-tier run still
|
|
135
|
+
* records characters), `clio-md` (bounded only), and
|
|
136
|
+
* `verification-expectations` (bounded only, never for ACP delegation). A
|
|
137
|
+
* none-tier block naming only `workspace-root` is a consistent record; the
|
|
138
|
+
* trust adapter treats any other none-tier content as contradictory.
|
|
134
139
|
*/
|
|
135
140
|
export interface RunProjectContextProvenance {
|
|
136
141
|
tier: "none" | "bounded";
|
|
@@ -321,7 +326,7 @@ export interface RunPhaseDurations {
|
|
|
321
326
|
* bumping one without the other is a compile error.
|
|
322
327
|
*/
|
|
323
328
|
export interface RunReceiptIntegrity {
|
|
324
|
-
version:
|
|
329
|
+
version: 20;
|
|
325
330
|
algorithm: "sha256";
|
|
326
331
|
digest: string;
|
|
327
332
|
}
|
|
@@ -714,6 +719,8 @@ export interface RunReceipt {
|
|
|
714
719
|
task: string;
|
|
715
720
|
/** Normalized dispatch intent admitted before worker execution. */
|
|
716
721
|
intent?: DispatchIntent;
|
|
722
|
+
/** Resolved policy-bearing paths with field source and confidence, without source prose. */
|
|
723
|
+
pathScope?: DispatchPathScopeProvenance;
|
|
717
724
|
/** Integrity-sealed recipe policy, invocation request, effective phase, and admission reasons. */
|
|
718
725
|
budget?: RunToolBudgetEnvelope;
|
|
719
726
|
/** Proof of briefing content without copying its prose into the receipt. */
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { THINKING_LEVELS, type ThinkingLevel } from "../../core/defaults.js";
|
|
10
|
+
import { asDirectoryPathBoundary, resolvePathBoundary } from "../../core/path-boundary.js";
|
|
10
11
|
import { cloneValidatedResponseSchema } from "../../core/response-schema.js";
|
|
11
12
|
import { isToolProfileName, type ToolProfileName } from "../../tools/profiles.js";
|
|
12
13
|
import type { AgentProduct } from "../agents/spec.js";
|
|
@@ -482,8 +483,8 @@ export function validateJobSpec(spec: unknown): Validated {
|
|
|
482
483
|
}
|
|
483
484
|
if (isDispatchIntent(spec.intent) && spec.intent.writeRoots.length > 0 && Array.isArray(spec.writeRoots)) {
|
|
484
485
|
const jobCwd = typeof spec.cwd === "string" && spec.cwd.length > 0 ? spec.cwd : process.cwd();
|
|
485
|
-
const legacy = [...new Set(spec.writeRoots.map((root) =>
|
|
486
|
-
const declared = [...new Set(spec.intent.writeRoots.map((root) =>
|
|
486
|
+
const legacy = [...new Set(spec.writeRoots.map((root) => resolvePathBoundary(jobCwd, String(root))))].sort();
|
|
487
|
+
const declared = [...new Set(spec.intent.writeRoots.map((root) => resolvePathBoundary(jobCwd, root)))].sort();
|
|
487
488
|
if (JSON.stringify(legacy) !== JSON.stringify(declared)) errors.push("intent_write_roots_contradiction");
|
|
488
489
|
}
|
|
489
490
|
|
|
@@ -597,7 +598,9 @@ export function validateJobSpec(spec: unknown): Validated {
|
|
|
597
598
|
: undefined;
|
|
598
599
|
if (effectiveWriteRoots !== undefined) {
|
|
599
600
|
const jobCwd = typeof spec.cwd === "string" && spec.cwd.length > 0 ? spec.cwd : process.cwd();
|
|
600
|
-
out.writeRoots =
|
|
601
|
+
out.writeRoots = isDispatchIntent(spec.intent)
|
|
602
|
+
? effectiveWriteRoots.map((root) => resolvePathBoundary(jobCwd, String(root)))
|
|
603
|
+
: effectiveWriteRoots.map((root) => asDirectoryPathBoundary(path.resolve(jobCwd, String(root))));
|
|
601
604
|
}
|
|
602
605
|
if (typeof spec.requestOrigin === "string" && VALID_REQUEST_ORIGINS.has(spec.requestOrigin)) {
|
|
603
606
|
out.requestOrigin = spec.requestOrigin as DispatchRequestOrigin;
|