kibi-opencode 0.16.0 → 0.18.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
@@ -56,7 +56,7 @@ The plugin now uses a posture-aware, low-token smart-enforcement model before em
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 a stop-state with MCP 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)
59
- - **Completion reminder**: when `completionReminder` is enabled, risky code edits append a single prompt-visible `kb_check` reminder exactly once per cached context
59
+ - **Completion reminder**: when `completionReminder` is enabled, risky code edits append a single prompt-visible impact-enabled `kb_check` reminder exactly once per cached context
60
60
  - **Runtime maintenance overlay**: static `maintenanceDegraded` from posture is merged with a latched runtime overlay (sync disabled/unavailable/failing) so degraded state is consistently reflected in prompts, logs, and mode decisions
61
61
  - **Three-mode enforcement**: `advisory` by default keeps guidance non-blocking; opt-in `strict` adds elevated validation cues; `hard` blocks the prompt for authoritative workspaces until the checkpoint cycle succeeds. Git hooks and KB validation checks remain the durable enforcement boundary for all modes.
62
62
  - **Structured observability**: posture, risk, cache, degraded-mode, targeted-check, and guidance events flow through structured plugin logs
@@ -75,11 +75,13 @@ modified, the plugin evaluates KB freshness evidence and surfaces a visible
75
75
  discovery via `kb_search` or `kb_query(sourceFile=...)` and `kb_check`.
76
76
  - **Deferred/Failed**: Do not claim task completion.
77
77
 
78
+ For meaningful source edits, the first gate is MCP `kb_check` with impact options, for example `kb_check({sourceFiles:["src/feature.ts"], includeImpactDiagnostics:true, includeWorkingTreeDiff:true})`. These diagnostics guide symbol granularity, requirement ownership, and semantic review of linked requirements/tests; they do not prove source behavior matches prose.
79
+
78
80
  ### Dynamic Contextual Guidance
79
81
 
80
82
  The plugin provides context-aware prompt guidance based on recent edits and workspace state:
81
83
 
82
- - **Code edits**: Guidance for querying Kibi by sourceFile, preferring Kibi over comments, and adding `// implements REQ-xxx` traceability
84
+ - **Code edits**: Guidance for running impact-enabled `kb_check`, querying Kibi by sourceFile, preferring Kibi over comments, and adding or updating symbol traceability
83
85
  - **Requirement edits**: Guidance for maintaining separate REQ/SCEN/TEST artifacts and avoiding embedded scenarios
84
86
  - **KB doc edits**: Guidance for proper entity relationships and validation
85
87
  - **Bootstrap needed**: Detection and nudges for uninitialized repos
@@ -25,6 +25,7 @@ const ADVISORY_EDIT_KINDS = new Set([
25
25
  "symbol",
26
26
  "kb",
27
27
  ]);
