@kentwynn/kgraph 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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/cli/commands/context.d.ts +2 -0
  4. package/dist/cli/commands/context.js +43 -0
  5. package/dist/cli/commands/init.d.ts +2 -0
  6. package/dist/cli/commands/init.js +10 -0
  7. package/dist/cli/commands/scan.d.ts +2 -0
  8. package/dist/cli/commands/scan.js +32 -0
  9. package/dist/cli/commands/update.d.ts +2 -0
  10. package/dist/cli/commands/update.js +19 -0
  11. package/dist/cli/errors.d.ts +5 -0
  12. package/dist/cli/errors.js +23 -0
  13. package/dist/cli/index.d.ts +3 -0
  14. package/dist/cli/index.js +21 -0
  15. package/dist/cognition/cognition-updater.d.ts +10 -0
  16. package/dist/cognition/cognition-updater.js +77 -0
  17. package/dist/cognition/markdown-note-parser.d.ts +2 -0
  18. package/dist/cognition/markdown-note-parser.js +71 -0
  19. package/dist/config/config.d.ts +5 -0
  20. package/dist/config/config.js +49 -0
  21. package/dist/context/context-query.d.ts +10 -0
  22. package/dist/context/context-query.js +42 -0
  23. package/dist/context/ranking.d.ts +10 -0
  24. package/dist/context/ranking.js +29 -0
  25. package/dist/scanner/file-classifier.d.ts +4 -0
  26. package/dist/scanner/file-classifier.js +24 -0
  27. package/dist/scanner/repo-scanner.d.ts +3 -0
  28. package/dist/scanner/repo-scanner.js +85 -0
  29. package/dist/scanner/ts-symbol-extractor.d.ts +8 -0
  30. package/dist/scanner/ts-symbol-extractor.js +99 -0
  31. package/dist/storage/cognition-store.d.ts +9 -0
  32. package/dist/storage/cognition-store.js +90 -0
  33. package/dist/storage/kgraph-paths.d.ts +7 -0
  34. package/dist/storage/kgraph-paths.js +65 -0
  35. package/dist/storage/map-store.d.ts +11 -0
  36. package/dist/storage/map-store.js +60 -0
  37. package/dist/types/cognition.d.ts +43 -0
  38. package/dist/types/cognition.js +1 -0
  39. package/dist/types/config.d.ts +24 -0
  40. package/dist/types/config.js +1 -0
  41. package/dist/types/maps.d.ts +62 -0
  42. package/dist/types/maps.js +1 -0
  43. package/package.json +54 -0
