kibi-cli 0.2.3 → 0.2.5

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 (95) hide show
  1. package/dist/cli.js +3 -28
  2. package/dist/commands/aggregated-checks.d.ts +4 -1
  3. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  4. package/dist/commands/aggregated-checks.js +13 -3
  5. package/dist/commands/branch.d.ts.map +1 -1
  6. package/dist/commands/branch.js +3 -41
  7. package/dist/commands/check.d.ts.map +1 -1
  8. package/dist/commands/check.js +54 -44
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +0 -27
  11. package/dist/commands/gc.d.ts.map +1 -1
  12. package/dist/commands/gc.js +2 -51
  13. package/dist/commands/init-helpers.d.ts.map +1 -1
  14. package/dist/commands/init-helpers.js +23 -36
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +0 -27
  17. package/dist/commands/query.d.ts.map +1 -1
  18. package/dist/commands/query.js +7 -288
  19. package/dist/commands/sync/cache.d.ts +13 -0
  20. package/dist/commands/sync/cache.d.ts.map +1 -0
  21. package/dist/commands/sync/cache.js +76 -0
  22. package/dist/commands/sync/discovery.d.ts +8 -0
  23. package/dist/commands/sync/discovery.d.ts.map +1 -0
  24. package/dist/commands/sync/discovery.js +50 -0
  25. package/dist/commands/sync/extraction.d.ts +11 -0
  26. package/dist/commands/sync/extraction.d.ts.map +1 -0
  27. package/dist/commands/sync/extraction.js +69 -0
  28. package/dist/commands/sync/manifest.d.ts +5 -0
  29. package/dist/commands/sync/manifest.d.ts.map +1 -0
  30. package/dist/commands/sync/manifest.js +118 -0
  31. package/dist/commands/sync/persistence.d.ts +16 -0
  32. package/dist/commands/sync/persistence.d.ts.map +1 -0
  33. package/dist/commands/sync/persistence.js +188 -0
  34. package/dist/commands/sync/staging.d.ts +4 -0
  35. package/dist/commands/sync/staging.d.ts.map +1 -0
  36. package/dist/commands/sync/staging.js +86 -0
  37. package/dist/commands/sync.d.ts +2 -1
  38. package/dist/commands/sync.d.ts.map +1 -1
  39. package/dist/commands/sync.js +69 -501
  40. package/dist/extractors/manifest.d.ts +0 -1
  41. package/dist/extractors/manifest.d.ts.map +1 -1
  42. package/dist/extractors/manifest.js +39 -48
  43. package/dist/extractors/markdown.d.ts +0 -1
  44. package/dist/extractors/markdown.d.ts.map +1 -1
  45. package/dist/extractors/markdown.js +16 -49
  46. package/dist/extractors/relationships.d.ts +39 -0
  47. package/dist/extractors/relationships.d.ts.map +1 -0
  48. package/dist/extractors/relationships.js +137 -0
  49. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  50. package/dist/extractors/symbols-coordinator.js +0 -27
  51. package/dist/extractors/symbols-ts.d.ts.map +1 -1
  52. package/dist/extractors/symbols-ts.js +0 -27
  53. package/dist/kb/target-resolver.d.ts +80 -0
  54. package/dist/kb/target-resolver.d.ts.map +1 -0
  55. package/dist/kb/target-resolver.js +313 -0
  56. package/dist/prolog/codec.d.ts +63 -0
  57. package/dist/prolog/codec.d.ts.map +1 -0
  58. package/dist/prolog/codec.js +434 -0
  59. package/dist/prolog.d.ts.map +1 -1
  60. package/dist/prolog.js +0 -27
  61. package/dist/public/extractors/symbols-coordinator.d.ts.map +1 -1
  62. package/dist/public/extractors/symbols-coordinator.js +0 -27
  63. package/dist/public/prolog/index.d.ts.map +1 -1
  64. package/dist/public/prolog/index.js +0 -27
  65. package/dist/public/schemas/entity.d.ts.map +1 -1
  66. package/dist/public/schemas/entity.js +0 -27
  67. package/dist/public/schemas/relationship.d.ts.map +1 -1
  68. package/dist/public/schemas/relationship.js +0 -27
  69. package/dist/query/service.d.ts +35 -0
  70. package/dist/query/service.d.ts.map +1 -0
  71. package/dist/query/service.js +149 -0
  72. package/dist/relationships/shards.d.ts +68 -0
  73. package/dist/relationships/shards.d.ts.map +1 -0
  74. package/dist/relationships/shards.js +263 -0
  75. package/dist/traceability/git-staged.d.ts +4 -1
  76. package/dist/traceability/git-staged.d.ts.map +1 -1
  77. package/dist/traceability/git-staged.js +24 -11
  78. package/dist/types/changeset.d.ts.map +1 -1
  79. package/dist/types/entities.d.ts.map +1 -1
  80. package/dist/types/relationships.d.ts.map +1 -1
  81. package/dist/utils/branch-resolver.d.ts +4 -0
  82. package/dist/utils/branch-resolver.d.ts.map +1 -1
  83. package/dist/utils/branch-resolver.js +4 -0
  84. package/dist/utils/config.d.ts +10 -1
  85. package/dist/utils/config.d.ts.map +1 -1
  86. package/dist/utils/config.js +27 -1
  87. package/dist/utils/rule-registry.d.ts +47 -0
  88. package/dist/utils/rule-registry.d.ts.map +1 -0
  89. package/dist/utils/rule-registry.js +139 -0
  90. package/package.json +5 -1
  91. package/schema/config.json +156 -0
  92. package/src/public/extractors/symbols-coordinator.ts +0 -27
  93. package/src/public/prolog/index.ts +0 -27
  94. package/src/public/schemas/entity.ts +0 -27
  95. package/src/public/schemas/relationship.ts +0 -27
