@prodero/curator 0.0.0-stage → 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 (137) hide show
  1. package/ARCHITECTURE.md +298 -0
  2. package/CHANGELOG.md +16 -0
  3. package/CONTRIBUTING.md +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +94 -2
  6. package/SECURITY.md +7 -0
  7. package/bin/prodero +7 -0
  8. package/dist/cli.js +228 -0
  9. package/dist/context-instructions.js +2 -0
  10. package/dist/contracts.js +50 -0
  11. package/dist/conversation-presentation.js +262 -0
  12. package/dist/core/agent-instructions.js +39 -0
  13. package/dist/core/applicability-label.js +11 -0
  14. package/dist/core/applicability.js +143 -0
  15. package/dist/core/bootstrap.js +129 -0
  16. package/dist/core/consolidation.js +315 -0
  17. package/dist/core/constants.js +21 -0
  18. package/dist/core/context-allocation.js +54 -0
  19. package/dist/core/context-snapshot.js +12 -0
  20. package/dist/core/context.js +489 -0
  21. package/dist/core/curation-clauses.js +68 -0
  22. package/dist/core/curation-evidence.js +98 -0
  23. package/dist/core/curation-graphical.js +105 -0
  24. package/dist/core/curation-review-presentation.js +4 -0
  25. package/dist/core/curation-review-state.js +62 -0
  26. package/dist/core/curation-review.js +573 -0
  27. package/dist/core/curation-revision.js +106 -0
  28. package/dist/core/curation-semantic-contract.js +239 -0
  29. package/dist/core/curation-semantic-jobs.js +280 -0
  30. package/dist/core/diagnostics.js +17 -0
  31. package/dist/core/format.js +114 -0
  32. package/dist/core/ids.js +6 -0
  33. package/dist/core/imports.js +169 -0
  34. package/dist/core/index.js +93 -0
  35. package/dist/core/instruction-review.js +86 -0
  36. package/dist/core/integrity-files.js +174 -0
  37. package/dist/core/intelligence.js +95 -0
  38. package/dist/core/knowledge-analysis.js +46 -0
  39. package/dist/core/knowledge-authority.js +143 -0
  40. package/dist/core/knowledge-curation.js +316 -0
  41. package/dist/core/knowledge-engine.js +475 -0
  42. package/dist/core/knowledge-graph.js +191 -0
  43. package/dist/core/knowledge-lifecycle.js +212 -0
  44. package/dist/core/knowledge-management.js +113 -0
  45. package/dist/core/knowledge-migration.js +84 -0
  46. package/dist/core/knowledge-policy.js +38 -0
  47. package/dist/core/knowledge-provenance.js +90 -0
  48. package/dist/core/knowledge-write-policy.js +85 -0
  49. package/dist/core/language.js +14 -0
  50. package/dist/core/matching.js +6 -0
  51. package/dist/core/memory-index.js +239 -0
  52. package/dist/core/memory-intelligence.js +288 -0
  53. package/dist/core/memory-link-repair.js +66 -0
  54. package/dist/core/memory-operations.js +122 -0
  55. package/dist/core/memory-pressure.js +212 -0
  56. package/dist/core/memory-sync-curator.js +62 -0
  57. package/dist/core/memory-sync.js +463 -0
  58. package/dist/core/memory-transformation-bundle.js +163 -0
  59. package/dist/core/memory-transformation-proposal.js +135 -0
  60. package/dist/core/memory-transformation.js +108 -0
  61. package/dist/core/memory.js +186 -0
  62. package/dist/core/migrations.js +569 -0
  63. package/dist/core/path-scope.js +27 -0
  64. package/dist/core/plugin-files.js +116 -0
  65. package/dist/core/project-root.js +39 -0
  66. package/dist/core/project.js +634 -0
  67. package/dist/core/promotion.js +279 -0
  68. package/dist/core/reconciliation.js +61 -0
  69. package/dist/core/secrets.js +67 -0
  70. package/dist/core/semantic-safety.js +33 -0
  71. package/dist/core/session-index.js +112 -0
  72. package/dist/core/session-review.js +150 -0
  73. package/dist/core/sessions.js +289 -0
  74. package/dist/core/settings.js +76 -0
  75. package/dist/core/skill-curation.js +239 -0
  76. package/dist/core/skill-polish.js +41 -0
  77. package/dist/core/skills.js +368 -0
  78. package/dist/core/storage.js +408 -0
  79. package/dist/core/sync-transaction.js +254 -0
  80. package/dist/core/telemetry.js +64 -0
  81. package/dist/core/templates.js +53 -0
  82. package/dist/core/types.js +2 -0
  83. package/dist/core/update.js +320 -0
  84. package/dist/curation-review-adapter.js +92 -0
  85. package/dist/diagnostic-boundary.js +54 -0
  86. package/dist/filesystem/permissions.js +39 -0
  87. package/dist/hosts/adapters.js +119 -0
  88. package/dist/hosts/files.js +133 -0
  89. package/dist/hosts/manager.js +150 -0
  90. package/dist/hosts/public-entry.js +47 -0
  91. package/dist/hosts/runtime-command.js +66 -0
  92. package/dist/hosts/runtime.js +139 -0
  93. package/dist/hosts/wizard.js +114 -0
  94. package/dist/knowledge-table.js +38 -0
  95. package/dist/mcp-development.js +69 -0
  96. package/dist/mcp-workspace.js +141 -0
  97. package/dist/memory-presentation.js +172 -0
  98. package/dist/plugins/api.js +2 -0
  99. package/dist/plugins/context.js +20 -0
  100. package/dist/plugins/mcp.js +72 -0
  101. package/dist/plugins/registry.js +106 -0
  102. package/dist/presentation-cards.js +32 -0
  103. package/dist/presentation-server.js +79 -0
  104. package/dist/presentation-trace.js +174 -0
  105. package/dist/presentation.js +412 -0
  106. package/dist/providers/mcp-sampling.js +45 -0
  107. package/dist/server.js +656 -0
  108. package/dist/settings-definition.js +22 -0
  109. package/dist/skill-instructions.js +2 -0
  110. package/dist-ui/index.html +214 -0
  111. package/docs/APPLICABILITY.md +151 -0
  112. package/docs/COMPATIBILITY.md +49 -0
  113. package/docs/CONTEXT-SELECTION-V2.md +61 -0
  114. package/docs/CURATION-APP-REVIEW.md +156 -0
  115. package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
  116. package/docs/IMPORTER-CURATION.md +142 -0
  117. package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
  118. package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
  119. package/docs/MIGRATIONS.md +99 -0
  120. package/docs/PRIVACY.md +28 -0
  121. package/docs/RECOVERY.md +56 -0
  122. package/docs/RELEASING.md +40 -0
  123. package/docs/hosts.md +134 -0
  124. package/docs/plugins.md +83 -0
  125. package/docs/ui-themes.md +43 -0
  126. package/instructions/AGENTS.md +33 -0
  127. package/package.json +88 -4
  128. package/plugins/migrate_ai_studio/README.md +101 -0
  129. package/plugins/migrate_ai_studio/index.cjs +396 -0
  130. package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
  131. package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
  132. package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
  133. package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
  134. package/plugins/migrate_ai_studio/plugin.json +11 -0
  135. package/plugins/migrate_ai_studio/schema.cjs +20 -0
  136. package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
  137. package/prodero.release.json +5 -0
