kibi-cli 0.5.1 → 0.6.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 (78) hide show
  1. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  2. package/dist/commands/aggregated-checks.js +3 -2
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +64 -53
  5. package/dist/commands/discovery-shared.d.ts +12 -5
  6. package/dist/commands/discovery-shared.d.ts.map +1 -1
  7. package/dist/commands/discovery-shared.js +21 -13
  8. package/dist/commands/doctor.js +9 -1
  9. package/dist/commands/init-helpers.d.ts.map +1 -1
  10. package/dist/commands/init-helpers.js +2 -3
  11. package/dist/commands/query.d.ts.map +1 -1
  12. package/dist/commands/query.js +15 -5
  13. package/dist/commands/search.js +1 -1
  14. package/dist/commands/sync/cache.d.ts +14 -4
  15. package/dist/commands/sync/cache.d.ts.map +1 -1
  16. package/dist/commands/sync/cache.js +36 -14
  17. package/dist/commands/sync/extraction.d.ts.map +1 -1
  18. package/dist/commands/sync/extraction.js +4 -9
  19. package/dist/commands/sync/manifest.d.ts +14 -3
  20. package/dist/commands/sync/manifest.d.ts.map +1 -1
  21. package/dist/commands/sync/manifest.js +29 -10
  22. package/dist/commands/sync/persistence.d.ts.map +1 -1
  23. package/dist/commands/sync/persistence.js +9 -5
  24. package/dist/commands/sync/staging.d.ts +19 -3
  25. package/dist/commands/sync/staging.d.ts.map +1 -1
  26. package/dist/commands/sync/staging.js +50 -27
  27. package/dist/commands/sync.d.ts.map +1 -1
  28. package/dist/commands/sync.js +16 -20
  29. package/dist/diagnostics.d.ts +1 -10
  30. package/dist/diagnostics.d.ts.map +1 -1
  31. package/dist/diagnostics.js +6 -12
  32. package/dist/env.d.ts +7 -0
  33. package/dist/env.d.ts.map +1 -0
  34. package/dist/env.js +41 -0
  35. package/dist/extractors/manifest.d.ts.map +1 -1
  36. package/dist/extractors/manifest.js +17 -15
  37. package/dist/extractors/markdown.d.ts +2 -0
  38. package/dist/extractors/markdown.d.ts.map +1 -1
  39. package/dist/extractors/markdown.js +124 -30
  40. package/dist/extractors/relationships.d.ts.map +1 -1
  41. package/dist/extractors/relationships.js +10 -4
  42. package/dist/extractors/symbols-coordinator.d.ts +5 -2
  43. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  44. package/dist/extractors/symbols-coordinator.js +5 -2
  45. package/dist/extractors/symbols-ts.d.ts.map +1 -1
  46. package/dist/extractors/symbols-ts.js +56 -10
  47. package/dist/prolog/codec.d.ts +0 -43
  48. package/dist/prolog/codec.d.ts.map +1 -1
  49. package/dist/prolog/codec.js +68 -74
  50. package/dist/prolog.d.ts.map +1 -1
  51. package/dist/prolog.js +39 -25
  52. package/dist/public/schemas/relationship.d.ts.map +1 -1
  53. package/dist/public/schemas/relationship.js +1 -0
  54. package/dist/query/service.d.ts +9 -0
  55. package/dist/query/service.d.ts.map +1 -1
  56. package/dist/query/service.js +27 -10
  57. package/dist/schemas/entity.schema.json +22 -0
  58. package/dist/search-ranking.d.ts.map +1 -1
  59. package/dist/search-ranking.js +19 -3
  60. package/dist/traceability/git-staged.d.ts.map +1 -1
  61. package/dist/traceability/git-staged.js +22 -6
  62. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  63. package/dist/traceability/symbol-extract.js +10 -4
  64. package/dist/traceability/temp-kb.d.ts +12 -0
  65. package/dist/traceability/temp-kb.d.ts.map +1 -1
  66. package/dist/traceability/temp-kb.js +42 -8
  67. package/dist/traceability/validate.d.ts.map +1 -1
  68. package/dist/traceability/validate.js +11 -2
  69. package/dist/utils/branch-resolver.d.ts +4 -0
  70. package/dist/utils/branch-resolver.d.ts.map +1 -1
  71. package/dist/utils/branch-resolver.js +29 -12
  72. package/dist/utils/config.d.ts.map +1 -1
  73. package/dist/utils/config.js +8 -2
  74. package/dist/utils/rule-registry.js +2 -2
  75. package/package.json +3 -9
  76. package/src/public/schemas/relationship.ts +1 -0
  77. package/src/schemas/entity.schema.json +22 -0
  78. package/src/schemas/relationship.schema.json +1 -0
