@ryuenn3123/agentic-senior-core 3.0.5 → 3.0.7

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/lib/cli/utils.mjs CHANGED
@@ -112,6 +112,66 @@ export async function copyDirectory(sourceDirectoryPath, targetDirectoryPath) {
112
112
  }
113
113
  }
114
114
 
115
+ /**
116
+ * Synchronizes a single file between source and target, returning the operation status.
117
+ */
118
+ export async function syncFile(sourcePath, targetPath) {
119
+ if (!(await pathExists(sourcePath))) return { status: 'skipped' };
120
+
121
+ if (!(await pathExists(targetPath))) {
122
+ await ensureDirectory(path.dirname(targetPath));
123
+ await fs.copyFile(sourcePath, targetPath);
124
+ return { status: 'created' };
125
+ }
126
+
127
+ const sourceContent = await fs.readFile(sourcePath);
128
+ const targetContent = await fs.readFile(targetPath);
129
+
130
+ if (sourceContent.equals(targetContent)) {
131
+ return { status: 'unchanged' };
132
+ }
133
+
134
+ await fs.copyFile(sourcePath, targetPath);
135
+ return { status: 'updated' };
136
+ }
137
+
138
+ /**
139
+ * Intelligent MCP configuration synchronization.
140
+ * Merges the agentic-senior-core server into existing config or creates new.
141
+ */
142
+ async function syncMcpConfig(mcpJsonPath, templateConfig) {
143
+ const topKey = Object.keys(templateConfig)[0]; // e.g. "mcpServers" or "servers"
144
+ const serverName = 'agentic-senior-core';
145
+ const templateServer = templateConfig[topKey][serverName];
146
+
147
+ if (!(await pathExists(mcpJsonPath))) {
148
+ await ensureDirectory(path.dirname(mcpJsonPath));
149
+ await fs.writeFile(mcpJsonPath, JSON.stringify(templateConfig, null, 2) + '\n', 'utf8');
150
+ return { status: 'created' };
151
+ }
152
+
153
+ try {
154
+ const existingContent = await fs.readFile(mcpJsonPath, 'utf8');
155
+ const config = JSON.parse(existingContent);
156
+
157
+ if (!config[topKey]) config[topKey] = {};
158
+
159
+ const existingServer = config[topKey][serverName];
160
+
161
+ if (JSON.stringify(existingServer) === JSON.stringify(templateServer)) {
162
+ return { status: 'unchanged' };
163
+ }
164
+
165
+ config[topKey][serverName] = templateServer;
166
+ await fs.writeFile(mcpJsonPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
167
+ return { status: 'updated' };
168
+ } catch {
169
+ // If JSON is broken, overwrite it as a last resort to recovery
170
+ await fs.writeFile(mcpJsonPath, JSON.stringify(templateConfig, null, 2) + '\n', 'utf8');
171
+ return { status: 'updated' };
172
+ }
173
+ }
174
+
115
175
  function toPosixRelativePath(relativePath) {
116
176
  return relativePath.split(path.sep).join('/');
117
177
  }
@@ -281,9 +341,7 @@ export async function analyzeManagedGovernanceSurface(
281
341
  managedTargetFileCount: targetManifest.files.size,
282
342
  managedTargetDirectoryCount: targetManifest.directories.size,
283
343
  };
284
- }
285
-
286
- export async function copyGovernanceAssetsToTarget(
344
+ }export async function copyGovernanceAssetsToTarget(
287
345
  resolvedTargetDirectoryPath,
288
346
  options = {}
289
347
  ) {
@@ -294,6 +352,9 @@ export async function copyGovernanceAssetsToTarget(
294
352
  : null;
295
353
  const deletedManagedFiles = [];
296
354
  const deletedManagedDirectories = [];
355
+ const createdFiles = [];
356
+ const updatedFiles = [];
357
+ const unchangedFiles = [];
297
358
 
298
359
  for (const sourceDirectoryName of directoryCopies) {
299
360
  const sourceDirectoryPath = path.join(REPO_ROOT, sourceDirectoryName);
@@ -301,7 +362,16 @@ export async function copyGovernanceAssetsToTarget(
301
362
  continue;
302
363
  }
303
364
 
304
- await copyDirectory(sourceDirectoryPath, path.join(resolvedTargetDirectoryPath, sourceDirectoryName));
365
+ const sourceTree = await collectRelativeTreeEntries(sourceDirectoryPath, sourceDirectoryName);
366
+ for (const relativeFilePath of sourceTree.files) {
367
+ const sourcePath = path.join(REPO_ROOT, ...relativeFilePath.split('/'));
368
+ const targetPath = path.join(resolvedTargetDirectoryPath, ...relativeFilePath.split('/'));
369
+ const syncResult = await syncFile(sourcePath, targetPath);
370
+
371
+ if (syncResult.status === 'created') createdFiles.push(relativeFilePath);
372
+ else if (syncResult.status === 'updated') updatedFiles.push(relativeFilePath);
373
+ else if (syncResult.status === 'unchanged') unchangedFiles.push(relativeFilePath);
374
+ }
305
375
  }
306
376
 
307
377
  for (const entryPointFileName of entryPointFiles) {
@@ -316,8 +386,10 @@ export async function copyGovernanceAssetsToTarget(
316
386
  continue;
317
387
  }
318
388
 
319
- await ensureDirectory(path.dirname(targetFilePath));
320
- await fs.copyFile(sourceFilePath, targetFilePath);
389
+ const syncResult = await syncFile(sourceFilePath, targetFilePath);
390
+ if (syncResult.status === 'created') createdFiles.push(entryPointFileName);
391
+ else if (syncResult.status === 'updated') updatedFiles.push(entryPointFileName);
392
+ else if (syncResult.status === 'unchanged') unchangedFiles.push(entryPointFileName);
321
393
  }
322
394
 
323
395
  if (shouldPruneManagedSurface && managedSurfacePlan) {
@@ -346,16 +418,14 @@ export async function copyGovernanceAssetsToTarget(
346
418
  const projectName = path.basename(resolvedTargetDirectoryPath);
347
419
  const mcpArgs = ['./scripts/mcp-server.mjs'];
348
420
 
349
- // Ensure the MCP server entrypoint exists in the target project.
350
- // The workspace MCP configs point at ./scripts/mcp-server.mjs, so we must copy it.
351
421
  const sourceMcpServerPath = path.join(REPO_ROOT, 'scripts', 'mcp-server.mjs');
352
422
  const targetMcpServerPath = path.join(resolvedTargetDirectoryPath, 'scripts', 'mcp-server.mjs');
353
- await ensureDirectory(path.dirname(targetMcpServerPath));
354
- await fs.copyFile(sourceMcpServerPath, targetMcpServerPath);
423
+ const mcpServerSync = await syncFile(sourceMcpServerPath, targetMcpServerPath);
424
+ if (mcpServerSync.status === 'created') createdFiles.push('scripts/mcp-server.mjs');
425
+ else if (mcpServerSync.status === 'updated') updatedFiles.push('scripts/mcp-server.mjs');
355
426
 
356
427
  // 1. VS Code (Workspace Local Settings)
357
- const vscodeDirPath = path.join(resolvedTargetDirectoryPath, '.vscode');
358
- const vscodeMcpJsonPath = path.join(vscodeDirPath, 'mcp.json');
428
+ const vscodeMcpJsonPath = path.join(resolvedTargetDirectoryPath, '.vscode', 'mcp.json');
359
429
  const vscodeWorkspaceMcpConfig = {
360
430
  servers: {
361
431
  'agentic-senior-core': {
@@ -366,15 +436,12 @@ export async function copyGovernanceAssetsToTarget(
366
436
  },
367
437
  },
368
438
  };
369
-
370
- if (!(await pathExists(vscodeMcpJsonPath))) {
371
- await ensureDirectory(vscodeDirPath);
372
- await fs.writeFile(vscodeMcpJsonPath, JSON.stringify(vscodeWorkspaceMcpConfig, null, 2) + '\n', 'utf8');
373
- }
439
+ const vscodeSync = await syncMcpConfig(vscodeMcpJsonPath, vscodeWorkspaceMcpConfig);
440
+ if (vscodeSync.status === 'created') createdFiles.push('.vscode/mcp.json');
441
+ else if (vscodeSync.status === 'updated') updatedFiles.push('.vscode/mcp.json');
374
442
 
375
443
  // 2. Cursor (Workspace Local Settings)
376
- const cursorDirPath = path.join(resolvedTargetDirectoryPath, '.cursor');
377
- const cursorMcpJsonPath = path.join(cursorDirPath, 'mcp.json');
444
+ const cursorMcpJsonPath = path.join(resolvedTargetDirectoryPath, '.cursor', 'mcp.json');
378
445
  const cursorWorkspaceMcpConfig = {
379
446
  mcpServers: {
380
447
  'agentic-senior-core': {
@@ -384,15 +451,12 @@ export async function copyGovernanceAssetsToTarget(
384
451
  },
385
452
  },
386
453
  };
387
-
388
- if (!(await pathExists(cursorMcpJsonPath))) {
389
- await ensureDirectory(cursorDirPath);
390
- await fs.writeFile(cursorMcpJsonPath, JSON.stringify(cursorWorkspaceMcpConfig, null, 2) + '\n', 'utf8');
391
- }
454
+ const cursorSync = await syncMcpConfig(cursorMcpJsonPath, cursorWorkspaceMcpConfig);
455
+ if (cursorSync.status === 'created') createdFiles.push('.cursor/mcp.json');
456
+ else if (cursorSync.status === 'updated') updatedFiles.push('.cursor/mcp.json');
392
457
 
393
458
  // 3. Zed IDE (Workspace Local Settings)
394
- const zedDirPath = path.join(resolvedTargetDirectoryPath, '.zed');
395
- const zedSettingsPath = path.join(zedDirPath, 'settings.json');
459
+ const zedSettingsPath = path.join(resolvedTargetDirectoryPath, '.zed', 'settings.json');
396
460
  const zedMcpConfig = {
397
461
  context_servers: {
398
462
  'agentic-senior-core': {
@@ -404,22 +468,22 @@ export async function copyGovernanceAssetsToTarget(
404
468
  };
405
469
 
406
470
  if (!(await pathExists(zedSettingsPath))) {
407
- await ensureDirectory(zedDirPath);
471
+ await ensureDirectory(path.dirname(zedSettingsPath));
408
472
  await fs.writeFile(zedSettingsPath, JSON.stringify(zedMcpConfig, null, 2) + '\n', 'utf8');
473
+ createdFiles.push('.zed/settings.json');
409
474
  } else {
410
475
  try {
411
476
  const existingZedContent = await fs.readFile(zedSettingsPath, 'utf8');
412
477
  const parsedZedSettings = JSON.parse(existingZedContent);
413
- if (!parsedZedSettings.context_servers) {
414
- parsedZedSettings.context_servers = {};
415
- }
416
- if (!parsedZedSettings.context_servers['agentic-senior-core']) {
417
- parsedZedSettings.context_servers['agentic-senior-core'] = zedMcpConfig.context_servers['agentic-senior-core'];
478
+ if (!parsedZedSettings.context_servers) parsedZedSettings.context_servers = {};
479
+
480
+ const templateServer = zedMcpConfig.context_servers['agentic-senior-core'];
481
+ if (JSON.stringify(parsedZedSettings.context_servers['agentic-senior-core']) !== JSON.stringify(templateServer)) {
482
+ parsedZedSettings.context_servers['agentic-senior-core'] = templateServer;
418
483
  await fs.writeFile(zedSettingsPath, JSON.stringify(parsedZedSettings, null, 2) + '\n', 'utf8');
484
+ updatedFiles.push('.zed/settings.json');
419
485
  }
420
- } catch {
421
- // Fallback or ignore if user has broken JSON
422
- }
486
+ } catch { /* Ignore malformed Zed JSON */ }
423
487
  }
424
488
 
425
489
  // 4. Antigravity / Gemini (Global Settings)
@@ -441,28 +505,29 @@ export async function copyGovernanceAssetsToTarget(
441
505
 
442
506
  const safeProjectName = projectName.replace(/[^a-zA-Z0-9_-]/g, '-');
443
507
  const uniqueServerName = `agentic-senior-core-${safeProjectName}`;
444
-
445
- // For global configs, we use absolute path for cwd
446
- geminiConfig.mcpServers[uniqueServerName] = {
508
+ const templateServer = {
447
509
  command: 'node',
448
510
  args: mcpArgs,
449
511
  cwd: resolvedTargetDirectoryPath,
450
512
  };
451
513
 
452
- await fs.writeFile(globalGeminiMcpPath, JSON.stringify(geminiConfig, null, 2) + '\n', 'utf8');
514
+ if (JSON.stringify(geminiConfig.mcpServers[uniqueServerName]) !== JSON.stringify(templateServer)) {
515
+ geminiConfig.mcpServers[uniqueServerName] = templateServer;
516
+ await fs.writeFile(globalGeminiMcpPath, JSON.stringify(geminiConfig, null, 2) + '\n', 'utf8');
517
+ }
453
518
  }
454
- } catch {
455
- // Ignore global injection errors
456
- }
519
+ } catch { /* Ignore global injection errors */ }
457
520
  }
458
521
  }
459
522
 
460
523
  return {
461
524
  deletedManagedFiles,
462
525
  deletedManagedDirectories,
526
+ createdFiles,
527
+ updatedFiles,
528
+ unchangedFiles,
463
529
  managedSurfacePlan,
464
530
  };
465
-
466
531
  }
467
532
 
468
533
  export async function askChoice(promptMessage, options, userInterface) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuenn3123/agentic-senior-core",
3
- "version": "3.0.5",
3
+ "version": "3.0.7",
4
4
  "type": "module",
5
5
  "description": "Force your AI Agent to code like a Staff Engineer, not a Junior.",
6
6
  "bin": {
@@ -211,19 +211,19 @@ const REQUIRED_UNIVERSAL_SOP_SNIPPETS = [
211
211
  snippets: [
212
212
  '### 15. Universal SOP Consolidation',
213
213
  'Coding flow is blocked if `docs/architecture-decision-record.md` (or `docs/Architecture-Decision-Record.md`) is missing',
214
- 'UI implementation flow is blocked if `docs/DESIGN.md` is missing',
214
+ 'UI implementation flow is blocked if `docs/DESIGN.md` or `docs/design-intent.json` is missing',
215
215
  ],
216
216
  },
217
217
  {
218
218
  path: '.agent-context/prompts/review-code.md',
219
219
  snippets: [
220
- 'Enforce Universal SOP hard gate: block coding flow when required project docs are missing (`docs/architecture-decision-record.md`, and for UI scope `docs/DESIGN.md`).',
220
+ 'Enforce Universal SOP hard gate: block coding flow when required project docs are missing (`docs/architecture-decision-record.md`, and for UI scope `docs/DESIGN.md` plus `docs/design-intent.json`).',
221
221
  ],
222
222
  },
223
223
  {
224
224
  path: '.agent-context/prompts/refactor.md',
225
225
  snippets: [
226
- '6. Enforce Universal SOP hard gate: stop implementation if `docs/architecture-decision-record.md` is missing, and for UI scope stop if `docs/DESIGN.md` is missing.',
226
+ '6. Enforce Universal SOP hard gate: stop implementation if `docs/architecture-decision-record.md` is missing, and for UI scope stop if `docs/DESIGN.md` or `docs/design-intent.json` is missing.',
227
227
  ],
228
228
  },
229
229
  {
@@ -231,7 +231,7 @@ const REQUIRED_UNIVERSAL_SOP_SNIPPETS = [
231
231
  snippets: [
232
232
  'Universal SOP hard block policy:',
233
233
  'Hard block: do not write application code until docs/project-brief.md and docs/architecture-decision-record.md exist.',
234
- 'For UI scope: if docs/DESIGN.md is missing, execute bootstrap-design prompt before implementing UI surfaces.',
234
+ 'For UI scope: if docs/DESIGN.md or docs/design-intent.json is missing, execute bootstrap-design prompt before implementing UI surfaces.',
235
235
  ],
236
236
  },
237
237
  ];
@@ -250,10 +250,22 @@ const REQUIRED_TEMPLATE_FREE_BOOTSTRAP_SNIPPETS = [
250
250
  snippets: [
251
251
  'Project docs will be authored dynamically by your IDE assistant from these prompts.',
252
252
  'bootstrap-project-context.md',
253
+ 'Seed docs:',
253
254
  'I prepared dynamic synthesis bootstrap prompts',
254
255
  ],
255
256
  },
256
257
  ];
258
+ const REQUIRED_UPGRADE_UI_CONTRACT_WARNING_SNIPPETS = [
259
+ {
260
+ path: 'lib/cli/commands/upgrade.mjs',
261
+ snippets: [
262
+ 'UI/frontend scope was detected, but the dynamic design contract is incomplete:',
263
+ 'docs/design-intent.json',
264
+ 'Upgrade synchronizes governance assets, but it does not author project-specific design docs automatically.',
265
+ 'collectProjectMarkers',
266
+ ],
267
+ },
268
+ ];
257
269
  const FORBIDDEN_TEMPLATE_BOOTSTRAP_SNIPPETS = [
258
270
  {
259
271
  path: 'lib/cli/project-scaffolder.mjs',
@@ -1021,6 +1033,28 @@ async function validateTemplateFreeBootstrapCoverage() {
1021
1033
  }
1022
1034
  }
1023
1035
 
1036
+ async function validateUpgradeUiContractWarningCoverage() {
1037
+ console.log('\nChecking upgrade UI contract warning coverage...');
1038
+
1039
+ for (const coverageRule of REQUIRED_UPGRADE_UI_CONTRACT_WARNING_SNIPPETS) {
1040
+ const absoluteCoveragePath = join(ROOT_DIR, coverageRule.path);
1041
+
1042
+ if (!(await fileExists(absoluteCoveragePath))) {
1043
+ fail(`Missing upgrade UI contract warning source: ${coverageRule.path}`);
1044
+ continue;
1045
+ }
1046
+
1047
+ const coverageContent = await readTextFile(absoluteCoveragePath);
1048
+ for (const requiredSnippet of coverageRule.snippets) {
1049
+ if (coverageContent.includes(requiredSnippet)) {
1050
+ pass(`${coverageRule.path} includes upgrade UI contract warning snippet: ${requiredSnippet}`);
1051
+ } else {
1052
+ fail(`${coverageRule.path} is missing upgrade UI contract warning snippet: ${requiredSnippet}`);
1053
+ }
1054
+ }
1055
+ }
1056
+ }
1057
+
1024
1058
  async function validateDeterministicBoundaryEnforcementCoverage() {
1025
1059
  console.log('\nChecking deterministic boundary enforcement coverage...');
1026
1060
 
@@ -1289,6 +1323,7 @@ async function main() {
1289
1323
  await validateStackResearchEngineCoverage();
1290
1324
  await validateUniversalSopConsolidationCoverage();
1291
1325
  await validateTemplateFreeBootstrapCoverage();
1326
+ await validateUpgradeUiContractWarningCoverage();
1292
1327
  await validateDeterministicBoundaryEnforcementCoverage();
1293
1328
  await validateStackResearchSnapshotState();
1294
1329
  await validateMcpConfiguration();