@ryuenn3123/agentic-senior-core 3.0.17 → 3.0.20

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 (85) hide show
  1. package/.agent-context/prompts/bootstrap-design.md +84 -94
  2. package/.agent-context/prompts/init-project.md +32 -100
  3. package/.agent-context/prompts/refactor.md +22 -44
  4. package/.agent-context/prompts/review-code.md +28 -52
  5. package/.agent-context/review-checklists/architecture-review.md +31 -62
  6. package/.agent-context/review-checklists/pr-checklist.md +74 -108
  7. package/.agent-context/rules/api-docs.md +18 -206
  8. package/.agent-context/rules/architecture.md +40 -207
  9. package/.agent-context/rules/database-design.md +10 -199
  10. package/.agent-context/rules/docker-runtime.md +5 -5
  11. package/.agent-context/rules/efficiency-vs-hype.md +11 -149
  12. package/.agent-context/rules/error-handling.md +9 -231
  13. package/.agent-context/rules/event-driven.md +17 -221
  14. package/.agent-context/rules/frontend-architecture.md +66 -119
  15. package/.agent-context/rules/git-workflow.md +1 -1
  16. package/.agent-context/rules/microservices.md +28 -161
  17. package/.agent-context/rules/naming-conv.md +8 -138
  18. package/.agent-context/rules/performance.md +9 -175
  19. package/.agent-context/rules/realtime.md +11 -44
  20. package/.agent-context/rules/security.md +11 -295
  21. package/.agent-context/rules/testing.md +9 -174
  22. package/.agent-context/state/benchmark-analysis.json +3 -3
  23. package/.agent-context/state/memory-continuity-benchmark.json +1 -1
  24. package/.agent-context/state/onboarding-report.json +71 -11
  25. package/.agents/workflows/init-project.md +7 -24
  26. package/.agents/workflows/refactor.md +7 -24
  27. package/.agents/workflows/review-code.md +7 -24
  28. package/.cursorrules +22 -21
  29. package/.gemini/instructions.md +2 -2
  30. package/.github/copilot-instructions.md +2 -2
  31. package/.instructions.md +112 -213
  32. package/.windsurfrules +22 -21
  33. package/AGENTS.md +4 -4
  34. package/CONTRIBUTING.md +13 -22
  35. package/README.md +6 -20
  36. package/lib/cli/commands/init.mjs +102 -148
  37. package/lib/cli/commands/launch.mjs +3 -3
  38. package/lib/cli/commands/optimize.mjs +14 -4
  39. package/lib/cli/commands/upgrade.mjs +25 -23
  40. package/lib/cli/compiler.mjs +96 -62
  41. package/lib/cli/constants.mjs +28 -136
  42. package/lib/cli/detector/design-evidence.mjs +189 -6
  43. package/lib/cli/detector.mjs +6 -7
  44. package/lib/cli/init-detection-flow.mjs +10 -93
  45. package/lib/cli/init-selection.mjs +2 -68
  46. package/lib/cli/project-scaffolder/constants.mjs +1 -1
  47. package/lib/cli/project-scaffolder/design-contract.mjs +438 -335
  48. package/lib/cli/project-scaffolder/discovery.mjs +36 -82
  49. package/lib/cli/project-scaffolder/prompt-builders.mjs +55 -63
  50. package/lib/cli/project-scaffolder/storage.mjs +0 -4
  51. package/lib/cli/token-optimization.mjs +1 -1
  52. package/lib/cli/utils.mjs +75 -9
  53. package/package.json +2 -2
  54. package/scripts/detection-benchmark.mjs +4 -15
  55. package/scripts/documentation-boundary-audit.mjs +9 -9
  56. package/scripts/explain-on-demand-audit.mjs +11 -11
  57. package/scripts/forbidden-content-check.mjs +9 -9
  58. package/scripts/frontend-usability-audit.mjs +57 -36
  59. package/scripts/llm-judge.mjs +1 -1
  60. package/scripts/mcp-server/constants.mjs +60 -0
  61. package/scripts/mcp-server/tool-registry.mjs +149 -0
  62. package/scripts/mcp-server/tools.mjs +446 -0
  63. package/scripts/mcp-server.mjs +23 -661
  64. package/scripts/release-gate/audit-checks.mjs +426 -0
  65. package/scripts/release-gate/constants.mjs +53 -0
  66. package/scripts/release-gate/runtime.mjs +63 -0
  67. package/scripts/release-gate/static-checks.mjs +182 -0
  68. package/scripts/release-gate.mjs +13 -794
  69. package/scripts/rules-guardian-audit.mjs +14 -13
  70. package/scripts/single-source-lazy-loading-audit.mjs +3 -3
  71. package/scripts/sync-thin-adapters.mjs +5 -5
  72. package/scripts/ui-design-judge/constants.mjs +24 -0
  73. package/scripts/ui-design-judge/design-execution-summary.mjs +259 -0
  74. package/scripts/ui-design-judge/git-input.mjs +131 -0
  75. package/scripts/ui-design-judge/prompting.mjs +73 -0
  76. package/scripts/ui-design-judge/providers.mjs +102 -0
  77. package/scripts/ui-design-judge/reporting.mjs +182 -0
  78. package/scripts/ui-design-judge/rubric-calibration.mjs +214 -0
  79. package/scripts/ui-design-judge/rubric-goldset.json +188 -0
  80. package/scripts/ui-design-judge.mjs +166 -771
  81. package/scripts/ui-rubric-calibration.mjs +35 -0
  82. package/scripts/validate/config.mjs +198 -55
  83. package/scripts/validate/coverage-checks.mjs +32 -7
  84. package/scripts/validate.mjs +8 -4
  85. package/lib/cli/architect.mjs +0 -431
