toolroll 0.9.36 → 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 +58 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- 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-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 +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 +2 -2
- 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-tools.d.ts +4 -0
- package/dist/mate-tools.js +108 -275
- 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 +9 -2
- 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.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 +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 +1 -1
package/dist/memory-pass.js
CHANGED
|
@@ -19,10 +19,17 @@ import { changeKnowledge, knowledgeView } from './project-knowledge.js';
|
|
|
19
19
|
import { listDecisions, recordDecision } from './project-memory.js';
|
|
20
20
|
import { composeMateRequest, performMateRequest, isDirectChatProvider, CHAT_KEY_ENV } from './converse.js';
|
|
21
21
|
import { performSubscriptionMateRequest } from './subscription-chat.js';
|
|
22
|
-
|
|
22
|
+
import { TEXT_LIMITS } from './text-limits.js';
|
|
23
|
+
import { contractError, parseContract } from './contracts/contract.js';
|
|
24
|
+
import { MEMORY_VERDICT_MODEL_SCHEMA, MEMORY_VERDICT_VERSION, memoryEvidenceSchema, memoryProposalSchema, memoryVerdictReadSchema, readSavedVerdict, savedVerdictSchema } from './contracts/memory-pass.js';
|
|
25
|
+
/** One session's trace, in characters (TEXT_LIMITS.memoryTrace). */
|
|
26
|
+
export const MEMORY_TRACE_BYTES = TEXT_LIMITS.memoryTrace;
|
|
23
27
|
export const MEMORY_MIN_SESSIONS = 2;
|
|
24
28
|
export const MEMORY_MAX_PROPOSALS = 6;
|
|
25
|
-
|
|
29
|
+
/** The project instructions' budget: the same limit knowledge holds them to. */
|
|
30
|
+
export const INSTRUCTION_BUDGET_BYTES = TEXT_LIMITS.knowledgeInstructionsBytes;
|
|
31
|
+
/** How many claims of each kind one verdict keeps. */
|
|
32
|
+
const VERDICT_ITEMS = 20;
|
|
26
33
|
export const MEMORY_GAP_MAX_AGE_MS = 90 * 24 * 3_600_000;
|
|
27
34
|
const sha = (text) => createHash('sha256').update(text).digest('hex');
|
|
28
35
|
/** The Claude Code project directory for a checkout: the path with every slash turned into a dash. */
|
|
@@ -174,7 +181,7 @@ export function analysisPrompt(surface, openGaps) {
|
|
|
174
181
|
'INSTRUCTIONS (refer to them ONLY by id):', ...surface.instructions.map(i => `[${i.id}] ${i.text}`),
|
|
175
182
|
'DECISIONS on record (refer by id):', ...surface.decisions.map(d => `[${d.id}] ${d.claim}`),
|
|
176
183
|
'GAPS ALREADY ON THE BOOKS (cite the key in matchesGap when the same underlying gap):', ...openGaps.map(g => `[${g.key}] ${g.mistake}`),
|
|
177
|
-
|
|
184
|
+
`Return ONE JSON object and nothing else, matching this JSON Schema: ${JSON.stringify(MEMORY_VERDICT_MODEL_SCHEMA)}`,
|
|
178
185
|
'Rules: every item needs a verbatim quote copied from the trace or it is discarded; negative evidence outranks positive; class harm means following the instruction caused damage, non-compliance means it was ignored; a mistake caused by the harness or task framing is domain orchestration; report nothing rather than something weak; an empty array is a good answer.',
|
|
179
186
|
].join('\n');
|
|
180
187
|
}
|
|
@@ -204,31 +211,43 @@ export function defaultAnalyzer(store, options) {
|
|
|
204
211
|
const answer = await performMateRequest(request, config.provider, AbortSignal.timeout(options.timeoutMs ?? 120_000), options.fetcher);
|
|
205
212
|
return answer.ok ? { ok: true, text: answer.answer.text } : { ok: false, problem: answer.problem };
|
|
206
213
|
}
|
|
207
|
-
|
|
214
|
+
// The verdict's own schema is the harness's structured output, so the answer is the verdict itself.
|
|
215
|
+
const answer = await performSubscriptionMateRequest({ provider: config.provider, model: config.model, system, dataDocument, history, tools: [], outputSchema: MEMORY_VERDICT_MODEL_SCHEMA, timeoutMs: options.timeoutMs ?? 180_000 }, options.runner);
|
|
208
216
|
return answer.ok ? { ok: true, text: answer.answer.text } : { ok: false, problem: answer.problem };
|
|
209
217
|
};
|
|
210
218
|
}
|
|
211
|
-
|
|
219
|
+
const UNREADABLE = 'The analysis was not a readable verdict.';
|
|
220
|
+
/**
|
|
221
|
+
* Read the analyser's answer with the tolerant parse-side schema, then keep only claims whose quote
|
|
222
|
+
* is really in the trace and, for an instruction's effect, whose id is on the surface audited.
|
|
223
|
+
*/
|
|
212
224
|
export function parseVerdict(text, trace, surface) {
|
|
213
225
|
const start = text.indexOf('{'), end = text.lastIndexOf('}');
|
|
214
226
|
if (start < 0 || end <= start)
|
|
215
|
-
return
|
|
227
|
+
return { ok: false, problem: UNREADABLE };
|
|
216
228
|
let raw;
|
|
217
229
|
try {
|
|
218
|
-
raw =
|
|
230
|
+
raw = JSON.parse(text.slice(start, end + 1));
|
|
219
231
|
}
|
|
220
232
|
catch {
|
|
221
|
-
return
|
|
233
|
+
return { ok: false, problem: UNREADABLE };
|
|
222
234
|
}
|
|
235
|
+
const read = parseContract(memoryVerdictReadSchema, raw);
|
|
236
|
+
if (!read.ok)
|
|
237
|
+
return { ok: false, problem: `${UNREADABLE.slice(0, -1)}: ${read.issues.slice(0, 5).map(issue => issue.line).join('; ')}` };
|
|
223
238
|
const ids = new Set(surface.instructions.map(i => i.id));
|
|
224
|
-
const quoted = (item) =>
|
|
225
|
-
const list = (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
239
|
+
const quoted = (item) => item.quote.trim().length >= 12 && trace.includes(item.quote.trim());
|
|
240
|
+
const list = (items) => items
|
|
241
|
+
.filter(item => item !== null && quoted(item)).slice(0, VERDICT_ITEMS)
|
|
242
|
+
.flatMap(item => item === null || item.claim === null ? [] : [{ ...item.claim, quote: item.quote.trim() }]);
|
|
243
|
+
return { ok: true, verdict: {
|
|
244
|
+
positive: list(read.value.positive).filter(i => ids.has(i.instruction)),
|
|
245
|
+
negative: list(read.value.negative).filter(i => ids.has(i.instruction)),
|
|
246
|
+
gaps: list(read.value.gaps),
|
|
247
|
+
} };
|
|
231
248
|
}
|
|
249
|
+
/** A verdict as a session keeps it: through its schema, with its version. */
|
|
250
|
+
const keptVerdict = (verdict) => JSON.stringify(savedVerdictSchema.parse({ version: MEMORY_VERDICT_VERSION, ...verdict }));
|
|
232
251
|
const gapKey = (mistake) => sha(mistake.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, ' ').trim()).slice(0, 16);
|
|
233
252
|
/**
|
|
234
253
|
* Collect, distil, analyse and aggregate for one project. Never writes memory:
|
|
@@ -263,20 +282,21 @@ export async function runMemoryPass(store, args, now = new Date()) {
|
|
|
263
282
|
ON CONFLICT(id) DO UPDATE SET surface=excluded.surface, trace_sha=excluded.trace_sha, analyzed_at=NULL, verdict=NULL, problem=NULL`).run(session.id, args.repo, session.kind, session.source, session.at || now.toISOString(), surface.version, sha(trace));
|
|
264
283
|
const gaps = openGaps();
|
|
265
284
|
const answer = await args.analyzer({ trace, surface, openGaps: gaps, kind: session.kind });
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
285
|
+
const parsed = answer.ok ? parseVerdict(answer.text, trace, surface) : null;
|
|
286
|
+
if (parsed === null || !parsed.ok) {
|
|
268
287
|
report.failed++;
|
|
269
|
-
const problem = answer.ok ?
|
|
288
|
+
const problem = parsed === null ? (answer.ok ? UNREADABLE : answer.problem) : parsed.problem;
|
|
270
289
|
report.problems.push(`${session.id}: ${problem}`);
|
|
271
290
|
store.handle.prepare('UPDATE memory_session SET problem=? WHERE id=?').run(problem, session.id);
|
|
272
291
|
continue;
|
|
273
292
|
}
|
|
293
|
+
const verdict = parsed.verdict;
|
|
274
294
|
store.transact(() => {
|
|
275
|
-
store.handle.prepare('UPDATE memory_session SET analyzed_at=?, verdict=?, problem=NULL WHERE id=?').run(now.toISOString(),
|
|
295
|
+
store.handle.prepare('UPDATE memory_session SET analyzed_at=?, verdict=?, problem=NULL WHERE id=?').run(now.toISOString(), keptVerdict(verdict), session.id);
|
|
276
296
|
for (const gap of verdict.gaps) {
|
|
277
297
|
if (gap.domain === 'orchestration')
|
|
278
298
|
continue;
|
|
279
|
-
const key = gap.matchesGap !==
|
|
299
|
+
const key = gap.matchesGap !== null && gaps.some(g => g.key === gap.matchesGap) ? gap.matchesGap : gapKey(gap.mistake);
|
|
280
300
|
store.handle.prepare(`INSERT INTO memory_gap(repo,key,mistake,proposed,domain,first_seen,last_seen) VALUES (?,?,?,?,?,?,?)
|
|
281
301
|
ON CONFLICT(repo,key) DO UPDATE SET last_seen=excluded.last_seen, retired_at=NULL, retired_reason=NULL`).run(args.repo, key, gap.mistake, gap.proposedInstruction, gap.domain, now.toISOString(), now.toISOString());
|
|
282
302
|
const id = Number(store.handle.prepare('SELECT id FROM memory_gap WHERE repo=? AND key=?').get(args.repo, key)['id']);
|
|
@@ -289,10 +309,15 @@ export async function runMemoryPass(store, args, now = new Date()) {
|
|
|
289
309
|
report.proposals = synthesizeProposals(store, args.repo, args.actor, surface, now);
|
|
290
310
|
return report;
|
|
291
311
|
}
|
|
312
|
+
// ---- 4. propose --------------------------------------------------------------------
|
|
313
|
+
/** A proposal row through its schema; one that does not read as a proposal names the field. */
|
|
292
314
|
function proposalOf(row) {
|
|
293
|
-
|
|
315
|
+
const read = memoryProposalSchema.safeParse({ id: Number(row['id']), repo: String(row['repo']), kind: row['kind'], fingerprint: String(row['fingerprint']), title: String(row['title']), rationale: String(row['rationale']),
|
|
294
316
|
beforeText: row['before_text'] === null ? null : String(row['before_text']), afterText: String(row['after_text']), evidence: JSON.parse(String(row['evidence'])),
|
|
295
|
-
sessions: Number(row['sessions']), status: String(row['status']), createdAt: String(row['created_at']), surface: String(row['surface']) };
|
|
317
|
+
sessions: Number(row['sessions']), status: String(row['status']), createdAt: String(row['created_at']), surface: String(row['surface']) }, { reportInput: true });
|
|
318
|
+
if (!read.success)
|
|
319
|
+
throw Error(`Memory proposal ${String(row['id'])} could not be read: ${contractError(read.error).join('; ')}`);
|
|
320
|
+
return read.data;
|
|
296
321
|
}
|
|
297
322
|
/**
|
|
298
323
|
* Turn corroborated gaps and harm into a few proposals: an addition needs
|
|
@@ -317,7 +342,7 @@ export function synthesizeProposals(store, repo, actor, surface, now = new Date(
|
|
|
317
342
|
if (sessions < MEMORY_MIN_SESSIONS || (rejected.get(fingerprint) ?? -1) >= sessions)
|
|
318
343
|
return;
|
|
319
344
|
store.handle.prepare('INSERT INTO memory_proposal(repo,kind,fingerprint,title,rationale,before_text,after_text,evidence,sessions,status,created_at,surface) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)')
|
|
320
|
-
.run(repo, kind, fingerprint, title, rationale, beforeText, afterText, JSON.stringify(evidence.slice(0, 6)), sessions, 'pending', now.toISOString(), surface.version);
|
|
345
|
+
.run(repo, kind, fingerprint, title, rationale, beforeText, afterText, JSON.stringify(memoryEvidenceSchema.parse(evidence.slice(0, 6))), sessions, 'pending', now.toISOString(), surface.version);
|
|
321
346
|
pendingFingerprints.add(fingerprint);
|
|
322
347
|
made++;
|
|
323
348
|
};
|
|
@@ -342,14 +367,17 @@ export function synthesizeProposals(store, repo, actor, surface, now = new Date(
|
|
|
342
367
|
// Removals from harm.
|
|
343
368
|
const harm = new Map();
|
|
344
369
|
for (const row of store.handle.prepare("SELECT id, verdict FROM memory_session WHERE repo=? AND surface=? AND verdict IS NOT NULL").all(repo, surface.version)) {
|
|
345
|
-
let
|
|
370
|
+
let kept;
|
|
346
371
|
try {
|
|
347
|
-
|
|
372
|
+
kept = JSON.parse(String(row['verdict']));
|
|
348
373
|
}
|
|
349
374
|
catch {
|
|
350
375
|
continue;
|
|
351
376
|
}
|
|
352
|
-
|
|
377
|
+
const verdict = readSavedVerdict(kept);
|
|
378
|
+
if (!verdict.ok)
|
|
379
|
+
continue;
|
|
380
|
+
for (const item of verdict.value.negative)
|
|
353
381
|
if (item.class === 'harm')
|
|
354
382
|
harm.set(item.instruction, [...(harm.get(item.instruction) ?? []), { session: String(row['id']), quote: item.quote }]);
|
|
355
383
|
}
|
package/dist/phase-routing.d.ts
CHANGED
|
@@ -45,22 +45,21 @@
|
|
|
45
45
|
* sealed route. Only a row proven to predate v47 (no route era) reads as
|
|
46
46
|
* a legacy approval governed by its sealed profile alone.
|
|
47
47
|
*/
|
|
48
|
+
import type { EvidenceKind } from "./contracts/acceptance-terms.js";
|
|
49
|
+
import { MODEL_ID_SHAPE, RISKS, ROUTE_VERSION, type PhaseRoute, type RouteLeg, type RouteOverride, type RouteVersion, type TaskSizing } from "./contracts/route.js";
|
|
48
50
|
import type { Phase, ProviderId } from "./provider.js";
|
|
49
51
|
import type { QualityMode } from "./quality.js";
|
|
52
|
+
/** The sealed route's shape is its contract (contracts/route.ts); these are its types. */
|
|
53
|
+
export type { PhaseRoute, RouteLeg, RouteOverride, RouteVersion, TaskSizing };
|
|
54
|
+
export { MODEL_ID_SHAPE };
|
|
50
55
|
export declare const PROVIDER_ID_LIST: readonly ProviderId[];
|
|
51
|
-
/** provider.ts's MODEL_ID, restated for the same reason and pinned by the
|
|
52
|
-
* same test: an exact model id is argv-safe — no leading dash, no
|
|
53
|
-
* whitespace or control bytes — or it is not a model id this policy can
|
|
54
|
-
* seal, stamp, or rehydrate. */
|
|
55
|
-
export declare const MODEL_ID_SHAPE: RegExp;
|
|
56
56
|
/** The same exactness for callers outside this policy (the profile
|
|
57
57
|
* rehydrator): one shape, one definition. */
|
|
58
58
|
export declare function exactModelId(v: unknown): v is string;
|
|
59
59
|
/** v2 adds the task's size (small / medium / large, risky or not), the
|
|
60
60
|
* light tier, and plan headroom. A v1 route — sealed before sizing — still
|
|
61
61
|
* reads back exactly and keeps its own digest domain. */
|
|
62
|
-
export
|
|
63
|
-
export type RouteVersion = 1 | 2;
|
|
62
|
+
export { ROUTE_VERSION };
|
|
64
63
|
/** The durable route ERA a scope row carries once it was filed under this
|
|
65
64
|
* policy: NULL on a row proven to predate v47 (legacy — the sealed profile
|
|
66
65
|
* alone governs), this value on every row filed since. A row with an era
|
|
@@ -70,27 +69,21 @@ export declare const ROUTE_ERA = 1;
|
|
|
70
69
|
export declare const PHASES: readonly Phase[];
|
|
71
70
|
/** The review leg remains readable in signed history but is no longer scheduled. */
|
|
72
71
|
export declare const ACTIVE_PHASES: readonly Phase[];
|
|
73
|
-
export type RiskLevel =
|
|
72
|
+
export type RiskLevel = (typeof RISKS)[number];
|
|
74
73
|
export declare const RISK_LEVELS: readonly RiskLevel[];
|
|
75
74
|
export declare function isRiskLevel(value: unknown): value is RiskLevel;
|
|
76
75
|
/** How big a change the task is, sized once at filing (by the classifier,
|
|
77
76
|
* by the description when it cannot answer, or by a person). */
|
|
78
|
-
export type TaskSize = "
|
|
77
|
+
export type TaskSize = TaskSizing["size"];
|
|
79
78
|
export declare const TASK_SIZES: readonly TaskSize[];
|
|
80
|
-
export type SizeSource = "
|
|
81
|
-
export type TaskSizing = {
|
|
82
|
-
size: TaskSize;
|
|
83
|
-
risky: boolean;
|
|
84
|
-
source: SizeSource;
|
|
85
|
-
reason: string;
|
|
86
|
-
};
|
|
79
|
+
export type SizeSource = TaskSizing["source"];
|
|
87
80
|
export declare function isTaskSize(value: unknown): value is TaskSize;
|
|
88
81
|
/** Whether a task makes no plan: a small change that is not risky, at
|
|
89
82
|
* routine risk. Elevated or high risk always plans, whatever the size. */
|
|
90
83
|
export declare function makesNoPlan(size: TaskSizing | null | undefined, risk: RiskLevel): boolean;
|
|
91
84
|
/** Where a size came from, in words. */
|
|
92
85
|
export declare function sizeSourceWords(source: SizeSource): string;
|
|
93
|
-
/** Strict read of a stored sizing: exact keys, known words, bounded reason. */
|
|
86
|
+
/** Strict read of a stored sizing (the route contract's): exact keys, known words, bounded reason. */
|
|
94
87
|
export declare function parseSizing(raw: unknown): TaskSizing | null;
|
|
95
88
|
export declare function riskTitle(risk: RiskLevel): string;
|
|
96
89
|
/**
|
|
@@ -159,16 +152,9 @@ export type RouteCandidates = {
|
|
|
159
152
|
repair: RepairCandidates;
|
|
160
153
|
review: PhaseCandidates;
|
|
161
154
|
};
|
|
162
|
-
/** An approver's explicit per-phase choice, recorded with attribution.
|
|
163
|
-
* Always an exact pair. */
|
|
164
|
-
export type RouteOverride = ExactSpec & {
|
|
165
|
-
phase: Phase;
|
|
166
|
-
by: string;
|
|
167
|
-
at: string;
|
|
168
|
-
};
|
|
169
155
|
/** The evidence kinds an acceptance rubric demands — `scope.ts` owns the
|
|
170
156
|
* full type; the policy only needs the names. */
|
|
171
|
-
export type RouteEvidenceKind =
|
|
157
|
+
export type RouteEvidenceKind = EvidenceKind;
|
|
172
158
|
/** How far the plane may carry a result without a person: no publication
|
|
173
159
|
* grant at all, a grant whose merges wait for a human (notify), or a live
|
|
174
160
|
* mode that merges by itself (automerge). */
|
|
@@ -220,48 +206,6 @@ export type RouteInput = {
|
|
|
220
206
|
* provider past its limits gives way even to a stronger tier. */
|
|
221
207
|
headroom?: readonly ProviderRoom[];
|
|
222
208
|
};
|
|
223
|
-
export type RouteLeg = {
|
|
224
|
-
phase: Phase;
|
|
225
|
-
provider: ProviderId;
|
|
226
|
-
/** The exact model id this leg runs — never null on a frozen route. */
|
|
227
|
-
model: string;
|
|
228
|
-
/** The tier the recommendation drew from; an override or pin still
|
|
229
|
-
* records what tier the policy would have used. */
|
|
230
|
-
tier: CandidateTier;
|
|
231
|
-
chosen: "recommended" | "override" | "pinned";
|
|
232
|
-
/** What the policy recommended before any override or pin — shown so an
|
|
233
|
-
* overridden leg can say what it replaced. */
|
|
234
|
-
recommended: {
|
|
235
|
-
provider: ProviderId;
|
|
236
|
-
model: string;
|
|
237
|
-
tier: CandidateTier;
|
|
238
|
-
};
|
|
239
|
-
/** Plain-English, ordered, deterministic. */
|
|
240
|
-
reasons: string[];
|
|
241
|
-
/** A configuration that cannot run this leg at all (gemini on review,
|
|
242
|
-
* cross-provider repair) — stated, never repaired by substitution; a
|
|
243
|
-
* route with any problem files its scope unresolved. */
|
|
244
|
-
problem: string | null;
|
|
245
|
-
};
|
|
246
|
-
export type PhaseRoute = {
|
|
247
|
-
version: RouteVersion;
|
|
248
|
-
risk: RiskLevel;
|
|
249
|
-
qualityMode: QualityMode;
|
|
250
|
-
publication: PublicationAuthority;
|
|
251
|
-
evidence: RouteEvidenceKind[];
|
|
252
|
-
/** The posture the route ACTUALLY runs under: "strong" only when at
|
|
253
|
-
* least one leg draws from a configured strong agent; "economy" when
|
|
254
|
-
* every selected agent is the routine one — however demanding the task,
|
|
255
|
-
* a route that keeps every default never claims stronger agents. */
|
|
256
|
-
posture: "economy" | "strong";
|
|
257
|
-
/** Why a stronger tier was wanted, in order (empty for a routine task). */
|
|
258
|
-
demands: string[];
|
|
259
|
-
legs: RouteLeg[];
|
|
260
|
-
overrides: RouteOverride[];
|
|
261
|
-
/** The task's size as it was routed (v2); null on a v1 route or an
|
|
262
|
-
* unsized task. */
|
|
263
|
-
size: TaskSizing | null;
|
|
264
|
-
};
|
|
265
209
|
export declare function specWords(spec: {
|
|
266
210
|
provider: string;
|
|
267
211
|
model: string;
|
|
@@ -291,12 +235,15 @@ export declare function canonicalRouteJson(route: PhaseRoute): string;
|
|
|
291
235
|
* same 128 bits every other safety digest here uses. Its own domain, so a
|
|
292
236
|
* route digest can never collide with a profile or chain digest. */
|
|
293
237
|
export declare function routeDigestOf(route: PhaseRoute): string;
|
|
294
|
-
/** Strict rehydration of a stored route
|
|
295
|
-
* model an exact non-empty string
|
|
296
|
-
* guess. A null from a row that carries
|
|
238
|
+
/** Strict rehydration of a stored route through the route contract — every
|
|
239
|
+
* field type-proved, every model an exact non-empty string, nothing unknown;
|
|
240
|
+
* anything unexpected is null, never a guess. A null from a row that carries
|
|
241
|
+
* a route era is a corrupt seal. */
|
|
297
242
|
export declare function routeFromJson(json: string | null): PhaseRoute | null;
|
|
298
|
-
/**
|
|
299
|
-
|
|
243
|
+
/** Why a stored route does not read, as path-named lines (`legs[1].model: …`); null when it reads. */
|
|
244
|
+
export declare function routeJsonProblem(json: string): string | null;
|
|
245
|
+
/** Strict parse of an override list (the route contract's): one per phase,
|
|
246
|
+
* every field proved, every model exact, in phase order. */
|
|
300
247
|
export declare function parseOverrides(raw: unknown): RouteOverride[] | null;
|
|
301
248
|
/** The stored override list (a task-level column). Malformed reads as
|
|
302
249
|
* null — the caller files the scope unresolved rather than routing as if
|
package/dist/phase-routing.js
CHANGED
|
@@ -46,19 +46,16 @@
|
|
|
46
46
|
* a legacy approval governed by its sealed profile alone.
|
|
47
47
|
*/
|
|
48
48
|
import { createHash } from "node:crypto";
|
|
49
|
-
|
|
49
|
+
import { MODEL_ID_SHAPE, postureOf, readRouteOverrides, readSealedRoute, RISKS, ROUTE_PHASES, ROUTE_PROVIDERS, ROUTE_VERSION, TASK_SIZE_WORDS, taskSizingSchema, } from "./contracts/route.js";
|
|
50
|
+
export { MODEL_ID_SHAPE };
|
|
51
|
+
// Type-only imports from provider.ts: it sits under evidence.ts and scope.ts
|
|
50
52
|
// in the module graph, and this policy is imported by scope.ts — a value
|
|
51
|
-
// import would be a cycle at load time. The
|
|
52
|
-
// pinned to provider.ts's by the test suite.
|
|
53
|
-
export const PROVIDER_ID_LIST =
|
|
53
|
+
// import would be a cycle at load time. The route contract restates the id
|
|
54
|
+
// list, pinned to provider.ts's by the test suite.
|
|
55
|
+
export const PROVIDER_ID_LIST = ROUTE_PROVIDERS;
|
|
54
56
|
function isProviderId(value) {
|
|
55
57
|
return PROVIDER_ID_LIST.includes(value);
|
|
56
58
|
}
|
|
57
|
-
/** provider.ts's MODEL_ID, restated for the same reason and pinned by the
|
|
58
|
-
* same test: an exact model id is argv-safe — no leading dash, no
|
|
59
|
-
* whitespace or control bytes — or it is not a model id this policy can
|
|
60
|
-
* seal, stamp, or rehydrate. */
|
|
61
|
-
export const MODEL_ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
|
|
62
59
|
function exactModel(v) {
|
|
63
60
|
return typeof v === "string" && MODEL_ID_SHAPE.test(v);
|
|
64
61
|
}
|
|
@@ -70,21 +67,21 @@ export function exactModelId(v) {
|
|
|
70
67
|
/** v2 adds the task's size (small / medium / large, risky or not), the
|
|
71
68
|
* light tier, and plan headroom. A v1 route — sealed before sizing — still
|
|
72
69
|
* reads back exactly and keeps its own digest domain. */
|
|
73
|
-
export
|
|
70
|
+
export { ROUTE_VERSION };
|
|
74
71
|
/** The durable route ERA a scope row carries once it was filed under this
|
|
75
72
|
* policy: NULL on a row proven to predate v47 (legacy — the sealed profile
|
|
76
73
|
* alone governs), this value on every row filed since. A row with an era
|
|
77
74
|
* and no readable route is corrupt and fails closed. The era marks a ROUTED
|
|
78
75
|
* row; which route version it holds rides in the snapshot itself. */
|
|
79
76
|
export const ROUTE_ERA = 1;
|
|
80
|
-
export const PHASES =
|
|
77
|
+
export const PHASES = ROUTE_PHASES;
|
|
81
78
|
/** The review leg remains readable in signed history but is no longer scheduled. */
|
|
82
79
|
export const ACTIVE_PHASES = ["plan", "build", "repair"];
|
|
83
|
-
export const RISK_LEVELS =
|
|
80
|
+
export const RISK_LEVELS = RISKS;
|
|
84
81
|
export function isRiskLevel(value) {
|
|
85
82
|
return value === "routine" || value === "elevated" || value === "high";
|
|
86
83
|
}
|
|
87
|
-
export const TASK_SIZES =
|
|
84
|
+
export const TASK_SIZES = TASK_SIZE_WORDS;
|
|
88
85
|
export function isTaskSize(value) {
|
|
89
86
|
return value === "small" || value === "medium" || value === "large";
|
|
90
87
|
}
|
|
@@ -97,20 +94,10 @@ export function makesNoPlan(size, risk) {
|
|
|
97
94
|
export function sizeSourceWords(source) {
|
|
98
95
|
return source === "classifier" ? "sized automatically" : source === "person" ? "set by a person" : "sized from the description";
|
|
99
96
|
}
|
|
100
|
-
/** Strict read of a stored sizing: exact keys, known words, bounded reason. */
|
|
97
|
+
/** Strict read of a stored sizing (the route contract's): exact keys, known words, bounded reason. */
|
|
101
98
|
export function parseSizing(raw) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const v = raw;
|
|
105
|
-
if (!exactKeysOf(v, ["size", "risky", "source", "reason"]))
|
|
106
|
-
return null;
|
|
107
|
-
if (!isTaskSize(v["size"]) || typeof v["risky"] !== "boolean")
|
|
108
|
-
return null;
|
|
109
|
-
if (v["source"] !== "classifier" && v["source"] !== "heuristic" && v["source"] !== "person")
|
|
110
|
-
return null;
|
|
111
|
-
if (typeof v["reason"] !== "string" || v["reason"].length > 300)
|
|
112
|
-
return null;
|
|
113
|
-
return { size: v["size"], risky: v["risky"], source: v["source"], reason: v["reason"] };
|
|
99
|
+
const parsed = taskSizingSchema.safeParse(raw);
|
|
100
|
+
return parsed.success ? parsed.data : null;
|
|
114
101
|
}
|
|
115
102
|
export function riskTitle(risk) {
|
|
116
103
|
return risk === "high" ? "High risk" : risk === "elevated" ? "Elevated risk" : "Routine";
|
|
@@ -491,7 +478,7 @@ function withHeadroom(input, phase, chosen) {
|
|
|
491
478
|
}
|
|
492
479
|
/** Whether any leg actually runs a configured strong agent. */
|
|
493
480
|
function drawsStrong(legs) {
|
|
494
|
-
return legs
|
|
481
|
+
return postureOf(legs) === "strong";
|
|
495
482
|
}
|
|
496
483
|
export function legOf(route, phase) {
|
|
497
484
|
const leg = route.legs.find(one => one.phase === phase);
|
|
@@ -559,156 +546,39 @@ export function routeDigestOf(route) {
|
|
|
559
546
|
function str(v) {
|
|
560
547
|
return typeof v === "string" && v !== "";
|
|
561
548
|
}
|
|
562
|
-
/**
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
function strOrNull(v) {
|
|
568
|
-
return v === null || str(v);
|
|
569
|
-
}
|
|
570
|
-
function stringList(v) {
|
|
571
|
-
return Array.isArray(v) && v.every(one => typeof one === "string");
|
|
572
|
-
}
|
|
573
|
-
/** Strict rehydration of a stored route — every field type-proved, every
|
|
574
|
-
* model an exact non-empty string; anything unexpected is null, never a
|
|
575
|
-
* guess. A null from a row that carries a route era is a corrupt seal. */
|
|
549
|
+
/** Strict rehydration of a stored route through the route contract — every
|
|
550
|
+
* field type-proved, every model an exact non-empty string, nothing unknown;
|
|
551
|
+
* anything unexpected is null, never a guess. A null from a row that carries
|
|
552
|
+
* a route era is a corrupt seal. */
|
|
576
553
|
// Recovery and normal admission share this strict decoder; neither may replace
|
|
577
554
|
// unreadable signed routing with a convenient current default.
|
|
578
555
|
export function routeFromJson(json) {
|
|
579
556
|
if (json === null)
|
|
580
557
|
return null;
|
|
558
|
+
const read = readRouteJson(json);
|
|
559
|
+
return read.ok ? read.route : null;
|
|
560
|
+
}
|
|
561
|
+
/** Why a stored route does not read, as path-named lines (`legs[1].model: …`); null when it reads. */
|
|
562
|
+
export function routeJsonProblem(json) {
|
|
563
|
+
const read = readRouteJson(json);
|
|
564
|
+
return read.ok ? null : read.lines.join("; ");
|
|
565
|
+
}
|
|
566
|
+
function readRouteJson(json) {
|
|
581
567
|
let parsed;
|
|
582
568
|
try {
|
|
583
569
|
parsed = JSON.parse(json);
|
|
584
570
|
}
|
|
585
571
|
catch {
|
|
586
|
-
return
|
|
572
|
+
return { ok: false, lines: ["payload: not valid JSON"] };
|
|
587
573
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const r = parsed;
|
|
591
|
-
// Exact keys (v48 integrity): the snapshot carries what canonicalRouteJson
|
|
592
|
-
// writes and nothing else — on the route, on every leg, on every leg's
|
|
593
|
-
// recommendation, and on every override.
|
|
594
|
-
// A v1 route (sealed before sizing) reads back exactly as it was written:
|
|
595
|
-
// no size, routine and strong tiers only.
|
|
596
|
-
const version = r["version"];
|
|
597
|
-
if (version !== 1 && version !== 2)
|
|
598
|
-
return null;
|
|
599
|
-
const v1Keys = ["version", "risk", "qualityMode", "publication", "evidence", "posture", "demands", "legs", "overrides"];
|
|
600
|
-
if (!exactKeysOf(r, version === 1 ? v1Keys : [...v1Keys, "size"]))
|
|
601
|
-
return null;
|
|
602
|
-
const tierOk = (tier) => tier === "routine" || tier === "strong" || (version === 2 && tier === "light");
|
|
603
|
-
const size = version === 1 || r["size"] === null ? null : parseSizing(r["size"]);
|
|
604
|
-
if (version === 2 && r["size"] !== null && size === null)
|
|
605
|
-
return null;
|
|
606
|
-
if (!isRiskLevel(r["risk"]))
|
|
607
|
-
return null;
|
|
608
|
-
if (r["qualityMode"] !== "default" && r["qualityMode"] !== "strict")
|
|
609
|
-
return null;
|
|
610
|
-
if (r["publication"] !== "none" && r["publication"] !== "notify" && r["publication"] !== "automerge")
|
|
611
|
-
return null;
|
|
612
|
-
if (r["posture"] !== "economy" && r["posture"] !== "strong")
|
|
613
|
-
return null;
|
|
614
|
-
if (!stringList(r["demands"]))
|
|
615
|
-
return null;
|
|
616
|
-
const evidence = r["evidence"];
|
|
617
|
-
if (!Array.isArray(evidence) || !evidence.every(one => one === "check" || one === "screenshot" || one === "changed-path" || one === "manual-review"))
|
|
618
|
-
return null;
|
|
619
|
-
if (!Array.isArray(r["legs"]) || r["legs"].length !== PHASES.length)
|
|
620
|
-
return null;
|
|
621
|
-
const legs = [];
|
|
622
|
-
for (const [index, raw] of r["legs"].entries()) {
|
|
623
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
624
|
-
return null;
|
|
625
|
-
const leg = raw;
|
|
626
|
-
if (!exactKeysOf(leg, ["phase", "provider", "model", "tier", "chosen", "recommended", "reasons", "problem"]))
|
|
627
|
-
return null;
|
|
628
|
-
if (leg["phase"] !== PHASES[index])
|
|
629
|
-
return null;
|
|
630
|
-
if (!str(leg["provider"]) || !isProviderId(leg["provider"]))
|
|
631
|
-
return null;
|
|
632
|
-
if (!exactModel(leg["model"]))
|
|
633
|
-
return null;
|
|
634
|
-
if (!tierOk(leg["tier"]))
|
|
635
|
-
return null;
|
|
636
|
-
if (leg["chosen"] !== "recommended" && leg["chosen"] !== "override" && leg["chosen"] !== "pinned")
|
|
637
|
-
return null;
|
|
638
|
-
const rec = leg["recommended"];
|
|
639
|
-
if (rec === null || typeof rec !== "object" || Array.isArray(rec))
|
|
640
|
-
return null;
|
|
641
|
-
const recommended = rec;
|
|
642
|
-
if (!exactKeysOf(recommended, ["provider", "model", "tier"]))
|
|
643
|
-
return null;
|
|
644
|
-
if (!str(recommended["provider"]) || !isProviderId(recommended["provider"]))
|
|
645
|
-
return null;
|
|
646
|
-
if (!exactModel(recommended["model"]))
|
|
647
|
-
return null;
|
|
648
|
-
if (!tierOk(recommended["tier"]))
|
|
649
|
-
return null;
|
|
650
|
-
if (!stringList(leg["reasons"]))
|
|
651
|
-
return null;
|
|
652
|
-
if (!strOrNull(leg["problem"]))
|
|
653
|
-
return null;
|
|
654
|
-
legs.push({
|
|
655
|
-
phase: PHASES[index],
|
|
656
|
-
provider: leg["provider"],
|
|
657
|
-
model: leg["model"],
|
|
658
|
-
tier: leg["tier"],
|
|
659
|
-
chosen: leg["chosen"],
|
|
660
|
-
recommended: { provider: recommended["provider"], model: recommended["model"], tier: recommended["tier"] },
|
|
661
|
-
reasons: [...leg["reasons"]],
|
|
662
|
-
problem: leg["problem"],
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
// The posture must be what the legs say — a snapshot claiming a strong
|
|
666
|
-
// posture over routine legs (or the reverse) is not one this policy wrote.
|
|
667
|
-
if ((drawsStrong(legs) ? "strong" : "economy") !== r["posture"])
|
|
668
|
-
return null;
|
|
669
|
-
const overrides = parseOverrides(r["overrides"]);
|
|
670
|
-
if (overrides === null)
|
|
671
|
-
return null;
|
|
672
|
-
return {
|
|
673
|
-
version,
|
|
674
|
-
risk: r["risk"],
|
|
675
|
-
qualityMode: r["qualityMode"],
|
|
676
|
-
publication: r["publication"],
|
|
677
|
-
evidence: [...evidence].sort(),
|
|
678
|
-
posture: r["posture"],
|
|
679
|
-
demands: [...r["demands"]],
|
|
680
|
-
legs,
|
|
681
|
-
overrides,
|
|
682
|
-
size,
|
|
683
|
-
};
|
|
574
|
+
const read = readSealedRoute(parsed);
|
|
575
|
+
return read.ok ? { ok: true, route: read.value } : { ok: false, lines: read.issues.map(one => one.line) };
|
|
684
576
|
}
|
|
685
|
-
/** Strict parse of an override list: one per phase,
|
|
686
|
-
* every model exact. */
|
|
577
|
+
/** Strict parse of an override list (the route contract's): one per phase,
|
|
578
|
+
* every field proved, every model exact, in phase order. */
|
|
687
579
|
export function parseOverrides(raw) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const overrides = [];
|
|
691
|
-
const seen = new Set();
|
|
692
|
-
for (const entry of raw) {
|
|
693
|
-
if (entry === null || typeof entry !== "object" || Array.isArray(entry))
|
|
694
|
-
return null;
|
|
695
|
-
const o = entry;
|
|
696
|
-
if (!exactKeysOf(o, ["phase", "provider", "model", "by", "at"]))
|
|
697
|
-
return null;
|
|
698
|
-
if (!str(o["phase"]) || !PHASES.includes(o["phase"]))
|
|
699
|
-
return null;
|
|
700
|
-
if (seen.has(o["phase"]))
|
|
701
|
-
return null;
|
|
702
|
-
seen.add(o["phase"]);
|
|
703
|
-
if (!str(o["provider"]) || !isProviderId(o["provider"]))
|
|
704
|
-
return null;
|
|
705
|
-
if (!exactModel(o["model"]))
|
|
706
|
-
return null;
|
|
707
|
-
if (!str(o["by"]) || !str(o["at"]))
|
|
708
|
-
return null;
|
|
709
|
-
overrides.push({ phase: o["phase"], provider: o["provider"], model: o["model"], by: o["by"], at: o["at"] });
|
|
710
|
-
}
|
|
711
|
-
return overrides.sort((a, b) => PHASES.indexOf(a.phase) - PHASES.indexOf(b.phase));
|
|
580
|
+
const read = readRouteOverrides(raw);
|
|
581
|
+
return read.ok ? read.value : null;
|
|
712
582
|
}
|
|
713
583
|
/** The stored override list (a task-level column). Malformed reads as
|
|
714
584
|
* null — the caller files the scope unresolved rather than routing as if
|
|
@@ -1,26 +1,7 @@
|
|
|
1
|
-
import { type DecisionLine } from './project-memory.js';
|
|
2
|
-
import { type RepositoryContext } from './repository-context.js';
|
|
3
1
|
import type { Store } from './store.js';
|
|
2
|
+
import { type Knowledge, type KnowledgeDraft, type KnowledgeReference, type KnowledgeSelection } from './contracts/project-knowledge.js';
|
|
4
3
|
export declare const KNOWLEDGE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS project_knowledge (\n repo TEXT PRIMARY KEY, identity TEXT NOT NULL, revision INTEGER NOT NULL, payload TEXT NOT NULL, sha TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS knowledge_change (\n id INTEGER PRIMARY KEY, repo TEXT NOT NULL, identity TEXT NOT NULL, revision INTEGER NOT NULL,\n actor TEXT NOT NULL, at TEXT NOT NULL, payload TEXT NOT NULL, sha TEXT NOT NULL,\n UNIQUE(repo,identity,revision)\n);\nCREATE TABLE IF NOT EXISTS knowledge_snapshot (\n run INTEGER PRIMARY KEY REFERENCES run(id), repo TEXT NOT NULL, identity TEXT NOT NULL,\n payload TEXT NOT NULL, sha TEXT NOT NULL\n);\n-- A saved project identity carried forward (a restart renumbered the volume, or an approver applied it).\nCREATE TABLE IF NOT EXISTS project_identity_carry (\n id INTEGER PRIMARY KEY, repo TEXT NOT NULL, from_identity TEXT NOT NULL, to_identity TEXT NOT NULL,\n actor TEXT NOT NULL, at TEXT NOT NULL, how TEXT NOT NULL CHECK(how IN ('automatic','approver')),\n UNIQUE(repo,from_identity,to_identity)\n);\nCREATE TRIGGER IF NOT EXISTS project_identity_carry_no_update BEFORE UPDATE ON project_identity_carry BEGIN SELECT RAISE(ABORT,'Project identity history is immutable'); END;\nCREATE TRIGGER IF NOT EXISTS project_identity_carry_no_delete BEFORE DELETE ON project_identity_carry BEGIN SELECT RAISE(ABORT,'Project identity history is immutable'); END;\nCREATE TRIGGER IF NOT EXISTS knowledge_change_no_update BEFORE UPDATE ON knowledge_change BEGIN SELECT RAISE(ABORT,'Knowledge history is immutable'); END;\nCREATE TRIGGER IF NOT EXISTS knowledge_change_no_delete BEFORE DELETE ON knowledge_change BEGIN SELECT RAISE(ABORT,'Knowledge history is immutable'); END;\nCREATE TRIGGER IF NOT EXISTS knowledge_snapshot_no_update BEFORE UPDATE ON knowledge_snapshot BEGIN SELECT RAISE(ABORT,'Knowledge context is immutable'); END;\nCREATE TRIGGER IF NOT EXISTS knowledge_snapshot_no_delete BEFORE DELETE ON knowledge_snapshot BEGIN SELECT RAISE(ABORT,'Knowledge context is immutable'); END;\n";
|
|
5
|
-
export type KnowledgeReference
|
|
6
|
-
id: string;
|
|
7
|
-
title: string;
|
|
8
|
-
content: string;
|
|
9
|
-
path: string | null;
|
|
10
|
-
sourceSha: string | null;
|
|
11
|
-
sourceRevision: string | null;
|
|
12
|
-
};
|
|
13
|
-
export type Knowledge = {
|
|
14
|
-
instructions: string;
|
|
15
|
-
references: KnowledgeReference[];
|
|
16
|
-
};
|
|
17
|
-
export type KnowledgeDraft = {
|
|
18
|
-
instructions?: string;
|
|
19
|
-
title?: string;
|
|
20
|
-
content?: string;
|
|
21
|
-
path?: string;
|
|
22
|
-
id?: string;
|
|
23
|
-
};
|
|
4
|
+
export type { Knowledge, KnowledgeDraft, KnowledgeReference, KnowledgeSelection };
|
|
24
5
|
export declare const STALE_KNOWLEDGE = "This project's saved knowledge is from before a change; review and apply it";
|
|
25
6
|
export declare function knowledgeView(store: Store, repo: string, actor: string): {
|
|
26
7
|
repo: string;
|
|
@@ -53,7 +34,20 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
53
34
|
decisions?: never;
|
|
54
35
|
} | {
|
|
55
36
|
stale?: never;
|
|
56
|
-
decision:
|
|
37
|
+
decision: {
|
|
38
|
+
repo: string;
|
|
39
|
+
revision: number;
|
|
40
|
+
claim: string;
|
|
41
|
+
why: string;
|
|
42
|
+
status: "active" | "retired" | "superseded";
|
|
43
|
+
supersedes: number | null;
|
|
44
|
+
decidedBy: string;
|
|
45
|
+
decidedAt: string;
|
|
46
|
+
sourceKind: "backward-pass" | "conversation" | "manual" | "result" | "task";
|
|
47
|
+
sourceRef: string | null;
|
|
48
|
+
recordedBy: string;
|
|
49
|
+
id: number;
|
|
50
|
+
};
|
|
57
51
|
notice: string;
|
|
58
52
|
title?: never;
|
|
59
53
|
content?: never;
|
|
@@ -110,19 +104,6 @@ export declare function changeKnowledge(store: Store, args: {
|
|
|
110
104
|
* Returns the new revision, or null when nothing was waiting.
|
|
111
105
|
*/
|
|
112
106
|
export declare function applySavedKnowledge(store: Store, repo: string, actor: string, now?: Date): number | null;
|
|
113
|
-
export type KnowledgeSelection = {
|
|
114
|
-
version: 1;
|
|
115
|
-
revision: number;
|
|
116
|
-
instructions: string;
|
|
117
|
-
references: KnowledgeReference[];
|
|
118
|
-
omitted: {
|
|
119
|
-
title: string;
|
|
120
|
-
reason: string;
|
|
121
|
-
}[];
|
|
122
|
-
inheritedFrom: number | null;
|
|
123
|
-
repository?: RepositoryContext;
|
|
124
|
-
decisions?: DecisionLine[];
|
|
125
|
-
};
|
|
126
107
|
/** Read the same bounded, source-checked selection without inventing a worker run. */
|
|
127
108
|
export declare function selectProjectKnowledge(store: Store, args: {
|
|
128
109
|
repo: string;
|