@kentwynn/kgraph 0.1.5 → 0.1.7

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 CHANGED
@@ -84,12 +84,12 @@ kgraph context "auth token refresh" --json
84
84
 
85
85
  KGraph writes local instruction files and command/prompt packs so AI tools can use the repository knowledge layer during normal coding chats.
86
86
 
87
- | Integration | Always-on guidance | KGraph command assets |
88
- | --- | --- | --- |
89
- | Codex | `AGENTS.md` | `.agents/skills/kgraph/SKILL.md` |
87
+ | Integration | Always-on guidance | KGraph command assets |
88
+ | -------------- | --------------------------------- | ---------------------------------- |
89
+ | Codex | `AGENTS.md` | `.agents/skills/kgraph/SKILL.md` |
90
90
  | GitHub Copilot | `.github/copilot-instructions.md` | `.github/prompts/kgraph.prompt.md` |
91
- | Cursor | `.cursor/rules/kgraph.mdc` | Built into the KGraph Cursor rule |
92
- | Claude Code | `CLAUDE.md` | `.claude/commands/kgraph.md` |
91
+ | Cursor | `.cursor/rules/kgraph.mdc` | Built into the KGraph Cursor rule |
92
+ | Claude Code | `CLAUDE.md` | `.claude/commands/kgraph.md` |
93
93
 
94
94
  Example:
95
95
 
@@ -150,7 +150,21 @@ KGraph stores project intelligence in local files inside `.kgraph/`. The MVP doe
150
150
  npm install
151
151
  npm run build
152
152
  npm test
153
+ ```
154
+
155
+ Test a command without installing:
156
+
157
+ ```bash
153
158
  npm run kgraph -- init --integrations codex,cursor
159
+ npm run kgraph -- context "auth token refresh"
160
+ ```
161
+
162
+ Install the local build globally to test the `kgraph` binary end-to-end:
163
+
164
+ ```bash
165
+ npm install -g .
166
+ kgraph --version
167
+ kgraph init --integrations codex,copilot
154
168
  ```
155
169
 
156
170
  ## Release
@@ -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,27 +1,33 @@
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
- - Start repository work by checking \`kgraph context "<topic>"\` when the user asks about a domain, bug, workflow, or feature.
8
- - Convert stable discoveries from chat into Markdown notes under \`.kgraph/inbox/\`.
9
- - Run \`kgraph update\` to preserve those notes as durable cognition.
10
- - Run \`kgraph scan\` after structural code changes.
11
- - Run \`kgraph visualize\` when visualization support is available and the user wants to inspect the current knowledge map.
7
+ Before exploring the repository, run \`kgraph context "<topic>"\` to load existing repo intelligence. Use /kgraph for the full automated workflow. Run \`kgraph scan\` and \`kgraph update\` manually when needed.
12
8
  `,
13
9
  commandFiles: [
14
10
  {
15
- path: ".claude/commands/kgraph.md",
11
+ path: '.claude/commands/kgraph.md',
16
12
  content: `Use KGraph persistent repo intelligence for the current request.
17
13
 
18
14
  1. Infer the topic from the user's request.
19
15
  2. Run \`kgraph context "<topic>"\`.
20
16
  3. Use the returned files, symbols, relationships, and cognition before broad exploration.
21
- 4. Save durable discoveries to \`.kgraph/inbox/\` and run \`kgraph update\` when appropriate.
22
- 5. Run \`kgraph scan\` after structural changes and report the scan summary.
23
- `
24
- }
17
+ 4. Save durable discoveries to \`.kgraph/inbox/\` and immediately run \`kgraph update\`.
18
+ 5. If you created, moved, deleted, or renamed files or symbols during this session, run \`kgraph scan\` and report the summary. Skip it otherwise.
19
+ `,
20
+ },
21
+ {
22
+ path: '.claude/commands/kgraph-scan.md',
23
+ content: `Run \`kgraph scan\` to refresh the repository maps, then summarize what changed.
24
+ `,
25
+ },
26
+ {
27
+ path: '.claude/commands/kgraph-update.md',
28
+ content: `Run \`kgraph update\` to process any pending Markdown notes in \`.kgraph/inbox/\` into durable cognition.
29
+ `,
30
+ },
25
31
  ],
