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/README.md CHANGED
@@ -24,8 +24,8 @@ The plugin now uses a posture-aware, low-token smart-enforcement model before em
24
24
 
25
25
  - **Repo posture detection**: distinguishes `root_active`, `root_partial`, `root_uninitialized`, `vendored_only`, and `hybrid_root_plus_vendored`
26
26
  - **Risk classification**: separates `safe_docs_only`, `safe_test_only`, `kb_doc_structural`, `req_policy_candidate`, `behavior_candidate`, `traceability_candidate`, and `manual_kb_edit`
27
- - **Source-linked micro-briefs**: risky code edits (`behavior_candidate`, `traceability_candidate`) prepend a concise list of existing Kibi links (e.g., `- Existing Kibi links: REQ-001, REQ-002`) when 1-3 concrete source-linked KB hits are found in `documentation/symbols.yaml`. Skip on cache hit.
28
- - **Start-task risky cue**: authoritative risky edits also add a compact `/brief-kibi` cue so agents can start with an explicit Kibi briefing before acting, while staying inside the same single prompt block and token budget.
27
+ - **Source-linked Kibi links**: risky code edits (`behavior_candidate`, `traceability_candidate`) prepend a concise list of existing Kibi links (e.g., `- Existing Kibi links: REQ-001, REQ-002`) when 1-3 concrete source-linked KB hits are found in `documentation/symbols.yaml`. Skip on cache hit.
28
+ - **Start-task risky cue**: authoritative risky edits also add a compact Kibi context cue so agents can start with explicit source-linked guidance before acting, while staying inside the same single prompt block and token budget.
29
29
  - **Effective mode gating**: `advisory` (default) never blocks; `strict` escalates checks and reminders for `root_active` and `hybrid_root_plus_vendored` postures when `requireRootKbForStrict` is enabled; `hard` fails closed for authoritative roots and linked git worktrees, injecting a stop-state with MCP recovery steps until the Kibi checkpoint passes. `maintenanceDegraded` overrides everything back to `advisory`.
30
30
  - **Low-token prompt policy**: docs-only and test-only edits avoid unnecessary discovery prompts; vendored-only repos suppress operational bootstrap nudges; at most one contextual block is injected per prompt (≤120 words, ≤5 bullets)
31
31
  - **Completion reminder**: when `completionReminder` is enabled, risky code edits append a single prompt-visible `kb_check` reminder exactly once per cached context
@@ -127,13 +127,6 @@ The plugin injects guidance into OpenCode sessions to improve agent grounding. U
127
127
 