@@ -1,218 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, readFileSync } from 'node:fs';
4
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
5
- import { spawn } from 'node:child_process';
6
- import { dirname, resolve, sep } from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
-
9
- const SCRIPT_FILE_PATH = fileURLToPath(import.meta.url);
10
- const REPOSITORY_ROOT = resolve(dirname(SCRIPT_FILE_PATH), '..');
11
- const STATE_DIRECTORY = resolve(REPOSITORY_ROOT, '.agent-context', 'state');
12
- const DEFAULT_PROTOCOL_VERSION = '2024-11-05';
13
- const DEFAULT_FETCH_TIMEOUT_MS = 15000;
14
- const DEFAULT_FETCH_MAX_CHARS = 6000;
15
- const MAX_FETCH_MAX_CHARS = 20000;
16
- const DEFAULT_TREND_WINDOW_DAYS = 90;
17
- const MAX_TREND_PACKAGES = 10;
18
- const FALLBACK_PACKAGE_VERSION = '0.0.0-local';
19
-
20
- function resolvePackageVersion() {
21
- try {
22
- const parsedPackageManifest = JSON.parse(
23
- readFileSync(resolve(REPOSITORY_ROOT, 'package.json'), 'utf8')
24
- );
25
- const rawVersion = typeof parsedPackageManifest?.version === 'string'
26
- ? parsedPackageManifest.version.trim()
27
- : '';
28
-
29
- return rawVersion || FALLBACK_PACKAGE_VERSION;
30
- } catch {
31
- return FALLBACK_PACKAGE_VERSION;
32
- }
33
- }
34
-
35
- const PACKAGE_VERSION = resolvePackageVersion();
36
-
37
- const TEST_SUITE_ARGS = {
38
- full: ['--test', './tests/cli-smoke.test.mjs', './tests/mcp-server.test.mjs', './tests/llm-judge.test.mjs', './tests/enterprise-ops.test.mjs'],
39
- cli: ['--test', './tests/cli-smoke.test.mjs'],
40
- enterprise: ['--test', './tests/enterprise-ops.test.mjs'],
41
- 'llm-judge': ['--test', './tests/llm-judge.test.mjs'],
42
- };
43
-
44
- const INTERNAL_SCRIPT_PATHS = {
45
- validate: resolve(REPOSITORY_ROOT, 'scripts', 'validate.mjs'),
46
- release_gate: resolve(REPOSITORY_ROOT, 'scripts', 'release-gate.mjs'),
47
- forbidden_content_check: resolve(REPOSITORY_ROOT, 'scripts', 'forbidden-content-check.mjs'),
48
- };
49
-
50
- function getAvailableTestSuites() {
51
- return Object.entries(TEST_SUITE_ARGS)
52
- .filter(([, commandArguments]) => (
53
- Array.isArray(commandArguments)
54
- && commandArguments.length > 1
55
- && commandArguments
56
- .slice(1)
57
- .every((relativeTestPath) => existsSync(resolve(REPOSITORY_ROOT, relativeTestPath)))
58
- ))
59
- .map(([suiteName]) => suiteName);
60
- }
61
-
62
- const AVAILABLE_TEST_SUITES = getAvailableTestSuites();
63
-
64
- function buildToolDefinitions() {
65
- const toolDefinitions = [];
66
-
67
- if (existsSync(INTERNAL_SCRIPT_PATHS.validate)) {
68
- toolDefinitions.push({
69
- name: 'validate',
70
- description: 'Run repository validation checks.',
71
- inputSchema: {
72
- type: 'object',
73
- properties: {},
74
- additionalProperties: false,
75
- },
76
- });
77
- }
78
-
79
- if (AVAILABLE_TEST_SUITES.length > 0) {
80
- toolDefinitions.push({
81
- name: 'test',
82
- description: 'Run test suites (full or targeted).',
83
- inputSchema: {
84
- type: 'object',
85
- properties: {
86
- suite: {
87
- type: 'string',
88
- enum: AVAILABLE_TEST_SUITES,
89
- description: 'Target test suite. Defaults to the first available suite.',
90
- },
91
- },
92
- additionalProperties: false,
93
- },
94
- });
95
- }
96
-
97
- if (existsSync(INTERNAL_SCRIPT_PATHS.release_gate)) {
98
- toolDefinitions.push({
99
- name: 'release_gate',
100
- description: 'Run release gate checks.',
101
- inputSchema: {
102
- type: 'object',
103
- properties: {},
104
- additionalProperties: false,
105
- },
106
- });
107
- }
108
-
109
- if (existsSync(INTERNAL_SCRIPT_PATHS.forbidden_content_check)) {
110
- toolDefinitions.push({
111
- name: 'forbidden_content_check',
112
- description: 'Run forbidden content scan used by publish gate.',
113
- inputSchema: {
114
- type: 'object',
115
- properties: {},
116
- additionalProperties: false,
117
- },
118
- });
119
- }
120
-
121
- toolDefinitions.push(
122
- {
123
- name: 'research_fetch',
124
- description: 'Fetch external documentation/news content and return query-focused excerpts with citation metadata.',
125
- inputSchema: {
126
- type: 'object',
127
- properties: {
128
- url: {
129
- type: 'string',
130
- description: 'Absolute HTTP/HTTPS URL to fetch.',
131
- },
132
- query: {
133
- type: 'string',
134
- description: 'Optional search query used to extract focused excerpts.',
135
- },
136
- maxChars: {
137
- type: 'integer',
138
- description: 'Maximum characters to return when query is not provided (default 6000, max 20000).',
139
- },
140
- },
141
- required: ['url'],
142
- additionalProperties: false,
143
- },
144
- },
145
- {
146
- name: 'trend_snapshot',
147
- description: 'Generate ecosystem trend snapshot from npm registry metadata with source timestamps.',
148
- inputSchema: {
149
- type: 'object',
150
- properties: {
151
- packages: {
152
- type: 'array',
153
- items: { type: 'string' },
154
- description: 'Package names to inspect (max 10).',
155
- },
156
- windowDays: {
157
- type: 'integer',
158
- description: 'Release activity window in days (default 90).',
159
- },
160
- },
161
- required: ['packages'],
162
- additionalProperties: false,
163
- },
164
- },
165
- {
166
- name: 'state_read',
167
- description: 'Read a file from .agent-context/state for cross-session continuity.',
168
- inputSchema: {
169
- type: 'object',
170
- properties: {
171
- path: {
172
- type: 'string',
173
- description: 'Path relative to .agent-context/state (for example memory-continuity-benchmark.json).',
174
- },
175
- },
176
- required: ['path'],
177
- additionalProperties: false,
178
- },
179
- },
180
- {
181
- name: 'state_write',
182
- description: 'Write a file under .agent-context/state for cross-session continuity updates.',
183
- inputSchema: {
184
- type: 'object',
185
- properties: {
186
- path: {
187
- type: 'string',
188
- description: 'Path relative to .agent-context/state.',
189
- },
190
- content: {
191
- type: 'string',
192
- description: 'UTF-8 content to write.',
193
- },
194
- mode: {
195
- type: 'string',
196
- enum: ['overwrite', 'append'],
197
- description: 'Write mode. Defaults to overwrite.',
198
- },
199
- },
200
- required: ['path', 'content'],
201
- additionalProperties: false,
202
- },
203
- },
204
- );
205
-
206
- return toolDefinitions;
207
- }
3
+ import { buildToolDefinitions } from './mcp-server/tool-registry.mjs';
4
+ import { DEFAULT_PROTOCOL_VERSION, PACKAGE_VERSION } from './mcp-server/constants.mjs';
5
+ import { executeToolCall } from './mcp-server/tools.mjs';
208
6
 
