toolroll 0.9.36 → 0.9.38
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 +77 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- package/dist/browser/THIRD_PARTY_NOTICES.txt +3 -3
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +26 -26
- package/dist/browser-crew.js +5 -0
- package/dist/browser-workspace.d.ts +2 -0
- package/dist/build-review.d.ts +1 -1
- package/dist/build-review.js +2 -1
- package/dist/chat-acceptance.d.ts +1 -1
- package/dist/chat-actions.d.ts +18 -12
- package/dist/chat-actions.js +15 -65
- package/dist/chat-ask.js +5 -5
- package/dist/chat-channel.js +20 -9
- package/dist/chat-decide.d.ts +18 -4
- package/dist/chat-decide.js +31 -11
- package/dist/chat-delivery-state.d.ts +12 -70
- package/dist/chat-delivery-state.js +17 -135
- package/dist/chat-delivery.js +58 -30
- package/dist/chat-flow.js +8 -6
- package/dist/chat-inbox.js +1 -1
- package/dist/coding-context.d.ts +2 -39
- package/dist/coding-context.js +2 -1
- package/dist/coding-handoff.d.ts +6 -25
- package/dist/coding-handoff.js +11 -5
- package/dist/coding-types.d.ts +2 -26
- package/dist/coding-workspace.d.ts +2 -0
- package/dist/coding-workspace.js +50 -14
- package/dist/contracts/acceptance-terms.d.ts +14 -0
- package/dist/contracts/acceptance-terms.js +16 -0
- package/dist/contracts/chat-actions.d.ts +328 -0
- package/dist/contracts/chat-actions.js +133 -0
- package/dist/contracts/chat-callback-rows.d.ts +59 -0
- package/dist/contracts/chat-callback-rows.js +46 -0
- package/dist/contracts/chat-content.d.ts +276 -0
- package/dist/contracts/chat-content.js +127 -0
- package/dist/contracts/chat-tables.d.ts +6 -0
- package/dist/contracts/chat-tables.js +136 -0
- package/dist/contracts/coding-context.d.ts +97 -0
- package/dist/contracts/coding-context.js +60 -0
- package/dist/contracts/coding-handoff.d.ts +44 -0
- package/dist/contracts/coding-handoff.js +47 -0
- package/dist/contracts/coding-workspace.d.ts +165 -0
- package/dist/contracts/coding-workspace.js +71 -0
- package/dist/contracts/contract.d.ts +7 -2
- package/dist/contracts/contract.js +21 -12
- package/dist/contracts/decision.d.ts +45 -0
- package/dist/contracts/decision.js +45 -0
- package/dist/contracts/discord-callback.d.ts +45 -0
- package/dist/contracts/discord-callback.js +34 -0
- package/dist/contracts/flow-card.d.ts +50 -0
- package/dist/contracts/flow-card.js +46 -0
- package/dist/contracts/flow-send.d.ts +119 -0
- package/dist/contracts/flow-send.js +112 -0
- package/dist/contracts/flow.d.ts +5 -0
- package/dist/contracts/flow.js +5 -0
- package/dist/contracts/gateway-tools.d.ts +374 -0
- package/dist/contracts/gateway-tools.js +94 -0
- package/dist/contracts/lead-context.d.ts +165 -0
- package/dist/contracts/lead-context.js +73 -0
- package/dist/contracts/lead-tools.d.ts +1905 -0
- package/dist/contracts/lead-tools.js +265 -0
- package/dist/contracts/memory-pass.d.ts +158 -0
- package/dist/contracts/memory-pass.js +102 -0
- package/dist/contracts/project-knowledge.d.ts +126 -0
- package/dist/contracts/project-knowledge.js +93 -0
- package/dist/contracts/project-memory.d.ts +117 -0
- package/dist/contracts/project-memory.js +51 -0
- package/dist/contracts/project-skills.d.ts +121 -0
- package/dist/contracts/project-skills.js +76 -0
- package/dist/contracts/review-findings.d.ts +46 -0
- package/dist/contracts/review-findings.js +48 -0
- package/dist/contracts/route.d.ts +484 -0
- package/dist/contracts/route.js +128 -0
- package/dist/contracts/scope.d.ts +105 -0
- package/dist/contracts/scope.js +114 -0
- package/dist/contracts/scout-report.d.ts +234 -0
- package/dist/contracts/scout-report.js +141 -0
- package/dist/contracts/slack-callback.d.ts +43 -0
- package/dist/contracts/slack-callback.js +35 -0
- package/dist/contracts/sort-answer.d.ts +83 -0
- package/dist/contracts/sort-answer.js +77 -0
- package/dist/contracts/stage-output.d.ts +127 -0
- package/dist/contracts/stage-output.js +166 -0
- package/dist/contracts/task-sizing.d.ts +24 -0
- package/dist/contracts/task-sizing.js +28 -0
- package/dist/contracts/teammate-turn.d.ts +43 -0
- package/dist/contracts/teammate-turn.js +51 -0
- package/dist/contracts/teams-callback.d.ts +14 -0
- package/dist/contracts/teams-callback.js +14 -0
- package/dist/contracts/telegram-callback.d.ts +120 -0
- package/dist/contracts/telegram-callback.js +97 -0
- package/dist/decision.d.ts +11 -20
- package/dist/decision.js +86 -167
- package/dist/desktop-update.d.ts +1 -1
- package/dist/discord-chat.js +31 -20
- package/dist/flow-engine.js +14 -17
- package/dist/flow-items.d.ts +4 -13
- package/dist/flow-items.js +1 -15
- package/dist/flow-send.d.ts +6 -37
- package/dist/flow-send.js +18 -14
- package/dist/flow-share.js +7 -1
- package/dist/flow-sort.d.ts +7 -26
- package/dist/flow-sort.js +25 -20
- package/dist/flow-steps.js +3 -2
- package/dist/flows.d.ts +2 -4
- package/dist/flows.js +9 -5
- package/dist/invoke.js +4 -4
- package/dist/lead-context.d.ts +4 -2
- package/dist/lead-context.js +24 -17
- package/dist/mate-progress.d.ts +26 -0
- package/dist/mate-progress.js +24 -0
- package/dist/mate-tools.d.ts +4 -0
- package/dist/mate-tools.js +108 -275
- package/dist/mate.js +11 -2
- package/dist/mcp-connect.d.ts +24 -4
- package/dist/mcp-connect.js +78 -15
- package/dist/mcp.js +84 -216
- package/dist/memory-pass.d.ts +17 -42
- package/dist/memory-pass.js +54 -26
- package/dist/phase-routing.d.ts +19 -72
- package/dist/phase-routing.js +34 -164
- package/dist/project-knowledge.d.ts +16 -35
- package/dist/project-knowledge.js +42 -21
- package/dist/project-memory.d.ts +2 -28
- package/dist/project-memory.js +28 -13
- package/dist/project-skills.d.ts +16 -38
- package/dist/project-skills.js +41 -32
- package/dist/project-tools.d.ts +1 -0
- package/dist/project-tools.js +4 -4
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +4 -16
- package/dist/result-shots.d.ts +1 -1
- package/dist/result-shots.js +2 -1
- package/dist/review-switch.d.ts +3 -8
- package/dist/reviewer.d.ts +8 -12
- package/dist/reviewer.js +11 -35
- package/dist/scope.d.ts +21 -43
- package/dist/scope.js +42 -128
- package/dist/scout-report.d.ts +9 -194
- package/dist/scout-report.js +113 -264
- package/dist/scout.js +15 -11
- package/dist/serve.js +23 -5
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.js +26 -20
- package/dist/slack-state.js +2 -1
- package/dist/store.d.ts +9 -32
- package/dist/store.js +18 -15
- package/dist/structured-output.js +2 -1
- package/dist/subscription-chat.d.ts +4 -0
- package/dist/subscription-chat.js +4 -1
- package/dist/task-review-brief.d.ts +1 -7
- package/dist/task-review-brief.js +6 -6
- package/dist/task-sizing.d.ts +3 -27
- package/dist/task-sizing.js +11 -26
- package/dist/task-text.d.ts +0 -5
- package/dist/task-text.js +1 -3
- package/dist/teammate-question.js +7 -5
- package/dist/teammate-work.js +9 -5
- package/dist/teammates.d.ts +13 -64
- package/dist/teammates.js +32 -33
- package/dist/teams-chat.js +20 -11
- package/dist/telegram-flow.d.ts +9 -0
- package/dist/telegram-flow.js +44 -8
- package/dist/telegram-mate.d.ts +4 -14
- package/dist/telegram-mate.js +9 -8
- package/dist/telegram-team.d.ts +2 -19
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +94 -68
- package/dist/text-limits.d.ts +142 -1
- package/dist/text-limits.js +144 -1
- package/dist/tools-ui.d.ts +2 -1
- package/dist/tools-ui.js +7 -4
- package/package.json +17 -12
package/dist/provider.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ export type Invocation = {
|
|
|
81
81
|
/** A research step's project tools: only these servers, each offering only
|
|
82
82
|
* these read-only actions (Codex's `enabled_tools`). Others are left out. */
|
|
83
83
|
readOnlyTools?: Readonly<Record<string, readonly string[]>>;
|
|
84
|
+
/** Why a research step's connected service was left out, by server, when it isn't the usual reason (its grant writes). */
|
|
85
|
+
researchWithheld?: Readonly<Record<string, string>>;
|
|
84
86
|
};
|
|
85
87
|
export type ProviderRunner = (file: string, args: readonly string[], options?: RunOptions) => Promise<ExecResult>;
|
|
86
88
|
/** The harness's own account of how a turn ended. */
|
package/dist/provider.js
CHANGED
|
@@ -22,6 +22,7 @@ import { claudeFenceSettings, codexFenceArgv } from "./agent-fence.js";
|
|
|
22
22
|
import {} from "./exec.js";
|
|
23
23
|
import { runStreamJsonl, runClaudeStreamJsonl, runGeminiStreamJsonl } from "./exec.js";
|
|
24
24
|
import { scanForSecrets } from "./evidence.js";
|
|
25
|
+
import { FINDINGS_MODEL_SCHEMA } from "./contracts/review-findings.js";
|
|
25
26
|
import { REVIEW_OUTPUT_LIMITS } from "./structured-output.js";
|
|
26
27
|
export const PROVIDER_IDS = ["claude", "codex", "openrouter", "gemini"];
|
|
27
28
|
export function isProviderId(value) {
|
|
@@ -169,22 +170,9 @@ const CLAUDE_REVIEW_JSON_SCHEMA = {
|
|
|
169
170
|
additionalProperties: false,
|
|
170
171
|
},
|
|
171
172
|
},
|
|
172
|
-
// The automatic build review's reply (build-review.ts)
|
|
173
|
-
// parseBuildFindings
|
|
174
|
-
findings:
|
|
175
|
-
type: "array",
|
|
176
|
-
items: {
|
|
177
|
-
type: "object",
|
|
178
|
-
properties: {
|
|
179
|
-
severity: { type: "string", enum: ["HIGH", "MEDIUM", "LOW"] },
|
|
180
|
-
file: { type: "string" },
|
|
181
|
-
line: { type: "integer", minimum: 1 },
|
|
182
|
-
scenario: { type: "string" },
|
|
183
|
-
},
|
|
184
|
-
required: ["severity", "file", "line", "scenario"],
|
|
185
|
-
additionalProperties: false,
|
|
186
|
-
},
|
|
187
|
-
},
|
|
173
|
+
// The automatic build review's reply (build-review.ts), from its one
|
|
174
|
+
// schema (src/contracts/review-findings.ts); parseBuildFindings reads it.
|
|
175
|
+
findings: FINDINGS_MODEL_SCHEMA,
|
|
188
176
|
// The read request rides the same structured reply channel and
|
|
189
177
|
// session. The machine validates the exact allowlist/hash/range
|
|
190
178
|
// before supplying bytes; this only shapes the request.
|
package/dist/result-shots.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Notification, type ResultScreenshots, type Store } from "./store.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type ChatContent, type ChatState } from "./chat-delivery-state.js";
|
|
3
3
|
/** "Up to 4": the most screenshots one result ever sends. */
|
|
4
4
|
export declare const RESULT_SHOTS_MAX = 4;
|
|
5
5
|
/** The choices in the order Settings and the CLI show them. */
|
package/dist/result-shots.js
CHANGED
|
@@ -21,6 +21,7 @@ import { publicChatText } from "./chat-display.js";
|
|
|
21
21
|
import { chatTitle } from "./chat-voice.js";
|
|
22
22
|
import { itemCaption, readFlowItems } from "./flow-items.js";
|
|
23
23
|
import { FLOW_SHOTS_KIND, RESULT_SHOTS_KIND } from "./store.js";
|
|
24
|
+
import { savedChatPart } from "./chat-delivery-state.js";
|
|
24
25
|
/** "Up to 4": the most screenshots one result ever sends. */
|
|
25
26
|
export const RESULT_SHOTS_MAX = 4;
|
|
26
27
|
/** The choices in the order Settings and the CLI show them. */
|
|
@@ -168,6 +169,6 @@ export function refuseResultShots(state, store, part, content, app, now) {
|
|
|
168
169
|
const title = chatTitle(store, content.image?.taskId ?? content.task ?? "");
|
|
169
170
|
const line = { text: `Screenshots for ${title} weren't sent: the ${app} app isn't allowed to upload files here.`,
|
|
170
171
|
...(content.task === undefined ? {} : { task: content.task }), ...(content.run === undefined ? {} : { run: content.run }) };
|
|
171
|
-
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL,problem=NULL,file=NULL,uploaded=0,uncertain=0,created=? WHERE id=?").run(
|
|
172
|
+
state.prepare("UPDATE chat_part SET payload=?,state='pending',next_at=NULL,problem=NULL,file=NULL,uploaded=0,uncertain=0,created=? WHERE id=?").run(savedChatPart(line), now.toISOString(), part.id);
|
|
172
173
|
state.prepare("UPDATE chat_part SET state='dropped',problem='Uploads are not allowed here' WHERE event=? AND id<>? AND state='pending'").run(part.event, part.id);
|
|
173
174
|
}
|
package/dist/review-switch.d.ts
CHANGED
|
@@ -2,15 +2,10 @@
|
|
|
2
2
|
* build gets (build-review.ts runs it): tables, and the read-only views every
|
|
3
3
|
* surface shows. No schema version bump. Kept free of heavy imports so the
|
|
4
4
|
* status readers can use it without an import cycle. */
|
|
5
|
+
import type { BuildFinding } from "./contracts/review-findings.js";
|
|
5
6
|
import type { Store } from "./store.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export type BuildFinding = {
|
|
9
|
-
severity: FindingSeverity;
|
|
10
|
-
file: string;
|
|
11
|
-
line: number;
|
|
12
|
-
scenario: string;
|
|
13
|
-
};
|
|
7
|
+
/** One automatic-review finding: how bad, where, and how it fails (src/contracts/review-findings.ts). */
|
|
8
|
+
export type { BuildFinding, FindingSeverity } from "./contracts/review-findings.js";
|
|
14
9
|
export declare const REVIEW_SCHEMA = "\n-- A project's explicit review switch. No row: on while the project is under a hands-off mode, off otherwise.\nCREATE TABLE IF NOT EXISTS review_switch (\n repo TEXT PRIMARY KEY,\n enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),\n changed_by TEXT NOT NULL,\n changed_at TEXT NOT NULL\n);\n-- One row per reviewed build run: pending until its one reviewer run settles it.\nCREATE TABLE IF NOT EXISTS build_review (\n run INTEGER PRIMARY KEY,\n task_id TEXT NOT NULL,\n repo TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('pending', 'reviewed', 'not-reviewed')),\n request INTEGER,\n reviewer_run INTEGER,\n findings_json TEXT,\n reason TEXT,\n sent_back_as TEXT,\n queued_at TEXT NOT NULL,\n finished_at TEXT\n);\nCREATE INDEX IF NOT EXISTS build_review_pending ON build_review (repo) WHERE state = 'pending';\nCREATE INDEX IF NOT EXISTS build_review_sent_back ON build_review (sent_back_as) WHERE sent_back_as IS NOT NULL;\n";
|
|
15
10
|
export type ReviewSwitch = {
|
|
16
11
|
on: boolean;
|
package/dist/reviewer.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* automatic build review's findings (build-review.ts runs it). This module
|
|
3
3
|
* cannot launch providers, create runs or retry work. */
|
|
4
4
|
import type { CriterionJudgementWord } from "./proof.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type BuildFinding } from "./contracts/review-findings.js";
|
|
6
6
|
/** The files the pass writes INTO the scratch directory for the agent —
|
|
7
7
|
* the patch always; the rubric, re-serialized proof, check log, and
|
|
8
8
|
* screenshots only when this run actually has one (v40) — a grandfathered
|
|
@@ -97,18 +97,14 @@ export declare function parseReview(raw: string, patchPaths: ReadonlySet<string>
|
|
|
97
97
|
ok: false;
|
|
98
98
|
problems: ReviewProblem[];
|
|
99
99
|
};
|
|
100
|
-
export type { BuildFinding, FindingSeverity } from "./review-
|
|
101
|
-
export declare const FINDING_LIMITS: {
|
|
102
|
-
readonly findings: 40;
|
|
103
|
-
readonly file: 300;
|
|
104
|
-
readonly scenario: 500;
|
|
105
|
-
};
|
|
100
|
+
export type { BuildFinding, FindingSeverity } from "./contracts/review-findings.js";
|
|
106
101
|
/**
|
|
107
|
-
* The automatic build review's reply,
|
|
108
|
-
* `{"version":1,"findings":[{"severity","file","line","scenario"}]}`. Any
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* to one line so it can travel in a
|
|
102
|
+
* The automatic build review's reply, read through its contract (src/contracts/review-findings.ts), with all findings
|
|
103
|
+
* checked together: `{"version":1,"findings":[{"severity","file","line","scenario"}]}`. Any invalid
|
|
104
|
+
* finding refuses the whole reply — a review that did not answer what it was asked is "not reviewed", never a partial
|
|
105
|
+
* pass — and the refusal names each path (`findings[0].line: at least 1`). The reply shares Claude's review channel
|
|
106
|
+
* (provider.ts), so only its version and findings are read, as always. Text is kept to one line so it can travel in a
|
|
107
|
+
* revision note and a terminal; its limits are checked after whitespace collapses, as before.
|
|
112
108
|
*/
|
|
113
109
|
export declare function parseBuildFindings(raw: string): {
|
|
114
110
|
ok: true;
|
package/dist/reviewer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { citesSuppliedProvenance } from "./review-context.js";
|
|
2
2
|
import { normalizeStructuredJson, REVIEW_OUTPUT_LIMITS } from "./structured-output.js";
|
|
3
|
+
import { readBuildFindings } from "./contracts/review-findings.js";
|
|
3
4
|
/** The files the pass writes INTO the scratch directory for the agent —
|
|
4
5
|
* the patch always; the rubric, re-serialized proof, check log, and
|
|
5
6
|
* screenshots only when this run actually has one (v40) — a grandfathered
|
|
@@ -200,13 +201,13 @@ export function parseReview(raw, patchPaths, approvedCriteriaIds = new Set(), pr
|
|
|
200
201
|
return { ok: false, problems };
|
|
201
202
|
return { ok: true, comments, criteria, ...(payload["learning"] === undefined ? {} : { learning: payload["learning"] }), ...(payload["learningAssessment"] === undefined ? {} : { learningAssessment: payload["learningAssessment"] }) };
|
|
202
203
|
}
|
|
203
|
-
export const FINDING_LIMITS = { findings: 40, file: REVIEW_OUTPUT_LIMITS.path, scenario: REVIEW_OUTPUT_LIMITS.note };
|
|
204
204
|
/**
|
|
205
|
-
* The automatic build review's reply,
|
|
206
|
-
* `{"version":1,"findings":[{"severity","file","line","scenario"}]}`. Any
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* to one line so it can travel in a
|
|
205
|
+
* The automatic build review's reply, read through its contract (src/contracts/review-findings.ts), with all findings
|
|
206
|
+
* checked together: `{"version":1,"findings":[{"severity","file","line","scenario"}]}`. Any invalid
|
|
207
|
+
* finding refuses the whole reply — a review that did not answer what it was asked is "not reviewed", never a partial
|
|
208
|
+
* pass — and the refusal names each path (`findings[0].line: at least 1`). The reply shares Claude's review channel
|
|
209
|
+
* (provider.ts), so only its version and findings are read, as always. Text is kept to one line so it can travel in a
|
|
210
|
+
* revision note and a terminal; its limits are checked after whitespace collapses, as before.
|
|
210
211
|
*/
|
|
211
212
|
export function parseBuildFindings(raw) {
|
|
212
213
|
let parsed;
|
|
@@ -218,33 +219,8 @@ export function parseBuildFindings(raw) {
|
|
|
218
219
|
}
|
|
219
220
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
220
221
|
return { ok: false, problem: "the reply is not an object" };
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
return { ok: false, problem:
|
|
224
|
-
|
|
225
|
-
if (!Array.isArray(list))
|
|
226
|
-
return { ok: false, problem: "findings must be an array" };
|
|
227
|
-
if (list.length > FINDING_LIMITS.findings)
|
|
228
|
-
return { ok: false, problem: `at most ${FINDING_LIMITS.findings} findings` };
|
|
229
|
-
const findings = [];
|
|
230
|
-
const oneLine = (text) => text.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]+/g, " ").replace(/\s+/g, " ").trim();
|
|
231
|
-
for (const [index, one] of list.entries()) {
|
|
232
|
-
if (one === null || typeof one !== "object" || Array.isArray(one))
|
|
233
|
-
return { ok: false, problem: `finding ${index}: not an object` };
|
|
234
|
-
const finding = one;
|
|
235
|
-
const severity = finding["severity"];
|
|
236
|
-
if (severity !== "HIGH" && severity !== "MEDIUM" && severity !== "LOW")
|
|
237
|
-
return { ok: false, problem: `finding ${index}: severity must be HIGH, MEDIUM or LOW` };
|
|
238
|
-
const file = typeof finding["file"] === "string" ? oneLine(finding["file"]) : "";
|
|
239
|
-
if (file.length === 0 || file.length > FINDING_LIMITS.file)
|
|
240
|
-
return { ok: false, problem: `finding ${index}: file must be a path of 1..${FINDING_LIMITS.file} characters` };
|
|
241
|
-
const line = finding["line"];
|
|
242
|
-
if (typeof line !== "number" || !Number.isInteger(line) || line < 1)
|
|
243
|
-
return { ok: false, problem: `finding ${index}: line must be a positive whole number` };
|
|
244
|
-
const scenario = typeof finding["scenario"] === "string" ? oneLine(finding["scenario"]) : "";
|
|
245
|
-
if (scenario.length === 0 || scenario.length > FINDING_LIMITS.scenario)
|
|
246
|
-
return { ok: false, problem: `finding ${index}: scenario must be one sentence of 1..${FINDING_LIMITS.scenario} characters` };
|
|
247
|
-
findings.push({ severity, file, line, scenario });
|
|
248
|
-
}
|
|
249
|
-
return { ok: true, findings };
|
|
222
|
+
const read = readBuildFindings(parsed);
|
|
223
|
+
if (!read.ok)
|
|
224
|
+
return { ok: false, problem: read.issues.map(issue => issue.line).join("; ") };
|
|
225
|
+
return { ok: true, findings: read.value.findings };
|
|
250
226
|
}
|
package/dist/scope.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
export { ACCEPTANCE_LIMITS, EVIDENCE_KINDS, type EvidenceKind } from "./contracts/acceptance-terms.js";
|
|
1
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { type EvidenceKind } from "./contracts/acceptance-terms.js";
|
|
4
|
+
import { type AcceptanceCriterion, type AcceptanceProblem, type ScopeTerms } from "./contracts/scope.js";
|
|
2
5
|
import type { Store, Mutation } from "./store.js";
|
|
3
6
|
import type { QualityMode } from "./quality.js";
|
|
4
7
|
import type { AuthMode } from "./keys.js";
|
|
@@ -176,40 +179,14 @@ export declare function exactKeys(value: object, required: readonly string[], op
|
|
|
176
179
|
/** Strict re-hydration of a stored snapshot — every field type-proved;
|
|
177
180
|
* anything unexpected is null, never a guess. */
|
|
178
181
|
export declare function profileFromJson(json: string | null): ExecutionProfile | null;
|
|
179
|
-
export type
|
|
180
|
-
export declare const EVIDENCE_KINDS: readonly EvidenceKind[];
|
|
181
|
-
export type AcceptanceCriterion = {
|
|
182
|
-
id: string;
|
|
183
|
-
/** The signed outcome statement. Restating it differently in a proof is
|
|
184
|
-
* refuted, not short — the same severity as any other altered term. */
|
|
185
|
-
statement: string;
|
|
186
|
-
/** Advisory guidance for HOW to satisfy the statement. Never enters the
|
|
187
|
-
* digest and is never a term the approval binds — an operator can leave
|
|
188
|
-
* it, change it, or ignore it without touching what was signed. */
|
|
189
|
-
how: string | null;
|
|
190
|
-
/** SIGNED: the evidence kinds a proof must reference, by exact id, to
|
|
191
|
-
* answer this criterion. Never empty. */
|
|
192
|
-
evidence: EvidenceKind[];
|
|
193
|
-
};
|
|
194
|
-
export declare const ACCEPTANCE_LIMITS: {
|
|
195
|
-
readonly criteria: 12;
|
|
196
|
-
readonly id: 40;
|
|
197
|
-
readonly statement: 1000;
|
|
198
|
-
readonly how: 500;
|
|
199
|
-
readonly evidenceKinds: number;
|
|
200
|
-
};
|
|
201
|
-
export type AcceptanceProblem = {
|
|
202
|
-
reason: string;
|
|
203
|
-
message: string;
|
|
204
|
-
};
|
|
182
|
+
export type { AcceptanceCriterion, AcceptanceProblem };
|
|
205
183
|
/**
|
|
206
184
|
* Parse a rubric from already-JSON-parsed input (the planner's handoff, a
|
|
207
185
|
* console form, a routine or template definition): fail closed, every
|
|
208
|
-
* problem reported at once,
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* road says no), never for this parser.
|
|
186
|
+
* problem reported at once, each naming its path — the acceptance contract
|
|
187
|
+
* (contracts/scope.ts) reads it. An absent or empty `value` parses to `[]`
|
|
188
|
+
* with no problems: whether that is ALLOWED is a question for the caller
|
|
189
|
+
* (`propose` says yes; every authoring road says no), never for this parser.
|
|
213
190
|
*/
|
|
214
191
|
export declare function parseAcceptanceCriteria(value: unknown): {
|
|
215
192
|
criteria: AcceptanceCriterion[];
|
|
@@ -257,19 +234,20 @@ export declare function canonicalAcceptance(criteria: readonly AcceptanceCriteri
|
|
|
257
234
|
statement: string;
|
|
258
235
|
evidence: EvidenceKind[];
|
|
259
236
|
}[];
|
|
260
|
-
|
|
237
|
+
/**
|
|
238
|
+
* A scope's terms, from the scope contract (contracts/scope.ts): what
|
|
239
|
+
* success looks like in the operator's words (`goal`), what the task is
|
|
240
|
+
* explicitly not allowed to turn into (`outOfScope`), the paths it expects
|
|
241
|
+
* to touch (advisory, and worth stating), and the signed acceptance rubric
|
|
242
|
+
* — `[]` on every scope proposed before v39 and on any legacy road that
|
|
243
|
+
* still calls `propose` directly: a scope-producing ROAD enforces
|
|
244
|
+
* non-emptiness, never this type, never `propose`, never the digest.
|
|
245
|
+
*/
|
|
246
|
+
export type SavedScopeTerms = {
|
|
247
|
+
[K in keyof ScopeTerms]-?: Exclude<ScopeTerms[K], undefined>;
|
|
248
|
+
};
|
|
249
|
+
export type Scope = SavedScopeTerms & {
|
|
261
250
|
taskId: string;
|
|
262
|
-
/** What success looks like, in the operator's words. */
|
|
263
|
-
goal: string;
|
|
264
|
-
/** What this task is explicitly not allowed to turn into. */
|
|
265
|
-
outOfScope: string | null;
|
|
266
|
-
/** Paths the work is expected to touch. Advisory, and worth stating. */
|
|
267
|
-
touches: string[];
|
|
268
|
-
/** v39: the signed acceptance rubric. `[]` on every scope proposed before
|
|
269
|
-
* this migration and on any legacy road that still calls `propose`
|
|
270
|
-
* directly — a scope-producing ROAD enforces non-emptiness, never this
|
|
271
|
-
* type, never `propose`, never the digest. */
|
|
272
|
-
acceptance: AcceptanceCriterion[];
|
|
273
251
|
/** v41: evidence policy, signed with the scope. Default is omitted from
|
|
274
252
|
* the digest so every pre-v41 approval remains byte-for-byte valid. */
|
|
275
253
|
qualityMode?: QualityMode;
|
package/dist/scope.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// The rubric's vocabulary loads before anything that reaches the plan contract (see contracts/acceptance-terms.ts).
|
|
2
|
+
export { ACCEPTANCE_LIMITS, EVIDENCE_KINDS } from "./contracts/acceptance-terms.js";
|
|
1
3
|
import { passwordGuardOf } from "./sign-in-guard.js";
|
|
2
4
|
import { validateScopeText } from "./task-text.js";
|
|
3
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -29,9 +31,11 @@ import { claimActor, currentActor, parseLeadToken } from "./actor.js";
|
|
|
29
31
|
* glance is the whole safety property.
|
|
30
32
|
*/
|
|
31
33
|
import { createHash, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
34
|
+
import { z } from "zod";
|
|
35
|
+
import {} from "./contracts/acceptance-terms.js";
|
|
36
|
+
import { parseContract } from "./contracts/contract.js";
|
|
37
|
+
import { readAcceptance, readScopeTerms, storedRubricSchema } from "./contracts/scope.js";
|
|
38
|
+
import { NO_READINESS, exactModelId, isRiskLevel, legOf, projectRoute, routeDigestOf, routeFromJson, routeJsonProblem, routeWords, } from "./phase-routing.js";
|
|
35
39
|
/** Same shape as a runner's credential, for the same reasons. */
|
|
36
40
|
function mintToken() {
|
|
37
41
|
return randomBytes(32).toString("base64url");
|
|
@@ -248,13 +252,18 @@ export function exactAcceptance(value) {
|
|
|
248
252
|
}
|
|
249
253
|
if (!Array.isArray(parsed))
|
|
250
254
|
return { ok: false, problem: "the rubric is not a JSON list" };
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
// Stored, a criterion carries exactly the keys this code writes: the saved rubric schema is strict about them.
|
|
256
|
+
const strict = parseContract(z.object({ acceptance: storedRubricSchema }), { acceptance: parsed });
|
|
257
|
+
for (const issue of strict.ok ? [] : strict.issues) {
|
|
258
|
+
const entry = /^acceptance\[(\d+)\]$/.exec(issue.path);
|
|
259
|
+
if (entry === null)
|
|
260
|
+
continue;
|
|
261
|
+
if (issue.kind === "wrong-type" || issue.kind === "required")
|
|
262
|
+
return { ok: false, problem: `rubric entry ${Number(entry[1]) + 1} is not an object` };
|
|
263
|
+
if (issue.kind === "unknown-key")
|
|
264
|
+
return { ok: false, problem: `rubric entry ${Number(entry[1]) + 1} carries a key this code never writes` };
|
|
256
265
|
}
|
|
257
|
-
const read =
|
|
266
|
+
const read = readAcceptance(parsed);
|
|
258
267
|
if (read.problems.length > 0)
|
|
259
268
|
return { ok: false, problem: `the rubric does not parse: ${read.problems.map(one => one.message).join("; ")}` };
|
|
260
269
|
return { ok: true, criteria: read.criteria };
|
|
@@ -397,126 +406,28 @@ export function profileFromJson(json) {
|
|
|
397
406
|
}
|
|
398
407
|
return null;
|
|
399
408
|
}
|
|
400
|
-
export const EVIDENCE_KINDS = ["check", "screenshot", "changed-path", "manual-review"];
|
|
401
|
-
export const ACCEPTANCE_LIMITS = {
|
|
402
|
-
criteria: 12,
|
|
403
|
-
id: TEXT_LIMITS.acceptanceIdBytes,
|
|
404
|
-
// A statement is one testable outcome; three hundred bytes forced people to
|
|
405
|
-
// drop the qualifying clause that made it testable.
|
|
406
|
-
statement: TEXT_LIMITS.acceptanceStatementBytes,
|
|
407
|
-
how: TEXT_LIMITS.acceptanceHowBytes,
|
|
408
|
-
evidenceKinds: EVIDENCE_KINDS.length,
|
|
409
|
-
};
|
|
410
|
-
function acceptanceDescribe(value) {
|
|
411
|
-
if (value === undefined)
|
|
412
|
-
return "nothing";
|
|
413
|
-
if (value === null)
|
|
414
|
-
return "null";
|
|
415
|
-
if (typeof value === "string")
|
|
416
|
-
return `a ${value.length}-char string`;
|
|
417
|
-
return `a ${Array.isArray(value) ? "array" : typeof value}`;
|
|
418
|
-
}
|
|
419
|
-
function acceptanceProse(value, field, cap, required, problems) {
|
|
420
|
-
if (value === undefined || value === null || value === "") {
|
|
421
|
-
if (required)
|
|
422
|
-
problems.push({ reason: `missing-${field}`, message: `${field} is required` });
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
if (typeof value !== "string") {
|
|
426
|
-
problems.push({ reason: `bad-${field}`, message: `${field} must be a string (got ${acceptanceDescribe(value)})` });
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
if (Buffer.byteLength(value, "utf8") > cap) {
|
|
430
|
-
problems.push({ reason: `${field}-too-long`, message: `${field} is over ${cap} bytes` });
|
|
431
|
-
return null;
|
|
432
|
-
}
|
|
433
|
-
if (hasForbiddenControls(value)) {
|
|
434
|
-
problems.push({ reason: `${field}-controls`, message: `${field} carries control characters that could become terminal escapes` });
|
|
435
|
-
return null;
|
|
436
|
-
}
|
|
437
|
-
return value;
|
|
438
|
-
}
|
|
439
409
|
/**
|
|
440
410
|
* Parse a rubric from already-JSON-parsed input (the planner's handoff, a
|
|
441
411
|
* console form, a routine or template definition): fail closed, every
|
|
442
|
-
* problem reported at once,
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
* road says no), never for this parser.
|
|
412
|
+
* problem reported at once, each naming its path — the acceptance contract
|
|
413
|
+
* (contracts/scope.ts) reads it. An absent or empty `value` parses to `[]`
|
|
414
|
+
* with no problems: whether that is ALLOWED is a question for the caller
|
|
415
|
+
* (`propose` says yes; every authoring road says no), never for this parser.
|
|
447
416
|
*/
|
|
448
417
|
export function parseAcceptanceCriteria(value) {
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
for (const [index, entry] of value.entries()) {
|
|
463
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
464
|
-
problems.push({ reason: `acceptance[${index}]-shape`, message: `acceptance[${index}] must be an object` });
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
const one = entry;
|
|
468
|
-
const id = acceptanceProse(one["id"], `acceptance[${index}].id`, ACCEPTANCE_LIMITS.id, true, problems);
|
|
469
|
-
if (id !== null && seen.has(id)) {
|
|
470
|
-
problems.push({ reason: `acceptance[${index}]-duplicate-id`, message: `criterion id "${id}" appears twice` });
|
|
471
|
-
}
|
|
472
|
-
else if (id !== null) {
|
|
473
|
-
seen.add(id);
|
|
474
|
-
}
|
|
475
|
-
const statement = acceptanceProse(one["statement"], `acceptance[${index}].statement`, ACCEPTANCE_LIMITS.statement, true, problems);
|
|
476
|
-
const how = acceptanceProse(one["how"], `acceptance[${index}].how`, ACCEPTANCE_LIMITS.how, false, problems);
|
|
477
|
-
const rawEvidence = one["evidence"];
|
|
478
|
-
let evidence = null;
|
|
479
|
-
if (!Array.isArray(rawEvidence)) {
|
|
480
|
-
problems.push({
|
|
481
|
-
reason: `acceptance[${index}]-bad-evidence`,
|
|
482
|
-
message: `acceptance[${index}].evidence must be a non-empty array of evidence kinds (got ${acceptanceDescribe(rawEvidence)})`,
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
else if (rawEvidence.length === 0 || rawEvidence.length > ACCEPTANCE_LIMITS.evidenceKinds) {
|
|
486
|
-
problems.push({
|
|
487
|
-
reason: `acceptance[${index}]-evidence-count`,
|
|
488
|
-
message: `acceptance[${index}].evidence must name 1-${ACCEPTANCE_LIMITS.evidenceKinds} evidence kinds`,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
else {
|
|
492
|
-
const kinds = [];
|
|
493
|
-
const seenKinds = new Set();
|
|
494
|
-
let bad = false;
|
|
495
|
-
for (const k of rawEvidence) {
|
|
496
|
-
if (typeof k !== "string" || !EVIDENCE_KINDS.includes(k)) {
|
|
497
|
-
problems.push({
|
|
498
|
-
reason: `acceptance[${index}]-bad-evidence-kind`,
|
|
499
|
-
message: `acceptance[${index}].evidence must draw from ${EVIDENCE_KINDS.join(", ")} (got ${acceptanceDescribe(k)})`,
|
|
500
|
-
});
|
|
501
|
-
bad = true;
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
if (seenKinds.has(k)) {
|
|
505
|
-
problems.push({ reason: `acceptance[${index}]-duplicate-evidence-kind`, message: `evidence kind "${k}" appears twice` });
|
|
506
|
-
bad = true;
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
seenKinds.add(k);
|
|
510
|
-
kinds.push(k);
|
|
511
|
-
}
|
|
512
|
-
if (!bad)
|
|
513
|
-
evidence = kinds;
|
|
514
|
-
}
|
|
515
|
-
if (id === null || statement === null || evidence === null)
|
|
516
|
-
continue;
|
|
517
|
-
criteria.push({ id, statement, how, evidence });
|
|
518
|
-
}
|
|
519
|
-
return { criteria, problems };
|
|
418
|
+
const read = readAcceptance(value);
|
|
419
|
+
// Keep the prose shape plan.ts's existing acceptanceLine consumes. Validation stays in the contract; only this
|
|
420
|
+
// compatibility boundary formats messages. That mapping takes a field followed by a space, not a colon, and
|
|
421
|
+
// evidence-array indices belong in the detail because it recognizes only the criterion's index.
|
|
422
|
+
return {
|
|
423
|
+
...read,
|
|
424
|
+
problems: read.problems.map(problem => ({
|
|
425
|
+
...problem,
|
|
426
|
+
message: problem.message
|
|
427
|
+
.replace(/^(acceptance\[\d+\]\.evidence)\[(\d+)\]: /, (_, path, index) => `${path} entry ${Number(index) + 1}: `)
|
|
428
|
+
.replace(/^(acceptance(?:\[\d+\])?(?:\.[A-Za-z]+)?): /, "$1 "),
|
|
429
|
+
})),
|
|
430
|
+
};
|
|
520
431
|
}
|
|
521
432
|
/** The exact bytes a rubric's SIGNED terms reduce to for the digest: sorted
|
|
522
433
|
* by id, `how` dropped (advisory, never signed), evidence kinds sorted so
|
|
@@ -710,15 +621,18 @@ export function propose(store, input) {
|
|
|
710
621
|
*/
|
|
711
622
|
export function proposeGuarded(store, input) {
|
|
712
623
|
const touches = [...(input.touches ?? [])].map(one => one.trim()).filter(one => one !== "");
|
|
624
|
+
// Plain words first for what a person typed (lengths, bytes, hidden text), then the scope contract.
|
|
713
625
|
const badText = validateScopeText({ goal: input.goal, outOfScope: input.outOfScope ?? null, touches });
|
|
714
626
|
if (badText !== null)
|
|
715
627
|
return badText;
|
|
716
628
|
const goal = input.goal.trim();
|
|
717
629
|
const outOfScope = input.outOfScope?.trim() || null;
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
720
|
-
|
|
630
|
+
const terms = readScopeTerms({ goal, outOfScope, touches, acceptance: input.acceptance });
|
|
631
|
+
if (!terms.ok) {
|
|
632
|
+
const reason = terms.field === "acceptance" ? "bad-acceptance" : terms.field === "touches" ? "bad-touches" : terms.field === "outOfScope" ? "bad-out-of-scope" : "bad-goal";
|
|
633
|
+
return { ok: false, reason, message: terms.problems.map(one => one.message).join("; ") };
|
|
721
634
|
}
|
|
635
|
+
const acceptanceParse = { criteria: terms.terms.acceptance };
|
|
722
636
|
if (acceptanceParse.criteria.length === 0) {
|
|
723
637
|
return { ok: false, reason: "acceptance-required" };
|
|
724
638
|
}
|
|
@@ -1070,7 +984,7 @@ export function scopeAuthorityOf(scope, env = {}) {
|
|
|
1070
984
|
const routeJson = scope.proposedRouteJson ?? null;
|
|
1071
985
|
const route = routeFromJson(routeJson);
|
|
1072
986
|
if (route === null) {
|
|
1073
|
-
return { ok: false, reason: "route", problem: routeJson === null ? "the scope was filed under agent routing but carries no route" :
|
|
987
|
+
return { ok: false, reason: "route", problem: routeJson === null ? "the scope was filed under agent routing but carries no route" : `the scope's agent route cannot be read exactly (${routeJsonProblem(routeJson)})` };
|
|
1074
988
|
}
|
|
1075
989
|
const parity = routeParityProblem(route, profile, { riskLevel: scope.riskLevel ?? "routine", qualityMode: scope.qualityMode ?? "default" });
|
|
1076
990
|
if (parity !== null)
|