pumuki 6.3.26 → 6.3.28

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 (76) hide show
  1. package/README.md +3 -1
  2. package/bin/pumuki-mcp-enterprise-stdio.js +5 -0
  3. package/bin/pumuki-mcp-evidence-stdio.js +5 -0
  4. package/core/gate/conditionMatches.ts +1 -21
  5. package/core/gate/evaluateGate.js +5 -0
  6. package/core/gate/evaluateRules.js +5 -0
  7. package/core/gate/evaluateRules.ts +1 -24
  8. package/core/gate/scopeMatcher.ts +84 -0
  9. package/docs/EXECUTION_BOARD.md +749 -376
  10. package/docs/MCP_SERVERS.md +41 -2
  11. package/docs/README.md +6 -2
  12. package/docs/REFRACTOR_PROGRESS.md +374 -6
  13. package/docs/validation/README.md +11 -1
  14. package/docs/validation/p9-ruralgo-bug-registry.md +607 -0
  15. package/docs/validation/p9-ruralgo-fork-validation-tracking.md +904 -0
  16. package/docs/validation/real-repo-manual-e2e-ruralgo-fork.md +372 -0
  17. package/integrations/config/skillsCompliance.ts +212 -0
  18. package/integrations/evidence/integrity.ts +352 -0
  19. package/integrations/evidence/rulesCoverage.ts +94 -0
  20. package/integrations/evidence/schema.test.ts +16 -0
  21. package/integrations/evidence/schema.ts +41 -0
  22. package/integrations/evidence/writeEvidence.test.ts +68 -0
  23. package/integrations/evidence/writeEvidence.ts +23 -2
  24. package/integrations/gate/evaluateAiGate.ts +382 -15
  25. package/integrations/gate/stagePolicies.ts +70 -15
  26. package/integrations/gate/waivers.ts +209 -0
  27. package/integrations/git/findingTraceability.ts +3 -23
  28. package/integrations/git/index.js +5 -0
  29. package/integrations/git/runCliCommand.ts +16 -0
  30. package/integrations/git/runPlatformGate.ts +53 -1
  31. package/integrations/git/runPlatformGateEvaluation.ts +13 -0
  32. package/integrations/git/stageRunners.ts +168 -5
  33. package/integrations/lifecycle/adapter.templates.json +72 -5
  34. package/integrations/lifecycle/adapter.ts +78 -4
  35. package/integrations/lifecycle/cli.ts +384 -14
  36. package/integrations/lifecycle/doctor.ts +534 -0
  37. package/integrations/lifecycle/hookBlock.ts +2 -1
  38. package/integrations/lifecycle/index.js +5 -0
  39. package/integrations/lifecycle/install.ts +115 -3
  40. package/integrations/lifecycle/openSpecBootstrap.ts +68 -8
  41. package/integrations/lifecycle/preWriteAutomation.ts +142 -0
  42. package/integrations/mcp/aiGateCheck.ts +6 -0
  43. package/integrations/mcp/aiGateReceipt.ts +188 -0
  44. package/integrations/mcp/enterpriseServer.ts +14 -1
  45. package/integrations/mcp/enterpriseStdioServer.cli.ts +315 -0
  46. package/integrations/mcp/evidenceStdioServer.cli.ts +342 -0
  47. package/integrations/mcp/index.js +5 -0
  48. package/integrations/sdd/index.js +5 -0
  49. package/integrations/sdd/index.ts +2 -0
  50. package/integrations/sdd/policy.ts +191 -2
  51. package/integrations/sdd/sessionStore.ts +139 -19
  52. package/integrations/sdd/syncDocs.ts +180 -0
  53. package/integrations/sdd/types.ts +4 -1
  54. package/integrations/telemetry/structuredTelemetry.ts +197 -0
  55. package/package.json +27 -8
  56. package/scripts/build-p9-validation-manifests.ts +53 -0
  57. package/scripts/check-p9-ruralgo-baseline-clean.ts +200 -0
  58. package/scripts/check-p9-ruralgo-baseline-versioned.ts +198 -0
  59. package/scripts/check-p9-ruralgo-branch-ready.ts +215 -0
  60. package/scripts/check-p9-ruralgo-install-health.ts +288 -0
  61. package/scripts/check-p9-ruralgo-runtime-ready.ts +188 -0
  62. package/scripts/check-package-manifest.ts +49 -0
  63. package/scripts/check-tracking-single-active.sh +40 -0
  64. package/scripts/framework-menu-consumer-preflight-lib.ts +31 -0
  65. package/scripts/framework-menu-consumer-runtime-lib.ts +3 -3
  66. package/scripts/framework-menu-legacy-audit-lib.ts +35 -7
  67. package/scripts/framework-menu-matrix-evidence-lib.ts +6 -2
  68. package/scripts/manage-library.sh +1 -1
  69. package/scripts/p9-ruralgo-baseline-clean-lib.ts +117 -0
  70. package/scripts/p9-ruralgo-baseline-versioned-lib.ts +119 -0
  71. package/scripts/p9-ruralgo-branch-ready-lib.ts +128 -0
  72. package/scripts/p9-ruralgo-install-health-lib.ts +121 -0
  73. package/scripts/p9-ruralgo-runtime-ready-lib.ts +149 -0
  74. package/scripts/p9-validation-manifests-lib.ts +366 -0
  75. package/scripts/package-manifest-lib.ts +9 -0
  76. package/skills.lock.json +1 -1
