kibi-cli 0.1.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 (75) hide show
  1. package/bin/kibi +19 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +117 -0
  5. package/dist/commands/branch.d.ts +3 -0
  6. package/dist/commands/branch.d.ts.map +1 -0
  7. package/dist/commands/branch.js +66 -0
  8. package/dist/commands/check.d.ts +12 -0
  9. package/dist/commands/check.d.ts.map +1 -0
  10. package/dist/commands/check.js +439 -0
  11. package/dist/commands/doctor.d.ts +2 -0
  12. package/dist/commands/doctor.d.ts.map +1 -0
  13. package/dist/commands/doctor.js +268 -0
  14. package/dist/commands/gc.d.ts +6 -0
  15. package/dist/commands/gc.d.ts.map +1 -0
  16. package/dist/commands/gc.js +117 -0
  17. package/dist/commands/init-helpers.d.ts +8 -0
  18. package/dist/commands/init-helpers.d.ts.map +1 -0
  19. package/dist/commands/init-helpers.js +150 -0
  20. package/dist/commands/init.d.ts +6 -0
  21. package/dist/commands/init.d.ts.map +1 -0
  22. package/dist/commands/init.js +85 -0
  23. package/dist/commands/query.d.ts +12 -0
  24. package/dist/commands/query.d.ts.map +1 -0
  25. package/dist/commands/query.js +469 -0
  26. package/dist/commands/sync.d.ts +7 -0
  27. package/dist/commands/sync.d.ts.map +1 -0
  28. package/dist/commands/sync.js +587 -0
  29. package/dist/extractors/manifest.d.ts +30 -0
  30. package/dist/extractors/manifest.d.ts.map +1 -0
  31. package/dist/extractors/manifest.js +122 -0
  32. package/dist/extractors/markdown.d.ts +39 -0
  33. package/dist/extractors/markdown.d.ts.map +1 -0
  34. package/dist/extractors/markdown.js +203 -0
  35. package/dist/extractors/symbols-coordinator.d.ts +4 -0
  36. package/dist/extractors/symbols-coordinator.d.ts.map +1 -0
  37. package/dist/extractors/symbols-coordinator.js +131 -0
  38. package/dist/extractors/symbols-ts.d.ts +21 -0
  39. package/dist/extractors/symbols-ts.d.ts.map +1 -0
  40. package/dist/extractors/symbols-ts.js +197 -0
  41. package/dist/prolog.d.ts +35 -0
  42. package/dist/prolog.d.ts.map +1 -0
  43. package/dist/prolog.js +328 -0
  44. package/dist/public/extractors/symbols-coordinator.d.ts +2 -0
  45. package/dist/public/extractors/symbols-coordinator.d.ts.map +1 -0
  46. package/dist/public/extractors/symbols-coordinator.js +46 -0
  47. package/dist/public/prolog/index.d.ts +2 -0
  48. package/dist/public/prolog/index.d.ts.map +1 -0
  49. package/dist/public/prolog/index.js +46 -0
  50. package/dist/public/schemas/entity.d.ts +58 -0
  51. package/dist/public/schemas/entity.d.ts.map +1 -0
  52. package/dist/public/schemas/entity.js +102 -0
  53. package/dist/public/schemas/relationship.d.ts +35 -0
  54. package/dist/public/schemas/relationship.d.ts.map +1 -0
  55. package/dist/public/schemas/relationship.js +81 -0
  56. package/dist/types/changeset.d.ts +22 -0
  57. package/dist/types/changeset.d.ts.map +1 -0
  58. package/dist/types/changeset.js +18 -0
  59. package/dist/types/entities.d.ts +40 -0
  60. package/dist/types/entities.d.ts.map +1 -0
  61. package/dist/types/entities.js +18 -0
  62. package/dist/types/relationships.d.ts +11 -0
  63. package/dist/types/relationships.d.ts.map +1 -0
  64. package/dist/types/relationships.js +18 -0
  65. package/package.json +57 -0
  66. package/schema/entities.pl +50 -0
  67. package/schema/relationships.pl +47 -0
  68. package/schema/validation.pl +49 -0
  69. package/src/public/extractors/symbols-coordinator.ts +50 -0
  70. package/src/public/prolog/index.ts +47 -0
  71. package/src/public/schemas/entity.ts +104 -0
  72. package/src/public/schemas/relationship.ts +83 -0
  73. package/src/schemas/changeset.schema.json +48 -0
  74. package/src/schemas/entity.schema.json +55 -0
  75. package/src/schemas/relationship.schema.json +34 -0
