kibi-opencode 0.19.3 → 1.0.0

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 CHANGED
@@ -52,7 +52,7 @@ The plugin now uses a posture-aware, low-token smart-enforcement model before em
52
52
 
53
53
  - **Repo posture detection**: distinguishes `root_active`, `root_partial`, `root_uninitialized`, `vendored_only`, and `hybrid_root_plus_vendored`
54
54
  - **Risk classification**: separates `safe_docs_only`, `safe_test_only`, `kb_doc_structural`, `req_policy_candidate`, `behavior_candidate`, `traceability_candidate`, and `manual_kb_edit`
55
- - **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.
55
+ - **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 `.kb/symbols.yaml`. Skip on cache hit.
56
56
  - **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.
57
57
  - **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 capability-based recovery steps until the Kibi checkpoint passes. `maintenanceDegraded` overrides everything back to `advisory`.
58
58
  - **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)
@@ -155,9 +155,11 @@ The plugin injects guidance into OpenCode sessions to improve agent grounding. U
155
155
 
156
156
  ### Bootstrap Command
157
157
 
158
- OpenCode exposes Kibi MCP prompts as slash commands. The \`/init-kibi\` command triggers the \`kb_autopilot_generate\` workflow to assist in retroactive bootstrap. Agents use visible MCP tools when available, a trusted project-local CLI JSON route with `--input` when MCP is unavailable, and stop for operator action when neither interface is available.
158
+ OpenCode exposes the canonical bootstrap skill as `/kibi-bootstrap`. The
159
+ command routes an explicit request to the read-only planner and its typed
160
+ approval/apply workflow.
159
161
 
160
- OpenCode may show Kibi MCP tools with the configured server prefix. For example, canonical MCP names such as `kb_autopilot_generate`, `kb_upsert`, and `kb_check` can appear to agents as `kibi_kb_autopilot_generate`, `kibi_kb_upsert`, and `kibi_kb_check`. Use the visible `kibi_kb_*` identifier when OpenCode requires an exact tool name; it maps to the same MCP operation.
162
+ OpenCode may show Kibi MCP tools with the configured server prefix. For example, canonical MCP names such as `kb_plan_bootstrap`, `kb_upsert`, and `kb_check` can appear to agents as `kibi_kb_plan_bootstrap`, `kibi_kb_upsert`, and `kibi_kb_check`. Use the visible `kibi_kb_*` identifier when OpenCode requires an exact tool name; it maps to the same MCP operation.
161
163
 
162
164
  ### Discovery-first capability guidance
163
165
 
@@ -301,7 +303,7 @@ Disable specific features while keeping others:
301
303
 
302
304
  ## Troubleshooting
303
305
 
304
- If you see a false "workspace needs Kibi bootstrap" warning even though your workspace is already initialized with `.kb/config.json` pointing at relocated `kibi-docs/*` paths, this indicates a stale plugin cache. See [the main troubleshooting docs](../../docs/troubleshooting.md#opencode-shows-workspace-needs-kibi-bootstrap-before-the-tui) for recovery steps.
306
+ If you see a false "workspace needs Kibi bootstrap" warning even though `.kb/manifest.json` and the canonical knowledge lanes exist, this usually means a stale plugin cache. Leftover `.kb/config.json` custom paths are ignored. See [the main troubleshooting docs](../../docs/troubleshooting.md#opencode-shows-workspace-needs-kibi-bootstrap-before-the-tui) for recovery steps.
305
307
 
306
308
  ## Architecture
307
309
 
@@ -318,7 +320,7 @@ A proposed enhancement would inject Kibi context hints into file-read results (e
318
320
 
319
321
  1. OpenCode's current plugin surface does not expose file-content interception hooks
320
322
  2. The `experimental.chat.system.transform` hook only supports system prompt injection
321
- 3. Symbol metadata from `documentation/symbols.yaml` can inform this feature once host support exists
323
+ 3. Symbol metadata from `.kb/symbols.yaml` can inform this feature once host support exists
322
324
 
323
325
  Current workaround: static system prompt guidance directs agents to query Kibi explicitly.
324
326
 
@@ -335,7 +337,7 @@ The plugin provides proactive guidance when agents perform file operations:
335
337
  The OpenCode plugin respects the repository ignore policy used by Kibi's discovery pipeline. In practice this means:
336
338
 
337
339
  - The plugin's background sync and file-event handling skip paths matched by repository `.gitignore` files, nested `.gitignore` files, and `.git/info/exclude`.
338
- - 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`).
340
+ - The plugin also treats a curated set of tool/runtime directories as never-relevant for KB sync (for example: `.sisyphus`, `.opencode`, `.git`, `node_modules`, `vendor`, `third_party`). Derived `.kb/branches`, `.kb/recovery`, `.kb/verification`, and `.kb/briefs` trees are ignored; authored knowledge lanes under `.kb/` are not.
339
341
 
340
342
  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.
341
343
 
@@ -4,13 +4,13 @@ import * as path from "node:path";
4
4
  import { getFileLinkedTargetsByType } from "./file-entity-links.js";
5
5
  // ── TEST doc reader ──────────────────────────────────────────────
6
6
  //
7
- // Reads a TEST-*.md file from documentation/tests/ and extracts
7
+ // Reads a TEST-*.md file from .kb/tests/ and extracts
8
8
  // frontmatter tags, source, and body.
9
9
  function readTestDoc(worktree, testId) {
10
10
  // Try common locations for TEST docs
11
11
  const candidates = [
12
- `documentation/tests/${testId}.md`,
13
- `documentation/tests/${testId.toLowerCase()}.md`,
12
+ `.kb/tests/${testId}.md`,
13
+ `.kb/tests/${testId.toLowerCase()}.md`,
14
14
  ];
15
15
  for (const rel of candidates) {
16
16
  const fullPath = path.join(worktree, rel);
@@ -185,30 +185,20 @@ function hardBlockText(events) {
185
185
  const representativePath = events[0]?.normalizedPath ?? "<changed-file>";
186
186
  const evidenceNotes = [linkedIdsText(events), e2eEvidenceText(events)].filter((note) => note !== null);
187
187
  const deletionCleanup = deletedWithoutLinks
188
- ? "Deleted files without linked IDs still need sourceFile cleanup: use `kb_search` plus `kb_query` with `sourceFile` for the deleted path before deciding whether `kb_upsert` cleanup is needed."
189
- : "Use `kb_upsert` when traceability, relationships, or source-linked facts need updates.";
188
+ ? "Deleted paths without linked IDs still require a source-linked traceability review before cleanup."
189
+ : "Use the canonical traceability workflow when relationships or source-linked facts need updates.";
190
190
  return {
191
191
  text: [
192
192
  "🛑 **Hard Kibi checkpoint required**",
193
193
  "Changed relevant files require Kibi verification before continuing:",
194
194
  ...pathLines,
195
195
  ...evidenceNotes,
196
- "Capability-based checkpoint instructions:",
197
- "- MCP tools and the trusted project-local CLI are peer surfaces over the same 18 operations; choose by what is visible and approved here.",
198
- "- If Kibi MCP tools are visible and approved, use MCP.",
199
- "- Otherwise, in a trusted workspace, use the project-local CLI's dedicated JSON routes with `--input <file|->`.",
200
- "- If neither interface is available, Kibi operation is blocked; tell the operator to enable MCP or the trusted project-local CLI.",
201
- "- Do not infer MCP availability from config file existence. Do not read or edit `.kb/` files directly.",
202
- "- Query before mutate. Run `kb_upsert` sequentially. Run `kb_check` before completion.",
203
- "- Run `kb_search` to discover impacted requirements, tests, ADRs, and facts.",
204
- `- Run \`kb_query\` with \`sourceFile\` (example sourceFile: \"${representativePath}\") for each listed path.`,
205
- "- Run `kb_status` if branch or snapshot freshness matters.",
196
+ "- Inspect typed Kibi status `nextActions` and route to the canonical kibi-usage, kibi-freshness, or kibi-traceability skill.",
197
+ "- Use the host-visible approved Kibi peer surface; if it is unavailable, report the blocked capability.",
198
+ "- Never read or edit `.kb/` files directly.",
199
+ `- Preserve source-linked evidence for \`${representativePath}\` through the typed traceability workflow.`,
206
200
  `- ${deletionCleanup}`,
207
- "- Run `kb_check` before completing the task.",
208
- "KB freshness resolution:",
209
- "- **KB updated**: run `kb_search` for discovery, then `kb_upsert`/`kb_delete` for mutations, then `kb_check`.",
210
- "- **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`.",
211
- "- **Deferred/failed**: do not claim task completion.",
201
+ "- Follow typed check/status next actions before completing the task; unresolved or blocked state is not completion.",
212
202
  ].join("\n"),
213
203
  shownPaths,
214
204
  remainingCount,
@@ -139,14 +139,12 @@ function resolveDocPathIdentity(relPath, syncPaths) {
139
139
  if (!pattern)
140
140
  continue;
141
141
  // Strip glob from pattern to get the root dir prefix
142
- // e.g. "documentation/requirements/**/*.md" → "documentation/requirements"
142
+ // e.g. ".kb/requirements/**/*.md" → ".kb/requirements"
143
143
  const rootDir = pattern.replace(/\/\*\*\/.*$/, "").replace(/\/+$/, "");
