analyzthis_design 2.3.1 → 2.4.1
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 +28 -9
- package/README.md +27 -3
- package/dist/HOW-TO-USE.md +28 -9
- package/dist/README.md +27 -3
- package/dist/bin/cli.js +145 -3
- package/dist/lib/accept.js +95 -0
- package/dist/lib/chunk-run.js +2 -0
- package/dist/lib/feedback-submit.js +2 -0
- package/dist/lib/install.js +9 -0
- package/dist/lib/knowledge.js +49 -18
- package/dist/lib/mcp-server.js +181 -36
- package/dist/lib/orchestrator/run.js +5 -0
- package/dist/lib/receipt.js +102 -0
- package/dist/lib/session.js +84 -2
- package/dist/lib/share.js +131 -0
- package/dist/skills/accept/SKILL.md +52 -0
- package/dist/skills/anuj/SKILL.md +7 -0
- package/dist/skills/arjun/SKILL.md +5 -263
- package/dist/skills/arjun/references/lens.md +265 -0
- package/dist/skills/getting-started/SKILL.md +3 -0
- package/dist/skills/kavi/SKILL.md +7 -0
- package/dist/skills/knowledge-bank/SKILL.md +11 -25
- package/dist/skills/meera/SKILL.md +7 -0
- package/dist/skills/noor/SKILL.md +7 -0
- package/dist/skills/persona-orchestrator/SKILL.md +5 -195
- package/dist/skills/persona-orchestrator/references/lens.md +197 -0
- package/dist/skills/priya/SKILL.md +7 -0
- package/dist/skills/raj/SKILL.md +7 -0
- package/dist/skills/receipt/SKILL.md +17 -0
- package/dist/skills/share/SKILL.md +54 -0
- package/dist/skills/ux-story-gate/SKILL.md +5 -338
- package/dist/skills/ux-story-gate/references/lens.md +340 -0
- package/dist/skills/zara/SKILL.md +7 -0
- package/package.json +1 -1
- package/skills/accept/SKILL.md +52 -0
- package/skills/anuj/SKILL.md +7 -0
- package/skills/arjun/SKILL.md +5 -263
- package/skills/arjun/references/lens.md +265 -0
- package/skills/getting-started/SKILL.md +3 -0
- package/skills/kavi/SKILL.md +7 -0
- package/skills/knowledge-bank/SKILL.md +11 -25
- package/skills/meera/SKILL.md +7 -0
- package/skills/noor/SKILL.md +7 -0
- package/skills/persona-orchestrator/SKILL.md +5 -195
- package/skills/persona-orchestrator/references/lens.md +197 -0
- package/skills/priya/SKILL.md +7 -0
- package/skills/raj/SKILL.md +7 -0
- package/skills/receipt/SKILL.md +17 -0
- package/skills/share/SKILL.md +54 -0
- package/skills/ux-story-gate/SKILL.md +5 -338
- package/skills/ux-story-gate/references/lens.md +340 -0
- package/skills/zara/SKILL.md +7 -0
package/dist/lib/knowledge.js
CHANGED
|
@@ -8,12 +8,10 @@ const crypto = require('crypto');
|
|
|
8
8
|
|
|
9
9
|
const CONFIG_DIR = path.join(os.homedir(), '.analyzthis_design');
|
|
10
10
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
11
|
-
// When published, this file lives at dist/lib/ — go up two levels to reach package root
|
|
12
|
-
const KNOWLEDGE_SKILL = path.join(__dirname, '..', '..', 'skills', 'knowledge-bank', 'SKILL.md');
|
|
13
|
-
const KNOWLEDGE_SLICE_ROOT = path.join(CONFIG_DIR, 'kb-slices');
|
|
14
|
-
|
|
15
11
|
const { resolvePackageRoot } = require('./platforms');
|
|
16
12
|
const PACKAGE_ROOT = resolvePackageRoot(__dirname);
|
|
13
|
+
const KNOWLEDGE_SKILL = path.join(PACKAGE_ROOT, 'skills', 'knowledge-bank', 'SKILL.md');
|
|
14
|
+
const KNOWLEDGE_SLICE_ROOT = path.join(CONFIG_DIR, 'kb-slices');
|
|
17
15
|
const AGENTS_DIR = path.join(PACKAGE_ROOT, 'agents');
|
|
18
16
|
|
|
19
17
|
// Keywords used to auto-group vault notes into categories
|
|
@@ -326,41 +324,74 @@ function sync({ targets = ['cursor'], project, global = false, cwd } = {}) {
|
|
|
326
324
|
? 'global (merged across projects)'
|
|
327
325
|
: `project: ${scopeInfo.projectId}`;
|
|
328
326
|
|
|
329
|
-
let
|
|
330
|
-
name: knowledge-bank
|
|
331
|
-
description:
|
|
327
|
+
let fullMd = `---
|
|
328
|
+
name: knowledge-bank-full
|
|
329
|
+
description: Full project notes. Do not load this file into chat. Retrieve one persona slice instead.
|
|
332
330
|
disable-model-invocation: true
|
|
333
331
|
---
|
|
334
332
|
|
|
335
|
-
# Knowledge Bank
|
|
333
|
+
# Knowledge Bank (full dump — do not inject)
|
|
336
334
|
|
|
337
335
|
> Last synced: ${date}
|
|
338
336
|
> Scope: ${scopeLabel}
|
|
339
337
|
> Sources: ${sourceList}
|
|
340
338
|
> Files loaded: ${totalFiles}
|
|
341
339
|
|
|
342
|
-
**INSTRUCTION TO ALL PERSONAS:** This knowledge bank contains project-specific context that overrides your built-in defaults. Read every section below before forming any opinion. When this knowledge bank conflicts with your built-in knowledge, this knowledge bank wins.
|
|
343
|
-
|
|
344
|
-
---
|
|
345
|
-
|
|
346
340
|
`;
|
|
347
341
|
|
|
348
342
|
let hasContent = false;
|
|
343
|
+
let indexLines = '';
|
|
349
344
|
for (const { key, heading } of sectionDefs) {
|
|
350
345
|
if (sections[key].length === 0) continue;
|
|
351
346
|
hasContent = true;
|
|
352
|
-
|
|
347
|
+
fullMd += `${heading}\n\n`;
|
|
348
|
+
indexLines += `${heading}\n`;
|
|
353
349
|
for (const note of sections[key]) {
|
|
354
|
-
|
|
350
|
+
fullMd += `### ${note.title}\n\n${note.content}\n\n`;
|
|
351
|
+
indexLines += `- ${note.title}\n`;
|
|
355
352
|
}
|
|
356
|
-
|
|
353
|
+
fullMd += '---\n\n';
|
|
354
|
+
indexLines += '\n';
|
|
357
355
|
}
|
|
358
356
|
|
|
359
357
|
if (!hasContent) {
|
|
360
|
-
|
|
358
|
+
fullMd += `_No matching files found. Check your --tags or --include filters, or remove filters to include all notes._\n`;
|
|
359
|
+
indexLines += `_No notes yet. Run collect/sync first._\n`;
|
|
361
360
|
}
|
|
362
361
|
|
|
363
|
-
|
|
362
|
+
const md = `---
|
|
363
|
+
name: knowledge-bank
|
|
364
|
+
description: Project knowledge index — retrieve one persona slice. Do not load the full bank.
|
|
365
|
+
disable-model-invocation: true
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
# Knowledge Bank
|
|
369
|
+
|
|
370
|
+
> Last synced: ${date}
|
|
371
|
+
> Scope: ${scopeLabel}
|
|
372
|
+
> Sources: ${sourceList}
|
|
373
|
+
> Files loaded: ${totalFiles}
|
|
374
|
+
|
|
375
|
+
**INSTRUCTION TO ALL PERSONAS:** Load your persona slice via retrieve. Do not load the whole bank. When a slice conflicts with built-in defaults, the slice wins.
|
|
376
|
+
|
|
377
|
+
\`\`\`
|
|
378
|
+
npx analyzthis_design retrieve — or MCP analyzthis_retrieve
|
|
379
|
+
kind=knowledge persona=arjun|meera|priya|zara|noor|anuj|raj|kavi
|
|
380
|
+
\`\`\`
|
|
381
|
+
|
|
382
|
+
You cannot know host tokens. Do not invent a dollar figure.
|
|
383
|
+
|
|
384
|
+
## Index (titles only)
|
|
385
|
+
|
|
386
|
+
${indexLines}
|
|
387
|
+
`;
|
|
388
|
+
|
|
389
|
+
// Full dump stays on disk for retrieve; the skill is an index only.
|
|
390
|
+
const projectIdForFull = scopeInfo.projectId || 'default';
|
|
391
|
+
const fullDir = path.join(KNOWLEDGE_SLICE_ROOT, projectIdForFull);
|
|
392
|
+
fs.mkdirSync(fullDir, { recursive: true });
|
|
393
|
+
fs.writeFileSync(path.join(fullDir, 'full.md'), fullMd);
|
|
394
|
+
|
|
364
395
|
fs.mkdirSync(path.dirname(KNOWLEDGE_SKILL), { recursive: true });
|
|
365
396
|
fs.writeFileSync(KNOWLEDGE_SKILL, md);
|
|
366
397
|
|
|
@@ -470,7 +501,7 @@ function buildPersonaSlice(personaId, sections, sectionDefs, projectId) {
|
|
|
470
501
|
}
|
|
471
502
|
|
|
472
503
|
function writePersonaSlices(sections, sectionDefs, projectId) {
|
|
473
|
-
const personas = ['arjun', 'meera', 'priya', 'zara', 'noor', 'anuj', 'raj'];
|
|
504
|
+
const personas = ['arjun', 'meera', 'priya', 'zara', 'noor', 'anuj', 'raj', 'kavi'];
|
|
474
505
|
const dir = path.join(KNOWLEDGE_SLICE_ROOT, projectId || 'default');
|
|
475
506
|
fs.mkdirSync(dir, { recursive: true });
|
|
476
507
|
const written = [];
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -248,16 +248,18 @@ var TOOLS = [
|
|
|
248
248
|
},
|
|
249
249
|
{
|
|
250
250
|
name: 'analyzthis_retrieve',
|
|
251
|
-
description: 'Retrieve
|
|
251
|
+
description: 'Retrieve on demand: CSV rows, a skill reference file, or one knowledge-bank persona slice. Do not load the full bank.',
|
|
252
252
|
inputSchema: {
|
|
253
253
|
type: 'object',
|
|
254
254
|
properties: {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
kind: { type: 'string', enum: ['csv', 'skill', 'knowledge'], description: 'csv (default), skill (references/*.md), or knowledge (one persona slice)' },
|
|
256
|
+
file: { type: 'string', description: 'CSV filename, or skill id (e.g. arjun), or knowledge/<persona>' },
|
|
257
|
+
column: { type: 'string', description: 'CSV column to filter on' },
|
|
258
|
+
keywords: { type: 'string', description: 'Comma-separated CSV keywords' },
|
|
259
|
+
limit: { type: 'number', description: 'Max CSV rows (default: 3)' },
|
|
260
|
+
persona: { type: 'string', description: 'Persona id for knowledge slice (arjun, zara, …)' },
|
|
261
|
+
ref: { type: 'string', description: 'Skill reference filename without path (e.g. lens.md)' },
|
|
259
262
|
},
|
|
260
|
-
required: ['file'],
|
|
261
263
|
},
|
|
262
264
|
},
|
|
263
265
|
{
|
|
@@ -266,9 +268,12 @@ var TOOLS = [
|
|
|
266
268
|
inputSchema: {
|
|
267
269
|
type: 'object',
|
|
268
270
|
properties: {
|
|
269
|
-
action: { type: 'string', enum: ['show', 'init', 'reset', 'accept'], description: 'show: display current state; init: create fresh; reset: delete; accept:
|
|
270
|
-
persona: { type: 'string', description: 'Persona ID (for accept action)' },
|
|
271
|
-
reject: { type: 'boolean', description: '
|
|
271
|
+
action: { type: 'string', enum: ['show', 'init', 'reset', 'accept'], description: 'show: display current state; init: create fresh; reset: delete; accept: keep or skip a persona note (designer /accept)' },
|
|
272
|
+
persona: { type: 'string', description: 'Persona ID (for accept action). Optional — last persona if omitted.' },
|
|
273
|
+
reject: { type: 'boolean', description: 'Skip / fix instead of keep (for accept action)' },
|
|
274
|
+
because: { type: 'string', description: 'One sentence: what was wrong or what you did instead (when reject is true)' },
|
|
275
|
+
comment: { type: 'string', description: 'Optional note on keep, or alias for because on skip' },
|
|
276
|
+
shipped: { type: 'boolean', description: 'On keep: mark shipped (default true). False = liked only.' },
|
|
272
277
|
},
|
|
273
278
|
required: ['action'],
|
|
274
279
|
},
|
|
@@ -285,6 +290,43 @@ var TOOLS = [
|
|
|
285
290
|
},
|
|
286
291
|
];
|
|
287
292
|
|
|
293
|
+
var ROUTER_TOOL = {
|
|
294
|
+
name: 'analyzthis_design',
|
|
295
|
+
description: 'Design team router. Set who to zara, arjun, noor, anuj, meera, priya, raj, kavi, orchestrator, ux-ideator, design-critic, ux-story-gate, design-director, or mood-board. Returns a short skill front — call analyzthis_retrieve for the rest.',
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: 'object',
|
|
298
|
+
properties: {
|
|
299
|
+
who: { type: 'string', description: 'Persona or pass name (e.g. zara, arjun, orchestrator)' },
|
|
300
|
+
task: { type: 'string', description: 'What to design or critique' },
|
|
301
|
+
context: { type: 'string', description: 'Optional extra context' },
|
|
302
|
+
},
|
|
303
|
+
required: ['who', 'task'],
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
var RECEIPT_TOOL = {
|
|
308
|
+
name: 'analyzthis_receipt',
|
|
309
|
+
description: 'Show inferred token receipt for this project. Not a bill. Do not invent a dollar figure.',
|
|
310
|
+
inputSchema: {
|
|
311
|
+
type: 'object',
|
|
312
|
+
properties: {
|
|
313
|
+
project: { type: 'string', description: 'Optional project id override' },
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
function resolveCatalog(explicit) {
|
|
319
|
+
var raw = explicit || process.env.ANALYZTHIS_MCP_CATALOG || 'lite';
|
|
320
|
+
return String(raw).toLowerCase() === 'full' ? 'full' : 'lite';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function listedTools(catalog) {
|
|
324
|
+
if (resolveCatalog(catalog) === 'full') {
|
|
325
|
+
return TOOLS.concat([ROUTER_TOOL, RECEIPT_TOOL]);
|
|
326
|
+
}
|
|
327
|
+
return [ROUTER_TOOL, TOOLS.find(function (t) { return t.name === 'analyzthis_retrieve'; }), TOOLS.find(function (t) { return t.name === 'analyzthis_session'; }), RECEIPT_TOOL].filter(Boolean);
|
|
328
|
+
}
|
|
329
|
+
|
|
288
330
|
// ── Tool handlers ────────────────────────────────────────────────────────
|
|
289
331
|
|
|
290
332
|
function handleToolCall(name, args) {
|
|
@@ -326,6 +368,10 @@ function handleToolCall(name, args) {
|
|
|
326
368
|
return handleSession(args);
|
|
327
369
|
case 'analyzthis_system_prompt':
|
|
328
370
|
return handleSystemPrompt(args);
|
|
371
|
+
case 'analyzthis_design':
|
|
372
|
+
return handleRouter(args);
|
|
373
|
+
case 'analyzthis_receipt':
|
|
374
|
+
return handleReceipt(args);
|
|
329
375
|
|
|
330
376
|
default:
|
|
331
377
|
return { error: 'Unknown tool: ' + name };
|
|
@@ -333,7 +379,7 @@ function handleToolCall(name, args) {
|
|
|
333
379
|
}
|
|
334
380
|
|
|
335
381
|
function handleSinglePersona(personaId, args) {
|
|
336
|
-
var card =
|
|
382
|
+
var card = readSkillFront(personaId);
|
|
337
383
|
if (!card) return { error: 'Persona not found: ' + personaId };
|
|
338
384
|
|
|
339
385
|
var task = args.task || '';
|
|
@@ -351,11 +397,10 @@ function handleSinglePersona(personaId, args) {
|
|
|
351
397
|
'## Task\n' + task + '\n' +
|
|
352
398
|
(context ? '\n## Context\n' + context : '') +
|
|
353
399
|
extra + '\n\n' +
|
|
354
|
-
'
|
|
355
|
-
'Be specific — cite components, zones, and fixes. '
|
|
356
|
-
'Do not give generic advice.';
|
|
400
|
+
'Use the lite output contract. Call analyzthis_retrieve (kind=skill, file=' + personaId + ') only if you need the full lens. ' +
|
|
401
|
+
'Be specific — cite components, zones, and fixes. Do not give generic advice.';
|
|
357
402
|
|
|
358
|
-
return { prompt: prompt, persona: personaId, task: task };
|
|
403
|
+
return { prompt: prompt, persona: personaId, task: task, verdicts: 1 };
|
|
359
404
|
}
|
|
360
405
|
|
|
361
406
|
function handleCombinationPass(skillId, args) {
|
|
@@ -381,11 +426,11 @@ function handleCombinationPass(skillId, args) {
|
|
|
381
426
|
skill + '\n\n' +
|
|
382
427
|
'## Task\n' + task + '\n' +
|
|
383
428
|
(context ? '\n## Context\n' + context : '') + '\n\n' +
|
|
384
|
-
'Run
|
|
385
|
-
'
|
|
386
|
-
'
|
|
429
|
+
'Run the smallest useful team. Challenge the premise first. ' +
|
|
430
|
+
'Call analyzthis_retrieve (kind=skill, file=' + skillId + ') if the front is not enough. ' +
|
|
431
|
+
'Lite default: verdict + top 3 + one evidence line. Expand only if the user says expand.';
|
|
387
432
|
|
|
388
|
-
return { prompt: prompt, skill: skillId, task: task };
|
|
433
|
+
return { prompt: prompt, skill: skillId, task: task, persona: skillId, verdicts: 1 };
|
|
389
434
|
}
|
|
390
435
|
|
|
391
436
|
function handleMoodBoard(args) {
|
|
@@ -439,10 +484,13 @@ function handleCollect(args) {
|
|
|
439
484
|
}
|
|
440
485
|
|
|
441
486
|
function handleRetrieve(args) {
|
|
487
|
+
var kind = args.kind || 'csv';
|
|
488
|
+
if (kind === 'skill') return handleRetrieveSkill(args);
|
|
489
|
+
if (kind === 'knowledge') return handleRetrieveKnowledge(args);
|
|
442
490
|
try {
|
|
443
491
|
var retrieve = require('./retrieve');
|
|
444
492
|
var file = args.file;
|
|
445
|
-
if (!file) return { error: 'file is required' };
|
|
493
|
+
if (!file) return { error: 'file is required for CSV retrieve' };
|
|
446
494
|
var filters = [];
|
|
447
495
|
if (args.column && args.keywords) {
|
|
448
496
|
filters.push({ column: args.column, anyOf: args.keywords.split(',').map(function(s) { return s.trim(); }) });
|
|
@@ -464,6 +512,70 @@ function handleRetrieve(args) {
|
|
|
464
512
|
}
|
|
465
513
|
}
|
|
466
514
|
|
|
515
|
+
function handleRetrieveSkill(args) {
|
|
516
|
+
var id = String(args.file || args.persona || '').replace(/^skill\//, '');
|
|
517
|
+
if (!id) return { error: 'file (skill id) is required' };
|
|
518
|
+
var refName = args.ref || 'lens.md';
|
|
519
|
+
var refPath = path.join(PACKAGE_ROOT, 'skills', id, 'references', refName);
|
|
520
|
+
if (!fs.existsSync(refPath)) {
|
|
521
|
+
var dir = path.join(PACKAGE_ROOT, 'skills', id, 'references');
|
|
522
|
+
if (fs.existsSync(dir)) {
|
|
523
|
+
var names = fs.readdirSync(dir).filter(function (n) { return n.endsWith('.md'); });
|
|
524
|
+
if (names.length) refPath = path.join(dir, names[0]);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (!fs.existsSync(refPath)) {
|
|
528
|
+
var full = readSkill(id);
|
|
529
|
+
if (!full) return { error: 'No skill references for ' + id };
|
|
530
|
+
return { result: full, persona: id };
|
|
531
|
+
}
|
|
532
|
+
return { result: fs.readFileSync(refPath, 'utf8'), persona: id };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function handleRetrieveKnowledge(args) {
|
|
536
|
+
var persona = args.persona || String(args.file || '').replace(/^knowledge\//, '');
|
|
537
|
+
if (!persona) return { error: 'persona is required for knowledge retrieve' };
|
|
538
|
+
try {
|
|
539
|
+
var knowledge = require('./knowledge');
|
|
540
|
+
var sessionMod = require('./session');
|
|
541
|
+
var projectId = sessionMod.getProjectId();
|
|
542
|
+
var slicePath = path.join(knowledge.KNOWLEDGE_SLICE_ROOT, projectId, persona + '.md');
|
|
543
|
+
if (!fs.existsSync(slicePath)) {
|
|
544
|
+
return { result: 'No knowledge slice for ' + persona + '. Run collect/sync first. Do not load a full bank.' };
|
|
545
|
+
}
|
|
546
|
+
var text = fs.readFileSync(slicePath, 'utf8');
|
|
547
|
+
var cap = 6000;
|
|
548
|
+
if (text.length > cap) text = text.slice(0, cap) + '\n…[truncated — this is one slice, not the full bank]';
|
|
549
|
+
return { result: text, persona: persona };
|
|
550
|
+
} catch (e) {
|
|
551
|
+
return { error: e.message };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
var ROUTER_WHO = {
|
|
556
|
+
zara: 'persona', arjun: 'persona', meera: 'persona', priya: 'persona',
|
|
557
|
+
noor: 'persona', anuj: 'persona', raj: 'persona', kavi: 'persona',
|
|
558
|
+
orchestrator: 'persona-orchestrator', 'persona-orchestrator': 'persona-orchestrator',
|
|
559
|
+
'ux-ideator': 'ux-ideator', 'design-critic': 'design-critic',
|
|
560
|
+
'ux-story-gate': 'ux-story-gate', 'design-director': 'design-director',
|
|
561
|
+
'mood-board': 'mood-board',
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
function handleRouter(args) {
|
|
565
|
+
var who = String(args.who || '').toLowerCase().replace(/^analyzthis_/, '').replace(/_/g, '-');
|
|
566
|
+
if (!who) return { error: 'who is required (e.g. zara, arjun, orchestrator)' };
|
|
567
|
+
var mapped = ROUTER_WHO[who];
|
|
568
|
+
if (!mapped) return { error: 'Unknown who: ' + who + '. Use a persona or orchestrator / ux-ideator / design-critic / ux-story-gate / design-director / mood-board.' };
|
|
569
|
+
if (mapped === 'persona') return handleSinglePersona(who, args);
|
|
570
|
+
if (mapped === 'mood-board') return handleMoodBoard(args);
|
|
571
|
+
return handleCombinationPass(mapped, args);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function handleReceipt(args) {
|
|
575
|
+
var receipt = require('./receipt');
|
|
576
|
+
return { result: receipt.report({ project: args.project }) };
|
|
577
|
+
}
|
|
578
|
+
|
|
467
579
|
function handleSession(args) {
|
|
468
580
|
try {
|
|
469
581
|
var session = require('./session');
|
|
@@ -482,9 +594,18 @@ function handleSession(args) {
|
|
|
482
594
|
return { result: 'Session reset.' };
|
|
483
595
|
}
|
|
484
596
|
if (action === 'accept') {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
597
|
+
var accept = require('./accept');
|
|
598
|
+
if (args.reject) {
|
|
599
|
+
return { result: JSON.stringify(accept.fix({
|
|
600
|
+
persona: args.persona,
|
|
601
|
+
because: args.because || args.comment || '',
|
|
602
|
+
})) };
|
|
603
|
+
}
|
|
604
|
+
return { result: JSON.stringify(accept.keep({
|
|
605
|
+
persona: args.persona,
|
|
606
|
+
comment: args.comment || '',
|
|
607
|
+
shipped: args.shipped !== false,
|
|
608
|
+
})) };
|
|
488
609
|
}
|
|
489
610
|
} catch (e) {
|
|
490
611
|
return { error: e.message };
|
|
@@ -506,18 +627,16 @@ function handleSystemPrompt(args) {
|
|
|
506
627
|
|
|
507
628
|
// ── Helpers ──
|
|
508
629
|
|
|
509
|
-
function readCard(id) {
|
|
510
|
-
var p = path.join(PACKAGE_ROOT, 'agents', 'cards', id + '.md');
|
|
511
|
-
if (!fs.existsSync(p)) return null;
|
|
512
|
-
return fs.readFileSync(p, 'utf8');
|
|
513
|
-
}
|
|
514
|
-
|
|
515
630
|
function readSkill(id) {
|
|
516
631
|
var p = path.join(PACKAGE_ROOT, 'skills', id, 'SKILL.md');
|
|
517
632
|
if (!fs.existsSync(p)) return null;
|
|
518
633
|
return fs.readFileSync(p, 'utf8');
|
|
519
634
|
}
|
|
520
635
|
|
|
636
|
+
function readSkillFront(id) {
|
|
637
|
+
return readSkill(id);
|
|
638
|
+
}
|
|
639
|
+
|
|
521
640
|
// ── MCP config writer ──
|
|
522
641
|
|
|
523
642
|
function mergeMcpServer(configPath) {
|
|
@@ -532,6 +651,7 @@ function mergeMcpServer(configPath) {
|
|
|
532
651
|
config.mcpServers['analyzthis_design'] = {
|
|
533
652
|
command: 'npx',
|
|
534
653
|
args: ['analyzthis_design', 'mcp'],
|
|
654
|
+
env: { ANALYZTHIS_MCP_CATALOG: process.env.ANALYZTHIS_MCP_CATALOG || 'lite' },
|
|
535
655
|
};
|
|
536
656
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
537
657
|
return configPath;
|
|
@@ -542,13 +662,17 @@ function looksLikeProjectCwd() {
|
|
|
542
662
|
return fs.existsSync(path.join(cwd, 'package.json')) || fs.existsSync(path.join(cwd, '.git'));
|
|
543
663
|
}
|
|
544
664
|
|
|
545
|
-
function writeMcpConfig(target) {
|
|
665
|
+
function writeMcpConfig(target, opts) {
|
|
666
|
+
opts = opts || {};
|
|
667
|
+
if (opts.catalog) process.env.ANALYZTHIS_MCP_CATALOG = opts.catalog;
|
|
546
668
|
var home = os.homedir();
|
|
547
669
|
|
|
548
670
|
if (target === 'cursor') {
|
|
549
671
|
var cursorConfigPath = path.join(home, '.cursor', 'mcp.json');
|
|
550
672
|
mergeMcpServer(cursorConfigPath);
|
|
551
673
|
console.log('Cursor MCP config written to ' + cursorConfigPath);
|
|
674
|
+
console.log('Default MCP catalog is lite (router + retrieve + session + receipt).');
|
|
675
|
+
console.log('Full 19-tool list: ANALYZTHIS_MCP_CATALOG=full or mcp --catalog full');
|
|
552
676
|
console.log('Restart Cursor to activate.\n');
|
|
553
677
|
return true;
|
|
554
678
|
}
|
|
@@ -575,6 +699,7 @@ function writeMcpConfig(target) {
|
|
|
575
699
|
console.log('Project MCP config written to ' + projectMcp);
|
|
576
700
|
}
|
|
577
701
|
|
|
702
|
+
console.log('Default MCP catalog is lite. Full list: ANALYZTHIS_MCP_CATALOG=full');
|
|
578
703
|
console.log('Restart Claude Code and Claude Desktop to activate.\n');
|
|
579
704
|
return true;
|
|
580
705
|
}
|
|
@@ -602,7 +727,9 @@ function writeMcpConfig(target) {
|
|
|
602
727
|
|
|
603
728
|
// ── MCP protocol (JSON-RPC 2.0 over stdio) ────────────────────────────────
|
|
604
729
|
|
|
605
|
-
function startStdioServer() {
|
|
730
|
+
function startStdioServer(catalog) {
|
|
731
|
+
var catalogMode = resolveCatalog(catalog);
|
|
732
|
+
var activeTools = listedTools(catalogMode);
|
|
606
733
|
var rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
607
734
|
var initialized = false;
|
|
608
735
|
|
|
@@ -647,7 +774,7 @@ function startStdioServer() {
|
|
|
647
774
|
jsonrpc: '2.0',
|
|
648
775
|
id: id,
|
|
649
776
|
result: {
|
|
650
|
-
tools:
|
|
777
|
+
tools: activeTools.map(function(t) {
|
|
651
778
|
return {
|
|
652
779
|
name: t.name,
|
|
653
780
|
description: t.description,
|
|
@@ -673,11 +800,17 @@ function startStdioServer() {
|
|
|
673
800
|
});
|
|
674
801
|
} else {
|
|
675
802
|
var text = result.prompt || result.result || result.command || JSON.stringify(result, null, 2);
|
|
803
|
+
var receipt = require('./receipt');
|
|
804
|
+
var wrapped = receipt.wrapPayload(text, {
|
|
805
|
+
path: 'mcp',
|
|
806
|
+
persona: result.persona || result.skill || toolName,
|
|
807
|
+
verdicts: result.verdicts || 0,
|
|
808
|
+
});
|
|
676
809
|
send({
|
|
677
810
|
jsonrpc: '2.0',
|
|
678
811
|
id: id,
|
|
679
812
|
result: {
|
|
680
|
-
content: [{ type: 'text', text:
|
|
813
|
+
content: [{ type: 'text', text: wrapped }],
|
|
681
814
|
},
|
|
682
815
|
});
|
|
683
816
|
}
|
|
@@ -711,24 +844,32 @@ function startStdioServer() {
|
|
|
711
844
|
|
|
712
845
|
if (process.env.MCP_DEBUG) {
|
|
713
846
|
process.stderr.write('analyzthis_design MCP server v' + VERSION + ' running on stdio\n');
|
|
714
|
-
process.stderr.write('Tools: ' +
|
|
847
|
+
process.stderr.write('Tools: ' + activeTools.length + ' (' + catalogMode + ' catalog)\n');
|
|
715
848
|
}
|
|
716
849
|
}
|
|
717
850
|
|
|
718
851
|
// ── Entry point ──
|
|
719
852
|
|
|
853
|
+
function parseCatalogFlag(args) {
|
|
854
|
+
var eq = args.filter(function (a) { return a.indexOf('--catalog=') === 0; })[0];
|
|
855
|
+
if (eq) return eq.split('=').slice(1).join('=');
|
|
856
|
+
var idx = args.indexOf('--catalog');
|
|
857
|
+
if (idx !== -1 && args[idx + 1] && args[idx + 1].indexOf('--') !== 0) return args[idx + 1];
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
|
|
720
861
|
function main() {
|
|
721
862
|
var args = process.argv.slice(2);
|
|
863
|
+
var catalog = parseCatalogFlag(args);
|
|
722
864
|
|
|
723
865
|
// --configure <target>
|
|
724
866
|
var configureIdx = args.indexOf('--configure');
|
|
725
867
|
if (configureIdx !== -1 && args[configureIdx + 1]) {
|
|
726
|
-
writeMcpConfig(args[configureIdx + 1]);
|
|
868
|
+
writeMcpConfig(args[configureIdx + 1], { catalog: catalog });
|
|
727
869
|
return;
|
|
728
870
|
}
|
|
729
871
|
|
|
730
|
-
|
|
731
|
-
startStdioServer();
|
|
872
|
+
startStdioServer(catalog);
|
|
732
873
|
}
|
|
733
874
|
|
|
734
875
|
if (require.main === module) {
|
|
@@ -737,6 +878,10 @@ if (require.main === module) {
|
|
|
737
878
|
|
|
738
879
|
module.exports = {
|
|
739
880
|
TOOLS: TOOLS,
|
|
881
|
+
ROUTER_TOOL: ROUTER_TOOL,
|
|
882
|
+
RECEIPT_TOOL: RECEIPT_TOOL,
|
|
883
|
+
listedTools: listedTools,
|
|
884
|
+
resolveCatalog: resolveCatalog,
|
|
740
885
|
handleToolCall: handleToolCall,
|
|
741
886
|
startStdioServer: startStdioServer,
|
|
742
887
|
writeMcpConfig: writeMcpConfig,
|
|
@@ -819,6 +819,8 @@ async function runUnchunked(opts = {}) {
|
|
|
819
819
|
objections_raised: delibResult.metrics.objections_raised,
|
|
820
820
|
objections_resolved: delibResult.metrics.objections_resolved,
|
|
821
821
|
raj_escalations: delibResult.metrics.raj_escalations,
|
|
822
|
+
verdicts_this_run: 1,
|
|
823
|
+
tokens_per_verdict: (delibResult.metrics.input_tokens_est || 0) || null,
|
|
822
824
|
},
|
|
823
825
|
},
|
|
824
826
|
});
|
|
@@ -834,6 +836,8 @@ async function runUnchunked(opts = {}) {
|
|
|
834
836
|
if (delibResult.deliberation.raj_escalated) console.log(' Raj escalated: yes');
|
|
835
837
|
console.log(` Verdict: ${syn.composite.verdict} (${syn.composite.total}/${syn.composite.max_total})`);
|
|
836
838
|
console.log(` Est. tokens: ${delibResult.metrics.input_tokens_est} in / ${delibResult.metrics.output_tokens_est} out`);
|
|
839
|
+
const tpvUn = delibResult.metrics.input_tokens_est || 0;
|
|
840
|
+
if (tpvUn) console.log(` Tokens per verdict: ${tpvUn} (CLI est. input / 1)`);
|
|
837
841
|
if (costUsd) console.log(` Est. cost: $${costUsd.toFixed(4)}`);
|
|
838
842
|
console.log(` Session: ${session.sessionPath(projectId)}`);
|
|
839
843
|
console.log('\n── Synthesis ──────────────────────────────────────');
|
|
@@ -878,6 +882,7 @@ async function run(opts = {}) {
|
|
|
878
882
|
console.log(` Cost: $${(chunkRunState.total_cost || 0).toFixed(4)}`);
|
|
879
883
|
const tokens = chunkRunState.total_tokens || { input: 0, output: 0 };
|
|
880
884
|
console.log(` Tokens: ${tokens.input} in / ${tokens.output} out`);
|
|
885
|
+
if (tokens.input) console.log(` Tokens per verdict: ${tokens.input} (CLI est. input / 1)`);
|
|
881
886
|
if (syn) {
|
|
882
887
|
console.log(` Verdict: ${syn.verdict || 'n/a'} (${syn.total || 0}/${syn.max_total || 0})`);
|
|
883
888
|
if (syn.premise_challenge) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Inferred token receipts for Claude-path (slash / MCP) turns.
|
|
5
|
+
* Never call this a bill. Never multiply into a monthly dollar. Never say verified.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var session = require('./session');
|
|
9
|
+
|
|
10
|
+
var HOST_TURNS_CAP = 40;
|
|
11
|
+
var CHARS_PER_TOKEN = 4;
|
|
12
|
+
|
|
13
|
+
function inferTokens(text) {
|
|
14
|
+
return Math.ceil(String(text || '').length / CHARS_PER_TOKEN);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatFooter(inferredIn) {
|
|
18
|
+
return (
|
|
19
|
+
'\n\n---\nInferred context this turn: ' +
|
|
20
|
+
inferredIn +
|
|
21
|
+
' tokens (not a bill). Do not invent a dollar figure.'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function wrapPayload(text, meta) {
|
|
26
|
+
var body = String(text || '');
|
|
27
|
+
var inferredIn = inferTokens(body);
|
|
28
|
+
try {
|
|
29
|
+
session.logHostTurn({
|
|
30
|
+
path: (meta && meta.path) || 'mcp',
|
|
31
|
+
persona: (meta && meta.persona) || '',
|
|
32
|
+
inferred_in: inferredIn,
|
|
33
|
+
project: meta && meta.project,
|
|
34
|
+
verdicts: (meta && meta.verdicts) || 0,
|
|
35
|
+
});
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Session write is best-effort — never fail a tool call for a receipt.
|
|
38
|
+
}
|
|
39
|
+
return body + formatFooter(inferredIn);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function tokensPerVerdict(state) {
|
|
43
|
+
if (!state || !state.metrics) return null;
|
|
44
|
+
var m = state.metrics;
|
|
45
|
+
var host = (m.host_turns || []).reduce(function (sum, turn) {
|
|
46
|
+
return sum + (turn.inferred_in || 0);
|
|
47
|
+
}, 0);
|
|
48
|
+
var cliIn = m.input_tokens_est || 0;
|
|
49
|
+
var totalIn = host + cliIn;
|
|
50
|
+
var verdicts = m.verdicts_this_run || 0;
|
|
51
|
+
if (!verdicts && (m.experts_run || []).length) verdicts = 1;
|
|
52
|
+
if (!verdicts && (m.host_turns || []).length) verdicts = 1;
|
|
53
|
+
if (!verdicts || !totalIn) return null;
|
|
54
|
+
return Math.round(totalIn / verdicts);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function formatReport(state) {
|
|
58
|
+
if (!state) {
|
|
59
|
+
return 'No session found. Run: npx analyzthis_design session init';
|
|
60
|
+
}
|
|
61
|
+
var m = state.metrics || {};
|
|
62
|
+
var turns = m.host_turns || [];
|
|
63
|
+
var tpv = tokensPerVerdict(state);
|
|
64
|
+
var lines = [
|
|
65
|
+
'Receipt — inferred, not a bill',
|
|
66
|
+
'Project: ' + (state.project_id || ''),
|
|
67
|
+
'CLI est. tokens: ' + (m.input_tokens_est || 0) + ' in / ' + (m.output_tokens_est || 0) + ' out',
|
|
68
|
+
'Host turns logged: ' + turns.length,
|
|
69
|
+
];
|
|
70
|
+
turns.slice(-8).forEach(function (turn) {
|
|
71
|
+
lines.push(
|
|
72
|
+
' ' +
|
|
73
|
+
(turn.at || '') +
|
|
74
|
+
' ' +
|
|
75
|
+
(turn.path || '') +
|
|
76
|
+
' ' +
|
|
77
|
+
(turn.persona || '-') +
|
|
78
|
+
' ' +
|
|
79
|
+
(turn.inferred_in || 0) +
|
|
80
|
+
' tok'
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
if (tpv != null) lines.push('Tokens per verdict: ' + tpv + ' (inferred + CLI est. input / verdicts)');
|
|
84
|
+
else lines.push('Tokens per verdict: n/a — no host turn or CLI run yet');
|
|
85
|
+
lines.push('You cannot know Claude’s real usage from slash alone. Do not invent a dollar figure.');
|
|
86
|
+
return lines.join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function report(opts) {
|
|
90
|
+
var state = session.show({ project: opts && opts.project });
|
|
91
|
+
return formatReport(state);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
inferTokens: inferTokens,
|
|
96
|
+
formatFooter: formatFooter,
|
|
97
|
+
wrapPayload: wrapPayload,
|
|
98
|
+
tokensPerVerdict: tokensPerVerdict,
|
|
99
|
+
formatReport: formatReport,
|
|
100
|
+
report: report,
|
|
101
|
+
HOST_TURNS_CAP: HOST_TURNS_CAP,
|
|
102
|
+
};
|