kibi-opencode 0.7.2 → 0.8.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 +14 -4
- package/dist/index.js +3 -1
- package/dist/knowledge-classifier.js +1 -1
- package/dist/plugin-startup.js +11 -1
- package/dist/prompt.js +42 -18
- package/dist/scheduler.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ The plugin now uses a posture-aware, low-token smart-enforcement model before em
|
|
|
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
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.
|
|
28
29
|
- **Effective mode gating**: `strict` is only possible for `root_active` and `hybrid_root_plus_vendored` when `requireRootKbForStrict` is enabled; `maintenanceDegraded` overrides everything back to `advisory`
|
|
29
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)
|
|
30
31
|
- **Completion reminder**: when `completionReminder` is enabled, risky code edits append a single prompt-visible `kb_check` reminder exactly once per cached context
|
|
@@ -45,10 +46,10 @@ The plugin provides context-aware prompt guidance based on recent edits and work
|
|
|
45
46
|
|
|
46
47
|
After KB-document edits, the plugin queues targeted validation rules to run via background sync operations:
|
|
47
48
|
|
|
48
|
-
- **Must-priority requirement edits**: elevated validation including coverage checks (`must-priority-coverage`)
|
|
49
|
+
- **Must-priority requirement edits**: elevated validation including coverage checks (`must-priority-coverage`) and `strict-req-fact-pairing`
|
|
49
50
|
- **Traceability candidate code edits**: schedules `symbol-traceability` via reason `smart-enforcement.traceability`
|
|
50
51
|
- **Fact KB doc edits**: includes `strict-fact-shape` validation alongside standard structural checks
|
|
51
|
-
- **
|
|
52
|
+
- **Requirement KB doc edits**: includes `strict-req-fact-pairing` validation alongside standard structural checks
|
|
52
53
|
|
|
53
54
|
The plugin inspects requirement frontmatter to detect `priority: must` and schedules elevated validation for critical requirements. Runs in background after sync completes, non-blocking. Can be disabled via `guidance.targetedChecks.enabled: false`.
|
|
54
55
|
|
|
@@ -87,9 +88,9 @@ When editing code files, the plugin analyzes long comments and docstrings for du
|
|
|
87
88
|
|
|
88
89
|
- **Supported languages**: JavaScript/TypeScript (`//`, `/* */`, `/** */`) and Python (`#` blocks, true docstrings)
|
|
89
90
|
- **Smart filtering**: Only analyzes comments above `guidance.commentDetection.minLines` threshold
|
|
90
|
-
- **Classification**: Automatically categorizes as FACT (invariants/limits), ADR (decisions/tradeoffs), REQ (behavior), SCEN (flows), or TEST (verification)
|
|
91
|
+
- **Classification**: Automatically categorizes as FACT (strict domain facts: invariants/limits/cardinalities), ADR (decisions/tradeoffs), REQ (behavior), SCEN (flows), or TEST (verification)
|
|
91
92
|
- **Specific routing guidance**: Injects targeted prompts based on classification:
|
|
92
|
-
- FACT: "This looks like a domain invariant; route to a FACT via Kibi"
|
|
93
|
+
- FACT: "This looks like a strict domain fact (invariant/property/limit); route to a FACT via Kibi. Bug/workaround notes use `fact_kind: observation` or `meta`."
|
|
93
94
|
- ADR: "This looks like decision rationale; route to an ADR"
|
|
94
95
|
- REQ: "This looks like behavior intent; route to a REQ"
|
|
95
96
|
- **Deduplication**: Tracks seen comments by fingerprint to avoid repeated guidance
|
|
@@ -111,6 +112,15 @@ The plugin injects guidance into OpenCode sessions to improve agent grounding. U
|
|
|
111
112
|
### Bootstrap Command
|
|
112
113
|
|
|
113
114
|
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.
|
|
115
|
+
|
|
116
|
+
### Start-Task Briefing
|
|
117
|
+
|
|
118
|
+
Use `/brief-kibi` at the start of authoritative risky edit work when the prompt hints for it.
|
|
119
|
+
|
|
120
|
+
- The plugin only hints about `/brief-kibi` inside smart-enforcement guidance.
|
|
121
|
+
- The actual briefing content is generated by the registered MCP prompt/tool, not by the prompt hook.
|
|
122
|
+
- Live automatic briefing fetch from `experimental.chat.system.transform` is deferred; the hook remains text-only.
|
|
123
|
+
|
|
114
124
|
### Discovery-first MCP guidance
|
|
115
125
|
|
|
116
126
|
Agent-visible guidance is intentionally limited to the curated public MCP surface:
|
package/dist/index.js
CHANGED
|
@@ -143,6 +143,7 @@ const kibiOpencodePlugin = async (input) => {
|
|
|
143
143
|
"required-fields",
|
|
144
144
|
"no-dangling-refs",
|
|
145
145
|
...(pathAnalysis.kind === "fact" ? ["strict-fact-shape"] : []),
|
|
146
|
+
...(pathAnalysis.kind === "requirement" ? ["strict-req-fact-pairing"] : []),
|
|
146
147
|
]
|
|
147
148
|
: null;
|
|
148
149
|
const checkRules = traceabilityRules ?? kbStructuralRules;
|
|
@@ -260,11 +261,12 @@ const kibiOpencodePlugin = async (input) => {
|
|
|
260
261
|
"required-fields",
|
|
261
262
|
"no-dangling-refs",
|
|
262
263
|
"must-priority-coverage",
|
|
264
|
+
"strict-req-fact-pairing",
|
|
263
265
|
];
|
|
264
266
|
logger.info(`kibi-opencode: must-priority requirement detected, scheduling elevated checks for ${filePath}`);
|
|
265
267
|
}
|
|
266
268
|
else {
|
|
267
|
-
checkRules = ["required-fields", "no-dangling-refs"];
|
|
269
|
+
checkRules = ["required-fields", "no-dangling-refs", "strict-req-fact-pairing"];
|
|
268
270
|
}
|
|
269
271
|
}
|
|
270
272
|
logger.info("smart-enforcement.targeted-checks", {
|
|
@@ -94,7 +94,7 @@ export function classifyKnowledge(text) {
|
|
|
94
94
|
bestMatch = {
|
|
95
95
|
type: "fact",
|
|
96
96
|
confidence: factScore >= 3 ? "high" : "medium",
|
|
97
|
-
reasoning: 'Contains domain
|
|
97
|
+
reasoning: 'Contains strict domain fact cues (invariants, properties, limits, cardinalities) for the strict fact lane',
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
if (reqScore > maxMatches) {
|
package/dist/plugin-startup.js
CHANGED
|
@@ -9,6 +9,15 @@ import { createSyncScheduler } from "./scheduler.js";
|
|
|
9
9
|
import { getSessionTracker } from "./session-tracker.js";
|
|
10
10
|
import { computeEffectiveMode, } from "./smart-enforcement.js";
|
|
11
11
|
import { checkWorkspaceHealth } from "./workspace-health.js";
|
|
12
|
+
function isSmartEnforcementSyncReason(reason) {
|
|
13
|
+
if (!reason) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const normalizedReason = reason.endsWith(".trailing")
|
|
17
|
+
? reason.slice(0, -".trailing".length)
|
|
18
|
+
: reason;
|
|
19
|
+
return normalizedReason.startsWith("smart-enforcement.");
|
|
20
|
+
}
|
|
12
21
|
const workspaceCacheState = new Map();
|
|
13
22
|
function resolveCurrentBranch(cwd) {
|
|
14
23
|
try {
|
|
@@ -148,7 +157,8 @@ export async function runPluginStartup(input) {
|
|
|
148
157
|
worktree: input.worktree,
|
|
149
158
|
config: cfg,
|
|
150
159
|
onRunComplete: (meta) => {
|
|
151
|
-
if (meta.exitCode !== 0
|
|
160
|
+
if (meta.exitCode !== 0 &&
|
|
161
|
+
!isSmartEnforcementSyncReason(meta.reason))
|
|
152
162
|
latchRuntimeDegraded("scheduler_sync_failed");
|
|
153
163
|
if (meta.checkExitCode !== undefined &&
|
|
154
164
|
meta.checkExitCode !== 0) {
|
package/dist/prompt.js
CHANGED
|
@@ -5,6 +5,10 @@ const SENTINEL = "<!-- kibi-opencode -->";
|
|
|
5
5
|
// ── Token budget enforcement ───────────────────────────────────────────
|
|
6
6
|
const MAX_BULLETS = 5;
|
|
7
7
|
const MAX_WORDS = 117; // Reserve 3 words for sentinel so total injected prompt stays ≤ 120
|
|
8
|
+
const AUTHORITATIVE_POSTURES = [
|
|
9
|
+
"root_active",
|
|
10
|
+
"hybrid_root_plus_vendored",
|
|
11
|
+
];
|
|
8
12
|
function countWords(text) {
|
|
9
13
|
return text.split(/\s+/).filter(Boolean).length;
|
|
10
14
|
}
|
|
@@ -35,6 +39,15 @@ function enforceBudget(block) {
|
|
|
35
39
|
}
|
|
36
40
|
return block;
|
|
37
41
|
}
|
|
42
|
+
function insertBulletAfterHeader(block, bullet) {
|
|
43
|
+
const headerEnd = block.indexOf("\n");
|
|
44
|
+
if (headerEnd === -1)
|
|
45
|
+
return `${block}\n${bullet}`;
|
|
46
|
+
return `${block.slice(0, headerEnd + 1)}${bullet}\n${block.slice(headerEnd + 1)}`;
|
|
47
|
+
}
|
|
48
|
+
function isAuthoritativePosture(posture) {
|
|
49
|
+
return AUTHORITATIVE_POSTURES.includes(posture);
|
|
50
|
+
}
|
|
38
51
|
// ── File bucket derivation ─────────────────────────────────────────────
|
|
39
52
|
function deriveFileBucket(pathKind) {
|
|
40
53
|
return pathKind;
|
|
@@ -56,12 +69,15 @@ Requirement edits need policy alignment. Run kb_check with required-fields and n
|
|
|
56
69
|
- Add verification: create or update linked SCEN and TEST entities`,
|
|
57
70
|
behavior_candidate: `📝 **Code changes detected**
|
|
58
71
|
|
|
59
|
-
Production code: use \`implements\` (symbol→req) for requirement ownership. Test code: use \`executable_for\` (symbol→test).
|
|
72
|
+
Production code: use \`implements\` (symbol→req) for requirement ownership. Test code: use \`executable_for\` (symbol→test).
|
|
73
|
+
- \`covered_by\` is coverage evidence only
|
|
74
|
+
- Prefer scenario-first: req→scenario→test when scenarios exist`,
|
|
60
75
|
traceability_candidate: `📝 **Code changes detected**
|
|
61
76
|
|
|
62
|
-
Production code: use \`implements\` (symbol→req) for requirement ownership. Test code: use \`executable_for\` (symbol→test).
|
|
63
|
-
-
|
|
64
|
-
-
|
|
77
|
+
Production code: use \`implements\` (symbol→req) for requirement ownership. Test code: use \`executable_for\` (symbol→test).
|
|
78
|
+
- \`covered_by\` is coverage evidence only
|
|
79
|
+
- Prefer scenario-first: req→scenario→test when scenarios exist
|
|
80
|
+
- Route durable knowledge comments to KB entities, not inline comments`,
|
|
65
81
|
manual_kb_edit: `⚠️ **WARNING: Direct .kb/ edits bypass validation**
|
|
66
82
|
|
|
67
83
|
The Kibi knowledge base is managed through public MCP tools. Direct manual edits to .kb/** can cause inconsistencies.
|
|
@@ -196,7 +212,7 @@ Before implementing or explaining code:
|
|
|
196
212
|
4. **Add traceability** - Production code: \`implements\` (symbol→req) for ownership. Test code: \`executable_for\`. \`covered_by\` is coverage evidence only for production symbols.
|
|
197
213
|
|
|
198
214
|
If you're adding long explanatory comments, consider routing that knowledge to:
|
|
199
|
-
- \`FACT\` for domain invariants, properties, limits, cardinalities
|
|
215
|
+
- \`FACT\` for strict domain facts (invariants, properties, limits, cardinalities); bug/workaround notes use \`fact_kind: observation\` or \`meta\`
|
|
200
216
|
- \`ADR\` for technical decisions, tradeoffs, rationale
|
|
201
217
|
- \`REQ\` for system behavior requirements`;
|
|
202
218
|
}
|
|
@@ -209,6 +225,13 @@ If you're adding long explanatory comments, consider routing that knowledge to:
|
|
|
209
225
|
}
|
|
210
226
|
}
|
|
211
227
|
} // closing brace for Priority 2-4 else block starting at 187
|
|
228
|
+
if (selectedBlock &&
|
|
229
|
+
(riskClass === "behavior_candidate" ||
|
|
230
|
+
riskClass === "traceability_candidate") &&
|
|
231
|
+
isAuthoritativePosture(posture) &&
|
|
232
|
+
!context.maintenanceDegraded) {
|
|
233
|
+
selectedBlock = insertBulletAfterHeader(selectedBlock, "- Authoritative risky edit: run `/brief-kibi` before acting.");
|
|
234
|
+
}
|
|
212
235
|
// Source-linked micro-brief: insert after header line for code risk classes
|
|
213
236
|
// Inserting after the header (not prepending before it) preserves the header
|
|
214
237
|
// under enforceBudget's trimming logic, which only collects non-bullet lines
|
|
@@ -226,10 +249,7 @@ If you're adding long explanatory comments, consider routing that knowledge to:
|
|
|
226
249
|
: path.join(context.workspaceRoot, editedPath);
|
|
227
250
|
const linkedIds = getSourceLinkedRequirementIds(context.workspaceRoot, absEdited);
|
|
228
251
|
if (linkedIds.length >= 1 && linkedIds.length <= 3) {
|
|
229
|
-
|
|
230
|
-
if (headerEnd !== -1) {
|
|
231
|
-
selectedBlock = `${selectedBlock.slice(0, headerEnd + 1)}- Existing Kibi links: ${linkedIds.join(", ")}\n${selectedBlock.slice(headerEnd + 1)}`;
|
|
232
|
-
}
|
|
252
|
+
selectedBlock = insertBulletAfterHeader(selectedBlock, `- Existing Kibi links: ${linkedIds.join(", ")}`);
|
|
233
253
|
}
|
|
234
254
|
}
|
|
235
255
|
}
|
|
@@ -266,24 +286,28 @@ The Kibi workspace is in a maintenance-degraded state. Guidance remains advisory
|
|
|
266
286
|
};
|
|
267
287
|
context.cache.recordSatisfied(key, "guidance");
|
|
268
288
|
}
|
|
289
|
+
// Apply budget enforcement before appending the completion reminder so the
|
|
290
|
+
// reminder bullet is never silently trimmed when bullet count exceeds MAX_BULLETS.
|
|
291
|
+
const budgeted = selectedBlock ? enforceBudget(selectedBlock) : null;
|
|
269
292
|
// Append completion reminder for risky classes when enabled
|
|
270
293
|
const REMINDER_RISK_CLASSES = [
|
|
271
294
|
"behavior_candidate",
|
|
272
295
|
"traceability_candidate",
|
|
273
296
|
"req_policy_candidate",
|
|
274
297
|
];
|
|
275
|
-
|
|
298
|
+
let finalBlock = budgeted;
|
|
299
|
+
if (finalBlock &&
|
|
276
300
|
context.completionReminder === true &&
|
|
277
301
|
!context.maintenanceDegraded &&
|
|
278
302
|
riskClass &&
|
|
279
303
|
REMINDER_RISK_CLASSES.includes(riskClass) &&
|
|
280
304
|
posture !== "root_uninitialized" &&
|
|
281
305
|
posture !== "root_partial") {
|
|
282
|
-
|
|
306
|
+
finalBlock = `${finalBlock}\n- Run \`kb_check\` before completing this task.`;
|
|
283
307
|
}
|
|
284
308
|
// Return: sentinel + one targeted block (or just sentinel if no block)
|
|
285
|
-
return
|
|
286
|
-
? `${SENTINEL}\n\n${
|
|
309
|
+
return finalBlock
|
|
310
|
+
? `${SENTINEL}\n\n${finalBlock}`
|
|
287
311
|
: SENTINEL;
|
|
288
312
|
}
|
|
289
313
|
// ── Comment suggestion guidance (legacy compat) ────────────────────────
|
|
@@ -292,14 +316,14 @@ function buildCommentSuggestionGuidance(suggestion) {
|
|
|
292
316
|
case "fact":
|
|
293
317
|
return `🎯 **Durable knowledge detected: FACT**
|
|
294
318
|
|
|
295
|
-
Your recent code edit contains a comment that looks like a **domain
|
|
319
|
+
Your recent code edit contains a comment that looks like a **strict domain fact** (invariants, properties, limits, defaults, or cardinality constraints).
|
|
296
320
|
|
|
297
|
-
**Action**:
|
|
298
|
-
- Create \`documentation/facts/FACT-xxx.md\` with the invariant
|
|
321
|
+
**Action**: Route to a FACT entity in the strict fact lane:
|
|
322
|
+
- Create \`documentation/facts/FACT-xxx.md\` with the invariant (use \`constrains\` + \`requires_property\` for contradiction-safe reasoning)
|
|
323
|
+
- Bug/workaround notes: use \`fact_kind: observation\` or \`meta\` instead — these are non-blocking and excluded from contradiction inference
|
|
299
324
|
- Link it to relevant requirements
|
|
300
|
-
- Reference the FACT in code with a comment
|
|
301
325
|
|
|
302
|
-
This keeps domain truths centralized and
|
|
326
|
+
This keeps domain truths centralized, searchable, and contradiction-safe.`;
|
|
303
327
|
case "adr":
|
|
304
328
|
return `🎯 **Durable knowledge detected: ADR**
|
|
305
329
|
|
package/dist/scheduler.js
CHANGED
|
@@ -151,6 +151,10 @@ class WorktreeSyncScheduler {
|
|
|
151
151
|
}
|
|
152
152
|
emitCompletion(trigger, startedAt, exitCode, checkExitCode, checkRules) {
|
|
153
153
|
const durationMs = Math.max(0, this.now() - startedAt);
|
|
154
|
+
const normalizedReason = trigger.reason.endsWith(".trailing")
|
|
155
|
+
? trigger.reason.slice(0, -".trailing".length)
|
|
156
|
+
: trigger.reason;
|
|
157
|
+
const isSmartEnforcementSync = normalizedReason.startsWith("smart-enforcement.");
|
|
154
158
|
const meta = {
|
|
155
159
|
reason: trigger.reason,
|
|
156
160
|
worktree: this.worktree,
|
|
@@ -164,6 +168,9 @@ class WorktreeSyncScheduler {
|
|
|
164
168
|
if (exitCode === 0) {
|
|
165
169
|
logger.info(`sync.succeeded ${JSON.stringify(meta)}`);
|
|
166
170
|
}
|
|
171
|
+
else if (isSmartEnforcementSync) {
|
|
172
|
+
logger.errorStructuredOnly(`sync.failed ${JSON.stringify(meta)}`);
|
|
173
|
+
}
|
|
167
174
|
else {
|
|
168
175
|
logger.error(`sync.failed ${JSON.stringify(meta)}`);
|
|
169
176
|
}
|