kibi-opencode 0.14.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.
Files changed (62) hide show
  1. package/README.md +18 -12
  2. package/dist/config.d.ts +0 -3
  3. package/dist/config.js +0 -9
  4. package/dist/e2e-coverage-signals.js +1 -3
  5. package/dist/enforcement-policy.d.ts +2 -0
  6. package/dist/enforcement-policy.js +18 -3
  7. package/dist/enforcement-scope.js +1 -3
  8. package/dist/file-entity-links.js +2 -6
  9. package/dist/file-operation-reminders.d.ts +4 -4
  10. package/dist/file-operation-state.js +4 -2
  11. package/dist/init-kibi-alias.js +0 -1
  12. package/dist/init-kibi-capability.js +1 -5
  13. package/dist/kb-freshness-state.d.ts +62 -0
  14. package/dist/kb-freshness-state.js +215 -0
  15. package/dist/kibi-checkpoint-runner.d.ts +1 -1
  16. package/dist/kibi-checkpoint-runner.js +6 -3
  17. package/dist/knowledge-classifier.js +2 -1
  18. package/dist/meaningful-change-classifier.d.ts +30 -0
  19. package/dist/meaningful-change-classifier.js +103 -0
  20. package/dist/plugin-startup.js +0 -1
  21. package/dist/plugin.js +101 -391
  22. package/dist/prompt.d.ts +6 -5
  23. package/dist/prompt.js +48 -84
  24. package/dist/reconcile-engine.d.ts +15 -1
  25. package/dist/reconcile-engine.js +1 -3
  26. package/dist/scheduler.js +16 -4
  27. package/dist/session-edit-state.js +2 -1
  28. package/dist/source-linked-guidance.js +3 -1
  29. package/dist/startup-notifier.d.ts +7 -1
  30. package/dist/startup-notifier.js +51 -17
  31. package/dist/tui.js +1 -403
  32. package/dist/tui.jsx +1 -113
  33. package/dist/version-metadata.d.ts +13 -0
  34. package/dist/version-metadata.js +78 -0
  35. package/dist/version-metadata.json +1 -0
  36. package/package.json +5 -7
  37. package/dist/brief-delivery-reasons.d.ts +0 -12
  38. package/dist/brief-delivery-reasons.js +0 -132
  39. package/dist/brief-intent.d.ts +0 -41
  40. package/dist/brief-intent.js +0 -142
  41. package/dist/briefing-runtime.d.ts +0 -24
  42. package/dist/briefing-runtime.js +0 -277
  43. package/dist/graph-narrator.d.ts +0 -25
  44. package/dist/graph-narrator.js +0 -408
  45. package/dist/idle-brief-audit.d.ts +0 -36
  46. package/dist/idle-brief-audit.js +0 -186
  47. package/dist/idle-brief-paths.d.ts +0 -6
  48. package/dist/idle-brief-paths.js +0 -120
  49. package/dist/idle-brief-reader.d.ts +0 -37
  50. package/dist/idle-brief-reader.js +0 -163
  51. package/dist/idle-brief-runtime.d.ts +0 -55
  52. package/dist/idle-brief-runtime.js +0 -496
  53. package/dist/idle-brief-store.d.ts +0 -113
  54. package/dist/idle-brief-store.js +0 -262
  55. package/dist/session-fingerprint.d.ts +0 -11
  56. package/dist/session-fingerprint.js +0 -21
  57. package/dist/tui-brief-delivery.d.ts +0 -67
  58. package/dist/tui-brief-delivery.js +0 -279
  59. package/dist/tui-brief-view-model.d.ts +0 -63
  60. package/dist/tui-brief-view-model.js +0 -209
  61. package/dist/utils/brief-marker.d.ts +0 -19
  62. package/dist/utils/brief-marker.js +0 -101
package/dist/tui.js CHANGED
@@ -1,408 +1,6 @@
1
1
  // @bun
