@ryuenn3123/agentic-senior-core 2.5.22 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/.agent-context/prompts/init-project.md +5 -5
  2. package/.agent-context/prompts/refactor.md +2 -1
  3. package/.agent-context/prompts/review-code.md +3 -2
  4. package/.agent-context/review-checklists/pr-checklist.md +8 -1
  5. package/.agent-context/rules/architecture.md +11 -0
  6. package/.agent-context/rules/frontend-architecture.md +2 -2
  7. package/.agent-context/state/architecture-map.md +1 -1
  8. package/.agent-context/state/memory-continuity-benchmark.json +1 -1
  9. package/.agents/workflows/init-project.md +3 -3
  10. package/.agents/workflows/refactor.md +1 -1
  11. package/.agents/workflows/review-code.md +4 -5
  12. package/.cursorrules +27 -71
  13. package/.gemini/instructions.md +6 -7
  14. package/.github/copilot-instructions.md +5 -6
  15. package/.windsurfrules +27 -71
  16. package/AGENTS.md +7 -9
  17. package/CONTRIBUTING.md +18 -31
  18. package/README.md +21 -4
  19. package/bin/agentic-senior-core.js +0 -6
  20. package/lib/cli/commands/init.mjs +113 -650
  21. package/lib/cli/commands/launch.mjs +1 -23
  22. package/lib/cli/commands/rollback.mjs +1 -1
  23. package/lib/cli/commands/upgrade.mjs +1 -23
  24. package/lib/cli/compiler.mjs +77 -72
  25. package/lib/cli/constants.mjs +84 -26
  26. package/lib/cli/init-architecture-flow.mjs +231 -0
  27. package/lib/cli/init-detection-flow.mjs +123 -0
  28. package/lib/cli/init-options.mjs +344 -0
  29. package/lib/cli/init-selection.mjs +100 -0
  30. package/lib/cli/preflight.mjs +1 -1
  31. package/lib/cli/profile-packs.mjs +15 -1
  32. package/lib/cli/project-scaffolder.mjs +18 -154
  33. package/lib/cli/utils.mjs +16 -12
  34. package/mcp.json +19 -19
  35. package/package.json +5 -2
  36. package/scripts/context-triggered-audit.mjs +18 -18
  37. package/scripts/documentation-boundary-audit.mjs +92 -5
  38. package/scripts/forbidden-content-check.mjs +1 -1
  39. package/scripts/frontend-usability-audit.mjs +21 -28
  40. package/scripts/governance-weekly-report.mjs +29 -15
  41. package/scripts/llm-judge.mjs +2 -5
  42. package/scripts/mcp-server.mjs +389 -5
  43. package/scripts/release-gate.mjs +121 -145
  44. package/scripts/sync-thin-adapters.mjs +161 -0
  45. package/scripts/v3-purge-audit.mjs +231 -0
  46. package/scripts/validate-evidence-bundle.mjs +1 -1
  47. package/scripts/validate.mjs +224 -272
  48. package/.agent-context/blueprints/api-nextjs.md +0 -184
  49. package/.agent-context/blueprints/aspnet-api.md +0 -247
  50. package/.agent-context/blueprints/ci-github-actions.md +0 -226
  51. package/.agent-context/blueprints/ci-gitlab.md +0 -200
  52. package/.agent-context/blueprints/fastapi-service.md +0 -210
  53. package/.agent-context/blueprints/go-service.md +0 -217
  54. package/.agent-context/blueprints/graphql-grpc-api.md +0 -51
  55. package/.agent-context/blueprints/infrastructure-as-code.md +0 -62
  56. package/.agent-context/blueprints/kubernetes-manifests.md +0 -76
  57. package/.agent-context/blueprints/laravel-api.md +0 -233
  58. package/.agent-context/blueprints/mobile-app.md +0 -91
  59. package/.agent-context/blueprints/nestjs-logic.md +0 -247
  60. package/.agent-context/blueprints/observability.md +0 -227
  61. package/.agent-context/blueprints/spring-boot-api.md +0 -218
  62. package/.agent-context/profiles/platform.md +0 -13
  63. package/.agent-context/profiles/regulated.md +0 -13
  64. package/.agent-context/profiles/startup.md +0 -13
  65. package/.agent-context/review-checklists/frontend-excellence-rubric.md +0 -73
  66. package/.agent-context/review-checklists/frontend-skill-parity.md +0 -29
  67. package/.agent-context/review-checklists/frontend-usability.md +0 -35
  68. package/.agent-context/review-checklists/marketplace-acceptance.md +0 -60
  69. package/.agent-context/review-checklists/performance-audit.md +0 -71
  70. package/.agent-context/review-checklists/release-operations.md +0 -33
  71. package/.agent-context/review-checklists/security-audit.md +0 -119
  72. package/.agent-context/skills/README.md +0 -63
  73. package/.agent-context/skills/backend/README.md +0 -68
  74. package/.agent-context/skills/backend/architecture.md +0 -361
  75. package/.agent-context/skills/backend/compatibility-manifest.json +0 -8
  76. package/.agent-context/skills/backend/data-access.md +0 -231
  77. package/.agent-context/skills/backend/errors.md +0 -138
  78. package/.agent-context/skills/backend/validation.md +0 -117
  79. package/.agent-context/skills/backend.md +0 -29
  80. package/.agent-context/skills/cli/.evidence/compatibility-manifest.json +0 -5
  81. package/.agent-context/skills/cli/.evidence/sbom-excerpt.json +0 -10
  82. package/.agent-context/skills/cli/.evidence/test-report.json +0 -8
  83. package/.agent-context/skills/cli/CHANGELOG.md +0 -6
  84. package/.agent-context/skills/cli/README.md +0 -56
  85. package/.agent-context/skills/cli/compatibility-manifest.json +0 -8
  86. package/.agent-context/skills/cli/init.md +0 -38
  87. package/.agent-context/skills/cli/output.md +0 -36
  88. package/.agent-context/skills/cli/package.json +0 -5
  89. package/.agent-context/skills/cli/safety-telemetry.md +0 -39
  90. package/.agent-context/skills/cli/tests/.gitkeep +0 -1
  91. package/.agent-context/skills/cli/upgrade.md +0 -38
  92. package/.agent-context/skills/cli.md +0 -32
  93. package/.agent-context/skills/distribution/.evidence/compatibility-manifest.json +0 -9
  94. package/.agent-context/skills/distribution/.evidence/sbom-excerpt.json +0 -6
  95. package/.agent-context/skills/distribution/.evidence/test-report.json +0 -8
  96. package/.agent-context/skills/distribution/CHANGELOG.md +0 -7
  97. package/.agent-context/skills/distribution/README.md +0 -27
  98. package/.agent-context/skills/distribution/compatibility-manifest.json +0 -8
  99. package/.agent-context/skills/distribution/compatibility.md +0 -32
  100. package/.agent-context/skills/distribution/package.json +0 -5
  101. package/.agent-context/skills/distribution/provenance-attestation.md +0 -47
  102. package/.agent-context/skills/distribution/publish.md +0 -37
  103. package/.agent-context/skills/distribution/rollback.md +0 -32
  104. package/.agent-context/skills/distribution/tests/.gitkeep +0 -1
  105. package/.agent-context/skills/distribution.md +0 -32
  106. package/.agent-context/skills/frontend/.evidence/compatibility-manifest.json +0 -9
  107. package/.agent-context/skills/frontend/.evidence/sbom-excerpt.json +0 -6
  108. package/.agent-context/skills/frontend/.evidence/test-report.json +0 -8
  109. package/.agent-context/skills/frontend/CHANGELOG.md +0 -7
  110. package/.agent-context/skills/frontend/README.md +0 -50
  111. package/.agent-context/skills/frontend/accessibility.md +0 -107
  112. package/.agent-context/skills/frontend/compatibility-manifest.json +0 -8
  113. package/.agent-context/skills/frontend/conversion-clarity.md +0 -51
  114. package/.agent-context/skills/frontend/motion.md +0 -67
  115. package/.agent-context/skills/frontend/package.json +0 -5
  116. package/.agent-context/skills/frontend/performance.md +0 -63
  117. package/.agent-context/skills/frontend/responsive-delivery.md +0 -41
  118. package/.agent-context/skills/frontend/tests/.gitkeep +0 -1
  119. package/.agent-context/skills/frontend/ui-architecture.md +0 -128
  120. package/.agent-context/skills/frontend.md +0 -40
  121. package/.agent-context/skills/fullstack/.evidence/compatibility-manifest.json +0 -9
  122. package/.agent-context/skills/fullstack/.evidence/sbom-excerpt.json +0 -6
  123. package/.agent-context/skills/fullstack/.evidence/test-report.json +0 -8
  124. package/.agent-context/skills/fullstack/CHANGELOG.md +0 -7
  125. package/.agent-context/skills/fullstack/README.md +0 -27
  126. package/.agent-context/skills/fullstack/compatibility-manifest.json +0 -8
  127. package/.agent-context/skills/fullstack/contracts.md +0 -53
  128. package/.agent-context/skills/fullstack/end-to-end.md +0 -42
  129. package/.agent-context/skills/fullstack/feature-slicing.md +0 -65
  130. package/.agent-context/skills/fullstack/package.json +0 -5
  131. package/.agent-context/skills/fullstack/release-coordination.md +0 -51
  132. package/.agent-context/skills/fullstack/tests/.gitkeep +0 -1
  133. package/.agent-context/skills/fullstack.md +0 -30
  134. package/.agent-context/skills/index.json +0 -107
  135. package/.agent-context/skills/review-quality/.evidence/compatibility-manifest.json +0 -9
  136. package/.agent-context/skills/review-quality/.evidence/sbom-excerpt.json +0 -6
  137. package/.agent-context/skills/review-quality/.evidence/test-report.json +0 -8
  138. package/.agent-context/skills/review-quality/CHANGELOG.md +0 -7
  139. package/.agent-context/skills/review-quality/README.md +0 -27
  140. package/.agent-context/skills/review-quality/benchmark.md +0 -30
  141. package/.agent-context/skills/review-quality/compatibility-manifest.json +0 -8
  142. package/.agent-context/skills/review-quality/package.json +0 -5
  143. package/.agent-context/skills/review-quality/planning.md +0 -38
  144. package/.agent-context/skills/review-quality/release-decision.md +0 -49
  145. package/.agent-context/skills/review-quality/security.md +0 -34
  146. package/.agent-context/skills/review-quality/tests/.gitkeep +0 -1
  147. package/.agent-context/skills/review-quality.md +0 -34
  148. package/.agent-context/stacks/csharp.md +0 -149
  149. package/.agent-context/stacks/flutter.md +0 -16
  150. package/.agent-context/stacks/go.md +0 -181
  151. package/.agent-context/stacks/java.md +0 -135
  152. package/.agent-context/stacks/php.md +0 -192
  153. package/.agent-context/stacks/python.md +0 -153
  154. package/.agent-context/stacks/react-native.md +0 -16
  155. package/.agent-context/stacks/ruby.md +0 -80
  156. package/.agent-context/stacks/rust.md +0 -86
  157. package/.agent-context/stacks/typescript.md +0 -317
  158. package/.agent-context/state/skill-platform.json +0 -38
  159. package/lib/cli/skill-selector.mjs +0 -232
  160. package/lib/cli/templates/api-contract.md.id.tmpl +0 -143
  161. package/lib/cli/templates/api-contract.md.tmpl +0 -143
  162. package/lib/cli/templates/architecture-decision-record.md.id.tmpl +0 -106
  163. package/lib/cli/templates/architecture-decision-record.md.tmpl +0 -145
  164. package/lib/cli/templates/database-schema.md.id.tmpl +0 -74
  165. package/lib/cli/templates/database-schema.md.tmpl +0 -74
  166. package/lib/cli/templates/flow-overview.md.id.tmpl +0 -118
  167. package/lib/cli/templates/flow-overview.md.tmpl +0 -131
  168. package/lib/cli/templates/project-brief.md.id.tmpl +0 -55
  169. package/lib/cli/templates/project-brief.md.tmpl +0 -79
  170. package/scripts/init-project.ps1 +0 -105
  171. package/scripts/init-project.sh +0 -131
  172. package/scripts/skill-tier-policy.mjs +0 -76
  173. package/scripts/trust-scorer.mjs +0 -119
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readFileSync } from 'node:fs';
4
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
5
  import { spawn } from 'node:child_process';
