kibi-opencode 0.12.1 → 0.14.0

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.
@@ -0,0 +1,408 @@
1
+ // implements REQ-opencode-kibi-briefing-v6
2
+ function asRecord(value) {
3
+ return typeof value === "object" && value !== null
4
+ ? value
5
+ : null;
6
+ }
7
+ function asString(value) {
8
+ return typeof value === "string" ? value : "";
9
+ }
10
+ function asStringArray(value) {
11
+ return Array.isArray(value)
12
+ ? value.filter((entry) => typeof entry === "string")
13
+ : [];
14
+ }
15
+ function getSessionApi(client) {
16
+ const root = asRecord(client);
17
+ const session = asRecord(root?.session);
18
+ if (!session) {
19
+ return null;
20
+ }
21
+ const create = session.create;
22
+ const prompt = session.prompt;
23
+ if (typeof create !== "function" || typeof prompt !== "function") {
24
+ return null;
25
+ }
26
+ return {
27
+ create: create,
28
+ prompt: prompt,
29
+ };
30
+ }
31
+ function extractSessionId(response) {
32
+ const root = asRecord(response);
33
+ if (!root) {
34
+ return null;
35
+ }
36
+ const directId = asString(root.id).trim();
37
+ if (directId) {
38
+ return directId;
39
+ }
40
+ const data = asRecord(root.data);
41
+ return asString(data?.id).trim() || null;
42
+ }
43
+ function extractPromptResponseJson(response) {
44
+ const root = asRecord(response);
45
+ if (!root)
46
+ return null;
47
+ const data = asRecord(root.data);
48
+ const parts = Array.isArray(data?.parts)
49
+ ? data.parts
50
+ : Array.isArray(root.parts)
51
+ ? root.parts
52
+ : null;
53
+ if (!parts)
54
+ return null;
55
+ for (const part of parts) {
56
+ const record = asRecord(part);
57
+ if (record?.type !== "text") {
58
+ continue;
59
+ }
60
+ const text = asString(record.text);
61
+ if (!text) {
62
+ continue;
63
+ }
64
+ try {
65
+ return JSON.parse(text);
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ async function invokeTool(sessionApi, sessionID, tool, args) {
74
+ const response = await sessionApi.prompt({
75
+ sessionID,
76
+ parts: [{ type: "text", text: JSON.stringify({ tool, args }) }],
77
+ tools: { [tool]: true },
78
+ format: {
79
+ type: "json_schema",
80
+ schema: {
81
+ type: ["object", "array"],
82
+ },
83
+ },
84
+ });
85
+ return extractPromptResponseJson(response);
86
+ }
87
+ function inferTypeFromId(id) {
88
+ const prefix = id.split("-")[0]?.toLowerCase() ?? "entity";
89
+ switch (prefix) {
90
+ case "req":
91
+ case "scenario":
92
+ case "test":
93
+ case "adr":
94
+ case "flag":
95
+ case "event":
96
+ case "symbol":
97
+ case "fact":
98
+ return prefix;
99
+ case "scen":
100
+ return "scenario";
101
+ case "sym":
102
+ return "symbol";
103
+ default:
104
+ return prefix;
105
+ }
106
+ }
107
+ function humanizeType(type, count = 1) {
108
+ const singular = {
109
+ req: "requirement",
110
+ scenario: "scenario",
111
+ test: "test",
112
+ adr: "ADR",
113
+ flag: "flag",
114
+ event: "event",
115
+ symbol: "symbol",
116
+ fact: "fact",
117
+ }[type] ?? type;
118
+ if (count === 1) {
119
+ return singular;
120
+ }
121
+ if (singular === "ADR") {
122
+ return "ADRs";
123
+ }
124
+ return singular.endsWith("s") ? singular : `${singular}s`;
125
+ }
126
+ function humanizeDomain(domain) {
127
+ const normalized = domain.trim().toLowerCase();
128
+ switch (normalized) {
129
+ case "opencode":
130
+ return "OpenCode";
131
+ case "mcp":
132
+ return "MCP";
133
+ case "cli":
134
+ return "CLI";
135
+ case "vscode":
136
+ return "VSCode";
137
+ case "core":
138
+ return "Core";
139
+ case "requirements":
140
+ return "Requirements";
141
+ default:
142
+ return normalized ? normalized.charAt(0).toUpperCase() + normalized.slice(1) : "Changes";
143
+ }
144
+ }
145
+ function formatList(items) {
146
+ if (items.length === 0)
147
+ return "";
148
+ if (items.length === 1)
149
+ return items[0] ?? "";
150
+ if (items.length === 2)
151
+ return `${items[0]} and ${items[1]}`;
152
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
153
+ }
154
+ function normalizeEntity(record, idFallback) {
155
+ const properties = asRecord(record.properties) ?? {};
156
+ const id = asString(record.id || properties.id).trim() || idFallback;
157
+ const type = asString(record.type).trim() ||
158
+ asString(record.entityType).trim() ||
159
+ inferTypeFromId(id);
160
+ const title = asString(record.title).trim() ||
161
+ asString(properties.title).trim() ||
162
+ id;
163
+ const status = asString(record.status).trim() ||
164
+ asString(properties.status).trim() ||
165
+ asString(properties.change_kind).trim();
166
+ const source = asString(record.source).trim() ||
167
+ asString(properties.source).trim();
168
+ const tags = [
169
+ ...asStringArray(record.tags),
170
+ ...asStringArray(properties.tags),
171
+ ];
172
+ const factKind = asString(record.fact_kind).trim() ||
173
+ asString(properties.fact_kind).trim();
174
+ return {
175
+ id,
176
+ type,
177
+ title,
178
+ status,
179
+ source,
180
+ tags,
181
+ factKind,
182
+ removed: false,
183
+ };
184
+ }
185
+ function normalizeGraph(value) {
186
+ const root = asRecord(value);
187
+ if (!root) {
188
+ return null;
189
+ }
190
+ const edges = Array.isArray(root.edges)
191
+ ? root.edges
192
+ .map((edge) => {
193
+ const record = asRecord(edge);
194
+ if (!record)
195
+ return null;
196
+ const from = asString(record.from).trim();
197
+ const to = asString(record.to).trim();
198
+ const type = asString(record.type).trim();
199
+ return from && to && type ? { from, to, type } : null;
200
+ })
201
+ .filter((edge) => edge !== null)
202
+ : [];
203
+ return { edges };
204
+ }
205
+ function describeEntity(entity, id) {
206
+ if (!entity) {
207
+ return id;
208
+ }
209
+ return `${entity.title} (${entity.id})`;
210
+ }
211
+ function inferDomain(entity) {
212
+ const source = entity.source.replaceAll("\\", "/");
213
+ const packageMatch = source.match(/packages\/([^/]+)\//);
214
+ if (packageMatch?.[1]) {
215
+ return humanizeDomain(packageMatch[1]);
216
+ }
217
+ const tagDomain = entity.tags.find((tag) => ["cli", "mcp", "vscode", "opencode", "core"].includes(tag.toLowerCase()));
218
+ if (tagDomain) {
219
+ return humanizeDomain(tagDomain);
220
+ }
221
+ if (["req", "scenario", "test"].includes(entity.type)) {
222
+ return "Requirements";
223
+ }
224
+ return humanizeDomain(entity.type);
225
+ }
226
+ function classifyRelationship(relationship, entities) {
227
+ const fromEntity = entities.get(relationship.from);
228
+ const toEntity = entities.get(relationship.to);
229
+ const fromText = describeEntity(fromEntity, relationship.from);
230
+ const toText = describeEntity(toEntity, relationship.to);
231
+ switch (relationship.type) {
232
+ case "supersedes":
233
+ return `${toText} was superseded by ${fromText}`;
234
+ case "implements":
235
+ return `${fromText} now implements ${toText}`;
236
+ case "covered_by":
237
+ return `${fromText} gained test coverage via ${toText}`;
238
+ case "verified_by":
239
+ return `${fromText} is verified by ${toText}`;
240
+ case "specified_by":
241
+ return `${fromText} is specified by ${toText}`;
242
+ case "requires_property":
243
+ return `${fromText} constrains property ${toText}`;
244
+ case "constrains":
245
+ if (toEntity?.type === "fact" && toEntity.factKind === "subject") {
246
+ return `${fromText} is linked to fact ${toText}`;
247
+ }
248
+ return `${fromText} is linked to ${toText}`;
249
+ default:
250
+ return null;
251
+ }
252
+ }
253
+ function uniqueStrings(items) {
254
+ return [...new Set(items.filter(Boolean))];
255
+ }
256
+ function buildEntityChange(entity, relationships, graph) {
257
+ const label = describeEntity(entity, entity.id);
258
+ if (entity.removed) {
259
+ return `${label} was removed`;
260
+ }
261
+ if (entity.status === "superseded") {
262
+ return `${label} was marked superseded`;
263
+ }
264
+ const hasRelationshipChange = relationships.some((relationship) => relationship.from === entity.id || relationship.to === entity.id);
265
+ const hasGraphEdges = (graph?.edges.length ?? 0) > 0;
266
+ if (!hasRelationshipChange && !hasGraphEdges) {
267
+ return `${label} was created`;
268
+ }
269
+ return `${label} was updated`;
270
+ }
271
+ function summarizeTypes(entities) {
272
+ const counts = new Map();
273
+ for (const entity of entities) {
274
+ counts.set(entity.type, (counts.get(entity.type) ?? 0) + 1);
275
+ }
276
+ return formatList([...counts.entries()].map(([type, count]) => `${count} ${humanizeType(type, count)}`));
277
+ }
278
+ function summarizeTopChanges(domainChanges, relationshipChanges) {
279
+ const focus = relationshipChanges.length > 0 ? relationshipChanges : domainChanges;
280
+ const selected = focus.slice(0, 2);
281
+ return selected.length > 0 ? `Key changes: ${selected.join("; ")}.` : "";
282
+ }
283
+ function validationStatusText(checkResult) {
284
+ if (checkResult.count <= 0) {
285
+ return "All checks pass";
286
+ }
287
+ return `${checkResult.count} validation issue${checkResult.count === 1 ? "" : "s"}`;
288
+ }
289
+ async function loadEntity(sessionApi, sessionID, id) {
290
+ const response = await invokeTool(sessionApi, sessionID, "kb_query", { id, limit: 1 });
291
+ if (!Array.isArray(response)) {
292
+ throw new Error(`kb_query returned unsupported payload for ${id}`);
293
+ }
294
+ if (response.length === 0) {
295
+ return {
296
+ id,
297
+ type: inferTypeFromId(id),
298
+ title: id,
299
+ status: "removed",
300
+ source: "",
301
+ tags: [],
302
+ factKind: "",
303
+ removed: true,
304
+ };
305
+ }
306
+ const record = asRecord(response[0]);
307
+ if (!record) {
308
+ throw new Error(`kb_query returned invalid entity for ${id}`);
309
+ }
310
+ return normalizeEntity(record, id);
311
+ }
312
+ async function loadGraph(sessionApi, sessionID, id) {
313
+ const response = await invokeTool(sessionApi, sessionID, "kb_graph", {
314
+ seedIds: [id],
315
+ direction: "both",
316
+ depth: 2,
317
+ maxNodes: 100,
318
+ maxEdges: 200,
319
+ });
320
+ return normalizeGraph(response);
321
+ }
322
+ // implements REQ-opencode-kibi-briefing-v2
323
+ export async function generateGraphNarrative(client, workspaceCtx, changedEntityIds, changedRelationships, checkResult) {
324
+ const uniqueIds = uniqueStrings(changedEntityIds);
325
+ if (uniqueIds.length === 0) {
326
+ return null;
327
+ }
328
+ const sessionApi = getSessionApi(client);
329
+ if (!sessionApi) {
330
+ return null;
331
+ }
332
+ let sessionID = null;
333
+ try {
334
+ const worker = await sessionApi.create({
335
+ directory: workspaceCtx.workspaceRoot,
336
+ title: `Kibi Graph Narrator (${workspaceCtx.branch})`,
337
+ });
338
+ sessionID = extractSessionId(worker);
339
+ }
340
+ catch {
341
+ return null;
342
+ }
343
+ if (!sessionID) {
344
+ return null;
345
+ }
346
+ const entities = new Map();
347
+ const graphs = new Map();
348
+ for (const id of uniqueIds) {
349
+ let entity;
350
+ try {
351
+ entity = await loadEntity(sessionApi, sessionID, id);
352
+ }
353
+ catch {
354
+ continue;
355
+ }
356
+ if (!entity) {
357
+ continue;
358
+ }
359
+ entities.set(id, entity);
360
+ if (entity.removed) {
361
+ graphs.set(id, null);
362
+ continue;
363
+ }
364
+ try {
365
+ graphs.set(id, await loadGraph(sessionApi, sessionID, id));
366
+ }
367
+ catch {
368
+ graphs.set(id, null);
369
+ }
370
+ }
371
+ const entityList = [...entities.values()];
372
+ if (entityList.length === 0) {
373
+ return null;
374
+ }
375
+ const relationshipChanges = uniqueStrings(changedRelationships
376
+ .map((relationship) => classifyRelationship(relationship, entities))
377
+ .filter((sentence) => !!sentence));
378
+ const domainMap = new Map();
379
+ for (const entity of entityList) {
380
+ const domain = inferDomain(entity);
381
+ const changes = domainMap.get(domain) ?? [];
382
+ changes.push(buildEntityChange(entity, changedRelationships, graphs.get(entity.id) ?? null));
383
+ domainMap.set(domain, changes);
384
+ }
385
+ const domains = [...domainMap.entries()]
386
+ .map(([name, changes]) => ({ name, changes: uniqueStrings(changes) }))
387
+ .sort((left, right) => left.name.localeCompare(right.name));
388
+ const domainNames = domains.map((domain) => domain.name);
389
+ const headline = `${summarizeTypes(entityList)} changed${domainNames.length > 1 ? ` across ${formatList(domainNames)} domains` : ""}.`;
390
+ const allDomainChanges = domains.flatMap((domain) => domain.changes);
391
+ const validationStatus = validationStatusText(checkResult);
392
+ const tldr = [
393
+ headline,
394
+ summarizeTopChanges(allDomainChanges, relationshipChanges),
395
+ validationStatus === "All checks pass"
396
+ ? "Validation remains clean."
397
+ : `${validationStatus}.`,
398
+ ]
399
+ .filter(Boolean)
400
+ .join(" ");
401
+ return {
402
+ headline,
403
+ tldr,
404
+ domains,
405
+ relationshipChanges,
406
+ validationStatus,
407
+ };
408
+ }
@@ -3,6 +3,8 @@ import type { RiskClass } from "./risk-classifier.js";
3
3
  /**
4
4
  * Cache key uniquely identifies a preflight context by combining
5
5
  * workspace root, branch, posture, risk class, and file bucket.
6
+ * Hard-enforcement callers may additionally pass scopeKey to prevent
7
+ * cross-session/worktree dirty-state cache hits. Advisory callers omit it.
6
8
  */
7
9
  export interface CacheKey {
8
10
  workspaceRoot: string;
@@ -10,6 +12,7 @@ export interface CacheKey {
10
12
  posture: RepoPosture;
11
13
  riskClass: RiskClass;
12
14
  fileBucket: string;
15
+ scopeKey?: string;
13
16
  }
14
17
  /**
15
18
  * Cache entry tracking when a preflight was last satisfied.
@@ -3,7 +3,7 @@
3
3
  * Serializes a CacheKey into a deterministic string for use as a Map key.
4
4
  */
5
5
  function serializeKey(key) {
6
- return `${key.workspaceRoot}\0${key.branch}\0${key.posture}\0${key.riskClass}\0${key.fileBucket}`;
6
+ return `${key.workspaceRoot}\0${key.branch}\0${key.posture}\0${key.riskClass}\0${key.fileBucket}\0${key.scopeKey ?? ""}`;
7
7
  }
8
8
  /**
9
9
  * In-memory cache tracking satisfied preflight checks per unique context.
@@ -42,7 +42,14 @@ export interface IdleBriefingResult {
42
42
  regressionRisks?: IdleBriefStatement[];
43
43
  missingEvidence?: IdleBriefStatement[];
44
44
  }
45
+ type IdleBriefRelationship = {
46
+ from: string;
47
+ to: string;
48
+ type: string;
49
+ };
45
50
  export declare function generateIdleBrief(client: unknown, workspaceCtx: BriefingWorkspaceCtx, auditDelta: AuditDelta, sessionId: string, options?: {
46
51
  sourceFiles?: string[];
47
52
  changedEntityIds?: string[];
53
+ relationships?: IdleBriefRelationship[];
48
54
  }): Promise<IdleBriefResult>;
55
+ export {};
@@ -1,6 +1,7 @@
1
1
  // implements REQ-opencode-kibi-briefing-v4
2
2
  import { buildBriefingContext } from "./brief-intent.js";
3
3
  import { buildDeliveryReasons } from "./brief-delivery-reasons.js";
4
+ import { generateGraphNarrative } from "./graph-narrator.js";
4
5
  import { atomicWriteBrief, pruneOldBriefs, resolveBriefFilePath, } from "./idle-brief-paths.js";
5
6
  import { computeContentHash, createBriefId, } from "./idle-brief-store.js";
6
7
  import { reconcileAuditEntries } from "./reconcile-engine.js";
@@ -318,7 +319,7 @@ function buildChangeNarrative(auditDelta) {
318
319
  }
319
320
  return lines;
320
321
  }
321
- function buildEnvelopeParts(briefId, type, sessionId, branch, createdAt, auditDelta, summary, counts, checkResult, briefingResult, deliveryReasons) {
322
+ function buildEnvelopeParts(briefId, type, sessionId, branch, createdAt, auditDelta, summary, counts, checkResult, briefingResult, changeNarrative, deliveryReasons) {
322
323
  const reconciled = reconcileAuditEntries(auditDelta.entries);
323
324
  return {
324
325
  schemaVersion: "2.0",
@@ -350,7 +351,7 @@ function buildEnvelopeParts(briefId, type, sessionId, branch, createdAt, auditDe
350
351
  tldr: briefingResult.tldr || summary,
351
352
  promptBlock: briefingResult.promptBlock,
352
353
  citations: briefingResult.citations,
353
- changeNarrative: buildChangeNarrative(auditDelta),
354
+ changeNarrative,
354
355
  ...(deliveryReasons ? { deliveryReasons } : {}),
355
356
  ...(briefingResult.constraints && briefingResult.constraints.length > 0
356
357
  ? { constraints: briefingResult.constraints }
@@ -388,11 +389,15 @@ export async function generateIdleBrief(client, workspaceCtx, auditDelta, sessio
388
389
  : derivedSourceFiles.length > 0
389
390
  ? derivedSourceFiles
390
391
  : [auditDelta.entries[0]?.entityId ?? "unknown"];
392
+ const relationshipEntityIds = (options?.relationships ?? []).flatMap((relationship) => [relationship.from, relationship.to]);
393
+ const mergedChangedEntityIds = options?.changedEntityIds
394
+ ? [...new Set([...options.changedEntityIds, ...relationshipEntityIds])]
395
+ : relationshipEntityIds.length > 0
396
+ ? [...new Set(relationshipEntityIds)]
397
+ : undefined;
391
398
  const briefingContext = buildBriefingContext({
392
399
  sourceFiles,
393
- ...(options?.changedEntityIds
394
- ? { changedEntityIds: options.changedEntityIds }
395
- : {}),
400
+ ...(mergedChangedEntityIds ? { changedEntityIds: mergedChangedEntityIds } : {}),
396
401
  });
397
402
  const { seedIds } = briefingContext;
398
403
  let checkResult;
@@ -431,6 +436,12 @@ export async function generateIdleBrief(client, workspaceCtx, auditDelta, sessio
431
436
  const isSuccess = violationsCount === 0;
432
437
  const type = isSuccess ? "success" : "warning";
433
438
  const summary = computeSummary(counts, violationsCount);
439
+ const changedEntityIdsForNarrative = mergedChangedEntityIds ?? [
440
+ ...reconciled.added.map((item) => item.id),
441
+ ...reconciled.modified.map((item) => item.id),
442
+ ...reconciled.removed.map((item) => item.id),
443
+ ];
444
+ const changedRelationships = options?.relationships ?? [];
434
445
  const deliveryReasons = buildDeliveryReasons({
435
446
  entitiesAdded: reconciled.added
436
447
  .filter((item) => item.id !== "workspace-sync")
@@ -444,6 +455,13 @@ export async function generateIdleBrief(client, workspaceCtx, auditDelta, sessio
444
455
  relationshipsChanged: counts.relationshipsChanged,
445
456
  validationCount: checkResult.count,
446
457
  });
458
+ const graphNarrative = await generateGraphNarrative(client, workspaceCtx, changedEntityIdsForNarrative, changedRelationships, checkResult);
459
+ const changeNarrative = graphNarrative?.relationshipChanges.length || graphNarrative?.domains.length
460
+ ? [
461
+ ...graphNarrative.relationshipChanges,
462
+ ...graphNarrative.domains.flatMap((domain) => domain.changes),
463
+ ]
464
+ : buildChangeNarrative(auditDelta);
447
465
  if (counts.entitiesAdded === 0 &&
448
466
  counts.entitiesModified === 0 &&
449
467
  counts.entitiesRemoved === 0 &&
@@ -455,7 +473,7 @@ export async function generateIdleBrief(client, workspaceCtx, auditDelta, sessio
455
473
  const briefId = createBriefId();
456
474
  const timestamp = Date.now();
457
475
  const createdAt = new Date().toISOString();
458
- const envelopeWithoutHash = buildEnvelopeParts(briefId, type, sessionId, workspaceCtx.branch, createdAt, auditDelta, summary, counts, checkResult, briefingResult, deliveryReasons);
476
+ const envelopeWithoutHash = buildEnvelopeParts(briefId, type, sessionId, workspaceCtx.branch, createdAt, auditDelta, summary, counts, checkResult, briefingResult, changeNarrative, deliveryReasons);
459
477
  const contentHash = computeContentHash(envelopeWithoutHash);
460
478
  const envelope = {
461
479
  ...envelopeWithoutHash,
@@ -0,0 +1,83 @@
1
+ import { type KibiConfig } from "./config.js";
2
+ import { type EnforcementLifecycleEvent, type PolicyLinkedEntityResult } from "./enforcement-policy.js";
3
+ import type { E2eCoverageSignal } from "./e2e-coverage-signals.js";
4
+ import type { PathKind } from "./path-kind.js";
5
+ import { type CheckRunner, type SyncRunMetadata, type SyncRunner, type TimeoutHandle } from "./scheduler.js";
6
+ import type { WorkContext } from "./work-context-resolver.js";
7
+ export interface KibiCheckpointContext {
8
+ workContext: WorkContext;
9
+ config?: KibiConfig;
10
+ filePath?: string;
11
+ checkRules?: string[] | undefined;
12
+ maintenanceDegraded?: boolean;
13
+ hardGuidanceText?: string | null;
14
+ lifecycleEvents?: EnforcementLifecycleEvent[];
15
+ pathKinds?: PathKind[];
16
+ linkedEntityResults?: PolicyLinkedEntityResult[];
17
+ e2eSignals?: E2eCoverageSignal[];
18
+ }
19
+ export interface KibiCheckpointRunnerOptions {
20
+ config?: KibiConfig;
21
+ runSync?: SyncRunner;
22
+ runCheck?: CheckRunner;
23
+ onRunComplete?: (meta: SyncRunMetadata) => void;
24
+ timeoutMs?: number;
25
+ setTimeoutFn?: (fn: () => void, ms: number) => TimeoutHandle;
26
+ clearTimeoutFn?: (handle: TimeoutHandle) => void;
27
+ }
28
+ export interface KibiCheckpointMetadata {
29
+ fingerprint: string;
30
+ scopeKey: string;
31
+ worktree: string;
32
+ branch: string;
33
+ sessionId?: string;
34
+ agentIdentity: string;
35
+ guidanceRendered?: boolean;
36
+ guidanceText?: string;
37
+ sync?: SyncRunMetadata;
38
+ reason?: string;
39
+ timeoutMs?: number;
40
+ checkRules?: string[] | undefined;
41
+ restoreInstructions?: string;
42
+ }
43
+ export type KibiCheckpointRequestResult = {
44
+ kind: "requested";
45
+ metadata: KibiCheckpointMetadata;
46
+ } | {
47
+ kind: "skip";
48
+ metadata: KibiCheckpointMetadata;
49
+ } | {
50
+ kind: "hard_block";
51
+ metadata: KibiCheckpointMetadata;
52
+ };
53
+ export type KibiCheckpointRunResult = {
54
+ kind: "passed";
55
+ metadata: KibiCheckpointMetadata;
56
+ } | {
57
+ kind: "hard_block";
58
+ metadata: KibiCheckpointMetadata;
59
+ } | {
60
+ kind: "skip";
61
+ metadata: KibiCheckpointMetadata;
62
+ };
63
+ export declare class KibiCheckpointRunner {
64
+ private readonly config;
65
+ private readonly runSync;
66
+ private readonly runCheck;
67
+ private readonly onRunComplete;
68
+ private readonly timeoutMs;
69
+ private readonly setTimeoutFn;
70
+ private readonly clearTimeoutFn;
71
+ private readonly requested;
72
+ private readonly passed;
73
+ private readonly passedScopeKeysByFingerprint;
74
+ constructor(options?: KibiCheckpointRunnerOptions);
75
+ requestCheckpoint(context: KibiCheckpointContext, fingerprint: string): KibiCheckpointRequestResult;
76
+ isCheckpointPassed(fingerprint: string, context?: KibiCheckpointContext): boolean;
77
+ runCheckpoint(context: KibiCheckpointContext, fingerprint: string): Promise<KibiCheckpointRunResult>;
78
+ private baseMetadata;
79
+ private scopeKey;
80
+ private renderHardGuidance;
81
+ private withTimeout;
82
+ private recordPassed;
83
+ }