analyzthis_design 2.0.1 → 2.1.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 (46) hide show
  1. package/README.md +10 -5
  2. package/agents/cards/evolve-check.md +38 -0
  3. package/agents/manifests/evolve-check.json +16 -0
  4. package/dist/README.md +10 -5
  5. package/dist/agents/cards/evolve-check.md +38 -0
  6. package/dist/agents/manifests/evolve-check.json +16 -0
  7. package/dist/bin/cli.js +1225 -1
  8. package/dist/lib/cache.js +111 -1
  9. package/dist/lib/chunk-executor.js +219 -1
  10. package/dist/lib/chunk-models.js +228 -1
  11. package/dist/lib/chunk-planner.js +328 -1
  12. package/dist/lib/chunk-router.js +66 -1
  13. package/dist/lib/chunk-run.js +199 -1
  14. package/dist/lib/chunk-synthesis.js +176 -1
  15. package/dist/lib/chunk-telemetry.js +88 -1
  16. package/dist/lib/collect.js +858 -1
  17. package/dist/lib/cost.js +119 -1
  18. package/dist/lib/dedup.js +167 -1
  19. package/dist/lib/deliberation.js +721 -1
  20. package/dist/lib/design-spec.js +236 -1
  21. package/dist/lib/evolution-metrics.js +197 -0
  22. package/dist/lib/evolve.js +361 -1
  23. package/dist/lib/export.js +77 -1
  24. package/dist/lib/feedback-submit.js +324 -1
  25. package/dist/lib/feedback.js +182 -1
  26. package/dist/lib/host-llm.js +251 -1
  27. package/dist/lib/install.js +301 -1
  28. package/dist/lib/knowledge.js +384 -1
  29. package/dist/lib/lessons.js +217 -1
  30. package/dist/lib/moodboard.js +563 -1
  31. package/dist/lib/orchestrator/run.js +935 -1
  32. package/dist/lib/outcome.js +193 -1
  33. package/dist/lib/platforms.js +166 -1
  34. package/dist/lib/provider.js +57 -1
  35. package/dist/lib/query-expander.js +83 -1
  36. package/dist/lib/ranker.js +105 -1
  37. package/dist/lib/reference-pack.js +221 -1
  38. package/dist/lib/research.js +143 -1
  39. package/dist/lib/retrieve.js +131 -1
  40. package/dist/lib/session.js +185 -1
  41. package/dist/lib/source-discovery.js +486 -1
  42. package/dist/lib/synthesis.js +155 -1
  43. package/dist/lib/token-gate.js +46 -1
  44. package/dist/skills/evolve-check/SKILL.md +106 -0
  45. package/package.json +3 -6
  46. package/skills/evolve-check/SKILL.md +106 -0
