kibi-opencode 0.15.0 → 0.15.1
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/README.md +4 -12
- package/dist/config.d.ts +0 -3
- package/dist/config.js +0 -9
- package/dist/e2e-coverage-signals.js +1 -3
- package/dist/enforcement-policy.d.ts +1 -1
- package/dist/enforcement-policy.js +9 -4
- package/dist/enforcement-scope.js +1 -3
- package/dist/file-entity-links.js +2 -6
- package/dist/file-operation-reminders.d.ts +4 -4
- package/dist/file-operation-state.js +4 -2
- package/dist/init-kibi-alias.js +0 -1
- package/dist/init-kibi-capability.js +1 -5
- package/dist/kb-freshness-state.js +6 -2
- package/dist/kibi-checkpoint-runner.d.ts +1 -1
- package/dist/kibi-checkpoint-runner.js +6 -3
- package/dist/knowledge-classifier.js +2 -1
- package/dist/meaningful-change-classifier.d.ts +1 -1
- package/dist/meaningful-change-classifier.js +1 -2
- package/dist/plugin-startup.js +0 -1
- package/dist/plugin.js +45 -395
- package/dist/prompt.d.ts +2 -6
- package/dist/prompt.js +21 -84
- package/dist/reconcile-engine.d.ts +15 -1
- package/dist/reconcile-engine.js +1 -3
- package/dist/scheduler.js +16 -4
- package/dist/session-edit-state.js +2 -1
- package/dist/source-linked-guidance.js +3 -1
- package/dist/tui.js +1 -403
- package/dist/tui.jsx +1 -113
- package/dist/version-metadata.json +1 -1
- package/package.json +3 -5
- package/dist/brief-delivery-reasons.d.ts +0 -12
- package/dist/brief-delivery-reasons.js +0 -132
- package/dist/brief-intent.d.ts +0 -41
- package/dist/brief-intent.js +0 -142
- package/dist/briefing-runtime.d.ts +0 -24
- package/dist/briefing-runtime.js +0 -277
- package/dist/graph-narrator.d.ts +0 -25
- package/dist/graph-narrator.js +0 -408
- package/dist/idle-brief-audit.d.ts +0 -36
- package/dist/idle-brief-audit.js +0 -186
- package/dist/idle-brief-paths.d.ts +0 -6
- package/dist/idle-brief-paths.js +0 -120
- package/dist/idle-brief-reader.d.ts +0 -37
- package/dist/idle-brief-reader.js +0 -163
- package/dist/idle-brief-runtime.d.ts +0 -55
- package/dist/idle-brief-runtime.js +0 -496
- package/dist/idle-brief-store.d.ts +0 -113
- package/dist/idle-brief-store.js +0 -262
- package/dist/session-fingerprint.d.ts +0 -11
- package/dist/session-fingerprint.js +0 -21
- package/dist/tui-brief-delivery.d.ts +0 -67
- package/dist/tui-brief-delivery.js +0 -279
- package/dist/tui-brief-view-model.d.ts +0 -63
- package/dist/tui-brief-view-model.js +0 -209
- package/dist/utils/brief-marker.d.ts +0 -19
- package/dist/utils/brief-marker.js +0 -101
package/dist/idle-brief-store.js
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import * as crypto from "node:crypto";
|
|
2
|
-
function isRecord(value) {
|
|
3
|
-
return typeof value === "object" && value !== null;
|
|
4
|
-
}
|
|
5
|
-
function isStringArray(value) {
|
|
6
|
-
return (Array.isArray(value) && value.every((entry) => typeof entry === "string"));
|
|
7
|
-
}
|
|
8
|
-
function isCitation(value) {
|
|
9
|
-
return isRecord(value) && typeof value.id === "string";
|
|
10
|
-
}
|
|
11
|
-
function isStatement(value) {
|
|
12
|
-
return (isRecord(value) &&
|
|
13
|
-
typeof value.statement === "string" &&
|
|
14
|
-
isStringArray(value.citationIds));
|
|
15
|
-
}
|
|
16
|
-
function isValidationViolation(value) {
|
|
17
|
-
return (isRecord(value) &&
|
|
18
|
-
typeof value.rule === "string" &&
|
|
19
|
-
typeof value.entityId === "string" &&
|
|
20
|
-
typeof value.description === "string");
|
|
21
|
-
}
|
|
22
|
-
function isValidationDiagnostic(value) {
|
|
23
|
-
return (isRecord(value) &&
|
|
24
|
-
typeof value.category === "string" &&
|
|
25
|
-
typeof value.severity === "string" &&
|
|
26
|
-
typeof value.message === "string");
|
|
27
|
-
}
|
|
28
|
-
function isAuditCursor(value) {
|
|
29
|
-
return (isRecord(value) &&
|
|
30
|
-
typeof value.lastTimestamp === "string" &&
|
|
31
|
-
typeof value.lastOperation === "string" &&
|
|
32
|
-
typeof value.entryCount === "number" &&
|
|
33
|
-
typeof value.fileSize === "number");
|
|
34
|
-
}
|
|
35
|
-
function isValidation(value) {
|
|
36
|
-
return (isRecord(value) &&
|
|
37
|
-
Array.isArray(value.violations) &&
|
|
38
|
-
value.violations.every(isValidationViolation) &&
|
|
39
|
-
typeof value.count === "number" &&
|
|
40
|
-
Array.isArray(value.diagnostics) &&
|
|
41
|
-
value.diagnostics.every(isValidationDiagnostic));
|
|
42
|
-
}
|
|
43
|
-
function isBriefingBase(value) {
|
|
44
|
-
return (isRecord(value) &&
|
|
45
|
-
typeof value.tldr === "string" &&
|
|
46
|
-
typeof value.promptBlock === "string" &&
|
|
47
|
-
Array.isArray(value.citations) &&
|
|
48
|
-
value.citations.every(isCitation) &&
|
|
49
|
-
(value.constraints === undefined ||
|
|
50
|
-
(Array.isArray(value.constraints) &&
|
|
51
|
-
value.constraints.every(isStatement))) &&
|
|
52
|
-
(value.regressionRisks === undefined ||
|
|
53
|
-
(Array.isArray(value.regressionRisks) &&
|
|
54
|
-
value.regressionRisks.every(isStatement))) &&
|
|
55
|
-
(value.missingEvidence === undefined ||
|
|
56
|
-
(Array.isArray(value.missingEvidence) &&
|
|
57
|
-
value.missingEvidence.every(isStatement))));
|
|
58
|
-
}
|
|
59
|
-
function isBriefingV2(value) {
|
|
60
|
-
return (isBriefingBase(value) &&
|
|
61
|
-
isStringArray(value.changeNarrative) &&
|
|
62
|
-
(value.deliveryReasons === undefined ||
|
|
63
|
-
isDeliveryReasons(value.deliveryReasons)));
|
|
64
|
-
}
|
|
65
|
-
function isReasonItem(value) {
|
|
66
|
-
return (isRecord(value) &&
|
|
67
|
-
typeof value.kind === "string" &&
|
|
68
|
-
typeof value.text === "string" &&
|
|
69
|
-
isStringArray(value.entityIds) &&
|
|
70
|
-
(value.citationIds === undefined || isStringArray(value.citationIds)) &&
|
|
71
|
-
(value.severity === undefined ||
|
|
72
|
-
value.severity === "info" ||
|
|
73
|
-
value.severity === "warning" ||
|
|
74
|
-
value.severity === "error"));
|
|
75
|
-
}
|
|
76
|
-
function isDeliveryReasons(value) {
|
|
77
|
-
return (isRecord(value) &&
|
|
78
|
-
value.version === 1 &&
|
|
79
|
-
isRecord(value.toast) &&
|
|
80
|
-
typeof value.toast.title === "string" &&
|
|
81
|
-
typeof value.toast.summary === "string" &&
|
|
82
|
-
typeof value.toast.whyItMatters === "string" &&
|
|
83
|
-
Array.isArray(value.items) &&
|
|
84
|
-
value.items.every(isReasonItem));
|
|
85
|
-
}
|
|
86
|
-
function isChangeItem(value) {
|
|
87
|
-
return (isRecord(value) &&
|
|
88
|
-
typeof value.id === "string" &&
|
|
89
|
-
typeof value.type === "string");
|
|
90
|
-
}
|
|
91
|
-
function hasRenderableText(value) {
|
|
92
|
-
return value.trim().replace(/\s+/g, " ").length > 0;
|
|
93
|
-
}
|
|
94
|
-
export function isIdleBriefEnvelope(
|
|
95
|
-
// implements REQ-opencode-kibi-briefing-v4
|
|
96
|
-
value) {
|
|
97
|
-
if (!isRecord(value))
|
|
98
|
-
return false;
|
|
99
|
-
const hasBaseFields = (value.schemaVersion === "1.0" || value.schemaVersion === "2.0") &&
|
|
100
|
-
typeof value.briefId === "string" &&
|
|
101
|
-
(value.type === "success" || value.type === "warning") &&
|
|
102
|
-
typeof value.sessionId === "string" &&
|
|
103
|
-
typeof value.branch === "string" &&
|
|
104
|
-
typeof value.createdAt === "string" &&
|
|
105
|
-
typeof value.unread === "boolean" &&
|
|
106
|
-
isAuditCursor(value.auditCursor) &&
|
|
107
|
-
typeof value.summary === "string" &&
|
|
108
|
-
isValidation(value.validation) &&
|
|
109
|
-
typeof value.contentHash === "string";
|
|
110
|
-
if (!hasBaseFields)
|
|
111
|
-
return false;
|
|
112
|
-
if (value.schemaVersion === "1.0") {
|
|
113
|
-
return (isRecord(value.counts) &&
|
|
114
|
-
typeof value.counts.requirementsAdded === "number" &&
|
|
115
|
-
typeof value.counts.relationshipsAdded === "number" &&
|
|
116
|
-
typeof value.counts.entitiesDeleted === "number" &&
|
|
117
|
-
isBriefingBase(value.briefing));
|
|
118
|
-
}
|
|
119
|
-
return (isRecord(value.counts) &&
|
|
120
|
-
typeof value.counts.entitiesAdded === "number" &&
|
|
121
|
-
typeof value.counts.entitiesModified === "number" &&
|
|
122
|
-
typeof value.counts.entitiesRemoved === "number" &&
|
|
123
|
-
typeof value.counts.relationshipsChanged === "number" &&
|
|
124
|
-
isRecord(value.changes) &&
|
|
125
|
-
isRecord(value.changes.entities) &&
|
|
126
|
-
Array.isArray(value.changes.entities.added) &&
|
|
127
|
-
value.changes.entities.added.every(isChangeItem) &&
|
|
128
|
-
Array.isArray(value.changes.entities.modified) &&
|
|
129
|
-
value.changes.entities.modified.every(isChangeItem) &&
|
|
130
|
-
Array.isArray(value.changes.entities.removed) &&
|
|
131
|
-
value.changes.entities.removed.every(isChangeItem) &&
|
|
132
|
-
isRecord(value.changes.relationships) &&
|
|
133
|
-
typeof value.changes.relationships.changed === "number" &&
|
|
134
|
-
isBriefingV2(value.briefing));
|
|
135
|
-
}
|
|
136
|
-
export function createBriefId() {
|
|
137
|
-
// implements REQ-opencode-kibi-briefing-v4
|
|
138
|
-
return `brief-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
139
|
-
}
|
|
140
|
-
export function computeContentHash(payload) {
|
|
141
|
-
// implements REQ-opencode-kibi-briefing-v4
|
|
142
|
-
const env = payload;
|
|
143
|
-
// Normalize string: trim and collapse internal whitespace
|
|
144
|
-
const norm = (s) => s.trim().replace(/\s+/g, " ");
|
|
145
|
-
const normalizeCitations = (citations) => citations.map((c) => ({
|
|
146
|
-
id: c.id,
|
|
147
|
-
...(c.type ? { type: norm(c.type) } : {}),
|
|
148
|
-
...(c.title ? { title: norm(c.title) } : {}),
|
|
149
|
-
...(c.source ? { source: norm(c.source) } : {}),
|
|
150
|
-
...(c.textRef ? { textRef: norm(c.textRef) } : {}),
|
|
151
|
-
}));
|
|
152
|
-
const normalizeStatements = (statements = []) => statements.map((statement) => ({
|
|
153
|
-
statement: norm(statement.statement),
|
|
154
|
-
citationIds: statement.citationIds,
|
|
155
|
-
}));
|
|
156
|
-
const normalizeReasonItems = (items = []) => items
|
|
157
|
-
.map((item) => ({
|
|
158
|
-
kind: item.kind,
|
|
159
|
-
text: norm(item.text),
|
|
160
|
-
entityIds: [...item.entityIds].sort(),
|
|
161
|
-
...(item.citationIds ? { citationIds: [...item.citationIds].sort() } : {}),
|
|
162
|
-
...(item.severity ? { severity: item.severity } : {}),
|
|
163
|
-
}))
|
|
164
|
-
.filter((item) => hasRenderableText(item.text));
|
|
165
|
-
const normalizeDeliveryReasons = (deliveryReasons) => {
|
|
166
|
-
if (!deliveryReasons)
|
|
167
|
-
return undefined;
|
|
168
|
-
const items = normalizeReasonItems(deliveryReasons.items);
|
|
169
|
-
if (items.length === 0)
|
|
170
|
-
return undefined;
|
|
171
|
-
return {
|
|
172
|
-
version: deliveryReasons.version,
|
|
173
|
-
toast: {
|
|
174
|
-
title: norm(deliveryReasons.toast.title),
|
|
175
|
-
summary: norm(deliveryReasons.toast.summary),
|
|
176
|
-
whyItMatters: norm(deliveryReasons.toast.whyItMatters),
|
|
177
|
-
},
|
|
178
|
-
items,
|
|
179
|
-
};
|
|
180
|
-
};
|
|
181
|
-
const normalizeChangeItems = (items) => items.map((item) => ({
|
|
182
|
-
id: item.id,
|
|
183
|
-
type: norm(item.type),
|
|
184
|
-
...(item.title ? { title: norm(item.title) } : {}),
|
|
185
|
-
...(item.source ? { source: norm(item.source) } : {}),
|
|
186
|
-
...(item.textRef ? { textRef: norm(item.textRef) } : {}),
|
|
187
|
-
}));
|
|
188
|
-
// Build canonical visible-content projection (ignoring volatile fields)
|
|
189
|
-
const projection = env.schemaVersion === "2.0"
|
|
190
|
-
? {
|
|
191
|
-
schemaVersion: "2.0",
|
|
192
|
-
type: env.type,
|
|
193
|
-
summary: norm(env.summary),
|
|
194
|
-
counts: env.counts,
|
|
195
|
-
changes: {
|
|
196
|
-
entities: {
|
|
197
|
-
added: normalizeChangeItems(env.changes.entities.added),
|
|
198
|
-
modified: normalizeChangeItems(env.changes.entities.modified),
|
|
199
|
-
removed: normalizeChangeItems(env.changes.entities.removed),
|
|
200
|
-
},
|
|
201
|
-
relationships: {
|
|
202
|
-
changed: env.changes.relationships.changed,
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
briefing: {
|
|
206
|
-
tldr: norm(env.briefing.tldr),
|
|
207
|
-
normalizedPromptBlock: norm(env.briefing.promptBlock),
|
|
208
|
-
citations: normalizeCitations(env.briefing.citations ?? []),
|
|
209
|
-
changeNarrative: env.briefing.changeNarrative.map((line) => norm(line)),
|
|
210
|
-
deliveryReasons: normalizeDeliveryReasons(env.briefing.deliveryReasons),
|
|
211
|
-
constraints: normalizeStatements(env.briefing.constraints),
|
|
212
|
-
regressionRisks: normalizeStatements(env.briefing.regressionRisks),
|
|
213
|
-
missingEvidence: normalizeStatements(env.briefing.missingEvidence),
|
|
214
|
-
},
|
|
215
|
-
validation: {
|
|
216
|
-
count: env.validation.count,
|
|
217
|
-
violations: env.validation.violations.map((v) => ({
|
|
218
|
-
rule: v.rule,
|
|
219
|
-
entityId: v.entityId,
|
|
220
|
-
description: norm(v.description),
|
|
221
|
-
})),
|
|
222
|
-
},
|
|
223
|
-
}
|
|
224
|
-
: {
|
|
225
|
-
type: env.type,
|
|
226
|
-
summary: norm(env.summary),
|
|
227
|
-
counts: env.counts,
|
|
228
|
-
briefing: {
|
|
229
|
-
tldr: norm(env.briefing.tldr),
|
|
230
|
-
normalizedPromptBlock: norm(env.briefing.promptBlock),
|
|
231
|
-
citations: (env.briefing.citations ?? []).map((c) => ({
|
|
232
|
-
id: c.id,
|
|
233
|
-
title: c.title ?? "",
|
|
234
|
-
})),
|
|
235
|
-
constraints: (env.briefing.constraints ?? []).map((c) => ({
|
|
236
|
-
statement: norm(c.statement),
|
|
237
|
-
citationIds: c.citationIds,
|
|
238
|
-
})),
|
|
239
|
-
regressionRisks: (env.briefing.regressionRisks ?? []).map((r) => ({
|
|
240
|
-
statement: norm(r.statement),
|
|
241
|
-
citationIds: r.citationIds,
|
|
242
|
-
})),
|
|
243
|
-
missingEvidence: (env.briefing.missingEvidence ?? []).map((m) => ({
|
|
244
|
-
statement: norm(m.statement),
|
|
245
|
-
citationIds: m.citationIds,
|
|
246
|
-
})),
|
|
247
|
-
deliveryReasons: normalizeDeliveryReasons(env.briefing.deliveryReasons),
|
|
248
|
-
},
|
|
249
|
-
validation: {
|
|
250
|
-
count: env.validation.count,
|
|
251
|
-
violations: env.validation.violations.map((v) => ({
|
|
252
|
-
rule: v.rule,
|
|
253
|
-
entityId: v.entityId,
|
|
254
|
-
description: norm(v.description),
|
|
255
|
-
})),
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
return crypto
|
|
259
|
-
.createHash("sha256")
|
|
260
|
-
.update(JSON.stringify(projection))
|
|
261
|
-
.digest("hex");
|
|
262
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export interface SessionFingerprintInput {
|
|
2
|
-
sessionId?: string | undefined;
|
|
3
|
-
branch: string;
|
|
4
|
-
worktree: string;
|
|
5
|
-
}
|
|
6
|
-
export interface SessionBaselineState<Cursor> {
|
|
7
|
-
fingerprint: string | null;
|
|
8
|
-
cursor: Cursor | null;
|
|
9
|
-
}
|
|
10
|
-
export declare function buildSessionFingerprint(input: SessionFingerprintInput): string;
|
|
11
|
-
export declare function syncSessionBaselineState<Cursor>(state: SessionBaselineState<Cursor>, input: SessionFingerprintInput, captureBaseline: () => Cursor | null): SessionBaselineState<Cursor>;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export function buildSessionFingerprint(
|
|
2
|
-
// implements REQ-opencode-kibi-briefing-v6
|
|
3
|
-
input) {
|
|
4
|
-
return [
|
|
5
|
-
input.sessionId?.trim() || "unknown",
|
|
6
|
-
input.branch,
|
|
7
|
-
input.worktree,
|
|
8
|
-
].join("\0");
|
|
9
|
-
}
|
|
10
|
-
export function syncSessionBaselineState(
|
|
11
|
-
// implements REQ-opencode-kibi-briefing-v6
|
|
12
|
-
state, input, captureBaseline) {
|
|
13
|
-
const fingerprint = buildSessionFingerprint(input);
|
|
14
|
-
if (state.fingerprint === fingerprint) {
|
|
15
|
-
return state;
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
fingerprint,
|
|
19
|
-
cursor: captureBaseline(),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { ToastCapableClient as SendToastCapableClient } from "./toast.js";
|
|
2
|
-
import type { IdleBriefEnvelope } from "./idle-brief-store.js";
|
|
3
|
-
export type ToastPayload = {
|
|
4
|
-
variant?: "info" | "success" | "warning" | "error";
|
|
5
|
-
title?: string;
|
|
6
|
-
message: string;
|
|
7
|
-
duration?: number;
|
|
8
|
-
};
|
|
9
|
-
export type ToastCapableClient = {
|
|
10
|
-
tui?: {
|
|
11
|
-
showToast?: (payload: {
|
|
12
|
-
body: ToastPayload;
|
|
13
|
-
}) => void | Promise<void>;
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
export type SharedBriefPolicy = {
|
|
17
|
-
briefs: {
|
|
18
|
-
enabled: boolean;
|
|
19
|
-
channels: {
|
|
20
|
-
tui: boolean;
|
|
21
|
-
vscode: boolean;
|
|
22
|
-
};
|
|
23
|
-
tui: {
|
|
24
|
-
toast: boolean;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
export type LocalBriefConfig = {
|
|
29
|
-
autoSubmit: boolean;
|
|
30
|
-
};
|
|
31
|
-
export type DeliverResult = {
|
|
32
|
-
delivered: boolean;
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* Delivers a Kibi briefing to the TUI via toast notification.
|
|
36
|
-
*
|
|
37
|
-
* Uses the REAL OpenCode plugin API:
|
|
38
|
-
* - client.tui.showToast(payload) — primary (and only) delivery mechanism
|
|
39
|
-
*
|
|
40
|
-
* The toast contains a rich summary from the envelope and is displayed
|
|
41
|
-
* for 8 seconds so users can read the content.
|
|
42
|
-
*
|
|
43
|
-
* @param client - OpenCode client with optional TUI capabilities
|
|
44
|
-
* @param envelope - Idle brief envelope containing briefing content
|
|
45
|
-
* @param sharedPolicy - Shared brief policy from `.kb/config.json`
|
|
46
|
-
* @param localConfig - Local OpenCode config
|
|
47
|
-
*/
|
|
48
|
-
export declare function deliverBriefTui(client: ToastCapableClient, envelope: IdleBriefEnvelope, sharedPolicy: SharedBriefPolicy, _localConfig: LocalBriefConfig): Promise<DeliverResult>;
|
|
49
|
-
/**
|
|
50
|
-
* Client type for announcement-only TUI delivery.
|
|
51
|
-
* Extends toast capability with the SDK command bridge.
|
|
52
|
-
*/
|
|
53
|
-
export type AnnouncementClient = SendToastCapableClient;
|
|
54
|
-
export type AnnouncementResult = {
|
|
55
|
-
toastDelivered: boolean;
|
|
56
|
-
commandPublished: boolean;
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* Announcement-only TUI delivery coordinator.
|
|
60
|
-
*
|
|
61
|
-
* Sends the summary toast and invokes the official SDK bridge
|
|
62
|
-
* (`executeCommand`) but does NOT mutate read/seen state.
|
|
63
|
-
* The caller is responsible for any state transitions after the
|
|
64
|
-
* TUI route confirms render success.
|
|
65
|
-
*/
|
|
66
|
-
export declare function announceBriefTui(// implements REQ-opencode-kibi-briefing-v6
|
|
67
|
-
client: AnnouncementClient, envelope: IdleBriefEnvelope, sharedPolicy: SharedBriefPolicy): Promise<AnnouncementResult>;
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
-
* Copyright (C) 2026 Piotr Franczyk
|
|
4
|
-
*
|
|
5
|
-
* This program is free software: you can redistribute it and/or modify
|
|
6
|
-
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
-
* (at your option) any later version.
|
|
9
|
-
*/
|
|
10
|
-
import { sendToast } from "./toast.js";
|
|
11
|
-
import { renderToastSummary } from "./brief-delivery-reasons.js";
|
|
12
|
-
import * as logger from "./logger.js";
|
|
13
|
-
function firstNonEmpty(...values) {
|
|
14
|
-
for (const value of values) {
|
|
15
|
-
const trimmed = value?.trim();
|
|
16
|
-
if (trimmed) {
|
|
17
|
-
return trimmed;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
function buildTuiBriefMessage(envelope) {
|
|
23
|
-
const lines = [];
|
|
24
|
-
const briefing = envelope.briefing;
|
|
25
|
-
const deliveryReasons = briefing.deliveryReasons;
|
|
26
|
-
const renderedToast = deliveryReasons?.items?.length
|
|
27
|
-
? renderToastSummary(deliveryReasons)
|
|
28
|
-
: undefined;
|
|
29
|
-
const whatChanged = renderedToast
|
|
30
|
-
? [renderedToast.summary]
|
|
31
|
-
: envelope.schemaVersion === "2.0"
|
|
32
|
-
? envelope.briefing.changeNarrative.map((line) => line.trim()).filter(Boolean).filter((line) => !line.includes(".sisyphus/"))
|
|
33
|
-
: [];
|
|
34
|
-
lines.push("## What changed");
|
|
35
|
-
if (whatChanged.length > 0) {
|
|
36
|
-
lines.push(...whatChanged.slice(0, 2));
|
|
37
|
-
}
|
|
38
|
-
else if (envelope.schemaVersion === "2.0") {
|
|
39
|
-
const fallbackEntity = envelope.changes.entities.modified[0] ?? envelope.changes.entities.added[0];
|
|
40
|
-
if (fallbackEntity) {
|
|
41
|
-
const action = envelope.changes.entities.modified[0] ? "Modified" : "Added";
|
|
42
|
-
lines.push(`${action} ${fallbackEntity.id}: ${fallbackEntity.title ?? "Untitled"}`);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
const fallback = firstNonEmpty(envelope.summary, envelope.briefing.tldr);
|
|
46
|
-
if (fallback)
|
|
47
|
-
lines.push(fallback);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
const fallback = firstNonEmpty(envelope.summary, envelope.briefing.tldr);
|
|
52
|
-
if (fallback)
|
|
53
|
-
lines.push(fallback);
|
|
54
|
-
}
|
|
55
|
-
lines.push("");
|
|
56
|
-
const whyItMatters = firstNonEmpty(deliveryReasons?.items?.length ? renderedToast?.whyItMatters : undefined);
|
|
57
|
-
if (whyItMatters) {
|
|
58
|
-
lines.push("## Why it matters");
|
|
59
|
-
lines.push(whyItMatters);
|
|
60
|
-
lines.push("");
|
|
61
|
-
}
|
|
62
|
-
const hasKnowledgeImpact = envelope.briefing.citations.length > 0 ||
|
|
63
|
-
(envelope.briefing.constraints?.length ?? 0) > 0 ||
|
|
64
|
-
(envelope.briefing.regressionRisks?.length ?? 0) > 0;
|
|
65
|
-
if (hasKnowledgeImpact) {
|
|
66
|
-
lines.push("## Project knowledge impact");
|
|
67
|
-
if (envelope.briefing.citations.length > 0) {
|
|
68
|
-
for (const citation of envelope.briefing.citations) {
|
|
69
|
-
lines.push(`- **${citation.id}**${citation.title ? `: ${citation.title}` : ""}${citation.source ? ` (${citation.source})` : ""}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if ((envelope.briefing.constraints?.length ?? 0) > 0) {
|
|
73
|
-
for (const constraint of envelope.briefing.constraints ?? []) {
|
|
74
|
-
lines.push(`- ${constraint.statement}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if ((envelope.briefing.regressionRisks?.length ?? 0) > 0) {
|
|
78
|
-
for (const risk of envelope.briefing.regressionRisks ?? []) {
|
|
79
|
-
lines.push(`- ${risk.statement}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
lines.push("");
|
|
83
|
-
}
|
|
84
|
-
const hasMissingEvidence = (envelope.briefing.missingEvidence?.length ?? 0) > 0;
|
|
85
|
-
if (envelope.validation.count > 0 || hasMissingEvidence) {
|
|
86
|
-
lines.push("## Interpretation note");
|
|
87
|
-
if (envelope.validation.count > 0) {
|
|
88
|
-
lines.push(`Validation checks reported unresolved items: ${envelope.validation.count} issue(s).`);
|
|
89
|
-
}
|
|
90
|
-
if (hasMissingEvidence) {
|
|
91
|
-
lines.push("This brief includes unresolved evidence notes:");
|
|
92
|
-
for (const item of envelope.briefing.missingEvidence ?? []) {
|
|
93
|
-
lines.push(`- ${item.statement}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
lines.push("");
|
|
97
|
-
}
|
|
98
|
-
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
99
|
-
lines.pop();
|
|
100
|
-
}
|
|
101
|
-
const result = lines.join("\n");
|
|
102
|
-
if (result === "## What changed") {
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
return result;
|
|
106
|
-
}
|
|
107
|
-
function buildTuiBriefToastPayload(envelope) {
|
|
108
|
-
const message = buildTuiBriefMessage(envelope);
|
|
109
|
-
if (message === undefined) {
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
return {
|
|
113
|
-
variant: envelope.type === "warning" ? "warning" : "info",
|
|
114
|
-
title: "Kibi Knowledge Update",
|
|
115
|
-
message,
|
|
116
|
-
duration: 8000,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
function hasSignificantBriefingImpact(envelope) {
|
|
120
|
-
const briefing = envelope.briefing;
|
|
121
|
-
return !(briefing.citations.length === 0 &&
|
|
122
|
-
(!briefing.constraints || briefing.constraints.length === 0) &&
|
|
123
|
-
(!briefing.regressionRisks || briefing.regressionRisks.length === 0) &&
|
|
124
|
-
(!briefing.missingEvidence || briefing.missingEvidence.length === 0));
|
|
125
|
-
}
|
|
126
|
-
function isNoOpBriefEnvelope(envelope) {
|
|
127
|
-
const counts = envelope.counts;
|
|
128
|
-
const zeroCounts = "relationshipsChanged" in counts
|
|
129
|
-
? counts.entitiesAdded === 0 &&
|
|
130
|
-
counts.entitiesModified === 0 &&
|
|
131
|
-
counts.entitiesRemoved === 0 &&
|
|
132
|
-
counts.relationshipsChanged === 0
|
|
133
|
-
: counts.requirementsAdded === 0 &&
|
|
134
|
-
counts.relationshipsAdded === 0 &&
|
|
135
|
-
counts.entitiesDeleted === 0;
|
|
136
|
-
const briefing = envelope.briefing;
|
|
137
|
-
const hasDeliveryReasons = (briefing.deliveryReasons?.items.length ?? 0) > 0;
|
|
138
|
-
if (hasDeliveryReasons) {
|
|
139
|
-
const toast = briefing.deliveryReasons ? renderToastSummary(briefing.deliveryReasons) : undefined;
|
|
140
|
-
if (toast === undefined)
|
|
141
|
-
return true; // all operational → no-op
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
// Suppress legacy (no deliveryReasons) envelopes only when all three conditions hold:
|
|
145
|
-
// zero change counts, no validation issues, and no significant briefing impact.
|
|
146
|
-
// Matching summary/tldr alone is not sufficient — a domain-specific brief may legitimately
|
|
147
|
-
// have the same value in both fields.
|
|
148
|
-
return (zeroCounts &&
|
|
149
|
-
envelope.validation.count === 0 &&
|
|
150
|
-
!hasSignificantBriefingImpact(envelope));
|
|
151
|
-
}
|
|
152
|
-
function getEnvelopeChangeTotal(envelope) {
|
|
153
|
-
const counts = envelope.counts;
|
|
154
|
-
return "relationshipsChanged" in counts
|
|
155
|
-
? counts.entitiesAdded +
|
|
156
|
-
counts.entitiesModified +
|
|
157
|
-
counts.entitiesRemoved +
|
|
158
|
-
counts.relationshipsChanged
|
|
159
|
-
: counts.requirementsAdded + counts.relationshipsAdded + counts.entitiesDeleted;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Delivers a Kibi briefing to the TUI via toast notification.
|
|
163
|
-
*
|
|
164
|
-
* Uses the REAL OpenCode plugin API:
|
|
165
|
-
* - client.tui.showToast(payload) — primary (and only) delivery mechanism
|
|
166
|
-
*
|
|
167
|
-
* The toast contains a rich summary from the envelope and is displayed
|
|
168
|
-
* for 8 seconds so users can read the content.
|
|
169
|
-
*
|
|
170
|
-
* @param client - OpenCode client with optional TUI capabilities
|
|
171
|
-
* @param envelope - Idle brief envelope containing briefing content
|
|
172
|
-
* @param sharedPolicy - Shared brief policy from `.kb/config.json`
|
|
173
|
-
* @param localConfig - Local OpenCode config
|
|
174
|
-
*/
|
|
175
|
-
// implements REQ-opencode-kibi-briefing-v4
|
|
176
|
-
export async function deliverBriefTui(client, envelope, sharedPolicy, _localConfig) {
|
|
177
|
-
// Early exit if TUI delivery is disabled
|
|
178
|
-
if (!sharedPolicy.briefs.channels.tui) {
|
|
179
|
-
logger.info("TUI brief delivery disabled by shared policy");
|
|
180
|
-
return { delivered: false };
|
|
181
|
-
}
|
|
182
|
-
const tui = client.tui;
|
|
183
|
-
// Toast is the primary delivery mechanism
|
|
184
|
-
if (sharedPolicy.briefs.tui.toast && typeof tui?.showToast === "function") {
|
|
185
|
-
if (isNoOpBriefEnvelope(envelope)) {
|
|
186
|
-
return { delivered: false };
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
const message = buildTuiBriefMessage(envelope);
|
|
190
|
-
if (message === undefined) {
|
|
191
|
-
return { delivered: false };
|
|
192
|
-
}
|
|
193
|
-
await tui.showToast({
|
|
194
|
-
body: {
|
|
195
|
-
variant: envelope.type === "warning" ? "warning" : "info",
|
|
196
|
-
title: "Kibi Knowledge Update",
|
|
197
|
-
message,
|
|
198
|
-
duration: 8000,
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
return { delivered: true };
|
|
202
|
-
}
|
|
203
|
-
catch (err) {
|
|
204
|
-
logger.error("Failed to deliver brief toast", {
|
|
205
|
-
event: "idle_brief_toast_failed",
|
|
206
|
-
error: err instanceof Error ? err.message : String(err),
|
|
207
|
-
});
|
|
208
|
-
return { delivered: false };
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
logger.info("TUI showToast API unavailable, brief not delivered");
|
|
213
|
-
return { delivered: false };
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Announcement-only TUI delivery coordinator.
|
|
218
|
-
*
|
|
219
|
-
* Sends the summary toast and invokes the official SDK bridge
|
|
220
|
-
* (`executeCommand`) but does NOT mutate read/seen state.
|
|
221
|
-
* The caller is responsible for any state transitions after the
|
|
222
|
-
* TUI route confirms render success.
|
|
223
|
-
*/
|
|
224
|
-
export async function announceBriefTui(// implements REQ-opencode-kibi-briefing-v6
|
|
225
|
-
client, envelope, sharedPolicy) {
|
|
226
|
-
if (!sharedPolicy.briefs.channels.tui) {
|
|
227
|
-
logger.info("TUI brief delivery disabled by shared policy");
|
|
228
|
-
return { toastDelivered: false, commandPublished: false };
|
|
229
|
-
}
|
|
230
|
-
const briefing = envelope.briefing;
|
|
231
|
-
if (!envelope.unread &&
|
|
232
|
-
isNoOpBriefEnvelope(envelope) &&
|
|
233
|
-
!(briefing.deliveryReasons?.items.length ?? 0)) {
|
|
234
|
-
return { toastDelivered: false, commandPublished: false };
|
|
235
|
-
}
|
|
236
|
-
const totalChanges = getEnvelopeChangeTotal(envelope);
|
|
237
|
-
const hasDeliveryReasons = (briefing.deliveryReasons?.items.length ?? 0) > 0;
|
|
238
|
-
if (!envelope.unread &&
|
|
239
|
-
totalChanges === 0 &&
|
|
240
|
-
envelope.validation.count === 0 &&
|
|
241
|
-
!hasDeliveryReasons &&
|
|
242
|
-
isNoOpBriefEnvelope(envelope)) {
|
|
243
|
-
return { toastDelivered: false, commandPublished: false };
|
|
244
|
-
}
|
|
245
|
-
let toastDelivered = false;
|
|
246
|
-
let commandPublished = false;
|
|
247
|
-
if (sharedPolicy.briefs.tui.toast) {
|
|
248
|
-
const payload = buildTuiBriefToastPayload(envelope);
|
|
249
|
-
if (payload !== undefined) {
|
|
250
|
-
const toastResult = await sendToast(client, payload);
|
|
251
|
-
if (toastResult.status === "delivered") {
|
|
252
|
-
toastDelivered = true;
|
|
253
|
-
}
|
|
254
|
-
else if (toastResult.status === "failed") {
|
|
255
|
-
logger.error("Failed to deliver brief toast", {
|
|
256
|
-
event: "idle_brief_toast_failed",
|
|
257
|
-
error: toastResult.error ?? toastResult.reason,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
logger.info("TUI showToast API unavailable, brief not delivered");
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// Step 2: Invoke the SDK command bridge to open the brief in the TUI
|
|
266
|
-
if (typeof client.tui?.executeCommand === "function") {
|
|
267
|
-
try {
|
|
268
|
-
await client.tui.executeCommand("kibi.open_latest_brief", {});
|
|
269
|
-
commandPublished = true;
|
|
270
|
-
}
|
|
271
|
-
catch (err) {
|
|
272
|
-
logger.error("Failed to publish open_latest_brief command", {
|
|
273
|
-
event: "idle_brief_command_failed",
|
|
274
|
-
error: err instanceof Error ? err.message : String(err),
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return { toastDelivered, commandPublished };
|
|
279
|
-
}
|