@kentwynn/kgraph 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
2
+ import { Command } from 'commander';
3
3
  export declare function createProgram(): Command;
package/dist/cli/index.js CHANGED
@@ -1,23 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
4
- import { Command } from "commander";
5
- import { registerInitCommand } from "./commands/init.js";
6
- import { registerScanCommand } from "./commands/scan.js";
7
- import { registerUpdateCommand } from "./commands/update.js";
8
- import { registerContextCommand } from "./commands/context.js";
9
- import { registerIntegrateCommand } from "./commands/integrate.js";
10
- import { renderRootHelp } from "./help.js";
2
+ import { Command } from 'commander';
3
+ import { realpathSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { registerContextCommand } from './commands/context.js';
7
+ import { registerInitCommand } from './commands/init.js';
8
+ import { registerIntegrateCommand } from './commands/integrate.js';
9
+ import { registerScanCommand } from './commands/scan.js';
10
+ import { registerUpdateCommand } from './commands/update.js';
11
+ import { renderRootHelp } from './help.js';
12
+ const require = createRequire(import.meta.url);
13
+ const { version } = require('../../package.json');
11
14
  export function createProgram() {
12
15
  const program = new Command();
13
16
  program
14
- .name("kgraph")
15
- .description("Persistent repo intelligence for AI coding assistants")
16
- .version("0.1.2")
17
- .addHelpText("beforeAll", renderRootHelp())
17
+ .name('kgraph')
18
+ .description('Persistent repo intelligence for AI coding assistants')
19
+ .version(version)
20
+ .addHelpText('beforeAll', renderRootHelp())
18
21
  .helpOption(false);
19
- program.option("-h, --help", "Show this help");
20
- program.hook("preAction", (thisCommand) => {
22
+ program.option('-h, --help', 'Show this help');
23
+ program.hook('preAction', (thisCommand) => {
21
24
  if (thisCommand.opts().help) {
22
25
  console.log(renderRootHelp());
23
26
  process.exitCode = 0;
@@ -32,7 +35,9 @@ export function createProgram() {
32
35
  }
33
36
  if (isCliEntrypoint()) {
34
37
  const program = createProgram();
35
- if (process.argv.length <= 2 || process.argv.includes("-h") || process.argv.includes("--help")) {
38
+ if (process.argv.length <= 2 ||
39
+ process.argv.includes('-h') ||
40
+ process.argv.includes('--help')) {
36
41
  console.log(renderRootHelp());
37
42
  }
38
43
  else {
@@ -44,7 +49,8 @@ function isCliEntrypoint() {
44
49
  return false;
45
50
  }
46
51
  try {
47
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
52
+ return (realpathSync(fileURLToPath(import.meta.url)) ===
53
+ realpathSync(process.argv[1]));
48
54
  }
49
55
  catch {
50
56
  return import.meta.url === `file://${process.argv[1]}`;
@@ -1,4 +1,4 @@
1
- import type { KGraphConfig, KGraphWorkspace } from "../types/config.js";
1
+ import type { KGraphConfig, KGraphWorkspace } from '../types/config.js';
2
2
  export declare const DEFAULT_CONFIG: KGraphConfig;
3
3
  export declare function writeDefaultConfig(workspace: KGraphWorkspace): Promise<boolean>;
4
4
  export declare function saveConfig(workspace: KGraphWorkspace, config: KGraphConfig): Promise<void>;
@@ -1,62 +1,62 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
- import YAML from "yaml";
3
- import { pathExists } from "../storage/kgraph-paths.js";
4
- import { KGraphError } from "../cli/errors.js";
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import YAML from 'yaml';
3
+ import { KGraphError } from '../cli/errors.js';
4
+ import { pathExists } from '../storage/kgraph-paths.js';
5
5
  export const DEFAULT_CONFIG = {
6
- include: ["**/*"],
6
+ include: ['**/*'],
7
7
  exclude: [
8
- ".git",
9
- "node_modules",
10
- "dist",
11
- "build",
12
- ".next",
13
- "coverage",
14
- ".kgraph",
15
- ".npm-cache",
16
- ".cache",
17
- ".turbo",
18
- ".vite",
19
- ".nuxt",
20
- ".output",
21
- ".vercel",
22
- ".serverless",
23
- ".agents",
24
- ".specify",
25
- "specs",
26
- ".cursor",
27
- ".claude",
28
- ".github/copilot-instructions.md",
29
- ".github/prompts",
30
- "AGENTS.md",
31
- "CLAUDE.md",
32
- "REQUIREMENTS.md",
33
- "*.log",
34
- "*.tgz",
35
- ".DS_Store"
8
+ '.git',
9
+ 'node_modules',
10
+ 'dist',
11
+ 'build',
12
+ '.next',
13
+ 'coverage',
14
+ '.kgraph',
15
+ '.npm-cache',
16
+ '.cache',
17
+ '.turbo',
18
+ '.vite',
19
+ '.nuxt',
20
+ '.output',
21
+ '.vercel',
22
+ '.serverless',
23
+ '.agents',
24
+ '.specify',
25
+ 'specs',
26
+ '.cursor',
27
+ '.claude',
28
+ '.github/copilot-instructions.md',
29
+ '.github/prompts',
30
+ 'AGENTS.md',
31
+ 'CLAUDE.md',
32
+ 'REQUIREMENTS.md',
33
+ '*.log',
34
+ '*.tgz',
35
+ '.DS_Store',
36
36
  ],
37
37
  languages: {
38
- precise: [".js", ".jsx", ".ts", ".tsx"]
38
+ precise: ['.js', '.jsx', '.ts', '.tsx'],
39
39
  },
40
40
  maxContextItems: 8,
41
41
  domainHints: {},
42
- integrations: []
42
+ integrations: [],
43
43
  };
44
44
  export async function writeDefaultConfig(workspace) {
45
45
  if (await pathExists(workspace.configPath)) {
46
46
  return false;
47
47
  }
48
- await writeFile(workspace.configPath, YAML.stringify(DEFAULT_CONFIG), "utf8");
48
+ await writeFile(workspace.configPath, YAML.stringify(DEFAULT_CONFIG), 'utf8');
49
49
  return true;
50
50
  }
51
51
  export async function saveConfig(workspace, config) {
52
- await writeFile(workspace.configPath, YAML.stringify(config), "utf8");
52
+ await writeFile(workspace.configPath, YAML.stringify(config), 'utf8');
53
53
  }
54
54
  export async function loadConfig(workspace) {
55
55
  if (!(await pathExists(workspace.configPath))) {
56
56
  return DEFAULT_CONFIG;
57
57
  }
58
58
  try {
59
- const raw = await readFile(workspace.configPath, "utf8");
59
+ const raw = await readFile(workspace.configPath, 'utf8');
60
60
  const parsed = YAML.parse(raw);
61
61
  return normalizeConfig(parsed ?? {});
62
62
  }
@@ -67,18 +67,22 @@ export async function loadConfig(workspace) {
67
67
  }
68
68
  export function normalizeConfig(config) {
69
69
  return {
70
- include: Array.isArray(config.include) ? config.include : DEFAULT_CONFIG.include,
70
+ include: Array.isArray(config.include)
71
+ ? config.include
72
+ : DEFAULT_CONFIG.include,
71
73
  exclude: mergeUnique(DEFAULT_CONFIG.exclude, Array.isArray(config.exclude) ? config.exclude : []),
72
74
  languages: {
73
75
  precise: Array.isArray(config.languages?.precise)
74
76
  ? config.languages.precise
75
- : DEFAULT_CONFIG.languages.precise
77
+ : DEFAULT_CONFIG.languages.precise,
76
78
  },
77
- maxContextItems: typeof config.maxContextItems === "number" && config.maxContextItems > 0
79
+ maxContextItems: typeof config.maxContextItems === 'number' && config.maxContextItems > 0
78
80
  ? config.maxContextItems
79
81
  : DEFAULT_CONFIG.maxContextItems,
80
- domainHints: config.domainHints && typeof config.domainHints === "object" ? config.domainHints : {},
81
- integrations: normalizeIntegrations(config.integrations)
82
+ domainHints: config.domainHints && typeof config.domainHints === 'object'
83
+ ? config.domainHints
84
+ : {},
85
+ integrations: normalizeIntegrations(config.integrations),
82
86
  };
83
87
  }
84
88
  function mergeUnique(base, extra) {
@@ -91,23 +95,23 @@ function normalizeIntegrations(value) {
91
95
  const seen = new Set();
92
96
  const integrations = [];
93
97
  for (const item of value) {
94
- if (!item || typeof item !== "object") {
98
+ if (!item || typeof item !== 'object') {
95
99
  continue;
96
100
  }
97
101
  const candidate = item;
98
- if (typeof candidate.name !== "string" ||
99
- typeof candidate.targetPath !== "string" ||
102
+ if (typeof candidate.name !== 'string' ||
103
+ typeof candidate.targetPath !== 'string' ||
100
104
  seen.has(candidate.name)) {
101
105
  continue;
102
106
  }
103
- if (!["claude-code", "codex", "copilot", "cursor"].includes(candidate.name)) {
107
+ if (!['claude-code', 'codex', 'copilot', 'cursor'].includes(candidate.name)) {
104
108
  continue;
105
109
  }
106
110
  seen.add(candidate.name);
107
111
  integrations.push({
108
112
  name: candidate.name,
109
113
  enabled: candidate.enabled !== false,
110
- targetPath: candidate.targetPath
114
+ targetPath: candidate.targetPath,
111
115
  });
112
116
  }
113
117
  return integrations;
@@ -1,2 +1,2 @@
1
- import type { IntegrationAdapter } from "../integration-registry.js";
1
+ import type { IntegrationAdapter } from '../integration-registry.js';
2
2
  export declare const claudeCodeAdapter: IntegrationAdapter;
@@ -1,7 +1,7 @@
1
1
  export const claudeCodeAdapter = {
2
- name: "claude-code",
3
- label: "Claude Code",
4
- targetPath: "CLAUDE.md",
2
+ name: 'claude-code',
3
+ label: 'Claude Code',
4
+ targetPath: 'CLAUDE.md',
5
5
  instructions: `## KGraph Workflow
6
6
 
7
7
  - Start repository work by checking \`kgraph context "<topic>"\` when the user asks about a domain, bug, workflow, or feature.
@@ -12,7 +12,7 @@ export const claudeCodeAdapter = {
12
12
  `,
13
13
  commandFiles: [
14
14
  {
15
- path: ".claude/commands/kgraph.md",
15
+ path: '.claude/commands/kgraph.md',
16
16
  content: `Use KGraph persistent repo intelligence for the current request.
17
17
 
18
18
  1. Infer the topic from the user's request.
@@ -20,8 +20,18 @@ export const claudeCodeAdapter = {
20
20
  3. Use the returned files, symbols, relationships, and cognition before broad exploration.
21
21
  4. Save durable discoveries to \`.kgraph/inbox/\` and run \`kgraph update\` when appropriate.
22
22
  5. Run \`kgraph scan\` after structural changes and report the scan summary.
23
- `
24
- }
23
+ `,
24
+ },
25
+ {
26
+ path: '.claude/commands/kgraph-scan.md',
27
+ content: `Run \`kgraph scan\` to refresh the repository maps, then summarize what changed.
28
+ `,
29
+ },
30
+ {
31
+ path: '.claude/commands/kgraph-update.md',
32
+ content: `Run \`kgraph update\` to process any pending Markdown notes in \`.kgraph/inbox/\` into durable cognition.
33
+ `,
34
+ },
25
35
  ],
26
- obsoleteCommandFiles: [".claude/commands/kgraph-update.md", ".claude/commands/kgraph-scan.md"]
36
+ obsoleteCommandFiles: [],
27
37
  };
@@ -1,2 +1,2 @@
1
- import type { IntegrationAdapter } from "../integration-registry.js";
1
+ import type { IntegrationAdapter } from '../integration-registry.js';
2
2
  export declare const copilotAdapter: IntegrationAdapter;
@@ -1,7 +1,7 @@
1
1
  export const copilotAdapter = {
2
- name: "copilot",
3
- label: "GitHub Copilot",
4
- targetPath: ".github/copilot-instructions.md",
2
+ name: 'copilot',
3
+ label: 'GitHub Copilot',
4
+ targetPath: '.github/copilot-instructions.md',
5
5
  instructions: `## KGraph Workflow
6
6
 
7
7
  - Use \`kgraph context "<topic>"\` before scanning many files manually.
@@ -12,7 +12,7 @@ export const copilotAdapter = {
12
12
  `,
13
13
  commandFiles: [
14
14
  {
15
- path: ".github/prompts/kgraph.prompt.md",
15
+ path: '.github/prompts/kgraph.prompt.md',
16
16
  content: `---
17
17
  mode: agent
18
18
  description: Use KGraph persistent repo intelligence for this request
@@ -25,8 +25,28 @@ Use KGraph for the current task.
25
25
  3. If you discover durable architecture, debugging, workflow, or gotcha knowledge, create a Markdown note in \`.kgraph/inbox/\`.
26
26
  4. If you add an inbox note, run \`kgraph update\`.
27
27
  5. If code structure changed, run \`kgraph scan\`.
28
- `
29
- }
28
+ `,
29
+ },
30
+ {
31
+ path: '.github/prompts/kgraph-scan.prompt.md',
32
+ content: `---
33
+ mode: agent
34
+ description: Refresh KGraph file, symbol, import, and relationship maps
35
+ ---
36
+
37
+ Run \`kgraph scan\` to refresh the repository maps, then summarize what changed.
38
+ `,
39
+ },
40
+ {
41
+ path: '.github/prompts/kgraph-update.prompt.md',
42
+ content: `---
43
+ mode: agent
44
+ description: Process KGraph inbox notes into durable cognition
45
+ ---
46
+
47
+ Run \`kgraph update\` to process any pending Markdown notes in \`.kgraph/inbox/\` into durable cognition.
48
+ `,
49
+ },
30
50
  ],
31
- obsoleteCommandFiles: [".github/prompts/kgraph-update.prompt.md", ".github/prompts/kgraph-scan.prompt.md"]
51
+ obsoleteCommandFiles: [],
32
52
  };
@@ -1,5 +1,6 @@
1
- import type { KGraphConfig } from "../types/config.js";
1
+ import type { KGraphConfig } from '../types/config.js';
2
2
  export declare function shouldExclude(repoPath: string, config: KGraphConfig): boolean;
3
3
  export declare function buildFastGlobIgnore(exclude: string[]): string[];
4
+ export declare function readGitignorePatterns(rootPath: string): Promise<string[]>;
4
5
  export declare function detectLanguage(filePath: string): string;
5
6
  export declare function isPreciseLanguage(filePath: string, config: KGraphConfig): boolean;
@@ -1,13 +1,88 @@
1
- import path from "node:path";
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
2
3
  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"
4
+ // JavaScript / TypeScript
5
+ '.js': 'javascript',
6
+ '.jsx': 'javascriptreact',
7
+ '.ts': 'typescript',
8
+ '.tsx': 'typescriptreact',
9
+ '.mjs': 'javascript',
10
+ '.cjs': 'javascript',
11
+ '.mts': 'typescript',
12
+ '.cts': 'typescript',
13
+ // Python
14
+ '.py': 'python',
15
+ '.pyw': 'python',
16
+ '.pyi': 'python',
17
+ // Go
18
+ '.go': 'go',
19
+ // Rust
20
+ '.rs': 'rust',
21
+ // Java / JVM
22
+ '.java': 'java',
23
+ '.kt': 'kotlin',
24
+ '.kts': 'kotlin',
25
+ '.scala': 'scala',
26
+ '.groovy': 'groovy',
27
+ // C / C++
28
+ '.c': 'c',
29
+ '.h': 'c',
30
+ '.cpp': 'cpp',
31
+ '.cc': 'cpp',
32
+ '.cxx': 'cpp',
33
+ '.hpp': 'cpp',
34
+ '.hxx': 'cpp',
35
+ // C#
36
+ '.cs': 'csharp',
37
+ // Ruby
38
+ '.rb': 'ruby',
39
+ '.rake': 'ruby',
40
+ // PHP
41
+ '.php': 'php',
42
+ // Swift
43
+ '.swift': 'swift',
44
+ // Shell
45
+ '.sh': 'shell',
46
+ '.bash': 'shell',
47
+ '.zsh': 'shell',
48
+ '.fish': 'shell',
49
+ // Web
50
+ '.html': 'html',
51
+ '.htm': 'html',
52
+ '.css': 'css',
53
+ '.scss': 'scss',
54
+ '.sass': 'sass',
55
+ '.less': 'less',
56
+ '.vue': 'vue',
57
+ '.svelte': 'svelte',
58
+ // Data / Config
59
+ '.json': 'json',
60
+ '.jsonc': 'json',
61
+ '.yaml': 'yaml',
62
+ '.yml': 'yaml',
63
+ '.toml': 'toml',
64
+ '.xml': 'xml',
65
+ '.graphql': 'graphql',
66
+ '.gql': 'graphql',
67
+ // Docs
68
+ '.md': 'markdown',
69
+ '.mdx': 'markdown',
70
+ '.rst': 'restructuredtext',
71
+ '.tex': 'latex',
72
+ // Other
73
+ '.lua': 'lua',
74
+ '.r': 'r',
75
+ '.R': 'r',
76
+ '.dart': 'dart',
77
+ '.ex': 'elixir',
78
+ '.exs': 'elixir',
79
+ '.erl': 'erlang',
80
+ '.hrl': 'erlang',
81
+ '.hs': 'haskell',
82
+ '.clj': 'clojure',
83
+ '.tf': 'terraform',
84
+ '.proto': 'protobuf',
85
+ '.sql': 'sql',
11
86
  };
12
87
  export function shouldExclude(repoPath, config) {
13
88
  const normalizedPath = normalizeRepoPath(repoPath);
@@ -16,7 +91,7 @@ export function shouldExclude(repoPath, config) {
16
91
  export function buildFastGlobIgnore(exclude) {
17
92
  const patterns = new Set();
18
93
  for (const pattern of exclude) {
19
- const normalized = normalizeRepoPath(pattern).replace(/\/$/, "");
94
+ const normalized = normalizeRepoPath(pattern).replace(/\/$/, '');
20
95
  if (!normalized) {
21
96
  continue;
22
97
  }
@@ -32,50 +107,63 @@ export function buildFastGlobIgnore(exclude) {
32
107
  }
33
108
  return [...patterns];
34
109
  }
110
+ export async function readGitignorePatterns(rootPath) {
111
+ try {
112
+ const raw = await readFile(path.join(rootPath, '.gitignore'), 'utf8');
113
+ return raw
114
+ .split('\n')
115
+ .map((line) => line.trim())
116
+ .filter((line) => line.length > 0 && !line.startsWith('#') && !line.startsWith('!'));
117
+ }
118
+ catch {
119
+ return [];
120
+ }
121
+ }
35
122
  export function detectLanguage(filePath) {
36
- return LANGUAGE_BY_EXTENSION[path.extname(filePath)] ?? "unknown";
123
+ return LANGUAGE_BY_EXTENSION[path.extname(filePath)] ?? 'unknown';
37
124
  }
38
125
  export function isPreciseLanguage(filePath, config) {
39
126
  return config.languages.precise.includes(path.extname(filePath));
40
127
  }
41
128
  function matchesExcludePattern(repoPath, pattern) {
42
- const normalized = normalizeRepoPath(pattern).replace(/\/$/, "");
129
+ const normalized = normalizeRepoPath(pattern).replace(/\/$/, '');
43
130
  if (!normalized) {
44
131
  return false;
45
132
  }
46
133
  if (hasGlob(normalized)) {
47
- return globToRegExp(normalized).test(repoPath) || globToRegExp(`**/${normalized}`).test(repoPath);
134
+ return (globToRegExp(normalized).test(repoPath) ||
135
+ globToRegExp(`**/${normalized}`).test(repoPath));
48
136
  }
49
137
  if (repoPath === normalized || repoPath.startsWith(`${normalized}/`)) {
50
138
  return true;
51
139
  }
52
- if (!normalized.includes("/")) {
53
- return repoPath.split("/").includes(normalized);
140
+ if (!normalized.includes('/')) {
141
+ return repoPath.split('/').includes(normalized);
54
142
  }
55
143
  return false;
56
144
  }
57
145
  function normalizeRepoPath(value) {
58
- return value.replace(/\\/g, "/").replace(/^\.\/+/, "");
146
+ return value.replace(/\\/g, '/').replace(/^\.\/+/, '');
59
147
  }
60
148
  function hasGlob(pattern) {
61
149
  return /[*?[\]{}]/.test(pattern);
62
150
  }
63
151
  function globToRegExp(pattern) {
64
- let source = "";
152
+ let source = '';
65
153
  for (let index = 0; index < pattern.length; index += 1) {
66
154
  const char = pattern[index];
67
155
  const next = pattern[index + 1];
68
- if (char === "*" && next === "*") {
69
- source += ".*";
156
+ if (char === '*' && next === '*') {
157
+ source += '.*';
70
158
  index += 1;
71
159
  continue;
72
160
  }
73
- if (char === "*") {
74
- source += "[^/]*";
161
+ if (char === '*') {
162
+ source += '[^/]*';
75
163
  continue;
76
164
  }
77
- if (char === "?") {
78
- source += "[^/]";
165
+ if (char === '?') {
166
+ source += '[^/]';
79
167
  continue;
80
168
  }
81
169
  source += escapeRegExp(char);
@@ -83,5 +171,5 @@ function globToRegExp(pattern) {
83
171
  return new RegExp(`^${source}$`);
84
172
  }
85
173
  function escapeRegExp(value) {
86
- return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
174
+ return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
87
175
  }
@@ -1,3 +1,3 @@
1
- import type { KGraphConfig } from "../types/config.js";
2
- import type { ScanResult } from "../types/maps.js";
1
+ import type { KGraphConfig } from '../types/config.js';
2
+ import type { ScanResult } from '../types/maps.js';
3
3
  export declare function scanRepository(rootPath: string, config: KGraphConfig, previous?: ScanResult): Promise<ScanResult>;
@@ -1,16 +1,19 @@
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 { buildFastGlobIgnore, detectLanguage, isPreciseLanguage, shouldExclude } from "./file-classifier.js";
6
- import { extractTsSymbols } from "./ts-symbol-extractor.js";
1
+ import fg from 'fast-glob';
2
+ import crypto from 'node:crypto';
3
+ import { readFile, stat } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { buildFastGlobIgnore, detectLanguage, isPreciseLanguage, readGitignorePatterns, shouldExclude, } from './file-classifier.js';
6
+ import { extractTsSymbols } from './ts-symbol-extractor.js';
7
7
  export async function scanRepository(rootPath, config, previous) {
8
+ const gitignorePatterns = await readGitignorePatterns(rootPath);
9
+ const allExcludes = [...config.exclude, ...gitignorePatterns];
10
+ const mergedConfig = { ...config, exclude: allExcludes };
8
11
  const entries = await fg(config.include, {
9
12
  cwd: rootPath,
10
13
  dot: true,
11
14
  onlyFiles: true,
12
15
  unique: true,
13
- ignore: buildFastGlobIgnore(config.exclude)
16
+ ignore: buildFastGlobIgnore(allExcludes),
14
17
  });
15
18
  const files = [];
16
19
  const symbols = [];
@@ -18,14 +21,20 @@ export async function scanRepository(rootPath, config, previous) {
18
21
  const relationships = [];
19
22
  const warnings = [];
20
23
  for (const repoPath of entries.sort()) {
21
- if (shouldExclude(repoPath, config)) {
24
+ if (shouldExclude(repoPath, mergedConfig)) {
22
25
  continue;
23
26
  }
24
27
  const absolutePath = path.join(rootPath, repoPath);
25
28
  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 [info, content] = await Promise.all([
30
+ stat(absolutePath),
31
+ readFile(absolutePath),
32
+ ]);
33
+ const text = content.toString('utf8');
34
+ const contentHash = crypto
35
+ .createHash('sha256')
36
+ .update(content)
37
+ .digest('hex');
29
38
  const file = {
30
39
  id: repoPath,
31
40
  path: repoPath,
@@ -34,8 +43,8 @@ export async function scanRepository(rootPath, config, previous) {
34
43
  sizeBytes: info.size,
35
44
  modifiedAt: info.mtime.toISOString(),
36
45
  contentHash,
37
- scanStatus: isPreciseLanguage(repoPath, config) ? "mapped" : "generic",
38
- warnings: []
46
+ scanStatus: isPreciseLanguage(repoPath, config) ? 'mapped' : 'generic',
47
+ warnings: [],
39
48
  };
40
49
  if (isPreciseLanguage(repoPath, config)) {
41
50
  const extracted = extractTsSymbols(text, repoPath);
@@ -55,9 +64,9 @@ export async function scanRepository(rootPath, config, previous) {
55
64
  extension: path.extname(repoPath),
56
65
  language: detectLanguage(repoPath),
57
66
  sizeBytes: 0,
58
- contentHash: "",
59
- scanStatus: "failed",
60
- warnings: [message]
67
+ contentHash: '',
68
+ scanStatus: 'failed',
69
+ warnings: [message],
61
70
  });
62
71
  }
63
72
  }
@@ -66,18 +75,22 @@ export async function scanRepository(rootPath, config, previous) {
66
75
  }
67
76
  function detectMovedFiles(previousFiles, currentFiles) {
68
77
  const currentPaths = new Set(currentFiles.map((file) => file.path));
69
- const previousByHash = new Map(previousFiles.filter((file) => file.contentHash).map((file) => [file.contentHash, file]));
78
+ const previousByHash = new Map(previousFiles
79
+ .filter((file) => file.contentHash)
80
+ .map((file) => [file.contentHash, file]));
70
81
  const relationships = [];
71
82
  for (const file of currentFiles) {
72
83
  const previous = previousByHash.get(file.contentHash);
73
- if (previous && previous.path !== file.path && !currentPaths.has(previous.path)) {
84
+ if (previous &&
85
+ previous.path !== file.path &&
86
+ !currentPaths.has(previous.path)) {
74
87
  relationships.push({
75
- sourceType: "file",
88
+ sourceType: 'file',
76
89
  sourceId: file.path,
77
- targetType: "file",
90
+ targetType: 'file',
78
91
  targetId: previous.path,
79
- relationshipType: "moved-from",
80
- confidence: "high"
92
+ relationshipType: 'moved-from',
93
+ confidence: 'high',
81
94
  });
82
95
  }
83
96
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kentwynn/kgraph",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Persistent repo intelligence for AI coding assistants.",
5
5
  "type": "module",
6
6
  "bin": {