@ryuenn3123/agentic-senior-core 2.5.22 → 3.0.2

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 (174) hide show
  1. package/.agent-context/prompts/bootstrap-design.md +22 -0
  2. package/.agent-context/prompts/init-project.md +55 -39
  3. package/.agent-context/prompts/refactor.md +2 -1
  4. package/.agent-context/prompts/review-code.md +3 -2
  5. package/.agent-context/review-checklists/pr-checklist.md +8 -1
  6. package/.agent-context/rules/architecture.md +11 -0
  7. package/.agent-context/rules/frontend-architecture.md +2 -2
  8. package/.agent-context/state/architecture-map.md +1 -1
  9. package/.agent-context/state/memory-continuity-benchmark.json +1 -1
  10. package/.agents/workflows/init-project.md +3 -3
  11. package/.agents/workflows/refactor.md +1 -1
  12. package/.agents/workflows/review-code.md +4 -5
  13. package/.cursorrules +27 -71
  14. package/.gemini/instructions.md +6 -7
  15. package/.github/copilot-instructions.md +5 -6
  16. package/.windsurfrules +27 -71
  17. package/AGENTS.md +7 -9
  18. package/CONTRIBUTING.md +18 -31
  19. package/README.md +17 -43
  20. package/bin/agentic-senior-core.js +0 -6
  21. package/lib/cli/commands/init.mjs +113 -650
  22. package/lib/cli/commands/launch.mjs +1 -23
  23. package/lib/cli/commands/rollback.mjs +1 -1
  24. package/lib/cli/commands/upgrade.mjs +47 -28
  25. package/lib/cli/compiler.mjs +77 -72
  26. package/lib/cli/constants.mjs +84 -26
  27. package/lib/cli/init-architecture-flow.mjs +231 -0
  28. package/lib/cli/init-detection-flow.mjs +123 -0
  29. package/lib/cli/init-options.mjs +344 -0
  30. package/lib/cli/init-selection.mjs +100 -0
  31. package/lib/cli/preflight.mjs +1 -1
  32. package/lib/cli/profile-packs.mjs +15 -1
  33. package/lib/cli/project-scaffolder.mjs +18 -154
  34. package/lib/cli/utils.mjs +224 -13
  35. package/mcp.json +19 -19
  36. package/package.json +5 -2
  37. package/scripts/context-triggered-audit.mjs +18 -18
  38. package/scripts/documentation-boundary-audit.mjs +92 -5
  39. package/scripts/forbidden-content-check.mjs +1 -1
  40. package/scripts/frontend-usability-audit.mjs +21 -28
  41. package/scripts/governance-weekly-report.mjs +29 -15
  42. package/scripts/llm-judge.mjs +2 -5
  43. package/scripts/mcp-server.mjs +389 -5
  44. package/scripts/release-gate.mjs +121 -145
  45. package/scripts/sync-thin-adapters.mjs +161 -0
  46. package/scripts/v3-purge-audit.mjs +231 -0
  47. package/scripts/validate-evidence-bundle.mjs +1 -1
  48. package/scripts/validate.mjs +224 -272
  49. package/.agent-context/blueprints/api-nextjs.md +0 -184
  50. package/.agent-context/blueprints/aspnet-api.md +0 -247
  51. package/.agent-context/blueprints/ci-github-actions.md +0 -226
  52. package/.agent-context/blueprints/ci-gitlab.md +0 -200
  53. package/.agent-context/blueprints/fastapi-service.md +0 -210
  54. package/.agent-context/blueprints/go-service.md +0 -217
  55. package/.agent-context/blueprints/graphql-grpc-api.md +0 -51
  56. package/.agent-context/blueprints/infrastructure-as-code.md +0 -62
  57. package/.agent-context/blueprints/kubernetes-manifests.md +0 -76
  58. package/.agent-context/blueprints/laravel-api.md +0 -233
  59. package/.agent-context/blueprints/mobile-app.md +0 -91
  60. package/.agent-context/blueprints/nestjs-logic.md +0 -247
  61. package/.agent-context/blueprints/observability.md +0 -227
  62. package/.agent-context/blueprints/spring-boot-api.md +0 -218
  63. package/.agent-context/profiles/platform.md +0 -13
  64. package/.agent-context/profiles/regulated.md +0 -13
  65. package/.agent-context/profiles/startup.md +0 -13
  66. package/.agent-context/review-checklists/frontend-excellence-rubric.md +0 -73
  67. package/.agent-context/review-checklists/frontend-skill-parity.md +0 -29
  68. package/.agent-context/review-checklists/frontend-usability.md +0 -35
  69. package/.agent-context/review-checklists/marketplace-acceptance.md +0 -60
  70. package/.agent-context/review-checklists/performance-audit.md +0 -71
  71. package/.agent-context/review-checklists/release-operations.md +0 -33
  72. package/.agent-context/review-checklists/security-audit.md +0 -119
  73. package/.agent-context/skills/README.md +0 -63
  74. package/.agent-context/skills/backend/README.md +0 -68
  75. package/.agent-context/skills/backend/architecture.md +0 -361
  76. package/.agent-context/skills/backend/compatibility-manifest.json +0 -8
  77. package/.agent-context/skills/backend/data-access.md +0 -231
  78. package/.agent-context/skills/backend/errors.md +0 -138
  79. package/.agent-context/skills/backend/validation.md +0 -117
  80. package/.agent-context/skills/backend.md +0 -29
  81. package/.agent-context/skills/cli/.evidence/compatibility-manifest.json +0 -5
  82. package/.agent-context/skills/cli/.evidence/sbom-excerpt.json +0 -10
  83. package/.agent-context/skills/cli/.evidence/test-report.json +0 -8
  84. package/.agent-context/skills/cli/CHANGELOG.md +0 -6
  85. package/.agent-context/skills/cli/README.md +0 -56
  86. package/.agent-context/skills/cli/compatibility-manifest.json +0 -8
  87. package/.agent-context/skills/cli/init.md +0 -38
  88. package/.agent-context/skills/cli/output.md +0 -36
  89. package/.agent-context/skills/cli/package.json +0 -5
  90. package/.agent-context/skills/cli/safety-telemetry.md +0 -39
  91. package/.agent-context/skills/cli/tests/.gitkeep +0 -1
  92. package/.agent-context/skills/cli/upgrade.md +0 -38
  93. package/.agent-context/skills/cli.md +0 -32
  94. package/.agent-context/skills/distribution/.evidence/compatibility-manifest.json +0 -9
  95. package/.agent-context/skills/distribution/.evidence/sbom-excerpt.json +0 -6
  96. package/.agent-context/skills/distribution/.evidence/test-report.json +0 -8
  97. package/.agent-context/skills/distribution/CHANGELOG.md +0 -7
  98. package/.agent-context/skills/distribution/README.md +0 -27
  99. package/.agent-context/skills/distribution/compatibility-manifest.json +0 -8
  100. package/.agent-context/skills/distribution/compatibility.md +0 -32
  101. package/.agent-context/skills/distribution/package.json +0 -5
  102. package/.agent-context/skills/distribution/provenance-attestation.md +0 -47
  103. package/.agent-context/skills/distribution/publish.md +0 -37
  104. package/.agent-context/skills/distribution/rollback.md +0 -32
  105. package/.agent-context/skills/distribution/tests/.gitkeep +0 -1
  106. package/.agent-context/skills/distribution.md +0 -32
  107. package/.agent-context/skills/frontend/.evidence/compatibility-manifest.json +0 -9
  108. package/.agent-context/skills/frontend/.evidence/sbom-excerpt.json +0 -6
  109. package/.agent-context/skills/frontend/.evidence/test-report.json +0 -8
  110. package/.agent-context/skills/frontend/CHANGELOG.md +0 -7
  111. package/.agent-context/skills/frontend/README.md +0 -50
  112. package/.agent-context/skills/frontend/accessibility.md +0 -107
  113. package/.agent-context/skills/frontend/compatibility-manifest.json +0 -8
  114. package/.agent-context/skills/frontend/conversion-clarity.md +0 -51
  115. package/.agent-context/skills/frontend/motion.md +0 -67
  116. package/.agent-context/skills/frontend/package.json +0 -5
  117. package/.agent-context/skills/frontend/performance.md +0 -63
  118. package/.agent-context/skills/frontend/responsive-delivery.md +0 -41
  119. package/.agent-context/skills/frontend/tests/.gitkeep +0 -1
  120. package/.agent-context/skills/frontend/ui-architecture.md +0 -128
  121. package/.agent-context/skills/frontend.md +0 -40
  122. package/.agent-context/skills/fullstack/.evidence/compatibility-manifest.json +0 -9
  123. package/.agent-context/skills/fullstack/.evidence/sbom-excerpt.json +0 -6
  124. package/.agent-context/skills/fullstack/.evidence/test-report.json +0 -8
  125. package/.agent-context/skills/fullstack/CHANGELOG.md +0 -7
  126. package/.agent-context/skills/fullstack/README.md +0 -27
  127. package/.agent-context/skills/fullstack/compatibility-manifest.json +0 -8
  128. package/.agent-context/skills/fullstack/contracts.md +0 -53
  129. package/.agent-context/skills/fullstack/end-to-end.md +0 -42
  130. package/.agent-context/skills/fullstack/feature-slicing.md +0 -65
  131. package/.agent-context/skills/fullstack/package.json +0 -5
  132. package/.agent-context/skills/fullstack/release-coordination.md +0 -51
  133. package/.agent-context/skills/fullstack/tests/.gitkeep +0 -1
  134. package/.agent-context/skills/fullstack.md +0 -30
  135. package/.agent-context/skills/index.json +0 -107
  136. package/.agent-context/skills/review-quality/.evidence/compatibility-manifest.json +0 -9
  137. package/.agent-context/skills/review-quality/.evidence/sbom-excerpt.json +0 -6
  138. package/.agent-context/skills/review-quality/.evidence/test-report.json +0 -8
  139. package/.agent-context/skills/review-quality/CHANGELOG.md +0 -7
  140. package/.agent-context/skills/review-quality/README.md +0 -27
  141. package/.agent-context/skills/review-quality/benchmark.md +0 -30
  142. package/.agent-context/skills/review-quality/compatibility-manifest.json +0 -8
  143. package/.agent-context/skills/review-quality/package.json +0 -5
  144. package/.agent-context/skills/review-quality/planning.md +0 -38
  145. package/.agent-context/skills/review-quality/release-decision.md +0 -49
  146. package/.agent-context/skills/review-quality/security.md +0 -34
  147. package/.agent-context/skills/review-quality/tests/.gitkeep +0 -1
  148. package/.agent-context/skills/review-quality.md +0 -34
  149. package/.agent-context/stacks/csharp.md +0 -149
  150. package/.agent-context/stacks/flutter.md +0 -16
  151. package/.agent-context/stacks/go.md +0 -181
  152. package/.agent-context/stacks/java.md +0 -135
  153. package/.agent-context/stacks/php.md +0 -192
  154. package/.agent-context/stacks/python.md +0 -153
  155. package/.agent-context/stacks/react-native.md +0 -16
  156. package/.agent-context/stacks/ruby.md +0 -80
  157. package/.agent-context/stacks/rust.md +0 -86
  158. package/.agent-context/stacks/typescript.md +0 -317
  159. package/.agent-context/state/skill-platform.json +0 -38
  160. package/lib/cli/skill-selector.mjs +0 -232
  161. package/lib/cli/templates/api-contract.md.id.tmpl +0 -143
  162. package/lib/cli/templates/api-contract.md.tmpl +0 -143
  163. package/lib/cli/templates/architecture-decision-record.md.id.tmpl +0 -106
  164. package/lib/cli/templates/architecture-decision-record.md.tmpl +0 -145
  165. package/lib/cli/templates/database-schema.md.id.tmpl +0 -74
  166. package/lib/cli/templates/database-schema.md.tmpl +0 -74
  167. package/lib/cli/templates/flow-overview.md.id.tmpl +0 -118
  168. package/lib/cli/templates/flow-overview.md.tmpl +0 -131
  169. package/lib/cli/templates/project-brief.md.id.tmpl +0 -55
  170. package/lib/cli/templates/project-brief.md.tmpl +0 -79
  171. package/scripts/init-project.ps1 +0 -105
  172. package/scripts/init-project.sh +0 -131
  173. package/scripts/skill-tier-policy.mjs +0 -76
  174. package/scripts/trust-scorer.mjs +0 -119
