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.
Files changed (57) hide show
  1. package/README.md +4 -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 +1 -1
  6. package/dist/enforcement-policy.js +9 -4
  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.js +6 -2
  14. package/dist/kibi-checkpoint-runner.d.ts +1 -1
  15. package/dist/kibi-checkpoint-runner.js +6 -3
  16. package/dist/knowledge-classifier.js +2 -1
  17. package/dist/meaningful-change-classifier.d.ts +1 -1
  18. package/dist/meaningful-change-classifier.js +1 -2
  19. package/dist/plugin-startup.js +0 -1
  20. package/dist/plugin.js +45 -395
  21. package/dist/prompt.d.ts +2 -6
  22. package/dist/prompt.js +21 -84
  23. package/dist/reconcile-engine.d.ts +15 -1
  24. package/dist/reconcile-engine.js +1 -3
  25. package/dist/scheduler.js +16 -4
  26. package/dist/session-edit-state.js +2 -1
  27. package/dist/source-linked-guidance.js +3 -1
  28. package/dist/tui.js +1 -403
  29. package/dist/tui.jsx +1 -113
  30. package/dist/version-metadata.json +1 -1
  31. package/package.json +3 -5
  32. package/dist/brief-delivery-reasons.d.ts +0 -12
  33. package/dist/brief-delivery-reasons.js +0 -132
  34. package/dist/brief-intent.d.ts +0 -41
  35. package/dist/brief-intent.js +0 -142
  36. package/dist/briefing-runtime.d.ts +0 -24
  37. package/dist/briefing-runtime.js +0 -277
  38. package/dist/graph-narrator.d.ts +0 -25
  39. package/dist/graph-narrator.js +0 -408
  40. package/dist/idle-brief-audit.d.ts +0 -36
  41. package/dist/idle-brief-audit.js +0 -186
  42. package/dist/idle-brief-paths.d.ts +0 -6
  43. package/dist/idle-brief-paths.js +0 -120
  44. package/dist/idle-brief-reader.d.ts +0 -37
  45. package/dist/idle-brief-reader.js +0 -163
  46. package/dist/idle-brief-runtime.d.ts +0 -55
  47. package/dist/idle-brief-runtime.js +0 -496
  48. package/dist/idle-brief-store.d.ts +0 -113
  49. package/dist/idle-brief-store.js +0 -262
  50. package/dist/session-fingerprint.d.ts +0 -11
  51. package/dist/session-fingerprint.js +0 -21
  52. package/dist/tui-brief-delivery.d.ts +0 -67
  53. package/dist/tui-brief-delivery.js +0 -279
  54. package/dist/tui-brief-view-model.d.ts +0 -63
  55. package/dist/tui-brief-view-model.js +0 -209
  56. package/dist/utils/brief-marker.d.ts +0 -19
  57. package/dist/utils/brief-marker.js +0 -101
package/dist/prompt.js CHANGED
@@ -5,13 +5,7 @@ import { getSourceLinkedRequirementIds } from "./source-linked-guidance.js";
5
5
  const SENTINEL = "<!-- kibi-opencode -->";
6
6
  // ── Token budget enforcement ───────────────────────────────────────────
7
7
  const MAX_BULLETS = 5;
8
- const MAX_AUTO_BRIEF_BULLETS_WITH_REMINDER = 4;
9
8
  const MAX_WORDS = 117; // Reserve 3 words for sentinel so total injected prompt stays ≤ 120
10
- const AUTO_BRIEF_HEADER = "🧠 **Kibi briefing available**";
11
- const AUTHORITATIVE_POSTURES = [
12
- "root_active",
13
- "hybrid_root_plus_vendored",
14
- ];
15
9
  function countWords(text) {
16
10
  return text.split(/\s+/).filter(Boolean).length;
17
11
  }
@@ -55,50 +49,12 @@ function insertBulletAfterHeader(block, bullet) {
55
49
  return `${block}\n${bullet}`;
56
50
  return `${block.slice(0, headerEnd + 1)}${bullet}\n${block.slice(headerEnd + 1)}`;
57
51
  }