26
- obsoleteCommandFiles: [".claude/commands/kgraph-update.md", ".claude/commands/kgraph-scan.md"]
32
+ obsoleteCommandFiles: [],
27
33
  };
@@ -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 codexAdapter: IntegrationAdapter;
@@ -1,35 +1,33 @@
1
1
  export const codexAdapter = {
2
- name: "codex",
3
- label: "Codex",
4
- targetPath: "AGENTS.md",
2
+ name: 'codex',
3
+ label: 'Codex',
4
+ targetPath: 'AGENTS.md',
5
5
  instructions: `## KGraph Workflow
6
6
 
7
- - Before broad repository exploration, run \`kgraph context "<topic>"\` to load current repo intelligence.
8
- - After useful debugging, architecture discovery, workflow learning, or implementation work, write a concise Markdown note into \`.kgraph/inbox/\`.
9
- - Run \`kgraph update\` after adding inbox notes so KGraph preserves durable cognition.
10
- - Run \`kgraph scan\` after file moves, function renames, refactors, or other structural changes.
11
- - When visualization support is available and the developer asks to inspect KGraph, run \`kgraph visualize\` and report the generated local artifact.
7
+ Before exploring the repository, run \`kgraph context "<topic>"\` to load existing repo intelligence. The /kgraph skill handles the full automated workflow. Run \`kgraph scan\` and \`kgraph update\` manually when needed.
12
8
  `,
13
9
  commandFiles: [
14
10
  {
15
- path: ".agents/skills/kgraph/SKILL.md",
11
+ path: '.agents/skills/kgraph/SKILL.md',
16
12
  content: `---
17
- name: "kgraph"
18
- description: "Use KGraph persistent repo intelligence for the current coding task."
13
+ name: kgraph
14
+ description: Use KGraph persistent repo intelligence before broad repository exploration. Use when asked about repo structure, debugging context, architecture decisions, or to avoid rediscovering what is already known.
19
15
  ---
20
16
 
21
- Use this skill when the user asks to use KGraph, requests repo context, or asks you to avoid rediscovering repository structure.
17
+ # KGraph Skill
22
18
 
23
19
  Workflow:
24
20
 
25
21
  1. Infer the current topic from the user request.
26
22
  2. Run \`kgraph context "<topic>"\` before broad repo exploration.
27
- 3. Use KGraph's files, symbols, relationships, and cognition as navigation hints.
28
- 4. After durable discoveries, write a concise Markdown note to \`.kgraph/inbox/\`.
29
- 5. Run \`kgraph update\` if you created an inbox note.
30
- 6. Run \`kgraph scan\` after structural changes.
31
- `
32
- }
23
+ 3. Use KGraph's returned files, symbols, relationships, and cognition as navigation hints.
24
+ 4. After durable discoveries, write a concise Markdown note to \`.kgraph/inbox/\` and immediately run \`kgraph update\`.
25
+ 5. If you created, moved, deleted, or renamed files or symbols during this session, run \`kgraph scan\`. Skip it otherwise.
26
+ `,
27
+ },
28
+ ],
29
+ obsoleteCommandFiles: [
30
+ '.agents/skills/kgraph-update',
31
+ '.agents/skills/kgraph-scan',
33
32
  ],
34
- obsoleteCommandFiles: [".agents/skills/kgraph-update", ".agents/skills/kgraph-scan"]
35
33
  };
@@ -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,32 +1,32 @@
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
- - Use \`kgraph context "<topic>"\` before scanning many files manually.
8
- - Preserve stable findings by creating Markdown notes in \`.kgraph/inbox/\`.
9
- - Use \`kgraph update\` to process chat summaries and debugging conclusions into durable cognition.
10
- - Use \`kgraph scan\` when code structure changes.
11
- - Use \`kgraph visualize\` when visualization support is available and the developer asks to inspect the repository knowledge map.
7
+ Before exploring the repository, run \`kgraph context "<topic>"\` to load existing repo intelligence. Use /kgraph-scan and /kgraph-update for manual maintenance.
12
8
  `,
