analyzthis_design 2.5.1 → 2.6.0

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.
Files changed (100) hide show
  1. package/HOW-TO-USE.md +249 -326
  2. package/README.md +71 -25
  3. package/agents/calibration-benchmark.json +187 -0
  4. package/agents/cards/anuj.md +1 -1
  5. package/agents/cards/arjun.md +1 -1
  6. package/agents/cards/kavi.md +2 -1
  7. package/agents/cards/mood-board.md +7 -7
  8. package/agents/cards/noor.md +1 -1
  9. package/agents/cards/priya.md +1 -1
  10. package/agents/cards/zara.md +3 -3
  11. package/agents/chain.json +1 -1
  12. package/agents/design-spec-schema.json +18 -1
  13. package/agents/manifests/mood-board.json +13 -8
  14. package/agents/model-policy.json +12 -0
  15. package/agents/router.json +2 -2
  16. package/agents/session-schema.json +12 -2
  17. package/dist/HOW-TO-USE.md +249 -326
  18. package/dist/README.md +71 -25
  19. package/dist/agents/calibration-benchmark.json +187 -0
  20. package/dist/agents/cards/anuj.md +1 -1
  21. package/dist/agents/cards/arjun.md +1 -1
  22. package/dist/agents/cards/kavi.md +2 -1
  23. package/dist/agents/cards/mood-board.md +7 -7
  24. package/dist/agents/cards/noor.md +1 -1
  25. package/dist/agents/cards/priya.md +1 -1
  26. package/dist/agents/cards/zara.md +3 -3
  27. package/dist/agents/chain.json +1 -1
  28. package/dist/agents/design-spec-schema.json +18 -1
  29. package/dist/agents/manifests/mood-board.json +13 -8
  30. package/dist/agents/model-policy.json +12 -0
  31. package/dist/agents/router.json +2 -2
  32. package/dist/agents/session-schema.json +12 -2
  33. package/dist/bin/cli.js +308 -10
  34. package/dist/lib/calibrate.js +816 -0
  35. package/dist/lib/chunk-executor.js +41 -1
  36. package/dist/lib/chunk-models.js +4 -18
  37. package/dist/lib/chunk-router.js +22 -6
  38. package/dist/lib/chunk-run.js +55 -1
  39. package/dist/lib/chunk-synthesis.js +18 -2
  40. package/dist/lib/chunk-telemetry.js +77 -17
  41. package/dist/lib/collect.js +15 -1
  42. package/dist/lib/cost.js +15 -7
  43. package/dist/lib/design-spec.js +32 -2
  44. package/dist/lib/evolution-metrics.js +23 -1
  45. package/dist/lib/feedback-submit.js +50 -1
  46. package/dist/lib/feedback.js +15 -0
  47. package/dist/lib/install.js +1 -0
  48. package/dist/lib/lessons.js +8 -0
  49. package/dist/lib/mcp-server.js +189 -7
  50. package/dist/lib/model-policy.js +174 -0
  51. package/dist/lib/model-registry.js +71 -0
  52. package/dist/lib/moodboard-film.js +119 -0
  53. package/dist/lib/moodboard-view.js +426 -0
  54. package/dist/lib/moodboard.js +638 -27
  55. package/dist/lib/orchestrator/run.js +67 -8
  56. package/dist/lib/outcome.js +7 -2
  57. package/dist/lib/output-quality.js +95 -0
  58. package/dist/lib/reference-pack.js +9 -1
  59. package/dist/lib/session.js +48 -4
  60. package/dist/lib/system-prompt.js +3 -0
  61. package/dist/lib/ui-libraries.js +232 -0
  62. package/dist/skills/anuj/SKILL.md +4 -0
  63. package/dist/skills/arjun/SKILL.md +2 -0
  64. package/dist/skills/arjun/references/lens.md +2 -0
  65. package/dist/skills/design-director/SKILL.md +2 -1
  66. package/dist/skills/design-reference/SKILL.md +6 -0
  67. package/dist/skills/design-reference/motion.csv +11 -0
  68. package/dist/skills/design-reference/schema.json +12 -0
  69. package/dist/skills/design-reference/stacks/shadcn.csv +6 -0
  70. package/dist/skills/design-reference/ui-libraries.csv +46 -0
  71. package/dist/skills/design-spec/SKILL.md +1 -1
  72. package/dist/skills/getting-started/SKILL.md +2 -2
  73. package/dist/skills/kavi/SKILL.md +3 -2
  74. package/dist/skills/meera/SKILL.md +2 -0
  75. package/dist/skills/mood-board/SKILL.md +94 -87
  76. package/dist/skills/noor/SKILL.md +4 -0
  77. package/dist/skills/persona-orchestrator/references/lens.md +4 -4
  78. package/dist/skills/priya/SKILL.md +5 -0
  79. package/dist/skills/raj/SKILL.md +2 -0
  80. package/dist/skills/zara/SKILL.md +49 -4
  81. package/package.json +13 -2
  82. package/skills/anuj/SKILL.md +4 -0
  83. package/skills/arjun/SKILL.md +2 -0
  84. package/skills/arjun/references/lens.md +2 -0
  85. package/skills/design-director/SKILL.md +2 -1
  86. package/skills/design-reference/SKILL.md +6 -0
  87. package/skills/design-reference/motion.csv +11 -0
  88. package/skills/design-reference/schema.json +12 -0
  89. package/skills/design-reference/stacks/shadcn.csv +6 -0
  90. package/skills/design-reference/ui-libraries.csv +46 -0
  91. package/skills/design-spec/SKILL.md +1 -1
  92. package/skills/getting-started/SKILL.md +2 -2
  93. package/skills/kavi/SKILL.md +3 -2
  94. package/skills/meera/SKILL.md +2 -0
  95. package/skills/mood-board/SKILL.md +94 -87
  96. package/skills/noor/SKILL.md +4 -0
  97. package/skills/persona-orchestrator/references/lens.md +4 -4
  98. package/skills/priya/SKILL.md +5 -0
  99. package/skills/raj/SKILL.md +2 -0
  100. package/skills/zara/SKILL.md +49 -4
