kibi-cli 0.7.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.
Files changed (44) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +9 -0
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +25 -1
  5. package/dist/commands/migrate.d.ts +9 -0
  6. package/dist/commands/migrate.d.ts.map +1 -0
  7. package/dist/commands/migrate.js +183 -0
  8. package/dist/commands/sync/staging.d.ts +3 -0
  9. package/dist/commands/sync/staging.d.ts.map +1 -1
  10. package/dist/commands/sync/staging.js +58 -1
  11. package/dist/commands/sync.d.ts +18 -1
  12. package/dist/commands/sync.d.ts.map +1 -1
  13. package/dist/commands/sync.js +19 -4
  14. package/dist/public/check-types.d.ts +3 -1
  15. package/dist/public/check-types.d.ts.map +1 -1
  16. package/dist/public/check-types.js +1 -0
  17. package/dist/public/ignore-policy.d.ts +10 -0
  18. package/dist/public/ignore-policy.d.ts.map +1 -0
  19. package/dist/public/ignore-policy.js +219 -0
  20. package/dist/public/operational-artifacts.d.ts +2 -0
  21. package/dist/public/operational-artifacts.d.ts.map +1 -0
  22. package/dist/public/operational-artifacts.js +4 -0
  23. package/dist/public/schema-version.d.ts +3 -0
  24. package/dist/public/schema-version.d.ts.map +1 -0
  25. package/dist/public/schema-version.js +1 -0
  26. package/dist/search-ranking.d.ts.map +1 -1
  27. package/dist/search-ranking.js +132 -25
  28. package/dist/utils/config.d.ts +1 -0
  29. package/dist/utils/config.d.ts.map +1 -1
  30. package/dist/utils/config.js +35 -22
  31. package/dist/utils/rule-registry.d.ts.map +1 -1
  32. package/dist/utils/rule-registry.js +6 -0
  33. package/dist/utils/schema-version.d.ts +14 -0
  34. package/dist/utils/schema-version.d.ts.map +1 -0
  35. package/dist/utils/schema-version.js +59 -0
  36. package/dist/utils/strict-modeling.d.ts +64 -0
  37. package/dist/utils/strict-modeling.d.ts.map +1 -0
  38. package/dist/utils/strict-modeling.js +371 -0
  39. package/package.json +17 -3
  40. package/schema/config.json +8 -1
  41. package/src/public/check-types.ts +15 -1
  42. package/src/public/ignore-policy.ts +229 -0
  43. package/src/public/operational-artifacts.ts +5 -0
  44. 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":"AAsCA,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
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,CAkR/B"}
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"}
@@ -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,9 @@
1
+ interface MigrateOptions {
2
+ dryRun?: boolean;
3
+ yes?: boolean;
4
+ }
5
+ export declare function migrateCommand(options?: MigrateOptions): Promise<{
6
+ exitCode: number;
7
+ }>;
8
+ export {};
9
+ //# sourceMappingURL=migrate.d.ts.map
@@ -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
+ }
@@ -8,11 +8,14 @@ interface StagingDeps {
8
8
  cwd: () => string;
9
9
  existsSync: typeof existsSync;
10
10
  fg: typeof fg;
11
+ isProcessAlive: (pid: number) => boolean;
11
12
  mkdirSync: typeof mkdirSync;
12
13
  moduleDir: string;
13
14
  renameSync: typeof renameSync;
14
15
  rmSync: typeof rmSync;
15
16
  }
17
+ export declare function createUniqueStagingPath(currentBranch: string, rootDir: string, pid?: number, now?: number): string;
18
+ export declare function cleanupAbandonedStagingDirectories(stagingPath: string, deps?: Partial<StagingDeps>): Promise<void>;
16
19
  export declare function prepareStagingEnvironment(stagingPath: string, livePath: string, rebuild: boolean, deps?: Partial<StagingDeps>): Promise<void>;
17
20
  export declare function atomicPublish(stagingPath: string, livePath: string, deps?: Partial<StagingDeps>): void;
