@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 +19 -5
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +23 -17
- package/dist/config/config.d.ts +1 -1
- package/dist/config/config.js +52 -48
- package/dist/integrations/adapters/claude-code.d.ts +1 -1
- package/dist/integrations/adapters/claude-code.js +20 -14
- package/dist/integrations/adapters/codex.d.ts +1 -1
- package/dist/integrations/adapters/codex.js +17 -19
- package/dist/integrations/adapters/copilot.d.ts +1 -1
- package/dist/integrations/adapters/copilot.js +19 -19
- package/dist/integrations/adapters/cursor.d.ts +1 -1
- package/dist/integrations/adapters/cursor.js +5 -9
- package/dist/scanner/file-classifier.d.ts +2 -1
- package/dist/scanner/file-classifier.js +112 -24
- package/dist/scanner/repo-scanner.d.ts +2 -2
- package/dist/scanner/repo-scanner.js +35 -22
- package/package.json +2 -1
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
|
|
88
|
-
|
|
|
89
|
-
| Codex
|
|
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
|
|
92
|
-
| Claude Code
|
|
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
|
package/dist/cli/index.d.ts
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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(
|
|
15
|
-
.description(
|
|
16
|
-
.version(
|
|
17
|
-
.addHelpText(
|
|
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(
|
|
20
|
-
program.hook(
|
|
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 ||
|
|
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)) ===
|
|
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]}`;
|
package/dist/config/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { KGraphConfig, KGraphWorkspace } from
|
|
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>;
|
package/dist/config/config.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { readFile, writeFile } from
|
|
2
|
-
import YAML from
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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: [
|
|
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),
|
|
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),
|
|
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,
|
|
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)
|
|
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 ===
|
|
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 ===
|
|
81
|
-
|
|
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 !==
|
|
98
|
+
if (!item || typeof item !== 'object') {
|
|
95
99
|
continue;
|
|
96
100
|
}
|
|
97
101
|
const candidate = item;
|
|
98
|
-
if (typeof candidate.name !==
|
|
99
|
-
typeof candidate.targetPath !==
|
|
102
|
+
if (typeof candidate.name !== 'string' ||
|
|
103
|
+
typeof candidate.targetPath !== 'string' ||
|
|
100
104
|
seen.has(candidate.name)) {
|
|
101
105
|
continue;
|
|
102
106
|
}
|
|
103
|
-
if (![
|
|
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
|
|
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:
|
|
3
|
-
label:
|
|
4
|
-
targetPath:
|
|
2
|
+
name: 'claude-code',
|
|
3
|
+
label: 'Claude Code',
|
|
4
|
+
targetPath: 'CLAUDE.md',
|
|
5
5
|
instructions: `## KGraph Workflow
|
|
6
6
|
|
|
7
|
-
|
|
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:
|
|
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
|
|
22
|
-
5.
|
|
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: [
|
|
32
|
+
obsoleteCommandFiles: [],
|
|
27
33
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { IntegrationAdapter } from
|
|
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:
|
|
3
|
-
label:
|
|
4
|
-
targetPath:
|
|
2
|
+
name: 'codex',
|
|
3
|
+
label: 'Codex',
|
|
4
|
+
targetPath: 'AGENTS.md',
|
|
5
5
|
instructions: `## KGraph Workflow
|
|
6
6
|
|
|
7
|
-
|
|
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:
|
|
11
|
+
path: '.agents/skills/kgraph/SKILL.md',
|
|
16
12
|
content: `---
|
|
17
|
-
name:
|
|
18
|
-
description:
|
|
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
|
-
|
|
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.
|
|
30
|
-
|
|
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
|
|
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:
|
|
3
|
-
label:
|
|
4
|
-
targetPath:
|
|
2
|
+
name: 'copilot',
|
|
3
|
+
label: 'GitHub Copilot',
|
|
4
|
+
targetPath: '.github/copilot-instructions.md',
|
|
5
5
|
instructions: `## KGraph Workflow
|
|
6
6
|
|
|
7
|
-
|
|
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:
|
|
11
|
+
path: '.github/prompts/kgraph-scan.prompt.md',
|
|
16
12
|
content: `---
|
|
17
13
|
mode: agent
|
|
18
|
-
description:
|
|
14
|
+
description: Refresh KGraph file, symbol, import, and relationship maps
|
|
19
15
|
---
|
|
20
16
|
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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: [
|
|
31
|
+
obsoleteCommandFiles: ['.github/prompts/kgraph.prompt.md'],
|
|
32
32
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { IntegrationAdapter } from
|
|
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:
|
|
3
|
-
label:
|
|
4
|
-
targetPath:
|
|
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
|
-
|
|
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: [
|
|
14
|
+
obsoleteCommandFiles: ['.cursor/rules/kgraph-commands.mdc'],
|
|
19
15
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { KGraphConfig } from
|
|
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
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
const LANGUAGE_BY_EXTENSION = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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)] ??
|
|
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) ||
|
|
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(
|
|
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,
|
|
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 ===
|
|
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
|
|
2
|
-
import type { ScanResult } from
|
|
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
|
|
2
|
-
import crypto from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { buildFastGlobIgnore, detectLanguage, isPreciseLanguage, shouldExclude } from
|
|
6
|
-
import { extractTsSymbols } from
|
|
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(
|
|
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,
|
|
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([
|
|
27
|
-
|
|
28
|
-
|
|
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) ?
|
|
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:
|
|
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
|
|
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 &&
|
|
84
|
+
if (previous &&
|
|
85
|
+
previous.path !== file.path &&
|
|
86
|
+
!currentPaths.has(previous.path)) {
|
|
74
87
|
relationships.push({
|
|
75
|
-
sourceType:
|
|
88
|
+
sourceType: 'file',
|
|
76
89
|
sourceId: file.path,
|
|
77
|
-
targetType:
|
|
90
|
+
targetType: 'file',
|
|
78
91
|
targetId: previous.path,
|
|
79
|
-
relationshipType:
|
|
80
|
-
confidence:
|
|
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.
|
|
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",
|