kibi-opencode 0.7.1 → 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 +30 -11
- package/dist/index.js +9 -2
- package/dist/knowledge-classifier.js +1 -1
- package/dist/logger.d.ts +28 -0
- package/dist/logger.js +17 -1
- package/dist/plugin-startup.js +15 -2
- package/dist/prompt.js +52 -31
- package/dist/scheduler.js +8 -1
- 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
|
|
@@ -110,7 +111,15 @@ The plugin injects guidance into OpenCode sessions to improve agent grounding. U
|
|
|
110
111
|
|
|
111
112
|
### Bootstrap Command
|
|
112
113
|
|
|
113
|
-
OpenCode exposes Kibi MCP prompts as slash commands. The
|
|
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.
|
|
114
123
|
|
|
115
124
|
### Discovery-first MCP guidance
|
|
116
125
|
|
|
@@ -181,14 +190,24 @@ Smart enforcement keeps the plugin advisory-only: prompt injection can warn, exp
|
|
|
181
190
|
|
|
182
191
|
### Logging Policy
|
|
183
192
|
|
|
184
|
-
The plugin follows a **silent-except-errors** policy for terminal output:
|
|
193
|
+
The plugin follows a **silent-except-operational-errors** policy for terminal output, with a three-tier failure classification:
|
|
194
|
+
|
|
195
|
+
| Classification | Examples | Surface | Terminal | Structured |
|
|
196
|
+
|---------------|----------|---------|----------|------------|
|
|
197
|
+
| **Advisory (background)** | scheduler check failures, degraded-mode latches | `errorStructuredOnly()` | No | Yes, via `client.app.log()` |
|
|
198
|
+
| **Operational (plugin)** | bootstrap-needed, sync failure, hook/init failure | `error()` | Yes, via `console.error` | Yes, via `client.app.log()` |
|
|
199
|
+
| **Authoritative external** | git hooks, CLI checks | Outside plugin surface | N/A | N/A |
|
|
200
|
+
|
|
201
|
+
### Failure Routing Contract
|
|
202
|
+
|
|
203
|
+
The logger exposes two error-level surfaces with distinct routing semantics:
|
|
204
|
+
|
|
205
|
+
- **`error(msg, metadata?)`** — Operational plugin failures. Always emits to `console.error` for terminal visibility, plus `client.app.log()` when a client is bound. Use for bootstrap-needed, hook/init failures, and sync failures that require developer attention.
|
|
206
|
+
- **`errorStructuredOnly(msg, metadata?)`** — Advisory background maintenance failures. Routes through `client.app.log()` only when a client is bound; completely silent when no client is bound (no `console.error` fallback). Use for scheduler check failures and degraded-mode latches.
|
|
185
207
|
|
|
186
|
-
|
|
187
|
-
|---------|----------|---------------|
|
|
188
|
-
| Normal operation (sync success, guidance injection, session summaries) | No | Yes, via `client.app.log()` |
|
|
189
|
-
| Error-class events (bootstrap-needed, sync/check failure, hook/init failure) | Yes, via `console.error` | Yes, via `client.app.log()` |
|
|
208
|
+
**Contract rule:** Once `client` is bound (after `setClient()`), advisory logging MUST use `errorStructuredOnly()`. When no client is bound, `errorStructuredOnly()` is completely silent — it does not fall back to `console.error`.
|
|
190
209
|
|
|
191
|
-
Routine diagnostics route through [`client.app.log()`](https://opencode.ai/docs/plugins/) and never appear in the terminal. Only error-class events break terminal silence. This keeps the developer's workspace clean while preserving full visibility in structured logs for debugging.
|
|
210
|
+
Routine diagnostics route through [`client.app.log()`](https://opencode.ai/docs/plugins/) and never appear in the terminal. Only operational error-class events break terminal silence. This keeps the developer's workspace clean while preserving full visibility in structured logs for debugging.
|
|
192
211
|
|
|
193
212
|
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.
|
|
194
213
|
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import * as fs from "node:fs";
|
|
|
13
13
|
function deriveFileBucket(kind) {
|
|
14
14
|
return kind;
|
|
15
15
|
}
|
|
16
|
+
const startupNotifyGlobals = globalThis;
|
|
16
17
|
/**
|
|
17
18
|
* Lint requirement documents for embedded scenarios/tests and oversized content.
|
|
18
19
|
*/
|
|
@@ -142,6 +143,7 @@ const kibiOpencodePlugin = async (input) => {
|
|
|
142
143
|
"required-fields",
|
|
143
144
|
"no-dangling-refs",
|
|
144
145
|
...(pathAnalysis.kind === "fact" ? ["strict-fact-shape"] : []),
|
|
146
|
+
...(pathAnalysis.kind === "requirement" ? ["strict-req-fact-pairing"] : []),
|
|
145
147
|
]
|
|
146
148
|
: null;
|
|
147
149
|
const checkRules = traceabilityRules ?? kbStructuralRules;
|
|
@@ -259,11 +261,12 @@ const kibiOpencodePlugin = async (input) => {
|
|
|
259
261
|
"required-fields",
|
|
260
262
|
"no-dangling-refs",
|
|
261
263
|
"must-priority-coverage",
|
|
264
|
+
"strict-req-fact-pairing",
|
|
262
265
|
];
|
|
263
266
|
logger.info(`kibi-opencode: must-priority requirement detected, scheduling elevated checks for ${filePath}`);
|
|
264
267
|
}
|
|
265
268
|
else {
|
|
266
|
-
checkRules = ["required-fields", "no-dangling-refs"];
|
|
269
|
+
checkRules = ["required-fields", "no-dangling-refs", "strict-req-fact-pairing"];
|
|
267
270
|
}
|
|
268
271
|
}
|
|
269
272
|
logger.info("smart-enforcement.targeted-checks", {
|
|
@@ -426,7 +429,11 @@ const kibiOpencodePlugin = async (input) => {
|
|
|
426
429
|
logger.info("kibi-opencode: setup complete");
|
|
427
430
|
if (input.client && !maintenanceDegraded) {
|
|
428
431
|
const client = input.client;
|
|
429
|
-
|
|
432
|
+
const scheduleStartupNotify = startupNotifyGlobals.__kibi_test_schedule_startup_notify ??
|
|
433
|
+
((callback, delayMs) => {
|
|
434
|
+
setTimeout(callback, delayMs);
|
|
435
|
+
});
|
|
436
|
+
scheduleStartupNotify(() => {
|
|
430
437
|
notifyStartup(client, {
|
|
431
438
|
suppressToast: cfg.ux.toastStartup === false,
|
|
432
439
|
directory: input.directory,
|
|
@@ -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/logger.d.ts
CHANGED
|
@@ -8,4 +8,32 @@ export declare function setClient(c: PluginClient): void;
|
|
|
8
8
|
export declare function resetClient(): void;
|
|
9
9
|
export declare function info(msg: string, metadata?: LogMetadata): void;
|
|
10
10
|
export declare function warn(msg: string, metadata?: LogMetadata): void;
|
|
11
|
+
/**
|
|
12
|
+
* Failure classification contract for kibi-opencode logging.
|
|
13
|
+
*
|
|
14
|
+
* Three categories of failures, each with distinct routing:
|
|
15
|
+
*
|
|
16
|
+
* 1. **Advisory (background maintenance)** — e.g. scheduler sync/check failures,
|
|
17
|
+
* degraded-mode latches. These are non-blocking and advisory-only.
|
|
18
|
+
* Route through `errorStructuredOnly()`: emits to `client.app.log()` when
|
|
19
|
+
* client is bound; completely silent (no console.error) when no client exists.
|
|
20
|
+
* Advisory noise must never pollute the TUI.
|
|
21
|
+
*
|
|
22
|
+
* 2. **Operational (plugin failures)** — e.g. bootstrap-needed, hook/init failures.
|
|
23
|
+
* Route through `error()`: always emits to `console.error` for terminal
|
|
24
|
+
* visibility, plus `client.app.log()` when client is bound.
|
|
25
|
+
*
|
|
26
|
+
* 3. **Authoritative external failures** — git hooks, CLI checks. These are
|
|
27
|
+
* outside the plugin's logging surface entirely.
|
|
28
|
+
*
|
|
29
|
+
* ## Contract Rules
|
|
30
|
+
*
|
|
31
|
+
* - Once `client` is bound (after `setClient()`), advisory logging MUST use
|
|
32
|
+
* `errorStructuredOnly()` which routes through `client.app.log()` only.
|
|
33
|
+
* - `errorStructuredOnly()` is completely silent when no client is bound
|
|
34
|
+
* (no console.error fallback). Advisory failures never pollute the terminal.
|
|
35
|
+
* - `error()` preserves full terminal visibility for operational failures.
|
|
36
|
+
*/
|
|
37
|
+
export type FailureClassification = "advisory_background" | "operational_plugin" | "authoritative_external";
|
|
38
|
+
export declare function errorStructuredOnly(msg: string, metadata?: LogMetadata): void;
|
|
11
39
|
export declare function error(msg: string, metadata?: LogMetadata): void;
|
package/dist/logger.js
CHANGED
|
@@ -41,8 +41,24 @@ export function warn(msg, metadata) {
|
|
|
41
41
|
// Fallback when no client is available
|
|
42
42
|
}
|
|
43
43
|
// implements REQ-opencode-kibi-plugin-v1
|
|
44
|
+
export function errorStructuredOnly(msg, metadata) {
|
|
45
|
+
if (client) {
|
|
46
|
+
void client.app
|
|
47
|
+
.log({
|
|
48
|
+
body: buildBody("error", msg, metadata),
|
|
49
|
+
})
|
|
50
|
+
.catch(() => {
|
|
51
|
+
// Advisory failures are intentionally silent even on client logging
|
|
52
|
+
// failures — advisory noise must never pollute the TUI or terminal.
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// No client bound: advisory failures are intentionally silent
|
|
57
|
+
// (no console.error fallback — advisory noise must not pollute TUI)
|
|
58
|
+
}
|
|
59
|
+
// implements REQ-opencode-kibi-plugin-v1
|
|
44
60
|
export function error(msg, metadata) {
|
|
45
|
-
// Always emit to console for user visibility
|
|
61
|
+
// Always emit to console for user visibility (operational failures)
|
|
46
62
|
console.error("[kibi-opencode]", msg);
|
|
47
63
|
// Also emit to structured logs if client is available
|
|
48
64
|
if (client) {
|
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 {
|
|
@@ -136,7 +145,10 @@ export async function runPluginStartup(input) {
|
|
|
136
145
|
branch: currentBranch,
|
|
137
146
|
});
|
|
138
147
|
logger.info("kibi-opencode: setting up hooks");
|
|
139
|
-
const
|
|
148
|
+
const schedulerFactoryGlobals = globalThis;
|
|
149
|
+
const schedulerFactory = schedulerFactoryGlobals.__kibi_test_scheduler_factory_by_worktree?.get(input.worktree) ??
|
|
150
|
+
schedulerFactoryGlobals.__kibi_test_scheduler_factory ??
|
|
151
|
+
createSyncScheduler;
|
|
140
152
|
const scheduler = cfg.sync
|
|
141
153
|
.enabled
|
|
142
154
|
? (() => {
|
|
@@ -145,7 +157,8 @@ export async function runPluginStartup(input) {
|
|
|
145
157
|
worktree: input.worktree,
|
|
146
158
|
config: cfg,
|
|
147
159
|
onRunComplete: (meta) => {
|
|
148
|
-
if (meta.exitCode !== 0
|
|
160
|
+
if (meta.exitCode !== 0 &&
|
|
161
|
+
!isSmartEnforcementSyncReason(meta.reason))
|
|
149
162
|
latchRuntimeDegraded("scheduler_sync_failed");
|
|
150
163
|
if (meta.checkExitCode !== undefined &&
|
|
151
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.
|
|
@@ -80,10 +96,11 @@ export function postureGuidance(posture) {
|
|
|
80
96
|
return `🔧 **Bootstrap required**
|
|
81
97
|
|
|
82
98
|
This repository does not appear to have Kibi initialized. Agents should:
|
|
83
|
-
-
|
|
84
|
-
-
|
|
99
|
+
- Start with \`kb_autopilot_generate\` to discover entities and bootstrap the KB (preferred workflow)
|
|
100
|
+
- Use \`/init-kibi\` as the sanctioned slash command for initial repo setup
|
|
101
|
+
- Ask the user/operator to run setup or repair outside this session if bootstrap is insufficient
|
|
85
102
|
|
|
86
|
-
Do not run \`kibi\` CLI commands directly; use
|
|
103
|
+
Do not run \`kibi\` CLI commands directly; use public MCP tools (kb_autopilot_generate, kb_search, kb_query, kb_status, kb_find_gaps, kb_coverage, kb_graph, kb_upsert, kb_delete, kb_check).`;
|
|
87
104
|
case "root_partial":
|
|
88
105
|
return `⚠️ **Partial KB setup detected**
|
|
89
106
|
|
|
@@ -119,17 +136,17 @@ function buildContextualGuidance(context) {
|
|
|
119
136
|
if (postureBlock)
|
|
120
137
|
selectedBlock = postureBlock;
|
|
121
138
|
}
|
|
122
|
-
// Priority 4: Legacy workspace health bootstrap (only when no posture) — not cache-suppressed
|
|
123
139
|
else if (!context.posture && context.workspaceHealth?.needsBootstrap) {
|
|
124
140
|
selectedBlock = `🔧 **Bootstrap required**
|
|
125
141
|
|
|
126
142
|
This repository does not appear to have Kibi initialized. Agents should:
|
|
127
|
-
-
|
|
128
|
-
-
|
|
143
|
+
- Start with \`kb_autopilot_generate\` to discover entities and bootstrap the KB (preferred workflow)
|
|
144
|
+
- Use \`/init-kibi\` as the sanctioned slash command for initial repo setup
|
|
145
|
+
- Ask the user/operator to run setup or repair outside this session if bootstrap is insufficient
|
|
129
146
|
|
|
130
|
-
Do not run \`kibi\` CLI commands directly; use
|
|
147
|
+
Do not run \`kibi\` CLI commands directly; use public MCP tools (kb_autopilot_generate, kb_search, kb_query, kb_status, kb_find_gaps, kb_coverage, kb_graph, kb_upsert, kb_delete, kb_check).`;
|
|
148
|
+
// Advisory guidance: check cache before selecting, since these blocks can be safely suppressed
|
|
131
149
|
}
|
|
132
|
-
// Advisory guidance: check cache before selecting, since these blocks can be safely suppressed
|
|
133
150
|
else {
|
|
134
151
|
// Cache check: skip repeated advisory guidance — only after critical signals are handled above
|
|
135
152
|
// Allow degraded advisory to bypass cache so it is always visible
|
|
@@ -195,7 +212,7 @@ Before implementing or explaining code:
|
|
|
195
212
|
4. **Add traceability** - Production code: \`implements\` (symbol→req) for ownership. Test code: \`executable_for\`. \`covered_by\` is coverage evidence only for production symbols.
|
|
196
213
|
|
|
197
214
|
If you're adding long explanatory comments, consider routing that knowledge to:
|
|
198
|
-
- \`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\`
|
|
199
216
|
- \`ADR\` for technical decisions, tradeoffs, rationale
|
|
200
217
|
- \`REQ\` for system behavior requirements`;
|
|
201
218
|
}
|
|
@@ -207,6 +224,13 @@ If you're adding long explanatory comments, consider routing that knowledge to:
|
|
|
207
224
|
selectedBlock = GUIDANCE_BY_RISK.kb_doc_structural;
|
|
208
225
|
}
|
|
209
226
|
}
|
|
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.");
|
|
210
234
|
}
|
|
211
235
|
// Source-linked micro-brief: insert after header line for code risk classes
|
|
212
236
|
// Inserting after the header (not prepending before it) preserves the header
|
|
@@ -225,10 +249,7 @@ If you're adding long explanatory comments, consider routing that knowledge to:
|
|
|
225
249
|
: path.join(context.workspaceRoot, editedPath);
|
|
226
250
|
const linkedIds = getSourceLinkedRequirementIds(context.workspaceRoot, absEdited);
|
|
227
251
|
if (linkedIds.length >= 1 && linkedIds.length <= 3) {
|
|
228
|
-
|
|
229
|
-
if (headerEnd !== -1) {
|
|
230
|
-
selectedBlock = `${selectedBlock.slice(0, headerEnd + 1)}- Existing Kibi links: ${linkedIds.join(", ")}\n${selectedBlock.slice(headerEnd + 1)}`;
|
|
231
|
-
}
|
|
252
|
+
selectedBlock = insertBulletAfterHeader(selectedBlock, `- Existing Kibi links: ${linkedIds.join(", ")}`);
|
|
232
253
|
}
|
|
233
254
|
}
|
|
234
255
|
}
|
|
@@ -265,24 +286,28 @@ The Kibi workspace is in a maintenance-degraded state. Guidance remains advisory
|
|
|
265
286
|
};
|
|
266
287
|
context.cache.recordSatisfied(key, "guidance");
|
|
267
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;
|
|
268
292
|
// Append completion reminder for risky classes when enabled
|
|
269
293
|
const REMINDER_RISK_CLASSES = [
|
|
270
294
|
"behavior_candidate",
|
|
271
295
|
"traceability_candidate",
|
|
272
296
|
"req_policy_candidate",
|
|
273
297
|
];
|
|
274
|
-
|
|
298
|
+
let finalBlock = budgeted;
|
|
299
|
+
if (finalBlock &&
|
|
275
300
|
context.completionReminder === true &&
|
|
276
301
|
!context.maintenanceDegraded &&
|
|
277
302
|
riskClass &&
|
|
278
303
|
REMINDER_RISK_CLASSES.includes(riskClass) &&
|
|
279
304
|
posture !== "root_uninitialized" &&
|
|
280
305
|
posture !== "root_partial") {
|
|
281
|
-
|
|
306
|
+
finalBlock = `${finalBlock}\n- Run \`kb_check\` before completing this task.`;
|
|
282
307
|
}
|
|
283
308
|
// Return: sentinel + one targeted block (or just sentinel if no block)
|
|
284
|
-
return
|
|
285
|
-
? `${SENTINEL}\n\n${
|
|
309
|
+
return finalBlock
|
|
310
|
+
? `${SENTINEL}\n\n${finalBlock}`
|
|
286
311
|
: SENTINEL;
|
|
287
312
|
}
|
|
288
313
|
// ── Comment suggestion guidance (legacy compat) ────────────────────────
|
|
@@ -291,14 +316,14 @@ function buildCommentSuggestionGuidance(suggestion) {
|
|
|
291
316
|
case "fact":
|
|
292
317
|
return `🎯 **Durable knowledge detected: FACT**
|
|
293
318
|
|
|
294
|
-
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).
|
|
295
320
|
|
|
296
|
-
**Action**:
|
|
297
|
-
- 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
|
|
298
324
|
- Link it to relevant requirements
|
|
299
|
-
- Reference the FACT in code with a comment
|
|
300
325
|
|
|
301
|
-
This keeps domain truths centralized and
|
|
326
|
+
This keeps domain truths centralized, searchable, and contradiction-safe.`;
|
|
302
327
|
case "adr":
|
|
303
328
|
return `🎯 **Durable knowledge detected: ADR**
|
|
304
329
|
|
|
@@ -354,11 +379,7 @@ Dogfood note for this repo: OpenCode here uses local built \`kibi-mcp\` and \`ki
|
|
|
354
379
|
5. **Link during work**: When creating KB entities, include relationship rows: specified_by (req→scenario), implements (symbol→req for ownership), covered_by (symbol→test for coverage), executable_for (test code→test).
|
|
355
380
|
6. **Validate**: Run kb_check after KB mutations to catch violations early.
|
|
356
381
|
|
|
357
|
-
**Public Kibi tools only:** kb_search, kb_query, kb_status, kb_find_gaps, kb_coverage, kb_graph, kb_upsert, kb_delete, kb_check
|
|
358
|
-
|
|
359
|
-
Do not invoke Kibi CLI commands directly from the agent.
|
|
360
|
-
|
|
361
|
-
Bootstrap existing repos: use \`/init-kibi\` to run the retroactive initialization workflow.`;
|
|
382
|
+
**Public Kibi tools only:** kb_autopilot_generate, kb_search, kb_query, kb_status, kb_find_gaps, kb_coverage, kb_graph, kb_upsert, kb_delete, kb_check.\n\nDo not invoke Kibi CLI commands directly from the agent.\n\nBootstrap existing repos: use \`/init-kibi\` to run the retroactive initialization (\`kb_autopilot_generate\`) workflow.`;
|
|
362
383
|
/**
|
|
363
384
|
* Build prompt with contextual guidance based on posture, risk class, and cache state.
|
|
364
385
|
*/
|
package/dist/scheduler.js
CHANGED
|
@@ -118,7 +118,7 @@ class WorktreeSyncScheduler {
|
|
|
118
118
|
const checkResult = await this.runCheck(this.worktree, checkRules);
|
|
119
119
|
checkExitCode = checkResult.exitCode;
|
|
120
120
|
if (checkExitCode !== 0) {
|
|
121
|
-
logger.
|
|
121
|
+
logger.errorStructuredOnly(`check.failed ${JSON.stringify({ rules: checkRules, exitCode: checkExitCode })}`);
|
|
122
122
|
}
|
|
123
123
|
else {
|
|
124
124
|
logger.info(`check.succeeded ${JSON.stringify({ rules: checkRules })}`);
|
|
@@ -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
|
}
|