package/dist/cli.js CHANGED
@@ -15,33 +15,7 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- /*
19
- How to apply this header to source files (examples)
20
-
21
- 1) Prepend header to a single file (POSIX shells):
22
-
23
- cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
24
-
25
- 2) Apply to multiple files (example: the project's main entry files):
26
-
27
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
28
- if [ -f "$f" ]; then
29
- cp "$f" "$f".bak
30
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
31
- fi
32
- done
33
-
34
- 3) Avoid duplicating the header: run a quick guard to only add if missing
35
-
36
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
37
- if [ -f "$f" ]; then
38
- if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
39
- cp "$f" "$f".bak
40
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
41
- fi
42
- fi
43
- done
44
- */
18
+ import { readFileSync } from "node:fs";
45
19
  import { Command } from "commander";
46
20
  import { branchEnsureCommand } from "./commands/branch.js";
47
21
  import { checkCommand } from "./commands/check.js";
@@ -50,7 +24,8 @@ import { gcCommand } from "./commands/gc.js";
50
24
  import { initCommand } from "./commands/init.js";
51
25
  import { queryCommand } from "./commands/query.js";
52
26
  import { syncCommand } from "./commands/sync.js";
53
- const VERSION = "0.1.0";
27
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
28
+ const VERSION = packageJson.version ?? "0.1.0";
54
29
  const program = new Command();
55
30
  program
56
31
  .name("kibi")
@@ -4,6 +4,9 @@ import type { Violation } from "./check.js";
4
4
  * Run all checks using the aggregated Prolog predicates.
5
5
  * This makes a single Prolog call and parses JSON output, significantly
6
6
  * faster than running individual checks with multiple round-trips.
7
+ * @param prolog - The Prolog process
8
+ * @param rulesAllowlist - Set of rule names to run (null = all)
9
+ * @param requireAdr - Whether to require ADR constraints for symbol-traceability
7
10
  */
8
- export declare function runAggregatedChecks(prolog: PrologProcess, rulesAllowlist: Set<string> | null): Promise<Violation[]>;
11
+ export declare function runAggregatedChecks(prolog: PrologProcess, rulesAllowlist: Set<string> | null, requireAdr?: boolean): Promise<Violation[]>;
9
12
  //# sourceMappingURL=aggregated-checks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"aggregated-checks.d.ts","sourceRoot":"","sources":["../../src/commands/aggregated-checks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAmB,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU5C;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GACjC,OAAO,CAAC,SAAS,EAAE,CAAC,CAqDtB"}