@@ -30,8 +30,9 @@
30
30
  "experts": ["string — persona ids"],
31
31
  "reason": "string"
32
32
  },
33
+ "active_run": "object | null — { id, mode, at }; feedback only accepts outputs carrying this run id",
33
34
  "persona_outputs": {
34
- "<persona_id>": "object — structured output block per agents/manifests/<persona_id>.json"
35
+ "<persona_id>": "object — output plus run_id, attribution { provider, model, model_id, effort, policy_version, prompt_hash, validator_version }, quality, and accepted state"
35
36
  },
36
37
  "full_prompts": {
37
38
  "<persona_id>": "object — { system: string, user: string } exact prompts sent to LLM for training"
@@ -73,6 +74,8 @@
73
74
  "correction": "string — how the user corrected or what they wanted instead",
74
75
  "tags": ["string — e.g. wrong_hierarchy, invented_tokens, off_brief"],
75
76
  "original_output": "string — snapshot of persona output at feedback time",
77
+ "run_id": "string | null — exact run that produced the rated output",
78
+ "attribution": "object — provider/model/effort and policy metadata copied from the output",
76
79
  "submitted_at": "ISO 8601 | omitted — set when feedback submit succeeds",
77
80
  "context": "object — task_map_summary, problem_type, mode"
78
81
  }
@@ -83,6 +86,13 @@
83
86
  },
84
87
  "vault_path": "string — Obsidian vault written by Kavi collect (empty until first collect)",
85
88
  "last_collect_at": "ISO 8601 timestamp — last successful collect run",