18
21
  export declare function cleanupStaging(stagingPath: string, deps?: Partial<StagingDeps>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/staging.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,MAAM,WAAW,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,UAAU,WAAW;IACnB,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,EAAE,EAAE,OAAO,EAAE,CAAC;IACd,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,MAAM,EAAE,OAAO,MAAM,CAAC;CACvB;AAiBD,wBAAsB,yBAAyB,CAE7C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAaf;AA0CD,wBAAgB,aAAa,CAE3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,IAAI,CAeN;AAED,wBAAgB,cAAc,CAE5B,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,IAAI,CAKN"}
1
+ {"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/staging.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,MAAM,WAAW,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,UAAU,WAAW;IACnB,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,EAAE,EAAE,OAAO,EAAE,CAAC;IACd,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,MAAM,EAAE,OAAO,MAAM,CAAC;CACvB;AA8BD,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,GAAG,SAAc,EACjB,GAAG,SAAa,GACf,MAAM,CAOR;AAGD,wBAAsB,kCAAkC,CACtD,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED,wBAAsB,yBAAyB,CAE7C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAaf;AA0CD,wBAAgB,aAAa,CAE3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,IAAI,CAeN;AAED,wBAAgB,cAAc,CAE5B,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,IAAI,CAKN"}
@@ -27,6 +27,17 @@ function resolveDeps(overrides) {
27
27
  cwd: () => process.cwd(),
28
28
  existsSync,
29
29
  fg,
30
+ isProcessAlive: (pid) => {
31
+ try {
32
+ process.kill(pid, 0);
33
+ return true;
34
+ }
35
+ catch (error) {
36
+ return !(error instanceof Error &&
37
+ "code" in error &&
38
+ error.code === "ESRCH");
39
+ }
40
+ },
30
41
  mkdirSync,
31
42
  moduleDir: import.meta.dirname,
32
43
  renameSync,
@@ -34,11 +45,54 @@ function resolveDeps(overrides) {
34
45
  ...overrides,
35
46
  };
36
47
  }
48
+ // implements REQ-003
49
+ export function createUniqueStagingPath(currentBranch, rootDir, pid = process.pid, now = Date.now()) {
50
+ return path.join(rootDir, ".kb", "branches", `${currentBranch}.staging.${pid}.${now}`);
51
+ }
52
+ // implements REQ-003
53
+ export async function cleanupAbandonedStagingDirectories(stagingPath, deps) {
54
+ const resolved = resolveDeps(deps);
55
+ const stagingDir = path.dirname(stagingPath);
56
+ const stagingBase = path.basename(stagingPath);
57
+ const match = /^(?<branch>.+)\.staging\.(?<pid>\d+)\.(?<timestamp>\d+)$/.exec(stagingBase);
58
+ if (!match?.groups) {
59
+ return;
60
+ }
61
+ const branch = match.groups.branch;
62
+ if (!branch) {
63
+ return;
64
+ }
65
+ const candidates = await resolved.fg(`${branch}.staging.*`, {
66
+ cwd: stagingDir,
67
+ absolute: true,
68
+ onlyDirectories: true,
69
+ suppressErrors: true,
70
+ });
71
+ for (const candidate of candidates) {
72
+ if (candidate === stagingPath) {
73
+ continue;
74
+ }
75
+ const candidateBase = path.basename(candidate);
76
+ const candidateMatch = new RegExp(`^${escapeRegex(branch)}\\.staging\\.(\\d+)\\.(\\d+)$`).exec(candidateBase);
77
+ if (!candidateMatch) {
78
+ continue;
79
+ }
80
+ const candidatePidText = candidateMatch[1];
81
+ if (!candidatePidText) {
82
+ continue;
83
+ }
84
+ const candidatePid = Number.parseInt(candidatePidText, 10);
85
+ if (!Number.isFinite(candidatePid) || resolved.isProcessAlive(candidatePid)) {
86
+ continue;
87
+ }
88
+ cleanupStaging(candidate, resolved);
89
+ }
90
+ }
37
91
  export async function prepareStagingEnvironment(
38
92
  // implements REQ-003
39
93
  stagingPath, livePath, rebuild, deps) {
40
94
  const resolved = resolveDeps(deps);
41
- // Cleanup any existing staging directory
95
+ await cleanupAbandonedStagingDirectories(stagingPath, resolved);
42
96
  cleanupStaging(stagingPath, resolved);
43
97
  resolved.mkdirSync(stagingPath, { recursive: true });
44
98
  if (!rebuild && resolved.existsSync(livePath)) {
@@ -107,3 +161,6 @@ stagingPath, deps) {
107
161
  resolved.rmSync(stagingPath, { recursive: true, force: true });
108
162
  }
109
163
  }
164
+ function escapeRegex(value) {
165
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
166
+ }
@@ -1,13 +1,30 @@
1
1
  import type { SyncSummary } from "../diagnostics.js";
2
+ import { PrologProcess } from "../prolog.js";
2
3
  export declare class SyncError extends Error {
3
4
  constructor(message: string);
4
5
  }
5
6
  export interface SyncResult extends SyncSummary {
6
7
  exitCode?: number;
7
8
  }
9
+ interface SyncCommandRuntimeContext {
10
+ currentBranch: string;
11
+ livePath: string;
12
+ rebuild: boolean;
13
+ stagingPath: string;
14
+ validateOnly: boolean;
15
+ }
16
+ interface SyncCommandRuntime {
17
+ afterAttach?: (context: SyncCommandRuntimeContext) => Promise<void> | void;
18
+ beforeSave?: (context: SyncCommandRuntimeContext & {
19
+ kbModified: boolean;
20
+ }) => Promise<void> | void;
21
+ createProlog?: (options: {
22
+ timeout: number;
23
+ }) => PrologProcess;
24
+ }
8
25
  export declare function syncCommand(options?: {
9
26
  validateOnly?: boolean;
10
27
  rebuild?: boolean;
11
- }): Promise<SyncResult>;
28
+ }, runtime?: SyncCommandRuntime): Promise<SyncResult>;
12
29
  export { normalizeMarkdownPath } from "./sync/discovery.js";
