nothumanallowed 14.2.5 → 14.2.6

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.2.5",
3
+ "version": "14.2.6",
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.2.5';
8
+ export const VERSION = '14.2.6';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -848,20 +848,17 @@ function isFileTruncated(content, filename) {
848
848
  const trimmed = content.trimEnd();
849
849
  const ext = filename.split('.').pop()?.toLowerCase();
850
850
 
851
- // Common truncation signs: ends mid-statement without closing bracket/brace
852
851
  if (ext === 'js' || ext === 'mjs' || ext === 'ts') {
853
- // Balanced braces check (fast approximation)
854
852
  const open = (trimmed.match(/\{/g) || []).length;
855
853
  const close = (trimmed.match(/\}/g) || []).length;
856
- if (open > close + 2) return true;
857
- // Last meaningful line should not end with an operator or comma
854
+ if (open > close) return true; // ANY unbalanced brace = truncated
858
855
  const lastLine = trimmed.split('\n').pop()?.trim() ?? '';
859
856
  if (/[,({=+\-*/<>|&]$/.test(lastLine)) return true;
860
857
  }
861
858
  if (ext === 'css') {
862
859
  const open = (trimmed.match(/\{/g) || []).length;
863
860
  const close = (trimmed.match(/\}/g) || []).length;
864
- if (open > close + 1) return true;
861
+ if (open > close) return true;
865
862
  }
866
863
  if (ext === 'html') {
867
864
  if (!trimmed.includes('</html>') && !trimmed.includes('</body>')) return true;
@@ -872,6 +869,69 @@ function isFileTruncated(content, filename) {
872
869
  return false;
873
870
  }
874
871
 
872
+ /**
873
+ * Deterministic repair of common truncation artifacts.
874
+ * Adds missing closing braces, tags, etc. WITHOUT calling the LLM.
875
+ */
876
+ function repairTruncation(content, filename) {
877
+ if (!content) return content;
878
+ const ext = filename.split('.').pop()?.toLowerCase();
879
+ let result = content.trimEnd();
880
+
881
+ if (ext === 'js' || ext === 'mjs' || ext === 'ts') {
882
+ // Balance curly braces
883
+ const open = (result.match(/\{/g) || []).length;
884
+ const close = (result.match(/\}/g) || []).length;
885
+ const missing = open - close;
886
+ if (missing > 0 && missing <= 10) {
887
+ result += '\n' + '}\n'.repeat(missing);
888
+ }
889
+ // Balance parentheses
890
+ const openP = (result.match(/\(/g) || []).length;
891
+ const closeP = (result.match(/\)/g) || []).length;
892
+ const missingP = openP - closeP;
893
+ if (missingP > 0 && missingP <= 5) {
894
+ // Find last line and append
895
+ const lines = result.split('\n');
896
+ const lastIdx = lines.length - 1;
897
+ lines[lastIdx] = lines[lastIdx] + ')'.repeat(missingP) + ';';
898
+ result = lines.join('\n');
899
+ }
900
+ }
901
+
902
+ if (ext === 'css') {
903
+ const open = (result.match(/\{/g) || []).length;
904
+ const close = (result.match(/\}/g) || []).length;
905
+ const missing = open - close;
906
+ if (missing > 0 && missing <= 10) {
907
+ result += '\n' + '}\n'.repeat(missing);
908
+ }
909
+ }
910
+
911
+ if (ext === 'html' || ext === 'htm') {
912
+ if (!result.includes('</body>')) result += '\n</body>';
913
+ if (!result.includes('</html>')) result += '\n</html>';
914
+ }
915
+
916
+ if (ext === 'json') {
917
+ // Try to fix unclosed JSON
918
+ try { JSON.parse(result); } catch {
919
+ // Count brackets
920
+ const openB = (result.match(/\[/g) || []).length;
921
+ const closeB = (result.match(/\]/g) || []).length;
922
+ const openC = (result.match(/\{/g) || []).length;
923
+ const closeC = (result.match(/\}/g) || []).length;
924
+ // Remove trailing comma if present
925
+ result = result.replace(/,\s*$/, '');
926
+ // Add missing closers
927
+ if (openB > closeB) result += '\n' + ']'.repeat(openB - closeB);
928
+ if (openC > closeC) result += '\n' + '}'.repeat(openC - closeC);
929
+ }
930
+ }
931
+
932
+ return result;
933
+ }
934
+
875
935
  async function runGenerate(config, projectName, description, blocks, authFields, emit) {
876
936
  const blocksDesc = Object.entries(blocks)
877
937
  .filter(([, enabled]) => enabled)
@@ -1058,7 +1118,8 @@ Continue from here:`;
1058
1118
  }
1059
1119
  }
1060
1120
 
1061
- fileContent = rawOutput;
1121
+ // Deterministic repair of truncation artifacts (missing braces, tags)
1122
+ fileContent = repairTruncation(rawOutput, fileSpec.name);
1062
1123
  syntaxError = null;
1063
1124
 
1064
1125
  const fileTokensOut = countTokens(fileContent);
@@ -1109,10 +1170,27 @@ Continue from here:`;
1109
1170
  return isFileTruncated(f.content, f.name);
1110
1171
  });
1111
1172
 
1112
- if (brokenFiles.length > 0 && brokenFiles.length <= 10) {
1173
+ if (brokenFiles.length > 0) {
1113
1174
  emit({ type: 'phase', phase: 'autofix', msg: `Post-generation fix: ${brokenFiles.length} file(s) need repair...` });
1114
1175
  for (const broken of brokenFiles) {
1115
1176
  try {
1177
+ // Step 1: Try deterministic repair first (fast, no LLM call)
1178
+ const deterministicFix = repairTruncation(broken.content, broken.name);
1179
+ let isFixed = false;
1180
+ if (deterministicFix !== broken.content) {
1181
+ // Verify the fix actually resolved the issue
1182
+ if (!isFileTruncated(deterministicFix, broken.name)) {
1183
+ emit({ type: 'status', msg: `Auto-fixed ${broken.name} (deterministic repair)` });
1184
+ broken.content = deterministicFix;
1185
+ const abs = path.join(projectDir, broken.name);
1186
+ fs.writeFileSync(abs, deterministicFix, 'utf-8');
1187
+ isFixed = true;
1188
+ }
1189
+ }
1190
+ if (isFixed) continue;
1191
+
1192
+ // Step 2: If deterministic repair wasn't enough, regenerate with LLM
1193
+ if (brokenFiles.length > 15) continue; // don't LLM-regenerate too many files
1116
1194
  emit({ type: 'status', msg: `Regenerating ${broken.name}...` });
1117
1195
  let fixedContent = '';
1118
1196
  const fixPrompt = `Regenerate this file COMPLETELY. It was truncated or has errors.\n\nFile: ${broken.name}\nProject: ${projectName}\nDescription: ${description}\nFull file list: ${allFileNames}\n\nOutput the COMPLETE file content only, no explanation.`;
@@ -1121,7 +1199,8 @@ Continue from here:`;
1121
1199
  }, { max_tokens: 16384 });
1122
1200
  fixedContent = fixedContent
1123
1201
  .replace(/^```[\w]*\n/, '').replace(/\n```$/, '').trim();
1124
- if (fixedContent.length > broken.content.length) {
1202
+ fixedContent = repairTruncation(fixedContent, broken.name); // repair the regenerated content too
1203
+ if (fixedContent.length > 50) {
1125
1204
  broken.content = fixedContent;
1126
1205
  const abs = path.join(projectDir, broken.name);
1127
1206
  fs.writeFileSync(abs, fixedContent, 'utf-8');