58
- // implements REQ-opencode-kibi-briefing-v2
59
- export function buildAutoBriefingGuidance(autoBriefResult, completionReminder) {
60
- if (!autoBriefResult)
61
- return null;
62
- // Defensive: idle-brief results are persisted to .kb/briefs/, never injected into prompts.
63
- // This function only handles auto-briefs from the file.edited risk-classification flow.
64
- if (typeof autoBriefResult === "object" &&
65
- autoBriefResult !== null &&
66
- ("briefId" in autoBriefResult || "schemaVersion" in autoBriefResult)) {
67
- return null;
68
- }
69
- if (autoBriefResult.state === "ready") {
70
- const promptBlock = autoBriefResult.promptBlock.trim();
71
- if (!promptBlock)
72
- return null;
73
- const maxBullets = completionReminder
74
- ? MAX_AUTO_BRIEF_BULLETS_WITH_REMINDER
75
- : MAX_BULLETS;
76
- const briefingLines = promptBlock
77
- .split("\n")
78
- .map((line) => line.trim())
79
- .filter((line) => line.startsWith("-"))
80
- .slice(0, maxBullets);
81
- if (briefingLines.length === 0)
82
- return null;
83
- return `${AUTO_BRIEF_HEADER}\n${briefingLines.join("\n")}`;
84
- }
85
- if (autoBriefResult.state === "tldr_fallback") {
86
- const promptBlock = autoBriefResult.promptBlock.trim();
87
- if (!promptBlock)
88
- return null;
89
- return `${AUTO_BRIEF_HEADER}\n${promptBlock}`;
90
- }
91
- return null;
92
- }
93
- function isAuthoritativePosture(posture) {
94
- return AUTHORITATIVE_POSTURES.includes(posture);
95
- }
96
52
  // ── File bucket derivation ─────────────────────────────────────────────
97
53
  function deriveFileBucket(pathKind) {
98
54
  return pathKind;
99
55
  }
100
56
  function getFocusEdit(context) {
101
- return context.focusEdit ?? context.recentEdits[context.recentEdits.length - 1];
57
+ return (context.focusEdit ?? context.recentEdits[context.recentEdits.length - 1]);
102
58
  }