@@ -0,0 +1,42 @@
1
+ import { readCognitionNotes, readDomainRecords } from "../storage/cognition-store.js";
2
+ import { rankByFields } from "./ranking.js";
3
+ export async function queryContext(workspace, config, maps, query) {
4
+ const cognition = await readCognitionNotes(workspace);
5
+ const domains = await readDomainRecords(workspace);
6
+ const max = config.maxContextItems;
7
+ const relevantFiles = rankByFields(query, maps.fileMap.files, [
8
+ { name: "path", value: (file) => file.path },
9
+ { name: "language", value: (file) => file.language }
10
+ ]).slice(0, max);
11
+ const relevantSymbols = rankByFields(query, maps.symbolMap.symbols, [
12
+ { name: "name", value: (symbol) => symbol.name },
13
+ { name: "path", value: (symbol) => symbol.filePath },
14
+ { name: "kind", value: (symbol) => symbol.kind }
15
+ ]).slice(0, max);
16
+ const relevantCognition = rankByFields(query, cognition, [
17
+ { name: "title", value: (note) => note.title },
18
+ { name: "domain", value: (note) => note.domain },
19
+ { name: "tags", value: (note) => note.tags },
20
+ { name: "files", value: (note) => note.relatedFiles },
21
+ { name: "symbols", value: (note) => note.relatedSymbols },
22
+ { name: "summary", value: (note) => note.summary }
23
+ ]).slice(0, max);
24
+ const matchedDomains = rankByFields(query, domains, [
25
+ { name: "name", value: (domain) => domain.name },
26
+ { name: "tags", value: (domain) => domain.tags },
27
+ { name: "path", value: (domain) => domain.pathHints }
28
+ ]).slice(0, max);
29
+ const staleReferences = cognition
30
+ .filter((note) => note.referencesStatus === "stale" || note.referencesStatus === "unresolved" || note.referencesStatus === "mixed")
31
+ .flatMap((note) => [...note.relatedFiles, ...note.relatedSymbols].map((ref) => `${note.title}: ${ref}`));
32
+ return {
33
+ query,
34
+ matchedDomains,
35
+ relevantFiles,
36
+ relevantSymbols,
37
+ relevantCognition,
38
+ relationships: maps.relationshipMap.relationships.slice(0, max),
39
+ staleReferences,
40
+ warnings: []
41
+ };
42
+ }
@@ -0,0 +1,10 @@
1
+ export interface Ranked<T> {
2
+ item: T;
3
+ score: number;
4
+ reasons: string[];
5
+ }
6
+ export declare function tokenize(query: string): string[];
7
+ export declare function rankByFields<T>(query: string, items: T[], fields: Array<{
8
+ name: string;
9
+ value: (item: T) => string | string[] | undefined;
10
+ }>): Ranked<T>[];
@@ -0,0 +1,29 @@
1
+ export function tokenize(query) {
2
+ return query
3
+ .toLowerCase()
4
+ .split(/[^a-z0-9_$./-]+/)
5
+ .map((token) => token.trim())
6
+ .filter(Boolean);
7
+ }
8
+ export function rankByFields(query, items, fields) {
9
+ const tokens = tokenize(query);
10
+ return items
11
+ .map((item) => {
12
+ let score = 0;
13
+ const reasons = [];
14
+ for (const field of fields) {
15
+ const value = field.value(item);
16
+ const values = Array.isArray(value) ? value : value ? [value] : [];
17
+ const haystack = values.join(" ").toLowerCase();
18
+ for (const token of tokens) {
19
+ if (haystack.includes(token)) {
20
+ score += field.name === "path" || field.name === "name" ? 3 : 1;
21
+ reasons.push(`${field.name} matched "${token}"`);
22
+ }
23
+ }
24
+ }
25
+ return { item, score, reasons };
26
+ })
27
+ .filter((ranked) => ranked.score > 0)
28
+ .sort((a, b) => b.score - a.score);
29
+ }
@@ -0,0 +1,4 @@
1
+ import type { KGraphConfig } from "../types/config.js";
2
+ export declare function shouldExclude(repoPath: string, config: KGraphConfig): boolean;
3
+ export declare function detectLanguage(filePath: string): string;
4
+ export declare function isPreciseLanguage(filePath: string, config: KGraphConfig): boolean;
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ const LANGUAGE_BY_EXTENSION = {
3
+ ".js": "javascript",
4
+ ".jsx": "javascriptreact",
5
+ ".ts": "typescript",
6
+ ".tsx": "typescriptreact",
7
+ ".json": "json",
8
+ ".md": "markdown",
9
+ ".yaml": "yaml",
10
+ ".yml": "yaml"
11
+ };
12
+ export function shouldExclude(repoPath, config) {
13
+ const parts = repoPath.split(/[\\/]/);
14
+ return config.exclude.some((pattern) => {
15
+ const normalized = pattern.replace(/\/$/, "");
16
+ return parts.includes(normalized) || repoPath === normalized || repoPath.startsWith(`${normalized}/`);
17
+ });
18
+ }
19
+ export function detectLanguage(filePath) {
20
+ return LANGUAGE_BY_EXTENSION[path.extname(filePath)] ?? "unknown";
21
+ }
22
+ export function isPreciseLanguage(filePath, config) {
23
+ return config.languages.precise.includes(path.extname(filePath));
24
+ }
@@ -0,0 +1,3 @@
1
+ import type { KGraphConfig } from "../types/config.js";
2
+ import type { ScanResult } from "../types/maps.js";
3
+ export declare function scanRepository(rootPath: string, config: KGraphConfig, previous?: ScanResult): Promise<ScanResult>;
@@ -0,0 +1,85 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import crypto from "node:crypto";
3
+ import path from "node:path";
4
+ import fg from "fast-glob";
5
+ import { detectLanguage, isPreciseLanguage, shouldExclude } from "./file-classifier.js";
6
+ import { extractTsSymbols } from "./ts-symbol-extractor.js";
7
+ export async function scanRepository(rootPath, config, previous) {
8
+ const entries = await fg(config.include, {
9
+ cwd: rootPath,
10
+ dot: true,
11
+ onlyFiles: true,
12
+ unique: true,
13
+ ignore: config.exclude.map((item) => `**/${item}/**`)
14
+ });
15
+ const files = [];
16
+ const symbols = [];
17
+ const dependencies = [];
18
+ const relationships = [];
19
+ const warnings = [];
20
+ for (const repoPath of entries.sort()) {
21
+ if (shouldExclude(repoPath, config)) {
22
+ continue;
23
+ }
24
+ const absolutePath = path.join(rootPath, repoPath);
25
+ try {
26
+ const [info, content] = await Promise.all([stat(absolutePath), readFile(absolutePath)]);
27
+ const text = content.toString("utf8");
28
+ const contentHash = crypto.createHash("sha256").update(content).digest("hex");
29
+ const file = {
30
+ id: repoPath,
31
+ path: repoPath,
32
+ extension: path.extname(repoPath),
33
+ language: detectLanguage(repoPath),
34
+ sizeBytes: info.size,
35
+ modifiedAt: info.mtime.toISOString(),
36
+ contentHash,
37
+ scanStatus: isPreciseLanguage(repoPath, config) ? "mapped" : "generic",
38
+ warnings: []
39
+ };
40
+ if (isPreciseLanguage(repoPath, config)) {
41
+ const extracted = extractTsSymbols(text, repoPath);
42
+ symbols.push(...extracted.symbols);
43
+ dependencies.push(...extracted.dependencies);
44
+ relationships.push(...extracted.relationships);
45
+ file.warnings.push(...extracted.warnings);
46
+ }
47
+ files.push(file);
48
+ }
49
+ catch (error) {
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ warnings.push(`${repoPath}: ${message}`);
52
+ files.push({
53
+ id: repoPath,
54
+ path: repoPath,
55
+ extension: path.extname(repoPath),
56
+ language: detectLanguage(repoPath),
57
+ sizeBytes: 0,
58
+ contentHash: "",
59
+ scanStatus: "failed",
60
+ warnings: [message]
61
+ });
62
+ }
63
+ }
64
+ relationships.push(...detectMovedFiles(previous?.files ?? [], files));
65
+ return { files, symbols, dependencies, relationships, warnings };
66
+ }
67
+ function detectMovedFiles(previousFiles, currentFiles) {
68
+ const currentPaths = new Set(currentFiles.map((file) => file.path));
69
+ const previousByHash = new Map(previousFiles.filter((file) => file.contentHash).map((file) => [file.contentHash, file]));
70
+ const relationships = [];
71
+ for (const file of currentFiles) {
72
+ const previous = previousByHash.get(file.contentHash);
73
+ if (previous && previous.path !== file.path && !currentPaths.has(previous.path)) {
74
+ relationships.push({
75
+ sourceType: "file",
76
+ sourceId: file.path,
77
+ targetType: "file",
78
+ targetId: previous.path,
79
+ relationshipType: "moved-from",
80
+ confidence: "high"
81
+ });
82
+ }
83
+ }
84
+ return relationships;
85
+ }
@@ -0,0 +1,8 @@
1
+ import type { CodeSymbol, Dependency, Relationship } from "../types/maps.js";
2
+ export interface SymbolExtractionResult {
3
+ symbols: CodeSymbol[];
4
+ dependencies: Dependency[];
5
+ relationships: Relationship[];
6
+ warnings: string[];
7
+ }
8
+ export declare function extractTsSymbols(sourceText: string, filePath: string): SymbolExtractionResult;
@@ -0,0 +1,99 @@
1
+ import path from "node:path";
2
+ import ts from "typescript";
3
+ export function extractTsSymbols(sourceText, filePath) {
4
+ const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
5
+ const symbols = [];
6
+ const dependencies = [];
7
+ const relationships = [];
8
+ const warnings = [];
9
+ const addSymbol = (name, kind, node, exported = false, parentName) => {
10
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
11
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
12
+ const id = [filePath, kind, parentName, name, start.line + 1, end.line + 1].filter(Boolean).join("#");
13
+ symbols.push({
14
+ id,
15
+ name,
16
+ kind,
17
+ filePath,
18
+ startLine: start.line + 1,
19
+ endLine: end.line + 1,
20
+ exported,
21
+ parentName
22
+ });
23
+ relationships.push({
24
+ sourceType: "file",
25
+ sourceId: filePath,
26
+ targetType: "symbol",
27
+ targetId: id,
28
+ relationshipType: "contains",
29
+ confidence: "high"
30
+ });
31
+ };
32
+ const visit = (node, parentName) => {
33
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
34
+ const specifier = node.moduleSpecifier.text;
35
+ const dependency = {
36
+ fromFile: filePath,
37
+ specifier,
38
+ resolvedFile: resolveLocalImport(filePath, specifier),
39
+ kind: specifier.startsWith(".") ? "local" : "package"
40
+ };
41
+ dependencies.push(dependency);
42
+ addSymbol(specifier, "import", node);
43
+ relationships.push({
44
+ sourceType: "file",
45
+ sourceId: filePath,
46
+ targetType: dependency.kind === "local" ? "file" : "package",
47
+ targetId: dependency.resolvedFile ?? specifier,
48
+ relationshipType: "import",
49
+ confidence: dependency.resolvedFile ? "high" : "medium"
50
+ });
51
+ }
52
+ if (ts.isExportDeclaration(node)) {
53
+ const name = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : "export";
54
+ addSymbol(name, "export", node, true);
55
+ }
56
+ if (ts.isFunctionDeclaration(node) && node.name) {
57
+ addSymbol(node.name.text, "function", node, isExported(node), parentName);
58
+ }
59
+ if (ts.isVariableStatement(node)) {
60
+ for (const declaration of node.declarationList.declarations) {
61
+ if (ts.isIdentifier(declaration.name) &&
62
+ declaration.initializer &&
63
+ (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) {
64
+ addSymbol(declaration.name.text, "function", declaration, isExported(node), parentName);
65
+ }
66
+ }
67
+ }
68
+ if (ts.isClassDeclaration(node) && node.name) {
69
+ addSymbol(node.name.text, "class", node, isExported(node), parentName);
70
+ node.members.forEach((member) => {
71
+ if (ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
72
+ addSymbol(member.name.text, "method", member, false, node.name?.text);
73
+ }
74
+ });
75
+ }
76
+ ts.forEachChild(node, (child) => visit(child, parentName));
77
+ };
78
+ try {
79
+ visit(sourceFile);
80
+ }
81
+ catch (error) {
82
+ warnings.push(error instanceof Error ? error.message : String(error));
83
+ }
84
+ return { symbols, dependencies, relationships, warnings };
85
+ }
86
+ function isExported(node) {
87
+ return ts.canHaveModifiers(node) && Boolean(ts.getModifiers(node)?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword));
88
+ }
89
+ function resolveLocalImport(fromFile, specifier) {
90
+ if (!specifier.startsWith(".")) {
91
+ return undefined;
92
+ }
93
+ const joined = path.posix.normalize(path.posix.join(path.posix.dirname(fromFile), specifier));
94
+ const ext = path.posix.extname(joined);
95
+ if (ext) {
96
+ return joined;
97
+ }
98
+ return `${joined}.ts`;
99
+ }
@@ -0,0 +1,9 @@
1
+ import type { KGraphWorkspace } from "../types/config.js";
2
+ import type { CognitionNote, DomainRecord } from "../types/cognition.js";
3
+ export declare function listInboxNotes(workspace: KGraphWorkspace): Promise<string[]>;
4
+ export declare function archiveInboxNote(workspace: KGraphWorkspace, inboxPath: string, timestamp: string): Promise<string>;
5
+ export declare function writeCognitionNote(workspace: KGraphWorkspace, note: CognitionNote): Promise<string>;
6
+ export declare function writeDomainRecord(workspace: KGraphWorkspace, domain: DomainRecord): Promise<string>;
7
+ export declare function readCognitionNotes(workspace: KGraphWorkspace): Promise<CognitionNote[]>;
8
+ export declare function readDomainRecords(workspace: KGraphWorkspace): Promise<DomainRecord[]>;
9
+ export declare function slugify(value: string): string;
@@ -0,0 +1,90 @@
1
+ import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathExists } from "./kgraph-paths.js";
4
+ export async function listInboxNotes(workspace) {
5
+ if (!(await pathExists(workspace.inboxPath))) {
6
+ return [];
7
+ }
8
+ const entries = await readdir(workspace.inboxPath, { withFileTypes: true });
9
+ return entries
10
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
11
+ .map((entry) => path.join(workspace.inboxPath, entry.name))
12
+ .sort();
13
+ }
14
+ export async function archiveInboxNote(workspace, inboxPath, timestamp) {
15
+ await mkdir(workspace.processedInteractionsPath, { recursive: true });
16
+ const target = path.join(workspace.processedInteractionsPath, `${timestamp}-${path.basename(inboxPath)}`);
17
+ await rename(inboxPath, target);
18
+ return target;
19
+ }
20
+ export async function writeCognitionNote(workspace, note) {
21
+ await mkdir(workspace.cognitionPath, { recursive: true });
22
+ const filePath = path.join(workspace.cognitionPath, `${note.id}.md`);
23
+ await writeFile(filePath, renderCognitionNote(note), "utf8");
24
+ return filePath;
25
+ }
26
+ export async function writeDomainRecord(workspace, domain) {
27
+ await mkdir(workspace.domainsPath, { recursive: true });
28
+ const filePath = path.join(workspace.domainsPath, `${slugify(domain.name)}.md`);
29
+ await writeFile(filePath, renderDomainRecord(domain), "utf8");
30
+ return filePath;
31
+ }
32
+ export async function readCognitionNotes(workspace) {
33
+ if (!(await pathExists(workspace.cognitionPath))) {
34
+ return [];
35
+ }
36
+ const entries = await readdir(workspace.cognitionPath, { withFileTypes: true });
37
+ const notes = [];
38
+ for (const entry of entries) {
39
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
40
+ continue;
41
+ }
42
+ const filePath = path.join(workspace.cognitionPath, entry.name);
43
+ const raw = await readFile(filePath, "utf8");
44
+ const encoded = raw.match(/```json\n([\s\S]*?)\n```/);
45
+ if (encoded) {
46
+ notes.push(JSON.parse(encoded[1]));
47
+ }
48
+ }
49
+ return notes;
50
+ }
51
+ export async function readDomainRecords(workspace) {
52
+ if (!(await pathExists(workspace.domainsPath))) {
53
+ return [];
54
+ }
55
+ const entries = await readdir(workspace.domainsPath, { withFileTypes: true });
56
+ const domains = [];
57
+ for (const entry of entries) {
58
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
59
+ continue;
60
+ }
61
+ const raw = await readFile(path.join(workspace.domainsPath, entry.name), "utf8");
62
+ const encoded = raw.match(/```json\n([\s\S]*?)\n```/);
63
+ if (encoded) {
64
+ domains.push(JSON.parse(encoded[1]));
65
+ }
66
+ }
67
+ return domains;
68
+ }
69
+ export function slugify(value) {
70
+ return value
71
+ .trim()
72
+ .toLowerCase()
73
+ .replace(/[^a-z0-9]+/g, "-")
74
+ .replace(/^-|-$/g, "");
75
+ }
76
+ function renderCognitionNote(note) {
77
+ const sectionText = Object.entries(note.sections)
78
+ .map(([heading, content]) => `## ${heading}\n\n${content.trim()}`)
79
+ .join("\n\n");
80
+ return `# ${note.title}\n\nStatus: ${note.referencesStatus}\n\n${sectionText}\n\n## KGraph Metadata\n\n\`\`\`json\n${JSON.stringify(note, null, 2)}\n\`\`\`\n`;
81
+ }
82
+ function renderDomainRecord(domain) {
83
+ return `# ${domain.name}\n\n${domain.description ?? ""}\n\n## Files\n\n${domain.files
84
+ .map((file) => `- ${file}`)
85
+ .join("\n")}\n\n## Symbols\n\n${domain.symbols
86
+ .map((symbol) => `- ${symbol}`)
87
+ .join("\n")}\n\n## Cognition Notes\n\n${domain.cognitionNotes
88
+ .map((note) => `- ${note}`)
89
+ .join("\n")}\n\n## KGraph Metadata\n\n\`\`\`json\n${JSON.stringify(domain, null, 2)}\n\`\`\`\n`;
90
+ }
@@ -0,0 +1,7 @@
1
+ import type { KGraphWorkspace } from "../types/config.js";
2
+ export declare function resolveWorkspace(rootPath?: string): KGraphWorkspace;
3
+ export declare function pathExists(targetPath: string): Promise<boolean>;
4
+ export declare function assertWorkspace(rootPath?: string): Promise<KGraphWorkspace>;
5
+ export declare function ensureWorkspace(rootPath?: string): Promise<KGraphWorkspace>;
6
+ export declare function toRepoPath(rootPath: string, targetPath: string): string;
7
+ export declare function fromRepoPath(rootPath: string, repoPath: string): string;
@@ -0,0 +1,65 @@
1
+ import { access, mkdir, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { KGraphError } from "../cli/errors.js";
4
+ const WORKSPACE_DIRS = [
5
+ "map",
6
+ "cognition",
7
+ "domains",
8
+ "inbox",
9
+ "interactions/processed",
10
+ "context"
11
+ ];
12
+ export function resolveWorkspace(rootPath = process.cwd()) {
13
+ const kgraphPath = path.join(rootPath, ".kgraph");
14
+ return {
15
+ rootPath,
16
+ kgraphPath,
17
+ configPath: path.join(kgraphPath, "config.yaml"),
18
+ mapPath: path.join(kgraphPath, "map"),
19
+ cognitionPath: path.join(kgraphPath, "cognition"),
20
+ domainsPath: path.join(kgraphPath, "domains"),
21
+ inboxPath: path.join(kgraphPath, "inbox"),
22
+ processedInteractionsPath: path.join(kgraphPath, "interactions", "processed"),
23
+ contextPath: path.join(kgraphPath, "context")
24
+ };
25
+ }
26
+ export async function pathExists(targetPath) {
27
+ try {
28
+ await access(targetPath);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ export async function assertWorkspace(rootPath = process.cwd()) {
36
+ const workspace = resolveWorkspace(rootPath);
37
+ if (!(await pathExists(workspace.kgraphPath))) {
38
+ throw new KGraphError("KGraph is not initialized. Run `kgraph init` first.");
39
+ }
40
+ const info = await stat(workspace.kgraphPath);
41
+ if (!info.isDirectory()) {
42
+ throw new KGraphError(".kgraph exists but is not a directory.");
43
+ }
44
+ return workspace;
45
+ }
46
+ export async function ensureWorkspace(rootPath = process.cwd()) {
47
+ const workspace = resolveWorkspace(rootPath);
48
+ if (await pathExists(workspace.kgraphPath)) {
49
+ const info = await stat(workspace.kgraphPath);
50
+ if (!info.isDirectory()) {
51
+ throw new KGraphError(".kgraph exists but is not a directory.");
52
+ }
53
+ }
54
+ await mkdir(workspace.kgraphPath, { recursive: true });
55
+ for (const dir of WORKSPACE_DIRS) {
56
+ await mkdir(path.join(workspace.kgraphPath, dir), { recursive: true });
57
+ }
58
+ return workspace;
59
+ }
60
+ export function toRepoPath(rootPath, targetPath) {
61
+ return path.relative(rootPath, targetPath).split(path.sep).join("/");
62
+ }
63
+ export function fromRepoPath(rootPath, repoPath) {
64
+ return path.join(rootPath, ...repoPath.split("/"));
65
+ }
@@ -0,0 +1,11 @@
1
+ import type { KGraphWorkspace } from "../types/config.js";
2
+ import type { DependencyMap, FileMap, RelationshipMap, ScanResult, SymbolMap } from "../types/maps.js";
3
+ export declare function mapPaths(workspace: KGraphWorkspace): Record<string, string>;
4
+ export declare function readMaps(workspace: KGraphWorkspace): Promise<{
5
+ fileMap: FileMap;
6
+ symbolMap: SymbolMap;
7
+ dependencyMap: DependencyMap;
8
+ relationshipMap: RelationshipMap;
9
+ }>;
10
+ export declare function writeMaps(workspace: KGraphWorkspace, result: ScanResult): Promise<void>;
11
+ export declare function mapsExist(workspace: KGraphWorkspace): Promise<boolean>;
@@ -0,0 +1,60 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathExists } from "./kgraph-paths.js";
4
+ import { KGraphError } from "../cli/errors.js";
5
+ async function readJson(filePath, fallback) {
6
+ if (!(await pathExists(filePath))) {
7
+ return fallback;
8
+ }
9
+ try {
10
+ return JSON.parse(await readFile(filePath, "utf8"));
11
+ }
12
+ catch (error) {
13
+ const message = error instanceof Error ? error.message : String(error);
14
+ throw new KGraphError(`Unable to read JSON map ${filePath}: ${message}`);
15
+ }
16
+ }
17
+ async function writeJson(filePath, value) {
18
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
19
+ }
20
+ export function mapPaths(workspace) {
21
+ return {
22
+ files: path.join(workspace.mapPath, "files.json"),
23
+ symbols: path.join(workspace.mapPath, "symbols.json"),
24
+ dependencies: path.join(workspace.mapPath, "dependencies.json"),
25
+ relationships: path.join(workspace.mapPath, "relationships.json")
26
+ };
27
+ }
28
+ export async function readMaps(workspace) {
29
+ const paths = mapPaths(workspace);
30
+ return {
31
+ fileMap: await readJson(paths.files, { generatedAt: "", files: [] }),
32
+ symbolMap: await readJson(paths.symbols, { generatedAt: "", symbols: [] }),
33
+ dependencyMap: await readJson(paths.dependencies, { generatedAt: "", dependencies: [] }),
34
+ relationshipMap: await readJson(paths.relationships, {
35
+ generatedAt: "",
36
+ relationships: []
37
+ })
38
+ };
39
+ }
40
+ export async function writeMaps(workspace, result) {
41
+ const generatedAt = new Date().toISOString();
42
+ const paths = mapPaths(workspace);
43
+ await writeJson(paths.files, { generatedAt, files: result.files });
44
+ await writeJson(paths.symbols, { generatedAt, symbols: result.symbols });
45
+ await writeJson(paths.dependencies, {
46
+ generatedAt,
47
+ dependencies: result.dependencies
48
+ });
49
+ await writeJson(paths.relationships, {
50
+ generatedAt,
51
+ relationships: result.relationships
52
+ });
53
+ }
54
+ export async function mapsExist(workspace) {
55
+ const paths = mapPaths(workspace);
56
+ return ((await pathExists(paths.files)) &&
57
+ (await pathExists(paths.symbols)) &&
58
+ (await pathExists(paths.dependencies)) &&
59
+ (await pathExists(paths.relationships)));
60
+ }
@@ -0,0 +1,43 @@
1
+ import type { CodeSymbol, Relationship, RepositoryFile } from "./maps.js";
2
+ export type ReferenceStatus = "current" | "stale" | "unresolved" | "mixed";
3
+ export interface ParsedCognitionNote {
4
+ title: string;
5
+ domain?: string;
6
+ tags: string[];
7
+ summary?: string;
8
+ sections: Record<string, string>;
9
+ relatedFiles: string[];
10
+ relatedSymbols: string[];
11
+ warnings: string[];
12
+ }
13
+ export interface CognitionNote extends ParsedCognitionNote {
14
+ id: string;
15
+ sourceInboxPath: string;
16
+ processedPath: string;
17
+ createdAt: string;
18
+ referencesStatus: ReferenceStatus;
19
+ }
20
+ export interface DomainRecord {
21
+ name: string;
22
+ description?: string;
23
+ pathHints: string[];
24
+ tags: string[];
25
+ files: string[];
26
+ symbols: string[];
27
+ cognitionNotes: string[];
28
+ }
29
+ export interface RankedItem<T> {
30
+ item: T;
31
+ score: number;
32
+ reasons: string[];
33
+ }
34
+ export interface ContextResponse {
35
+ query: string;
36
+ matchedDomains: RankedItem<DomainRecord>[];
37
+ relevantFiles: RankedItem<RepositoryFile>[];
38
+ relevantSymbols: RankedItem<CodeSymbol>[];
39
+ relevantCognition: RankedItem<CognitionNote>[];
40
+ relationships: Relationship[];
41
+ staleReferences: string[];
42
+ warnings: string[];
43
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ export interface KGraphConfig {
2
+ include: string[];
3
+ exclude: string[];
4
+ languages: {
5
+ precise: string[];
6
+ };
7
+ maxContextItems: number;
8
+ domainHints: Record<string, DomainHint>;
9
+ }
10
+ export interface DomainHint {
11
+ paths?: string[];
12
+ tags?: string[];
13
+ }
14
+ export interface KGraphWorkspace {
15
+ rootPath: string;
16
+ kgraphPath: string;
17
+ configPath: string;
18
+ mapPath: string;
19
+ cognitionPath: string;
20
+ domainsPath: string;
21
+ inboxPath: string;
22
+ processedInteractionsPath: string;
23
+ contextPath: string;
24
+ }
@@ -0,0 +1 @@
1
+ export {};