209
7
  const TOOL_DEFINITIONS = buildToolDefinitions();
210
-
211
8
  let incomingBuffer = Buffer.alloc(0);
212
9
 
213
10
  function writeMessage(payload) {
214
11
  const serializedPayload = JSON.stringify(payload);
215
- // MCP standard: line-delimited JSON (no Content-Length header)
216
12
  process.stdout.write(serializedPayload + '\n');
217
13
  }
218
14
 
@@ -240,434 +36,6 @@ function normalizeToolName(rawToolName) {
240
36
  return typeof rawToolName === 'string' ? rawToolName.trim() : '';
241
37
  }
242
38
 
243
- function buildCommandOutput(commandLabel, commandArguments, exitCode, stdoutContent, stderrContent) {
244
- const outputSections = [
245
- `Command: node ${commandArguments.join(' ')}`,
246
- `Exit code: ${exitCode}`,
247
- ];
248
-
249
- if (stdoutContent.trim().length > 0) {
250
- outputSections.push(`STDOUT:\n${stdoutContent.trimEnd()}`);
251
- }
252
-
253
- if (stderrContent.trim().length > 0) {
254
- outputSections.push(`STDERR:\n${stderrContent.trimEnd()}`);
255
- }
256
-
257
- return [
258
- `[${commandLabel}]`,
259
- outputSections.join('\n\n'),
260
- ].join('\n\n');
261
- }
262
-
263
- function buildJsonResult(payload, isError = false) {
264
- return {
265
- content: [
266
- {
267
- type: 'text',
268
- text: JSON.stringify(payload, null, 2),
269
- },
270
- ],
271
- isError,
272
- };
273
- }
274
-
275
- function normalizePlainText(rawText) {
276
- return rawText
277
- .replace(/<script[\s\S]*?<\/script>/gi, ' ')
278
- .replace(/<style[\s\S]*?<\/style>/gi, ' ')
279
- .replace(/<[^>]+>/g, ' ')
280
- .replace(/&nbsp;/gi, ' ')
281
- .replace(/&amp;/gi, '&')
282
- .replace(/&lt;/gi, '<')
283
- .replace(/&gt;/gi, '>')
284
- .replace(/\s+/g, ' ')
285
- .trim();
286
- }
287
-
288
- function extractQuerySnippets(textContent, queryText) {
289
- const normalizedQuery = String(queryText || '').trim().toLowerCase();
290
- if (!normalizedQuery) {
291
- return [];
292
- }
293
-
294
- const normalizedContent = String(textContent || '');
295
- const normalizedLowerContent = normalizedContent.toLowerCase();
296
- const snippets = [];
297
- let searchStartIndex = 0;
298
-
299
- while (snippets.length < 5) {
300
- const matchedIndex = normalizedLowerContent.indexOf(normalizedQuery, searchStartIndex);
301
- if (matchedIndex === -1) {
302
- break;
303
- }
304
-
305
- const contextRadius = 180;
306
- const snippetStart = Math.max(0, matchedIndex - contextRadius);
307
- const snippetEnd = Math.min(normalizedContent.length, matchedIndex + normalizedQuery.length + contextRadius);
308
- const prefix = snippetStart > 0 ? '...' : '';
309
- const suffix = snippetEnd < normalizedContent.length ? '...' : '';
310
- snippets.push(`${prefix}${normalizedContent.slice(snippetStart, snippetEnd).trim()}${suffix}`);
311
- searchStartIndex = matchedIndex + normalizedQuery.length;
312
- }
313
-
314
- return snippets;
315
- }
316
-
317
- async function fetchWithTimeout(targetUrl, timeoutMs) {
318
- const fetchController = new AbortController();
319
- const timeoutHandle = setTimeout(() => fetchController.abort(), timeoutMs);
320
-
321
- try {
322
- return await fetch(targetUrl, {
323
- signal: fetchController.signal,
324
- headers: {
325
- 'User-Agent': `agentic-senior-core/${PACKAGE_VERSION}`,
326
- },
327
- });
328
- } finally {
329
- clearTimeout(timeoutHandle);
330
- }
331
- }
332
-
333
- async function runResearchFetchTool(toolArguments = {}) {
334
- const targetUrl = String(toolArguments.url || '').trim();
335
- const queryText = typeof toolArguments.query === 'string' ? toolArguments.query.trim() : '';
336
- const maxCharsInput = Number(toolArguments.maxChars);
337
- const maxChars = Number.isFinite(maxCharsInput)
338
- ? Math.max(200, Math.min(MAX_FETCH_MAX_CHARS, Math.floor(maxCharsInput)))
339
- : DEFAULT_FETCH_MAX_CHARS;
340
-
341
- if (!/^https?:\/\//i.test(targetUrl)) {
342
- return buildJsonResult({
343
- error: 'Invalid url. Provide absolute HTTP/HTTPS URL.',
344
- input: targetUrl,
345
- }, true);
346
- }
347
-
348
- try {
349
- const startedAt = new Date().toISOString();
350
- const fetchResponse = await fetchWithTimeout(targetUrl, DEFAULT_FETCH_TIMEOUT_MS);
351
- const rawBody = await fetchResponse.text();
352
- const plainTextBody = normalizePlainText(rawBody);
353
- const querySnippets = queryText ? extractQuerySnippets(plainTextBody, queryText) : [];
354
- const selectedContent = querySnippets.length > 0
355
- ? querySnippets.join('\n\n')
356
- : plainTextBody.slice(0, maxChars);
357
-
358
- return buildJsonResult({
359
- source: {
360
- url: targetUrl,
361
- status: fetchResponse.status,
362
- ok: fetchResponse.ok,
363
- fetchedAt: new Date().toISOString(),
364
- requestedAt: startedAt,
365
- contentType: fetchResponse.headers.get('content-type') || null,
366
- },
367
- query: queryText || null,
368
- excerptCount: querySnippets.length,
369
- truncated: !queryText && plainTextBody.length > selectedContent.length,
370
- content: selectedContent,
371
- }, !fetchResponse.ok);
372
- } catch (error) {
373
- return buildJsonResult({
374
- error: error instanceof Error ? error.message : String(error),
375
- source: targetUrl,
376
- }, true);
377
- }
378
- }
379
-
380
- async function runTrendSnapshotTool(toolArguments = {}) {
381
- const packageInputs = Array.isArray(toolArguments.packages)
382
- ? toolArguments.packages.filter((packageName) => typeof packageName === 'string' && packageName.trim().length > 0)
383
- : [];
384
- const packageNames = Array.from(new Set(packageInputs.map((packageName) => packageName.trim()))).slice(0, MAX_TREND_PACKAGES);
385
- const windowDaysInput = Number(toolArguments.windowDays);
386
- const windowDays = Number.isFinite(windowDaysInput)
387
- ? Math.max(1, Math.min(3650, Math.floor(windowDaysInput)))
388
- : DEFAULT_TREND_WINDOW_DAYS;
389
-
390
- if (packageNames.length === 0) {
391
- return buildJsonResult({
392
- error: 'packages[] must include at least one package name.',
393
- }, true);
394
- }
395
-
396
- const nowTimestamp = Date.now();
397
- const windowStartTimestamp = nowTimestamp - (windowDays * 24 * 60 * 60 * 1000);
398
- const packageReports = [];
399
-
400
- for (const packageName of packageNames) {
401
- const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
402
-
403
- try {
404
- const response = await fetchWithTimeout(registryUrl, DEFAULT_FETCH_TIMEOUT_MS);
405
- if (!response.ok) {
406
- packageReports.push({
407
- package: packageName,
408
- source: registryUrl,
409
- status: response.status,
410
- error: `Registry request failed with HTTP ${response.status}`,
411
- });
412
- continue;
413
- }
414
-
415
- const registryPayload = await response.json();
416
- const latestVersion = registryPayload?.['dist-tags']?.latest || null;
417
- const releaseTimes = Object.entries(registryPayload?.time || {})
418
- .filter(([versionName, publishedAt]) => {
419
- if (versionName === 'created' || versionName === 'modified') {
420
- return false;
421
- }
422
-
423
- return typeof publishedAt === 'string' && Number.isFinite(Date.parse(publishedAt));
424
- })
425
- .map(([versionName, publishedAt]) => ({
426
- version: versionName,
427
- publishedAt,
428
- publishedAtMs: Date.parse(publishedAt),
429
- }))
430
- .sort((leftEntry, rightEntry) => rightEntry.publishedAtMs - leftEntry.publishedAtMs);
431
-
432
- const releasesInWindow = releaseTimes.filter((releaseEntry) => releaseEntry.publishedAtMs >= windowStartTimestamp);
433
- const latestPublishedAt = latestVersion && typeof registryPayload?.time?.[latestVersion] === 'string'
434
- ? registryPayload.time[latestVersion]
435
- : registryPayload?.time?.modified || null;
436
-
437
- packageReports.push({
438
- package: packageName,
439
- source: registryUrl,
440
- latestVersion,
441
- latestPublishedAt,
442
- releasesInWindow: releasesInWindow.length,
443
- recentReleases: releasesInWindow.slice(0, 5).map((releaseEntry) => ({
444
- version: releaseEntry.version,
445
- publishedAt: releaseEntry.publishedAt,
446
- })),
447
- });
448
- } catch (error) {
449
- packageReports.push({
450
- package: packageName,
451
- source: registryUrl,
452
- error: error instanceof Error ? error.message : String(error),
453
- });
454
- }
455
- }
456
-
457
- const errorCount = packageReports.filter((packageReport) => typeof packageReport.error === 'string').length;
458
-
459
- return buildJsonResult({
460
- generatedAt: new Date().toISOString(),
461
- windowDays,
462
- packageCount: packageNames.length,
463
- errorCount,
464
- packages: packageReports,
465
- citation: {
466
- source: 'npm registry public API',
467
- fetchedAt: new Date().toISOString(),
468
- },
469
- }, errorCount > 0);
470
- }
471
-
472
- function resolveStatePath(relativeStatePath) {
473
- const normalizedRelativePath = String(relativeStatePath || '').replace(/\\/g, '/').replace(/^\/+/, '').trim();
474
- if (!normalizedRelativePath) {
475
- throw new Error('path is required and must be relative to .agent-context/state');
476
- }
477
-
478
- const resolvedStatePath = resolve(STATE_DIRECTORY, normalizedRelativePath);
479
- const stateRootPrefix = `${STATE_DIRECTORY}${sep}`;
480
- if (resolvedStatePath !== STATE_DIRECTORY && !resolvedStatePath.startsWith(stateRootPrefix)) {
481
- throw new Error('path traversal is not allowed outside .agent-context/state');
482
- }
483
-
484
- return {
485
- normalizedRelativePath,
486
- resolvedStatePath,
487
- };
488
- }
489
-
490
- async function runStateReadTool(toolArguments = {}) {
491
- try {
492
- const { normalizedRelativePath, resolvedStatePath } = resolveStatePath(toolArguments.path);
493
- const fileContent = await readFile(resolvedStatePath, 'utf8');
494
-
495
- return buildJsonResult({
496
- path: normalizedRelativePath,
497
- readAt: new Date().toISOString(),
498
- bytes: Buffer.byteLength(fileContent, 'utf8'),
499
- content: fileContent,
500
- });
501
- } catch (error) {
502
- return buildJsonResult({
503
- error: error instanceof Error ? error.message : String(error),
504
- path: toolArguments.path || null,
505
- }, true);
506
- }
507
- }
508
-
509
- async function runStateWriteTool(toolArguments = {}) {
510
- const writeMode = toolArguments.mode === 'append' ? 'append' : 'overwrite';
511
- const contentToWrite = typeof toolArguments.content === 'string' ? toolArguments.content : '';
512
-
513
- if (typeof toolArguments.content !== 'string') {
514
- return buildJsonResult({
515
- error: 'content must be a string.',
516
- }, true);
517
- }
518
-
519
- try {
520
- const { normalizedRelativePath, resolvedStatePath } = resolveStatePath(toolArguments.path);
521
- await mkdir(dirname(resolvedStatePath), { recursive: true });
522
-
523
- if (writeMode === 'append') {
524
- await writeFile(resolvedStatePath, contentToWrite, { encoding: 'utf8', flag: 'a' });
525
- } else {
526
- await writeFile(resolvedStatePath, contentToWrite, 'utf8');
527
- }
528
-
529
- return buildJsonResult({
530
- path: normalizedRelativePath,
531
- wroteAt: new Date().toISOString(),
532
- mode: writeMode,
533
- bytesWritten: Buffer.byteLength(contentToWrite, 'utf8'),
534
- });
535
- } catch (error) {
536
- return buildJsonResult({
537
- error: error instanceof Error ? error.message : String(error),
538
- path: toolArguments.path || null,
539
- mode: writeMode,
540
- }, true);
541
- }
542
- }
543
-
544
- function runNodeCommand(commandLabel, commandArguments) {
545
- return new Promise((resolveResult) => {
546
- const childProcess = spawn(process.execPath, commandArguments, {
547
- cwd: REPOSITORY_ROOT,
548
- env: process.env,
549
- });
550
-
551
- let stdoutContent = '';
552
- let stderrContent = '';
553
-
554
- childProcess.stdout.on('data', (chunk) => {
555
- stdoutContent += chunk.toString('utf8');
556
- });
557
-
558
- childProcess.stderr.on('data', (chunk) => {
559
- stderrContent += chunk.toString('utf8');
560
- });
561
-
562
- childProcess.on('error', (error) => {
563
- resolveResult({
564
- content: [
565
- {
566
- type: 'text',
567
- text: `[${commandLabel}] Failed to start command: ${error.message}`,
568
- },
569
- ],
570
- isError: true,
571
- });
572
- });
573
-
574
- childProcess.on('close', (exitCode) => {
575
- const normalizedExitCode = typeof exitCode === 'number' ? exitCode : 1;
576
- resolveResult({
577
- content: [
578
- {
579
- type: 'text',
580
- text: buildCommandOutput(
581
- commandLabel,
582
- commandArguments,
583
- normalizedExitCode,
584
- stdoutContent,
585
- stderrContent
586
- ),
587
- },
588
- ],
589
- isError: normalizedExitCode !== 0,
590
- });
591
- });
592
- });
593
- }
594
-
595
- async function executeToolCall(toolName, toolArguments = {}) {
596
- if (toolName === 'validate') {
597
- if (!existsSync(INTERNAL_SCRIPT_PATHS.validate)) {
598
- return buildJsonResult({
599
- error: 'validate tool is unavailable because scripts/validate.mjs is missing in this workspace.',
600
- }, true);
601
- }
602
-
603
- return runNodeCommand('validate', ['./scripts/validate.mjs']);
604
- }
605
-
606
- if (toolName === 'test') {
607
- if (AVAILABLE_TEST_SUITES.length === 0) {
608
- return buildJsonResult({
609
- error: 'test tool is unavailable because the managed test suites are not present in this workspace.',
610
- }, true);
611
- }
612
-
613
- const defaultSuite = AVAILABLE_TEST_SUITES[0];
614
- const requestedSuite = typeof toolArguments.suite === 'string'
615
- ? toolArguments.suite
616
- : defaultSuite;
617
-
618
- const selectedSuite = AVAILABLE_TEST_SUITES.includes(requestedSuite)
619
- ? requestedSuite
620
- : defaultSuite;
621
- return runNodeCommand(`test:${selectedSuite}`, TEST_SUITE_ARGS[selectedSuite]);
622
- }
623
-
624
- if (toolName === 'release_gate') {
625
- if (!existsSync(INTERNAL_SCRIPT_PATHS.release_gate)) {
626
- return buildJsonResult({
627
- error: 'release_gate tool is unavailable because scripts/release-gate.mjs is missing in this workspace.',
628
- }, true);
629
- }
630
-
631
- return runNodeCommand('release_gate', ['./scripts/release-gate.mjs']);
632
- }
633
-
634
- if (toolName === 'forbidden_content_check') {
635
- if (!existsSync(INTERNAL_SCRIPT_PATHS.forbidden_content_check)) {
636
- return buildJsonResult({
637
- error: 'forbidden_content_check tool is unavailable because scripts/forbidden-content-check.mjs is missing in this workspace.',
638
- }, true);
639
- }
640
-
641
- return runNodeCommand('forbidden_content_check', ['./scripts/forbidden-content-check.mjs']);
642
- }
643
-
644
- if (toolName === 'research_fetch') {
645
- return runResearchFetchTool(toolArguments);
646
- }
647
-
648
- if (toolName === 'trend_snapshot') {
649
- return runTrendSnapshotTool(toolArguments);
650
- }
651
-
652
- if (toolName === 'state_read') {
653
- return runStateReadTool(toolArguments);
654
- }
655
-
656
- if (toolName === 'state_write') {
657
- return runStateWriteTool(toolArguments);
658
- }
659
-
660
- return {
661
- content: [
662
- {
663
- type: 'text',
664
- text: `Unknown tool: ${toolName}`,
665
- },
666
- ],
667
- isError: true,
668
- };
669
- }
670
-
671
39
  async function handleRequest(requestMessage) {
672
40
  const requestId = requestMessage.id;
673
41
  const requestMethod = requestMessage.method;
@@ -738,46 +106,42 @@ async function handleRequest(requestMessage) {
738
106
 
739
107
  function processIncomingBuffer() {
740
108
  const fullContent = incomingBuffer.toString('utf8');
741
-
742
- // Try to parse as line-delimited JSON first (modern MCP standard)
743
109
  let parseMode = 'line-delimited';
744
-
745
- // Check if Content-Length header is present (LSP-style for backward compatibility)
110
+
746
111
  if (fullContent.includes('Content-Length:')) {
747
112
  parseMode = 'content-length';
748
113
  }
749
-
114
+
750
115
  if (parseMode === 'content-length') {
751
- // LSP-style: Parse Content-Length headers
752
116
  const headerTerminatorIndex = Math.max(
753
117
  fullContent.indexOf('\r\n\r\n'),
754
118
  fullContent.indexOf('\n\n')
755
119
  );
756
-
120
+
757
121
  if (headerTerminatorIndex === -1) {
758
- return; // Incomplete header, wait for more data
122
+ return;
759
123
  }
760
-
124
+
761
125
  const headerText = fullContent.slice(0, headerTerminatorIndex);
762
126
  const contentLengthMatch = headerText.match(/Content-Length:\s*(\d+)/i);
763
-
127
+
764
128
  if (!contentLengthMatch) {
765
129
  incomingBuffer = Buffer.alloc(0);
766
130
  return;
767
131
  }
768
-
132
+
769
133
  const contentLength = parseInt(contentLengthMatch[1], 10);
770
134
  const headerEndLength = fullContent[headerTerminatorIndex] === '\r' ? 4 : 2;
771
135
  const bodyStartIndex = headerTerminatorIndex + headerEndLength;
772
136
  const bodyEndIndex = bodyStartIndex + contentLength;
773
-
137
+
774
138
  if (fullContent.length < bodyEndIndex) {
775
- return; // Body not yet complete
139
+ return;
776
140
  }
777
-
141
+
778
142
  const messageBody = fullContent.slice(bodyStartIndex, bodyEndIndex);
779
143
  incomingBuffer = Buffer.from(fullContent.slice(bodyEndIndex), 'utf8');
780
-
144
+
781
145
  try {
782
146
  const parsedRequest = JSON.parse(messageBody);
783
147
  Promise.resolve(handleRequest(parsedRequest)).catch((error) => {
@@ -786,37 +150,35 @@ function processIncomingBuffer() {
786
150
  }
787
151
  });
788
152
  } catch {
789
- // Ignore parse errors
153
+ // Ignore parse errors.
790
154
  }
791
-
792
- // Recursively process if there's more data
155
+
793
156
  if (incomingBuffer.length > 0) {
794
157
  processIncomingBuffer();
795
158
  }
796
159
  } else {
797
- // Line-delimited: parse one JSON per line
798
160
  const lines = fullContent.split('\n');
799
-
800
- // Keep incomplete last line in buffer
161
+
801
162
  if (fullContent.endsWith('\n')) {
802
163
  incomingBuffer = Buffer.alloc(0);
803
164
  } else {
804
165
  const lastLine = lines.pop() || '';
805
166
  incomingBuffer = Buffer.from(lastLine, 'utf8');
806
167
  }
807
-
168
+
808
169
  for (const line of lines) {
809
170
  const trimmed = line.trim();
810
- if (!trimmed) continue;
811
-
171
+ if (!trimmed) {
172
+ continue;
173
+ }
174
+
812
175
  let parsedRequest;
813
176
  try {
814
177
  parsedRequest = JSON.parse(trimmed);
815
178
  } catch {
816
- // Ignore non-JSON lines
817
179
  continue;
818
180
  }
819
-
181
+
820
182
  Promise.resolve(handleRequest(parsedRequest)).catch((error) => {
821
183
  if (typeof parsedRequest?.id !== 'undefined') {
822
184
  sendError(parsedRequest.id, -32603, 'Internal error', String(error?.message || error));