@@ -21,8 +21,8 @@ const REQUIRED_FILES = [
21
21
  'docs/v1.7-issue-breakdown.md',
22
22
  'docs/v1.7-execution-playbook.md',
23
23
  '.agent-context/rules/frontend-architecture.md',
24
- '.agent-context/review-checklists/frontend-usability.md',
25
- '.agent-context/review-checklists/frontend-excellence-rubric.md',
24
+ '.agent-context/review-checklists/pr-checklist.md',
25
+ '.agent-context/review-checklists/architecture-review.md',
26
26
  ];
27
27
 
28
28
  const REQUIRED_ROADMAP_SNIPPETS = [
@@ -32,24 +32,17 @@ const REQUIRED_ROADMAP_SNIPPETS = [
32
32
  'Delivered Scope',
33
33
  ];
34
34
 
35
- const REQUIRED_CHECKLIST_SNIPPETS = [
36
- 'Responsiveness',
37
- 'Accessibility',
38
- 'Performance',
39
- 'Documentation and Release Evidence',
35
+ const REQUIRED_PR_CHECKLIST_SNIPPETS = [
36
+ '### 2. Architecture (→ rules/architecture.md)',
37
+ '### 10. Documentation',
38
+ '### 15. Universal SOP Consolidation',
40
39
  ];
41
40
 
42
- const REQUIRED_EXCELLENCE_RUBRIC_SNIPPETS = [
43
- 'Visual Direction and Identity',
44
- 'Typography Quality',
45
- 'Color System Diversity and Contrast',
46
- 'Interaction Choreography',
47
- 'Language and Content Consistency',
48
- 'Text Contrast and Collision Safety',
49
- 'UX Narrative and Conversion Clarity',
50
- 'Template Diversity and Originality',
51
- 'Low-Diversity Template Output Policy',
52
- 'Awwwards-level reference quality',
41
+ const REQUIRED_ARCHITECTURE_CHECKLIST_SNIPPETS = [
42
+ '## Backend Universal Principles',
43
+ 'No clever hacks in backend and shared core modules',
44
+ 'No premature abstraction',
45
+ 'Readability over brevity',
53
46
  ];
54
47
 
55
48
  const REQUIRED_FRONTEND_RULE_SNIPPETS = [
@@ -86,17 +79,17 @@ function runAudit() {
86
79
 
87
80
  const roadmapPath = 'docs/roadmap.md';
88
81
  const frontendRulePath = '.agent-context/rules/frontend-architecture.md';
89
- const checklistPath = '.agent-context/review-checklists/frontend-usability.md';
90
- const excellenceRubricPath = '.agent-context/review-checklists/frontend-excellence-rubric.md';
82
+ const prChecklistPath = '.agent-context/review-checklists/pr-checklist.md';
83
+ const architectureChecklistPath = '.agent-context/review-checklists/architecture-review.md';
91
84
 
92
85
  if (existsSync(resolve(REPOSITORY_ROOT, roadmapPath))) {
93
86
  const roadmapContent = readFileSync(resolve(REPOSITORY_ROOT, roadmapPath), 'utf8');
94
87
  assertContains('Roadmap', roadmapPath, roadmapContent, REQUIRED_ROADMAP_SNIPPETS, failures);
95
88
  }
96
89
 
97
- if (existsSync(resolve(REPOSITORY_ROOT, checklistPath))) {
98
- const checklistContent = readFileSync(resolve(REPOSITORY_ROOT, checklistPath), 'utf8');
99
- assertContains('Checklist', checklistPath, checklistContent, REQUIRED_CHECKLIST_SNIPPETS, failures);
90
+ if (existsSync(resolve(REPOSITORY_ROOT, prChecklistPath))) {
91
+ const checklistContent = readFileSync(resolve(REPOSITORY_ROOT, prChecklistPath), 'utf8');
92
+ assertContains('PR checklist', prChecklistPath, checklistContent, REQUIRED_PR_CHECKLIST_SNIPPETS, failures);
100
93
  }
101
94
 
102
95
  if (existsSync(resolve(REPOSITORY_ROOT, frontendRulePath))) {
@@ -104,13 +97,13 @@ function runAudit() {
104
97
  assertContains('Frontend rule', frontendRulePath, frontendRuleContent, REQUIRED_FRONTEND_RULE_SNIPPETS, failures);
105
98
  }
106
99
 
107
- if (existsSync(resolve(REPOSITORY_ROOT, excellenceRubricPath))) {
108
- const excellenceRubricContent = readFileSync(resolve(REPOSITORY_ROOT, excellenceRubricPath), 'utf8');
100
+ if (existsSync(resolve(REPOSITORY_ROOT, architectureChecklistPath))) {
101
+ const excellenceRubricContent = readFileSync(resolve(REPOSITORY_ROOT, architectureChecklistPath), 'utf8');
109
102
  assertContains(
110
- 'Frontend excellence rubric',
111
- excellenceRubricPath,
103
+ 'Architecture checklist',
104
+ architectureChecklistPath,
112
105
  excellenceRubricContent,
113
- REQUIRED_EXCELLENCE_RUBRIC_SNIPPETS,
106
+ REQUIRED_ARCHITECTURE_CHECKLIST_SNIPPETS,
114
107
  failures
115
108
  );
116
109
  }
@@ -12,7 +12,6 @@ import fs from 'node:fs/promises';
12
12
  import { spawnSync } from 'node:child_process';
13
13
  import { dirname, join, resolve } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
- import { calculateTrustScore } from './trust-scorer.mjs';
16
15
 
17
16
  const SCRIPT_FILE_PATH = fileURLToPath(import.meta.url);
18
17
  const SCRIPT_DIR = dirname(SCRIPT_FILE_PATH);
@@ -23,7 +22,20 @@ const ARGUMENT_FLAGS = new Set(process.argv.slice(2));
23
22
  const isStdoutOnlyMode = ARGUMENT_FLAGS.has('--stdout-only');
24
23
  const WEEKLY_WINDOW_DAYS = 7;
25
24
  const HISTORY_LIMIT = 26;
26
- const REQUIRED_VERIFIED_DOMAINS = new Set(['cli', 'frontend', 'fullstack', 'distribution', 'review-quality']);
25
+ const REQUIRED_VERIFIED_DOMAINS = new Set([
26
+ 'canonical-instructions',
27
+ 'pr-checklist',
28
+ 'architecture-review',
29
+ 'mcp-server',
30
+ 'state-continuity',
31
+ ]);
32
+ const GOVERNANCE_SURFACE_PATHS = {
33
+ 'canonical-instructions': '.instructions.md',
34
+ 'pr-checklist': '.agent-context/review-checklists/pr-checklist.md',
35
+ 'architecture-review': '.agent-context/review-checklists/architecture-review.md',
36
+ 'mcp-server': 'scripts/mcp-server.mjs',
37
+ 'state-continuity': '.agent-context/state',
38
+ };
27
39
 
28
40
  function readJsonOrNull(filePath) {
29
41
  if (!existsSync(filePath)) {
@@ -139,13 +151,6 @@ function collectCommitSignals(windowDays) {
139
151
  }
140
152
 
141
153
  async function collectSkillTrustSignals() {
142
- const skillDirectoryPath = join(REPOSITORY_ROOT, '.agent-context', 'skills');
143
- const skillDirectoryEntries = await fs.readdir(skillDirectoryPath, { withFileTypes: true });
144
- const skillDomainNames = skillDirectoryEntries
145
- .filter((directoryEntry) => directoryEntry.isDirectory())
146
- .map((directoryEntry) => directoryEntry.name)
147
- .sort((leftDomainName, rightDomainName) => leftDomainName.localeCompare(rightDomainName));
148
-
149
154
  const trustRows = [];
150
155
  const tierCounts = {
151
156
  verified: 0,
@@ -153,17 +158,26 @@ async function collectSkillTrustSignals() {
153
158
  experimental: 0,
154
159
  };
155
160
 
156
- for (const skillDomainName of skillDomainNames) {
157
- const trustResult = await calculateTrustScore(join(skillDirectoryPath, skillDomainName));
161
+ const sortedDomainNames = Array.from(REQUIRED_VERIFIED_DOMAINS).sort((leftName, rightName) => {
162
+ return leftName.localeCompare(rightName);
163
+ });
164
+
165
+ for (const skillDomainName of sortedDomainNames) {
166
+ const relativeSurfacePath = GOVERNANCE_SURFACE_PATHS[skillDomainName];
167
+ const absoluteSurfacePath = join(REPOSITORY_ROOT, relativeSurfacePath);
168
+ const surfaceExists = existsSync(absoluteSurfacePath);
169
+ const trustTier = surfaceExists ? 'verified' : 'experimental';
170
+ const trustScore = surfaceExists ? 100 : 0;
158
171
 
159
- if (typeof tierCounts[trustResult.tier] === 'number') {
160
- tierCounts[trustResult.tier] += 1;
172
+ if (typeof tierCounts[trustTier] === 'number') {
173
+ tierCounts[trustTier] += 1;
161
174
  }
162
175
 
163
176
  trustRows.push({
164
177
  domain: skillDomainName,
165
- tier: trustResult.tier,
166
- score: trustResult.score,
178
+ tier: trustTier,
179
+ score: trustScore,
180
+ sourcePath: relativeSurfacePath,
167
181
  });
168
182
  }
169
183
 
@@ -53,9 +53,6 @@ const IS_DRY_RUN = process.argv.includes('--dry-run');
53
53
  const SHOULD_EMIT_MACHINE_REPORT = process.env.LLM_JUDGE_EMIT_JSON !== 'false';
54
54
  const MACHINE_REPORT_PATH = process.env.LLM_JUDGE_OUTPUT_PATH || DEFAULT_MACHINE_REPORT_PATH;
55
55
 
56
- /** @type {string[]} Source code file extensions to include in the diff */
57
- const SOURCE_CODE_EXTENSIONS = ['*.ts', '*.tsx', '*.js', '*.mjs', '*.cjs', '*.py', '*.go', '*.java', '*.cs', '*.rb', '*.php'];
58
-
59
56
  /** @type {Record<string, string>} */
60
57
  const SEVERITY_NORMALIZATION_TABLE = {
61
58
  critical: 'critical',
@@ -184,12 +181,12 @@ function collectPullRequestDiff() {
184
181
  console.log(' Source: local HEAD~1..HEAD fallback');
185
182
  try {
186
183
  return execSync('git diff HEAD~1 HEAD', execOptions);
187
- } catch (err) {
184
+ } catch {
188
185
  try {
189
186
  // Initial commit has no parent — diff against empty tree
190
187
  const emptyTreeSha = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
191
188
  return execSync(`git diff "${emptyTreeSha}" HEAD`, execOptions);
192
- } catch (e2) {
189
+ } catch {
193
190
  console.warn(' ⚠️ Unable to execute git diff. Defaulting to empty diff.');
194
191
  return '';
195
192
  }
@@ -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:')) {