2
- // src/idle-brief-reader.ts
3
- import * as fs from "fs";
4
- import * as path2 from "path";
5
-
6
- // src/idle-brief-paths.ts
7
- import * as path from "path";
8
- import { loadBriefConfig } from "kibi-cli/brief-config";
9
- function resolveBriefsDir(workspaceRoot) {
10
- return path.join(workspaceRoot, ".kb", "briefs");
11
- }
12
-
13
- // src/idle-brief-store.ts
14
- function isRecord(value) {
15
- return typeof value === "object" && value !== null;
16
- }
17
- function isStringArray(value) {
18
- return Array.isArray(value) && value.every((entry) => typeof entry === "string");
19
- }
20
- function isCitation(value) {
21
- return isRecord(value) && typeof value.id === "string";
22
- }
23
- function isStatement(value) {
24
- return isRecord(value) && typeof value.statement === "string" && isStringArray(value.citationIds);
25
- }
26
- function isValidationViolation(value) {
27
- return isRecord(value) && typeof value.rule === "string" && typeof value.entityId === "string" && typeof value.description === "string";
28
- }
29
- function isValidationDiagnostic(value) {
30
- return isRecord(value) && typeof value.category === "string" && typeof value.severity === "string" && typeof value.message === "string";
31
- }
32
- function isAuditCursor(value) {
33
- return isRecord(value) && typeof value.lastTimestamp === "string" && typeof value.lastOperation === "string" && typeof value.entryCount === "number" && typeof value.fileSize === "number";
34
- }
35
- function isValidation(value) {
36
- return isRecord(value) && Array.isArray(value.violations) && value.violations.every(isValidationViolation) && typeof value.count === "number" && Array.isArray(value.diagnostics) && value.diagnostics.every(isValidationDiagnostic);
37
- }
38
- function isBriefingBase(value) {
39
- return isRecord(value) && typeof value.tldr === "string" && typeof value.promptBlock === "string" && Array.isArray(value.citations) && value.citations.every(isCitation) && (value.constraints === undefined || Array.isArray(value.constraints) && value.constraints.every(isStatement)) && (value.regressionRisks === undefined || Array.isArray(value.regressionRisks) && value.regressionRisks.every(isStatement)) && (value.missingEvidence === undefined || Array.isArray(value.missingEvidence) && value.missingEvidence.every(isStatement));
40
- }
41
- function isBriefingV2(value) {
42
- return isBriefingBase(value) && isStringArray(value.changeNarrative) && (value.deliveryReasons === undefined || isDeliveryReasons(value.deliveryReasons));
43
- }
44
- function isReasonItem(value) {
45
- return isRecord(value) && typeof value.kind === "string" && typeof value.text === "string" && isStringArray(value.entityIds) && (value.citationIds === undefined || isStringArray(value.citationIds)) && (value.severity === undefined || value.severity === "info" || value.severity === "warning" || value.severity === "error");
46
- }
47
- function isDeliveryReasons(value) {
48
- return isRecord(value) && value.version === 1 && isRecord(value.toast) && typeof value.toast.title === "string" && typeof value.toast.summary === "string" && typeof value.toast.whyItMatters === "string" && Array.isArray(value.items) && value.items.every(isReasonItem);
49
- }
50
- function isChangeItem(value) {
51
- return isRecord(value) && typeof value.id === "string" && typeof value.type === "string";
52
- }
53
- function isIdleBriefEnvelope(value) {
54
- if (!isRecord(value))
55
- return false;
56
- const hasBaseFields = (value.schemaVersion === "1.0" || value.schemaVersion === "2.0") && typeof value.briefId === "string" && (value.type === "success" || value.type === "warning") && typeof value.sessionId === "string" && typeof value.branch === "string" && typeof value.createdAt === "string" && typeof value.unread === "boolean" && isAuditCursor(value.auditCursor) && typeof value.summary === "string" && isValidation(value.validation) && typeof value.contentHash === "string";
57
- if (!hasBaseFields)
58
- return false;
59
- if (value.schemaVersion === "1.0") {
60
- return isRecord(value.counts) && typeof value.counts.requirementsAdded === "number" && typeof value.counts.relationshipsAdded === "number" && typeof value.counts.entitiesDeleted === "number" && isBriefingBase(value.briefing);
61
- }
62
- return isRecord(value.counts) && typeof value.counts.entitiesAdded === "number" && typeof value.counts.entitiesModified === "number" && typeof value.counts.entitiesRemoved === "number" && typeof value.counts.relationshipsChanged === "number" && isRecord(value.changes) && isRecord(value.changes.entities) && Array.isArray(value.changes.entities.added) && value.changes.entities.added.every(isChangeItem) && Array.isArray(value.changes.entities.modified) && value.changes.entities.modified.every(isChangeItem) && Array.isArray(value.changes.entities.removed) && value.changes.entities.removed.every(isChangeItem) && isRecord(value.changes.relationships) && typeof value.changes.relationships.changed === "number" && isBriefingV2(value.briefing);
63
- }
64
-
65
- // src/idle-brief-reader.ts
66
- var BRIEF_FILENAME_RE = /^(\d+)_brief\.json$/;
67
- var TUI_SEEN_FILE = ".tui-seen.json";
68
- function resolveTuiSeenPath(workspaceRoot) {
69
- return path2.join(resolveBriefsDir(workspaceRoot), TUI_SEEN_FILE);
70
- }
71
- function markBriefTuiSeen(workspaceRoot, branch, contentHash) {
72
- const briefsDir = resolveBriefsDir(workspaceRoot);
73
- fs.mkdirSync(briefsDir, { recursive: true });
74
- const seenPath = resolveTuiSeenPath(workspaceRoot);
75
- let parsed = {};
76
- try {
77
- const raw = JSON.parse(fs.readFileSync(seenPath, "utf-8"));
78
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
79
- parsed = raw;
80
- }
81
- } catch {}
82
- const existing = Array.isArray(parsed[branch]) ? parsed[branch] : [];
83
- const next = [contentHash, ...existing.filter((entry) => entry !== contentHash)].slice(0, 100);
84
- parsed[branch] = next;
85
- const tempPath = `${seenPath}.tmp`;
86
- fs.writeFileSync(tempPath, JSON.stringify(parsed, null, 2), "utf-8");
87
- fs.renameSync(tempPath, seenPath);
88
- }
89
- function extractTimestamp(filename) {
90
- const match = filename.match(BRIEF_FILENAME_RE);
91
- if (!match)
92
- return null;
93
- return Number(match[1]);
94
- }
95
- function scanBriefs(workspaceRoot, branch, filterUnread) {
96
- const briefsDir = resolveBriefsDir(workspaceRoot);
97
- if (!fs.existsSync(briefsDir)) {
98
- return null;
99
- }
100
- const files = fs.readdirSync(briefsDir);
101
- const candidates = [];
102
- for (const file of files) {
103
- if (file.endsWith(".tmp"))
104
- continue;
105
- const timestamp = extractTimestamp(file);
106
- if (timestamp === null)
107
- continue;
108
- const filePath = path2.join(briefsDir, file);
109
- let envelope;
110
- try {
111
- const raw = fs.readFileSync(filePath, "utf-8");
112
- const parsed = JSON.parse(raw);
113
- if (!isIdleBriefEnvelope(parsed)) {
114
- continue;
115
- }
116
- envelope = parsed;
117
- } catch {
118
- continue;
119
- }
120
- if (envelope.branch === branch && (envelope.schemaVersion === "1.0" || envelope.schemaVersion === "2.0") && (!filterUnread || envelope.unread === true)) {
121
- candidates.push({ timestamp, envelope, filePath });
122
- }
123
- }
124
- if (candidates.length === 0) {
125
- return null;
126
- }
127
- candidates.sort((a, b) => b.timestamp - a.timestamp);
128
- const latest = candidates[0];
129
- if (!latest) {
130
- return null;
131
- }
132
- return {
133
- envelope: latest.envelope,
134
- filePath: latest.filePath
135
- };
136
- }
137
- function selectLatestPersistedBrief(workspaceRoot, branch) {
138
- return scanBriefs(workspaceRoot, branch, false);
139
- }
140
- function markBriefRead(workspaceRoot, briefPath) {
141
- const briefsDir = resolveBriefsDir(workspaceRoot);
142
- const resolvedBriefPath = path2.resolve(briefPath);
143
- const resolvedBriefsDir = path2.resolve(briefsDir);
144
- if (!resolvedBriefPath.startsWith(resolvedBriefsDir + path2.sep)) {
145
- throw new Error(`Invalid brief path: ${briefPath} is not inside ${briefsDir}`);
146
- }
147
- const raw = fs.readFileSync(briefPath, "utf-8");
148
- const brief = JSON.parse(raw);
149
- brief.unread = false;
150
- const tempPath = `${briefPath}.tmp`;
151
- fs.writeFileSync(tempPath, JSON.stringify(brief, null, 2), "utf-8");
152
- fs.renameSync(tempPath, briefPath);
153
- }
154
-
155
2
  // src/tui.tsx