13
30
  //# sourceMappingURL=sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA0CjE,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAGD,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1D,OAAO,CAAC,UAAU,CAAC,CAqarB;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAejE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4B7C,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAGD,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,yBAAyB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,WAAW,CAAC,EAAE,CACZ,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,yBAAyB,GAAG;QAAE,UAAU,EAAE,OAAO,CAAA;KAAE,KACzD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,aAAa,CAAC;CAChE;AAGD,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,EAC3D,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,UAAU,CAAC,CAmbrB;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -29,7 +29,7 @@ import { discoverSourceFiles, } from "./sync/discovery.js";
29
29
  import { processExtractions } from "./sync/extraction.js";
30
30
  import { refreshManifestCoordinates } from "./sync/manifest.js";
31
31
  import { persistEntities, persistRelationships } from "./sync/persistence.js";
32
- import { atomicPublish, cleanupStaging, prepareStagingEnvironment, } from "./sync/staging.js";
32
+ import { atomicPublish, cleanupStaging, createUniqueStagingPath, prepareStagingEnvironment, } from "./sync/staging.js";
33
33
  export class SyncError extends Error {
34
34
  constructor(message) {
35
35
  super(message);
@@ -37,7 +37,7 @@ export class SyncError extends Error {
37
37
  }
38
38
  }
39
39
  // implements REQ-003, REQ-007
40
- export async function syncCommand(options = {}) {
40
+ export async function syncCommand(options = {}, runtime = {}) {
41
41
  const validateOnly = options.validateOnly ?? false;
42
42
  const rebuild = options.rebuild ?? false;
43
43
  const startTime = Date.now();
@@ -45,6 +45,7 @@ export async function syncCommand(options = {}) {
45
45
  const entityCounts = {};
46
46
  let published = false;
47
47
  let currentBranch;
48
+ let stagingPath;
48
49
  const getCurrentCommit = () => {
49
50
  try {
50
51
  return execSync("git rev-parse HEAD", {
@@ -199,16 +200,25 @@ export async function syncCommand(options = {}) {
199
200
  if (!kbExists && !rebuild) {
200
201
  diagnostics.push(createKbMissingDiagnostic(currentBranch, livePath));
201
202
  }
202
- const stagingPath = path.join(process.cwd(), `.kb/branches/${currentBranch}.staging`);
203
+ stagingPath = createUniqueStagingPath(currentBranch, process.cwd());
204
+ const runtimeContext = {
205
+ currentBranch,
206
+ livePath,
207
+ rebuild,
208
+ stagingPath,
209
+ validateOnly,
210
+ };
203
211
  await prepareStagingEnvironment(stagingPath, livePath, rebuild);
204
212
  try {
205
- const prolog = new PrologProcess({ timeout: 120000 });
213
+ const prolog = runtime.createProlog?.({ timeout: 120000 }) ??
214
+ new PrologProcess({ timeout: 120000 });
206
215
  await prolog.start();
207
216
  const attachResult = await prolog.query(`kb_attach('${stagingPath}')`);
208
217
  if (!attachResult.success) {
209
218
  await prolog.terminate();
210
219
  throw new SyncError(`Failed to attach to staging KB: ${attachResult.error || "Unknown error"}`);
211
220
  }
221
+ await runtime.afterAttach?.(runtimeContext);
212
222
  const entityIds = new Set();
213
223
  for (const { entity } of results) {
214
224
  entityCounts[entity.type] = (entityCounts[entity.type] || 0) + 1;
@@ -263,6 +273,7 @@ export async function syncCommand(options = {}) {
263
273
  if (kbModified) {
264
274
  prolog.invalidateCache();
265
275
  }
276
+ await runtime.beforeSave?.({ ...runtimeContext, kbModified });
266
277
  const saveResult = await prolog.query("kb_save");
267
278
  if (!saveResult.success) {
268
279
  throw new SyncError(`Failed to save staging KB: ${saveResult.error || "Unknown error"}`);
@@ -270,6 +281,7 @@ export async function syncCommand(options = {}) {
270
281
  await prolog.query("kb_detach");
271
282
  await prolog.terminate();
272
283
  atomicPublish(stagingPath, livePath);
284
+ cleanupStaging(stagingPath);
273
285
  const evictedHashes = {};
274
286
  const evictedSeenAt = {};
275
287
  for (const [key, hash] of Object.entries(nextHashes)) {
@@ -312,6 +324,9 @@ export async function syncCommand(options = {}) {
312
324
  }
313
325
  }
314
326
  catch (error) {
327
+ if (stagingPath) {
328
+ cleanupStaging(stagingPath);
329
+ }
315
330
  const errorMessage = error instanceof Error ? error.message : String(error);
316
331
  console.error(`Error: ${errorMessage}`);
317
332
  const commit = getCurrentCommit();
@@ -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;AAEnC,OAAO,EACL,qBAAqB,EACrB,UAAU,EACV,KAAK,EACL,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,GACjB,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"}