kibi-opencode 0.15.0 → 0.15.2

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/plugin.js CHANGED
@@ -1,7 +1,4 @@
1
1
  import * as path from "node:path";
2
- import { loadBriefConfig } from "kibi-cli/brief-config";
3
- import { computeBriefIntent } from "./brief-intent.js";
4
- import { fetchBriefingResult, } from "./briefing-runtime.js";
5
2
  import { analyzeCodeFile, } from "./comment-analysis.js";
6
3
  import { getE2eCoverageSignal } from "./e2e-coverage-signals.js"; // implements REQ-opencode-file-context-guidance-v1
7
4
  import { buildDirtyRelevantFingerprint, buildEnforcementScopeKey, } from "./enforcement-scope.js";
@@ -9,48 +6,26 @@ import { getFileLinkedEntityIds } from "./file-entity-links.js"; // implements R
9
6
  import * as fileFilter from "./file-filter.js";
10
7
  import { deriveFileOperationReminder } from "./file-operation-reminders.js"; // implements REQ-opencode-file-context-guidance-v1
11
8
  import { createFileOperationState, } from "./file-operation-state.js"; // implements REQ-opencode-file-context-guidance-v1
12
- import { KibiCheckpointRunner, } from "./kibi-checkpoint-runner.js";
13
9
  import { getInitKibiCommandCapability, registerInitKibiCommand, } from "./init-kibi-capability.js";
14
- import { computeAuditDelta, getAuditTailCursor, guardBranchChanged, } from "./idle-brief-audit.js";
15
- import { hasTuiSeenBrief, selectLatestUnreadBrief, } from "./idle-brief-reader.js";
16
- import { generateIdleBrief } from "./idle-brief-runtime.js";
10
+ import { createKbFreshnessEvidenceStore, evaluateKbFreshness, } from "./kb-freshness-state.js";
11
+ import { KibiCheckpointRunner, } from "./kibi-checkpoint-runner.js";
17
12
  import * as logger from "./logger.js";
13
+ import { classifyMeaningfulChange } from "./meaningful-change-classifier.js";
18
14
  import { analyzePath } from "./path-kind.js";
19
15
  import { runPluginStartup } from "./plugin-startup.js";
20
- import { resolveCurrentBranch } from "./plugin-startup.js";
21
16
  import { SENTINEL, buildPrompt } from "./prompt.js";
22
- import { reconcileAuditEntries } from "./reconcile-engine.js";
23
17
  import { isMustPriorityRequirement } from "./requirement-doc.js";
24
18
  import { classifyRisk } from "./risk-classifier.js";
25
19
  import { createSyncScheduler } from "./scheduler.js";
26
20
  import { createSessionEditState, } from "./session-edit-state.js";
27
- import { syncSessionBaselineState, } from "./session-fingerprint.js";
28
21
  import { getSessionTracker } from "./session-tracker.js";
29
22
  import { notifyStartup, } from "./startup-notifier.js";
30
23
  import { readKibiPackageVersions } from "./version-metadata.js";
31
- import { sendToast, } from "./toast.js";
32
- import { announceBriefTui, } from "./tui-brief-delivery.js";
33
- import { createKbFreshnessEvidenceStore, evaluateKbFreshness, } from "./kb-freshness-state.js";
34
- import { classifyMeaningfulChange } from "./meaningful-change-classifier.js";
35
- import { deletePendingBriefMarkers, loadPendingBriefMarkers, } from "./utils/brief-marker.js";
36
24
  import { resolveWorkContext, } from "./work-context-resolver.js";
37
25
  import * as fs from "node:fs";
38
26
  function deriveFileBucket(kind) {
39
27
  return kind;
40
28
  }
41
- function resolveIdleBriefDeliveryDelayMs(worktree) {
42
- const envValue = Number(process.env.KIBI_OPENCODE_IDLE_BRIEF_DELAY_MS);
43
- if (Number.isFinite(envValue) && envValue >= 0) {
44
- return Math.min(60_000, Math.trunc(envValue));
45
- }
46
- const sharedPolicy = loadBriefConfig(worktree);
47
- const configValue = Number(sharedPolicy.tui?.idleDelayMs ?? 1500);
48
- if (!Number.isFinite(configValue))
49
- return 1500;
50
- if (configValue < 0)
51
- return 0;
52
- return Math.min(60_000, Math.trunc(configValue));
53
- }
54
29
  const startupNotifyGlobals = globalThis;
