@kentwynn/kgraph 0.1.0
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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +43 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +10 -0
- package/dist/cli/commands/scan.d.ts +2 -0
- package/dist/cli/commands/scan.js +32 -0
- package/dist/cli/commands/update.d.ts +2 -0
- package/dist/cli/commands/update.js +19 -0
- package/dist/cli/errors.d.ts +5 -0
- package/dist/cli/errors.js +23 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +21 -0
- package/dist/cognition/cognition-updater.d.ts +10 -0
- package/dist/cognition/cognition-updater.js +77 -0
- package/dist/cognition/markdown-note-parser.d.ts +2 -0
- package/dist/cognition/markdown-note-parser.js +71 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/config.js +49 -0
- package/dist/context/context-query.d.ts +10 -0
- package/dist/context/context-query.js +42 -0
- package/dist/context/ranking.d.ts +10 -0
- package/dist/context/ranking.js +29 -0
- package/dist/scanner/file-classifier.d.ts +4 -0
- package/dist/scanner/file-classifier.js +24 -0
- package/dist/scanner/repo-scanner.d.ts +3 -0
- package/dist/scanner/repo-scanner.js +85 -0
- package/dist/scanner/ts-symbol-extractor.d.ts +8 -0
- package/dist/scanner/ts-symbol-extractor.js +99 -0
- package/dist/storage/cognition-store.d.ts +9 -0
- package/dist/storage/cognition-store.js +90 -0
- package/dist/storage/kgraph-paths.d.ts +7 -0
- package/dist/storage/kgraph-paths.js +65 -0
- package/dist/storage/map-store.d.ts +11 -0
- package/dist/storage/map-store.js +60 -0
- package/dist/types/cognition.d.ts +43 -0
- package/dist/types/cognition.js +1 -0
- package/dist/types/config.d.ts +24 -0
- package/dist/types/config.js +1 -0
- package/dist/types/maps.d.ts +62 -0
- package/dist/types/maps.js +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kent Wynn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# KGraph
|
|
2
|
+
|
|
3
|
+
Persistent repo intelligence for AI coding assistants.
|
|
4
|
+
|
|
5
|
+
KGraph is a local-first CLI for building an inspectable knowledge layer around a codebase. It helps AI coding sessions stop rediscovering the same repository structure, workflows, architecture decisions, and debugging history every time a new chat starts.
|
|
6
|
+
|
|
7
|
+
## Why KGraph
|
|
8
|
+
|
|
9
|
+
The biggest waste in AI-assisted coding is often not generation. It is repeated exploration:
|
|
10
|
+
|
|
11
|
+
- rereading the same files
|
|
12
|
+
- rediscovering the same architecture
|
|
13
|
+
- re-inferring the same workflows
|
|
14
|
+
- repeating prior debugging conclusions
|
|
15
|
+
- spending tokens just to find the right place to work
|
|
16
|
+
|
|
17
|
+
KGraph stores durable repository context in a local `.kgraph/` workspace so future AI sessions can navigate directly to relevant files, symbols, domains, and prior cognition.
|
|
18
|
+
|
|
19
|
+
## What KGraph Is
|
|
20
|
+
|
|
21
|
+
KGraph is:
|
|
22
|
+
|
|
23
|
+
- persistent repo cognition
|
|
24
|
+
- semantic navigation infrastructure
|
|
25
|
+
- a context engineering layer
|
|
26
|
+
- local filesystem-based project intelligence
|
|
27
|
+
- an inspectable map of structure, relationships, and durable notes
|
|
28
|
+
|
|
29
|
+
KGraph is not:
|
|
30
|
+
|
|
31
|
+
- an AI coding assistant
|
|
32
|
+
- a chatbot
|
|
33
|
+
- a vector database
|
|
34
|
+
- a simple RAG wrapper
|
|
35
|
+
- a cloud service
|
|
36
|
+
- an autonomous agent system
|
|
37
|
+
|
|
38
|
+
## Install And Run
|
|
39
|
+
|
|
40
|
+
KGraph is designed to feel like other developer-native CLI tools: one command to try it, optional global install if you use it often, and a quick version check before running commands.
|
|
41
|
+
|
|
42
|
+
Public npm usage is the intended distribution path once the package is published.
|
|
43
|
+
|
|
44
|
+
Install and run a specific stable release:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx @kentwynn/kgraph@0.1.0 init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or run the latest published release:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @kentwynn/kgraph@latest init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use the CLI directly after initialization:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx @kentwynn/kgraph@latest scan
|
|
60
|
+
npx @kentwynn/kgraph@latest update
|
|
61
|
+
npx @kentwynn/kgraph@latest context "auth token refresh"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Optional global installation:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install -g @kentwynn/kgraph@latest
|
|
68
|
+
kgraph --version
|
|
69
|
+
kgraph init
|
|
70
|
+
kgraph scan
|
|
71
|
+
kgraph update
|
|
72
|
+
kgraph context "auth token refresh"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Current local development flow from a fresh clone:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm install
|
|
79
|
+
npm run build
|
|
80
|
+
npm run kgraph -- --version
|
|
81
|
+
npm run kgraph -- init
|
|
82
|
+
npm run kgraph -- scan
|
|
83
|
+
npm run kgraph -- update
|
|
84
|
+
npm run kgraph -- context "auth token refresh"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Until the npm package is publicly released, use the local development commands or install directly from the release workflow artifact.
|
|
88
|
+
|
|
89
|
+
## MVP CLI
|
|
90
|
+
|
|
91
|
+
The MVP command surface is intentionally small:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
kgraph init
|
|
95
|
+
kgraph scan
|
|
96
|
+
kgraph update
|
|
97
|
+
kgraph context "auth token refresh"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`init` creates the local `.kgraph/` workspace. `scan` refreshes deterministic structure maps. `update` processes Markdown cognition notes. `context` returns compact repository context for a topic.
|
|
101
|
+
|
|
102
|
+
The package is prepared for npm-style CLI distribution. The MVP includes CI and release artifact packaging, but automatic npm publishing is intentionally left for a later release policy.
|
|
103
|
+
|
|
104
|
+
## Local-First Privacy
|
|
105
|
+
|
|
106
|
+
KGraph writes project intelligence to local files in `.kgraph/`.
|
|
107
|
+
|
|
108
|
+
The MVP does not require:
|
|
109
|
+
|
|
110
|
+
- accounts
|
|
111
|
+
- telemetry
|
|
112
|
+
- cloud infrastructure
|
|
113
|
+
- hosted services
|
|
114
|
+
- databases
|
|
115
|
+
- LLM providers
|
|
116
|
+
- embeddings
|
|
117
|
+
- vector search
|
|
118
|
+
- background daemons
|
|
119
|
+
|
|
120
|
+
Generated KGraph data is meant to be human-readable and inspectable with normal text tools.
|
|
121
|
+
|
|
122
|
+
## MVP Scope
|
|
123
|
+
|
|
124
|
+
The first version focuses on:
|
|
125
|
+
|
|
126
|
+
- initializing `.kgraph/`
|
|
127
|
+
- scanning repository files
|
|
128
|
+
- extracting JavaScript and TypeScript symbols
|
|
129
|
+
- writing file, symbol, dependency, and relationship maps
|
|
130
|
+
- processing Markdown cognition notes
|
|
131
|
+
- returning compact context for a query
|
|
132
|
+
- keeping maps current as code changes
|
|
133
|
+
|
|
134
|
+
Out of scope for the MVP:
|
|
135
|
+
|
|
136
|
+
- npm publishing automation
|
|
137
|
+
- deployment
|
|
138
|
+
- cloud infrastructure
|
|
139
|
+
- hosted dashboards
|
|
140
|
+
- graph databases
|
|
141
|
+
- vector databases
|
|
142
|
+
- embeddings
|
|
143
|
+
- autonomous agents
|
|
144
|
+
- VS Code extension
|
|
145
|
+
|
|
146
|
+
## CI
|
|
147
|
+
|
|
148
|
+
The CI pipeline is intentionally small and practical. It runs on pushes to `main` and pull requests targeting `main`, and validates:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm ci
|
|
152
|
+
npm run build
|
|
153
|
+
npm test
|
|
154
|
+
npm pack --dry-run
|
|
155
|
+
npm run check:artifacts
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`check:artifacts` fails if local/generated Spec Kit or KGraph artifacts are committed by mistake, including:
|
|
159
|
+
|
|
160
|
+
- `.kgraph/`
|
|
161
|
+
- `.specify/`
|
|
162
|
+
- `.agents/`
|
|
163
|
+
- `AGENTS.md`
|
|
164
|
+
- `REQUIREMENTS.md`
|
|
165
|
+
- `specs/`
|
|
166
|
+
|
|
167
|
+
## CD
|
|
168
|
+
|
|
169
|
+
For a CLI package, CD means packaging an intentional release, not deploying infrastructure.
|
|
170
|
+
|
|
171
|
+
KGraph includes a release package workflow that runs only when a maintainer pushes a version tag such as `v0.1.0` or starts the workflow manually. It repeats the core gates, creates the npm tarball, and uploads the package as a GitHub Actions artifact:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm ci
|
|
175
|
+
npm run build
|
|
176
|
+
npm test
|
|
177
|
+
npm run check:artifacts
|
|
178
|
+
npm pack
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This gives maintainers an inspectable package artifact before public npm publishing is enabled.
|
|
182
|
+
|
|
183
|
+
## Roadmap
|
|
184
|
+
|
|
185
|
+
Near-term:
|
|
186
|
+
|
|
187
|
+
- stabilize the CLI contract
|
|
188
|
+
- improve JS/TS symbol extraction
|
|
189
|
+
- improve cognition note parsing
|
|
190
|
+
- validate context quality against real repositories
|
|
191
|
+
- make package publishing safe and intentional
|
|
192
|
+
|
|
193
|
+
Later:
|
|
194
|
+
|
|
195
|
+
- richer language scanners
|
|
196
|
+
- Git-aware history and rename detection
|
|
197
|
+
- optional MCP integration
|
|
198
|
+
- optional editor integrations
|
|
199
|
+
- visual graph exploration
|
|
200
|
+
- optional LLM-assisted cognition extraction
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { loadConfig } from "../../config/config.js";
|
|
2
|
+
import { queryContext } from "../../context/context-query.js";
|
|
3
|
+
import { assertWorkspace } from "../../storage/kgraph-paths.js";
|
|
4
|
+
import { mapsExist, readMaps } from "../../storage/map-store.js";
|
|
5
|
+
import { KGraphError, runCommand } from "../errors.js";
|
|
6
|
+
export function registerContextCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command("context <query>")
|
|
9
|
+
.description("Return compact repo context for a query")
|
|
10
|
+
.option("--json", "Print JSON output")
|
|
11
|
+
.action((query, options) => runCommand(async () => {
|
|
12
|
+
if (!query.trim()) {
|
|
13
|
+
throw new KGraphError("Query cannot be empty.");
|
|
14
|
+
}
|
|
15
|
+
const workspace = await assertWorkspace(process.cwd());
|
|
16
|
+
if (!(await mapsExist(workspace))) {
|
|
17
|
+
throw new KGraphError("KGraph maps are missing. Run `kgraph scan` first.");
|
|
18
|
+
}
|
|
19
|
+
const config = await loadConfig(workspace);
|
|
20
|
+
const maps = await readMaps(workspace);
|
|
21
|
+
const response = await queryContext(workspace, config, maps, query);
|
|
22
|
+
console.log(options.json ? JSON.stringify(response, null, 2) : renderMarkdown(response));
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
function renderMarkdown(response) {
|
|
26
|
+
const lines = [`# KGraph Context`, ``, `Query: ${response.query}`, ``];
|
|
27
|
+
lines.push("## Matched Domains", "");
|
|
28
|
+
lines.push(...formatList(response.matchedDomains.map((item) => `- ${item.item.name} (${item.reasons.join(", ")})`)));
|
|
29
|
+
lines.push("", "## Relevant Files", "");
|
|
30
|
+
lines.push(...formatList(response.relevantFiles.map((item) => `- ${item.item.path} (${item.reasons.join(", ")})`)));
|
|
31
|
+
lines.push("", "## Relevant Symbols", "");
|
|
32
|
+
lines.push(...formatList(response.relevantSymbols.map((item) => `- ${item.item.name} in ${item.item.filePath}`)));
|
|
33
|
+
lines.push("", "## Relevant Cognition", "");
|
|
34
|
+
lines.push(...formatList(response.relevantCognition.map((item) => `- ${item.item.title} [${item.item.referencesStatus}]`)));
|
|
35
|
+
lines.push("", "## Relationships", "");
|
|
36
|
+
lines.push(...formatList(response.relationships.map((relationship) => `- ${relationship.sourceId} ${relationship.relationshipType} ${relationship.targetId} (${relationship.confidence})`)));
|
|
37
|
+
lines.push("", "## Stale References", "");
|
|
38
|
+
lines.push(...formatList(response.staleReferences.map((ref) => `- ${ref}`)));
|
|
39
|
+
return lines.join("\n");
|
|
40
|
+
}
|
|
41
|
+
function formatList(items) {
|
|
42
|
+
return items.length > 0 ? items : ["- None"];
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { writeDefaultConfig } from "../../config/config.js";
|
|
2
|
+
import { ensureWorkspace } from "../../storage/kgraph-paths.js";
|
|
3
|
+
import { runCommand } from "../errors.js";
|
|
4
|
+
export function registerInitCommand(program) {
|
|
5
|
+
program.command("init").description("Initialize a .kgraph workspace").action(() => runCommand(async () => {
|
|
6
|
+
const workspace = await ensureWorkspace(process.cwd());
|
|
7
|
+
const wroteConfig = await writeDefaultConfig(workspace);
|
|
8
|
+
console.log(wroteConfig ? "Initialized .kgraph workspace." : ".kgraph workspace already initialized.");
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { refreshCognitionReferenceStatuses } from "../../cognition/cognition-updater.js";
|
|
2
|
+
import { loadConfig } from "../../config/config.js";
|
|
3
|
+
import { scanRepository } from "../../scanner/repo-scanner.js";
|
|
4
|
+
import { assertWorkspace } from "../../storage/kgraph-paths.js";
|
|
5
|
+
import { readMaps, writeMaps } from "../../storage/map-store.js";
|
|
6
|
+
import { runCommand } from "../errors.js";
|
|
7
|
+
export function registerScanCommand(program) {
|
|
8
|
+
program
|
|
9
|
+
.command("scan")
|
|
10
|
+
.description("Scan the repository into deterministic KGraph maps")
|
|
11
|
+
.option("--verbose", "Print scan warnings")
|
|
12
|
+
.action((options) => runCommand(async () => {
|
|
13
|
+
const workspace = await assertWorkspace(process.cwd());
|
|
14
|
+
const config = await loadConfig(workspace);
|
|
15
|
+
const previousMaps = await readMaps(workspace);
|
|
16
|
+
const result = await scanRepository(workspace.rootPath, config, {
|
|
17
|
+
files: previousMaps.fileMap.files,
|
|
18
|
+
symbols: previousMaps.symbolMap.symbols,
|
|
19
|
+
dependencies: previousMaps.dependencyMap.dependencies,
|
|
20
|
+
relationships: previousMaps.relationshipMap.relationships,
|
|
21
|
+
warnings: []
|
|
22
|
+
});
|
|
23
|
+
await writeMaps(workspace, result);
|
|
24
|
+
await refreshCognitionReferenceStatuses(workspace, { files: result.files, symbols: result.symbols });
|
|
25
|
+
console.log(`Scanned ${result.files.length} files and ${result.symbols.length} symbols.`);
|
|
26
|
+
if (options.verbose && result.warnings.length > 0) {
|
|
27
|
+
for (const warning of result.warnings) {
|
|
28
|
+
console.warn(`Warning: ${warning}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { updateCognition } from "../../cognition/cognition-updater.js";
|
|
2
|
+
import { assertWorkspace } from "../../storage/kgraph-paths.js";
|
|
3
|
+
import { readMaps } from "../../storage/map-store.js";
|
|
4
|
+
import { runCommand } from "../errors.js";
|
|
5
|
+
export function registerUpdateCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("update")
|
|
8
|
+
.description("Process Markdown cognition notes from .kgraph/inbox")
|
|
9
|
+
.option("--dry-run", "Parse notes without writing cognition files")
|
|
10
|
+
.action((options) => runCommand(async () => {
|
|
11
|
+
const workspace = await assertWorkspace(process.cwd());
|
|
12
|
+
const maps = await readMaps(workspace);
|
|
13
|
+
const result = await updateCognition(workspace, { files: maps.fileMap.files, symbols: maps.symbolMap.symbols }, Boolean(options.dryRun));
|
|
14
|
+
console.log(`${options.dryRun ? "Parsed" : "Processed"} ${result.processed.length} cognition notes.`);
|
|
15
|
+
for (const warning of result.warnings) {
|
|
16
|
+
console.warn(`Warning: ${warning}`);
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class KGraphError extends Error {
|
|
2
|
+
exitCode;
|
|
3
|
+
constructor(message, exitCode = 1) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.exitCode = exitCode;
|
|
6
|
+
this.name = "KGraphError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export async function runCommand(action) {
|
|
10
|
+
try {
|
|
11
|
+
await action();
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error instanceof KGraphError) {
|
|
15
|
+
console.error(`Error: ${error.message}`);
|
|
16
|
+
process.exitCode = error.exitCode;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
console.error(`Unexpected error: ${message}`);
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerInitCommand } from "./commands/init.js";
|
|
4
|
+
import { registerScanCommand } from "./commands/scan.js";
|
|
5
|
+
import { registerUpdateCommand } from "./commands/update.js";
|
|
6
|
+
import { registerContextCommand } from "./commands/context.js";
|
|
7
|
+
export function createProgram() {
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("kgraph")
|
|
11
|
+
.description("Persistent repo intelligence for AI coding assistants")
|
|
12
|
+
.version("0.1.0");
|
|
13
|
+
registerInitCommand(program);
|
|
14
|
+
registerScanCommand(program);
|
|
15
|
+
registerUpdateCommand(program);
|
|
16
|
+
registerContextCommand(program);
|
|
17
|
+
return program;
|
|
18
|
+
}
|
|
19
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
20
|
+
await createProgram().parseAsync(process.argv);
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { KGraphWorkspace } from "../types/config.js";
|
|
2
|
+
import type { CognitionNote, ReferenceStatus } from "../types/cognition.js";
|
|
3
|
+
import type { ScanResult } from "../types/maps.js";
|
|
4
|
+
export interface UpdateResult {
|
|
5
|
+
processed: CognitionNote[];
|
|
6
|
+
warnings: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function updateCognition(workspace: KGraphWorkspace, currentMaps: Pick<ScanResult, "files" | "symbols">, dryRun?: boolean): Promise<UpdateResult>;
|
|
9
|
+
export declare function refreshCognitionReferenceStatuses(workspace: KGraphWorkspace, currentMaps: Pick<ScanResult, "files" | "symbols">): Promise<void>;
|
|
10
|
+
export declare function evaluateReferenceStatus(relatedFiles: string[], relatedSymbols: string[], currentMaps: Pick<ScanResult, "files" | "symbols">): ReferenceStatus;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseMarkdownNote } from "./markdown-note-parser.js";
|
|
4
|
+
import { archiveInboxNote, readCognitionNotes, listInboxNotes, slugify, writeCognitionNote, writeDomainRecord } from "../storage/cognition-store.js";
|
|
5
|
+
export async function updateCognition(workspace, currentMaps, dryRun = false) {
|
|
6
|
+
const inboxNotes = await listInboxNotes(workspace);
|
|
7
|
+
const processed = [];
|
|
8
|
+
const warnings = [];
|
|
9
|
+
for (const inboxPath of inboxNotes) {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await readFile(inboxPath, "utf8");
|
|
12
|
+
const parsed = parseMarkdownNote(raw);
|
|
13
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
14
|
+
const id = `${timestamp}-${slugify(parsed.title) || path.basename(inboxPath, ".md")}`;
|
|
15
|
+
const archivedPath = path.join(workspace.processedInteractionsPath, `${timestamp}-${path.basename(inboxPath)}`);
|
|
16
|
+
const note = {
|
|
17
|
+
...parsed,
|
|
18
|
+
id,
|
|
19
|
+
sourceInboxPath: path.relative(workspace.rootPath, inboxPath).split(path.sep).join("/"),
|
|
20
|
+
processedPath: path.relative(workspace.rootPath, archivedPath).split(path.sep).join("/"),
|
|
21
|
+
createdAt: new Date().toISOString(),
|
|
22
|
+
referencesStatus: evaluateReferenceStatus(parsed.relatedFiles, parsed.relatedSymbols, currentMaps)
|
|
23
|
+
};
|
|
24
|
+
processed.push(note);
|
|
25
|
+
warnings.push(...parsed.warnings.map((warning) => `${path.basename(inboxPath)}: ${warning}`));
|
|
26
|
+
if (!dryRun) {
|
|
27
|
+
await archiveInboxNote(workspace, inboxPath, timestamp);
|
|
28
|
+
await writeCognitionNote(workspace, note);
|
|
29
|
+
await writeDomainRecord(workspace, toDomainRecord(note, currentMaps));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
warnings.push(`${path.basename(inboxPath)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { processed, warnings };
|
|
37
|
+
}
|
|
38
|
+
export async function refreshCognitionReferenceStatuses(workspace, currentMaps) {
|
|
39
|
+
const notes = await readCognitionNotes(workspace);
|
|
40
|
+
for (const note of notes) {
|
|
41
|
+
const nextStatus = evaluateReferenceStatus(note.relatedFiles, note.relatedSymbols, currentMaps);
|
|
42
|
+
if (nextStatus !== note.referencesStatus) {
|
|
43
|
+
await writeCognitionNote(workspace, { ...note, referencesStatus: nextStatus });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function evaluateReferenceStatus(relatedFiles, relatedSymbols, currentMaps) {
|
|
48
|
+
const filePaths = new Set(currentMaps.files.map((file) => file.path));
|
|
49
|
+
const symbolNames = new Set(currentMaps.symbols.map((symbol) => symbol.name));
|
|
50
|
+
const references = [
|
|
51
|
+
...relatedFiles.map((file) => filePaths.has(file)),
|
|
52
|
+
...relatedSymbols.map((symbol) => symbolNames.has(symbol))
|
|
53
|
+
];
|
|
54
|
+
if (references.length === 0) {
|
|
55
|
+
return "unresolved";
|
|
56
|
+
}
|
|
57
|
+
if (references.every(Boolean)) {
|
|
58
|
+
return "current";
|
|
59
|
+
}
|
|
60
|
+
if (references.every((value) => !value)) {
|
|
61
|
+
return "stale";
|
|
62
|
+
}
|
|
63
|
+
return "mixed";
|
|
64
|
+
}
|
|
65
|
+
function toDomainRecord(note, currentMaps) {
|
|
66
|
+
const name = note.domain ?? "general";
|
|
67
|
+
const fileSet = new Set(currentMaps.files.map((file) => file.path));
|
|
68
|
+
const symbolSet = new Set(currentMaps.symbols.map((symbol) => symbol.name));
|
|
69
|
+
return {
|
|
70
|
+
name,
|
|
71
|
+
pathHints: note.relatedFiles,
|
|
72
|
+
tags: note.tags,
|
|
73
|
+
files: note.relatedFiles.filter((file) => fileSet.has(file)),
|
|
74
|
+
symbols: note.relatedSymbols.filter((symbol) => symbolSet.has(symbol)),
|
|
75
|
+
cognitionNotes: [note.id]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import YAML from "yaml";
|
|
2
|
+
const PATH_REF = /(?:^|\s)([\w./-]+\.(?:ts|tsx|js|jsx|json|md|yaml|yml))(?:\s|$|[),.;])/g;
|
|
3
|
+
const SYMBOL_REF = /`?([A-Za-z_$][\w$]{2,})`?/g;
|
|
4
|
+
export function parseMarkdownNote(markdown) {
|
|
5
|
+
const warnings = [];
|
|
6
|
+
const { frontmatter, body } = splitFrontmatter(markdown, warnings);
|
|
7
|
+
const sections = parseSections(body);
|
|
8
|
+
const frontmatterTitle = typeof frontmatter.title === "string" ? frontmatter.title : undefined;
|
|
9
|
+
const title = extractTitle(body) ?? frontmatterTitle ?? "Untitled Cognition Note";
|
|
10
|
+
const combined = Object.values(sections).join("\n");
|
|
11
|
+
return {
|
|
12
|
+
title,
|
|
13
|
+
domain: typeof frontmatter.domain === "string" ? frontmatter.domain : undefined,
|
|
14
|
+
tags: Array.isArray(frontmatter.tags) ? frontmatter.tags.map(String) : [],
|
|
15
|
+
summary: sections.Summary,
|
|
16
|
+
sections,
|
|
17
|
+
relatedFiles: unique(extractMatches(combined, PATH_REF)),
|
|
18
|
+
relatedSymbols: unique(extractSymbolRefs(combined)),
|
|
19
|
+
warnings
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function splitFrontmatter(markdown, warnings) {
|
|
23
|
+
if (!markdown.startsWith("---\n")) {
|
|
24
|
+
return { frontmatter: {}, body: markdown };
|
|
25
|
+
}
|
|
26
|
+
const end = markdown.indexOf("\n---", 4);
|
|
27
|
+
if (end === -1) {
|
|
28
|
+
warnings.push("Frontmatter start found without closing delimiter.");
|
|
29
|
+
return { frontmatter: {}, body: markdown };
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return {
|
|
33
|
+
frontmatter: (YAML.parse(markdown.slice(4, end)) ?? {}),
|
|
34
|
+
body: markdown.slice(end + 4)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
warnings.push(`Invalid frontmatter: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
return { frontmatter: {}, body: markdown.slice(end + 4) };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function extractTitle(body) {
|
|
43
|
+
return body.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
44
|
+
}
|
|
45
|
+
function parseSections(body) {
|
|
46
|
+
const sections = {};
|
|
47
|
+
const matches = [...body.matchAll(/^##\s+(.+)$/gm)];
|
|
48
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
49
|
+
const match = matches[index];
|
|
50
|
+
const heading = match[1].trim();
|
|
51
|
+
const start = (match.index ?? 0) + match[0].length;
|
|
52
|
+
const end = matches[index + 1]?.index ?? body.length;
|
|
53
|
+
sections[heading] = body.slice(start, end).trim();
|
|
54
|
+
}
|
|
55
|
+
if (Object.keys(sections).length === 0) {
|
|
56
|
+
sections.Summary = body.replace(/^#\s+.+$/m, "").trim();
|
|
57
|
+
}
|
|
58
|
+
return sections;
|
|
59
|
+
}
|
|
60
|
+
function extractMatches(text, regex) {
|
|
61
|
+
return [...text.matchAll(regex)].map((match) => match[1]);
|
|
62
|
+
}
|
|
63
|
+
function extractSymbolRefs(text) {
|
|
64
|
+
const stopwords = new Set(["Summary", "Related", "Files", "Functions", "Decisions", "Debugging", "Conclusions"]);
|
|
65
|
+
return [...text.matchAll(SYMBOL_REF)]
|
|
66
|
+
.map((match) => match[1])
|
|
67
|
+
.filter((item) => !stopwords.has(item) && !item.includes("."));
|
|
68
|
+
}
|
|
69
|
+
function unique(items) {
|
|
70
|
+
return [...new Set(items)];
|
|
71
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { KGraphConfig, KGraphWorkspace } from "../types/config.js";
|
|
2
|
+
export declare const DEFAULT_CONFIG: KGraphConfig;
|
|
3
|
+
export declare function writeDefaultConfig(workspace: KGraphWorkspace): Promise<boolean>;
|
|
4
|
+
export declare function loadConfig(workspace: KGraphWorkspace): Promise<KGraphConfig>;
|
|
5
|
+
export declare function normalizeConfig(config: Partial<KGraphConfig>): KGraphConfig;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import YAML from "yaml";
|
|
3
|
+
import { pathExists } from "../storage/kgraph-paths.js";
|
|
4
|
+
import { KGraphError } from "../cli/errors.js";
|
|
5
|
+
export const DEFAULT_CONFIG = {
|
|
6
|
+
include: ["**/*"],
|
|
7
|
+
exclude: [".git", "node_modules", "dist", "build", ".next", "coverage", ".kgraph"],
|
|
8
|
+
languages: {
|
|
9
|
+
precise: [".js", ".jsx", ".ts", ".tsx"]
|
|
10
|
+
},
|
|
11
|
+
maxContextItems: 8,
|
|
12
|
+
domainHints: {}
|
|
13
|
+
};
|
|
14
|
+
export async function writeDefaultConfig(workspace) {
|
|
15
|
+
if (await pathExists(workspace.configPath)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
await writeFile(workspace.configPath, YAML.stringify(DEFAULT_CONFIG), "utf8");
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
export async function loadConfig(workspace) {
|
|
22
|
+
if (!(await pathExists(workspace.configPath))) {
|
|
23
|
+
return DEFAULT_CONFIG;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const raw = await readFile(workspace.configPath, "utf8");
|
|
27
|
+
const parsed = YAML.parse(raw);
|
|
28
|
+
return normalizeConfig(parsed ?? {});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
+
throw new KGraphError(`Invalid config at ${workspace.configPath}: ${message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function normalizeConfig(config) {
|
|
36
|
+
return {
|
|
37
|
+
include: Array.isArray(config.include) ? config.include : DEFAULT_CONFIG.include,
|
|
38
|
+
exclude: Array.isArray(config.exclude) ? config.exclude : DEFAULT_CONFIG.exclude,
|
|
39
|
+
languages: {
|
|
40
|
+
precise: Array.isArray(config.languages?.precise)
|
|
41
|
+
? config.languages.precise
|
|
42
|
+
: DEFAULT_CONFIG.languages.precise
|
|
43
|
+
},
|
|
44
|
+
maxContextItems: typeof config.maxContextItems === "number" && config.maxContextItems > 0
|
|
45
|
+
? config.maxContextItems
|
|
46
|
+
: DEFAULT_CONFIG.maxContextItems,
|
|
47
|
+
domainHints: config.domainHints && typeof config.domainHints === "object" ? config.domainHints : {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { KGraphConfig } from "../types/config.js";
|
|
2
|
+
import type { ContextResponse } from "../types/cognition.js";
|
|
3
|
+
import type { DependencyMap, FileMap, RelationshipMap, SymbolMap } from "../types/maps.js";
|
|
4
|
+
import type { KGraphWorkspace } from "../types/config.js";
|
|
5
|
+
export declare function queryContext(workspace: KGraphWorkspace, config: KGraphConfig, maps: {
|
|
6
|
+
fileMap: FileMap;
|
|
7
|
+
symbolMap: SymbolMap;
|
|
8
|
+
dependencyMap: DependencyMap;
|
|
9
|
+
relationshipMap: RelationshipMap;
|
|
10
|
+
}, query: string): Promise<ContextResponse>;
|