@kentwynn/kgraph 0.2.16 → 0.2.17

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 CHANGED
@@ -121,12 +121,14 @@ After useful AI work, assistants save durable runtime-capture notes into `.kgrap
121
121
  Normal agent flow is intentionally small:
122
122
 
123
123
  ```bash
124
- kgraph "topic"
124
+ kgraph pack "topic" --budget 8000 --json
125
125
  # work normally
126
126
  # if repo files changed, write an inbox note before the final refresh
127
127
  kgraph
128
128
  ```
129
129
 
130
+ `kgraph "<topic>"` remains the human-readable briefing. Agents should prefer `kgraph pack "<topic>" --budget 8000 --json` because it returns the stable `ContextPack` contract with atoms, source ranges, git changes, omitted items, token estimates, and inclusion reasons.
131
+
130
132
  Use `kgraph doctor` after setup and before trusting a repo's saved intelligence. It checks initialization, maps, pending inbox notes, integration targets, and actionable quality problems. Use `kgraph doctor --quality` and `kgraph repair --dry-run` when stale or noisy atom references start making context harder to trust.
131
133
 
132
134
  Agents can also report session activity so KGraph can estimate token waste:
@@ -249,7 +251,7 @@ kgraph pack "auth token refresh" --budget 8000
249
251
  kgraph pack "auth token refresh" --budget 8000 --json
250
252
  ```
251
253
 
252
- Build a budget-aware context pack from files, symbols, relationships, git changes, session history, and knowledge atoms. JSON output is the stable machine-readable contract for agents.
254
+ Build a budget-aware context pack from files, source ranges, symbols, relationships, git changes, session history, and knowledge atoms. JSON output is the stable machine-readable contract for agents; text output is an Atom Core briefing for humans.
253
255
 
254
256
  ```bash
255
257
  kgraph compact --dry-run
@@ -1,2 +1,4 @@
1
1
  import type { Command } from 'commander';
2
+ import type { ContextPack } from '../../types/knowledge.js';
2
3
  export declare function registerPackCommand(program: Command): void;
4
+ export declare function renderPackText(pack: ContextPack): string;
@@ -31,19 +31,66 @@ export function registerPackCommand(program) {
31
31
  console.log(JSON.stringify(pack, null, 2));
32
32
  return;
33
33
  }
34
- console.log(`# KGraph Context Pack`);
35
- console.log('');
36
- console.log(`Task: ${pack.task}`);
37
- console.log(`Budget: ${pack.budget}`);
38
- console.log(`Used: ${pack.usedTokens}`);
39
- console.log('');
40
- for (const item of pack.items) {
41
- console.log(`- [${item.kind}] ${item.title} (~${item.tokenEstimate} tokens)`);
42
- console.log(` because ${item.reasons.slice(0, 3).join('; ')}`);
34
+ console.log(renderPackText(pack));
35
+ }));
36
+ }
37
+ export function renderPackText(pack) {
38
+ const lines = [
39
+ `KGraph Pack · ${pack.task}`,
40
+ `local-first · budget-aware · machine contract: --json`,
41
+ ``,
42
+ `● Budget`,
43
+ ` used ${pack.usedTokens} / ${pack.budget}`,
44
+ ` included ${pack.items.length}`,
45
+ ` omitted ${pack.omitted.length}`,
46
+ ``,
47
+ ];
48
+ appendGroup(lines, 'Atoms', pack.items.filter((item) => item.kind === 'atom'));
49
+ appendGroup(lines, 'Git Changes', pack.items.filter((item) => item.kind === 'git-change'));
50
+ appendGroup(lines, 'Source Ranges', pack.items.filter((item) => item.kind === 'file-range'));
51
+ appendGroup(lines, 'Symbols', pack.items.filter((item) => item.kind === 'symbol'));
52
+ appendGroup(lines, 'Files', pack.items.filter((item) => item.kind === 'file'));
53
+ appendGroup(lines, 'Graph', pack.items.filter((item) => item.kind === 'relationship'));
54
+ lines.push(`● Omitted`);
55
+ const omitted = pack.omitted.slice(0, 8);
56
+ if (omitted.length === 0) {
57
+ lines.push('- None');
58
+ }
59
+ else {
60
+ for (const item of omitted) {
61
+ lines.push(` ◌ ${item.kind} ${item.title} (~${item.tokenEstimate} tokens)`);
43
62
  }
44
- if (pack.omitted.length > 0) {
45
- console.log('');
46
- console.log(`Omitted: ${pack.omitted.length} item(s) over budget`);
63
+ if (pack.omitted.length > omitted.length) {
64
+ lines.push(` ◌ ${pack.omitted.length - omitted.length} more omitted items`);
47
65
  }
48
- }));
66
+ }
67
+ lines.push('', '● Next', ' agents should consume this command with --json for the full ContextPack contract');
68
+ return lines.join('\n');
69
+ }
70
+ function appendGroup(lines, title, items) {
71
+ lines.push(`● ${title}`);
72
+ if (items.length === 0) {
73
+ lines.push('- None', '');
74
+ return;
75
+ }
76
+ for (const item of items.slice(0, 6)) {
77
+ lines.push(` ● ${item.title} (~${item.tokenEstimate} tokens)`);
78
+ lines.push(` because ${formatReasons(item.reasons)}`);
79
+ if (item.kind === 'file-range') {
80
+ const data = item.data;
81
+ if (data.path && data.startLine != null && data.endLine != null) {
82
+ lines.push(` range ${data.path}:${data.startLine}-${data.endLine}`);
83
+ }
84
+ }
85
+ }
86
+ if (items.length > 6)
87
+ lines.push(` ◌ ${items.length - 6} more ${title.toLowerCase()} omitted from display`);
88
+ lines.push('');
89
+ }
90
+ function formatReasons(reasons) {
91
+ if (reasons.length === 0)
92
+ return 'included by pack ranking';
93
+ const shown = reasons.slice(0, 3);
94
+ const remaining = reasons.length - shown.length;
95
+ return remaining > 0 ? `${shown.join('; ')}; and ${remaining} more` : shown.join('; ');
49
96
  }