1
+ {"version":3,"file":"aggregated-checks.d.ts","sourceRoot":"","sources":["../../src/commands/aggregated-checks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAmB,MAAM,cAAc,CAAC;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU5C;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAClC,UAAU,UAAQ,GACjB,OAAO,CAAC,SAAS,EAAE,CAAC,CA2DtB"}
@@ -1,15 +1,25 @@
1
1
  import path from "node:path";
2
2
  import { resolveKbPlPath } from "../prolog.js";
3
+ import { escapeAtom } from "../prolog/codec.js";
3
4
  /**
4
5
  * Run all checks using the aggregated Prolog predicates.
5
6
  * This makes a single Prolog call and parses JSON output, significantly
6
7
  * faster than running individual checks with multiple round-trips.
8
+ * @param prolog - The Prolog process
9
+ * @param rulesAllowlist - Set of rule names to run (null = all)
10
+ * @param requireAdr - Whether to require ADR constraints for symbol-traceability
7
11
  */
8
- export async function runAggregatedChecks(prolog, rulesAllowlist) {
12
+ export async function runAggregatedChecks(prolog, rulesAllowlist, requireAdr = false) {
9
13
  const violations = [];
10
14
  const checksPlPath = path.join(path.dirname(resolveKbPlPath()), "checks.pl");
11
- const checksPlPathEscaped = checksPlPath.replace(/'/g, "''");
12
- const query = `(use_module('${checksPlPathEscaped}'), call(checks:check_all_json(JsonString)))`;
15
+ const checksPlPathEscaped = escapeAtom(checksPlPath);
16
+ // Use check_all_json_with_options if available, otherwise fall back to check_all_json
17
+ const requireAdrStr = requireAdr ? "true" : "false";
18
+ const query = `(use_module('${checksPlPathEscaped}'),
19
+ ( predicate_property(checks:check_all_json_with_options(_, _), _)
20
+ -> call(checks:check_all_json_with_options(JsonString, ${requireAdrStr}))
21
+ ; call(checks:check_all_json(JsonString))
22
+ ))`;
13
23
  try {
14
24
  const result = await prolog.query(query);
15
25
  if (!result.success) {
@@ -1 +1 @@
1
- {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AAwDA,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA+DD,wBAAsB,mBAAmB,CACvC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmDD,wBAAsB,mBAAmB,CACvC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,eAAe,mBAAmB,CAAC"}
@@ -15,37 +15,9 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- /*
19
- How to apply this header to source files (examples)
20
-
21
- 1) Prepend header to a single file (POSIX shells):
22
-
23
- cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
24
-
25
- 2) Apply to multiple files (example: the project's main entry files):
26
-
27
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
28
- if [ -f "$f" ]; then
29
- cp "$f" "$f".bak
30
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
31
- fi
32
- done
33
-
34
- 3) Avoid duplicating the header: run a quick guard to only add if missing
35
-
36
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
37
- if [ -f "$f" ]; then
38
- if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
39
- cp "$f" "$f".bak
40
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
41
- fi
42
- fi
43
- done
44
- */
45
18
  import * as fs from "node:fs";
46
19
  import * as path from "node:path";
47
- import { copyCleanSnapshot, getBranchDiagnostic, isValidBranchName, resolveActiveBranch, resolveDefaultBranch, } from "../utils/branch-resolver.js";
48
- import { loadConfig } from "../utils/config.js";
20
+ import { copyCleanSnapshot, getBranchDiagnostic, isValidBranchName, resolveActiveBranch, } from "../utils/branch-resolver.js";
49
21
  function resolveExplicitFromBranch(fromBranch) {
50
22
  if (!isValidBranchName(fromBranch)) {
51
23
  console.warn(`Warning: invalid branch name provided via --from: '${fromBranch}'`);
@@ -59,18 +31,8 @@ function resolveExplicitFromBranch(fromBranch) {
59
31
  return null;
60
32
  }
61
33
  function resolveDefaultSourceBranch() {
62
- const config = loadConfig(process.cwd());
63
- const defaultResult = resolveDefaultBranch(process.cwd(), config);
64
- if ("branch" in defaultResult) {
65
- const defaultBranch = defaultResult.branch;
66
- const defaultPath = path.join(process.cwd(), ".kb/branches", defaultBranch);
67
- if (fs.existsSync(defaultPath)) {
68
- return defaultBranch;
69
- }
70
- }
71
- else {
72
- console.warn(`Warning: could not resolve default branch: ${defaultResult.error}`);
73
- }
34
+ // No default branch concept - branches are independent
35
+ // When --from is not specified, an empty branch KB will be created
74
36
  return null;
75
37
  }
76
38
  function determineSourceBranch(explicitFromBranch) {
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA+DA,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;AAED,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,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2OvE"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA2CA,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;AAED,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,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAuPvE"}
@@ -15,40 +15,16 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- /*
19
- How to apply this header to source files (examples)
20
-
21
- 1) Prepend header to a single file (POSIX shells):
22
-
23
- cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
24
-
25
- 2) Apply to multiple files (example: the project's main entry files):
26
-
27
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
28
- if [ -f "$f" ]; then
29
- cp "$f" "$f".bak
30
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
31
- fi
32
- done
33
-
34
- 3) Avoid duplicating the header: run a quick guard to only add if missing
35
-
36
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
37
- if [ -f "$f" ]; then
38
- if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
39
- cp "$f" "$f".bak
40
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
41
- fi
42
- fi
43
- done
44
- */
45
18
  import * as path from "node:path";
46
19
  import { PrologProcess } from "../prolog.js";
20
+ import { escapeAtom } from "../prolog/codec.js";
47
21
  import { getStagedFiles } from "../traceability/git-staged.js";
48
22
  import { validateStagedMarkdown } from "../traceability/markdown-validate.js";
49
23
  import { extractSymbolsFromStagedFile } from "../traceability/symbol-extract.js";
50
24
  import { cleanupTempKb, consultOverlay, createOverlayFacts, createTempKb, } from "../traceability/temp-kb.js";
51
25
  import { formatViolations as formatStagedViolations, validateStagedSymbols, } from "../traceability/validate.js";
26
+ import { loadConfig } from "../utils/config.js";
27
+ import { RULES, getEffectiveRules, } from "../utils/rule-registry.js";
52
28
  import { runAggregatedChecks } from "./aggregated-checks.js";
53
29
  import { getCurrentBranch } from "./init-helpers.js";
54
30
  export async function checkCommand(options) {
@@ -111,7 +87,7 @@ export async function checkCommand(options) {
111
87
  for (const f of codeFiles) {
112
88
  try {
113
89
  const symbols = extractSymbolsFromStagedFile(f);
114
- if (symbols && symbols.length) {
90
+ if (symbols?.length) {
115
91
  allSymbols.push(...symbols);
116
92
  }
117
93
  }
@@ -165,7 +141,7 @@ export async function checkCommand(options) {
165
141
  }
166
142
  const prolog = new PrologProcess({ timeout: 120000 });
167
143
  await prolog.start();
168
- const kbPathEscaped = resolvedKbPath.replace(/'/g, "''");
144
+ const kbPathEscaped = escapeAtom(resolvedKbPath);
169
145
  const attachResult = await prolog.query(`kb_attach('${kbPathEscaped}')`);
170
146
  if (!attachResult.success) {
171
147
  await prolog.terminate();
@@ -173,21 +149,20 @@ export async function checkCommand(options) {
173
149
  process.exit(1);
174
150
  }
175
151
  const violations = [];
176
- // Parse rules allowlist if provided
177
- let rulesAllowlist = null;
178
- if (options.rules) {
179
- const parts = options.rules
180
- .split(",")
181
- .map((s) => s.trim())
182
- .filter(Boolean);
183
- rulesAllowlist = new Set(parts);
184
- }
152
+ // Load config to get rule enablement settings
153
+ const config = loadConfig(process.cwd());
154
+ const checksConfig = config.checks ?? {
155
+ rules: Object.fromEntries(RULES.map((r) => [r.name, true])),
156
+ symbolTraceability: { requireAdr: false },
157
+ };
158
+ // Get effective rules based on config and CLI --rules filter
159
+ const effectiveRules = getEffectiveRules(checksConfig.rules, options.rules);
185
160
  // Helper to conditionally run a check by name
186
161
  async function runCheck(name, fn, ...args) {
187
- if (rulesAllowlist?.has(name) === false)
162
+ if (!effectiveRules.has(name))
188
163
  return;
189
164
  const res = await fn(prolog, ...args);
190
- if (res && res.length)
165
+ if (res?.length)
191
166
  violations.push(...res);
192
167
  }
193
168
  // Use aggregated checks (single Prolog call) when possible for better performance
@@ -196,27 +171,32 @@ export async function checkCommand(options) {
196
171
  const supportedRules = [
197
172
  "must-priority-coverage",
198
173
  "symbol-coverage",
174
+ "symbol-traceability",
199
175
  "no-dangling-refs",
200
176
  "no-cycles",
201
177
  "required-fields",
202
178
  "deprecated-adr-no-successor",
203
179
  "domain-contradictions",
204
180
  ];
205
- const canUseAggregated = !rulesAllowlist ||
206
- Array.from(rulesAllowlist).every((r) => supportedRules.includes(r));
181
+ const canUseAggregated = Array.from(effectiveRules).every((r) => supportedRules.includes(r));
207
182
  if (canUseAggregated) {
208
183
  // Fast path: single Prolog call returning all violations
209
- const aggregatedViolations = await runAggregatedChecks(prolog, rulesAllowlist);
184
+ // Pass the requireAdr option for symbol-traceability
185
+ const aggregatedViolations = await runAggregatedChecks(prolog, effectiveRules, checksConfig.symbolTraceability?.requireAdr ?? false);
210
186
  violations.push(...aggregatedViolations);
211
187
  }
212
188
  else {
213
189
  // Legacy path: individual checks for backward compatibility
214
190
  await runCheck("must-priority-coverage", checkMustPriorityCoverage);
215
191
  await runCheck("symbol-coverage", checkSymbolCoverage);
192
+ await runCheck("symbol-traceability", (p) => checkSymbolTraceability(p, checksConfig.symbolTraceability?.requireAdr ?? false));
216
193
  await runCheck("no-dangling-refs", checkNoDanglingRefs);
217
194
  await runCheck("no-cycles", checkNoCycles);
218
195
  const allEntityIds = await getAllEntityIds(prolog);
219
- await runCheck("required-fields", checkRequiredFields, allEntityIds);
196
+ if (effectiveRules.has("required-fields")) {
197
+ const requiredViolations = await checkRequiredFields(prolog, allEntityIds);
198
+ violations.push(...requiredViolations);
199
+ }
220
200
  await runCheck("deprecated-adr-no-successor", checkDeprecatedAdrs);
221
201
  await runCheck("domain-contradictions", checkDomainContradictions);
222
202
  }
@@ -573,6 +553,36 @@ async function checkSymbolCoverage(prolog) {
573
553
  }
574
554
  return violations;
575
555
  }
556
+ async function checkSymbolTraceability(prolog, requireAdr) {
557
+ const violations = [];
558
+ const requireAdrStr = requireAdr ? "true" : "false";
559
+ const result = await prolog.query(`findall(violation(Rule, EntityId, Desc, Sugg, Src),
560
+ checks:symbol_traceability_violation(${requireAdrStr}, violation(Rule, EntityId, Desc, Sugg, Src)),
561
+ Violations)`);
562
+ if (!result.success || !result.bindings.Violations) {
563
+ return violations;
564
+ }
565
+ // Parse the violations from Prolog format
566
+ const violationsStr = result.bindings.Violations;
567
+ if (violationsStr && violationsStr !== "[]") {
568
+ // Parse each violation term
569
+ const violationRegex = /violation\(([^,]+),'?([^',]+)'?,([^,]+),([^,]+),'?([^']*)'?\)/g;
570
+ let match;
571
+ do {
572
+ match = violationRegex.exec(violationsStr);
573
+ if (match) {
574
+ violations.push({
575
+ rule: match[1].trim().replace(/^'|'$/g, ""),
576
+ entityId: match[2].trim(),
577
+ description: match[3].trim().replace(/^"|"$/g, ""),
578
+ suggestion: match[4].trim().replace(/^"|"$/g, ""),
579
+ source: match[5].trim() || undefined,
580
+ });
581
+ }
582
+ } while (match);
583
+ }
584
+ return violations;
585
+ }
576
586
  function parseTripleRows(raw) {
577
587
  const cleaned = raw.trim();
578
588
  if (cleaned === "[]" || cleaned.length === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAsDA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA0DnD"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AA2BA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA0DnD"}
@@ -15,33 +15,6 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- /*
19
- How to apply this header to source files (examples)
20
-
21
- 1) Prepend header to a single file (POSIX shells):
22
-
23
- cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
24
-
25
- 2) Apply to multiple files (example: the project's main entry files):
26
-
27
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
28
- if [ -f "$f" ]; then
29
- cp "$f" "$f".bak
30
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
31
- fi
32
- done
33
-
34
- 3) Avoid duplicating the header: run a quick guard to only add if missing
35
-
36
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
37
- if [ -f "$f" ]; then
38
- if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
39
- cp "$f" "$f".bak
40
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
41
- fi
42
- fi
43
- done
44
- */
45
18
  import { execSync } from "node:child_process";
46
19
  import { existsSync, readFileSync, statSync } from "node:fs";
47
20
  import * as path from "node:path";
@@ -1 +1 @@
1
- {"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"AAmDA,wBAAsB,SAAS,CAAC,OAAO,EAAE;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,iBAsGA;AAED,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"AAsBA,wBAAsB,SAAS,CAAC,OAAO,EAAE;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,iBAiFA;AAED,eAAe,SAAS,CAAC"}
@@ -15,38 +15,9 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- /*
19
- How to apply this header to source files (examples)
20
-
21
- 1) Prepend header to a single file (POSIX shells):
22
-
23
- cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
24
-
25
- 2) Apply to multiple files (example: the project's main entry files):
26
-
27
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
28
- if [ -f "$f" ]; then
29
- cp "$f" "$f".bak
30
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
31
- fi
32
- done
33
-
34
- 3) Avoid duplicating the header: run a quick guard to only add if missing
35
-
36
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
37
- if [ -f "$f" ]; then
38
- if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
39
- cp "$f" "$f".bak
40
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
41
- fi
42
- fi
43
- done
44
- */
45
18
  import { execSync } from "node:child_process";
46
19
  import * as fs from "node:fs";
47
20
  import * as path from "node:path";
48
- import { resolveDefaultBranch } from "../utils/branch-resolver.js";
49
- import { loadConfig } from "../utils/config.js";
50
21
  export async function gcCommand(options) {
51
22
  // If force is true, perform deletion. Otherwise default to dry run.
52
23
  const dryRun = options?.force ? false : (options?.dryRun ?? true);
@@ -87,28 +58,8 @@ export async function gcCommand(options) {
87
58
  .readdirSync(kbRoot, { withFileTypes: true })
88
59
  .filter((d) => d.isDirectory())
89
60
  .map((d) => d.name);
90
- // Resolve configured/default branch to protect
91
- const config = loadConfig(process.cwd());
92
- // Prefer explicit configured defaultBranch if set
93
- const configured = config?.defaultBranch;
94
- let defaultBranch;
95
- if (configured && typeof configured === "string" && configured.trim()) {
96
- defaultBranch = configured.trim();
97
- }
98
- else {
99
- const resolved = resolveDefaultBranch(process.cwd(), config);
100
- defaultBranch =
101
- "branch" in resolved && typeof resolved.branch === "string"
102
- ? resolved.branch
103
- : "main";
104
- }
105
- // Protect resolved branch and its 'master'->'main' normalization
106
- const protectedBranches = new Set([defaultBranch]);
107
- if (defaultBranch === "main")
108
- protectedBranches.add("master");
109
- if (defaultBranch === "master")
110
- protectedBranches.add("main");
111
- const staleBranches = kbBranches.filter((kb) => !protectedBranches.has(kb) && !gitBranches.has(kb));
61
+ // Branches are protected if they exist in git - no default branch concept
62
+ const staleBranches = kbBranches.filter((kb) => !gitBranches.has(kb));
112
63
  // Perform deletion when dryRun is false (force requested)
113
64
  const performDelete = !dryRun;
114
65
  let deletedCount = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"AA8GA,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAajD;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAuBnE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiBpD"}
1
+ {"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"AAmFA,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAajD;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAYf;AASD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAmCnE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiBpD"}
@@ -15,33 +15,6 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- /*
19
- How to apply this header to source files (examples)
20
-
21
- 1) Prepend header to a single file (POSIX shells):
22
-
23
- cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
24
-
25
- 2) Apply to multiple files (example: the project's main entry files):
26
-
27
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
28
- if [ -f "$f" ]; then
29
- cp "$f" "$f".bak
30
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
31
- fi
32
- done
33
-
34
- 3) Avoid duplicating the header: run a quick guard to only add if missing
35
-
36
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
37
- if [ -f "$f" ]; then
38
- if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
39
- cp "$f" "$f".bak
40
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
41
- fi
42
- fi
43
- done
44
- */
45
18
  import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
46
19
  import * as path from "node:path";
47
20
  import fg from "fast-glob";
@@ -58,7 +31,7 @@ branch_flag=$3
58
31
 
59
32
  if [ "$branch_flag" = "1" ]; then
60
33
  # Try to resolve the branch we just left (strip decorations like ^ and ~)
61
- old_branch=$(git name-rev --name-only "$old_ref" 2>/dev/null | sed 's/\^.*//')
34
+ old_branch=$(git name-rev --name-only "$old_ref" 2>/dev/null | sed 's/\\^.*//')
62
35
 
63
36
  # Basic validation: non-empty and does not contain ~ or ^
64
37
  if [ -n "$old_branch" ] && echo "$old_branch" | grep -qv '[~^]'; then
@@ -138,21 +111,35 @@ export async function copySchemaFiles(kbDir, schemaSourceDir) {
138
111
  }
139
112
  console.log(`✓ Copied ${schemaFiles.length} schema files`);
140
113
  }
114
+ const KIBI_HOOK_BEGIN = "# BEGIN kibi-managed";
115
+ const KIBI_HOOK_END = "# END kibi-managed";
116
+ function escapeRegex(s) {
117
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
118
+ }
141
119
  export function installHook(hookPath, content) {
120
+ const kibiSection = `${KIBI_HOOK_BEGIN}\n${content}\n${KIBI_HOOK_END}`;
142
121
  if (existsSync(hookPath)) {
143
122
  const existing = readFileSync(hookPath, "utf8");
144
- if (!existing.includes("kibi")) {
145
- writeFileSync(hookPath, `${existing}
146
- ${content}`, {
147
- mode: 0o755,
148
- });
123
+ if (existing.includes(KIBI_HOOK_BEGIN) &&
124
+ existing.includes(KIBI_HOOK_END)) {
125
+ // Replace only the kibi-managed section, preserving any user-authored content
126
+ const updated = existing.replace(new RegExp(`${escapeRegex(KIBI_HOOK_BEGIN)}[\\s\\S]*?${escapeRegex(KIBI_HOOK_END)}`), kibiSection);
127
+ writeFileSync(hookPath, updated, { mode: 0o755 });
128
+ }
129
+ else if (existing.includes("kibi branch ensure")) {
130
+ // Legacy format: already has the complete kibi logic, skip
131
+ return;
132
+ }
133
+ else {
134
+ // Hook exists with user content (no kibi section) - append kibi section
135
+ const shebang = existing.startsWith("#!/") ? "" : "#!/bin/sh\n";
136
+ writeFileSync(hookPath, `${shebang}${existing.trimEnd()}\n${kibiSection}\n`, { mode: 0o755 });
149
137
  }
150
138
  }
151
139
  else {
152
- writeFileSync(hookPath, `#!/bin/sh
153
- ${content}`, { mode: 0o755 });
140
+ writeFileSync(hookPath, `#!/bin/sh\n${kibiSection}\n`, { mode: 0o755 });
154
141
  }
155
- // Explicitly ensure hook is executable (mode option can be inconsistent in Docker)
142
+ // Explicitly ensure hook is executable
156
143
  chmodSync(hookPath, 0o755);
157
144
  }
158
145
  export function installGitHooks(gitDir) {
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4DA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DrE"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiCA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DrE"}
@@ -15,33 +15,6 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- /*
19
- How to apply this header to source files (examples)
20
-
21
- 1) Prepend header to a single file (POSIX shells):
22
-
23
- cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
24
-
25
- 2) Apply to multiple files (example: the project's main entry files):
26
-
27
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
28
- if [ -f "$f" ]; then
29
- cp "$f" "$f".bak
30
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
31
- fi
32
- done
33
-
34
- 3) Avoid duplicating the header: run a quick guard to only add if missing
35
-
36
- for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
37
- if [ -f "$f" ]; then
38
- if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
39
- cp "$f" "$f".bak
40
- (cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
41
- fi
42
- fi
43
- done
44
- */
45
18
  import { existsSync } from "node:fs";
46
19
  import * as path from "node:path";
47
20
  import { fileURLToPath } from "node:url";
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AAqDA,UAAU,YAAY;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAmLf"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AAmCA,UAAU,YAAY;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAyKf"}