kibi-opencode 0.19.4 → 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 +8 -6
- package/dist/e2e-coverage-signals.js +3 -3
- package/dist/enforcement-policy.js +7 -17
- package/dist/file-entity-links.js +1 -3
- package/dist/file-filter.d.ts +9 -9
- package/dist/file-filter.js +16 -68
- package/dist/kibi-bootstrap-alias.d.ts +2 -0
- package/dist/kibi-bootstrap-alias.js +14 -0
- package/dist/kibi-bootstrap-capability.d.ts +32 -0
- package/dist/{init-kibi-capability.js → kibi-bootstrap-capability.js} +12 -11
- package/dist/meaningful-change-classifier.js +27 -8
- package/dist/path-kind.js +21 -39
- package/dist/plugin-startup.js +3 -3
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +4 -4
- package/dist/prompt.d.ts +4 -4
- package/dist/prompt.js +24 -60
- package/dist/repo-posture.js +31 -84
- package/dist/risk-classifier.d.ts +3 -3
- package/dist/risk-classifier.js +9 -6
- package/dist/scheduler.js +2 -2
- package/dist/version-metadata.json +1 -1
- package/dist/work-context-resolver.js +2 -2
- package/dist/workspace-health.js +8 -56
- package/package.json +6 -4
- package/dist/init-kibi-alias.d.ts +0 -14
- package/dist/init-kibi-alias.js +0 -43
- package/dist/init-kibi-capability.d.ts +0 -32
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
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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`, `.
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
189
|
-
: "Use
|
|
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
|
-
"
|
|
197
|
-
"-
|
|
198
|
-
"-
|
|
199
|
-
|
|
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
|
-
"-
|
|
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. "
|
|
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 ─────────────────────────────────────────
|
package/dist/file-filter.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export declare function loadKbSyncPaths(
|
|
2
|
-
requirements:
|
|
3
|
-
scenarios:
|
|
4
|
-
tests:
|
|
5
|
-
adr:
|
|
6
|
-
flags:
|
|
7
|
-
events:
|
|
8
|
-
facts:
|
|
9
|
-
symbols:
|
|
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;
|
package/dist/file-filter.js
CHANGED
|
@@ -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
|
-
//
|
|
27
|
-
//
|
|
28
|
-
const
|
|
29
|
-
requirements: "
|
|
30
|
-
scenarios: "
|
|
31
|
-
tests: "
|
|
32
|
-
adr: "
|
|
33
|
-
flags: "
|
|
34
|
-
events: "
|
|
35
|
-
facts: "
|
|
36
|
-
symbols: "
|
|
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
|
|
39
|
-
|
|
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,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 {
|
|
6
|
-
export const
|
|
7
|
-
export const
|
|
8
|
-
export const
|
|
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
|
|
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
|
|
172
|
+
export function getKibiBootstrapCommandCapability() {
|
|
173
173
|
if (cachedCapability?.supported) {
|
|
174
174
|
return cachedCapability;
|
|
175
175
|
}
|
|
176
|
-
cachedCapability =
|
|
176
|
+
cachedCapability = detectKibiBootstrapCommandCapability(resolveHostCapabilityInputs());
|
|
177
177
|
return cachedCapability;
|
|
178
178
|
}
|
|
179
|
-
|
|
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
|
-
[
|
|
193
|
-
template:
|
|
194
|
-
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
|
|
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
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
35
|
+
}
|
|
36
|
+
if (kind === "unknown") {
|
|
37
|
+
kind = "kb";
|
|
56
38
|
}
|
|
57
39
|
}
|
|
58
40
|
if (kind === "unknown") {
|
package/dist/plugin-startup.js
CHANGED
|
@@ -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
|
|
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
|
|
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", "
|
|
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
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
|
|
104
|
-
if (
|
|
103
|
+
const kibiBootstrapCommandCapability = getKibiBootstrapCommandCapability();
|
|
104
|
+
if (kibiBootstrapCommandCapability.supported) {
|
|
105
105
|
hooks.config = async (configInput) => {
|
|
106
|
-
|
|
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?:
|
|
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?:
|
|
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?:
|
|
66
|
+
export declare function injectPrompt(current: string, config: KibiConfig, context?: PromptContext, capability?: KibiBootstrapCommandCapability): string;
|
|
67
67
|
export { SENTINEL };
|