@@ -53,10 +53,15 @@ export function buildContextPack(response, budget, rootPath) {
53
53
  })),
54
54
  ];
55
55
  const orderedCandidates = candidates.sort(comparePackCandidates);
56
+ const strongPaths = strongPackPaths(candidates);
56
57
  const items = [];
57
58
  const omitted = [];
58
59
  let usedTokens = 0;
59
60
  for (const candidate of orderedCandidates) {
61
+ if (isLowSignalCandidate(candidate, strongPaths)) {
62
+ omitted.push(candidate);
63
+ continue;
64
+ }
60
65
  if (usedTokens + candidate.tokenEstimate <= budget) {
61
66
  items.push(candidate);
62
67
  usedTokens += candidate.tokenEstimate;
@@ -102,6 +107,60 @@ function packPriority(item) {
102
107
  score -= Math.floor(item.tokenEstimate / 2000);
103
108
  return score;
104
109
  }
110
+ function strongPackPaths(candidates) {
111
+ const paths = new Set();
112
+ for (const candidate of candidates) {
113
+ if (candidate.kind === 'file-range' || candidate.kind === 'git-change') {
114
+ const pathValue = candidatePath(candidate);
115
+ if (pathValue)
116
+ paths.add(pathValue);
117
+ }
118
+ if (candidate.kind === 'file' && hasStrongReason(candidate)) {
119
+ const pathValue = candidatePath(candidate);
120
+ if (pathValue)
121
+ paths.add(pathValue);
122
+ }
123
+ if (candidate.kind === 'atom') {
124
+ const atom = candidate.data;
125
+ for (const file of atom?.relatedFiles ?? [])
126
+ paths.add(file);
127
+ }
128
+ }
129
+ return paths;
130
+ }
131
+ function isLowSignalCandidate(candidate, strongPaths) {
132
+ if (strongPaths.size === 0)
133
+ return false;
134
+ if (candidate.kind === 'atom' || candidate.kind === 'git-change' || candidate.kind === 'file-range') {
135
+ return false;
136
+ }
137
+ if (hasStrongReason(candidate))
138
+ return false;
139
+ if (candidateTouchesStrongPath(candidate, strongPaths))
140
+ return false;
141
+ return candidate.kind === 'file' || candidate.kind === 'symbol' || candidate.kind === 'relationship';
142
+ }
143
+ function hasStrongReason(candidate) {
144
+ return candidate.reasons.some((reason) => reason.includes('matched atom') ||
145
+ reason.includes('current git change') ||
146
+ reason.includes('changed in recent commits') ||
147
+ reason.includes('unstaged change') ||
148
+ reason.includes('staged change'));
149
+ }
150
+ function candidateTouchesStrongPath(candidate, strongPaths) {
151
+ const pathValue = candidatePath(candidate);
152
+ if (pathValue && strongPaths.has(pathValue))
153
+ return true;
154
+ if (candidate.kind !== 'relationship')
155
+ return false;
156
+ const relationship = candidate.data;
157
+ return [...strongPaths].some((strongPath) => relationship.sourceId?.includes(strongPath) ||
158
+ relationship.targetId?.includes(strongPath));
159
+ }
160
+ function candidatePath(candidate) {
161
+ const data = candidate.data;
162
+ return data?.path ?? data?.filePath;
163
+ }
105
164
  const GENERIC_RANGE_TOKENS = new Set([
106
165
  'app',
107
166
  'code',
@@ -36,17 +36,17 @@ export function applyContextPolicy(content, mode) {
36
36
  .replaceAll(KGRAPH_CAPTURE_POLICY_PLACEHOLDER, renderCapturePolicy());
37
37
  }
38
38
  export function renderContextPolicy(mode) {
39
- const useResultBoundary = 'Use the returned KGraph candidate files as the first-pass source of truth. Do not rerun the same KGraph query just to tail or reformat output, do not continue broad repository search after the target file is identified, do not retry malformed shell commands with broader variants, and do not run broad `find`, recursive `grep`, or repeated full-file dumps after KGraph has narrowed the target.';
39
+ const useResultBoundary = 'Use the returned KGraph ContextPack items as the first-pass source of truth. Prefer source ranges, atoms, git changes, and inclusion reasons from the pack before broad repository search. Do not rerun the same KGraph query just to tail or reformat output, do not continue broad repository search after the target file or range is identified, do not retry malformed shell commands with broader variants, and do not run broad `find`, recursive `grep`, or repeated full-file dumps after KGraph has narrowed the target.';
40
40
  switch (mode) {
41
41
  case 'always':
42
- return `Every chat in this repository must start by running \`kgraph "<topic>"\` before answering or exploring files. Infer the topic from the user's message; no exceptions, including simple or conversational requests. ${useResultBoundary}`;
42
+ return `Every chat in this repository must start by running \`kgraph pack "<topic>" --budget 8000 --json\` before answering or exploring files. Infer the topic from the user's message; no exceptions, including simple or conversational requests. Use \`kgraph "<topic>"\` only when a human-readable briefing is explicitly needed. ${useResultBoundary}`;
43
43
  case 'manual':
44
- return 'Do not run KGraph automatically. Run `kgraph "<topic>"` only when the user explicitly asks for KGraph context or invokes the KGraph command.';
44
+ return 'Do not run KGraph automatically. Run `kgraph pack "<topic>" --budget 8000 --json` only when the user explicitly asks for KGraph context, invokes KGraph, or needs a machine-readable repo-memory pack.';
45
45
  case 'off':
46
46
  return 'KGraph is disabled for this integration.';
47
47
  case 'smart':
48
48
  default:
49
- return `For repo-specific coding, debugging, architecture, refactor, review, or file-exploration requests, run \`kgraph "<topic>"\` before broad repository exploration. Infer the topic from the user's message. Skip KGraph for simple conversational requests that do not depend on repo knowledge. ${useResultBoundary}`;
49
+ return `For repo-specific coding, debugging, architecture, refactor, review, or file-exploration requests, run \`kgraph pack "<topic>" --budget 8000 --json\` before broad repository exploration. Infer the topic from the user's message. Skip KGraph for simple conversational requests that do not depend on repo knowledge. Use \`kgraph "<topic>"\` only when a human-readable briefing is explicitly needed. ${useResultBoundary}`;
50
50
  }
51
51
  }
52
52
  export function renderCapturePolicy() {
@@ -56,7 +56,7 @@ export function renderCapturePolicy() {
56
56
  - Use \`.kgraph/inbox/<slug>.md\` only when a longer structured note is clearer than a single \`kgraph conclude\` command.
57
57
  - A \`.kgraph/inbox/*.md\` note is KGraph runtime capture, not project documentation. It is allowed by this workflow unless the user explicitly says not to capture to KGraph.
58
58
  - Do not skip capture for meaningful UI text, button, link, route, styling, or small file edits. Skip capture only when no reusable repository knowledge was created.
59
- - Do not run KGraph repeatedly. Run it once at the start with \`kgraph "<topic>"\`. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
59
+ - Do not run KGraph repeatedly. Run it once at the start with \`kgraph pack "<topic>" --budget 8000 --json\` for agent-readable context. If repo files changed, write the inbox note first, then run \`kgraph\` once at the end.
60
60
  - After the final \`kgraph\` run, mention whether durable cognition was stored or processed.
61
61
 
62
62
  When using an inbox note, use this structure:
@@ -8,7 +8,7 @@ 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
- const PACK_STEP = `Run \`kgraph pack "<task>" --budget 8000 --json\` when an agent needs a machine-readable, token-budgeted context pack instead of human Markdown context.`;
11
+ const PACK_STEP = `Treat \`kgraph pack "<task>" --budget 8000 --json\` as the primary agent contract: use atoms, source ranges, git changes, omitted items, and inclusion reasons from the ContextPack before reading files. Use human \`kgraph "<topic>"\` output only when the user explicitly wants a briefing.`;
12
12
  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.`;
13
13
  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.`;
14
14
  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.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kentwynn/kgraph",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "Persistent repo intelligence for AI coding assistants.",
5
5
  "type": "module",
6
6
  "bin": {