55
30
  /**
56
31
  * Lint requirement documents for embedded scenarios/tests and oversized content.
@@ -121,7 +96,7 @@ const kibiOpencodePlugin = async (input) => {
121
96
  if (!startup) {
122
97
  return {};
123
98
  }
124
- const { cfg, workspaceHealth, posture, currentBranch, cache, runtimeOverlay, scheduler: startupScheduler, maintenanceDegraded, getMaintenanceDegraded, getEffectiveMode, latchRuntimeDegraded, } = startup;
99
+ const { cfg, workspaceHealth, posture, cache, runtimeOverlay, scheduler: startupScheduler, maintenanceDegraded, getMaintenanceDegraded, getEffectiveMode, latchRuntimeDegraded, } = startup;
125
100
  const hooks = {};
126
101
  const initKibiCommandCapability = getInitKibiCommandCapability();
127
102
  if (initKibiCommandCapability.supported) {
@@ -135,11 +110,6 @@ const kibiOpencodePlugin = async (input) => {
135
110
  let hasRecentKbEdit = false;
136
111
  let recentCommentSuggestion = null;
137
112
  const seenFingerprints = new Set(); // For deduplication
138
- // NOTE: autoBriefResults is ONLY for prompt-time auto-brief guidance (file.edited flow).
139
- // Idle-brief runtime (session.idle flow) writes directly to .kb/briefs/ via generateIdleBrief()
140
- // and MUST NEVER store results in this map or leak into prompt guidance.
141
- const autoBriefResults = new Map();
142
- const toastedFingerprints = new Set();
143
113
  let lastRiskClass = null;
144
114
  let lastRiskFilePath = null;
145
115
  let lastRiskScopeKey = null;
@@ -282,36 +252,10 @@ const kibiOpencodePlugin = async (input) => {
282
252
  return cacheKey;
283
253
  }
284
254
  const rootWorkContext = resolveScopedWorkContext();
285
- const sessionEditState = getSessionEditState(rootWorkContext);
286
255
  const fileOperationState = getFileOperationState(rootWorkContext);
287
256
  const scheduler = getSchedulerForContext(rootWorkContext);
288
257
  let degradedWarnedOnce = false;
289
258
  const pathKindCache = getPathKindCache(rootWorkContext);
290
- // Idle-brief state — dedupe via semantic contentHash (persisted envelope is the delivery authority)
291
- let idleBriefInFlight = false;
292
- let idleBriefTrailingRerun = false;
293
- let idleBriefTimer = null;
294
- const idleBriefDeliveredHashes = new Set();
295
- // Session-scoped flag: at most one idle-brief.sync-suppressed breadcrumb per session
296
- let idleSyncSuppressedOnce = false;
297
- const replayedBriefContentHashes = new Set();
298
- // Session-local baseline cursor: captured once per session/worktree/branch from the audit-log tail,
299
- // so the first idle brief in a fresh session only reports post-baseline changes.
300
- let sessionBaselineCursor = null;
301
- let sessionBaselineFingerprint = null;
302
- function syncSessionBaseline(branch) {
303
- const nextState = syncSessionBaselineState({
304
- fingerprint: sessionBaselineFingerprint,
305
- cursor: sessionBaselineCursor,
306
- }, {
307
- sessionId: input.sessionId,
308
- branch,
309
- worktree: input.worktree,
310
- }, () => getAuditTailCursor(input.worktree, branch));
311
- sessionBaselineFingerprint = nextState.fingerprint;
312
- sessionBaselineCursor = nextState.cursor;
313
- }
314
- syncSessionBaseline(currentBranch);
315
259
  function normalizeSessionPath(filePath, worktree = input.worktree) {
316
260
  if (path.isAbsolute(filePath)) {
317
261
  const relativePath = path.relative(worktree, filePath);
@@ -327,44 +271,6 @@ const kibiOpencodePlugin = async (input) => {
327
271
  function buildRiskPathScopeKey(context, filePath) {
328
272
  return `${buildStateScopeKey(context, "risk")}:${normalizeSessionPath(filePath, context.worktreeRoot)}`;
329
273
  }
330
- function getKbSnapshotFingerprint(worktree, branch) {
331
- try {
332
- const snapshotPath = path.join(worktree, ".kb", "branches", branch, "kb.rdf");
333
- const stat = fs.statSync(snapshotPath);
334
- return `${stat.size}:${stat.mtimeMs}`;
335
- }
336
- catch {
337
- return "missing";
338
- }
339
- }
340
- function buildSyntheticSyncAuditDelta(baseDelta, sourceFiles) {
341
- const timestamp = new Date().toISOString();
342
- const fileSource = sourceFiles[0] ?? "workspace-sync";
343
- const entityId = path.basename(fileSource).replace(/\.md$/, "") || "workspace-sync";
344
- return {
345
- ...baseDelta,
346
- hasChanges: true,
347
- entries: [
348
- {
349
- timestamp,
350
- operation: "upsert",
351
- entityId,
352
- payload: {
353
- kind: "entity",
354
- entityType: "fact",
355
- changeKind: "updated",
356
- title: entityId,
357
- source: fileSource,
358
- properties: {
359
- id: entityId,
360
- title: entityId,
361
- source: fileSource,
362
- },
363
- },
364
- },
365
- ],
366
- };
367
- }
368
274
  function getTransformFocusFilePath(transformInput) {
369
275
  if (!transformInput || typeof transformInput !== "object") {
370
276
  return null;
@@ -433,65 +339,31 @@ const kibiOpencodePlugin = async (input) => {
433
339
  precomputedSuggestion,
434
340
  };
435
341
  }
436
- function buildBriefingWorkspaceContext(context = rootWorkContext, branch = context.branch) {
437
- return {
438
- workspaceRoot: context.worktreeRoot,
439
- branch,
440
- directory: context.worktreeRoot,
441
- ...(input.workspace !== undefined ? { workspace: input.workspace } : {}),
442
- };
443
- }
444
- function buildWorkspaceContextForBranch(branch, context = rootWorkContext) {
445
- return {
446
- ...buildBriefingWorkspaceContext(context),
447
- branch,
448
- };
449
- }
450
- function queueBriefingFetch(intentResult, options = {}) {
451
- if (!intentResult.eligible ||
452
- !input.client ||
453
- getMaintenanceDegraded() ||
454
- ((options.postureState ?? posture.state) !== "root_active" &&
455
- (options.postureState ?? posture.state) !== "hybrid_root_plus_vendored")) {
456
- return;
457
- }
458
- if (options.skipIfCachedResultExists === true &&
459
- autoBriefResults.has(intentResult.fingerprint)) {
460
- return;
461
- }
462
- const client = input.client;
463
- const fingerprint = intentResult.fingerprint;
464
- const workspaceCtx = options.workspaceCtx ?? buildBriefingWorkspaceContext();
465
- void fetchBriefingResult(client, workspaceCtx, intentResult).then((result) => {
466
- autoBriefResults.set(fingerprint, result);
467
- if (!toastedFingerprints.has(fingerprint)) {
468
- toastedFingerprints.add(fingerprint);
469
- void sendToast(makeToastClient(client), {
470
- message: result.toastMessage,
471
- });
472
- }
473
- });
474
- }
475
342
  hooks.event = async ({ event }) => {
476
- const activeBranch = resolveCurrentBranch(input.worktree);
477
- syncSessionBaseline(activeBranch);
478
343
  // Observe KB tool execution events for freshness evidence (best-effort)
479
344
  const TOOL_EVENT_TYPES = new Set([
480
- "tool.execute.after", "tool.executed", "tool.call.completed",
481
- "tool.Execute.after", "tool.Call.completed",
345
+ "tool.execute.after",
346
+ "tool.executed",
347
+ "tool.call.completed",
348
+ "tool.Execute.after",
349
+ "tool.Call.completed",
482
350
  ]);
483
351
  if (TOOL_EVENT_TYPES.has(event.type)) {
484
352
  const props = event.properties ?? {};
485
- const toolName = (props.tool ?? props.toolName ?? props.name ??
353
+ const toolName = (props.tool ??
354
+ props.toolName ??
355
+ props.name ??
486
356
  props.call?.name ??
487
357
  props.input?.tool);
488
358
  if (typeof toolName === "string" && toolName.startsWith("kb_")) {
489
359
  const scope = {
490
- ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}),
360
+ ...(input.sessionId !== undefined
361
+ ? { sessionId: input.sessionId }
362
+ : {}),
491
363
  agentIdentity: input.agentIdentity ?? "unknown",
492
364
  worktree: input.worktree,
493
- branch: activeBranch,
494
- fingerprint: `${input.sessionId ?? ""}-${activeBranch}`,
365
+ branch: rootWorkContext.branch,
366
+ fingerprint: `${input.sessionId ?? ""}-${rootWorkContext.branch}`,
495
367
  };
496
368
  try {
497
369
  freshnessStore.recordToolEvidence(scope, toolName);
@@ -506,172 +378,12 @@ const kibiOpencodePlugin = async (input) => {
506
378
  }
507
379
  return; // tool events don't need file lifecycle processing
508
380
  }
509
- // Handle session.idle for idle-brief generation. OpenCode can emit idle
510
- // while an assistant is between tool calls, so debounce until the work
511
- // burst settles before generating/delivering a brief.
512
- if (event.type === "session.idle") {
513
- if (!input.client)
514
- return;
515
- const idleBranch = activeBranch;
516
- const idleWorkspaceRoot = input.worktree;
517
- const runIdleBrief = async () => {
518
- if (idleBriefInFlight) {
519
- idleBriefTrailingRerun = true;
520
- return;
521
- }
522
- idleBriefInFlight = true;
523
- idleBriefTrailingRerun = false;
524
- try {
525
- // Gather session edits
526
- const sessionEdits = sessionEditState.getSessionEdits();
527
- const sourceFiles = sessionEdits.map((e) => e.filePath);
528
- const markerResult = loadPendingBriefMarkers(idleWorkspaceRoot, idleBranch);
529
- for (const issue of markerResult.issues) {
530
- logger.warn("idle-brief.marker-invalid", {
531
- event: "idle_brief_marker_invalid",
532
- branch: idleBranch,
533
- filePath: issue.filePath,
534
- reason: issue.reason,
535
- });
536
- }
537
- const markerEntityIds = markerResult.entityIds;
538
- const markerRelationships = markerResult.relationships;
539
- const snapshotBeforeSync = getKbSnapshotFingerprint(idleWorkspaceRoot, idleBranch);
540
- if (scheduler) {
541
- const idleSyncBlocked = runtimeOverlay.primaryCause === "scheduler_sync_failed";
542
- if (!idleSyncBlocked) {
543
- scheduler.scheduleSync("session.idle");
544
- await scheduler.flush();
545
- }
546
- else if (!idleSyncSuppressedOnce) {
547
- idleSyncSuppressedOnce = true;
548
- logger.info("idle-brief.sync-suppressed", {
549
- event: "idle_brief_sync_suppressed",
550
- primaryCause: runtimeOverlay.primaryCause,
551
- });
552
- }
553
- }
554
- const snapshotAfterSync = getKbSnapshotFingerprint(idleWorkspaceRoot, idleBranch);
555
- const rawAuditDelta = computeAuditDelta(idleWorkspaceRoot, idleBranch, sessionBaselineCursor);
556
- const auditDelta = rawAuditDelta.hasChanges || snapshotBeforeSync === snapshotAfterSync
557
- ? rawAuditDelta
558
- : buildSyntheticSyncAuditDelta(rawAuditDelta, sourceFiles);
559
- if (!auditDelta.hasChanges)
560
- return;
561
- // Branch switch guard
562
- const currentBranchNow = resolveCurrentBranch(input.worktree);
563
- if (guardBranchChanged(idleBranch, currentBranchNow)) {
564
- logger.info("idle-brief.branch-changed", {
565
- event: "idle_brief_branch_changed",
566
- idleBranch,
567
- currentBranch: currentBranchNow,
568
- });
569
- return;
570
- }
571
- // Generate brief
572
- const workspaceCtx = buildWorkspaceContextForBranch(idleBranch);
573
- const client = input.client;
574
- if (!client)
575
- return;
576
- const reconciled = reconcileAuditEntries(auditDelta.entries);
577
- const changedEntityIds = [
578
- ...reconciled.added.map((e) => e.id),
579
- ...reconciled.modified.map((e) => e.id),
580
- ...reconciled.removed.map((e) => e.id),
581
- ];
582
- const mergedChangedEntityIds = [
583
- ...new Set([...changedEntityIds, ...markerEntityIds]),
584
- ];
585
- const mergedSourceFiles = [...new Set([...sourceFiles, ...markerEntityIds])];
586
- const result = await generateIdleBrief(input.client, workspaceCtx, auditDelta, input.sessionId ?? "unknown", mergedSourceFiles.length > 0
587
- ? {
588
- sourceFiles: mergedSourceFiles,
589
- changedEntityIds: mergedChangedEntityIds,
590
- relationships: markerRelationships,
591
- }
592
- : mergedChangedEntityIds.length > 0
593
- ? {
594
- changedEntityIds: mergedChangedEntityIds,
595
- relationships: markerRelationships,
596
- }
597
- : undefined);
598
- if (result.success) {
599
- const deleteResult = await deletePendingBriefMarkers(markerResult.markerPaths);
600
- for (const issue of deleteResult.issues) {
601
- logger.warn("idle-brief.marker-delete-failed", {
602
- event: "idle_brief_marker_delete_failed",
603
- branch: idleBranch,
604
- filePath: issue.filePath,
605
- reason: issue.reason,
606
- });
607
- }
608
- }
609
- if (result.success && result.envelope) {
610
- const envelope = result.envelope;
611
- // Dedupe by semantic contentHash — persisted envelope is the delivery authority
612
- const dedupeKey = `${idleWorkspaceRoot}:${idleBranch}:tui:${envelope.contentHash}`;
613
- if (!idleBriefDeliveredHashes.has(dedupeKey)) {
614
- idleBriefDeliveredHashes.add(dedupeKey);
615
- const sharedPolicy = { briefs: loadBriefConfig(input.worktree) };
616
- if (client) {
617
- try {
618
- const announcementResult = await announceBriefTui(makeToastClient(client), envelope, sharedPolicy);
619
- if (announcementResult.toastDelivered ||
620
- announcementResult.commandPublished) {
621
- replayedBriefContentHashes.add(envelope.contentHash);
622
- }
623
- }
624
- catch (err) {
625
- logger.error("idle-brief.delivery-failed", {
626
- event: "idle_brief_delivery_failed",
627
- error: err instanceof Error ? err.message : String(err),
628
- });
629
- }
630
- }
631
- }
632
- }
633
- else {
634
- logger.info("idle-brief.no-brief-generated", {
635
- event: "idle_brief_no_brief_generated",
636
- success: result.success,
637
- hasEnvelope: !!result.envelope,
638
- });
639
- }
640
- }
641
- catch (error) {
642
- logger.error("idle-brief.error", {
643
- event: "idle_brief_error",
644
- error: error instanceof Error ? error.message : String(error),
645
- });
646
- }
647
- finally {
648
- idleBriefInFlight = false;
649
- // If trailing rerun was requested, run again
650
- if (idleBriefTrailingRerun) {
651
- idleBriefTrailingRerun = false;
652
- void runIdleBrief();
653
- }
654
- }
655
- };
656
- if (idleBriefTimer) {
657
- clearTimeout(idleBriefTimer);
658
- }
659
- idleBriefTimer = setTimeout(() => {
660
- idleBriefTimer = null;
661
- void runIdleBrief();
662
- }, resolveIdleBriefDeliveryDelayMs(idleWorkspaceRoot));
663
- return;
664
- }
665
381
  // Accept file.created, file.edited, and file.deleted lifecycle events
666
382
  const isFileLifecycle = event.type === "file.created" ||
667
383
  event.type === "file.edited" ||
668
384
  event.type === "file.deleted";
669
385
  if (!isFileLifecycle)
670
386
  return;
671
- if (idleBriefTimer) {
672
- clearTimeout(idleBriefTimer);
673
- idleBriefTimer = null;
674
- }
675
387
  const filePath = event
676
388
  .properties.file;
677
389
  if (!filePath)
@@ -700,7 +412,7 @@ const kibiOpencodePlugin = async (input) => {
700
412
  scopedPathKindCache.set(normalizedFilePath, pathAnalysis.kind);
701
413
  }
702
414
  else {
703
- // Not tracked — classify only for reminder routing, not auto-briefing
415
+ // Not tracked — classify only for reminder routing.
704
416
  scopedPathKindCache.set(normalizedFilePath, pathAnalysis.kind);
705
417
  }
706
418
  scopedSessionEditState.recordEventHint(normalizedFilePath, pathAnalysis.kind, Date.now());
@@ -746,8 +458,6 @@ const kibiOpencodePlugin = async (input) => {
746
458
  const effectiveRiskClass = riskClass === "safe_docs_only" && precomputedSuggestion
747
459
  ? "traceability_candidate"
748
460
  : riskClass;
749
- const isAutoBriefRisk = effectiveRiskClass === "behavior_candidate" ||
750
- effectiveRiskClass === "traceability_candidate";
751
461
  lastRiskClass = effectiveRiskClass;
752
462
  lastRiskFilePath = normalizedFilePath;
753
463
  lastRiskScopeKey = buildRiskPathScopeKey(eventContext, normalizedFilePath);
@@ -849,9 +559,7 @@ const kibiOpencodePlugin = async (input) => {
849
559
  posture: eventContext.posture,
850
560
  posture_state: eventContext.posture,
851
561
  });
852
- if (!isAutoBriefRisk) {
853
- return;
854
- }
562
+ return;
855
563
  }
856
564
  logger.info("smart-enforcement.cache", {
857
565
  event: "smart_enforcement_cache",
@@ -951,7 +659,8 @@ const kibiOpencodePlugin = async (input) => {
951
659
  }
952
660
  return;
953
661
  }
954
- if (isAutoBriefRisk) {
662
+ if (effectiveRiskClass === "behavior_candidate" ||
663
+ effectiveRiskClass === "traceability_candidate") {
955
664
  if (pathAnalysis.kind === "code" &&
956
665
  cfg.guidance.commentDetection.enabled) {
957
666
  const suggestion = precomputedSuggestion;
@@ -976,25 +685,6 @@ const kibiOpencodePlugin = async (input) => {
976
685
  else {
977
686
  recentCommentSuggestion = null;
978
687
  }
979
- if (!focusEdit) {
980
- // No surviving edits (all reverted to baseline) — skip auto-brief fetch
981
- return;
982
- }
983
- const sessionSourceFiles = sessionEdits.map((e) => e.filePath);
984
- const briefingContext = resolveScopedWorkContext(focusEdit.filePath);
985
- const intentResult = computeBriefIntent({
986
- riskClass: effectiveRiskClass,
987
- posture: briefingContext.posture,
988
- maintenanceDegraded: getMaintenanceDegraded(),
989
- sourceFiles: sessionSourceFiles,
990
- focusFilePath: focusEdit.filePath,
991
- worktreeRoot: briefingContext.worktreeRoot,
992
- branch: briefingContext.branch,
993
- });
994
- queueBriefingFetch(intentResult, {
995
- workspaceCtx: buildBriefingWorkspaceContext(briefingContext),
996
- postureState: briefingContext.posture,
997
- });
998
688
  }
999
689
  return;
1000
690
  };
@@ -1052,58 +742,7 @@ const kibiOpencodePlugin = async (input) => {
1052
742
  if (effectiveRiskClass === null && lastRiskClass !== null) {
1053
743
  effectiveRiskClass = lastRiskClass;
1054
744
  }
1055
- const promptSourceFiles = transformSessionEdits.map((entry) => entry.filePath);
1056
745
  const promptFocusFilePath = transformFocusEdit?.filePath ?? transformFocusFilePath ?? undefined;
1057
- const intentResult = effectiveRiskClass
1058
- ? computeBriefIntent({
1059
- riskClass: effectiveRiskClass,
1060
- posture: promptWorkContext.posture,
1061
- maintenanceDegraded,
1062
- sourceFiles: promptSourceFiles,
1063
- worktreeRoot: promptWorkContext.worktreeRoot,
1064
- branch: promptWorkContext.branch,
1065
- ...(promptFocusFilePath !== undefined
1066
- ? {
1067
- focusFilePath: promptFocusFilePath,
1068
- }
1069
- : {}),
1070
- })
1071
- : null;
1072
- const autoBriefResult = intentResult
1073
- ? autoBriefResults.get(intentResult.fingerprint)
1074
- : undefined;
1075
- const isAutoBriefRisk = effectiveRiskClass === "behavior_candidate" ||
1076
- effectiveRiskClass === "traceability_candidate";
1077
- if (!autoBriefResult && isAutoBriefRisk && intentResult) {
1078
- queueBriefingFetch(intentResult, {
1079
- skipIfCachedResultExists: true,
1080
- workspaceCtx: buildBriefingWorkspaceContext(promptWorkContext),
1081
- postureState: promptWorkContext.posture,
1082
- });
1083
- }
1084
- // Replay latest unread idle brief if available // implements REQ-opencode-kibi-briefing-v4
1085
- if (input.worktree && currentBranch && input.client) {
1086
- const unreadBrief = selectLatestUnreadBrief(input.worktree, currentBranch);
1087
- if (unreadBrief &&
1088
- !replayedBriefContentHashes.has(unreadBrief.envelope.contentHash) &&
1089
- !hasTuiSeenBrief(input.worktree, currentBranch, unreadBrief.envelope.contentHash)) {
1090
- const sharedPolicy = { briefs: loadBriefConfig(input.worktree) };
1091
- const client = input.client;
1092
- try {
1093
- const announcementResult = await announceBriefTui(makeToastClient(client), unreadBrief.envelope, sharedPolicy);
1094
- if (announcementResult.toastDelivered ||
1095
- announcementResult.commandPublished) {
1096
- replayedBriefContentHashes.add(unreadBrief.envelope.contentHash);
1097
- }
1098
- }
1099
- catch (err) {
1100
- logger.error("idle-brief.replay-failed", {
1101
- event: "idle_brief_replay_failed",
1102
- error: err instanceof Error ? err.message : String(err),
1103
- });
1104
- }
1105
- }
1106
- }
1107
746
  // Steps 3-4: File-operation reminder selection with suppression // implements REQ-opencode-file-context-guidance-v1
1108
747
  let fileOperationReminder;
1109
748
  let hardGateBlock;
@@ -1137,8 +776,10 @@ const kibiOpencodePlugin = async (input) => {
1137
776
  focusPathKind,
1138
777
  effectiveRiskClass ?? "safe_docs_only",
1139
778
  ]);
1140
- if (effectiveMode === "hard" && promptWorkContext.isAuthoritative) {
1141
- checkpointRunner = getCheckpointRunnerForContext(promptWorkContext);
779
+ if (effectiveMode === "hard" &&
780
+ promptWorkContext.isAuthoritative) {
781
+ checkpointRunner =
782
+ getCheckpointRunnerForContext(promptWorkContext);
1142
783
  checkpointContext = {
1143
784
  workContext: promptWorkContext,
1144
785
  config: cfg,
@@ -1171,13 +812,18 @@ const kibiOpencodePlugin = async (input) => {
1171
812
  if (reminderResult.policyDecision === "hard_block") {
1172
813
  const policyResult = reminderResult.policyResult;
1173
814
  hardGateBlock = {
1174
- shownPaths: "shownPaths" in policyResult ? policyResult.shownPaths : [normalizedFocusPath],
1175
- remainingCount: "remainingCount" in policyResult ? policyResult.remainingCount : 0,
815
+ shownPaths: "shownPaths" in policyResult
816
+ ? policyResult.shownPaths
817
+ : [normalizedFocusPath],
818
+ remainingCount: "remainingCount" in policyResult
819
+ ? policyResult.remainingCount
820
+ : 0,
1176
821
  reason: "checkpoint_required",
1177
822
  };
1178
823
  hardGateConsumedPath = normalizedFocusPath;
1179
824
  hardGateFingerprint = checkpointFingerprint;
1180
- hardGateReminderKindsToMark = reminderResult.reminderKindsToMark;
825
+ hardGateReminderKindsToMark =
826
+ reminderResult.reminderKindsToMark;
1181
827
  if (checkpointRunner && checkpointContext) {
1182
828
  const checkpointContextWithGuidance = {
1183
829
  ...checkpointContext,
@@ -1223,7 +869,9 @@ const kibiOpencodePlugin = async (input) => {
1223
869
  let freshnessEval;
1224
870
  const freshnessFingerprint = `${promptWorkContext.sessionId ?? ""}-${promptWorkContext.branch}`;
1225
871
  const freshnessScope = {
1226
- ...(promptWorkContext.sessionId !== undefined ? { sessionId: promptWorkContext.sessionId } : {}),
872
+ ...(promptWorkContext.sessionId !== undefined
873
+ ? { sessionId: promptWorkContext.sessionId }
874
+ : {}),
1227
875
  agentIdentity: promptWorkContext.agentIdentity,
1228
876
  worktree: promptWorkContext.worktreeRoot,
1229
877
  branch: promptWorkContext.branch,
@@ -1231,7 +879,8 @@ const kibiOpencodePlugin = async (input) => {
1231
879
  };
1232
880
  if (transformFocusFilePath) {
1233
881
  const normalizedFocusPathForFreshness = promptFileOperationState.normalizePath(transformFocusFilePath);
1234
- const focusPathKindForFreshness = promptPathKindCache.get(normalizedFocusPathForFreshness) ?? "unknown";
882
+ const focusPathKindForFreshness = promptPathKindCache.get(normalizedFocusPathForFreshness) ??
883
+ "unknown";
1235
884
  const pendingLifecycleForFreshness = promptFileOperationState.peekPending(normalizedFocusPathForFreshness);
1236
885
  const effectiveRiskClassForFreshness = effectiveRiskClass ?? "safe_docs_only";
1237
886
  const meaningfulChange = classifyMeaningfulChange({
@@ -1259,7 +908,6 @@ const kibiOpencodePlugin = async (input) => {
1259
908
  maintenanceDegraded,
1260
909
  degradedMode: cfg.guidance.smartEnforcement.degradedMode,
1261
910
  showDegradedAdvisory,
1262
- ...(autoBriefResult !== undefined ? { autoBriefResult } : {}),
1263
911
  ...(effectiveRiskClass != null
1264
912
  ? { riskClass: effectiveRiskClass }
1265
913
  : {}),
@@ -1269,7 +917,11 @@ const kibiOpencodePlugin = async (input) => {
1269
917
  ...(hardGateBlock !== undefined ? { hardGateBlock } : {}),
1270
918
  ...(freshnessEval ? { kbFreshness: freshnessEval } : {}),
1271
919
  ...(freshnessEval && transformSessionEdits.length > 0
1272
- ? { freshnessChangedPaths: [...new Set(transformSessionEdits.map(e => e.filePath))].slice(0, 5) }
920
+ ? {
921
+ freshnessChangedPaths: [
922
+ ...new Set(transformSessionEdits.map((e) => e.filePath)),
923
+ ].slice(0, 5),
924
+ }
1273
925
  : {}),
1274
926
  });
1275
927
  logger.info("smart-enforcement.guidance", {
@@ -1317,8 +969,7 @@ const kibiOpencodePlugin = async (input) => {
1317
969
  // Mark shown and log only for reminders that were actually emitted
1318
970
  if (lifecycleEmitted) {
1319
971
  const kind = promptFileOperationState.peekPending(focusPathForConsume)
1320
- ?.lifecycle ===
1321
- "deleted"
972
+ ?.lifecycle === "deleted"
1322
973
  ? "kibi_delete"
1323
974
  : "kibi_write";
1324
975
  promptFileOperationState.markShown(focusPathForConsume, kind);
@@ -1333,8 +984,7 @@ const kibiOpencodePlugin = async (input) => {
1333
984
  }
1334
985
  if (e2eEmitted) {
1335
986
  const kind = promptFileOperationState.peekPending(focusPathForConsume)
1336
- ?.lifecycle ===
1337
- "deleted"
987
+ ?.lifecycle === "deleted"
1338
988
  ? "e2e_delete"
1339
989
  : "e2e_write";
1340
990
  promptFileOperationState.markShown(focusPathForConsume, kind);
package/dist/prompt.d.ts CHANGED
@@ -1,15 +1,13 @@
1
1
  import type { CommentAnalysisResult } from "./comment-analysis.js";
2
- import type { BriefingRuntimeResult } from "./briefing-runtime.js";
3
2
  import type { KibiConfig } from "./config.js";
4
- import { type InitKibiCommandCapability } from "./init-kibi-capability.js";
5
3
  import type { GuidanceCache } from "./guidance-cache.js";
4
+ import { type InitKibiCommandCapability } from "./init-kibi-capability.js";
5
+ import type { KbFreshnessEvaluation } from "./kb-freshness-state.js";
6
6
  import type { PathKind } from "./path-kind.js";
7
7
  import type { RepoPosture } from "./repo-posture.js";
8
8
  import type { RiskClass } from "./risk-classifier.js";
9
9
  import type { WorkspaceHealth } from "./workspace-health.js";
10
- import type { KbFreshnessEvaluation } from "./kb-freshness-state.js";
11
10
  declare const SENTINEL = "<!-- kibi-opencode -->";
12
- export declare function buildAutoBriefingGuidance(autoBriefResult: BriefingRuntimeResult | undefined, completionReminder: boolean): string | null;
13
11
  export interface PromptContext {
14
12
  recentEdits: Array<{
15
13
  path: string;
@@ -40,8 +38,6 @@ export interface PromptContext {
40
38
  degradedMode?: "warn-once" | "structured-only";
41
39
  /** Whether to show the degraded advisory block this invocation */
42
40
  showDegradedAdvisory?: boolean;
43
- /** Stored auto-brief runtime result for the current fingerprint */
44
- autoBriefResult?: BriefingRuntimeResult;
45
41
  /** File-operation reminder from lifecycle and e2e coverage signals */
46
42
  fileOperationReminder?: {
47
43
  path: string;