@prodero/curator 0.0.0-stage → 0.2.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 (148) hide show
  1. package/ARCHITECTURE.md +298 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +93 -2
  6. package/SECURITY.md +7 -0
  7. package/bin/prodero +7 -0
  8. package/dist/cli.js +266 -0
  9. package/dist/context-instructions.js +2 -0
  10. package/dist/contracts.js +51 -0
  11. package/dist/conversation-presentation.js +297 -0
  12. package/dist/core/agent-instructions.js +52 -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 +117 -0
  24. package/dist/core/curation-question.js +28 -0
  25. package/dist/core/curation-review-presentation.js +4 -0
  26. package/dist/core/curation-review-state.js +62 -0
  27. package/dist/core/curation-review.js +573 -0
  28. package/dist/core/curation-revision.js +106 -0
  29. package/dist/core/curation-semantic-contract.js +242 -0
  30. package/dist/core/curation-semantic-jobs.js +340 -0
  31. package/dist/core/diagnostics.js +17 -0
  32. package/dist/core/format.js +114 -0
  33. package/dist/core/ids.js +6 -0
  34. package/dist/core/imports.js +169 -0
  35. package/dist/core/index.js +93 -0
  36. package/dist/core/instruction-review.js +86 -0
  37. package/dist/core/integrity-files.js +174 -0
  38. package/dist/core/intelligence.js +95 -0
  39. package/dist/core/knowledge-analysis.js +46 -0
  40. package/dist/core/knowledge-authority.js +143 -0
  41. package/dist/core/knowledge-curation.js +316 -0
  42. package/dist/core/knowledge-engine.js +475 -0
  43. package/dist/core/knowledge-graph.js +192 -0
  44. package/dist/core/knowledge-lifecycle.js +212 -0
  45. package/dist/core/knowledge-management.js +113 -0
  46. package/dist/core/knowledge-migration.js +84 -0
  47. package/dist/core/knowledge-policy.js +38 -0
  48. package/dist/core/knowledge-provenance.js +90 -0
  49. package/dist/core/knowledge-write-policy.js +85 -0
  50. package/dist/core/language.js +14 -0
  51. package/dist/core/local-preferences.js +76 -0
  52. package/dist/core/matching.js +6 -0
  53. package/dist/core/memory-index.js +241 -0
  54. package/dist/core/memory-intelligence.js +288 -0
  55. package/dist/core/memory-link-repair.js +70 -0
  56. package/dist/core/memory-operations.js +122 -0
  57. package/dist/core/memory-pressure.js +212 -0
  58. package/dist/core/memory-sync-curator.js +82 -0
  59. package/dist/core/memory-sync.js +558 -0
  60. package/dist/core/memory-transformation-bundle.js +163 -0
  61. package/dist/core/memory-transformation-proposal.js +135 -0
  62. package/dist/core/memory-transformation.js +108 -0
  63. package/dist/core/memory.js +186 -0
  64. package/dist/core/migrations.js +569 -0
  65. package/dist/core/npm-update.js +289 -0
  66. package/dist/core/path-scope.js +27 -0
  67. package/dist/core/plugin-files.js +116 -0
  68. package/dist/core/project-root.js +39 -0
  69. package/dist/core/project.js +634 -0
  70. package/dist/core/promotion.js +279 -0
  71. package/dist/core/reconciliation.js +61 -0
  72. package/dist/core/secrets.js +67 -0
  73. package/dist/core/semantic-safety.js +33 -0
  74. package/dist/core/session-index.js +116 -0
  75. package/dist/core/session-review.js +150 -0
  76. package/dist/core/sessions.js +289 -0
  77. package/dist/core/settings.js +94 -0
  78. package/dist/core/skill-curation.js +239 -0
  79. package/dist/core/skill-polish.js +41 -0
  80. package/dist/core/skills.js +387 -0
  81. package/dist/core/storage.js +462 -0
  82. package/dist/core/sync-transaction.js +290 -0
  83. package/dist/core/telemetry.js +64 -0
  84. package/dist/core/templates.js +53 -0
  85. package/dist/core/types.js +2 -0
  86. package/dist/core/update.js +326 -0
  87. package/dist/curation-review-adapter.js +92 -0
  88. package/dist/diagnostic-boundary.js +55 -0
  89. package/dist/filesystem/permissions.js +39 -0
  90. package/dist/hosts/adapters.js +119 -0
  91. package/dist/hosts/files.js +133 -0
  92. package/dist/hosts/manager.js +150 -0
  93. package/dist/hosts/public-entry.js +47 -0
  94. package/dist/hosts/runtime-command.js +66 -0
  95. package/dist/hosts/runtime.js +139 -0
  96. package/dist/hosts/wizard.js +114 -0
  97. package/dist/knowledge-table.js +38 -0
  98. package/dist/mcp-development.js +69 -0
  99. package/dist/mcp-workspace.js +141 -0
  100. package/dist/memory-presentation.js +182 -0
  101. package/dist/memory-sync-operation.js +88 -0
  102. package/dist/plugins/api.js +2 -0
  103. package/dist/plugins/context.js +20 -0
  104. package/dist/plugins/mcp.js +72 -0
  105. package/dist/plugins/registry.js +106 -0
  106. package/dist/presentation-cards.js +32 -0
  107. package/dist/presentation-server.js +94 -0
  108. package/dist/presentation-trace.js +174 -0
  109. package/dist/presentation.js +443 -0
  110. package/dist/providers/mcp-sampling.js +45 -0
  111. package/dist/server.js +664 -0
  112. package/dist/settings-definition.js +22 -0
  113. package/dist/skill-instructions.js +2 -0
  114. package/dist/status-presentation.js +20 -0
  115. package/dist/text-layout.js +278 -0
  116. package/dist/update-command.js +53 -0
  117. package/dist/update-instructions.js +4 -0
  118. package/dist-ui/index.html +216 -0
  119. package/docs/APPLICABILITY.md +151 -0
  120. package/docs/COMPATIBILITY.md +68 -0
  121. package/docs/CONTEXT-SELECTION-V2.md +61 -0
  122. package/docs/CURATION-APP-REVIEW.md +156 -0
  123. package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
  124. package/docs/IMPORTER-CURATION.md +142 -0
  125. package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
  126. package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
  127. package/docs/MIGRATIONS.md +103 -0
  128. package/docs/PRIVACY.md +28 -0
  129. package/docs/RECOVERY.md +58 -0
  130. package/docs/RELEASING.md +98 -0
  131. package/docs/hosts.md +152 -0
  132. package/docs/images/prodero-context-ready-cropped.png +0 -0
  133. package/docs/images/prodero-overview-cropped.png +0 -0
  134. package/docs/images/prodero-session-learned-cropped.png +0 -0
  135. package/docs/plugins.md +83 -0
  136. package/docs/ui-themes.md +49 -0
  137. package/instructions/AGENTS.md +39 -0
  138. package/package.json +92 -4
  139. package/plugins/migrate_ai_studio/README.md +101 -0
  140. package/plugins/migrate_ai_studio/index.cjs +396 -0
  141. package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
  142. package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
  143. package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
  144. package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
  145. package/plugins/migrate_ai_studio/plugin.json +11 -0
  146. package/plugins/migrate_ai_studio/schema.cjs +20 -0
  147. package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
  148. package/prodero.release.json +5 -0
