kibi-cli 0.8.0 → 0.10.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +25 -1
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +183 -0
- package/dist/public/check-types.d.ts +3 -1
- package/dist/public/check-types.d.ts.map +1 -1
- package/dist/public/check-types.js +1 -0
- package/dist/public/ignore-policy.d.ts +10 -0
- package/dist/public/ignore-policy.d.ts.map +1 -0
- package/dist/public/ignore-policy.js +219 -0
- package/dist/public/schema-version.d.ts +3 -0
- package/dist/public/schema-version.d.ts.map +1 -0
- package/dist/public/schema-version.js +1 -0
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +35 -22
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/dist/utils/rule-registry.js +6 -0
- package/dist/utils/schema-version.d.ts +14 -0
- package/dist/utils/schema-version.d.ts.map +1 -0
- package/dist/utils/schema-version.js +59 -0
- package/dist/utils/strict-modeling.d.ts +64 -0
- package/dist/utils/strict-modeling.d.ts.map +1 -0
- package/dist/utils/strict-modeling.js +371 -0
- package/package.json +13 -3
- package/schema/config.json +8 -1
- package/src/public/check-types.ts +15 -1
- package/src/public/ignore-policy.ts +229 -0
- package/src/public/schema-version.ts +6 -0
package/dist/utils/config.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { existsSync, readFileSync } from "node:fs";
|
|
19
19
|
import * as path from "node:path";
|
|
20
20
|
import { DEFAULT_CHECKS_CONFIG, } from "./rule-registry.js";
|
|
21
|
+
import { LATEST_KB_SCHEMA_VERSION } from "./schema-version.js";
|
|
21
22
|
/**
|
|
22
23
|
* Default configuration values for new repositories.
|
|
23
24
|
*/
|
|
@@ -41,6 +42,7 @@ const DEFAULT_BRIEFS_CONFIG = {
|
|
|
41
42
|
// implements REQ-003
|
|
42
43
|
export const DEFAULT_CONFIG = {
|
|
43
44
|
$schema: "https://raw.githubusercontent.com/Looted/kibi/master/packages/cli/schema/config.json",
|
|
45
|
+
schemaVersion: LATEST_KB_SCHEMA_VERSION,
|
|
44
46
|
paths: {
|
|
45
47
|
requirements: "documentation/requirements",
|
|
46
48
|
scenarios: "documentation/scenarios",
|
|
@@ -85,6 +87,27 @@ function mergeBriefsConfig(userBriefs) {
|
|
|
85
87
|
},
|
|
86
88
|
};
|
|
87
89
|
}
|
|
90
|
+
function readUserConfig(configPath) {
|
|
91
|
+
if (!existsSync(configPath)) {
|
|
92
|
+
return {
|
|
93
|
+
userConfig: {},
|
|
94
|
+
useDefaultSchemaVersion: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const content = readFileSync(configPath, "utf8");
|
|
99
|
+
return {
|
|
100
|
+
userConfig: JSON.parse(content),
|
|
101
|
+
useDefaultSchemaVersion: false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return {
|
|
106
|
+
userConfig: {},
|
|
107
|
+
useDefaultSchemaVersion: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
88
111
|
/**
|
|
89
112
|
* Load and parse the Kibi configuration from .kb/config.json.
|
|
90
113
|
* Falls back to DEFAULT_CONFIG if the file doesn't exist or is invalid.
|
|
@@ -95,22 +118,17 @@ function mergeBriefsConfig(userBriefs) {
|
|
|
95
118
|
export function loadConfig(cwd = process.cwd()) {
|
|
96
119
|
// implements REQ-003
|
|
97
120
|
const configPath = path.join(cwd, ".kb/config.json");
|
|
98
|
-
|
|
99
|
-
if (existsSync(configPath)) {
|
|
100
|
-
try {
|
|
101
|
-
const content = readFileSync(configPath, "utf8");
|
|
102
|
-
userConfig = JSON.parse(content);
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Invalid config, use defaults
|
|
106
|
-
userConfig = {};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
121
|
+
const { userConfig, useDefaultSchemaVersion } = readUserConfig(configPath);
|
|
109
122
|
return {
|
|
110
123
|
paths: {
|
|
111
124
|
...DEFAULT_CONFIG.paths,
|
|
112
125
|
...userConfig.paths,
|
|
113
126
|
},
|
|
127
|
+
...((userConfig.schemaVersion !== undefined || useDefaultSchemaVersion)
|
|
128
|
+
? {
|
|
129
|
+
schemaVersion: userConfig.schemaVersion ?? DEFAULT_CONFIG.schemaVersion,
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
114
132
|
briefs: mergeBriefsConfig(userConfig.briefs),
|
|
115
133
|
...(userConfig.defaultBranch !== undefined
|
|
116
134
|
? { defaultBranch: userConfig.defaultBranch }
|
|
@@ -140,22 +158,17 @@ export function loadConfig(cwd = process.cwd()) {
|
|
|
140
158
|
export function loadSyncConfig(cwd = process.cwd()) {
|
|
141
159
|
// implements REQ-003
|
|
142
160
|
const configPath = path.join(cwd, ".kb/config.json");
|
|
143
|
-
|
|
144
|
-
if (existsSync(configPath)) {
|
|
145
|
-
try {
|
|
146
|
-
const content = readFileSync(configPath, "utf8");
|
|
147
|
-
userConfig = JSON.parse(content);
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
// Invalid config, use defaults
|
|
151
|
-
userConfig = {};
|
|
152
|
-
}
|
|
153
|
-
}
|
|
161
|
+
const { userConfig, useDefaultSchemaVersion } = readUserConfig(configPath);
|
|
154
162
|
return {
|
|
155
163
|
paths: {
|
|
156
164
|
...DEFAULT_SYNC_PATHS,
|
|
157
165
|
...userConfig.paths,
|
|
158
166
|
},
|
|
167
|
+
...((userConfig.schemaVersion !== undefined || useDefaultSchemaVersion)
|
|
168
|
+
? {
|
|
169
|
+
schemaVersion: userConfig.schemaVersion ?? DEFAULT_CONFIG.schemaVersion,
|
|
170
|
+
}
|
|
171
|
+
: {}),
|
|
159
172
|
briefs: mergeBriefsConfig(userConfig.briefs),
|
|
160
173
|
...(userConfig.defaultBranch !== undefined
|
|
161
174
|
? { defaultBranch: userConfig.defaultBranch }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,
|
|
1
|
+
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,EA4EjC,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,UAAU,aAAoC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF;;;;;GAKG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,GAChB,GAAG,CAAC,MAAM,CAAC,CAsBb;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,YAAY,CAWd"}
|
|
@@ -81,6 +81,12 @@ export const RULES = [
|
|
|
81
81
|
defaultEnabled: false,
|
|
82
82
|
category: "integrity",
|
|
83
83
|
},
|
|
84
|
+
{
|
|
85
|
+
name: "strict-readiness",
|
|
86
|
+
description: "Report strict contradiction-readiness levels for requirements that are still prose-only or otherwise not contradiction-ready",
|
|
87
|
+
defaultEnabled: false,
|
|
88
|
+
category: "integrity",
|
|
89
|
+
},
|
|
84
90
|
];
|
|
85
91
|
/**
|
|
86
92
|
* Set of all rule names for quick lookups.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { KbConfig } from "./config.js";
|
|
2
|
+
export declare const LATEST_KB_SCHEMA_VERSION = 1;
|
|
3
|
+
export interface SchemaVersionStatus {
|
|
4
|
+
status: "missing" | "invalid" | "older" | "current" | "newer";
|
|
5
|
+
currentVersion: number | null;
|
|
6
|
+
latestVersion: number;
|
|
7
|
+
needsMigration: boolean;
|
|
8
|
+
warning: string | null;
|
|
9
|
+
}
|
|
10
|
+
type SchemaVersionConfig = Pick<KbConfig, "schemaVersion"> | null | undefined;
|
|
11
|
+
export declare function normalizeSchemaVersion(schemaVersion: number | string | null | undefined): number | null;
|
|
12
|
+
export declare function getSchemaVersionStatus(config?: SchemaVersionConfig): SchemaVersionStatus;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=schema-version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-version.d.ts","sourceRoot":"","sources":["../../src/utils/schema-version.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;IAC9D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,KAAK,mBAAmB,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAG9E,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAChD,MAAM,GAAG,IAAI,CAgBf;AAGD,wBAAgB,sBAAsB,CACpC,MAAM,CAAC,EAAE,mBAAmB,GAC3B,mBAAmB,CA8CrB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// implements REQ-003
|
|
2
|
+
export const LATEST_KB_SCHEMA_VERSION = 1;
|
|
3
|
+
// implements REQ-003
|
|
4
|
+
export function normalizeSchemaVersion(schemaVersion) {
|
|
5
|
+
if (schemaVersion === undefined || schemaVersion === null) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (typeof schemaVersion === "number") {
|
|
9
|
+
return Number.isInteger(schemaVersion) ? schemaVersion : null;
|
|
10
|
+
}
|
|
11
|
+
const trimmed = schemaVersion.trim();
|
|
12
|
+
if (trimmed.length === 0) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const parsed = Number(trimmed);
|
|
16
|
+
return Number.isInteger(parsed) ? parsed : null;
|
|
17
|
+
}
|
|
18
|
+
// implements REQ-003
|
|
19
|
+
export function getSchemaVersionStatus(config) {
|
|
20
|
+
const latestVersion = LATEST_KB_SCHEMA_VERSION;
|
|
21
|
+
const hasSchemaVersion = config !== null && config !== undefined && "schemaVersion" in config;
|
|
22
|
+
const currentVersion = normalizeSchemaVersion(config?.schemaVersion);
|
|
23
|
+
if (currentVersion === null) {
|
|
24
|
+
return {
|
|
25
|
+
status: hasSchemaVersion ? "invalid" : "missing",
|
|
26
|
+
currentVersion: null,
|
|
27
|
+
latestVersion,
|
|
28
|
+
needsMigration: true,
|
|
29
|
+
warning: hasSchemaVersion
|
|
30
|
+
? "KB config schemaVersion is invalid and should be migrated."
|
|
31
|
+
: "KB config schemaVersion is missing; legacy config should be migrated.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (currentVersion > latestVersion) {
|
|
35
|
+
return {
|
|
36
|
+
status: "newer",
|
|
37
|
+
currentVersion,
|
|
38
|
+
latestVersion,
|
|
39
|
+
needsMigration: false,
|
|
40
|
+
warning: `KB config schemaVersion ${currentVersion} is newer than the latest supported version ${latestVersion}.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (currentVersion < latestVersion) {
|
|
44
|
+
return {
|
|
45
|
+
status: "older",
|
|
46
|
+
currentVersion,
|
|
47
|
+
latestVersion,
|
|
48
|
+
needsMigration: true,
|
|
49
|
+
warning: `KB config schemaVersion ${currentVersion} is older than the latest version ${latestVersion} and should be migrated.`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
status: "current",
|
|
54
|
+
currentVersion,
|
|
55
|
+
latestVersion,
|
|
56
|
+
needsMigration: false,
|
|
57
|
+
warning: null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type KbConfigPaths } from "./config.js";
|
|
2
|
+
import type { BaseEntity, FactFields } from "../types/entities.js";
|
|
3
|
+
import type { BaseRelationship } from "../types/relationships.js";
|
|
4
|
+
export interface SemanticClaim {
|
|
5
|
+
source: string;
|
|
6
|
+
subjectKey: string;
|
|
7
|
+
propertyKey: string;
|
|
8
|
+
operator: "eq" | "gte" | "lte" | "neq" | "bool" | "polarity";
|
|
9
|
+
value: string | number | boolean;
|
|
10
|
+
confidence: number;
|
|
11
|
+
provenance?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface StrictModelInput {
|
|
14
|
+
claim: SemanticClaim;
|
|
15
|
+
statement: string;
|
|
16
|
+
config?: Pick<{
|
|
17
|
+
paths: KbConfigPaths;
|
|
18
|
+
}, "paths">;
|
|
19
|
+
}
|
|
20
|
+
type EntityProperties = Partial<Omit<BaseEntity, "created_at" | "updated_at"> & FactFields> & {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
status: string;
|
|
24
|
+
source: string;
|
|
25
|
+
};
|
|
26
|
+
export interface EntitySpec<TType extends "req" | "fact" = "req" | "fact", TProperties extends EntityProperties = EntityProperties> {
|
|
27
|
+
type: TType;
|
|
28
|
+
id: string;
|
|
29
|
+
properties: TProperties;
|
|
30
|
+
}
|
|
31
|
+
export type RelationshipSpec = Pick<BaseRelationship, "type" | "from" | "to" | "source" | "confidence">;
|
|
32
|
+
export interface StableRequirementIds {
|
|
33
|
+
stableKey: string;
|
|
34
|
+
reqId: string;
|
|
35
|
+
subjectFactId: string;
|
|
36
|
+
propertyFactId: string;
|
|
37
|
+
observationFactId: string;
|
|
38
|
+
normalizedSource: string;
|
|
39
|
+
normalizedSubjectKey: string;
|
|
40
|
+
normalizedPropertyKey: string;
|
|
41
|
+
normalizedValue: string;
|
|
42
|
+
}
|
|
43
|
+
interface StrictRequirementWriteSet {
|
|
44
|
+
req: EntitySpec<"req">;
|
|
45
|
+
subjectFact: EntitySpec<"fact">;
|
|
46
|
+
propertyFact: EntitySpec<"fact">;
|
|
47
|
+
relationships: RelationshipSpec[];
|
|
48
|
+
isStrict: true;
|
|
49
|
+
confidence: number;
|
|
50
|
+
}
|
|
51
|
+
interface ObservationWriteSet {
|
|
52
|
+
observationFact: EntitySpec<"fact">;
|
|
53
|
+
relationships: RelationshipSpec[];
|
|
54
|
+
isStrict: false;
|
|
55
|
+
confidence: number;
|
|
56
|
+
}
|
|
57
|
+
export type StrictWriteSet = StrictRequirementWriteSet | ObservationWriteSet;
|
|
58
|
+
export declare function normalizeSubjectKey(value: string): string;
|
|
59
|
+
export declare function normalizePropertyKey(value: string): string;
|
|
60
|
+
export declare function buildStableRequirementIds(claim: SemanticClaim): StableRequirementIds;
|
|
61
|
+
export declare function buildStrictWriteSet(input: StrictModelInput): StrictWriteSet;
|
|
62
|
+
export declare function modelRequirementClaims(inputs: ReadonlyArray<StrictModelInput>): StrictWriteSet[];
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=strict-modeling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strict-modeling.d.ts","sourceRoot":"","sources":["../../src/utils/strict-modeling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
* Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { DEFAULT_CONFIG } from "./config.js";
|
|
21
|
+
const STRICT_CONFIDENCE_THRESHOLD = 0.7;
|
|
22
|
+
export function normalizeSubjectKey(value) {
|
|
23
|
+
const normalized = value
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[\\/]+/g, ".")
|
|
27
|
+
.replace(/[^a-z0-9.]+/g, "_")
|
|
28
|
+
.replace(/_+/g, "_")
|
|
29
|
+
.replace(/\.+/g, ".")
|
|
30
|
+
.replace(/(^[._]+|[._]+$)/g, "");
|
|
31
|
+
if (!normalized) {
|
|
32
|
+
throw new Error("Semantic claim subjectKey must normalize to a non-empty value");
|
|
33
|
+
}
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
export function normalizePropertyKey(value) {
|
|
37
|
+
const normalized = value
|
|
38
|
+
.trim()
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
41
|
+
.replace(/_+/g, "_")
|
|
42
|
+
.replace(/^_+|_+$/g, "");
|
|
43
|
+
if (!normalized) {
|
|
44
|
+
throw new Error("Semantic claim propertyKey must normalize to a non-empty value");
|
|
45
|
+
}
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
export function buildStableRequirementIds(claim) {
|
|
49
|
+
const normalizedSource = normalizeSourceKey(claim.source);
|
|
50
|
+
const normalizedSubjectKey = normalizeSubjectKey(claim.subjectKey);
|
|
51
|
+
const normalizedPropertyKey = normalizePropertyKey(claim.propertyKey);
|
|
52
|
+
const normalizedValue = normalizeStableValue(claim.value);
|
|
53
|
+
const stableKey = [
|
|
54
|
+
normalizedSource,
|
|
55
|
+
normalizedSubjectKey,
|
|
56
|
+
normalizedPropertyKey,
|
|
57
|
+
claim.operator,
|
|
58
|
+
normalizedValue,
|
|
59
|
+
].join(":");
|
|
60
|
+
return {
|
|
61
|
+
stableKey,
|
|
62
|
+
reqId: buildEntityId("REQ-AUTO", `req:${stableKey}`),
|
|
63
|
+
subjectFactId: buildEntityId("FACT-SUBJECT", `subject:${stableKey}`),
|
|
64
|
+
propertyFactId: buildEntityId("FACT-PROP", `property:${stableKey}`),
|
|
65
|
+
observationFactId: buildEntityId("FACT-OBS", `observation:${stableKey}`),
|
|
66
|
+
normalizedSource,
|
|
67
|
+
normalizedSubjectKey,
|
|
68
|
+
normalizedPropertyKey,
|
|
69
|
+
normalizedValue,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function buildStrictWriteSet(input) {
|
|
73
|
+
const statement = normalizeStatement(input.statement);
|
|
74
|
+
const confidence = normalizeConfidence(input.claim.confidence);
|
|
75
|
+
const ids = buildStableRequirementIds(input.claim);
|
|
76
|
+
const textRef = normalizeTextRef(input.claim.provenance, input.claim.source);
|
|
77
|
+
const metadataTags = buildMetadataTags({
|
|
78
|
+
confidence,
|
|
79
|
+
provenance: textRef,
|
|
80
|
+
});
|
|
81
|
+
const sourcePaths = resolveEntitySourcePaths(input.config?.paths);
|
|
82
|
+
if (confidence < STRICT_CONFIDENCE_THRESHOLD) {
|
|
83
|
+
return {
|
|
84
|
+
observationFact: {
|
|
85
|
+
type: "fact",
|
|
86
|
+
id: ids.observationFactId,
|
|
87
|
+
properties: {
|
|
88
|
+
id: ids.observationFactId,
|
|
89
|
+
title: statement,
|
|
90
|
+
status: "active",
|
|
91
|
+
source: buildEntitySourcePath(sourcePaths.facts, ids.observationFactId),
|
|
92
|
+
text_ref: textRef,
|
|
93
|
+
tags: buildUniqueTags([...metadataTags, "lane:observation", "review:required"]),
|
|
94
|
+
fact_kind: "observation",
|
|
95
|
+
subject_key: ids.normalizedSubjectKey,
|
|
96
|
+
property_key: ids.normalizedPropertyKey,
|
|
97
|
+
canonical_key: ids.stableKey,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
relationships: [],
|
|
101
|
+
isStrict: false,
|
|
102
|
+
confidence,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const req = {
|
|
106
|
+
type: "req",
|
|
107
|
+
id: ids.reqId,
|
|
108
|
+
properties: {
|
|
109
|
+
id: ids.reqId,
|
|
110
|
+
title: statement,
|
|
111
|
+
status: "open",
|
|
112
|
+
source: buildEntitySourcePath(sourcePaths.requirements, ids.reqId),
|
|
113
|
+
text_ref: textRef,
|
|
114
|
+
tags: buildUniqueTags([...metadataTags, "lane:strict"]),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
const subjectFact = {
|
|
118
|
+
type: "fact",
|
|
119
|
+
id: ids.subjectFactId,
|
|
120
|
+
properties: {
|
|
121
|
+
id: ids.subjectFactId,
|
|
122
|
+
title: humanizeKey(ids.normalizedSubjectKey),
|
|
123
|
+
status: "active",
|
|
124
|
+
source: buildEntitySourcePath(sourcePaths.facts, ids.subjectFactId),
|
|
125
|
+
text_ref: textRef,
|
|
126
|
+
tags: buildUniqueTags([...metadataTags, "lane:strict", "fact:subject"]),
|
|
127
|
+
fact_kind: "subject",
|
|
128
|
+
subject_key: ids.normalizedSubjectKey,
|
|
129
|
+
canonical_key: ids.normalizedSubjectKey,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
const propertyFact = {
|
|
133
|
+
type: "fact",
|
|
134
|
+
id: ids.propertyFactId,
|
|
135
|
+
properties: {
|
|
136
|
+
id: ids.propertyFactId,
|
|
137
|
+
title: buildPropertyFactTitle(ids.normalizedPropertyKey, input.claim),
|
|
138
|
+
status: "active",
|
|
139
|
+
source: buildEntitySourcePath(sourcePaths.facts, ids.propertyFactId),
|
|
140
|
+
text_ref: textRef,
|
|
141
|
+
tags: buildUniqueTags([...metadataTags, "lane:strict", "fact:property_value"]),
|
|
142
|
+
fact_kind: "property_value",
|
|
143
|
+
subject_key: ids.normalizedSubjectKey,
|
|
144
|
+
property_key: ids.normalizedPropertyKey,
|
|
145
|
+
canonical_key: ids.stableKey,
|
|
146
|
+
...buildPropertyFactFields(input.claim),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const relationships = dedupeRelationships([
|
|
150
|
+
{
|
|
151
|
+
type: "constrains",
|
|
152
|
+
from: req.id,
|
|
153
|
+
to: subjectFact.id,
|
|
154
|
+
source: input.claim.source,
|
|
155
|
+
confidence,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: "requires_property",
|
|
159
|
+
from: req.id,
|
|
160
|
+
to: propertyFact.id,
|
|
161
|
+
source: input.claim.source,
|
|
162
|
+
confidence,
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
return {
|
|
166
|
+
req,
|
|
167
|
+
subjectFact,
|
|
168
|
+
propertyFact,
|
|
169
|
+
relationships,
|
|
170
|
+
isStrict: true,
|
|
171
|
+
confidence,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
export function modelRequirementClaims(inputs) {
|
|
175
|
+
const seen = new Set();
|
|
176
|
+
const modeled = [];
|
|
177
|
+
for (const input of inputs) {
|
|
178
|
+
const writeSet = buildStrictWriteSet(input);
|
|
179
|
+
const stableId = writeSet.isStrict ? writeSet.req.id : writeSet.observationFact.id;
|
|
180
|
+
if (seen.has(stableId)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
seen.add(stableId);
|
|
184
|
+
modeled.push(writeSet);
|
|
185
|
+
}
|
|
186
|
+
return modeled;
|
|
187
|
+
}
|
|
188
|
+
function buildEntityId(prefix, value) {
|
|
189
|
+
const hash = createHash("sha256");
|
|
190
|
+
hash.update(value);
|
|
191
|
+
return `${prefix}-${hash.digest("hex").substring(0, 16).toUpperCase()}`;
|
|
192
|
+
}
|
|
193
|
+
function normalizeSourceKey(value) {
|
|
194
|
+
const normalized = value
|
|
195
|
+
.trim()
|
|
196
|
+
.toLowerCase()
|
|
197
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
198
|
+
.replace(/-+/g, "-")
|
|
199
|
+
.replace(/(^-|-$)/g, "");
|
|
200
|
+
if (!normalized) {
|
|
201
|
+
throw new Error("Semantic claim source must normalize to a non-empty value");
|
|
202
|
+
}
|
|
203
|
+
return normalized;
|
|
204
|
+
}
|
|
205
|
+
function normalizeStableValue(value) {
|
|
206
|
+
if (typeof value === "string") {
|
|
207
|
+
const normalized = value
|
|
208
|
+
.trim()
|
|
209
|
+
.toLowerCase()
|
|
210
|
+
.replace(/\s+/g, "_")
|
|
211
|
+
.replace(/[^a-z0-9._-]+/g, "_")
|
|
212
|
+
.replace(/_+/g, "_")
|
|
213
|
+
.replace(/^_+|_+$/g, "");
|
|
214
|
+
return normalized || "empty";
|
|
215
|
+
}
|
|
216
|
+
return String(value);
|
|
217
|
+
}
|
|
218
|
+
function normalizeStatement(statement) {
|
|
219
|
+
const normalized = statement.trim();
|
|
220
|
+
if (!normalized) {
|
|
221
|
+
throw new Error("Strict modeling requires a non-empty prose statement");
|
|
222
|
+
}
|
|
223
|
+
return normalized;
|
|
224
|
+
}
|
|
225
|
+
function normalizeTextRef(provenance, source) {
|
|
226
|
+
const value = provenance?.trim() || source.trim();
|
|
227
|
+
if (!value) {
|
|
228
|
+
throw new Error("Strict modeling requires claim provenance or source");
|
|
229
|
+
}
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
function normalizeConfidence(value) {
|
|
233
|
+
if (!Number.isFinite(value)) {
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
return Math.round(Math.min(1, Math.max(0, value)) * 100) / 100;
|
|
237
|
+
}
|
|
238
|
+
function toConfidenceBand(confidence) {
|
|
239
|
+
if (confidence >= 0.9)
|
|
240
|
+
return "high";
|
|
241
|
+
if (confidence >= 0.8)
|
|
242
|
+
return "medium";
|
|
243
|
+
return "low";
|
|
244
|
+
}
|
|
245
|
+
function buildMetadataTags({ confidence, provenance, }) {
|
|
246
|
+
return buildUniqueTags([
|
|
247
|
+
"strict-modeling",
|
|
248
|
+
`confidence:${confidence.toFixed(2)}`,
|
|
249
|
+
`confidence-band:${toConfidenceBand(confidence)}`,
|
|
250
|
+
`provenance:${normalizeSourceKey(provenance)}`,
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
function buildUniqueTags(tags) {
|
|
254
|
+
return Array.from(new Set(tags));
|
|
255
|
+
}
|
|
256
|
+
function buildPropertyFactTitle(normalizedPropertyKey, claim) {
|
|
257
|
+
const label = humanizeKey(normalizedPropertyKey);
|
|
258
|
+
if (claim.operator === "polarity") {
|
|
259
|
+
return `${label} ${normalizePolarityValue(claim.value)}`;
|
|
260
|
+
}
|
|
261
|
+
const operatorLabel = claim.operator === "bool"
|
|
262
|
+
? "="
|
|
263
|
+
: claim.operator === "eq"
|
|
264
|
+
? "="
|
|
265
|
+
: claim.operator === "neq"
|
|
266
|
+
? "!="
|
|
267
|
+
: claim.operator === "gte"
|
|
268
|
+
? ">="
|
|
269
|
+
: "<=";
|
|
270
|
+
return `${label} ${operatorLabel} ${String(claim.value)}`;
|
|
271
|
+
}
|
|
272
|
+
function buildPropertyFactFields(claim) {
|
|
273
|
+
if (claim.operator === "polarity") {
|
|
274
|
+
return {
|
|
275
|
+
polarity: normalizePolarityValue(claim.value),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (claim.operator === "bool") {
|
|
279
|
+
return {
|
|
280
|
+
operator: "eq",
|
|
281
|
+
value_type: "bool",
|
|
282
|
+
value_bool: normalizeBooleanValue(claim.value),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
operator: claim.operator,
|
|
287
|
+
...buildTypedValueFields(claim.value),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function buildTypedValueFields(value) {
|
|
291
|
+
if (typeof value === "boolean") {
|
|
292
|
+
return {
|
|
293
|
+
value_type: "bool",
|
|
294
|
+
value_bool: value,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (typeof value === "number") {
|
|
298
|
+
if (Number.isInteger(value)) {
|
|
299
|
+
return {
|
|
300
|
+
value_type: "int",
|
|
301
|
+
value_int: value,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
value_type: "number",
|
|
306
|
+
value_number: value,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
value_type: "string",
|
|
311
|
+
value_string: value,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function normalizePolarityValue(value) {
|
|
315
|
+
if (typeof value === "boolean") {
|
|
316
|
+
return value ? "require" : "forbid";
|
|
317
|
+
}
|
|
318
|
+
if (typeof value === "string") {
|
|
319
|
+
const normalized = value.trim().toLowerCase();
|
|
320
|
+
if (normalized === "require" || normalized === "forbid") {
|
|
321
|
+
return normalized;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
throw new Error("Polarity claims must use a boolean or the string 'require'/'forbid'");
|
|
325
|
+
}
|
|
326
|
+
function normalizeBooleanValue(value) {
|
|
327
|
+
if (typeof value === "boolean") {
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
if (typeof value === "string") {
|
|
331
|
+
const normalized = value.trim().toLowerCase();
|
|
332
|
+
if (normalized === "true")
|
|
333
|
+
return true;
|
|
334
|
+
if (normalized === "false")
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
throw new Error("Boolean claims must use a boolean or the string 'true'/'false'");
|
|
338
|
+
}
|
|
339
|
+
function humanizeKey(value) {
|
|
340
|
+
return value
|
|
341
|
+
.split(/[._-]+/)
|
|
342
|
+
.filter(Boolean)
|
|
343
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
344
|
+
.join(" ");
|
|
345
|
+
}
|
|
346
|
+
function dedupeRelationships(relationships) {
|
|
347
|
+
const seen = new Set();
|
|
348
|
+
const deduped = [];
|
|
349
|
+
for (const relationship of relationships) {
|
|
350
|
+
const key = `${relationship.type}:${relationship.from}:${relationship.to}`;
|
|
351
|
+
if (seen.has(key)) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
seen.add(key);
|
|
355
|
+
deduped.push(relationship);
|
|
356
|
+
}
|
|
357
|
+
return deduped;
|
|
358
|
+
}
|
|
359
|
+
function resolveEntitySourcePaths(configPaths) {
|
|
360
|
+
return {
|
|
361
|
+
requirements: normalizeEntityDirectory(configPaths?.requirements, DEFAULT_CONFIG.paths.requirements),
|
|
362
|
+
facts: normalizeEntityDirectory(configPaths?.facts, DEFAULT_CONFIG.paths.facts),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function normalizeEntityDirectory(directory, fallback) {
|
|
366
|
+
const selected = directory?.trim() || fallback?.trim() || "documentation";
|
|
367
|
+
return selected.split(path.sep).join("/").replace(/\/+$/g, "");
|
|
368
|
+
}
|
|
369
|
+
function buildEntitySourcePath(directory, entityId) {
|
|
370
|
+
return `${directory}/${entityId}.md`;
|
|
371
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -79,9 +79,18 @@
|
|
|
79
79
|
"types": "./dist/public/brief-config.d.ts",
|
|
80
80
|
"default": "./dist/public/brief-config.js"
|
|
81
81
|
},
|
|
82
|
+
"./schema-version": {
|
|
83
|
+
"types": "./dist/public/schema-version.d.ts",
|
|
84
|
+
"default": "./dist/public/schema-version.js"
|
|
85
|
+
},
|
|
82
86
|
"./operational-artifacts": {
|
|
83
87
|
"types": "./dist/public/operational-artifacts.d.ts",
|
|
84
88
|
"default": "./dist/public/operational-artifacts.js"
|
|
89
|
+
},
|
|
90
|
+
"./ignore-policy": {
|
|
91
|
+
"types": "./dist/public/ignore-policy.d.ts",
|
|
92
|
+
"import": "./dist/public/ignore-policy.js",
|
|
93
|
+
"default": "./dist/public/ignore-policy.js"
|
|
85
94
|
}
|
|
86
95
|
},
|
|
87
96
|
"types": "./dist/cli.d.ts",
|
|
@@ -92,8 +101,9 @@
|
|
|
92
101
|
"fast-glob": "^3.2.12",
|
|
93
102
|
"gray-matter": "^4.0.3",
|
|
94
103
|
"js-yaml": "^4.1.0",
|
|
95
|
-
"kibi-core": "^0.5.
|
|
96
|
-
"ts-morph": "^23.0.0"
|
|
104
|
+
"kibi-core": "^0.5.3",
|
|
105
|
+
"ts-morph": "^23.0.0",
|
|
106
|
+
"ignore": "^5.3.0"
|
|
97
107
|
},
|
|
98
108
|
"devDependencies": {
|
|
99
109
|
"@types/node": "latest",
|