@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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/cli/commands/context.d.ts +2 -0
  4. package/dist/cli/commands/context.js +43 -0
  5. package/dist/cli/commands/init.d.ts +2 -0
  6. package/dist/cli/commands/init.js +10 -0
  7. package/dist/cli/commands/scan.d.ts +2 -0
  8. package/dist/cli/commands/scan.js +32 -0
  9. package/dist/cli/commands/update.d.ts +2 -0
  10. package/dist/cli/commands/update.js +19 -0
  11. package/dist/cli/errors.d.ts +5 -0
  12. package/dist/cli/errors.js +23 -0
  13. package/dist/cli/index.d.ts +3 -0
  14. package/dist/cli/index.js +21 -0
  15. package/dist/cognition/cognition-updater.d.ts +10 -0
  16. package/dist/cognition/cognition-updater.js +77 -0
  17. package/dist/cognition/markdown-note-parser.d.ts +2 -0
  18. package/dist/cognition/markdown-note-parser.js +71 -0
  19. package/dist/config/config.d.ts +5 -0
  20. package/dist/config/config.js +49 -0
  21. package/dist/context/context-query.d.ts +10 -0
  22. package/dist/context/context-query.js +42 -0
  23. package/dist/context/ranking.d.ts +10 -0
  24. package/dist/context/ranking.js +29 -0
  25. package/dist/scanner/file-classifier.d.ts +4 -0
  26. package/dist/scanner/file-classifier.js +24 -0
  27. package/dist/scanner/repo-scanner.d.ts +3 -0
  28. package/dist/scanner/repo-scanner.js +85 -0
  29. package/dist/scanner/ts-symbol-extractor.d.ts +8 -0
  30. package/dist/scanner/ts-symbol-extractor.js +99 -0
  31. package/dist/storage/cognition-store.d.ts +9 -0
  32. package/dist/storage/cognition-store.js +90 -0
  33. package/dist/storage/kgraph-paths.d.ts +7 -0
  34. package/dist/storage/kgraph-paths.js +65 -0
  35. package/dist/storage/map-store.d.ts +11 -0
  36. package/dist/storage/map-store.js +60 -0
  37. package/dist/types/cognition.d.ts +43 -0
  38. package/dist/types/cognition.js +1 -0
  39. package/dist/types/config.d.ts +24 -0
  40. package/dist/types/config.js +1 -0
  41. package/dist/types/maps.d.ts +62 -0
  42. package/dist/types/maps.js +1 -0
  43. 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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerContextCommand(program: Command): void;
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInitCommand(program: Command): void;
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerScanCommand(program: Command): void;
@@ -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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerUpdateCommand(program: Command): void;
@@ -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,5 @@
1
+ export declare class KGraphError extends Error {
2
+ readonly exitCode: number;
3
+ constructor(message: string, exitCode?: number);
4
+ }
5
+ export declare function runCommand(action: () => Promise<void>): Promise<void>;
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ export declare function createProgram(): Command;
@@ -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,2 @@
1
+ import type { ParsedCognitionNote } from "../types/cognition.js";
2
+ export declare function parseMarkdownNote(markdown: string): ParsedCognitionNote;
@@ -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>;