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.
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +3 -2
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +64 -53
- package/dist/commands/discovery-shared.d.ts +12 -5
- package/dist/commands/discovery-shared.d.ts.map +1 -1
- package/dist/commands/discovery-shared.js +21 -13
- package/dist/commands/doctor.js +9 -1
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +2 -3
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +15 -5
- package/dist/commands/search.js +1 -1
- package/dist/commands/sync/cache.d.ts +14 -4
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +36 -14
- package/dist/commands/sync/extraction.d.ts.map +1 -1
- package/dist/commands/sync/extraction.js +4 -9
- package/dist/commands/sync/manifest.d.ts +14 -3
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +29 -10
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +9 -5
- package/dist/commands/sync/staging.d.ts +19 -3
- package/dist/commands/sync/staging.d.ts.map +1 -1
- package/dist/commands/sync/staging.js +50 -27
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +16 -20
- package/dist/diagnostics.d.ts +1 -10
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +6 -12
- package/dist/env.d.ts +7 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +41 -0
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +17 -15
- package/dist/extractors/markdown.d.ts +2 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +124 -30
- package/dist/extractors/relationships.d.ts.map +1 -1
- package/dist/extractors/relationships.js +10 -4
- package/dist/extractors/symbols-coordinator.d.ts +5 -2
- package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/extractors/symbols-coordinator.js +5 -2
- package/dist/extractors/symbols-ts.d.ts.map +1 -1
- package/dist/extractors/symbols-ts.js +56 -10
- package/dist/prolog/codec.d.ts +0 -43
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +68 -74
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +39 -25
- package/dist/public/schemas/relationship.d.ts.map +1 -1
- package/dist/public/schemas/relationship.js +1 -0
- package/dist/query/service.d.ts +9 -0
- package/dist/query/service.d.ts.map +1 -1
- package/dist/query/service.js +27 -10
- package/dist/schemas/entity.schema.json +22 -0
- package/dist/search-ranking.d.ts.map +1 -1
- package/dist/search-ranking.js +19 -3
- package/dist/traceability/git-staged.d.ts.map +1 -1
- package/dist/traceability/git-staged.js +22 -6
- package/dist/traceability/symbol-extract.d.ts.map +1 -1
- package/dist/traceability/symbol-extract.js +10 -4
- package/dist/traceability/temp-kb.d.ts +12 -0
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +42 -8
- package/dist/traceability/validate.d.ts.map +1 -1
- package/dist/traceability/validate.js +11 -2
- package/dist/utils/branch-resolver.d.ts +4 -0
- package/dist/utils/branch-resolver.d.ts.map +1 -1
- package/dist/utils/branch-resolver.js +29 -12
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +8 -2
- package/dist/utils/rule-registry.js +2 -2
- package/package.json +3 -9
- package/src/public/schemas/relationship.ts +1 -0
- package/src/schemas/entity.schema.json +22 -0
- 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
|
|
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(
|
|
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
|
|
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
|
-
|
|
139
|
-
|
|
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({
|
|
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;
|
|
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([
|
|
5
|
-
|
|
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-
|
|
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":"
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
147
|
-
const suggestion = "Add
|
|
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":"
|
|
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 =
|
|
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 =
|
|
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
|
-
})
|
|
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 =
|
|
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
|
-
})
|
|
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
|
|
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 =
|
|
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
|
-
})
|
|
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 =
|
|
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
|
-
})
|
|
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,
|
|
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"}
|
package/dist/utils/config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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: "
|
|
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: "
|
|
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.
|
|
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.
|
|
73
|
+
"kibi-core": "^0.5.0",
|
|
80
74
|
"ts-morph": "^23.0.0"
|
|
81
75
|
},
|
|
82
76
|
"devDependencies": {
|
|
@@ -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" } }
|