kibi-opencode 0.9.0 → 0.11.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 +38 -13
- package/dist/brief-delivery-reasons.d.ts +12 -0
- package/dist/brief-delivery-reasons.js +132 -0
- package/dist/brief-intent.d.ts +15 -4
- package/dist/brief-intent.js +78 -25
- package/dist/briefing-runtime.js +2 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +9 -0
- package/dist/e2e-coverage-signals.d.ts +6 -0
- package/dist/e2e-coverage-signals.js +186 -0
- package/dist/file-entity-links.d.ts +15 -0
- package/dist/file-entity-links.js +254 -0
- package/dist/file-operation-reminders.d.ts +24 -0
- package/dist/file-operation-reminders.js +55 -0
- package/dist/file-operation-state.d.ts +29 -0
- package/dist/file-operation-state.js +113 -0
- package/dist/idle-brief-audit.d.ts +36 -0
- package/dist/idle-brief-audit.js +186 -0
- package/dist/idle-brief-paths.d.ts +6 -0
- package/dist/idle-brief-paths.js +120 -0
- package/dist/idle-brief-reader.d.ts +37 -0
- package/dist/idle-brief-reader.js +163 -0
- package/dist/idle-brief-runtime.d.ts +48 -0
- package/dist/idle-brief-runtime.js +478 -0
- package/dist/idle-brief-store.d.ts +113 -0
- package/dist/idle-brief-store.js +262 -0
- package/dist/index.d.ts +2 -39
- package/dist/index.js +1 -492
- package/dist/init-kibi-alias.d.ts +14 -0
- package/dist/init-kibi-alias.js +38 -0
- package/dist/init-kibi-capability.d.ts +32 -0
- package/dist/init-kibi-capability.js +202 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +17 -4
- package/dist/plugin-startup.d.ts +1 -0
- package/dist/plugin-startup.js +11 -2
- package/dist/plugin.d.ts +52 -0
- package/dist/plugin.js +1068 -0
- package/dist/prompt.d.ts +15 -3
- package/dist/prompt.js +106 -36
- package/dist/reconcile-engine.d.ts +15 -0
- package/dist/reconcile-engine.js +112 -0
- package/dist/scheduler.d.ts +13 -2
- package/dist/scheduler.js +86 -7
- package/dist/session-edit-state.d.ts +25 -0
- package/dist/session-edit-state.js +177 -0
- package/dist/session-fingerprint.d.ts +11 -0
- package/dist/session-fingerprint.js +21 -0
- package/dist/source-linked-guidance.d.ts +1 -2
- package/dist/source-linked-guidance.js +5 -168
- package/dist/startup-notifier.js +42 -31
- package/dist/toast.d.ts +23 -22
- package/dist/toast.js +36 -14
- package/dist/tui-brief-delivery.d.ts +67 -0
- package/dist/tui-brief-delivery.js +279 -0
- package/dist/tui-brief-view-model.d.ts +63 -0
- package/dist/tui-brief-view-model.js +209 -0
- package/dist/tui.d.ts +8 -0
- package/dist/tui.js +413 -0
- package/dist/tui.jsx +120 -0
- package/package.json +13 -4
package/dist/index.js
CHANGED
|
@@ -1,492 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { computeBriefIntent } from "./brief-intent.js";
|
|
3
|
-
import { fetchBriefingResult, } from "./briefing-runtime.js";
|
|
4
|
-
import { analyzeCodeFile, } from "./comment-analysis.js";
|
|
5
|
-
import * as fileFilter from "./file-filter.js";
|
|
6
|
-
import * as logger from "./logger.js";
|
|
7
|
-
import { analyzePath } from "./path-kind.js";
|
|
8
|
-
import { SENTINEL, buildPrompt } from "./prompt.js";
|
|
9
|
-
import { isMustPriorityRequirement } from "./requirement-doc.js";
|
|
10
|
-
import { classifyRisk } from "./risk-classifier.js";
|
|
11
|
-
import { getSessionTracker } from "./session-tracker.js";
|
|
12
|
-
import { notifyStartup } from "./startup-notifier.js";
|
|
13
|
-
import { runPluginStartup } from "./plugin-startup.js";
|
|
14
|
-
import { sendToast } from "./toast.js";
|
|
15
|
-
import * as fs from "node:fs";
|
|
16
|
-
function deriveFileBucket(kind) {
|
|
17
|
-
return kind;
|
|
18
|
-
}
|
|
19
|
-
const startupNotifyGlobals = globalThis;
|
|
20
|
-
/**
|
|
21
|
-
* Lint requirement documents for embedded scenarios/tests and oversized content.
|
|
22
|
-
*/
|
|
23
|
-
// implements REQ-opencode-kibi-plugin-v1
|
|
24
|
-
function lintRequirementDoc(filePath, worktree) {
|
|
25
|
-
const warnings = [];
|
|
26
|
-
try {
|
|
27
|
-
const resolvedPath = worktree && !filePath.startsWith("/")
|
|
28
|
-
? `${worktree}/${filePath}`
|
|
29
|
-
: filePath;
|
|
30
|
-
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
31
|
-
if (/given\s+[\s\S]*?when\s+[\s\S]*?then/i.test(content)) {
|
|
32
|
-
warnings.push({
|
|
33
|
-
category: "embedded-scenario-in-req",
|
|
34
|
-
message: `Requirement file ${filePath} appears to contain embedded scenario (Given/When/Then). Consider extracting to a separate SCEN entity.`,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
if (/\b(assert|verify|expected\s+to|should\s+return)\b/i.test(content)) {
|
|
38
|
-
warnings.push({
|
|
39
|
-
category: "embedded-test-in-req",
|
|
40
|
-
message: `Requirement file ${filePath} appears to contain embedded test assertions. Consider extracting to a separate TEST entity.`,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
const lines = content.split("\n");
|
|
44
|
-
const contentLines = lines.filter((l) => l.trim() && !l.startsWith("---") && !l.startsWith("#"));
|
|
45
|
-
if (contentLines.length > 50) {
|
|
46
|
-
warnings.push({
|
|
47
|
-
category: "missing-traceability",
|
|
48
|
-
message: `Requirement file ${filePath} is very long (${contentLines.length} content lines). Consider splitting into multiple requirements or extracting scenarios/tests.`,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// Ignore read errors
|
|
54
|
-
}
|
|
55
|
-
return warnings;
|
|
56
|
-
}
|
|
57
|
-
// implements REQ-opencode-kibi-plugin-v1
|
|
58
|
-
const kibiOpencodePlugin = async (input) => {
|
|
59
|
-
const startup = await runPluginStartup(input);
|
|
60
|
-
if (!startup) {
|
|
61
|
-
return {};
|
|
62
|
-
}
|
|
63
|
-
const { cfg, workspaceHealth, posture, currentBranch, cache, runtimeOverlay, scheduler, maintenanceDegraded, getMaintenanceDegraded, getEffectiveMode, latchRuntimeDegraded, } = startup;
|
|
64
|
-
const hooks = {};
|
|
65
|
-
// Plugin instance state (not module globals)
|
|
66
|
-
const MAX_RECENT_EDITS = 5;
|
|
67
|
-
let recentEdits = [];
|
|
68
|
-
let hasRecentKbEdit = false;
|
|
69
|
-
let recentCommentSuggestion = null;
|
|
70
|
-
const seenFingerprints = new Set(); // For deduplication
|
|
71
|
-
const autoBriefResults = new Map();
|
|
72
|
-
const toastedFingerprints = new Set();
|
|
73
|
-
let lastRiskClass = null;
|
|
74
|
-
let lastEditedFilePath = null;
|
|
75
|
-
let lastBriefFingerprint = null;
|
|
76
|
-
let degradedWarnedOnce = false;
|
|
77
|
-
hooks.event = async ({ event }) => {
|
|
78
|
-
if (event.type !== "file.edited")
|
|
79
|
-
return;
|
|
80
|
-
const filePath = event
|
|
81
|
-
.properties.file;
|
|
82
|
-
if (!filePath)
|
|
83
|
-
return;
|
|
84
|
-
const pathAnalysis = analyzePath(filePath, input.worktree);
|
|
85
|
-
let fileContent = "";
|
|
86
|
-
try {
|
|
87
|
-
const resolvedPath = input.worktree && !path.isAbsolute(filePath)
|
|
88
|
-
? path.join(input.worktree, filePath)
|
|
89
|
-
: filePath;
|
|
90
|
-
fileContent = fs.readFileSync(resolvedPath, "utf-8");
|
|
91
|
-
}
|
|
92
|
-
catch { }
|
|
93
|
-
const hasMustPriority = pathAnalysis.kind === "requirement"
|
|
94
|
-
? isMustPriorityRequirement(filePath, input.worktree)
|
|
95
|
-
: false;
|
|
96
|
-
let precomputedSuggestion = null;
|
|
97
|
-
if (pathAnalysis.kind === "code" && cfg.guidance.commentDetection.enabled) {
|
|
98
|
-
const resolvedPath = input.worktree && !path.isAbsolute(filePath)
|
|
99
|
-
? path.join(input.worktree, filePath)
|
|
100
|
-
: filePath;
|
|
101
|
-
precomputedSuggestion = analyzeCodeFile(resolvedPath, {
|
|
102
|
-
minLines: cfg.guidance.commentDetection.minLines,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
const { riskClass } = classifyRisk({
|
|
106
|
-
pathKind: pathAnalysis.kind,
|
|
107
|
-
isUnderKb: pathAnalysis.isUnderKb,
|
|
108
|
-
hasMustPriority,
|
|
109
|
-
hasDurableComment: !!precomputedSuggestion,
|
|
110
|
-
fileContent,
|
|
111
|
-
});
|
|
112
|
-
const effectiveRiskClass = riskClass === "safe_docs_only" && precomputedSuggestion
|
|
113
|
-
? "traceability_candidate"
|
|
114
|
-
: riskClass;
|
|
115
|
-
const isAutoBriefRisk = effectiveRiskClass === "behavior_candidate" ||
|
|
116
|
-
effectiveRiskClass === "traceability_candidate";
|
|
117
|
-
lastRiskClass = effectiveRiskClass;
|
|
118
|
-
lastEditedFilePath = filePath;
|
|
119
|
-
logger.info("smart-enforcement.risk", {
|
|
120
|
-
event: "smart_enforcement_risk",
|
|
121
|
-
file: filePath,
|
|
122
|
-
path_kind: pathAnalysis.kind,
|
|
123
|
-
risk_class: effectiveRiskClass,
|
|
124
|
-
posture_state: posture.state,
|
|
125
|
-
maintenance_state: getMaintenanceDegraded()
|
|
126
|
-
? "maintenance_degraded"
|
|
127
|
-
: "maintenance_available",
|
|
128
|
-
under_kb: pathAnalysis.isUnderKb,
|
|
129
|
-
has_must_priority: hasMustPriority,
|
|
130
|
-
posture: posture.state,
|
|
131
|
-
reason_code: effectiveRiskClass,
|
|
132
|
-
effective_mode: getEffectiveMode(),
|
|
133
|
-
static_degraded: posture.maintenanceDegraded,
|
|
134
|
-
runtime_degraded: runtimeOverlay.degraded,
|
|
135
|
-
merged_degraded: getMaintenanceDegraded(),
|
|
136
|
-
overlay_cause: runtimeOverlay.primaryCause ?? null,
|
|
137
|
-
});
|
|
138
|
-
const targetedChecksBlocked = getMaintenanceDegraded() ||
|
|
139
|
-
runtimeOverlay.primaryCause === "sync_disabled" ||
|
|
140
|
-
runtimeOverlay.primaryCause === "scheduler_unavailable" ||
|
|
141
|
-
runtimeOverlay.primaryCause === "scheduler_sync_failed" ||
|
|
142
|
-
runtimeOverlay.primaryCause === "scheduler_check_failed";
|
|
143
|
-
if (!targetedChecksBlocked &&
|
|
144
|
-
cfg.sync.enabled &&
|
|
145
|
-
scheduler &&
|
|
146
|
-
cfg.guidance.targetedChecks.enabled) {
|
|
147
|
-
const traceabilityRules = effectiveRiskClass === "traceability_candidate"
|
|
148
|
-
? ["symbol-traceability"]
|
|
149
|
-
: null;
|
|
150
|
-
const kbStructuralRules = effectiveRiskClass === "kb_doc_structural" &&
|
|
151
|
-
fileFilter.shouldHandleFile(filePath, input.worktree)
|
|
152
|
-
? [
|
|
153
|
-
"required-fields",
|
|
154
|
-
"no-dangling-refs",
|
|
155
|
-
...(pathAnalysis.kind === "fact" ? ["strict-fact-shape"] : []),
|
|
156
|
-
...(pathAnalysis.kind === "requirement" ? ["strict-req-fact-pairing"] : []),
|
|
157
|
-
]
|
|
158
|
-
: null;
|
|
159
|
-
const checkRules = traceabilityRules ?? kbStructuralRules;
|
|
160
|
-
if (checkRules) {
|
|
161
|
-
logger.info("smart-enforcement.targeted-checks", {
|
|
162
|
-
event: "smart_enforcement_targeted_checks",
|
|
163
|
-
file: filePath,
|
|
164
|
-
risk_class: effectiveRiskClass,
|
|
165
|
-
posture: posture.state,
|
|
166
|
-
posture_state: posture.state,
|
|
167
|
-
guidance_action: "targeted_checks",
|
|
168
|
-
effective_mode: getEffectiveMode(),
|
|
169
|
-
rules: checkRules,
|
|
170
|
-
static_degraded: posture.maintenanceDegraded,
|
|
171
|
-
runtime_degraded: runtimeOverlay.degraded,
|
|
172
|
-
merged_degraded: getMaintenanceDegraded(),
|
|
173
|
-
overlay_cause: runtimeOverlay.primaryCause ?? null,
|
|
174
|
-
});
|
|
175
|
-
logger.info(`kibi-opencode: scheduling sync for ${filePath}`);
|
|
176
|
-
scheduler.scheduleSync(effectiveRiskClass === "traceability_candidate"
|
|
177
|
-
? "smart-enforcement.traceability"
|
|
178
|
-
: "smart-enforcement.kb-doc", filePath, checkRules);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const now = Date.now();
|
|
182
|
-
recentEdits.push({
|
|
183
|
-
path: filePath,
|
|
184
|
-
kind: pathAnalysis.kind,
|
|
185
|
-
timestamp: now,
|
|
186
|
-
});
|
|
187
|
-
if (recentEdits.length > MAX_RECENT_EDITS) {
|
|
188
|
-
recentEdits = recentEdits.slice(-MAX_RECENT_EDITS);
|
|
189
|
-
}
|
|
190
|
-
if (effectiveRiskClass === "safe_docs_only" ||
|
|
191
|
-
effectiveRiskClass === "safe_test_only") {
|
|
192
|
-
recentCommentSuggestion = null;
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const cacheKey = {
|
|
196
|
-
workspaceRoot: input.worktree,
|
|
197
|
-
branch: currentBranch,
|
|
198
|
-
posture: posture.state,
|
|
199
|
-
riskClass: effectiveRiskClass,
|
|
200
|
-
fileBucket: deriveFileBucket(pathAnalysis.kind),
|
|
201
|
-
};
|
|
202
|
-
// Always process manual_kb_edit before cache check — this is a critical safety signal
|
|
203
|
-
if (effectiveRiskClass === "manual_kb_edit") {
|
|
204
|
-
hasRecentKbEdit = true;
|
|
205
|
-
if (cfg.guidance.warnOnKbEdits) {
|
|
206
|
-
logger.warn(`kibi-opencode: .kb edit detected for ${filePath}`);
|
|
207
|
-
getSessionTracker().recordWarning("kb-edit", filePath, `Manual .kb edit: ${filePath}`);
|
|
208
|
-
}
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
// Always emit requirement lint warnings before cache check — these are safety signals
|
|
212
|
-
if (effectiveRiskClass === "req_policy_candidate") {
|
|
213
|
-
const lintWarnings = lintRequirementDoc(filePath, input.worktree);
|
|
214
|
-
for (const warning of lintWarnings) {
|
|
215
|
-
getSessionTracker().recordWarning(warning.category, filePath, warning.message);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Cache check: after critical signals have been emitted
|
|
219
|
-
if (cache.isSatisfied(cacheKey)) {
|
|
220
|
-
logger.info("smart-enforcement.cache", {
|
|
221
|
-
event: "smart_enforcement_cache",
|
|
222
|
-
cache_hit: true,
|
|
223
|
-
cache_state: "hit",
|
|
224
|
-
file: filePath,
|
|
225
|
-
risk_class: effectiveRiskClass,
|
|
226
|
-
posture: posture.state,
|
|
227
|
-
posture_state: posture.state,
|
|
228
|
-
});
|
|
229
|
-
if (!isAutoBriefRisk) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
logger.info("smart-enforcement.cache", {
|
|
234
|
-
event: "smart_enforcement_cache",
|
|
235
|
-
cache_hit: false,
|
|
236
|
-
cache_state: "miss",
|
|
237
|
-
file: filePath,
|
|
238
|
-
risk_class: effectiveRiskClass,
|
|
239
|
-
posture: posture.state,
|
|
240
|
-
posture_state: posture.state,
|
|
241
|
-
});
|
|
242
|
-
if (effectiveRiskClass === "req_policy_candidate") {
|
|
243
|
-
if (getMaintenanceDegraded()) {
|
|
244
|
-
const logFn = cfg.guidance.smartEnforcement.degradedMode === "warn-once"
|
|
245
|
-
? logger.warn
|
|
246
|
-
: logger.info;
|
|
247
|
-
logFn("smart-enforcement.degraded", {
|
|
248
|
-
event: "smart_enforcement_degraded",
|
|
249
|
-
file: filePath,
|
|
250
|
-
risk_class: effectiveRiskClass,
|
|
251
|
-
posture: posture.state,
|
|
252
|
-
posture_state: posture.state,
|
|
253
|
-
maintenance_state: getMaintenanceDegraded()
|
|
254
|
-
? "maintenance_degraded"
|
|
255
|
-
: "maintenance_available",
|
|
256
|
-
reason: runtimeOverlay.primaryCause ?? "non_authoritative_posture",
|
|
257
|
-
reason_code: runtimeOverlay.primaryCause ?? "non_authoritative_posture",
|
|
258
|
-
static_degraded: posture.maintenanceDegraded,
|
|
259
|
-
runtime_degraded: runtimeOverlay.degraded,
|
|
260
|
-
merged_degraded: getMaintenanceDegraded(),
|
|
261
|
-
overlay_cause: runtimeOverlay.primaryCause ?? null,
|
|
262
|
-
effective_mode: getEffectiveMode(),
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
if (!getMaintenanceDegraded() &&
|
|
266
|
-
cfg.sync.enabled &&
|
|
267
|
-
scheduler &&
|
|
268
|
-
fileFilter.shouldHandleFile(filePath, input.worktree)) {
|
|
269
|
-
let checkRules;
|
|
270
|
-
if (cfg.guidance.targetedChecks.enabled) {
|
|
271
|
-
if (hasMustPriority && getEffectiveMode() === "strict") {
|
|
272
|
-
checkRules = [
|
|
273
|
-
"required-fields",
|
|
274
|
-
"no-dangling-refs",
|
|
275
|
-
"must-priority-coverage",
|
|
276
|
-
"strict-req-fact-pairing",
|
|
277
|
-
];
|
|
278
|
-
logger.info(`kibi-opencode: must-priority requirement detected, scheduling elevated checks for ${filePath}`);
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
checkRules = ["required-fields", "no-dangling-refs", "strict-req-fact-pairing"];
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
logger.info("smart-enforcement.targeted-checks", {
|
|
285
|
-
event: "smart_enforcement_targeted_checks",
|
|
286
|
-
file: filePath,
|
|
287
|
-
risk_class: effectiveRiskClass,
|
|
288
|
-
posture: posture.state,
|
|
289
|
-
posture_state: posture.state,
|
|
290
|
-
guidance_action: "targeted_checks",
|
|
291
|
-
effective_mode: getEffectiveMode(),
|
|
292
|
-
rules: checkRules ?? [],
|
|
293
|
-
static_degraded: posture.maintenanceDegraded,
|
|
294
|
-
runtime_degraded: runtimeOverlay.degraded,
|
|
295
|
-
merged_degraded: getMaintenanceDegraded(),
|
|
296
|
-
overlay_cause: runtimeOverlay.primaryCause ?? null,
|
|
297
|
-
});
|
|
298
|
-
scheduler?.scheduleSync("file.edited", filePath, checkRules);
|
|
299
|
-
}
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (effectiveRiskClass === "kb_doc_structural") {
|
|
303
|
-
if (getMaintenanceDegraded()) {
|
|
304
|
-
const logFn = cfg.guidance.smartEnforcement.degradedMode === "warn-once"
|
|
305
|
-
? logger.warn
|
|
306
|
-
: logger.info;
|
|
307
|
-
logFn("smart-enforcement.degraded", {
|
|
308
|
-
event: "smart_enforcement_degraded",
|
|
309
|
-
file: filePath,
|
|
310
|
-
risk_class: effectiveRiskClass,
|
|
311
|
-
posture: posture.state,
|
|
312
|
-
posture_state: posture.state,
|
|
313
|
-
maintenance_state: getMaintenanceDegraded()
|
|
314
|
-
? "maintenance_degraded"
|
|
315
|
-
: "maintenance_available",
|
|
316
|
-
reason: runtimeOverlay.primaryCause ?? "non_authoritative_posture",
|
|
317
|
-
reason_code: runtimeOverlay.primaryCause ?? "non_authoritative_posture",
|
|
318
|
-
static_degraded: posture.maintenanceDegraded,
|
|
319
|
-
runtime_degraded: runtimeOverlay.degraded,
|
|
320
|
-
merged_degraded: getMaintenanceDegraded(),
|
|
321
|
-
overlay_cause: runtimeOverlay.primaryCause ?? null,
|
|
322
|
-
effective_mode: getEffectiveMode(),
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (isAutoBriefRisk) {
|
|
328
|
-
if (pathAnalysis.kind === "code" &&
|
|
329
|
-
cfg.guidance.commentDetection.enabled) {
|
|
330
|
-
const suggestion = precomputedSuggestion;
|
|
331
|
-
if (suggestion) {
|
|
332
|
-
recentCommentSuggestion = suggestion;
|
|
333
|
-
const dedupeKey = `${filePath}:${suggestion.suggestionType}:${suggestion.fingerprint}`;
|
|
334
|
-
if (!seenFingerprints.has(dedupeKey)) {
|
|
335
|
-
seenFingerprints.add(dedupeKey);
|
|
336
|
-
const warningCategory = suggestion.suggestionType === "fact"
|
|
337
|
-
? "long-comment-missed-fact"
|
|
338
|
-
: suggestion.suggestionType === "adr"
|
|
339
|
-
? "long-comment-missed-adr"
|
|
340
|
-
: "missing-traceability";
|
|
341
|
-
logger.warn(`kibi-opencode: detected durable ${suggestion.suggestionType} knowledge in ${filePath}`);
|
|
342
|
-
getSessionTracker().recordWarning(warningCategory, filePath, `Consider routing this ${suggestion.suggestionType} knowledge to Kibi instead of inline comments: ${suggestion.reasoning}`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
recentCommentSuggestion = null;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
recentCommentSuggestion = null;
|
|
351
|
-
}
|
|
352
|
-
const intentResult = computeBriefIntent({
|
|
353
|
-
riskClass: effectiveRiskClass,
|
|
354
|
-
posture: posture.state,
|
|
355
|
-
maintenanceDegraded: getMaintenanceDegraded(),
|
|
356
|
-
editedFile: filePath,
|
|
357
|
-
worktreeRoot: input.worktree,
|
|
358
|
-
branch: currentBranch,
|
|
359
|
-
});
|
|
360
|
-
lastBriefFingerprint = intentResult.fingerprint;
|
|
361
|
-
if (intentResult.eligible &&
|
|
362
|
-
input.client &&
|
|
363
|
-
!getMaintenanceDegraded() &&
|
|
364
|
-
(posture.state === "root_active" ||
|
|
365
|
-
posture.state === "hybrid_root_plus_vendored")) {
|
|
366
|
-
const client = input.client;
|
|
367
|
-
const fingerprint = intentResult.fingerprint;
|
|
368
|
-
const workspaceCtx = {
|
|
369
|
-
workspaceRoot: input.worktree,
|
|
370
|
-
branch: currentBranch,
|
|
371
|
-
directory: input.directory,
|
|
372
|
-
...(input.workspace !== undefined ? { workspace: input.workspace } : {}),
|
|
373
|
-
};
|
|
374
|
-
void fetchBriefingResult(client, workspaceCtx, intentResult).then((result) => {
|
|
375
|
-
autoBriefResults.set(fingerprint, result);
|
|
376
|
-
if (!toastedFingerprints.has(fingerprint)) {
|
|
377
|
-
toastedFingerprints.add(fingerprint);
|
|
378
|
-
void sendToast(client, { message: result.toastMessage }).catch(() => {
|
|
379
|
-
// toast delivery failure is non-fatal
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return;
|
|
386
|
-
};
|
|
387
|
-
if (cfg.prompt.enabled) {
|
|
388
|
-
const hookMode = cfg.prompt.hookMode;
|
|
389
|
-
if (hookMode === "system-transform" || hookMode === "auto") {
|
|
390
|
-
hooks["experimental.chat.system.transform"] = async (_input, output) => {
|
|
391
|
-
// Skip if sentinel already present in any existing entry
|
|
392
|
-
if (output.system.some((entry) => entry.includes(SENTINEL))) {
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const maintenanceDegraded = getMaintenanceDegraded();
|
|
396
|
-
const showDegradedAdvisory = maintenanceDegraded &&
|
|
397
|
-
cfg.guidance.smartEnforcement.degradedMode === "warn-once" &&
|
|
398
|
-
!degradedWarnedOnce;
|
|
399
|
-
const autoBriefResult = lastBriefFingerprint != null
|
|
400
|
-
? autoBriefResults.get(lastBriefFingerprint)
|
|
401
|
-
: undefined;
|
|
402
|
-
// Build only the guidance block and append it; existing entries are preserved
|
|
403
|
-
const guidance = buildPrompt({
|
|
404
|
-
recentEdits,
|
|
405
|
-
workspaceHealth,
|
|
406
|
-
hasRecentKbEdit,
|
|
407
|
-
recentCommentSuggestion,
|
|
408
|
-
posture: posture.state,
|
|
409
|
-
cache,
|
|
410
|
-
workspaceRoot: input.worktree,
|
|
411
|
-
branch: currentBranch,
|
|
412
|
-
completionReminder: cfg.guidance.smartEnforcement.completionReminder,
|
|
413
|
-
maintenanceDegraded,
|
|
414
|
-
degradedMode: cfg.guidance.smartEnforcement.degradedMode,
|
|
415
|
-
showDegradedAdvisory,
|
|
416
|
-
...(autoBriefResult !== undefined ? { autoBriefResult } : {}),
|
|
417
|
-
...(lastRiskClass != null ? { riskClass: lastRiskClass } : {}),
|
|
418
|
-
});
|
|
419
|
-
logger.info("smart-enforcement.guidance", {
|
|
420
|
-
event: "smart_enforcement_guidance",
|
|
421
|
-
emitted: guidance.trim() !== "" && guidance.trim() !== SENTINEL,
|
|
422
|
-
posture: posture.state,
|
|
423
|
-
posture_state: posture.state,
|
|
424
|
-
guidance_action: guidance.trim() !== "" && guidance.trim() !== SENTINEL
|
|
425
|
-
? "emit"
|
|
426
|
-
: "skip",
|
|
427
|
-
risk_class: lastRiskClass,
|
|
428
|
-
recent_edits: recentEdits.length,
|
|
429
|
-
static_degraded: posture.maintenanceDegraded,
|
|
430
|
-
runtime_degraded: runtimeOverlay.degraded,
|
|
431
|
-
merged_degraded: maintenanceDegraded,
|
|
432
|
-
overlay_cause: runtimeOverlay.primaryCause ?? null,
|
|
433
|
-
});
|
|
434
|
-
// Emit completion-reminder log only when prompt-visible reminder text is present
|
|
435
|
-
const REMINDER_TEXT = "Run `kb_check` before completing this task.";
|
|
436
|
-
if (cfg.guidance.smartEnforcement.completionReminder &&
|
|
437
|
-
!maintenanceDegraded &&
|
|
438
|
-
guidance.includes(REMINDER_TEXT)) {
|
|
439
|
-
logger.info("smart-enforcement.completion-reminder", {
|
|
440
|
-
event: "smart_enforcement_completion_reminder",
|
|
441
|
-
risk_class: lastRiskClass,
|
|
442
|
-
posture: posture.state,
|
|
443
|
-
posture_state: posture.state,
|
|
444
|
-
guidance_action: "completion_reminder",
|
|
445
|
-
reminder: "kb_check",
|
|
446
|
-
static_degraded: posture.maintenanceDegraded,
|
|
447
|
-
runtime_degraded: runtimeOverlay.degraded,
|
|
448
|
-
merged_degraded: maintenanceDegraded,
|
|
449
|
-
overlay_cause: runtimeOverlay.primaryCause ?? null,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
// Latch degraded advisory warning-once state
|
|
453
|
-
if (showDegradedAdvisory && guidance.includes("Maintenance degraded")) {
|
|
454
|
-
degradedWarnedOnce = true;
|
|
455
|
-
}
|
|
456
|
-
const last = output.system.length > 0
|
|
457
|
-
? output.system[output.system.length - 1]
|
|
458
|
-
: undefined;
|
|
459
|
-
if (last !== guidance) {
|
|
460
|
-
output.system.push(guidance);
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
if (hookMode === "chat-params" || hookMode === "auto") {
|
|
465
|
-
hooks["chat.params"] = async (_input, _output) => {
|
|
466
|
-
// chat.params only exposes model options, not prompt text.
|
|
467
|
-
// In auto mode the system.transform hook handles injection;
|
|
468
|
-
// this hook is a no-op but kept registered so OpenCode knows
|
|
469
|
-
// the plugin is active.
|
|
470
|
-
if (hookMode === "auto") {
|
|
471
|
-
logger.info("kibi-opencode: chat.params hook active (prompt injection via system.transform)");
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
logger.info("kibi-opencode: setup complete");
|
|
477
|
-
if (input.client && !maintenanceDegraded) {
|
|
478
|
-
const client = input.client;
|
|
479
|
-
const scheduleStartupNotify = startupNotifyGlobals.__kibi_test_schedule_startup_notify ??
|
|
480
|
-
((callback, delayMs) => {
|
|
481
|
-
setTimeout(callback, delayMs);
|
|
482
|
-
});
|
|
483
|
-
scheduleStartupNotify(() => {
|
|
484
|
-
notifyStartup(client, {
|
|
485
|
-
suppressToast: cfg.ux.toastStartup === false,
|
|
486
|
-
directory: input.directory,
|
|
487
|
-
});
|
|
488
|
-
}, 2000);
|
|
489
|
-
}
|
|
490
|
-
return hooks;
|
|
491
|
-
};
|
|
492
|
-
export default kibiOpencodePlugin;
|
|
1
|
+
export { default } from "./plugin.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the canonical native alias for the Kibi MCP bootstrap workflow.
|
|
3
|
+
* This is a thin wrapper over the MCP-defined workflow, preserving all
|
|
4
|
+
* semantic markers while removing namespacing and keeping text concise.
|
|
5
|
+
*
|
|
6
|
+
* Markers (MUST PRESERVE):
|
|
7
|
+
* - "at most 4 bounded questions"
|
|
8
|
+
* - "kb_autopilot_generate"
|
|
9
|
+
* - "preview" or "approval"
|
|
10
|
+
* - "kb_upsert"
|
|
11
|
+
* - "kb_check"
|
|
12
|
+
* - "sequential" or similar ordering language
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildInitKibiAlias(): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the canonical native alias for the Kibi MCP bootstrap workflow.
|
|
3
|
+
* This is a thin wrapper over the MCP-defined workflow, preserving all
|
|
4
|
+
* semantic markers while removing namespacing and keeping text concise.
|
|
5
|
+
*
|
|
6
|
+
* Markers (MUST PRESERVE):
|
|
7
|
+
* - "at most 4 bounded questions"
|
|
8
|
+
* - "kb_autopilot_generate"
|
|
9
|
+
* - "preview" or "approval"
|
|
10
|
+
* - "kb_upsert"
|
|
11
|
+
* - "kb_check"
|
|
12
|
+
* - "sequential" or similar ordering language
|
|
13
|
+
*/
|
|
14
|
+
// implements REQ-opencode-kibi-briefing-v2
|
|
15
|
+
export function buildInitKibiAlias() {
|
|
16
|
+
const lines = [
|
|
17
|
+
"# /init-kibi: Interactive Activation",
|
|
18
|
+
"",
|
|
19
|
+
"Use this workflow to onboard a new or empty repository into Kibi through interactive discovery.",
|
|
20
|
+
"",
|
|
21
|
+
"## 1. Gather Declared Context",
|
|
22
|
+
"Ask at most 4 bounded questions to gather intent: Project Summary, Source of Truth, Priority Root, and Verification Anchors.",
|
|
23
|
+
"",
|
|
24
|
+
"## 2. Synthesize Candidates",
|
|
25
|
+
"Call `kb_autopilot_generate` with the gathered context to synthesize candidate entities. This tool is **read-only**.",
|
|
26
|
+
"",
|
|
27
|
+
"## 3. Preview and Approval",
|
|
28
|
+
"Present the `promptBlock` and a summary of synthesized `candidates` to the user. **Wait for explicit approval** before proceeding to writes.",
|
|
29
|
+
"",
|
|
30
|
+
"## 4. Apply Approved Candidates",
|
|
31
|
+
"Apply approved candidates sequentially using `kb_upsert` (following ascending phase order). Confirm success of each write before moving to the next. Run `kb_check` after the batch to verify KB integrity.",
|
|
32
|
+
"",
|
|
33
|
+
"## Rules",
|
|
34
|
+
"- Never apply changes without a user-facing preview and approval.",
|
|
35
|
+
"- Guidance must stay MCP-only; do not suggest `kibi` CLI commands.",
|
|
36
|
+
];
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const INIT_KIBI_COMMAND_NAME = "init-kibi";
|
|
2
|
+
export declare const INIT_KIBI_COMMAND_TEMPLATE: string;
|
|
3
|
+
export declare const INIT_KIBI_COMMAND_DESCRIPTION = "Run the Kibi interactive activation workflow.";
|
|
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 InitKibiCommandCapability = {
|
|
16
|
+
supported: true;
|
|
17
|
+
pluginVersion: string;
|
|
18
|
+
} | {
|
|
19
|
+
supported: false;
|
|
20
|
+
reason: string;
|
|
21
|
+
pluginVersion?: string;
|
|
22
|
+
};
|
|
23
|
+
interface InitKibiCapabilityDetectionInput {
|
|
24
|
+
pluginVersion?: string;
|
|
25
|
+
pluginHooksDts?: string;
|
|
26
|
+
sdkTypesDts?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function findSdkPackageJsonForPluginRoot(pluginRoot: string): string | undefined;
|
|
29
|
+
export declare function detectInitKibiCommandCapability(input: InitKibiCapabilityDetectionInput): InitKibiCommandCapability;
|
|
30
|
+
export declare function getInitKibiCommandCapability(): InitKibiCommandCapability;
|
|
31
|
+
export declare function registerInitKibiCommand(configInput: unknown, capability?: InitKibiCommandCapability): InitKibiCommandCapability;
|
|
32
|
+
export {};
|