103
59
  function buildInitKibiBootstrapReference(capability = getInitKibiCommandCapability()) {
104
60
  if (capability.supported) {
@@ -198,9 +154,6 @@ function buildContextualGuidance(context, capability = getInitKibiCommandCapabil
198
154
  }
199
155
  const posture = context.posture ?? "root_active";
200
156
  const riskClass = context.riskClass;
201
- const readyAutoBriefingAvailable = context.autoBriefResult?.showManualCue === false;
202
- const suppressSourceLinkedBrief = context.autoBriefResult?.state === "ready" ||
203
- context.autoBriefResult?.state === "tldr_fallback";
204
157
  const showDegraded = context.showDegradedAdvisory === true &&
205
158
  context.maintenanceDegraded === true &&
206
159
  context.degradedMode === "warn-once";
@@ -264,25 +217,16 @@ ${buildBootstrapRequiredBody(capability)}`;
264
217
  riskClass &&
265
218
  riskClass !== "safe_docs_only" &&
266
219
  riskClass !== "safe_test_only") {
267
- const autoBriefBlock = riskClass === "behavior_candidate" ||
268
- riskClass === "traceability_candidate"
269
- ? buildAutoBriefingGuidance(context.autoBriefResult, context.completionReminder === true)
270
- : null;
271
- if (autoBriefBlock) {
272
- selectedBlock = autoBriefBlock;
220
+ // For behavior/traceability with comment suggestions, use suggestion guidance
221
+ if ((riskClass === "behavior_candidate" ||
222
+ riskClass === "traceability_candidate") &&
223
+ context.recentCommentSuggestion) {
224
+ selectedBlock = buildCommentSuggestionGuidance(context.recentCommentSuggestion);
273
225
  }
274
226
  else {
275
- // For behavior/traceability with comment suggestions, use suggestion guidance
276
- if ((riskClass === "behavior_candidate" ||
277
- riskClass === "traceability_candidate") &&
278
- context.recentCommentSuggestion) {
279
- selectedBlock = buildCommentSuggestionGuidance(context.recentCommentSuggestion);
280
- }
281
- else {
282
- const block = GUIDANCE_BY_RISK[riskClass];
283
- if (block)
284
- selectedBlock = block;
285
- }
227
+ const block = GUIDANCE_BY_RISK[riskClass];
228
+ if (block)
229
+ selectedBlock = block;
286
230
  }
287
231
  }
288
232
  // Priority 6: Legacy path-kind fallback (when no risk class)
@@ -327,23 +271,14 @@ If you're adding long explanatory comments, consider routing that knowledge to:
327
271
  }
328
272
  }
329
273
  } // closing brace for Priority 2-4 else block starting at 187
330
- if (selectedBlock &&
331
- (riskClass === "behavior_candidate" ||
332
- riskClass === "traceability_candidate") &&
333
- isAuthoritativePosture(posture) &&
334
- !context.maintenanceDegraded &&
335
- !readyAutoBriefingAvailable) {
336
- selectedBlock = insertBulletAfterHeader(selectedBlock, "- Authoritative risky edit: run `/brief-kibi` before acting.");
337
- }
338
- // Source-linked micro-brief: insert after header line for code risk classes
274
+ // Source-linked context: insert after header line for code risk classes
339
275
  // Inserting after the header (not prepending before it) preserves the header
340
276
  // under enforceBudget's trimming logic, which only collects non-bullet lines
341
277
  // before the first bullet.
342
278
  if (selectedBlock &&
343
279
  (riskClass === "behavior_candidate" ||
344
280
  riskClass === "traceability_candidate") &&
345
- context.workspaceRoot &&
346
- !suppressSourceLinkedBrief) {
281
+ context.workspaceRoot) {
347
282
  try {
348
283
  const focusEdit = getFocusEdit(context);
349
284
  if (focusEdit?.path) {
@@ -358,7 +293,7 @@ If you're adding long explanatory comments, consider routing that knowledge to:
358
293
  }
359
294
  }
360
295
  catch {
361
- // Non-fatal: source-linked brief is best-effort
296
+ // Non-fatal: source-linked context is best-effort
362
297
  }
363
298
  }
364
299
  // ── File-operation reminder folding ─────────────────────────────────
@@ -369,7 +304,7 @@ If you're adding long explanatory comments, consider routing that knowledge to:
369
304
  if (hasFileOpReminders && fileOpReminder) {
370
305
  const foBullets = [];
371
306
  if (fileOpReminder.lifecycleReminder) {
372
- // Skip lifecycleReminder if source-linked brief already shows the same IDs
307
+ // Skip lifecycleReminder if source-linked context already shows the same IDs
373
308
  const hasSourceLinked = selectedBlock?.includes("- Existing Kibi links:") === true;
374
309
  const lifecycleHasEntities = fileOpReminder.lifecycleReminder.includes("Kibi entities:");
375
310
  const overlapsSourceLinked = hasSourceLinked &&
@@ -459,7 +394,10 @@ The Kibi workspace is in a maintenance-degraded state. Guidance remains advisory
459
394
  // Prepend KB freshness block when evidence is required
460
395
  if (context.kbFreshness?.requiresEvidence) {
461
396
  const changedListing = context.freshnessChangedPaths?.length
462
- ? `Changed: ${context.freshnessChangedPaths.slice(0, 5).map(p => `\`${p}\``).join(", ")}`
397
+ ? `Changed: ${context.freshnessChangedPaths
398
+ .slice(0, 5)
399
+ .map((p) => `\`${p}\``)
400
+ .join(", ")}`
463
401
  : null;
464
402
  const freshnessLines = [
465
403
  "🧠 **Kibi freshness required**",
@@ -471,15 +409,15 @@ The Kibi workspace is in a maintenance-degraded state. Guidance remains advisory
471
409
  "Resolution:",
472
410
  "- **KB updated**: run `kb_search` for discovery, then `kb_upsert`/`kb_delete` for mutations, then `kb_check`.",
473
411
  "- **No KB impact**: include a no-impact rationale in your final report after source-linked discovery via `kb_search`/`kb_query(sourceFile=...)` and `kb_check`.",
474
- ].filter(Boolean).join("\n");
412
+ ]
413
+ .filter(Boolean)
414
+ .join("\n");
475
415
  finalBlock = finalBlock
476
416
  ? `${freshnessLines}\n\n${finalBlock}`
477
417
  : freshnessLines;
478
418
  }
479
419
  // Return: sentinel + one targeted block (or just sentinel if no block)
480
- return finalBlock
481
- ? `${SENTINEL}\n\n${finalBlock}`
482
- : SENTINEL;
420
+ return finalBlock ? `${SENTINEL}\n\n${finalBlock}` : SENTINEL;
483
421
  }
484
422
  // ── Comment suggestion guidance (legacy compat) ────────────────────────
485
423
  function buildCommentSuggestionGuidance(suggestion) {
@@ -572,7 +510,6 @@ export function buildPrompt(context, capability = getInitKibiCommandCapability()
572
510
  /**
573
511
  * Inject prompt guidance if not already present.
574
512
  */
575
- // implements REQ-opencode-kibi-briefing-v2
576
513
  export function injectPrompt(current, config, context, capability = getInitKibiCommandCapability()) {
577
514
  if (!config.prompt.enabled || !isPluginEnabled(config)) {
578
515
  return current;
@@ -1,4 +1,18 @@
1
- import type { AuditEntry } from "./idle-brief-audit.js";
1
+ export interface AuditEntityPayload {
2
+ kind: "entity";
3
+ entityType: string;
4
+ changeKind?: "created" | "updated";
5
+ title?: string;
6
+ source?: string;
7
+ textRef?: string;
8
+ properties: Record<string, unknown>;
9
+ }
10
+ export interface AuditEntry {
11
+ timestamp: string;
12
+ operation: "upsert" | "delete" | "upsert_rel";
13
+ entityId: string;
14
+ payload?: AuditEntityPayload | Record<string, unknown>;
15
+ }
2
16
  export interface EntityChangeItem {
3
17
  id: string;
4
18
  type: string;
@@ -46,9 +46,7 @@ function toChangeItem(payload, entityId) {
46
46
  function compareChangeItems(left, right) {
47
47
  return left.type.localeCompare(right.type) || left.id.localeCompare(right.id);
48
48
  }
49
- export function reconcileAuditEntries(
50
- // implements REQ-opencode-kibi-briefing-v6
51
- entries) {
49
+ export function reconcileAuditEntries(entries) {
52
50
  const states = new Map();
53
51
  let relationshipsChanged = 0;
54
52
  for (const entry of [...entries].sort((left, right) => left.timestamp.localeCompare(right.timestamp))) {
package/dist/scheduler.js CHANGED
@@ -34,7 +34,9 @@ class WorktreeSyncScheduler {
34
34
  if (!this.config.sync.enabled)
35
35
  return;
36
36
  // Treat file.created, file.edited, and file.deleted same relevance-wise
37
- const isFileLifecycle = reason === "file.edited" || reason === "file.created" || reason === "file.deleted";
37
+ const isFileLifecycle = reason === "file.edited" ||
38
+ reason === "file.created" ||
39
+ reason === "file.deleted";
38
40
  if (isFileLifecycle) {
39
41
  if (!filePath)
40
42
  return;
@@ -181,7 +183,11 @@ class WorktreeSyncScheduler {
181
183
  }
182
184
  }
183
185
  isIdle() {
184
- return !this.inFlight && !this.timer && !this.pending && !this.dirty && !this.trailing;
186
+ return (!this.inFlight &&
187
+ !this.timer &&
188
+ !this.pending &&
189
+ !this.dirty &&
190
+ !this.trailing);
185
191
  }
186
192
  resolveFlushWaitersIfIdle() {
187
193
  if (!this.isIdle())
@@ -245,7 +251,9 @@ async function runKibiSync(worktree) {
245
251
  const truncatedOut = truncateSyncOutput(stdout || undefined);
246
252
  const truncatedErr = truncateSyncOutput(stderr || undefined);
247
253
  const signal = error.signal ? ` (signal: ${error.signal})` : "";
248
- const errorMessage = error.message ? `${error.message}${signal}` : signal || undefined;
254
+ const errorMessage = error.message
255
+ ? `${error.message}${signal}`
256
+ : signal || undefined;
249
257
  resolve({
250
258
  exitCode: error.code ?? 1,
251
259
  syncCommand: "kibi sync",
@@ -261,7 +269,11 @@ async function runKibiSync(worktree) {
261
269
  }
262
270
  catch (err) {
263
271
  const message = err instanceof Error ? err.message : String(err);
264
- resolve({ exitCode: 1, syncCommand: "kibi sync", syncErrorMessage: message });
272
+ resolve({
273
+ exitCode: 1,
274
+ syncCommand: "kibi sync",
275
+ syncErrorMessage: message,
276
+ });
265
277
  }
266
278
  });
267
279
  }
@@ -125,7 +125,8 @@ export function createSessionEditState(opts) {
125
125
  if (edits.length === 0)
126
126
  return null;
127
127
  // edits are sorted ascending by lastReconciledAt, so last = most recent
128
- return edits[edits.length - 1];
128
+ const focusEdit = edits.at(-1);
129
+ return focusEdit ?? null;
129
130
  }
130
131
  function hasSessionEdits() {
131
132
  for (const [, entry] of tracked) {
@@ -12,5 +12,7 @@ import { getFileLinkedTargetsByType } from "./file-entity-links.js";
12
12
  export function getSourceLinkedRequirementIds(worktree, editedAbsolutePath) {
13
13
  // Delegate to the shared file-entity-links resolver with implements-only filter.
14
14
  // implements relationships always target REQ- IDs, so no additional filtering needed.
15
- return getFileLinkedTargetsByType(worktree, editedAbsolutePath, ["implements"]).slice(0, 3);
15
+ return getFileLinkedTargetsByType(worktree, editedAbsolutePath, [
16
+ "implements",
17
+ ]).slice(0, 3);
16
18
  }