156
- import { loadBriefConfig as loadBriefConfig2 } from "kibi-cli/brief-config";
157
-
158
- // src/brief-delivery-reasons.ts
159
- function toastSummary(items) {
160
- const first = items[0]?.text?.trim() ?? "";
161
- const second = items[1]?.text?.trim() ?? "";
162
- if (first && second)
163
- return `${first}, ${second}`;
164
- return first || second || undefined;
165
- }
166
- function toastWhy(items) {
167
- if (items.some((i) => i.kind === "conflict_detected"))
168
- return "There is a knowledge conflict to resolve before using the brief.";
169
- if (items.some((i) => i.kind === "validation_issue"))
170
- return "Validation issues need attention before the update is treated as settled.";
171
- const hasEntities = items.some((i) => i.kind === "entity_added" || i.kind === "entity_modified" || i.kind === "entity_removed");
172
- const hasRelationships = items.some((i) => i.kind === "relationship_changed");
173
- if (hasEntities && hasRelationships)
174
- return "Requirements and facts were updated.";
175
- if (hasEntities)
176
- return "Entities were updated.";
177
- if (hasRelationships)
178
- return "Relationships were updated.";
179
- return;
180
- }
181
- function isOperationalByEntityIds(item) {
182
- if (item.entityIds.length === 0)
183
- return false;
184
- return item.entityIds.every((id) => {
185
- const dashIdx = id.indexOf("-");
186
- if (dashIdx < 0)
187
- return false;
188
- const name = id.slice(dashIdx + 1);
189
- return /\.[a-zA-Z0-9]+$/.test(name);
190
- });
191
- }
192
- function isOperationalItem(item, allItems) {
193
- if (item.kind === "relationship_changed") {
194
- const entityItems = allItems.filter((i) => i.kind === "entity_added" || i.kind === "entity_modified" || i.kind === "entity_removed");
195
- return entityItems.length > 0 && entityItems.every(isOperationalByEntityIds);
196
- }
197
- return isOperationalByEntityIds(item);
198
- }
199
- function renderToastSummary(reasons) {
200
- const domainItems = reasons.items.filter((i) => !isOperationalItem(i, reasons.items));
201
- if (domainItems.length === 0) {
202
- return;
203
- }
204
- return {
205
- title: "Kibi Knowledge Update",
206
- summary: toastSummary(domainItems) ?? "",
207
- whyItMatters: toastWhy(domainItems) ?? ""
208
- };
209
- }
210
-
211
- // src/tui-brief-view-model.ts
212
- function firstNonEmpty(...values) {
213
- for (const value of values) {
214
- const trimmed = value?.trim();
215
- if (trimmed) {
216
- return trimmed;
217
- }
218
- }
219
- return;
220
- }
221
- function isOperationalByEntityIds2(item) {
222
- if (item.entityIds.length === 0)
223
- return false;
224
- return item.entityIds.every((id) => {
225
- const dashIdx = id.indexOf("-");
226
- if (dashIdx < 0)
227
- return false;
228
- return /\.[a-zA-Z0-9]+$/.test(id.slice(dashIdx + 1));
229
- });
230
- }
231
- function isOperationalDeliveryItem(item, allItems) {
232
- if (item.kind === "relationship_changed") {
233
- const entityItems = allItems.filter((i) => i.kind === "entity_added" || i.kind === "entity_modified" || i.kind === "entity_removed");
234
- return entityItems.length > 0 && entityItems.every(isOperationalByEntityIds2);
235
- }
236
- return isOperationalByEntityIds2(item);
237
- }
238
- function deriveWhatChanged(envelope) {
239
- const briefing = envelope.briefing;
240
- const deliveryReasons = briefing.deliveryReasons;
241
- if (deliveryReasons?.items?.length) {
242
- const domainItems = deliveryReasons.items.filter((item) => !isOperationalDeliveryItem(item, deliveryReasons.items));
243
- if (domainItems.length > 0) {
244
- return domainItems.map((item) => item.text);
245
- }
246
- }
247
- if (envelope.schemaVersion === "2.0") {
248
- const narrative = envelope.briefing.changeNarrative.map((line) => line.trim()).filter(Boolean).filter((line) => !line.includes(".sisyphus/"));
249
- if (narrative.length > 0) {
250
- return narrative.slice(0, 2);
251
- }
252
- const fallbackEntity = envelope.changes.entities.modified[0] ?? envelope.changes.entities.added[0];
253
- if (fallbackEntity) {
254
- const action = envelope.changes.entities.modified[0] ? "Modified" : "Added";
255
- return [
256
- `${action} ${fallbackEntity.id}: ${fallbackEntity.title ?? "Untitled"}`
257
- ];
258
- }
259
- }
260
- const fallback = firstNonEmpty(envelope.summary, envelope.briefing.tldr);
261
- return fallback ? [fallback] : [];
262
- }
263
- function buildTuiBriefViewModel(envelope) {
264
- const briefing = envelope.briefing;
265
- const deliveryReasons = briefing.deliveryReasons;
266
- let title = firstNonEmpty(envelope.summary, envelope.briefing.tldr);
267
- if (deliveryReasons?.items?.length) {
268
- const filteredToast = renderToastSummary(deliveryReasons);
269
- if (filteredToast) {
270
- title = filteredToast.summary;
271
- }
272
- } else if (envelope.schemaVersion === "2.0" && envelope.briefing.changeNarrative.length > 0) {
273
- title = envelope.briefing.changeNarrative[0]?.trim() ?? title;
274
- }
275
- const base = {
276
- briefId: envelope.briefId,
277
- schemaVersion: envelope.schemaVersion,
278
- branch: envelope.branch,
279
- createdAt: envelope.createdAt,
280
- type: envelope.type,
281
- unread: envelope.unread,
282
- contentHash: envelope.contentHash,
283
- title: title ?? "Kibi Brief",
284
- whatChanged: deriveWhatChanged(envelope),
285
- whyItMatters: deliveryReasons?.items?.length ? renderToastSummary(deliveryReasons)?.whyItMatters || undefined : undefined,
286
- knowledgeImpact: {
287
- citations: envelope.briefing.citations,
288
- constraints: envelope.briefing.constraints ?? [],
289
- regressionRisks: envelope.briefing.regressionRisks ?? []
290
- },
291
- interpretationNote: {
292
- validationCount: envelope.validation.count,
293
- missingEvidence: envelope.briefing.missingEvidence ?? []
294
- }
295
- };
296
- if (envelope.schemaVersion === "2.0") {
297
- return {
298
- ...base,
299
- counts: {
300
- schemaVersion: "2.0",
301
- entitiesAdded: envelope.counts.entitiesAdded,
302
- entitiesModified: envelope.counts.entitiesModified,
303
- entitiesRemoved: envelope.counts.entitiesRemoved,
304
- relationshipsChanged: envelope.counts.relationshipsChanged
305
- }
306
- };
307
- }
308
- return {
309
- ...base,
310
- counts: {
311
- schemaVersion: "1.0",
312
- requirementsAdded: envelope.counts.requirementsAdded,
313
- relationshipsAdded: envelope.counts.relationshipsAdded,
314
- entitiesDeleted: envelope.counts.entitiesDeleted
315
- }
316
- };
317
- }
318
-
319
- // src/tui.tsx
320
- var tui = async (api, _options, _meta) => {
321
- let currentContentHash = null;
322
- api.route.register([
323
- {
324
- name: "kibi.brief",
325
- render: () => {
326
- const workspace = api.state.path.worktree || "";
327
- const branch = api.state.vcs?.branch || "main";
328
- const brief = selectLatestPersistedBrief(workspace, branch);
329
- if (!brief) {
330
- currentContentHash = null;
331
- return /* @__PURE__ */ h("box", {
332
- flexDirection: "column",
333
- gap: 1,
334
- padding: 1
335
- }, /* @__PURE__ */ h("text", {
336
- fg: api.theme.current.error
337
- }, "No meaningful KB update"), /* @__PURE__ */ h("text", null, "There is no latest persisted brief for this branch."));
338
- }
339
- const { envelope } = brief;
340
- const isNewContent = envelope.contentHash !== currentContentHash;
341
- currentContentHash = envelope.contentHash;
342
- if (isNewContent && envelope.unread) {
343
- markBriefTuiSeen(workspace, branch, envelope.contentHash);
344
- try {
345
- const config = loadBriefConfig2(workspace);
346
- if (!config.channels.vscode) {
347
- markBriefRead(workspace, brief.filePath);
348
- }
349
- } catch {}
350
- }
351
- const viewModel = buildTuiBriefViewModel(envelope);
352
- return /* @__PURE__ */ h("scrollbox", {
353
- flexDirection: "column",
354
- gap: 1,
355
- padding: 1
356
- }, /* @__PURE__ */ h("text", {
357
- fg: api.theme.current.accent
358
- }, /* @__PURE__ */ h("strong", null, viewModel.title)), /* @__PURE__ */ h("box", {
359
- flexDirection: "column"
360
- }, /* @__PURE__ */ h("text", null, /* @__PURE__ */ h("strong", null, "What changed:")), /* @__PURE__ */ h("text", null, viewModel.whatChanged.map((line) => `- ${line}`).join(`
361
- `))), /* @__PURE__ */ h("box", {
362
- flexDirection: "column"
363
- }, /* @__PURE__ */ h("text", null, /* @__PURE__ */ h("strong", null, "Why it matters:")), /* @__PURE__ */ h("text", null, viewModel.whyItMatters)), (viewModel.knowledgeImpact.citations.length > 0 || viewModel.knowledgeImpact.constraints.length > 0 || viewModel.knowledgeImpact.regressionRisks.length > 0) && /* @__PURE__ */ h("box", {
364
- flexDirection: "column"
365
- }, /* @__PURE__ */ h("text", null, /* @__PURE__ */ h("strong", null, "Project knowledge impact:")), viewModel.knowledgeImpact.citations.length > 0 && /* @__PURE__ */ h("text", null, viewModel.knowledgeImpact.citations.map((citation) => `- ${citation.id}${citation.title ? `: ${citation.title}` : ""}`).join(`
366
- `)), viewModel.knowledgeImpact.constraints.length > 0 && /* @__PURE__ */ h("text", null, viewModel.knowledgeImpact.constraints.map((constraint) => `- ${constraint.statement}`).join(`
367
- `)), viewModel.knowledgeImpact.regressionRisks.length > 0 && /* @__PURE__ */ h("text", null, viewModel.knowledgeImpact.regressionRisks.map((risk) => `- ${risk.statement}`).join(`
368
- `))), (viewModel.interpretationNote.validationCount > 0 || viewModel.interpretationNote.missingEvidence.length > 0) && /* @__PURE__ */ h("box", {
369
- flexDirection: "column"
370
- }, /* @__PURE__ */ h("text", {
371
- fg: api.theme.current.warning
372
- }, /* @__PURE__ */ h("strong", null, "Interpretation note:")), viewModel.interpretationNote.validationCount > 0 && /* @__PURE__ */ h("text", null, "Validation checks reported unresolved items: ", viewModel.interpretationNote.validationCount, " issue(s)."), viewModel.interpretationNote.missingEvidence.length > 0 && /* @__PURE__ */ h("text", null, viewModel.interpretationNote.missingEvidence.map((item) => `- ${item.statement}`).join(`
373
- `))));
374
- }
375
- }
376
- ]);
377
- if (api.command?.register) {
378
- api.command.register(() => [
379
- {
380
- title: "Kibi: Open Latest Brief",
381
- value: "kibi.open_latest_brief",
382
- description: "Opens the latest persisted brief for the current workspace and branch",
383
- onSelect: () => {
384
- api.route.navigate("kibi.brief");
385
- }
386
- },
387
- {
388
- title: "Kibi: Open Latest Brief",
389
- value: "kibi-brief",
390
- description: "Opens the latest persisted brief for the current workspace and branch",
391
- onSelect: () => {
392
- api.route.navigate("kibi.brief");
393
- }
394
- },
395
- {
396
- title: "Kibi: Refresh Brief",
397
- value: "kibi.refresh_brief",
398
- description: "Re-reads the latest persisted brief and refreshes the view",
399
- onSelect: () => {
400
- api.route.navigate("kibi.brief");
401
- }
402
- }
403
- ]);
404
- }
405
- };
3
+ var tui = async (api, _options, _meta) => {};
406
4
  var tui_default = {
407
5
  id: "kibi-opencode",
408
6
  tui
package/dist/tui.jsx CHANGED
@@ -1,117 +1,5 @@
1
- import { selectLatestPersistedBrief, markBriefTuiSeen, markBriefRead } from "./idle-brief-reader.js";
2
- import { loadBriefConfig } from "kibi-cli/brief-config";
3
- import { buildTuiBriefViewModel } from "./tui-brief-view-model.js";
4
1
  const tui = async (api, _options, _meta) => {
5
- // State: track the currently displayed contentHash for in-place refresh detection
6
- let currentContentHash = null;
7
- api.route.register([
8
- {
9
- name: "kibi.brief",
10
- render: () => {
11
- const workspace = api.state.path.worktree || "";
12
- const branch = api.state.vcs?.branch || "main";
13
- const brief = selectLatestPersistedBrief(workspace, branch);
14
- if (!brief) {
15
- currentContentHash = null;
16
- return (<box flexDirection="column" gap={1} padding={1}>
17
- <text fg={api.theme.current.error}>No meaningful KB update</text>
18
- <text>There is no latest persisted brief for this branch.</text>
19
- </box>);
20
- }
21
- const { envelope } = brief;
22
- const isNewContent = envelope.contentHash !== currentContentHash;
23
- currentContentHash = envelope.contentHash;
24
- // Mark as TUI-seen when this is a new (previously unseen) brief and it's unread
25
- if (isNewContent && envelope.unread) {
26
- markBriefTuiSeen(workspace, branch, envelope.contentHash);
27
- // When VSCode channel is disabled, TUI is the sole delivery channel —
28
- // viewing the brief here should also mark it as fully read
29
- try {
30
- const config = loadBriefConfig(workspace);
31
- if (!config.channels.vscode) {
32
- markBriefRead(workspace, brief.filePath);
33
- }
34
- }
35
- catch {
36
- // Gracefully handle config load or markBriefRead failures
37
- }
38
- }
39
- const viewModel = buildTuiBriefViewModel(envelope);
40
- return (<scrollbox flexDirection="column" gap={1} padding={1}>
41
- <text fg={api.theme.current.accent}><strong>{viewModel.title}</strong></text>
42
-
43
- <box flexDirection="column">
44
- <text><strong>What changed:</strong></text>
45
- <text>{viewModel.whatChanged.map((line) => `- ${line}`).join("\n")}</text>
46
- </box>
47
-
48
- <box flexDirection="column">
49
- <text><strong>Why it matters:</strong></text>
50
- <text>{viewModel.whyItMatters}</text>
51
- </box>
52
-
53
- {(viewModel.knowledgeImpact.citations.length > 0 || viewModel.knowledgeImpact.constraints.length > 0 || viewModel.knowledgeImpact.regressionRisks.length > 0) && (<box flexDirection="column">
54
- <text><strong>Project knowledge impact:</strong></text>
55
- {viewModel.knowledgeImpact.citations.length > 0 && (<text>
56
- {viewModel.knowledgeImpact.citations
57
- .map((citation) => `- ${citation.id}${citation.title ? `: ${citation.title}` : ""}`)
58
- .join("\n")}
59
- </text>)}
60
- {viewModel.knowledgeImpact.constraints.length > 0 && (<text>
61
- {viewModel.knowledgeImpact.constraints
62
- .map((constraint) => `- ${constraint.statement}`)
63
- .join("\n")}
64
- </text>)}
65
- {viewModel.knowledgeImpact.regressionRisks.length > 0 && (<text>
66
- {viewModel.knowledgeImpact.regressionRisks
67
- .map((risk) => `- ${risk.statement}`)
68
- .join("\n")}
69
- </text>)}
70
- </box>)}
71
-
72
- {(viewModel.interpretationNote.validationCount > 0 || viewModel.interpretationNote.missingEvidence.length > 0) && (<box flexDirection="column">
73
- <text fg={api.theme.current.warning}><strong>Interpretation note:</strong></text>
74
- {viewModel.interpretationNote.validationCount > 0 && (<text>Validation checks reported unresolved items: {viewModel.interpretationNote.validationCount} issue(s).</text>)}
75
- {viewModel.interpretationNote.missingEvidence.length > 0 && (<text>
76
- {viewModel.interpretationNote.missingEvidence
77
- .map((item) => `- ${item.statement}`)
78
- .join("\n")}
79
- </text>)}
80
- </box>)}
81
- </scrollbox>);
82
- },
83
- },
84
- ]);
85
- if (api.command?.register) {
86
- api.command.register(() => [
87
- {
88
- title: "Kibi: Open Latest Brief",
89
- value: "kibi.open_latest_brief",
90
- description: "Opens the latest persisted brief for the current workspace and branch",
91
- // implements REQ-opencode-kibi-briefing-v6
92
- onSelect: () => {
93
- api.route.navigate("kibi.brief");
94
- },
95
- },
96
- {
97
- title: "Kibi: Open Latest Brief",
98
- value: "kibi-brief",
99
- description: "Opens the latest persisted brief for the current workspace and branch",
100
- // implements REQ-opencode-kibi-briefing-v6
101
- onSelect: () => {
102
- api.route.navigate("kibi.brief");
103
- },
104
- },
105
- {
106
- title: "Kibi: Refresh Brief",
107
- value: "kibi.refresh_brief",
108
- description: "Re-reads the latest persisted brief and refreshes the view",
109
- onSelect: () => {
110
- api.route.navigate("kibi.brief");
111
- },
112
- },
113
- ]);
114
- }
2
+ void api;
115
3
  };
116
4
  export { tui };
117
5
  export default {
@@ -0,0 +1,13 @@
1
+ export interface KibiPackageVersions {
2
+ opencode: string;
3
+ mcp: string;
4
+ cli: string;
5
+ core: string;
6
+ source: "generated-dist" | "workspace-packages" | "unknown";
7
+ missing: string[];
8
+ }
9
+ export interface ReadKibiPackageVersionsOptions {
10
+ baseUrl?: string | URL;
11
+ readFileSync?: (path: string | URL, encoding: "utf-8") => string;
12
+ }
13
+ export declare function readKibiPackageVersions(options?: ReadKibiPackageVersionsOptions): KibiPackageVersions;
@@ -0,0 +1,78 @@
1
+ import { readFileSync as fsReadFileSync } from "node:fs";
2
+ // implements REQ-opencode-toast-package-versions-v1
3
+ export function readKibiPackageVersions(options) {
4
+ const baseUrl = options?.baseUrl ?? import.meta.url;
5
+ const readFs = options?.readFileSync ?? fsReadFileSync;
6
+ // Tier 1: generated dist JSON (build-time artifact)
7
+ try {
8
+ const distUrl = new URL("./version-metadata.json", baseUrl);
9
+ const raw = readFs(distUrl, "utf-8");
10
+ const parsed = JSON.parse(raw);
11
+ if (typeof parsed.opencode === "string" &&
12
+ typeof parsed.mcp === "string" &&
13
+ typeof parsed.cli === "string" &&
14
+ typeof parsed.core === "string") {
15
+ return {
16
+ opencode: parsed.opencode,
17
+ mcp: parsed.mcp,
18
+ cli: parsed.cli,
19
+ core: parsed.core,
20
+ source: "generated-dist",
21
+ missing: [],
22
+ };
23
+ }
24
+ }
25
+ catch {
26
+ // fall through
27
+ }
28
+ // Tier 2: workspace package.json files
29
+ try {
30
+ const entries = [
31
+ ["opencode", "../package.json"],
32
+ ["mcp", "../../mcp/package.json"],
33
+ ["cli", "../../cli/package.json"],
34
+ ["core", "../../core/package.json"],
35
+ ];
36
+ const versions = {};
37
+ const missing = [];
38
+ for (const [name, relPath] of entries) {
39
+ try {
40
+ const pkgUrl = new URL(relPath, baseUrl);
41
+ const raw = readFs(pkgUrl, "utf-8");
42
+ const pkg = JSON.parse(raw);
43
+ versions[name] =
44
+ typeof pkg.version === "string" ? pkg.version : "unknown";
45
+ if (typeof pkg.version !== "string") {
46
+ missing.push(name);
47
+ }
48
+ }
49
+ catch {
50
+ versions[name] = "unknown";
51
+ missing.push(name);
52
+ }
53
+ }
54
+ // Only claim workspace-packages if at least one resolved
55
+ if (Object.values(versions).some((v) => v !== "unknown")) {
56
+ return {
57
+ opencode: versions.opencode ?? "unknown",
58
+ mcp: versions.mcp ?? "unknown",
59
+ cli: versions.cli ?? "unknown",
60
+ core: versions.core ?? "unknown",
61
+ source: "workspace-packages",
62
+ missing: missing.length > 0 ? missing : [],
63
+ };
64
+ }
65
+ }
66
+ catch {
67
+ // fall through
68
+ }
69
+ // Tier 3: all unknown — never throws
70
+ return {
71
+ opencode: "unknown",
72
+ mcp: "unknown",
73
+ cli: "unknown",
74
+ core: "unknown",
75
+ source: "unknown",
76
+ missing: ["opencode", "mcp", "cli", "core"],
77
+ };
78
+ }
@@ -0,0 +1 @@
1
+ {"opencode":"0.15.1","mcp":"0.14.3","cli":"0.11.3","core":"0.5.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-opencode",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "Kibi OpenCode plugin - thin adapter to integrate Kibi with OpenCode sessions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,9 +37,7 @@
37
37
  "default": "./dist/tui.js"
38
38
  }
39
39
  },
40
- "files": [
41
- "dist"
42
- ],
40
+ "files": ["dist"],
43
41
  "engines": {
44
42
  "node": ">=18"
45
43
  },
@@ -52,7 +50,7 @@
52
50
  "access": "public"
53
51
  },