89
+ "installed_ui_libraries": {
90
+ "installed": [{ "library": "string", "job": "string", "how": "package.json | folder | package+folder" }],
91
+ "missing_defaults": [{ "job": "string", "library": "string", "install": "string", "docs": "string" }],
92
+ "full_kits": ["string — Chakra, Mantine, HeroUI, MUI, Ant Design, Ark"],
93
+ "mixed_kits": "boolean — more than one full kit, or a full kit plus Shadcn",
94
+ "folders": ["string — components/ui, components/magicui, ..."]
95
+ },
86
96
  "deliberation": {
87
97
  "round": "number — current deliberation round",
88
98
  "max_rounds": "number",
@@ -141,7 +151,7 @@
141
151
  "output_tokens_est": "number — estimated output tokens across the last run",
142
152
  "cache_hits": "number — retrieve/cache hits during the last run",
143
153
  "mode": "lite | full",
144
- "effort_log": [{ "persona": "string", "effort": "trivial|standard|hard", "model": "string|null", "scoped_mode": "string|null" }],
154
+ "effort_log": [{ "persona": "string", "effort": "trivial|standard|hard", "provider": "string|null", "model": "string|null", "model_id": "string|null", "input_tokens": "number", "output_tokens": "number", "run_id": "string", "scoped_mode": "string|null" }],
145
155
  "cost_usd": "number — estimated $ cost of the run from config.pricing (0 when pricing missing)",
146
156
  "deliberation_rounds": "number — adversarial rounds executed in last run",
147
157
  "objections_raised": "number",
package/dist/bin/cli.js CHANGED
@@ -19,6 +19,7 @@ const designSpec = require('../lib/design-spec');
19
19
  const { run: orchestratorRun } = require('../lib/orchestrator/run');
20
20
  const hostLlm = require('../lib/host-llm');
21
21
  const moodboard = require('../lib/moodboard');
22
+ const calibrator = require('../lib/calibrate');
22
23
 
23
24
  const HELP = `
24
25
  Analyzthis_Design — 8 AI design personas with a personal knowledge bank
@@ -79,6 +80,9 @@ Usage:
79
80
  ── Efficiency / cost commands ────────────────────────────────
80
81
  metrics Print the last run's cost metrics (--all for every project)
81
82
  cost Print the last run's $ cost from config.pricing (--all for every project)
83
+ calibrate Compare available models on the fixed persona benchmark
84
+ (--max-usd 5, --max-calls 500, --runs 3, --judge, --apply)
85
+ Preview only with --dry-run; share text-free results with --share
82
86
  receipt Inferred token receipt for host (slash/MCP) turns — not a bill
83
87
  export-training Write accepted-output JSONL pairs for LoRA prep
84
88
  (--persona <id>, --all, --output <path>)
@@ -98,12 +102,26 @@ Usage:
98
102
  full: all 19 named tools. Or ANALYZTHIS_MCP_CATALOG=full
99
103
 
100
104
  ── Mood board commands ─────────────────────────────────────
101
- moodboard create --task <text> Build a mood board from web + DS references
102
- [--url <url> ...] [--auto]
103
- moodboard critique --board <id> Run team deliberation over a board
104
- moodboard add --board <id> Add a user reference and rerun critique
105
- [--url <url>] [--title <t>] [--desc <d>]
106
- [--tags a,b] [--note <text>]
105
+ moodboard create --task <text> Start a visual mood board (Zara scouts element crops)
106
+ [--adjectives a,b,c] [--not "..."] [--sections components,typography]
107
+ [--kind mood] for the older link-and-tag board
108
+ moodboard brief --board <id> Edit the brief [--adjectives] [--not] [--sections] [--yes]
109
+ moodboard capture-script Print the page script that reads real colours/fonts
110
+ [--selector ".pricing-card"]
111
+ moodboard tile --board <id> Add one element crop: --page <url> --image <file|url>
112
+ --element "<what>" --section <id> [--steal] [--refuse]
113
+ [--peak] [--colors #hex,..] [--fonts a,b] [--concept]
114
+ [--video film.webm] [--stills a.png,b.png]
115
+ moodboard film --board <id> Record real sites scrolling as motion tiles: --url <url>
116
+ (repeatable) [--section motion] [--steal] [--refuse]
117
+ Needs playwright-core + Google Chrome
118
+ moodboard critique --board <id> Team votes on each tile (advice only)
119
+ moodboard votes --board <id> Save team votes from a JSON file --votes votes.json
120
+ moodboard show --board <id> Open the clickable board on 127.0.0.1 [--port 4317]
121
+ moodboard decide --board <id> Your choice: --tile <n> --decision keep|maybe|reject|clear
122
+ moodboard rescout --board <id> Send Zara back with your note [--note "..."]
123
+ moodboard lock --board <id> Lock your direction for every persona [--note "..."]
124
+ moodboard add --board <id> Add a reference to a link board and rerun critique
107
125
  moodboard list List mood boards for this project
108
126
 
109
127
  ── Reference data (retrieve-on-demand) ───────────────────────
@@ -156,6 +174,14 @@ Usage:
156
174
  --budget free | cheap | auto — chunk model budget (run command; default: auto)
157
175
  --sequential Run chunks sequentially (run command; default on)
158
176
  --max-chunks Cap number of planner chunks (run command; default 6)
177
+ --max-usd Calibration hard spending limit in USD (default 5)
178
+ --max-calls Calibration hard model-call limit (default 500)
179
+ --runs Calibration repeats per fixed task/model (default 3)
180
+ --delta Non-inferiority margin on the 0-1 score scale (default 0.05)
181
+ --judge Enable blind cross-family calibration judges
182
+ --apply Write qualified calibration winners to the local policy
183
+ --resume Resume a calibration checkpoint path
184
+ --share Opt-in submission of text-free fixed-benchmark aggregates
159
185
  --unchunked Force legacy single-pass orchestrator (run command)
160
186
  --output Write final session-state.json to this path (run / run-unchunked command); or JSONL path (export-training)
161
187
  --persona Persona id (session accept; export-training; feedback commands)
@@ -210,7 +236,10 @@ Usage:
210
236
  npx analyzthis_design research --query "EY design system tokens"
211
237
 
212
238
  npx analyzthis_design moodboard create --task "B2B fintech dashboard, trustworthy, high-contrast" --auto
239
+ npx analyzthis_design moodboard create --task "Invoice approval, partner signs" --adjectives calm,precise,trusted --not "AI purple hero"
240
+ npx analyzthis_design moodboard tile --board <id> --page https://linear.app/pricing --image ./crop.png --element "Pricing card" --section components --steal "one quiet CTA"
213
241
  npx analyzthis_design moodboard critique --board <boardId>
242
+ npx analyzthis_design moodboard show --board <boardId>
214
243
  npx analyzthis_design moodboard add --board <boardId> --url https://dribbble.com/shots/example --title "Alt hero" --tags "landing,trust"
215
244
 
216
245
  npx analyzthis_design retrieve --file colors.csv --column "Product Type" --keywords saas,dashboard
@@ -326,6 +355,10 @@ const boardVal = getFlag('board');
326
355
  const titleVal = getFlag('title');
327
356
  const descVal = getFlag('desc');
328
357
  const autoFlag = flags.includes('--auto');
358
+ const kindVal = getFlag('kind');
359
+ const stealVal = getFlag('steal');
360
+ const refuseVal = getFlag('refuse');
361
+ const peakVal = getFlag('peak');
329
362
  const budgetVal = getFlag('budget');
330
363
  const sequentialFlag = flags.includes('--sequential');
331
364
  const maxChunksVal = getFlag('max-chunks');
@@ -335,6 +368,32 @@ const fixFlag = flags.includes('--fix') || flags.includes('--no');
335
368
  const likedFlag = flags.includes('--liked');
336
369
  const becauseVal = getFlag('because');
337
370
  const sendFlag = flags.includes('--send');
371
+ const applyFlag = flags.includes('--apply');
372
+ const shareFlag = flags.includes('--share');
373
+ const judgeFlag = flags.includes('--judge');
374
+ const maxUsdVal = getFlag('max-usd');
375
+ const maxCallsVal = getFlag('max-calls');
376
+ const runsVal = getFlag('runs');
377
+ const deltaVal = getFlag('delta');
378
+ const resumeVal = getFlag('resume');
379
+ const sectionVal = getFlag('section');
380
+ const sectionsVal = getFlag('sections');
381
+ const elementVal = getFlag('element');
382
+ const imageVal = getFlag('image');
383
+ const tileVal = getFlag('tile');
384
+ const decisionVal = getFlag('decision');
385
+ const noteVal = getFlag('note');
386
+ const portVal = getFlag('port');
387
+ const selectorVal = getFlag('selector');
388
+ const adjectivesVal = getFlag('adjectives');
389
+ const notThisVal = getFlag('not');
390
+ const colorsVal = getFlag('colors');
391
+ const fontsVal = getFlag('fonts');
392
+ const votesVal = getFlag('votes');
393
+ const conceptFlag = flags.includes('--concept');
394
+ const videoVal = getFlag('video');
395
+ const stillsVal = getFlag('stills');
396
+ const splitList = (raw) => (raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : undefined);
338
397
 
339
398
  // ─── Commands ────────────────────────────────────────────────────────────────
340
399
 
@@ -660,7 +719,7 @@ switch (cmd) {
660
719
  const msub = flags.find((f) => !f.startsWith('--')) || 'create';
661
720
  if (msub === 'create') {
662
721
  if (!taskVal) {
663
- console.error('\n ✗ --task is required. Example: npx analyzthis_design moodboard create --task "B2B fintech dashboard" --auto\n');
722
+ console.error('\n ✗ --task is required. Example: npx analyzthis_design moodboard create --task "B2B fintech dashboard" --adjectives calm,dense,trusted\n');
664
723
  process.exit(1);
665
724
  }
666
725
  (async () => {
@@ -675,11 +734,23 @@ switch (cmd) {
675
734
  project: projectVal,
676
735
  task: taskVal,
677
736
  urls: urls,
737
+ kind: kindVal || 'ui',
678
738
  discover: autoFlag || urls.length > 0,
679
739
  dryRun: dryRunFlag,
740
+ brief: { adjectives: splitList(adjectivesVal), not_this: notThisVal || '', sections: splitList(sectionsVal) },
680
741
  });
681
- console.log(`\n✅ Mood board created: ${result.board.board_id}`);
682
- console.log(` References: ${result.board.references.length}`);
742
+ console.log(`\n✅ ${result.board.kind === 'ui' ? 'Mood board (visual)' : 'Link mood board'} created: ${result.board.board_id}`);
743
+ if (result.board.kind === 'ui') {
744
+ console.log(` Sections: ${result.board.brief.sections.join(', ')}`);
745
+ console.log(` Scout: Zara round ${result.board.scout.round} — ${result.board.scout.status}`);
746
+ console.log(` Next: Zara crops elements, then moodboard tile --board ${result.board.board_id} --page <url> --image <file> --element "<what>" --section components`);
747
+ console.log(` Then: moodboard show --board ${result.board.board_id} (you decide in the browser)`);
748
+ console.log('');
749
+ console.log(result.board.scout.brief);
750
+ console.log('');
751
+ } else {
752
+ console.log(` References: ${result.board.references.length}`);
753
+ }
683
754
  console.log(` Board dir: ${result.boardDir}`);
684
755
  console.log(` Workspace: ${result.workspacePath}\n`);
685
756
  if (dryRunFlag) {
@@ -712,6 +783,10 @@ switch (cmd) {
712
783
  });
713
784
  console.log(`\n✅ Mood board critique complete: ${result.board.board_id}`);
714
785
  console.log(` Verdict: ${result.board.synthesis?.verdict || 'n/a'}`);
786
+ if (result.board.kind === 'ui' && result.board.scout) {
787
+ console.log(` Scout: round ${result.board.scout.round} — ${result.board.scout.status}`);
788
+ console.log(` Team votes are advice. You decide: moodboard show --board ${result.board.board_id}`);
789
+ }
715
790
  console.log(` Rounds: ${result.board.deliberation?.round || 0}`);
716
791
  console.log(` Board: ${result.boardDir || path.join(session.sessionDir(projectVal || session.getProjectId()), 'moodboard', boardVal)}`);
717
792
  console.log(` Workspace: ${result.workspacePath}\n`);
@@ -726,6 +801,149 @@ switch (cmd) {
726
801
  })();
727
802
  break;
728
803
  }
804
+ if (msub === 'film') {
805
+ const urls = flags.filter((f, i) => i > 0 && flags[i - 1] === '--url' && !f.startsWith('--'))
806
+ .concat(flags.filter((f) => f.startsWith('--url=')).map((f) => f.slice(6)));
807
+ if (!boardVal || !urls.length) {
808
+ console.error('\n ✗ --board and --url are required. Example: npx analyzthis_design moodboard film --board <id> --url https://osmo.supply --steal "easing taste"\n');
809
+ process.exit(1);
810
+ }
811
+ (async () => {
812
+ try {
813
+ console.log(`\n 🎬 Filming ${urls.length} site${urls.length === 1 ? '' : 's'} (about 15 seconds each)…`);
814
+ const out = await require('../lib/moodboard-film').filmTiles({
815
+ project: projectVal, boardId: boardVal, urls,
816
+ section: sectionVal, element: elementVal, steal: stealVal, refuse: refuseVal, peak: peakVal,
817
+ });
818
+ out.failed.forEach((f) => console.error(` ✗ ${f.url}: ${f.reason}`));
819
+ const added = out.saved ? out.saved.added : [];
820
+ (out.saved ? out.saved.skipped : []).forEach((s) => console.error(` ✗ ${s.element}: ${s.reason}`));
821
+ if (!added.length) process.exit(1);
822
+ console.log(`\n✅ ${added.length} film tile${added.length === 1 ? '' : 's'} added: ${added.map((t) => '#' + t.n + ' ' + t.site).join(', ')}`);
823
+ console.log(` Next: moodboard show --board ${boardVal}\n`);
824
+ } catch (err) {
825
+ console.error(`\n ✗ ${err.message}\n`);
826
+ process.exit(1);
827
+ }
828
+ })();
829
+ break;
830
+ }
831
+ if (msub === 'tile' || msub === 'frames') {
832
+ const pageUrl = getFlag('page') || urlVal;
833
+ if (!boardVal || !pageUrl) {
834
+ console.error('\n ✗ --board and --page are required. Example: npx analyzthis_design moodboard tile --board <id> --page https://linear.app/pricing --image ./crop.png --element "Pricing card" --section components --steal "one quiet CTA"\n');
835
+ process.exit(1);
836
+ }
837
+ try {
838
+ const result = moodboard.addTiles({
839
+ project: projectVal,
840
+ boardId: boardVal,
841
+ tile: {
842
+ page_url: pageUrl,
843
+ element: elementVal || titleVal || '',
844
+ section: sectionVal || 'components',
845
+ image_path: imageVal && !/^https?:\/\//i.test(imageVal) ? imageVal : undefined,
846
+ image_url: imageVal && /^https?:\/\//i.test(imageVal) ? imageVal : (msub === 'frames' ? urlVal : undefined),
847
+ video_path: videoVal,
848
+ stills: splitList(stillsVal),
849
+ tokens: { colors: splitList(colorsVal), fonts: splitList(fontsVal) },
850
+ steal: stealVal || '',
851
+ refuse: refuseVal || '',
852
+ peak: peakVal || '',
853
+ concept: conceptFlag,
854
+ },
855
+ });
856
+ if (result.skipped.length) {
857
+ console.error(`\n ✗ Not added: ${result.skipped.map((s) => s.reason).join('; ')}\n`);
858
+ process.exit(1);
859
+ }
860
+ console.log(`\n✅ Tile #${result.added[0].n} added (${result.board.tiles.length} on the board)`);
861
+ console.log(` Next: more tiles, then moodboard critique or moodboard show --board ${result.board.board_id}\n`);
862
+ } catch (err) {
863
+ console.error(`\n ✗ ${err.message}\n`);
864
+ process.exit(1);
865
+ }
866
+ break;
867
+ }
868
+ if (msub === 'brief') {
869
+ try {
870
+ const result = moodboard.setBrief({
871
+ project: projectVal, boardId: boardVal,
872
+ adjectives: splitList(adjectivesVal), not_this: notThisVal, sections: splitList(sectionsVal),
873
+ confirmed_by_user: yesFlag ? true : undefined,
874
+ });
875
+ console.log(`\n✅ Brief saved: ${JSON.stringify(result.board.brief)}\n`);
876
+ } catch (err) {
877
+ console.error(`\n ✗ ${err.message}\n`);
878
+ process.exit(1);
879
+ }
880
+ break;
881
+ }
882
+ if (msub === 'votes') {
883
+ try {
884
+ const raw = JSON.parse(fs.readFileSync(votesVal, 'utf8'));
885
+ const result = moodboard.recordPersonaVotes({ project: projectVal, boardId: boardVal, votes: raw.votes || [], verdict: raw.verdict, summary: raw.summary });
886
+ console.log(`\n✅ Team advice saved. ${result.ignored.length} ignored. Next: moodboard show --board ${result.board.board_id}\n`);
887
+ } catch (err) {
888
+ console.error(`\n ✗ ${err.message} (usage: moodboard votes --board <id> --votes votes.json)\n`);
889
+ process.exit(1);
890
+ }
891
+ break;
892
+ }
893
+ if (msub === 'decide') {
894
+ try {
895
+ moodboard.recordUserDecision({ project: projectVal, boardId: boardVal, tile: tileVal, decision: decisionVal, note: noteVal || '' });
896
+ console.log(`\n✅ Tile ${tileVal}: ${decisionVal}. Your choice overrides every persona.\n`);
897
+ } catch (err) {
898
+ console.error(`\n ✗ ${err.message} (usage: moodboard decide --board <id> --tile 3 --decision keep|maybe|reject|clear --note "...")\n`);
899
+ process.exit(1);
900
+ }
901
+ break;
902
+ }
903
+ if (msub === 'rescout') {
904
+ try {
905
+ const result = moodboard.requestRescout({ project: projectVal, boardId: boardVal, note: noteVal || '' });
906
+ console.log(`\n✅ Zara goes back out (round ${result.board.scout.round}).\n\n${result.board.scout.brief}\n`);
907
+ } catch (err) {
908
+ console.error(`\n ✗ ${err.message}\n`);
909
+ process.exit(1);
910
+ }
911
+ break;
912
+ }
913
+ if (msub === 'lock') {
914
+ try {
915
+ const result = moodboard.lockDirection({ project: projectVal, boardId: boardVal, note: noteVal || '' });
916
+ const d = result.board.direction;
917
+ console.log(`\n✅ Direction locked: ${d.kept.length} kept, ${d.rejected.length} rejected. Saved to session as design_direction.`);
918
+ if (d.palette.length) console.log(` Palette: ${d.palette.join(' ')}`);
919
+ if (d.fonts.length) console.log(` Fonts: ${d.fonts.join(', ')}`);
920
+ console.log('');
921
+ } catch (err) {
922
+ console.error(`\n ✗ ${err.message}\n`);
923
+ process.exit(1);
924
+ }
925
+ break;
926
+ }
927
+ if (msub === 'show') {
928
+ if (!boardVal) {
929
+ console.error('\n ✗ --board is required. Example: npx analyzthis_design moodboard show --board <id>\n');
930
+ process.exit(1);
931
+ }
932
+ require('../lib/moodboard-view').serveBoard({ project: projectVal, boardId: boardVal, port: portVal != null ? portVal : undefined, onChange: (what, b) => {
933
+ console.log(` ${new Date().toLocaleTimeString()} ${what} — scout: ${b.scout && b.scout.status}`);
934
+ } }).then((live) => {
935
+ console.log(`\n✅ Mood board live at ${live.url}`);
936
+ console.log(' Open it in the browser. Keep / maybe / reject saves instantly. Ctrl+C to stop.\n');
937
+ }).catch((err) => {
938
+ console.error(`\n ✗ ${err.message}\n`);
939
+ process.exit(1);
940
+ });
941
+ break;
942
+ }
943
+ if (msub === 'capture-script') {
944
+ console.log(moodboard.captureScript(selectorVal || 'body', getFlag('text') || ''));
945
+ break;
946
+ }
729
947
  if (msub === 'add') {
730
948
  if (!boardVal) {
731
949
  console.error('\n ✗ --board is required. Example: npx analyzthis_design moodboard add --board <id> --url <url>\n');
@@ -776,7 +994,7 @@ switch (cmd) {
776
994
  }
777
995
  break;
778
996
  }
779
- console.error('\n Unknown moodboard subcommand. Use: create | critique | add | list\n');
997
+ console.error('\n Unknown moodboard subcommand. Use: create | brief | tile | critique | votes | show | decide | rescout | lock | capture-script | add | list\n');
780
998
  process.exit(1);
781
999
  }
