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/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 {
|
|
15
|
-
import {
|
|
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,
|
|
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",
|
|
481
|
-
"tool.
|
|
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 ??
|
|
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
|
|
360
|
+
...(input.sessionId !== undefined
|
|
361
|
+
? { sessionId: input.sessionId }
|
|
362
|
+
: {}),
|
|
491
363
|
agentIdentity: input.agentIdentity ?? "unknown",
|
|
492
364
|
worktree: input.worktree,
|
|
493
|
-
branch:
|
|
494
|
-
fingerprint: `${input.sessionId ?? ""}-${
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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" &&
|
|
1141
|
-
|
|
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
|
|
1175
|
-
|
|
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 =
|
|
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
|
|
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) ??
|
|
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
|
-
? {
|
|
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;
|