@ryuenn3123/agentic-senior-core 3.0.14 → 3.0.16
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/prompts/bootstrap-design.md +30 -16
- package/.agent-context/prompts/init-project.md +4 -0
- package/.agent-context/rules/architecture.md +13 -0
- package/.agent-context/rules/docker-runtime.md +12 -0
- package/.agent-context/rules/efficiency-vs-hype.md +17 -6
- package/.agent-context/rules/frontend-architecture.md +5 -0
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.agent-context/state/onboarding-report.json +0 -1
- package/.cursorrules +66 -29
- package/.gemini/instructions.md +1 -1
- package/.github/copilot-instructions.md +1 -1
- package/.instructions.md +4 -3
- package/.windsurfrules +66 -29
- package/AGENTS.md +1 -1
- package/lib/cli/architect.mjs +71 -784
- package/lib/cli/commands/init.mjs +32 -98
- package/lib/cli/commands/optimize.mjs +0 -4
- package/lib/cli/commands/upgrade.mjs +2 -5
- package/lib/cli/compiler.mjs +3 -11
- package/lib/cli/constants.mjs +3 -73
- package/lib/cli/detector/design-evidence.mjs +427 -0
- package/lib/cli/detector.mjs +13 -116
- package/lib/cli/init-options.mjs +0 -118
- package/lib/cli/project-scaffolder/constants.mjs +67 -0
- package/lib/cli/project-scaffolder/design-contract.mjs +554 -0
- package/lib/cli/project-scaffolder/discovery.mjs +315 -0
- package/lib/cli/project-scaffolder/prompt-builders.mjs +196 -0
- package/lib/cli/project-scaffolder/storage.mjs +154 -0
- package/lib/cli/project-scaffolder.mjs +32 -1160
- package/lib/cli/utils.mjs +2 -11
- package/package.json +1 -1
- package/scripts/frontend-usability-audit.mjs +53 -0
- package/scripts/validate/config.mjs +401 -0
- package/scripts/validate/coverage-checks.mjs +429 -0
- package/scripts/validate.mjs +44 -754
- package/lib/cli/init-architecture-flow.mjs +0 -233
- package/lib/cli/profile-packs.mjs +0 -108
package/lib/cli/architect.mjs
CHANGED
|
@@ -7,109 +7,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { BLUEPRINT_RECOMMENDATIONS } from './constants.mjs';
|
|
8
8
|
import { ensureDirectory, pathExists, toTitleCase } from './utils.mjs';
|
|
9
9
|
|
|
10
|
-
export const ARCHITECT_DEFAULT_TOKEN_BUDGET = 900;
|
|
11
|
-
export const ARCHITECT_DEFAULT_TIMEOUT_MS = 1500;
|
|
12
|
-
export const ARCHITECT_MIN_TOKEN_BUDGET = 200;
|
|
13
|
-
export const ARCHITECT_MAX_TOKEN_BUDGET = 4000;
|
|
14
|
-
export const ARCHITECT_MIN_TIMEOUT_MS = 200;
|
|
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
10
|
const ARCHITECT_MODULE_FILE_PATH = fileURLToPath(import.meta.url);
|
|
33
11
|
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
|
-
};
|
|
108
12
|
|
|
109
13
|
const ARCHITECT_PREFERENCE_FILE_PATH = process.env.AGENTIC_ARCHITECT_PREF_FILE
|
|
110
14
|
? path.resolve(process.env.AGENTIC_ARCHITECT_PREF_FILE)
|
|
111
15
|
: path.join(os.homedir(), '.agentic-senior-core', 'architect-preferences.json');
|
|
112
16
|
|
|
17
|
+
// Keyword hints — low-confidence bias only, not authoritative research.
|
|
113
18
|
const STACK_SIGNAL_WEIGHTS = {
|
|
114
19
|
'typescript.md': [
|
|
115
20
|
{ term: 'typescript', weight: 0.9 },
|
|
@@ -205,14 +110,6 @@ const STACK_TRADEOFF_SUMMARIES = {
|
|
|
205
110
|
'flutter.md': 'consistent UI across mobile platforms, but ecosystem fit should be checked per package.',
|
|
206
111
|
};
|
|
207
112
|
|
|
208
|
-
function clampNumericValue(value, minimumValue, maximumValue) {
|
|
209
|
-
return Math.min(Math.max(value, minimumValue), maximumValue);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function estimateTokenUsage(textValue) {
|
|
213
|
-
return Math.ceil(String(textValue || '').length / 4);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
113
|
function resolveConfidenceLabel(confidenceScore) {
|
|
217
114
|
if (confidenceScore >= 0.85) {
|
|
218
115
|
return 'high';
|
|
@@ -234,481 +131,23 @@ function resolveRecommendedBlueprintFileName(stackFileName, blueprintFileNames)
|
|
|
234
131
|
return blueprintFileNames[0] || null;
|
|
235
132
|
}
|
|
236
133
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
514
|
-
function buildFallbackRecommendation({
|
|
515
|
-
stackFileNames,
|
|
516
|
-
blueprintFileNames,
|
|
517
|
-
tokenBudget,
|
|
518
|
-
timeoutMs,
|
|
519
|
-
usedTokens,
|
|
520
|
-
elapsedMs,
|
|
521
|
-
timeoutTriggered,
|
|
522
|
-
requestedResearchMode,
|
|
523
|
-
effectiveResearchMode,
|
|
524
|
-
snapshot,
|
|
525
|
-
snapshotSourcePath,
|
|
526
|
-
realtimeGateEnabled,
|
|
527
|
-
realtimeSignalsLoaded,
|
|
528
|
-
realtimeSignalSourcePath,
|
|
529
|
-
normalizedProjectDescription,
|
|
530
|
-
}) {
|
|
531
|
-
const fallbackStackFileName = stackFileNames.includes('typescript.md')
|
|
532
|
-
? 'typescript.md'
|
|
533
|
-
: stackFileNames[0] || 'typescript.md';
|
|
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
|
-
});
|
|
558
|
-
|
|
559
|
-
return {
|
|
560
|
-
projectDescription: String(normalizedProjectDescription || '').trim(),
|
|
561
|
-
recommendedStackFileName: fallbackStackFileName,
|
|
562
|
-
recommendedBlueprintFileName: fallbackBlueprintFileName,
|
|
563
|
-
confidenceLabel: 'low',
|
|
564
|
-
confidenceScore: 0.45,
|
|
565
|
-
rationaleSentences: [
|
|
566
|
-
`I recommend ${toTitleCase(fallbackStackFileName)} with ${toTitleCase(fallbackBlueprintFileName)} as a safe fallback path.`,
|
|
567
|
-
'The architecture recommendation budget was constrained before a stronger stack signal could be computed.',
|
|
568
|
-
`Main trade-off: ${STACK_TRADEOFF_SUMMARIES[fallbackStackFileName] || 'validate ecosystem fit before production rollout.'}`,
|
|
569
|
-
],
|
|
570
|
-
alternatives: [],
|
|
571
|
-
uncertaintyNotes: [
|
|
572
|
-
timeoutTriggered
|
|
573
|
-
? 'Timeout guardrail triggered before recommendation analysis completed.'
|
|
574
|
-
: 'Input signals were not strong enough for a high-confidence architecture recommendation.',
|
|
575
|
-
],
|
|
576
|
-
signalSummary: 'fallback mode',
|
|
577
|
-
failureModes: {
|
|
578
|
-
lowConfidence: true,
|
|
579
|
-
dataConflict: false,
|
|
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
|
-
: [],
|
|
597
|
-
},
|
|
598
|
-
evidenceCitations: fallbackEvidenceCitations,
|
|
599
|
-
designGuidance: fallbackDesignGuidance,
|
|
600
|
-
researchBudget: {
|
|
601
|
-
tokenBudget,
|
|
602
|
-
timeoutMs,
|
|
603
|
-
usedTokens: Math.min(usedTokens, tokenBudget),
|
|
604
|
-
elapsedMs: Math.min(elapsedMs, timeoutMs),
|
|
605
|
-
tokenBudgetCapped: usedTokens >= tokenBudget,
|
|
606
|
-
timeoutTriggered,
|
|
607
|
-
},
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Generates a repo-grounded architecture brief based on project description
|
|
136
|
+
* keywords and repository marker detection. This is an offline brief —
|
|
137
|
+
* for ecosystem-level research, the consuming agent should perform live
|
|
138
|
+
* web research rather than relying on stale local snapshots.
|
|
139
|
+
*/
|
|
611
140
|
export function recommendArchitecture({
|
|
612
141
|
projectDescription,
|
|
613
142
|
projectDetection,
|
|
614
143
|
stackFileNames,
|
|
615
144
|
blueprintFileNames,
|
|
616
|
-
tokenBudget = ARCHITECT_DEFAULT_TOKEN_BUDGET,
|
|
617
|
-
timeoutMs = ARCHITECT_DEFAULT_TIMEOUT_MS,
|
|
618
|
-
researchMode = ARCHITECT_DEFAULT_RESEARCH_MODE,
|
|
619
|
-
enableRealtimeResearch = false,
|
|
620
|
-
realtimeSignalFilePath = null,
|
|
621
145
|
}) {
|
|
622
|
-
const startedAt = Date.now();
|
|
623
|
-
const boundedTokenBudget = clampNumericValue(tokenBudget, ARCHITECT_MIN_TOKEN_BUDGET, ARCHITECT_MAX_TOKEN_BUDGET);
|
|
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';
|
|
638
146
|
const normalizedDescription = String(projectDescription || '').trim().toLowerCase();
|
|
639
|
-
const
|
|
640
|
-
let effectiveDescription = effectiveDescriptionSeed;
|
|
641
|
-
let usedTokens = estimateTokenUsage(effectiveDescription) + 120;
|
|
147
|
+
const effectiveDescription = normalizedDescription || 'general software project';
|
|
642
148
|
const uncertaintyNotes = [];
|
|
643
149
|
|
|
644
|
-
|
|
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
|
-
|
|
652
|
-
if (usedTokens > boundedTokenBudget) {
|
|
653
|
-
effectiveDescription = effectiveDescription.slice(0, Math.max(120, boundedTokenBudget * 4));
|
|
654
|
-
usedTokens = boundedTokenBudget;
|
|
655
|
-
uncertaintyNotes.push('Token budget guardrail trimmed input context before recommendation.');
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
if ((Date.now() - startedAt) > boundedTimeoutMs) {
|
|
659
|
-
return buildFallbackRecommendation({
|
|
660
|
-
stackFileNames,
|
|
661
|
-
blueprintFileNames,
|
|
662
|
-
tokenBudget: boundedTokenBudget,
|
|
663
|
-
timeoutMs: boundedTimeoutMs,
|
|
664
|
-
usedTokens,
|
|
665
|
-
elapsedMs: Date.now() - startedAt,
|
|
666
|
-
timeoutTriggered: true,
|
|
667
|
-
requestedResearchMode,
|
|
668
|
-
effectiveResearchMode,
|
|
669
|
-
snapshot: stackResearchSnapshot,
|
|
670
|
-
snapshotSourcePath,
|
|
671
|
-
realtimeGateEnabled,
|
|
672
|
-
realtimeSignalsLoaded,
|
|
673
|
-
realtimeSignalSourcePath: realtimeSignalPayloadResult.sourcePath,
|
|
674
|
-
normalizedProjectDescription: normalizedDescription,
|
|
675
|
-
});
|
|
676
|
-
}
|
|
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
|
-
|
|
150
|
+
// Repo marker detection scoring (grounded in actual project files).
|
|
712
151
|
const detectionScoreByStackFileName = new Map();
|
|
713
152
|
for (const rankedCandidate of projectDetection?.rankedCandidates || []) {
|
|
714
153
|
const confidenceScore = Number(rankedCandidate.confidenceScore) || 0;
|
|
@@ -722,63 +161,64 @@ export function recommendArchitecture({
|
|
|
722
161
|
);
|
|
723
162
|
}
|
|
724
163
|
|
|
164
|
+
// Keyword hint scoring (low-confidence bias, not research).
|
|
725
165
|
const scoredStackCandidates = stackFileNames.map((stackFileName) => {
|
|
726
166
|
const configuredSignals = STACK_SIGNAL_WEIGHTS[stackFileName] || [];
|
|
727
|
-
const
|
|
728
|
-
let
|
|
167
|
+
const matchedKeywords = [];
|
|
168
|
+
let keywordScore = 0;
|
|
729
169
|
|
|
730
170
|
for (const configuredSignal of configuredSignals) {
|
|
731
171
|
if (!effectiveDescription.includes(configuredSignal.term)) {
|
|
732
172
|
continue;
|
|
733
173
|
}
|
|
734
174
|
|
|
735
|
-
|
|
736
|
-
|
|
175
|
+
keywordScore += configuredSignal.weight;
|
|
176
|
+
matchedKeywords.push(configuredSignal.term);
|
|
737
177
|
}
|
|
738
178
|
|
|
739
|
-
const
|
|
740
|
-
const
|
|
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;
|
|
179
|
+
const detectionScore = detectionScoreByStackFileName.get(stackFileName) || 0;
|
|
180
|
+
const totalScore = 0.2 + keywordScore + detectionScore;
|
|
746
181
|
|
|
747
182
|
return {
|
|
748
183
|
stackFileName,
|
|
749
184
|
totalScore,
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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,
|
|
760
|
-
matchedSignals,
|
|
185
|
+
keywordScore,
|
|
186
|
+
detectionScore,
|
|
187
|
+
matchedKeywords,
|
|
761
188
|
};
|
|
762
189
|
}).sort((leftCandidate, rightCandidate) => rightCandidate.totalScore - leftCandidate.totalScore);
|
|
763
190
|
|
|
191
|
+
// Fallback when no candidates can be scored.
|
|
764
192
|
if (scoredStackCandidates.length === 0) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
193
|
+
const fallbackStackFileName = stackFileNames.includes('typescript.md')
|
|
194
|
+
? 'typescript.md'
|
|
195
|
+
: stackFileNames[0] || 'typescript.md';
|
|
196
|
+
const fallbackBlueprintFileName = resolveRecommendedBlueprintFileName(fallbackStackFileName, blueprintFileNames);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
briefType: 'offline',
|
|
200
|
+
projectDescription: String(projectDescription || '').trim(),
|
|
201
|
+
recommendedStackFileName: fallbackStackFileName,
|
|
202
|
+
recommendedBlueprintFileName: fallbackBlueprintFileName,
|
|
203
|
+
confidenceLabel: 'low',
|
|
204
|
+
confidenceScore: 0.35,
|
|
205
|
+
rationaleSentences: [
|
|
206
|
+
`Defaulting to ${toTitleCase(fallbackStackFileName)} with ${toTitleCase(fallbackBlueprintFileName)} as a safe fallback.`,
|
|
207
|
+
'No keyword or detection signals were strong enough for a grounded recommendation.',
|
|
208
|
+
'For ecosystem-level validation, perform live web research before committing to this stack.',
|
|
209
|
+
],
|
|
210
|
+
alternatives: [],
|
|
211
|
+
uncertaintyNotes: [
|
|
212
|
+
'This is an offline brief based on keyword hints and repo markers only.',
|
|
213
|
+
'Ecosystem fitness was not verified — the agent should research before finalizing.',
|
|
214
|
+
],
|
|
215
|
+
signalSummary: 'fallback (no strong signals)',
|
|
216
|
+
failureModes: {
|
|
217
|
+
lowConfidence: true,
|
|
218
|
+
dataConflict: false,
|
|
219
|
+
repeatedOverride: false,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
782
222
|
}
|
|
783
223
|
|
|
784
224
|
const strongestCandidate = scoredStackCandidates[0];
|
|
@@ -791,7 +231,7 @@ export function recommendArchitecture({
|
|
|
791
231
|
+ Math.min(strongestCandidate.totalScore / 8, 0.25)
|
|
792
232
|
+ Math.min(Math.max(scoreGap, 0) / 3, 0.18);
|
|
793
233
|
|
|
794
|
-
if (strongestCandidate.
|
|
234
|
+
if (strongestCandidate.matchedKeywords.length === 0) {
|
|
795
235
|
confidenceScore -= 0.2;
|
|
796
236
|
}
|
|
797
237
|
|
|
@@ -799,7 +239,7 @@ export function recommendArchitecture({
|
|
|
799
239
|
confidenceScore -= 0.1;
|
|
800
240
|
}
|
|
801
241
|
|
|
802
|
-
confidenceScore =
|
|
242
|
+
confidenceScore = Math.min(Math.max(confidenceScore, 0.35), 0.97);
|
|
803
243
|
|
|
804
244
|
const confidenceLabel = resolveConfidenceLabel(confidenceScore);
|
|
805
245
|
const lowConfidence = confidenceScore < 0.7;
|
|
@@ -813,114 +253,36 @@ export function recommendArchitecture({
|
|
|
813
253
|
uncertaintyNotes.push('Data conflict: top stack candidates are close, so trade-offs need manual confirmation.');
|
|
814
254
|
}
|
|
815
255
|
|
|
256
|
+
// Offline briefs should always be transparent about their grounding source.
|
|
257
|
+
uncertaintyNotes.push(
|
|
258
|
+
'This brief is grounded in repo markers and keyword hints only. '
|
|
259
|
+
+ 'For ecosystem-level validation, the agent should perform live web research.'
|
|
260
|
+
);
|
|
261
|
+
|
|
816
262
|
const recommendedStackFileName = strongestCandidate.stackFileName;
|
|
817
263
|
const recommendedBlueprintFileName = resolveRecommendedBlueprintFileName(recommendedStackFileName, blueprintFileNames);
|
|
264
|
+
|
|
818
265
|
const signalSummaryParts = [];
|
|
819
|
-
if (strongestCandidate.
|
|
820
|
-
signalSummaryParts.push(strongestCandidate.
|
|
266
|
+
if (strongestCandidate.matchedKeywords.length > 0) {
|
|
267
|
+
signalSummaryParts.push(`keywords: ${strongestCandidate.matchedKeywords.slice(0, 4).join(', ')}`);
|
|
821
268
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
signalSummaryParts.push(`realtime ${strongestCandidate.realtimeSignalScore.toFixed(2)}`);
|
|
269
|
+
if (strongestCandidate.detectionScore > 0) {
|
|
270
|
+
signalSummaryParts.push(`repo detection: ${strongestCandidate.detectionScore.toFixed(2)}`);
|
|
825
271
|
}
|
|
826
|
-
const signalSummary = signalSummaryParts.
|
|
272
|
+
const signalSummary = signalSummaryParts.length > 0
|
|
273
|
+
? signalSummaryParts.join('; ')
|
|
274
|
+
: 'weak signals only';
|
|
827
275
|
|
|
828
276
|
const rationaleSentences = [
|
|
829
|
-
`
|
|
830
|
-
`
|
|
277
|
+
`Stack brief: ${toTitleCase(recommendedStackFileName)} with ${toTitleCase(recommendedBlueprintFileName)}.`,
|
|
278
|
+
`Grounding signals: ${signalSummary}.`,
|
|
831
279
|
`Main trade-off: ${STACK_TRADEOFF_SUMMARIES[recommendedStackFileName] || 'validate ecosystem fit before production rollout.'}`,
|
|
832
280
|
];
|
|
833
281
|
|
|
834
282
|
if (lowConfidence) {
|
|
835
|
-
rationaleSentences.push('Confidence is low
|
|
283
|
+
rationaleSentences.push('Confidence is low — review alternatives and perform live research before finalizing.');
|
|
836
284
|
}
|
|
837
285
|
|
|
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
|
-
|
|
924
286
|
const alternatives = scoredStackCandidates
|
|
925
287
|
.slice(1, 3)
|
|
926
288
|
.map((stackCandidate) => {
|
|
@@ -930,32 +292,12 @@ export function recommendArchitecture({
|
|
|
930
292
|
blueprintFileName: alternativeBlueprintFileName,
|
|
931
293
|
oneLineTradeoff: STACK_TRADEOFF_SUMMARIES[stackCandidate.stackFileName]
|
|
932
294
|
|| 'validate fit with your runtime and team constraints.',
|
|
933
|
-
evidenceSummary: `keyword=${stackCandidate.
|
|
295
|
+
evidenceSummary: `keyword=${stackCandidate.keywordScore.toFixed(2)}, detection=${stackCandidate.detectionScore.toFixed(2)}`,
|
|
934
296
|
};
|
|
935
297
|
});
|
|
936
298
|
|
|
937
|
-
const elapsedMs = Date.now() - startedAt;
|
|
938
|
-
if (elapsedMs > boundedTimeoutMs) {
|
|
939
|
-
return buildFallbackRecommendation({
|
|
940
|
-
stackFileNames,
|
|
941
|
-
blueprintFileNames,
|
|
942
|
-
tokenBudget: boundedTokenBudget,
|
|
943
|
-
timeoutMs: boundedTimeoutMs,
|
|
944
|
-
usedTokens,
|
|
945
|
-
elapsedMs,
|
|
946
|
-
timeoutTriggered: true,
|
|
947
|
-
requestedResearchMode,
|
|
948
|
-
effectiveResearchMode,
|
|
949
|
-
snapshot: stackResearchSnapshot,
|
|
950
|
-
snapshotSourcePath,
|
|
951
|
-
realtimeGateEnabled,
|
|
952
|
-
realtimeSignalsLoaded,
|
|
953
|
-
realtimeSignalSourcePath: realtimeSignalPayloadResult.sourcePath,
|
|
954
|
-
normalizedProjectDescription: normalizedDescription,
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
|
|
958
299
|
return {
|
|
300
|
+
briefType: 'offline',
|
|
959
301
|
projectDescription: String(projectDescription || '').trim(),
|
|
960
302
|
recommendedStackFileName,
|
|
961
303
|
recommendedBlueprintFileName,
|
|
@@ -969,46 +311,16 @@ export function recommendArchitecture({
|
|
|
969
311
|
lowConfidence,
|
|
970
312
|
dataConflict,
|
|
971
313
|
repeatedOverride: false,
|
|
972
|
-
realtimeGated: requestedResearchMode === 'realtime' && !realtimeGateEnabled,
|
|
973
|
-
realtimeUnavailable: requestedResearchMode === 'realtime' && realtimeGateEnabled && !realtimeSignalsLoaded,
|
|
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,
|
|
991
|
-
researchBudget: {
|
|
992
|
-
tokenBudget: boundedTokenBudget,
|
|
993
|
-
timeoutMs: boundedTimeoutMs,
|
|
994
|
-
usedTokens: Math.min(usedTokens, boundedTokenBudget),
|
|
995
|
-
elapsedMs,
|
|
996
|
-
tokenBudgetCapped: usedTokens >= boundedTokenBudget,
|
|
997
|
-
timeoutTriggered: false,
|
|
998
314
|
},
|
|
999
315
|
};
|
|
1000
316
|
}
|
|
1001
317
|
|
|
1002
318
|
export function formatArchitectureRecommendation(architectureRecommendation) {
|
|
1003
|
-
const researchModeSummary = architectureRecommendation.research
|
|
1004
|
-
? `${architectureRecommendation.research.requestedMode} -> ${architectureRecommendation.research.effectiveMode}`
|
|
1005
|
-
: 'snapshot';
|
|
1006
319
|
const outputLines = [
|
|
1007
|
-
'\nArchitecture
|
|
320
|
+
'\nArchitecture brief (offline, repo-grounded):',
|
|
1008
321
|
`- Stack: ${toTitleCase(architectureRecommendation.recommendedStackFileName)}`,
|
|
1009
322
|
`- Blueprint: ${toTitleCase(architectureRecommendation.recommendedBlueprintFileName)}`,
|
|
1010
323
|
`- Confidence: ${architectureRecommendation.confidenceLabel} (${architectureRecommendation.confidenceScore})`,
|
|
1011
|
-
`- Research mode: ${researchModeSummary}`,
|
|
1012
324
|
'- Rationale:',
|
|
1013
325
|
...architectureRecommendation.rationaleSentences.map((sentence, sentenceIndex) => ` ${sentenceIndex + 1}. ${sentence}`),
|
|
1014
326
|
'- Alternatives:',
|
|
@@ -1048,31 +360,6 @@ export function formatArchitectureRecommendation(architectureRecommendation) {
|
|
|
1048
360
|
outputLines.push(`- Caution labels: ${cautionLabels.join(', ')}`);
|
|
1049
361
|
}
|
|
1050
362
|
|
|
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
|
-
|
|
1072
|
-
outputLines.push(
|
|
1073
|
-
`- Research guardrails: ${architectureRecommendation.researchBudget.usedTokens}/${architectureRecommendation.researchBudget.tokenBudget} tokens, ${architectureRecommendation.researchBudget.elapsedMs}ms/${architectureRecommendation.researchBudget.timeoutMs}ms`
|
|
1074
|
-
);
|
|
1075
|
-
|
|
1076
363
|
return outputLines.join('\n');
|
|
1077
364
|
}
|
|
1078
365
|
|