nothumanallowed 14.1.81 → 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
|
|
|
@@ -475,168 +475,259 @@ const ChatStore = {
|
|
|
475
475
|
* Uses structured SSE events: { type: 'text', token } | { type: 'tool', ... } | { type: 'done', changed }
|
|
476
476
|
*/
|
|
477
477
|
async function runWebCraftAgent(config, projectName, message, attachments, emit) {
|
|
478
|
+
const MAX_STEPS = 8; // max agentic loop iterations
|
|
478
479
|
const dir = ProjectStore.dir(projectName);
|
|
479
480
|
if (!fs.existsSync(dir)) { emit({ type: 'error', msg: 'Project not found' }); return; }
|
|
480
481
|
|
|
481
|
-
// Ensure context files always exist
|
|
482
482
|
SkillStore.ensureDefaults(projectName, config);
|
|
483
483
|
|
|
484
|
-
const files = _listProjectFiles(dir);
|
|
485
|
-
const skillCtx = SkillStore.context(projectName);
|
|
486
|
-
|
|
487
|
-
const fileIndex = files.map((f) => `- ${f}`).join('\n');
|
|
488
484
|
const today = new Date().toISOString().slice(0, 10);
|
|
489
485
|
const LANG_MAP = { en:'English',it:'Italian',es:'Spanish',fr:'French',de:'German',pt:'Portuguese' };
|
|
490
486
|
const language = LANG_MAP[(config?.language||'it').slice(0,2)] || 'Italian';
|
|
491
487
|
|
|
492
|
-
// Build context: include files mentioned in the message + key project files
|
|
493
|
-
const mentionedFiles = files.filter((f) => message.toLowerCase().includes(f.toLowerCase().split('/').pop() ?? ''));
|
|
494
|
-
// Always include key structural files
|
|
495
|
-
const keyFiles = files.filter((f) => /^(server|app|index)\.(js|mjs|ts)$/.test(f) || f === 'package.json' || f.includes('routes/index'));
|
|
496
|
-
const contextFiles = [...new Set([...mentionedFiles, ...keyFiles, ...files.slice(0, 12)])].slice(0, 15);
|
|
497
|
-
const fileContents = contextFiles.map((rel) => {
|
|
498
|
-
try {
|
|
499
|
-
const content = fs.readFileSync(path.join(dir, rel), 'utf-8');
|
|
500
|
-
return `### FILE: ${rel}\n\`\`\`\n${content.slice(0, 8000)}\n\`\`\``;
|
|
501
|
-
} catch { return ''; }
|
|
502
|
-
}).filter(Boolean).join('\n\n');
|
|
503
|
-
|
|
504
488
|
const toolSpec = `
|
|
505
|
-
AVAILABLE TOOLS (use
|
|
489
|
+
AVAILABLE TOOLS (use exactly ONE tool per <tool> tag):
|
|
506
490
|
|
|
507
|
-
1. Read a file:
|
|
508
|
-
<tool>{"op":"read","path":"
|
|
491
|
+
1. Read a file (to see its current content before editing):
|
|
492
|
+
<tool>{"op":"read","path":"relative/path.js"}</tool>
|
|
509
493
|
|
|
510
|
-
2. Edit a file (
|
|
511
|
-
<tool>{"op":"edit","path":"
|
|
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>
|
|
512
496
|
|
|
513
|
-
3. Write a
|
|
514
|
-
<tool>{"op":"write","path":"
|
|
497
|
+
3. Write/create a file (complete content):
|
|
498
|
+
<tool>{"op":"write","path":"relative/path.js","content":"FULL_FILE_CONTENT"}</tool>
|
|
499
|
+
|
|
500
|
+
4. Check syntax of a JS file:
|
|
501
|
+
<tool>{"op":"check","path":"relative/path.js"}</tool>
|
|
502
|
+
|
|
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/>
|
|
515
509
|
|
|
516
510
|
RULES:
|
|
517
|
-
-
|
|
518
|
-
- Use
|
|
519
|
-
-
|
|
520
|
-
-
|
|
521
|
-
-
|
|
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
|
|
522
516
|
`;
|
|
523
517
|
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
|
|
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 {}
|
|
531
531
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
+
}
|
|
545
562
|
|
|
546
|
-
|
|
547
|
-
`You are WebCraft Agent — a team of 200 senior developers working as one entity. Today is ${today}. Respond in ${language}.`,
|
|
548
|
-
`\nYou have FULL control of the project IDE. You read files, edit them surgically, and write new ones.`,
|
|
549
|
-
`\nYour edits MUST be enterprise-grade: security, error handling, responsive design, accessibility.`,
|
|
550
|
-
`\nWhen the user asks for changes, you MUST use tools to implement them — never just explain. ACT, don't talk.`,
|
|
551
|
-
`\nAfter each tool use, briefly explain what changed and why.`,
|
|
552
|
-
`\n\n## PROJECT: ${projectName}`,
|
|
553
|
-
`\n## FILES:\n${fileIndex}`,
|
|
554
|
-
skillContext,
|
|
555
|
-
skillCtx ? `\n\n## ADDITIONAL CONTEXT:\n${skillCtx}` : '',
|
|
556
|
-
attachments?.length ? `\n\n## ATTACHMENTS: ${attachments.map((a) => a.name).join(', ')}` : '',
|
|
557
|
-
`\n\n## CURRENT FILE CONTENTS:\n${fileContents}`,
|
|
558
|
-
`\n\n${toolSpec}`,
|
|
559
|
-
].join('');
|
|
560
|
-
|
|
561
|
-
// Prepare user content (text + images if any)
|
|
563
|
+
// Prepare user content
|
|
562
564
|
const userContent = attachments?.length
|
|
563
565
|
? _buildMultimodalContent(message, attachments)
|
|
564
566
|
: message;
|
|
565
567
|
|
|
566
|
-
|
|
568
|
+
// ── Agentic loop ───────────────────────────────────────────────────────────
|
|
567
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
|
+
}
|
|
568
613
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// ── Execute all tool calls found in the response ───────────────────────────
|
|
577
|
-
const toolRegex = /<tool>([\s\S]*?)<\/tool>/g;
|
|
578
|
-
let match;
|
|
579
|
-
while ((match = toolRegex.exec(fullResponse)) !== null) {
|
|
580
|
-
let toolCall;
|
|
581
|
-
try { toolCall = JSON.parse(match[1].trim()); } catch {
|
|
582
|
-
emit({ type: 'tool', op: 'parse_error', path: '', result: 'json_parse_failed' });
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const { op, path: relPath, old: oldStr, new: newStr, content } = toolCall;
|
|
587
|
-
if (!relPath || !_isSafePath(relPath)) {
|
|
588
|
-
emit({ type: 'tool', op, path: relPath ?? '', result: 'unsafe_path' });
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
if (op === 'read') {
|
|
593
|
-
const src = ProjectStore.readFile(projectName, relPath);
|
|
594
|
-
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
|
+
}
|
|
595
620
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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);
|
|
605
661
|
hasChanges = true;
|
|
606
|
-
|
|
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' });
|
|
607
671
|
} else {
|
|
608
|
-
|
|
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' });
|
|
609
677
|
}
|
|
610
|
-
} else {
|
|
611
|
-
const newSrc = src.replace(oldStr, newStr ?? '');
|
|
612
|
-
ProjectStore.writeFile(projectName, relPath, newSrc);
|
|
613
|
-
hasChanges = true;
|
|
614
|
-
emit({ type: 'tool', op: 'edit', path: relPath, result: 'ok', oldSnippet: oldStr.slice(0, 300), newSnippet: newStr?.slice(0, 300) ?? '' });
|
|
615
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/>` });
|
|
616
695
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
+
}
|
|
624
711
|
}
|
|
625
712
|
}
|
|
626
713
|
}
|
|
714
|
+
if (syntaxErrors.length > 0) {
|
|
715
|
+
emit({ type: 'syntax_errors', errors: syntaxErrors });
|
|
716
|
+
}
|
|
627
717
|
|
|
628
|
-
// Log chat interaction
|
|
718
|
+
// Log chat interaction
|
|
629
719
|
if (hasChanges) {
|
|
630
720
|
try {
|
|
631
|
-
const
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
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');
|
|
636
725
|
} catch {}
|
|
637
726
|
}
|
|
638
727
|
|
|
639
|
-
|
|
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 });
|
|
640
731
|
}
|
|
641
732
|
|
|
642
733
|
// ── Generation pipeline (SSE) ─────────────────────────────────────────────────
|