@kentwynn/kgraph 0.1.6 → 0.1.8
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 +199 -91
- package/dist/cli/commands/history.d.ts +16 -0
- package/dist/cli/commands/history.js +124 -0
- package/dist/cli/commands/visualize.d.ts +2 -0
- package/dist/cli/commands/visualize.js +73 -0
- package/dist/cli/help.js +46 -44
- package/dist/cli/index.js +4 -0
- package/dist/cognition/markdown-note-parser.d.ts +1 -1
- package/dist/cognition/markdown-note-parser.js +28 -17
- package/dist/context/context-query.d.ts +3 -4
- package/dist/context/context-query.js +30 -19
- package/dist/integrations/adapters/claude-code.js +15 -7
- package/dist/integrations/adapters/codex.d.ts +1 -1
- package/dist/integrations/adapters/codex.js +19 -19
- package/dist/integrations/adapters/copilot.js +23 -23
- package/dist/integrations/adapters/cursor.d.ts +1 -1
- package/dist/integrations/adapters/cursor.js +5 -9
- package/dist/visualization/graph-builder.d.ts +16 -0
- package/dist/visualization/graph-builder.js +100 -0
- package/dist/visualization/html-template.d.ts +2 -0
- package/dist/visualization/html-template.js +293 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,148 +1,220 @@
|
|
|
1
1
|
# KGraph
|
|
2
2
|
|
|
3
|
-
Persistent repository intelligence for AI coding tools
|
|
3
|
+
> **Persistent repository intelligence for AI coding tools.**
|
|
4
|
+
> Stop paying the context tax on every session. KGraph gives your AI assistant a memory.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## The Problem
|
|
8
9
|
|
|
9
|
-
AI coding
|
|
10
|
+
Every AI coding session starts with the same expensive ritual:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-> structured repo knowledge
|
|
17
|
-
-> compact context for future AI sessions
|
|
12
|
+
```
|
|
13
|
+
"Let me read your package.json..."
|
|
14
|
+
"Let me trace the imports in auth.ts..."
|
|
15
|
+
"Let me find where sessions are created..."
|
|
16
|
+
"Let me understand the database layer..."
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
On a medium-sized codebase, this exploration burns **3,000–8,000 tokens** before the AI writes a single line of code. Multiply that across 10 sessions per day and you're spending the majority of your context budget re-learning things you already know.
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Worse: AI tools forget. The debugging insight from Tuesday, the architecture decision from last sprint, the "don't touch this or it breaks payments" gotcha — gone after every session.
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## The Solution
|
|
26
|
+
|
|
27
|
+
KGraph builds a local knowledge layer that grows with your project. It maps your codebase once, captures reasoning from your AI sessions, and serves compact, targeted context on demand.
|
|
25
28
|
|
|
26
|
-
```bash
|
|
27
|
-
npx @kentwynn/kgraph@latest init
|
|
28
29
|
```
|
|
30
|
+
Without KGraph With KGraph
|
|
31
|
+
───────────────────────────────── ──────────────────────────────────
|
|
32
|
+
Session start: ~5,000 tokens Session start: ~300 tokens
|
|
33
|
+
exploring files and structure kgraph context "auth token refresh"
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
Re-learns same architecture Recalls prior decisions instantly
|
|
36
|
+
every single session from cognition store
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
Context limit hit mid-task Full context budget for actual work
|
|
39
|
+
|
|
40
|
+
Debugging insight lost forever Captured in .kgraph/inbox/
|
|
41
|
+
available in every future session
|
|
34
42
|
```
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Token Savings — What This Looks Like in Practice
|
|
47
|
+
|
|
48
|
+
A typical `kgraph context` response for a focused topic:
|
|
37
49
|
|
|
38
|
-
```bash
|
|
39
|
-
npm install -g @kentwynn/kgraph@latest
|
|
40
|
-
kgraph --version
|
|
41
50
|
```
|
|
51
|
+
topic: auth token refresh
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
files:
|
|
54
|
+
src/lib/auth.ts (createSession, validateToken, refreshToken)
|
|
55
|
+
app/api/auth/route.ts (POST handler → createSession)
|
|
56
|
+
middleware.ts (reads session cookie, calls validateToken)
|
|
44
57
|
|
|
45
|
-
|
|
58
|
+
key relationships:
|
|
59
|
+
POST /api/auth → createSession → writes JWT to cookie
|
|
60
|
+
middleware → validateToken → redirects on expiry
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
cognition:
|
|
63
|
+
refreshToken has a race condition under concurrent requests — see issue #47
|
|
64
|
+
JWT secret must come from env, never hardcoded — broke staging in March
|
|
65
|
+
token TTL is 15 min by design, not a bug
|
|
49
66
|
```
|
|
50
67
|
|
|
51
|
-
|
|
68
|
+
**~280 tokens.** The equivalent file-by-file exploration: **4,200+ tokens.**
|
|
69
|
+
That's a **15x reduction** in context cost for navigation alone.
|
|
70
|
+
|
|
71
|
+
The gap widens every week as cognition accumulates — past decisions, debugging discoveries, and architectural gotchas that would otherwise cost the AI thousands of tokens to re-derive.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## How It Works
|
|
52
76
|
|
|
53
|
-
```bash
|
|
54
|
-
kgraph scan
|
|
55
77
|
```
|
|
78
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
79
|
+
│ Your Codebase │
|
|
80
|
+
└──────────────────────────────┬──────────────────────────────┘
|
|
81
|
+
│ kgraph scan
|
|
82
|
+
▼
|
|
83
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
84
|
+
│ .kgraph/ │
|
|
85
|
+
│ ├── maps/ file graph, symbol index, imports │
|
|
86
|
+
│ ├── cognition.md decisions, gotchas, debugging history │
|
|
87
|
+
│ └── config.yaml include/exclude rules │
|
|
88
|
+
└──────────────────────────────┬──────────────────────────────┘
|
|
89
|
+
│ kgraph context "topic"
|
|
90
|
+
▼
|
|
91
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
92
|
+
│ AI Tool (Copilot / Codex / Cursor / Claude Code) │
|
|
93
|
+
│ Reads compact context → navigates directly → works faster │
|
|
94
|
+
└─────────────────────────────────────────────────────────────┘
|
|
95
|
+
│ session ends
|
|
96
|
+
▼
|
|
97
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
98
|
+
│ .kgraph/inbox/ AI drops a note: what it learned │
|
|
99
|
+
│ kgraph update → distilled into │
|
|
100
|
+
│ cognition.md for the next session │
|
|
101
|
+
└─────────────────────────────────────────────────────────────┘
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This creates a **compounding feedback loop**: the more you use KGraph, the richer the cognition store, the less exploration the AI needs to do.
|
|
56
105
|
|
|
57
|
-
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Install
|
|
58
109
|
|
|
59
110
|
```bash
|
|
60
|
-
|
|
111
|
+
npm install -g @kentwynn/kgraph@latest
|
|
112
|
+
kgraph --version
|
|
61
113
|
```
|
|
62
114
|
|
|
63
|
-
|
|
115
|
+
Or run without installing:
|
|
64
116
|
|
|
65
117
|
```bash
|
|
66
|
-
kgraph
|
|
118
|
+
npx @kentwynn/kgraph@latest init
|
|
67
119
|
```
|
|
68
120
|
|
|
69
|
-
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Quick Start
|
|
70
124
|
|
|
71
125
|
```bash
|
|
72
|
-
|
|
73
|
-
kgraph init --integrations codex,cursor
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
kgraph integrate remove cursor
|
|
126
|
+
# 1. Initialize and connect your AI tools
|
|
127
|
+
kgraph init --integrations codex,copilot,cursor,claude-code
|
|
128
|
+
|
|
129
|
+
# 2. Scan the codebase
|
|
77
130
|
kgraph scan
|
|
78
|
-
|
|
131
|
+
|
|
132
|
+
# 3. Ask for context before exploring (your AI does this automatically)
|
|
79
133
|
kgraph context "auth token refresh"
|
|
80
|
-
|
|
134
|
+
|
|
135
|
+
# 4. After an AI session, save what was learned
|
|
136
|
+
kgraph update
|
|
81
137
|
```
|
|
82
138
|
|
|
83
|
-
|
|
139
|
+
That's the entire loop. From session 2 onward, your AI tool loads existing intelligence before touching a single file.
|
|
84
140
|
|
|
85
|
-
|
|
141
|
+
---
|
|
86
142
|
|
|
87
|
-
|
|
88
|
-
| --- | --- | --- |
|
|
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` |
|
|
143
|
+
## AI Tool Integrations
|
|
93
144
|
|
|
94
|
-
|
|
145
|
+
`kgraph integrate` writes instruction files and command/skill packs directly into your repo so each tool knows how to use the knowledge layer — no manual setup.
|
|
95
146
|
|
|
96
147
|
```bash
|
|
97
148
|
kgraph integrate add codex copilot cursor claude-code
|
|
98
149
|
kgraph integrate list
|
|
99
150
|
```
|
|
100
151
|
|
|
101
|
-
|
|
152
|
+
| Tool | Always-on instruction | Skills / commands |
|
|
153
|
+
| -------------- | --------------------------------- | ------------------------------------------------------------------- |
|
|
154
|
+
| GitHub Copilot | `.github/copilot-instructions.md` | `/kgraph-scan` · `/kgraph-update` · `/kgraph-visualize` |
|
|
155
|
+
| Codex | `AGENTS.md` | `.agents/skills/kgraph/SKILL.md` (VS Code Agent Skills standard) |
|
|
156
|
+
| Cursor | `.cursor/rules/kgraph.mdc` | Built into the rule |
|
|
157
|
+
| Claude Code | `CLAUDE.md` | `/kgraph` · `/kgraph-scan` · `/kgraph-update` · `/kgraph-visualize` |
|
|
102
158
|
|
|
103
|
-
|
|
104
|
-
- KGraph update: save durable chat/debugging/workflow discoveries to `.kgraph/inbox/`, then run `kgraph update`
|
|
105
|
-
- KGraph scan: run `kgraph scan` after refactors, file moves, renamed functions, or dependency changes
|
|
159
|
+
Each integration installs a `/kgraph` skill or command that handles the full workflow automatically: load context → work → capture findings → update cognition. `/kgraph-scan`, `/kgraph-update`, and `/kgraph-visualize` are available for manual maintenance.
|
|
106
160
|
|
|
107
|
-
|
|
161
|
+
Existing user content in `AGENTS.md`, `CLAUDE.md`, etc. is preserved — KGraph manages only its own clearly-marked blocks.
|
|
108
162
|
|
|
109
|
-
|
|
163
|
+
---
|
|
110
164
|
|
|
111
|
-
##
|
|
165
|
+
## CLI Reference
|
|
112
166
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- Markdown cognition inbox for AI chat summaries, decisions, gotchas, and debugging notes
|
|
117
|
-
- Compact context output for AI assistants and scripts
|
|
118
|
-
- JSON output for tool-friendly context retrieval
|
|
119
|
-
- Integration management and command packs for Codex, Copilot, Cursor, and Claude Code
|
|
120
|
-
- Stale-reference handling when code changes over time
|
|
121
|
-
- Local-first storage with human-readable JSON, YAML, and Markdown
|
|
167
|
+
```bash
|
|
168
|
+
kgraph init # initialize .kgraph/ workspace
|
|
169
|
+
kgraph init --integrations codex,copilot # init + configure integrations
|
|
122
170
|
|
|
123
|
-
|
|
171
|
+
kgraph scan # scan codebase, update maps
|
|
172
|
+
kgraph context "auth token refresh" # get compact context for a topic
|
|
173
|
+
kgraph context "auth token refresh" --json # machine-readable output
|
|
174
|
+
kgraph update # process inbox notes into cognition
|
|
124
175
|
|
|
125
|
-
|
|
176
|
+
kgraph integrate list # show integration status
|
|
177
|
+
kgraph integrate add codex copilot cursor # add integrations
|
|
178
|
+
kgraph integrate remove cursor # remove an integration
|
|
126
179
|
|
|
127
|
-
|
|
128
|
-
kgraph
|
|
129
|
-
|
|
180
|
+
kgraph visualize # interactive graph at http://localhost:4242
|
|
181
|
+
kgraph visualize --port 3000 # custom port
|
|
182
|
+
kgraph visualize --no-open # print URL, don't open browser
|
|
130
183
|
|
|
131
|
-
|
|
132
|
-
|
|
184
|
+
kgraph history # timeline of processed cognition sessions
|
|
185
|
+
kgraph history --last 10 # show last 10 entries
|
|
186
|
+
kgraph history --json # machine-readable output
|
|
187
|
+
```
|
|
133
188
|
|
|
134
|
-
|
|
135
|
-
converts notes into durable cognition
|
|
189
|
+
---
|
|
136
190
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
191
|
+
## What KGraph Tracks
|
|
192
|
+
|
|
193
|
+
| Category | Examples |
|
|
194
|
+
| ----------------- | ---------------------------------------------------------------------- |
|
|
195
|
+
| **File map** | every source file, language, size |
|
|
196
|
+
| **Symbol index** | functions, classes, methods, exports per file |
|
|
197
|
+
| **Import graph** | which files import which, dependency chains |
|
|
198
|
+
| **Relationships** | call sites, re-exports, shared types |
|
|
199
|
+
| **Cognition** | past decisions, architectural constraints, debugging insights, gotchas |
|
|
200
|
+
|
|
201
|
+
Supported languages: TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, C/C++, C#, Ruby, PHP, Swift, and 30+ more — detected by file extension, no configuration needed.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Local-First, Zero Dependencies
|
|
140
206
|
|
|
141
|
-
|
|
207
|
+
KGraph requires nothing beyond Node.js ≥ 20:
|
|
142
208
|
|
|
143
|
-
|
|
209
|
+
- No accounts or API keys
|
|
210
|
+
- No embeddings or vector databases
|
|
211
|
+
- No cloud services or telemetry
|
|
212
|
+
- No background daemons
|
|
213
|
+
- No model provider
|
|
144
214
|
|
|
145
|
-
|
|
215
|
+
All data lives in `.kgraph/` as human-readable JSON, YAML, and Markdown. Commit it, diff it, inspect it anytime.
|
|
216
|
+
|
|
217
|
+
---
|
|
146
218
|
|
|
147
219
|
## Development
|
|
148
220
|
|
|
@@ -150,26 +222,62 @@ KGraph stores project intelligence in local files inside `.kgraph/`. The MVP doe
|
|
|
150
222
|
npm install
|
|
151
223
|
npm run build
|
|
152
224
|
npm test
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Test a command without installing:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
153
230
|
npm run kgraph -- init --integrations codex,cursor
|
|
231
|
+
npm run kgraph -- context "auth token refresh"
|
|
154
232
|
```
|
|
155
233
|
|
|
156
|
-
|
|
234
|
+
Install the local build globally to test the `kgraph` binary end-to-end:
|
|
157
235
|
|
|
158
|
-
|
|
236
|
+
```bash
|
|
237
|
+
npm install -g .
|
|
238
|
+
kgraph --version
|
|
239
|
+
kgraph init --integrations codex,copilot
|
|
240
|
+
```
|
|
159
241
|
|
|
160
|
-
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Release
|
|
245
|
+
|
|
246
|
+
Releases are tag-driven. Bump the version, push the commit and tag:
|
|
161
247
|
|
|
162
248
|
```bash
|
|
163
249
|
npm version patch
|
|
164
250
|
git push origin main --follow-tags
|
|
165
251
|
```
|
|
166
252
|
|
|
167
|
-
|
|
253
|
+
CI verifies the tag matches `package.json`, checks the version is unpublished, publishes to npm, creates a GitHub Release, and attaches the tarball.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Visualization
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
kgraph visualize
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Starts a local server at `http://localhost:4242` and opens an interactive dependency graph in your browser. No install required — two CDN scripts (Cytoscape.js + dagre layout) are loaded at view time.
|
|
264
|
+
|
|
265
|
+
**What you see:**
|
|
266
|
+
|
|
267
|
+
- File nodes colored by language (TypeScript, JavaScript, Markdown, YAML, …)
|
|
268
|
+
- Cognition notes as diamonds, colored by health (green = current, amber = mixed, red = stale)
|
|
269
|
+
- Import edges showing real dependency flow
|
|
270
|
+
- Dashed blue edges linking cognition notes to the files they describe
|
|
271
|
+
- Click any node for a metadata panel (path, size, domain, related symbols)
|
|
272
|
+
- Toggle cognition overlay on/off
|
|
273
|
+
- Switch layout: Hierarchical (default), Force-directed, Grid, Concentric
|
|
274
|
+
- **Export PNG** — 2× resolution, dark background, ready for reports or slides
|
|
275
|
+
|
|
276
|
+
---
|
|
168
277
|
|
|
169
278
|
## Roadmap
|
|
170
279
|
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
- optional editor and MCP integrations
|
|
280
|
+
- Git-aware history and rename tracking
|
|
281
|
+
- richer language scanners (deeper AST, cross-file type resolution)
|
|
282
|
+
- MCP server for editor tool-call access
|
|
283
|
+
- team-shared cognition via committed `.kgraph/`
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export interface HistoryEntry {
|
|
3
|
+
timestamp: Date;
|
|
4
|
+
filename: string;
|
|
5
|
+
title: string;
|
|
6
|
+
author?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function registerHistoryCommand(program: Command): void;
|
|
9
|
+
export declare function readHistoryEntries(processedPath: string, rootPath: string): Promise<HistoryEntry[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Parses the UTC timestamp embedded in a processed interaction filename.
|
|
12
|
+
* Filename format: 2026-05-09T09-36-06-247Z-slug.md
|
|
13
|
+
* (colons and dot replaced by dashes when written to disk)
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseTimestampFromFilename(filename: string): Date | undefined;
|
|
16
|
+
export declare function renderHistory(entries: HistoryEntry[], useColor?: boolean): string;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Chalk } from 'chalk';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { assertWorkspace, pathExists } from '../../storage/kgraph-paths.js';
|
|
7
|
+
import { KGraphError, runCommand } from '../errors.js';
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
export function registerHistoryCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('history')
|
|
12
|
+
.description('Show a timeline of processed cognition sessions')
|
|
13
|
+
.option('--last <n>', 'Show only the last N entries')
|
|
14
|
+
.option('--json', 'Print JSON output')
|
|
15
|
+
.action((options) => runCommand(async () => {
|
|
16
|
+
const workspace = await assertWorkspace(process.cwd());
|
|
17
|
+
const entries = await readHistoryEntries(workspace.processedInteractionsPath, workspace.rootPath);
|
|
18
|
+
const limit = options.last !== undefined ? parseInt(options.last, 10) : 0;
|
|
19
|
+
if (options.last !== undefined && (isNaN(limit) || limit < 1)) {
|
|
20
|
+
throw new KGraphError('--last must be a positive integer.');
|
|
21
|
+
}
|
|
22
|
+
const shown = limit > 0 ? entries.slice(-limit) : entries;
|
|
23
|
+
if (options.json) {
|
|
24
|
+
console.log(JSON.stringify(shown.map((e) => ({
|
|
25
|
+
timestamp: e.timestamp.toISOString(),
|
|
26
|
+
filename: e.filename,
|
|
27
|
+
title: e.title,
|
|
28
|
+
...(e.author !== undefined ? { author: e.author } : {}),
|
|
29
|
+
})), null, 2));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log(renderHistory(shown));
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
export async function readHistoryEntries(processedPath, rootPath) {
|
|
37
|
+
if (!(await pathExists(processedPath))) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const dirents = await readdir(processedPath, { withFileTypes: true });
|
|
41
|
+
const filenames = dirents
|
|
42
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
43
|
+
.map((e) => e.name)
|
|
44
|
+
.sort(); // ISO-prefixed filenames sort chronologically
|
|
45
|
+
const entries = [];
|
|
46
|
+
for (const filename of filenames) {
|
|
47
|
+
const timestamp = parseTimestampFromFilename(filename);
|
|
48
|
+
if (!timestamp)
|
|
49
|
+
continue;
|
|
50
|
+
const filePath = path.join(processedPath, filename);
|
|
51
|
+
const content = await readFile(filePath, 'utf8');
|
|
52
|
+
const title = content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? filename;
|
|
53
|
+
const relPath = path.relative(rootPath, filePath);
|
|
54
|
+
const author = await getGitAuthor(rootPath, relPath);
|
|
55
|
+
entries.push({ timestamp, filename, title, author });
|
|
56
|
+
}
|
|
57
|
+
return entries;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parses the UTC timestamp embedded in a processed interaction filename.
|
|
61
|
+
* Filename format: 2026-05-09T09-36-06-247Z-slug.md
|
|
62
|
+
* (colons and dot replaced by dashes when written to disk)
|
|
63
|
+
*/
|
|
64
|
+
export function parseTimestampFromFilename(filename) {
|
|
65
|
+
const match = filename.match(/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z-/);
|
|
66
|
+
if (!match)
|
|
67
|
+
return undefined;
|
|
68
|
+
const iso = `${match[1]}T${match[2]}:${match[3]}:${match[4]}.${match[5]}Z`;
|
|
69
|
+
const d = new Date(iso);
|
|
70
|
+
return isNaN(d.getTime()) ? undefined : d;
|
|
71
|
+
}
|
|
72
|
+
export function renderHistory(entries, useColor = supportsColor()) {
|
|
73
|
+
const chalk = new Chalk({ level: useColor ? 3 : 0 });
|
|
74
|
+
if (entries.length === 0) {
|
|
75
|
+
return ('\n' +
|
|
76
|
+
chalk.dim(' No processed cognition notes found. Write Markdown notes to .kgraph/inbox/ and run `kgraph update`.') +
|
|
77
|
+
'\n');
|
|
78
|
+
}
|
|
79
|
+
const header = ` ${chalk.bold('KGraph History')} ${chalk.dim(`· ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`)}`;
|
|
80
|
+
const lines = ['', header, ''];
|
|
81
|
+
const titleWidth = Math.max(...entries.map((e) => e.title.length));
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const when = chalk.dim(`${formatDate(entry.timestamp)} ${formatTime(entry.timestamp)}`);
|
|
84
|
+
const title = chalk.bold(entry.title.padEnd(titleWidth));
|
|
85
|
+
const who = entry.author !== undefined
|
|
86
|
+
? chalk.cyan(`by ${entry.author}`)
|
|
87
|
+
: chalk.dim('(uncommitted)');
|
|
88
|
+
lines.push(` ${when} ${title} ${who}`);
|
|
89
|
+
}
|
|
90
|
+
lines.push('');
|
|
91
|
+
return lines.join('\n');
|
|
92
|
+
}
|
|
93
|
+
async function getGitAuthor(rootPath, relFilePath) {
|
|
94
|
+
try {
|
|
95
|
+
const { stdout } = await execFileAsync('git', ['log', '--format=%an', '-1', '--', relFilePath], { cwd: rootPath, timeout: 3000 });
|
|
96
|
+
return stdout.trim() || undefined;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function formatDate(d) {
|
|
103
|
+
const months = [
|
|
104
|
+
'Jan',
|
|
105
|
+
'Feb',
|
|
106
|
+
'Mar',
|
|
107
|
+
'Apr',
|
|
108
|
+
'May',
|
|
109
|
+
'Jun',
|
|
110
|
+
'Jul',
|
|
111
|
+
'Aug',
|
|
112
|
+
'Sep',
|
|
113
|
+
'Oct',
|
|
114
|
+
'Nov',
|
|
115
|
+
'Dec',
|
|
116
|
+
];
|
|
117
|
+
return `${months[d.getUTCMonth()]} ${String(d.getUTCDate()).padStart(2, ' ')}, ${d.getUTCFullYear()}`;
|
|
118
|
+
}
|
|
119
|
+
function formatTime(d) {
|
|
120
|
+
return `${String(d.getUTCHours()).padStart(2, '0')}:${String(d.getUTCMinutes()).padStart(2, '0')}`;
|
|
121
|
+
}
|
|
122
|
+
function supportsColor() {
|
|
123
|
+
return Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined;
|
|
124
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { loadConfig } from '../../config/config.js';
|
|
4
|
+
import { readCognitionNotes } from '../../storage/cognition-store.js';
|
|
5
|
+
import { assertWorkspace } from '../../storage/kgraph-paths.js';
|
|
6
|
+
import { mapsExist, readMaps } from '../../storage/map-store.js';
|
|
7
|
+
import { buildGraph } from '../../visualization/graph-builder.js';
|
|
8
|
+
import { renderHtml } from '../../visualization/html-template.js';
|
|
9
|
+
import { KGraphError, runCommand } from '../errors.js';
|
|
10
|
+
export function registerVisualizeCommand(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('visualize')
|
|
13
|
+
.description('Start a local server and open the interactive dependency graph in browser')
|
|
14
|
+
.option('--port <port>', 'Port to listen on', '4242')
|
|
15
|
+
.option('--no-open', 'Print URL without opening browser')
|
|
16
|
+
.action((options) => runCommand(async () => {
|
|
17
|
+
const port = parseInt(options.port, 10);
|
|
18
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
19
|
+
throw new KGraphError('Invalid port. Use a value between 1 and 65535.');
|
|
20
|
+
}
|
|
21
|
+
const workspace = await assertWorkspace(process.cwd());
|
|
22
|
+
if (!(await mapsExist(workspace))) {
|
|
23
|
+
throw new KGraphError('KGraph maps are missing. Run `kgraph scan` first.');
|
|
24
|
+
}
|
|
25
|
+
const [maps, cognition] = await Promise.all([
|
|
26
|
+
readMaps(workspace),
|
|
27
|
+
readCognitionNotes(workspace),
|
|
28
|
+
]);
|
|
29
|
+
await loadConfig(workspace); // ensure workspace is valid
|
|
30
|
+
const graphData = buildGraph(maps.fileMap, maps.symbolMap, maps.dependencyMap, maps.relationshipMap, cognition);
|
|
31
|
+
const html = renderHtml(graphData, workspace.rootPath);
|
|
32
|
+
await serveGraph(html, port, options.open);
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
async function serveGraph(html, port, autoOpen) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const server = createServer((_req, res) => {
|
|
38
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
39
|
+
res.end(html);
|
|
40
|
+
});
|
|
41
|
+
server.on('error', (err) => {
|
|
42
|
+
if (err.code === 'EADDRINUSE') {
|
|
43
|
+
reject(new KGraphError(`Port ${port} is already in use. Try --port <number>.`));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
reject(err);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
server.listen(port, '127.0.0.1', () => {
|
|
50
|
+
const url = `http://localhost:${port}`;
|
|
51
|
+
console.log(`\nKGraph visualization at ${url}\n`);
|
|
52
|
+
console.log('Press Ctrl+C to stop.');
|
|
53
|
+
if (autoOpen) {
|
|
54
|
+
openBrowser(url);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
process.on('SIGINT', () => {
|
|
58
|
+
server.close(() => {
|
|
59
|
+
console.log('\nVisualization server stopped.');
|
|
60
|
+
resolve();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function openBrowser(url) {
|
|
67
|
+
const cmd = process.platform === 'darwin'
|
|
68
|
+
? `open "${url}"`
|
|
69
|
+
: process.platform === 'win32'
|
|
70
|
+
? `start "" "${url}"`
|
|
71
|
+
: `xdg-open "${url}"`;
|
|
72
|
+
exec(cmd);
|
|
73
|
+
}
|