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/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAuCA,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/cli.js
CHANGED
|
@@ -25,6 +25,7 @@ import { gapsCommand } from "./commands/gaps.js";
|
|
|
25
25
|
import { gcCommand } from "./commands/gc.js";
|
|
26
26
|
import { graphCommand } from "./commands/graph.js";
|
|
27
27
|
import { initCommand } from "./commands/init.js";
|
|
28
|
+
import { migrateCommand } from "./commands/migrate.js";
|
|
28
29
|
import { queryCommand } from "./commands/query.js";
|
|
29
30
|
import { searchCommand } from "./commands/search.js";
|
|
30
31
|
import { statusCommand } from "./commands/status.js";
|
|
@@ -53,6 +54,14 @@ program
|
|
|
53
54
|
.action(withExitCode(async (options) => {
|
|
54
55
|
return initCommand(options);
|
|
55
56
|
}));
|
|
57
|
+
program
|
|
58
|
+
.command("migrate")
|
|
59
|
+
.description("Migrate .kb/config.json to the latest schema version")
|
|
60
|
+
.option("--dry-run", "Preview migration changes without writing files")
|
|
61
|
+
.option("--yes", "Apply migration changes without prompting")
|
|
62
|
+
.action(withExitCode(async (options) => {
|
|
63
|
+
return migrateCommand(options);
|
|
64
|
+
}));
|
|
56
65
|
program
|
|
57
66
|
.command("sync")
|
|
58
67
|
.description("Sync entities from documents")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAuDA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAI1B,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA+GD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAuDA,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EAAE,SAAS,EAAE,CAAC;AAI1B,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA+GD,wBAAsB,YAAY,CAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0R/B"}
|
package/dist/commands/check.js
CHANGED
|
@@ -278,6 +278,7 @@ export async function checkCommand(options) {
|
|
|
278
278
|
"domain-contradictions",
|
|
279
279
|
"strict-fact-shape",
|
|
280
280
|
"strict-req-fact-pairing",
|
|
281
|
+
"strict-readiness",
|
|
281
282
|
];
|
|
282
283
|
const canUseAggregated = Array.from(effectiveRules).every((r) => supportedRules.includes(r));
|
|
283
284
|
if (canUseAggregated) {
|
|
@@ -302,6 +303,7 @@ export async function checkCommand(options) {
|
|
|
302
303
|
await runCheck("domain-contradictions", checkDomainContradictions);
|
|
303
304
|
await runCheck("strict-fact-shape", checkStrictFactShape);
|
|
304
305
|
await runCheck("strict-req-fact-pairing", checkStrictReqFactPairing);
|
|
306
|
+
await runCheck("strict-readiness", checkStrictReadiness);
|
|
305
307
|
}
|
|
306
308
|
if (violations.length === 0) {
|
|
307
309
|
console.log("✓ No violations found. KB is valid.");
|
|
@@ -312,6 +314,12 @@ export async function checkCommand(options) {
|
|
|
312
314
|
for (const v of violations) {
|
|
313
315
|
const filename = v.source ? path.basename(v.source, ".md") : v.entityId;
|
|
314
316
|
console.log(`[${v.rule}] ${filename}`);
|
|
317
|
+
if (filename !== v.entityId) {
|
|
318
|
+
console.log(` Entity: ${v.entityId}`);
|
|
319
|
+
}
|
|
320
|
+
if (v.source) {
|
|
321
|
+
console.log(` Source: ${v.source}`);
|
|
322
|
+
}
|
|
315
323
|
console.log(` ${v.description}`);
|
|
316
324
|
if (options.fix && v.suggestion) {
|
|
317
325
|
console.log(` Suggestion: ${v.suggestion}`);
|
|
@@ -620,7 +628,7 @@ async function checkDomainContradictions(prolog) {
|
|
|
620
628
|
violations.push({
|
|
621
629
|
rule: "domain-contradictions",
|
|
622
630
|
entityId: `${reqA}/${reqB}`,
|
|
623
|
-
description: reason
|
|
631
|
+
description: `${reason} [strict-readiness: contradiction-ready]`,
|
|
624
632
|
suggestion: "Supersede one requirement or align both to the same required property",
|
|
625
633
|
});
|
|
626
634
|
}
|
|
@@ -658,6 +666,22 @@ async function checkStrictReqFactPairing(prolog) {
|
|
|
658
666
|
}
|
|
659
667
|
return violations;
|
|
660
668
|
}
|
|
669
|
+
async function checkStrictReadiness(prolog) {
|
|
670
|
+
const violations = [];
|
|
671
|
+
const result = await prolog.query(`findall(violation(Rule, EntityId, Desc, Sugg, Src),
|
|
672
|
+
checks:strict_readiness_violation(violation(Rule, EntityId, Desc, Sugg, Src)),
|
|
673
|
+
Violations)`);
|
|
674
|
+
if (!result.success || !result.bindings.Violations) {
|
|
675
|
+
return violations;
|
|
676
|
+
}
|
|
677
|
+
const violationsStr = result.bindings.Violations;
|
|
678
|
+
if (violationsStr && violationsStr !== "[]") {
|
|
679
|
+
for (const v of parseViolationRows(violationsStr)) {
|
|
680
|
+
violations.push(v);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return violations;
|
|
684
|
+
}
|
|
661
685
|
async function checkSymbolCoverage(prolog) {
|
|
662
686
|
const violations = [];
|
|
663
687
|
const uncoveredResult = await prolog.query("setof(Symbol, symbol_no_req_coverage(Symbol, _), Symbols)");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAkCA,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AA6ID,wBAAsB,cAAc,CAClC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAwG/B"}
|
|
@@ -0,0 +1,183 @@
|
|
|
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 { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { resolveActiveBranch } from "../utils/branch-resolver.js";
|
|
21
|
+
import { LATEST_KB_SCHEMA_VERSION, getSchemaVersionStatus, normalizeSchemaVersion, } from "../utils/schema-version.js";
|
|
22
|
+
const MIGRATION_AUDIT_VERSION = 1;
|
|
23
|
+
function printWarning(message) {
|
|
24
|
+
console.log(`Warning: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
function toRelativePath(cwd, filePath) {
|
|
27
|
+
const relativePath = path.relative(cwd, filePath);
|
|
28
|
+
return relativePath.length > 0 ? relativePath : path.basename(filePath);
|
|
29
|
+
}
|
|
30
|
+
function resolveMigrationBranch(cwd) {
|
|
31
|
+
const result = resolveActiveBranch(cwd);
|
|
32
|
+
if ("error" in result) {
|
|
33
|
+
const isNonGitContext = result.code === "NOT_A_GIT_REPO" || result.code === "GIT_NOT_AVAILABLE";
|
|
34
|
+
if (isNonGitContext) {
|
|
35
|
+
return {
|
|
36
|
+
branch: "main",
|
|
37
|
+
warnings: [
|
|
38
|
+
"Not in a git repository; using 'main' for migration audit metadata.",
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
error: `Failed to resolve active branch: ${result.error}`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
branch: result.branch,
|
|
48
|
+
warnings: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function loadRawConfigDocument(cwd) {
|
|
52
|
+
const kbDir = path.join(cwd, ".kb");
|
|
53
|
+
const configPath = path.join(kbDir, "config.json");
|
|
54
|
+
if (!existsSync(kbDir)) {
|
|
55
|
+
return {
|
|
56
|
+
error: "Missing .kb/ directory. Run 'kibi init' before 'kibi migrate'.",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (!existsSync(configPath)) {
|
|
60
|
+
return {
|
|
61
|
+
error: "Missing .kb/config.json. Run 'kibi init' to create a baseline config before migrating.",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
66
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
67
|
+
return {
|
|
68
|
+
error: ".kb/config.json must contain a JSON object. Fix the file and retry 'kibi migrate'.",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
config: parsed,
|
|
73
|
+
configPath,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
78
|
+
return {
|
|
79
|
+
error: `Invalid .kb/config.json: ${message}. Fix the JSON or re-run 'kibi init'.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function writeJsonAtomically(filePath, value) {
|
|
84
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
85
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
86
|
+
writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
87
|
+
renameSync(tempPath, filePath);
|
|
88
|
+
}
|
|
89
|
+
function formatSchemaVersion(rawSchemaVersion, normalized) {
|
|
90
|
+
if (normalized === null) {
|
|
91
|
+
if (rawSchemaVersion === undefined) {
|
|
92
|
+
return "missing";
|
|
93
|
+
}
|
|
94
|
+
return `invalid (${JSON.stringify(rawSchemaVersion)})`;
|
|
95
|
+
}
|
|
96
|
+
return String(normalized);
|
|
97
|
+
}
|
|
98
|
+
function buildMigrationAuditRecord(args) {
|
|
99
|
+
return {
|
|
100
|
+
auditVersion: MIGRATION_AUDIT_VERSION,
|
|
101
|
+
branch: args.branch,
|
|
102
|
+
configPath: args.configPath,
|
|
103
|
+
fromVersion: args.fromVersion,
|
|
104
|
+
migratedAt: args.migratedAt,
|
|
105
|
+
status: "applied",
|
|
106
|
+
toVersion: LATEST_KB_SCHEMA_VERSION,
|
|
107
|
+
warning: args.warning,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// implements REQ-003
|
|
111
|
+
export async function migrateCommand(options = {}) {
|
|
112
|
+
const cwd = process.cwd();
|
|
113
|
+
const branchResult = resolveMigrationBranch(cwd);
|
|
114
|
+
if ("error" in branchResult) {
|
|
115
|
+
console.error(branchResult.error);
|
|
116
|
+
return { exitCode: 1 };
|
|
117
|
+
}
|
|
118
|
+
const configResult = loadRawConfigDocument(cwd);
|
|
119
|
+
if ("error" in configResult) {
|
|
120
|
+
console.error(configResult.error);
|
|
121
|
+
return { exitCode: 1 };
|
|
122
|
+
}
|
|
123
|
+
const { branch, warnings: branchWarnings } = branchResult;
|
|
124
|
+
const { config, configPath } = configResult;
|
|
125
|
+
const configStatus = getSchemaVersionStatus(config);
|
|
126
|
+
const normalizedVersion = normalizeSchemaVersion(config.schemaVersion);
|
|
127
|
+
const rawSchemaVersion = config.schemaVersion;
|
|
128
|
+
const needsCanonicalSchemaWrite = normalizedVersion === LATEST_KB_SCHEMA_VERSION &&
|
|
129
|
+
rawSchemaVersion !== undefined &&
|
|
130
|
+
rawSchemaVersion !== LATEST_KB_SCHEMA_VERSION;
|
|
131
|
+
const migrationWarning = needsCanonicalSchemaWrite
|
|
132
|
+
? "KB config schemaVersion should be normalized to the latest numeric version."
|
|
133
|
+
: configStatus.warning;
|
|
134
|
+
const warnings = [...branchWarnings, ...(migrationWarning ? [migrationWarning] : [])];
|
|
135
|
+
const auditPath = path.join(cwd, ".kb", "migrations", `${branch}.json`);
|
|
136
|
+
const configPathRelative = toRelativePath(cwd, configPath);
|
|
137
|
+
const auditPathRelative = toRelativePath(cwd, auditPath);
|
|
138
|
+
for (const warning of warnings) {
|
|
139
|
+
printWarning(warning);
|
|
140
|
+
}
|
|
141
|
+
if (configStatus.currentVersion !== null &&
|
|
142
|
+
configStatus.currentVersion > configStatus.latestVersion) {
|
|
143
|
+
console.error(`Unsupported schemaVersion ${configStatus.currentVersion}. Upgrade kibi-cli before migrating this KB.`);
|
|
144
|
+
return { exitCode: 1 };
|
|
145
|
+
}
|
|
146
|
+
if (!configStatus.needsMigration && !needsCanonicalSchemaWrite) {
|
|
147
|
+
console.log(`No migration needed: ${configPathRelative} is already at schemaVersion ${LATEST_KB_SCHEMA_VERSION}.`);
|
|
148
|
+
if (existsSync(auditPath)) {
|
|
149
|
+
console.log(`Existing migration audit metadata: ${auditPathRelative}`);
|
|
150
|
+
}
|
|
151
|
+
return { exitCode: 0 };
|
|
152
|
+
}
|
|
153
|
+
const fromVersionLabel = formatSchemaVersion(rawSchemaVersion, normalizedVersion);
|
|
154
|
+
if (options.dryRun) {
|
|
155
|
+
console.log(`dry run: would migrate ${configPathRelative} schemaVersion from ${fromVersionLabel} to ${LATEST_KB_SCHEMA_VERSION}.`);
|
|
156
|
+
console.log(`dry run: would write migration audit metadata to ${auditPathRelative}.`);
|
|
157
|
+
console.log("Re-run with --yes to apply these changes.");
|
|
158
|
+
return { exitCode: 0 };
|
|
159
|
+
}
|
|
160
|
+
if (!options.yes) {
|
|
161
|
+
printWarning(`Migration required for ${configPathRelative}.`);
|
|
162
|
+
console.log("No changes applied.");
|
|
163
|
+
console.log("Use --dry-run to preview or --yes to apply the migration.");
|
|
164
|
+
return { exitCode: 0 };
|
|
165
|
+
}
|
|
166
|
+
const nextConfig = {
|
|
167
|
+
...config,
|
|
168
|
+
schemaVersion: LATEST_KB_SCHEMA_VERSION,
|
|
169
|
+
};
|
|
170
|
+
const migratedAt = new Date().toISOString();
|
|
171
|
+
writeJsonAtomically(configPath, nextConfig);
|
|
172
|
+
writeJsonAtomically(auditPath, buildMigrationAuditRecord({
|
|
173
|
+
branch,
|
|
174
|
+
configPath: configPathRelative,
|
|
175
|
+
fromVersion: configStatus.currentVersion,
|
|
176
|
+
migratedAt,
|
|
177
|
+
warning: migrationWarning,
|
|
178
|
+
}));
|
|
179
|
+
console.log(`Migrated ${configPathRelative} schemaVersion from ${fromVersionLabel} to ${LATEST_KB_SCHEMA_VERSION}.`);
|
|
180
|
+
console.log(`Wrote migration audit metadata to ${auditPathRelative}.`);
|
|
181
|
+
console.log("Migration complete. Future 'kibi migrate' runs will be a no-op.");
|
|
182
|
+
return { exitCode: 0 };
|
|
183
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Public re-export barrel for shared check types.
|
|
2
|
+
* Public re-export barrel for shared check types and MCP-consumed modeling helpers.
|
|
3
3
|
* Import from "kibi-cli/public/check-types" in MCP or external consumers.
|
|
4
4
|
*/
|
|
5
5
|
export type { ChecksConfig, RuleDefinition, SymbolTraceabilityOptions, Violation, } from "../utils/rule-registry.js";
|
|
6
|
+
export type { SemanticClaim, StableRequirementIds, StrictModelInput, StrictWriteSet, } from "../utils/strict-modeling.js";
|
|
6
7
|
export { DEFAULT_CHECKS_CONFIG, RULE_NAMES, RULES, getEffectiveRules, mergeChecksConfig, validateRuleName, } from "../utils/rule-registry.js";
|
|
8
|
+
export { buildStableRequirementIds, buildStrictWriteSet, modelRequirementClaims, normalizePropertyKey, normalizeSubjectKey, } from "../utils/strict-modeling.js";
|
|
7
9
|
//# sourceMappingURL=check-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check-types.d.ts","sourceRoot":"","sources":["../../src/public/check-types.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,YAAY,EACV,YAAY,EACZ,cAAc,EACd,yBAAyB,EACzB,SAAS,GACV,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"check-types.d.ts","sourceRoot":"","sources":["../../src/public/check-types.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,YAAY,EACV,YAAY,EACZ,cAAc,EACd,yBAAyB,EACzB,SAAS,GACV,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,GACf,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,qBAAqB,EACrB,UAAU,EACV,KAAK,EACL,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,6BAA6B,CAAC"}
|
|
@@ -16,3 +16,4 @@
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
export { DEFAULT_CHECKS_CONFIG, RULE_NAMES, RULES, getEffectiveRules, mergeChecksConfig, validateRuleName, } from "../utils/rule-registry.js";
|
|
19
|
+
export { buildStableRequirementIds, buildStrictWriteSet, modelRequirementClaims, normalizePropertyKey, normalizeSubjectKey, } from "../utils/strict-modeling.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface IgnorePolicy {
|
|
2
|
+
isIgnored(inputPath: string): boolean;
|
|
3
|
+
getFastGlobIgnoreGlobs(): string[];
|
|
4
|
+
explain(inputPath: string): {
|
|
5
|
+
ignored: boolean;
|
|
6
|
+
reason?: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare function createRepoIgnorePolicy(workspaceRoot: string): IgnorePolicy;
|
|
10
|
+
//# sourceMappingURL=ignore-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignore-policy.d.ts","sourceRoot":"","sources":["../../src/public/ignore-policy.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACtC,sBAAsB,IAAI,MAAM,EAAE,CAAC;IACnC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnE;AAoBD,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,CA8L1E"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import ignore from "ignore";
|
|
4
|
+
const HARD_DENYLIST = [
|
|
5
|
+
".kb",
|
|
6
|
+
".git",
|
|
7
|
+
"node_modules",
|
|
8
|
+
"vendor",
|
|
9
|
+
"third_party",
|
|
10
|
+
".sisyphus",
|
|
11
|
+
".opencode",
|
|
12
|
+
];
|
|
13
|
+
function readIgnoreFileLines(filePath) {
|
|
14
|
+
if (!existsSync(filePath))
|
|
15
|
+
return [];
|
|
16
|
+
try {
|
|
17
|
+
const content = readFileSync(filePath, "utf8");
|
|
18
|
+
return content
|
|
19
|
+
.split(/\r?\n/)
|
|
20
|
+
.map((l) => l.trim())
|
|
21
|
+
.filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function toPosix(p) {
|
|
28
|
+
return p.split(path.sep).join("/");
|
|
29
|
+
}
|
|
30
|
+
// implements REQ-001
|
|
31
|
+
export function createRepoIgnorePolicy(workspaceRoot) {
|
|
32
|
+
const root = path.resolve(workspaceRoot);
|
|
33
|
+
// Load root .gitignore
|
|
34
|
+
const rootGitignorePath = path.join(root, ".gitignore");
|
|
35
|
+
const rootGitPatterns = readIgnoreFileLines(rootGitignorePath);
|
|
36
|
+
// Load .git/info/exclude
|
|
37
|
+
const gitInfoExcludePath = path.join(root, ".git", "info", "exclude");
|
|
38
|
+
const gitInfoPatterns = readIgnoreFileLines(gitInfoExcludePath);
|
|
39
|
+
// Find nested .gitignore files (skip scanning inside hard denylist directories)
|
|
40
|
+
const nestedPatterns = new Map();
|
|
41
|
+
function walk(dirAbs) {
|
|
42
|
+
let entries;
|
|
43
|
+
try {
|
|
44
|
+
entries = readdirSync(dirAbs, { withFileTypes: true });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const ent of entries) {
|
|
50
|
+
const name = String(ent.name);
|
|
51
|
+
const abs = path.join(dirAbs, name);
|
|
52
|
+
if (ent.isDirectory()) {
|
|
53
|
+
// avoid descending into common heavy or control directories
|
|
54
|
+
if (HARD_DENYLIST.includes(name))
|
|
55
|
+
continue;
|
|
56
|
+
// also avoid .git itself to prevent reading internal excludes as nested
|
|
57
|
+
if (name === ".git")
|
|
58
|
+
continue;
|
|
59
|
+
walk(abs);
|
|
60
|
+
}
|
|
61
|
+
else if (ent.isFile()) {
|
|
62
|
+
if (name === ".gitignore") {
|
|
63
|
+
// skip root .gitignore (we already loaded it)
|
|
64
|
+
if (path.resolve(dirAbs) === root)
|
|
65
|
+
continue;
|
|
66
|
+
const patterns = readIgnoreFileLines(abs);
|
|
67
|
+
const relDir = path.relative(root, dirAbs) || ".";
|
|
68
|
+
nestedPatterns.set(toPosix(relDir), patterns);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
walk(root);
|
|
74
|
+
// Create ignore instances
|
|
75
|
+
const rootIgnore = ignore();
|
|
76
|
+
if (rootGitPatterns.length > 0)
|
|
77
|
+
rootIgnore.add(rootGitPatterns);
|
|
78
|
+
const gitInfoIgnore = ignore();
|
|
79
|
+
if (gitInfoPatterns.length > 0)
|
|
80
|
+
gitInfoIgnore.add(gitInfoPatterns);
|
|
81
|
+
const nestedIgnoreMap = new Map();
|
|
82
|
+
for (const [dirRel, pats] of nestedPatterns.entries()) {
|
|
83
|
+
const ig = ignore();
|
|
84
|
+
if (pats.length > 0)
|
|
85
|
+
ig.add(pats);
|
|
86
|
+
nestedIgnoreMap.set(dirRel, ig);
|
|
87
|
+
}
|
|
88
|
+
// Prepare nested directories sorted by specificity (longest first)
|
|
89
|
+
const nestedDirsSorted = Array.from(nestedIgnoreMap.keys()).sort((a, b) => b.length - a.length);
|
|
90
|
+
function isPathOutsideWorkspace(absPath) {
|
|
91
|
+
const rel = path.relative(root, absPath);
|
|
92
|
+
// path.relative returns paths starting with '..' for outside
|
|
93
|
+
return rel === "" ? false : rel.split(path.sep)[0] === "..";
|
|
94
|
+
}
|
|
95
|
+
function matchesHardDeny(relPosix) {
|
|
96
|
+
const segments = relPosix.split("/").filter(Boolean);
|
|
97
|
+
for (const deny of HARD_DENYLIST) {
|
|
98
|
+
if (segments.includes(deny))
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
function isIgnoredInternal(inputPath) {
|
|
104
|
+
// Resolve to absolute and relative path inside workspace
|
|
105
|
+
const abs = path.isAbsolute(inputPath) ? path.resolve(inputPath) : path.resolve(root, inputPath);
|
|
106
|
+
if (path.isAbsolute(inputPath) && isPathOutsideWorkspace(abs)) {
|
|
107
|
+
return { ignored: true, reason: "outside_workspace" };
|
|
108
|
+
}
|
|
109
|
+
const rel = path.relative(root, abs) || ".";
|
|
110
|
+
const relPosix = toPosix(rel);
|
|
111
|
+
// Hard denylist always wins
|
|
112
|
+
if (matchesHardDeny(relPosix))
|
|
113
|
+
return { ignored: true, reason: "hard_deny" };
|
|
114
|
+
// Root .gitignore
|
|
115
|
+
try {
|
|
116
|
+
if (rootGitPatterns.length > 0 && rootIgnore.ignores(relPosix)) {
|
|
117
|
+
return { ignored: true, reason: "gitignored" };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
// ignore errors from library usage; continue
|
|
122
|
+
}
|
|
123
|
+
// .git/info/exclude
|
|
124
|
+
try {
|
|
125
|
+
if (gitInfoPatterns.length > 0 && gitInfoIgnore.ignores(relPosix)) {
|
|
126
|
+
return { ignored: true, reason: "git_info_exclude" };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// noop
|
|
131
|
+
}
|
|
132
|
+
// Nested .gitignore (apply relative to their directory)
|
|
133
|
+
for (const dirRel of nestedDirsSorted) {
|
|
134
|
+
// dirRel is '.' for nested at root which we skipped, so dirRel will be like 'docs'
|
|
135
|
+
if (dirRel === ".")
|
|
136
|
+
continue;
|
|
137
|
+
if (relPosix === dirRel || relPosix.startsWith(dirRel + "/")) {
|
|
138
|
+
const sub = relPosix === dirRel ? "." : relPosix.slice(dirRel.length + 1);
|
|
139
|
+
const ig = nestedIgnoreMap.get(dirRel);
|
|
140
|
+
try {
|
|
141
|
+
if (ig && ig.ignores(sub))
|
|
142
|
+
return { ignored: true, reason: "gitignored" };
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
// noop
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { ignored: false };
|
|
150
|
+
}
|
|
151
|
+
function getFastGlobIgnoreGlobs() {
|
|
152
|
+
const globs = [];
|
|
153
|
+
// Hard denylist globs
|
|
154
|
+
for (const d of HARD_DENYLIST) {
|
|
155
|
+
// match directory and its contents anywhere
|
|
156
|
+
globs.push(`**/${d}/**`);
|
|
157
|
+
globs.push(`**/${d}`);
|
|
158
|
+
}
|
|
159
|
+
// Root .gitignore patterns (convert to simple globs)
|
|
160
|
+
for (const p of rootGitPatterns) {
|
|
161
|
+
if (!p || p.startsWith("#") || p.startsWith("!"))
|
|
162
|
+
continue;
|
|
163
|
+
let pat = p;
|
|
164
|
+
if (pat.startsWith("/"))
|
|
165
|
+
pat = pat.slice(1);
|
|
166
|
+
if (pat.includes("/")) {
|
|
167
|
+
// anchored path
|
|
168
|
+
globs.push(`**/${toPosix(pat)}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
globs.push(`**/${pat}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// .git/info/exclude patterns
|
|
175
|
+
for (const p of gitInfoPatterns) {
|
|
176
|
+
if (!p || p.startsWith("#") || p.startsWith("!"))
|
|
177
|
+
continue;
|
|
178
|
+
let pat = p;
|
|
179
|
+
if (pat.startsWith("/"))
|
|
180
|
+
pat = pat.slice(1);
|
|
181
|
+
if (pat.includes("/")) {
|
|
182
|
+
globs.push(`**/${toPosix(pat)}`);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
globs.push(`**/${pat}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Nested .gitignore patterns - prefix with directory path
|
|
189
|
+
// Use the raw patterns collected in nestedPatterns so we scope patterns
|
|
190
|
+
// to the nested directory instead of ignoring the entire directory.
|
|
191
|
+
// Debug: print nested patterns and the computed globs to help diagnosing test failures.
|
|
192
|
+
for (const [dirRel, patterns] of nestedPatterns.entries()) {
|
|
193
|
+
for (const p of patterns) {
|
|
194
|
+
if (!p || p.startsWith("#") || p.startsWith("!"))
|
|
195
|
+
continue;
|
|
196
|
+
let pat = p;
|
|
197
|
+
if (pat.startsWith("/"))
|
|
198
|
+
pat = pat.slice(1);
|
|
199
|
+
const prefix = dirRel === "." ? "" : `${dirRel}/`;
|
|
200
|
+
if (pat.includes("/")) {
|
|
201
|
+
globs.push(`**/${prefix}${toPosix(pat)}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
globs.push(`**/${prefix}${pat}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return Array.from(new Set(globs));
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
isIgnored(inputPath) {
|
|
212
|
+
return isIgnoredInternal(inputPath).ignored;
|
|
213
|
+
},
|
|
214
|
+
getFastGlobIgnoreGlobs,
|
|
215
|
+
explain(inputPath) {
|
|
216
|
+
return isIgnoredInternal(inputPath);
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-version.d.ts","sourceRoot":"","sources":["../../src/public/schema-version.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EACL,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LATEST_KB_SCHEMA_VERSION, getSchemaVersionStatus, normalizeSchemaVersion, } from "../utils/schema-version.js";
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,yBAAyB,EAC/B,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,yBAAyB,EAC/B,MAAM,oBAAoB,CAAC;AAG5B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE;QACR,MAAM,EAAE,OAAO,CAAC;QAChB,GAAG,EAAE,OAAO,CAAC;KACd,CAAC;IACF,GAAG,EAAE;QACH,KAAK,EAAE,OAAO,CAAC;QACf,YAAY,EAAE,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAwBxD,eAAO,MAAM,cAAc,EAAE,QAAQ,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAgBxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAShC,CAAC;AA8CF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAiChE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAiCpE"}
|