@@ -0,0 +1,122 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ /*
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
+ import { createHash } from "node:crypto";
46
+ import { readFileSync } from "node:fs";
47
+ import { load as parseYAML } from "js-yaml";
48
+ export class ManifestError extends Error {
49
+ filePath;
50
+ constructor(message, filePath) {
51
+ super(message);
52
+ this.filePath = filePath;
53
+ this.name = "ManifestError";
54
+ }
55
+ }
56
+ export function extractFromManifest(filePath) {
57
+ try {
58
+ const content = readFileSync(filePath, "utf8");
59
+ const manifest = parseYAML(content);
60
+ if (!manifest.symbols || !Array.isArray(manifest.symbols)) {
61
+ throw new ManifestError("No symbols array found in manifest", filePath);
62
+ }
63
+ return manifest.symbols.map((symbol) => {
64
+ if (!symbol.title) {
65
+ throw new ManifestError("Missing required field: title", filePath);
66
+ }
67
+ const id = symbol.id || generateId(filePath, symbol.title);
68
+ const relationships = extractRelationships(symbol.relationships || symbol.links || [], id);
69
+ return {
70
+ entity: {
71
+ id,
72
+ type: "symbol",
73
+ title: symbol.title,
74
+ status: symbol.status || "draft",
75
+ created_at: symbol.created_at || new Date().toISOString(),
76
+ updated_at: symbol.updated_at || new Date().toISOString(),
77
+ source: filePath,
78
+ tags: symbol.tags,
79
+ owner: symbol.owner,
80
+ priority: symbol.priority,
81
+ severity: symbol.severity,
82
+ links: symbol.links,
83
+ text_ref: symbol.text_ref,
84
+ },
85
+ relationships,
86
+ };
87
+ });
88
+ }
89
+ catch (error) {
90
+ if (error instanceof ManifestError) {
91
+ throw error;
92
+ }
93
+ if (error instanceof Error) {
94
+ throw new ManifestError(`Failed to parse manifest: ${error.message}`, filePath);
95
+ }
96
+ throw error;
97
+ }
98
+ }
99
+ function generateId(filePath, title) {
100
+ const hash = createHash("sha256");
101
+ hash.update(`${filePath}:${title}`);
102
+ return hash.digest("hex").substring(0, 16);
103
+ }
104
+ function extractRelationships(links, fromId) {
105
+ if (!Array.isArray(links))
106
+ return [];
107
+ return links.map((link) => {
108
+ if (typeof link === "string") {
109
+ return {
110
+ type: "relates_to",
111
+ from: fromId,
112
+ to: link,
113
+ };
114
+ }
115
+ const linkObj = link;
116
+ return {
117
+ type: linkObj.type || "relates_to",
118
+ from: fromId,
119
+ to: linkObj.target || linkObj.id || linkObj.to || "",
120
+ };
121
+ });
122
+ }
@@ -0,0 +1,39 @@
1
+ export interface ExtractedEntity {
2
+ id: string;
3
+ type: string;
4
+ title: string;
5
+ status: string;
6
+ created_at: string;
7
+ updated_at: string;
8
+ source: string;
9
+ tags?: string[];
10
+ owner?: string;
11
+ priority?: string;
12
+ severity?: string;
13
+ links?: unknown[];
14
+ text_ref?: string;
15
+ }
16
+ export interface ExtractedRelationship {
17
+ type: string;
18
+ from: string;
19
+ to: string;
20
+ }
21
+ export interface ExtractionResult {
22
+ entity: ExtractedEntity;
23
+ relationships: ExtractedRelationship[];
24
+ }
25
+ export declare class FrontmatterError extends Error {
26
+ filePath: string;
27
+ classification: string;
28
+ hint: string;
29
+ originalError?: string;
30
+ constructor(message: string, filePath: string, options?: {
31
+ classification?: string;
32
+ hint?: string;
33
+ originalError?: string;
34
+ });
35
+ toString(): string;
36
+ }
37
+ export declare function extractFromMarkdown(filePath: string): ExtractionResult;
38
+ export declare function inferTypeFromPath(filePath: string): string | null;
39
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/extractors/markdown.ts"],"names":[],"mappings":"AAiDA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,aAAa,EAAE,qBAAqB,EAAE,CAAC;CACxC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAOhC,QAAQ,EAAE,MAAM;IANlB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;gBAG5B,OAAO,EAAE,MAAM,EACR,QAAQ,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB;IASM,QAAQ;CAUlB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAqHtE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASjE"}
@@ -0,0 +1,203 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ /*
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
+ import { createHash } from "node:crypto";
46
+ import { readFileSync } from "node:fs";
47
+ import matter from "gray-matter";
48
+ export class FrontmatterError extends Error {
49
+ filePath;
50
+ classification;
51
+ hint;
52
+ originalError;
53
+ constructor(message, filePath, options) {
54
+ super(message);
55
+ this.filePath = filePath;
56
+ this.name = "FrontmatterError";
57
+ this.classification = options?.classification || "Generic Error";
58
+ this.hint = options?.hint || "Check the file for syntax errors.";
59
+ this.originalError = options?.originalError;
60
+ }
61
+ toString() {
62
+ let msg = `${this.filePath}: [${this.classification}] ${this.message}`;
63
+ if (this.hint) {
64
+ msg += `\nHow to fix:\n- ${this.hint}`;
65
+ }
66
+ if (this.originalError) {
67
+ msg += `\n\nOriginal error: ${this.originalError}`;
68
+ }
69
+ return msg;
70
+ }
71
+ }
72
+ export function extractFromMarkdown(filePath) {
73
+ let content;
74
+ try {
75
+ content = readFileSync(filePath, "utf8");
76
+ }
77
+ catch (error) {
78
+ throw new FrontmatterError(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`, filePath, { classification: "File Read Error" });
79
+ }
80
+ try {
81
+ const { data, content: body } = matter(content);
82
+ if (content.trim().startsWith("---")) {
83
+ const parts = content.split("---");
84
+ if (parts.length < 3) {
85
+ throw new FrontmatterError("Missing closing --- delimiter", filePath, {
86
+ classification: "Missing closing ---",
87
+ hint: "Ensure the frontmatter is enclosed between two '---' delimiters.",
88
+ });
89
+ }
90
+ }
91
+ const type = data.type || inferTypeFromPath(filePath);
92
+ if (!type) {
93
+ throw new FrontmatterError("Could not determine entity type from path or frontmatter", filePath, {
94
+ classification: "Missing Type",
95
+ hint: "Add 'type: <type>' to frontmatter or place file in a typed directory (e.g., /requirements/).",
96
+ });
97
+ }
98
+ if (!data.title) {
99
+ throw new FrontmatterError("Missing required field: title", filePath, {
100
+ classification: "Missing Field",
101
+ hint: "Add a 'title: ...' field to the YAML frontmatter.",
102
+ });
103
+ }
104
+ const id = data.id || generateId(filePath, data.title);
105
+ const relationships = extractRelationships(data.links || [], id);
106
+ return {
107
+ entity: {
108
+ id,
109
+ type,
110
+ title: data.title,
111
+ status: data.status || "draft",
112
+ created_at: data.created_at || new Date().toISOString(),
113
+ updated_at: data.updated_at || new Date().toISOString(),
114
+ source: filePath,
115
+ tags: data.tags,
116
+ owner: data.owner,
117
+ priority: data.priority,
118
+ severity: data.severity,
119
+ links: data.links,
120
+ text_ref: data.text_ref,
121
+ },
122
+ relationships,
123
+ };
124
+ }
125
+ catch (error) {
126
+ if (error instanceof FrontmatterError) {
127
+ throw error;
128
+ }
129
+ if (error instanceof Error) {
130
+ const message = error.message;
131
+ let classification = "Frontmatter YAML syntax error";
132
+ let hint = "Check the YAML syntax in your frontmatter.";
133
+ if (message.includes("incomplete explicit mapping pair") &&
134
+ message.includes(":")) {
135
+ classification = "Unquoted colon likely in title";
136
+ hint =
137
+ 'Wrap values containing colons in quotes (e.g., title: "Foo: Bar").';
138
+ }
139
+ else if (!content.trim().startsWith("---") ||
140
+ content.split("---").length < 3) {
141
+ if (content.trim().startsWith("---") &&
142
+ content.split("---").length < 3) {
143
+ classification = "Missing closing ---";
144
+ hint =
145
+ "Ensure the frontmatter is enclosed between two '---' delimiters.";
146
+ }
147
+ }
148
+ else if (message.includes("unexpected end of the stream") ||
149
+ message.includes("flow collection") ||
150
+ message.includes("end of the stream")) {
151
+ classification = "Generic YAML mapping error";
152
+ hint = "Check for unclosed brackets, braces, or quotes in your YAML.";
153
+ }
154
+ throw new FrontmatterError(`Failed to parse frontmatter: ${message}`, filePath, {
155
+ classification,
156
+ hint,
157
+ originalError: message,
158
+ });
159
+ }
160
+ throw error;
161
+ }
162
+ }
163
+ export function inferTypeFromPath(filePath) {
164
+ if (filePath.includes("/requirements/"))
165
+ return "req";
166
+ if (filePath.includes("/scenarios/"))
167
+ return "scenario";
168
+ if (filePath.includes("/tests/"))
169
+ return "test";
170
+ if (filePath.includes("/adr/"))
171
+ return "adr";
172
+ if (filePath.includes("/flags/"))
173
+ return "flag";
174
+ if (filePath.includes("/events/"))
175
+ return "event";
176
+ if (filePath.includes("/facts/"))
177
+ return "fact";
178
+ return null;
179
+ }
180
+ function generateId(filePath, title) {
181
+ const hash = createHash("sha256");
182
+ hash.update(`${filePath}:${title}`);
183
+ return hash.digest("hex").substring(0, 16);
184
+ }
185
+ function extractRelationships(links, fromId) {
186
+ if (!Array.isArray(links))
187
+ return [];
188
+ return links.map((link) => {
189
+ if (typeof link === "string") {
190
+ return {
191
+ type: "relates_to",
192
+ from: fromId,
193
+ to: link,
194
+ };
195
+ }
196
+ const linkObj = link;
197
+ return {
198
+ type: linkObj.type || "relates_to",
199
+ from: fromId,
200
+ to: linkObj.target || linkObj.id || linkObj.to || "",
201
+ };
202
+ });
203
+ }
@@ -0,0 +1,4 @@
1
+ import { type ManifestSymbolEntry } from "./symbols-ts.js";
2
+ export type { ManifestSymbolEntry };
3
+ export declare function enrichSymbolCoordinates(entries: ManifestSymbolEntry[], workspaceRoot: string): Promise<ManifestSymbolEntry[]>;
4
+ //# sourceMappingURL=symbols-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols-coordinator.d.ts","sourceRoot":"","sources":["../../src/extractors/symbols-coordinator.ts"],"names":[],"mappings":"AA+CA,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,iBAAiB,CAAC;AAazB,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAmChC"}
@@ -0,0 +1,131 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ /*
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
+ import * as fs from "node:fs";
46
+ import * as path from "node:path";
47
+ import { enrichSymbolCoordinatesWithTsMorph, } from "./symbols-ts.js";
48
+ const TS_JS_EXTENSIONS = new Set([
49
+ ".ts",
50
+ ".tsx",
51
+ ".js",
52
+ ".jsx",
53
+ ".mts",
54
+ ".cts",
55
+ ".mjs",
56
+ ".cjs",
57
+ ]);
58
+ export async function enrichSymbolCoordinates(entries, workspaceRoot) {
59
+ const output = entries.map((entry) => ({ ...entry }));
60
+ const tsIndices = [];
61
+ const tsEntries = [];
62
+ for (let index = 0; index < output.length; index++) {
63
+ const entry = output[index];
64
+ const resolved = resolveSourcePath(entry.sourceFile, workspaceRoot);
65
+ if (!resolved)
66
+ continue;
67
+ const ext = path.extname(resolved.absolutePath).toLowerCase();
68
+ if (TS_JS_EXTENSIONS.has(ext)) {
69
+ tsIndices.push(index);
70
+ tsEntries.push(entry);
71
+ continue;
72
+ }
73
+ output[index] = enrichWithRegexHeuristic(entry, resolved.absolutePath);
74
+ }
75
+ if (tsEntries.length > 0) {
76
+ const enrichedTs = await enrichSymbolCoordinatesWithTsMorph(tsEntries, workspaceRoot);
77
+ for (let i = 0; i < tsIndices.length; i++) {
78
+ const target = tsIndices[i];
79
+ const enriched = enrichedTs[i];
80
+ if (target === undefined || !enriched)
81
+ continue;
82
+ output[target] = enriched;
83
+ }
84
+ }
85
+ return output;
86
+ }
87
+ function enrichWithRegexHeuristic(entry, absolutePath) {
88
+ try {
89
+ const content = fs.readFileSync(absolutePath, "utf8");
90
+ const escaped = escapeRegex(entry.title);
91
+ const pattern = new RegExp(`\\b${escaped}\\b`);
92
+ const lines = content.split(/\r?\n/);
93
+ for (let i = 0; i < lines.length; i++) {
94
+ const line = lines[i] ?? "";
95
+ const match = pattern.exec(line);
96
+ if (!match)
97
+ continue;
98
+ const sourceLine = i + 1;
99
+ const sourceColumn = match.index;
100
+ const sourceEndLine = sourceLine;
101
+ const sourceEndColumn = sourceColumn + entry.title.length;
102
+ return {
103
+ ...entry,
104
+ sourceLine,
105
+ sourceColumn,
106
+ sourceEndLine,
107
+ sourceEndColumn,
108
+ coordinatesGeneratedAt: new Date().toISOString(),
109
+ };
110
+ }
111
+ return entry;
112
+ }
113
+ catch (error) {
114
+ const message = error instanceof Error ? error.message : String(error);
115
+ console.warn(`[kibi] Failed regex coordinate heuristic for ${entry.id}: ${message}`);
116
+ return entry;
117
+ }
118
+ }
119
+ function resolveSourcePath(sourceFile, workspaceRoot) {
120
+ if (!sourceFile)
121
+ return null;
122
+ const absolutePath = path.isAbsolute(sourceFile)
123
+ ? sourceFile
124
+ : path.resolve(workspaceRoot, sourceFile);
125
+ if (!fs.existsSync(absolutePath))
126
+ return null;
127
+ return { absolutePath };
128
+ }
129
+ function escapeRegex(value) {
130
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
131
+ }
@@ -0,0 +1,21 @@
1
+ export interface SymbolCoordinates {
2
+ sourceLine: number;
3
+ sourceColumn: number;
4
+ sourceEndLine: number;
5
+ sourceEndColumn: number;
6
+ coordinatesGeneratedAt: string;
7
+ }
8
+ export interface ManifestSymbolEntry {
9
+ id: string;
10
+ title: string;
11
+ sourceFile?: string;
12
+ sourceLine?: number;
13
+ sourceColumn?: number;
14
+ sourceEndLine?: number;
15
+ sourceEndColumn?: number;
16
+ coordinatesGeneratedAt?: string;
17
+ links?: string[];
18
+ [key: string]: unknown;
19
+ }
20
+ export declare function enrichSymbolCoordinatesWithTsMorph(entries: ManifestSymbolEntry[], workspaceRoot: string): Promise<ManifestSymbolEntry[]>;
21
+ //# sourceMappingURL=symbols-ts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols-ts.d.ts","sourceRoot":"","sources":["../../src/extractors/symbols-ts.ts"],"names":[],"mappings":"AAuDA,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAaD,wBAAsB,kCAAkC,CACtD,OAAO,EAAE,mBAAmB,EAAE,EAC9B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAwDhC"}