@ryuenn3123/agentic-senior-core 2.5.19 → 2.5.20

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-04-18T02:58:09.480Z",
2
+ "generatedAt": "2026-04-18T04:27:02.459Z",
3
3
  "reportName": "memory-continuity-benchmark",
4
4
  "schemaVersion": "1.0.0",
5
5
  "passed": true,
@@ -0,0 +1,112 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "snapshotId": "ecosystem-signals-2026-04-18",
4
+ "generatedAt": "2026-04-18T00:00:00.000Z",
5
+ "deterministic": true,
6
+ "sourceName": "Agentic-Senior-Core deterministic stack research snapshot",
7
+ "sourceUrl": "state://stack-research-snapshot/2026-04-18",
8
+ "trustedRealtimeSources": [
9
+ {
10
+ "sourceId": "awwwards-trend-feed",
11
+ "sourceName": "Awwwards Trend Feed",
12
+ "sourceUrl": "https://www.awwwards.com"
13
+ },
14
+ {
15
+ "sourceId": "github-ecosystem-signals",
16
+ "sourceName": "GitHub Ecosystem Signals",
17
+ "sourceUrl": "https://github.com"
18
+ }
19
+ ],
20
+ "stackSignals": [
21
+ {
22
+ "stackFileName": "typescript.md",
23
+ "measuredAt": "2026-04-18T00:00:00.000Z",
24
+ "metrics": {
25
+ "ecosystemMaturity": 0.91,
26
+ "talentAvailability": 0.9,
27
+ "deliveryVelocity": 0.89
28
+ }
29
+ },
30
+ {
31
+ "stackFileName": "python.md",
32
+ "measuredAt": "2026-04-18T00:00:00.000Z",
33
+ "metrics": {
34
+ "ecosystemMaturity": 0.92,
35
+ "talentAvailability": 0.88,
36
+ "deliveryVelocity": 0.9
37
+ }
38
+ },
39
+ {
40
+ "stackFileName": "java.md",
41
+ "measuredAt": "2026-04-18T00:00:00.000Z",
42
+ "metrics": {
43
+ "ecosystemMaturity": 0.89,
44
+ "talentAvailability": 0.83,
45
+ "deliveryVelocity": 0.8
46
+ }
47
+ },
48
+ {
49
+ "stackFileName": "php.md",
50
+ "measuredAt": "2026-04-18T00:00:00.000Z",
51
+ "metrics": {
52
+ "ecosystemMaturity": 0.79,
53
+ "talentAvailability": 0.75,
54
+ "deliveryVelocity": 0.84
55
+ }
56
+ },
57
+ {
58
+ "stackFileName": "go.md",
59
+ "measuredAt": "2026-04-18T00:00:00.000Z",
60
+ "metrics": {
61
+ "ecosystemMaturity": 0.84,
62
+ "talentAvailability": 0.78,
63
+ "deliveryVelocity": 0.82
64
+ }
65
+ },
66
+ {
67
+ "stackFileName": "csharp.md",
68
+ "measuredAt": "2026-04-18T00:00:00.000Z",
69
+ "metrics": {
70
+ "ecosystemMaturity": 0.86,
71
+ "talentAvailability": 0.8,
72
+ "deliveryVelocity": 0.79
73
+ }
74
+ },
75
+ {
76
+ "stackFileName": "rust.md",
77
+ "measuredAt": "2026-04-18T00:00:00.000Z",
78
+ "metrics": {
79
+ "ecosystemMaturity": 0.74,
80
+ "talentAvailability": 0.63,
81
+ "deliveryVelocity": 0.67
82
+ }
83
+ },
84
+ {
85
+ "stackFileName": "ruby.md",
86
+ "measuredAt": "2026-04-18T00:00:00.000Z",
87
+ "metrics": {
88
+ "ecosystemMaturity": 0.7,
89
+ "talentAvailability": 0.62,
90
+ "deliveryVelocity": 0.72
91
+ }
92
+ },
93
+ {
94
+ "stackFileName": "react-native.md",
95
+ "measuredAt": "2026-04-18T00:00:00.000Z",
96
+ "metrics": {
97
+ "ecosystemMaturity": 0.72,
98
+ "talentAvailability": 0.67,
99
+ "deliveryVelocity": 0.74
100
+ }
101
+ },
102
+ {
103
+ "stackFileName": "flutter.md",
104
+ "measuredAt": "2026-04-18T00:00:00.000Z",
105
+ "metrics": {
106
+ "ecosystemMaturity": 0.75,
107
+ "talentAvailability": 0.69,
108
+ "deliveryVelocity": 0.76
109
+ }
110
+ }
111
+ ]
112
+ }
package/.cursorrules CHANGED
@@ -1,6 +1,6 @@
1
1
  # AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
2
2
 
3
- Generated by Agentic-Senior-Core CLI v2.5.19
3
+ Generated by Agentic-Senior-Core CLI v2.5.20
4
4
  Timestamp: 2026-04-18T03:00:00.000Z
5
5
  Selected profile: beginner
6
6
  Selected policy file: .agent-context/policies/llm-judge-threshold.json
package/.windsurfrules CHANGED
@@ -1,6 +1,6 @@
1
1
  # AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
2
2
 
3
- Generated by Agentic-Senior-Core CLI v2.5.19
3
+ Generated by Agentic-Senior-Core CLI v2.5.20
4
4
  Timestamp: 2026-04-18T03:00:00.000Z
5
5
  Selected profile: beginner
6
6
  Selected policy file: .agent-context/policies/llm-judge-threshold.json
package/README.md CHANGED
@@ -36,6 +36,18 @@ Project-description-first path (AI as Architect with veto control):
36
36
  npx @ryuenn3123/agentic-senior-core init --project-description "Machine learning API for fraud detection"