144
144
  if (normalizedRel.startsWith(`${rootDir}/`)) {
145
145
  return entityId;
146
146
  }
147
147
  }
148
- // If no specific root matched but path starts with documentation/,
149
- // still accept (covers default configuration)
150
148
  return null;
151
149
  }
152
150
  // ── Symbols file resolution ─────────────────────────────────────────
@@ -1,12 +1,12 @@
1
- export declare function loadKbSyncPaths(cwd?: string): {
2
- requirements: string;
3
- scenarios: string;
4
- tests: string;
5
- adr: string;
6
- flags: string;
7
- events: string;
8
- facts: string;
9
- symbols: string;
1
+ export declare function loadKbSyncPaths(_cwd?: string): {
2
+ readonly requirements: ".kb/requirements/**/*.md";
3
+ readonly scenarios: ".kb/scenarios/**/*.md";
4
+ readonly tests: ".kb/tests/**/*.md";
5
+ readonly adr: ".kb/adr/**/*.md";
6
+ readonly flags: ".kb/flags/**/*.md";
7
+ readonly events: ".kb/events/**/*.md";
8
+ readonly facts: ".kb/facts/**/*.md";
9
+ readonly symbols: ".kb/symbols.yaml";
10
10
  };
11
11
  export interface KbExistenceTarget {
12
12
  key: string;
@@ -1,8 +1,6 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
1
  // implements REQ-opencode-kibi-plugin-v1
3
2
  import { createRequire } from "node:module";
4
3
  import * as path from "node:path";
5
- import { createRepoIgnorePolicy } from "kibi-cli/ignore-policy";
6
4
  const _require = createRequire(import.meta.url);
7
5
  // Lightweight fallback matcher if picomatch isn't installed.
8
6
  let picomatch;
@@ -23,40 +21,20 @@ catch {
23
21
  },
24
22
  };
25
23
  }