28
+ const SOURCE_IMPACT_KINDS = new Set(["code"]);
28
29
  function normalizeCheckpointEvidence(evidence) {
29
30
  if (typeof evidence === "boolean") {
30
31
  return evidence;
@@ -126,6 +127,13 @@ function advisoryText(event) {
126
127
  return "- New file detected. Add or update the necessary Kibi entities and traceability before completing this task.";
127
128
  }
128
129
  if (event.lifecycle === "edited") {
130
+ if (SOURCE_IMPACT_KINDS.has(event.pathKind)) {
131
+ const impactCheckCall = `kb_check({sourceFiles:${JSON.stringify([event.normalizedPath])}, includeImpactDiagnostics:true, includeWorkingTreeDiff:true})`;
132
+ return [
133
+ `- Edited source file detected. Run \`${impactCheckCall}\` while the edit context is fresh.`,
134
+ " Use the impact diagnostics to review symbol granularity, requirement ownership, and semantic review of linked requirements/tests before completing this task.",
135
+ ].join("\n");
136
+ }
129
137
  return `- Edited file detected. Review Kibi traceability for ${event.normalizedPath} before completing this task.`;
130
138
  }
131
139
  const ids = event.linkedEntityResult.ids;
@@ -24,7 +24,9 @@ export type SyncRunnerResult = {
24
24
  };
25
25
  export type SyncRunner = (worktree: string) => Promise<SyncRunnerResult>;
26
26
  export type CheckRunner = (worktree: string, rules: string[]) => Promise<{
27
- exitCode: number;
27
+ readonly exitCode: number;
28
+ readonly stdout?: string;
29
+ readonly stderr?: string;
28
30
  }>;
29
31
  export interface SchedulerOptions {
30
32
  worktree: string;
package/dist/scheduler.js CHANGED
@@ -154,6 +154,7 @@ class WorktreeSyncScheduler {
154
154
  }
155
155
  else {
156
156
  logger.info(`check.succeeded ${JSON.stringify({ rules: checkRules })}`);
157
+ logAdvisoryQualityDiagnostics(checkRules, checkResult.stdout, checkResult.stderr);
157
158
  }
158
159
  }
159
160
  }
@@ -280,11 +281,84 @@ async function runKibiSync(worktree) {
280
281
  async function runKibiCheck(worktree, rules) {
281
282
  return new Promise((resolve) => {
282
283
  const rulesArg = rules.join(",");
283
- exec(`kibi check --rules ${rulesArg}`, { cwd: worktree }, (error) => {
284
- resolve({ exitCode: error ? (error.code ?? 1) : 0 });
284
+ exec(`kibi check --rules ${rulesArg} --format json`, { cwd: worktree }, (error, stdout, stderr) => {
285
+ const truncatedOut = truncateSyncOutput(stdout || undefined);
286
+ const truncatedErr = truncateSyncOutput(stderr || undefined);
287
+ resolve({
288
+ exitCode: error ? (error.code ?? 1) : 0,
289
+ ...(truncatedOut !== undefined ? { stdout: truncatedOut } : {}),
290
+ ...(truncatedErr !== undefined ? { stderr: truncatedErr } : {}),
291
+ });
285
292
  });
286
293
  });
287
294
  }
295
+ function logAdvisoryQualityDiagnostics(rules, stdout, stderr) {
296
+ const diagnostics = parseQualityDiagnostics(stdout, stderr).filter((diagnostic) => !diagnostic.blocking);
297
+ if (diagnostics.length === 0)
298
+ return;
299
+ const severityCounts = new Map();
300
+ for (const diagnostic of diagnostics) {
301
+ severityCounts.set(diagnostic.severity, (severityCounts.get(diagnostic.severity) ?? 0) + 1);
302
+ }
303
+ const first = diagnostics[0];
304
+ if (first === undefined)
305
+ return;
306
+ logger.warn(`check.advisory_quality ${JSON.stringify({
307
+ rules,
308
+ count: diagnostics.length,
309
+ ...Object.fromEntries(severityCounts),
310
+ firstId: first.id,
311
+ firstMessage: first.message,
312
+ })}`);
313
+ }
314
+ function parseQualityDiagnostics(stdout, stderr) {
315
+ for (const output of [stdout, stderr]) {
316
+ if (output === undefined || output.trim().length === 0)
317
+ continue;
318
+ try {
319
+ const parsed = JSON.parse(output);
320
+ const candidates = qualityDiagnosticCandidates(parsed);
321
+ const diagnostics = candidates.flatMap((candidate) => parseQualityDiagnostic(candidate) ?? []);
322
+ if (diagnostics.length > 0)
323
+ return diagnostics;
324
+ }
325
+ catch (error) {
326
+ if (error instanceof SyntaxError)
327
+ continue;
328
+ throw error;
329
+ }
330
+ }
331
+ return [];
332
+ }
333
+ function qualityDiagnosticCandidates(value) {
334
+ if (!isRecord(value))
335
+ return [];
336
+ const structuredContent = value.structuredContent;
337
+ if (isRecord(structuredContent)) {
338
+ const diagnostics = structuredContent.qualityDiagnostics;
339
+ return Array.isArray(diagnostics) ? diagnostics : [];
340
+ }
341
+ const diagnostics = value.qualityDiagnostics;
342
+ return Array.isArray(diagnostics) ? diagnostics : [];
343
+ }
344
+ function parseQualityDiagnostic(value) {
345
+ if (!isRecord(value))
346
+ return null;
347
+ const id = value.id;
348
+ const severity = value.severity;
349
+ const blocking = value.blocking;
350
+ const message = value.message;
351
+ if (typeof id !== "string" ||
352
+ typeof severity !== "string" ||
353
+ typeof blocking !== "boolean" ||
354
+ typeof message !== "string") {
355
+ return null;
356
+ }
357
+ return { id, severity, blocking, message };
358
+ }
359
+ function isRecord(value) {
360
+ return typeof value === "object" && value !== null && !Array.isArray(value);
361
+ }
288
362
  // implements REQ-opencode-kibi-plugin-v1
289
363
  export function createSyncScheduler(opts) {
290
364
  return new WorktreeSyncScheduler(opts);
@@ -1 +1 @@
1
- {"opencode":"0.16.0","mcp":"0.16.0","cli":"0.12.4","core":"0.6.0"}
1
+ {"opencode":"0.18.0","mcp":"0.19.0","cli":"0.14.0","core":"0.7.0"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-opencode",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Kibi OpenCode plugin - thin adapter to integrate Kibi with OpenCode sessions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,9 +37,7 @@
37
37
  "default": "./dist/tui.js"
38
38
  }
39
39
  },
40
- "files": [
41
- "dist"
42
- ],
40
+ "files": ["dist"],
43
41
  "engines": {
44
42
  "node": ">=18"
45
43
  },
@@ -62,7 +60,7 @@
62
60
  "@opencode-ai/plugin": "^1.4.7",
63
61
  "@opentui/core": "^0.1.99",
64
62
  "@opentui/solid": "^0.1.99",
65
- "kibi-cli": "^0.12.3"
63
+ "kibi-cli": "^0.14.0"
66
64
  },
67
65
  "devDependencies": {
68
66
  "@types/node": "latest",