@ryuenn3123/agentic-senior-core 2.5.18 → 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.
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.agent-context/state/stack-research-snapshot.json +112 -0
- package/.cursorrules +1 -1
- package/.windsurfrules +1 -1
- package/README.md +12 -0
- package/lib/cli/architect.mjs +656 -5
- package/lib/cli/commands/init.mjs +198 -9
- package/lib/cli/commands/upgrade.mjs +64 -1
- package/lib/cli/compiler.mjs +2 -0
- package/lib/cli/utils.mjs +4 -1
- package/package.json +1 -1
- package/scripts/validate.mjs +161 -0
package/lib/cli/architect.mjs
CHANGED
|
@@ -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
|
|
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
|
|
312
|
-
|
|
313
|
-
|
|
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
|
);
|