@@ -1,4 +1,5 @@
1
1
  import { execSync } from "node:child_process";
2
+ import { isCliTraceOrDebugEnabled } from "../env.js";
2
3
  function runGit(cmd, exec) {
3
4
  try {
4
5
  return exec(cmd, { encoding: "utf8" });
@@ -49,7 +50,7 @@ const SUPPORTED_EXT = new Set([
49
50
  const SUPPORTED_MANIFEST = new Set(["symbols.yaml", "symbols.yml"]);
50
51
  const ENTITY_MARKDOWN_DIRS = ["/requirements/", "/scenarios/", "/tests/"];
51
52
  function shouldLogTraceDebug() {
52
- return Boolean(process.env.KIBI_TRACE || process.env.KIBI_DEBUG);
53
+ return isCliTraceOrDebugEnabled();
53
54
  }
54
55
  function escapePath(p) {
55
56
  return p.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -83,7 +84,9 @@ function isManifestFile(p) {
83
84
  /**
84
85
  * Parse unified diff hunks (new-file coordinates) from git diff output
85
86
  */
86
- export function parseHunksFromDiff(diffText, isNewFile = false) {
87
+ export function parseHunksFromDiff(
88
+ // implements REQ-014
89
+ diffText, isNewFile = false) {
87
90
  const ranges = [];
88
91
  if (!diffText)
89
92
  return ranges;
@@ -93,7 +96,10 @@ export function parseHunksFromDiff(diffText, isNewFile = false) {
93
96
  m = regex.exec(diffText);
94
97
  if (!m)
95
98
  break;
96
- const c = Number.parseInt(m[1], 10);
99
+ const startLine = m[1];
100
+ if (!startLine)
101
+ continue;
102
+ const c = Number.parseInt(startLine, 10);
97
103
  const d = m[2] ? Number.parseInt(m[2], 10) : 1;
98
104
  if (d > 0) {
99
105
  ranges.push({ start: c, end: c + d - 1 });
@@ -135,8 +141,12 @@ export function getStagedFiles(exec = execSync) {
135
141
  let oldPath;
136
142
  if (status === "R") {
137
143
  if (entry.parts.length >= 2) {
138
- oldPath = entry.parts[0];
139
- path = entry.parts[1];
144
+ const previousPath = entry.parts[0];
145
+ const renamedPath = entry.parts[1];
146
+ if (previousPath !== undefined && renamedPath !== undefined) {
147
+ oldPath = previousPath;
148
+ path = renamedPath;
149
+ }
140
150
  }
141
151
  }
142
152
  if (!hasSupportedExt(path) &&
@@ -183,7 +193,13 @@ export function getStagedFiles(exec = execSync) {
183
193
  r.end = Math.max(1, lines.length);
184
194
  }
185
195
  }
186
- results.push({ path, status, oldPath, hunkRanges, content });
196
+ results.push({
197
+ path,
198
+ status,
199
+ ...(oldPath !== undefined ? { oldPath } : {}),
200
+ hunkRanges,
201
+ content,
202
+ });
187
203
  }
188
204
  return results;
189
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAM7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IAC7D,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE9D,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG5E;AAyLD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CA+JnB"}
1
+ {"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,KAAK,wBAAwB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAU7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IAC7D,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,wBAAwB,EAAE,CAAC;CAC5C;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AAE9D,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG5E;AA2LD,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CA+JnB"}
@@ -1,8 +1,12 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Project, ScriptKind } from "ts-morph";
3
3
  import { extractFromManifest } from "../extractors/manifest.js";
4
- const TRACEABILITY_RELATIONSHIP_TYPES = new Set(["implements", "covered_by"]);
5
- const REQUIREMENT_ID_PATTERN = /^[A-Z][A-Z0-9\-_]*$/;
4
+ const TRACEABILITY_RELATIONSHIP_TYPES = new Set([
5
+ "implements",
6
+ "covered_by",
7
+ "executable_for",
8
+ ]);
9
+ const REQUIREMENT_ID_PATTERN = /^[A-Za-z][A-Za-z0-9\-_]*$/;
6
10
  const LOCAL_MANIFEST_NAMES = ["symbols.yaml", "symbols.yml"];
7
11
  const MANIFEST_SENTINEL_PREFIX = "__manifest__:";
8
12
  export function createManifestLookupSentinelKey(manifestPath) {
@@ -85,7 +89,7 @@ function buildSymbolResult(stagedFile, name, kind, span, inlineReqLinks, manifes
85
89
  },
86
90
  hunkRanges: intersectingHunks(span.startLine, span.endLine, stagedFile.hunkRanges),
87
91
  reqLinks: mergedReqLinks,
88
- relationships,
92
+ ...(relationships !== undefined ? { relationships } : {}),
89
93
  };
90
94
  }
91
95
  // Simple in-memory cache keyed by blob sha with 30s TTL
@@ -111,7 +115,7 @@ function parseReqDirectives(text) {
111
115
  // look for lines containing implements REQ-123 or implements: REQ-1, REQ-2
112
116
  // Stop at end-of-line and only accept IDs starting with an uppercase letter
113
117
  // to avoid capturing tokens like `export`, `function`, etc.
114
- const REQ_ID = "[A-Z][A-Z0-9\\-_]*";
118
+ const REQ_ID = "[A-Za-z][A-Za-z0-9\\-_]*";
115
119
  const regex = new RegExp(`implements\\s*:?\\s*(${REQ_ID}(?:\\s*,\\s*${REQ_ID})*)\\s*$`, "gim");
116
120
  const reqs = new Set();
117
121
  let m;
@@ -120,6 +124,8 @@ function parseReqDirectives(text) {
120
124
  if (!m)
121
125
  break;
122
126
  const list = m[1];
127
+ if (!list)
128
+ continue;
123
129
  for (const part of list.split(/[,\s]+/)) {
124
130
  const p = part.trim();
125
131
  if (!p)
@@ -7,6 +7,18 @@ export interface TempKbContext {
7
7
  overlayPath: string;
8
8
  prolog: PrologProcess;
9
9
  }
10
+ /**
11
+ * Reset module state - used by tests to clear state between test runs.
12
+ * This is necessary because module-level Maps/Sets persist across tests.
13
+ */
14
+ export declare function resetModuleState(): void;
15
+ /**
16
+ * Override the PrologProcess factory — used by tests to inject the real constructor
17
+ * when mock.module() has replaced the module-level binding.
18
+ */
19
+ export declare function _setPrologFactory(factory: (opts: {
20
+ timeout: number;
21
+ }) => PrologProcess): void;
10
22
  declare function consultOverlay(ctx: TempKbContext): Promise<void>;
11
23
  export { consultOverlay };
12
24
  export declare function projectStagedEntities(prolog: PrologProcess, results: ExtractionResult[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"temp-kb.d.ts","sourceRoot":"","sources":["../../src/traceability/temp-kb.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AA4ID,iBAAe,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/D;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAG1B,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA2C7E;AAGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAkBrE;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BlE"}
1
+ {"version":3,"file":"temp-kb.d.ts","sourceRoot":"","sources":["../../src/traceability/temp-kb.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CASvC;AAOD;;;GAGG;AACH,wBAAgB,iBAAiB,CAE/B,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,aAAa,GACpD,IAAI,CAEN;AA4ID,iBAAe,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/D;AAED,OAAO,EAAE,cAAc,EAAE,CAAC;AAG1B,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6C7E;AAGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAkBrE;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BlE"}
@@ -2,11 +2,39 @@ import { existsSync } from "node:fs";
2
2
  import { cp, mkdir, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import path from "node:path";
5
+ import { isCliTraceOrDebugEnabled } from "../env.js";
5
6
  import { PrologProcess } from "../prolog.js";
6
7
  import { toPrologAtom, toPrologString } from "../prolog/codec.js";
7
8
  const prologByTempDir = new Map();
8
9
  const cleanupByTempDir = new Map();
9
10
  const cleanedTempDirs = new Set();
11
+ /**
12
+ * Reset module state - used by tests to clear state between test runs.
13
+ * This is necessary because module-level Maps/Sets persist across tests.
14
+ */
15
+ export function resetModuleState() {
16
+ // implements REQ-014
17
+ // Terminate all tracked prolog processes
18
+ for (const prolog of prologByTempDir.values()) {
19
+ void prolog.terminate().catch(() => { });
20
+ }
21
+ prologByTempDir.clear();
22
+ cleanupByTempDir.clear();
23
+ cleanedTempDirs.clear();
24
+ }
25
+ // Factory function for creating PrologProcess instances.
26
+ // Default uses the imported PrologProcess. Tests can override via _setPrologFactory
27
+ // to bypass mock.module() pollution from other test files.
28
+ let _createProlog = (opts) => new PrologProcess(opts);
29
+ /**
30
+ * Override the PrologProcess factory — used by tests to inject the real constructor
31
+ * when mock.module() has replaced the module-level binding.
32
+ */
33
+ export function _setPrologFactory(
34
+ // implements REQ-014
35
+ factory) {
36
+ _createProlog = factory;
37
+ }
10
38
  const FACT_ATOM_FIELDS = new Set([
11
39
  "fact_kind",
12
40
  "operator",
@@ -26,7 +54,7 @@ const FACT_STRING_FIELDS = new Set([
26
54
  const FACT_NUMBER_FIELDS = new Set(["value_int", "value_number"]);
27
55
  const FACT_BOOLEAN_FIELDS = new Set(["value_bool", "closed_world"]);
28
56
  function isTraceEnabled() {
29
- return Boolean(process.env.KIBI_TRACE || process.env.KIBI_DEBUG);
57
+ return isCliTraceOrDebugEnabled();
30
58
  }
31
59
  function trace(message) {
32
60
  if (isTraceEnabled()) {
@@ -37,23 +65,27 @@ function trace(message) {
37
65
  function escapePrologAtom(value) {
38
66
  return `'${value.replace(/'/g, "''")}'`;
39
67
  }
68
+ function getEntityField(entity, field) {
69
+ // ExtractedEntity declares all fact fields as optional properties, so indexing
70
+ // via keyof is safe. The cast is confined to this single helper.
71
+ return entity[field];
72
+ }
40
73
  function serializeTypedFactFields(entity) {
41
74
  const fields = [];
42
- const entityRecord = entity;
43
75
  for (const field of FACT_STRING_FIELDS) {
44
- const value = entityRecord[field];
76
+ const value = getEntityField(entity, field);
45
77
  if (value !== undefined && value !== null) {
46
78
  fields.push(`${field}=${toPrologString(String(value))}`);
47
79
  }
48
80
  }
49
81
  for (const field of FACT_ATOM_FIELDS) {
50
- const value = entityRecord[field];
82
+ const value = getEntityField(entity, field);
51
83
  if (value !== undefined && value !== null) {
52
84
  fields.push(`${field}=${toPrologAtom(String(value))}`);
53
85
  }
54
86
  }
55
87
  for (const field of FACT_NUMBER_FIELDS) {
56
- const value = entityRecord[field];
88
+ const value = getEntityField(entity, field);
57
89
  if (value !== undefined && value !== null && typeof value === "number") {
58
90
  if (field === "value_int" && !Number.isInteger(value)) {
59
91
  continue;
@@ -62,7 +94,7 @@ function serializeTypedFactFields(entity) {
62
94
  }
63
95
  }
64
96
  for (const field of FACT_BOOLEAN_FIELDS) {
65
- const value = entityRecord[field];
97
+ const value = getEntityField(entity, field);
66
98
  if (value !== undefined && value !== null && typeof value === "boolean") {
67
99
  fields.push(`${field}=${value}`);
68
100
  }
@@ -157,10 +189,12 @@ export async function projectStagedEntities(prolog, results) {
157
189
  }
158
190
  }
159
191
  export async function createTempKb(baseKbPath) {
192
+ // implements REQ-014
160
193
  if (!existsSync(baseKbPath)) {
161
194
  throw new Error(`Base KB path does not exist: ${baseKbPath}`);
162
195
  }
163
- const tempDir = path.join(tmpdir(), `kibi-precommit-${process.pid}-${Date.now()}`);
196
+ // Use crypto.randomUUID() for uniqueness across concurrent calls
197
+ const tempDir = path.join(tmpdir(), `kibi-precommit-${process.pid}-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`);
164
198
  const kbPath = path.join(tempDir, "kb");
165
199
  const overlayPath = path.join(tempDir, "changed_symbols.pl");
166
200
  trace(`creating temp KB directory ${tempDir}`);
@@ -168,7 +202,7 @@ export async function createTempKb(baseKbPath) {
168
202
  trace(`copying base KB ${baseKbPath} -> ${kbPath}`);
169
203
  await cp(baseKbPath, kbPath, { recursive: true });
170
204
  await writeFile(overlayPath, "", "utf8");
171
- const prolog = new PrologProcess({ timeout: 120000 });
205
+ const prolog = _createProlog({ timeout: 120000 });
172
206
  await prolog.start();
173
207
  prologByTempDir.set(tempDir, prolog);
174
208
  // ctx includes prolog so callers can use it directly
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/traceability/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO1C;AAED,iBAAS,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CA0B/C;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA8CnD;AAED,iBAAS,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAuBzD;AAGD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,SAAS,EAAE,CAAC,CAqCtB;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,CAiBhE;AAGD,eAAO,MAAM,QAAQ;;;;;CAKpB,CAAC"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/traceability/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,iBAAS,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO1C;AAED,iBAAS,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CA0B/C;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA8CnD;AAED,iBAAS,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAuBzD;AAGD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,SAAS,EAAE,CAAC,CAgDtB;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,CAkBhE;AAGD,eAAO,MAAM,QAAQ;;;;;CAKpB,CAAC"}
@@ -114,6 +114,14 @@ export async function validateStagedSymbols(options) {
114
114
  if (row.length < 6)
115
115
  continue;
116
116
  const [sym, count, file, line, col, name] = row;
117
+ if (sym === undefined ||
118
+ count === undefined ||
119
+ file === undefined ||
120
+ line === undefined ||
121
+ col === undefined ||
122
+ name === undefined) {
123
+ continue;
124
+ }
117
125
  const symbolId = unquoteAtom(sym);
118
126
  const currentLinks = Number(count.replace(/[^0-9]/g, "")) || 0;
119
127
  const requiredLinks = minLinks;
@@ -134,6 +142,7 @@ export async function validateStagedSymbols(options) {
134
142
  return violations;
135
143
  }
136
144
  export function formatViolations(violations) {
145
+ // implements REQ-014
137
146
  if (!violations || violations.length === 0)
138
147
  return "";
139
148
  const total = violations.length;
@@ -143,8 +152,8 @@ export function formatViolations(violations) {
143
152
  for (const v of violations) {
144
153
  const loc = `${v.file}:${v.line}`;
145
154
  const name = `${v.name}()`;
146
- // Suggest adding requirement links via `implements:` using requirement IDs (e.g. REQ-001)
147
- const suggestion = "Add one or more requirement links, for example: implements: REQ-001";
155
+ // Suggest adding requirement links with role-specific guidance
156
+ const suggestion = "Add ownership: implements: REQ-001 (production code), use covered_by for production coverage, or executable_for for executable test code";
148
157
  lines.push(`${loc} ${name} -> ${suggestion}`);
149
158
  }
150
159
  return lines.join("\n");
@@ -7,6 +7,10 @@ export type BranchResolutionError = {
7
7
  };
8
8
  export type BranchResolutionResult = BranchResolutionSuccess | BranchResolutionError;
9
9
  export type BranchErrorCode = "ENV_OVERRIDE" | "DETACHED_HEAD" | "UNBORN_BRANCH" | "GIT_NOT_AVAILABLE" | "NOT_A_GIT_REPO" | "UNKNOWN_ERROR";
10
+ export interface BranchResolverDeps {
11
+ execSync: typeof import("node:child_process").execSync;
12
+ }
13
+ export declare function _setBranchResolverDepsForTests(deps: Partial<BranchResolverDeps>): void;
10
14
  /**
11
15
  * Resolve the active branch according to ADR-012 precedence:
12
16
  * 1. KIBI_BRANCH env var (if set)
@@ -1 +1 @@
1
- {"version":3,"file":"branch-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/branch-resolver.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,uBAAuB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,qBAAqB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,sBAAsB,GAC9B,uBAAuB,GACvB,qBAAqB,CAAC;AAE1B,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,gBAAgB,GAChB,eAAe,CAAC;AA8BpB;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,GAAE,MAAsB,GACpC,sBAAsB,CAmHxB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,aAAa,GAAE,MAAsB,GAAG,OAAO,CAa7E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,GACZ,MAAM,CAuBR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuBvD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,IAAI,CAUN;AA6BD;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAMtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,GAAE,MAAsB,EAC3B,MAAM,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqCtD"}
1
+ {"version":3,"file":"branch-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/branch-resolver.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,uBAAuB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,qBAAqB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAC7E,MAAM,MAAM,sBAAsB,GAC9B,uBAAuB,GACvB,qBAAqB,CAAC;AAE1B,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,gBAAgB,GAChB,eAAe,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,cAAc,oBAAoB,EAAE,QAAQ,CAAC;CACxD;AAID,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAChC,IAAI,CAGN;AA8BD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,GAAE,MAAsB,GACpC,sBAAsB,CAwHxB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,aAAa,GAAE,MAAsB,GAAG,OAAO,CAgB7E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,GACZ,MAAM,CAuBR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAuBvD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,IAAI,CAUN;AA6BD;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAMtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,GAAE,MAAsB,EAC3B,MAAM,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAwCtD"}
@@ -15,9 +15,15 @@
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
- import { execSync } from "node:child_process";
18
+ import { execSync as rawExecSync } from "node:child_process";
19
19
  import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync, } from "node:fs";
20
20
  import * as path from "node:path";
21
+ import { getBranchOverride } from "../env.js";
22
+ const defaultDeps = { execSync: rawExecSync };
23
+ export function _setBranchResolverDepsForTests(deps) {
24
+ // implements REQ-008
25
+ defaultDeps.execSync = deps.execSync ?? rawExecSync;
26
+ }
21
27
  // Files to exclude when copying branch snapshots (volatile artifacts)
22
28
  const VOLATILE_ARTIFACTS = new Set([
23
29
  "sync-cache.json",
@@ -53,8 +59,9 @@ function isVolatileArtifact(fileName) {
53
59
  * @returns BranchResolutionResult with either the branch name or an error
54
60
  */
55
61
  export function resolveActiveBranch(workspaceRoot = process.cwd()) {
62
+ // implements REQ-008
56
63
  // 1. Check KIBI_BRANCH env var first (highest precedence)
57
- const envBranch = process.env.KIBI_BRANCH?.trim();
64
+ const envBranch = getBranchOverride();
58
65
  if (envBranch) {
59
66
  // Validate the env branch name
60
67
  if (!isValidBranchName(envBranch)) {
@@ -67,12 +74,14 @@ export function resolveActiveBranch(workspaceRoot = process.cwd()) {
67
74
  }
68
75
  // 2. Try to get the current git branch
69
76
  try {
70
- const branch = execSync("git branch --show-current", {
77
+ const branch = defaultDeps
78
+ .execSync("git branch --show-current", {
71
79
  cwd: workspaceRoot,
72
80
  encoding: "utf8",
73
81
  timeout: 5000,
74
82
  stdio: ["pipe", "pipe", "pipe"],
75
- }).trim();
83
+ })
84
+ .trim();
76
85
  if (!branch) {
77
86
  // Empty result means detached HEAD
78
87
  return {
@@ -94,12 +103,14 @@ export function resolveActiveBranch(workspaceRoot = process.cwd()) {
94
103
  catch (error) {
95
104
  // Try alternative: git rev-parse --abbrev-ref HEAD
96
105
  try {
97
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
106
+ const branch = defaultDeps
107
+ .execSync("git rev-parse --abbrev-ref HEAD", {
98
108
  cwd: workspaceRoot,
99
109
  encoding: "utf8",
100
110
  timeout: 5000,
101
111
  stdio: ["pipe", "pipe", "pipe"],
102
- }).trim();
112
+ })
113
+ .trim();
103
114
  if (branch === "HEAD") {
104
115
  return {
105
116
  error: getBranchDiagnostic(undefined, "Git is in detached HEAD state"),
@@ -122,7 +133,7 @@ export function resolveActiveBranch(workspaceRoot = process.cwd()) {
122
133
  const normalizedBranch = branch === "master" ? "main" : branch;
123
134
  return { branch: normalizedBranch };
124
135
  }
125
- catch (fallbackError) {
136
+ catch {
126
137
  // Determine specific error type
127
138
  const errorMessage = error instanceof Error ? error.message : String(error);
128
139
  if (errorMessage.includes("not a git repository")) {
@@ -152,13 +163,16 @@ export function resolveActiveBranch(workspaceRoot = process.cwd()) {
152
163
  * @returns true if in detached HEAD, false otherwise
153
164
  */
154
165
  export function isDetachedHead(workspaceRoot = process.cwd()) {
166
+ // implements REQ-008
155
167
  try {
156
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
168
+ const branch = defaultDeps
169
+ .execSync("git rev-parse --abbrev-ref HEAD", {
157
170
  cwd: workspaceRoot,
158
171
  encoding: "utf8",
159
172
  timeout: 5000,
160
173
  stdio: ["pipe", "pipe", "pipe"],
161
- }).trim();
174
+ })
175
+ .trim();
162
176
  return branch === "HEAD";
163
177
  }
164
178
  catch {
@@ -278,6 +292,7 @@ export function getVolatileArtifactPatterns() {
278
292
  * @returns BranchResolutionResult with either the branch name or an error
279
293
  */
280
294
  export function resolveDefaultBranch(cwd = process.cwd(), config) {
295
+ // implements REQ-012
281
296
  // 1. Check config.defaultBranch first (highest precedence)
282
297
  const configuredBranch = config?.defaultBranch?.trim();
283
298
  if (configuredBranch) {
@@ -292,17 +307,19 @@ export function resolveDefaultBranch(cwd = process.cwd(), config) {
292
307
  }
293
308
  // 2. Try to get the remote default branch from origin/HEAD
294
309
  try {
295
- const remoteHead = execSync("git symbolic-ref refs/remotes/origin/HEAD", {
310
+ const remoteHead = defaultDeps
311
+ .execSync("git symbolic-ref refs/remotes/origin/HEAD", {
296
312
  cwd,
297
313
  encoding: "utf8",
298
314
  timeout: 5000,
299
315
  stdio: ["pipe", "pipe", "pipe"],
300
- }).trim();
316
+ })
317
+ .trim();
301
318
  // Parse refs/remotes/origin/BRANCH_NAME -> BRANCH_NAME
302
319
  const match = remoteHead.match(/^refs\/remotes\/origin\/(.+)$/);
303
320
  if (match) {
304
321
  const branch = match[1];
305
- if (isValidBranchName(branch)) {
322
+ if (branch && isValidBranchName(branch)) {
306
323
  return { branch };
307
324
  }
308
325
  }
@@ -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;AAE5B;;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;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAcxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAShC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAiChE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAiCpE"}
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;AAE5B;;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;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAcxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAShC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAoChE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAoCpE"}
@@ -56,6 +56,7 @@ export const DEFAULT_SYNC_PATHS = {
56
56
  * @returns The merged configuration (defaults + user config)
57
57
  */
58
58
  export function loadConfig(cwd = process.cwd()) {
59
+ // implements REQ-003
59
60
  const configPath = path.join(cwd, ".kb/config.json");
60
61
  let userConfig = {};
61
62
  if (existsSync(configPath)) {
@@ -73,7 +74,9 @@ export function loadConfig(cwd = process.cwd()) {
73
74
  ...DEFAULT_CONFIG.paths,
74
75
  ...userConfig.paths,
75
76
  },
76
- defaultBranch: userConfig.defaultBranch,
77
+ ...(userConfig.defaultBranch !== undefined
78
+ ? { defaultBranch: userConfig.defaultBranch }
79
+ : {}),
77
80
  checks: userConfig.checks
78
81
  ? {
79
82
  rules: {
@@ -97,6 +100,7 @@ export function loadConfig(cwd = process.cwd()) {
97
100
  * @returns The merged configuration with sync-compatible paths
98
101
  */
99
102
  export function loadSyncConfig(cwd = process.cwd()) {
103
+ // implements REQ-003
100
104
  const configPath = path.join(cwd, ".kb/config.json");
101
105
  let userConfig = {};
102
106
  if (existsSync(configPath)) {
@@ -114,7 +118,9 @@ export function loadSyncConfig(cwd = process.cwd()) {
114
118
  ...DEFAULT_SYNC_PATHS,
115
119
  ...userConfig.paths,
116
120
  },
117
- defaultBranch: userConfig.defaultBranch,
121
+ ...(userConfig.defaultBranch !== undefined
122
+ ? { defaultBranch: userConfig.defaultBranch }
123
+ : {}),
118
124
  checks: userConfig.checks
119
125
  ? {
120
126
  rules: {
@@ -29,13 +29,13 @@ export const RULES = [
29
29
  },
30
30
  {
31
31
  name: "symbol-coverage",
32
- description: "Symbols should be traceable to requirements via transitive implements",
32
+ description: "Production symbols need qualifying coverage via covered_by plus a canonical requirement/scenario test path",
33
33
  defaultEnabled: true,
34
34
  category: "coverage",
35
35
  },
36
36
  {
37
37
  name: "symbol-traceability",
38
- description: "Source symbols must have direct implements links to requirements; optionally constrained_by ADR",
38
+ description: "Production symbols must directly implement requirements for ownership; covered_by is coverage only, executable_for is test identity only, and ADR constraints are optional unless configured",
39
39
  defaultEnabled: true,
40
40
  category: "traceability",
41
41
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -11,13 +11,7 @@
11
11
  "bin": {
12
12
  "kibi": "bin/kibi"
13
13
  },
14
- "files": [
15
- "dist",
16
- "bin",
17
- "schema",
18
- "src/schemas",
19
- "src/public"
20
- ],
14
+ "files": ["dist", "bin", "schema", "src/schemas", "src/public"],
21
15
  "scripts": {
22
16
  "build": "tsc -p tsconfig.json",
23
17
  "prepack": "npm run build"
@@ -76,7 +70,7 @@
76
70
  "fast-glob": "^3.2.12",
77
71
  "gray-matter": "^4.0.3",
78
72
  "js-yaml": "^4.1.0",
79
- "kibi-core": "^0.4.1",
73
+ "kibi-core": "^0.5.0",
80
74
  "ts-morph": "^23.0.0"
81
75
  },
82
76
  "devDependencies": {
@@ -30,6 +30,7 @@ const relationshipSchema = {
30
30
  "validates",
31
31
  "implements",
32
32
  "covered_by",
33
+ "executable_for",
33
34
  "constrained_by",
34
35
  "constrains",
35
36
  "requires_property",
@@ -39,6 +39,14 @@
39
39
  "links": { "type": "array", "items": { "type": "string" } },
40
40
  "text_ref": { "type": "string" },
41
41
  "sourceFile": { "type": "string" },
42
+ "verification_scope": {
43
+ "type": "string",
44
+ "enum": ["unit", "integration", "end_to_end"]
45
+ },
46
+ "verification_perspective": {
47
+ "type": "string",
48
+ "enum": ["internal", "consumer"]
49
+ },
42
50
  "type": {
43
51
  "type": "string",
44
52
  "enum": [
@@ -88,6 +96,20 @@
88
96
  "type"
89
97
  ],
90
98
  "allOf": [
99
+ {
100
+ "if": {
101
+ "properties": { "type": { "const": "test" } }
102
+ },
103
+ "then": {},
104
+ "else": {
105
+ "not": {
106
+ "anyOf": [
107
+ { "required": ["verification_scope"] },
108
+ { "required": ["verification_perspective"] }
109
+ ]
110
+ }
111
+ }
112
+ },
91
113
  {
92
114
  "if": {
93
115
  "properties": { "type": { "const": "fact" } }
@@ -12,6 +12,7 @@
12
12
  "validates",
13
13
  "implements",
14
14
  "covered_by",
15
+ "executable_for",
15
16
  "constrained_by",
16
17
  "constrains",
17
18
  "requires_property",