782
1000
 
@@ -1000,6 +1218,86 @@ switch (cmd) {
1000
1218
  break;
1001
1219
  }
1002
1220
 
1221
+ case 'calibrate': {
1222
+ (async () => {
1223
+ try {
1224
+ if (shareFlag && dryRunFlag && resumeVal) {
1225
+ const saved = JSON.parse(fs.readFileSync(path.resolve(resumeVal), 'utf8'));
1226
+ const shared = await feedbackSubmit.submitCalibration(saved, { dryRun: true, yes: yesFlag });
1227
+ console.log(`\n── Calibration share preview (${shared.rows.length} text-free rows) ──`);
1228
+ console.log(JSON.stringify(shared.rows[0], null, 2));
1229
+ console.log('');
1230
+ return;
1231
+ }
1232
+
1233
+ const options = {
1234
+ maxUsd: maxUsdVal == null ? 5 : Number(maxUsdVal),
1235
+ maxCalls: maxCallsVal == null ? 500 : Number(maxCallsVal),
1236
+ runs: runsVal == null ? 3 : Number(runsVal),
1237
+ delta: deltaVal == null ? 0.05 : Number(deltaVal),
1238
+ judge: judgeFlag,
1239
+ apply: applyFlag,
1240
+ resume: resumeVal || null,
1241
+ };
1242
+ const preview = await calibrator.preview(options);
1243
+ console.log('\n── Calibration preview ─────────────────────────────');
1244
+ console.log(` Models: ${preview.models}`);
1245
+ console.log(` Fixed tasks: ${preview.tasks}`);
1246
+ console.log(` Planned calls: ${preview.estimated_calls}/${options.maxCalls}`);
1247
+ console.log(` Worst-case cost: $${preview.estimated_usd.toFixed(4)}/$${options.maxUsd.toFixed(2)}`);
1248
+ if (!preview.allowed) {
1249
+ console.error(` Blocked by: ${preview.reasons.join(', ')}\n`);
1250
+ process.exitCode = 1;
1251
+ return;
1252
+ }
1253
+ if (dryRunFlag) {
1254
+ console.log(' Preview only: no model calls or policy writes.\n');
1255
+ return;
1256
+ }
1257
+
1258
+ if (!yesFlag) {
1259
+ if (!process.stdin.isTTY) {
1260
+ throw new Error('Non-interactive terminal. Pass --yes after reviewing the cost preview.');
1261
+ }
1262
+ const readline = require('readline');
1263
+ const approved = await new Promise((resolve) => {
1264
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1265
+ rl.question('Run this calibration? [y/N] ', (answer) => {
1266
+ rl.close();
1267
+ resolve(/^y(es)?$/i.test(String(answer || '').trim()));
1268
+ });
1269
+ });
1270
+ if (!approved) {
1271
+ console.log('\n Calibration cancelled.\n');
1272
+ return;
1273
+ }
1274
+ }
1275
+
1276
+ const result = await calibrator.calibrate(options);
1277
+ console.log(`\n── Calibration ${result.status} ─────────────────────`);
1278
+ console.log(` Calls: ${result.calls}`);
1279
+ console.log(` Cost: $${Number(result.spent_usd || 0).toFixed(4)}`);
1280
+ console.log(` Checkpoint: ${result.checkpoint}`);
1281
+ Object.entries(result.selections || {}).forEach(([persona, efforts]) => {
1282
+ Object.entries(efforts).forEach(([effort, selection]) => {
1283
+ console.log(` ${persona}/${effort}: ${selection.winner || 'keep current'} (${selection.reason})`);
1284
+ });
1285
+ });
1286
+ if (result.policy_file) console.log(` Local policy: ${result.policy_file}`);
1287
+
1288
+ if (shareFlag) {
1289
+ const shared = await feedbackSubmit.submitCalibration(result, { dryRun: false, yes: yesFlag });
1290
+ console.log(` Shared: ${shared.submitted || 0} fixed-benchmark rows`);
1291
+ }
1292
+ console.log('');
1293
+ } catch (error) {
1294
+ console.error(`\n ✗ ${error.message}\n`);
1295
+ process.exitCode = 1;
1296
+ }
1297
+ })();
1298
+ break;
1299
+ }
1300
+
1003
1301
  case 'scores': {
1004
1302
  const em = require('../lib/evolution-metrics');
1005
1303
  if (flags.includes('--submit') || flags.includes('--submit-dry-run')) {