@aabadin/project-memory-context 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 (88) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +123 -0
  3. package/bin/pmc.mjs +11 -0
  4. package/cli/apply-enrichment-result.mjs +31 -0
  5. package/cli/batch-enrich.mjs +5 -0
  6. package/cli/bootstrap.mjs +357 -0
  7. package/cli/build-worklist.mjs +35 -0
  8. package/cli/context.mjs +49 -0
  9. package/cli/doctor.mjs +29 -0
  10. package/cli/enrich-batch.mjs +11 -0
  11. package/cli/enrich-loop.sh +117 -0
  12. package/cli/enrich-orchestrator.mjs +5 -0
  13. package/cli/enrich-queue.mjs +525 -0
  14. package/cli/enrich-sync.mjs +5 -0
  15. package/cli/enrich.mjs +51 -0
  16. package/cli/fail-enrichment.mjs +28 -0
  17. package/cli/finalize-enrichment.mjs +25 -0
  18. package/cli/init.mjs +66 -0
  19. package/cli/install-pmc.mjs +153 -0
  20. package/cli/materialize-enrichment-artifacts.mjs +38 -0
  21. package/cli/new-project.mjs +41 -0
  22. package/cli/prepare-semantic-jobs.mjs +8 -0
  23. package/cli/project-context.mjs +224 -0
  24. package/cli/sanitize.mjs +235 -0
  25. package/cli/save-intake-context.mjs +22 -0
  26. package/cli/setup.mjs +80 -0
  27. package/cli/status.mjs +81 -0
  28. package/mcp/local-model-server.mjs +74 -0
  29. package/package.json +60 -0
  30. package/plugin/index.mjs +27 -0
  31. package/src/artifacts.mjs +39 -0
  32. package/src/change-detector.mjs +10 -0
  33. package/src/command-dispatch.mjs +84 -0
  34. package/src/declared-intake.mjs +25 -0
  35. package/src/doctor.mjs +114 -0
  36. package/src/enrichment-artifacts.mjs +67 -0
  37. package/src/enrichment-attempts.mjs +17 -0
  38. package/src/enrichment-config.mjs +121 -0
  39. package/src/enrichment-driver.mjs +167 -0
  40. package/src/enrichment-errors.mjs +46 -0
  41. package/src/enrichment-linker.mjs +29 -0
  42. package/src/extractors/architecture-extractor.mjs +8 -0
  43. package/src/extractors/js-ts-extractor.mjs +118 -0
  44. package/src/extractors/regex-extractor.mjs +439 -0
  45. package/src/extractors/rules-extractor.mjs +9 -0
  46. package/src/extractors/stack-extractor.mjs +48 -0
  47. package/src/extractors/structure-extractor.mjs +31 -0
  48. package/src/fail-enrichment.mjs +33 -0
  49. package/src/finalize-enrichment.mjs +30 -0
  50. package/src/graph-backfill.mjs +35 -0
  51. package/src/graph-node-resolver.mjs +64 -0
  52. package/src/index.mjs +2 -0
  53. package/src/intake-context.mjs +16 -0
  54. package/src/invalidation-matrix.mjs +33 -0
  55. package/src/markdown-renderer.mjs +27 -0
  56. package/src/materializer.mjs +128 -0
  57. package/src/memory-payload.mjs +55 -0
  58. package/src/persist-enrichment-result.mjs +33 -0
  59. package/src/platform.mjs +111 -0
  60. package/src/plugin-config.mjs +17 -0
  61. package/src/prepare-semantic-jobs.mjs +33 -0
  62. package/src/project-context-schema.mjs +57 -0
  63. package/src/providers/cloud-api-provider.mjs +88 -0
  64. package/src/providers/local-model-provider.mjs +67 -0
  65. package/src/refresh-state.mjs +21 -0
  66. package/src/result-input.mjs +9 -0
  67. package/src/retrieval/context-renderer.mjs +97 -0
  68. package/src/retrieval/query-engine.mjs +230 -0
  69. package/src/semantic-report.mjs +26 -0
  70. package/src/semantic-unit.mjs +74 -0
  71. package/src/setup-bootstrap.mjs +131 -0
  72. package/src/symbol-extractor.mjs +29 -0
  73. package/src/symbol-index.mjs +30 -0
  74. package/src/symbol-keys.mjs +28 -0
  75. package/src/sync-manifest.mjs +119 -0
  76. package/src/template-installer.mjs +181 -0
  77. package/src/worklist-state.mjs +12 -0
  78. package/templates/claude-code/CLAUDE.md.snippet +36 -0
  79. package/templates/cursor/.cursorrules.snippet +36 -0
  80. package/templates/generic/README-SETUP.md +53 -0
  81. package/templates/opencode/agent/enrich.md +28 -0
  82. package/templates/opencode/autostart-snippet.md +13 -0
  83. package/templates/opencode/commands/get-context.md +22 -0
  84. package/templates/opencode/commands/new-project.md +32 -0
  85. package/templates/opencode/commands/sanitize.md +21 -0
  86. package/templates/opencode/commands/sync-context.md +22 -0
  87. package/templates/project-memory-context workflow.md +129 -0
  88. package/templates/project-memory-context.md +42 -0
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # @aabadin/project-memory-context
2
+
3
+ Portable project memory context CLI — bootstraps semantic enrichment workflows for any AI coding agent.
4
+
5
+ This package installs and wires together:
6
+
7
+ - a project bootstrapper (`pmc bootstrap` / `pmc setup`)
8
+ - a local semantic MCP backed by Ollama (`pmc-local-model`)
9
+ - an `agent-memory` MCP registration using `npx -y @aabadin/agent-memory-mcp`
10
+ - command/workflow templates for `project-memory-context`
11
+ - helper CLIs and artifact management for the semantic enrichment loop
12
+
13
+ ## What it installs for you
14
+
15
+ The setup flow is designed to resolve everything automatically except:
16
+
17
+ - installing Ollama itself
18
+ - pulling/downloading your local model
19
+
20
+ The setup flow handles:
21
+
22
+ - asking for `OLLAMA_BASE_URL`
23
+ - asking for `OLLAMA_MODEL`
24
+ - installing `graphifyy` with Python/pip
25
+ - creating `.planning/project-memory-context/`
26
+ - writing project install state
27
+ - registering MCP servers in `.mcp.json`
28
+ - registering the plugin in `.opencode/opencode.json` when using OpenCode
29
+ - copying `project-memory-context.md` and `project-memory-context workflow.md` into the target project
30
+
31
+ ## Install
32
+
33
+ Install the package globally, or run it with `npx`, then run setup inside the target repository.
34
+
35
+ ```bash
36
+ npm install -g @aabadin/project-memory-context
37
+ pmc setup
38
+ ```
39
+
40
+ Without a global install:
41
+
42
+ ```bash
43
+ npx @aabadin/project-memory-context setup
44
+ ```
45
+
46
+ ## Setup prompts
47
+
48
+ `pmc setup` asks for:
49
+
50
+ - Ollama base URL, default `http://localhost:11434`
51
+ - Ollama model name, default `deepseek-coder-v2:16b-ctx32k`
52
+
53
+ It then attempts to install:
54
+
55
+ ```bash
56
+ python -m pip install graphifyy
57
+ ```
58
+
59
+ or an equivalent Python launcher depending on platform.
60
+
61
+ ## Resulting project files
62
+
63
+ After setup, the target project contains:
64
+
65
+ ```text
66
+ .mcp.json
67
+ .planning/project-memory-context/install.json
68
+ project-memory-context.md
69
+ project-memory-context workflow.md
70
+ ```
71
+
72
+ ## Runtime layout
73
+
74
+ During workflow execution, artifacts accumulate under:
75
+
76
+ ```text
77
+ .planning/project-memory-context/
78
+ intake/
79
+ graph/
80
+ enrichment/
81
+ runs/
82
+ ```
83
+
84
+ ## MCP servers
85
+
86
+ The bootstrap flow writes `.mcp.json` with an `agent-memory` server that runs:
87
+
88
+ ```bash
89
+ npx -y @aabadin/agent-memory-mcp
90
+ ```
91
+
92
+ OpenCode plugin integration may also inject local MCP entries at runtime using values from `.planning/project-memory-context/install.json`:
93
+
94
+ - `pmc-local-model`
95
+ - `pmc-agent-memory`
96
+
97
+ `pmc-local-model` exposes a semantic report tool backed by Ollama.
98
+
99
+ `pmc-agent-memory` points at the public `agent-memory-mcp` binary when plugin injection is used.
100
+
101
+ ## Workflow order
102
+
103
+ 1. `stage-a`
104
+ - intake
105
+ - brainstorming clarification
106
+ - graphify structural mapping
107
+
108
+ 2. `stage-b`
109
+ - build worklist
110
+ - prepare semantic jobs
111
+ - call `pmc-local-model`
112
+ - materialize `*.memory.json`
113
+ - store/update in `pmc-agent-memory`
114
+ - materialize `*.result.json`
115
+ - finalize or fail each symbol
116
+
117
+ ## Development verification
118
+
119
+ Run the local test suite:
120
+
121
+ ```bash
122
+ node --test "tools/project-memory-context/tests/*.test.mjs"
123
+ ```
package/bin/pmc.mjs ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { runCommand } from '../src/command-dispatch.mjs';
3
+
4
+ const exitCode = await runCommand(process.argv.slice(2)).catch((error) => {
5
+ console.error('[pmc] FATAL:', error.message);
6
+ return 1;
7
+ });
8
+
9
+ if (exitCode !== 0) {
10
+ process.exit(exitCode);
11
+ }
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from 'node:path';
3
+
4
+ import { persistEnrichmentResult } from '../src/persist-enrichment-result.mjs';
5
+ import { loadResultInput } from '../src/result-input.mjs';
6
+
7
+ const [, , rawJson] = process.argv;
8
+
9
+ if (!rawJson) {
10
+ console.error('Usage: node apply-enrichment-result.mjs "{...json result...}"');
11
+ process.exit(1);
12
+ }
13
+
14
+ let result;
15
+ try {
16
+ result = await loadResultInput(rawJson);
17
+ } catch (error) {
18
+ console.error(`Invalid JSON payload: ${error.message}`);
19
+ process.exit(1);
20
+ }
21
+
22
+ const projectRoot = resolve(process.cwd());
23
+ const persisted = await persistEnrichmentResult({ projectRoot, result });
24
+ console.log(JSON.stringify({
25
+ saved: true,
26
+ graphFile: persisted.graphFile,
27
+ indexFile: persisted.indexFile,
28
+ symbolKey: result.symbolKey,
29
+ memoryId: result.memoryId ?? null,
30
+ graphNodeId: result.graphNodeId ?? null,
31
+ }, null, 2));
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ console.error('[batch-enrich] DEPRECATED: This script hardcodes Ollama. Use enrich-queue.mjs instead.');
3
+ console.error('[batch-enrich] The enrich-queue supports the shared fallback driver (local-model → cloud-api → agent-subagent).');
4
+ console.error('[batch-enrich] Run: node tools/project-memory-context/cli/enrich-queue.mjs');
5
+ process.exit(1);
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env node
2
+ import { resolve, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { access, constants, readFile, writeFile, mkdir } from 'node:fs/promises';
5
+ import { existsSync, readdirSync, mkdirSync, copyFileSync } from 'node:fs';
6
+ import { spawnSync } from 'node:child_process';
7
+
8
+ import { bootstrapProjectInstall } from '../src/setup-bootstrap.mjs';
9
+ import { resolveGraphify, spawnBackground, resolvePythonBin } from '../src/platform.mjs';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const PMC_CLI_ROOT = resolve(__dirname);
13
+ const PMC_ROOT = resolve(__dirname, '..', '..', '..');
14
+ const PMC_PACKAGE_ROOT = resolve(__dirname, '..');
15
+
16
+ const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
17
+ const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'deepseek-coder-v2:16b-ctx32k';
18
+ const PMC_CONCURRENCY = parseInt(process.env.PMC_CONCURRENCY || '8', 10);
19
+
20
+ function log(msg) { console.error(`[bootstrap] ${msg}`); }
21
+
22
+ export function buildDefaultEnrichmentConfig() {
23
+ return {
24
+ preferredModes: ['local-model', 'cloud-api', 'agent-subagent'],
25
+ localModel: {
26
+ provider: 'ollama',
27
+ baseUrl: OLLAMA_URL,
28
+ model: OLLAMA_MODEL,
29
+ },
30
+ cloudApi: {
31
+ provider: 'openai-compatible',
32
+ baseUrl: '',
33
+ model: '',
34
+ apiKeyEnv: 'PMC_CLOUD_API_KEY',
35
+ },
36
+ agentSubagent: {
37
+ enabled: true,
38
+ agentName: 'enrich',
39
+ },
40
+ };
41
+ }
42
+
43
+ export function mergeEnrichmentConfig(current = {}) {
44
+ current = current ?? {};
45
+ const source = current.enrichment ?? current;
46
+ const defaults = buildDefaultEnrichmentConfig();
47
+ return {
48
+ ...defaults,
49
+ ...source,
50
+ preferredModes: source.preferredModes ?? defaults.preferredModes,
51
+ localModel: {
52
+ ...defaults.localModel,
53
+ ...source.localModel,
54
+ },
55
+ cloudApi: {
56
+ ...defaults.cloudApi,
57
+ ...source.cloudApi,
58
+ },
59
+ agentSubagent: {
60
+ ...defaults.agentSubagent,
61
+ ...source.agentSubagent,
62
+ },
63
+ };
64
+ }
65
+
66
+ function getTargetDir(args) {
67
+ for (const arg of args) {
68
+ if (arg === '--help' || arg === '-h') return null;
69
+ if (!arg.startsWith('--')) return resolve(arg);
70
+ }
71
+ return process.cwd();
72
+ }
73
+
74
+ async function ensureDir(dir) {
75
+ try { await access(dir, constants.F_OK); return true; }
76
+ catch { return false; }
77
+ }
78
+
79
+ async function readJson(filePath) {
80
+ try { return JSON.parse(await readFile(filePath, 'utf8')); }
81
+ catch { return null; }
82
+ }
83
+
84
+ async function installGraphify() {
85
+ log('Installing graphify (Python)...');
86
+ const pythonBin = resolvePythonBin();
87
+ const r = spawnSync(pythonBin, ['-m', 'pip', 'install', 'graphifyy'], { stdio: 'inherit' });
88
+ if (r.status === 0) {
89
+ log(`graphifyy installed via ${pythonBin}`);
90
+ return true;
91
+ }
92
+ log('WARNING: graphifyy install failed. Graphify step may not work.');
93
+ return false;
94
+ }
95
+
96
+ async function syncToolsToTarget(projectRoot) {
97
+ log('Copying PMC tools to target repo...');
98
+ const srcCli = resolve(PMC_ROOT, 'tools', 'project-memory-context', 'cli');
99
+ const dstCli = resolve(projectRoot, 'tools', 'project-memory-context', 'cli');
100
+
101
+ mkdirSync(resolve(projectRoot, 'tools', 'project-memory-context'), { recursive: true });
102
+ mkdirSync(dstCli, { recursive: true });
103
+
104
+ const files = ['new-project.mjs', 'bootstrap.mjs', 'enrich-queue.mjs', 'build-worklist.mjs', 'project-context.mjs', 'sanitize.mjs'];
105
+ for (const f of files) {
106
+ try {
107
+ copyFileSync(resolve(srcCli, f), resolve(dstCli, f));
108
+ log(` copied ${f}`);
109
+ } catch {
110
+ log(` skipped ${f} (not found in source)`);
111
+ }
112
+ }
113
+
114
+ const srcSrc = resolve(PMC_ROOT, 'tools', 'project-memory-context', 'src');
115
+ const dstSrc = resolve(projectRoot, 'tools', 'project-memory-context', 'src');
116
+ mkdirSync(dstSrc, { recursive: true });
117
+
118
+ function copyTree(srcDir, dstDir) {
119
+ mkdirSync(dstDir, { recursive: true });
120
+ const entries = readdirSync(srcDir, { withFileTypes: true });
121
+ for (const entry of entries) {
122
+ const srcPath = resolve(srcDir, entry.name);
123
+ const dstPath = resolve(dstDir, entry.name);
124
+ if (entry.isDirectory()) {
125
+ copyTree(srcPath, dstPath);
126
+ } else if (entry.isFile() && entry.name.endsWith('.mjs')) {
127
+ copyFileSync(srcPath, dstPath);
128
+ log(` copied ${dstPath.replace(`${projectRoot}\\`, '').replace(/\\/g, '/')}`);
129
+ }
130
+ }
131
+ }
132
+
133
+ try {
134
+ copyTree(srcSrc, dstSrc);
135
+ } catch { log(' src/ copy skipped (may not exist)'); }
136
+
137
+ log(' PMC tools synced to target repo.');
138
+ }
139
+
140
+ async function runStageA(projectRoot) {
141
+ log('Running graphify update (structural AST analysis, no LLM)...');
142
+
143
+ let graphifyExe;
144
+ try {
145
+ graphifyExe = resolveGraphify();
146
+ } catch (e) {
147
+ log(` ${e.message}`);
148
+ log(' Stage-a skipped. Install graphify with `pip install graphifyy` or set PMC_GRAPHIFY_PATH.');
149
+ return true;
150
+ }
151
+
152
+ const graphOutDir = resolve(projectRoot, '.planning', 'project-memory-context', 'graph');
153
+ const graphifyOutDir = resolve(projectRoot, 'graphify-out');
154
+
155
+ const r = spawnSync(`"${graphifyExe}"`, ['update', projectRoot], {
156
+ cwd: projectRoot,
157
+ stdio: 'inherit',
158
+ shell: true,
159
+ });
160
+
161
+ if (r.status === 0) {
162
+ log(' Graph extraction complete. Copying to .planning...');
163
+ try {
164
+ if (!existsSync(graphifyOutDir)) { log(' graphify-out not found'); return false; }
165
+ const files = readdirSync(graphifyOutDir);
166
+ for (const f of files) {
167
+ if (f === 'graph.json' || f === 'graph.metadata.json' || f === 'graph.html' || f === 'GRAPH_REPORT.md') {
168
+ copyFileSync(resolve(graphifyOutDir, f), resolve(graphOutDir, f));
169
+ log(` copied ${f}`);
170
+ }
171
+ }
172
+ } catch (e) { log(` Copy error: ${e.message}`); }
173
+ log(' Graphify update complete (AST only, no semantic LLM).');
174
+ log(' For full semantic enrichment, set ANTHROPIC_API_KEY and run graphify extract.');
175
+ } else {
176
+ log(` Graphify update failed with code ${r.status}. Stage-b and enrichment still work.`);
177
+ }
178
+
179
+ return true;
180
+ }
181
+
182
+ function findFiles(dir, exts, ignore, projectRoot) {
183
+ const results = [];
184
+ try {
185
+ const entries = readdirSync(dir, { withFileTypes: true });
186
+ for (const entry of entries) {
187
+ const full = resolve(dir, entry.name);
188
+ if (ignore.some(i => full.includes(i))) continue;
189
+ if (entry.isDirectory()) {
190
+ results.push(...findFiles(full, exts, ignore, projectRoot));
191
+ } else if (exts.some(e => entry.name.endsWith(e))) {
192
+ results.push(full.replace(`${projectRoot}\\`, '').replace(`${projectRoot}/`, '').replace(/\\/g, '/'));
193
+ }
194
+ }
195
+ } catch { /* skip inaccessible dirs */ }
196
+ return results;
197
+ }
198
+
199
+ async function runStageB(projectRoot) {
200
+ log('Running stage-b (build-worklist)...');
201
+
202
+ const ignore = ['node_modules', 'dist', '.git', 'bin', 'obj', '.opencode', '.planning'];
203
+ const files = [
204
+ ...findFiles(projectRoot, ['.ts', '.mjs', '.js'], ignore, projectRoot),
205
+ ...findFiles(projectRoot, ['.cs'], ignore, projectRoot),
206
+ ];
207
+
208
+ log(` Found ${files.filter(f => f.endsWith('.cs')).length} CS files, ${files.filter(f => !f.endsWith('.cs')).length} TS/JS files`);
209
+
210
+ if (files.length === 0) {
211
+ log('No files to process. Skipping stage-b.');
212
+ return true;
213
+ }
214
+
215
+ const worklistScript = resolve(PMC_CLI_ROOT, 'build-worklist.mjs');
216
+ const r = spawnSync('node', [worklistScript, ...files], {
217
+ cwd: projectRoot,
218
+ stdio: 'inherit',
219
+ env: { ...process.env, PMC_CONCURRENCY: String(PMC_CONCURRENCY) },
220
+ });
221
+
222
+ if (r.status !== 0) {
223
+ const altWorklistScript = resolve(projectRoot, 'tools', 'project-memory-context', 'cli', 'build-worklist.mjs');
224
+ log(` Primary worklist failed, trying local: ${altWorklistScript}`);
225
+ const r2 = spawnSync('node', [altWorklistScript, ...files], {
226
+ cwd: projectRoot,
227
+ stdio: 'inherit',
228
+ env: { ...process.env, PMC_CONCURRENCY: String(PMC_CONCURRENCY) },
229
+ });
230
+ if (r2.status !== 0) {
231
+ log(`Stage-b failed with code ${r2.status}`);
232
+ return false;
233
+ }
234
+ }
235
+
236
+ log('Stage-b complete.');
237
+ return true;
238
+ }
239
+
240
+ function printUsage() {
241
+ console.log(`
242
+ bootstrap - Portable PMC bootstrap for any repo
243
+
244
+ Usage:
245
+ node bootstrap.mjs <target-repo> [--stage-a] [--stage-b] [--all] [--enrich]
246
+
247
+ Arguments:
248
+ target-repo Path to the target repository (default: current dir)
249
+
250
+ Options:
251
+ --stage-a Run intake + graphify after setup
252
+ --stage-b Run symbol extraction + worklist after setup
253
+ --all Run both stages after setup (default: setup only)
254
+ --enrich Also start the enrichment queue in background after setup
255
+ --help, -h Show this help
256
+
257
+ Environment variables:
258
+ OLLAMA_URL Ollama URL (default: http://localhost:11434)
259
+ OLLAMA_MODEL Ollama model (default: deepseek-coder-v2:16b-ctx32k)
260
+ PMC_CONCURRENCY Parallel slots (default: 8)
261
+ PMC_GRAPHIFY_PATH Custom path to graphify executable
262
+
263
+ Example:
264
+ node bootstrap.mjs /path/to/my-repo --all
265
+ node bootstrap.mjs /path/to/my-repo --all --enrich
266
+ OLLAMA_MODEL=qwen3-coder:30b node bootstrap.mjs . --stage-b --enrich
267
+ `);
268
+ }
269
+
270
+ export async function main(args = process.argv.slice(2)) {
271
+ if (args.includes('--help') || args.includes('-h')) {
272
+ printUsage();
273
+ return 0;
274
+ }
275
+
276
+ const targetDir = getTargetDir(args);
277
+ if (!targetDir) { printUsage(); return 0; }
278
+
279
+ const runStageAFlag = args.includes('--stage-a') || args.includes('--all');
280
+ const runStageBFlag = args.includes('--stage-b') || args.includes('--all');
281
+ const runEnrichFlag = args.includes('--enrich');
282
+
283
+ log(`Target repo: ${targetDir}`);
284
+ log(`PMC package root: ${PMC_ROOT}`);
285
+ log(`Ollama: ${OLLAMA_URL} | Model: ${OLLAMA_MODEL}`);
286
+
287
+ if (!await ensureDir(targetDir)) {
288
+ console.error(`[bootstrap] ERROR: Directory not found: ${targetDir}`);
289
+ return 1;
290
+ }
291
+
292
+ log('Installing graphify...');
293
+ await installGraphify();
294
+
295
+ log('Running portable project install...');
296
+ const { configPath } = await bootstrapProjectInstall({
297
+ projectRoot: targetDir,
298
+ packageRoot: PMC_PACKAGE_ROOT,
299
+ ollamaBaseUrl: OLLAMA_URL,
300
+ ollamaModel: OLLAMA_MODEL,
301
+ });
302
+
303
+ log('Syncing PMC tools to target repo...');
304
+ await syncToolsToTarget(targetDir);
305
+
306
+ log('Generating project context artifacts...');
307
+ const projectContextScript = resolve(PMC_CLI_ROOT, 'project-context.mjs');
308
+ const contextResult = spawnSync('node', [projectContextScript, targetDir], {
309
+ cwd: targetDir,
310
+ stdio: 'inherit',
311
+ env: { ...process.env },
312
+ });
313
+ if (contextResult.status !== 0) {
314
+ log(`WARNING: project-context generation failed with code ${contextResult.status}`);
315
+ }
316
+
317
+ log('');
318
+ log('========================================');
319
+ log('PMC installed successfully!');
320
+ log(` Target: ${targetDir}`);
321
+ log(` Config: ${configPath}`);
322
+ log('========================================');
323
+ log('');
324
+
325
+ if (runStageAFlag) {
326
+ log('Executing stage-a...');
327
+ await runStageA(targetDir);
328
+ }
329
+
330
+ if (runStageBFlag) {
331
+ log('Executing stage-b...');
332
+ const ok = await runStageB(targetDir);
333
+ if (!ok) log('WARNING: stage-b had issues. Check output above.');
334
+ }
335
+
336
+ if (runEnrichFlag && (runStageAFlag || runStageBFlag)) {
337
+ log('Starting background enrichment queue...');
338
+ const enrichScript = resolve(targetDir, 'tools', 'project-memory-context', 'cli', 'enrich-queue.mjs');
339
+ spawnBackground(process.execPath, [enrichScript], { cwd: targetDir });
340
+ log(' Enrichment running in background.');
341
+ } else if (!runStageAFlag && !runStageBFlag) {
342
+ log('Run enrichment with:');
343
+ log(` cd ${targetDir}`);
344
+ log(` node tools/project-memory-context/cli/enrich-queue.mjs`);
345
+ }
346
+
347
+ log('Done.');
348
+ return 0;
349
+ }
350
+
351
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
352
+ const exitCode = await main().catch((err) => {
353
+ console.error('[bootstrap] FATAL:', err.message);
354
+ return 1;
355
+ });
356
+ if (exitCode !== 0) process.exit(exitCode);
357
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join, relative, resolve } from 'node:path';
4
+
5
+ import {
6
+ ensureProjectMemoryContextDirs,
7
+ readJsonArtifact,
8
+ writeJsonArtifact,
9
+ } from '../src/artifacts.mjs';
10
+ import { attachGraphNodeIds } from '../src/graph-node-resolver.mjs';
11
+ import { buildEnrichmentWorklist, extractTopLevelSymbols } from '../src/symbol-extractor.mjs';
12
+
13
+ const projectRoot = resolve(process.cwd());
14
+ const dirs = await ensureProjectMemoryContextDirs(projectRoot);
15
+ const symbolIndex = await readJsonArtifact(join(dirs.enrichment, 'symbol-index.json'), {});
16
+ const graph = await readJsonArtifact(join(dirs.graph, 'graph.json'), { nodes: [], edges: [] });
17
+ const files = process.argv.slice(2);
18
+
19
+ if (files.length === 0) {
20
+ console.error('Usage: node build-worklist.mjs <file1> [file2 ...]');
21
+ process.exit(1);
22
+ }
23
+
24
+ const symbols = [];
25
+ for (const file of files) {
26
+ const absolute = resolve(projectRoot, file);
27
+ const content = await readFile(absolute, 'utf8');
28
+ symbols.push(...extractTopLevelSymbols({ filePath: relative(projectRoot, absolute), content }));
29
+ }
30
+
31
+ const resolvedSymbols = attachGraphNodeIds({ symbols, graph });
32
+ const worklist = buildEnrichmentWorklist({ symbols: resolvedSymbols, symbolIndex });
33
+ const outputFile = join(dirs.enrichment, 'worklist.json');
34
+ await writeJsonArtifact(outputFile, worklist);
35
+ console.log(JSON.stringify({ saved: true, file: outputFile, count: worklist.length }, null, 2));
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const SCRIPT_PATH = resolve(dirname(fileURLToPath(import.meta.url)), 'project-context.mjs');
7
+
8
+ function printHelp() {
9
+ console.log('Usage: pmc context [project-root] [--refresh]');
10
+ }
11
+
12
+ export async function runProjectContext(projectRoot = process.cwd(), refresh = false) {
13
+ const mod = await import('./project-context.mjs');
14
+ if (typeof mod.runProjectContextCli === 'function') {
15
+ return mod.runProjectContextCli(projectRoot, { refresh });
16
+ }
17
+ return null;
18
+ }
19
+
20
+ export async function main(args = process.argv.slice(2)) {
21
+ if (args.includes('--help') || args.includes('-h')) {
22
+ printHelp();
23
+ return 0;
24
+ }
25
+
26
+ return await new Promise((resolvePromise, rejectPromise) => {
27
+ const child = spawn(process.execPath, [SCRIPT_PATH, ...args], { stdio: 'inherit' });
28
+ child.once('error', rejectPromise);
29
+ child.once('exit', (code, signal) => {
30
+ if (signal) {
31
+ rejectPromise(new Error(`context exited from signal ${signal}`));
32
+ return;
33
+ }
34
+
35
+ resolvePromise(code ?? 0);
36
+ });
37
+ });
38
+ }
39
+
40
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
41
+ const exitCode = await main().catch((error) => {
42
+ console.error('[context] FATAL:', error.message);
43
+ return 1;
44
+ });
45
+
46
+ if (exitCode !== 0) {
47
+ process.exit(exitCode);
48
+ }
49
+ }
package/cli/doctor.mjs ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { runDoctor } from '../src/doctor.mjs';
4
+ import { resolvePythonBin } from '../src/platform.mjs';
5
+
6
+ function spawnCheck(bin, args) {
7
+ const result = spawnSync(bin, args, { encoding: 'utf-8', timeout: 5000 });
8
+ return { exitCode: result.status ?? 1, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
9
+ }
10
+
11
+ const { checks } = await runDoctor({ resolvePythonBin, spawnCheck });
12
+
13
+ const icon = { ok: '✓', warn: '⚠', fail: '✗' };
14
+ const color = { ok: '\x1b[32m', warn: '\x1b[33m', fail: '\x1b[31m', reset: '\x1b[0m' };
15
+
16
+ console.log('\npmc doctor\n');
17
+ for (const c of checks) {
18
+ const col = color[c.status] ?? '';
19
+ console.log(` ${col}${icon[c.status]}${color.reset} ${c.name.padEnd(22)} ${c.message}`);
20
+ }
21
+ console.log('');
22
+
23
+ const hasFail = checks.some(c => c.status === 'fail');
24
+ if (hasFail) {
25
+ console.log('Some checks failed. Fix the issues above before running pmc setup.\n');
26
+ process.exit(1);
27
+ } else {
28
+ console.log('All checks passed.\n');
29
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { resolve, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const PROJECT_ROOT = resolve(__dirname, '..', '..', '..');
7
+
8
+ console.error('[enrich-batch] DEPRECATED: This script hardcodes Ollama. Use enrich-queue.mjs instead.');
9
+ console.error('[enrich-batch] The enrich-queue supports the shared fallback driver (local-model → cloud-api → agent-subagent).');
10
+ console.error('[enrich-batch] Run: node tools/project-memory-context/cli/enrich-queue.mjs');
11
+ process.exit(1);