@@ -1 +1,563 @@
1
- 'use strict';var fs=require('fs'),path=require('path'),os=require('os'),crypto=require('crypto'),session=require('./session'),research=require('./research'),retrieve=require('./retrieve'),knowledge=require('./knowledge'),deliberation=require('./deliberation'),{run:orchestratorRun}=require('./orchestrator/run'),{resolvePackageRoot}=require('./platforms'),CONFIG_FILE=path['join'](os['homedir'](),'.analyzthis_design','config.json'),PACKAGE_ROOT=resolvePackageRoot(__dirname),DESIGN_REFERENCE_DIR=path['join'](PACKAGE_ROOT,'skills','design-reference');function loadConfig(){if(!fs['existsSync'](CONFIG_FILE))return{};try{return JSON['parse'](fs['readFileSync'](CONFIG_FILE,'utf8'));}catch(a){return{};}}function generateBoardId(){return crypto['randomBytes'](0x6)['toString']('hex');}function boardDir(a,b){return path['join'](session['sessionDir'](a),'moodboard',b);}function defaultWorkspaceDir(){return path['join'](process['cwd'](),'moodboard');}function ensureDir(a){fs['mkdirSync'](a,{'recursive':!![]});}function loadJson(a){if(!fs['existsSync'](a))return null;try{return JSON['parse'](fs['readFileSync'](a,'utf8'));}catch(b){return null;}}function writeJson(a,b){ensureDir(path['dirname'](a)),fs['writeFileSync'](a,JSON['stringify'](b,null,0x2));}var MOOD_REFERENCE_HOSTS=['dribbble.com','mobbin.com','behance.net','www.behance.net','awwwards.com','www.awwwards.com','screenlane.com','www.screenlane.com','ui-patterns.com','www.ui-patterns.com','pttrns.com','www.pttrns.com','land-book.com','www.land-book.com','onepagelove.com','www.onepagelove.com'];function isVisualReferenceUrl(a){try{var b=new URL(a),c=b['hostname']['toLowerCase'](),d=path['extname'](b['pathname'])['toLowerCase']();if(['.png','.jpg','.jpeg','.gif','.webp','.svg','.avif']['indexOf'](d)!==-0x1)return!![];return MOOD_REFERENCE_HOSTS['some'](function(f){return c===f||c['endsWith']('.'+f);});}catch(f){return![];}}function extractUrlsFromText(a){var b=/https?:\/\/[^\s)'">\]`]+/gi,c=String(a)['match'](b)||[];return c['map'](function(d){return d['replace'](/^[`'"]+|[`'"]+$/g,'')['replace'](/[.,;:!?)]+$/g,'');})['filter'](function(d){return d['length']>=0xc&&!/[{}]/['test'](d);});}function discoverReferenceUrls({task:a,urls:b,config:c}){var d=c['moodboard']||{},e=(d['urls']||[])['concat'](b||[]),f=[],g={};for(var h=0x0;h<e['length'];h++){var k=e[h];if(g[k])continue;g[k]=!![],f['push']({'url':k,'source':'config','type':isVisualReferenceUrl(k)?'visual':'article'});}var l=d['queries']||[],m=String(a)['slice'](0x0,0x50);if(l['indexOf'](m)===-0x1)l['push'](m);for(var n=0x0;n<l['length'];n++){var o=l[n],p='https://www.google.com/search?q='+encodeURIComponent(o+'\x20design\x20inspiration');!g[p]&&(g[p]=!![],f['push']({'url':p,'source':'query_stub','type':'search_stub','query':o}));}return f['slice'](0x0,d['limit']||0xc);}async function fetchReference(a){var b={'id':generateBoardId(),'url':a['url'],'source':a['source'],'type':a['type']||'article','query':a['query']||'','title':'','description':'','tags':[],'fetched_at':new Date()['toISOString'](),'error':null};if(a['type']==='search_stub')return b['title']='Search\x20stub:\x20'+a['query'],b['description']='Host\x20IDE:\x20perform\x20web\x20search\x20for\x20\x22'+a['query']+'\x22\x20and\x20add\x20the\x20resulting\x20references\x20to\x20the\x20board.',b['tags']=['search_stub'],b;if(a['type']==='visual')return b['title']='Visual\x20reference:\x20'+path['basename'](new URL(a['url'])['pathname']||'image'),b['description']='Image\x20URL.\x20Add\x20alt-text\x20or\x20a\x20local\x20caption\x20after\x20review.',b['tags']=['visual'],b;try{var c=await research['researchUrl']({'url':a['url'],'maxChars':0x7d0}),d=research['readWebContext']()||{'content':''},f=d['content']['split'](/^## /m),g=f[f['length']-0x1]||'',h=g['split']('\x0a')['filter'](function(m){return m['trim']();}),j=[];for(var k=0x0;k<h['length'];k++){var l=h[k];if(l['indexOf']('Source:')===0x0)continue;if(l['indexOf']('Fetched:')===0x0)continue;j['push'](l);}b['title']='Fetched:\x20'+a['url'],b['description']=j['join']('\x20')['slice'](0x0,0x258),b['tags']=['article'];}catch(m){b['error']=m['message'],b['description']='Could\x20not\x20fetch\x20this\x20URL.';}return b;}function tagHeuristics(a,b){var c=[]['concat'](a['tags']||[]),d=(a['description']+'\x20'+a['title'])['toLowerCase'](),e=String(b)['toLowerCase'](),f=[['minimal','minimalism'],['modern','modern'],['dark','dark\x20mode'],['light','light'],['b2b','b2b'],['saas','saas'],['dashboard','dashboard'],['fintech','fintech'],['healthcare','healthcare'],['ecommerce','e-commerce'],['luxury','luxury'],['playful','playful'],['trust','trust'],['data-dense','data-dense'],['card','cards'],['table','tables'],['glassmorphism','glassmorphism'],['neumorphism','neumorphism'],['brutalism','brutalism'],['typography','typography'],['color','color']];for(var g=0x0;g<f['length'];g++){var h=f[g][0x0],k=f[g][0x1];if(d['indexOf'](k)!==-0x1&&c['indexOf'](h)===-0x1)c['push'](h);}var l=['dashboard','landing','onboarding','settings','checkout','profile','invoice','analytics','calendar','form','wizard','modal'];for(var m=0x0;m<l['length'];m++){var n=l[m];if(e['indexOf'](n)!==-0x1&&c['indexOf'](n)===-0x1)c['push'](n);}return c;}async function tagReference(a,b,c){var d=tagHeuristics(a,b);if(!c)return a['tags']=d,a;var f=['You\x20are\x20a\x20design-librarian.\x20Tag\x20this\x20reference\x20for\x20a\x20mood\x20board.','Return\x20ONLY\x20a\x20JSON\x20object\x20with\x20keys:\x20\x22tags\x22\x20(array\x20of\x203-7\x20short\x20slugs),\x20\x22mood\x22\x20(one\x20word),\x20\x22surface\x22\x20(what\x20UI\x20surface\x20it\x20shows,\x20e.g.\x20dashboard,\x20landing,\x20settings),\x20\x22style\x22\x20(one\x20of:\x20minimal,\x20dense,\x20playful,\x20premium,\x20brutalist,\x20glassmorphism,\x20neumorphism,\x20corporate,\x20editorial).','','Task\x20context:\x20'+String(b)['slice'](0x0,0xc8),'','Reference\x20title:\x20'+(a['title']||'')['slice'](0x0,0xc8),'Description:\x20'+(a['description']||'')['slice'](0x0,0x1f4),'Existing\x20heuristic\x20tags:\x20'+d['join'](',\x20')]['join']('\x0a');try{var g=await c({'system':'','user':f,'maxTokens':0x190,'personaId':'mood-tagger'}),h=null,j=g['match'](/```json\s*([\s\S]*?)```/i);if(j)h=JSON['parse'](j[0x1]['trim']());else h=JSON['parse'](g['trim']());if(h&&Array['isArray'](h['tags']))for(var k=0x0;k<h['tags']['length'];k++){var l=h['tags'][k];if(d['indexOf'](l)===-0x1)d['push'](l);}if(h&&h['mood']&&d['indexOf'](h['mood'])===-0x1)d['push'](h['mood']);if(h&&h['surface']&&d['indexOf'](h['surface'])===-0x1)d['push'](h['surface']);if(h&&h['style']&&d['indexOf'](h['style'])===-0x1)d['push'](h['style']);}catch(m){}return a['tags']=d,a;}function getDsReferencePack(a,b){var c=[],d=[],f=String(a)['toLowerCase'](),g=[['saas','saas'],['b2b','b2b'],['dashboard','dashboard'],['fintech','fintech'],['healthcare','healthcare'],['ecommerce','e-commerce'],['luxury','luxury'],['landing','landing'],['mobile','mobile'],['analytics','analytics'],['productivity','productivity'],['social','social']];for(var h=0x0;h<g['length'];h++){if(f['indexOf'](g[h][0x0])!==-0x1)d['push'](g[h][0x1]);}if(!d['length'])d['push']('general');var j=[{'file':'ui-reasoning.csv','column':'UI_Category','limit':0x3},{'file':'colors.csv','column':'Product\x20Type','limit':0x3},{'file':'styles.csv','column':'Best\x20For','limit':0x3},{'file':'typography.csv','column':'Mood','limit':0x2},{'file':'ux-guidelines.csv','column':'Category','limit':0x3}];if(b){var k=path['join']('stacks',b+'.csv'),l=path['join'](DESIGN_REFERENCE_DIR,k);fs['existsSync'](l)&&j['unshift']({'file':k,'column':'Category','limit':0x3});}for(var m=0x0;m<j['length'];m++){var n=j[m];try{var o=retrieve['retrieve']({'file':n['file'],'filters':[{'column':n['column'],'anyOf':d}],'limit':n['limit']});o['rows']['length']&&c['push']({'file':n['file'],'rows':o['rows']});}catch(p){}}return c;}function formatDsPack(a){var b=['Design-system\x20reference\x20patterns:'];for(var c=0x0;c<a['length'];c++){var d=a[c];b['push']('\x0a['+d['file']+']');for(var e=0x0;e<d['rows']['length'];e++){var f=d['rows'][e],g=Object['keys'](f)['map'](function(h){return h+'='+f[h];})['join']('\x20|\x20');b['push']('\x20\x20•\x20'+g['slice'](0x0,0x12c));}}return b['join']('\x0a');}async function buildMoodBoard(a){a=a||{};var b=a['project']||session['getProjectId'](),c=a['task']||'',d=loadConfig(),f=a['workspaceDir']||d['moodboard']?.['workspace_dir']||defaultWorkspaceDir(),g=a['boardId']||generateBoardId(),h=boardDir(b,g);ensureDir(h);var j=[],k=a['userReferences']||[],l=a['discover']!==![];if(l){var m=discoverReferenceUrls({'task':c,'urls':a['urls']||[],'config':d});for(var n=0x0;n<m['length'];n++){var o=await fetchReference(m[n]),p=await tagReference(o,c,a['callLlmFn']||null);j['push'](p);}}for(var q=0x0;q<k['length'];q++){var r=k[q],s={'id':generateBoardId(),'url':r['url']||'','title':r['title']||(r['url']?path['basename'](new URL(r['url'])['pathname']||'user-ref'):'User\x20note'),'description':r['description']||'','source':'user','type':r['type']||'note','tags':r['tags']||[],'user_note':r['note']||'','fetched_at':new Date()['toISOString']()};if(!s['tags']['length'])s['tags']=tagHeuristics(s,c);j['push'](s);}var t=session['show']({'project':b})||session['init']({'project':b}),v=detectStack(t['vault_path']),w=getDsReferencePack(c,v),x={'board_id':g,'project_id':b,'task':c,'created_at':new Date()['toISOString'](),'updated_at':new Date()['toISOString'](),'references':j,'design_system_pack':w,'deliberation':null,'synthesis':null,'user_contributions':k['length']};writeJson(path['join'](h,'board.json'),x),ensureDir(f);var y=path['join'](f,g+'.json');try{fs['writeFileSync'](y,JSON['stringify'](x,null,0x2));}catch(z){}return{'board':x,'boardDir':h,'workspacePath':y};}function detectStack(a){if(!a||!fs['existsSync'](path['join'](a,'Tech')))return null;var b=fs['readdirSync'](path['join'](a,'Tech'))['map'](function(e){return e['toLowerCase']();}),c=['nextjs','react','shadcn','vue','angular','svelte','nuxtjs','astro'];for(var d=0x0;d<c['length'];d++){if(b['some'](function(e){return e['indexOf'](c[d])!==-0x1;}))return c[d];}return null;}async function critiqueMoodBoard(a){a=a||{};var b=a['project']||session['getProjectId'](),c=a['boardId'];if(!c)throw new Error('--board\x20is\x20required');var d=boardDir(b,c),f=path['join'](d,'board.json'),g=loadJson(f);if(!g)throw new Error('Board\x20not\x20found:\x20'+c);var h=a['experts']||['arjun','meera','priya','zara','noor'],i=a['task']||g['task'],j=g['references']['map'](function(p,q){return'['+(q+0x1)+']\x20'+p['title']+'\x20('+p['type']+')\x20tags='+p['tags']['join'](',')+'\x20—\x20'+(p['url']||p['description'])['slice'](0x0,0x78);})['join']('\x0a'),k=formatDsPack(g['design_system_pack']),l=['Mood-board\x20critique\x20for:\x20'+i,'','References:\x0a'+j,'',k,'','Each\x20persona\x20must\x20use\x20the\x20Honeycomb\x20rigor\x20matrix\x20to\x20evaluate\x20which\x20references\x20are\x20appropriate\x20for\x20the\x20user\x20and\x20why.\x20Output\x20scores,\x20top\x20concerns,\x20and\x20a\x20ranked\x20shortlist\x20of\x20the\x20best\x202-3\x20references.\x20Then\x20participate\x20in\x20adversarial\x20deliberation\x20until\x20consensus.']['join']('\x0a'),m=await orchestratorRun({'task':l,'experts':h,'project':b,'provider':a['provider']||'host','model':a['model'],'dryRun':!!a['dryRun'],'deliberate':a['noDeliberate']?![]:!![],'maxRounds':a['maxRounds']||0x3,'satisfaction':a['satisfaction']||0.4,'hostResponder':a['hostResponder']||null});g['deliberation']=m['deliberation'],g['synthesis']=m['synthesis'],g['updated_at']=new Date()['toISOString'](),g['critique_task']=l,writeJson(f,g);var n=a['workspaceDir']||defaultWorkspaceDir();ensureDir(n);var o=path['join'](n,c+'.json');try{fs['writeFileSync'](o,JSON['stringify'](g,null,0x2));}catch(p){}return{'board':g,'orchestratorResult':m};}async function addUserReferenceAndRerun(a){a=a||{};var b=a['project']||session['getProjectId'](),c=a['boardId'],d=a['reference'];if(!c||!d)throw new Error('--board\x20and\x20--reference\x20are\x20required');var e=boardDir(b,c),f=path['join'](e,'board.json'),g=loadJson(f);if(!g)throw new Error('Board\x20not\x20found:\x20'+c);var h={'id':generateBoardId(),'url':d['url']||'','title':d['title']||(d['url']?path['basename'](new URL(d['url'])['pathname']||'user-ref'):'User\x20note'),'description':d['description']||'','source':'user','type':d['type']||'note','tags':d['tags']||[],'user_note':d['note']||'','fetched_at':new Date()['toISOString']()};if(!h['tags']['length'])h['tags']=tagHeuristics(h,g['task']);g['references']['push'](h),g['user_contributions']=(g['user_contributions']||0x0)+0x1,g['updated_at']=new Date()['toISOString'](),writeJson(f,g);var i=await critiqueMoodBoard({'project':b,'boardId':c,'task':a['task']||g['task'],'experts':a['experts'],'provider':a['provider'],'model':a['model'],'dryRun':!!a['dryRun'],'noDeliberate':a['noDeliberate'],'maxRounds':a['maxRounds'],'satisfaction':a['satisfaction'],'hostResponder':a['hostResponder']});return{'board':i['board'],'added':h};}function listBoards(a){a=a||session['getProjectId']();var b=path['join'](session['sessionDir'](a),'moodboard');if(!fs['existsSync'](b))return[];return fs['readdirSync'](b,{'withFileTypes':!![]})['filter'](function(c){return c['isDirectory']();})['map'](function(c){return c['name'];});}module['exports']={'buildMoodBoard':buildMoodBoard,'critiqueMoodBoard':critiqueMoodBoard,'addUserReferenceAndRerun':addUserReferenceAndRerun,'listBoards':listBoards,'boardDir':boardDir,'formatDsPack':formatDsPack,'tagHeuristics':tagHeuristics};
1
+ 'use strict';
2
+
3
+ /**
4
+ * MoodBoard engine (v1.22).
5
+ *
6
+ * Collects visual + textual references from the web and design-system data,
7
+ * tags them, lets the user contribute references, then runs the persona team
8
+ * through an adversarial deliberation loop to converge on a direction.
9
+ *
10
+ * Artifacts are written to:
11
+ * ~/.analyzthis_design/sessions/{projectId}/moodboard/{boardId}/
12
+ * and symlinked/copied into the project workspace at:
13
+ * ./moodboard/ (unless the user changes the output path in config).
14
+ *
15
+ * All new files use CommonJS, 'use strict', no optional chaining, var, and
16
+ * try/catch around JSON/fs to stay compatible with the Llama 30B safety
17
+ * settings used elsewhere in this repo.
18
+ */
19
+
20
+ var fs = require('fs');
21
+ var path = require('path');
22
+ var os = require('os');
23
+ var crypto = require('crypto');
24
+ var session = require('./session');
25
+ var research = require('./research');
26
+ var retrieve = require('./retrieve');
27
+ var knowledge = require('./knowledge');
28
+ var deliberation = require('./deliberation');
29
+ var { run: orchestratorRun } = require('./orchestrator/run');
30
+ var { resolvePackageRoot } = require('./platforms');
31
+
32
+ var CONFIG_FILE = path.join(os.homedir(), '.analyzthis_design', 'config.json');
33
+ var PACKAGE_ROOT = resolvePackageRoot(__dirname);
34
+ var DESIGN_REFERENCE_DIR = path.join(PACKAGE_ROOT, 'skills', 'design-reference');
35
+
36
+ function loadConfig() {
37
+ if (!fs.existsSync(CONFIG_FILE)) return {};
38
+ try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); }
39
+ catch (e) { return {}; }
40
+ }
41
+
42
+ function generateBoardId() {
43
+ return crypto.randomBytes(6).toString('hex');
44
+ }
45
+
46
+ function boardDir(projectId, boardId) {
47
+ return path.join(session.sessionDir(projectId), 'moodboard', boardId);
48
+ }
49
+
50
+ function defaultWorkspaceDir() {
51
+ return path.join(process.cwd(), 'moodboard');
52
+ }
53
+
54
+ function ensureDir(p) {
55
+ fs.mkdirSync(p, { recursive: true });
56
+ }
57
+
58
+ function loadJson(filePath) {
59
+ if (!fs.existsSync(filePath)) return null;
60
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); }
61
+ catch (e) { return null; }
62
+ }
63
+
64
+ function writeJson(filePath, obj) {
65
+ ensureDir(path.dirname(filePath));
66
+ fs.writeFileSync(filePath, JSON.stringify(obj, null, 2));
67
+ }
68
+
69
+ // ─── Source discovery for mood references ───────────────────────────────────
70
+
71
+ // Known hosts that expose an image or a design article we can fetch as text.
72
+ var MOOD_REFERENCE_HOSTS = [
73
+ 'dribbble.com',
74
+ 'mobbin.com',
75
+ 'behance.net',
76
+ 'www.behance.net',
77
+ 'awwwards.com',
78
+ 'www.awwwards.com',
79
+ 'screenlane.com',
80
+ 'www.screenlane.com',
81
+ 'ui-patterns.com',
82
+ 'www.ui-patterns.com',
83
+ 'pttrns.com',
84
+ 'www.pttrns.com',
85
+ 'land-book.com',
86
+ 'www.land-book.com',
87
+ 'onepagelove.com',
88
+ 'www.onepagelove.com',
89
+ ];
90
+
91
+ function isVisualReferenceUrl(url) {
92
+ try {
93
+ var u = new URL(url);
94
+ var host = u.hostname.toLowerCase();
95
+ var ext = path.extname(u.pathname).toLowerCase();
96
+ if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.avif'].indexOf(ext) !== -1) return true;
97
+ return MOOD_REFERENCE_HOSTS.some(function(h) { return host === h || host.endsWith('.' + h); });
98
+ } catch (e) {
99
+ return false;
100
+ }
101
+ }
102
+
103
+ function extractUrlsFromText(text) {
104
+ var re = /https?:\/\/[^\s)'">\]`]+/gi;
105
+ var found = String(text).match(re) || [];
106
+ return found.map(function(u) {
107
+ return u.replace(/^[`'"]+|[`'"]+$/g, '').replace(/[.,;:!?)]+$/g, '');
108
+ }).filter(function(u) { return u.length >= 12 && !/[{}]/.test(u); });
109
+ }
110
+
111
+ function discoverReferenceUrls({ task, urls, config }) {
112
+ var cfg = config.moodboard || {};
113
+ var explicit = (cfg.urls || []).concat(urls || []);
114
+ var discovered = [];
115
+ var seen = {};
116
+
117
+ for (var i = 0; i < explicit.length; i++) {
118
+ var u = explicit[i];
119
+ if (seen[u]) continue;
120
+ seen[u] = true;
121
+ discovered.push({ url: u, source: 'config', type: isVisualReferenceUrl(u) ? 'visual' : 'article' });
122
+ }
123
+
124
+ // Add a small number of curated search stubs the host IDE can fill in.
125
+ var queries = cfg.queries || [];
126
+ var taskQuery = String(task).slice(0, 80);
127
+ if (queries.indexOf(taskQuery) === -1) queries.push(taskQuery);
128
+ for (var j = 0; j < queries.length; j++) {
129
+ var q = queries[j];
130
+ var stubUrl = 'https://www.google.com/search?q=' + encodeURIComponent(q + ' design inspiration');
131
+ if (!seen[stubUrl]) {
132
+ seen[stubUrl] = true;
133
+ discovered.push({ url: stubUrl, source: 'query_stub', type: 'search_stub', query: q });
134
+ }
135
+ }
136
+
137
+ return discovered.slice(0, cfg.limit || 12);
138
+ }
139
+
140
+ // ─── Fetch references (text + image metadata) ────────────────────────────────
141
+
142
+ async function fetchReference(ref) {
143
+ var result = {
144
+ id: generateBoardId(),
145
+ url: ref.url,
146
+ source: ref.source,
147
+ type: ref.type || 'article',
148
+ query: ref.query || '',
149
+ title: '',
150
+ description: '',
151
+ tags: [],
152
+ fetched_at: new Date().toISOString(),
153
+ error: null,
154
+ };
155
+
156
+ if (ref.type === 'search_stub') {
157
+ result.title = 'Search stub: ' + ref.query;
158
+ result.description = 'Host IDE: perform web search for "' + ref.query + '" and add the resulting references to the board.';
159
+ result.tags = ['search_stub'];
160
+ return result;
161
+ }
162
+
163
+ if (ref.type === 'visual') {
164
+ result.title = 'Visual reference: ' + path.basename(new URL(ref.url).pathname || 'image');
165
+ result.description = 'Image URL. Add alt-text or a local caption after review.';
166
+ result.tags = ['visual'];
167
+ return result;
168
+ }
169
+
170
+ try {
171
+ var fetched = await research.researchUrl({ url: ref.url, maxChars: 2000 });
172
+ var web = research.readWebContext() || { content: '' };
173
+ var blocks = web.content.split(/^## /m);
174
+ var lastBlock = blocks[blocks.length - 1] || '';
175
+ var lines = lastBlock.split('\n').filter(function(l) { return l.trim(); });
176
+ var bodyLines = [];
177
+ for (var i = 0; i < lines.length; i++) {
178
+ var line = lines[i];
179
+ if (line.indexOf('Source:') === 0) continue;
180
+ if (line.indexOf('Fetched:') === 0) continue;
181
+ bodyLines.push(line);
182
+ }
183
+ result.title = 'Fetched: ' + ref.url;
184
+ result.description = bodyLines.join(' ').slice(0, 600);
185
+ result.tags = ['article'];
186
+ } catch (e) {
187
+ result.error = e.message;
188
+ result.description = 'Could not fetch this URL.';
189
+ }
190
+
191
+ return result;
192
+ }
193
+
194
+ // ─── Tag references with an LLM or fallback heuristics ───────────────────────
195
+
196
+ function tagHeuristics(ref, task) {
197
+ var tags = [].concat(ref.tags || []);
198
+ var lowerDesc = (ref.description + ' ' + ref.title).toLowerCase();
199
+ var taskLower = String(task).toLowerCase();
200
+
201
+ var keywordMap = [
202
+ ['minimal', 'minimalism'],
203
+ ['modern', 'modern'],
204
+ ['dark', 'dark mode'],
205
+ ['light', 'light'],
206
+ ['b2b', 'b2b'],
207
+ ['saas', 'saas'],
208
+ ['dashboard', 'dashboard'],
209
+ ['fintech', 'fintech'],
210
+ ['healthcare', 'healthcare'],
211
+ ['ecommerce', 'e-commerce'],
212
+ ['luxury', 'luxury'],
213
+ ['playful', 'playful'],
214
+ ['trust', 'trust'],
215
+ ['data-dense', 'data-dense'],
216
+ ['card', 'cards'],
217
+ ['table', 'tables'],
218
+ ['glassmorphism', 'glassmorphism'],
219
+ ['neumorphism', 'neumorphism'],
220
+ ['brutalism', 'brutalism'],
221
+ ['typography', 'typography'],
222
+ ['color', 'color'],
223
+ ];
224
+
225
+ for (var i = 0; i < keywordMap.length; i++) {
226
+ var tag = keywordMap[i][0];
227
+ var needle = keywordMap[i][1];
228
+ if (lowerDesc.indexOf(needle) !== -1 && tags.indexOf(tag) === -1) tags.push(tag);
229
+ }
230
+
231
+ var taskKeywords = [
232
+ 'dashboard', 'landing', 'onboarding', 'settings', 'checkout', 'profile',
233
+ 'invoice', 'analytics', 'calendar', 'form', 'wizard', 'modal'
234
+ ];
235
+ for (var j = 0; j < taskKeywords.length; j++) {
236
+ var kw = taskKeywords[j];
237
+ if (taskLower.indexOf(kw) !== -1 && tags.indexOf(kw) === -1) tags.push(kw);
238
+ }
239
+
240
+ return tags;
241
+ }
242
+
243
+ async function tagReference(ref, task, callLlmFn) {
244
+ var tags = tagHeuristics(ref, task);
245
+ if (!callLlmFn) {
246
+ ref.tags = tags;
247
+ return ref;
248
+ }
249
+
250
+ var prompt = [
251
+ 'You are a design-librarian. Tag this reference for a mood board.',
252
+ 'Return ONLY a JSON object with keys: "tags" (array of 3-7 short slugs), "mood" (one word), "surface" (what UI surface it shows, e.g. dashboard, landing, settings), "style" (one of: minimal, dense, playful, premium, brutalist, glassmorphism, neumorphism, corporate, editorial).',
253
+ '',
254
+ 'Task context: ' + String(task).slice(0, 200),
255
+ '',
256
+ 'Reference title: ' + (ref.title || '').slice(0, 200),
257
+ 'Description: ' + (ref.description || '').slice(0, 500),
258
+ 'Existing heuristic tags: ' + tags.join(', '),
259
+ ].join('\n');
260
+
261
+ try {
262
+ var raw = await callLlmFn({ system: '', user: prompt, maxTokens: 400, personaId: 'mood-tagger' });
263
+ var parsed = null;
264
+ var fence = raw.match(/```json\s*([\s\S]*?)```/i);
265
+ if (fence) parsed = JSON.parse(fence[1].trim());
266
+ else parsed = JSON.parse(raw.trim());
267
+
268
+ if (parsed && Array.isArray(parsed.tags)) {
269
+ for (var i = 0; i < parsed.tags.length; i++) {
270
+ var t = parsed.tags[i];
271
+ if (tags.indexOf(t) === -1) tags.push(t);
272
+ }
273
+ }
274
+ if (parsed && parsed.mood && tags.indexOf(parsed.mood) === -1) tags.push(parsed.mood);
275
+ if (parsed && parsed.surface && tags.indexOf(parsed.surface) === -1) tags.push(parsed.surface);
276
+ if (parsed && parsed.style && tags.indexOf(parsed.style) === -1) tags.push(parsed.style);
277
+ } catch (e) {
278
+ // keep heuristic tags
279
+ }
280
+
281
+ ref.tags = tags;
282
+ return ref;
283
+ }
284
+
285
+ // ─── Design-system reference retrieval ────────────────────────────────────────
286
+
287
+ function getDsReferencePack(task, stack) {
288
+ var packs = [];
289
+ var keywords = [];
290
+ var taskLower = String(task).toLowerCase();
291
+ var keywordMap = [
292
+ ['saas', 'saas'], ['b2b', 'b2b'], ['dashboard', 'dashboard'],
293
+ ['fintech', 'fintech'], ['healthcare', 'healthcare'], ['ecommerce', 'e-commerce'],
294
+ ['luxury', 'luxury'], ['landing', 'landing'], ['mobile', 'mobile'],
295
+ ['analytics', 'analytics'], ['productivity', 'productivity'], ['social', 'social']
296
+ ];
297
+ for (var i = 0; i < keywordMap.length; i++) {
298
+ if (taskLower.indexOf(keywordMap[i][0]) !== -1) keywords.push(keywordMap[i][1]);
299
+ }
300
+ if (!keywords.length) keywords.push('general');
301
+
302
+ var specs = [
303
+ { file: 'ui-reasoning.csv', column: 'UI_Category', limit: 3 },
304
+ { file: 'colors.csv', column: 'Product Type', limit: 3 },
305
+ { file: 'styles.csv', column: 'Best For', limit: 3 },
306
+ { file: 'typography.csv', column: 'Mood', limit: 2 },
307
+ { file: 'ux-guidelines.csv', column: 'Category', limit: 3 },
308
+ ];
309
+
310
+ if (stack) {
311
+ var stackFile = path.join('stacks', stack + '.csv');
312
+ var stackPath = path.join(DESIGN_REFERENCE_DIR, stackFile);
313
+ if (fs.existsSync(stackPath)) {
314
+ specs.unshift({ file: stackFile, column: 'Category', limit: 3 });
315
+ }
316
+ }
317
+
318
+ for (var s = 0; s < specs.length; s++) {
319
+ var spec = specs[s];
320
+ try {
321
+ var result = retrieve.retrieve({
322
+ file: spec.file,
323
+ filters: [{ column: spec.column, anyOf: keywords }],
324
+ limit: spec.limit,
325
+ });
326
+ if (result.rows.length) {
327
+ packs.push({ file: spec.file, rows: result.rows });
328
+ }
329
+ } catch (e) {
330
+ // skip missing files
331
+ }
332
+ }
333
+
334
+ return packs;
335
+ }
336
+
337
+ function formatDsPack(packs) {
338
+ var lines = ['Design-system reference patterns:'];
339
+ for (var i = 0; i < packs.length; i++) {
340
+ var p = packs[i];
341
+ lines.push('\n[' + p.file + ']');
342
+ for (var r = 0; r < p.rows.length; r++) {
343
+ var row = p.rows[r];
344
+ var values = Object.keys(row).map(function(k) { return k + '=' + row[k]; }).join(' | ');
345
+ lines.push(' • ' + values.slice(0, 300));
346
+ }
347
+ }
348
+ return lines.join('\n');
349
+ }
350
+
351
+ // ─── Build the mood board ─────────────────────────────────────────────────────
352
+
353
+ async function buildMoodBoard(opts) {
354
+ opts = opts || {};
355
+ var projectId = opts.project || session.getProjectId();
356
+ var task = opts.task || '';
357
+ var config = loadConfig();
358
+ var workspaceDir = opts.workspaceDir || config.moodboard?.workspace_dir || defaultWorkspaceDir();
359
+ var boardId = opts.boardId || generateBoardId();
360
+ var bDir = boardDir(projectId, boardId);
361
+ ensureDir(bDir);
362
+
363
+ var refs = [];
364
+ var userRefs = opts.userReferences || [];
365
+ var discover = opts.discover !== false;
366
+
367
+ if (discover) {
368
+ var plan = discoverReferenceUrls({ task: task, urls: opts.urls || [], config: config });
369
+ for (var i = 0; i < plan.length; i++) {
370
+ var fetched = await fetchReference(plan[i]);
371
+ var tagged = await tagReference(fetched, task, opts.callLlmFn || null);
372
+ refs.push(tagged);
373
+ }
374
+ }
375
+
376
+ for (var u = 0; u < userRefs.length; u++) {
377
+ var ur = userRefs[u];
378
+ var ref = {
379
+ id: generateBoardId(),
380
+ url: ur.url || '',
381
+ title: ur.title || (ur.url ? path.basename(new URL(ur.url).pathname || 'user-ref') : 'User note'),
382
+ description: ur.description || '',
383
+ source: 'user',
384
+ type: ur.type || 'note',
385
+ tags: ur.tags || [],
386
+ user_note: ur.note || '',
387
+ fetched_at: new Date().toISOString(),
388
+ };
389
+ if (!ref.tags.length) ref.tags = tagHeuristics(ref, task);
390
+ refs.push(ref);
391
+ }
392
+
393
+ var state = session.show({ project: projectId }) || session.init({ project: projectId });
394
+ var stack = detectStack(state.vault_path);
395
+ var dsPack = getDsReferencePack(task, stack);
396
+
397
+ var board = {
398
+ board_id: boardId,
399
+ project_id: projectId,
400
+ task: task,
401
+ created_at: new Date().toISOString(),
402
+ updated_at: new Date().toISOString(),
403
+ references: refs,
404
+ design_system_pack: dsPack,
405
+ deliberation: null,
406
+ synthesis: null,
407
+ user_contributions: userRefs.length,
408
+ };
409
+
410
+ writeJson(path.join(bDir, 'board.json'), board);
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 };
422
+ }
423
+
424
+ function detectStack(vaultPath) {
425
+ if (!vaultPath || !fs.existsSync(path.join(vaultPath, 'Tech'))) return null;
426
+ var techNotes = fs.readdirSync(path.join(vaultPath, 'Tech')).map(function(f) { return f.toLowerCase(); });
427
+ var stacks = ['nextjs', 'react', 'shadcn', 'vue', 'angular', 'svelte', 'nuxtjs', 'astro'];
428
+ for (var i = 0; i < stacks.length; i++) {
429
+ if (techNotes.some(function(n) { return n.indexOf(stacks[i]) !== -1; })) return stacks[i];
430
+ }
431
+ return null;
432
+ }
433
+
434
+ // ─── Persona critique over a mood board ───────────────────────────────────────
435
+
436
+ async function critiqueMoodBoard(opts) {
437
+ opts = opts || {};
438
+ var projectId = opts.project || session.getProjectId();
439
+ var boardId = opts.boardId;
440
+ if (!boardId) throw new Error('--board is required');
441
+ var bDir = boardDir(projectId, boardId);
442
+ var boardPath = path.join(bDir, 'board.json');
443
+ var board = loadJson(boardPath);
444
+ if (!board) throw new Error('Board not found: ' + boardId);
445
+
446
+ var experts = opts.experts || ['arjun', 'meera', 'priya', 'zara', 'noor'];
447
+ var task = opts.task || board.task;
448
+
449
+ // Build a synthetic critique task that includes the board and DS pack.
450
+ var refsSummary = board.references.map(function(r, idx) {
451
+ return '[' + (idx + 1) + '] ' + r.title + ' (' + r.type + ') tags=' + r.tags.join(',') + ' — ' + (r.url || r.description).slice(0, 120);
452
+ }).join('\n');
453
+
454
+ var dsText = formatDsPack(board.design_system_pack);
455
+
456
+ var critiqueTask = [
457
+ 'Mood-board critique for: ' + task,
458
+ '',
459
+ 'References:\n' + refsSummary,
460
+ '',
461
+ dsText,
462
+ '',
463
+ '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
+ ].join('\n');
465
+
466
+ // Use the orchestrator directly, but force the experts to the mood-board set.
467
+ var result = await orchestratorRun({
468
+ task: critiqueTask,
469
+ experts: experts,
470
+ project: projectId,
471
+ provider: opts.provider || 'host',
472
+ model: opts.model,
473
+ dryRun: !!opts.dryRun,
474
+ deliberate: opts.noDeliberate ? false : true,
475
+ maxRounds: opts.maxRounds || 3,
476
+ satisfaction: opts.satisfaction || 0.4,
477
+ hostResponder: opts.hostResponder || null,
478
+ });
479
+
480
+ board.deliberation = result.deliberation;
481
+ board.synthesis = result.synthesis;
482
+ board.updated_at = new Date().toISOString();
483
+ board.critique_task = critiqueTask;
484
+
485
+ writeJson(boardPath, board);
486
+ var workspaceDir = opts.workspaceDir || defaultWorkspaceDir();
487
+ ensureDir(workspaceDir);
488
+ var workspacePath = path.join(workspaceDir, boardId + '.json');
489
+ try { fs.writeFileSync(workspacePath, JSON.stringify(board, null, 2)); }
490
+ catch (e) { /* best effort */ }
491
+
492
+ return { board: board, orchestratorResult: result };
493
+ }
494
+
495
+ // ─── Add user reference and rerun ─────────────────────────────────────────────
496
+
497
+ async function addUserReferenceAndRerun(opts) {
498
+ opts = opts || {};
499
+ var projectId = opts.project || session.getProjectId();
500
+ var boardId = opts.boardId;
501
+ var userRef = opts.reference;
502
+ if (!boardId || !userRef) throw new Error('--board and --reference are required');
503
+
504
+ var bDir = boardDir(projectId, boardId);
505
+ var boardPath = path.join(bDir, 'board.json');
506
+ var board = loadJson(boardPath);
507
+ if (!board) throw new Error('Board not found: ' + boardId);
508
+
509
+ var ref = {
510
+ id: generateBoardId(),
511
+ url: userRef.url || '',
512
+ title: userRef.title || (userRef.url ? path.basename(new URL(userRef.url).pathname || 'user-ref') : 'User note'),
513
+ description: userRef.description || '',
514
+ source: 'user',
515
+ type: userRef.type || 'note',
516
+ tags: userRef.tags || [],
517
+ user_note: userRef.note || '',
518
+ fetched_at: new Date().toISOString(),
519
+ };
520
+ if (!ref.tags.length) ref.tags = tagHeuristics(ref, board.task);
521
+ board.references.push(ref);
522
+ board.user_contributions = (board.user_contributions || 0) + 1;
523
+ board.updated_at = new Date().toISOString();
524
+ writeJson(boardPath, board);
525
+
526
+ // Rerun critique with updated board.
527
+ var critique = await critiqueMoodBoard({
528
+ project: projectId,
529
+ boardId: boardId,
530
+ task: opts.task || board.task,
531
+ experts: opts.experts,
532
+ provider: opts.provider,
533
+ model: opts.model,
534
+ dryRun: !!opts.dryRun,
535
+ noDeliberate: opts.noDeliberate,
536
+ maxRounds: opts.maxRounds,
537
+ satisfaction: opts.satisfaction,
538
+ hostResponder: opts.hostResponder,
539
+ });
540
+
541
+ return { board: critique.board, added: ref };
542
+ }
543
+
544
+ // ─── List boards ──────────────────────────────────────────────────────────────
545
+
546
+ function listBoards(projectId) {
547
+ projectId = projectId || session.getProjectId();
548
+ var bRoot = path.join(session.sessionDir(projectId), 'moodboard');
549
+ if (!fs.existsSync(bRoot)) return [];
550
+ return fs.readdirSync(bRoot, { withFileTypes: true })
551
+ .filter(function(d) { return d.isDirectory(); })
552
+ .map(function(d) { return d.name; });
553
+ }
554
+
555
+ module.exports = {
556
+ buildMoodBoard: buildMoodBoard,
557
+ critiqueMoodBoard: critiqueMoodBoard,
558
+ addUserReferenceAndRerun: addUserReferenceAndRerun,
559
+ listBoards: listBoards,
560
+ boardDir: boardDir,
561
+ formatDsPack: formatDsPack,
562
+ tagHeuristics: tagHeuristics,
563
+ };