kibi-cli 0.10.1 → 0.11.1
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +45 -0
- package/dist/commands/skills.d.ts +14 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +86 -0
- package/dist/commands/usage-metrics.d.ts +8 -0
- package/dist/commands/usage-metrics.d.ts.map +1 -0
- package/dist/commands/usage-metrics.js +323 -0
- package/dist/public/skills/kibi-usage/SKILL.md +125 -0
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +45 -0
- package/dist/public/skills/kibi-usage/resources/relationship-directions.md +89 -0
- package/dist/public/skills/kibi-usage/resources/workflows.md +35 -0
- package/dist/public/skills.d.ts +42 -0
- package/dist/public/skills.d.ts.map +1 -0
- package/dist/public/skills.js +262 -0
- package/package.json +6 -2
- package/src/public/skills/kibi-usage/SKILL.md +125 -0
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +45 -0
- package/src/public/skills/kibi-usage/resources/relationship-directions.md +89 -0
- package/src/public/skills/kibi-usage/resources/workflows.md +35 -0
- package/src/public/skills.ts +359 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Relationship Directions
|
|
2
|
+
|
|
3
|
+
## Direction Table
|
|
4
|
+
|
|
5
|
+
| Relationship | Source -> Target | Semantic Meaning |
|
|
6
|
+
|-------------|------------------|------------------|
|
|
7
|
+
| `implements` | symbol -> req | Production code symbol owns or implements requirement behavior |
|
|
8
|
+
| `specified_by` | req -> scenario | Requirement is specified by a BDD scenario |
|
|
9
|
+
| `verified_by` | req/scenario -> test | Requirement or scenario is verified by a test case |
|
|
10
|
+
| `validates` | test -> req/scenario | Test validates a requirement or scenario (inverse of verified_by) |
|
|
11
|
+
| `executable_for` | symbol -> test | Test symbol (code) is executable code for a test entity |
|
|
12
|
+
| `constrains` | req -> fact(subject) | Requirement constrains a strict-lane domain fact |
|
|
13
|
+
| `requires_property` | req -> fact(property_value) | Requirement requires a specific property value fact |
|
|
14
|
+
| `supersedes` | old-req -> new-req | Old requirement is formally replaced by a new requirement |
|
|
15
|
+
| `covered_by` | symbol -> test | Production symbol has test coverage evidence |
|
|
16
|
+
|
|
17
|
+
## Valid Payload Examples
|
|
18
|
+
|
|
19
|
+
### implements
|
|
20
|
+
```yaml
|
|
21
|
+
relationships:
|
|
22
|
+
- type: implements
|
|
23
|
+
from: SYM-001
|
|
24
|
+
to: REQ-001
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### specified_by
|
|
28
|
+
```yaml
|
|
29
|
+
relationships:
|
|
30
|
+
- type: specified_by
|
|
31
|
+
from: REQ-001
|
|
32
|
+
to: SCEN-001
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### verified_by
|
|
36
|
+
```yaml
|
|
37
|
+
relationships:
|
|
38
|
+
- type: verified_by
|
|
39
|
+
from: REQ-001
|
|
40
|
+
to: TEST-001
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### validates
|
|
44
|
+
```yaml
|
|
45
|
+
relationships:
|
|
46
|
+
- type: validates
|
|
47
|
+
from: TEST-001
|
|
48
|
+
to: SCEN-001
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### executable_for
|
|
52
|
+
```yaml
|
|
53
|
+
relationships:
|
|
54
|
+
- type: executable_for
|
|
55
|
+
from: SYM-test-login
|
|
56
|
+
to: TEST-001
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### constrains
|
|
60
|
+
```yaml
|
|
61
|
+
relationships:
|
|
62
|
+
- type: constrains
|
|
63
|
+
from: REQ-019
|
|
64
|
+
to: FACT-USER-ROLE
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### requires_property
|
|
68
|
+
```yaml
|
|
69
|
+
relationships:
|
|
70
|
+
- type: requires_property
|
|
71
|
+
from: REQ-019
|
|
72
|
+
to: FACT-LIMIT-3
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### supersedes
|
|
76
|
+
```yaml
|
|
77
|
+
relationships:
|
|
78
|
+
- type: supersedes
|
|
79
|
+
from: REQ-001
|
|
80
|
+
to: REQ-001-v2
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### covered_by
|
|
84
|
+
```yaml
|
|
85
|
+
relationships:
|
|
86
|
+
- type: covered_by
|
|
87
|
+
from: SYM-handler
|
|
88
|
+
to: TEST-005
|
|
89
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Workflows
|
|
2
|
+
|
|
3
|
+
## Discovery to Validation Sequence
|
|
4
|
+
|
|
5
|
+
The canonical workflow for any KB operation follows this pattern:
|
|
6
|
+
|
|
7
|
+
1. **Discover**: `kb_search` with focused probes
|
|
8
|
+
2. **Confirm**: `kb_query` for exact IDs and state
|
|
9
|
+
3. **Inspect**: `kb_status` when freshness matters
|
|
10
|
+
4. **Create endpoints**: `kb_upsert` for new entities (sequential)
|
|
11
|
+
5. **Link**: `kb_upsert` with relationship rows (sequential)
|
|
12
|
+
6. **Validate**: `kb_check` with targeted rules during work, full check at completion
|
|
13
|
+
|
|
14
|
+
## Creating a New Feature
|
|
15
|
+
```
|
|
16
|
+
1. kb_search to discover existing requirements and related knowledge
|
|
17
|
+
2. kb_query to confirm exact IDs and source-linked context
|
|
18
|
+
3. kb_upsert for new or updated requirements (include relationship rows)
|
|
19
|
+
4. kb_check with targeted rules
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Fixing a Traceability Gap
|
|
23
|
+
```
|
|
24
|
+
1. kb_query --sourceFile <code-file> to find linked entities
|
|
25
|
+
2. kb_find_gaps --type req --missing-rel specified_by to find orphan requirements
|
|
26
|
+
3. kb_upsert to add missing relationship rows (sequential)
|
|
27
|
+
4. kb_check with rules: ["no-dangling-refs", "symbol-traceability"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Before Risky Work
|
|
31
|
+
```
|
|
32
|
+
1. /brief-kibi or kb_briefing_generate for citation-backed briefing
|
|
33
|
+
2. Inspect briefingState; proceed only if ready
|
|
34
|
+
3. Use constraints, regressionRisks, and cited entities from the briefing
|
|
35
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare function setBundledSkillsDir(dir: string): void;
|
|
2
|
+
export declare function resetBundledSkillsDir(): void;
|
|
3
|
+
export interface SkillManifest {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
version: string;
|
|
8
|
+
kibiCompatibility: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
resources?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface SkillBundle {
|
|
13
|
+
manifest: SkillManifest;
|
|
14
|
+
body: string;
|
|
15
|
+
rootDir: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class SkillNotFoundError extends Error {
|
|
18
|
+
constructor(id: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class SkillResourceNotFoundError extends Error {
|
|
21
|
+
constructor(id: string, resourcePath: string);
|
|
22
|
+
}
|
|
23
|
+
export declare class SkillResourceOutOfBoundsError extends Error {
|
|
24
|
+
constructor(id: string, resourcePath: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class SkillValidationError extends Error {
|
|
27
|
+
readonly field: string;
|
|
28
|
+
constructor(field: string, message: string);
|
|
29
|
+
}
|
|
30
|
+
export declare class SkillOversizeError extends Error {
|
|
31
|
+
readonly maxBytes: number;
|
|
32
|
+
readonly actualBytes: number;
|
|
33
|
+
constructor(pathLike: string, maxBytes: number, actualBytes: number);
|
|
34
|
+
}
|
|
35
|
+
export declare function listBundledSkills(): SkillManifest[];
|
|
36
|
+
export declare function loadBundledSkill(id: string): SkillBundle;
|
|
37
|
+
export declare function readBundledSkillResource(id: string, resourcePath: string): string;
|
|
38
|
+
export declare function validateSkillBundle(pathLike: string): {
|
|
39
|
+
valid: boolean;
|
|
40
|
+
errors: SkillValidationError[];
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/public/skills.ts"],"names":[],"mappings":"AA0BA,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAErD;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,EAAE,EAAE,MAAM;CAIvB;AAED,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAI7C;AAED,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAI7C;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK3C;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAMpE;AAED,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAWnD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,CAOxD;AAED,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,MAAM,EACV,YAAY,EAAE,MAAM,GACnB,MAAM,CA4BR;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,oBAAoB,EAAE,CAAA;CAAE,CAiBpD"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync, } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join, normalize, relative, resolve, } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import matter from "gray-matter";
|
|
5
|
+
const SKILL_MARKDOWN_MAX_BYTES = 256 * 1024;
|
|
6
|
+
const RESOURCE_MAX_BYTES = 128 * 1024;
|
|
7
|
+
const SKILL_FILE_NAME = "SKILL.md";
|
|
8
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
let bundledSkillsDir = resolve(moduleDir, "skills");
|
|
10
|
+
const defaultBundledSkillsDir = bundledSkillsDir;
|
|
11
|
+
export function setBundledSkillsDir(dir) {
|
|
12
|
+
bundledSkillsDir = dir;
|
|
13
|
+
}
|
|
14
|
+
export function resetBundledSkillsDir() {
|
|
15
|
+
bundledSkillsDir = defaultBundledSkillsDir;
|
|
16
|
+
}
|
|
17
|
+
export class SkillNotFoundError extends Error {
|
|
18
|
+
constructor(id) {
|
|
19
|
+
super(`Skill not found: ${id}`);
|
|
20
|
+
this.name = "SkillNotFoundError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class SkillResourceNotFoundError extends Error {
|
|
24
|
+
constructor(id, resourcePath) {
|
|
25
|
+
super(`Skill resource not found: ${id}/${resourcePath}`);
|
|
26
|
+
this.name = "SkillResourceNotFoundError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class SkillResourceOutOfBoundsError extends Error {
|
|
30
|
+
constructor(id, resourcePath) {
|
|
31
|
+
super(`Skill resource escapes bundle root: ${id}/${resourcePath}`);
|
|
32
|
+
this.name = "SkillResourceOutOfBoundsError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class SkillValidationError extends Error {
|
|
36
|
+
field;
|
|
37
|
+
constructor(field, message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "SkillValidationError";
|
|
40
|
+
this.field = field;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export class SkillOversizeError extends Error {
|
|
44
|
+
maxBytes;
|
|
45
|
+
actualBytes;
|
|
46
|
+
constructor(pathLike, maxBytes, actualBytes) {
|
|
47
|
+
super(`Skill file exceeds ${maxBytes} bytes: ${pathLike} (${actualBytes} bytes)`);
|
|
48
|
+
this.name = "SkillOversizeError";
|
|
49
|
+
this.maxBytes = maxBytes;
|
|
50
|
+
this.actualBytes = actualBytes;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function listBundledSkills() {
|
|
54
|
+
if (!existsSync(bundledSkillsDir)) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
return readdirSync(bundledSkillsDir, { withFileTypes: true })
|
|
58
|
+
.filter((entry) => entry.isDirectory())
|
|
59
|
+
.map((entry) => join(bundledSkillsDir, entry.name))
|
|
60
|
+
.filter((rootDir) => existsSync(join(rootDir, SKILL_FILE_NAME)))
|
|
61
|
+
.map((rootDir) => parseSkillBundle(rootDir).manifest)
|
|
62
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
63
|
+
}
|
|
64
|
+
export function loadBundledSkill(id) {
|
|
65
|
+
const rootDir = findBundledSkillRoot(id);
|
|
66
|
+
if (!rootDir) {
|
|
67
|
+
throw new SkillNotFoundError(id);
|
|
68
|
+
}
|
|
69
|
+
return parseSkillBundle(rootDir);
|
|
70
|
+
}
|
|
71
|
+
export function readBundledSkillResource(id, resourcePath) {
|
|
72
|
+
const bundle = loadBundledSkill(id);
|
|
73
|
+
const declaredResource = normalizeResourcePath(resourcePath);
|
|
74
|
+
if (!declaredResource || isPathOutOfBounds(resourcePath)) {
|
|
75
|
+
throw new SkillResourceOutOfBoundsError(id, resourcePath);
|
|
76
|
+
}
|
|
77
|
+
if (!isDeclaredResource(bundle.manifest, declaredResource)) {
|
|
78
|
+
throw new SkillResourceNotFoundError(id, resourcePath);
|
|
79
|
+
}
|
|
80
|
+
const candidatePath = resolve(bundle.rootDir, declaredResource);
|
|
81
|
+
let realResourcePath;
|
|
82
|
+
let realRootDir;
|
|
83
|
+
try {
|
|
84
|
+
realResourcePath = realpathSync(candidatePath);
|
|
85
|
+
realRootDir = realpathSync(bundle.rootDir);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw new SkillResourceNotFoundError(id, resourcePath);
|
|
89
|
+
}
|
|
90
|
+
if (!isWithinRoot(realRootDir, realResourcePath)) {
|
|
91
|
+
throw new SkillResourceOutOfBoundsError(id, resourcePath);
|
|
92
|
+
}
|
|
93
|
+
assertMaxBytes(candidatePath, RESOURCE_MAX_BYTES);
|
|
94
|
+
return readFileSync(candidatePath, "utf8");
|
|
95
|
+
}
|
|
96
|
+
export function validateSkillBundle(pathLike) {
|
|
97
|
+
const skillFilePath = resolveSkillFilePath(pathLike);
|
|
98
|
+
const errors = [];
|
|
99
|
+
if (!existsSync(skillFilePath)) {
|
|
100
|
+
errors.push(new SkillValidationError("SKILL.md", `Missing ${SKILL_FILE_NAME}`));
|
|
101
|
+
return { valid: false, errors };
|
|
102
|
+
}
|
|
103
|
+
const parsed = matter(readFileSync(skillFilePath, "utf8"));
|
|
104
|
+
errors.push(...validateManifestData(parsed.data));
|
|
105
|
+
if (errors.length === 0) {
|
|
106
|
+
validateBundleContents(skillFilePath, coerceManifest(parsed.data), errors);
|
|
107
|
+
}
|
|
108
|
+
return { valid: errors.length === 0, errors };
|
|
109
|
+
}
|
|
110
|
+
function validateBundleContents(skillFilePath, manifest, errors) {
|
|
111
|
+
try {
|
|
112
|
+
assertMaxBytes(skillFilePath, SKILL_MARKDOWN_MAX_BYTES);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
errors.push(new SkillValidationError("SKILL.md", error instanceof Error ? error.message : String(error)));
|
|
116
|
+
}
|
|
117
|
+
const rootDir = dirname(skillFilePath);
|
|
118
|
+
let realRootDir;
|
|
119
|
+
try {
|
|
120
|
+
realRootDir = realpathSync(rootDir);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
errors.push(new SkillValidationError("SKILL.md", error instanceof Error ? error.message : String(error)));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for (const resource of manifest.resources ?? []) {
|
|
127
|
+
const resourcePath = resolve(rootDir, resource);
|
|
128
|
+
try {
|
|
129
|
+
if (!existsSync(resourcePath)) {
|
|
130
|
+
errors.push(new SkillValidationError("resources", `Missing skill resource: ${resource}`));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const realResourcePath = realpathSync(resourcePath);
|
|
134
|
+
if (!isWithinRoot(realRootDir, realResourcePath)) {
|
|
135
|
+
errors.push(new SkillValidationError("resources", `Skill resource escapes bundle root: ${resource}`));
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
assertMaxBytes(resourcePath, RESOURCE_MAX_BYTES);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
errors.push(new SkillValidationError("resources", error instanceof Error ? error.message : String(error)));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function findBundledSkillRoot(id) {
|
|
146
|
+
if (!existsSync(bundledSkillsDir)) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
for (const entry of readdirSync(bundledSkillsDir, { withFileTypes: true })) {
|
|
150
|
+
if (!entry.isDirectory()) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const rootDir = join(bundledSkillsDir, entry.name);
|
|
154
|
+
const skillFilePath = join(rootDir, SKILL_FILE_NAME);
|
|
155
|
+
if (!existsSync(skillFilePath)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const manifest = parseSkillBundle(rootDir).manifest;
|
|
159
|
+
if (manifest.id === id) {
|
|
160
|
+
return rootDir;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function parseSkillBundle(rootDir) {
|
|
166
|
+
const skillFilePath = join(rootDir, SKILL_FILE_NAME);
|
|
167
|
+
assertMaxBytes(skillFilePath, SKILL_MARKDOWN_MAX_BYTES);
|
|
168
|
+
const parsed = matter(readFileSync(skillFilePath, "utf8"));
|
|
169
|
+
const errors = validateManifestData(parsed.data);
|
|
170
|
+
if (errors.length > 0) {
|
|
171
|
+
throw errors[0];
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
manifest: coerceManifest(parsed.data),
|
|
175
|
+
body: parsed.content,
|
|
176
|
+
rootDir: resolve(rootDir),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function validateManifestData(data) {
|
|
180
|
+
const errors = [];
|
|
181
|
+
const requiredFields = [
|
|
182
|
+
"id",
|
|
183
|
+
"name",
|
|
184
|
+
"description",
|
|
185
|
+
"version",
|
|
186
|
+
"kibiCompatibility",
|
|
187
|
+
];
|
|
188
|
+
for (const field of requiredFields) {
|
|
189
|
+
if (typeof data[field] !== "string" || data[field].trim() === "") {
|
|
190
|
+
errors.push(new SkillValidationError(field, `Missing required skill field: ${field}`));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (typeof data.version === "string" && !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(data.version)) {
|
|
194
|
+
errors.push(new SkillValidationError("version", `Invalid skill version: ${data.version}`));
|
|
195
|
+
}
|
|
196
|
+
if (data.tags !== undefined && !isStringArray(data.tags)) {
|
|
197
|
+
errors.push(new SkillValidationError("tags", "Skill tags must be strings"));
|
|
198
|
+
}
|
|
199
|
+
if (data.resources !== undefined) {
|
|
200
|
+
if (!isStringArray(data.resources)) {
|
|
201
|
+
errors.push(new SkillValidationError("resources", "Skill resources must be strings"));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
for (const resource of data.resources) {
|
|
205
|
+
const normalized = normalizeResourcePath(resource);
|
|
206
|
+
if (!normalized || isPathOutOfBounds(resource)) {
|
|
207
|
+
errors.push(new SkillValidationError("resources", `Invalid skill resource: ${resource}`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return errors;
|
|
213
|
+
}
|
|
214
|
+
function coerceManifest(data) {
|
|
215
|
+
const manifest = {
|
|
216
|
+
id: String(data.id),
|
|
217
|
+
name: String(data.name),
|
|
218
|
+
description: String(data.description),
|
|
219
|
+
version: String(data.version),
|
|
220
|
+
kibiCompatibility: String(data.kibiCompatibility),
|
|
221
|
+
};
|
|
222
|
+
if (isStringArray(data.tags)) {
|
|
223
|
+
manifest.tags = data.tags;
|
|
224
|
+
}
|
|
225
|
+
if (isStringArray(data.resources)) {
|
|
226
|
+
manifest.resources = data.resources.map((resource) => normalizeResourcePath(resource));
|
|
227
|
+
}
|
|
228
|
+
return manifest;
|
|
229
|
+
}
|
|
230
|
+
function isStringArray(value) {
|
|
231
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
232
|
+
}
|
|
233
|
+
function normalizeResourcePath(pathLike) {
|
|
234
|
+
return normalize(pathLike).replaceAll("\\", "/");
|
|
235
|
+
}
|
|
236
|
+
function isPathOutOfBounds(pathLike) {
|
|
237
|
+
const normalized = normalizeResourcePath(pathLike);
|
|
238
|
+
return (isAbsolute(pathLike) ||
|
|
239
|
+
normalized === ".." ||
|
|
240
|
+
normalized.startsWith("../") ||
|
|
241
|
+
normalized.includes("/../"));
|
|
242
|
+
}
|
|
243
|
+
function isDeclaredResource(manifest, resourcePath) {
|
|
244
|
+
return (manifest.resources ?? []).some((declared) => normalizeResourcePath(declared) === resourcePath);
|
|
245
|
+
}
|
|
246
|
+
function isWithinRoot(rootDir, candidatePath) {
|
|
247
|
+
const relativePath = relative(rootDir, candidatePath);
|
|
248
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
|
|
249
|
+
}
|
|
250
|
+
function assertMaxBytes(pathLike, maxBytes) {
|
|
251
|
+
const size = statSync(pathLike).size;
|
|
252
|
+
if (size > maxBytes) {
|
|
253
|
+
throw new SkillOversizeError(pathLike, maxBytes, size);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function resolveSkillFilePath(pathLike) {
|
|
257
|
+
const resolved = resolve(pathLike);
|
|
258
|
+
if (existsSync(resolved) && statSync(resolved).isDirectory()) {
|
|
259
|
+
return join(resolved, SKILL_FILE_NAME);
|
|
260
|
+
}
|
|
261
|
+
return resolved.endsWith(SKILL_FILE_NAME) ? resolved : join(resolved, SKILL_FILE_NAME);
|
|
262
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"src/public"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"build": "tsc -p tsconfig.json && mkdir -p dist/public/skills && cp -r src/public/skills/. dist/public/skills/",
|
|
23
23
|
"prepack": "npm run build"
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
@@ -87,6 +87,10 @@
|
|
|
87
87
|
"types": "./dist/public/operational-artifacts.d.ts",
|
|
88
88
|
"default": "./dist/public/operational-artifacts.js"
|
|
89
89
|
},
|
|
90
|
+
"./skills": {
|
|
91
|
+
"types": "./dist/public/skills.d.ts",
|
|
92
|
+
"default": "./dist/public/skills.js"
|
|
93
|
+
},
|
|
90
94
|
"./ignore-policy": {
|
|
91
95
|
"types": "./dist/public/ignore-policy.d.ts",
|
|
92
96
|
"import": "./dist/public/ignore-policy.js",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: kibi-usage
|
|
3
|
+
name: Kibi Usage
|
|
4
|
+
description: Guides agents to use Kibi MCP, facts, relationships, and validation correctly
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
kibiCompatibility: ">=0.11.0"
|
|
7
|
+
tags:
|
|
8
|
+
- kibi
|
|
9
|
+
- mcp
|
|
10
|
+
- knowledge-base
|
|
11
|
+
- traceability
|
|
12
|
+
- agent-guidance
|
|
13
|
+
resources:
|
|
14
|
+
- resources/relationship-directions.md
|
|
15
|
+
- resources/fact-lanes.md
|
|
16
|
+
- resources/workflows.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Kibi Usage
|
|
20
|
+
|
|
21
|
+
Consult this skill before any Kibi knowledge base operation, on first interaction with a Kibi-enabled repo, after detecting stale or dirty KB status, and before performing mutations.
|
|
22
|
+
|
|
23
|
+
## MCP-Only Rules
|
|
24
|
+
|
|
25
|
+
Interact with the knowledge base exclusively through MCP tools. Do not read or edit files inside `.kb/` directly. Do not run any `kibi` CLI commands from the agent session. The MCP surface is the only sanctioned interface for agents.
|
|
26
|
+
|
|
27
|
+
## Discovery-First Workflow
|
|
28
|
+
|
|
29
|
+
Always discover before you mutate. Start with `kb_search` for exploratory discovery across metadata and markdown body text. Split broad queries into 1-3 focused probes. Review top hits for relevance before concluding the KB lacks knowledge.
|
|
30
|
+
|
|
31
|
+
Follow up with `kb_query` for exact lookups by `id`, `type`, `tags`, or `sourceFile`. Call `kb_status` to inspect branch attachment and freshness when stale context would affect decisions. Only after discovery and confirmation should you mutate.
|
|
32
|
+
|
|
33
|
+
## Relationship Directions
|
|
34
|
+
|
|
35
|
+
Relationship direction is fixed and semantic. Getting it wrong breaks traceability queries and validation.
|
|
36
|
+
|
|
37
|
+
| Relationship | Direction | Meaning |
|
|
38
|
+
|-------------|-----------|---------|
|
|
39
|
+
| `implements` | symbol -> req | Symbol owns or implements requirement behavior |
|
|
40
|
+
| `specified_by` | req -> scenario | Requirement is specified by a scenario |
|
|
41
|
+
| `verified_by` | req/scenario -> test | Requirement or scenario is verified by a test |
|
|
42
|
+
| `validates` | test -> req/scenario | Test validates a requirement or scenario |
|
|
43
|
+
| `executable_for` | symbol -> test | Symbol is executable test code for a test entity |
|
|
44
|
+
| `constrains` | req -> fact(subject) | Requirement constrains a domain fact |
|
|
45
|
+
| `requires_property` | req -> fact(property_value) | Requirement requires a property value |
|
|
46
|
+
| `supersedes` | old-req -> new-req | Old requirement is replaced by new requirement |
|
|
47
|
+
| `covered_by` | symbol -> test | Production symbol has coverage evidence from a test |
|
|
48
|
+
|
|
49
|
+
See `resources/relationship-directions.md` for detailed payload examples.
|
|
50
|
+
|
|
51
|
+
## Strict Fact Lane
|
|
52
|
+
|
|
53
|
+
Normative requirements that must participate in contradiction blocking use the strict fact lane. Create a `fact_kind: subject` fact and link it from the requirement via `constrains`. Create a `fact_kind: property_value` fact and link it via `requires_property`.
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
# Fact entity
|
|
57
|
+
id: FACT-USER-ROLE
|
|
58
|
+
title: User Role Assignment
|
|
59
|
+
status: active
|
|
60
|
+
fact_kind: subject
|
|
61
|
+
subject_key: user.role_assignment
|
|
62
|
+
|
|
63
|
+
# Requirement entity
|
|
64
|
+
id: REQ-019
|
|
65
|
+
title: Users can have up to 3 roles
|
|
66
|
+
status: open
|
|
67
|
+
relationships:
|
|
68
|
+
- type: constrains
|
|
69
|
+
from: REQ-019
|
|
70
|
+
to: FACT-USER-ROLE
|
|
71
|
+
- type: requires_property
|
|
72
|
+
from: REQ-019
|
|
73
|
+
to: FACT-LIMIT-3
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
See `resources/fact-lanes.md` for the full strict vs observation lane comparison.
|
|
77
|
+
|
|
78
|
+
## Fact vs Flag
|
|
79
|
+
|
|
80
|
+
Use `flag` for runtime or config gates only. Feature flags, kill-switches, and deferred capabilities are valid `flag` entities.
|
|
81
|
+
|
|
82
|
+
Bugs, incidents, and workarounds belong in `fact` entities with `fact_kind: observation` or `meta`. These fact kinds are excluded from contradiction inference, making them appropriate for non-blocking evidence.
|
|
83
|
+
|
|
84
|
+
Anti-example: do not create a `flag` named `BUG-123` to track a defect. Create a `fact` with `fact_kind: observation` instead.
|
|
85
|
+
|
|
86
|
+
## Create-Before-Link
|
|
87
|
+
|
|
88
|
+
Always confirm or create endpoint entities before linking them. Query target IDs with `kb_query` first. If an endpoint does not exist, create it with `kb_upsert` before creating the relationship. Creating relationships to non-existent entities produces dangling references that `kb_check` will flag.
|
|
89
|
+
|
|
90
|
+
## Sequential Upserts
|
|
91
|
+
|
|
92
|
+
Never fire `kb_upsert` calls in parallel. Execute them sequentially to avoid lock contention and ensure deterministic ordering. This is especially important when creating chains of related entities.
|
|
93
|
+
|
|
94
|
+
## Targeted and Final Checks
|
|
95
|
+
|
|
96
|
+
Run `kb_check` with specific rules during iteration for fast feedback. For example, use `rules: ["required-fields", "no-dangling-refs"]` after small changes. Run a full `kb_check` without rule filters before declaring work complete.
|
|
97
|
+
|
|
98
|
+
## Domain Contradictions and Evolution
|
|
99
|
+
|
|
100
|
+
The `domain-contradictions` rule detects conflicts between strict-lane facts linked to requirements. When a contradiction is found, the supported escape hatch is `supersedes`: create a new requirement that supersedes the old one, then link the new requirement to updated facts.
|
|
101
|
+
|
|
102
|
+
Use `kb_model_requirement` for automated strict-fact modeling. It generates the subject and property_value facts, links them via `constrains` and `requires_property`, and handles low-confidence downgrades to `observation` facts automatically.
|
|
103
|
+
|
|
104
|
+
## Stale or Dirty KB Handling
|
|
105
|
+
|
|
106
|
+
Call `kb_status` when you suspect the branch KB is stale or when switching context. Report freshness findings to the user rather than relying on outdated KB context. If `kb_status` indicates a schema migration is needed, ask the user or operator to handle it outside the agent session.
|
|
107
|
+
|
|
108
|
+
## Anti-Patterns and Remediation
|
|
109
|
+
|
|
110
|
+
| Anti-Pattern | Problem | Remediation |
|
|
111
|
+
|-------------|---------|-------------|
|
|
112
|
+
| Reversed relationship direction | Traceability queries break | Verify direction against the relationship table above |
|
|
113
|
+
| Bug-as-flag | `flag` misused for defect tracking | Use `fact` with `fact_kind: observation` or `meta` |
|
|
114
|
+
| Parallel upserts | Lock contention and nondeterminism | Execute `kb_upsert` calls sequentially |
|
|
115
|
+
| Embedded scenarios in reqs | Violates canonical traceability chain | Create separate `req`, `scen`, and `test` entities |
|
|
116
|
+
| Missing `kb_check` | Undetected dangling refs and violations | Run targeted checks during work, full check at completion |
|
|
117
|
+
| Tags as multi-ID lookup | Tags are metadata, not identifiers | Use `kb_query` with explicit `id` values |
|
|
118
|
+
| `relates_to` for strict modeling | Loses contradiction safety | Use `constrains` and `requires_property` instead |
|
|
119
|
+
|
|
120
|
+
Before/after for reversed direction:
|
|
121
|
+
|
|
122
|
+
- Wrong: `relationships: [{ type: "implements", from: "REQ-001", to: "SYM-001" }]`
|
|
123
|
+
- Right: `relationships: [{ type: "implements", from: "SYM-001", to: "REQ-001" }]`
|
|
124
|
+
|
|
125
|
+
See `resources/workflows.md` for the golden-path discovery to validation sequence.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Fact Lanes
|
|
2
|
+
|
|
3
|
+
## Strict Lane (Contradiction-Safe)
|
|
4
|
+
|
|
5
|
+
### fact_kind: subject
|
|
6
|
+
```yaml
|
|
7
|
+
id: FACT-USER-ROLE
|
|
8
|
+
title: User Role Assignment
|
|
9
|
+
status: active
|
|
10
|
+
fact_kind: subject
|
|
11
|
+
subject_key: user.role_assignment
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### fact_kind: property_value
|
|
15
|
+
```yaml
|
|
16
|
+
id: FACT-LIMIT-3
|
|
17
|
+
title: Maximum of Three Roles
|
|
18
|
+
status: active
|
|
19
|
+
fact_kind: property_value
|
|
20
|
+
subject_key: user.role_assignment
|
|
21
|
+
property_key: max_roles
|
|
22
|
+
operator: lte
|
|
23
|
+
value_type: int
|
|
24
|
+
value_int: 3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Context Lane (Non-Blocking)
|
|
28
|
+
|
|
29
|
+
### fact_kind: observation
|
|
30
|
+
For bug records, incident notes, and observed behavior.
|
|
31
|
+
```yaml
|
|
32
|
+
id: FACT-BUG-123
|
|
33
|
+
title: Login fails on Safari 17
|
|
34
|
+
status: active
|
|
35
|
+
fact_kind: observation
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### fact_kind: meta
|
|
39
|
+
For governance notes, process commentary, and workaround documentation.
|
|
40
|
+
```yaml
|
|
41
|
+
id: FACT-WORKAROUND-456
|
|
42
|
+
title: Temporary cache bypass for v2 migration
|
|
43
|
+
status: active
|
|
44
|
+
fact_kind: meta
|
|
45
|
+
```
|