kibi-cli 0.1.4 → 0.1.6

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.
@@ -0,0 +1,150 @@
1
+ function unquoteAtom(v) {
2
+ // remove surrounding single quotes and unescape doubled quotes
3
+ v = v.trim();
4
+ if (v.startsWith("'") && v.endsWith("'")) {
5
+ v = v.slice(1, -1).replace(/''/g, "'");
6
+ }
7
+ return v;
8
+ }
9
+ function splitTopLevelComma(s) {
10
+ const parts = [];
11
+ let cur = "";
12
+ let inQuote = false;
13
+ for (let i = 0; i < s.length; i++) {
14
+ const ch = s[i];
15
+ if (ch === "'") {
16
+ // handle doubled '' inside atom
17
+ if (inQuote && s[i + 1] === "'") {
18
+ cur += "'";
19
+ i++;
20
+ continue;
21
+ }
22
+ inQuote = !inQuote;
23
+ cur += ch;
24
+ continue;
25
+ }
26
+ if (ch === "," && !inQuote) {
27
+ parts.push(cur.trim());
28
+ cur = "";
29
+ continue;
30
+ }
31
+ cur += ch;
32
+ }
33
+ if (cur !== "")
34
+ parts.push(cur.trim());
35
+ return parts;
36
+ }
37
+ function splitTopLevelLists(value) {
38
+ const lists = [];
39
+ let cur = "";
40
+ let inQuote = false;
41
+ let depth = 0;
42
+ for (let i = 0; i < value.length; i++) {
43
+ const ch = value[i];
44
+ if (ch === "'") {
45
+ // handle doubled '' inside atom
46
+ if (inQuote && i + 1 < value.length && value[i + 1] === "'") {
47
+ cur += "'";
48
+ i++;
49
+ continue;
50
+ }
51
+ inQuote = !inQuote;
52
+ cur += ch;
53
+ continue;
54
+ }
55
+ if (!inQuote) {
56
+ if (ch === "[") {
57
+ depth++;
58
+ cur += ch;
59
+ continue;
60
+ }
61
+ if (ch === "]") {
62
+ depth--;
63
+ cur += ch;
64
+ if (depth === 0) {
65
+ lists.push(cur.trim());
66
+ cur = "";
67
+ }
68
+ continue;
69
+ }
70
+ }
71
+ // Only collect characters that are inside a list; commas and
72
+ // whitespace between top-level lists are ignored.
73
+ if (depth > 0) {
74
+ cur += ch;
75
+ }
76
+ }
77
+ return lists;
78
+ }
79
+ function parsePrologListOfLists(value) {
80
+ // value expected like: [[a,1,'file.pl',10,0,'name'],[...]] or []
81
+ const out = [];
82
+ const trimmed = value.trim();
83
+ if (trimmed === "[]" || trimmed === "")
84
+ return out;
85
+ // Strip a single outer list wrapper, if present.
86
+ const withoutOuter = trimmed.startsWith("[") && trimmed.endsWith("]")
87
+ ? trimmed.slice(1, -1)
88
+ : trimmed;
89
+ const rowStrings = splitTopLevelLists(withoutOuter);
90
+ for (const row of rowStrings) {
91
+ const rowTrimmed = row.trim();
92
+ if (!rowTrimmed.startsWith("[") || !rowTrimmed.endsWith("]")) {
93
+ continue;
94
+ }
95
+ const inner = rowTrimmed.slice(1, -1);
96
+ const cols = splitTopLevelComma(inner);
97
+ out.push(cols.map((c) => c.trim()));
98
+ }
99
+ return out;
100
+ }
101
+ export async function validateStagedSymbols(options) {
102
+ const { minLinks, prolog } = options;
103
+ const goal = `findall([Sym,Count,File,Line,Col,Name], changed_symbol_violation(Sym, ${minLinks}, Count, File, Line, Col, Name), Rows)`;
104
+ const res = await prolog.query(goal);
105
+ if (!res.success) {
106
+ throw new Error(`Prolog query failed: ${res.error || "unknown error"}`);
107
+ }
108
+ const rowsRaw = res.bindings["Rows"] ?? "[]";
109
+ const lists = parsePrologListOfLists(rowsRaw);
110
+ const violations = [];
111
+ for (const row of lists) {
112
+ // row: [Sym,Count,File,Line,Col,Name]
113
+ if (row.length < 6)
114
+ continue;
115
+ const [sym, count, file, line, col, name] = row;
116
+ const symbolId = unquoteAtom(sym);
117
+ const currentLinks = Number(count.replace(/[^0-9]/g, "")) || 0;
118
+ const requiredLinks = minLinks;
119
+ const fileStr = unquoteAtom(file);
120
+ const nameStr = unquoteAtom(name);
121
+ const lineNum = Number(line.replace(/[^0-9]/g, "")) || 0;
122
+ const colNum = Number(col.replace(/[^0-9]/g, "")) || 0;
123
+ violations.push({
124
+ symbolId,
125
+ name: nameStr,
126
+ file: fileStr,
127
+ line: lineNum,
128
+ column: colNum,
129
+ currentLinks,
130
+ requiredLinks,
131
+ });
132
+ }
133
+ return violations;
134
+ }
135
+ export function formatViolations(violations) {
136
+ if (!violations || violations.length === 0)
137
+ return "";
138
+ const total = violations.length;
139
+ const minLinks = violations[0]?.requiredLinks ?? 0;
140
+ const lines = [];
141
+ lines.push(`Traceability failed: ${total}/${total} staged symbols unlinked (minLinks=${minLinks})`);
142
+ for (const v of violations) {
143
+ const loc = `${v.file}:${v.line}`;
144
+ const name = `${v.name}()`;
145
+ // Suggest adding requirement links via `implements:` using requirement IDs (e.g. REQ-001)
146
+ const suggestion = "Add one or more requirement links, for example: implements: REQ-001";
147
+ lines.push(`${loc} ${name} -> ${suggestion}`);
148
+ }
149
+ return lines.join("\n");
150
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "main": "./dist/cli.js",
11
11
  "bin": {
12
- "kibi": "./bin/kibi"
12
+ "kibi": "bin/kibi"
13
13
  },
14
14
  "files": [
15
15
  "dist",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "build": "tsc -p tsconfig.json",
23
- "prepublishOnly": "npm run build"
23
+ "prepack": "npm run build"
24
24
  },
25
25
  "exports": {
26
26
  ".": {
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "types": "./dist/cli.d.ts",
48
48
  "dependencies": {
49
- "kibi-core": "^0.1.4",
50
49
  "ajv": "^8.12.0",
51
50
  "cli-table3": "^0.6.5",
52
51
  "commander": "^11.0.0",
53
52
  "fast-glob": "^3.2.12",
54
53
  "gray-matter": "^4.0.3",
55
54
  "js-yaml": "^4.1.0",
55
+ "kibi-core": "^0.1.5",
56
56
  "ts-morph": "^23.0.0"
57
57
  },
58
58
  "license": "AGPL-3.0-or-later",