26
- // Local copy of DEFAULT_CONFIG.paths to avoid cross-package TS rootDir issues.
27
- // Must stay in sync with DEFAULT_CONFIG.paths in packages/cli/src/utils/config.ts.
28
- const DEFAULT_SYNC_PATHS = {
29
- requirements: "documentation/requirements/**/*.md",
30
- scenarios: "documentation/scenarios/**/*.md",
31
- tests: "documentation/tests/**/*.md",
32
- adr: "documentation/adr/**/*.md",
33
- flags: "documentation/flags/**/*.md",
34
- events: "documentation/events/**/*.md",
35
- facts: "documentation/facts/**/*.md",
36
- symbols: "documentation/symbols.yaml",
24
+ // Canonical Kibi knowledge paths — must stay in sync with
25
+ // packages/cli/src/utils/kb-paths.ts CANONICAL_ENTITY_PATHS.
26
+ const CANONICAL_KB_PATHS = {
27
+ requirements: ".kb/requirements/**/*.md",
28
+ scenarios: ".kb/scenarios/**/*.md",
29
+ tests: ".kb/tests/**/*.md",
30
+ adr: ".kb/adr/**/*.md",
31
+ flags: ".kb/flags/**/*.md",
32
+ events: ".kb/events/**/*.md",
33
+ facts: ".kb/facts/**/*.md",
34
+ symbols: ".kb/symbols.yaml",
37
35
  };
38
- function loadSyncConfigLocal(cwd = process.cwd()) {
39
- const configPath = path.join(cwd, ".kb/config.json");
40
- let userConfig = {};
41
- if (existsSync(configPath)) {
42
- try {
43
- userConfig = JSON.parse(readFileSync(configPath, "utf8")) || {};
44
- }
45
- catch {
46
- userConfig = {};
47
- }
48
- }
49
- return {
50
- paths: {
51
- ...DEFAULT_SYNC_PATHS,
52
- ...(userConfig.paths ?? {}),
53
- },
54
- defaultBranch: userConfig.defaultBranch,
55
- };
56
- }
57
- export function loadKbSyncPaths(cwd = process.cwd()) {
58
- const cfg = loadSyncConfigLocal(cwd);
59
- return cfg.paths ?? DEFAULT_SYNC_PATHS;
36
+ export function loadKbSyncPaths(_cwd = process.cwd()) {
37
+ return CANONICAL_KB_PATHS;
60
38
  }
61
39
  // implements REQ-opencode-kibi-plugin-v1
62
40
  export function getKbExistenceTargets(cwd = process.cwd()) {
@@ -117,32 +95,12 @@ function normalizePattern(p) {
117
95
  // otherwise treat directory as markdown collection
118
96
  return `${p.replace(/\/+$/, "")}/**/*.md`;
119
97
  }
120
- const DEFAULT_IGNORES = [
121
- ".kb/**",
122
- ".git/**",
123
- "node_modules/**",
124
- "dist/**",
125
- "coverage/**",
126
- ".opencode/**",
127
- "**/*~",
128
- "**/~*",
129
- "**/.#*",
130
- "**/*.swp",
131
- "**/*.swo",
132
- "**/.DS_Store",
133
- ];
134
98
  // implements REQ-opencode-kibi-plugin-v1
135
99
  export function shouldHandleFile(filePath, cwd = process.cwd()) {
136
100
  const rel = path.isAbsolute(filePath)
137
101
  ? path.relative(cwd, filePath).split(path.sep).join("/")
138
102
  : filePath.split(path.sep).join("/");
139
- // Check shared ignore policy (root .gitignore, nested .gitignore, .git/info/exclude,
140
- // and hard denylist such as .sisyphus) before other pattern checks.
141
- const policy = createRepoIgnorePolicy(cwd);
142
- if (policy.isIgnored(rel))
143
- return false;
144
103
  const paths = loadKbSyncPaths(cwd);
145
- // Build include patterns from kibi paths
146
104
  const includeCandidates = [
147
105
  paths.requirements,
148
106
  paths.scenarios,
@@ -156,21 +114,11 @@ export function shouldHandleFile(filePath, cwd = process.cwd()) {
156
114
  const includePatterns = includeCandidates
157
115
  .map(normalizePattern)
158
116
  .filter((p) => Boolean(p));
159
- // default ignores then allow extension by .kb/config.json -> sync.ignore (not implemented here)
160
- const ignorePatterns = DEFAULT_IGNORES;
161
- // Compile matchers
162
- const isIgnored = ignorePatterns.some((ig) => picomatch.isMatch(rel, ig));
163
- if (isIgnored)
164
- return false;
165
- // If any include pattern matches, accept
166
117
  const included = includePatterns.some((pat) => picomatch.isMatch(rel, pat));
167
- if (included)
118
+ if (included ||
119
+ (paths.symbols !== undefined &&
120
+ (paths.symbols === rel || picomatch.isMatch(rel, paths.symbols)))) {
168
121
  return true;
169
- // If symbols path is configured as exact file and matches exactly, accept
170
- if (paths.symbols) {
171
- const sym = paths.symbols;
172
- if (sym === rel || picomatch.isMatch(rel, sym))
173
- return true;
174
122
  }
175
123
  return false;
176
124
  }
@@ -0,0 +1,2 @@
1
+ /** Builds the host-local route to the canonical kibi-bootstrap skill. */
2
+ export declare function buildKibiBootstrapAlias(): string;
@@ -0,0 +1,14 @@
1
+ /** Builds the host-local route to the canonical kibi-bootstrap skill. */
2
+ // implements REQ-KIBI-BOOTSTRAP-PLAN
3
+ export function buildKibiBootstrapAlias() {
4
+ const lines = [
5
+ "# /kibi-bootstrap",
6
+ "",
7
+ "Bootstrap Kibi knowledge for an existing repository.",
8
+ "",
9
+ "## Route",
10
+ "If Kibi infrastructure is missing, run `kibi init` first.",
11
+ "Then invoke the canonical `kibi-bootstrap` skill. OpenCode may expose this route as a native command or as `/kibi:kibi-bootstrap:mcp`; use whichever host syntax is available.",
12
+ ];
13
+ return lines.join("\n");
14
+ }
@@ -0,0 +1,32 @@
1
+ export declare const KIBI_BOOTSTRAP_COMMAND_NAME = "kibi-bootstrap";
2
+ export declare const KIBI_BOOTSTRAP_COMMAND_TEMPLATE: string;
3
+ export declare const KIBI_BOOTSTRAP_COMMAND_DESCRIPTION = "Bootstrap Kibi knowledge for an existing repository.";
4
+ export interface OpenCodeCommandDefinition {
5
+ template: string;
6
+ description?: string;
7
+ agent?: string;
8
+ model?: string;
9
+ subtask?: boolean;
10
+ }
11
+ export interface OpenCodeConfigHookInput {
12
+ command?: Record<string, OpenCodeCommandDefinition>;
13
+ [key: string]: unknown;
14
+ }
15
+ export type KibiBootstrapCommandCapability = {
16
+ supported: true;
17
+ pluginVersion: string;
18
+ } | {
19
+ supported: false;
20
+ reason: string;
21
+ pluginVersion?: string;
22
+ };
23
+ interface KibiBootstrapCapabilityDetectionInput {
24
+ pluginVersion?: string;
25
+ pluginHooksDts?: string;
26
+ sdkTypesDts?: string;
27
+ }
28
+ export declare function findSdkPackageJsonForPluginRoot(pluginRoot: string): string | undefined;
29
+ export declare function detectKibiBootstrapCommandCapability(input: KibiBootstrapCapabilityDetectionInput): KibiBootstrapCommandCapability;
30
+ export declare function getKibiBootstrapCommandCapability(): KibiBootstrapCommandCapability;
31
+ export declare function registerKibiBootstrapCommand(configInput: unknown, capability?: KibiBootstrapCommandCapability): KibiBootstrapCommandCapability;
32
+ export {};
@@ -2,10 +2,10 @@ import fs from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { buildInitKibiAlias } from "./init-kibi-alias.js";
6
- export const INIT_KIBI_COMMAND_NAME = "init-kibi";
7
- export const INIT_KIBI_COMMAND_TEMPLATE = buildInitKibiAlias();
8
- export const INIT_KIBI_COMMAND_DESCRIPTION = "Run the Kibi interactive activation workflow."; // implements REQ-001
5
+ import { buildKibiBootstrapAlias } from "./kibi-bootstrap-alias.js";
6
+ export const KIBI_BOOTSTRAP_COMMAND_NAME = "kibi-bootstrap";
7
+ export const KIBI_BOOTSTRAP_COMMAND_TEMPLATE = buildKibiBootstrapAlias();
8
+ export const KIBI_BOOTSTRAP_COMMAND_DESCRIPTION = "Bootstrap Kibi knowledge for an existing repository."; // implements REQ-001
9
9
  const require = createRequire(import.meta.url);
10
10
  let cachedCapability = null;
11
11
  const initialProcessCwd = process.cwd();
@@ -150,7 +150,7 @@ function isRecord(value) {
150
150
  function isCommandMap(value) {
151
151
  return isRecord(value);
152
152
  }
153
- export function detectInitKibiCommandCapability(input) {
153
+ export function detectKibiBootstrapCommandCapability(input) {
154
154
  const { pluginVersion, pluginHooksDts, sdkTypesDts } = input;
155
155
  if (typeof pluginHooksDts !== "string" || pluginHooksDts.length === 0) {
156
156
  return buildUnsupportedReason(pluginVersion, "host Hooks definition is unavailable for config hook inspection.");
@@ -169,14 +169,15 @@ export function detectInitKibiCommandCapability(input) {
169
169
  pluginVersion: pluginVersion ?? "unknown",
170
170
  };
171
171
  }
172
- export function getInitKibiCommandCapability() {
172
+ export function getKibiBootstrapCommandCapability() {
173
173
  if (cachedCapability?.supported) {
174
174
  return cachedCapability;
175
175
  }
176
- cachedCapability = detectInitKibiCommandCapability(resolveHostCapabilityInputs());
176
+ cachedCapability = detectKibiBootstrapCommandCapability(resolveHostCapabilityInputs());
177
177
  return cachedCapability;
178
178
  }
179
- export function registerInitKibiCommand(configInput, capability = getInitKibiCommandCapability()) {
179
+ // implements REQ-KIBI-BOOTSTRAP-PLAN
180
+ export function registerKibiBootstrapCommand(configInput, capability = getKibiBootstrapCommandCapability()) {
180
181
  if (!capability.supported) {
181
182
  return capability;
182
183
  }
@@ -189,9 +190,9 @@ export function registerInitKibiCommand(configInput, capability = getInitKibiCom
189
190
  }
190
191
  configInput.command = {
191
192
  ...(existingCommands ?? {}),
192
- [INIT_KIBI_COMMAND_NAME]: {
193
- template: INIT_KIBI_COMMAND_TEMPLATE,
194
- description: INIT_KIBI_COMMAND_DESCRIPTION,
193
+ [KIBI_BOOTSTRAP_COMMAND_NAME]: {
194
+ template: KIBI_BOOTSTRAP_COMMAND_TEMPLATE,
195
+ description: KIBI_BOOTSTRAP_COMMAND_DESCRIPTION,
195
196
  },
196
197
  };
197
198
  return capability;
@@ -8,6 +8,26 @@ const IGNORED_DIRECTORIES = [
8
8
  "dist",
9
9
  "coverage",
10
10
  ];
11
+ /** Derived `.kb/` runtime trees — must stay in sync with kb-paths.ts DERIVED_KB_PREFIXES. */
12
+ const DERIVED_KB_PREFIXES = [
13
+ ".kb/branches",
14
+ ".kb/recovery",
15
+ ".kb/verification",
16
+ ".kb/briefs",
17
+ ".kb/migrations",
18
+ ];
19
+ function isDerivedKbPath(normalizedPath) {
20
+ for (const prefix of DERIVED_KB_PREFIXES) {
21
+ if (normalizedPath === prefix ||
22
+ normalizedPath.startsWith(`${prefix}/`) ||
23
+ normalizedPath.includes(`/${prefix}/`) ||
24
+ normalizedPath.endsWith(`/${prefix}`)) {
25
+ return true;
26
+ }
27
+ }
28
+ return (normalizedPath === ".kb/config.json" ||
29
+ normalizedPath.endsWith("/.kb/config.json"));
30
+ }
11
31
  // Filenames that are advisory-only (lockfiles).
12
32
  const ADVISORY_FILENAMES = new Set([
13
33
  "bun.lock",
@@ -35,7 +55,7 @@ const UPGRADE_RISK_CLASSES = new Set([
35
55
  "traceability_candidate",
36
56
  "req_policy_candidate",
37
57
  ]);
38
- function isIgnoredPath(normalizedPath, pathKind) {
58
+ function isIgnoredPath(normalizedPath) {
39
59
  // Check extension-based ignored patterns.
40
60
  if (normalizedPath.endsWith(".tsbuildinfo") ||
41
61
  normalizedPath.endsWith(".map")) {
@@ -48,12 +68,11 @@ function isIgnoredPath(normalizedPath, pathKind) {
48
68
  return true;
49
69
  }
50
70
  }
51
- // Special handling for .kb/ — only ignored when pathKind is NOT "kb".
52
- // Actual KB entity files (pathKind=kb) should still require evidence.
53
- if (pathKind !== "kb") {
54
- if (normalizedPath.startsWith(".kb/") || normalizedPath.includes("/.kb/")) {
55
- return true;
56
- }
71
+ // Derived `.kb/` runtime (stores, recovery, leftover config) is ignored.
72
+ // Canonical entity lanes, symbols, relationships, and the manifest are
73
+ // authored knowledge and still require evidence.
74
+ if (isDerivedKbPath(normalizedPath)) {
75
+ return true;
57
76
  }
58
77
  return false;
59
78
  }
@@ -77,7 +96,7 @@ function getFilename(normalizedPath) {
77
96
  export function classifyMeaningfulChange(params) {
78
97
  const { normalizedPath, pathKind, lifecycle: _lifecycle, riskClass } = params;
79
98
  // 1. IGNORED paths (for freshness contract)
80
- if (isIgnoredPath(normalizedPath, pathKind)) {
99
+ if (isIgnoredPath(normalizedPath)) {
81
100
  return "ignored";
82
101
  }
83
102
  // 2. ADVISORY paths (low-risk lockfiles)
package/dist/path-kind.js CHANGED
@@ -2,15 +2,16 @@
2
2
  import path from "node:path";
3
3
  const CODE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".py"];
4
4
  const KB_PREFIX = ".kb";
5
- const KIBI_DOC_PATTERNS = [
6
- "requirements/**",
7
- "scenarios/**",
8
- "tests/**",
9
- "adr/**",
10
- "flags/**",
11
- "events/**",
12
- "facts/**",
13
- "symbols.yaml",
5
+ const KIBI_LANE_KINDS = [
6
+ { prefix: "requirements/", kind: "requirement" },
7
+ { prefix: "scenarios/", kind: "scenario" },
8
+ { prefix: "tests/", kind: "test" },
9
+ { prefix: "adr/", kind: "adr" },
10
+ { prefix: "flags/", kind: "flag" },
11
+ { prefix: "events/", kind: "event" },
12
+ { prefix: "facts/", kind: "fact" },
13
+ { prefix: "symbols.yaml", kind: "symbol" },
14
+ { prefix: "symbol-coordinates.yaml", kind: "symbol" },
14
15
  ];
15
16
  export function analyzePath(
16
17
  // implements REQ-opencode-kibi-plugin-v1
@@ -21,38 +22,19 @@ filePath, cwd = process.cwd()) {
21
22
  let kind = "unknown";
22
23
  let isUnderKb = false;
23
24
  let isKibiDocRelevant = false;
24
- // Check if under .kb/**
25
- if (rel.startsWith(`${KB_PREFIX}/`)) {
26
- kind = "kb";
27
- isUnderKb = true;
28
- }
29
- // Check for Kibi doc paths
30
25
  const normalized = rel.toLowerCase();
31
- for (const pattern of KIBI_DOC_PATTERNS) {
32
- const patternPrefix = pattern.replace(/\*\*/g, "");
33
- const fullPathPattern = `documentation/${patternPrefix}`;
34
- if (normalized.startsWith(fullPathPattern)) {
35
- isKibiDocRelevant = true;
36
- if (kind === "unknown") {
37
- // Map to specific kind based on path
38
- if (patternPrefix.includes("requirements"))
39
- kind = "requirement";
40
- else if (patternPrefix.includes("scenarios"))
41
- kind = "scenario";
42
- else if (patternPrefix.includes("tests"))
43
- kind = "test";
44
- else if (patternPrefix.includes("adr"))
45
- kind = "adr";
46
- else if (patternPrefix.includes("facts"))
47
- kind = "fact";
48
- else if (patternPrefix.includes("events"))
49
- kind = "event";
50
- else if (patternPrefix.includes("flags"))
51
- kind = "flag";
52
- else if (patternPrefix.includes("symbols"))
53
- kind = "symbol";
26
+ if (normalized.startsWith(`${KB_PREFIX}/`)) {
27
+ isUnderKb = true;
28
+ const rest = normalized.slice(KB_PREFIX.length + 1);
29
+ for (const lane of KIBI_LANE_KINDS) {
30
+ if (rest === lane.prefix || rest.startsWith(lane.prefix)) {
31
+ kind = lane.kind;
32
+ isKibiDocRelevant = true;
33
+ break;
54
34
  }
55
- break;
35
+ }
36
+ if (kind === "unknown") {
37
+ kind = "kb";
56
38
  }
57
39
  }
58
40
  if (kind === "unknown") {
@@ -23,7 +23,7 @@ export function resolveCurrentBranch(cwd) {
23
23
  // 1. Check KIBI_BRANCH env var first (highest precedence)
24
24
  const envBranch = process.env.KIBI_BRANCH?.trim();
25
25
  if (envBranch && envBranch.length > 0) {
26
- return envBranch === "master" ? "main" : envBranch;
26
+ return envBranch;
27
27
  }
28
28
  // 2. Fall back to git branch
29
29
  try {
@@ -33,7 +33,7 @@ export function resolveCurrentBranch(cwd) {
33
33
  stdio: ["ignore", "pipe", "ignore"],
34
34
  timeout: 5000,
35
35
  }).trim();
36
- return branch === "master" ? "main" : branch;
36
+ return branch;
37
37
  }
38
38
  catch {
39
39
  return "unknown";
@@ -41,7 +41,7 @@ export function resolveCurrentBranch(cwd) {
41
41
  }
42
42
  function readConfigFingerprint(cwd) {
43
43
  try {
44
- return fs.readFileSync(path.join(cwd, ".kb", "config.json"), "utf-8");
44
+ return fs.readFileSync(path.join(cwd, ".kb", "manifest.json"), "utf-8");
45
45
  }
46
46
  catch {
47
47
  return "missing";
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type OpenCodeConfigHookInput } from "./init-kibi-capability.js";
1
+ import { type OpenCodeConfigHookInput } from "./kibi-bootstrap-capability.js";
2
2
  export interface PluginInput {
3
3
  worktree: string;
4
4
  directory: string;
package/dist/plugin.js CHANGED
@@ -7,8 +7,8 @@ import { getFileLinkedEntityIds } from "./file-entity-links.js"; // implements R
7
7
  import * as fileFilter from "./file-filter.js";
8
8
  import { deriveFileOperationReminder } from "./file-operation-reminders.js"; // implements REQ-opencode-file-context-guidance-v1
9
9
  import { createFileOperationState, } from "./file-operation-state.js"; // implements REQ-opencode-file-context-guidance-v1
10
- import { getInitKibiCommandCapability, registerInitKibiCommand, } from "./init-kibi-capability.js";
11
10
  import { createKbFreshnessEvidenceStore, evaluateKbFreshness, } from "./kb-freshness-state.js";
11
+ import { getKibiBootstrapCommandCapability, registerKibiBootstrapCommand, } from "./kibi-bootstrap-capability.js";
12
12
  import { KibiCheckpointRunner, } from "./kibi-checkpoint-runner.js";
13
13
  import * as logger from "./logger.js";
14
14
  import { classifyMeaningfulChange } from "./meaningful-change-classifier.js";
@@ -100,10 +100,10 @@ const kibiOpencodePlugin = async (input) => {
100
100
  }
101
101
  const { cfg, workspaceHealth, posture, cache, runtimeOverlay, scheduler: startupScheduler, maintenanceDegraded, getMaintenanceDegraded, getEffectiveMode, latchRuntimeDegraded, } = startup;
102
102
  const hooks = {};
103
- const initKibiCommandCapability = getInitKibiCommandCapability();
104
- if (initKibiCommandCapability.supported) {
103
+ const kibiBootstrapCommandCapability = getKibiBootstrapCommandCapability();
104
+ if (kibiBootstrapCommandCapability.supported) {
105
105
  hooks.config = async (configInput) => {
106
- registerInitKibiCommand(configInput, initKibiCommandCapability);
106
+ registerKibiBootstrapCommand(configInput, kibiBootstrapCommandCapability);
107
107
  };
108
108
  }
109
109
  // Plugin instance state (not module globals)
package/dist/prompt.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { CommentAnalysisResult } from "./comment-analysis.js";
2
2
  import type { KibiConfig } from "./config.js";
3
3
  import type { GuidanceCache } from "./guidance-cache.js";
4
- import { type InitKibiCommandCapability } from "./init-kibi-capability.js";
5
4
  import type { KbFreshnessEvaluation } from "./kb-freshness-state.js";
5
+ import { type KibiBootstrapCommandCapability } from "./kibi-bootstrap-capability.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";
@@ -55,13 +55,13 @@ export interface PromptContext {
55
55
  /** Paths changed for freshness evaluation (capped at 5) */
56
56
  freshnessChangedPaths?: string[];
57
57
  }
58
- export declare function postureGuidance(posture: RepoPosture, capability?: InitKibiCommandCapability): string | null;
58
+ export declare function postureGuidance(posture: RepoPosture, capability?: KibiBootstrapCommandCapability): string | null;
59
59
  /**
60
60
  * Build prompt with contextual guidance based on posture, risk class, and cache state.
61
61
  */
62
- export declare function buildPrompt(context?: PromptContext, capability?: InitKibiCommandCapability): string;
62
+ export declare function buildPrompt(context?: PromptContext, capability?: KibiBootstrapCommandCapability): string;
63
63
  /**
64
64
  * Inject prompt guidance if not already present.
65
65
  */
66
- export declare function injectPrompt(current: string, config: KibiConfig, context?: PromptContext, capability?: InitKibiCommandCapability): string;
66
+ export declare function injectPrompt(current: string, config: KibiConfig, context?: PromptContext, capability?: KibiBootstrapCommandCapability): string;
67
67
  export { SENTINEL };