@@ -0,0 +1,315 @@
1
+ import { Socket, createServer } from 'node:net';
2
+ import { startEnterpriseMcpServer } from './enterpriseServer';
3
+
4
+ type JsonRpcId = string | number | null;
5
+
6
+ type JsonRpcRequest = {
7
+ jsonrpc?: unknown;
8
+ id?: unknown;
9
+ method?: unknown;
10
+ params?: unknown;
11
+ };
12
+
13
+ type JsonRpcResponse = {
14
+ jsonrpc: '2.0';
15
+ id: JsonRpcId;
16
+ result?: unknown;
17
+ error?: {
18
+ code: number;
19
+ message: string;
20
+ };
21
+ };
22
+
23
+ const MCP_PROTOCOL_VERSION = '2024-11-05';
24
+
25
+ const toJsonRpcId = (value: unknown): JsonRpcId => {
26
+ if (typeof value === 'string' || typeof value === 'number' || value === null) {
27
+ return value;
28
+ }
29
+ return null;
30
+ };
31
+
32
+ const sendMessage = (message: JsonRpcResponse): void => {
33
+ process.stdout.write(`${JSON.stringify(message)}\n`);
34
+ };
35
+
36
+ const sendResult = (id: JsonRpcId, result: unknown): void => {
37
+ sendMessage({
38
+ jsonrpc: '2.0',
39
+ id,
40
+ result,
41
+ });
42
+ };
43
+
44
+ const sendError = (id: JsonRpcId, code: number, message: string): void => {
45
+ sendMessage({
46
+ jsonrpc: '2.0',
47
+ id,
48
+ error: {
49
+ code,
50
+ message,
51
+ },
52
+ });
53
+ };
54
+
55
+ const isPortInUse = async (host: string, port: number): Promise<boolean> =>
56
+ await new Promise((resolve) => {
57
+ const socket = new Socket();
58
+ socket.setTimeout(600);
59
+ socket.once('connect', () => {
60
+ socket.destroy();
61
+ resolve(true);
62
+ });
63
+ socket.once('timeout', () => {
64
+ socket.destroy();
65
+ resolve(false);
66
+ });
67
+ socket.once('error', () => {
68
+ socket.destroy();
69
+ resolve(false);
70
+ });
71
+ socket.connect(port, host);
72
+ });
73
+
74
+ const findEphemeralPort = async (host: string): Promise<number> =>
75
+ await new Promise((resolve, reject) => {
76
+ const probe = createServer();
77
+ probe.once('error', reject);
78
+ probe.listen(0, host, () => {
79
+ const address = probe.address();
80
+ const port = address && typeof address === 'object' ? address.port : 0;
81
+ probe.close(() => resolve(port));
82
+ });
83
+ });
84
+
85
+ const fetchJson = async (url: string, options?: RequestInit): Promise<unknown> => {
86
+ const response = await fetch(url, options);
87
+ const text = await response.text();
88
+ if (text.trim().length === 0) {
89
+ return {};
90
+ }
91
+ try {
92
+ return JSON.parse(text) as unknown;
93
+ } catch {
94
+ return {
95
+ status: response.status,
96
+ body: text,
97
+ };
98
+ }
99
+ };
100
+
101
+ const startOrReuseEnterpriseHttp = async (): Promise<{
102
+ host: string;
103
+ port: number;
104
+ startedByThisProcess: boolean;
105
+ }> => {
106
+ const host = process.env.PUMUKI_ENTERPRISE_MCP_HOST ?? '127.0.0.1';
107
+ const parsedPort = Number.parseInt(process.env.PUMUKI_ENTERPRISE_MCP_PORT ?? '', 10);
108
+ const preferredPort = Number.isFinite(parsedPort) ? parsedPort : 7391;
109
+ const requestedPort = preferredPort > 0 ? preferredPort : await findEphemeralPort(host);
110
+
111
+ const healthUrl = `http://${host}:${requestedPort}/health`;
112
+ try {
113
+ const health = (await fetchJson(healthUrl)) as { status?: string };
114
+ if (health.status === 'ok') {
115
+ return {
116
+ host,
117
+ port: requestedPort,
118
+ startedByThisProcess: false,
119
+ };
120
+ }
121
+ } catch {
122
+ // Intentionally ignored: endpoint not available yet.
123
+ }
124
+
125
+ const portInUse = await isPortInUse(host, requestedPort);
126
+ const resolvedPort = portInUse ? await findEphemeralPort(host) : requestedPort;
127
+ startEnterpriseMcpServer({
128
+ host,
129
+ port: resolvedPort,
130
+ repoRoot: process.cwd(),
131
+ });
132
+
133
+ return {
134
+ host,
135
+ port: resolvedPort,
136
+ startedByThisProcess: true,
137
+ };
138
+ };
139
+
140
+ const toToolInputSchema = (): Record<string, unknown> => ({
141
+ type: 'object',
142
+ properties: {},
143
+ additionalProperties: true,
144
+ });
145
+
146
+ const run = async (): Promise<void> => {
147
+ const httpServer = await startOrReuseEnterpriseHttp();
148
+ const baseUrl = `http://${httpServer.host}:${httpServer.port}`;
149
+ let textBuffer = '';
150
+
151
+ const handleRequest = async (request: JsonRpcRequest): Promise<void> => {
152
+ if (request.jsonrpc !== '2.0') {
153
+ sendError(toJsonRpcId(request.id), -32600, 'Invalid JSON-RPC version.');
154
+ return;
155
+ }
156
+
157
+ const id = toJsonRpcId(request.id);
158
+ const method = typeof request.method === 'string' ? request.method : '';
159
+ const params = request.params;
160
+
161
+ if (method === 'notifications/initialized') {
162
+ return;
163
+ }
164
+
165
+ if (method === 'initialize') {
166
+ sendResult(id, {
167
+ protocolVersion: MCP_PROTOCOL_VERSION,
168
+ serverInfo: {
169
+ name: 'pumuki-enterprise-stdio',
170
+ version: '1.0.0',
171
+ },
172
+ capabilities: {
173
+ tools: {
174
+ listChanged: true,
175
+ },
176
+ resources: {
177
+ listChanged: true,
178
+ },
179
+ },
180
+ });
181
+ return;
182
+ }
183
+
184
+ if (method === 'ping') {
185
+ sendResult(id, {});
186
+ return;
187
+ }
188
+
189
+ if (method === 'tools/list') {
190
+ const payload = (await fetchJson(`${baseUrl}/tools`)) as {
191
+ tools?: Array<{ name?: string; description?: string }>;
192
+ };
193
+ const tools = (payload.tools ?? [])
194
+ .filter((entry) => typeof entry?.name === 'string')
195
+ .map((entry) => ({
196
+ name: entry.name as string,
197
+ description: typeof entry.description === 'string' ? entry.description : undefined,
198
+ inputSchema: toToolInputSchema(),
199
+ }));
200
+ sendResult(id, {
201
+ tools,
202
+ });
203
+ return;
204
+ }
205
+
206
+ if (method === 'tools/call') {
207
+ const callParams = typeof params === 'object' && params !== null
208
+ ? (params as { name?: unknown; arguments?: unknown })
209
+ : {};
210
+ const name = typeof callParams.name === 'string' ? callParams.name : '';
211
+ const args = typeof callParams.arguments === 'object' && callParams.arguments !== null
212
+ ? (callParams.arguments as Record<string, unknown>)
213
+ : {};
214
+ const payload = await fetchJson(`${baseUrl}/tool`, {
215
+ method: 'POST',
216
+ headers: {
217
+ 'Content-Type': 'application/json',
218
+ },
219
+ body: JSON.stringify({
220
+ name,
221
+ args,
222
+ dryRun: true,
223
+ }),
224
+ });
225
+ const envelope = payload as { success?: boolean };
226
+ sendResult(id, {
227
+ content: [
228
+ {
229
+ type: 'text',
230
+ text: JSON.stringify(payload),
231
+ },
232
+ ],
233
+ isError: envelope.success === false,
234
+ });
235
+ return;
236
+ }
237
+
238
+ if (method === 'resources/list') {
239
+ const payload = (await fetchJson(`${baseUrl}/resources`)) as {
240
+ resources?: Array<{ uri?: string; name?: string; description?: string }>;
241
+ };
242
+ const resources = (payload.resources ?? [])
243
+ .filter((entry) => typeof entry?.uri === 'string')
244
+ .map((entry) => ({
245
+ uri: entry.uri as string,
246
+ name: typeof entry.name === 'string' ? entry.name : entry.uri,
247
+ description: typeof entry.description === 'string' ? entry.description : undefined,
248
+ mimeType: 'application/json',
249
+ }));
250
+ sendResult(id, {
251
+ resources,
252
+ });
253
+ return;
254
+ }
255
+
256
+ if (method === 'resources/read') {
257
+ const readParams = typeof params === 'object' && params !== null
258
+ ? (params as { uri?: unknown })
259
+ : {};
260
+ const uri = typeof readParams.uri === 'string' ? readParams.uri : '';
261
+ const payload = await fetchJson(
262
+ `${baseUrl}/resource?uri=${encodeURIComponent(uri)}`
263
+ );
264
+ sendResult(id, {
265
+ contents: [
266
+ {
267
+ uri,
268
+ mimeType: 'application/json',
269
+ text: JSON.stringify(payload),
270
+ },
271
+ ],
272
+ });
273
+ return;
274
+ }
275
+
276
+ sendError(id, -32601, `Method not found: ${method}`);
277
+ };
278
+
279
+ const processBuffer = (): void => {
280
+ while (true) {
281
+ const lineEnd = textBuffer.indexOf('\n');
282
+ if (lineEnd === -1) {
283
+ return;
284
+ }
285
+ const rawLine = textBuffer.slice(0, lineEnd).trim();
286
+ textBuffer = textBuffer.slice(lineEnd + 1);
287
+ if (rawLine.length === 0) {
288
+ continue;
289
+ }
290
+ let payload: JsonRpcRequest;
291
+ try {
292
+ payload = JSON.parse(rawLine) as JsonRpcRequest;
293
+ } catch {
294
+ sendError(null, -32700, 'Parse error');
295
+ continue;
296
+ }
297
+ void handleRequest(payload).catch((error) => {
298
+ const id = toJsonRpcId(payload.id);
299
+ const message = error instanceof Error ? error.message : 'Internal error';
300
+ sendError(id, -32603, message);
301
+ });
302
+ }
303
+ };
304
+
305
+ process.stdin.on('data', (chunk) => {
306
+ textBuffer += chunk.toString('utf8');
307
+ processBuffer();
308
+ });
309
+ };
310
+
311
+ void run().catch((error) => {
312
+ const message = error instanceof Error ? error.message : 'Unknown MCP stdio bridge error';
313
+ process.stderr.write(`[pumuki-mcp-enterprise-stdio] ${message}\n`);
314
+ process.exit(1);
315
+ });
@@ -0,0 +1,342 @@
1
+ import { Socket, createServer } from 'node:net';
2
+ import { startEvidenceContextServer } from './evidenceContextServer';
3
+
4
+ type JsonRpcId = string | number | null;
5
+
6
+ type JsonRpcRequest = {
7
+ jsonrpc?: unknown;
8
+ id?: unknown;
9
+ method?: unknown;
10
+ params?: unknown;
11
+ };
12
+
13
+ type JsonRpcResponse = {
14
+ jsonrpc: '2.0';
15
+ id: JsonRpcId;
16
+ result?: unknown;
17
+ error?: {
18
+ code: number;
19
+ message: string;
20
+ };
21
+ };
22
+
23
+ const MCP_PROTOCOL_VERSION = '2024-11-05';
24
+
25
+ const toJsonRpcId = (value: unknown): JsonRpcId => {
26
+ if (typeof value === 'string' || typeof value === 'number' || value === null) {
27
+ return value;
28
+ }
29
+ return null;
30
+ };
31
+
32
+ const sendMessage = (message: JsonRpcResponse): void => {
33
+ process.stdout.write(`${JSON.stringify(message)}\n`);
34
+ };
35
+
36
+ const sendResult = (id: JsonRpcId, result: unknown): void => {
37
+ sendMessage({
38
+ jsonrpc: '2.0',
39
+ id,
40
+ result,
41
+ });
42
+ };
43
+
44
+ const sendError = (id: JsonRpcId, code: number, message: string): void => {
45
+ sendMessage({
46
+ jsonrpc: '2.0',
47
+ id,
48
+ error: {
49
+ code,
50
+ message,
51
+ },
52
+ });
53
+ };
54
+
55
+ const isPortInUse = async (host: string, port: number): Promise<boolean> =>
56
+ await new Promise((resolve) => {
57
+ const socket = new Socket();
58
+ socket.setTimeout(600);
59
+ socket.once('connect', () => {
60
+ socket.destroy();
61
+ resolve(true);
62
+ });
63
+ socket.once('timeout', () => {
64
+ socket.destroy();
65
+ resolve(false);
66
+ });
67
+ socket.once('error', () => {
68
+ socket.destroy();
69
+ resolve(false);
70
+ });
71
+ socket.connect(port, host);
72
+ });
73
+
74
+ const findEphemeralPort = async (host: string): Promise<number> =>
75
+ await new Promise((resolve, reject) => {
76
+ const probe = createServer();
77
+ probe.once('error', reject);
78
+ probe.listen(0, host, () => {
79
+ const address = probe.address();
80
+ const port = address && typeof address === 'object' ? address.port : 0;
81
+ probe.close(() => resolve(port));
82
+ });
83
+ });
84
+
85
+ const fetchJson = async (url: string): Promise<unknown> => {
86
+ const response = await fetch(url);
87
+ const text = await response.text();
88
+ if (text.trim().length === 0) {
89
+ return {};
90
+ }
91
+ return JSON.parse(text) as unknown;
92
+ };
93
+
94
+ const startOrReuseEvidenceHttp = async (): Promise<{
95
+ host: string;
96
+ port: number;
97
+ route: string;
98
+ }> => {
99
+ const host = process.env.PUMUKI_EVIDENCE_HOST ?? '127.0.0.1';
100
+ const route = process.env.PUMUKI_EVIDENCE_ROUTE ?? '/ai-evidence';
101
+ const parsedPort = Number.parseInt(process.env.PUMUKI_EVIDENCE_PORT ?? '', 10);
102
+ const preferredPort = Number.isFinite(parsedPort) ? parsedPort : 7341;
103
+ const requestedPort = preferredPort > 0 ? preferredPort : await findEphemeralPort(host);
104
+ const healthUrl = `http://${host}:${requestedPort}/health`;
105
+
106
+ try {
107
+ const health = (await fetchJson(healthUrl)) as { status?: string };
108
+ if (health.status === 'ok') {
109
+ return { host, port: requestedPort, route };
110
+ }
111
+ } catch {
112
+ // ignored
113
+ }
114
+
115
+ const portInUse = await isPortInUse(host, requestedPort);
116
+ const resolvedPort = portInUse ? await findEphemeralPort(host) : requestedPort;
117
+ startEvidenceContextServer({
118
+ host,
119
+ port: resolvedPort,
120
+ route,
121
+ repoRoot: process.cwd(),
122
+ });
123
+
124
+ return {
125
+ host,
126
+ port: resolvedPort,
127
+ route,
128
+ };
129
+ };
130
+
131
+ const run = async (): Promise<void> => {
132
+ const started = await startOrReuseEvidenceHttp();
133
+ const baseUrl = `http://${started.host}:${started.port}`;
134
+ const route = started.route.startsWith('/') ? started.route : `/${started.route}`;
135
+ let textBuffer = '';
136
+
137
+ const resourcesCatalog = [
138
+ {
139
+ uri: 'evidence://status',
140
+ path: '/status',
141
+ description: 'Evidence status payload',
142
+ },
143
+ {
144
+ uri: 'evidence://summary',
145
+ path: `${route}/summary`,
146
+ description: 'Evidence summary payload',
147
+ },
148
+ {
149
+ uri: 'evidence://snapshot',
150
+ path: `${route}/snapshot`,
151
+ description: 'Evidence snapshot payload',
152
+ },
153
+ {
154
+ uri: 'evidence://findings',
155
+ path: `${route}/findings`,
156
+ description: 'Evidence findings payload',
157
+ },
158
+ ] as const;
159
+
160
+ const toolsCatalog = [
161
+ {
162
+ name: 'evidence_status',
163
+ path: '/status',
164
+ description: 'Read evidence status payload.',
165
+ },
166
+ {
167
+ name: 'evidence_summary',
168
+ path: `${route}/summary`,
169
+ description: 'Read evidence summary payload.',
170
+ },
171
+ {
172
+ name: 'evidence_snapshot',
173
+ path: `${route}/snapshot`,
174
+ description: 'Read evidence snapshot payload.',
175
+ },
176
+ {
177
+ name: 'evidence_findings',
178
+ path: `${route}/findings`,
179
+ description: 'Read evidence findings payload.',
180
+ },
181
+ {
182
+ name: 'evidence_rulesets',
183
+ path: `${route}/rulesets`,
184
+ description: 'Read evidence rulesets payload.',
185
+ },
186
+ {
187
+ name: 'evidence_platforms',
188
+ path: `${route}/platforms`,
189
+ description: 'Read evidence platforms payload.',
190
+ },
191
+ ] as const;
192
+
193
+ const handleRequest = async (request: JsonRpcRequest): Promise<void> => {
194
+ if (request.jsonrpc !== '2.0') {
195
+ sendError(toJsonRpcId(request.id), -32600, 'Invalid JSON-RPC version.');
196
+ return;
197
+ }
198
+
199
+ const id = toJsonRpcId(request.id);
200
+ const method = typeof request.method === 'string' ? request.method : '';
201
+
202
+ if (method === 'notifications/initialized') {
203
+ return;
204
+ }
205
+
206
+ if (method === 'initialize') {
207
+ sendResult(id, {
208
+ protocolVersion: MCP_PROTOCOL_VERSION,
209
+ serverInfo: {
210
+ name: 'pumuki-evidence-stdio',
211
+ version: '1.0.0',
212
+ },
213
+ capabilities: {
214
+ tools: {
215
+ listChanged: true,
216
+ },
217
+ resources: {
218
+ listChanged: true,
219
+ },
220
+ },
221
+ });
222
+ return;
223
+ }
224
+
225
+ if (method === 'ping') {
226
+ sendResult(id, {});
227
+ return;
228
+ }
229
+
230
+ if (method === 'tools/list') {
231
+ sendResult(id, {
232
+ tools: toolsCatalog.map((entry) => ({
233
+ name: entry.name,
234
+ description: entry.description,
235
+ inputSchema: {
236
+ type: 'object',
237
+ properties: {},
238
+ additionalProperties: true,
239
+ },
240
+ })),
241
+ });
242
+ return;
243
+ }
244
+
245
+ if (method === 'tools/call') {
246
+ const params = typeof request.params === 'object' && request.params !== null
247
+ ? (request.params as { name?: unknown; arguments?: unknown })
248
+ : {};
249
+ const name = typeof params.name === 'string' ? params.name : '';
250
+ const tool = toolsCatalog.find((entry) => entry.name === name);
251
+ if (!tool) {
252
+ sendError(id, -32602, `Unknown tool: ${name}`);
253
+ return;
254
+ }
255
+ const payload = await fetchJson(`${baseUrl}${tool.path}`);
256
+ sendResult(id, {
257
+ content: [
258
+ {
259
+ type: 'text',
260
+ text: JSON.stringify(payload),
261
+ },
262
+ ],
263
+ isError: false,
264
+ });
265
+ return;
266
+ }
267
+
268
+ if (method === 'resources/list') {
269
+ sendResult(id, {
270
+ resources: resourcesCatalog.map((entry) => ({
271
+ uri: entry.uri,
272
+ name: entry.uri,
273
+ description: entry.description,
274
+ mimeType: 'application/json',
275
+ })),
276
+ });
277
+ return;
278
+ }
279
+
280
+ if (method === 'resources/read') {
281
+ const params = typeof request.params === 'object' && request.params !== null
282
+ ? (request.params as { uri?: unknown })
283
+ : {};
284
+ const uri = typeof params.uri === 'string' ? params.uri : '';
285
+ const resource = resourcesCatalog.find((entry) => entry.uri === uri);
286
+ if (!resource) {
287
+ sendError(id, -32602, `Unknown resource URI: ${uri}`);
288
+ return;
289
+ }
290
+ const payload = await fetchJson(`${baseUrl}${resource.path}`);
291
+ sendResult(id, {
292
+ contents: [
293
+ {
294
+ uri,
295
+ mimeType: 'application/json',
296
+ text: JSON.stringify(payload),
297
+ },
298
+ ],
299
+ });
300
+ return;
301
+ }
302
+
303
+ sendError(id, -32601, `Method not found: ${method}`);
304
+ };
305
+
306
+ const processBuffer = (): void => {
307
+ while (true) {
308
+ const lineEnd = textBuffer.indexOf('\n');
309
+ if (lineEnd === -1) {
310
+ return;
311
+ }
312
+ const rawLine = textBuffer.slice(0, lineEnd).trim();
313
+ textBuffer = textBuffer.slice(lineEnd + 1);
314
+ if (rawLine.length === 0) {
315
+ continue;
316
+ }
317
+ let payload: JsonRpcRequest;
318
+ try {
319
+ payload = JSON.parse(rawLine) as JsonRpcRequest;
320
+ } catch {
321
+ sendError(null, -32700, 'Parse error');
322
+ continue;
323
+ }
324
+ void handleRequest(payload).catch((error) => {
325
+ const id = toJsonRpcId(payload.id);
326
+ const message = error instanceof Error ? error.message : 'Internal error';
327
+ sendError(id, -32603, message);
328
+ });
329
+ }
330
+ };
331
+
332
+ process.stdin.on('data', (chunk) => {
333
+ textBuffer += chunk.toString('utf8');
334
+ processBuffer();
335
+ });
336
+ };
337
+
338
+ void run().catch((error) => {
339
+ const message = error instanceof Error ? error.message : 'Unknown MCP stdio bridge error';
340
+ process.stderr.write(`[pumuki-mcp-evidence-stdio] ${message}\n`);
341
+ process.exit(1);
342
+ });
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ require('tsx/cjs');
4
+
5
+ module.exports = require('./index.ts');
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ require('tsx/cjs');
4
+
5
+ module.exports = require('./index.ts');
@@ -9,3 +9,5 @@ export type {
9
9
  } from './types';
10
10
  export { evaluateSddPolicy, readSddStatus } from './policy';
11
11
  export { closeSddSession, openSddSession, readSddSession, refreshSddSession } from './sessionStore';
12
+ export { resolveSddSyncDocsPath, runSddSyncDocs } from './syncDocs';
13
+ export type { SddSyncDocsResult, SddSyncDocsStageCheck } from './syncDocs';