kibi-opencode 0.19.4 → 2.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/auto-update.d.ts +2 -0
- package/dist/auto-update.js +5 -4
- package/dist/e2e-coverage-signals.js +3 -3
- package/dist/enforcement-policy.d.ts +10 -0
- package/dist/enforcement-policy.js +23 -23
- package/dist/file-entity-links.d.ts +4 -0
- package/dist/file-entity-links.js +6 -4
- 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 +34 -0
- package/dist/{init-kibi-capability.js → kibi-bootstrap-capability.js} +26 -16
- package/dist/meaningful-change-classifier.d.ts +1 -0
- package/dist/meaningful-change-classifier.js +40 -10
- package/dist/path-kind.js +21 -39
- package/dist/plugin-startup.js +3 -3
- package/dist/plugin.d.ts +4 -1
- package/dist/plugin.js +81 -374
- 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/session-edit-state.d.ts +4 -0
- package/dist/session-edit-state.js +5 -3
- package/dist/smart-enforcement-events.d.ts +100 -0
- package/dist/smart-enforcement-events.js +414 -0
- package/dist/version-metadata.json +1 -1
- package/dist/work-context-resolver.d.ts +10 -0
- package/dist/work-context-resolver.js +15 -17
- 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/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 };
|
package/dist/prompt.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { isPluginEnabled } from "./config.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getKibiBootstrapCommandCapability, } from "./kibi-bootstrap-capability.js";
|
|
4
4
|
import { getSourceLinkedRequirementIds } from "./source-linked-guidance.js";
|
|
5
5
|
const SENTINEL = "<!-- kibi-opencode -->";
|
|
6
6
|
// ── Token budget enforcement ───────────────────────────────────────────
|
|
@@ -56,17 +56,17 @@ function deriveFileBucket(pathKind) {
|
|
|
56
56
|
function getFocusEdit(context) {
|
|
57
57
|
return (context.focusEdit ?? context.recentEdits[context.recentEdits.length - 1]);
|
|
58
58
|
}
|
|
59
|
-
function
|
|
59
|
+
function buildKibiBootstrapReference(capability = getKibiBootstrapCommandCapability()) {
|
|
60
60
|
if (capability.supported) {
|
|
61
|
-
return "Bootstrap existing
|
|
61
|
+
return "Bootstrap existing repositories with `/kibi-bootstrap`; the canonical `kibi-bootstrap` skill owns planner, approval, apply, and repair semantics.";
|
|
62
62
|
}
|
|
63
|
-
return "
|
|
63
|
+
return "This host cannot inject native `/kibi-bootstrap`; use `/kibi:kibi-bootstrap:mcp` to route to the canonical `kibi-bootstrap` skill.";
|
|
64
64
|
}
|
|
65
|
-
function buildBootstrapRequiredBody(capability =
|
|
66
|
-
const
|
|
67
|
-
? "
|
|
68
|
-
: "-
|
|
69
|
-
return `Kibi
|
|
65
|
+
function buildBootstrapRequiredBody(capability = getKibiBootstrapCommandCapability()) {
|
|
66
|
+
const command = capability.supported
|
|
67
|
+
? "Use `/kibi-bootstrap`."
|
|
68
|
+
: "Use `/kibi:kibi-bootstrap:mcp`; this host does not support native command injection.";
|
|
69
|
+
return `Kibi infrastructure is not initialized. Run \`kibi init\`, then inspect \`kb_status.bootstrap.nextAction\` and route the explicit bootstrap request to the canonical \`kibi-bootstrap\` skill. ${command}`;
|
|
70
70
|
}
|
|
71
71
|
function buildHardGateBlock(block) {
|
|
72
72
|
const pathLines = block.shownPaths.map((path) => `- \`${path}\``);
|
|
@@ -80,18 +80,7 @@ function buildHardGateBlock(block) {
|
|
|
80
80
|
reasonLine,
|
|
81
81
|
"Affected files:",
|
|
82
82
|
...pathLines,
|
|
83
|
-
"
|
|
84
|
-
"- MCP tools and the trusted project-local CLI are peer surfaces over the same 18 operations; choose by what is visible and approved here.",
|
|
85
|
-
"- If Kibi MCP tools are visible and approved, use MCP.",
|
|
86
|
-
"- Otherwise, in a trusted workspace, use the project-local CLI's dedicated JSON routes with `--input <file|->`.",
|
|
87
|
-
"- If neither interface is available, Kibi operation is blocked; tell the operator to enable MCP or the trusted project-local CLI.",
|
|
88
|
-
"- Do not infer MCP availability from config file existence. Do not read or edit `.kb/` files directly.",
|
|
89
|
-
"- Query before mutate. Run `kb_upsert` sequentially. Run `kb_check` before completion.",
|
|
90
|
-
"- Run `kb_search` for impacted requirements, tests, ADRs, and facts.",
|
|
91
|
-
"- Run `kb_query` with `sourceFile` for each affected file.",
|
|
92
|
-
"- Run `kb_status` if branch or snapshot freshness matters.",
|
|
93
|
-
"- Run `kb_upsert` for required traceability, relationship, or fact updates.",
|
|
94
|
-
"- Run `kb_check` before continuing.",
|
|
83
|
+
"Inspect typed Kibi status `nextActions`, then route to the canonical kibi-usage, kibi-freshness, or kibi-traceability skill. Use the host-visible approved peer surface and never read or edit `.kb` directly.",
|
|
95
84
|
]
|
|
96
85
|
.filter((line) => line !== null)
|
|
97
86
|
.join("\n");
|
|
@@ -123,17 +112,13 @@ Production code: use \`implements\` (symbol→req) for requirement ownership. Te
|
|
|
123
112
|
- \`covered_by\` is coverage evidence only
|
|
124
113
|
- Prefer scenario-first: req→scenario→test when scenarios exist
|
|
125
114
|
- Route durable knowledge comments to KB entities, not inline comments
|
|
126
|
-
-
|
|
115
|
+
- .kb/symbols.yaml refresh is required when extraction output changes; do not revert as scope creep`,
|
|
127
116
|
manual_kb_edit: `⚠️ **WARNING: Direct .kb/ edits bypass validation**
|
|
128
117
|
|
|
129
|
-
|
|
130
|
-
- Query before mutate and run kb_upsert sequentially
|
|
131
|
-
- Use kb_upsert to create/update entities
|
|
132
|
-
- Use kb_delete to remove entities
|
|
133
|
-
- Use kb_check to validate consistency`,
|
|
118
|
+
Do not read or edit \`.kb/\` directly. Follow typed Kibi status \`nextActions\` and route durable changes through the canonical kibi-usage, kibi-freshness, or kibi-traceability skill.`,
|
|
134
119
|
};
|
|
135
120
|
// ── Posture overrides ──────────────────────────────────────────────────
|
|
136
|
-
export function postureGuidance(posture, capability =
|
|
121
|
+
export function postureGuidance(posture, capability = getKibiBootstrapCommandCapability()) {
|
|
137
122
|
// implements REQ-opencode-prompt-injection
|
|
138
123
|
switch (posture) {
|
|
139
124
|
case "vendored_only":
|
|
@@ -146,7 +131,7 @@ ${buildBootstrapRequiredBody(capability)}`;
|
|
|
146
131
|
case "root_partial":
|
|
147
132
|
return `⚠️ **Partial KB setup detected**
|
|
148
133
|
|
|
149
|
-
Root .kb/
|
|
134
|
+
Root .kb/manifest.json exists but some canonical KB targets under .kb/ are missing. Guidance remains advisory until the user/operator restores the canonical layout.`;
|
|
150
135
|
default:
|
|
151
136
|
return null;
|
|
152
137
|
}
|
|
@@ -155,7 +140,7 @@ Root .kb/config.json exists but some configured KB targets are missing. Guidance
|
|
|
155
140
|
/**
|
|
156
141
|
* Build prompt guidance block based on posture, risk class, and cache state.
|
|
157
142
|
*/
|
|
158
|
-
function buildContextualGuidance(context, capability =
|
|
143
|
+
function buildContextualGuidance(context, capability = getKibiBootstrapCommandCapability()) {
|
|
159
144
|
if (context.hardGateBlock) {
|
|
160
145
|
return `${SENTINEL}\n\n${buildHardGateBlock(context.hardGateBlock)}`;
|
|
161
146
|
}
|
|
@@ -437,7 +422,7 @@ function buildCommentSuggestionGuidance(suggestion) {
|
|
|
437
422
|
Your recent code edit contains a comment that looks like a **strict domain fact** (invariants, properties, limits, defaults, or cardinality constraints).
|
|
438
423
|
|
|
439
424
|
**Action**: Route to a FACT entity in the strict fact lane:
|
|
440
|
-
- Create
|
|
425
|
+
- Create \`.kb/facts/FACT-xxx.md\` with the invariant (use \`constrains\` + \`requires_property\` for contradiction-safe reasoning)
|
|
441
426
|
- Bug/workaround notes: use \`fact_kind: observation\` or \`meta\` instead — these are non-blocking and excluded from contradiction inference
|
|
442
427
|
- Link it to relevant requirements
|
|
443
428
|
|
|
@@ -448,7 +433,7 @@ This keeps domain truths centralized, searchable, and contradiction-safe.`;
|
|
|
448
433
|
Your recent code edit contains a comment that looks like a **technical decision** (tradeoffs, rationale, or architecture choices).
|
|
449
434
|
|
|
450
435
|
**Action**: Instead of inline comments, route this to an ADR entity:
|
|
451
|
-
- Create
|
|
436
|
+
- Create \`.kb/adr/ADR-xxx.md\` documenting the decision
|
|
452
437
|
- Include context, options considered, and the chosen approach
|
|
453
438
|
- Link to constrained code symbols
|
|
454
439
|
|
|
@@ -459,7 +444,7 @@ This preserves decision context for future maintainers.`;
|
|
|
459
444
|
Your recent code edit contains a comment that looks like **behavior intent** (system capabilities or user-facing requirements).
|
|
460
445
|
|
|
461
446
|
**Action**: Instead of inline comments, route this to a REQ entity:
|
|
462
|
-
- Create
|
|
447
|
+
- Create \`.kb/requirements/REQ-xxx.md\` with the behavior description
|
|
463
448
|
- Add SCEN and TEST entities for specification and verification
|
|
464
449
|
- Link code: production uses \`implements\` (symbol→req) for ownership; test code uses \`executable_for\`; \`covered_by\` is coverage evidence only
|
|
465
450
|
|
|
@@ -478,41 +463,20 @@ Before implementing or explaining code:
|
|
|
478
463
|
/**
|
|
479
464
|
* Build the static guidance block (original behavior).
|
|
480
465
|
*/
|
|
481
|
-
function buildBaseGuidance(capability =
|
|
466
|
+
function buildBaseGuidance(capability = getKibiBootstrapCommandCapability()) {
|
|
482
467
|
return `${SENTINEL}
|
|
483
|
-
|
|
468
|
+
Use typed Kibi status and its \`nextActions\` as the workflow contract. Route general work to \`kibi-usage\`, freshness decisions to \`kibi-freshness\`, source proof to \`kibi-traceability\`, and an explicit bootstrap request to \`kibi-bootstrap\`. Use the host-visible approved Kibi peer surface; if it is unavailable, report the blocked capability. Never read or edit \`.kb/\` directly.
|
|
484
469
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
Keep changed symbols traceable: production code uses \`implements\` (symbol→req) for ownership; test code uses \`executable_for\`; \`covered_by\` is coverage evidence only. Inline \`// implements REQ-xxx\` comments remain backward-compatible.
|
|
488
|
-
|
|
489
|
-
Kibi capability selection (MCP tools and the trusted project-local CLI are peer surfaces over the same 18 operations):
|
|
490
|
-
1. If Kibi MCP tools are visible and approved, use MCP.
|
|
491
|
-
2. Otherwise, in a trusted workspace, use the project-local CLI's dedicated JSON routes with \`--input <file|->\`.
|
|
492
|
-
3. If neither interface is available, Kibi operation is blocked; tell the operator to enable MCP or the trusted project-local CLI.
|
|
493
|
-
Do not infer MCP availability from config file existence. Do not read or edit \`.kb/\` files directly. Query before mutate. Run \`kb_upsert\` sequentially. Run \`kb_check\` before completion.
|
|
470
|
+
Keep changed symbols traceable: production code uses \`implements\` (symbol→req) for ownership; test code uses \`executable_for\`; \`covered_by\` is coverage evidence only.
|
|
494
471
|
|
|
495
472
|
Dogfood note for this repo: OpenCode here uses local built \`kibi-mcp\` and \`kibi-opencode\` artifacts. If you change package versions or local package wiring, run \`bun run build\` before relying on OpenCode in this workspace.
|
|
496
473
|
|
|
497
|
-
|
|
498
|
-
1. **Discover**: Run kb_search for REQ, ADR, TEST, FACT. Decompose broad queries (e.g., "Apple Sign-In").
|
|
499
|
-
2. **Confirm**: Run kb_query with sourceFile, id, type, or tags once you know the exact follow-up target.
|
|
500
|
-
3. **Inspect freshness**: Run kb_status when branch or stale-state confidence matters.
|
|
501
|
-
4. **Document intent**: If you are about to explain code, STOP. Route that explanation to kb_upsert instead of inline comments.
|
|
502
|
-
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).
|
|
503
|
-
6. **Validate**: Run kb_check after KB mutations to catch violations early.
|
|
504
|
-
7. **Before completion/commit**: Kibi impact evidence is required before completion/commit. If extraction output changes, refresh documentation/symbols.yaml and do not revert that update as scope creep.
|
|
505
|
-
|
|
506
|
-
MCP and CLI expose the same 18 Kibi operations. Use the exact MCP tool or dedicated CLI route; do not invent a generic runner.
|
|
507
|
-
|
|
508
|
-
For comprehensive Kibi usage guidance (relationships, fact lanes, workflows), use \`kb_skills_load\` with skill id \`kibi-usage\`.
|
|
509
|
-
|
|
510
|
-
${buildInitKibiBootstrapReference(capability)}`;
|
|
474
|
+
${buildKibiBootstrapReference(capability)}`;
|
|
511
475
|
}
|
|
512
476
|
/**
|
|
513
477
|
* Build prompt with contextual guidance based on posture, risk class, and cache state.
|
|
514
478
|
*/
|
|
515
|
-
export function buildPrompt(context, capability =
|
|
479
|
+
export function buildPrompt(context, capability = getKibiBootstrapCommandCapability()) {
|
|
516
480
|
if (!context) {
|
|
517
481
|
return buildBaseGuidance(capability).trim();
|
|
518
482
|
}
|
|
@@ -521,7 +485,7 @@ export function buildPrompt(context, capability = getInitKibiCommandCapability()
|
|
|
521
485
|
/**
|
|
522
486
|
* Inject prompt guidance if not already present.
|
|
523
487
|
*/
|
|
524
|
-
export function injectPrompt(current, config, context, capability =
|
|
488
|
+
export function injectPrompt(current, config, context, capability = getKibiBootstrapCommandCapability()) {
|
|
525
489
|
if (!config.prompt.enabled || !isPluginEnabled(config)) {
|
|
526
490
|
return current;
|
|
527
491
|
}
|
package/dist/repo-posture.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// implements REQ-opencode-smart-enforcement-v1, REQ-opencode-kibi-plugin-v1
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
//
|
|
5
|
-
const
|
|
6
|
-
requirements: "
|
|
7
|
-
scenarios: "
|
|
8
|
-
tests: "
|
|
9
|
-
adr: "
|
|
10
|
-
flags: "
|
|
11
|
-
events: "
|
|
12
|
-
facts: "
|
|
13
|
-
symbols: "
|
|
4
|
+
// Canonical Kibi knowledge targets — must stay in sync with kb-paths.ts.
|
|
5
|
+
const CANONICAL_KB_TARGETS = {
|
|
6
|
+
requirements: ".kb/requirements",
|
|
7
|
+
scenarios: ".kb/scenarios",
|
|
8
|
+
tests: ".kb/tests",
|
|
9
|
+
adr: ".kb/adr",
|
|
10
|
+
flags: ".kb/flags",
|
|
11
|
+
events: ".kb/events",
|
|
12
|
+
facts: ".kb/facts",
|
|
13
|
+
symbols: ".kb/symbols.yaml",
|
|
14
14
|
};
|
|
15
15
|
// ── helpers ────────────────────────────────────────────────────────────
|
|
16
16
|
function findVendoredTrees(cwd) {
|
|
@@ -19,7 +19,7 @@ function findVendoredTrees(cwd) {
|
|
|
19
19
|
["kibi", "opencode.json"],
|
|
20
20
|
["kibi", "package.json"],
|
|
21
21
|
["kibi", "packages", "mcp"],
|
|
22
|
-
["kibi", "
|
|
22
|
+
["kibi", ".kb"],
|
|
23
23
|
];
|
|
24
24
|
for (const marker of vendoredMarkers) {
|
|
25
25
|
const markerPath = join(cwd, ...marker);
|
|
@@ -37,63 +37,22 @@ function findVendoredTrees(cwd) {
|
|
|
37
37
|
}
|
|
38
38
|
return [...new Set(results)];
|
|
39
39
|
}
|
|
40
|
-
/** Check if
|
|
41
|
-
function
|
|
42
|
-
return existsSync(join(cwd, ".kb", "
|
|
40
|
+
/** Check if `.kb/manifest.json` exists at root. */
|
|
41
|
+
function rootKbManifestExists(cwd) {
|
|
42
|
+
return existsSync(join(cwd, ".kb", "manifest.json"));
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
45
|
-
function readRootConfig(cwd) {
|
|
46
|
-
try {
|
|
47
|
-
return (JSON.parse(readFileSync(join(cwd, ".kb", "config.json"), "utf8")) || null);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/** Check if all configured KB targets resolve (directories / files exist). */
|
|
44
|
+
/** Check if all canonical KB targets resolve (directories / files exist). */
|
|
54
45
|
function rootTargetsAllResolve(cwd) {
|
|
55
|
-
const
|
|
56
|
-
let config = {};
|
|
57
|
-
try {
|
|
58
|
-
config = JSON.parse(readFileSync(configPath, "utf8")) || {};
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
const paths = config.paths;
|
|
64
|
-
const defaultKeys = [
|
|
65
|
-
"requirements",
|
|
66
|
-
"scenarios",
|
|
67
|
-
"tests",
|
|
68
|
-
"adr",
|
|
69
|
-
"flags",
|
|
70
|
-
"events",
|
|
71
|
-
"facts",
|
|
72
|
-
"symbols",
|
|
73
|
-
];
|
|
74
|
-
for (const key of defaultKeys) {
|
|
75
|
-
const raw = paths?.[key] ?? DEFAULT_SYNC_PATHS[key];
|
|
46
|
+
for (const raw of Object.values(CANONICAL_KB_TARGETS)) {
|
|
76
47
|
if (!raw)
|
|
77
48
|
return false;
|
|
78
|
-
|
|
79
|
-
const normalized = raw.replace(/\/+$/, "");
|
|
80
|
-
const isFile = normalized.endsWith(".yaml") || normalized.endsWith(".yml");
|
|
49
|
+
const isFile = raw.endsWith(".yaml") || raw.endsWith(".yml");
|
|
81
50
|
if (isFile) {
|
|
82
|
-
if (!existsSync(resolve(cwd,
|
|
51
|
+
if (!existsSync(resolve(cwd, raw)))
|
|
83
52
|
return false;
|
|
84
53
|
}
|
|
85
|
-
else {
|
|
86
|
-
|
|
87
|
-
const segments = normalized.split("/");
|
|
88
|
-
const rootSegments = [];
|
|
89
|
-
for (const seg of segments) {
|
|
90
|
-
if (seg.includes("*") || seg.includes("?") || seg.includes("["))
|
|
91
|
-
break;
|
|
92
|
-
rootSegments.push(seg);
|
|
93
|
-
}
|
|
94
|
-
const dirPath = rootSegments.join("/") || ".";
|
|
95
|
-
if (!existsSync(resolve(cwd, dirPath)))
|
|
96
|
-
return false;
|
|
54
|
+
else if (!existsSync(resolve(cwd, raw))) {
|
|
55
|
+
return false;
|
|
97
56
|
}
|
|
98
57
|
}
|
|
99
58
|
return true;
|
|
@@ -134,54 +93,42 @@ function hasRootKibiIntent(cwd) {
|
|
|
134
93
|
}
|
|
135
94
|
// ── main classifier ────────────────────────────────────────────────────
|
|
136
95
|
export function detectPosture(cwd) {
|
|
137
|
-
const
|
|
96
|
+
const hasRootManifest = rootKbManifestExists(cwd);
|
|
138
97
|
const vendoredTrees = findVendoredTrees(cwd);
|
|
139
|
-
// 1. hybrid_root_plus_vendored — root
|
|
140
|
-
if (
|
|
98
|
+
// 1. hybrid_root_plus_vendored — root manifest AND vendored subtrees
|
|
99
|
+
if (hasRootManifest && vendoredTrees.length > 0) {
|
|
141
100
|
return {
|
|
142
101
|
state: "hybrid_root_plus_vendored",
|
|
143
102
|
needsBootstrap: false,
|
|
144
|
-
reason: `Root .kb/
|
|
103
|
+
reason: `Root .kb/manifest.json exists alongside vendored tree(s): ${vendoredTrees.join(", ")}`,
|
|
145
104
|
maintenanceDegraded: false,
|
|
146
105
|
};
|
|
147
106
|
}
|
|
148
|
-
// 2-3. root
|
|
149
|
-
if (
|
|
150
|
-
const config = readRootConfig(cwd);
|
|
151
|
-
const maint = config?.maintenance;
|
|
152
|
-
const maintenanceDisabled = maint !== undefined && maint.enabled === false;
|
|
153
|
-
// maintenance_degraded overlay: maintenance explicitly disabled → root_active
|
|
154
|
-
if (maintenanceDisabled) {
|
|
155
|
-
return {
|
|
156
|
-
state: "root_active",
|
|
157
|
-
needsBootstrap: false,
|
|
158
|
-
reason: "Root .kb/config.json exists; maintenance mode explicitly disabled",
|
|
159
|
-
maintenanceDegraded: true,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
107
|
+
// 2-3. root manifest exists (check target resolution)
|
|
108
|
+
if (hasRootManifest) {
|
|
162
109
|
const allResolve = rootTargetsAllResolve(cwd);
|
|
163
110
|
if (allResolve) {
|
|
164
111
|
return {
|
|
165
112
|
state: "root_active",
|
|
166
113
|
needsBootstrap: false,
|
|
167
|
-
reason: "Root .kb/
|
|
114
|
+
reason: "Root .kb/manifest.json exists and all canonical KB targets resolve",
|
|
168
115
|
maintenanceDegraded: false,
|
|
169
116
|
};
|
|
170
117
|
}
|
|
171
|
-
// 3. root_partial —
|
|
118
|
+
// 3. root_partial — manifest exists but targets are missing
|
|
172
119
|
return {
|
|
173
120
|
state: "root_partial",
|
|
174
121
|
needsBootstrap: true,
|
|
175
|
-
reason: "Root .kb/
|
|
122
|
+
reason: "Root .kb/manifest.json exists but some canonical KB targets are missing",
|
|
176
123
|
maintenanceDegraded: false,
|
|
177
124
|
};
|
|
178
125
|
}
|
|
179
|
-
// 4. vendored_only — no root
|
|
126
|
+
// 4. vendored_only — no root manifest, but vendored markers found
|
|
180
127
|
if (vendoredTrees.length > 0) {
|
|
181
128
|
return {
|
|
182
129
|
state: "vendored_only",
|
|
183
130
|
needsBootstrap: false,
|
|
184
|
-
reason: `No root .kb/
|
|
131
|
+
reason: `No root .kb/manifest.json, but vendored Kibi tree(s) detected: ${vendoredTrees.join(", ")}`,
|
|
185
132
|
maintenanceDegraded: false,
|
|
186
133
|
};
|
|
187
134
|
}
|
|
@@ -27,10 +27,10 @@ export interface RiskClassification {
|
|
|
27
27
|
* Classify a file edit into a deterministic risk class.
|
|
28
28
|
*
|
|
29
29
|
* Classification order (first match wins):
|
|
30
|
-
* 1. manual_kb_edit —
|
|
30
|
+
* 1. manual_kb_edit — opaque/derived `.kb/` files (pathKind "kb")
|
|
31
31
|
* 2. safe_test_only — pathKind is "test"
|
|
32
|
-
* 3. req_policy_candidate — pathKind is "requirement"
|
|
33
|
-
* 4. kb_doc_structural — pathKind is "scenario", "adr", or "
|
|
32
|
+
* 3. req_policy_candidate — pathKind is "requirement" (including `.kb/requirements`)
|
|
33
|
+
* 4. kb_doc_structural — pathKind is "scenario", "adr", "fact", "flag", "event", or "symbol"
|
|
34
34
|
* 5. safe_docs_only — pathKind is "unknown" (markdown/config/etc.)
|
|
35
35
|
* 6. traceability_candidate — code with exports AND (hasDurableComment OR missing traceability)
|
|
36
36
|
* 7. behavior_candidate — code with exports, already has traceability, no durable comment
|
package/dist/risk-classifier.js
CHANGED
|
@@ -11,10 +11,10 @@ const TRACEABILITY_PATTERN = /implements\s+REQ-[A-Za-z0-9_-]+/i;
|
|
|
11
11
|
* Classify a file edit into a deterministic risk class.
|
|
12
12
|
*
|
|
13
13
|
* Classification order (first match wins):
|
|
14
|
-
* 1. manual_kb_edit —
|
|
14
|
+
* 1. manual_kb_edit — opaque/derived `.kb/` files (pathKind "kb")
|
|
15
15
|
* 2. safe_test_only — pathKind is "test"
|
|
16
|
-
* 3. req_policy_candidate — pathKind is "requirement"
|
|
17
|
-
* 4. kb_doc_structural — pathKind is "scenario", "adr", or "
|
|
16
|
+
* 3. req_policy_candidate — pathKind is "requirement" (including `.kb/requirements`)
|
|
17
|
+
* 4. kb_doc_structural — pathKind is "scenario", "adr", "fact", "flag", "event", or "symbol"
|
|
18
18
|
* 5. safe_docs_only — pathKind is "unknown" (markdown/config/etc.)
|
|
19
19
|
* 6. traceability_candidate — code with exports AND (hasDurableComment OR missing traceability)
|
|
20
20
|
* 7. behavior_candidate — code with exports, already has traceability, no durable comment
|
|
@@ -22,11 +22,14 @@ const TRACEABILITY_PATTERN = /implements\s+REQ-[A-Za-z0-9_-]+/i;
|
|
|
22
22
|
*/
|
|
23
23
|
export function classifyRisk(params) {
|
|
24
24
|
const { pathKind, isUnderKb, hasMustPriority, hasDurableComment, fileContent, } = params;
|
|
25
|
-
// 1. manual_kb_edit —
|
|
26
|
-
|
|
25
|
+
// 1. manual_kb_edit — derived/runtime `.kb/` files, not canonical lanes.
|
|
26
|
+
// Entity lanes under `.kb/` keep their typed pathKind (requirement, symbol, …).
|
|
27
|
+
if (isUnderKb && pathKind === "kb") {
|
|
27
28
|
return {
|
|
28
29
|
riskClass: "manual_kb_edit",
|
|
29
|
-
reasons: [
|
|
30
|
+
reasons: [
|
|
31
|
+
"File is opaque or derived `.kb/` runtime state — manual edits bypass KB validation",
|
|
32
|
+
],
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
35
|
// 2. safe_test_only — test files are low-risk
|
package/dist/scheduler.js
CHANGED
|
@@ -256,7 +256,7 @@ async function runKibiSync(worktree) {
|
|
|
256
256
|
? `${error.message}${signal}`
|
|
257
257
|
: signal || undefined;
|
|
258
258
|
resolve({
|
|
259
|
-
exitCode: error.code
|
|
259
|
+
exitCode: typeof error.code === "number" ? error.code : 1,
|
|
260
260
|
syncCommand: "kibi sync",
|
|
261
261
|
...(truncatedOut !== undefined ? { syncStdout: truncatedOut } : {}),
|
|
262
262
|
...(truncatedErr !== undefined ? { syncStderr: truncatedErr } : {}),
|
|
@@ -285,7 +285,7 @@ async function runKibiCheck(worktree, rules) {
|
|
|
285
285
|
const truncatedOut = truncateSyncOutput(stdout || undefined);
|
|
286
286
|
const truncatedErr = truncateSyncOutput(stderr || undefined);
|
|
287
287
|
resolve({
|
|
288
|
-
exitCode: error
|
|
288
|
+
exitCode: error && typeof error.code === "number" ? error.code : 0,
|
|
289
289
|
...(truncatedOut !== undefined ? { stdout: truncatedOut } : {}),
|
|
290
290
|
...(truncatedErr !== undefined ? { stderr: truncatedErr } : {}),
|
|
291
291
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export type EditEventKind = string;
|
|
2
|
+
export declare function pushEventHintIfKind(hints: Array<{
|
|
3
|
+
kind: string;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
}>, kind: string | undefined, timestamp: number): void;
|
|
2
6
|
export interface SessionEditEntry {
|
|
3
7
|
/** Relative file path (relative to worktree root). */
|
|
4
8
|
filePath: string;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
export function pushEventHintIfKind(hints, kind, timestamp) {
|
|
5
|
+
if (kind)
|
|
6
|
+
hints.push({ kind, timestamp });
|
|
7
|
+
}
|
|
4
8
|
// ---------------------------------------------------------------------------
|
|
5
9
|
// Constants
|
|
6
10
|
// ---------------------------------------------------------------------------
|
|
@@ -162,9 +166,7 @@ export function createSessionEditState(opts) {
|
|
|
162
166
|
const abs = resolveToAbsolute(rel);
|
|
163
167
|
entry.currentHash = hashFile(abs);
|
|
164
168
|
entry.lastReconciledAt = timestamp ?? now();
|
|
165
|
-
|
|
166
|
-
entry.eventHints.push({ kind, timestamp: timestamp ?? now() });
|
|
167
|
-
}
|
|
169
|
+
pushEventHintIfKind(entry.eventHints, kind, timestamp ?? now());
|
|
168
170
|
}
|
|
169
171
|
return {
|
|
170
172
|
recordEventHint,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { CommentAnalysisResult } from "./comment-analysis.js";
|
|
2
|
+
import type { FileOperationState } from "./file-operation-state.js";
|
|
3
|
+
import type { CacheKey } from "./guidance-cache.js";
|
|
4
|
+
import type { KbFreshnessScope } from "./kb-freshness-state.js";
|
|
5
|
+
import type { PathKind } from "./path-kind.js";
|
|
6
|
+
import type { RuntimeDegradedOverlay } from "./plugin-startup.js";
|
|
7
|
+
import type { RiskClass } from "./risk-classifier.js";
|
|
8
|
+
import type { SyncScheduler } from "./scheduler.js";
|
|
9
|
+
import type { SessionEditState } from "./session-edit-state.js";
|
|
10
|
+
import type { WarningCategory } from "./session-tracker.js";
|
|
11
|
+
import type { WorkContext } from "./work-context-resolver.js";
|
|
12
|
+
export interface PluginEventInput {
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
agentIdentity?: string;
|
|
15
|
+
worktree: string;
|
|
16
|
+
directory: string;
|
|
17
|
+
}
|
|
18
|
+
export interface PluginEvent {
|
|
19
|
+
type: string;
|
|
20
|
+
properties?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** Mutable cross-event state shared by the event and chat hooks. */
|
|
23
|
+
export interface SmartEnforcementState {
|
|
24
|
+
recentEdits: Array<{
|
|
25
|
+
path: string;
|
|
26
|
+
kind: PathKind;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}>;
|
|
29
|
+
hasRecentKbEdit: boolean;
|
|
30
|
+
recentCommentSuggestion: CommentAnalysisResult | null;
|
|
31
|
+
seenFingerprints: Set<string>;
|
|
32
|
+
lastRiskClass: RiskClass | null;
|
|
33
|
+
lastRiskFilePath: string | null;
|
|
34
|
+
lastRiskScopeKey: string | null;
|
|
35
|
+
}
|
|
36
|
+
export interface KibiEventEnv {
|
|
37
|
+
input: PluginEventInput;
|
|
38
|
+
cfg: {
|
|
39
|
+
sync: {
|
|
40
|
+
enabled: boolean;
|
|
41
|
+
};
|
|
42
|
+
guidance: {
|
|
43
|
+
warnOnKbEdits: boolean;
|
|
44
|
+
commentDetection: {
|
|
45
|
+
enabled: boolean;
|
|
46
|
+
minLines: number;
|
|
47
|
+
};
|
|
48
|
+
targetedChecks: {
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
};
|
|
51
|
+
smartEnforcement: {
|
|
52
|
+
degradedMode: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
cache: {
|
|
57
|
+
isSatisfied(key: CacheKey): boolean;
|
|
58
|
+
};
|
|
59
|
+
freshnessStore: {
|
|
60
|
+
recordToolEvidence(scope: KbFreshnessScope, tool: string): void;
|
|
61
|
+
};
|
|
62
|
+
fileFilter: {
|
|
63
|
+
shouldHandleFile(filePath: string, root: string): boolean;
|
|
64
|
+
};
|
|
65
|
+
log: {
|
|
66
|
+
info(...args: unknown[]): void;
|
|
67
|
+
warn(...args: unknown[]): void;
|
|
68
|
+
};
|
|
69
|
+
rootWorkContext: WorkContext;
|
|
70
|
+
state: SmartEnforcementState;
|
|
71
|
+
resolveScopedWorkContext(filePath?: string): WorkContext;
|
|
72
|
+
getSessionEditState(context: WorkContext): SessionEditState;
|
|
73
|
+
getFileOperationState(context: WorkContext): FileOperationState;
|
|
74
|
+
getPathKindCache(context: WorkContext): Map<string, PathKind>;
|
|
75
|
+
getSchedulerForContext(context: WorkContext): SyncScheduler | null;
|
|
76
|
+
normalizeSessionPath(filePath: string, worktree?: string): string;
|
|
77
|
+
resolveWorktreePath(filePath: string, worktree?: string): string;
|
|
78
|
+
buildRiskPathScopeKey(context: WorkContext, filePath: string): string;
|
|
79
|
+
buildScopedCacheKey(context: WorkContext, riskClass: RiskClass, fileBucket: string, dirtyRelevantInputs: Iterable<string | null | undefined>): CacheKey;
|
|
80
|
+
readFileContent(filePath: string, worktree?: string): string;
|
|
81
|
+
updateRecentEditsFromSession(sessionEdits: Array<{
|
|
82
|
+
filePath: string;
|
|
83
|
+
lastReconciledAt: number;
|
|
84
|
+
}>, scopedPathKindCache: Map<string, PathKind>): SmartEnforcementState["recentEdits"];
|
|
85
|
+
recordWarning(category: WarningCategory | string, filePath: string, message: string): void;
|
|
86
|
+
getMaintenanceDegraded(): boolean;
|
|
87
|
+
getEffectiveMode(): string;
|
|
88
|
+
posture: {
|
|
89
|
+
maintenanceDegraded: boolean;
|
|
90
|
+
state: string;
|
|
91
|
+
};
|
|
92
|
+
runtimeOverlay: RuntimeDegradedOverlay;
|
|
93
|
+
lintRequirementDoc(filePath: string, worktreeRoot: string): Array<{
|
|
94
|
+
category: string;
|
|
95
|
+
message: string;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
export declare function isKbToolEventType(eventType: string): boolean;
|
|
99
|
+
export declare function handleKbToolEvent(env: KibiEventEnv, event: PluginEvent, sessionId: string | undefined): void;
|
|
100
|
+
export declare function handleFileLifecycleEvent(env: KibiEventEnv, event: PluginEvent): void;
|