@kentwynn/kgraph 0.2.15 → 0.2.16
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 +2 -0
- package/dist/cli/commands/context.js +92 -25
- package/dist/cli/help.js +43 -29
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Persistent repository intelligence for AI coding tools.
|
|
|
4
4
|
|
|
5
5
|
KGraph gives Codex, GitHub Copilot, Cursor, Claude Code, Gemini CLI, Windsurf, and Cline a local knowledge layer for your repo: file maps, symbols, imports, relationships, and durable knowledge atoms from previous AI sessions. The goal is simple: your assistant should not spend every session re-learning the same codebase.
|
|
6
6
|
|
|
7
|
+
The CLI presents this as **Atom Core**: lightweight local atoms plus deterministic repo maps, context packs, and session history that remain inspectable under `.kgraph/`.
|
|
8
|
+
|
|
7
9
|
## The Workflow
|
|
8
10
|
|
|
9
11
|
Use KGraph in two steps:
|
|
@@ -25,11 +25,20 @@ export function registerContextCommand(program) {
|
|
|
25
25
|
}));
|
|
26
26
|
}
|
|
27
27
|
export function renderContextMarkdown(response) {
|
|
28
|
-
const lines = [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const lines = [
|
|
29
|
+
`KGraph Context · ${response.query}`,
|
|
30
|
+
`local-first · deterministic · inspectable`,
|
|
31
|
+
``,
|
|
32
|
+
`● Signal`,
|
|
33
|
+
` confidence ${contextConfidence(response)}`,
|
|
34
|
+
` source ${contextSources(response).join(' + ') || 'file map'}`,
|
|
35
|
+
` warnings ${response.warnings.length > 0 ? response.warnings.length : 'none'}`,
|
|
36
|
+
``,
|
|
37
|
+
];
|
|
38
|
+
lines.push('● Matched Domains', '');
|
|
39
|
+
lines.push(...formatList(response.matchedDomains.map((item) => atomLine(item.item.name, formatReasons(item.reasons)))));
|
|
40
|
+
lines.push('', '● Source', '');
|
|
41
|
+
lines.push(...formatList(limited(response.relevantFiles, 5).map((item) => {
|
|
33
42
|
const f = item.item;
|
|
34
43
|
const meta = [
|
|
35
44
|
f.language,
|
|
@@ -37,32 +46,34 @@ export function renderContextMarkdown(response) {
|
|
|
37
46
|
]
|
|
38
47
|
.filter(Boolean)
|
|
39
48
|
.join(', ');
|
|
40
|
-
return
|
|
49
|
+
return atomLine(`${f.path}${meta ? ` [${meta}]` : ''}`, formatReasons(item.reasons));
|
|
41
50
|
})));
|
|
42
|
-
lines.
|
|
43
|
-
lines.push(
|
|
51
|
+
appendMore(lines, response.relevantFiles.length, 5, 'source item');
|
|
52
|
+
lines.push('', '● Symbols', '');
|
|
53
|
+
lines.push(...formatList(limited(response.relevantSymbols, 6).map((item) => {
|
|
44
54
|
const s = item.item;
|
|
45
55
|
const kindInfo = [s.kind, s.parentName].filter(Boolean).join(', ');
|
|
46
56
|
const lineRange = s.startLine != null && s.endLine != null
|
|
47
57
|
? `:${s.startLine}-${s.endLine}`
|
|
48
58
|
: '';
|
|
49
|
-
return
|
|
59
|
+
return atomLine(`${s.name} (${kindInfo}) in ${s.filePath}${lineRange}`, formatReasons(item.reasons));
|
|
50
60
|
})));
|
|
51
|
-
lines.
|
|
52
|
-
lines.push(
|
|
53
|
-
lines.push('',
|
|
54
|
-
lines.push(
|
|
55
|
-
lines.push(
|
|
61
|
+
appendMore(lines, response.relevantSymbols.length, 6, 'symbol');
|
|
62
|
+
lines.push('', '● Atoms', '');
|
|
63
|
+
lines.push(...formatList(response.relevantCognition.map((item) => atomLine(`${item.item.title} [${item.item.kind ?? 'summary'}, ${item.item.confidence ?? 'medium'}, ${item.item.referencesStatus}]`, formatReasons(item.reasons)))));
|
|
64
|
+
lines.push('', '● Graph', '');
|
|
65
|
+
lines.push(...formatGroupedRelationships(relevantGraphRelationships(response), response.relationshipExplanations));
|
|
66
|
+
lines.push('', '● Nearby Symbols (1-hop imports)', '');
|
|
56
67
|
lines.push(...formatList(nearbySymbolItems(response).map(({ symbol: s, reasons }) => {
|
|
57
68
|
const kindInfo = [s.kind, s.parentName].filter(Boolean).join(', ');
|
|
58
69
|
const lineRange = s.startLine != null && s.endLine != null
|
|
59
70
|
? `:${s.startLine}-${s.endLine}`
|
|
60
71
|
: '';
|
|
61
|
-
return
|
|
72
|
+
return atomLine(`${s.name} (${kindInfo}) in ${s.filePath}${lineRange}`, formatReasons(reasons));
|
|
62
73
|
})));
|
|
63
|
-
lines.push('', '
|
|
64
|
-
lines.push(...formatList(response.staleReferences.map((ref) =>
|
|
65
|
-
lines.push('', '
|
|
74
|
+
lines.push('', '● Stale References', '');
|
|
75
|
+
lines.push(...formatList(response.staleReferences.map((ref) => ` ◌ ${ref}`)));
|
|
76
|
+
lines.push('', '● Recent Git Changes', '');
|
|
66
77
|
if (response.gitChanges && response.gitChanges.length > 0) {
|
|
67
78
|
const staged = response.gitChanges.filter((c) => c.status === 'staged');
|
|
68
79
|
const unstaged = response.gitChanges.filter((c) => c.status === 'unstaged');
|
|
@@ -70,22 +81,29 @@ export function renderContextMarkdown(response) {
|
|
|
70
81
|
if (staged.length > 0) {
|
|
71
82
|
lines.push('Staged:');
|
|
72
83
|
for (const c of staged)
|
|
73
|
-
lines.push(` ${c.path} (${c.reason})`);
|
|
84
|
+
lines.push(` ● ${c.path} (${c.reason})`);
|
|
74
85
|
}
|
|
75
86
|
if (unstaged.length > 0) {
|
|
76
87
|
lines.push('Unstaged:');
|
|
77
88
|
for (const c of unstaged)
|
|
78
|
-
lines.push(` ${c.path} (${c.reason})`);
|
|
89
|
+
lines.push(` ● ${c.path} (${c.reason})`);
|
|
79
90
|
}
|
|
80
91
|
if (recent.length > 0) {
|
|
81
92
|
lines.push('Recent commits:');
|
|
82
93
|
for (const c of recent)
|
|
83
|
-
lines.push(` ${c.path} (${c.reason})`);
|
|
94
|
+
lines.push(` ● ${c.path} (${c.reason})`);
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
97
|
else {
|
|
87
98
|
lines.push('- None');
|
|
88
99
|
}
|
|
100
|
+
lines.push('', '● Next', '');
|
|
101
|
+
if (response.relevantFiles.some((item) => (item.item.tokenEstimate ?? 0) > 4000)) {
|
|
102
|
+
lines.push(` use budgeted source ranges: kgraph pack "${response.query}" --budget 4000`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
lines.push(` read the ranked source, edit, verify, conclude only if durable knowledge changed`);
|
|
106
|
+
}
|
|
89
107
|
return lines.join('\n');
|
|
90
108
|
}
|
|
91
109
|
function formatGroupedRelationships(relationships, explanations) {
|
|
@@ -105,33 +123,49 @@ function formatGroupedRelationships(relationships, explanations) {
|
|
|
105
123
|
const lines = [];
|
|
106
124
|
if (imports.length > 0) {
|
|
107
125
|
lines.push('Imports:');
|
|
108
|
-
for (const r of imports) {
|
|
126
|
+
for (const r of imports.slice(0, 6)) {
|
|
109
127
|
lines.push(` ${r.sourceId} → ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
110
128
|
}
|
|
129
|
+
appendMore(lines, imports.length, 6, 'import edge');
|
|
111
130
|
}
|
|
112
131
|
if (calls.length > 0) {
|
|
113
132
|
lines.push('Calls:');
|
|
114
|
-
for (const r of calls) {
|
|
133
|
+
for (const r of calls.slice(0, 6)) {
|
|
115
134
|
lines.push(` ${r.sourceId} → ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
116
135
|
}
|
|
136
|
+
appendMore(lines, calls.length, 6, 'call edge');
|
|
117
137
|
}
|
|
118
138
|
if (contains.length > 0) {
|
|
119
139
|
lines.push('Contains:');
|
|
120
|
-
for (const r of contains) {
|
|
140
|
+
for (const r of contains.slice(0, 6)) {
|
|
121
141
|
lines.push(` ${r.sourceId} contains ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
122
142
|
}
|
|
143
|
+
appendMore(lines, contains.length, 6, 'containment edge');
|
|
123
144
|
}
|
|
124
145
|
if (other.length > 0) {
|
|
125
146
|
lines.push('Other:');
|
|
126
|
-
for (const r of other) {
|
|
147
|
+
for (const r of other.slice(0, 6)) {
|
|
127
148
|
lines.push(` ${r.sourceId} ${r.relationshipType} ${r.targetId}${formatRelationshipReason(r, reasonsByRelationship)}`);
|
|
128
149
|
}
|
|
150
|
+
appendMore(lines, other.length, 6, 'graph edge');
|
|
129
151
|
}
|
|
130
152
|
return lines.length > 0 ? lines : ['- None'];
|
|
131
153
|
}
|
|
132
154
|
function formatList(items) {
|
|
133
155
|
return items.length > 0 ? items : ['- None'];
|
|
134
156
|
}
|
|
157
|
+
function limited(items, count) {
|
|
158
|
+
return items.slice(0, count);
|
|
159
|
+
}
|
|
160
|
+
function appendMore(lines, total, shown, label) {
|
|
161
|
+
const remaining = total - shown;
|
|
162
|
+
if (remaining > 0) {
|
|
163
|
+
lines.push(` ◌ ${remaining} more ${label}${remaining === 1 ? '' : 's'} omitted from display`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function atomLine(title, detail) {
|
|
167
|
+
return ` ● ${title}\n because ${detail}`;
|
|
168
|
+
}
|
|
135
169
|
function formatReasons(reasons) {
|
|
136
170
|
if (reasons.length === 0) {
|
|
137
171
|
return 'it is near the query';
|
|
@@ -151,6 +185,39 @@ function nearbySymbolItems(response) {
|
|
|
151
185
|
reasons: ['exported symbol from 1-hop import'],
|
|
152
186
|
}));
|
|
153
187
|
}
|
|
188
|
+
function relevantGraphRelationships(response) {
|
|
189
|
+
const anchorPaths = response.relevantFiles
|
|
190
|
+
.filter((item) => !item.reasons.some((reason) => reason.includes('generic path-only match penalty')))
|
|
191
|
+
.slice(0, 3)
|
|
192
|
+
.map((item) => item.item.path);
|
|
193
|
+
const paths = anchorPaths.length > 0
|
|
194
|
+
? anchorPaths
|
|
195
|
+
: response.relevantFiles.slice(0, 1).map((item) => item.item.path);
|
|
196
|
+
if (paths.length === 0)
|
|
197
|
+
return response.relationships;
|
|
198
|
+
return response.relationships.filter((relationship) => paths.some((path) => relationship.sourceId.includes(path) ||
|
|
199
|
+
relationship.targetId.includes(path)));
|
|
200
|
+
}
|
|
201
|
+
function contextConfidence(response) {
|
|
202
|
+
const confidence = response.relevantCognition
|
|
203
|
+
.map((item) => item.item.confidence)
|
|
204
|
+
.find(Boolean);
|
|
205
|
+
return confidence ?? (response.relevantFiles.length > 0 ? 'medium' : 'low');
|
|
206
|
+
}
|
|
207
|
+
function contextSources(response) {
|
|
208
|
+
const sources = [];
|
|
209
|
+
if (response.relevantCognition.length > 0)
|
|
210
|
+
sources.push('atom');
|
|
211
|
+
if ((response.gitChanges ?? []).length > 0)
|
|
212
|
+
sources.push('git change');
|
|
213
|
+
if (response.relevantSymbols.length > 0)
|
|
214
|
+
sources.push('symbol');
|
|
215
|
+
if (relevantGraphRelationships(response).length > 0)
|
|
216
|
+
sources.push('graph');
|
|
217
|
+
if (response.relevantFiles.length > 0)
|
|
218
|
+
sources.push('file');
|
|
219
|
+
return sources;
|
|
220
|
+
}
|
|
154
221
|
function formatRelationshipReason(relationship, reasonsByRelationship) {
|
|
155
222
|
const reasons = reasonsByRelationship.get(relationshipKey(relationship));
|
|
156
223
|
return reasons && reasons.length > 0
|
package/dist/cli/help.js
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
import { Chalk } from 'chalk';
|
|
2
|
-
import figlet from 'figlet';
|
|
3
2
|
export function renderRootHelp(useColor = supportsColor()) {
|
|
4
3
|
const theme = new Chalk({ level: useColor ? 3 : 0 });
|
|
5
4
|
const command = (name, description) => ` ${theme.green(name.padEnd(42))} ${description}`;
|
|
6
|
-
const
|
|
5
|
+
const accent = atomAccent(theme);
|
|
7
6
|
return [
|
|
8
7
|
'',
|
|
9
|
-
theme
|
|
8
|
+
renderAtomLogo(theme),
|
|
10
9
|
'',
|
|
11
|
-
|
|
10
|
+
renderSignalPanel(theme, [
|
|
11
|
+
['purpose', 'durable engineering memory for AI coding tools'],
|
|
12
|
+
['storage', '.kgraph/ atoms, maps, indexes, and session history'],
|
|
13
|
+
['stance', 'local-first · deterministic-first · inspectable'],
|
|
14
|
+
['agents', 'Codex · Copilot · Cursor · Claude Code · Gemini · Windsurf · Cline'],
|
|
15
|
+
]),
|
|
12
16
|
'',
|
|
13
|
-
|
|
14
|
-
` ${theme.hex('#c084fc')('Claude Code, Gemini, Windsurf, and Cline reuse repo intelligence.')}`,
|
|
15
|
-
'',
|
|
16
|
-
theme.bold('Usage'),
|
|
17
|
+
sectionTitle(theme, `${accent} Usage`),
|
|
17
18
|
' kgraph [topic]',
|
|
18
19
|
' kgraph <command> [options]',
|
|
19
20
|
'',
|
|
20
|
-
theme
|
|
21
|
+
sectionTitle(theme, `${accent} Start`),
|
|
21
22
|
command('init', 'Required once: create .kgraph/ workspace'),
|
|
22
23
|
command('init --integrations codex,gemini', 'Initialize and connect AI tools'),
|
|
23
24
|
'',
|
|
24
|
-
theme
|
|
25
|
+
sectionTitle(theme, `${accent} Daily workflow`),
|
|
25
26
|
command('kgraph', 'Refresh scan maps and process pending capture notes'),
|
|
26
27
|
command('kgraph "auth token refresh"', 'Refresh everything and return compact context for a topic'),
|
|
27
28
|
'',
|
|
28
|
-
theme
|
|
29
|
+
sectionTitle(theme, `${accent} Workflows`),
|
|
29
30
|
command('scan', 'Optional: refresh only file, symbol, import, and relationship maps'),
|
|
30
31
|
command('session', 'Show agent read/write activity and token estimates'),
|
|
31
32
|
command('session read src/auth.ts --agent codex', 'Record an agent file read'),
|
|
@@ -48,7 +49,7 @@ export function renderRootHelp(useColor = supportsColor()) {
|
|
|
48
49
|
command('visualize', 'Interactive dependency graph at http://localhost:4242'),
|
|
49
50
|
command('history "blog button"', 'Search processed cognition sessions'),
|
|
50
51
|
'',
|
|
51
|
-
theme
|
|
52
|
+
sectionTitle(theme, `${accent} Integrations`),
|
|
52
53
|
command('integrate list', 'Show configured AI tool integrations'),
|
|
53
54
|
command('integrate add gemini windsurf cline', 'Write KGraph instructions using always mode by default'),
|
|
54
55
|
command('integrate add copilot --mode smart', 'Run KGraph for repo-specific Copilot work only'),
|
|
@@ -56,21 +57,24 @@ export function renderRootHelp(useColor = supportsColor()) {
|
|
|
56
57
|
command('integrate remove cursor', 'Remove KGraph-managed instruction blocks'),
|
|
57
58
|
command('--mode smart|always|manual|off', 'Control automatic KGraph involvement per integration'),
|
|
58
59
|
'',
|
|
59
|
-
theme
|
|
60
|
+
sectionTitle(theme, `${accent} Options`),
|
|
60
61
|
command('-V, --version', 'Show version'),
|
|
61
62
|
command('-h, --help', 'Show this help'),
|
|
62
63
|
'',
|
|
63
|
-
`${
|
|
64
|
+
sectionTitle(theme, `${accent} Examples`),
|
|
64
65
|
' kgraph init --integrations codex,copilot,cursor,claude-code,gemini,windsurf,cline',
|
|
65
66
|
' kgraph "blog admin token usage"',
|
|
67
|
+
' kgraph pack "about page update" --budget 4000',
|
|
66
68
|
' kgraph doctor',
|
|
67
69
|
'',
|
|
68
70
|
theme.dim('Docs: https://github.com/kentwynn/KGraph#readme'),
|
|
71
|
+
theme.dim('Powered by Kent Wynn: https://kentwynn.com'),
|
|
69
72
|
'',
|
|
70
73
|
].join('\n');
|
|
71
74
|
}
|
|
72
75
|
export function renderWorkflowBanner(stats, useColor = supportsColor()) {
|
|
73
76
|
const theme = new Chalk({ level: useColor ? 3 : 0 });
|
|
77
|
+
const accent = atomAccent(theme);
|
|
74
78
|
const command = (name, description) => ` ${theme.green(name.padEnd(42))} ${description}`;
|
|
75
79
|
const integrationLine = stats.integrations && stats.integrations.length > 0
|
|
76
80
|
? stats.integrations
|
|
@@ -81,11 +85,11 @@ export function renderWorkflowBanner(stats, useColor = supportsColor()) {
|
|
|
81
85
|
: 'none configured';
|
|
82
86
|
return [
|
|
83
87
|
'',
|
|
84
|
-
theme
|
|
88
|
+
renderAtomLogo(theme),
|
|
85
89
|
'',
|
|
86
|
-
` ${theme.bold('KGraph')} ${theme.dim('repo intelligence refreshed')}`,
|
|
90
|
+
` ${theme.bold('KGraph')} ${theme.dim('· repo intelligence refreshed')}`,
|
|
87
91
|
'',
|
|
88
|
-
theme
|
|
92
|
+
sectionTitle(theme, `${accent} Refresh Complete`),
|
|
89
93
|
command('files', String(stats.files) +
|
|
90
94
|
(stats.skippedFiles
|
|
91
95
|
? ` (${stats.skippedFiles} unchanged, skipped)`
|
|
@@ -94,7 +98,7 @@ export function renderWorkflowBanner(stats, useColor = supportsColor()) {
|
|
|
94
98
|
command('capture notes processed', String(stats.cognitionNotes)),
|
|
95
99
|
command('integration modes', integrationLine),
|
|
96
100
|
'',
|
|
97
|
-
theme
|
|
101
|
+
sectionTitle(theme, `${accent} Next`),
|
|
98
102
|
command('kgraph "auth token refresh"', 'Return compact context for a topic'),
|
|
99
103
|
command('kgraph doctor', 'Check workspace health'),
|
|
100
104
|
command('kgraph doctor --quality', 'Check atom quality'),
|
|
@@ -104,17 +108,27 @@ export function renderWorkflowBanner(stats, useColor = supportsColor()) {
|
|
|
104
108
|
command('kgraph --help', 'Show all commands'),
|
|
105
109
|
].join('\n');
|
|
106
110
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
function renderAtomLogo(theme) {
|
|
112
|
+
const title = `${theme.hex('#38bdf8').bold('KGraph')} ${theme.dim('·')} ${theme.hex('#c084fc').bold('Atom Core')}`;
|
|
113
|
+
const atom = theme.hex('#22d3ee').bold('⚛');
|
|
114
|
+
const memory = theme.hex('#a78bfa')('persistent repo intelligence for AI coding tools');
|
|
115
|
+
return [
|
|
116
|
+
` ${atom} ${theme.dim('atoms · evidence · context packs')}`,
|
|
117
|
+
` ${title}`,
|
|
118
|
+
` ${memory}`,
|
|
119
|
+
].join('\n');
|
|
120
|
+
}
|
|
121
|
+
function renderSignalPanel(theme, rows) {
|
|
122
|
+
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
123
|
+
return rows
|
|
124
|
+
.map(([label, value]) => ` ${theme.hex('#22d3ee')('●')} ${theme.bold(label.padEnd(labelWidth))} ${theme.dim(value)}`)
|
|
125
|
+
.join('\n');
|
|
126
|
+
}
|
|
127
|
+
function sectionTitle(theme, title) {
|
|
128
|
+
return theme.bold(title);
|
|
129
|
+
}
|
|
130
|
+
function atomAccent(theme) {
|
|
131
|
+
return theme.hex('#22d3ee')('●');
|
|
118
132
|
}
|
|
119
133
|
function supportsColor() {
|
|
120
134
|
return Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kentwynn/kgraph",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "Persistent repo intelligence for AI coding assistants.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"chalk": "^5.6.2",
|
|
48
48
|
"commander": "^12.1.0",
|
|
49
49
|
"fast-glob": "^3.3.2",
|
|
50
|
-
"figlet": "^1.11.0",
|
|
51
50
|
"tree-sitter-c": "^0.24.1",
|
|
52
51
|
"tree-sitter-c-sharp": "^0.23.5",
|
|
53
52
|
"tree-sitter-cpp": "^0.23.4",
|