@ryuenn3123/agentic-senior-core 3.0.17 → 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.
Files changed (34) hide show
  1. package/.agent-context/prompts/bootstrap-design.md +16 -7
  2. package/.agent-context/rules/frontend-architecture.md +5 -5
  3. package/.agent-context/state/memory-continuity-benchmark.json +1 -1
  4. package/.cursorrules +1 -1
  5. package/.gemini/instructions.md +1 -1
  6. package/.github/copilot-instructions.md +1 -1
  7. package/.instructions.md +1 -1
  8. package/.windsurfrules +1 -1
  9. package/AGENTS.md +1 -1
  10. package/lib/cli/project-scaffolder/design-contract.mjs +363 -314
  11. package/lib/cli/project-scaffolder/prompt-builders.mjs +28 -22
  12. package/lib/cli/project-scaffolder/storage.mjs +0 -2
  13. package/package.json +2 -2
  14. package/scripts/frontend-usability-audit.mjs +19 -8
  15. package/scripts/mcp-server/constants.mjs +60 -0
  16. package/scripts/mcp-server/tool-registry.mjs +149 -0
  17. package/scripts/mcp-server/tools.mjs +446 -0
  18. package/scripts/mcp-server.mjs +23 -661
  19. package/scripts/release-gate/audit-checks.mjs +426 -0
  20. package/scripts/release-gate/constants.mjs +53 -0
  21. package/scripts/release-gate/runtime.mjs +63 -0
  22. package/scripts/release-gate/static-checks.mjs +182 -0
  23. package/scripts/release-gate.mjs +12 -793
  24. package/scripts/ui-design-judge/constants.mjs +24 -0
  25. package/scripts/ui-design-judge/design-execution-summary.mjs +233 -0
  26. package/scripts/ui-design-judge/git-input.mjs +131 -0
  27. package/scripts/ui-design-judge/prompting.mjs +73 -0
  28. package/scripts/ui-design-judge/providers.mjs +102 -0
  29. package/scripts/ui-design-judge/reporting.mjs +181 -0
  30. package/scripts/ui-design-judge/rubric-calibration.mjs +211 -0
  31. package/scripts/ui-design-judge/rubric-goldset.json +188 -0
  32. package/scripts/ui-design-judge.mjs +105 -774
  33. package/scripts/ui-rubric-calibration.mjs +35 -0
  34. package/scripts/validate/config.mjs +69 -16
