@ryuenn3123/agentic-senior-core 3.0.16 → 3.0.19
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.
- package/.agent-context/prompts/bootstrap-design.md +31 -4
- package/.agent-context/rules/frontend-architecture.md +26 -0
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.cursorrules +1 -1
- package/.gemini/instructions.md +7 -1
- package/.github/copilot-instructions.md +7 -1
- package/.instructions.md +3 -0
- package/.windsurfrules +1 -1
- package/AGENTS.md +13 -1
- package/lib/cli/commands/init.mjs +2 -2
- package/lib/cli/memory-continuity.mjs +2 -1
- package/lib/cli/project-scaffolder/constants.mjs +1 -0
- package/lib/cli/project-scaffolder/design-contract.mjs +523 -171
- package/lib/cli/project-scaffolder/prompt-builders.mjs +38 -15
- package/lib/cli/project-scaffolder/storage.mjs +0 -2
- package/package.json +2 -2
- package/scripts/documentation-boundary-audit.mjs +5 -2
- package/scripts/frontend-usability-audit.mjs +34 -0
- package/scripts/mcp-server/constants.mjs +60 -0
- package/scripts/mcp-server/tool-registry.mjs +149 -0
- package/scripts/mcp-server/tools.mjs +446 -0
- package/scripts/mcp-server.mjs +23 -661
- package/scripts/release-gate/audit-checks.mjs +426 -0
- package/scripts/release-gate/constants.mjs +53 -0
- package/scripts/release-gate/runtime.mjs +63 -0
- package/scripts/release-gate/static-checks.mjs +182 -0
- package/scripts/release-gate.mjs +12 -771
- package/scripts/sync-thin-adapters.mjs +24 -0
- package/scripts/ui-design-judge/constants.mjs +24 -0
- package/scripts/ui-design-judge/design-execution-summary.mjs +233 -0
- package/scripts/ui-design-judge/git-input.mjs +131 -0
- package/scripts/ui-design-judge/prompting.mjs +73 -0
- package/scripts/ui-design-judge/providers.mjs +102 -0
- package/scripts/ui-design-judge/reporting.mjs +181 -0
- package/scripts/ui-design-judge/rubric-calibration.mjs +211 -0
- package/scripts/ui-design-judge/rubric-goldset.json +188 -0
- package/scripts/ui-design-judge.mjs +130 -441
- package/scripts/ui-rubric-calibration.mjs +35 -0
- package/scripts/validate/config.mjs +98 -0
package/scripts/mcp-server.mjs
CHANGED
|
@@ -1,218 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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(/ /gi, ' ')
|
|
281
|
-
.replace(/&/gi, '&')
|
|
282
|
-
.replace(/</gi, '<')
|
|
283
|
-
.replace(/>/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;
|
|
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;
|
|
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)
|
|
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));
|