5
- import { dirname, resolve } from 'node:path';
6
+ import { dirname, resolve, sep } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
8
 
8
9
  const SCRIPT_FILE_PATH = fileURLToPath(import.meta.url);
@@ -10,14 +11,19 @@ const REPOSITORY_ROOT = resolve(dirname(SCRIPT_FILE_PATH), '..');
10
11
  const PACKAGE_VERSION = JSON.parse(
11
12
  readFileSync(resolve(REPOSITORY_ROOT, 'package.json'), 'utf8')
12
13
  ).version;
14
+ const STATE_DIRECTORY = resolve(REPOSITORY_ROOT, '.agent-context', 'state');
13
15
  const DEFAULT_PROTOCOL_VERSION = '2024-11-05';
16
+ const DEFAULT_FETCH_TIMEOUT_MS = 15000;
17
+ const DEFAULT_FETCH_MAX_CHARS = 6000;
18
+ const MAX_FETCH_MAX_CHARS = 20000;
19
+ const DEFAULT_TREND_WINDOW_DAYS = 90;
20
+ const MAX_TREND_PACKAGES = 10;
14
21
 
15
22
  const TEST_SUITE_ARGS = {
16
- full: ['--test', './tests/cli-smoke.test.mjs', './tests/llm-judge.test.mjs', './tests/enterprise-ops.test.mjs', './tests/skill-tier-gate.test.mjs'],
23
+ full: ['--test', './tests/cli-smoke.test.mjs', './tests/mcp-server.test.mjs', './tests/llm-judge.test.mjs', './tests/enterprise-ops.test.mjs'],
17
24
  cli: ['--test', './tests/cli-smoke.test.mjs'],
18
25
  enterprise: ['--test', './tests/enterprise-ops.test.mjs'],
19
26
  'llm-judge': ['--test', './tests/llm-judge.test.mjs'],
20
- 'skill-tier': ['--test', './tests/skill-tier-gate.test.mjs'],
21
27
  };
