@kentwynn/kgraph 0.1.4 → 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.
- package/README.md +6 -6
- 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 +15 -17
- package/dist/integrations/adapters/codex.js +2 -23
- package/dist/integrations/adapters/copilot.d.ts +1 -1
- package/dist/integrations/adapters/copilot.js +16 -17
- package/dist/integrations/adapters/cursor.js +1 -18
- package/dist/integrations/integration-registry.d.ts +1 -0
- package/dist/integrations/integration-store.js +4 -1
- 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 +1 -1
package/README.md
CHANGED
|
@@ -86,10 +86,10 @@ KGraph writes local instruction files and command/prompt packs so AI tools can u
|
|
|
86
86
|
|
|
87
87
|
| Integration | Always-on guidance | KGraph command assets |
|
|
88
88
|
| --- | --- | --- |
|
|
89
|
-
| Codex | `AGENTS.md` | `.agents/skills/kgraph
|
|
90
|
-
| GitHub Copilot | `.github/copilot-instructions.md` | `.github/prompts/kgraph
|
|
91
|
-
| Cursor | `.cursor/rules/kgraph.mdc` |
|
|
92
|
-
| Claude Code | `CLAUDE.md` | `.claude/commands/kgraph
|
|
89
|
+
| Codex | `AGENTS.md` | `.agents/skills/kgraph/SKILL.md` |
|
|
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` |
|
|
93
93
|
|
|
94
94
|
Example:
|
|
95
95
|
|
|
@@ -98,13 +98,13 @@ kgraph integrate add codex copilot cursor claude-code
|
|
|
98
98
|
kgraph integrate list
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
This gives supported
|
|
101
|
+
This gives each supported tool one reusable KGraph entry point similar to a Spec Kit-style command:
|
|
102
102
|
|
|
103
103
|
- KGraph context: query `kgraph context "<topic>"` before broad repo exploration
|
|
104
104
|
- KGraph update: save durable chat/debugging/workflow discoveries to `.kgraph/inbox/`, then run `kgraph update`
|
|
105
105
|
- KGraph scan: run `kgraph scan` after refactors, file moves, renamed functions, or dependency changes
|
|
106
106
|
|
|
107
|
-
The exact invocation depends on the host tool. Copilot uses prompt
|
|
107
|
+
The exact invocation depends on the host tool. Copilot uses one prompt file, Codex uses one skill, Cursor uses one rule, and Claude Code uses one command file. Scan and update are workflows inside that single KGraph entry point, not separate duplicated commands.
|
|
108
108
|
|
|
109
109
|
KGraph-managed instruction blocks preserve existing user-authored content.
|
|
110
110
|
|
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,7 +1,7 @@
|
|
|
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
7
|
- Start repository work by checking \`kgraph context "<topic>"\` when the user asks about a domain, bug, workflow, or feature.
|
|
@@ -12,28 +12,26 @@ export const claudeCodeAdapter = {
|
|
|
12
12
|
`,
|
|
13
13
|
commandFiles: [
|
|
14
14
|
{
|
|
15
|
-
path:
|
|
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.
|
|
19
19
|
2. Run \`kgraph context "<topic>"\`.
|
|
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
|
+
`,
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
|
-
path:
|
|
26
|
-
content: `
|
|
27
|
-
|
|
28
|
-
Create a concise Markdown note in \`.kgraph/inbox/\` from durable discoveries in this conversation, then run \`kgraph update\`.
|
|
29
|
-
`
|
|
26
|
+
path: '.claude/commands/kgraph-scan.md',
|
|
27
|
+
content: `Run \`kgraph scan\` to refresh the repository maps, then summarize what changed.
|
|
28
|
+
`,
|
|
30
29
|
},
|
|
31
30
|
{
|
|
32
|
-
path:
|
|
33
|
-
content: `
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
]
|
|
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
|
+
},
|
|
35
|
+
],
|
|
36
|
+
obsoleteCommandFiles: [],
|
|
39
37
|
};
|
|
@@ -28,29 +28,8 @@ Workflow:
|
|
|
28
28
|
4. After durable discoveries, write a concise Markdown note to \`.kgraph/inbox/\`.
|
|
29
29
|
5. Run \`kgraph update\` if you created an inbox note.
|
|
30
30
|
6. Run \`kgraph scan\` after structural changes.
|
|
31
|
-
`
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
path: ".agents/skills/kgraph-update/SKILL.md",
|
|
35
|
-
content: `---
|
|
36
|
-
name: "kgraph-update"
|
|
37
|
-
description: "Preserve useful chat discoveries into KGraph cognition."
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
Use this skill when the user asks to update KGraph memory or preserve what was learned.
|
|
41
|
-
|
|
42
|
-
Write a concise Markdown note under \`.kgraph/inbox/\` with durable architecture, debugging, workflow, file, symbol, or gotcha knowledge. Then run \`kgraph update\`.
|
|
43
|
-
`
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
path: ".agents/skills/kgraph-scan/SKILL.md",
|
|
47
|
-
content: `---
|
|
48
|
-
name: "kgraph-scan"
|
|
49
|
-
description: "Refresh KGraph structural maps after code changes."
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
Run \`kgraph scan\` after files move, functions are renamed, folders are refactored, or dependencies change. Report the scan summary and any obvious exclude/config problems.
|
|
53
31
|
`
|
|
54
32
|
}
|
|
55
|
-
]
|
|
33
|
+
],
|
|
34
|
+
obsoleteCommandFiles: [".agents/skills/kgraph-update", ".agents/skills/kgraph-scan"]
|
|
56
35
|
};
|
|
@@ -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,7 +1,7 @@
|
|
|
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
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:
|
|
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,29 +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
|
-
|
|
28
|
+
`,
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
|
-
path:
|
|
31
|
+
path: '.github/prompts/kgraph-scan.prompt.md',
|
|
32
32
|
content: `---
|
|
33
33
|
mode: agent
|
|
34
|
-
description:
|
|
34
|
+
description: Refresh KGraph file, symbol, import, and relationship maps
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Capture only durable information: architecture discoveries, debugging conclusions, workflow conventions, important files, important functions, and gotchas. Do not store temporary chatter.
|
|
40
|
-
`
|
|
37
|
+
Run \`kgraph scan\` to refresh the repository maps, then summarize what changed.
|
|
38
|
+
`,
|
|
41
39
|
},
|
|
42
40
|
{
|
|
43
|
-
path:
|
|
41
|
+
path: '.github/prompts/kgraph-update.prompt.md',
|
|
44
42
|
content: `---
|
|
45
43
|
mode: agent
|
|
46
|
-
description:
|
|
44
|
+
description: Process KGraph inbox notes into durable cognition
|
|
47
45
|
---
|
|
48
46
|
|
|
49
|
-
Run \`kgraph
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
]
|
|
47
|
+
Run \`kgraph update\` to process any pending Markdown notes in \`.kgraph/inbox/\` into durable cognition.
|
|
48
|
+
`,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
obsoleteCommandFiles: [],
|
|
53
52
|
};
|
|
@@ -15,22 +15,5 @@ alwaysApply: true
|
|
|
15
15
|
- Run \`kgraph scan\` after refactors, moved folders, renamed functions, or other structure changes.
|
|
16
16
|
- Run \`kgraph visualize\` when visualization support is available and the developer asks to inspect the KGraph map.
|
|
17
17
|
`,
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
path: ".cursor/rules/kgraph-commands.mdc",
|
|
21
|
-
content: `---
|
|
22
|
-
description: KGraph command workflows for repo context, update, and scan
|
|
23
|
-
alwaysApply: false
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
# KGraph Commands
|
|
27
|
-
|
|
28
|
-
Use these workflows when the user asks for KGraph:
|
|
29
|
-
|
|
30
|
-
- KGraph context: run \`kgraph context "<topic>"\` before broad exploration.
|
|
31
|
-
- KGraph update: write durable findings to \`.kgraph/inbox/\`, then run \`kgraph update\`.
|
|
32
|
-
- KGraph scan: run \`kgraph scan\` after refactors, file moves, renamed functions, or dependency changes.
|
|
33
|
-
`
|
|
34
|
-
}
|
|
35
|
-
]
|
|
18
|
+
obsoleteCommandFiles: [".cursor/rules/kgraph-commands.mdc"]
|
|
36
19
|
};
|
|
@@ -27,6 +27,7 @@ export async function addIntegrations(workspace, names) {
|
|
|
27
27
|
};
|
|
28
28
|
byName.set(adapter.name, next);
|
|
29
29
|
await writeIntegrationInstructions(workspace.rootPath, adapter.targetPath, adapter.name, adapter.instructions);
|
|
30
|
+
await removeIntegrationCommandFiles(workspace.rootPath, adapter.obsoleteCommandFiles ?? []);
|
|
30
31
|
await writeIntegrationCommandFiles(workspace.rootPath, adapter.commandFiles ?? []);
|
|
31
32
|
changed.push(next);
|
|
32
33
|
}
|
|
@@ -42,6 +43,7 @@ export async function removeIntegrations(workspace, names) {
|
|
|
42
43
|
const adapter = getIntegrationAdapter(name);
|
|
43
44
|
await removeIntegrationInstructions(workspace.rootPath, adapter.targetPath, adapter.name);
|
|
44
45
|
await removeIntegrationCommandFiles(workspace.rootPath, adapter.commandFiles ?? []);
|
|
46
|
+
await removeIntegrationCommandFiles(workspace.rootPath, adapter.obsoleteCommandFiles ?? []);
|
|
45
47
|
removed.push(adapter.name);
|
|
46
48
|
}
|
|
47
49
|
config.integrations = config.integrations.filter((integration) => !removeNames.has(integration.name));
|
|
@@ -77,6 +79,7 @@ async function writeIntegrationCommandFiles(rootPath, files) {
|
|
|
77
79
|
}
|
|
78
80
|
async function removeIntegrationCommandFiles(rootPath, files) {
|
|
79
81
|
for (const file of files) {
|
|
80
|
-
|
|
82
|
+
const filePath = typeof file === "string" ? file : file.path;
|
|
83
|
+
await rm(path.join(rootPath, filePath), { force: true, recursive: true });
|
|
81
84
|
}
|
|
82
85
|
}
|
|
@@ -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
|
}
|