54
52
  "scripts": {
55
- "build": "tsc -p tsconfig.json && bun run scripts/build-tui.ts && tsc -p tsconfig.tui-types.json",
53
+ "build": "tsc -p tsconfig.json && bun run scripts/generate-version-metadata.ts && bun run scripts/build-tui.ts && tsc -p tsconfig.tui-types.json",
56
54
  "build:tui": "bun run scripts/build-tui.ts && tsc -p tsconfig.tui-types.json",
57
55
  "dev": "tsc -p tsconfig.json --watch",
58
56
  "clean": "rm -rf dist",
@@ -62,10 +60,10 @@
62
60
  "@opencode-ai/plugin": "^1.4.7",
63
61
  "@opentui/core": "^0.1.99",
64
62
  "@opentui/solid": "^0.1.99",
65
- "kibi-cli": "^0.11.0"
63
+ "kibi-cli": "^0.11.3"
66
64
  },
67
65
  "devDependencies": {
68
66
  "@types/node": "latest",
69
67
  "typescript": "^5.0.0"
70
68
  }
71
- }
69
+ }
@@ -1,12 +0,0 @@
1
- import type { DeliveryReasons } from "./idle-brief-store.js";
2
- export type BuildInput = {
3
- entitiesAdded: string[];
4
- entitiesModified: string[];
5
- entitiesRemoved: string[];
6
- relationshipsChanged: number;
7
- validationCount: number;
8
- conflictReasons?: string[];
9
- };
10
- export declare function buildDeliveryReasons(input: BuildInput): DeliveryReasons | undefined;
11
- export declare function renderToastSummary(reasons: DeliveryReasons): DeliveryReasons["toast"] | undefined;
12
- export declare function renderFullBriefReasons(reasons: DeliveryReasons): string;