toolroll 0.9.34 → 0.9.37
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 +102 -0
- package/README.md +7 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- package/dist/assignment.d.ts +1 -0
- package/dist/assignment.js +2 -2
- package/dist/browser-workspace.d.ts +3 -3
- package/dist/build-review.d.ts +1 -1
- package/dist/build-review.js +2 -1
- package/dist/builder.js +4 -7
- package/dist/chat-acceptance.d.ts +2 -2
- 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-decide.d.ts +14 -1
- package/dist/chat-decide.js +12 -6
- 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 +40 -7
- package/dist/contracts/contract.js +155 -19
- 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-propose.d.ts +325 -0
- package/dist/contracts/flow-propose.js +41 -0
- package/dist/contracts/flow-send.d.ts +119 -0
- package/dist/contracts/flow-send.js +112 -0
- package/dist/contracts/flow.d.ts +19405 -0
- package/dist/contracts/flow.js +364 -0
- package/dist/contracts/gateway-tools.d.ts +374 -0
- package/dist/contracts/gateway-tools.js +94 -0
- package/dist/contracts/handoff.d.ts +91 -0
- package/dist/contracts/handoff.js +82 -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/proof.d.ts +136 -0
- package/dist/contracts/proof.js +110 -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/contracts/verification-receipt.d.ts +148 -0
- package/dist/contracts/verification-receipt.js +87 -0
- package/dist/decision.d.ts +11 -20
- package/dist/decision.js +86 -167
- package/dist/demo.js +28 -11
- package/dist/discord-chat.js +31 -20
- package/dist/evidence.d.ts +3 -40
- package/dist/evidence.js +5 -1
- package/dist/flow-engine.js +14 -17
- package/dist/flow-gallery.d.ts +3 -2
- 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.d.ts +10 -32
- package/dist/flow-share.js +72 -84
- package/dist/flow-sort.d.ts +7 -26
- package/dist/flow-sort.js +25 -20
- package/dist/flow-starters.d.ts +2 -1
- package/dist/flow-steps.js +3 -2
- package/dist/flow-triggers.d.ts +15 -72
- package/dist/flow-triggers.js +94 -83
- package/dist/flows.d.ts +37 -220
- package/dist/flows.js +356 -387
- package/dist/invoke.js +4 -4
- package/dist/lead-context.d.ts +4 -2
- package/dist/lead-context.js +24 -17
- package/dist/mate-tools.d.ts +4 -0
- package/dist/mate-tools.js +111 -308
- package/dist/mcp-connect.d.ts +27 -4
- package/dist/mcp-connect.js +92 -17
- package/dist/mcp.js +84 -216
- package/dist/memory-pass.d.ts +17 -42
- package/dist/memory-pass.js +54 -26
- package/dist/operate.js +31 -2
- package/dist/phase-routing.d.ts +19 -72
- package/dist/phase-routing.js +34 -164
- package/dist/plan.d.ts +5 -5
- package/dist/plan.js +5 -25
- package/dist/process-recovery-eligibility.d.ts +2 -2
- package/dist/process-recovery-eligibility.js +13 -8
- 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/proof.d.ts +15 -91
- package/dist/proof.js +90 -249
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +4 -16
- package/dist/result-evidence-readers.d.ts +42 -0
- package/dist/result-evidence-readers.js +91 -0
- 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 +17 -101
- 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/surface.js +8 -1
- package/dist/task-review-brief.d.ts +69 -0
- package/dist/task-review-brief.js +179 -0
- 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.js +4 -3
- package/dist/telegram-mate.d.ts +4 -14
- package/dist/telegram-mate.js +8 -7
- package/dist/telegram-team.d.ts +2 -19
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +52 -42
- package/dist/text-limits.d.ts +212 -1
- package/dist/text-limits.js +214 -1
- package/dist/tools-ui.d.ts +2 -1
- package/dist/tools-ui.js +7 -4
- package/dist/verification-evidence.js +24 -12
- package/package.json +1 -1
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)
|
package/dist/scout-report.d.ts
CHANGED
|
@@ -1,44 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The scout's terminal handoff (mate arc §10),
|
|
3
|
-
* fail closed, every problem reported at once,
|
|
4
|
-
* control-character rejection on every string. A report reaches the task
|
|
5
|
-
* page, the ledger, the terminal, and — through mateView — the mate; and
|
|
6
|
-
*
|
|
7
|
-
* the park discipline exactly as the plan does.
|
|
2
|
+
* The scout's terminal handoff (mate arc §10), read against the report contract (src/contracts/scout-report.ts) with
|
|
3
|
+
* the 422 rule: fail closed, every problem reported at once, each naming its path (`items[0].url: must be an http or
|
|
4
|
+
* https address`), stable reasons, caps and control-character rejection on every string. A report reaches the task
|
|
5
|
+
* page, the ledger, the terminal, and — through mateView — the mate; and each follow-up becomes a filing's title and
|
|
6
|
+
* goal at one tap, so it gets the park discipline exactly as the plan does.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export type ParsedReport = {
|
|
14
|
-
title: string;
|
|
15
|
-
summary: string;
|
|
16
|
-
/** The report document, markdown, rendered fenced-inert everywhere. */
|
|
17
|
-
report: string;
|
|
18
|
-
/** Proposed follow-ups: each files as a task in the same repository. */
|
|
19
|
-
followUps: {
|
|
20
|
-
title: string;
|
|
21
|
-
goal: string;
|
|
22
|
-
}[];
|
|
23
|
-
/** What the scout found, as a short list later steps can read: each cites its URL, and may show one of `images`. */
|
|
24
|
-
items: ReportItem[];
|
|
25
|
-
/** Screenshots the scout saved during the run. From the scout, `file` names a file in its output folder; once
|
|
26
|
-
* stored, the runner adds the bytes' sha256 and the evidence row that holds them. */
|
|
27
|
-
images: ReportImage[];
|
|
28
|
-
};
|
|
29
|
-
export type ReportItem = {
|
|
30
|
-
title: string;
|
|
31
|
-
why: string;
|
|
32
|
-
url: string;
|
|
33
|
-
image: string | null;
|
|
34
|
-
};
|
|
35
|
-
export type ReportImage = {
|
|
36
|
-
file: string;
|
|
37
|
-
caption: string;
|
|
38
|
-
url: string;
|
|
39
|
-
sha256?: string;
|
|
40
|
-
artifact?: number;
|
|
41
|
-
};
|
|
8
|
+
import { type ContractProblem } from "./contracts/contract.js";
|
|
9
|
+
import { type ParsedReport } from "./contracts/scout-report.js";
|
|
10
|
+
export { REPORT_IMAGE_FILE, REPORT_LIMITS, REPORT_VERSION, SCOUT_OUTPUT_JSON_SCHEMA, type ParsedReport, type ReportImage, type ReportItem, } from "./contracts/scout-report.js";
|
|
11
|
+
export type ReportProblem = ContractProblem;
|
|
42
12
|
export type ReportParseResult = {
|
|
43
13
|
ok: true;
|
|
44
14
|
report: ParsedReport;
|
|
@@ -46,161 +16,6 @@ export type ReportParseResult = {
|
|
|
46
16
|
ok: false;
|
|
47
17
|
problems: ReportProblem[];
|
|
48
18
|
};
|
|
49
|
-
/** Caps are BYTES of UTF-8 (v4 review, finding 11): a 64 KiB report is
|
|
50
|
-
* 64 KiB whatever script it is written in. They hold what a scout writes;
|
|
51
|
-
* a stored report is read without them (parseReport's `stored`). */
|
|
52
|
-
export declare const REPORT_LIMITS: {
|
|
53
|
-
readonly payload: number;
|
|
54
|
-
readonly title: 200;
|
|
55
|
-
readonly summary: 2500;
|
|
56
|
-
readonly document: number;
|
|
57
|
-
readonly followUps: 5;
|
|
58
|
-
readonly followUpTitle: 200;
|
|
59
|
-
/** A follow-up files as a task: its goal is held to the task goal limit, in bytes of the same count. */
|
|
60
|
-
readonly followUpGoal: 8000;
|
|
61
|
-
readonly items: 6;
|
|
62
|
-
readonly itemTitle: 200;
|
|
63
|
-
readonly itemWhy: 1000;
|
|
64
|
-
readonly url: 2000;
|
|
65
|
-
readonly images: 8;
|
|
66
|
-
readonly caption: 300;
|
|
67
|
-
};
|
|
68
|
-
/** A screenshot's name in the scout's output folder: one plain file name, PNG or JPEG, never a path. */
|
|
69
|
-
export declare const REPORT_IMAGE_FILE: RegExp;
|
|
70
|
-
/**
|
|
71
|
-
* The scout's handback for Claude's `--json-schema` (run 2334's fix): plan
|
|
72
|
-
* mode only lets a session write its own plan file, so a Claude scout
|
|
73
|
-
* returns a report — or a question for the operator — as structured output
|
|
74
|
-
* on the terminal result event. Shape only, never the validator: byte caps,
|
|
75
|
-
* one-line titles and control characters stay `parseReport`'s and
|
|
76
|
-
* `parseDecision`'s, applied to the re-serialized body.
|
|
77
|
-
*/
|
|
78
|
-
export declare const SCOUT_OUTPUT_JSON_SCHEMA: {
|
|
79
|
-
readonly type: "object";
|
|
80
|
-
readonly properties: {
|
|
81
|
-
readonly kind: {
|
|
82
|
-
readonly type: "string";
|
|
83
|
-
readonly enum: readonly ["report", "question"];
|
|
84
|
-
};
|
|
85
|
-
readonly report: {
|
|
86
|
-
readonly type: "object";
|
|
87
|
-
readonly properties: {
|
|
88
|
-
readonly title: {
|
|
89
|
-
readonly type: "string";
|
|
90
|
-
};
|
|
91
|
-
readonly summary: {
|
|
92
|
-
readonly type: "string";
|
|
93
|
-
};
|
|
94
|
-
readonly report: {
|
|
95
|
-
readonly type: "string";
|
|
96
|
-
};
|
|
97
|
-
readonly followUps: {
|
|
98
|
-
readonly type: "array";
|
|
99
|
-
readonly items: {
|
|
100
|
-
readonly type: "object";
|
|
101
|
-
readonly properties: {
|
|
102
|
-
readonly title: {
|
|
103
|
-
readonly type: "string";
|
|
104
|
-
};
|
|
105
|
-
readonly goal: {
|
|
106
|
-
readonly type: "string";
|
|
107
|
-
};
|
|
108
|
-
};
|
|
109
|
-
readonly required: readonly ["title", "goal"];
|
|
110
|
-
readonly additionalProperties: false;
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
readonly items: {
|
|
114
|
-
readonly type: "array";
|
|
115
|
-
readonly items: {
|
|
116
|
-
readonly type: "object";
|
|
117
|
-
readonly properties: {
|
|
118
|
-
readonly title: {
|
|
119
|
-
readonly type: "string";
|
|
120
|
-
};
|
|
121
|
-
readonly why: {
|
|
122
|
-
readonly type: "string";
|
|
123
|
-
};
|
|
124
|
-
readonly url: {
|
|
125
|
-
readonly type: "string";
|
|
126
|
-
};
|
|
127
|
-
readonly image: {
|
|
128
|
-
readonly type: "string";
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
readonly required: readonly ["title", "why", "url"];
|
|
132
|
-
readonly additionalProperties: false;
|
|
133
|
-
};
|
|
134
|
-
};
|
|
135
|
-
readonly images: {
|
|
136
|
-
readonly type: "array";
|
|
137
|
-
readonly items: {
|
|
138
|
-
readonly type: "object";
|
|
139
|
-
readonly properties: {
|
|
140
|
-
readonly file: {
|
|
141
|
-
readonly type: "string";
|
|
142
|
-
};
|
|
143
|
-
readonly caption: {
|
|
144
|
-
readonly type: "string";
|
|
145
|
-
};
|
|
146
|
-
readonly url: {
|
|
147
|
-
readonly type: "string";
|
|
148
|
-
};
|
|
149
|
-
};
|
|
150
|
-
readonly required: readonly ["file", "caption", "url"];
|
|
151
|
-
readonly additionalProperties: false;
|
|
152
|
-
};
|
|
153
|
-
};
|
|
154
|
-
};
|
|
155
|
-
readonly required: readonly ["title", "summary", "report"];
|
|
156
|
-
readonly additionalProperties: false;
|
|
157
|
-
};
|
|
158
|
-
readonly decision: {
|
|
159
|
-
readonly type: "object";
|
|
160
|
-
readonly properties: {
|
|
161
|
-
readonly urgency: {
|
|
162
|
-
readonly type: "string";
|
|
163
|
-
readonly enum: readonly ["blocking"];
|
|
164
|
-
};
|
|
165
|
-
readonly recap: {
|
|
166
|
-
readonly type: "string";
|
|
167
|
-
};
|
|
168
|
-
readonly question: {
|
|
169
|
-
readonly type: "string";
|
|
170
|
-
};
|
|
171
|
-
readonly options: {
|
|
172
|
-
readonly type: "array";
|
|
173
|
-
readonly items: {
|
|
174
|
-
readonly type: "object";
|
|
175
|
-
readonly properties: {
|
|
176
|
-
readonly id: {
|
|
177
|
-
readonly type: "string";
|
|
178
|
-
};
|
|
179
|
-
readonly label: {
|
|
180
|
-
readonly type: "string";
|
|
181
|
-
};
|
|
182
|
-
readonly consequence: {
|
|
183
|
-
readonly type: "string";
|
|
184
|
-
};
|
|
185
|
-
readonly reversible: {
|
|
186
|
-
readonly type: "boolean";
|
|
187
|
-
};
|
|
188
|
-
};
|
|
189
|
-
readonly required: readonly ["id", "label", "consequence", "reversible"];
|
|
190
|
-
readonly additionalProperties: false;
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
readonly recommendation: {
|
|
194
|
-
readonly type: "string";
|
|
195
|
-
};
|
|
196
|
-
};
|
|
197
|
-
readonly required: readonly ["urgency", "recap", "question", "options", "recommendation"];
|
|
198
|
-
readonly additionalProperties: false;
|
|
199
|
-
};
|
|
200
|
-
};
|
|
201
|
-
readonly required: readonly ["kind"];
|
|
202
|
-
readonly additionalProperties: false;
|
|
203
|
-
};
|
|
204
19
|
/** A report, checked. `stored`: one already kept as evidence, read without the length caps (a report written under
|
|
205
20
|
* higher limits stays readable); every other check still holds. */
|
|
206
21
|
export declare function parseReport(raw: string, options?: {
|