kirograph 0.16.0 → 0.17.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/README.md +227 -8
- package/dist/bin/commands/affected.js +40 -5
- package/dist/bin/commands/affected.js.map +2 -2
- package/dist/bin/commands/data.js +665 -0
- package/dist/bin/commands/data.js.map +7 -0
- package/dist/bin/commands/help.js +5 -0
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/installer/config-prompt.js +28 -1
- package/dist/bin/installer/config-prompt.js.map +2 -2
- package/dist/bin/installer/index.js +28 -1
- package/dist/bin/installer/index.js.map +2 -2
- package/dist/bin/installer/steering.js +33 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/index.js.map +1 -1
- package/dist/bin/installer/targets/kiro.js +2 -2
- package/dist/bin/installer/targets/kiro.js.map +2 -2
- package/dist/bin/kirograph.js +3 -1
- package/dist/bin/kirograph.js.map +3 -3
- package/dist/bin/progress.js +30 -0
- package/dist/bin/progress.js.map +2 -2
- package/dist/compression/naive-cost.js +25 -0
- package/dist/compression/naive-cost.js.map +2 -2
- package/dist/compression/tracker.js +11 -2
- package/dist/compression/tracker.js.map +2 -2
- package/dist/compression/types.js.map +1 -1
- package/dist/config.js +37 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +56 -2
- package/dist/core/pipeline.js.map +2 -2
- package/dist/data/filters.js +105 -0
- package/dist/data/filters.js.map +7 -0
- package/dist/data/indexer.js +225 -0
- package/dist/data/indexer.js.map +7 -0
- package/dist/data/linker.js +150 -0
- package/dist/data/linker.js.map +7 -0
- package/dist/data/lint.js +109 -0
- package/dist/data/lint.js.map +7 -0
- package/dist/data/parsers/csv.js +132 -0
- package/dist/data/parsers/csv.js.map +7 -0
- package/dist/data/parsers/excel.js +88 -0
- package/dist/data/parsers/excel.js.map +7 -0
- package/dist/data/parsers/index.js +79 -0
- package/dist/data/parsers/index.js.map +7 -0
- package/dist/data/parsers/json-array.js +89 -0
- package/dist/data/parsers/json-array.js.map +7 -0
- package/dist/data/parsers/jsonl.js +84 -0
- package/dist/data/parsers/jsonl.js.map +7 -0
- package/dist/data/parsers/parquet.js +95 -0
- package/dist/data/parsers/parquet.js.map +7 -0
- package/dist/data/profiler.js +182 -0
- package/dist/data/profiler.js.map +7 -0
- package/dist/data/queries.js +512 -0
- package/dist/data/queries.js.map +7 -0
- package/dist/data/types.js +17 -0
- package/dist/data/types.js.map +7 -0
- package/dist/db/data-schema.sql +61 -0
- package/dist/db/database.js +11 -0
- package/dist/db/database.js.map +2 -2
- package/dist/mcp/tool-names.js +9 -1
- package/dist/mcp/tool-names.js.map +2 -2
- package/dist/mcp/tools.js +423 -0
- package/dist/mcp/tools.js.map +2 -2
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var filters_exports = {};
|
|
20
|
+
__export(filters_exports, {
|
|
21
|
+
buildWhereClause: () => buildWhereClause
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(filters_exports);
|
|
24
|
+
function validateColumn(column, validColumns) {
|
|
25
|
+
if (!validColumns.has(column)) {
|
|
26
|
+
throw new Error(`Invalid column: "${column}". Available columns: ${[...validColumns].join(", ")}`);
|
|
27
|
+
}
|
|
28
|
+
return `"${column.replace(/"/g, '""')}"`;
|
|
29
|
+
}
|
|
30
|
+
function buildWhereClause(filters, validColumns) {
|
|
31
|
+
if (filters.length === 0) {
|
|
32
|
+
return { where: "", params: [] };
|
|
33
|
+
}
|
|
34
|
+
const conditions = [];
|
|
35
|
+
const params = [];
|
|
36
|
+
for (const filter of filters) {
|
|
37
|
+
const col = validateColumn(filter.column, validColumns);
|
|
38
|
+
switch (filter.op) {
|
|
39
|
+
case "eq":
|
|
40
|
+
conditions.push(`${col} = ?`);
|
|
41
|
+
params.push(filter.value);
|
|
42
|
+
break;
|
|
43
|
+
case "neq":
|
|
44
|
+
conditions.push(`${col} != ?`);
|
|
45
|
+
params.push(filter.value);
|
|
46
|
+
break;
|
|
47
|
+
case "gt":
|
|
48
|
+
conditions.push(`${col} > ?`);
|
|
49
|
+
params.push(filter.value);
|
|
50
|
+
break;
|
|
51
|
+
case "gte":
|
|
52
|
+
conditions.push(`${col} >= ?`);
|
|
53
|
+
params.push(filter.value);
|
|
54
|
+
break;
|
|
55
|
+
case "lt":
|
|
56
|
+
conditions.push(`${col} < ?`);
|
|
57
|
+
params.push(filter.value);
|
|
58
|
+
break;
|
|
59
|
+
case "lte":
|
|
60
|
+
conditions.push(`${col} <= ?`);
|
|
61
|
+
params.push(filter.value);
|
|
62
|
+
break;
|
|
63
|
+
case "contains":
|
|
64
|
+
conditions.push(`${col} LIKE ?`);
|
|
65
|
+
params.push(`%${filter.value}%`);
|
|
66
|
+
break;
|
|
67
|
+
case "in": {
|
|
68
|
+
if (!Array.isArray(filter.value) || filter.value.length === 0) {
|
|
69
|
+
conditions.push("0");
|
|
70
|
+
} else {
|
|
71
|
+
const placeholders = filter.value.map(() => "?").join(",");
|
|
72
|
+
conditions.push(`${col} IN (${placeholders})`);
|
|
73
|
+
params.push(...filter.value);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "is_null":
|
|
78
|
+
if (filter.value === true || filter.value === "true") {
|
|
79
|
+
conditions.push(`${col} IS NULL`);
|
|
80
|
+
} else {
|
|
81
|
+
conditions.push(`${col} IS NOT NULL`);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "between": {
|
|
85
|
+
if (!Array.isArray(filter.value) || filter.value.length !== 2) {
|
|
86
|
+
throw new Error(`"between" filter requires a [min, max] array`);
|
|
87
|
+
}
|
|
88
|
+
conditions.push(`${col} BETWEEN ? AND ?`);
|
|
89
|
+
params.push(filter.value[0], filter.value[1]);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`Unknown filter operator: "${filter.op}"`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
where: `WHERE ${conditions.join(" AND ")}`,
|
|
98
|
+
params
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
102
|
+
0 && (module.exports = {
|
|
103
|
+
buildWhereClause
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=filters.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/data/filters.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Structured Filter \u2192 SQL WHERE Clause Builder\n *\n * Converts QueryFilter objects into parameterized SQL.\n * Zero injection surface \u2014 column names are validated against schema,\n * values are always parameterized.\n */\n\nimport type { QueryFilter } from './types';\n\ninterface SQLClause {\n where: string;\n params: any[];\n}\n\n/**\n * Validate that a column name exists in the schema.\n * Prevents SQL injection via column names.\n */\nfunction validateColumn(column: string, validColumns: Set<string>): string {\n if (!validColumns.has(column)) {\n throw new Error(`Invalid column: \"${column}\". Available columns: ${[...validColumns].join(', ')}`);\n }\n // Quote column name to handle spaces and reserved words\n return `\"${column.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Build a parameterized WHERE clause from structured filters.\n * All filters are ANDed together.\n *\n * @param filters - Array of QueryFilter objects\n * @param validColumns - Set of valid column names (from schema)\n * @returns SQL WHERE clause and parameter array\n */\nexport function buildWhereClause(filters: QueryFilter[], validColumns: Set<string>): SQLClause {\n if (filters.length === 0) {\n return { where: '', params: [] };\n }\n\n const conditions: string[] = [];\n const params: any[] = [];\n\n for (const filter of filters) {\n const col = validateColumn(filter.column, validColumns);\n\n switch (filter.op) {\n case 'eq':\n conditions.push(`${col} = ?`);\n params.push(filter.value);\n break;\n\n case 'neq':\n conditions.push(`${col} != ?`);\n params.push(filter.value);\n break;\n\n case 'gt':\n conditions.push(`${col} > ?`);\n params.push(filter.value);\n break;\n\n case 'gte':\n conditions.push(`${col} >= ?`);\n params.push(filter.value);\n break;\n\n case 'lt':\n conditions.push(`${col} < ?`);\n params.push(filter.value);\n break;\n\n case 'lte':\n conditions.push(`${col} <= ?`);\n params.push(filter.value);\n break;\n\n case 'contains':\n conditions.push(`${col} LIKE ?`);\n params.push(`%${filter.value}%`);\n break;\n\n case 'in': {\n if (!Array.isArray(filter.value) || filter.value.length === 0) {\n conditions.push('0'); // Always false\n } else {\n const placeholders = filter.value.map(() => '?').join(',');\n conditions.push(`${col} IN (${placeholders})`);\n params.push(...filter.value);\n }\n break;\n }\n\n case 'is_null':\n if (filter.value === true || filter.value === 'true') {\n conditions.push(`${col} IS NULL`);\n } else {\n conditions.push(`${col} IS NOT NULL`);\n }\n break;\n\n case 'between': {\n if (!Array.isArray(filter.value) || filter.value.length !== 2) {\n throw new Error(`\"between\" filter requires a [min, max] array`);\n }\n conditions.push(`${col} BETWEEN ? AND ?`);\n params.push(filter.value[0], filter.value[1]);\n break;\n }\n\n default:\n throw new Error(`Unknown filter operator: \"${(filter as any).op}\"`);\n }\n }\n\n return {\n where: `WHERE ${conditions.join(' AND ')}`,\n params,\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,SAAS,eAAe,QAAgB,cAAmC;AACzE,MAAI,CAAC,aAAa,IAAI,MAAM,GAAG;AAC7B,UAAM,IAAI,MAAM,oBAAoB,MAAM,yBAAyB,CAAC,GAAG,YAAY,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,EACnG;AAEA,SAAO,IAAI,OAAO,QAAQ,MAAM,IAAI,CAAC;AACvC;AAUO,SAAS,iBAAiB,SAAwB,cAAsC;AAC7F,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,EAAE,OAAO,IAAI,QAAQ,CAAC,EAAE;AAAA,EACjC;AAEA,QAAM,aAAuB,CAAC;AAC9B,QAAM,SAAgB,CAAC;AAEvB,aAAW,UAAU,SAAS;AAC5B,UAAM,MAAM,eAAe,OAAO,QAAQ,YAAY;AAEtD,YAAQ,OAAO,IAAI;AAAA,MACjB,KAAK;AACH,mBAAW,KAAK,GAAG,GAAG,MAAM;AAC5B,eAAO,KAAK,OAAO,KAAK;AACxB;AAAA,MAEF,KAAK;AACH,mBAAW,KAAK,GAAG,GAAG,OAAO;AAC7B,eAAO,KAAK,OAAO,KAAK;AACxB;AAAA,MAEF,KAAK;AACH,mBAAW,KAAK,GAAG,GAAG,MAAM;AAC5B,eAAO,KAAK,OAAO,KAAK;AACxB;AAAA,MAEF,KAAK;AACH,mBAAW,KAAK,GAAG,GAAG,OAAO;AAC7B,eAAO,KAAK,OAAO,KAAK;AACxB;AAAA,MAEF,KAAK;AACH,mBAAW,KAAK,GAAG,GAAG,MAAM;AAC5B,eAAO,KAAK,OAAO,KAAK;AACxB;AAAA,MAEF,KAAK;AACH,mBAAW,KAAK,GAAG,GAAG,OAAO;AAC7B,eAAO,KAAK,OAAO,KAAK;AACxB;AAAA,MAEF,KAAK;AACH,mBAAW,KAAK,GAAG,GAAG,SAAS;AAC/B,eAAO,KAAK,IAAI,OAAO,KAAK,GAAG;AAC/B;AAAA,MAEF,KAAK,MAAM;AACT,YAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,KAAK,OAAO,MAAM,WAAW,GAAG;AAC7D,qBAAW,KAAK,GAAG;AAAA,QACrB,OAAO;AACL,gBAAM,eAAe,OAAO,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AACzD,qBAAW,KAAK,GAAG,GAAG,QAAQ,YAAY,GAAG;AAC7C,iBAAO,KAAK,GAAG,OAAO,KAAK;AAAA,QAC7B;AACA;AAAA,MACF;AAAA,MAEA,KAAK;AACH,YAAI,OAAO,UAAU,QAAQ,OAAO,UAAU,QAAQ;AACpD,qBAAW,KAAK,GAAG,GAAG,UAAU;AAAA,QAClC,OAAO;AACL,qBAAW,KAAK,GAAG,GAAG,cAAc;AAAA,QACtC;AACA;AAAA,MAEF,KAAK,WAAW;AACd,YAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,KAAK,OAAO,MAAM,WAAW,GAAG;AAC7D,gBAAM,IAAI,MAAM,8CAA8C;AAAA,QAChE;AACA,mBAAW,KAAK,GAAG,GAAG,kBAAkB;AACxC,eAAO,KAAK,OAAO,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,CAAC;AAC5C;AAAA,MACF;AAAA,MAEA;AACE,cAAM,IAAI,MAAM,6BAA8B,OAAe,EAAE,GAAG;AAAA,IACtE;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,SAAS,WAAW,KAAK,OAAO,CAAC;AAAA,IACxC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var indexer_exports = {};
|
|
30
|
+
__export(indexer_exports, {
|
|
31
|
+
DataIndexer: () => DataIndexer
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(indexer_exports);
|
|
34
|
+
var fs = __toESM(require("fs"));
|
|
35
|
+
var path = __toESM(require("path"));
|
|
36
|
+
var crypto = __toESM(require("crypto"));
|
|
37
|
+
var import_picomatch = __toESM(require("picomatch"));
|
|
38
|
+
var import_parsers = require("./parsers/index");
|
|
39
|
+
var import_profiler = require("./profiler");
|
|
40
|
+
function generateDatasetId(filePath) {
|
|
41
|
+
return filePath.replace(/\.[^.]+$/, "").replace(/[/\\]/g, "-").replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
class DataIndexer {
|
|
44
|
+
constructor(db, config, projectRoot) {
|
|
45
|
+
this.db = db;
|
|
46
|
+
this.config = config;
|
|
47
|
+
this.projectRoot = projectRoot;
|
|
48
|
+
}
|
|
49
|
+
async indexAll(opts) {
|
|
50
|
+
const start = Date.now();
|
|
51
|
+
const result = {
|
|
52
|
+
datasetsIndexed: 0,
|
|
53
|
+
rowsIndexed: 0,
|
|
54
|
+
columnsProfiled: 0,
|
|
55
|
+
errors: [],
|
|
56
|
+
duration: 0
|
|
57
|
+
};
|
|
58
|
+
const files = this.scanDataFiles();
|
|
59
|
+
opts?.onProgress?.(`data: found ${files.length} data files`);
|
|
60
|
+
for (const relPath of files) {
|
|
61
|
+
try {
|
|
62
|
+
const indexed = await this.indexFile(relPath, opts?.force ?? false);
|
|
63
|
+
if (indexed) {
|
|
64
|
+
result.datasetsIndexed++;
|
|
65
|
+
result.rowsIndexed += indexed.rows;
|
|
66
|
+
result.columnsProfiled += indexed.columns;
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
result.errors.push(`${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
this.removeDeletedDatasets(files);
|
|
73
|
+
if (this.config.dataLinkCode !== false && result.datasetsIndexed > 0) {
|
|
74
|
+
try {
|
|
75
|
+
const { DataCodeLinker } = await Promise.resolve().then(() => require("./linker.js"));
|
|
76
|
+
const linker = new DataCodeLinker(this.db, this.projectRoot);
|
|
77
|
+
const refsCreated = linker.linkAll();
|
|
78
|
+
opts?.onProgress?.(`data: linked ${refsCreated} code references`);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
result.duration = Date.now() - start;
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
async indexFile(relPath, force = false) {
|
|
86
|
+
const absPath = path.join(this.projectRoot, relPath);
|
|
87
|
+
if (!fs.existsSync(absPath)) return null;
|
|
88
|
+
const stat = fs.statSync(absPath);
|
|
89
|
+
if (stat.size > this.config.dataMaxFileSize) return null;
|
|
90
|
+
const fileBuffer = fs.readFileSync(absPath);
|
|
91
|
+
const contentHash = crypto.createHash("sha256").update(fileBuffer).digest("hex");
|
|
92
|
+
const datasetId = generateDatasetId(relPath);
|
|
93
|
+
if (!force) {
|
|
94
|
+
const existing = this.db.get("SELECT content_hash FROM data_datasets WHERE id = ?", [datasetId]);
|
|
95
|
+
if (existing?.content_hash === contentHash) return null;
|
|
96
|
+
}
|
|
97
|
+
const parser = (0, import_parsers.getDataParser)(relPath);
|
|
98
|
+
if (!parser) return null;
|
|
99
|
+
const profiler = new import_profiler.ColumnProfiler();
|
|
100
|
+
const allRows = [];
|
|
101
|
+
const maxRows = this.config.dataMaxRows ?? 1e6;
|
|
102
|
+
const { columns, totalRows } = await parser.parse(absPath, {
|
|
103
|
+
maxRows,
|
|
104
|
+
onBatch: (rows, cols) => {
|
|
105
|
+
profiler.addBatch(rows, cols);
|
|
106
|
+
allRows.push(...rows);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
if (columns.length === 0) return null;
|
|
110
|
+
const columnProfiles = profiler.finalize(datasetId);
|
|
111
|
+
this.db.run("BEGIN");
|
|
112
|
+
try {
|
|
113
|
+
const rowTableName = `data_rows_${datasetId.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
114
|
+
this.db.run(`DROP TABLE IF EXISTS "${rowTableName}"`);
|
|
115
|
+
this.db.run("DELETE FROM data_columns WHERE dataset_id = ?", [datasetId]);
|
|
116
|
+
this.db.run("DELETE FROM data_datasets WHERE id = ?", [datasetId]);
|
|
117
|
+
this.db.run(`
|
|
118
|
+
INSERT INTO data_datasets (id, file_path, format, row_count, column_count, file_size, content_hash, summary, indexed_at)
|
|
119
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
120
|
+
`, [datasetId, relPath, parser.name, totalRows, columns.length, stat.size, contentHash, null, Date.now()]);
|
|
121
|
+
for (const col of columnProfiles) {
|
|
122
|
+
this.db.run(`
|
|
123
|
+
INSERT INTO data_columns (id, dataset_id, name, position, inferred_type, nullable, null_count, null_pct, cardinality, min_value, max_value, mean_value, sample_values, summary, updated_at)
|
|
124
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
125
|
+
`, [
|
|
126
|
+
col.id,
|
|
127
|
+
col.datasetId,
|
|
128
|
+
col.name,
|
|
129
|
+
col.position,
|
|
130
|
+
col.inferredType,
|
|
131
|
+
col.nullable ? 1 : 0,
|
|
132
|
+
col.nullCount,
|
|
133
|
+
col.nullPct,
|
|
134
|
+
col.cardinality,
|
|
135
|
+
col.minValue,
|
|
136
|
+
col.maxValue,
|
|
137
|
+
col.meanValue,
|
|
138
|
+
JSON.stringify(col.sampleValues),
|
|
139
|
+
col.summary,
|
|
140
|
+
col.updatedAt
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
const colDefs = columns.map((c) => `"${c.replace(/"/g, '""')}" TEXT`).join(", ");
|
|
144
|
+
this.db.run(`CREATE TABLE "${rowTableName}" (rowid INTEGER PRIMARY KEY AUTOINCREMENT, ${colDefs})`);
|
|
145
|
+
const colNames = columns.map((c) => `"${c.replace(/"/g, '""')}"`).join(", ");
|
|
146
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
147
|
+
const insertStmt = `INSERT INTO "${rowTableName}" (${colNames}) VALUES (${placeholders})`;
|
|
148
|
+
for (const row of allRows) {
|
|
149
|
+
const values = columns.map((c) => {
|
|
150
|
+
const v = row[c];
|
|
151
|
+
if (v === null || v === void 0) return null;
|
|
152
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
153
|
+
return v;
|
|
154
|
+
});
|
|
155
|
+
this.db.run(insertStmt, values);
|
|
156
|
+
}
|
|
157
|
+
const historyColumns = columnProfiles.map((col) => ({
|
|
158
|
+
name: col.name,
|
|
159
|
+
type: col.inferredType,
|
|
160
|
+
cardinality: col.cardinality,
|
|
161
|
+
nullPct: col.nullPct
|
|
162
|
+
}));
|
|
163
|
+
this.db.run(`
|
|
164
|
+
INSERT INTO data_dataset_history (dataset_id, snapshot_at, row_count, column_count, columns_json, content_hash)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
166
|
+
`, [datasetId, Date.now(), totalRows, columns.length, JSON.stringify(historyColumns), contentHash]);
|
|
167
|
+
this.db.run("COMMIT");
|
|
168
|
+
} catch (err) {
|
|
169
|
+
this.db.run("ROLLBACK");
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
return { rows: totalRows, columns: columns.length };
|
|
173
|
+
}
|
|
174
|
+
// ── Private ─────────────────────────────────────────────────────────────────
|
|
175
|
+
scanDataFiles() {
|
|
176
|
+
const includePatterns = this.config.dataInclude ?? [];
|
|
177
|
+
const excludePatterns = this.config.dataExclude ?? [];
|
|
178
|
+
const includeMatchers = includePatterns.map((p) => (0, import_picomatch.default)(p));
|
|
179
|
+
const excludeMatchers = excludePatterns.map((p) => (0, import_picomatch.default)(p));
|
|
180
|
+
const results = [];
|
|
181
|
+
const walk = (dir) => {
|
|
182
|
+
let entries;
|
|
183
|
+
try {
|
|
184
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (const entry of entries) {
|
|
189
|
+
const full = path.join(dir, entry.name);
|
|
190
|
+
const rel = path.relative(this.projectRoot, full).replace(/\\/g, "/");
|
|
191
|
+
if (entry.isDirectory()) {
|
|
192
|
+
if (excludeMatchers.some((m) => m(rel + "/"))) continue;
|
|
193
|
+
walk(full);
|
|
194
|
+
} else if (entry.isFile()) {
|
|
195
|
+
if (excludeMatchers.some((m) => m(rel))) continue;
|
|
196
|
+
if (includeMatchers.some((m) => m(rel))) {
|
|
197
|
+
if ((0, import_parsers.getDataParser)(rel)) {
|
|
198
|
+
results.push(rel);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
walk(this.projectRoot);
|
|
205
|
+
return results;
|
|
206
|
+
}
|
|
207
|
+
removeDeletedDatasets(currentFiles) {
|
|
208
|
+
const currentSet = new Set(currentFiles.map((f) => generateDatasetId(f)));
|
|
209
|
+
const indexed = this.db.all("SELECT id FROM data_datasets").map((r) => r.id);
|
|
210
|
+
for (const id of indexed) {
|
|
211
|
+
if (!currentSet.has(id)) {
|
|
212
|
+
const rowTableName = `data_rows_${id.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
213
|
+
this.db.run(`DROP TABLE IF EXISTS "${rowTableName}"`);
|
|
214
|
+
this.db.run("DELETE FROM data_columns WHERE dataset_id = ?", [id]);
|
|
215
|
+
this.db.run("DELETE FROM data_code_refs WHERE dataset_id = ?", [id]);
|
|
216
|
+
this.db.run("DELETE FROM data_datasets WHERE id = ?", [id]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
222
|
+
0 && (module.exports = {
|
|
223
|
+
DataIndexer
|
|
224
|
+
});
|
|
225
|
+
//# sourceMappingURL=indexer.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/data/indexer.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Data Indexer\n *\n * Orchestrates: scan data files \u2192 parse \u2192 profile columns \u2192 persist rows to SQLite.\n * Supports incremental re-indexing via content hashes.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as crypto from 'crypto';\nimport picomatch from 'picomatch';\nimport type { KiroGraphConfig } from '../config';\nimport type { DataSet, DataColumn, DataIndexResult, ParsedRow } from './types';\nimport { getDataParser } from './parsers/index';\nimport { ColumnProfiler } from './profiler';\n\n/**\n * Generate a dataset ID from a file path.\n * Slugifies the relative path: tests/fixtures/users.csv \u2192 tests-fixtures-users\n */\nfunction generateDatasetId(filePath: string): string {\n return filePath\n .replace(/\\.[^.]+$/, '') // remove extension\n .replace(/[/\\\\]/g, '-') // replace path separators\n .replace(/[^a-zA-Z0-9-_]/g, '-') // replace special chars\n .replace(/-+/g, '-') // collapse multiple dashes\n .replace(/^-|-$/g, '') // trim leading/trailing dashes\n .toLowerCase();\n}\n\nexport class DataIndexer {\n private readonly db: any;\n private readonly config: KiroGraphConfig;\n private readonly projectRoot: string;\n\n constructor(db: any, config: KiroGraphConfig, projectRoot: string) {\n this.db = db;\n this.config = config;\n this.projectRoot = projectRoot;\n }\n\n async indexAll(opts?: { force?: boolean; onProgress?: (msg: string) => void }): Promise<DataIndexResult> {\n const start = Date.now();\n const result: DataIndexResult = {\n datasetsIndexed: 0, rowsIndexed: 0, columnsProfiled: 0, errors: [], duration: 0,\n };\n\n const files = this.scanDataFiles();\n opts?.onProgress?.(`data: found ${files.length} data files`);\n\n for (const relPath of files) {\n try {\n const indexed = await this.indexFile(relPath, opts?.force ?? false);\n if (indexed) {\n result.datasetsIndexed++;\n result.rowsIndexed += indexed.rows;\n result.columnsProfiled += indexed.columns;\n }\n } catch (err) {\n result.errors.push(`${relPath}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n // Remove datasets for files that no longer exist\n this.removeDeletedDatasets(files);\n\n // Link data files to code (if enabled)\n if ((this.config as any).dataLinkCode !== false && result.datasetsIndexed > 0) {\n try {\n const { DataCodeLinker } = await Promise.resolve().then(() => require('./linker.js'));\n const linker = new DataCodeLinker(this.db, this.projectRoot);\n const refsCreated = linker.linkAll();\n opts?.onProgress?.(`data: linked ${refsCreated} code references`);\n } catch {\n // Non-critical \u2014 linking failure shouldn't block indexing\n }\n }\n\n result.duration = Date.now() - start;\n return result;\n }\n\n async indexFile(relPath: string, force = false): Promise<{ rows: number; columns: number } | null> {\n const absPath = path.join(this.projectRoot, relPath);\n if (!fs.existsSync(absPath)) return null;\n\n const stat = fs.statSync(absPath);\n if (stat.size > (this.config as any).dataMaxFileSize) return null;\n\n // Check content hash for incremental\n const fileBuffer = fs.readFileSync(absPath);\n const contentHash = crypto.createHash('sha256').update(fileBuffer).digest('hex');\n\n const datasetId = generateDatasetId(relPath);\n\n if (!force) {\n const existing = this.db.get('SELECT content_hash FROM data_datasets WHERE id = ?', [datasetId]);\n if (existing?.content_hash === contentHash) return null; // unchanged\n }\n\n // Get parser\n const parser = getDataParser(relPath);\n if (!parser) return null;\n\n // Parse and profile\n const profiler = new ColumnProfiler();\n const allRows: ParsedRow[] = [];\n const maxRows = (this.config as any).dataMaxRows ?? 1_000_000;\n\n const { columns, totalRows } = await parser.parse(absPath, {\n maxRows,\n onBatch: (rows, cols) => {\n profiler.addBatch(rows, cols);\n allRows.push(...rows);\n },\n });\n\n if (columns.length === 0) return null;\n\n // Finalize profiles\n const columnProfiles = profiler.finalize(datasetId);\n\n // Persist in transaction\n this.db.run('BEGIN');\n try {\n // Delete old dataset data\n const rowTableName = `data_rows_${datasetId.replace(/[^a-zA-Z0-9_]/g, '_')}`;\n this.db.run(`DROP TABLE IF EXISTS \"${rowTableName}\"`);\n this.db.run('DELETE FROM data_columns WHERE dataset_id = ?', [datasetId]);\n this.db.run('DELETE FROM data_datasets WHERE id = ?', [datasetId]);\n\n // Insert dataset\n this.db.run(`\n INSERT INTO data_datasets (id, file_path, format, row_count, column_count, file_size, content_hash, summary, indexed_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n `, [datasetId, relPath, parser.name, totalRows, columns.length, stat.size, contentHash, null, Date.now()]);\n\n // Insert column profiles\n for (const col of columnProfiles) {\n this.db.run(`\n INSERT INTO data_columns (id, dataset_id, name, position, inferred_type, nullable, null_count, null_pct, cardinality, min_value, max_value, mean_value, sample_values, summary, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `, [\n col.id, col.datasetId, col.name, col.position, col.inferredType,\n col.nullable ? 1 : 0, col.nullCount, col.nullPct, col.cardinality,\n col.minValue, col.maxValue, col.meanValue,\n JSON.stringify(col.sampleValues), col.summary, col.updatedAt,\n ]);\n }\n\n // Create row table with dynamic schema\n const colDefs = columns.map(c => `\"${c.replace(/\"/g, '\"\"')}\" TEXT`).join(', ');\n this.db.run(`CREATE TABLE \"${rowTableName}\" (rowid INTEGER PRIMARY KEY AUTOINCREMENT, ${colDefs})`);\n\n // Insert rows in batches\n const colNames = columns.map(c => `\"${c.replace(/\"/g, '\"\"')}\"`).join(', ');\n const placeholders = columns.map(() => '?').join(', ');\n const insertStmt = `INSERT INTO \"${rowTableName}\" (${colNames}) VALUES (${placeholders})`;\n\n for (const row of allRows) {\n const values = columns.map(c => {\n const v = row[c];\n if (v === null || v === undefined) return null;\n if (typeof v === 'object') return JSON.stringify(v);\n return v;\n });\n this.db.run(insertStmt, values);\n }\n\n // Save history snapshot for drift detection\n const historyColumns = columnProfiles.map(col => ({\n name: col.name,\n type: col.inferredType,\n cardinality: col.cardinality,\n nullPct: col.nullPct,\n }));\n this.db.run(`\n INSERT INTO data_dataset_history (dataset_id, snapshot_at, row_count, column_count, columns_json, content_hash)\n VALUES (?, ?, ?, ?, ?, ?)\n `, [datasetId, Date.now(), totalRows, columns.length, JSON.stringify(historyColumns), contentHash]);\n\n this.db.run('COMMIT');\n } catch (err) {\n this.db.run('ROLLBACK');\n throw err;\n }\n\n return { rows: totalRows, columns: columns.length };\n }\n\n // \u2500\u2500 Private \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private scanDataFiles(): string[] {\n const includePatterns = (this.config as any).dataInclude ?? [];\n const excludePatterns = (this.config as any).dataExclude ?? [];\n const includeMatchers = includePatterns.map((p: string) => picomatch(p));\n const excludeMatchers = excludePatterns.map((p: string) => picomatch(p));\n const results: string[] = [];\n\n const walk = (dir: string) => {\n let entries: fs.Dirent[];\n try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }\n\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n const rel = path.relative(this.projectRoot, full).replace(/\\\\/g, '/');\n\n if (entry.isDirectory()) {\n if (excludeMatchers.some((m: any) => m(rel + '/'))) continue;\n walk(full);\n } else if (entry.isFile()) {\n if (excludeMatchers.some((m: any) => m(rel))) continue;\n if (includeMatchers.some((m: any) => m(rel))) {\n // Verify a parser exists for this format\n if (getDataParser(rel)) {\n results.push(rel);\n }\n }\n }\n }\n };\n\n walk(this.projectRoot);\n return results;\n }\n\n private removeDeletedDatasets(currentFiles: string[]): void {\n const currentSet = new Set(currentFiles.map(f => generateDatasetId(f)));\n const indexed = (this.db.all('SELECT id FROM data_datasets') as Array<{ id: string }>).map(r => r.id);\n\n for (const id of indexed) {\n if (!currentSet.has(id)) {\n const rowTableName = `data_rows_${id.replace(/[^a-zA-Z0-9_]/g, '_')}`;\n this.db.run(`DROP TABLE IF EXISTS \"${rowTableName}\"`);\n this.db.run('DELETE FROM data_columns WHERE dataset_id = ?', [id]);\n this.db.run('DELETE FROM data_code_refs WHERE dataset_id = ?', [id]);\n this.db.run('DELETE FROM data_datasets WHERE id = ?', [id]);\n }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["picomatch"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var linker_exports = {};
|
|
20
|
+
__export(linker_exports, {
|
|
21
|
+
DataCodeLinker: () => DataCodeLinker,
|
|
22
|
+
detectDataReferences: () => detectDataReferences
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(linker_exports);
|
|
25
|
+
const READ_PATTERNS = [
|
|
26
|
+
// Node.js / TypeScript
|
|
27
|
+
/readFileSync\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
28
|
+
/readFile\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
29
|
+
/createReadStream\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
30
|
+
// Python pandas
|
|
31
|
+
/read_csv\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
32
|
+
/read_excel\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
33
|
+
/read_json\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
34
|
+
/read_parquet\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
35
|
+
/read_table\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
36
|
+
// Python open
|
|
37
|
+
/open\s*\(\s*['"`]([^'"`]+\.(?:csv|tsv|jsonl|ndjson|json|xlsx|xls|parquet))['"`]/g,
|
|
38
|
+
// SQL COPY
|
|
39
|
+
/COPY\s+\w+\s+FROM\s+['"`]([^'"`]+)['"`]/gi,
|
|
40
|
+
/\\copy\s+\w+\s+from\s+['"`]([^'"`]+)['"`]/gi,
|
|
41
|
+
// Generic path references in strings (data file extensions)
|
|
42
|
+
/['"`]((?:\.\.?\/)?(?:[\w\-./]+\/)?[\w\-]+\.(?:csv|tsv|jsonl|ndjson|xlsx|xls|parquet))['"`]/g
|
|
43
|
+
];
|
|
44
|
+
const WRITE_PATTERNS = [
|
|
45
|
+
/writeFileSync\s*\(\s*['"`]([^'"`]+\.(?:csv|tsv|jsonl|json|xlsx|parquet))['"`]/g,
|
|
46
|
+
/writeFile\s*\(\s*['"`]([^'"`]+\.(?:csv|tsv|jsonl|json|xlsx|parquet))['"`]/g,
|
|
47
|
+
/to_csv\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
48
|
+
/to_excel\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
49
|
+
/to_json\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
50
|
+
/to_parquet\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
51
|
+
/createWriteStream\s*\(\s*['"`]([^'"`]+\.(?:csv|tsv|jsonl|json|xlsx|parquet))['"`]/g,
|
|
52
|
+
/COPY\s+\w+\s+TO\s+['"`]([^'"`]+)['"`]/gi
|
|
53
|
+
];
|
|
54
|
+
function detectDataReferences(sourceContent, knownDataPaths) {
|
|
55
|
+
const refs = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const pattern of READ_PATTERNS) {
|
|
57
|
+
pattern.lastIndex = 0;
|
|
58
|
+
let match;
|
|
59
|
+
while ((match = pattern.exec(sourceContent)) !== null) {
|
|
60
|
+
const filePath = normalizePath(match[1]);
|
|
61
|
+
if (isDataFilePath(filePath, knownDataPaths)) {
|
|
62
|
+
const existing = refs.get(filePath);
|
|
63
|
+
if (!existing || existing.confidence < 0.9) {
|
|
64
|
+
const confidence = knownDataPaths.has(filePath) ? 1 : 0.7;
|
|
65
|
+
refs.set(filePath, { refType: "reads", confidence });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const pattern of WRITE_PATTERNS) {
|
|
71
|
+
pattern.lastIndex = 0;
|
|
72
|
+
let match;
|
|
73
|
+
while ((match = pattern.exec(sourceContent)) !== null) {
|
|
74
|
+
const filePath = normalizePath(match[1]);
|
|
75
|
+
if (isDataFilePath(filePath, knownDataPaths)) {
|
|
76
|
+
const confidence = knownDataPaths.has(filePath) ? 1 : 0.7;
|
|
77
|
+
refs.set(filePath, { refType: "writes", confidence });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return Array.from(refs.entries()).map(([filePath, info]) => ({
|
|
82
|
+
filePath,
|
|
83
|
+
...info
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
function generateDatasetId(filePath) {
|
|
87
|
+
return filePath.replace(/\.[^.]+$/, "").replace(/[/\\]/g, "-").replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
88
|
+
}
|
|
89
|
+
function normalizePath(p) {
|
|
90
|
+
return p.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
91
|
+
}
|
|
92
|
+
function isDataFilePath(filePath, knownPaths) {
|
|
93
|
+
if (knownPaths.has(filePath)) return true;
|
|
94
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
95
|
+
return ["csv", "tsv", "jsonl", "ndjson", "json", "xlsx", "xls", "parquet"].includes(ext ?? "");
|
|
96
|
+
}
|
|
97
|
+
class DataCodeLinker {
|
|
98
|
+
constructor(db, projectRoot) {
|
|
99
|
+
this.db = db;
|
|
100
|
+
this.projectRoot = projectRoot;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Populate data_code_refs by scanning source files for data file references.
|
|
104
|
+
* Returns the number of refs created.
|
|
105
|
+
*/
|
|
106
|
+
linkAll() {
|
|
107
|
+
const fs = require("fs");
|
|
108
|
+
const path = require("path");
|
|
109
|
+
const datasets = this.db.all("SELECT id, file_path FROM data_datasets");
|
|
110
|
+
if (datasets.length === 0) return 0;
|
|
111
|
+
const knownPaths = new Set(datasets.map((d) => d.file_path));
|
|
112
|
+
const pathToDatasetId = new Map(datasets.map((d) => [d.file_path, d.id]));
|
|
113
|
+
const sourceFiles = this.db.all(
|
|
114
|
+
`SELECT file_path FROM files WHERE file_path NOT LIKE '%.csv' AND file_path NOT LIKE '%.tsv' AND file_path NOT LIKE '%.jsonl' AND file_path NOT LIKE '%.ndjson' AND file_path NOT LIKE '%.xlsx' AND file_path NOT LIKE '%.parquet'`
|
|
115
|
+
);
|
|
116
|
+
this.db.run("DELETE FROM data_code_refs");
|
|
117
|
+
let refsCreated = 0;
|
|
118
|
+
for (const file of sourceFiles) {
|
|
119
|
+
const absPath = path.join(this.projectRoot, file.file_path);
|
|
120
|
+
let content;
|
|
121
|
+
try {
|
|
122
|
+
content = fs.readFileSync(absPath, "utf-8");
|
|
123
|
+
} catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const refs = detectDataReferences(content, knownPaths);
|
|
127
|
+
if (refs.length === 0) continue;
|
|
128
|
+
const qualifiedName = file.file_path;
|
|
129
|
+
for (const ref of refs) {
|
|
130
|
+
const datasetId = pathToDatasetId.get(ref.filePath);
|
|
131
|
+
if (!datasetId) continue;
|
|
132
|
+
try {
|
|
133
|
+
this.db.run(
|
|
134
|
+
`INSERT OR REPLACE INTO data_code_refs (dataset_id, qualified_name, ref_type, confidence) VALUES (?, ?, ?, ?)`,
|
|
135
|
+
[datasetId, qualifiedName, ref.refType, ref.confidence]
|
|
136
|
+
);
|
|
137
|
+
refsCreated++;
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return refsCreated;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
146
|
+
0 && (module.exports = {
|
|
147
|
+
DataCodeLinker,
|
|
148
|
+
detectDataReferences
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=linker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/data/linker.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Data \u2194 Code Linker\n *\n * Detects references to data files in source code and populates data_code_refs.\n * Patterns detected:\n * - readFileSync('data/users.csv'), readFile('path/to/file.csv')\n * - pd.read_csv('data/file.csv'), pd.read_excel(...)\n * - open('data/file.csv'), csv.reader(open(...))\n * - COPY FROM 'file.csv', \\copy ... from 'file.csv'\n * - require('./data/file.json'), import ... from './data/file.json'\n * - fs.createReadStream('data/file.csv')\n * - String literals matching known data file paths\n */\n\nimport type { DataCodeRef } from './types';\n\n/** Patterns that indicate a code symbol reads a data file */\nconst READ_PATTERNS = [\n // Node.js / TypeScript\n /readFileSync\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /readFile\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /createReadStream\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n // Python pandas\n /read_csv\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /read_excel\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /read_json\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /read_parquet\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /read_table\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n // Python open\n /open\\s*\\(\\s*['\"`]([^'\"`]+\\.(?:csv|tsv|jsonl|ndjson|json|xlsx|xls|parquet))['\"`]/g,\n // SQL COPY\n /COPY\\s+\\w+\\s+FROM\\s+['\"`]([^'\"`]+)['\"`]/gi,\n /\\\\copy\\s+\\w+\\s+from\\s+['\"`]([^'\"`]+)['\"`]/gi,\n // Generic path references in strings (data file extensions)\n /['\"`]((?:\\.\\.?\\/)?(?:[\\w\\-./]+\\/)?[\\w\\-]+\\.(?:csv|tsv|jsonl|ndjson|xlsx|xls|parquet))['\"`]/g,\n];\n\n/** Patterns that indicate a code symbol writes a data file */\nconst WRITE_PATTERNS = [\n /writeFileSync\\s*\\(\\s*['\"`]([^'\"`]+\\.(?:csv|tsv|jsonl|json|xlsx|parquet))['\"`]/g,\n /writeFile\\s*\\(\\s*['\"`]([^'\"`]+\\.(?:csv|tsv|jsonl|json|xlsx|parquet))['\"`]/g,\n /to_csv\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /to_excel\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /to_json\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /to_parquet\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g,\n /createWriteStream\\s*\\(\\s*['\"`]([^'\"`]+\\.(?:csv|tsv|jsonl|json|xlsx|parquet))['\"`]/g,\n /COPY\\s+\\w+\\s+TO\\s+['\"`]([^'\"`]+)['\"`]/gi,\n];\n\n/**\n * Scan source code content for references to data files.\n * Returns matched file paths with their reference type.\n */\nexport function detectDataReferences(\n sourceContent: string,\n knownDataPaths: Set<string>,\n): Array<{ filePath: string; refType: 'reads' | 'writes'; confidence: number }> {\n const refs = new Map<string, { refType: 'reads' | 'writes'; confidence: number }>();\n\n // Check read patterns\n for (const pattern of READ_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(sourceContent)) !== null) {\n const filePath = normalizePath(match[1]!);\n if (isDataFilePath(filePath, knownDataPaths)) {\n const existing = refs.get(filePath);\n if (!existing || existing.confidence < 0.9) {\n const confidence = knownDataPaths.has(filePath) ? 1.0 : 0.7;\n refs.set(filePath, { refType: 'reads', confidence });\n }\n }\n }\n }\n\n // Check write patterns\n for (const pattern of WRITE_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(sourceContent)) !== null) {\n const filePath = normalizePath(match[1]!);\n if (isDataFilePath(filePath, knownDataPaths)) {\n const confidence = knownDataPaths.has(filePath) ? 1.0 : 0.7;\n refs.set(filePath, { refType: 'writes', confidence });\n }\n }\n }\n\n return Array.from(refs.entries()).map(([filePath, info]) => ({\n filePath,\n ...info,\n }));\n}\n\n/**\n * Generate dataset ID from a file path (same logic as indexer).\n */\nfunction generateDatasetId(filePath: string): string {\n return filePath\n .replace(/\\.[^.]+$/, '')\n .replace(/[/\\\\]/g, '-')\n .replace(/[^a-zA-Z0-9-_]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .toLowerCase();\n}\n\n/**\n * Normalize a path reference found in code.\n * Strips leading ./ and resolves simple relative paths.\n */\nfunction normalizePath(p: string): string {\n return p.replace(/^\\.\\//, '').replace(/\\\\/g, '/');\n}\n\n/**\n * Check if a path looks like a data file (by extension or known path match).\n */\nfunction isDataFilePath(filePath: string, knownPaths: Set<string>): boolean {\n if (knownPaths.has(filePath)) return true;\n // Check extension\n const ext = filePath.split('.').pop()?.toLowerCase();\n return ['csv', 'tsv', 'jsonl', 'ndjson', 'json', 'xlsx', 'xls', 'parquet'].includes(ext ?? '');\n}\n\n/**\n * Link data files to code symbols.\n * Scans all indexed source files for references to known data file paths.\n */\nexport class DataCodeLinker {\n private readonly db: any;\n private readonly projectRoot: string;\n\n constructor(db: any, projectRoot: string) {\n this.db = db;\n this.projectRoot = projectRoot;\n }\n\n /**\n * Populate data_code_refs by scanning source files for data file references.\n * Returns the number of refs created.\n */\n linkAll(): number {\n const fs = require('fs');\n const path = require('path');\n\n // Get all known data file paths\n const datasets = this.db.all('SELECT id, file_path FROM data_datasets') as Array<{ id: string; file_path: string }>;\n if (datasets.length === 0) return 0;\n\n const knownPaths = new Set(datasets.map(d => d.file_path));\n const pathToDatasetId = new Map(datasets.map(d => [d.file_path, d.id]));\n\n // Get all indexed source files (from the graph)\n const sourceFiles = this.db.all(\n `SELECT file_path FROM files WHERE file_path NOT LIKE '%.csv' AND file_path NOT LIKE '%.tsv' AND file_path NOT LIKE '%.jsonl' AND file_path NOT LIKE '%.ndjson' AND file_path NOT LIKE '%.xlsx' AND file_path NOT LIKE '%.parquet'`\n ) as Array<{ file_path: string }>;\n\n // Clear existing refs\n this.db.run('DELETE FROM data_code_refs');\n\n let refsCreated = 0;\n\n for (const file of sourceFiles) {\n const absPath = path.join(this.projectRoot, file.file_path);\n let content: string;\n try {\n content = fs.readFileSync(absPath, 'utf-8');\n } catch {\n continue;\n }\n\n const refs = detectDataReferences(content, knownPaths);\n if (refs.length === 0) continue;\n\n // Find the qualified_name of the containing symbol (file-level)\n // Use the file path as the qualified name for file-level refs\n const qualifiedName = file.file_path;\n\n for (const ref of refs) {\n const datasetId = pathToDatasetId.get(ref.filePath);\n if (!datasetId) continue;\n\n try {\n this.db.run(\n `INSERT OR REPLACE INTO data_code_refs (dataset_id, qualified_name, ref_type, confidence) VALUES (?, ?, ?, ?)`,\n [datasetId, qualifiedName, ref.refType, ref.confidence],\n );\n refsCreated++;\n } catch {\n // Ignore duplicates\n }\n }\n }\n\n return refsCreated;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|