22
28
 
23
29
  const TOOL_DEFINITIONS = [
@@ -38,7 +44,7 @@ const TOOL_DEFINITIONS = [
38
44
  properties: {
39
45
  suite: {
40
46
  type: 'string',
41
- enum: ['full', 'cli', 'enterprise', 'llm-judge', 'skill-tier'],
47
+ enum: ['full', 'cli', 'enterprise', 'llm-judge'],
42
48
  description: 'Target test suite. Defaults to full.',
43
49
  },
44
50
  },
@@ -63,6 +69,88 @@ const TOOL_DEFINITIONS = [
63
69
  additionalProperties: false,
64
70
  },
65
71
  },
72
+ {
73
+ name: 'research_fetch',
74
+ description: 'Fetch external documentation/news content and return query-focused excerpts with citation metadata.',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ url: {
79
+ type: 'string',
80
+ description: 'Absolute HTTP/HTTPS URL to fetch.',
81
+ },
82
+ query: {
83
+ type: 'string',
84
+ description: 'Optional search query used to extract focused excerpts.',
85
+ },
86
+ maxChars: {
87
+ type: 'integer',
88
+ description: 'Maximum characters to return when query is not provided (default 6000, max 20000).',
89
+ },
90
+ },
91
+ required: ['url'],
92
+ additionalProperties: false,
93
+ },
94
+ },
95
+ {
96
+ name: 'trend_snapshot',
97
+ description: 'Generate ecosystem trend snapshot from npm registry metadata with source timestamps.',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ packages: {
102
+ type: 'array',
103
+ items: { type: 'string' },
104
+ description: 'Package names to inspect (max 10).',
105
+ },
106
+ windowDays: {
107
+ type: 'integer',
108
+ description: 'Release activity window in days (default 90).',
109
+ },
110
+ },
111
+ required: ['packages'],
112
+ additionalProperties: false,
113
+ },
114
+ },
115
+ {
116
+ name: 'state_read',
117
+ description: 'Read a file from .agent-context/state for cross-session continuity.',
118
+ inputSchema: {
119
+ type: 'object',
120
+ properties: {
121
+ path: {
122
+ type: 'string',
123
+ description: 'Path relative to .agent-context/state (for example memory-continuity-benchmark.json).',
124
+ },
125
+ },
126
+ required: ['path'],
127
+ additionalProperties: false,
128
+ },
129
+ },
130
+ {
131
+ name: 'state_write',
132
+ description: 'Write a file under .agent-context/state for cross-session continuity updates.',
133
+ inputSchema: {
134
+ type: 'object',
135
+ properties: {
136
+ path: {
137
+ type: 'string',
138
+ description: 'Path relative to .agent-context/state.',
139
+ },
140
+ content: {
141
+ type: 'string',
142
+ description: 'UTF-8 content to write.',
143
+ },
144
+ mode: {
145
+ type: 'string',
146
+ enum: ['overwrite', 'append'],
147
+ description: 'Write mode. Defaults to overwrite.',
148
+ },
149
+ },
150
+ required: ['path', 'content'],
151
+ additionalProperties: false,
152
+ },
153
+ },
66
154
  ];
