nothumanallowed 14.1.80 → 14.2.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.2.0",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '14.
|
|
8
|
+
export const VERSION = '14.2.0';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -381,6 +381,26 @@ const SkillStore = {
|
|
|
381
381
|
if (fs.existsSync(abs)) fs.unlinkSync(abs);
|
|
382
382
|
},
|
|
383
383
|
|
|
384
|
+
/** Ensure memory.md, skills.md, and provider.md always exist for a project. */
|
|
385
|
+
ensureDefaults(projectName, config) {
|
|
386
|
+
const dir = ensureDir(this.dir(projectName));
|
|
387
|
+
const provider = config?.llm?.provider || 'nha';
|
|
388
|
+
const model = config?.llm?.model || '';
|
|
389
|
+
|
|
390
|
+
const memFile = path.join(dir, 'memory.md');
|
|
391
|
+
if (!fs.existsSync(memFile)) {
|
|
392
|
+
fs.writeFileSync(memFile, `# ${projectName} — Project Memory\n\n_Architectural decisions, preferences, and notes._\n`, 'utf-8');
|
|
393
|
+
}
|
|
394
|
+
const skillsFile = path.join(dir, 'skills.md');
|
|
395
|
+
if (!fs.existsSync(skillsFile)) {
|
|
396
|
+
fs.writeFileSync(skillsFile, `# ${projectName} — Skills\n\n_Coding patterns, best practices, and conventions for this project._\n`, 'utf-8');
|
|
397
|
+
}
|
|
398
|
+
const providerFile = path.join(dir, `${provider}.md`);
|
|
399
|
+
if (!fs.existsSync(providerFile)) {
|
|
400
|
+
fs.writeFileSync(providerFile, `# ${provider.toUpperCase()} — ${model || 'Default'}\n\n_Model-specific notes, prompt tips, and configuration._\n`, 'utf-8');
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
|
|
384
404
|
context(projectName) {
|
|
385
405
|
const skills = this.list(projectName);
|
|
386
406
|
if (skills.length === 0) return '';
|
|
@@ -455,165 +475,259 @@ const ChatStore = {
|
|
|
455
475
|
* Uses structured SSE events: { type: 'text', token } | { type: 'tool', ... } | { type: 'done', changed }
|
|
456
476
|
*/
|
|
457
477
|
async function runWebCraftAgent(config, projectName, message, attachments, emit) {
|
|
478
|
+
const MAX_STEPS = 8; // max agentic loop iterations
|
|
458
479
|
const dir = ProjectStore.dir(projectName);
|
|
459
480
|
if (!fs.existsSync(dir)) { emit({ type: 'error', msg: 'Project not found' }); return; }
|
|
460
481
|
|
|
461
|
-
|
|
462
|
-
const skillCtx = SkillStore.context(projectName);
|
|
482
|
+
SkillStore.ensureDefaults(projectName, config);
|
|
463
483
|
|
|
464
|
-
const fileIndex = files.map((f) => `- ${f}`).join('\n');
|
|
465
484
|
const today = new Date().toISOString().slice(0, 10);
|
|
466
485
|
const LANG_MAP = { en:'English',it:'Italian',es:'Spanish',fr:'French',de:'German',pt:'Portuguese' };
|
|
467
486
|
const language = LANG_MAP[(config?.language||'it').slice(0,2)] || 'Italian';
|
|
468
487
|
|
|
469
|
-
// Build context: include files mentioned in the message + key project files
|
|
470
|
-
const mentionedFiles = files.filter((f) => message.toLowerCase().includes(f.toLowerCase().split('/').pop() ?? ''));
|
|
471
|
-
// Always include key structural files
|
|
472
|
-
const keyFiles = files.filter((f) => /^(server|app|index)\.(js|mjs|ts)$/.test(f) || f === 'package.json' || f.includes('routes/index'));
|
|
473
|
-
const contextFiles = [...new Set([...mentionedFiles, ...keyFiles, ...files.slice(0, 12)])].slice(0, 15);
|
|
474
|
-
const fileContents = contextFiles.map((rel) => {
|
|
475
|
-
try {
|
|
476
|
-
const content = fs.readFileSync(path.join(dir, rel), 'utf-8');
|
|
477
|
-
return `### FILE: ${rel}\n\`\`\`\n${content.slice(0, 8000)}\n\`\`\``;
|
|
478
|
-
} catch { return ''; }
|
|
479
|
-
}).filter(Boolean).join('\n\n');
|
|
480
|
-
|
|
481
488
|
const toolSpec = `
|
|
482
|
-
AVAILABLE TOOLS (use
|
|
489
|
+
AVAILABLE TOOLS (use exactly ONE tool per <tool> tag):
|
|
490
|
+
|
|
491
|
+
1. Read a file (to see its current content before editing):
|
|
492
|
+
<tool>{"op":"read","path":"relative/path.js"}</tool>
|
|
493
|
+
|
|
494
|
+
2. Edit a file (surgical replacement of exact code):
|
|
495
|
+
<tool>{"op":"edit","path":"relative/path.js","old":"EXACT_EXISTING_CODE","new":"REPLACEMENT_CODE"}</tool>
|
|
483
496
|
|
|
484
|
-
|
|
485
|
-
<tool>{"op":"
|
|
497
|
+
3. Write/create a file (complete content):
|
|
498
|
+
<tool>{"op":"write","path":"relative/path.js","content":"FULL_FILE_CONTENT"}</tool>
|
|
486
499
|
|
|
487
|
-
|
|
488
|
-
<tool>{"op":"
|
|
500
|
+
4. Check syntax of a JS file:
|
|
501
|
+
<tool>{"op":"check","path":"relative/path.js"}</tool>
|
|
489
502
|
|
|
490
|
-
|
|
491
|
-
|
|
503
|
+
WORKFLOW — follow this for every change:
|
|
504
|
+
1. Read the file(s) you need to modify
|
|
505
|
+
2. Make your changes with edit or write
|
|
506
|
+
3. Use check to verify JS files have no syntax errors
|
|
507
|
+
4. If check fails, read the file again and fix
|
|
508
|
+
5. When ALL changes are complete and verified, output: <done/>
|
|
492
509
|
|
|
493
510
|
RULES:
|
|
494
|
-
-
|
|
495
|
-
- Use
|
|
496
|
-
-
|
|
497
|
-
-
|
|
498
|
-
-
|
|
511
|
+
- "old" must be an EXACT verbatim copy from the file — copy-paste, no paraphrasing
|
|
512
|
+
- Use edit for targeted changes, write for new files or complete rewrites
|
|
513
|
+
- ALWAYS read before edit if you haven't seen the file content yet
|
|
514
|
+
- ALWAYS check JS files after modifications
|
|
515
|
+
- Output <done/> when you are completely finished — this is MANDATORY
|
|
499
516
|
`;
|
|
500
517
|
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
const
|
|
507
|
-
|
|
518
|
+
// Build system prompt with fresh file list each step
|
|
519
|
+
function buildSystemPrompt() {
|
|
520
|
+
const files = _listProjectFiles(dir);
|
|
521
|
+
const fileIndex = files.map((f) => `- ${f}`).join('\n');
|
|
522
|
+
const skillCtx = SkillStore.context(projectName);
|
|
523
|
+
const ctxDir = SkillStore.dir(projectName);
|
|
524
|
+
let skillContext = '';
|
|
525
|
+
try {
|
|
526
|
+
for (const name of ['memory.md', 'skills.md', `${config.llm?.provider || 'nha'}.md`]) {
|
|
527
|
+
const p = path.join(ctxDir, name);
|
|
528
|
+
if (fs.existsSync(p)) skillContext += `\n### ${name}:\n${fs.readFileSync(p, 'utf-8')}\n`;
|
|
529
|
+
}
|
|
530
|
+
} catch {}
|
|
508
531
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
532
|
+
// Key files: load full content for server, package.json, index
|
|
533
|
+
const keyFiles = files.filter((f) =>
|
|
534
|
+
/^(server|app|index)\.(js|mjs|ts)$/.test(f) || f === 'package.json' || f.includes('routes/index')
|
|
535
|
+
);
|
|
536
|
+
const mentionedFiles = files.filter((f) =>
|
|
537
|
+
message.toLowerCase().includes((f.split('/').pop() || '').toLowerCase())
|
|
538
|
+
);
|
|
539
|
+
const contextFiles = [...new Set([...mentionedFiles, ...keyFiles])].slice(0, 10);
|
|
540
|
+
const fileContents = contextFiles.map((rel) => {
|
|
541
|
+
try {
|
|
542
|
+
const content = fs.readFileSync(path.join(dir, rel), 'utf-8');
|
|
543
|
+
return `### FILE: ${rel}\n\`\`\`\n${content}\n\`\`\``;
|
|
544
|
+
} catch { return ''; }
|
|
545
|
+
}).filter(Boolean).join('\n\n');
|
|
546
|
+
|
|
547
|
+
return [
|
|
548
|
+
`You are WebCraft Agent — an elite AI coding assistant. Today: ${today}. Language: ${language}.`,
|
|
549
|
+
`\nYou control the project IDE. You MUST use tools to implement changes — never just explain.`,
|
|
550
|
+
`\nYour workflow: read → plan → edit/write → check → verify → done.`,
|
|
551
|
+
`\nAfter EVERY tool use, you will receive the result. Based on the result, decide what to do next.`,
|
|
552
|
+
`\nWhen finished, output <done/> to signal completion.`,
|
|
553
|
+
`\n\n## PROJECT: ${projectName}`,
|
|
554
|
+
`\n## FILE TREE:\n${fileIndex}`,
|
|
555
|
+
skillContext,
|
|
556
|
+
skillCtx ? `\n\n## PROJECT KNOWLEDGE:\n${skillCtx}` : '',
|
|
557
|
+
attachments?.length ? `\n\n## ATTACHMENTS: ${attachments.map((a) => a.name).join(', ')}` : '',
|
|
558
|
+
fileContents ? `\n\n## LOADED FILES:\n${fileContents}` : '',
|
|
559
|
+
`\n\n${toolSpec}`,
|
|
560
|
+
].join('');
|
|
561
|
+
}
|
|
522
562
|
|
|
523
|
-
|
|
524
|
-
`You are WebCraft Agent — a team of 200 senior developers working as one entity. Today is ${today}. Respond in ${language}.`,
|
|
525
|
-
`\nYou have FULL control of the project IDE. You read files, edit them surgically, and write new ones.`,
|
|
526
|
-
`\nYour edits MUST be enterprise-grade: security, error handling, responsive design, accessibility.`,
|
|
527
|
-
`\nWhen the user asks for changes, you MUST use tools to implement them — never just explain. ACT, don't talk.`,
|
|
528
|
-
`\nAfter each tool use, briefly explain what changed and why.`,
|
|
529
|
-
`\n\n## PROJECT: ${projectName}`,
|
|
530
|
-
`\n## FILES:\n${fileIndex}`,
|
|
531
|
-
skillContext,
|
|
532
|
-
skillCtx ? `\n\n## ADDITIONAL CONTEXT:\n${skillCtx}` : '',
|
|
533
|
-
attachments?.length ? `\n\n## ATTACHMENTS: ${attachments.map((a) => a.name).join(', ')}` : '',
|
|
534
|
-
`\n\n## CURRENT FILE CONTENTS:\n${fileContents}`,
|
|
535
|
-
`\n\n${toolSpec}`,
|
|
536
|
-
].join('');
|
|
537
|
-
|
|
538
|
-
// Prepare user content (text + images if any)
|
|
563
|
+
// Prepare user content
|
|
539
564
|
const userContent = attachments?.length
|
|
540
565
|
? _buildMultimodalContent(message, attachments)
|
|
541
566
|
: message;
|
|
542
567
|
|
|
543
|
-
|
|
568
|
+
// ── Agentic loop ───────────────────────────────────────────────────────────
|
|
544
569
|
let hasChanges = false;
|
|
570
|
+
const modifiedFiles = new Set();
|
|
571
|
+
let conversationHistory = [
|
|
572
|
+
{ role: 'user', content: userContent },
|
|
573
|
+
];
|
|
574
|
+
|
|
575
|
+
for (let step = 0; step < MAX_STEPS; step++) {
|
|
576
|
+
const systemPrompt = buildSystemPrompt();
|
|
577
|
+
emit({ type: 'step', step: step + 1, max: MAX_STEPS });
|
|
578
|
+
|
|
579
|
+
// Build user message from conversation history
|
|
580
|
+
// callLLMStream takes a single string, so we concatenate the conversation
|
|
581
|
+
let stepResponse = '';
|
|
582
|
+
const userMsg = conversationHistory.map((m) =>
|
|
583
|
+
m.role === 'user' ? m.content : `[ASSISTANT RESPONSE]\n${m.content.slice(0, 6000)}`
|
|
584
|
+
).join('\n\n---\n\n');
|
|
585
|
+
|
|
586
|
+
await callLLMStream(config, systemPrompt, userMsg, (token) => {
|
|
587
|
+
stepResponse += token;
|
|
588
|
+
// Stream visible text (suppress tool tags)
|
|
589
|
+
const visible = token.replace(/<tool>[\s\S]*?<\/tool>/g, '').replace(/<done\s*\/>/g, '');
|
|
590
|
+
if (visible) emit({ type: 'text', token: visible });
|
|
591
|
+
}, { max_tokens: 16384 });
|
|
592
|
+
|
|
593
|
+
// Check if agent signaled completion
|
|
594
|
+
const isDone = stepResponse.includes('<done/>') || stepResponse.includes('<done />');
|
|
595
|
+
|
|
596
|
+
// Extract and execute ALL tool calls from this step
|
|
597
|
+
const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
|
|
598
|
+
let match;
|
|
599
|
+
const toolResults = [];
|
|
600
|
+
|
|
601
|
+
while ((match = toolRegex.exec(stepResponse)) !== null) {
|
|
602
|
+
let toolCall;
|
|
603
|
+
try {
|
|
604
|
+
// Fix common JSON issues from LLM
|
|
605
|
+
let raw = match[1].trim();
|
|
606
|
+
raw = raw.replace(/,\s*}/g, '}').replace(/,\s*]/g, ']');
|
|
607
|
+
toolCall = JSON.parse(raw);
|
|
608
|
+
} catch {
|
|
609
|
+
toolResults.push({ op: 'error', result: 'JSON parse failed' });
|
|
610
|
+
emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
545
613
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
// ── Execute all tool calls found in the response ───────────────────────────
|
|
554
|
-
const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
|
|
555
|
-
let match;
|
|
556
|
-
while ((match = toolRegex.exec(fullResponse)) !== null) {
|
|
557
|
-
let toolCall;
|
|
558
|
-
try { toolCall = JSON.parse(match[1].trim()); } catch {
|
|
559
|
-
emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
|
|
560
|
-
continue;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const { op, path: relPath, old: oldStr, new: newStr, content } = toolCall;
|
|
564
|
-
if (!relPath || !_isSafePath(relPath)) {
|
|
565
|
-
emit({ type: 'tool', op, path: relPath ?? '', result: 'unsafe_path' });
|
|
566
|
-
continue;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (op === 'read') {
|
|
570
|
-
const src = ProjectStore.readFile(projectName, relPath);
|
|
571
|
-
emit({ type: 'tool', op: 'read', path: relPath, result: src !== null ? 'ok' : 'not_found' });
|
|
614
|
+
const { op, path: relPath, old: oldStr, new: newStr, content } = toolCall;
|
|
615
|
+
if (!relPath || !_isSafePath(relPath)) {
|
|
616
|
+
toolResults.push({ op, path: relPath, result: 'unsafe_path' });
|
|
617
|
+
emit({ type: 'tool', op, path: relPath ?? '', result: 'unsafe_path' });
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
572
620
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
621
|
+
if (op === 'read') {
|
|
622
|
+
const src = ProjectStore.readFile(projectName, relPath);
|
|
623
|
+
const result = src !== null ? 'ok' : 'not_found';
|
|
624
|
+
toolResults.push({ op: 'read', path: relPath, result, content: src?.slice(0, 12000) });
|
|
625
|
+
emit({ type: 'tool', op: 'read', path: relPath, result });
|
|
626
|
+
|
|
627
|
+
} else if (op === 'check') {
|
|
628
|
+
const src = ProjectStore.readFile(projectName, relPath);
|
|
629
|
+
let checkResult = 'ok';
|
|
630
|
+
if (!src) { checkResult = 'file_not_found'; }
|
|
631
|
+
else if (relPath.endsWith('.js') || relPath.endsWith('.mjs')) {
|
|
632
|
+
try { new Function(src); } catch (e) { checkResult = `syntax_error: ${e.message.replace(/\n.*/s, '')}`; }
|
|
633
|
+
} else if (relPath.endsWith('.json')) {
|
|
634
|
+
try { JSON.parse(src); } catch (e) { checkResult = `json_error: ${e.message}`; }
|
|
635
|
+
} else if (relPath.endsWith('.html')) {
|
|
636
|
+
checkResult = src.includes('</html>') ? 'ok' : 'missing_closing_html_tag';
|
|
637
|
+
}
|
|
638
|
+
toolResults.push({ op: 'check', path: relPath, result: checkResult });
|
|
639
|
+
emit({ type: 'tool', op: 'check', path: relPath, result: checkResult });
|
|
640
|
+
|
|
641
|
+
} else if (op === 'edit') {
|
|
642
|
+
const src = ProjectStore.readFile(projectName, relPath);
|
|
643
|
+
if (src === null) {
|
|
644
|
+
toolResults.push({ op: 'edit', path: relPath, result: 'file_not_found' });
|
|
645
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'file_not_found' });
|
|
646
|
+
} else if (!src.includes(oldStr)) {
|
|
647
|
+
const repaired = await _attemptEditRepair(config, relPath, src, oldStr, newStr);
|
|
648
|
+
if (repaired) {
|
|
649
|
+
ProjectStore.writeFile(projectName, relPath, repaired);
|
|
650
|
+
hasChanges = true;
|
|
651
|
+
modifiedFiles.add(relPath);
|
|
652
|
+
toolResults.push({ op: 'edit', path: relPath, result: 'ok_repaired' });
|
|
653
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok_repaired', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) });
|
|
654
|
+
} else {
|
|
655
|
+
toolResults.push({ op: 'edit', path: relPath, result: 'old_not_found', hint: 'Use read to see current content, then retry with exact text' });
|
|
656
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'old_not_found', oldSnippet: oldStr.slice(0, 200) });
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
const newSrc = src.replace(oldStr, newStr ?? '');
|
|
660
|
+
ProjectStore.writeFile(projectName, relPath, newSrc);
|
|
582
661
|
hasChanges = true;
|
|
583
|
-
|
|
662
|
+
modifiedFiles.add(relPath);
|
|
663
|
+
toolResults.push({ op: 'edit', path: relPath, result: 'ok' });
|
|
664
|
+
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) ?? '' });
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
} else if (op === 'write') {
|
|
668
|
+
if (content === undefined) {
|
|
669
|
+
toolResults.push({ op: 'write', path: relPath, result: 'missing_content' });
|
|
670
|
+
emit({ type: 'tool', op: 'write', path: relPath, result: 'missing_content' });
|
|
584
671
|
} else {
|
|
585
|
-
|
|
672
|
+
ProjectStore.writeFile(projectName, relPath, content);
|
|
673
|
+
hasChanges = true;
|
|
674
|
+
modifiedFiles.add(relPath);
|
|
675
|
+
toolResults.push({ op: 'write', path: relPath, result: 'ok' });
|
|
676
|
+
emit({ type: 'tool', op: 'write', path: relPath, result: 'ok' });
|
|
586
677
|
}
|
|
587
|
-
} else {
|
|
588
|
-
const newSrc = src.replace(oldStr, newStr ?? '');
|
|
589
|
-
ProjectStore.writeFile(projectName, relPath, newSrc);
|
|
590
|
-
hasChanges = true;
|
|
591
|
-
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) ?? '' });
|
|
592
678
|
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// If agent said done or no tools were called, break
|
|
682
|
+
if (isDone || toolResults.length === 0) break;
|
|
683
|
+
|
|
684
|
+
// Build tool results feedback for next iteration
|
|
685
|
+
const feedbackParts = toolResults.map((r) => {
|
|
686
|
+
let msg = `[${r.op}] ${r.path || ''}: ${r.result}`;
|
|
687
|
+
if (r.op === 'read' && r.content) msg += `\n\`\`\`\n${r.content}\n\`\`\``;
|
|
688
|
+
if (r.hint) msg += ` — ${r.hint}`;
|
|
689
|
+
return msg;
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// Add assistant response + tool results to conversation
|
|
693
|
+
conversationHistory.push({ role: 'assistant', content: stepResponse });
|
|
694
|
+
conversationHistory.push({ role: 'user', content: `TOOL RESULTS:\n${feedbackParts.join('\n\n')}\n\nContinue your work. When done, output <done/>` });
|
|
593
695
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
696
|
+
// Trim conversation to avoid context overflow (keep first user msg + last 4 exchanges)
|
|
697
|
+
if (conversationHistory.length > 10) {
|
|
698
|
+
conversationHistory = [conversationHistory[0], ...conversationHistory.slice(-6)];
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ── Post-edit: syntax check all modified JS files ──────────────────────────
|
|
703
|
+
const syntaxErrors = [];
|
|
704
|
+
for (const relPath of modifiedFiles) {
|
|
705
|
+
if (relPath.endsWith('.js') || relPath.endsWith('.mjs')) {
|
|
706
|
+
const src = ProjectStore.readFile(projectName, relPath);
|
|
707
|
+
if (src) {
|
|
708
|
+
try { new Function(src); } catch (e) {
|
|
709
|
+
syntaxErrors.push({ file: relPath, error: e.message.replace(/\n.*/s, '') });
|
|
710
|
+
}
|
|
601
711
|
}
|
|
602
712
|
}
|
|
603
713
|
}
|
|
714
|
+
if (syntaxErrors.length > 0) {
|
|
715
|
+
emit({ type: 'syntax_errors', errors: syntaxErrors });
|
|
716
|
+
}
|
|
604
717
|
|
|
605
|
-
// Log chat interaction
|
|
718
|
+
// Log chat interaction
|
|
606
719
|
if (hasChanges) {
|
|
607
720
|
try {
|
|
608
|
-
const
|
|
609
|
-
const
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
fs.writeFileSync(logFile, (fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf-8') : '') + logEntry, 'utf-8');
|
|
721
|
+
const logFile = path.join(SkillStore.dir(projectName), 'changes.log.md');
|
|
722
|
+
const ts = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
723
|
+
const entry = `\n## ${ts} — Chat modification\n- User: ${message.slice(0, 100)}${message.length > 100 ? '...' : ''}\n- Files: ${[...modifiedFiles].join(', ')}\n`;
|
|
724
|
+
fs.writeFileSync(logFile, (fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf-8') : '') + entry, 'utf-8');
|
|
613
725
|
} catch {}
|
|
614
726
|
}
|
|
615
727
|
|
|
616
|
-
|
|
728
|
+
// Notify about modified files so frontend can reload them
|
|
729
|
+
emit({ type: 'files_changed', files: [...modifiedFiles] });
|
|
730
|
+
emit({ type: 'done', changed: hasChanges, syntaxErrors: syntaxErrors.length });
|
|
617
731
|
}
|
|
618
732
|
|
|
619
733
|
// ── Generation pipeline (SSE) ─────────────────────────────────────────────────
|
|
@@ -970,17 +1084,15 @@ Continue from here:`;
|
|
|
970
1084
|
};
|
|
971
1085
|
fs.writeFileSync(ProjectStore.metaPath(projectName), JSON.stringify(meta, null, 2), 'utf-8');
|
|
972
1086
|
|
|
973
|
-
// Initialize skill context files
|
|
974
|
-
|
|
975
|
-
const
|
|
976
|
-
if (!fs.existsSync(memFile)) {
|
|
977
|
-
fs.writeFileSync(memFile, `# ${projectName} — Project Memory\n\n_Add architectural decisions, preferences, and notes here._\n`, 'utf-8');
|
|
978
|
-
}
|
|
1087
|
+
// Initialize skill context files (memory.md, skills.md, provider.md)
|
|
1088
|
+
SkillStore.ensureDefaults(projectName, config);
|
|
1089
|
+
const ctxDir = SkillStore.dir(projectName);
|
|
979
1090
|
|
|
980
|
-
// Generate skills.md with project context knowledge structure
|
|
1091
|
+
// Generate detailed skills.md with project context knowledge structure (first time only)
|
|
981
1092
|
const skillsFile = path.join(ctxDir, 'skills.md');
|
|
982
|
-
|
|
983
|
-
|
|
1093
|
+
const skillsContent = fs.existsSync(skillsFile) ? fs.readFileSync(skillsFile, 'utf-8') : '';
|
|
1094
|
+
if (skillsContent.length < 100) {
|
|
1095
|
+
const detailedSkills = `# ${projectName} — Skills & Knowledge Structure
|
|
984
1096
|
|
|
985
1097
|
## Context Discovery Strategy
|
|
986
1098
|
|
|
@@ -1018,36 +1130,11 @@ Agents score context relevance based on:
|
|
|
1018
1130
|
|
|
1019
1131
|
This approach ensures agents have the right context without being overwhelmed by irrelevant information.
|
|
1020
1132
|
`;
|
|
1021
|
-
fs.writeFileSync(skillsFile,
|
|
1133
|
+
fs.writeFileSync(skillsFile, detailedSkills, 'utf-8');
|
|
1022
1134
|
}
|
|
1023
|
-
|
|
1135
|
+
|
|
1024
1136
|
const provider = config.llm?.provider || 'nha';
|
|
1025
1137
|
const model = config.llm?.model || '';
|
|
1026
|
-
const providerFile = path.join(ctxDir, `${provider}.md`);
|
|
1027
|
-
if (!fs.existsSync(providerFile)) {
|
|
1028
|
-
const providerContent = `# ${provider.toUpperCase()} Model Configuration
|
|
1029
|
-
|
|
1030
|
-
## Current Model: ${model || 'Default'}
|
|
1031
|
-
|
|
1032
|
-
### Model Characteristics
|
|
1033
|
-
- **Provider**: ${provider}
|
|
1034
|
-
- **Model**: ${model || 'Default model for this provider'}
|
|
1035
|
-
- **Context Window**: Varies by model
|
|
1036
|
-
- **Strengths**: Add specific strengths of this model
|
|
1037
|
-
- **Limitations**: Add specific limitations to be aware of
|
|
1038
|
-
|
|
1039
|
-
### Best Practices for This Model
|
|
1040
|
-
- Write specific coding patterns this model excels at
|
|
1041
|
-
- Note any formatting preferences
|
|
1042
|
-
- Document prompt engineering tips that work well
|
|
1043
|
-
|
|
1044
|
-
### Configuration Notes
|
|
1045
|
-
- Add any specific configuration notes for this provider
|
|
1046
|
-
- Document any rate limits or special considerations
|
|
1047
|
-
`;
|
|
1048
|
-
fs.writeFileSync(providerFile, providerContent, 'utf-8');
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
1138
|
const logFile = path.join(ctxDir, 'changes.log.md');
|
|
1052
1139
|
const logEntry = `## ${new Date().toISOString().slice(0, 10)} — Initial generation\n- Generated ${generatedFiles.length} files\n- Tokens in: ${totalTokensIn} / out: ${totalTokensOut}\n- Description: ${description}\n- Provider: ${provider} (${model || 'default'})\n`;
|
|
1053
1140
|
fs.writeFileSync(logFile, (fs.existsSync(logFile) ? fs.readFileSync(logFile, 'utf-8') : '') + logEntry, 'utf-8');
|
|
@@ -1160,6 +1247,8 @@ export function register(router) {
|
|
|
1160
1247
|
// ── Skills get ────────────────────────────────────────────────────────────
|
|
1161
1248
|
router.get(/^\/api\/studio\/webcraft\/skills\/(?<name>[^/?]+)(?:\?|$)/, (req, res) => {
|
|
1162
1249
|
const projectName = decodeURIComponent(req.params.name ?? '');
|
|
1250
|
+
// Always ensure defaults exist when loading skills
|
|
1251
|
+
SkillStore.ensureDefaults(projectName, loadConfig());
|
|
1163
1252
|
sendJSON(res, 200, { skills: SkillStore.list(projectName) });
|
|
1164
1253
|
});
|
|
1165
1254
|
|