analyzthis_design 2.5.1 → 2.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HOW-TO-USE.md +249 -326
- package/README.md +54 -20
- package/agents/calibration-benchmark.json +187 -0
- package/agents/cards/anuj.md +1 -1
- package/agents/cards/arjun.md +1 -1
- package/agents/cards/kavi.md +2 -1
- package/agents/cards/mood-board.md +3 -2
- package/agents/cards/noor.md +1 -1
- package/agents/cards/priya.md +1 -1
- package/agents/cards/zara.md +3 -3
- package/agents/chain.json +1 -1
- package/agents/design-spec-schema.json +18 -1
- package/agents/manifests/mood-board.json +1 -0
- package/agents/model-policy.json +12 -0
- package/agents/router.json +1 -1
- package/agents/session-schema.json +12 -2
- package/dist/HOW-TO-USE.md +249 -326
- package/dist/README.md +54 -20
- package/dist/agents/calibration-benchmark.json +187 -0
- package/dist/agents/cards/anuj.md +1 -1
- package/dist/agents/cards/arjun.md +1 -1
- package/dist/agents/cards/kavi.md +2 -1
- package/dist/agents/cards/mood-board.md +3 -2
- package/dist/agents/cards/noor.md +1 -1
- package/dist/agents/cards/priya.md +1 -1
- package/dist/agents/cards/zara.md +3 -3
- package/dist/agents/chain.json +1 -1
- package/dist/agents/design-spec-schema.json +18 -1
- package/dist/agents/manifests/mood-board.json +1 -0
- package/dist/agents/model-policy.json +12 -0
- package/dist/agents/router.json +1 -1
- package/dist/agents/session-schema.json +12 -2
- package/dist/bin/cli.js +154 -5
- package/dist/lib/calibrate.js +816 -0
- package/dist/lib/chunk-executor.js +41 -1
- package/dist/lib/chunk-models.js +4 -18
- package/dist/lib/chunk-router.js +22 -6
- package/dist/lib/chunk-run.js +55 -1
- package/dist/lib/chunk-synthesis.js +18 -2
- package/dist/lib/chunk-telemetry.js +77 -17
- package/dist/lib/collect.js +15 -1
- package/dist/lib/cost.js +15 -7
- package/dist/lib/design-spec.js +32 -2
- package/dist/lib/evolution-metrics.js +23 -1
- package/dist/lib/feedback-submit.js +50 -1
- package/dist/lib/feedback.js +15 -0
- package/dist/lib/lessons.js +8 -0
- package/dist/lib/mcp-server.js +119 -5
- package/dist/lib/model-policy.js +174 -0
- package/dist/lib/model-registry.js +71 -0
- package/dist/lib/moodboard.js +162 -28
- package/dist/lib/orchestrator/run.js +67 -8
- package/dist/lib/outcome.js +7 -2
- package/dist/lib/output-quality.js +95 -0
- package/dist/lib/reference-pack.js +9 -1
- package/dist/lib/session.js +48 -4
- package/dist/lib/system-prompt.js +3 -0
- package/dist/lib/ui-libraries.js +232 -0
- package/dist/skills/anuj/SKILL.md +2 -0
- package/dist/skills/arjun/references/lens.md +2 -0
- package/dist/skills/design-director/SKILL.md +2 -1
- package/dist/skills/design-reference/SKILL.md +6 -0
- package/dist/skills/design-reference/motion.csv +11 -0
- package/dist/skills/design-reference/schema.json +12 -0
- package/dist/skills/design-reference/stacks/shadcn.csv +6 -0
- package/dist/skills/design-reference/ui-libraries.csv +46 -0
- package/dist/skills/design-spec/SKILL.md +1 -1
- package/dist/skills/getting-started/SKILL.md +2 -2
- package/dist/skills/kavi/SKILL.md +3 -2
- package/dist/skills/mood-board/SKILL.md +29 -3
- package/dist/skills/noor/SKILL.md +2 -0
- package/dist/skills/priya/SKILL.md +3 -0
- package/dist/skills/zara/SKILL.md +50 -4
- package/package.json +5 -2
- package/skills/anuj/SKILL.md +2 -0
- package/skills/arjun/references/lens.md +2 -0
- package/skills/design-director/SKILL.md +2 -1
- package/skills/design-reference/SKILL.md +6 -0
- package/skills/design-reference/motion.csv +11 -0
- package/skills/design-reference/schema.json +12 -0
- package/skills/design-reference/stacks/shadcn.csv +6 -0
- package/skills/design-reference/ui-libraries.csv +46 -0
- package/skills/design-spec/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +2 -2
- package/skills/kavi/SKILL.md +3 -2
- package/skills/mood-board/SKILL.md +29 -3
- package/skills/noor/SKILL.md +2 -0
- package/skills/priya/SKILL.md +3 -0
- package/skills/zara/SKILL.md +50 -4
package/dist/lib/moodboard.js
CHANGED
|
@@ -66,11 +66,43 @@ function writeJson(filePath, obj) {
|
|
|
66
66
|
fs.writeFileSync(filePath, JSON.stringify(obj, null, 2));
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function persistBoard(board, workspaceDir) {
|
|
70
|
+
var projectId = board.project_id;
|
|
71
|
+
var bDir = boardDir(projectId, board.board_id);
|
|
72
|
+
board.updated_at = new Date().toISOString();
|
|
73
|
+
writeJson(path.join(bDir, 'board.json'), board);
|
|
74
|
+
var ws = workspaceDir || defaultWorkspaceDir();
|
|
75
|
+
ensureDir(ws);
|
|
76
|
+
var workspacePath = path.join(ws, board.board_id + '.json');
|
|
77
|
+
try { fs.writeFileSync(workspacePath, JSON.stringify(board, null, 2)); }
|
|
78
|
+
catch (e) { /* best effort */ }
|
|
79
|
+
return { board: board, boardDir: bDir, workspacePath: workspacePath };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function defaultScoutBrief(task, round, vetoNotes) {
|
|
83
|
+
var lines = [
|
|
84
|
+
'Zara Scout Pass, round ' + (round || 1) + '.',
|
|
85
|
+
'Job: ' + String(task).slice(0, 240),
|
|
86
|
+
'Find 3-5 UI frames. Search Google Images, Dribbble, or Pinterest (or live product pages). Open them. Do not invent URLs. Do not generate AI comps.',
|
|
87
|
+
'Lens: Peak-End, first 2 seconds. Same job? Peak feeling? One steal, one refuse. Concept shots allowed if you say they are concept. Never sell a Dribbble hero as shipped product.',
|
|
88
|
+
'Skip generic AI-purple heroes and cinematic GSAP landings unless this job is a marketing page.',
|
|
89
|
+
'For each frame write: steal (one thing), refuse (one thing), peak (one first impression).',
|
|
90
|
+
'Then stop. The jury (Arjun, Noor, Priya, Meera) votes. You do not score your own picks.',
|
|
91
|
+
];
|
|
92
|
+
if (vetoNotes) {
|
|
93
|
+
lines.push('Last jury sent you back. Constraints: ' + String(vetoNotes).slice(0, 500));
|
|
94
|
+
}
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
69
98
|
// ─── Source discovery for mood references ───────────────────────────────────
|
|
70
99
|
|
|
71
100
|
// Known hosts that expose an image or a design article we can fetch as text.
|
|
72
101
|
var MOOD_REFERENCE_HOSTS = [
|
|
73
102
|
'dribbble.com',
|
|
103
|
+
'pinterest.com',
|
|
104
|
+
'www.pinterest.com',
|
|
105
|
+
'pin.it',
|
|
74
106
|
'mobbin.com',
|
|
75
107
|
'behance.net',
|
|
76
108
|
'www.behance.net',
|
|
@@ -362,7 +394,8 @@ async function buildMoodBoard(opts) {
|
|
|
362
394
|
|
|
363
395
|
var refs = [];
|
|
364
396
|
var userRefs = opts.userReferences || [];
|
|
365
|
-
var
|
|
397
|
+
var kind = opts.kind === 'ui' ? 'ui' : 'mood';
|
|
398
|
+
var discover = kind === 'ui' ? !!(opts.urls && opts.urls.length) : opts.discover !== false;
|
|
366
399
|
|
|
367
400
|
if (discover) {
|
|
368
401
|
var plan = discoverReferenceUrls({ task: task, urls: opts.urls || [], config: config });
|
|
@@ -397,28 +430,27 @@ async function buildMoodBoard(opts) {
|
|
|
397
430
|
var board = {
|
|
398
431
|
board_id: boardId,
|
|
399
432
|
project_id: projectId,
|
|
433
|
+
kind: kind,
|
|
400
434
|
task: task,
|
|
401
435
|
created_at: new Date().toISOString(),
|
|
402
436
|
updated_at: new Date().toISOString(),
|
|
403
437
|
references: refs,
|
|
438
|
+
frames: [],
|
|
404
439
|
design_system_pack: dsPack,
|
|
405
440
|
deliberation: null,
|
|
406
441
|
synthesis: null,
|
|
407
442
|
user_contributions: userRefs.length,
|
|
443
|
+
scout: kind === 'ui' ? {
|
|
444
|
+
persona: 'zara',
|
|
445
|
+
round: 1,
|
|
446
|
+
max_rounds: 3,
|
|
447
|
+
status: 'awaiting_scout',
|
|
448
|
+
brief: defaultScoutBrief(task, 1, ''),
|
|
449
|
+
last_veto: '',
|
|
450
|
+
} : null,
|
|
408
451
|
};
|
|
409
452
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// Copy/symlink to workspace so the user can open it directly.
|
|
413
|
-
ensureDir(workspaceDir);
|
|
414
|
-
var workspacePath = path.join(workspaceDir, boardId + '.json');
|
|
415
|
-
try {
|
|
416
|
-
fs.writeFileSync(workspacePath, JSON.stringify(board, null, 2));
|
|
417
|
-
} catch (e) {
|
|
418
|
-
// workspace write is best-effort (e.g. read-only cwd)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return { board: board, boardDir: bDir, workspacePath: workspacePath };
|
|
453
|
+
return persistBoard(board, workspaceDir);
|
|
422
454
|
}
|
|
423
455
|
|
|
424
456
|
function detectStack(vaultPath) {
|
|
@@ -443,17 +475,39 @@ async function critiqueMoodBoard(opts) {
|
|
|
443
475
|
var board = loadJson(boardPath);
|
|
444
476
|
if (!board) throw new Error('Board not found: ' + boardId);
|
|
445
477
|
|
|
446
|
-
var
|
|
478
|
+
var isUi = board.kind === 'ui';
|
|
479
|
+
var experts = opts.experts || (isUi ? ['arjun', 'meera', 'priya', 'noor'] : ['arjun', 'meera', 'priya', 'zara', 'noor']);
|
|
447
480
|
var task = opts.task || board.task;
|
|
448
481
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
482
|
+
if (isUi && (!board.frames || !board.frames.length)) {
|
|
483
|
+
var need = new Error('Zara has not scouted yet. Add 3-5 frames, then critique.');
|
|
484
|
+
need.code = 'NEED_SCOUT';
|
|
485
|
+
throw need;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
var refsSummary = isUi
|
|
489
|
+
? board.frames.map(function(f, idx) {
|
|
490
|
+
return '[' + (idx + 1) + '] ' + f.title + ' round=' + (f.round || 1) +
|
|
491
|
+
' steal=' + (f.steal || '') + ' refuse=' + (f.refuse || '') +
|
|
492
|
+
' peak=' + (f.peak || '') + ' — ' + (f.url || '').slice(0, 120);
|
|
493
|
+
}).join('\n')
|
|
494
|
+
: board.references.map(function(r, idx) {
|
|
495
|
+
return '[' + (idx + 1) + '] ' + r.title + ' (' + r.type + ') tags=' + (r.tags || []).join(',') + ' — ' + (r.url || r.description || '').slice(0, 120);
|
|
496
|
+
}).join('\n');
|
|
453
497
|
|
|
454
498
|
var dsText = formatDsPack(board.design_system_pack);
|
|
455
499
|
|
|
456
|
-
var critiqueTask = [
|
|
500
|
+
var critiqueTask = isUi ? [
|
|
501
|
+
'UI image-board jury for: ' + task,
|
|
502
|
+
'Zara scouted these frames. She does not vote. KEEP or SKIP each frame with one because-sentence.',
|
|
503
|
+
'Do not reward cinematic marketing motion on a daily working surface.',
|
|
504
|
+
'',
|
|
505
|
+
'Frames:\n' + refsSummary,
|
|
506
|
+
'',
|
|
507
|
+
dsText,
|
|
508
|
+
'',
|
|
509
|
+
'Use the Honeycomb rigor matrix. Rank the best 2-3 frames. Verdict SHIP only if the set fits the job. REVISE or BLOCK sends Zara to scout again.'
|
|
510
|
+
].join('\n') : [
|
|
457
511
|
'Mood-board critique for: ' + task,
|
|
458
512
|
'',
|
|
459
513
|
'References:\n' + refsSummary,
|
|
@@ -463,7 +517,6 @@ async function critiqueMoodBoard(opts) {
|
|
|
463
517
|
'Each persona must use the Honeycomb rigor matrix to evaluate which references are appropriate for the user and why. Output scores, top concerns, and a ranked shortlist of the best 2-3 references. Then participate in adversarial deliberation until consensus.'
|
|
464
518
|
].join('\n');
|
|
465
519
|
|
|
466
|
-
// Use the orchestrator directly, but force the experts to the mood-board set.
|
|
467
520
|
var result = await orchestratorRun({
|
|
468
521
|
task: critiqueTask,
|
|
469
522
|
experts: experts,
|
|
@@ -479,17 +532,94 @@ async function critiqueMoodBoard(opts) {
|
|
|
479
532
|
|
|
480
533
|
board.deliberation = result.deliberation;
|
|
481
534
|
board.synthesis = result.synthesis;
|
|
482
|
-
board.updated_at = new Date().toISOString();
|
|
483
535
|
board.critique_task = critiqueTask;
|
|
536
|
+
if (isUi) applyJuryOutcome(board, result.synthesis);
|
|
484
537
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
538
|
+
var saved = persistBoard(board, opts.workspaceDir || defaultWorkspaceDir());
|
|
539
|
+
return { board: saved.board, boardDir: saved.boardDir, workspacePath: saved.workspacePath, orchestratorResult: result };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function applyJuryOutcome(board, synthesis) {
|
|
543
|
+
var verdict = String((synthesis && synthesis.verdict) || '').toUpperCase();
|
|
544
|
+
var scout = board.scout || {};
|
|
545
|
+
var maxRounds = scout.max_rounds || 3;
|
|
546
|
+
var notes = '';
|
|
547
|
+
if (synthesis && synthesis.summary) notes = synthesis.summary;
|
|
548
|
+
else if (synthesis && synthesis.top3 && synthesis.top3.join) notes = synthesis.top3.join('; ');
|
|
549
|
+
else notes = verdict;
|
|
550
|
+
|
|
551
|
+
if (verdict === 'SHIP') {
|
|
552
|
+
scout.status = 'done';
|
|
553
|
+
scout.last_veto = '';
|
|
554
|
+
} else if ((scout.round || 1) >= maxRounds) {
|
|
555
|
+
scout.status = 'raj';
|
|
556
|
+
scout.last_veto = String(notes).slice(0, 600);
|
|
557
|
+
scout.brief = 'Max scout rounds reached. Raj or the designer picks from the frames on this board.\n' + scout.last_veto;
|
|
558
|
+
} else {
|
|
559
|
+
scout.round = (scout.round || 1) + 1;
|
|
560
|
+
scout.status = 'awaiting_scout';
|
|
561
|
+
scout.last_veto = String(notes).slice(0, 600);
|
|
562
|
+
scout.brief = defaultScoutBrief(board.task, scout.round, scout.last_veto);
|
|
563
|
+
}
|
|
564
|
+
board.scout = scout;
|
|
565
|
+
}
|
|
491
566
|
|
|
492
|
-
|
|
567
|
+
function addFrames(opts) {
|
|
568
|
+
opts = opts || {};
|
|
569
|
+
var projectId = opts.project || session.getProjectId();
|
|
570
|
+
var boardId = opts.boardId;
|
|
571
|
+
var incoming = opts.frames || (opts.frame ? [opts.frame] : []);
|
|
572
|
+
if (!boardId || !incoming.length) throw new Error('--board and at least one frame are required');
|
|
573
|
+
|
|
574
|
+
var bDir = boardDir(projectId, boardId);
|
|
575
|
+
var board = loadJson(path.join(bDir, 'board.json'));
|
|
576
|
+
if (!board) throw new Error('Board not found: ' + boardId);
|
|
577
|
+
if (board.kind !== 'ui') throw new Error('Frames belong on a --kind ui board.');
|
|
578
|
+
|
|
579
|
+
board.frames = board.frames || [];
|
|
580
|
+
var round = (board.scout && board.scout.round) || 1;
|
|
581
|
+
for (var i = 0; i < incoming.length; i++) {
|
|
582
|
+
var f = incoming[i] || {};
|
|
583
|
+
if (!f.url) continue;
|
|
584
|
+
board.frames.push({
|
|
585
|
+
id: generateBoardId(),
|
|
586
|
+
url: f.url,
|
|
587
|
+
title: f.title || 'Frame',
|
|
588
|
+
steal: f.steal || '',
|
|
589
|
+
refuse: f.refuse || '',
|
|
590
|
+
peak: f.peak || '',
|
|
591
|
+
surface: f.surface || '',
|
|
592
|
+
source: 'zara_scout',
|
|
593
|
+
round: round,
|
|
594
|
+
added_at: new Date().toISOString(),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (board.scout) board.scout.status = 'awaiting_jury';
|
|
598
|
+
return persistBoard(board, opts.workspaceDir);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function getBoard(opts) {
|
|
602
|
+
opts = opts || {};
|
|
603
|
+
var projectId = opts.project || session.getProjectId();
|
|
604
|
+
var boardId = opts.boardId;
|
|
605
|
+
if (!boardId) throw new Error('board is required');
|
|
606
|
+
var board = loadJson(path.join(boardDir(projectId, boardId), 'board.json'));
|
|
607
|
+
if (!board) throw new Error('Board not found: ' + boardId);
|
|
608
|
+
return board;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function recordJuryVerdict(opts) {
|
|
612
|
+
opts = opts || {};
|
|
613
|
+
var board = getBoard(opts);
|
|
614
|
+
if (board.kind !== 'ui') throw new Error('Jury verdict belongs on a ui board.');
|
|
615
|
+
var synthesis = {
|
|
616
|
+
verdict: String(opts.verdict || '').toUpperCase(),
|
|
617
|
+
summary: opts.summary || '',
|
|
618
|
+
top3: opts.top3 || [],
|
|
619
|
+
};
|
|
620
|
+
applyJuryOutcome(board, synthesis);
|
|
621
|
+
board.synthesis = synthesis;
|
|
622
|
+
return persistBoard(board, opts.workspaceDir);
|
|
493
623
|
}
|
|
494
624
|
|
|
495
625
|
// ─── Add user reference and rerun ─────────────────────────────────────────────
|
|
@@ -556,6 +686,10 @@ module.exports = {
|
|
|
556
686
|
buildMoodBoard: buildMoodBoard,
|
|
557
687
|
critiqueMoodBoard: critiqueMoodBoard,
|
|
558
688
|
addUserReferenceAndRerun: addUserReferenceAndRerun,
|
|
689
|
+
addFrames: addFrames,
|
|
690
|
+
getBoard: getBoard,
|
|
691
|
+
recordJuryVerdict: recordJuryVerdict,
|
|
692
|
+
defaultScoutBrief: defaultScoutBrief,
|
|
559
693
|
listBoards: listBoards,
|
|
560
694
|
boardDir: boardDir,
|
|
561
695
|
formatDsPack: formatDsPack,
|
|
@@ -30,6 +30,7 @@ const fs = require('fs');
|
|
|
30
30
|
const path = require('path');
|
|
31
31
|
const os = require('os');
|
|
32
32
|
const https = require('https');
|
|
33
|
+
const crypto = require('crypto');
|
|
33
34
|
const session = require('../session');
|
|
34
35
|
const retrieve = require('../retrieve');
|
|
35
36
|
const cache = require('../cache');
|
|
@@ -40,7 +41,10 @@ const ranker = require('../ranker');
|
|
|
40
41
|
const knowledge = require('../knowledge');
|
|
41
42
|
const chunkRun = require('../chunk-run');
|
|
42
43
|
const chunkModels = require('../chunk-models');
|
|
44
|
+
const modelPolicy = require('../model-policy');
|
|
45
|
+
const modelRegistry = require('../model-registry');
|
|
43
46
|
const referencePack = require('../reference-pack');
|
|
47
|
+
const outputQuality = require('../output-quality');
|
|
44
48
|
const { callHostLlm, createRun, HostLlmPendingError, printDeviInstructions, findLatestRun, loadManifest: loadHostManifest } = require('../host-llm');
|
|
45
49
|
const { resolveDefaultProvider, resolveTierProvider } = require('../provider');
|
|
46
50
|
const synthesis = require('../synthesis');
|
|
@@ -229,7 +233,7 @@ function classifyEffort({ routing, digest, manifest, scopedMode, fullChain, isDe
|
|
|
229
233
|
* 3. chain.tiers[manifest.tier] (legacy fallback, v1.9)
|
|
230
234
|
* 4. manifest.max_output_tokens (last resort for the cap)
|
|
231
235
|
*/
|
|
232
|
-
function resolveModel({ manifest, effort, chain, config, defaultProvider, defaultModel, gateName }) {
|
|
236
|
+
function resolveModel({ manifest, effort, chain, config, defaultProvider, defaultModel, gateName, explicitProvider, explicitModel }) {
|
|
233
237
|
let effKey = effort;
|
|
234
238
|
const gateOverride = chain.gate_override;
|
|
235
239
|
if (gateName && gateOverride && (gateOverride.applies_to || []).includes(gateName)) {
|
|
@@ -240,12 +244,35 @@ function resolveModel({ manifest, effort, chain, config, defaultProvider, defaul
|
|
|
240
244
|
const matrix = (chain.effort_matrix && chain.effort_matrix[effKey]) || {};
|
|
241
245
|
const legacy = (config.orchestrator?.tiers && config.orchestrator?.tiers[manifest.tier]) || {};
|
|
242
246
|
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
let provider = override.provider || matrix.provider || legacy.provider || defaultProvider;
|
|
248
|
+
let model = override.model || matrix.model || legacy.model || defaultModel;
|
|
249
|
+
let requestedModel = explicitModel;
|
|
250
|
+
if (explicitProvider && !requestedModel) {
|
|
251
|
+
if (config.orchestrator?.provider === explicitProvider && config.orchestrator?.model) {
|
|
252
|
+
requestedModel = config.orchestrator.model;
|
|
253
|
+
} else if (explicitProvider === 'ollama') {
|
|
254
|
+
requestedModel = config.ollama?.model || 'qwen2.5';
|
|
255
|
+
} else {
|
|
256
|
+
const known = modelRegistry.MODELS.find((item) =>
|
|
257
|
+
item.provider === explicitProvider && (!item.effort || item.effort.includes(effKey)));
|
|
258
|
+
requestedModel = known?.model;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const policy = modelPolicy.resolveAssignment({
|
|
262
|
+
persona: manifest.id,
|
|
263
|
+
effort: effKey,
|
|
264
|
+
config,
|
|
265
|
+
explicitProvider,
|
|
266
|
+
explicitModel: requestedModel,
|
|
267
|
+
});
|
|
268
|
+
if (policy) {
|
|
269
|
+
provider = policy.spec.provider;
|
|
270
|
+
model = policy.spec.model;
|
|
271
|
+
}
|
|
245
272
|
const resolvedProvider = resolveTierProvider(provider, defaultProvider);
|
|
246
273
|
const maxTokens = override.max_output_tokens || matrix.max_output_tokens
|
|
247
274
|
|| config.orchestrator?.max_tokens?.[manifest.tier] || manifest.max_output_tokens;
|
|
248
|
-
return { provider: resolvedProvider, model, maxTokens, effort: effKey };
|
|
275
|
+
return { provider: resolvedProvider, model, maxTokens, effort: effKey, policy: policy && policy.source };
|
|
249
276
|
}
|
|
250
277
|
|
|
251
278
|
function ollamaJson(baseUrl, model, system, user, maxTokens) {
|
|
@@ -500,6 +527,7 @@ async function runUnchunked(opts = {}) {
|
|
|
500
527
|
state = session.show({ project: init.projectId });
|
|
501
528
|
}
|
|
502
529
|
const projectId = state.project_id;
|
|
530
|
+
const runId = 'run-' + Date.now() + '-' + crypto.randomBytes(4).toString('hex');
|
|
503
531
|
|
|
504
532
|
// Host LLM run directory (Devi bridge — no API keys)
|
|
505
533
|
let hostContext = null;
|
|
@@ -554,6 +582,8 @@ async function runUnchunked(opts = {}) {
|
|
|
554
582
|
session.update({
|
|
555
583
|
project: projectId,
|
|
556
584
|
patch: {
|
|
585
|
+
active_run: { id: runId, mode: 'unchunked', at: new Date().toISOString() },
|
|
586
|
+
persona_outputs: {},
|
|
557
587
|
routing_decision: routing,
|
|
558
588
|
figma_node: figma ? { url: figma, confirmed: false } : state.figma_node,
|
|
559
589
|
mode: 'assess_only',
|
|
@@ -603,8 +633,13 @@ async function runUnchunked(opts = {}) {
|
|
|
603
633
|
let cacheHits = 0;
|
|
604
634
|
let costUsd = 0;
|
|
605
635
|
|
|
606
|
-
async function loadPersonaContext(personaId, { isProduceRound, isObjectionRound }) {
|
|
636
|
+
async function loadPersonaContext(personaId, { isProduceRound, isObjectionRound, gateName = null }) {
|
|
607
637
|
const manifest = loadManifest(personaId);
|
|
638
|
+
const effectiveGateName = gateName || (
|
|
639
|
+
personaId === 'arjun' && dsAtRisk.length
|
|
640
|
+
? 'ds_gate'
|
|
641
|
+
: null
|
|
642
|
+
);
|
|
608
643
|
const useDeepWireframe = ideation_chain && wireframePersonas.has(personaId);
|
|
609
644
|
const isReviewRound = isObjectionRound && !isProduceRound;
|
|
610
645
|
|
|
@@ -645,7 +680,9 @@ async function runUnchunked(opts = {}) {
|
|
|
645
680
|
});
|
|
646
681
|
const resolved = resolveModel({
|
|
647
682
|
manifest, effort: isObjectionRound ? 'trivial' : effort, chain, config,
|
|
648
|
-
defaultProvider: provider, defaultModel: model, gateName:
|
|
683
|
+
defaultProvider: provider, defaultModel: model, gateName: effectiveGateName,
|
|
684
|
+
explicitProvider: opts.provider && opts.provider !== 'auto' ? opts.provider : (opts.model ? provider : null),
|
|
685
|
+
explicitModel: opts.model || null,
|
|
649
686
|
});
|
|
650
687
|
|
|
651
688
|
let finalSystem = system;
|
|
@@ -701,7 +738,12 @@ async function runUnchunked(opts = {}) {
|
|
|
701
738
|
});
|
|
702
739
|
const inTok = Math.ceil((system.length + user.length) / 4);
|
|
703
740
|
const outTok = Math.ceil(text.length / 4);
|
|
704
|
-
const
|
|
741
|
+
const actualModel = callOpts.provider === 'host' ? 'unknown' : callOpts.model;
|
|
742
|
+
const quality = outputQuality.validateOutput(text, {
|
|
743
|
+
persona: personaId,
|
|
744
|
+
outputFormat: 'persona_lite',
|
|
745
|
+
});
|
|
746
|
+
const price = callOpts.provider !== 'host' && config.pricing && config.pricing[callOpts.model];
|
|
705
747
|
if (price) {
|
|
706
748
|
costUsd += (inTok / 1e6) * (price.input_per_m || 0) + (outTok / 1e6) * (price.output_per_m || 0);
|
|
707
749
|
}
|
|
@@ -712,10 +754,27 @@ async function runUnchunked(opts = {}) {
|
|
|
712
754
|
output_tokens_est: outTok,
|
|
713
755
|
structured_output: parseStructuredOutput(text, personaId),
|
|
714
756
|
full_prompt: { system, user },
|
|
757
|
+
at: new Date().toISOString(),
|
|
758
|
+
run_id: runId,
|
|
759
|
+
attribution: {
|
|
760
|
+
provider: callOpts.provider,
|
|
761
|
+
model: actualModel,
|
|
762
|
+
model_id: callOpts.provider + '/' + actualModel,
|
|
763
|
+
effort: callOpts.effort || 'standard',
|
|
764
|
+
policy_version: modelPolicy.VERSION,
|
|
765
|
+
prompt_hash: modelPolicy.promptHash(personaId),
|
|
766
|
+
validator_version: outputQuality.VERSION,
|
|
767
|
+
},
|
|
768
|
+
quality: quality,
|
|
715
769
|
effort_log_entry: {
|
|
716
770
|
persona: personaId,
|
|
717
771
|
effort: callOpts.effort || 'standard',
|
|
718
|
-
|
|
772
|
+
provider: callOpts.provider,
|
|
773
|
+
model: actualModel,
|
|
774
|
+
model_id: callOpts.provider + '/' + actualModel,
|
|
775
|
+
input_tokens: inTok,
|
|
776
|
+
output_tokens: outTok,
|
|
777
|
+
run_id: runId,
|
|
719
778
|
scoped_mode: null,
|
|
720
779
|
},
|
|
721
780
|
};
|
package/dist/lib/outcome.js
CHANGED
|
@@ -118,7 +118,9 @@ function inferAllOutcomes(opts) {
|
|
|
118
118
|
value: result.inferred,
|
|
119
119
|
confidence: result.confidence,
|
|
120
120
|
reason: result.reason,
|
|
121
|
-
at: new Date().toISOString()
|
|
121
|
+
at: new Date().toISOString(),
|
|
122
|
+
run_id: entry.run_id || null,
|
|
123
|
+
attribution: entry.attribution || null
|
|
122
124
|
};
|
|
123
125
|
session.update({ project: pid, patch: { outcome: outcomePatch } });
|
|
124
126
|
inferred++;
|
|
@@ -143,6 +145,7 @@ function confirmOutcome(opts) {
|
|
|
143
145
|
|
|
144
146
|
var state = session.show({ project: project });
|
|
145
147
|
if (!state) throw new Error('No session found');
|
|
148
|
+
var output = state.persona_outputs && state.persona_outputs[persona];
|
|
146
149
|
|
|
147
150
|
var outcomePatch = {
|
|
148
151
|
inferred: { ...(state.outcome && state.outcome.inferred ? state.outcome.inferred : {}) },
|
|
@@ -150,7 +153,9 @@ function confirmOutcome(opts) {
|
|
|
150
153
|
};
|
|
151
154
|
outcomePatch.confirmed[persona] = {
|
|
152
155
|
value: outcome,
|
|
153
|
-
at: new Date().toISOString()
|
|
156
|
+
at: new Date().toISOString(),
|
|
157
|
+
run_id: output && output.run_id || null,
|
|
158
|
+
attribution: output && output.attribution || null
|
|
154
159
|
};
|
|
155
160
|
delete outcomePatch.inferred[persona];
|
|
156
161
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format-aware output validation shared by normal runs and calibration.
|
|
5
|
+
* Hard failures may trigger a bounded retry; warnings only affect quality score.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var VERSION = 1;
|
|
9
|
+
|
|
10
|
+
var PERSONA_FORBIDDEN = {
|
|
11
|
+
arjun: [/delight pass/i, /ARR impact/i],
|
|
12
|
+
meera: [/color contrast/i, /typography scale/i, /WCAG/i],
|
|
13
|
+
priya: [/delight moment/i, /north-star metric/i],
|
|
14
|
+
zara: [/--color-primary/i, /contrast ratio/i, /fix the token/i],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function issue(code, message, hard) {
|
|
18
|
+
return { code: code, message: message, hard: !!hard };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function validateOutput(text, opts) {
|
|
22
|
+
opts = opts || {};
|
|
23
|
+
text = typeof text === 'string' ? text.trim() : '';
|
|
24
|
+
var format = String(opts.outputFormat || opts.format || 'generic').toLowerCase();
|
|
25
|
+
var persona = String(opts.persona || '').toLowerCase();
|
|
26
|
+
var issues = [];
|
|
27
|
+
|
|
28
|
+
if (!text) issues.push(issue('empty', 'The model returned no text.', true));
|
|
29
|
+
if (text && text.length < 40) issues.push(issue('too_short', 'The answer is too short to evaluate.', true));
|
|
30
|
+
|
|
31
|
+
if (format === 'persona_lite' || format === 'lite') {
|
|
32
|
+
if (!/##\s*[A-Za-z]+(?:\s*[—-]\s*Lite)?/i.test(text)) {
|
|
33
|
+
issues.push(issue('missing_header', 'Missing persona output header.', false));
|
|
34
|
+
}
|
|
35
|
+
if (!/Top\s*2|fix|verdict|no delight needed/i.test(text)) {
|
|
36
|
+
issues.push(issue('missing_action', 'Missing an actionable finding or verdict.', true));
|
|
37
|
+
}
|
|
38
|
+
} else if (format === 'ux_audit') {
|
|
39
|
+
if (!/usab|access|hierarch|visual|UX/i.test(text)) {
|
|
40
|
+
issues.push(issue('missing_audit', 'UX audit has no UX or visual assessment.', true));
|
|
41
|
+
}
|
|
42
|
+
if (!/fix|recommend|change|keep|revise|ship|block/i.test(text)) {
|
|
43
|
+
issues.push(issue('missing_action', 'UX audit has no actionable conclusion.', true));
|
|
44
|
+
}
|
|
45
|
+
} else if (format === 'premise_assessment' || format === 'premise_check') {
|
|
46
|
+
if (!/premise|problem|assumption|framing|right problem/i.test(text)) {
|
|
47
|
+
issues.push(issue('missing_premise', 'Premise assessment does not examine the framing.', true));
|
|
48
|
+
}
|
|
49
|
+
if (!/proceed|change|reject|accept|revise|evidence/i.test(text)) {
|
|
50
|
+
issues.push(issue('missing_decision', 'Premise assessment has no decision or evidence request.', true));
|
|
51
|
+
}
|
|
52
|
+
} else if (format === 'synthesis') {
|
|
53
|
+
if (!/\b(SHIP|REVISE|BLOCK)\b/i.test(text)) {
|
|
54
|
+
issues.push(issue('missing_verdict', 'Synthesis has no SHIP, REVISE, or BLOCK verdict.', true));
|
|
55
|
+
}
|
|
56
|
+
if (!/top\s*3|action|next step|fix/i.test(text)) {
|
|
57
|
+
issues.push(issue('missing_actions', 'Synthesis has no next actions.', false));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var forbidden = PERSONA_FORBIDDEN[persona] || [];
|
|
62
|
+
for (var i = 0; i < forbidden.length; i++) {
|
|
63
|
+
if (forbidden[i].test(text)) {
|
|
64
|
+
issues.push(issue('forbidden_scope', 'The persona crossed into a forbidden scope.', true));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
var hardFailures = issues.filter(function(item) { return item.hard; });
|
|
69
|
+
var warnings = issues.filter(function(item) { return !item.hard; });
|
|
70
|
+
var score = Math.max(0, 1 - hardFailures.length * 0.35 - warnings.length * 0.1);
|
|
71
|
+
return {
|
|
72
|
+
version: VERSION,
|
|
73
|
+
format: format,
|
|
74
|
+
pass: hardFailures.length === 0,
|
|
75
|
+
score: Number(score.toFixed(2)),
|
|
76
|
+
issues: issues,
|
|
77
|
+
hard_failures: hardFailures.length,
|
|
78
|
+
warnings: warnings.length,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function classifyError(error) {
|
|
83
|
+
var message = String(error && error.message ? error.message : error || '');
|
|
84
|
+
if (/429|rate.?limit|too many requests|quota/i.test(message)) return 'rate_limit';
|
|
85
|
+
if (/timeout|timed out|ECONNRESET|ENOTFOUND|network|socket/i.test(message)) return 'transport';
|
|
86
|
+
if (/401|403|unauthori|forbidden|api.?key/i.test(message)) return 'auth';
|
|
87
|
+
return 'provider';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
VERSION: VERSION,
|
|
92
|
+
PERSONA_FORBIDDEN: PERSONA_FORBIDDEN,
|
|
93
|
+
validateOutput: validateOutput,
|
|
94
|
+
classifyError: classifyError,
|
|
95
|
+
};
|
|
@@ -37,6 +37,7 @@ var REFERENCE_MAP = {
|
|
|
37
37
|
{ file: 'ux-guidelines.csv', column: 'Category', limit: 3 },
|
|
38
38
|
{ file: 'ui-reasoning.csv', column: 'UI Category', limit: 3 },
|
|
39
39
|
{ file: 'charts.csv', column: 'Data Type', limit: 3 },
|
|
40
|
+
{ file: 'ui-libraries.csv', column: 'Best For Tags', limit: 3 },
|
|
40
41
|
],
|
|
41
42
|
zara: [
|
|
42
43
|
{ file: 'colors.csv', column: 'Product Type', limit: 3 },
|
|
@@ -44,6 +45,8 @@ var REFERENCE_MAP = {
|
|
|
44
45
|
{ file: 'styles.csv', column: 'Best For Tags', limit: 3 },
|
|
45
46
|
{ file: 'landing.csv', column: 'Keywords', limit: 3 },
|
|
46
47
|
{ file: 'icons.csv', column: 'Keywords', limit: 3 },
|
|
48
|
+
{ file: 'motion.csv', column: 'Category', limit: 3 },
|
|
49
|
+
{ file: 'ui-libraries.csv', column: 'Job', limit: 3 },
|
|
47
50
|
],
|
|
48
51
|
meera: [
|
|
49
52
|
{ file: 'products.csv', column: 'Product Type', limit: 3 },
|
|
@@ -55,14 +58,17 @@ var REFERENCE_MAP = {
|
|
|
55
58
|
{ file: 'ui-reasoning.csv', column: 'UI Category', limit: 3 },
|
|
56
59
|
{ file: 'app-interface.csv', column: 'Keywords', limit: 3 },
|
|
57
60
|
{ file: 'icons.csv', column: 'Keywords', limit: 3 },
|
|
61
|
+
{ file: 'ui-libraries.csv', column: 'Job', limit: 3 },
|
|
58
62
|
],
|
|
59
63
|
anuj: [
|
|
60
64
|
{ file: 'ux-guidelines.csv', column: 'Category', limit: 3 },
|
|
61
65
|
{ file: 'app-interface.csv', column: 'Keywords', limit: 3 },
|
|
62
66
|
{ file: 'stacks/shadcn.csv', column: 'Category', limit: 3 },
|
|
67
|
+
{ file: 'ui-libraries.csv', column: 'Job', limit: 3 },
|
|
63
68
|
],
|
|
64
69
|
priya: [
|
|
65
70
|
{ file: 'react-performance.csv', column: 'Category', limit: 3 },
|
|
71
|
+
{ file: 'ui-libraries.csv', column: 'Job', limit: 3 },
|
|
66
72
|
],
|
|
67
73
|
raj: [
|
|
68
74
|
{ file: 'products.csv', column: 'Product Type', limit: 3 },
|
|
@@ -101,7 +107,9 @@ function getReferenceSpec(id, state) {
|
|
|
101
107
|
if (!Array.isArray(specs)) specs = [specs];
|
|
102
108
|
if (id === 'priya') {
|
|
103
109
|
var stack = detectStack(state && state.vault_path);
|
|
104
|
-
if (stack)
|
|
110
|
+
if (stack) {
|
|
111
|
+
return [{ file: 'stacks/' + stack + '.csv', column: 'Category', limit: 3 }].concat(specs);
|
|
112
|
+
}
|
|
105
113
|
}
|
|
106
114
|
return specs;
|
|
107
115
|
}
|
package/dist/lib/session.js
CHANGED
|
@@ -49,6 +49,7 @@ function defaultState(projectId) {
|
|
|
49
49
|
},
|
|
50
50
|
information_hierarchy: { declared_by: 'undeclared', ranking: [] },
|
|
51
51
|
routing_decision: { problem_type: '', experts: [], reason: '' },
|
|
52
|
+
active_run: null,
|
|
52
53
|
persona_outputs: {},
|
|
53
54
|
full_prompts: {},
|
|
54
55
|
structured_outputs: {},
|
|
@@ -64,6 +65,13 @@ function defaultState(projectId) {
|
|
|
64
65
|
},
|
|
65
66
|
vault_path: '',
|
|
66
67
|
last_collect_at: '',
|
|
68
|
+
installed_ui_libraries: {
|
|
69
|
+
installed: [],
|
|
70
|
+
missing_defaults: [],
|
|
71
|
+
full_kits: [],
|
|
72
|
+
mixed_kits: false,
|
|
73
|
+
folders: [],
|
|
74
|
+
},
|
|
67
75
|
deliberation: {
|
|
68
76
|
round: 0,
|
|
69
77
|
max_rounds: 3,
|
|
@@ -214,11 +222,22 @@ function ensurePersonaOutput({ project, persona, text } = {}) {
|
|
|
214
222
|
const outputs = { ...(state.persona_outputs || {}) };
|
|
215
223
|
let created = false;
|
|
216
224
|
if (!outputs[persona]) {
|
|
225
|
+
if (state.active_run && !text) {
|
|
226
|
+
return { projectId, state, created: false, stale: true };
|
|
227
|
+
}
|
|
228
|
+
const now = new Date().toISOString();
|
|
217
229
|
outputs[persona] = {
|
|
218
230
|
text: text || '(slash — designer marked this note; full text was in chat)',
|
|
219
|
-
at:
|
|
231
|
+
at: now,
|
|
220
232
|
accepted: null,
|
|
221
233
|
source: 'slash',
|
|
234
|
+
run_id: state.active_run?.id || `slash-${Date.now()}`,
|
|
235
|
+
attribution: {
|
|
236
|
+
provider: 'host',
|
|
237
|
+
model: 'unknown',
|
|
238
|
+
model_id: 'host/unknown',
|
|
239
|
+
effort: 'unknown',
|
|
240
|
+
},
|
|
222
241
|
};
|
|
223
242
|
created = true;
|
|
224
243
|
state = update({ project: projectId, patch: { persona_outputs: outputs } });
|
|
@@ -233,7 +252,11 @@ function markAccepted({ project, persona, accepted = true } = {}) {
|
|
|
233
252
|
if (!state || !state.persona_outputs || !state.persona_outputs[persona]) {
|
|
234
253
|
return { updated: false, reason: `No output recorded for persona "${persona}" in this session yet.` };
|
|
235
254
|
}
|
|
236
|
-
state.persona_outputs[persona]
|
|
255
|
+
const output = state.persona_outputs[persona];
|
|
256
|
+
if (state.active_run && output.run_id !== state.active_run.id) {
|
|
257
|
+
return { updated: false, reason: `The "${persona}" output is from an older run. Run the persona again before rating it.` };
|
|
258
|
+
}
|
|
259
|
+
output.accepted = accepted;
|
|
237
260
|
const merged = update({ project: projectId, patch: { persona_outputs: state.persona_outputs } });
|
|
238
261
|
return { updated: true, state: merged };
|
|
239
262
|
}
|
|
@@ -246,8 +269,10 @@ function logHostTurn({ project, path: turnPath, persona, inferred_in, verdicts =
|
|
|
246
269
|
const existing = show({ project: projectId }) || defaultState(projectId);
|
|
247
270
|
const metrics = { ...(existing.metrics || {}) };
|
|
248
271
|
const turns = Array.isArray(metrics.host_turns) ? metrics.host_turns.slice() : [];
|
|
272
|
+
const now = new Date().toISOString();
|
|
273
|
+
const runId = `host-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
249
274
|
turns.push({
|
|
250
|
-
at:
|
|
275
|
+
at: now,
|
|
251
276
|
path: turnPath || 'mcp',
|
|
252
277
|
persona: persona || '',
|
|
253
278
|
inferred_in: Number(inferred_in) || 0,
|
|
@@ -259,7 +284,26 @@ function logHostTurn({ project, path: turnPath, persona, inferred_in, verdicts =
|
|
|
259
284
|
const cliIn = metrics.input_tokens_est || 0;
|
|
260
285
|
const v = metrics.verdicts_this_run || (metrics.experts_run && metrics.experts_run.length ? 1 : 0) || (metrics.host_turns.length ? 1 : 0);
|
|
261
286
|
metrics.tokens_per_verdict = v && (host + cliIn) ? Math.round((host + cliIn) / v) : null;
|
|
262
|
-
|
|
287
|
+
const patch = { metrics };
|
|
288
|
+
if (persona && PERSONA_IDS.indexOf(persona) !== -1) {
|
|
289
|
+
patch.active_run = { id: runId, mode: 'host', at: now };
|
|
290
|
+
patch.persona_outputs = {
|
|
291
|
+
[persona]: {
|
|
292
|
+
text: '(host chat — full response remains in the IDE conversation)',
|
|
293
|
+
at: now,
|
|
294
|
+
accepted: null,
|
|
295
|
+
source: turnPath || 'mcp',
|
|
296
|
+
run_id: runId,
|
|
297
|
+
attribution: {
|
|
298
|
+
provider: 'host',
|
|
299
|
+
model: 'unknown',
|
|
300
|
+
model_id: 'host/unknown',
|
|
301
|
+
effort: 'unknown',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return update({ project: projectId, patch });
|
|
263
307
|
}
|
|
264
308
|
|
|
265
309
|
module.exports = {
|