@kentwynn/kgraph 0.2.31 → 0.2.33
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/dist/cli/commands/init.js +23 -2
- package/dist/cli/commands/workflow.js +26 -9
- package/dist/cli/index.js +3 -1
- package/dist/cli/init-prompt.d.ts +20 -1
- package/dist/cli/init-prompt.js +57 -0
- package/dist/cli/workspace-detection.d.ts +23 -0
- package/dist/cli/workspace-detection.js +169 -0
- package/dist/integrations/adapters/copilot.js +5 -1
- package/dist/integrations/agent-skills.js +5 -5
- package/dist/integrations/instruction-blocks.js +2 -2
- package/dist/integrations/workflow-steps.d.ts +1 -1
- package/dist/integrations/workflow-steps.js +22 -31
- package/dist/knowledge/atom-store.js +1 -1
- package/dist/visualization/html-template.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadConfig, writeDefaultConfig } from '../../config/config.js';
|
|
1
|
+
import { loadConfig, saveConfig, writeDefaultConfig, } from '../../config/config.js';
|
|
2
2
|
import { normalizeIntegrationNames } from '../../integrations/integration-registry.js';
|
|
3
3
|
import { addIntegrations } from '../../integrations/integration-store.js';
|
|
4
4
|
import { ensureKnowledgeStore } from '../../knowledge/atom-store.js';
|
|
@@ -6,9 +6,10 @@ import { scanRepository } from '../../scanner/repo-scanner.js';
|
|
|
6
6
|
import { ensureWorkspace } from '../../storage/kgraph-paths.js';
|
|
7
7
|
import { readMaps, writeMaps } from '../../storage/map-store.js';
|
|
8
8
|
import { KGraphError, runCommand } from '../errors.js';
|
|
9
|
-
import { promptForInitIntegrations, shouldPromptForInitIntegrations, } from '../init-prompt.js';
|
|
9
|
+
import { promptForInitIntegrations, promptScopeConfirmation, promptWorkspaceSetup, shouldPromptForInitIntegrations, } from '../init-prompt.js';
|
|
10
10
|
import { detectMachineIntegrationRecommendations, recommendedIntegrationsForInit, } from '../init-recommendations.js';
|
|
11
11
|
import { renderInitSummary } from '../init-summary.js';
|
|
12
|
+
import { countScopeFiles, detectWorkspaces } from '../workspace-detection.js';
|
|
12
13
|
export function registerInitCommand(program) {
|
|
13
14
|
program
|
|
14
15
|
.command('init')
|
|
@@ -33,6 +34,26 @@ export function registerInitCommand(program) {
|
|
|
33
34
|
console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
|
|
34
35
|
}
|
|
35
36
|
let config = await loadConfig(workspace);
|
|
37
|
+
// Workspace detection — only for fresh init, non-destructive
|
|
38
|
+
const workspaceInfo = await detectWorkspaces(workspace.rootPath);
|
|
39
|
+
if (workspaceInfo && Object.keys(config.domainHints).length === 0) {
|
|
40
|
+
const result = await promptWorkspaceSetup(workspaceInfo);
|
|
41
|
+
if (result.applyDomains && result.domainHints) {
|
|
42
|
+
config = { ...config, domainHints: result.domainHints };
|
|
43
|
+
await saveConfig(workspace, config);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Pre-scan scope check — fast file count before heavy scan
|
|
47
|
+
const fileCount = await countScopeFiles(workspace.rootPath, config);
|
|
48
|
+
const scopeResult = await promptScopeConfirmation(fileCount);
|
|
49
|
+
if (!scopeResult.proceed) {
|
|
50
|
+
console.log('Init cancelled. Edit .kgraph/config.yaml to adjust scope, then run `kgraph init` again.');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (scopeResult.narrowedInclude) {
|
|
54
|
+
config = { ...config, include: scopeResult.narrowedInclude };
|
|
55
|
+
await saveConfig(workspace, config);
|
|
56
|
+
}
|
|
36
57
|
const previousMaps = await readMaps(workspace);
|
|
37
58
|
const result = await scanRepository(workspace.rootPath, config, {
|
|
38
59
|
files: previousMaps.fileMap.files,
|
|
@@ -3,8 +3,8 @@ import { concludeTopic } from '../../cognition/conclusion.js';
|
|
|
3
3
|
import { loadConfig } from '../../config/config.js';
|
|
4
4
|
import { queryContext } from '../../context/context-query.js';
|
|
5
5
|
import { refreshKnowledgeAtomStatuses } from '../../knowledge/atom-store.js';
|
|
6
|
-
import { getWorkingTreeChanges } from '../../scanner/git-utils.js';
|
|
7
6
|
import { shouldExclude } from '../../scanner/file-classifier.js';
|
|
7
|
+
import { getWorkingTreeChanges } from '../../scanner/git-utils.js';
|
|
8
8
|
import { scanRepository } from '../../scanner/repo-scanner.js';
|
|
9
9
|
import { assertSessionAgent, recordSessionEvent, } from '../../session/session-store.js';
|
|
10
10
|
import { listInboxNotes } from '../../storage/cognition-store.js';
|
|
@@ -104,8 +104,7 @@ export async function runDefaultWorkflow(query, options = {}) {
|
|
|
104
104
|
atomsProcessed: update.processed.length,
|
|
105
105
|
pendingInbox: pendingInbox.length,
|
|
106
106
|
activeAtoms: activeAtoms.length,
|
|
107
|
-
needsReviewAtoms: atoms.filter((atom) => atom.status === 'needs-review')
|
|
108
|
-
.length,
|
|
107
|
+
needsReviewAtoms: atoms.filter((atom) => atom.status === 'needs-review').length,
|
|
109
108
|
staleAtoms: atoms.filter((atom) => atom.status === 'stale').length,
|
|
110
109
|
highConfidenceMissingEvidence: activeAtoms.filter((atom) => atom.confidence === 'high' && atom.evidenceRefs.length === 0).length,
|
|
111
110
|
captureRequired: captureCheck.required,
|
|
@@ -250,17 +249,33 @@ function meaningfulTopicOverlap(a, b) {
|
|
|
250
249
|
const weakTokens = new Set([
|
|
251
250
|
'add',
|
|
252
251
|
'after',
|
|
252
|
+
'and',
|
|
253
253
|
'behavior',
|
|
254
254
|
'change',
|
|
255
255
|
'changed',
|
|
256
|
+
'check',
|
|
257
|
+
'code',
|
|
258
|
+
'current',
|
|
259
|
+
'file',
|
|
260
|
+
'fix',
|
|
261
|
+
'for',
|
|
262
|
+
'from',
|
|
263
|
+
'get',
|
|
264
|
+
'into',
|
|
256
265
|
'new',
|
|
266
|
+
'not',
|
|
257
267
|
'old',
|
|
258
268
|
'review',
|
|
259
|
-
'
|
|
260
|
-
'current',
|
|
269
|
+
'run',
|
|
261
270
|
'session',
|
|
271
|
+
'set',
|
|
262
272
|
'smoke',
|
|
273
|
+
'test',
|
|
274
|
+
'that',
|
|
275
|
+
'the',
|
|
276
|
+
'this',
|
|
263
277
|
'update',
|
|
278
|
+
'use',
|
|
264
279
|
'with',
|
|
265
280
|
]);
|
|
266
281
|
const aTokens = new Set(a
|
|
@@ -277,12 +292,14 @@ function tokenOverlap(a, b) {
|
|
|
277
292
|
const aTokens = new Set(a
|
|
278
293
|
.toLowerCase()
|
|
279
294
|
.split(/[^a-z0-9]+/)
|
|
280
|
-
.filter(
|
|
281
|
-
|
|
295
|
+
.filter((token) => token.length >= 4));
|
|
296
|
+
if (aTokens.size === 0)
|
|
297
|
+
return false;
|
|
298
|
+
const matches = b
|
|
282
299
|
.toLowerCase()
|
|
283
300
|
.split(/[^a-z0-9]+/)
|
|
284
|
-
.filter(
|
|
285
|
-
|
|
301
|
+
.filter((token) => token.length >= 4 && aTokens.has(token));
|
|
302
|
+
return matches.length >= 2;
|
|
286
303
|
}
|
|
287
304
|
function matchingInvalidatedAtoms(atoms, topic) {
|
|
288
305
|
const tokens = new Set((topic ?? '')
|
package/dist/cli/index.js
CHANGED
|
@@ -130,6 +130,8 @@ function isCliEntrypoint() {
|
|
|
130
130
|
realpathSync(process.argv[1]));
|
|
131
131
|
}
|
|
132
132
|
catch {
|
|
133
|
-
|
|
133
|
+
const argv1 = process.argv[1].replace(/\\/g, '/');
|
|
134
|
+
return (import.meta.url === `file://${argv1}` ||
|
|
135
|
+
import.meta.url === `file:///${argv1}`);
|
|
134
136
|
}
|
|
135
137
|
}
|
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
import type { IntegrationConfig, IntegrationName } from '../types/config.js';
|
|
1
|
+
import type { DomainHint, IntegrationConfig, IntegrationName } from '../types/config.js';
|
|
2
2
|
import type { InitIntegrationRecommendation } from './init-recommendations.js';
|
|
3
|
+
import type { WorkspaceInfo } from './workspace-detection.js';
|
|
3
4
|
export declare function shouldPromptForInitIntegrations(options: {
|
|
4
5
|
explicitIntegrationsRequested: boolean;
|
|
5
6
|
configuredIntegrations: Pick<IntegrationConfig, 'name'>[];
|
|
6
7
|
interactive?: boolean;
|
|
7
8
|
}): boolean;
|
|
8
9
|
export declare function promptForInitIntegrations(recommendations: InitIntegrationRecommendation[]): Promise<IntegrationName[]>;
|
|
10
|
+
export interface ScopeConfirmResult {
|
|
11
|
+
proceed: boolean;
|
|
12
|
+
narrowedInclude?: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* If file count exceeds threshold, ask user whether to proceed or narrow scope.
|
|
16
|
+
* For small repos, returns { proceed: true } without prompting.
|
|
17
|
+
*/
|
|
18
|
+
export declare function promptScopeConfirmation(fileCount: number, threshold?: number): Promise<ScopeConfirmResult>;
|
|
19
|
+
export interface WorkspacePromptResult {
|
|
20
|
+
applyDomains: boolean;
|
|
21
|
+
domainHints?: Record<string, DomainHint>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* If a monorepo workspace is detected, offer to configure domain hints.
|
|
25
|
+
* For simple projects (no workspace detected), skips silently.
|
|
26
|
+
*/
|
|
27
|
+
export declare function promptWorkspaceSetup(info: WorkspaceInfo): Promise<WorkspacePromptResult>;
|
package/dist/cli/init-prompt.js
CHANGED
|
@@ -59,3 +59,60 @@ export async function promptForInitIntegrations(recommendations) {
|
|
|
59
59
|
function isInteractiveTerminal() {
|
|
60
60
|
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* If file count exceeds threshold, ask user whether to proceed or narrow scope.
|
|
64
|
+
* For small repos, returns { proceed: true } without prompting.
|
|
65
|
+
*/
|
|
66
|
+
export async function promptScopeConfirmation(fileCount, threshold = 500) {
|
|
67
|
+
if (fileCount <= threshold || !isInteractiveTerminal()) {
|
|
68
|
+
return { proceed: true };
|
|
69
|
+
}
|
|
70
|
+
const action = await clack.select({
|
|
71
|
+
message: `Found ${fileCount.toLocaleString()} files in scope`,
|
|
72
|
+
options: [
|
|
73
|
+
{ value: 'proceed', label: 'Continue with all files' },
|
|
74
|
+
{
|
|
75
|
+
value: 'narrow',
|
|
76
|
+
label: 'Narrow to src/ only',
|
|
77
|
+
hint: 'include: ["src/**"]',
|
|
78
|
+
},
|
|
79
|
+
{ value: 'cancel', label: "Cancel — I'll edit config.yaml manually" },
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
if (clack.isCancel(action) || action === 'cancel') {
|
|
83
|
+
return { proceed: false };
|
|
84
|
+
}
|
|
85
|
+
if (action === 'narrow') {
|
|
86
|
+
return { proceed: true, narrowedInclude: ['src/**'] };
|
|
87
|
+
}
|
|
88
|
+
return { proceed: true };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* If a monorepo workspace is detected, offer to configure domain hints.
|
|
92
|
+
* For simple projects (no workspace detected), skips silently.
|
|
93
|
+
*/
|
|
94
|
+
export async function promptWorkspaceSetup(info) {
|
|
95
|
+
if (!isInteractiveTerminal()) {
|
|
96
|
+
return { applyDomains: false };
|
|
97
|
+
}
|
|
98
|
+
const packageNames = info.packages.map((p) => p.name).join(', ');
|
|
99
|
+
const action = await clack.select({
|
|
100
|
+
message: `Detected ${info.tool} workspace (${info.packages.length} packages: ${packageNames})`,
|
|
101
|
+
options: [
|
|
102
|
+
{
|
|
103
|
+
value: 'apply',
|
|
104
|
+
label: 'Configure domain hints from packages',
|
|
105
|
+
hint: 'context packs will prefer the active package',
|
|
106
|
+
},
|
|
107
|
+
{ value: 'skip', label: 'Skip — scan everything flat' },
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
if (clack.isCancel(action) || action === 'skip') {
|
|
111
|
+
return { applyDomains: false };
|
|
112
|
+
}
|
|
113
|
+
const hints = {};
|
|
114
|
+
for (const pkg of info.packages) {
|
|
115
|
+
hints[pkg.name] = { paths: [pkg.path + '/**'] };
|
|
116
|
+
}
|
|
117
|
+
return { applyDomains: true, domainHints: hints };
|
|
118
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DomainHint, KGraphConfig } from '../types/config.js';
|
|
2
|
+
export interface WorkspaceInfo {
|
|
3
|
+
tool: string;
|
|
4
|
+
packages: WorkspacePackage[];
|
|
5
|
+
}
|
|
6
|
+
export interface WorkspacePackage {
|
|
7
|
+
name: string;
|
|
8
|
+
path: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Detect monorepo workspace tools and their packages.
|
|
12
|
+
* Returns null for simple single-package projects.
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectWorkspaces(rootPath: string): Promise<WorkspaceInfo | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Convert detected workspace packages into domainHints.
|
|
17
|
+
*/
|
|
18
|
+
export declare function workspacesToDomainHints(info: WorkspaceInfo): Record<string, DomainHint>;
|
|
19
|
+
/**
|
|
20
|
+
* Quick file count using fast-glob (no content read).
|
|
21
|
+
* Used to warn about large scopes before a full scan.
|
|
22
|
+
*/
|
|
23
|
+
export declare function countScopeFiles(rootPath: string, config: KGraphConfig): Promise<number>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import fg from 'fast-glob';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { buildFastGlobIgnore, readGitignorePatterns, } from '../scanner/file-classifier.js';
|
|
5
|
+
import { pathExists } from '../storage/kgraph-paths.js';
|
|
6
|
+
/**
|
|
7
|
+
* Detect monorepo workspace tools and their packages.
|
|
8
|
+
* Returns null for simple single-package projects.
|
|
9
|
+
*/
|
|
10
|
+
export async function detectWorkspaces(rootPath) {
|
|
11
|
+
// pnpm
|
|
12
|
+
const pnpmPath = path.join(rootPath, 'pnpm-workspace.yaml');
|
|
13
|
+
if (await pathExists(pnpmPath)) {
|
|
14
|
+
const packages = await resolvePnpmPackages(rootPath, pnpmPath);
|
|
15
|
+
if (packages.length > 1)
|
|
16
|
+
return { tool: 'pnpm', packages };
|
|
17
|
+
}
|
|
18
|
+
// nx
|
|
19
|
+
const nxPath = path.join(rootPath, 'nx.json');
|
|
20
|
+
if (await pathExists(nxPath)) {
|
|
21
|
+
const packages = await resolveNxPackages(rootPath);
|
|
22
|
+
if (packages.length > 1)
|
|
23
|
+
return { tool: 'nx', packages };
|
|
24
|
+
}
|
|
25
|
+
// lerna
|
|
26
|
+
const lernaPath = path.join(rootPath, 'lerna.json');
|
|
27
|
+
if (await pathExists(lernaPath)) {
|
|
28
|
+
const packages = await resolveLernaPackages(rootPath, lernaPath);
|
|
29
|
+
if (packages.length > 1)
|
|
30
|
+
return { tool: 'lerna', packages };
|
|
31
|
+
}
|
|
32
|
+
// rush
|
|
33
|
+
const rushPath = path.join(rootPath, 'rush.json');
|
|
34
|
+
if (await pathExists(rushPath)) {
|
|
35
|
+
const packages = await resolveRushPackages(rootPath, rushPath);
|
|
36
|
+
if (packages.length > 1)
|
|
37
|
+
return { tool: 'rush', packages };
|
|
38
|
+
}
|
|
39
|
+
// npm/yarn workspaces (package.json)
|
|
40
|
+
const pkgPath = path.join(rootPath, 'package.json');
|
|
41
|
+
if (await pathExists(pkgPath)) {
|
|
42
|
+
const packages = await resolveNpmWorkspaces(rootPath, pkgPath);
|
|
43
|
+
if (packages.length > 1)
|
|
44
|
+
return { tool: 'npm', packages };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convert detected workspace packages into domainHints.
|
|
50
|
+
*/
|
|
51
|
+
export function workspacesToDomainHints(info) {
|
|
52
|
+
const hints = {};
|
|
53
|
+
for (const pkg of info.packages) {
|
|
54
|
+
hints[pkg.name] = { paths: [pkg.path + '/**'] };
|
|
55
|
+
}
|
|
56
|
+
return hints;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Quick file count using fast-glob (no content read).
|
|
60
|
+
* Used to warn about large scopes before a full scan.
|
|
61
|
+
*/
|
|
62
|
+
export async function countScopeFiles(rootPath, config) {
|
|
63
|
+
const gitignorePatterns = await readGitignorePatterns(rootPath);
|
|
64
|
+
const allExcludes = [...config.exclude, ...gitignorePatterns];
|
|
65
|
+
const entries = await fg(config.include, {
|
|
66
|
+
cwd: rootPath,
|
|
67
|
+
dot: true,
|
|
68
|
+
onlyFiles: true,
|
|
69
|
+
unique: true,
|
|
70
|
+
ignore: buildFastGlobIgnore(allExcludes),
|
|
71
|
+
stats: false,
|
|
72
|
+
});
|
|
73
|
+
return entries.length;
|
|
74
|
+
}
|
|
75
|
+
// --- Resolvers ---
|
|
76
|
+
async function resolvePnpmPackages(rootPath, filePath) {
|
|
77
|
+
try {
|
|
78
|
+
const content = await readFile(filePath, 'utf8');
|
|
79
|
+
// Simple YAML parsing for packages: array
|
|
80
|
+
const lines = content.split(/\r?\n/);
|
|
81
|
+
const globs = [];
|
|
82
|
+
let inPackages = false;
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
if (/^packages:/i.test(line.trim())) {
|
|
85
|
+
inPackages = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (inPackages) {
|
|
89
|
+
const match = line.match(/^\s+-\s+['"]?([^'"]+)['"]?\s*$/);
|
|
90
|
+
if (match) {
|
|
91
|
+
globs.push(match[1]);
|
|
92
|
+
}
|
|
93
|
+
else if (/^\S/.test(line) && line.trim().length > 0) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (globs.length === 0)
|
|
99
|
+
globs.push('packages/*');
|
|
100
|
+
return await resolvePackageGlobs(rootPath, globs);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function resolveNxPackages(rootPath) {
|
|
107
|
+
// Nx projects live in common dirs
|
|
108
|
+
const candidates = ['apps/*', 'libs/*', 'packages/*'];
|
|
109
|
+
return await resolvePackageGlobs(rootPath, candidates);
|
|
110
|
+
}
|
|
111
|
+
async function resolveLernaPackages(rootPath, filePath) {
|
|
112
|
+
try {
|
|
113
|
+
const content = await readFile(filePath, 'utf8');
|
|
114
|
+
const parsed = JSON.parse(content);
|
|
115
|
+
const globs = Array.isArray(parsed.packages)
|
|
116
|
+
? parsed.packages
|
|
117
|
+
: ['packages/*'];
|
|
118
|
+
return await resolvePackageGlobs(rootPath, globs);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function resolveRushPackages(rootPath, filePath) {
|
|
125
|
+
try {
|
|
126
|
+
const content = await readFile(filePath, 'utf8');
|
|
127
|
+
const parsed = JSON.parse(content);
|
|
128
|
+
if (!Array.isArray(parsed.projects))
|
|
129
|
+
return [];
|
|
130
|
+
return parsed.projects
|
|
131
|
+
.filter((p) => p.projectFolder)
|
|
132
|
+
.map((p) => ({
|
|
133
|
+
name: p.packageName || path.basename(p.projectFolder),
|
|
134
|
+
path: p.projectFolder,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function resolveNpmWorkspaces(rootPath, filePath) {
|
|
142
|
+
try {
|
|
143
|
+
const content = await readFile(filePath, 'utf8');
|
|
144
|
+
const parsed = JSON.parse(content);
|
|
145
|
+
const workspaces = parsed.workspaces;
|
|
146
|
+
if (!workspaces)
|
|
147
|
+
return [];
|
|
148
|
+
const globs = Array.isArray(workspaces)
|
|
149
|
+
? workspaces
|
|
150
|
+
: (workspaces.packages ?? []);
|
|
151
|
+
if (globs.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
return await resolvePackageGlobs(rootPath, globs);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function resolvePackageGlobs(rootPath, globs) {
|
|
160
|
+
const dirs = await fg(globs, {
|
|
161
|
+
cwd: rootPath,
|
|
162
|
+
onlyDirectories: true,
|
|
163
|
+
deep: 1,
|
|
164
|
+
});
|
|
165
|
+
return dirs.sort().map((dir) => ({
|
|
166
|
+
name: path.basename(dir),
|
|
167
|
+
path: dir.split(path.sep).join('/'),
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
@@ -16,7 +16,11 @@ ${numberedWorkflow('copilot')}
|
|
|
16
16
|
description: Use KGraph persistent repo intelligence — runs kgraph before answering to provide file maps, symbols, relationships, and durable knowledge atoms.
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# KGraph Agent
|
|
20
|
+
|
|
21
|
+
You are the KGraph agent. You use the \`kgraph\` CLI via the terminal to provide persistent repo intelligence before answering questions or making edits.
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
20
24
|
|
|
21
25
|
${numberedWorkflow('copilot')}
|
|
22
26
|
`,
|
|
@@ -45,7 +45,7 @@ name: kgraph-pack
|
|
|
45
45
|
description: Build a budget-aware KGraph context pack
|
|
46
46
|
---
|
|
47
47
|
|
|
48
|
-
Run \`kgraph pack "
|
|
48
|
+
Run \`kgraph pack "<topic>" --budget 8000 --json --agent $AGENT\` to build a machine-readable context pack and record lightweight session context. Summarize token use, included files, symbols, relationships, git changes, session history, atoms, and omitted items with the inclusion reasons. When a symbol item includes an \`excerpt\` field, you already have the source — do not read that file for the symbol.
|
|
49
49
|
`,
|
|
50
50
|
},
|
|
51
51
|
{
|
|
@@ -75,7 +75,7 @@ name: kgraph-blame
|
|
|
75
75
|
description: Show KGraph atom provenance and evidence
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
-
Run \`kgraph blame
|
|
78
|
+
Run \`kgraph blame <atom-id>\` to show who or what created a knowledge atom, the source command/session/commit, evidence refs, and lifecycle links.
|
|
79
79
|
`,
|
|
80
80
|
},
|
|
81
81
|
{
|
|
@@ -127,7 +127,7 @@ name: kgraph-conclude
|
|
|
127
127
|
description: Store a typed durable KGraph engineering conclusion
|
|
128
128
|
---
|
|
129
129
|
|
|
130
|
-
Prefer \`kgraph "<topic>" --capture "
|
|
130
|
+
Prefer \`kgraph "<topic>" --capture "<durable conclusion>" --capture-file <path> --capture-symbol <name>\` when the session produced reusable engineering knowledge. Use low-level \`kgraph conclude "<topic>" --note "<conclusion>"\` only when the user explicitly asks for the conclude command. Choose one type from finding, decision, gotcha, summary, relationship, and one confidence from high, medium, low. Add file or symbol evidence whenever possible; high-confidence conclusions require evidence. Store only durable conclusions, not raw chain-of-thought, temporary reasoning, speculative exploration, or low-value observations.
|
|
131
131
|
`,
|
|
132
132
|
},
|
|
133
133
|
{
|
|
@@ -137,7 +137,7 @@ name: kgraph-impact
|
|
|
137
137
|
description: Show KGraph change impact for a file, symbol, or topic
|
|
138
138
|
---
|
|
139
139
|
|
|
140
|
-
Run \`kgraph impact "
|
|
140
|
+
Run \`kgraph impact "<file-or-symbol>"\` to show matched files/symbols, import users, callers, callees, related knowledge atoms, and risk hints.
|
|
141
141
|
`,
|
|
142
142
|
},
|
|
143
143
|
{
|
|
@@ -157,7 +157,7 @@ name: kgraph-history
|
|
|
157
157
|
description: Show timeline of KGraph cognition sessions with git attribution
|
|
158
158
|
---
|
|
159
159
|
|
|
160
|
-
Run \`kgraph history\` or \`kgraph history "
|
|
160
|
+
Run \`kgraph history\` or \`kgraph history "<topic>"\` to display processed cognition sessions. Summarize who contributed what and when. Use \`--last <n>\` to limit entries.
|
|
161
161
|
`,
|
|
162
162
|
},
|
|
163
163
|
];
|
|
@@ -9,7 +9,7 @@ export function upsertManagedBlock(content, integrationName, instructions) {
|
|
|
9
9
|
if (pattern.test(content)) {
|
|
10
10
|
return content.replace(pattern, block);
|
|
11
11
|
}
|
|
12
|
-
return normalized ? `${
|
|
12
|
+
return normalized ? `${normalized}\n\n${block}\n` : `${block}\n`;
|
|
13
13
|
}
|
|
14
14
|
export function removeManagedBlock(content, integrationName) {
|
|
15
15
|
return (content
|
|
@@ -25,7 +25,7 @@ function renderManagedBlock(integrationName, instructions) {
|
|
|
25
25
|
].join('\n');
|
|
26
26
|
}
|
|
27
27
|
function managedBlockPattern(integrationName) {
|
|
28
|
-
return new RegExp(`${escapeRegExp(MARKER_PREFIX)} BEGIN KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}[\\s\\S]*?${escapeRegExp(MARKER_PREFIX)} END KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}\\n?`, 'm');
|
|
28
|
+
return new RegExp(`${escapeRegExp(MARKER_PREFIX)} BEGIN KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}[\\s\\S]*?${escapeRegExp(MARKER_PREFIX)} END KGRAPH ${escapeRegExp(integrationName)} ${escapeRegExp(MARKER_SUFFIX)}\\r?\\n?`, 'm');
|
|
29
29
|
}
|
|
30
30
|
function escapeRegExp(value) {
|
|
31
31
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -7,7 +7,7 @@ export interface WorkflowOptions {
|
|
|
7
7
|
sessionQualifier?: string;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Returns the
|
|
10
|
+
* Returns the numbered workflow for skill/agent/command files.
|
|
11
11
|
* Used by: copilot (agent file), codex (skill file), claude-code (command file).
|
|
12
12
|
*/
|
|
13
13
|
export declare function numberedWorkflow(agentName: string, options?: WorkflowOptions): string;
|
|
@@ -8,49 +8,41 @@ const REPAIR_STEP = `Run \`kgraph repair --dry-run\` before cleanup when stale/n
|
|
|
8
8
|
const COMPACT_STEP = `Run \`kgraph compact --dry-run\` when cognition looks duplicated, noisy, or stale. Run \`kgraph compact\` only when the user asks to merge/archive cognition.`;
|
|
9
9
|
const HISTORY_STEP = `Run \`kgraph history\` or \`kgraph history "<topic>"\` to review past cognition sessions with git author attribution.`;
|
|
10
10
|
const KNOWLEDGE_STEP = `Run \`kgraph knowledge list --topic "<topic>"\` or \`kgraph knowledge get <atom-id>\` when the user asks what KGraph remembers or atom provenance/lifecycle matters.`;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return `For normal coding context, treat \`kgraph pack "<task>" --budget 8000 --json --agent ${agentName}\` as the machine-readable context contract: use atoms, source ranges, git changes, omitted items, and inclusion reasons from the ContextPack before reading files.`;
|
|
16
|
-
}
|
|
11
|
+
const STALE_STEP = `Run \`kgraph stale\` when changed or deleted code may have invalidated durable knowledge. Run \`kgraph blame <atom-id>\` when provenance or evidence for a memory matters.`;
|
|
12
|
+
const EXPLORATION_BOUNDARY_STEP = `Keep exploration bounded by the task. For simple edits, use KGraph to identify the likely file, then read only that file or a narrow range and make the edit. Do not keep searching after the target file is found, do not retry malformed shell commands with broader variants, and do not run broad \`find\`, recursive \`grep\`, or repeated full-file dumps after KGraph already returned candidate files.`;
|
|
13
|
+
const VERIFY_EDIT_STEP = `After editing, verify the change actually landed before claiming completion. Prefer a narrow read of the changed range or \`git diff -- <path>\`; if there is no diff or the expected text is missing, say the edit did not apply and fix it before summarizing.`;
|
|
14
|
+
const SCAN_STEP = `After bulk file creation, deletion, or rename (3+ files), run \`kgraph scan\` before the next \`kgraph pack\` so maps stay accurate.`;
|
|
17
15
|
function smartRootStep(agentName) {
|
|
18
16
|
return `Use the root workflow when refresh or memory processing matters: run \`kgraph "<topic>" --agent ${agentName}\` to refresh maps, process inbox notes, and return a briefing; run \`kgraph "<topic>" --final --agent ${agentName}\` before the final answer when repository files changed; run \`kgraph "<topic>" --capture "<durable conclusion>" --capture-file <path> --capture-symbol <name> --agent ${agentName}\` when the final check requires durable knowledge.`;
|
|
19
17
|
}
|
|
20
|
-
const STALE_STEP = `Run \`kgraph stale\` when changed or deleted code may have invalidated durable knowledge. Run \`kgraph blame <atom-id>\` when provenance or evidence for a memory matters.`;
|
|
21
|
-
const EXPLORATION_BOUNDARY_STEP = `Keep exploration bounded by the task. For simple edits, use KGraph to identify the likely file, then read only that file or a narrow range and make the edit. Do not keep searching after the target file is found, do not retry malformed shell commands with broader variants, and do not run broad \`find\`, recursive \`grep\`, or repeated full-file dumps after KGraph already returned candidate files. Use \`rg --files\` and quoted paths when a path must be located.`;
|
|
22
|
-
const VERIFY_EDIT_STEP = `After editing, verify the change actually landed before claiming completion. Prefer a narrow read of the changed range or \`git diff -- <path>\`; if there is no diff or the expected text is missing, say the edit did not apply and fix it before summarizing.`;
|
|
23
|
-
const ROUTING_STEP = `Route explicit KGraph requests to the matching command before any default context lookup: history/prior work -> \`kgraph history "<topic>"\`; inbox/pending capture/process notes/update cognition -> \`kgraph "<topic>"\` or \`kgraph update\`; remembered knowledge/atoms/provenance -> \`kgraph knowledge list --topic "<topic>"\` or \`kgraph knowledge get <atom-id>\`; setup/health -> \`kgraph doctor\`.`;
|
|
24
18
|
function sessionStep(agentName, qualifier) {
|
|
25
19
|
const base = `Track meaningful session activity with \`kgraph session start --agent ${agentName}\`, \`kgraph session read <path> --agent ${agentName}\`, \`kgraph session write <path> --agent ${agentName}\`, and \`kgraph session end --agent ${agentName} --conclude --topic "<topic>"\` when durable session memory is useful`;
|
|
26
20
|
return qualifier ? `${base} ${qualifier}.` : `${base}.`;
|
|
27
21
|
}
|
|
28
22
|
/**
|
|
29
|
-
* Returns the
|
|
23
|
+
* Returns the numbered workflow for skill/agent/command files.
|
|
30
24
|
* Used by: copilot (agent file), codex (skill file), claude-code (command file).
|
|
31
25
|
*/
|
|
32
26
|
export function numberedWorkflow(agentName, options = {}) {
|
|
33
27
|
return `1. Infer the topic from the user's request.
|
|
34
28
|
2. {{KGRAPH_CONTEXT_POLICY}}
|
|
35
|
-
3.
|
|
36
|
-
4. ${
|
|
37
|
-
5.
|
|
38
|
-
6. ${
|
|
39
|
-
7. ${
|
|
40
|
-
8. ${
|
|
41
|
-
9. ${
|
|
42
|
-
10. ${
|
|
43
|
-
11. ${
|
|
44
|
-
12. ${
|
|
45
|
-
13. ${sessionStep(agentName, options.sessionQualifier)}
|
|
46
|
-
14. ${IMPACT_STEP}
|
|
29
|
+
3. When the pack includes a symbol with an \`excerpt\` field, you already have the source code — do not read that file again for that symbol. Items in the \`omitted\` array were evaluated and excluded — do not manually search for them unless the user explicitly asks.
|
|
30
|
+
4. ${EXPLORATION_BOUNDARY_STEP}
|
|
31
|
+
5. ${VERIFY_EDIT_STEP}
|
|
32
|
+
6. ${smartRootStep(agentName)}
|
|
33
|
+
7. ${KNOWLEDGE_STEP}
|
|
34
|
+
8. ${DOCTOR_STEP}
|
|
35
|
+
9. ${STALE_STEP}
|
|
36
|
+
10. ${SCAN_STEP}
|
|
37
|
+
11. ${sessionStep(agentName, options.sessionQualifier)}
|
|
38
|
+
12. ${IMPACT_STEP}
|
|
47
39
|
|
|
48
40
|
{{KGRAPH_CAPTURE_POLICY}}
|
|
49
41
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
13. ${REPAIR_STEP}
|
|
43
|
+
14. ${COMPACT_STEP}
|
|
44
|
+
15. Run \`kgraph visualize\` when the user wants to inspect the dependency graph — opens an interactive graph locally with PNG export.
|
|
45
|
+
16. ${HISTORY_STEP}`;
|
|
54
46
|
}
|
|
55
47
|
/**
|
|
56
48
|
* Returns the bullet-list workflow for rules files.
|
|
@@ -58,20 +50,19 @@ export function numberedWorkflow(agentName, options = {}) {
|
|
|
58
50
|
*/
|
|
59
51
|
export function bulletWorkflow(agentName, options = {}) {
|
|
60
52
|
return `- {{KGRAPH_CONTEXT_POLICY}}
|
|
61
|
-
-
|
|
62
|
-
- ${ROUTING_STEP}
|
|
53
|
+
- When the pack includes a symbol with an \`excerpt\` field, you already have the source code — do not read that file again for that symbol. Items in the \`omitted\` array were evaluated and excluded — do not manually search for them unless the user explicitly asks.
|
|
63
54
|
- ${EXPLORATION_BOUNDARY_STEP}
|
|
64
55
|
- ${VERIFY_EDIT_STEP}
|
|
65
56
|
- ${smartRootStep(agentName)}
|
|
66
|
-
- ${packStep(agentName)}
|
|
67
57
|
- ${KNOWLEDGE_STEP}
|
|
68
58
|
- ${DOCTOR_STEP}
|
|
69
59
|
- ${STALE_STEP}
|
|
60
|
+
- ${SCAN_STEP}
|
|
70
61
|
- ${sessionStep(agentName, options.sessionQualifier)}
|
|
71
62
|
- ${IMPACT_STEP}
|
|
72
63
|
{{KGRAPH_CAPTURE_POLICY}}
|
|
73
64
|
- ${REPAIR_STEP}
|
|
74
65
|
- ${COMPACT_STEP}
|
|
75
|
-
- Run \`kgraph visualize\` to open the interactive dependency graph
|
|
66
|
+
- Run \`kgraph visualize\` to open the interactive dependency graph locally with PNG export.
|
|
76
67
|
- ${HISTORY_STEP}`;
|
|
77
68
|
}
|
|
@@ -33,7 +33,7 @@ export async function readAtomsFile(workspace) {
|
|
|
33
33
|
}
|
|
34
34
|
export function parseAtomsJsonl(raw) {
|
|
35
35
|
const atoms = [];
|
|
36
|
-
for (const [index, line] of raw.split(
|
|
36
|
+
for (const [index, line] of raw.split(/\r?\n/).entries()) {
|
|
37
37
|
if (!line.trim())
|
|
38
38
|
continue;
|
|
39
39
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function renderHtml(graphData, rootPath) {
|
|
2
|
-
const repoName = escAttr(rootPath.split(
|
|
2
|
+
const repoName = escAttr(rootPath.split(/[\\/]/).pop() ?? 'Repository');
|
|
3
3
|
const { meta } = graphData;
|
|
4
4
|
// Prevent </script> tag injection from embedded JSON
|
|
5
5
|
const safeData = JSON.stringify(graphData).replace(/<\/script>/gi, '<\\/script>');
|