@@ -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
@@ -0,0 +1,182 @@
1
+ /** Pure product copy shared by Open, Curate Memory and text clients. Core owns decisions. */
2
+ import { memorySyncOperation } from './memory-sync-operation.js';
3
+ const count = (n, one, many = one + 's') => `${n} ${n === 1 ? one : many}`;
4
+ const tokens = (n) => n >= 1000 ? `${(n / 1000).toFixed(2)}K` : String(n);
5
+ export const MEMORY_COPY = {
6
+ automatic: 'Prodero remembers automatically. Sessions save useful knowledge into Local Knowledge when they close.',
7
+ purpose: 'Curate Memory keeps that memory useful and shareable.',
8
+ noHistory: 'Not enough usage history yet to evaluate memory optimization.',
9
+ stale: 'Project memory changed since this curation started. Re-analyze before continuing.',
10
+ incompleteProvenance: 'Some provenance fields are incomplete; this is not proof of lost history. Existing records are preserved. Verify actual sources before proposing repairs; diagnostics do not infer or backfill provenance.',
11
+ shared: 'Versioned Knowledge is Git-shareable project state that your agent can change. Curator does not commit or push; review the repository diff before doing so.',
12
+ };
13
+ /** Display only: never infer a topic from a knowledge title. Full labels remain in text/details. */
14
+ export function memoryAreaLabel(j) {
15
+ const refs = [...new Set((j.records ?? []).map((r) => r.scopeRef).filter(Boolean))];
16
+ const path = refs.length === 1 ? String(refs[0]) : String(j.label ?? '');
17
+ const reliablePath = /^(?:[\w.@-]+\/)+[\w.@-]+\/?$/.test(path);
18
+ if (reliablePath)
19
+ return path.length <= 48 ? path : `${path.slice(0, 20)}…${path.slice(-27)}`;
20
+ return `Area ${String(j.id ?? j.candidateId ?? '').replace(/^area_/, '').slice(0, 8) || 'overview'}`;
21
+ }
22
+ export function memoryArea(j) {
23
+ const signals = j.signals ?? [], c = j.counts ?? {};
24
+ const reasons = [];
25
+ if (signals.includes('context_pressure'))
26
+ reasons.push(c.budgetOmissionSessions ? `Some relevant knowledge did not fit into ${count(c.budgetOmissionSessions, 'recent session')}.` : 'Some relevant knowledge repeatedly did not fit into recent task context.');
27
+ if (signals.includes('fragmentation'))
28
+ reasons.push('Related memories are spread across several records used together.');
29
+ if (signals.includes('recent_evolution'))
30
+ reasons.push('Knowledge has evolved through related updates.');
31
+ if (signals.includes('redundancy'))
32
+ reasons.push('Repeated information may be easier to use together.');
33
+ if (signals.includes('unresolved'))
34
+ reasons.push('Some related information still needs clarification.');
35
+ const conflict = signals.includes('potential_conflict');
36
+ if (conflict)
37
+ reasons.push('Prodero found incompatible current knowledge and could not determine which state is correct.');
38
+ const statuses = { unselected: '', pending: 'Waiting', semantic_work: 'Improving…', awaiting_review: 'Ready for your review', applied: 'Processed', skipped: 'Skipped', failed: 'Could not finish this area' };
39
+ return { id: j.id, title: j.label, displayLabel: memoryAreaLabel(j), body: `${count(j.recordCount, 'related memory', 'related memories')} · ${j.layers?.local ?? 0} Local / ${j.layers?.versioned ?? 0} Shared`, reasons, conflict, status: statuses[j.status] ?? '', error: j.error ? 'This area could not be completed. Retry it or continue with other areas.' : null };
40
+ }
41
+ export function memoryOpenPresentation(p) {
42
+ const o = p.memoryOverview, areas = (o?.areas ?? []).map(memoryArea), conflicts = areas.filter((a) => a.conflict).length;
43
+ const hasHistory = o?.available && o.sessions > 0;
44
+ const headline = !o?.available ? 'Memory overview unavailable' : areas.length ? `${count(areas.length, 'area')} can be improved` : hasHistory ? 'No improvements suggested by recent usage' : 'Memory is ready to learn';
45
+ const notes = [MEMORY_COPY.automatic, MEMORY_COPY.purpose];
46
+ if (!o?.available)
47
+ notes.push('Memory analysis is temporarily unavailable. Try Curate Memory again when current work has finished.');
48
+ else if (!hasHistory)
49
+ notes.push(MEMORY_COPY.noHistory);
50
+ else if (!areas.length)
51
+ notes.push('No transformation is suggested by the available recent usage.');
52
+ if (p.knowledgeCount === 0)
53
+ notes.push('No current memories yet. Normal work will build Local Knowledge.');
54
+ if (p.promotionRecommendedCount)
55
+ notes.push(`${count(p.promotionRecommendedCount, 'Local memory', 'Local memories')} can be reviewed for team sharing.`);
56
+ if (areas.some((a) => a.reasons.some((r) => r.includes('did not fit'))))
57
+ notes.push('Some relevant knowledge has not fit into recent task context.');
58
+ if (o?.areasOmitted)
59
+ notes.push(`${count(o.areasOmitted, 'additional area')} can be inspected in a later Curate Memory run.`);
60
+ const workflow = o?.workflow;
61
+ if (workflow)
62
+ notes.push(workflow.state === 'stale' ? MEMORY_COPY.stale : 'Curate Memory is in progress. Continue when you are ready.');
63
+ const signal = areas.some((a) => a.reasons.some((r) => r.includes('did not fit'))) ? 'Relevant knowledge has not fit into recent task context.'
64
+ : areas.length ? areas[0].reasons.find((r) => !r.includes('incompatible')) ?? 'Related knowledge is ready for review.'
65
+ : !o?.available ? 'Memory analysis is temporarily unavailable.' : !hasHistory ? 'Not enough usage history yet to evaluate improvements.' : 'No improvements suggested by recent usage.';
66
+ const total = areas.length + (o?.areasOmitted ?? 0);
67
+ const dashboard = { headline: areas.length ? 'Memory can be improved' : headline, areasLabel: total ? `${count(total, 'area')} identified` : null, signal,
68
+ sharing: typeof p.localKnowledgeCount === 'number' ? p.localKnowledgeCount ? `${count(p.localKnowledgeCount, 'Local memory', 'Local memories')} available for sharing review.` : 'No Local memories to share.' : null,
69
+ established: p.knowledgeCount > 0, workflowNotice: workflow ? workflow.state === 'stale' ? MEMORY_COPY.stale : 'Curate Memory is in progress.' : null };
70
+ return { headline, notes, areas, conflicts, workflow, dashboard, button: workflow ? (workflow.state === 'stale' ? 'Re-analyze memory' : 'Continue curation') : 'Curate Memory' };
71
+ }
72
+ export function memorySyncPresentation(w) {
73
+ const areas = (w.jobs ?? []).map(memoryArea), s = w.summary ?? {}, p = w.review?.preview;
74
+ const operation = w.operation ?? memorySyncOperation(w);
75
+ const headlines = { awaiting_selection: 'Curate Memory', semantic_work: 'Improving memory…', awaiting_clarification: 'A question about your project', awaiting_review: w.review?.kind === 'promotion' ? 'Review knowledge for team sharing' : p?.requiresVersionedReview ? 'Review shared-memory changes' : 'Review memory changes', applying: 'Approved changes pending verification', awaiting_promotion_selection: 'Review Local knowledge for sharing', completed: 'Memory curated', stale: 'Re-analyze memory' };
76
+ const notes = [], warnings = [], metrics = [], sections = [];
77
+ const section = (title, body) => sections.push({ title, items: [{ title: '', body, notes: [] }] });
78
+ if (w.state === 'awaiting_selection') {
79
+ // Operation choices are the entry surface; planner data is diagnostic.
80
+ }
81
+ if (['semantic_work', 'awaiting_clarification'].includes(w.state) && operation.phase !== 'question')
82
+ notes.push(operation.phase === 'submitted' ? 'The result was submitted and is awaiting validation.' : operation.phase === 'awaiting_agent' ? 'Prepared evidence is waiting for host-agent analysis. No background worker is confirmed.' : 'Analysis is queued. Continue with the prepared evidence; you may be asked about missing project facts.');
83
+ if (w.state === 'applying')
84
+ notes.push('The approved intention is saved. Check progress to verify the result or resume recovery; no active writer is assumed.');
85
+ if (w.state === 'stale')
86
+ notes.push(MEMORY_COPY.stale);
87
+ if (w.state === 'awaiting_promotion_selection')
88
+ notes.push('Select Local knowledge to move to Versioned Knowledge.');
89
+ if (w.state !== 'stale' && w.review?.kind === 'transformation') {
90
+ const r = p.summary, before = r.consolidated.length + r.superseded.length + r.kept.length + r.unresolved.length, after = r.created.length + r.kept.length + r.unresolved.length;
91
+ metrics.push({ label: 'Before · current memories', value: before }, { label: 'After · current memories', value: after }, { label: 'Estimated tokens · before → after', value: `${tokens(r.estimatedTokens.before)} → ${tokens(r.estimatedTokens.after)}` });
92
+ section(r.label, `${count(r.consolidated.length, 'memory', 'memories')} consolidated; ${count(r.superseded.length, 'memory', 'memories')} replaced by newer project state; ${count(r.kept.length + r.unresolved.length, 'previous memory', 'previous memories')} remain current.\n${count(r.archives.length, 'previous memory', 'previous memories')} will be preserved in history.`);
93
+ for (const category of ['instruction', 'principle', 'invariant', 'constraint', 'decision']) {
94
+ const n = r.protectedSources.filter((x) => x.category === category && x.action === 'keep').length;
95
+ if (n)
96
+ notes.push(`${count(n, `project ${category}`)} ${n === 1 ? 'was' : 'were'} preserved unchanged.`);
97
+ }
98
+ if (r.unresolved.length)
99
+ notes.push(`Prodero could not safely determine how ${count(r.unresolved.length, 'memory', 'memories')} should be handled. It will remain current.`);
100
+ const l = p.limitations;
101
+ notes.push(...(l.warnings ?? []), ...(l.additionalEvidenceRequired ?? []));
102
+ if (l.omittedAsRedundant?.length)
103
+ section('Information proposed as repetition', l.omittedAsRedundant.flatMap((x) => x.information).join('\n'));
104
+ if (l.unresolved?.length)
105
+ section('Information still unresolved', l.unresolved.flatMap((x) => x.information).join('\n'));
106
+ if (p.requiresVersionedReview) {
107
+ notes.push(MEMORY_COPY.shared, 'Local and shared changes will be saved together after approval.');
108
+ const v = p.versionedEffects;
109
+ const sharedIds = new Set(r.created.filter((x) => x.layer === 'versioned').map((x) => x.id));
110
+ const localIds = new Set(r.archives.filter((x) => x.layer === 'local').map((x) => x.id));
111
+ const sharedLocal = new Set(r.provenance.filter((x) => sharedIds.has(x.from) && localIds.has(x.to)).map((x) => x.to));
112
+ if (sharedLocal.size)
113
+ notes.push(`${count(sharedLocal.size, 'Local memory', 'Local memories')} will be represented in the new shared knowledge; the originals stay in history.`);
114
+ section('Shared-memory changes', `${count(v.filter((x) => x.action === 'create').length, 'new current memory', 'new current memories')}; ${count(v.filter((x) => x.action === 'consolidate').length, 'memory', 'memories')} consolidated; ${count(v.filter((x) => x.action === 'supersede').length, 'memory', 'memories')} replaced by newer project state.\n${count(r.archives.filter((x) => x.layer === 'versioned').length, 'previous shared memory', 'previous shared memories')} preserved in Versioned archive files.`);
115
+ }
116
+ notes.push('Please review the proposed wording. Prodero checks data integrity but cannot guarantee that every meaning was preserved.');
117
+ sections.push({ title: 'Proposed current knowledge', items: p.proposal.records.map((x) => ({ title: x.title, body: x.body, notes: [`${x.category} · ${x.scope}${x.path ? ' · ' + x.path : ''} · ${r.created.find((c) => c.key === x.key)?.layer === 'versioned' ? 'Shared / Versioned' : 'Local'}`] })) });
118
+ }
119
+ if (w.state !== 'stale' && w.review?.kind === 'link-repair') {
120
+ notes.push(`Remove ${count(p.count, 'broken link')} to missing memories. No replacement memory is inferred.`, 'Memory text and valid links stay unchanged. Original records, including removed links, are saved in a checkpoint.');
121
+ if (p.requiresVersionedReview)
122
+ notes.push(MEMORY_COPY.shared);
123
+ if (p.remaining)
124
+ notes.push(`${count(p.remaining, 'additional broken link')} will remain for a later curation run.`);
125
+ sections.push({ title: 'Broken links to remove', items: p.changes.map((c) => ({ title: c.title, body: c.links.map((l) => `${l.type} → ${l.target}`).join('\n'), notes: [`${c.layer === 'versioned' ? 'Shared / Versioned' : 'Local'} · ${c.id}`] })) });
126
+ }
127
+ if (w.state !== 'stale' && w.review?.kind === 'promotion') {
128
+ notes.push(MEMORY_COPY.shared, 'This operation applies the knowledge shown here to Versioned files; it does not share it through Git.');
129
+ const labels = { new: 'New shared memory', merge: 'Update existing shared memory', supersede: 'Replace older shared state', covered: 'Already represented in shared knowledge', conflict: 'Needs review · remains Local' };
130
+ sections.push({ title: 'Sharing review', items: p.actions.flatMap((a) => [
131
+ ...(a.before ? [{ title: `Current · ${a.before.title}`, body: a.before.content, notes: [] }] : []),
132
+ { title: a.draft?.title ?? a.before?.title ?? 'Local knowledge', body: a.draft?.content ?? a.reason, notes: [labels[a.disposition], a.reason] },
133
+ ]) });
134
+ if (p.actions.some((a) => a.disposition === 'conflict'))
135
+ notes.push('Some selected knowledge cannot be shared safely and will remain Local. Review the reasons below.');
136
+ }
137
+ if (w.state === 'completed') {
138
+ if (s.areasProcessed && s.archived)
139
+ notes.push(`${s.sourcesBefore} previous memories → ${s.currentAfter} current memories.`, `${count(s.archived, 'previous memory', 'previous memories')} preserved in history.`);
140
+ else if (s.areasProcessed)
141
+ notes.push('Memory reviewed. No previous memories needed to be replaced.');
142
+ else if (w.jobs?.some((j) => j.selected) || s.areasSkipped || s.areasFailed)
143
+ notes.push('No memory improvements were applied.');
144
+ else if (w.improveEnabled === false)
145
+ notes.push('Memory improvement was not selected.');
146
+ else if (w.jobs?.length)
147
+ notes.push('No memory improvements were selected.');
148
+ else
149
+ notes.push(w.usage?.available && w.usage?.sessions ? 'No transformation was needed based on recent usage.' : MEMORY_COPY.noHistory);
150
+ if (s.brokenLinksFixed)
151
+ notes.push(`${count(s.brokenLinksFixed, 'broken link')} fixed.`);
152
+ if (s.brokenLinksRemaining)
153
+ notes.push(`${count(s.brokenLinksRemaining, 'broken link')} remain unchanged.`);
154
+ if (s.promoted)
155
+ notes.push(`${count(s.promoted, 'Local memory', 'Local memories')} prepared in Versioned files. Review the Git diff before sharing; Curator did not commit or push.`);
156
+ if (s.areasProcessed)
157
+ notes.push(`Estimated context for reviewed knowledge: ${tokens(s.estimatedTokens.before)} → ${tokens(s.estimatedTokens.after)} tokens.`);
158
+ if (s.unresolved)
159
+ notes.push(`${count(s.unresolved, 'unresolved item')} still need clarification and remain current.`);
160
+ if (s.areasSkipped)
161
+ notes.push('Some knowledge was retained because a safe improvement could not be established.');
162
+ if (s.areasFailed)
163
+ notes.push('Some analysis could not be completed. Saved knowledge is unchanged for those items.');
164
+ if (s.questionsDeferred)
165
+ notes.push('Deferred questions are saved for later. Independent work continued.');
166
+ if (s.promotionConflicts)
167
+ notes.push(`${count(s.promotionConflicts, 'sharing item')} still need review and remain Local.`);
168
+ notes.push('Previous knowledge remains available through history.');
169
+ }
170
+ notes.push('Your place and saved answers are retained. Leave and return with Continue curation.');
171
+ if (['semantic_work', 'awaiting_clarification', 'awaiting_review'].includes(w.state))
172
+ notes.push('Skip or Answer later retains original knowledge and saved answers, then continues other selected work.');
173
+ if (w.state === 'stale' || w.state === 'completed')
174
+ notes.push('Re-analyze starts a fresh workflow from current project data. Changes already saved remain in place.');
175
+ if (w.error && w.state !== 'stale')
176
+ notes.push('This step could not finish. Continue to check saved progress before retrying.');
177
+ if (w.state === 'awaiting_promotion_selection' && w.coverage?.promotionCandidatesOmitted)
178
+ notes.push(`${count(w.coverage.promotionCandidatesOmitted, 'additional Local memory', 'additional Local memories')} can be reviewed in a later Curate Memory run.`);
179
+ const headline = w.state === 'completed' && operation.outcome === 'remaining' ? 'Completed with remaining work' : operation.phase === 'awaiting_agent' ? 'Awaiting agent analysis' : operation.phase === 'submitted' ? 'Submitted · validation pending' : w.state === 'semantic_work' ? 'Analysis queued' : headlines[w.state] ?? 'Curate Memory';
180
+ return { headline, graphicalHeadline: headline, operation, reviewAreaLabel: 'Memory changes', notes, warnings, metrics, sections, areas };
181
+ }
182
+ //# sourceMappingURL=memory-presentation.js.map