128
128
  OpenCode exposes Kibi MCP prompts as slash commands. The \`/init-kibi\` command triggers the \`kb_autopilot_generate\` workflow to assist in retroactive bootstrap using only public MCP tools.
129
129
 
130
- When the plugin detects an authoritative risky edit (`behavior_candidate` or `traceability_candidate` risk class), it automatically renders a Kibi briefing before the prompt. The plugin uses two complementary paths: the `file.edited` event hook as a fast-path hint, and prompt-cycle reconciliation as an authoritative fallback for programmatic edits that bypass the event bus. Briefings are rendered directly into the prompt to ensure immediate visibility.
131
-
132
- - **Immediate delivery**: Briefings are rendered-first into the prompt guidance block headed `🧠 **Kibi briefing available**` and TUI toasts titled `Kibi Knowledge Update`. Unread briefings automatically open the interactive TUI via the `kibi.brief` route.
133
- - **Narrative structure**: Delivery favors user-facing prose with `What changed` and `Why it matters`, plus conditional `Project knowledge impact` / `Interpretation note` sections when evidence or caveats exist.
134
- - **TL;DR fallback**: If a full briefing is unavailable, fallback output still preserves `What changed` / `Why it matters` framing while keeping the manual command cue available.
135
- - **Manual command**: Use `/brief-kibi` or the `kibi.open_latest_brief` command at any time to trigger an on-demand briefing if auto-delivery is skipped or fails.
136
-
137
130
  ### Discovery-first MCP guidance
138
131
 
139
132
  Agent-visible guidance is intentionally limited to the curated public MCP surface:
@@ -179,7 +172,6 @@ Config files (project overrides global):
179
172
  | `ux.toastFailures` | boolean | `true` | Show failure toasts for sync/check issues |
180
173
  | `ux.toastSuccesses` | boolean | `false` | Show success toasts for sync/check completion |
181
174
  | `ux.toastCooldownMs` | number | `10000` | Cooldown between repeated UX toasts |
182
- | `ux.briefs.autoSubmit` | boolean | `true` | **Deprecated/No-op**: Auto-submission is no longer needed with render-first briefing |
183
175
  PP|| `guidance.dynamic` | boolean | `true` | Enable dynamic contextual guidance |
184
176
  | `guidance.dynamic` | boolean | `true` | Enable dynamic contextual guidance |
185
177
  | `guidance.warnOnKbEdits` | boolean | `true` | Enable loud warnings for .kb/** edits |
@@ -234,7 +226,7 @@ The plugin uses the official OpenCode toast APIs with automatic capability detec
234
226
  1. **Legacy transport**: `client.tui.toast(payload)` — used when available in plugin context
235
227
  2. **SDK transport**: `client.tui.showToast({ body: payload })` — used as fallback
236
228
  3. **No capability**: Returns `{ status: "unavailable", reason: "missing-capability" }`
237
- The `sendToast` helper returns a discriminated `SendToastResult` union and never throws. There is no raw HTTP fallback. For rich briefing delivery, the plugin uses the `kibi.brief` route and the `kibi.open_latest_brief` command for manual retrieval.
229
+ The `sendToast` helper returns a discriminated `SendToastResult` union and never throws. There is no raw HTTP fallback.
238
230
  The `experimental.chat.system.transform` hook handles prompt injection (see [Hook Policy](#hook-policy)). The `chat.params` hook is compatibility-only and never carries prompt text.
239
231
 
240
232
  ### Hook Modes
@@ -315,7 +307,7 @@ The OpenCode plugin respects the repository ignore policy used by Kibi's discove
315
307
  - The plugin's background sync and file-event handling skip paths matched by repository `.gitignore` files, nested `.gitignore` files, and `.git/info/exclude`.
316
308
  - The plugin also treats a curated set of tool/runtime directories as never-relevant for KB sync (for example: `.sisyphus`, `.opencode`, `.kb`, `.git`, `node_modules`, `vendor`, `third_party`).
317
309
 
318
- When a file event occurs for an ignored path, the plugin skips processing and will not surface candidate guidance for that file. This avoids noisy briefings and prevents build/editor artifacts from triggering KB sync work.
310
+ When a file event occurs for an ignored path, the plugin skips processing and will not surface candidate guidance for that file. This avoids noisy sync triggers and prevents build/editor artifacts from triggering KB sync work.
319
311
 
320
312
  - **E2e reminder evidence**: File-operation reminders use exact Kibi graph evidence first (`covered_by` links to `[e2e]`-tagged entities or `/e2e/`-sourced entities) and narrow path heuristics second. Package-level e2e tests do not trigger "authoritative evidence" flags at the file level.
321
313
 
package/dist/config.d.ts CHANGED
@@ -15,9 +15,6 @@ export interface KibiConfig {
15
15
  toastFailures: boolean;
16
16
  toastSuccesses: boolean;
17
17
  toastCooldownMs: number;
18
- briefs?: {
19
- autoSubmit: boolean;
20
- };
21
18
  };
22
19
  guidance: {
23
20
  dynamic: boolean;
package/dist/config.js CHANGED
@@ -11,9 +11,6 @@ const DEFAULTS = {
11
11
  toastFailures: true,
12
12
  toastSuccesses: false,
13
13
  toastCooldownMs: 10000,
14
- briefs: {
15
- autoSubmit: true,
16
- },
17
14
  },
18
15
  guidance: {
19
16
  dynamic: true,
@@ -103,12 +100,6 @@ export function validateAndMerge(obj) {
103
100
  out.ux.toastSuccesses = u.toastSuccesses;
104
101
  if (typeof u.toastCooldownMs === "number")
105
102
  out.ux.toastCooldownMs = u.toastCooldownMs;
106
- if (u.briefs && typeof u.briefs === "object") {
107
- const b = u.briefs;
108
- out.ux.briefs = { autoSubmit: true };
109
- if (typeof b.autoSubmit === "boolean")
110
- out.ux.briefs.autoSubmit = b.autoSubmit;
111
- }
112
103
  }
113
104
  if (typeof src.logLevel === "string")
114
105
  out.logLevel = src.logLevel;
@@ -19,9 +19,7 @@ function readTestDoc(worktree, testId) {
19
19
  const content = readFileSync(fullPath, "utf8");
20
20
  return parseTestDoc(content, testId);
21
21
  }
22
- catch {
23
- continue;
24
- }
22
+ catch { }
25
23
  }
26
24
  }
27
25
  return null;
@@ -1,11 +1,11 @@
1
1
  import type { E2eCoverageSignal } from "./e2e-coverage-signals.js";
2
2
  import type { FileLifecycle, ReminderKind } from "./file-operation-state.js";
3
+ import type { KbFreshnessEvidence } from "./kb-freshness-state.js";
3
4
  import type { PathKind } from "./path-kind.js";
4
5
  import type { RepoPosture } from "./repo-posture.js";
5
6
  import type { RiskClass } from "./risk-classifier.js";
6
7
  import type { EffectiveMode } from "./smart-enforcement.js";
7
8
  import type { WorkContext } from "./work-context-resolver.js";
8
- import type { KbFreshnessEvidence } from "./kb-freshness-state.js";
9
9
  export interface PolicyLinkedEntityResult {
10
10
  ids: string[];
11
11
  source: "symbols" | "doc-path" | "none";
@@ -55,7 +55,10 @@ function normalizeEvents(input) {
55
55
  return input.lifecycleEvents.map((event, index) => {
56
56
  const linkedEntityResult = input.linkedEntityResults?.[index] ??
57
57
  (input.linkedEntityIds?.[index]
58
- ? { ids: input.linkedEntityIds[index] ?? [], source: "symbols" }
58
+ ? {
59
+ ids: input.linkedEntityIds[index] ?? [],
60
+ source: "symbols",
61
+ }
59
62
  : DEFAULT_LINKED_ENTITY_RESULT);
60
63
  return {
61
64
  ...event,
@@ -97,7 +100,8 @@ function uniqueReminderKinds(events) {
97
100
  else {
98
101
  add("kibi_write");
99
102
  }
100
- if (event.e2eSignal.level !== "none" && event.e2eSignal.reminderText !== null) {
103
+ if (event.e2eSignal.level !== "none" &&
104
+ event.e2eSignal.reminderText !== null) {
101
105
  add(event.lifecycle === "deleted" ? "e2e_delete" : "e2e_write");
102
106
  }
103
107
  }
@@ -168,7 +172,8 @@ function hardBlockText(events) {
168
172
  if (remainingCount > 0) {
169
173
  pathLines.push(`- +${remainingCount} more dirty files`);
170
174
  }
171
- const deletedWithoutLinks = events.some((event) => event.lifecycle === "deleted" && event.linkedEntityResult.ids.length === 0);
175
+ const deletedWithoutLinks = events.some((event) => event.lifecycle === "deleted" &&
176
+ event.linkedEntityResult.ids.length === 0);
172
177
  const representativePath = events[0]?.normalizedPath ?? "<changed-file>";
173
178
  const evidenceNotes = [linkedIdsText(events), e2eEvidenceText(events)].filter((note) => note !== null);
174
179
  const deletionCleanup = deletedWithoutLinks
@@ -189,7 +194,7 @@ function hardBlockText(events) {
189
194
  "KB freshness resolution:",
190
195
  "- **KB updated**: run `kb_search` for discovery, then `kb_upsert`/`kb_delete` for mutations, then `kb_check`.",
191
196
  "- **No KB impact**: provide a no-impact rationale in your final report after source-linked discovery via `kb_search` or `kb_query(sourceFile=...)` and `kb_check`.",
192
- "- **Deferred/failed**: do not claim task completion."
197
+ "- **Deferred/failed**: do not claim task completion.",
193
198
  ].join("\n"),
194
199
  shownPaths,
195
200
  remainingCount,
@@ -30,7 +30,5 @@ export function buildDirtyRelevantFingerprint(values) {
30
30
  if (normalized.length === 0) {
31
31
  return "clean";
32
32
  }
33
- return createHash("sha256")
34
- .update(JSON.stringify(normalized))
35
- .digest("hex");
33
+ return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
36
34
  }
@@ -108,7 +108,6 @@ export function parseSymbolsYaml(content) {
108
108
  if (target === undefined)
109
109
  continue;
110
110
  pendingRel.target = target.trim();
111
- continue;
112
111
  }
113
112
  }
114
113
  }
@@ -142,7 +141,7 @@ function resolveDocPathIdentity(relPath, syncPaths) {
142
141
  // Strip glob from pattern to get the root dir prefix
143
142
  // e.g. "documentation/requirements/**/*.md" → "documentation/requirements"
144
143
  const rootDir = pattern.replace(/\/\*\*\/.*$/, "").replace(/\/+$/, "");
145
- if (normalizedRel.startsWith(rootDir + "/")) {
144
+ if (normalizedRel.startsWith(`${rootDir}/`)) {
146
145
  return entityId;
147
146
  }
148
147
  }
@@ -168,10 +167,7 @@ function normalizeFilePath(worktree, filePath) {
168
167
  const absPath = path.isAbsolute(filePath)
169
168
  ? filePath
170
169
  : path.resolve(worktree, filePath);
171
- return path
172
- .relative(worktree, absPath)
173
- .split(path.sep)
174
- .join("/");
170
+ return path.relative(worktree, absPath).split(path.sep).join("/");
175
171
  }
176
172
  // ── Public API ──────────────────────────────────────────────────────
177
173
  // implements REQ-opencode-file-context-guidance-v1
@@ -1,9 +1,9 @@
1
- import type { RepoPosture } from "./repo-posture.js";
2
- import type { PathKind } from "./path-kind.js";
3
- import type { RiskClass } from "./risk-classifier.js";
4
- import type { ReminderKind } from "./file-operation-state.js";
5
1
  import type { E2eCoverageSignal } from "./e2e-coverage-signals.js";
6
2
  import { type CheckpointEvidence, type EnforcementLifecycleEvent, type EnforcementPolicyResult } from "./enforcement-policy.js";
3
+ import type { ReminderKind } from "./file-operation-state.js";
4
+ import type { PathKind } from "./path-kind.js";
5
+ import type { RepoPosture } from "./repo-posture.js";
6
+ import type { RiskClass } from "./risk-classifier.js";
7
7
  import type { EffectiveMode } from "./smart-enforcement.js";
8
8
  import type { WorkContext } from "./work-context-resolver.js";
9
9
  export interface LinkedEntityResult {
@@ -37,11 +37,13 @@ export function createFileOperationState(opts) {
37
37
  return "edited";
38
38
  }
39
39
  // created|edited + deleted -> deleted
40
- if ((existing === "created" || existing === "edited") && incoming === "deleted") {
40
+ if ((existing === "created" || existing === "edited") &&
41
+ incoming === "deleted") {
41
42
  return "deleted";
42
43
  }
43
44
  // deleted + created|edited -> deleted
44
- if (existing === "deleted" && (incoming === "created" || incoming === "edited")) {
45
+ if (existing === "deleted" &&
46
+ (incoming === "created" || incoming === "edited")) {
45
47
  return "deleted";
46
48
  }
47
49
  // Fallback: use incoming
@@ -11,7 +11,6 @@
11
11
  * - "kb_check"
12
12
  * - "sequential" or similar ordering language
13
13
  */
14
- // implements REQ-opencode-kibi-briefing-v2
15
14
  export function buildInitKibiAlias() {
16
15
  const lines = [
17
16
  "# /init-kibi: Interactive Activation",
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
2
  import { createRequire } from "node:module";
3
+ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { buildInitKibiAlias } from "./init-kibi-alias.js";
6
6
  export const INIT_KIBI_COMMAND_NAME = "init-kibi";
@@ -97,7 +97,6 @@ function readPackageVersion(filePath) {
97
97
  return undefined;
98
98
  }
99
99
  }
100
- // implements REQ-opencode-kibi-briefing-v2
101
100
  export function findSdkPackageJsonForPluginRoot(pluginRoot) {
102
101
  const scopeRoot = path.dirname(pluginRoot);
103
102
  const candidates = [
@@ -151,7 +150,6 @@ function isRecord(value) {
151
150
  function isCommandMap(value) {
152
151
  return isRecord(value);
153
152
  }
154
- // implements REQ-opencode-kibi-briefing-v2
155
153
  export function detectInitKibiCommandCapability(input) {
156
154
  const { pluginVersion, pluginHooksDts, sdkTypesDts } = input;
157
155
  if (typeof pluginHooksDts !== "string" || pluginHooksDts.length === 0) {
@@ -171,7 +169,6 @@ export function detectInitKibiCommandCapability(input) {
171
169
  pluginVersion: pluginVersion ?? "unknown",
172
170
  };
173
171
  }
174
- // implements REQ-opencode-kibi-briefing-v2
175
172
  export function getInitKibiCommandCapability() {
176
173
  if (cachedCapability?.supported) {
177
174
  return cachedCapability;
@@ -179,7 +176,6 @@ export function getInitKibiCommandCapability() {
179
176
  cachedCapability = detectInitKibiCommandCapability(resolveHostCapabilityInputs());
180
177
  return cachedCapability;
181
178
  }
182
- // implements REQ-opencode-kibi-briefing-v2
183
179
  export function registerInitKibiCommand(configInput, capability = getInitKibiCommandCapability()) {
184
180
  if (!capability.supported) {
185
181
  return capability;
@@ -199,8 +199,12 @@ export function createKbFreshnessEvidenceStore() {
199
199
  kbMutation: record.kbMutation,
200
200
  kbCheck: record.kbCheck,
201
201
  ...(record.decision !== undefined ? { decision: record.decision } : {}),
202
- ...(record.rationale !== undefined ? { rationale: record.rationale } : {}),
203
- ...(record.checkRules !== undefined ? { checkRules: record.checkRules } : {}),
202
+ ...(record.rationale !== undefined
203
+ ? { rationale: record.rationale }
204
+ : {}),
205
+ ...(record.checkRules !== undefined
206
+ ? { checkRules: record.checkRules }
207
+ : {}),
204
208
  };
205
209
  },
206
210
  resetScope(scope) {
@@ -1,6 +1,6 @@
1
1
  import { type KibiConfig } from "./config.js";
2
- import { type EnforcementLifecycleEvent, type PolicyLinkedEntityResult } from "./enforcement-policy.js";
3
2
  import type { E2eCoverageSignal } from "./e2e-coverage-signals.js";
3
+ import { type EnforcementLifecycleEvent, type PolicyLinkedEntityResult } from "./enforcement-policy.js";
4
4
  import type { PathKind } from "./path-kind.js";
5
5
  import { type CheckRunner, type SyncRunMetadata, type SyncRunner, type TimeoutHandle } from "./scheduler.js";
6
6
  import type { WorkContext } from "./work-context-resolver.js";
@@ -1,7 +1,7 @@
1
1
  // implements REQ-opencode-worktree-hard-enforcement-v1
2
2
  import { DEFAULTS } from "./config.js";
3
- import { buildEnforcementScopeKey } from "./enforcement-scope.js";
4
3
  import { computeEnforcementPolicy, } from "./enforcement-policy.js";
4
+ import { buildEnforcementScopeKey } from "./enforcement-scope.js";
5
5
  import { createSyncScheduler, } from "./scheduler.js";
6
6
  const HARD_CHECKPOINT_TIMEOUT_MS = 30_000;
7
7
  const CHECKPOINT_REASON = "smart-enforcement.checkpoint";
@@ -69,7 +69,8 @@ export class KibiCheckpointRunner {
69
69
  if (context) {
70
70
  return this.passed.has(this.scopeKey(context, normalizedFingerprint));
71
71
  }
72
- return (this.passedScopeKeysByFingerprint.get(normalizedFingerprint)?.size ?? 0) > 0;
72
+ return ((this.passedScopeKeysByFingerprint.get(normalizedFingerprint)?.size ??
73
+ 0) > 0);
73
74
  }
74
75
  // implements REQ-opencode-worktree-hard-enforcement-v1
75
76
  async runCheckpoint(context, fingerprint) {
@@ -193,7 +194,9 @@ export class KibiCheckpointRunner {
193
194
  const policy = computeEnforcementPolicy({
194
195
  resolvedContext: context.workContext,
195
196
  effectiveMode: "hard",
196
- lifecycleEvents: context.lifecycleEvents ?? [defaultLifecycleEvent(context)],
197
+ lifecycleEvents: context.lifecycleEvents ?? [
198
+ defaultLifecycleEvent(context),
199
+ ],
197
200
  pathKinds: context.pathKinds ?? ["code"],
198
201
  linkedEntityResults: context.linkedEntityResults ?? [
199
202
  { ids: [], source: "none" },
@@ -73,6 +73,7 @@ const TEST_CUES = [
73
73
  * Analyze a comment or prose block and suggest most appropriate entity type.
74
74
  */
75
75
  export function classifyKnowledge(text) {
76
+ // implements REQ-opencode-kibi-plugin-v1
76
77
  if (!text || text.trim().length < 50) {
77
78
  return null;
78
79
  }
@@ -94,7 +95,7 @@ export function classifyKnowledge(text) {
94
95
  bestMatch = {
95
96
  type: "fact",
96
97
  confidence: factScore >= 3 ? "high" : "medium",
97
- reasoning: 'Contains strict domain fact cues (invariants, properties, limits, cardinalities) for the strict fact lane',
98
+ reasoning: "Contains strict domain fact cues (invariants, properties, limits, cardinalities) for the strict fact lane",
98
99
  };
99
100
  }
100
101
  if (reqScore > maxMatches) {
@@ -1,5 +1,5 @@
1
- import type { PathKind } from "./path-kind.js";
2
1
  import type { FileLifecycle } from "./file-operation-state.js";
2
+ import type { PathKind } from "./path-kind.js";
3
3
  import type { RiskClass } from "./risk-classifier.js";
4
4
  /**
5
5
  * Classification for whether a file change requires KB freshness evidence.
@@ -51,8 +51,7 @@ function isIgnoredPath(normalizedPath, pathKind) {
51
51
  // Special handling for .kb/ — only ignored when pathKind is NOT "kb".
52
52
  // Actual KB entity files (pathKind=kb) should still require evidence.
53
53
  if (pathKind !== "kb") {
54
- if (normalizedPath.startsWith(".kb/") ||
55
- normalizedPath.includes("/.kb/")) {
54
+ if (normalizedPath.startsWith(".kb/") || normalizedPath.includes("/.kb/")) {
56
55
  return true;
57
56
  }
58
57
  }
@@ -20,7 +20,6 @@ function isSmartEnforcementSyncReason(reason) {
20
20
  }
21
21
  const workspaceCacheState = new Map();
22
22
  export function resolveCurrentBranch(cwd) {
23
- // implements REQ-opencode-kibi-briefing-v3
24
23
  // 1. Check KIBI_BRANCH env var first (highest precedence)
25
24
  const envBranch = process.env.KIBI_BRANCH?.trim();
26
25
  if (envBranch && envBranch.length > 0) {