@@ -0,0 +1,139 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { adapters, block, replaceBlock, LAUNCHER, GLOBAL_START, GLOBAL_END, MCP_START, MCP_END } from './adapters.js';
5
+ import { environment, expectedEntry, inspect, receiptSchema } from './manager.js';
6
+ import { verifyPublicEntry, resolveNpmCli } from './public-entry.js';
7
+ import { read, fail, transact } from './files.js';
8
+ const stable = (value) => JSON.stringify(value, (_key, item) => item && typeof item === 'object' && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item);
9
+ async function packageEntry(host, entry, env) {
10
+ // Accept historical receipt targets only to migrate them to the public executable.
11
+ const cli = entry.args.length === 2 && entry.args[1] === 'mcp' && path.isAbsolute(entry.args[0])
12
+ ? entry.args[0] : path.resolve(path.dirname(entry.command), '../dist/cli.js');
13
+ const adapter = { id: host };
14
+ const result = expectedEntry(adapter, cli, env);
15
+ await verifyPublicEntry(result);
16
+ return result;
17
+ }
18
+ async function readPackage(root) {
19
+ try {
20
+ return JSON.parse(await fs.readFile(path.join(root, 'package.json'), 'utf8'));
21
+ }
22
+ catch {
23
+ return fail('HOST_RUNTIME_MISSING', 'Cannot read the selected runtime package.json. No settings changed.');
24
+ }
25
+ }
26
+ async function developmentEntry(host, checkout, env) {
27
+ if (!path.isAbsolute(checkout))
28
+ fail('HOST_UNSUPPORTED_PATH', '--checkout must be an absolute Prodero repository path.');
29
+ const root = await fs.realpath(checkout).catch(() => fail('HOST_RUNTIME_MISSING', 'Development checkout does not exist.'));
30
+ const metadata = await readPackage(root);
31
+ if (!['@prodero/curator', '@prodero/cli'].includes(metadata.name ?? '') || !metadata.scripts?.['mcp:dev'])
32
+ fail('HOST_RUNTIME_MISSING', 'Select a Prodero checkout with an mcp:dev script.');
33
+ for (const relative of ['src/cli.ts', 'scripts/mcp-dev.ts', 'scripts/mcp-dev-launcher.mjs', 'node_modules/tsx/package.json']) {
34
+ if (!(await fs.stat(path.join(root, relative)).catch(() => null))?.isFile())
35
+ fail('HOST_RUNTIME_MISSING', 'Development source or dependencies are missing. Install checkout dependencies first.');
36
+ }
37
+ await resolveNpmCli(env);
38
+ return { root };
39
+ }
40
+ /** Refuse to silently switch a global registration shadowed by workspace policy. */
41
+ async function checkWorkspace(host, workspace, globalConfig, nativeConfig) {
42
+ if (!path.isAbsolute(workspace))
43
+ fail('HOST_UNSUPPORTED_PATH', '--workspace must be absolute.');
44
+ workspace = await fs.realpath(workspace).catch(() => fail('HOST_SCOPE_CONFLICT', 'The selected workspace does not exist.'));
45
+ if (!(await fs.stat(workspace)).isDirectory())
46
+ fail('HOST_SCOPE_CONFLICT', 'The selected workspace is not a directory.');
47
+ const globalReal = await fs.realpath(globalConfig).catch(() => globalConfig);
48
+ for (let dir = path.resolve(workspace);; dir = path.dirname(dir)) {
49
+ const relatives = host === 'codex' ? ['.codex/config.toml'] : ['.mcp.json', '.claude/settings.json', '.claude/settings.local.json'];
50
+ for (const relative of relatives) {
51
+ const file = path.join(dir, relative);
52
+ if (file === globalConfig || await fs.realpath(file).catch(() => null) === globalReal)
53
+ continue;
54
+ const text = await fs.readFile(file, 'utf8').catch((error) => {
55
+ if (error.code === 'ENOENT')
56
+ return '';
57
+ return fail('HOST_SCOPE_CONFLICT', 'Cannot inspect workspace MCP overrides. No settings changed.');
58
+ });
59
+ if (/prodero/i.test(text))
60
+ fail('HOST_SCOPE_CONFLICT', `Workspace Prodero settings in ${file} require review before switching the user-level connection.`);
61
+ }
62
+ if (dir === path.dirname(dir))
63
+ break;
64
+ }
65
+ if (host === 'claude') {
66
+ const projects = JSON.parse(nativeConfig).projects ?? {};
67
+ for (const [project, settings] of Object.entries(projects)) {
68
+ const relative = path.relative(project, workspace);
69
+ if ((relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) && /prodero/i.test(JSON.stringify(settings))) {
70
+ fail('HOST_SCOPE_CONFLICT', 'Claude project-specific Prodero settings require review before switching the user-level connection.');
71
+ }
72
+ }
73
+ }
74
+ }
75
+ /** Explicit single-host operation. Never discovers a target host or updates the running process. */
76
+ export async function switchRuntime(hostId, mode, options = {}) {
77
+ if (hostId !== 'codex' && hostId !== 'claude')
78
+ fail('HOST_UNKNOWN', 'Choose exactly one host: codex or claude.');
79
+ if (mode !== 'package' && mode !== 'dev')
80
+ fail('HOST_RUNTIME_MODE', 'Choose package or dev.');
81
+ if (options.packageRoot && !path.isAbsolute(options.packageRoot))
82
+ fail('HOST_UNSUPPORTED_PATH', '--package-root must be absolute.');
83
+ const ctx = environment(options);
84
+ const host = (await adapters(ctx.home, ctx.env, hostId))[0];
85
+ const view = await inspect(host, ctx, false, true);
86
+ await checkWorkspace(hostId, options.workspace ?? process.cwd(), host.config, view.text);
87
+ const { receipt } = view;
88
+ const migrating = !receipt && ['externally-configured', 'external-incomplete'].includes(view.base.state);
89
+ if (!receipt && !migrating)
90
+ fail('HOST_NOT_INSTALLED', 'Configure this agent with prodero install before switching runtimes.');
91
+ if (migrating && !options.adoptDev && !options.dryRun)
92
+ fail('HOST_ADOPTION_REQUIRED', 'Preview with --dry-run, then use --adopt-dev to migrate this manual prodero-dev registration.');
93
+ const stored = receipt?.format === 2 ? receipt.runtime : undefined;
94
+ const selectedPackage = options.packageRoot
95
+ ? expectedEntry(host, path.join(options.packageRoot, 'dist/cli.js'))
96
+ : stored?.packageEntry ?? receipt?.entry ?? expectedEntry(host, fileURLToPath(new URL('../cli.js', import.meta.url)));
97
+ const packaged = await packageEntry(hostId, selectedPackage, ctx.env);
98
+ let devRoot = stored?.devRoot ?? null, devEntry = stored?.devEntry ?? null;
99
+ if (mode === 'dev') {
100
+ const checkout = options.checkout ?? devRoot;
101
+ if (!checkout)
102
+ fail('HOST_RUNTIME_MISSING', 'Supply --checkout with the absolute Prodero repository path.');
103
+ const development = await developmentEntry(hostId, checkout, ctx.env);
104
+ devRoot = development.root;
105
+ let explicit = false;
106
+ const other = hostId === 'codex' ? 'claude' : 'codex';
107
+ const saved = await read(ctx.home, path.join(ctx.state, other, 'receipt.json'));
108
+ if (saved !== null) {
109
+ const otherReceipt = receiptSchema.parse(JSON.parse(saved));
110
+ explicit = otherReceipt.format === 2 && Boolean(otherReceipt.runtime.devRoot) && otherReceipt.runtime.devRoot !== devRoot;
111
+ }
112
+ devEntry = { ...packaged, args: ['mcp', '--dev', ...(explicit ? ['--checkout', devRoot] : [])] };
113
+ }
114
+ const entry = mode === 'dev' ? devEntry : packaged;
115
+ const config = host.edit(migrating ? host.removeAlternate(view.text) : view.text, entry);
116
+ const launcher = replaceBlock(view.launcher ?? '', GLOBAL_START, GLOBAL_END, LAUNCHER);
117
+ const next = {
118
+ format: 2, host: hostId, config: host.config, launcherPath: view.launcherPath,
119
+ entry, launcher: LAUNCHER, mcpBlock: hostId === 'codex' ? block(config, MCP_START, MCP_END).text : null,
120
+ originalConfig: receipt ? receipt.originalConfig : view.config,
121
+ originalLauncher: receipt ? receipt.originalLauncher : view.launcher,
122
+ installedConfig: config, installedLauncher: launcher,
123
+ runtime: { mode, packageEntry: packaged, devRoot, devEntry },
124
+ };
125
+ receiptSchema.parse(next);
126
+ host.inspectConfig(config);
127
+ const unchanged = receipt?.format === 2 && stable(receipt.runtime) === stable(next.runtime) && stable(host.entry(view.text)) === stable(entry) && launcher === view.launcher;
128
+ const plan = { host: hostId, mode, previousMode: stored?.mode ?? (migrating ? 'manual-dev' : 'package'), changed: !unchanged, migration: migrating,
129
+ config: host.config, launcher: view.launcherPath, runtimePath: mode === 'dev' ? devRoot : packaged.command, restartRequired: true };
130
+ if (options.dryRun || unchanged)
131
+ return { ...plan, dryRun: Boolean(options.dryRun) };
132
+ await transact(ctx.home, view.dir, [
133
+ { path: host.config, before: view.config, after: config },
134
+ { path: view.launcherPath, before: view.launcher, after: launcher },
135
+ { path: view.receiptPath, before: view.saved, after: JSON.stringify(next, null, 2) + '\n' },
136
+ ]);
137
+ return { ...plan, dryRun: false };
138
+ }
139
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,114 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { PRODERO_VERSION } from '../core/constants.js';
3
+ import { machineIntegrations } from './manager.js';
4
+ const labels = {
5
+ installed: 'Installed',
6
+ 'externally-configured': 'Already configured (prodero-dev, manual)',
7
+ 'external-incomplete': 'Manual setup needs attention',
8
+ 'not-configured': 'Not configured',
9
+ 'not-detected': 'Agent not found',
10
+ outdated: 'Update available',
11
+ conflicted: 'Configuration needs review',
12
+ 'unsupported-configuration': 'Cannot read or update configuration',
13
+ 'recovery-required': 'Recovery required',
14
+ unsupported: 'Not supported yet',
15
+ };
16
+ const actionable = (host) => host.state === 'outdated' || (host.detected && host.state === 'not-configured');
17
+ const needsAttention = (host) => ['conflicted', 'unsupported-configuration', 'recovery-required', 'external-incomplete'].includes(host.state);
18
+ // Native paths can contain terminal control characters. Never render those as commands.
19
+ const printable = (text) => text.replace(/[\x00-\x1f\x7f-\x9f]/g, ' ');
20
+ export async function runInstallWizard(io, options = {}) {
21
+ io.write(`\n Prodero ${PRODERO_VERSION} · Installation\n`);
22
+ io.write(' Configure Prodero for your coding agents.\n');
23
+ let result = await machineIntegrations('status', undefined, options);
24
+ while (true) {
25
+ io.write('\n Agent Prodero status\n ───────────────────────────────────────────────────────────\n');
26
+ for (const host of result.hosts) {
27
+ io.write(` ${host.name.padEnd(15)}${labels[host.state] ?? host.state}${host.runtimeMode === 'dev' ? ' · dev' : ''}${host.support === 'experimental' ? ' · Experimental' : ''}\n`);
28
+ if (needsAttention(host) || host.state === 'externally-configured') {
29
+ io.write(` ${printable(host.message).replace(/^HOST_[A-Z_]+: /, '')}\n`);
30
+ }
31
+ }
32
+ io.write('\n Status reflects saved settings; server connectivity is not tested.\n');
33
+ const available = result.hosts.filter(actionable);
34
+ if (!io.interactive) {
35
+ io.write('\n Open an interactive terminal and run prodero install to choose an agent.\n No settings were changed.\n');
36
+ return 1;
37
+ }
38
+ if (!available.length) {
39
+ io.write(needsAttentionIn(result.hosts)
40
+ ? '\n Review the settings above, then run prodero install again.\n'
41
+ : '\n No agents need installation. Run prodero install again after adding an agent.\n');
42
+ return needsAttentionIn(result.hosts) ? 1 : 0;
43
+ }
44
+ io.write('\n Choose an action\n');
45
+ available.forEach((host, index) => io.write(` ${index + 1}. ${host.state === 'outdated' ? 'Update' : 'Install for'} ${host.name}${host.support === 'experimental' ? ' (experimental)' : ''}\n`));
46
+ io.write(' 0. Finish\n\n');
47
+ const answer = (await io.question(' Selection [0]: '))?.trim();
48
+ if (answer === null || answer === undefined || answer === '' || answer === '0') {
49
+ io.write('\n Finished.\n');
50
+ return needsAttentionIn(result.hosts) ? 1 : 0;
51
+ }
52
+ if (!/^[1-9]\d*$/.test(answer) || !available[Number(answer) - 1]) {
53
+ io.write('\n Enter a number from the menu.\n');
54
+ continue;
55
+ }
56
+ const selected = available[Number(answer) - 1];
57
+ io.write(`\n ${selected.state === 'outdated' ? 'Update' : 'Install'} Prodero for ${selected.name}\n`);
58
+ io.write(' This configures the MCP server and global launch instruction.\n Existing unrelated settings are preserved; changes are backed up locally.\n');
59
+ if (selected.support === 'experimental')
60
+ io.write(' Claude Code support is experimental.\n');
61
+ const confirmation = (await io.question(' Continue? [y/N]: '))?.trim().toLowerCase();
62
+ if (confirmation !== 'y' && confirmation !== 'yes') {
63
+ io.write('\n Cancelled. No settings were changed for this agent.\n');
64
+ if (confirmation === undefined)
65
+ return 0;
66
+ continue;
67
+ }
68
+ io.write(`\n Configuring ${selected.name}…\n`);
69
+ const installed = await machineIntegrations('install', selected.id, options);
70
+ const host = installed.hosts.find(item => item.id === selected.id);
71
+ if (host.state === 'installed') {
72
+ io.write(` ✓ Prodero is installed for ${selected.name}.\n Restart ${selected.name}, open a repository and type prodero.\n`);
73
+ }
74
+ else {
75
+ io.write(` ${printable(host.message)}\n`);
76
+ }
77
+ // Re-read the settings after each action, including failures and concurrent edits.
78
+ result = await machineIntegrations('status', undefined, options);
79
+ if (!installed.ok)
80
+ return 1;
81
+ }
82
+ }
83
+ function needsAttentionIn(hosts) { return hosts.some(needsAttention); }
84
+ export async function installWizard() {
85
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
86
+ if (!interactive)
87
+ return runInstallWizard({ interactive, write: text => process.stdout.write(text), question: async () => null });
88
+ const terminal = createInterface({ input: process.stdin, output: process.stdout });
89
+ const abort = new AbortController();
90
+ terminal.on('close', () => abort.abort());
91
+ terminal.on('SIGINT', () => terminal.close());
92
+ try {
93
+ return await runInstallWizard({
94
+ interactive,
95
+ write: text => process.stdout.write(text),
96
+ async question(prompt) {
97
+ if (abort.signal.aborted)
98
+ return null;
99
+ try {
100
+ return await terminal.question(prompt, { signal: abort.signal });
101
+ }
102
+ catch (error) {
103
+ if (abort.signal.aborted)
104
+ return null;
105
+ throw error;
106
+ }
107
+ },
108
+ });
109
+ }
110
+ finally {
111
+ terminal.close();
112
+ }
113
+ }
114
+ //# sourceMappingURL=wizard.js.map
@@ -0,0 +1,38 @@
1
+ const segments = new Intl.Segmenter('en', { granularity: 'grapheme' });
2
+ const width = (s) => {
3
+ const cp = s.codePointAt(0) ?? 0;
4
+ return /\p{Extended_Pictographic}/u.test(s) || cp >= 0x1100 && (cp <= 0x115f || cp >= 0x2e80 && cp <= 0xa4cf || cp >= 0xac00 && cp <= 0xd7a3 ||
5
+ cp >= 0xf900 && cp <= 0xfaff || cp >= 0xfe10 && cp <= 0xfe6f ||
6
+ cp >= 0xff01 && cp <= 0xff60 || cp >= 0x20000 && cp <= 0x3ffff) ? 2 : 1;
7
+ };
8
+ const cells = (value, limit) => {
9
+ // Titles are untrusted data: keep terminal controls and Markdown fences out.
10
+ const text = value.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '').replace(/[\p{Cc}\p{Cf}]/gu, ' ').replace(/`/g, "'").replace(/\|/g, '/').replace(/\s+/g, ' ').trim();
11
+ const lines = [];
12
+ let line = '', used = 0;
13
+ for (const { segment } of segments.segment(text)) {
14
+ const size = width(segment);
15
+ if (used + size > limit) {
16
+ lines.push(line + ' '.repeat(limit - used));
17
+ line = '';
18
+ used = 0;
19
+ }
20
+ line += segment;
21
+ used += size;
22
+ }
23
+ lines.push(line + ' '.repeat(limit - used));
24
+ return lines;
25
+ };
26
+ export function renderKnowledgeTable(rows) {
27
+ if (!rows.length)
28
+ return 'No knowledge matches these filters.';
29
+ const widths = [5, 44, 11, 10, 9];
30
+ const border = '+' + widths.map(w => '-'.repeat(w + 2)).join('+') + '+';
31
+ const row = (values) => {
32
+ const columns = values.map((v, i) => cells(v, widths[i]));
33
+ return Array.from({ length: Math.max(...columns.map(c => c.length)) }, (_, i) => '| ' + columns.map((c, j) => c[i] ?? ' '.repeat(widths[j])).join(' | ') + ' |').join('\n');
34
+ };
35
+ return ['```text', border, row(['#', 'Knowledge', 'Type', 'Priority', 'Layer']), border,
36
+ ...rows.flatMap(r => [row([r.key, r.title, r.category, r.priority, r.layer]), border]), '```'].join('\n');
37
+ }
38
+ //# sourceMappingURL=knowledge-table.js.map
@@ -0,0 +1,69 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import { environment, receiptSchema } from './hosts/manager.js';
5
+ import { resolveNpmCli } from './hosts/public-entry.js';
6
+ import { read, fail } from './hosts/files.js';
7
+ /** Read installation state only to choose runtime CODE, never workspace authority. */
8
+ export async function developmentRoot(explicit) {
9
+ if (explicit !== undefined) {
10
+ if (!path.isAbsolute(explicit))
11
+ fail('HOST_UNSUPPORTED_PATH', '--checkout must be absolute.');
12
+ return fs.realpath(explicit);
13
+ }
14
+ const { home, state } = environment({});
15
+ const executable = await fs.realpath(fileURLToPath(new URL('../bin/prodero', import.meta.url)));
16
+ const roots = new Set();
17
+ for (const host of ['codex', 'claude']) {
18
+ const dir = path.join(state, host);
19
+ const saved = await read(home, path.join(dir, 'receipt.json'));
20
+ if (saved === null)
21
+ continue;
22
+ let receipt;
23
+ try {
24
+ receipt = receiptSchema.parse(JSON.parse(saved));
25
+ }
26
+ catch {
27
+ fail('HOST_UNSUPPORTED_CONFIG', 'Invalid integration receipt. Repair the managed integration before using --dev.');
28
+ }
29
+ if (receipt.host !== host)
30
+ fail('HOST_UNSUPPORTED_CONFIG', 'Integration receipt host mismatch.');
31
+ if (receipt.format !== 2 || !receipt.runtime.devRoot || !receipt.runtime.devEntry)
32
+ continue;
33
+ const dev = receipt.runtime.devEntry;
34
+ // An explicit multi-checkout entry does not compete with an ordinary --dev entry.
35
+ if (JSON.stringify(dev.args) !== JSON.stringify(['mcp', '--dev']) || await fs.realpath(dev.command).catch(() => null) !== executable)
36
+ continue;
37
+ if (await read(home, path.join(dir, 'pending.json')) !== null || await fs.lstat(path.join(dir, 'lock')).catch(() => null))
38
+ fail('HOST_RECOVERY_REQUIRED', 'Integration update is incomplete. Reconnect after recovery.');
39
+ const { runtime } = receipt;
40
+ const stable = (value) => JSON.stringify(value, (_k, v) => v && typeof v === 'object' && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a.localeCompare(b))) : v);
41
+ if (stable(receipt.entry) !== stable(runtime.mode === 'dev' ? runtime.devEntry : runtime.packageEntry))
42
+ fail('HOST_UNSUPPORTED_CONFIG', 'Inconsistent runtime receipt.');
43
+ const root = runtime.devRoot;
44
+ if (!path.isAbsolute(root) || await fs.realpath(root).catch(() => null) !== root)
45
+ fail('HOST_RUNTIME_MISSING', 'Saved development checkout was moved or replaced. Select it again with prodero runtime dev.');
46
+ roots.add(root);
47
+ }
48
+ if (roots.size !== 1)
49
+ fail('HOST_RUNTIME_MISSING', roots.size ? 'Multiple development checkouts are configured. Use an explicit --checkout or switch the host runtime again.' : 'No managed development checkout for this executable. Run prodero runtime dev --host HOST --checkout PATH.');
50
+ return [...roots][0];
51
+ }
52
+ export async function startDevelopmentRuntime(localStdio, options) {
53
+ const root = await developmentRoot(options.checkout);
54
+ let metadata;
55
+ try {
56
+ metadata = JSON.parse(await fs.readFile(path.join(root, 'package.json'), 'utf8'));
57
+ }
58
+ catch {
59
+ fail('HOST_RUNTIME_MISSING', 'Cannot read development runtime metadata.');
60
+ }
61
+ if (!['@prodero/curator', '@prodero/cli'].includes(metadata.name ?? ''))
62
+ fail('HOST_RUNTIME_MISSING', 'Development runtime must be a Prodero checkout.');
63
+ const npmCli = await resolveNpmCli(process.env);
64
+ const entry = path.join(root, 'scripts/mcp-dev-launcher.mjs');
65
+ const { startDevelopmentServer } = await import(pathToFileURL(entry).href);
66
+ // In-process transfer of the captured object: never serialize authority into env or tool metadata.
67
+ await startDevelopmentServer(localStdio, options.projectRoot, npmCli);
68
+ }
69
+ //# sourceMappingURL=mcp-development.js.map
@@ -0,0 +1,141 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import fs from 'node:fs/promises';
3
+ import { realpathSync, statSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { explicitProjectRoot } from './core/project-root.js';
7
+ function pin(value) {
8
+ if (!value.trim() || !path.isAbsolute(value))
9
+ throw new Error('Project root must be an absolute existing directory.');
10
+ try {
11
+ const canonical = realpathSync(value);
12
+ if (!statSync(canonical).isDirectory())
13
+ throw new Error();
14
+ return Object.freeze({ path: path.resolve(value), canonical });
15
+ }
16
+ catch {
17
+ throw new Error('Project root is not an existing directory.');
18
+ }
19
+ }
20
+ /** Capture before build/npm can change the workspace. PWD and INIT_CWD are never read. */
21
+ export function captureLocalStdioContext() {
22
+ let claudeProject, invalidClaudeProject = false;
23
+ if (process.env.CLAUDE_PROJECT_DIR !== undefined) {
24
+ try {
25
+ claudeProject = pin(process.env.CLAUDE_PROJECT_DIR);
26
+ }
27
+ catch {
28
+ invalidClaudeProject = true;
29
+ }
30
+ }
31
+ return Object.freeze({ cwd: pin(process.cwd()), claudeProject, invalidClaudeProject,
32
+ // npm scripts execute in their package, not necessarily in the host workspace.
33
+ // This flag only disables launch authority; environment data never grant it.
34
+ indirectLaunch: Boolean(process.env.npm_lifecycle_event) });
35
+ }
36
+ async function canonical(value) {
37
+ if (!value.trim() || !path.isAbsolute(value))
38
+ throw new Error('Project root must be an absolute existing directory.');
39
+ const root = await fs.realpath(value).catch(() => null);
40
+ if (!root || !(await fs.stat(root).catch(() => null))?.isDirectory())
41
+ throw new Error('Project root is not an existing directory.');
42
+ return root;
43
+ }
44
+ async function recheck(root) {
45
+ if (await canonical(root.path) !== root.canonical)
46
+ throw new Error('PRODERO_ROOT_UNAUTHORIZED: Workspace boundary changed. Reconnect from the intended workspace.');
47
+ return root;
48
+ }
49
+ /** One lazy authority snapshot and selected root per tools/call, including Apps/plugins/text. */
50
+ export function installWorkspaceBoundary(server, options) {
51
+ const fixed = options.projectRoot === undefined ? undefined : pin(options.projectRoot);
52
+ const launch = options.localStdio;
53
+ // Remember identities only to detect replacement; this map never grants access.
54
+ const hostIdentities = new Map();
55
+ const operations = new AsyncLocalStorage();
56
+ const register = server.server.setRequestHandler.bind(server.server);
57
+ server.server.setRequestHandler = (schema, handler) => register(schema, (request, extra) => request.method === 'tools/call' ? operations.run({}, () => handler(request, extra)) : handler(request, extra));
58
+ async function authority() {
59
+ if (fixed)
60
+ return { roots: [await recheck(fixed)], primary: fixed };
61
+ const client = server.server.getClientVersion()?.name;
62
+ const claude = client === 'claude-code';
63
+ if (server.server.getClientCapabilities()?.roots) {
64
+ let roots;
65
+ try {
66
+ roots = (await server.server.listRoots()).roots;
67
+ }
68
+ catch {
69
+ throw new Error('PRODERO_WORKSPACE_UNAVAILABLE: Cannot read MCP workspace roots. Reconnect the host.');
70
+ }
71
+ const unique = new Map();
72
+ try {
73
+ for (const { uri } of roots) {
74
+ const url = new URL(uri);
75
+ if (url.protocol !== 'file:')
76
+ throw new Error();
77
+ const value = path.resolve(fileURLToPath(url));
78
+ const real = await canonical(value);
79
+ const prior = hostIdentities.get(value);
80
+ if (prior && prior.canonical !== real)
81
+ throw new Error('Host root was replaced.');
82
+ const root = prior ?? Object.freeze({ path: value, canonical: real });
83
+ hostIdentities.set(value, root);
84
+ if (!unique.has(real))
85
+ unique.set(real, root);
86
+ }
87
+ }
88
+ catch {
89
+ throw new Error('PRODERO_WORKSPACE_UNAVAILABLE: MCP workspace roots must be file URIs for existing directories.');
90
+ }
91
+ // Empty/revoked/invalid negotiated roots never fall back to launch context.
92
+ if (!unique.size)
93
+ throw new Error('PRODERO_WORKSPACE_UNAVAILABLE: No MCP workspace roots are authorized by the host.');
94
+ let primary;
95
+ if (claude && launch?.invalidClaudeProject)
96
+ throw new Error('PRODERO_WORKSPACE_UNAVAILABLE: Invalid host project context. Reconnect the host.');
97
+ if (claude && launch?.claudeProject) {
98
+ await recheck(launch.claudeProject);
99
+ primary = unique.get(launch.claudeProject.canonical);
100
+ // Primary removal is legitimate revocation; never restore it through env.
101
+ }
102
+ return { roots: [...unique.values()], primary };
103
+ }
104
+ if (claude && launch?.claudeProject && !launch.invalidClaudeProject) {
105
+ const root = await recheck(launch.claudeProject);
106
+ return { roots: [root], primary: root };
107
+ }
108
+ if (client === 'codex-mcp-client' && launch && !launch.indirectLaunch) {
109
+ const root = await recheck(launch.cwd);
110
+ return { roots: [root], primary: root };
111
+ }
112
+ throw new Error('PRODERO_WORKSPACE_UNAVAILABLE: No trusted MCP workspace context. Use a supported local host and the direct Prodero launcher; reconnect after updating the integration.');
113
+ }
114
+ return async function workspaceRoot(explicit) {
115
+ const operation = operations.getStore();
116
+ if (!operation)
117
+ throw new Error('PRODERO_WORKSPACE_UNAVAILABLE: Project access requires an MCP operation context.');
118
+ const allowed = await (operation.authority ??= authority());
119
+ const selectedCanonical = explicit === undefined ? undefined : await canonical(explicit);
120
+ if (!operation.selected) {
121
+ operation.selected = (async () => {
122
+ const selected = selectedCanonical === undefined
123
+ ? allowed.primary ?? (allowed.roots.length === 1 ? allowed.roots[0] : undefined)
124
+ : allowed.roots.find(root => root.canonical === selectedCanonical);
125
+ if (!selected)
126
+ throw new Error(selectedCanonical === undefined
127
+ ? 'Missing or ambiguous MCP workspace roots. Select an authorized root with projectRoot.'
128
+ : 'PRODERO_ROOT_UNAUTHORIZED: projectRoot is outside the host-authorized workspace boundary.');
129
+ await recheck(selected);
130
+ await explicitProjectRoot(selected.path);
131
+ return selected;
132
+ })();
133
+ }
134
+ const selected = await operation.selected;
135
+ if (selectedCanonical !== undefined && selectedCanonical !== selected.canonical)
136
+ throw new Error('PRODERO_ROOT_UNAUTHORIZED: An operation cannot switch workspace boundaries.');
137
+ await recheck(selected);
138
+ return selected.path;
139
+ };
140
+ }
141
+ //# sourceMappingURL=mcp-workspace.js.map