67
155
 
68
156
  let incomingBuffer = Buffer.alloc(0);
@@ -117,6 +205,287 @@ function buildCommandOutput(commandLabel, commandArguments, exitCode, stdoutCont
117
205
  ].join('\n\n');
118
206
  }
119
207
 
208
+ function buildJsonResult(payload, isError = false) {
209
+ return {
210
+ content: [
211
+ {
212
+ type: 'text',
213
+ text: JSON.stringify(payload, null, 2),
214
+ },
215
+ ],
216
+ isError,
217
+ };
218
+ }
219
+
220
+ function normalizePlainText(rawText) {
221
+ return rawText
222
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
223
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
224
+ .replace(/<[^>]+>/g, ' ')
225
+ .replace(/&nbsp;/gi, ' ')
226
+ .replace(/&amp;/gi, '&')
227
+ .replace(/&lt;/gi, '<')
228
+ .replace(/&gt;/gi, '>')
229
+ .replace(/\s+/g, ' ')
230
+ .trim();
231
+ }
232
+
233
+ function extractQuerySnippets(textContent, queryText) {
234
+ const normalizedQuery = String(queryText || '').trim().toLowerCase();
235
+ if (!normalizedQuery) {
236
+ return [];
237
+ }
238
+
239
+ const normalizedContent = String(textContent || '');
240
+ const normalizedLowerContent = normalizedContent.toLowerCase();
241
+ const snippets = [];
242
+ let searchStartIndex = 0;
243
+
244
+ while (snippets.length < 5) {
245
+ const matchedIndex = normalizedLowerContent.indexOf(normalizedQuery, searchStartIndex);
246
+ if (matchedIndex === -1) {
247
+ break;
248
+ }
249
+
250
+ const contextRadius = 180;
251
+ const snippetStart = Math.max(0, matchedIndex - contextRadius);
252
+ const snippetEnd = Math.min(normalizedContent.length, matchedIndex + normalizedQuery.length + contextRadius);
253
+ const prefix = snippetStart > 0 ? '...' : '';
254
+ const suffix = snippetEnd < normalizedContent.length ? '...' : '';
255
+ snippets.push(`${prefix}${normalizedContent.slice(snippetStart, snippetEnd).trim()}${suffix}`);
256
+ searchStartIndex = matchedIndex + normalizedQuery.length;
257
+ }
258
+
259
+ return snippets;
260
+ }
261
+
262
+ async function fetchWithTimeout(targetUrl, timeoutMs) {
263
+ const fetchController = new AbortController();
264
+ const timeoutHandle = setTimeout(() => fetchController.abort(), timeoutMs);
265
+
266
+ try {
267
+ return await fetch(targetUrl, {
268
+ signal: fetchController.signal,
269
+ headers: {
270
+ 'User-Agent': `agentic-senior-core/${PACKAGE_VERSION}`,
271
+ },
272
+ });
273
+ } finally {
274
+ clearTimeout(timeoutHandle);
275
+ }
276
+ }
277
+
278
+ async function runResearchFetchTool(toolArguments = {}) {
279
+ const targetUrl = String(toolArguments.url || '').trim();
280
+ const queryText = typeof toolArguments.query === 'string' ? toolArguments.query.trim() : '';
281
+ const maxCharsInput = Number(toolArguments.maxChars);
282
+ const maxChars = Number.isFinite(maxCharsInput)
283
+ ? Math.max(200, Math.min(MAX_FETCH_MAX_CHARS, Math.floor(maxCharsInput)))
284
+ : DEFAULT_FETCH_MAX_CHARS;
285
+
286
+ if (!/^https?:\/\//i.test(targetUrl)) {
287
+ return buildJsonResult({
288
+ error: 'Invalid url. Provide absolute HTTP/HTTPS URL.',
289
+ input: targetUrl,
290
+ }, true);
291
+ }
292
+
293
+ try {
294
+ const startedAt = new Date().toISOString();
295
+ const fetchResponse = await fetchWithTimeout(targetUrl, DEFAULT_FETCH_TIMEOUT_MS);
296
+ const rawBody = await fetchResponse.text();
297
+ const plainTextBody = normalizePlainText(rawBody);
298
+ const querySnippets = queryText ? extractQuerySnippets(plainTextBody, queryText) : [];
299
+ const selectedContent = querySnippets.length > 0
300
+ ? querySnippets.join('\n\n')
301
+ : plainTextBody.slice(0, maxChars);
302
+
303
+ return buildJsonResult({
304
+ source: {
305
+ url: targetUrl,
306
+ status: fetchResponse.status,
307
+ ok: fetchResponse.ok,
308
+ fetchedAt: new Date().toISOString(),
309
+ requestedAt: startedAt,
310
+ contentType: fetchResponse.headers.get('content-type') || null,
311
+ },
312
+ query: queryText || null,
313
+ excerptCount: querySnippets.length,
314
+ truncated: !queryText && plainTextBody.length > selectedContent.length,
315
+ content: selectedContent,
316
+ }, !fetchResponse.ok);
317
+ } catch (error) {
318
+ return buildJsonResult({
319
+ error: error instanceof Error ? error.message : String(error),
320
+ source: targetUrl,
321
+ }, true);
322
+ }
323
+ }
324
+
325
+ async function runTrendSnapshotTool(toolArguments = {}) {
326
+ const packageInputs = Array.isArray(toolArguments.packages)
327
+ ? toolArguments.packages.filter((packageName) => typeof packageName === 'string' && packageName.trim().length > 0)
328
+ : [];
329
+ const packageNames = Array.from(new Set(packageInputs.map((packageName) => packageName.trim()))).slice(0, MAX_TREND_PACKAGES);
330
+ const windowDaysInput = Number(toolArguments.windowDays);
331
+ const windowDays = Number.isFinite(windowDaysInput)
332
+ ? Math.max(1, Math.min(3650, Math.floor(windowDaysInput)))
333
+ : DEFAULT_TREND_WINDOW_DAYS;
334
+
335
+ if (packageNames.length === 0) {
336
+ return buildJsonResult({
337
+ error: 'packages[] must include at least one package name.',
338
+ }, true);
339
+ }
340
+
341
+ const nowTimestamp = Date.now();
342
+ const windowStartTimestamp = nowTimestamp - (windowDays * 24 * 60 * 60 * 1000);
343
+ const packageReports = [];
344
+
345
+ for (const packageName of packageNames) {
346
+ const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
347
+
348
+ try {
349
+ const response = await fetchWithTimeout(registryUrl, DEFAULT_FETCH_TIMEOUT_MS);
350
+ if (!response.ok) {
351
+ packageReports.push({
352
+ package: packageName,
353
+ source: registryUrl,
354
+ status: response.status,
355
+ error: `Registry request failed with HTTP ${response.status}`,
356
+ });
357
+ continue;
358
+ }
359
+
360
+ const registryPayload = await response.json();
361
+ const latestVersion = registryPayload?.['dist-tags']?.latest || null;
362
+ const releaseTimes = Object.entries(registryPayload?.time || {})
363
+ .filter(([versionName, publishedAt]) => {
364
+ if (versionName === 'created' || versionName === 'modified') {
365
+ return false;
366
+ }
367
+
368
+ return typeof publishedAt === 'string' && Number.isFinite(Date.parse(publishedAt));
369
+ })
370
+ .map(([versionName, publishedAt]) => ({
371
+ version: versionName,
372
+ publishedAt,
373
+ publishedAtMs: Date.parse(publishedAt),
374
+ }))
375
+ .sort((leftEntry, rightEntry) => rightEntry.publishedAtMs - leftEntry.publishedAtMs);
376
+
377
+ const releasesInWindow = releaseTimes.filter((releaseEntry) => releaseEntry.publishedAtMs >= windowStartTimestamp);
378
+ const latestPublishedAt = latestVersion && typeof registryPayload?.time?.[latestVersion] === 'string'
379
+ ? registryPayload.time[latestVersion]
380
+ : registryPayload?.time?.modified || null;
381
+
382
+ packageReports.push({
383
+ package: packageName,
384
+ source: registryUrl,
385
+ latestVersion,
386
+ latestPublishedAt,
387
+ releasesInWindow: releasesInWindow.length,
388
+ recentReleases: releasesInWindow.slice(0, 5).map((releaseEntry) => ({
389
+ version: releaseEntry.version,
390
+ publishedAt: releaseEntry.publishedAt,
391
+ })),
392
+ });
393
+ } catch (error) {
394
+ packageReports.push({
395
+ package: packageName,
396
+ source: registryUrl,
397
+ error: error instanceof Error ? error.message : String(error),
398
+ });
399
+ }
400
+ }
401
+
402
+ const errorCount = packageReports.filter((packageReport) => typeof packageReport.error === 'string').length;
403
+
404
+ return buildJsonResult({
405
+ generatedAt: new Date().toISOString(),
406
+ windowDays,
407
+ packageCount: packageNames.length,
408
+ errorCount,
409
+ packages: packageReports,
410
+ citation: {
411
+ source: 'npm registry public API',
412
+ fetchedAt: new Date().toISOString(),
413
+ },
414
+ }, errorCount > 0);
415
+ }
416
+
417
+ function resolveStatePath(relativeStatePath) {
418
+ const normalizedRelativePath = String(relativeStatePath || '').replace(/\\/g, '/').replace(/^\/+/, '').trim();
419
+ if (!normalizedRelativePath) {
420
+ throw new Error('path is required and must be relative to .agent-context/state');
421
+ }
422
+
423
+ const resolvedStatePath = resolve(STATE_DIRECTORY, normalizedRelativePath);
424
+ const stateRootPrefix = `${STATE_DIRECTORY}${sep}`;
425
+ if (resolvedStatePath !== STATE_DIRECTORY && !resolvedStatePath.startsWith(stateRootPrefix)) {
426
+ throw new Error('path traversal is not allowed outside .agent-context/state');
427
+ }
428
+
429
+ return {
430
+ normalizedRelativePath,
431
+ resolvedStatePath,
432
+ };
433
+ }
434
+
435
+ async function runStateReadTool(toolArguments = {}) {
436
+ try {
437
+ const { normalizedRelativePath, resolvedStatePath } = resolveStatePath(toolArguments.path);
438
+ const fileContent = await readFile(resolvedStatePath, 'utf8');
439
+
440
+ return buildJsonResult({
441
+ path: normalizedRelativePath,
442
+ readAt: new Date().toISOString(),
443
+ bytes: Buffer.byteLength(fileContent, 'utf8'),
444
+ content: fileContent,
445
+ });
446
+ } catch (error) {
447
+ return buildJsonResult({
448
+ error: error instanceof Error ? error.message : String(error),
449
+ path: toolArguments.path || null,
450
+ }, true);
451
+ }
452
+ }
453
+
454
+ async function runStateWriteTool(toolArguments = {}) {
455
+ const writeMode = toolArguments.mode === 'append' ? 'append' : 'overwrite';
456
+ const contentToWrite = typeof toolArguments.content === 'string' ? toolArguments.content : '';
457
+
458
+ if (typeof toolArguments.content !== 'string') {
459
+ return buildJsonResult({
460
+ error: 'content must be a string.',
461
+ }, true);
462
+ }
463
+
464
+ try {
465
+ const { normalizedRelativePath, resolvedStatePath } = resolveStatePath(toolArguments.path);
466
+ await mkdir(dirname(resolvedStatePath), { recursive: true });
467
+
468
+ if (writeMode === 'append') {
469
+ await writeFile(resolvedStatePath, contentToWrite, { encoding: 'utf8', flag: 'a' });
470
+ } else {
471
+ await writeFile(resolvedStatePath, contentToWrite, 'utf8');
472
+ }
473
+
474
+ return buildJsonResult({
475
+ path: normalizedRelativePath,
476
+ wroteAt: new Date().toISOString(),
477
+ mode: writeMode,
478
+ bytesWritten: Buffer.byteLength(contentToWrite, 'utf8'),
479
+ });
480
+ } catch (error) {
481
+ return buildJsonResult({
482
+ error: error instanceof Error ? error.message : String(error),
483
+ path: toolArguments.path || null,
484
+ mode: writeMode,
485
+ }, true);
486
+ }
487
+ }
488
+
120
489
  function runNodeCommand(commandLabel, commandArguments) {
121
490
  return new Promise((resolveResult) => {
122
491
  const childProcess = spawn(process.execPath, commandArguments, {
@@ -190,6 +559,22 @@ async function executeToolCall(toolName, toolArguments = {}) {
190
559
  return runNodeCommand('forbidden_content_check', ['./scripts/forbidden-content-check.mjs']);
191
560
  }
192
561
 
562
+ if (toolName === 'research_fetch') {
563
+ return runResearchFetchTool(toolArguments);
564
+ }
565
+
566
+ if (toolName === 'trend_snapshot') {
567
+ return runTrendSnapshotTool(toolArguments);
568
+ }
569
+
570
+ if (toolName === 'state_read') {
571
+ return runStateReadTool(toolArguments);
572
+ }
573
+
574
+ if (toolName === 'state_write') {
575
+ return runStateWriteTool(toolArguments);
576
+ }
577
+
193
578
  return {
194
579
  content: [
195
580
  {
@@ -274,7 +659,6 @@ function processIncomingBuffer() {
274
659
 
275
660
  // Try to parse as line-delimited JSON first (modern MCP standard)
276
661
  let parseMode = 'line-delimited';
277
- let contentToProcess = fullContent;
278
662
 
279
663
  // Check if Content-Length header is present (LSP-style for backward compatibility)
280
664
  if (fullContent.includes('Content-Length:')) {