37
37
  ```
38
38
 
39
+ Deterministic stack research mode (default):
40
+
41
+ ```bash
42
+ npx @ryuenn3123/agentic-senior-core init --project-description "Event-driven payments platform" --architect-research-mode snapshot
43
+ ```
44
+
45
+ Optional trusted realtime enrichment (explicitly gated):
46
+
47
+ ```bash
48
+ npx @ryuenn3123/agentic-senior-core init --project-description "Modern conversion-focused product website" --architect-research-mode realtime --enable-realtime-research --architect-realtime-signal-file ./realtime-signals.json
49
+ ```
50
+
39
51
  ---
40
52
 
41
53
  ## Before / After
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs/promises';
2
+ import { existsSync, readFileSync } from 'node:fs';
2
3
  import os from 'node:os';
3
4
  import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
4
6
 
5
7
  import { BLUEPRINT_RECOMMENDATIONS } from './constants.mjs';
6
8
  import { ensureDirectory, pathExists, toTitleCase } from './utils.mjs';
@@ -11,6 +13,98 @@ export const ARCHITECT_MIN_TOKEN_BUDGET = 200;
11
13
  export const ARCHITECT_MAX_TOKEN_BUDGET = 4000;
12
14
  export const ARCHITECT_MIN_TIMEOUT_MS = 200;
13
15
  export const ARCHITECT_MAX_TIMEOUT_MS = 10000;
16
+ export const ARCHITECT_DEFAULT_RESEARCH_MODE = 'snapshot';
17
+
18
+ const ARCHITECT_ALLOWED_RESEARCH_MODES = new Set(['snapshot', 'realtime']);
19
+ const ARCHITECT_DEFAULT_REALTIME_SIGNAL_ENV_KEY = 'AGENTIC_ARCHITECT_REALTIME_SIGNAL_JSON';
20
+ const ARCHITECT_DEFAULT_REALTIME_SIGNAL_PATH_ENV_KEY = 'AGENTIC_ARCHITECT_REALTIME_SIGNAL_PATH';
21
+ const ARCHITECT_ALLOWED_PALETTE_ROLES = ['base', 'surface', 'accent', 'success', 'danger'];
22
+ const ARCHITECT_ALLOWED_TYPOGRAPHY_SCALES = ['compact', 'balanced', 'expressive'];
23
+ const ARCHITECT_ALLOWED_SPACING_PATTERNS = ['compact-grid', 'balanced-grid', 'airy-grid'];
24
+ const ARCHITECT_ALLOWED_MOTION_CHARACTERISTICS = [
25
+ 'subtle-enter',
26
+ 'staggered-reveal',
27
+ 'state-feedback',
28
+ 'conversion-focus',
29
+ 'calm-transition',
30
+ ];
31
+
32
+ const ARCHITECT_MODULE_FILE_PATH = fileURLToPath(import.meta.url);
33
+ const ARCHITECT_PACKAGE_ROOT = path.resolve(path.dirname(ARCHITECT_MODULE_FILE_PATH), '..', '..');
34
+ const ARCHITECT_RESEARCH_SNAPSHOT_FILE_PATH = path.join(
35
+ ARCHITECT_PACKAGE_ROOT,
36
+ '.agent-context',
37
+ 'state',
38
+ 'stack-research-snapshot.json'
39
+ );
40
+
41
+ const FALLBACK_STACK_RESEARCH_SNAPSHOT = {
42
+ schemaVersion: '1.0.0',
43
+ snapshotId: 'ecosystem-signals-fallback',
44
+ generatedAt: '2026-04-18T00:00:00.000Z',
45
+ deterministic: true,
46
+ sourceName: 'Agentic-Senior-Core internal fallback snapshot',
47
+ sourceUrl: 'state://stack-research-snapshot/fallback',
48
+ trustedRealtimeSources: [
49
+ {
50
+ sourceId: 'awwwards-trend-feed',
51
+ sourceName: 'Awwwards Trend Feed',
52
+ sourceUrl: 'https://www.awwwards.com',
53
+ },
54
+ ],
55
+ stackSignals: [
56
+ {
57
+ stackFileName: 'typescript.md',
58
+ measuredAt: '2026-04-18T00:00:00.000Z',
59
+ metrics: { ecosystemMaturity: 0.91, talentAvailability: 0.9, deliveryVelocity: 0.89 },
60
+ },
61
+ {
62
+ stackFileName: 'python.md',
63
+ measuredAt: '2026-04-18T00:00:00.000Z',
64
+ metrics: { ecosystemMaturity: 0.92, talentAvailability: 0.88, deliveryVelocity: 0.9 },
65
+ },
66
+ {
67
+ stackFileName: 'java.md',
68
+ measuredAt: '2026-04-18T00:00:00.000Z',
69
+ metrics: { ecosystemMaturity: 0.89, talentAvailability: 0.83, deliveryVelocity: 0.8 },
70
+ },
71
+ {
72
+ stackFileName: 'php.md',
73
+ measuredAt: '2026-04-18T00:00:00.000Z',
74
+ metrics: { ecosystemMaturity: 0.79, talentAvailability: 0.75, deliveryVelocity: 0.84 },
75
+ },
76
+ {
77
+ stackFileName: 'go.md',
78
+ measuredAt: '2026-04-18T00:00:00.000Z',
79
+ metrics: { ecosystemMaturity: 0.84, talentAvailability: 0.78, deliveryVelocity: 0.82 },
80
+ },
81
+ {
82
+ stackFileName: 'csharp.md',
83
+ measuredAt: '2026-04-18T00:00:00.000Z',
84
+ metrics: { ecosystemMaturity: 0.86, talentAvailability: 0.8, deliveryVelocity: 0.79 },
85
+ },
86
+ {
87
+ stackFileName: 'rust.md',
88
+ measuredAt: '2026-04-18T00:00:00.000Z',
89
+ metrics: { ecosystemMaturity: 0.74, talentAvailability: 0.63, deliveryVelocity: 0.67 },
90
+ },
91
+ {
92
+ stackFileName: 'ruby.md',
93
+ measuredAt: '2026-04-18T00:00:00.000Z',
94
+ metrics: { ecosystemMaturity: 0.7, talentAvailability: 0.62, deliveryVelocity: 0.72 },
95
+ },
96
+ {
97
+ stackFileName: 'react-native.md',
98
+ measuredAt: '2026-04-18T00:00:00.000Z',
99
+ metrics: { ecosystemMaturity: 0.72, talentAvailability: 0.67, deliveryVelocity: 0.74 },
100
+ },
101
+ {
102
+ stackFileName: 'flutter.md',
103
+ measuredAt: '2026-04-18T00:00:00.000Z',
104
+ metrics: { ecosystemMaturity: 0.75, talentAvailability: 0.69, deliveryVelocity: 0.76 },
105
+ },
106
+ ],
107
+ };
14
108
 
15
109
  const ARCHITECT_PREFERENCE_FILE_PATH = process.env.AGENTIC_ARCHITECT_PREF_FILE
16
110
  ? path.resolve(process.env.AGENTIC_ARCHITECT_PREF_FILE)
@@ -140,6 +234,283 @@ function resolveRecommendedBlueprintFileName(stackFileName, blueprintFileNames)
140
234
  return blueprintFileNames[0] || null;
141
235
  }
142
236
 
237
+ function normalizeArchitectResearchMode(rawMode) {
238
+ const normalizedMode = String(rawMode || ARCHITECT_DEFAULT_RESEARCH_MODE).trim().toLowerCase();
239
+ if (ARCHITECT_ALLOWED_RESEARCH_MODES.has(normalizedMode)) {
240
+ return normalizedMode;
241
+ }
242
+
243
+ return ARCHITECT_DEFAULT_RESEARCH_MODE;
244
+ }
245
+
246
+ function isValidIsoTimestamp(value) {
247
+ if (typeof value !== 'string' || value.trim().length === 0) {
248
+ return false;
249
+ }
250
+
251
+ return !Number.isNaN(Date.parse(value));
252
+ }
253
+
254
+ function toIsoTimestamp(value, fallbackValue = new Date().toISOString()) {
255
+ return isValidIsoTimestamp(value) ? new Date(value).toISOString() : fallbackValue;
256
+ }
257
+
258
+ function loadStackResearchSnapshot() {
259
+ try {
260
+ if (!existsSync(ARCHITECT_RESEARCH_SNAPSHOT_FILE_PATH)) {
261
+ return {
262
+ snapshot: FALLBACK_STACK_RESEARCH_SNAPSHOT,
263
+ sourcePath: 'fallback://embedded-snapshot',
264
+ };
265
+ }
266
+
267
+ const snapshotContent = readFileSync(ARCHITECT_RESEARCH_SNAPSHOT_FILE_PATH, 'utf8');
268
+ const parsedSnapshot = JSON.parse(snapshotContent);
269
+
270
+ if (!Array.isArray(parsedSnapshot?.stackSignals) || parsedSnapshot.stackSignals.length === 0) {
271
+ return {
272
+ snapshot: FALLBACK_STACK_RESEARCH_SNAPSHOT,
273
+ sourcePath: 'fallback://embedded-snapshot',
274
+ };
275
+ }
276
+
277
+ return {
278
+ snapshot: parsedSnapshot,
279
+ sourcePath: ARCHITECT_RESEARCH_SNAPSHOT_FILE_PATH,
280
+ };
281
+ } catch {
282
+ return {
283
+ snapshot: FALLBACK_STACK_RESEARCH_SNAPSHOT,
284
+ sourcePath: 'fallback://embedded-snapshot',
285
+ };
286
+ }
287
+ }
288
+
289
+ function normalizeMetricValue(value, fallbackValue = 0) {
290
+ const parsedValue = Number(value);
291
+ if (!Number.isFinite(parsedValue)) {
292
+ return fallbackValue;
293
+ }
294
+
295
+ return clampNumericValue(parsedValue, 0, 1);
296
+ }
297
+
298
+ function calculateSnapshotSignalScore(snapshotSignalEntry) {
299
+ const metrics = snapshotSignalEntry?.metrics || {};
300
+ const ecosystemMaturity = normalizeMetricValue(metrics.ecosystemMaturity, 0.65);
301
+ const talentAvailability = normalizeMetricValue(metrics.talentAvailability, 0.65);
302
+ const deliveryVelocity = normalizeMetricValue(metrics.deliveryVelocity, 0.65);
303
+ const aggregateScore = (ecosystemMaturity + talentAvailability + deliveryVelocity) / 3;
304
+
305
+ return {
306
+ aggregateScore,
307
+ metrics: {
308
+ ecosystemMaturity,
309
+ talentAvailability,
310
+ deliveryVelocity,
311
+ },
312
+ };
313
+ }
314
+
315
+ function parseRealtimeSignalPayloadFromJson(rawJsonContent) {
316
+ try {
317
+ const parsedPayload = JSON.parse(rawJsonContent);
318
+ if (!Array.isArray(parsedPayload?.stackSignals)) {
319
+ return null;
320
+ }
321
+
322
+ return parsedPayload;
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
327
+
328
+ function loadRealtimeSignalPayload(realtimeSignalFilePath) {
329
+ const normalizedRealtimeSignalFilePath = String(realtimeSignalFilePath || '').trim();
330
+ if (normalizedRealtimeSignalFilePath) {
331
+ try {
332
+ const absoluteRealtimeSignalPath = path.resolve(normalizedRealtimeSignalFilePath);
333
+ if (existsSync(absoluteRealtimeSignalPath)) {
334
+ const filePayload = parseRealtimeSignalPayloadFromJson(readFileSync(absoluteRealtimeSignalPath, 'utf8'));
335
+ if (filePayload) {
336
+ return {
337
+ payload: filePayload,
338
+ sourcePath: absoluteRealtimeSignalPath,
339
+ };
340
+ }
341
+ }
342
+ } catch {
343
+ // Ignore file loading errors and continue to environment fallback.
344
+ }
345
+ }
346
+
347
+ const signalPathFromEnvironment = String(process.env[ARCHITECT_DEFAULT_REALTIME_SIGNAL_PATH_ENV_KEY] || '').trim();
348
+ if (signalPathFromEnvironment) {
349
+ try {
350
+ const absoluteRealtimeSignalPath = path.resolve(signalPathFromEnvironment);
351
+ if (existsSync(absoluteRealtimeSignalPath)) {
352
+ const filePayload = parseRealtimeSignalPayloadFromJson(readFileSync(absoluteRealtimeSignalPath, 'utf8'));
353
+ if (filePayload) {
354
+ return {
355
+ payload: filePayload,
356
+ sourcePath: absoluteRealtimeSignalPath,
357
+ };
358
+ }
359
+ }
360
+ } catch {
361
+ // Ignore file loading errors and continue to environment JSON fallback.
362
+ }
363
+ }
364
+
365
+ const signalJsonFromEnvironment = String(process.env[ARCHITECT_DEFAULT_REALTIME_SIGNAL_ENV_KEY] || '').trim();
366
+ if (signalJsonFromEnvironment) {
367
+ const payloadFromEnvironment = parseRealtimeSignalPayloadFromJson(signalJsonFromEnvironment);
368
+ if (payloadFromEnvironment) {
369
+ return {
370
+ payload: payloadFromEnvironment,
371
+ sourcePath: `env://${ARCHITECT_DEFAULT_REALTIME_SIGNAL_ENV_KEY}`,
372
+ };
373
+ }
374
+ }
375
+
376
+ return {
377
+ payload: null,
378
+ sourcePath: null,
379
+ };
380
+ }
381
+
382
+ function normalizeStackSignalBoost(value) {
383
+ const normalizedSignal = normalizeMetricValue(value, 0);
384
+ return Number((normalizedSignal * 0.35).toFixed(4));
385
+ }
386
+
387
+ function normalizePaletteRoles(rawPaletteRoles) {
388
+ if (!Array.isArray(rawPaletteRoles) || rawPaletteRoles.length === 0) {
389
+ return ['base', 'surface', 'accent'];
390
+ }
391
+
392
+ const sanitizedRoles = rawPaletteRoles
393
+ .map((paletteRole) => String(paletteRole || '').trim().toLowerCase())
394
+ .filter((paletteRole) => ARCHITECT_ALLOWED_PALETTE_ROLES.includes(paletteRole));
395
+
396
+ if (sanitizedRoles.length === 0) {
397
+ return ['base', 'surface', 'accent'];
398
+ }
399
+
400
+ return Array.from(new Set(sanitizedRoles));
401
+ }
402
+
403
+ function normalizeTypographyScale(rawTypographyScale) {
404
+ const normalizedScale = String(rawTypographyScale || '').trim().toLowerCase();
405
+ if (ARCHITECT_ALLOWED_TYPOGRAPHY_SCALES.includes(normalizedScale)) {
406
+ return normalizedScale;
407
+ }
408
+
409
+ return 'balanced';
410
+ }
411
+
412
+ function normalizeSpacingPattern(rawSpacingPattern) {
413
+ const normalizedPattern = String(rawSpacingPattern || '').trim().toLowerCase();
414
+ if (ARCHITECT_ALLOWED_SPACING_PATTERNS.includes(normalizedPattern)) {
415
+ return normalizedPattern;
416
+ }
417
+
418
+ return 'balanced-grid';
419
+ }
420
+
421
+ function normalizeMotionCharacteristics(rawMotionCharacteristics) {
422
+ if (!Array.isArray(rawMotionCharacteristics) || rawMotionCharacteristics.length === 0) {
423
+ return ['subtle-enter', 'state-feedback'];
424
+ }
425
+
426
+ const sanitizedCharacteristics = rawMotionCharacteristics
427
+ .map((motionCharacteristic) => String(motionCharacteristic || '').trim().toLowerCase())
428
+ .filter((motionCharacteristic) => ARCHITECT_ALLOWED_MOTION_CHARACTERISTICS.includes(motionCharacteristic));
429
+
430
+ if (sanitizedCharacteristics.length === 0) {
431
+ return ['subtle-enter', 'state-feedback'];
432
+ }
433
+
434
+ return Array.from(new Set(sanitizedCharacteristics));
435
+ }
436
+
437
+ function synthesizeDesignSignals({
438
+ normalizedProjectDescription,
439
+ recommendedStackFileName,
440
+ realtimePayload,
441
+ snapshotGeneratedAt,
442
+ }) {
443
+ const descriptionText = String(normalizedProjectDescription || '');
444
+ const looksLikeFinancialSurface = /fraud|bank|payment|regulated|security/.test(descriptionText);
445
+ const looksLikeGrowthSurface = /marketing|landing|conversion|sales|campaign/.test(descriptionText);
446
+ const looksLikeDataProduct = /analytics|dashboard|report|insight|data/.test(descriptionText);
447
+
448
+ let paletteRoles = ['base', 'surface', 'accent'];
449
+ let typographyScale = 'balanced';
450
+ let spacingPattern = 'balanced-grid';
451
+ let motionCharacteristics = ['subtle-enter', 'state-feedback'];
452
+
453
+ if (looksLikeFinancialSurface) {
454
+ paletteRoles = ['base', 'surface', 'accent', 'danger', 'success'];
455
+ motionCharacteristics = ['calm-transition', 'state-feedback'];
456
+ } else if (looksLikeGrowthSurface) {
457
+ paletteRoles = ['base', 'surface', 'accent', 'success'];
458
+ typographyScale = 'expressive';
459
+ motionCharacteristics = ['staggered-reveal', 'conversion-focus', 'state-feedback'];
460
+ }
461
+
462
+ if (looksLikeDataProduct || recommendedStackFileName === 'python.md') {
463
+ spacingPattern = 'compact-grid';
464
+ }
465
+
466
+ const realtimeDesignSignals = realtimePayload?.designSignals || null;
467
+ if (realtimeDesignSignals) {
468
+ paletteRoles = normalizePaletteRoles(realtimeDesignSignals.paletteRoles || paletteRoles);
469
+ typographyScale = normalizeTypographyScale(realtimeDesignSignals.typographyScale || typographyScale);
470
+ spacingPattern = normalizeSpacingPattern(realtimeDesignSignals.spacingPattern || spacingPattern);
471
+ motionCharacteristics = normalizeMotionCharacteristics(
472
+ realtimeDesignSignals.motionCharacteristics || motionCharacteristics
473
+ );
474
+ }
475
+
476
+ return {
477
+ generatedAt: snapshotGeneratedAt,
478
+ normalizedSignals: {
479
+ paletteRoles,
480
+ typographyScale,
481
+ spacingPattern,
482
+ motionCharacteristics,
483
+ },
484
+ sourcePolicy: {
485
+ normalizedSignalsOnly: true,
486
+ copiedExternalProse: false,
487
+ blockedInputPacks: ['DESIGN.md'],
488
+ },
489
+ };
490
+ }
491
+
492
+ function buildEvidenceCitation({
493
+ citationId,
494
+ sourceType,
495
+ sourceName,
496
+ sourceUrl,
497
+ measuredAt,
498
+ stackFileName,
499
+ metrics,
500
+ note,
501
+ }) {
502
+ return {
503
+ citationId,
504
+ sourceType,
505
+ sourceName,
506
+ sourceUrl,
507
+ measuredAt: toIsoTimestamp(measuredAt),
508
+ stackFileName,
509
+ metrics,
510
+ note,
511
+ };
512
+ }
513
+
143
514
  function buildFallbackRecommendation({
144
515
  stackFileNames,
145
516
  blueprintFileNames,
@@ -148,14 +519,45 @@ function buildFallbackRecommendation({
148
519
  usedTokens,
149
520
  elapsedMs,
150
521
  timeoutTriggered,
522
+ requestedResearchMode,
523
+ effectiveResearchMode,
524
+ snapshot,
525
+ snapshotSourcePath,
526
+ realtimeGateEnabled,
527
+ realtimeSignalsLoaded,
528
+ realtimeSignalSourcePath,
529
+ normalizedProjectDescription,
151
530
  }) {
152
531
  const fallbackStackFileName = stackFileNames.includes('typescript.md')
153
532
  ? 'typescript.md'
154
533
  : stackFileNames[0] || 'typescript.md';
155
534
  const fallbackBlueprintFileName = resolveRecommendedBlueprintFileName(fallbackStackFileName, blueprintFileNames);
535
+ const fallbackSnapshotSignal = Array.isArray(snapshot?.stackSignals)
536
+ ? snapshot.stackSignals.find((stackSignal) => stackSignal.stackFileName === fallbackStackFileName) || null
537
+ : null;
538
+ const fallbackSnapshotMetrics = calculateSnapshotSignalScore(fallbackSnapshotSignal);
539
+ const fallbackEvidenceCitations = [
540
+ buildEvidenceCitation({
541
+ citationId: `snapshot:${snapshot?.snapshotId || 'fallback'}:${fallbackStackFileName}`,
542
+ sourceType: 'snapshot',
543
+ sourceName: snapshot?.sourceName || 'Stack research snapshot',
544
+ sourceUrl: snapshot?.sourceUrl || snapshotSourcePath,
545
+ measuredAt: fallbackSnapshotSignal?.measuredAt || snapshot?.generatedAt,
546
+ stackFileName: fallbackStackFileName,
547
+ metrics: fallbackSnapshotMetrics.metrics,
548
+ note: 'Deterministic snapshot baseline used for fallback recommendation.',
549
+ }),
550
+ ];
551
+
552
+ const fallbackDesignGuidance = synthesizeDesignSignals({
553
+ normalizedProjectDescription,
554
+ recommendedStackFileName: fallbackStackFileName,
555
+ realtimePayload: null,
556
+ snapshotGeneratedAt: toIsoTimestamp(snapshot?.generatedAt),
557
+ });
156
558
 
157
559
  return {
158
- projectDescription: '',
560
+ projectDescription: String(normalizedProjectDescription || '').trim(),
159
561
  recommendedStackFileName: fallbackStackFileName,
160
562
  recommendedBlueprintFileName: fallbackBlueprintFileName,
161
563
  confidenceLabel: 'low',
@@ -176,7 +578,25 @@ function buildFallbackRecommendation({
176
578
  lowConfidence: true,
177
579
  dataConflict: false,
178
580
  repeatedOverride: false,
581
+ realtimeGated: requestedResearchMode === 'realtime' && !realtimeGateEnabled,
582
+ realtimeUnavailable: requestedResearchMode === 'realtime' && realtimeGateEnabled && !realtimeSignalsLoaded,
583
+ },
584
+ research: {
585
+ requestedMode: requestedResearchMode,
586
+ effectiveMode: effectiveResearchMode,
587
+ deterministic: effectiveResearchMode === 'snapshot',
588
+ snapshotId: snapshot?.snapshotId || 'fallback',
589
+ snapshotGeneratedAt: toIsoTimestamp(snapshot?.generatedAt),
590
+ snapshotSourcePath,
591
+ realtimeGateEnabled,
592
+ realtimeSignalsLoaded,
593
+ realtimeSignalSourcePath,
594
+ trustedRealtimeSources: Array.isArray(snapshot?.trustedRealtimeSources)
595
+ ? snapshot.trustedRealtimeSources
596
+ : [],
179
597
  },
598
+ evidenceCitations: fallbackEvidenceCitations,
599
+ designGuidance: fallbackDesignGuidance,
180
600
  researchBudget: {
181
601
  tokenBudget,
182
602
  timeoutMs,
@@ -195,16 +615,40 @@ export function recommendArchitecture({
195
615
  blueprintFileNames,
196
616
  tokenBudget = ARCHITECT_DEFAULT_TOKEN_BUDGET,
197
617
  timeoutMs = ARCHITECT_DEFAULT_TIMEOUT_MS,
618
+ researchMode = ARCHITECT_DEFAULT_RESEARCH_MODE,
619
+ enableRealtimeResearch = false,
620
+ realtimeSignalFilePath = null,
198
621
  }) {
199
622
  const startedAt = Date.now();
200
623
  const boundedTokenBudget = clampNumericValue(tokenBudget, ARCHITECT_MIN_TOKEN_BUDGET, ARCHITECT_MAX_TOKEN_BUDGET);
201
624
  const boundedTimeoutMs = clampNumericValue(timeoutMs, ARCHITECT_MIN_TIMEOUT_MS, ARCHITECT_MAX_TIMEOUT_MS);
625
+ const requestedResearchMode = normalizeArchitectResearchMode(researchMode);
626
+ const { snapshot: stackResearchSnapshot, sourcePath: snapshotSourcePath } = loadStackResearchSnapshot();
627
+ const snapshotGeneratedAt = toIsoTimestamp(stackResearchSnapshot.generatedAt);
628
+ const realtimeGateEnabled = requestedResearchMode === 'realtime' && enableRealtimeResearch === true;
629
+ const realtimeSignalPayloadResult = realtimeGateEnabled
630
+ ? loadRealtimeSignalPayload(realtimeSignalFilePath)
631
+ : { payload: null, sourcePath: null };
632
+ const realtimeSignalPayload = realtimeSignalPayloadResult.payload;
633
+ const realtimeSignalsLoaded = Array.isArray(realtimeSignalPayload?.stackSignals)
634
+ && realtimeSignalPayload.stackSignals.length > 0;
635
+ const effectiveResearchMode = realtimeGateEnabled && realtimeSignalsLoaded
636
+ ? 'realtime'
637
+ : 'snapshot';
202
638
  const normalizedDescription = String(projectDescription || '').trim().toLowerCase();
203
639
  const effectiveDescriptionSeed = normalizedDescription || 'general software project';
204
640
  let effectiveDescription = effectiveDescriptionSeed;
205
641
  let usedTokens = estimateTokenUsage(effectiveDescription) + 120;
206
642
  const uncertaintyNotes = [];
207
643
 
644
+ if (requestedResearchMode === 'realtime' && !realtimeGateEnabled) {
645
+ uncertaintyNotes.push('Realtime research mode requested but gate is off. Using deterministic snapshot baseline.');
646
+ }
647
+
648
+ if (requestedResearchMode === 'realtime' && realtimeGateEnabled && !realtimeSignalsLoaded) {
649
+ uncertaintyNotes.push('Realtime research gate is on, but no trusted realtime payload was available. Using deterministic snapshot baseline.');
650
+ }
651
+
208
652
  if (usedTokens > boundedTokenBudget) {
209
653
  effectiveDescription = effectiveDescription.slice(0, Math.max(120, boundedTokenBudget * 4));
210
654
  usedTokens = boundedTokenBudget;
@@ -220,9 +664,51 @@ export function recommendArchitecture({
220
664
  usedTokens,
221
665
  elapsedMs: Date.now() - startedAt,
222
666
  timeoutTriggered: true,
667
+ requestedResearchMode,
668
+ effectiveResearchMode,
669
+ snapshot: stackResearchSnapshot,
670
+ snapshotSourcePath,
671
+ realtimeGateEnabled,
672
+ realtimeSignalsLoaded,
673
+ realtimeSignalSourcePath: realtimeSignalPayloadResult.sourcePath,
674
+ normalizedProjectDescription: normalizedDescription,
223
675
  });
224
676
  }
225
677
 
678
+ const snapshotSignalByStackFileName = new Map();
679
+ for (const snapshotSignalEntry of stackResearchSnapshot?.stackSignals || []) {
680
+ if (!snapshotSignalEntry?.stackFileName) {
681
+ continue;
682
+ }
683
+
684
+ snapshotSignalByStackFileName.set(snapshotSignalEntry.stackFileName, snapshotSignalEntry);
685
+ }
686
+
687
+ const realtimeSignalByStackFileName = new Map();
688
+ if (realtimeSignalsLoaded) {
689
+ for (const realtimeSignalEntry of realtimeSignalPayload.stackSignals) {
690
+ const stackFileName = realtimeSignalEntry?.stackFileName;
691
+ if (!stackFileName || !stackFileNames.includes(stackFileName)) {
692
+ continue;
693
+ }
694
+
695
+ const rawRealtimeSignalStrength = realtimeSignalEntry.signalStrength
696
+ ?? realtimeSignalEntry.metrics?.signalStrength
697
+ ?? 0;
698
+ const normalizedRealtimeSignalStrength = normalizeMetricValue(rawRealtimeSignalStrength, 0);
699
+ realtimeSignalByStackFileName.set(stackFileName, {
700
+ signalBoost: normalizeStackSignalBoost(normalizedRealtimeSignalStrength),
701
+ measuredAt: realtimeSignalEntry.measuredAt || realtimeSignalPayload.generatedAt || snapshotGeneratedAt,
702
+ sourceName: realtimeSignalEntry.sourceName || realtimeSignalPayload.sourceName || 'Trusted realtime source',
703
+ sourceUrl: realtimeSignalEntry.sourceUrl || realtimeSignalPayload.sourceUrl || realtimeSignalPayloadResult.sourcePath,
704
+ metrics: {
705
+ signalStrength: normalizedRealtimeSignalStrength,
706
+ freshnessHours: Number(realtimeSignalEntry.metrics?.freshnessHours) || 0,
707
+ },
708
+ });
709
+ }
710
+ }
711
+
226
712
  const detectionScoreByStackFileName = new Map();
227
713
  for (const rankedCandidate of projectDetection?.rankedCandidates || []) {
228
714
  const confidenceScore = Number(rankedCandidate.confidenceScore) || 0;
@@ -251,13 +737,26 @@ export function recommendArchitecture({
251
737
  }
252
738
 
253
739
  const detectionSignalScore = detectionScoreByStackFileName.get(stackFileName) || 0;
254
- const totalScore = 0.2 + keywordSignalScore + detectionSignalScore;
740
+ const snapshotSignalEntry = snapshotSignalByStackFileName.get(stackFileName) || null;
741
+ const snapshotSignalScorePayload = calculateSnapshotSignalScore(snapshotSignalEntry);
742
+ const snapshotSignalScore = normalizeStackSignalBoost(snapshotSignalScorePayload.aggregateScore);
743
+ const realtimeSignalPayloadEntry = realtimeSignalByStackFileName.get(stackFileName) || null;
744
+ const realtimeSignalScore = realtimeSignalPayloadEntry?.signalBoost || 0;
745
+ const totalScore = 0.2 + keywordSignalScore + detectionSignalScore + snapshotSignalScore + realtimeSignalScore;
255
746
 
256
747
  return {
257
748
  stackFileName,
258
749
  totalScore,
259
750
  keywordSignalScore,
260
751
  detectionSignalScore,
752
+ snapshotSignalScore,
753
+ snapshotMetrics: snapshotSignalScorePayload.metrics,
754
+ realtimeSignalScore,
755
+ realtimeMetrics: realtimeSignalPayloadEntry?.metrics || null,
756
+ snapshotMeasuredAt: snapshotSignalEntry?.measuredAt || stackResearchSnapshot.generatedAt,
757
+ realtimeMeasuredAt: realtimeSignalPayloadEntry?.measuredAt || null,
758
+ realtimeSourceName: realtimeSignalPayloadEntry?.sourceName || null,
759
+ realtimeSourceUrl: realtimeSignalPayloadEntry?.sourceUrl || null,
261
760
  matchedSignals,
262
761
  };
263
762
  }).sort((leftCandidate, rightCandidate) => rightCandidate.totalScore - leftCandidate.totalScore);
@@ -271,6 +770,14 @@ export function recommendArchitecture({
271
770
  usedTokens,
272
771
  elapsedMs: Date.now() - startedAt,
273
772
  timeoutTriggered: false,
773
+ requestedResearchMode,
774
+ effectiveResearchMode,
775
+ snapshot: stackResearchSnapshot,
776
+ snapshotSourcePath,
777
+ realtimeGateEnabled,
778
+ realtimeSignalsLoaded,
779
+ realtimeSignalSourcePath: realtimeSignalPayloadResult.sourcePath,
780
+ normalizedProjectDescription: normalizedDescription,
274
781
  });
275
782
  }
276
783
 
@@ -308,9 +815,15 @@ export function recommendArchitecture({
308
815
 
309
816
  const recommendedStackFileName = strongestCandidate.stackFileName;
310
817
  const recommendedBlueprintFileName = resolveRecommendedBlueprintFileName(recommendedStackFileName, blueprintFileNames);
311
- const signalSummary = strongestCandidate.matchedSignals.length > 0
312
- ? strongestCandidate.matchedSignals.slice(0, 4).join(', ')
313
- : 'limited direct stack keywords';
818
+ const signalSummaryParts = [];
819
+ if (strongestCandidate.matchedSignals.length > 0) {
820
+ signalSummaryParts.push(strongestCandidate.matchedSignals.slice(0, 4).join(', '));
821
+ }
822
+ signalSummaryParts.push(`snapshot ${strongestCandidate.snapshotSignalScore.toFixed(2)}`);
823
+ if (strongestCandidate.realtimeSignalScore > 0) {
824
+ signalSummaryParts.push(`realtime ${strongestCandidate.realtimeSignalScore.toFixed(2)}`);
825
+ }
826
+ const signalSummary = signalSummaryParts.join(', ');
314
827
 
315
828
  const rationaleSentences = [
316
829
  `I recommend ${toTitleCase(recommendedStackFileName)} with ${toTitleCase(recommendedBlueprintFileName)} for this project.`,
@@ -322,6 +835,92 @@ export function recommendArchitecture({
322
835
  rationaleSentences.push('Confidence is low, so review alternatives before finalizing architecture.');
323
836
  }
324
837
 
838
+ const evidenceCitations = [
839
+ buildEvidenceCitation({
840
+ citationId: `snapshot:${stackResearchSnapshot.snapshotId || 'snapshot'}:${recommendedStackFileName}`,
841
+ sourceType: 'snapshot',
842
+ sourceName: stackResearchSnapshot.sourceName || 'Stack research snapshot',
843
+ sourceUrl: stackResearchSnapshot.sourceUrl || snapshotSourcePath,
844
+ measuredAt: strongestCandidate.snapshotMeasuredAt || snapshotGeneratedAt,
845
+ stackFileName: recommendedStackFileName,
846
+ metrics: strongestCandidate.snapshotMetrics,
847
+ note: 'Deterministic stack research snapshot signal.',
848
+ }),
849
+ ];
850
+
851
+ if (projectDetection?.recommendedStackFileName) {
852
+ evidenceCitations.push(
853
+ buildEvidenceCitation({
854
+ citationId: `detector:${projectDetection.recommendedStackFileName}`,
855
+ sourceType: 'detection',
856
+ sourceName: 'Existing project marker detector',
857
+ sourceUrl: 'state://project-detection',
858
+ measuredAt: snapshotGeneratedAt,
859
+ stackFileName: projectDetection.recommendedStackFileName,
860
+ metrics: {
861
+ confidenceScore: Number(projectDetection.confidenceScore || 0),
862
+ confidenceGap: Number(projectDetection.confidenceGap || 0),
863
+ },
864
+ note: 'Repository marker detection signal used for stack recommendation bias.',
865
+ })
866
+ );
867
+ }
868
+
869
+ if (effectiveResearchMode === 'realtime') {
870
+ const realtimeCitationCandidate = strongestCandidate.realtimeSignalScore > 0
871
+ ? strongestCandidate
872
+ : scoredStackCandidates.find((stackCandidate) => stackCandidate.realtimeSignalScore > 0) || null;
873
+
874
+ if (realtimeCitationCandidate) {
875
+ const realtimeCitationOnRecommendedStack = realtimeCitationCandidate.stackFileName === recommendedStackFileName;
876
+ evidenceCitations.push(
877
+ buildEvidenceCitation({
878
+ citationId: `realtime:${realtimeCitationCandidate.stackFileName}`,
879
+ sourceType: 'realtime',
880
+ sourceName: realtimeCitationCandidate.realtimeSourceName || 'Trusted realtime source',
881
+ sourceUrl: realtimeCitationCandidate.realtimeSourceUrl || realtimeSignalPayloadResult.sourcePath,
882
+ measuredAt: realtimeCitationCandidate.realtimeMeasuredAt || snapshotGeneratedAt,
883
+ stackFileName: realtimeCitationCandidate.stackFileName,
884
+ metrics: realtimeCitationCandidate.realtimeMetrics
885
+ || { signalStrength: realtimeCitationCandidate.realtimeSignalScore },
886
+ note: realtimeCitationOnRecommendedStack
887
+ ? 'Optional realtime signal, loaded only when realtime research is explicitly gated on.'
888
+ : 'Optional realtime signal loaded for an alternative stack signal; used as supporting evidence in realtime mode.',
889
+ })
890
+ );
891
+ } else if (realtimeSignalsLoaded && Array.isArray(realtimeSignalPayload?.stackSignals) && realtimeSignalPayload.stackSignals.length > 0) {
892
+ const fallbackRealtimeSignal = realtimeSignalPayload.stackSignals[0];
893
+ const fallbackRealtimeSignalStrength = normalizeMetricValue(
894
+ fallbackRealtimeSignal?.signalStrength
895
+ ?? fallbackRealtimeSignal?.metrics?.signalStrength
896
+ ?? 0,
897
+ 0
898
+ );
899
+
900
+ evidenceCitations.push(
901
+ buildEvidenceCitation({
902
+ citationId: 'realtime:payload-fallback',
903
+ sourceType: 'realtime',
904
+ sourceName: fallbackRealtimeSignal?.sourceName || realtimeSignalPayload.sourceName || 'Trusted realtime source',
905
+ sourceUrl: fallbackRealtimeSignal?.sourceUrl || realtimeSignalPayload.sourceUrl || realtimeSignalPayloadResult.sourcePath,
906
+ measuredAt: fallbackRealtimeSignal?.measuredAt || realtimeSignalPayload.generatedAt || snapshotGeneratedAt,
907
+ stackFileName: recommendedStackFileName,
908
+ metrics: {
909
+ signalStrength: fallbackRealtimeSignalStrength,
910
+ },
911
+ note: 'Optional realtime payload loaded, but stack mapping did not match current candidate set. Stored as trace citation.',
912
+ })
913
+ );
914
+ }
915
+ }
916
+
917
+ const designGuidance = synthesizeDesignSignals({
918
+ normalizedProjectDescription: normalizedDescription,
919
+ recommendedStackFileName,
920
+ realtimePayload: effectiveResearchMode === 'realtime' ? realtimeSignalPayload : null,
921
+ snapshotGeneratedAt,
922
+ });
923
+
325
924
  const alternatives = scoredStackCandidates
326
925
  .slice(1, 3)
327
926
  .map((stackCandidate) => {
@@ -331,6 +930,7 @@ export function recommendArchitecture({
331
930
  blueprintFileName: alternativeBlueprintFileName,
332
931
  oneLineTradeoff: STACK_TRADEOFF_SUMMARIES[stackCandidate.stackFileName]
333
932
  || 'validate fit with your runtime and team constraints.',
933
+ evidenceSummary: `keyword=${stackCandidate.keywordSignalScore.toFixed(2)}, detection=${stackCandidate.detectionSignalScore.toFixed(2)}, snapshot=${stackCandidate.snapshotSignalScore.toFixed(2)}, realtime=${stackCandidate.realtimeSignalScore.toFixed(2)}`,
334
934
  };
335
935
  });
336
936
 
@@ -344,6 +944,14 @@ export function recommendArchitecture({
344
944
  usedTokens,
345
945
  elapsedMs,
346
946
  timeoutTriggered: true,
947
+ requestedResearchMode,
948
+ effectiveResearchMode,
949
+ snapshot: stackResearchSnapshot,
950
+ snapshotSourcePath,
951
+ realtimeGateEnabled,
952
+ realtimeSignalsLoaded,
953
+ realtimeSignalSourcePath: realtimeSignalPayloadResult.sourcePath,
954
+ normalizedProjectDescription: normalizedDescription,
347
955
  });
348
956
  }
349
957
 
@@ -361,7 +969,25 @@ export function recommendArchitecture({
361
969
  lowConfidence,
362
970
  dataConflict,
363
971
  repeatedOverride: false,
972
+ realtimeGated: requestedResearchMode === 'realtime' && !realtimeGateEnabled,
973
+ realtimeUnavailable: requestedResearchMode === 'realtime' && realtimeGateEnabled && !realtimeSignalsLoaded,
364
974
  },
975
+ research: {
976
+ requestedMode: requestedResearchMode,
977
+ effectiveMode: effectiveResearchMode,
978
+ deterministic: effectiveResearchMode === 'snapshot',
979
+ snapshotId: stackResearchSnapshot.snapshotId || 'snapshot',
980
+ snapshotGeneratedAt,
981
+ snapshotSourcePath,
982
+ realtimeGateEnabled,
983
+ realtimeSignalsLoaded,
984
+ realtimeSignalSourcePath: realtimeSignalPayloadResult.sourcePath,
985
+ trustedRealtimeSources: Array.isArray(stackResearchSnapshot.trustedRealtimeSources)
986
+ ? stackResearchSnapshot.trustedRealtimeSources
987
+ : [],
988
+ },
989
+ evidenceCitations,
990
+ designGuidance,
365
991
  researchBudget: {
366
992
  tokenBudget: boundedTokenBudget,
367
993
  timeoutMs: boundedTimeoutMs,
@@ -374,11 +1000,15 @@ export function recommendArchitecture({
374
1000
  }
375
1001
 
376
1002
  export function formatArchitectureRecommendation(architectureRecommendation) {
1003
+ const researchModeSummary = architectureRecommendation.research
1004
+ ? `${architectureRecommendation.research.requestedMode} -> ${architectureRecommendation.research.effectiveMode}`
1005
+ : 'snapshot';
377
1006
  const outputLines = [
378
1007
  '\nArchitecture recommendation (project-description-first):',
379
1008
  `- Stack: ${toTitleCase(architectureRecommendation.recommendedStackFileName)}`,
380
1009
  `- Blueprint: ${toTitleCase(architectureRecommendation.recommendedBlueprintFileName)}`,
381
1010
  `- Confidence: ${architectureRecommendation.confidenceLabel} (${architectureRecommendation.confidenceScore})`,
1011
+ `- Research mode: ${researchModeSummary}`,
382
1012
  '- Rationale:',
383
1013
  ...architectureRecommendation.rationaleSentences.map((sentence, sentenceIndex) => ` ${sentenceIndex + 1}. ${sentence}`),
384
1014
  '- Alternatives:',
@@ -418,6 +1048,27 @@ export function formatArchitectureRecommendation(architectureRecommendation) {
418
1048
  outputLines.push(`- Caution labels: ${cautionLabels.join(', ')}`);
419
1049
  }
420
1050
 
1051
+ if (Array.isArray(architectureRecommendation.evidenceCitations) && architectureRecommendation.evidenceCitations.length > 0) {
1052
+ outputLines.push('- Evidence citations (measurable source + timestamp):');
1053
+ outputLines.push(
1054
+ ...architectureRecommendation.evidenceCitations.map((citationEntry, citationIndex) => {
1055
+ const metricsSummary = Object.entries(citationEntry.metrics || {})
1056
+ .map(([metricKey, metricValue]) => `${metricKey}=${metricValue}`)
1057
+ .join(', ');
1058
+ return ` ${citationIndex + 1}. [${citationEntry.sourceType}] ${citationEntry.sourceName} @ ${citationEntry.measuredAt} (${metricsSummary || 'no metrics'})`;
1059
+ })
1060
+ );
1061
+ }
1062
+
1063
+ if (architectureRecommendation.designGuidance?.normalizedSignals) {
1064
+ const normalizedSignals = architectureRecommendation.designGuidance.normalizedSignals;
1065
+ outputLines.push('- Design signal synthesis (normalized, no copied external prose):');
1066
+ outputLines.push(` 1. Palette roles: ${normalizedSignals.paletteRoles.join(', ')}`);
1067
+ outputLines.push(` 2. Typography scale: ${normalizedSignals.typographyScale}`);
1068
+ outputLines.push(` 3. Spacing pattern: ${normalizedSignals.spacingPattern}`);
1069
+ outputLines.push(` 4. Motion characteristics: ${normalizedSignals.motionCharacteristics.join(', ')}`);
1070
+ }
1071
+
421
1072
  outputLines.push(
422
1073
  `- Research guardrails: ${architectureRecommendation.researchBudget.usedTokens}/${architectureRecommendation.researchBudget.tokenBudget} tokens, ${architectureRecommendation.researchBudget.elapsedMs}ms/${architectureRecommendation.researchBudget.timeoutMs}ms`
423
1074
  );
@@ -65,6 +65,7 @@ import { evaluateSkillDomainCompatibility } from '../compatibility.mjs';
65
65
  import {
66
66
  ARCHITECT_DEFAULT_TOKEN_BUDGET,
67
67
  ARCHITECT_DEFAULT_TIMEOUT_MS,
68
+ ARCHITECT_DEFAULT_RESEARCH_MODE,
68
69
  ARCHITECT_MIN_TOKEN_BUDGET,
69
70
  ARCHITECT_MAX_TOKEN_BUDGET,
70
71
  ARCHITECT_MIN_TIMEOUT_MS,
@@ -100,6 +101,9 @@ export function parseInitArguments(commandArguments) {
100
101
  projectDescription: '',
101
102
  architectTokenBudget: ARCHITECT_DEFAULT_TOKEN_BUDGET,
102
103
  architectTimeoutMs: ARCHITECT_DEFAULT_TIMEOUT_MS,
104
+ architectResearchMode: ARCHITECT_DEFAULT_RESEARCH_MODE,
105
+ enableRealtimeResearch: false,
106
+ architectRealtimeSignalFile: null,
103
107
  runtimeEnv: 'auto',
104
108
  runtimeEnvProvided: false,
105
109
  };
@@ -308,6 +312,38 @@ export function parseInitArguments(commandArguments) {
308
312
  continue;
309
313
  }
310
314
 
315
+ if (currentArgument === '--architect-research-mode') {
316
+ parsedInitOptions.architectResearchMode = commandArguments[argumentIndex + 1] || ARCHITECT_DEFAULT_RESEARCH_MODE;
317
+ argumentIndex += 1;
318
+ continue;
319
+ }
320
+
321
+ if (currentArgument.startsWith('--architect-research-mode=')) {
322
+ parsedInitOptions.architectResearchMode = currentArgument.split('=')[1] || ARCHITECT_DEFAULT_RESEARCH_MODE;
323
+ continue;
324
+ }
325
+
326
+ if (currentArgument === '--enable-realtime-research') {
327
+ parsedInitOptions.enableRealtimeResearch = true;
328
+ continue;
329
+ }
330
+
331
+ if (currentArgument === '--disable-realtime-research') {
332
+ parsedInitOptions.enableRealtimeResearch = false;
333
+ continue;
334
+ }
335
+
336
+ if (currentArgument === '--architect-realtime-signal-file') {
337
+ parsedInitOptions.architectRealtimeSignalFile = commandArguments[argumentIndex + 1] || null;
338
+ argumentIndex += 1;
339
+ continue;
340
+ }
341
+
342
+ if (currentArgument.startsWith('--architect-realtime-signal-file=')) {
343
+ parsedInitOptions.architectRealtimeSignalFile = currentArgument.split('=')[1] || null;
344
+ continue;
345
+ }
346
+
311
347
  if (currentArgument === '--runtime-env') {
312
348
  parsedInitOptions.runtimeEnv = commandArguments[argumentIndex + 1] || 'auto';
313
349
  parsedInitOptions.runtimeEnvProvided = true;
@@ -350,9 +386,18 @@ export function parseInitArguments(commandArguments) {
350
386
  throw new Error(`--architect-timeout-ms must be an integer between ${ARCHITECT_MIN_TIMEOUT_MS} and ${ARCHITECT_MAX_TIMEOUT_MS}`);
351
387
  }
352
388
 
389
+ const normalizedArchitectResearchMode = normalizeChoiceInput(
390
+ parsedInitOptions.architectResearchMode || ARCHITECT_DEFAULT_RESEARCH_MODE
391
+ );
392
+ const supportedArchitectResearchModes = new Set(['snapshot', 'realtime']);
393
+ if (!supportedArchitectResearchModes.has(normalizedArchitectResearchMode)) {
394
+ throw new Error('--architect-research-mode must be one of: snapshot, realtime');
395
+ }
396
+
353
397
  parsedInitOptions.docsLang = normalizedDocsLanguage;
354
398
  parsedInitOptions.runtimeEnv = normalizedRuntimeEnvironment;
355
399
  parsedInitOptions.tokenAgent = normalizeAgentName(parsedInitOptions.tokenAgent);
400
+ parsedInitOptions.architectResearchMode = normalizedArchitectResearchMode;
356
401
 
357
402
  return parsedInitOptions;
358
403
  }
@@ -843,6 +888,9 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
843
888
  blueprintFileNames,
844
889
  tokenBudget: initOptions.architectTokenBudget,
845
890
  timeoutMs: initOptions.architectTimeoutMs,
891
+ researchMode: initOptions.architectResearchMode,
892
+ enableRealtimeResearch: initOptions.enableRealtimeResearch,
893
+ realtimeSignalFilePath: initOptions.architectRealtimeSignalFile,
846
894
  });
847
895
 
848
896
  architectureRecommendation.userVeto = {
package/lib/cli/utils.mjs CHANGED
@@ -29,7 +29,7 @@ export function printUsage() {
29
29
  console.log('');
30
30
  console.log('Usage:');
31
31
  console.log(' agentic-senior-core launch');
32
- console.log(' agentic-senior-core init [target-directory] [--preset <name>] [--profile <beginner|balanced|strict>] [--profile-pack <name>] [--stack <name>] [--blueprint <name>] [--project-description <text>] [--architect-token-budget <number>] [--architect-timeout-ms <number>] [--ci <true|false>] [--newbie] [--token-optimize] [--no-token-optimize] [--token-agent <name>] [--memory-continuity] [--no-memory-continuity] [--scaffold-docs] [--no-scaffold-docs] [--docs-lang <en|id>] [--project-config <path>] [--runtime-env <auto|linux-wsl|linux|windows|macos>]');
32
+ console.log(' agentic-senior-core init [target-directory] [--preset <name>] [--profile <beginner|balanced|strict>] [--profile-pack <name>] [--stack <name>] [--blueprint <name>] [--project-description <text>] [--architect-token-budget <number>] [--architect-timeout-ms <number>] [--architect-research-mode <snapshot|realtime>] [--enable-realtime-research] [--architect-realtime-signal-file <path>] [--ci <true|false>] [--newbie] [--token-optimize] [--no-token-optimize] [--token-agent <name>] [--memory-continuity] [--no-memory-continuity] [--scaffold-docs] [--no-scaffold-docs] [--docs-lang <en|id>] [--project-config <path>] [--runtime-env <auto|linux-wsl|linux|windows|macos>]');
33
33
  console.log(' agentic-senior-core upgrade [target-directory] [--dry-run] [--yes] [--mcp-template]');
34
34
  console.log(' agentic-senior-core optimize [target-directory] [--agent <copilot|claude|cursor|windsurf|gemini|codex|cline>] [--enable|--disable] [--show]');
35
35
  console.log(' agentic-senior-core mcp');
@@ -49,6 +49,9 @@ export function printUsage() {
49
49
  console.log(' --project-description Architecture intent text used for stack/blueprint recommendation');
50
50
  console.log(' --architect-token-budget Max token estimate used by recommendation research (default: 900)');
51
51
  console.log(' --architect-timeout-ms Max recommendation research time in milliseconds (default: 1500)');
52
+ console.log(' --architect-research-mode Recommendation evidence mode (snapshot or realtime; default: snapshot)');
53
+ console.log(' --enable-realtime-research Explicit gate to allow trusted realtime evidence ingestion');
54
+ console.log(' --architect-realtime-signal-file Optional JSON payload path for trusted realtime stack/design signals');
52
55
  console.log(' --ci Override CI/CD quality checks (guardrails) (true|false)');
53
56
  console.log(' --token-optimize Explicitly enable token optimization policy during init (default behavior)');
54
57
  console.log(' --token-agent Set token optimization agent target (copilot, claude, cursor, windsurf, gemini, codex, cline)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuenn3123/agentic-senior-core",
3
- "version": "2.5.19",
3
+ "version": "2.5.20",
4
4
  "type": "module",
5
5
  "description": "Force your AI Agent to code like a Staff Engineer, not a Junior.",
6
6
  "bin": {
@@ -180,6 +180,29 @@ const REQUIRED_DETECTION_TRANSPARENCY_SNIPPETS = [
180
180
  ],
181
181
  },
182
182
  ];
183
+ const REQUIRED_STACK_RESEARCH_ENGINE_SNIPPETS = [
184
+ {
185
+ path: 'lib/cli/architect.mjs',
186
+ snippets: [
187
+ 'ARCHITECT_RESEARCH_SNAPSHOT_FILE_PATH',
188
+ 'evidenceCitations',
189
+ 'designGuidance',
190
+ 'copiedExternalProse: false',
191
+ 'realtimeGateEnabled',
192
+ 'requestedMode: requestedResearchMode',
193
+ ],
194
+ },
195
+ {
196
+ path: 'lib/cli/commands/init.mjs',
197
+ snippets: [
198
+ '--architect-research-mode',
199
+ '--enable-realtime-research',
200
+ '--architect-realtime-signal-file',
201
+ 'researchMode: initOptions.architectResearchMode',
202
+ 'enableRealtimeResearch: initOptions.enableRealtimeResearch',
203
+ ],
204
+ },
205
+ ];
183
206
 
184
207
  const validationResult = {
185
208
  passed: 0,
@@ -300,6 +323,7 @@ async function validateRequiredFiles() {
300
323
  '.agent-context/state/benchmark-reproducibility.json',
301
324
  '.agent-context/state/benchmark-writer-judge-config.json',
302
325
  '.agent-context/state/benchmark-watchlist.json',
326
+ '.agent-context/state/stack-research-snapshot.json',
303
327
  '.agent-context/state/memory-schema-v1.json',
304
328
  '.agent-context/state/memory-adapter-contract.json',
305
329
  '.agent-context/state/skill-platform.json',
@@ -975,6 +999,93 @@ async function validateDetectionTransparencyCoverage() {
975
999
  }
976
1000
  }
977
1001
 
1002
+ async function validateStackResearchEngineCoverage() {
1003
+ console.log('\nChecking stack research engine coverage...');
1004
+
1005
+ for (const coverageRule of REQUIRED_STACK_RESEARCH_ENGINE_SNIPPETS) {
1006
+ const absoluteCoveragePath = join(ROOT_DIR, coverageRule.path);
1007
+
1008
+ if (!(await fileExists(absoluteCoveragePath))) {
1009
+ fail(`Missing stack research source: ${coverageRule.path}`);
1010
+ continue;
1011
+ }
1012
+
1013
+ const coverageContent = await readTextFile(absoluteCoveragePath);
1014
+ for (const requiredSnippet of coverageRule.snippets) {
1015
+ if (coverageContent.includes(requiredSnippet)) {
1016
+ pass(`${coverageRule.path} includes stack research snippet: ${requiredSnippet}`);
1017
+ } else {
1018
+ fail(`${coverageRule.path} is missing stack research snippet: ${requiredSnippet}`);
1019
+ }
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ function isNormalizedMetricValue(value) {
1025
+ return Number.isFinite(Number(value)) && Number(value) >= 0 && Number(value) <= 1;
1026
+ }
1027
+
1028
+ async function validateStackResearchSnapshotState() {
1029
+ console.log('\nChecking deterministic stack research snapshot state...');
1030
+
1031
+ const snapshotPath = join(ROOT_DIR, '.agent-context', 'state', 'stack-research-snapshot.json');
1032
+ if (!(await fileExists(snapshotPath))) {
1033
+ fail('Missing deterministic stack research snapshot: .agent-context/state/stack-research-snapshot.json');
1034
+ return;
1035
+ }
1036
+
1037
+ let snapshotPayload;
1038
+ try {
1039
+ snapshotPayload = JSON.parse(await readTextFile(snapshotPath));
1040
+ } catch {
1041
+ fail('Invalid JSON in .agent-context/state/stack-research-snapshot.json');
1042
+ return;
1043
+ }
1044
+
1045
+ if (snapshotPayload?.deterministic === true) {
1046
+ pass('stack-research-snapshot.json declares deterministic: true');
1047
+ } else {
1048
+ fail('stack-research-snapshot.json must declare deterministic: true');
1049
+ }
1050
+
1051
+ const generatedAtValue = String(snapshotPayload?.generatedAt || '');
1052
+ if (!Number.isNaN(Date.parse(generatedAtValue))) {
1053
+ pass('stack-research-snapshot.json includes valid generatedAt timestamp');
1054
+ } else {
1055
+ fail('stack-research-snapshot.json must include a valid generatedAt timestamp');
1056
+ }
1057
+
1058
+ if (Array.isArray(snapshotPayload?.trustedRealtimeSources) && snapshotPayload.trustedRealtimeSources.length > 0) {
1059
+ pass('stack-research-snapshot.json includes trustedRealtimeSources');
1060
+ } else {
1061
+ fail('stack-research-snapshot.json must include at least one trustedRealtimeSources entry');
1062
+ }
1063
+
1064
+ if (!Array.isArray(snapshotPayload?.stackSignals) || snapshotPayload.stackSignals.length === 0) {
1065
+ fail('stack-research-snapshot.json must include non-empty stackSignals array');
1066
+ return;
1067
+ }
1068
+
1069
+ pass(`stack-research-snapshot.json includes ${snapshotPayload.stackSignals.length} stack signal entries`);
1070
+
1071
+ const invalidSignalEntries = snapshotPayload.stackSignals.filter((signalEntry) => {
1072
+ const hasStackName = typeof signalEntry?.stackFileName === 'string' && signalEntry.stackFileName.trim().length > 0;
1073
+ const hasMeasuredAt = !Number.isNaN(Date.parse(String(signalEntry?.measuredAt || '')));
1074
+ const metrics = signalEntry?.metrics || {};
1075
+ const hasValidMetrics = isNormalizedMetricValue(metrics.ecosystemMaturity)
1076
+ && isNormalizedMetricValue(metrics.talentAvailability)
1077
+ && isNormalizedMetricValue(metrics.deliveryVelocity);
1078
+
1079
+ return !(hasStackName && hasMeasuredAt && hasValidMetrics);
1080
+ });
1081
+
1082
+ if (invalidSignalEntries.length === 0) {
1083
+ pass('stack-research-snapshot.json stackSignals keep measurable metrics and timestamps');
1084
+ } else {
1085
+ fail(`stack-research-snapshot.json has invalid stackSignals entries: ${invalidSignalEntries.length}`);
1086
+ }
1087
+ }
1088
+
978
1089
  async function validateMcpConfiguration() {
979
1090
  console.log('\nChecking MCP configuration...');
980
1091
 
@@ -1225,6 +1336,8 @@ async function main() {
1225
1336
  await validateDocumentationFlow();
1226
1337
  await validateTerminologyMapping();
1227
1338
  await validateDetectionTransparencyCoverage();
1339
+ await validateStackResearchEngineCoverage();
1340
+ await validateStackResearchSnapshotState();
1228
1341
  await validateMcpConfiguration();
1229
1342
  await validateHumanWritingGovernance();
1230
1343
  await validateInstructionAdapters();