@@ -0,0 +1,446 @@
1
+ // @ts-check
2
+
3
+ import { existsSync } 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 {
8
+ AVAILABLE_TEST_SUITES,
9
+ DEFAULT_FETCH_MAX_CHARS,
10
+ DEFAULT_FETCH_TIMEOUT_MS,
11
+ DEFAULT_TREND_WINDOW_DAYS,
12
+ INTERNAL_SCRIPT_PATHS,
13
+ MAX_FETCH_MAX_CHARS,
14
+ MAX_TREND_PACKAGES,
15
+ PACKAGE_VERSION,
16
+ REPOSITORY_ROOT,
17
+ STATE_DIRECTORY,
18
+ TEST_SUITE_ARGS,
19
+ } from './constants.mjs';
20
+
21
+ function buildCommandOutput(commandLabel, commandArguments, exitCode, stdoutContent, stderrContent) {
22
+ const outputSections = [
23
+ `Command: node ${commandArguments.join(' ')}`,
24
+ `Exit code: ${exitCode}`,
25
+ ];
26
+
27
+ if (stdoutContent.trim().length > 0) {
28
+ outputSections.push(`STDOUT:\n${stdoutContent.trimEnd()}`);
29
+ }
30
+
31
+ if (stderrContent.trim().length > 0) {
32
+ outputSections.push(`STDERR:\n${stderrContent.trimEnd()}`);
33
+ }
34
+
35
+ return [
36
+ `[${commandLabel}]`,
37
+ outputSections.join('\n\n'),
38
+ ].join('\n\n');
39
+ }
40
+
41
+ function buildJsonResult(payload, isError = false) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: 'text',
46
+ text: JSON.stringify(payload, null, 2),
47
+ },
48
+ ],
49
+ isError,
50
+ };
51
+ }
52
+
53
+ function normalizePlainText(rawText) {
54
+ return rawText
55
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
56
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
57
+ .replace(/<[^>]+>/g, ' ')
58
+ .replace(/&nbsp;/gi, ' ')
59
+ .replace(/&amp;/gi, '&')
60
+ .replace(/&lt;/gi, '<')
61
+ .replace(/&gt;/gi, '>')
62
+ .replace(/\s+/g, ' ')
63
+ .trim();
64
+ }
65
+
66
+ function extractQuerySnippets(textContent, queryText) {
67
+ const normalizedQuery = String(queryText || '').trim().toLowerCase();
68
+ if (!normalizedQuery) {
69
+ return [];
70
+ }
71
+
72
+ const normalizedContent = String(textContent || '');
73
+ const normalizedLowerContent = normalizedContent.toLowerCase();
74
+ const snippets = [];
75
+ let searchStartIndex = 0;
76
+
77
+ while (snippets.length < 5) {
78
+ const matchedIndex = normalizedLowerContent.indexOf(normalizedQuery, searchStartIndex);
79
+ if (matchedIndex === -1) {
80
+ break;
81
+ }
82
+
83
+ const contextRadius = 180;
84
+ const snippetStart = Math.max(0, matchedIndex - contextRadius);
85
+ const snippetEnd = Math.min(normalizedContent.length, matchedIndex + normalizedQuery.length + contextRadius);
86
+ const prefix = snippetStart > 0 ? '...' : '';
87
+ const suffix = snippetEnd < normalizedContent.length ? '...' : '';
88
+ snippets.push(`${prefix}${normalizedContent.slice(snippetStart, snippetEnd).trim()}${suffix}`);
89
+ searchStartIndex = matchedIndex + normalizedQuery.length;
90
+ }
91
+
92
+ return snippets;
93
+ }
94
+
95
+ async function fetchWithTimeout(targetUrl, timeoutMs) {
96
+ const fetchController = new AbortController();
97
+ const timeoutHandle = setTimeout(() => fetchController.abort(), timeoutMs);
98
+
99
+ try {
100
+ return await fetch(targetUrl, {
101
+ signal: fetchController.signal,
102
+ headers: {
103
+ 'User-Agent': `agentic-senior-core/${PACKAGE_VERSION}`,
104
+ },
105
+ });
106
+ } finally {
107
+ clearTimeout(timeoutHandle);
108
+ }
109
+ }
110
+
111
+ async function runResearchFetchTool(toolArguments = {}) {
112
+ const targetUrl = String(toolArguments.url || '').trim();
113
+ const queryText = typeof toolArguments.query === 'string' ? toolArguments.query.trim() : '';
114
+ const maxCharsInput = Number(toolArguments.maxChars);
115
+ const maxChars = Number.isFinite(maxCharsInput)
116
+ ? Math.max(200, Math.min(MAX_FETCH_MAX_CHARS, Math.floor(maxCharsInput)))
117
+ : DEFAULT_FETCH_MAX_CHARS;
118
+
119
+ if (!/^https?:\/\//i.test(targetUrl)) {
120
+ return buildJsonResult({
121
+ error: 'Invalid url. Provide absolute HTTP/HTTPS URL.',
122
+ input: targetUrl,
123
+ }, true);
124
+ }
125
+
126
+ try {
127
+ const startedAt = new Date().toISOString();
128
+ const fetchResponse = await fetchWithTimeout(targetUrl, DEFAULT_FETCH_TIMEOUT_MS);
129
+ const rawBody = await fetchResponse.text();
130
+ const plainTextBody = normalizePlainText(rawBody);
131
+ const querySnippets = queryText ? extractQuerySnippets(plainTextBody, queryText) : [];
132
+ const selectedContent = querySnippets.length > 0
133
+ ? querySnippets.join('\n\n')
134
+ : plainTextBody.slice(0, maxChars);
135
+
136
+ return buildJsonResult({
137
+ source: {
138
+ url: targetUrl,
139
+ status: fetchResponse.status,
140
+ ok: fetchResponse.ok,
141
+ fetchedAt: new Date().toISOString(),
142
+ requestedAt: startedAt,
143
+ contentType: fetchResponse.headers.get('content-type') || null,
144
+ },
145
+ query: queryText || null,
146
+ excerptCount: querySnippets.length,
147
+ truncated: !queryText && plainTextBody.length > selectedContent.length,
148
+ content: selectedContent,
149
+ }, !fetchResponse.ok);
150
+ } catch (error) {
151
+ return buildJsonResult({
152
+ error: error instanceof Error ? error.message : String(error),
153
+ source: targetUrl,
154
+ }, true);
155
+ }
156
+ }
157
+
158
+ async function runTrendSnapshotTool(toolArguments = {}) {
159
+ const packageInputs = Array.isArray(toolArguments.packages)
160
+ ? toolArguments.packages.filter((packageName) => typeof packageName === 'string' && packageName.trim().length > 0)
161
+ : [];
162
+ const packageNames = Array.from(new Set(packageInputs.map((packageName) => packageName.trim()))).slice(0, MAX_TREND_PACKAGES);
163
+ const windowDaysInput = Number(toolArguments.windowDays);
164
+ const windowDays = Number.isFinite(windowDaysInput)
165
+ ? Math.max(1, Math.min(3650, Math.floor(windowDaysInput)))
166
+ : DEFAULT_TREND_WINDOW_DAYS;
167
+
168
+ if (packageNames.length === 0) {
169
+ return buildJsonResult({
170
+ error: 'packages[] must include at least one package name.',
171
+ }, true);
172
+ }
173
+
174
+ const nowTimestamp = Date.now();
175
+ const windowStartTimestamp = nowTimestamp - (windowDays * 24 * 60 * 60 * 1000);
176
+ const packageReports = [];
177
+
178
+ for (const packageName of packageNames) {
179
+ const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
180
+
181
+ try {
182
+ const response = await fetchWithTimeout(registryUrl, DEFAULT_FETCH_TIMEOUT_MS);
183
+ if (!response.ok) {
184
+ packageReports.push({
185
+ package: packageName,
186
+ source: registryUrl,
187
+ status: response.status,
188
+ error: `Registry request failed with HTTP ${response.status}`,
189
+ });
190
+ continue;
191
+ }
192
+
193
+ const registryPayload = await response.json();
194
+ const latestVersion = registryPayload?.['dist-tags']?.latest || null;
195
+ const releaseTimes = Object.entries(registryPayload?.time || {})
196
+ .filter(([versionName, publishedAt]) => {
197
+ if (versionName === 'created' || versionName === 'modified') {
198
+ return false;
199
+ }
200
+
201
+ return typeof publishedAt === 'string' && Number.isFinite(Date.parse(publishedAt));
202
+ })
203
+ .map(([versionName, publishedAt]) => ({
204
+ version: versionName,
205
+ publishedAt,
206
+ publishedAtMs: Date.parse(publishedAt),
207
+ }))
208
+ .sort((leftEntry, rightEntry) => rightEntry.publishedAtMs - leftEntry.publishedAtMs);
209
+
210
+ const releasesInWindow = releaseTimes.filter((releaseEntry) => releaseEntry.publishedAtMs >= windowStartTimestamp);
211
+ const latestPublishedAt = latestVersion && typeof registryPayload?.time?.[latestVersion] === 'string'
212
+ ? registryPayload.time[latestVersion]
213
+ : registryPayload?.time?.modified || null;
214
+
215
+ packageReports.push({
216
+ package: packageName,
217
+ source: registryUrl,
218
+ latestVersion,
219
+ latestPublishedAt,
220
+ releasesInWindow: releasesInWindow.length,
221
+ recentReleases: releasesInWindow.slice(0, 5).map((releaseEntry) => ({
222
+ version: releaseEntry.version,
223
+ publishedAt: releaseEntry.publishedAt,
224
+ })),
225
+ });
226
+ } catch (error) {
227
+ packageReports.push({
228
+ package: packageName,
229
+ source: registryUrl,
230
+ error: error instanceof Error ? error.message : String(error),
231
+ });
232
+ }
233
+ }
234
+
235
+ const errorCount = packageReports.filter((packageReport) => typeof packageReport.error === 'string').length;
236
+
237
+ return buildJsonResult({
238
+ generatedAt: new Date().toISOString(),
239
+ windowDays,
240
+ packageCount: packageNames.length,
241
+ errorCount,
242
+ packages: packageReports,
243
+ citation: {
244
+ source: 'npm registry public API',
245
+ fetchedAt: new Date().toISOString(),
246
+ },
247
+ }, errorCount > 0);
248
+ }
249
+
250
+ function resolveStatePath(relativeStatePath) {
251
+ const normalizedRelativePath = String(relativeStatePath || '').replace(/\\/g, '/').replace(/^\/+/, '').trim();
252
+ if (!normalizedRelativePath) {
253
+ throw new Error('path is required and must be relative to .agent-context/state');
254
+ }
255
+
256
+ const resolvedStatePath = resolve(STATE_DIRECTORY, normalizedRelativePath);
257
+ const stateRootPrefix = `${STATE_DIRECTORY}${sep}`;
258
+ if (resolvedStatePath !== STATE_DIRECTORY && !resolvedStatePath.startsWith(stateRootPrefix)) {
259
+ throw new Error('path traversal is not allowed outside .agent-context/state');
260
+ }
261
+
262
+ return {
263
+ normalizedRelativePath,
264
+ resolvedStatePath,
265
+ };
266
+ }
267
+
268
+ async function runStateReadTool(toolArguments = {}) {
269
+ try {
270
+ const { normalizedRelativePath, resolvedStatePath } = resolveStatePath(toolArguments.path);
271
+ const fileContent = await readFile(resolvedStatePath, 'utf8');
272
+
273
+ return buildJsonResult({
274
+ path: normalizedRelativePath,
275
+ readAt: new Date().toISOString(),
276
+ bytes: Buffer.byteLength(fileContent, 'utf8'),
277
+ content: fileContent,
278
+ });
279
+ } catch (error) {
280
+ return buildJsonResult({
281
+ error: error instanceof Error ? error.message : String(error),
282
+ path: toolArguments.path || null,
283
+ }, true);
284
+ }
285
+ }
286
+
287
+ async function runStateWriteTool(toolArguments = {}) {
288
+ const writeMode = toolArguments.mode === 'append' ? 'append' : 'overwrite';
289
+ const contentToWrite = typeof toolArguments.content === 'string' ? toolArguments.content : '';
290
+
291
+ if (typeof toolArguments.content !== 'string') {
292
+ return buildJsonResult({
293
+ error: 'content must be a string.',
294
+ }, true);
295
+ }
296
+
297
+ try {
298
+ const { normalizedRelativePath, resolvedStatePath } = resolveStatePath(toolArguments.path);
299
+ await mkdir(dirname(resolvedStatePath), { recursive: true });
300
+
301
+ if (writeMode === 'append') {
302
+ await writeFile(resolvedStatePath, contentToWrite, { encoding: 'utf8', flag: 'a' });
303
+ } else {
304
+ await writeFile(resolvedStatePath, contentToWrite, 'utf8');
305
+ }
306
+
307
+ return buildJsonResult({
308
+ path: normalizedRelativePath,
309
+ wroteAt: new Date().toISOString(),
310
+ mode: writeMode,
311
+ bytesWritten: Buffer.byteLength(contentToWrite, 'utf8'),
312
+ });
313
+ } catch (error) {
314
+ return buildJsonResult({
315
+ error: error instanceof Error ? error.message : String(error),
316
+ path: toolArguments.path || null,
317
+ mode: writeMode,
318
+ }, true);
319
+ }
320
+ }
321
+
322
+ function runNodeCommand(commandLabel, commandArguments) {
323
+ return new Promise((resolveResult) => {
324
+ const childProcess = spawn(process.execPath, commandArguments, {
325
+ cwd: REPOSITORY_ROOT,
326
+ env: process.env,
327
+ });
328
+
329
+ let stdoutContent = '';
330
+ let stderrContent = '';
331
+
332
+ childProcess.stdout.on('data', (chunk) => {
333
+ stdoutContent += chunk.toString('utf8');
334
+ });
335
+
336
+ childProcess.stderr.on('data', (chunk) => {
337
+ stderrContent += chunk.toString('utf8');
338
+ });
339
+
340
+ childProcess.on('error', (error) => {
341
+ resolveResult({
342
+ content: [
343
+ {
344
+ type: 'text',
345
+ text: `[${commandLabel}] Failed to start command: ${error.message}`,
346
+ },
347
+ ],
348
+ isError: true,
349
+ });
350
+ });
351
+
352
+ childProcess.on('close', (exitCode) => {
353
+ const normalizedExitCode = typeof exitCode === 'number' ? exitCode : 1;
354
+ resolveResult({
355
+ content: [
356
+ {
357
+ type: 'text',
358
+ text: buildCommandOutput(
359
+ commandLabel,
360
+ commandArguments,
361
+ normalizedExitCode,
362
+ stdoutContent,
363
+ stderrContent
364
+ ),
365
+ },
366
+ ],
367
+ isError: normalizedExitCode !== 0,
368
+ });
369
+ });
370
+ });
371
+ }
372
+
373
+ export async function executeToolCall(toolName, toolArguments = {}) {
374
+ if (toolName === 'validate') {
375
+ if (!existsSync(INTERNAL_SCRIPT_PATHS.validate)) {
376
+ return buildJsonResult({
377
+ error: 'validate tool is unavailable because scripts/validate.mjs is missing in this workspace.',
378
+ }, true);
379
+ }
380
+
381
+ return runNodeCommand('validate', ['./scripts/validate.mjs']);
382
+ }
383
+
384
+ if (toolName === 'test') {
385
+ if (AVAILABLE_TEST_SUITES.length === 0) {
386
+ return buildJsonResult({
387
+ error: 'test tool is unavailable because the managed test suites are not present in this workspace.',
388
+ }, true);
389
+ }
390
+
391
+ const defaultSuite = AVAILABLE_TEST_SUITES[0];
392
+ const requestedSuite = typeof toolArguments.suite === 'string'
393
+ ? toolArguments.suite
394
+ : defaultSuite;
395
+ const selectedSuite = AVAILABLE_TEST_SUITES.includes(requestedSuite)
396
+ ? requestedSuite
397
+ : defaultSuite;
398
+ return runNodeCommand(`test:${selectedSuite}`, TEST_SUITE_ARGS[selectedSuite]);
399
+ }
400
+
401
+ if (toolName === 'release_gate') {
402
+ if (!existsSync(INTERNAL_SCRIPT_PATHS.release_gate)) {
403
+ return buildJsonResult({
404
+ error: 'release_gate tool is unavailable because scripts/release-gate.mjs is missing in this workspace.',
405
+ }, true);
406
+ }
407
+
408
+ return runNodeCommand('release_gate', ['./scripts/release-gate.mjs']);
409
+ }
410
+
411
+ if (toolName === 'forbidden_content_check') {
412
+ if (!existsSync(INTERNAL_SCRIPT_PATHS.forbidden_content_check)) {
413
+ return buildJsonResult({
414
+ error: 'forbidden_content_check tool is unavailable because scripts/forbidden-content-check.mjs is missing in this workspace.',
415
+ }, true);
416
+ }
417
+
418
+ return runNodeCommand('forbidden_content_check', ['./scripts/forbidden-content-check.mjs']);
419
+ }
420
+
421
+ if (toolName === 'research_fetch') {
422
+ return runResearchFetchTool(toolArguments);
423
+ }
424
+
425
+ if (toolName === 'trend_snapshot') {
426
+ return runTrendSnapshotTool(toolArguments);
427
+ }
428
+
429
+ if (toolName === 'state_read') {
430
+ return runStateReadTool(toolArguments);
431
+ }
432
+
433
+ if (toolName === 'state_write') {
434
+ return runStateWriteTool(toolArguments);
435
+ }
436
+
437
+ return {
438
+ content: [
439
+ {
440
+ type: 'text',
441
+ text: `Unknown tool: ${toolName}`,
442
+ },
443
+ ],
444
+ isError: true,
445
+ };
446
+ }