13
9
  commandFiles: [
14
10
  {
15
- path: ".github/prompts/kgraph.prompt.md",
11
+ path: '.github/prompts/kgraph-scan.prompt.md',
16
12
  content: `---
17
13
  mode: agent
18
- description: Use KGraph persistent repo intelligence for this request
14
+ description: Refresh KGraph file, symbol, import, and relationship maps
19
15
  ---
20
16
 
21
- Use KGraph for the current task.
17
+ Run \`kgraph scan\` to refresh the repository maps, then summarize what changed.
18
+ `,
19
+ },
20
+ {
21
+ path: '.github/prompts/kgraph-update.prompt.md',
22
+ content: `---
23
+ mode: agent
24
+ description: Process KGraph inbox notes into durable cognition
25
+ ---
22
26
 
23
- 1. If the user provided a topic, run \`kgraph context "<topic>"\` first. If not, infer a concise topic from the request.
24
- 2. Use the returned files, symbols, relationships, and cognition before broad repository exploration.
25
- 3. If you discover durable architecture, debugging, workflow, or gotcha knowledge, create a Markdown note in \`.kgraph/inbox/\`.
26
- 4. If you add an inbox note, run \`kgraph update\`.
27
- 5. If code structure changed, run \`kgraph scan\`.
28
- `
29
- }
27
+ Run \`kgraph update\` to process any pending Markdown notes in \`.kgraph/inbox/\` into durable cognition.
28
+ `,
29
+ },
30
30
  ],
31
- obsoleteCommandFiles: [".github/prompts/kgraph-update.prompt.md", ".github/prompts/kgraph-scan.prompt.md"]
31
+ obsoleteCommandFiles: ['.github/prompts/kgraph.prompt.md'],
32
32
  };
@@ -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 cursorAdapter: IntegrationAdapter;
@@ -1,7 +1,7 @@
1
1
  export const cursorAdapter = {
2
- name: "cursor",
3
- label: "Cursor",
4
- targetPath: ".cursor/rules/kgraph.mdc",
2
+ name: 'cursor',
3
+ label: 'Cursor',
4
+ targetPath: '.cursor/rules/kgraph.mdc',
5
5
  instructions: `---
6
6
  description: Use KGraph persistent repo intelligence before broad repository exploration
7
7
  alwaysApply: true
@@ -9,11 +9,7 @@ alwaysApply: true
9
9
 
10
10
  ## KGraph Workflow
11
11
 
12
- - Query \`kgraph context "<topic>"\` before broad file searches when repo cognition may already exist.
13
- - Store durable chat, debugging, architecture, and workflow discoveries as Markdown notes in \`.kgraph/inbox/\`.
14
- - Run \`kgraph update\` after adding useful notes.
15
- - Run \`kgraph scan\` after refactors, moved folders, renamed functions, or other structure changes.
16
- - Run \`kgraph visualize\` when visualization support is available and the developer asks to inspect the KGraph map.
12
+ Before exploring the repository, run \`kgraph context "<topic>"\` to load existing repo intelligence. Run \`kgraph scan\` and \`kgraph update\` manually when needed.
17
13
  `,
18
- obsoleteCommandFiles: [".cursor/rules/kgraph-commands.mdc"]
14
+ obsoleteCommandFiles: ['.cursor/rules/kgraph-commands.mdc'],
19
15
  };
@@ -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.7",
4
4
  "description": "Persistent repo intelligence for AI coding assistants.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "scripts": {
15
15
  "clean": "node scripts/clean-dist.mjs",
16
16
  "build": "npm run clean && tsc -p tsconfig.json",
17
+ "postbuild": "chmod +x dist/cli/index.js",
17
18
  "test": "vitest run",
18
19
  "kgraph": "tsx src/cli/index.ts",
19
20
  "check:artifacts": "node scripts/check-clean-artifacts.mjs",