@synergenius/flow-weaver-pack-weaver 0.9.0 → 0.9.4

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 (235) hide show
  1. package/dist/bot/ai-client.d.ts +22 -2
  2. package/dist/bot/ai-client.d.ts.map +1 -1
  3. package/dist/bot/ai-client.js +168 -20
  4. package/dist/bot/ai-client.js.map +1 -1
  5. package/dist/bot/ansi.d.ts +13 -0
  6. package/dist/bot/ansi.d.ts.map +1 -0
  7. package/dist/bot/ansi.js +13 -0
  8. package/dist/bot/ansi.js.map +1 -0
  9. package/dist/bot/assistant-core.d.ts +25 -0
  10. package/dist/bot/assistant-core.d.ts.map +1 -0
  11. package/dist/bot/assistant-core.js +272 -0
  12. package/dist/bot/assistant-core.js.map +1 -0
  13. package/dist/bot/assistant-tools.d.ts +10 -0
  14. package/dist/bot/assistant-tools.d.ts.map +1 -0
  15. package/dist/bot/assistant-tools.js +324 -0
  16. package/dist/bot/assistant-tools.js.map +1 -0
  17. package/dist/bot/audit-logger.d.ts.map +1 -1
  18. package/dist/bot/audit-logger.js +9 -5
  19. package/dist/bot/audit-logger.js.map +1 -1
  20. package/dist/bot/bot-manager.d.ts +49 -0
  21. package/dist/bot/bot-manager.d.ts.map +1 -0
  22. package/dist/bot/bot-manager.js +279 -0
  23. package/dist/bot/bot-manager.js.map +1 -0
  24. package/dist/bot/child-process-tracker.d.ts +6 -0
  25. package/dist/bot/child-process-tracker.d.ts.map +1 -0
  26. package/dist/bot/child-process-tracker.js +35 -0
  27. package/dist/bot/child-process-tracker.js.map +1 -0
  28. package/dist/bot/cli-provider.d.ts.map +1 -1
  29. package/dist/bot/cli-provider.js +13 -8
  30. package/dist/bot/cli-provider.js.map +1 -1
  31. package/dist/bot/conversation-store.d.ts +40 -0
  32. package/dist/bot/conversation-store.d.ts.map +1 -0
  33. package/dist/bot/conversation-store.js +182 -0
  34. package/dist/bot/conversation-store.js.map +1 -0
  35. package/dist/bot/error-classifier.d.ts +27 -0
  36. package/dist/bot/error-classifier.d.ts.map +1 -0
  37. package/dist/bot/error-classifier.js +71 -0
  38. package/dist/bot/error-classifier.js.map +1 -0
  39. package/dist/bot/error-guide.d.ts +5 -0
  40. package/dist/bot/error-guide.d.ts.map +1 -0
  41. package/dist/bot/error-guide.js +5 -0
  42. package/dist/bot/error-guide.js.map +1 -0
  43. package/dist/bot/knowledge-store.d.ts +17 -0
  44. package/dist/bot/knowledge-store.d.ts.map +1 -0
  45. package/dist/bot/knowledge-store.js +53 -0
  46. package/dist/bot/knowledge-store.js.map +1 -0
  47. package/dist/bot/paths.d.ts +11 -0
  48. package/dist/bot/paths.d.ts.map +1 -0
  49. package/dist/bot/paths.js +26 -0
  50. package/dist/bot/paths.js.map +1 -0
  51. package/dist/bot/retry-utils.d.ts +5 -0
  52. package/dist/bot/retry-utils.d.ts.map +1 -0
  53. package/dist/bot/retry-utils.js +5 -0
  54. package/dist/bot/retry-utils.js.map +1 -0
  55. package/dist/bot/runner.d.ts.map +1 -1
  56. package/dist/bot/runner.js +12 -1
  57. package/dist/bot/runner.js.map +1 -1
  58. package/dist/bot/safety.d.ts +10 -0
  59. package/dist/bot/safety.d.ts.map +1 -0
  60. package/dist/bot/safety.js +14 -0
  61. package/dist/bot/safety.js.map +1 -0
  62. package/dist/bot/session-state.d.ts.map +1 -1
  63. package/dist/bot/session-state.js +3 -1
  64. package/dist/bot/session-state.js.map +1 -1
  65. package/dist/bot/steering.js +2 -2
  66. package/dist/bot/steering.js.map +1 -1
  67. package/dist/bot/step-executor.d.ts +10 -5
  68. package/dist/bot/step-executor.d.ts.map +1 -1
  69. package/dist/bot/step-executor.js +252 -3
  70. package/dist/bot/step-executor.js.map +1 -1
  71. package/dist/bot/system-prompt.d.ts +1 -1
  72. package/dist/bot/system-prompt.d.ts.map +1 -1
  73. package/dist/bot/system-prompt.js +69 -43
  74. package/dist/bot/system-prompt.js.map +1 -1
  75. package/dist/bot/task-decomposer.d.ts +24 -0
  76. package/dist/bot/task-decomposer.d.ts.map +1 -0
  77. package/dist/bot/task-decomposer.js +75 -0
  78. package/dist/bot/task-decomposer.js.map +1 -0
  79. package/dist/bot/task-queue.d.ts +17 -4
  80. package/dist/bot/task-queue.d.ts.map +1 -1
  81. package/dist/bot/task-queue.js +83 -5
  82. package/dist/bot/task-queue.js.map +1 -1
  83. package/dist/bot/terminal-renderer.d.ts +60 -0
  84. package/dist/bot/terminal-renderer.d.ts.map +1 -0
  85. package/dist/bot/terminal-renderer.js +204 -0
  86. package/dist/bot/terminal-renderer.js.map +1 -0
  87. package/dist/bot/tool-registry.d.ts +24 -0
  88. package/dist/bot/tool-registry.d.ts.map +1 -0
  89. package/dist/bot/tool-registry.js +458 -0
  90. package/dist/bot/tool-registry.js.map +1 -0
  91. package/dist/bot/types.d.ts +7 -0
  92. package/dist/bot/types.d.ts.map +1 -1
  93. package/dist/bot/weaver-tools.d.ts +18 -0
  94. package/dist/bot/weaver-tools.d.ts.map +1 -0
  95. package/dist/bot/weaver-tools.js +124 -0
  96. package/dist/bot/weaver-tools.js.map +1 -0
  97. package/dist/cli-bridge.d.ts.map +1 -1
  98. package/dist/cli-bridge.js +5 -1
  99. package/dist/cli-bridge.js.map +1 -1
  100. package/dist/cli-handlers.d.ts +13 -1
  101. package/dist/cli-handlers.d.ts.map +1 -1
  102. package/dist/cli-handlers.js +615 -48
  103. package/dist/cli-handlers.js.map +1 -1
  104. package/dist/mcp-tools.js +2 -2
  105. package/dist/mcp-tools.js.map +1 -1
  106. package/dist/node-types/abort-task.d.ts.map +1 -1
  107. package/dist/node-types/abort-task.js +4 -3
  108. package/dist/node-types/abort-task.js.map +1 -1
  109. package/dist/node-types/agent-execute.d.ts +38 -0
  110. package/dist/node-types/agent-execute.d.ts.map +1 -0
  111. package/dist/node-types/agent-execute.js +252 -0
  112. package/dist/node-types/agent-execute.js.map +1 -0
  113. package/dist/node-types/bot-report.d.ts +5 -3
  114. package/dist/node-types/bot-report.d.ts.map +1 -1
  115. package/dist/node-types/bot-report.js +39 -7
  116. package/dist/node-types/bot-report.js.map +1 -1
  117. package/dist/node-types/build-context.d.ts +3 -3
  118. package/dist/node-types/build-context.d.ts.map +1 -1
  119. package/dist/node-types/build-context.js +108 -24
  120. package/dist/node-types/build-context.js.map +1 -1
  121. package/dist/node-types/detect-provider.d.ts +2 -2
  122. package/dist/node-types/detect-provider.d.ts.map +1 -1
  123. package/dist/node-types/detect-provider.js +3 -1
  124. package/dist/node-types/detect-provider.js.map +1 -1
  125. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  126. package/dist/node-types/exec-validate-retry.js +43 -6
  127. package/dist/node-types/exec-validate-retry.js.map +1 -1
  128. package/dist/node-types/execute-plan.d.ts.map +1 -1
  129. package/dist/node-types/execute-plan.js +31 -8
  130. package/dist/node-types/execute-plan.js.map +1 -1
  131. package/dist/node-types/execute-target.d.ts.map +1 -1
  132. package/dist/node-types/execute-target.js +3 -1
  133. package/dist/node-types/execute-target.js.map +1 -1
  134. package/dist/node-types/fix-errors.d.ts.map +1 -1
  135. package/dist/node-types/fix-errors.js +21 -5
  136. package/dist/node-types/fix-errors.js.map +1 -1
  137. package/dist/node-types/genesis-observe.d.ts.map +1 -1
  138. package/dist/node-types/genesis-observe.js +3 -1
  139. package/dist/node-types/genesis-observe.js.map +1 -1
  140. package/dist/node-types/genesis-report.js +4 -1
  141. package/dist/node-types/genesis-report.js.map +1 -1
  142. package/dist/node-types/git-ops.d.ts.map +1 -1
  143. package/dist/node-types/git-ops.js +98 -4
  144. package/dist/node-types/git-ops.js.map +1 -1
  145. package/dist/node-types/index.d.ts +2 -0
  146. package/dist/node-types/index.d.ts.map +1 -1
  147. package/dist/node-types/index.js +2 -0
  148. package/dist/node-types/index.js.map +1 -1
  149. package/dist/node-types/load-config.d.ts +2 -2
  150. package/dist/node-types/load-config.d.ts.map +1 -1
  151. package/dist/node-types/load-config.js.map +1 -1
  152. package/dist/node-types/plan-task.d.ts.map +1 -1
  153. package/dist/node-types/plan-task.js +14 -2
  154. package/dist/node-types/plan-task.js.map +1 -1
  155. package/dist/node-types/read-workflow.js +8 -2
  156. package/dist/node-types/read-workflow.js.map +1 -1
  157. package/dist/node-types/receive-task.d.ts.map +1 -1
  158. package/dist/node-types/receive-task.js +35 -26
  159. package/dist/node-types/receive-task.js.map +1 -1
  160. package/dist/node-types/send-notify.js +2 -1
  161. package/dist/node-types/send-notify.js.map +1 -1
  162. package/dist/node-types/validate-gate.d.ts +18 -0
  163. package/dist/node-types/validate-gate.d.ts.map +1 -0
  164. package/dist/node-types/validate-gate.js +96 -0
  165. package/dist/node-types/validate-gate.js.map +1 -0
  166. package/dist/workflows/genesis-task.d.ts +20 -12
  167. package/dist/workflows/genesis-task.d.ts.map +1 -1
  168. package/dist/workflows/genesis-task.js +20 -12
  169. package/dist/workflows/genesis-task.js.map +1 -1
  170. package/dist/workflows/weaver-agent.d.ts +35 -0
  171. package/dist/workflows/weaver-agent.d.ts.map +1 -0
  172. package/dist/workflows/weaver-agent.js +777 -0
  173. package/dist/workflows/weaver-agent.js.map +1 -0
  174. package/dist/workflows/weaver-bot-batch.d.ts +19 -26
  175. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  176. package/dist/workflows/weaver-bot-batch.js +1043 -27
  177. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  178. package/dist/workflows/weaver-bot.d.ts +21 -35
  179. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  180. package/dist/workflows/weaver-bot.js +1119 -36
  181. package/dist/workflows/weaver-bot.js.map +1 -1
  182. package/flowweaver.manifest.json +21 -1
  183. package/package.json +5 -2
  184. package/src/bot/ai-client.ts +180 -19
  185. package/src/bot/ansi.ts +12 -0
  186. package/src/bot/assistant-core.ts +312 -0
  187. package/src/bot/assistant-tools.ts +318 -0
  188. package/src/bot/audit-logger.ts +6 -5
  189. package/src/bot/bot-manager.ts +293 -0
  190. package/src/bot/child-process-tracker.ts +40 -0
  191. package/src/bot/cli-provider.ts +13 -8
  192. package/src/bot/conversation-store.ts +222 -0
  193. package/src/bot/error-classifier.ts +90 -0
  194. package/src/bot/error-guide.ts +4 -0
  195. package/src/bot/knowledge-store.ts +59 -0
  196. package/src/bot/paths.ts +27 -0
  197. package/src/bot/retry-utils.ts +4 -0
  198. package/src/bot/runner.ts +12 -1
  199. package/src/bot/safety.ts +16 -0
  200. package/src/bot/session-state.ts +2 -1
  201. package/src/bot/steering.ts +2 -2
  202. package/src/bot/step-executor.ts +313 -5
  203. package/src/bot/system-prompt.ts +70 -47
  204. package/src/bot/task-decomposer.ts +100 -0
  205. package/src/bot/task-queue.ts +100 -8
  206. package/src/bot/terminal-renderer.ts +238 -0
  207. package/src/bot/tool-registry.ts +477 -0
  208. package/src/bot/types.ts +8 -0
  209. package/src/bot/weaver-tools.ts +134 -0
  210. package/src/cli-bridge.ts +7 -1
  211. package/src/cli-handlers.ts +624 -48
  212. package/src/mcp-tools.ts +2 -2
  213. package/src/node-types/abort-task.ts +5 -4
  214. package/src/node-types/agent-execute.ts +303 -0
  215. package/src/node-types/bot-report.ts +40 -9
  216. package/src/node-types/build-context.ts +112 -25
  217. package/src/node-types/detect-provider.ts +4 -3
  218. package/src/node-types/exec-validate-retry.ts +47 -8
  219. package/src/node-types/execute-plan.ts +32 -8
  220. package/src/node-types/execute-target.ts +2 -1
  221. package/src/node-types/fix-errors.ts +20 -5
  222. package/src/node-types/genesis-observe.ts +2 -1
  223. package/src/node-types/genesis-report.ts +1 -1
  224. package/src/node-types/git-ops.ts +93 -4
  225. package/src/node-types/index.ts +2 -0
  226. package/src/node-types/load-config.ts +3 -3
  227. package/src/node-types/plan-task.ts +15 -3
  228. package/src/node-types/read-workflow.ts +2 -2
  229. package/src/node-types/receive-task.ts +31 -26
  230. package/src/node-types/send-notify.ts +1 -1
  231. package/src/node-types/validate-gate.ts +112 -0
  232. package/src/workflows/genesis-task.ts +20 -12
  233. package/src/workflows/weaver-agent.ts +799 -0
  234. package/src/workflows/weaver-bot-batch.ts +1049 -27
  235. package/src/workflows/weaver-bot.ts +1123 -36
@@ -1,8 +1,8 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
- import type { WeaverContext } from '../bot/types.js';
5
- import { callAI, parseJsonResponse } from '../bot/ai-client.js';
4
+ import type { WeaverContext, StepLogEntry } from '../bot/types.js';
5
+ import { callAI, parseJsonResponse, normalizePlan } from '../bot/ai-client.js';
6
6
  import { executeStep } from '../bot/step-executor.js';
7
7
  import { validateFiles } from '../bot/file-validator.js';
8
8
  import { auditEmit } from '../bot/audit-logger.js';
@@ -38,19 +38,29 @@ export async function weaverExecValidateRetry(
38
38
 
39
39
  const { providerInfo: pInfo, projectDir } = env;
40
40
  const maxAttempts = 3;
41
+ const taskDeadline = Date.now() + 180_000; // 3 minute max per task
41
42
  let currentPlan = JSON.parse(context.planJson!);
42
43
  let allFilesModified: string[] = [];
44
+ let allStepLog: StepLogEntry[] = [];
43
45
  let lastExecResult: Record<string, unknown> = {};
44
46
  let lastValidation: Array<{ file: string; valid: boolean; errors: string[] }> = [];
45
47
  let allValid = false;
48
+ let prevErrorCount = Infinity;
46
49
 
47
50
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
51
+ // Task timeout — stop if we've exceeded the deadline
52
+ if (Date.now() > taskDeadline) {
53
+ console.error('\x1b[33m→ Task timeout (3 min), moving on\x1b[0m');
54
+ break;
55
+ }
56
+
48
57
  console.log(`\x1b[36m→ Attempt ${attempt}/${maxAttempts}\x1b[0m`);
49
58
  auditEmit('step-start', { attempt, stepCount: currentPlan.steps?.length });
50
59
 
51
60
  const execResult = await executePlanSteps(currentPlan, projectDir);
52
61
  lastExecResult = execResult;
53
62
  allFilesModified = [...new Set([...allFilesModified, ...execResult.filesModified])];
63
+ allStepLog.push(...execResult.stepLog);
54
64
  auditEmit('step-complete', { attempt, filesModified: execResult.filesModified, errors: execResult.errors });
55
65
 
56
66
  const validation = await validateFiles(execResult.filesModified, projectDir);
@@ -59,6 +69,13 @@ export async function weaverExecValidateRetry(
59
69
  const errorCount = validation.filter(v => !v.valid).length;
60
70
  auditEmit('validation-run', { attempt, allValid, errorCount });
61
71
 
72
+ // Stop retrying if errors didn't decrease — AI is stuck
73
+ if (attempt > 1 && errorCount >= prevErrorCount) {
74
+ console.error(`\x1b[33m→ Errors not decreasing (${errorCount} >= ${prevErrorCount}), stopping retry\x1b[0m`);
75
+ break;
76
+ }
77
+ prevErrorCount = errorCount;
78
+
62
79
  // Collect design warnings from valid files
63
80
  const designWarnings = validation
64
81
  .filter(v => v.valid && v.warnings.length > 0)
@@ -97,16 +114,23 @@ export async function weaverExecValidateRetry(
97
114
  try {
98
115
  const mod = await import('../bot/system-prompt.js');
99
116
  systemPrompt = await mod.buildSystemPrompt();
100
- } catch {
117
+ } catch (err) {
118
+ if (process.env.WEAVER_VERBOSE) console.error('[exec-validate-retry] system prompt build failed:', err);
101
119
  systemPrompt = 'You are Weaver. Return ONLY valid JSON.';
102
120
  }
103
121
 
104
- const fixPrompt = `The following validation errors occurred:\n${errors}\n\nProvide a fix plan as JSON with steps and summary.`;
122
+ // Include step outputs so the AI has context from discovery steps
123
+ const outputContext = Object.entries(execResult.stepOutputs)
124
+ .map(([id, out]) => `--- Output of ${id} ---\n${out}`)
125
+ .join('\n\n');
126
+
127
+ const fixPrompt = `The following validation errors occurred:\n${errors}\n\n${outputContext ? `Discovery step outputs:\n${outputContext}\n\n` : ''}Provide a CONCRETE fix plan. Every patch-file step MUST include "file" (absolute path from the outputs above) and "patches" array with exact "find"/"replace" strings. Do NOT use placeholders.`;
105
128
 
106
129
  const text = await callAI(pInfo, systemPrompt, fixPrompt, 8192);
107
130
 
108
- currentPlan = parseJsonResponse(text);
109
- console.log(`\x1b[36m→ Fix plan: ${(currentPlan as { summary?: string }).summary ?? 'generated'}\x1b[0m`);
131
+ const parsed = parseJsonResponse(text);
132
+ currentPlan = normalizePlan(parsed);
133
+ console.log(`\x1b[36m→ Fix plan: ${currentPlan.summary} (${currentPlan.steps.length} steps)\x1b[0m`);
110
134
  } catch (err: unknown) {
111
135
  const msg = err instanceof Error ? err.message : String(err);
112
136
  console.error(`\x1b[31m→ Fix planning failed: ${msg}\x1b[0m`);
@@ -118,6 +142,7 @@ export async function weaverExecValidateRetry(
118
142
  context.resultJson = JSON.stringify(lastExecResult);
119
143
  context.validationResultJson = JSON.stringify(lastValidation);
120
144
  context.filesModified = JSON.stringify(allFilesModified);
145
+ context.stepLogJson = JSON.stringify(allStepLog);
121
146
  context.allValid = allValid;
122
147
 
123
148
  return { onSuccess: allValid, onFailure: !allValid, ctx: JSON.stringify(context) };
@@ -126,9 +151,11 @@ export async function weaverExecValidateRetry(
126
151
  async function executePlanSteps(
127
152
  plan: { steps: Array<{ id: string; operation: string; description: string; args: Record<string, unknown> }> },
128
153
  projectDir: string,
129
- ): Promise<{ success: boolean; filesModified: string[]; errors: string[]; stepsCompleted: number; stepsTotal: number }> {
154
+ ): Promise<{ success: boolean; filesModified: string[]; errors: string[]; stepsCompleted: number; stepsTotal: number; stepLog: StepLogEntry[]; stepOutputs: Record<string, string> }> {
130
155
  const filesModified: string[] = [];
131
156
  const errors: string[] = [];
157
+ const stepLog: StepLogEntry[] = [];
158
+ const stepOutputs: Record<string, string> = {};
132
159
  let completed = 0;
133
160
  const steps = plan.steps ?? [];
134
161
 
@@ -136,23 +163,35 @@ async function executePlanSteps(
136
163
  const steering = checkSteeringSignal();
137
164
  if (steering === 'cancel') {
138
165
  errors.push(`Cancelled at step ${step.id}`);
166
+ stepLog.push({ step: step.id, status: 'error', detail: 'Cancelled by steering signal' });
139
167
  break;
140
168
  }
141
169
 
142
170
  try {
143
171
  const result = await executeStep(step, projectDir);
172
+ if (result.blocked) {
173
+ console.error(`\x1b[33m ⚠ ${step.id}: ${result.blockReason}\x1b[0m`);
174
+ stepLog.push({ step: step.id, status: 'blocked', detail: result.blockReason });
175
+ continue;
176
+ }
144
177
  if (result.file) filesModified.push(result.file);
145
178
  if (result.files) filesModified.push(...result.files);
179
+ // Capture step output for feeding into fix prompts
180
+ if (result.output) {
181
+ stepOutputs[step.id] = result.output.slice(0, 4000); // cap at 4k to fit in prompt
182
+ }
146
183
  completed++;
147
184
  console.log(`\x1b[32m + ${step.id}: ${step.description}\x1b[0m`);
185
+ stepLog.push({ step: step.id, status: 'ok', detail: step.description });
148
186
  } catch (err: unknown) {
149
187
  const msg = err instanceof Error ? err.message : String(err);
150
188
  errors.push(`${step.id}: ${msg}`);
151
189
  console.error(`\x1b[31m x ${step.id}: ${msg}\x1b[0m`);
190
+ stepLog.push({ step: step.id, status: 'error', detail: msg });
152
191
  }
153
192
  }
154
193
 
155
- return { success: errors.length === 0, filesModified: [...new Set(filesModified)], errors, stepsCompleted: completed, stepsTotal: steps.length };
194
+ return { success: errors.length === 0, filesModified: [...new Set(filesModified)], errors, stepsCompleted: completed, stepsTotal: steps.length, stepLog, stepOutputs };
156
195
  }
157
196
 
158
197
  function checkSteeringSignal(): 'cancel' | null {
@@ -2,7 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
4
  import type { WeaverEnv } from '../bot/types.js';
5
- import { executeStep } from '../bot/step-executor.js';
5
+ import { executeStep, resetPlanFileCounter } from '../bot/step-executor.js';
6
6
 
7
7
  /**
8
8
  * Executes plan steps via the flow-weaver CLI. Checks steering
@@ -39,6 +39,7 @@ export async function weaverExecutePlan(
39
39
  }
40
40
 
41
41
  const { projectDir } = env;
42
+ resetPlanFileCounter(); // Reset per-plan write counter for safety guards
42
43
  const plan = JSON.parse(planJson) as { steps: Array<{ id: string; operation: string; description: string; args: Record<string, unknown> }> };
43
44
  const filesModified: string[] = [];
44
45
  const filesCreated: string[] = [];
@@ -55,10 +56,27 @@ export async function weaverExecutePlan(
55
56
  };
56
57
  }
57
58
 
58
- for (const step of plan.steps) {
59
+ for (let i = 0; i < plan.steps.length; i++) {
60
+ const step = plan.steps[i];
61
+ // Defensive: ensure step has required fields
62
+ if (!step || typeof step !== 'object') {
63
+ errors.push(`step-${i + 1}: Malformed step (not an object)`);
64
+ continue;
65
+ }
66
+ const stepId = step.id ?? `step-${i + 1}`;
67
+ const stepDesc = step.description ?? step.operation ?? 'unknown';
68
+ if (!step.operation) {
69
+ errors.push(`${stepId}: Missing "operation" field`);
70
+ console.error(`\x1b[31m x ${stepId}: Missing operation\x1b[0m`);
71
+ continue;
72
+ }
73
+ if (!step.args) {
74
+ step.args = {};
75
+ }
76
+
59
77
  const steering = checkSteering();
60
78
  if (steering === 'cancel') {
61
- output.push(`Cancelled at step ${step.id}`);
79
+ output.push(`Cancelled at ${stepId}`);
62
80
  break;
63
81
  }
64
82
  if (steering === 'pause') {
@@ -68,18 +86,24 @@ export async function weaverExecutePlan(
68
86
 
69
87
  try {
70
88
  const result = await executeStep(step, projectDir);
89
+ if (result.blocked) {
90
+ errors.push(`${stepId}: BLOCKED - ${result.blockReason}`);
91
+ output.push(`${stepId}: BLOCKED - ${result.blockReason}`);
92
+ console.error(`\x1b[33m ⚠ ${stepId}: ${result.blockReason}\x1b[0m`);
93
+ continue;
94
+ }
71
95
  if (result.file) {
72
96
  if (result.created) filesCreated.push(result.file);
73
97
  else filesModified.push(result.file);
74
98
  }
75
99
  completed++;
76
- output.push(`${step.id}: ${step.description} - done`);
77
- console.log(`\x1b[32m + ${step.id}: ${step.description}\x1b[0m`);
100
+ output.push(`${stepId}: ${stepDesc} - done`);
101
+ console.log(`\x1b[32m + ${stepId}: ${stepDesc}\x1b[0m`);
78
102
  } catch (err: unknown) {
79
103
  const msg = err instanceof Error ? err.message : String(err);
80
- errors.push(`${step.id}: ${msg}`);
81
- output.push(`${step.id}: FAILED - ${msg}`);
82
- console.error(`\x1b[31m x ${step.id}: ${msg}\x1b[0m`);
104
+ errors.push(`${stepId}: ${msg}`);
105
+ output.push(`${stepId}: FAILED - ${msg}`);
106
+ console.error(`\x1b[31m x ${stepId}: ${msg}\x1b[0m`);
83
107
  }
84
108
  }
85
109
 
@@ -137,7 +137,8 @@ When stabilize mode is active, only fix-up operations are allowed: removeNode, r
137
137
  ## Response Format
138
138
 
139
139
  Return ONLY valid JSON. No markdown, no code fences, no explanation outside the JSON structure.`;
140
- } catch {
140
+ } catch (err) {
141
+ if (process.env.WEAVER_VERBOSE) console.error('[execute-target] prompt build failed:', err);
141
142
  return FALLBACK;
142
143
  }
143
144
  }
@@ -1,5 +1,5 @@
1
1
  import type { WeaverEnv } from '../bot/types.js';
2
- import { callAI, parseJsonResponse } from '../bot/ai-client.js';
2
+ import { callAI, parseJsonResponse, normalizePlan } from '../bot/ai-client.js';
3
3
 
4
4
  /**
5
5
  * When validation fails, sends errors + context to the AI and
@@ -42,18 +42,33 @@ export async function weaverFixErrors(
42
42
  try {
43
43
  const mod = await import('../bot/system-prompt.js');
44
44
  systemPrompt = await mod.buildSystemPrompt();
45
- } catch {
45
+ } catch (err) {
46
+ if (process.env.WEAVER_VERBOSE) console.error('[fix-errors] system prompt build failed:', err);
46
47
  systemPrompt = 'You are Weaver. Return ONLY valid JSON.';
47
48
  }
48
49
 
49
50
  const errorSummary = errors.map(e => `${e.file}: ${e.errors.join(', ')}`).join('\n');
50
- const userPrompt = `The following validation errors occurred:\n${errorSummary}\n\nProvide a fix plan as JSON with "steps" and "summary". Each step needs "id", "operation", "description", and "args".`;
51
+ const userPrompt = `The following validation errors occurred:
52
+ ${errorSummary}
53
+
54
+ Provide a fix plan as JSON: {"steps": [...], "summary": "..."}
55
+
56
+ Each step MUST have: "id" (string), "operation" (string), "description" (string), "args" (object).
57
+
58
+ Available operations for fixes:
59
+ - patch-file: Surgical find-and-replace. args: { "file": "path", "patches": [{ "find": "old text", "replace": "new text" }] }
60
+ PREFERRED for fixing @input annotations. Example: { "find": "@input portName", "replace": "@input [portName]" }
61
+ - run-shell: Execute a command. args: { "command": "..." }
62
+ - read-file: Read file content. args: { "file": "path" }
63
+
64
+ Return ONLY valid JSON. No explanation outside the JSON.`;
51
65
 
52
66
  try {
53
67
  const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
54
68
 
55
- const plan = parseJsonResponse(text);
56
- console.log(`\x1b[36m→ Fix plan: ${(plan as { summary?: string }).summary ?? 'generated'}\x1b[0m`);
69
+ const parsed = parseJsonResponse(text);
70
+ const plan = normalizePlan(parsed);
71
+ console.log(`\x1b[36m→ Fix plan: ${plan.summary} (${plan.steps.length} steps)\x1b[0m`);
57
72
  return { onSuccess: true, onFailure: false, env, taskJson, fixPlanJson: JSON.stringify(plan) };
58
73
  } catch (err: unknown) {
59
74
  const msg = err instanceof Error ? err.message : String(err);
@@ -77,8 +77,9 @@ export async function genesisObserve(
77
77
  gitCommit = execFileSync('git', ['rev-parse', 'HEAD'], {
78
78
  cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
79
79
  }).trim();
80
- } catch {
80
+ } catch (err) {
81
81
  // Not a git repo or git unavailable
82
+ if (process.env.WEAVER_VERBOSE) console.error('[genesis-observe] git unavailable:', err);
82
83
  }
83
84
 
84
85
  const existingWorkflows: string[] = [];
@@ -34,7 +34,7 @@ export function genesisReport(successCtx?: string, failCtx?: string, proposeFail
34
34
  try {
35
35
  const result = JSON.parse(context.applyResultJson) as { applied: number; failed: number };
36
36
  summary += ` (applied: ${result.applied}, failed: ${result.failed})`;
37
- } catch { /* ignore */ }
37
+ } catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[genesis-report] applyResultJson parse failed:', err); }
38
38
  }
39
39
  summary += elapsed ? ` [${elapsed}]` : '';
40
40
  console.log(`\n\x1b[31m${summary}\x1b[0m\n`);
@@ -1,7 +1,62 @@
1
1
  import { execFileSync } from 'node:child_process';
2
+ import * as path from 'node:path';
2
3
  import type { WeaverContext } from '../bot/types.js';
3
4
  import { auditEmit } from '../bot/audit-logger.js';
4
5
 
6
+ /**
7
+ * Review staged diff for suspicious changes before committing.
8
+ * Returns an array of issues found (empty = safe to commit).
9
+ */
10
+ function reviewStagedDiff(cwd: string): string[] {
11
+ const issues: string[] = [];
12
+
13
+ try {
14
+ const diff = execFileSync('git', ['diff', '--cached', '--stat'], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
15
+ if (!diff) return issues;
16
+
17
+ // Check for large deletions (file became empty or nearly empty)
18
+ const numstat = execFileSync('git', ['diff', '--cached', '--numstat'], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
19
+ for (const line of numstat.split('\n').filter(Boolean)) {
20
+ const [added, deleted, file] = line.split('\t');
21
+ if (!file || !added || !deleted) continue;
22
+ const addedN = parseInt(added, 10) || 0;
23
+ const deletedN = parseInt(deleted, 10) || 0;
24
+
25
+ // Flag: file lost >80% of its content with minimal additions
26
+ if (deletedN > 50 && addedN < 5 && deletedN > addedN * 10) {
27
+ issues.push(`${file}: deleted ${deletedN} lines, added only ${addedN} (possible truncation)`);
28
+ }
29
+
30
+ // Flag: file became empty (0 additions, all deletions)
31
+ if (addedN === 0 && deletedN > 10) {
32
+ issues.push(`${file}: file emptied (${deletedN} lines deleted, 0 added)`);
33
+ }
34
+ }
35
+
36
+ // Check for sensitive patterns in added lines
37
+ const patchDiff = execFileSync('git', ['diff', '--cached', '-U0'], { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
38
+ const sensitivePatterns = [
39
+ /api[_-]?key\s*[:=]\s*["'][^"']{20,}/i,
40
+ /secret\s*[:=]\s*["'][^"']{10,}/i,
41
+ /password\s*[:=]\s*["'][^"']{5,}/i,
42
+ ];
43
+ for (const line of patchDiff.split('\n')) {
44
+ if (!line.startsWith('+') || line.startsWith('+++')) continue;
45
+ for (const pattern of sensitivePatterns) {
46
+ if (pattern.test(line)) {
47
+ issues.push('Possible credential/secret in staged changes');
48
+ return issues; // One is enough to block
49
+ }
50
+ }
51
+ }
52
+ } catch (err) {
53
+ // If diff commands fail, don't block — let commit proceed
54
+ if (process.env.WEAVER_VERBOSE) console.error('[git-ops] diff review failed:', err);
55
+ }
56
+
57
+ return issues;
58
+ }
59
+
5
60
  /**
6
61
  * Git operations on created/modified files: stage, commit, branch.
7
62
  * Runs in parallel with notifications after execution.
@@ -48,25 +103,59 @@ export function weaverGitOps(ctx: string): { ctx: string } {
48
103
  }
49
104
  }
50
105
 
51
- // Stage files
106
+ // Get actually-changed files from git (avoids phantom commits)
107
+ let changedFiles: Set<string>;
108
+ try {
109
+ const diff = execFileSync('git', ['diff', '--name-only'], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
110
+ const untracked = execFileSync('git', ['ls-files', '--others', '--exclude-standard'], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
111
+ changedFiles = new Set([...diff.split('\n'), ...untracked.split('\n')].filter(Boolean));
112
+ } catch (err) {
113
+ if (process.env.WEAVER_VERBOSE) console.error('[git-ops] git diff failed, using filesModified fallback:', err);
114
+ changedFiles = new Set(files); // fallback: trust filesModified
115
+ }
116
+
117
+ // Stage only files that are both in filesModified AND actually changed
118
+ let staged = 0;
52
119
  for (const file of files) {
120
+ // Resolve to relative path for comparison
121
+ const relative = path.relative(projectDir, path.resolve(projectDir, file));
122
+ if (!changedFiles.has(relative) && !changedFiles.has(file)) continue;
53
123
  try {
54
124
  execFileSync('git', ['add', file], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
125
+ staged++;
55
126
  } catch { /* ignore unstaged files */ }
56
127
  }
57
128
 
129
+ if (staged === 0) {
130
+ results.push('No actual changes to commit');
131
+ auditEmit('git-operation', { branch: gitConfig.branch, filesCount: 0, results });
132
+ context.gitResultJson = JSON.stringify({ skipped: true, reason: 'no actual changes', results });
133
+ return { ctx: JSON.stringify(context) };
134
+ }
135
+
136
+ // Diff review: check for suspicious changes before committing
137
+ const diffIssues = reviewStagedDiff(projectDir);
138
+ if (diffIssues.length > 0) {
139
+ // Unstage and skip commit — something looks wrong
140
+ try { execFileSync('git', ['reset', 'HEAD'], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); } catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[git-ops] reset failed:', err); }
141
+ results.push(`Commit blocked: ${diffIssues.join('; ')}`);
142
+ auditEmit('git-operation', { branch: gitConfig.branch, filesCount: 0, results, blocked: true });
143
+ context.gitResultJson = JSON.stringify({ skipped: true, reason: 'diff review failed', issues: diffIssues, results });
144
+ return { ctx: JSON.stringify(context) };
145
+ }
146
+
58
147
  // Commit
59
148
  const prefix = gitConfig.commitPrefix ?? 'weaver:';
60
- const commitMsg = `${prefix} bot task (${files.length} file${files.length === 1 ? '' : 's'})`;
149
+ const commitMsg = `${prefix} bot task (${staged} file${staged === 1 ? '' : 's'})`;
61
150
  try {
62
151
  execFileSync('git', ['commit', '-m', commitMsg], { cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
63
152
  results.push(`Committed: ${commitMsg}`);
64
- console.log(`\x1b[36m→ Git: ${commitMsg}\x1b[0m`);
153
+ if (process.env.WEAVER_VERBOSE) process.stderr.write(`\x1b[2m Git: ${commitMsg}\x1b[0m\n`);
65
154
  } catch {
66
155
  results.push('Nothing to commit');
67
156
  }
68
157
 
69
- auditEmit('git-operation', { branch: gitConfig.branch, filesCount: files.length, results });
158
+ auditEmit('git-operation', { branch: gitConfig.branch, filesCount: staged, results });
70
159
  context.gitResultJson = JSON.stringify({ skipped: false, results });
71
160
  return { ctx: JSON.stringify(context) };
72
161
  }
@@ -17,6 +17,8 @@ export { weaverValidateResult } from './validate-result.js';
17
17
  export { weaverFixErrors } from './fix-errors.js';
18
18
  export { weaverGitOps } from './git-ops.js';
19
19
  export { weaverBotReport } from './bot-report.js';
20
+ export { weaverAgentExecute } from './agent-execute.js';
21
+ export { weaverValidateGate } from './validate-gate.js';
20
22
  export { genesisLoadConfig } from './genesis-load-config.js';
21
23
  export { genesisObserve } from './genesis-observe.js';
22
24
  export { genesisDiffFingerprint } from './genesis-diff-fingerprint.js';
@@ -1,6 +1,6 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import type { WeaverConfig } from '../bot/types.js';
3
+ import type { BotConfig } from '../bot/types.js';
4
4
 
5
5
  /**
6
6
  * Read .weaver.json, merge with defaults, and output the config object.
@@ -13,10 +13,10 @@ import type { WeaverConfig } from '../bot/types.js';
13
13
  * @output config [order:1] - Weaver configuration
14
14
  * @output onFailure [hidden]
15
15
  */
16
- export function weaverLoadConfig(projectDir?: string): { projectDir: string; config: WeaverConfig } {
16
+ export function weaverLoadConfig(projectDir?: string): { projectDir: string; config: BotConfig } {
17
17
  const dir = projectDir || process.cwd();
18
18
  const configPath = path.join(dir, '.weaver.json');
19
- let config: WeaverConfig = { provider: 'auto' };
19
+ let config: BotConfig = { provider: 'auto' };
20
20
  if (fs.existsSync(configPath)) {
21
21
  config = { ...config, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) };
22
22
  console.log(`\x1b[36m→ Loaded config from ${configPath}\x1b[0m`);
@@ -29,7 +29,12 @@ export async function weaverPlanTask(
29
29
  }
30
30
 
31
31
  const { providerInfo: pInfo } = env;
32
- const task = JSON.parse(context.taskJson!);
32
+
33
+ if (!context.taskJson) {
34
+ context.planJson = JSON.stringify({ steps: [], summary: 'Planning failed: missing taskJson' });
35
+ return { onSuccess: false, onFailure: true, ctx: JSON.stringify(context) };
36
+ }
37
+ const task = JSON.parse(context.taskJson);
33
38
 
34
39
  let systemPrompt: string;
35
40
  try {
@@ -42,11 +47,18 @@ export async function weaverPlanTask(
42
47
  } catch { /* older flow-weaver version */ }
43
48
  const botPrompt = mod.buildBotSystemPrompt(context.contextBundle!, cliCommands);
44
49
  systemPrompt = basePrompt + '\n\n' + botPrompt;
45
- } catch {
50
+ } catch (err) {
51
+ if (process.env.WEAVER_VERBOSE) console.error('[plan-task] system prompt build failed:', err);
46
52
  systemPrompt = 'You are Weaver, an AI workflow bot. Return ONLY valid JSON with a plan.';
47
53
  }
48
54
 
49
- const userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}\n\nPlan this task. Return a JSON plan with steps and summary.`;
55
+ const userPrompt = `Task: ${task.instruction}\nMode: ${task.mode ?? 'create'}\n${task.targets ? 'Targets: ' + task.targets.join(', ') : ''}
56
+
57
+ Plan this task. IMPORTANT rules:
58
+ 1. Every step MUST have complete, concrete args — no empty patches, no placeholders.
59
+ 2. If you need to discover file contents or errors before fixing, plan ONLY the discovery steps (run-shell, read-file, list-files) in this first plan. The system will automatically provide a second planning round with actual errors if validation fails.
60
+ 3. Do NOT plan patch-file steps unless you already know the exact find/replace strings.
61
+ 4. Prefer run-shell with "npx flow-weaver validate <file> --json" to discover errors, then read-file to see the file, then patch-file with exact strings.`;
50
62
 
51
63
  try {
52
64
  const text = await callAI(pInfo, systemPrompt, userPrompt, 8192);
@@ -47,7 +47,7 @@ export function weaverReadWorkflow(ctx: string): { ctx: string } {
47
47
  timeout: 30_000,
48
48
  cwd: projectDir,
49
49
  }).trim();
50
- } catch { /* diagram generation failed, continue without it */ }
50
+ } catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[read-workflow] diagram failed:', err); }
51
51
 
52
52
  try {
53
53
  description = execFileSync('flow-weaver', ['describe', filePath], {
@@ -56,7 +56,7 @@ export function weaverReadWorkflow(ctx: string): { ctx: string } {
56
56
  timeout: 30_000,
57
57
  cwd: projectDir,
58
58
  }).trim();
59
- } catch { /* description failed, continue without it */ }
59
+ } catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[read-workflow] describe failed:', err); }
60
60
 
61
61
  results.push({ file: target, source, diagram, description });
62
62
  console.log(`\x1b[36m→ Read: ${target}\x1b[0m`);
@@ -2,6 +2,7 @@ import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
4
  import type { WeaverEnv, WeaverContext } from '../bot/types.js';
5
+ import { withFileLock } from '../bot/file-lock.js';
5
6
 
6
7
  interface QueuedTask {
7
8
  id: string;
@@ -54,38 +55,42 @@ export async function weaverReceiveTask(
54
55
  } catch { /* fall through to queue check */ }
55
56
  }
56
57
 
57
- // Check task queue
58
+ // Check task queue (with file locking to prevent race conditions)
58
59
  const queuePath = path.join(os.homedir(), '.weaver', 'task-queue.ndjson');
59
60
  try {
60
- if (fs.existsSync(queuePath)) {
61
+ const claimed = await withFileLock(queuePath, () => {
62
+ if (!fs.existsSync(queuePath)) return null;
61
63
  const content = fs.readFileSync(queuePath, 'utf-8').trim();
62
- if (content) {
63
- const tasks: QueuedTask[] = content.split('\n').map(l => JSON.parse(l));
64
- const pending = tasks
65
- .filter(t => t.status === 'pending')
66
- .sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);
64
+ if (!content) return null;
67
65
 
68
- if (pending.length > 0) {
69
- const task = pending[0]!;
70
- // Mark as running
71
- const updated = tasks.map(t => t.id === task.id ? { ...t, status: 'running' } : t);
72
- fs.writeFileSync(queuePath, updated.map(t => JSON.stringify(t)).join('\n') + '\n', 'utf-8');
66
+ const tasks: QueuedTask[] = content.split('\n').filter(Boolean).map(l => JSON.parse(l));
67
+ const pending = tasks
68
+ .filter(t => t.status === 'pending')
69
+ .sort((a, b) => b.priority - a.priority || a.addedAt - b.addedAt);
73
70
 
74
- const botTask = {
75
- instruction: task.instruction,
76
- mode: task.mode ?? 'create',
77
- targets: task.targets,
78
- options: task.options,
79
- queueId: task.id,
80
- };
81
- console.log(`\x1b[36m→ Task from queue [${task.id}]: ${task.instruction.slice(0, 80)}\x1b[0m`);
82
- context.taskJson = JSON.stringify(botTask);
83
- context.hasTask = true;
84
- return { onSuccess: true, onFailure: false, ctx: JSON.stringify(context) };
85
- }
86
- }
71
+ if (pending.length === 0) return null;
72
+
73
+ const task = pending[0]!;
74
+ // Atomically mark as running inside the lock
75
+ const updated = tasks.map(t => t.id === task.id ? { ...t, status: 'running' } : t);
76
+ fs.writeFileSync(queuePath, updated.map(t => JSON.stringify(t)).join('\n') + '\n', 'utf-8');
77
+ return task;
78
+ });
79
+
80
+ if (claimed) {
81
+ const botTask = {
82
+ instruction: claimed.instruction,
83
+ mode: claimed.mode ?? 'create',
84
+ targets: claimed.targets,
85
+ options: claimed.options,
86
+ queueId: claimed.id,
87
+ };
88
+ console.log(`\x1b[36m→ Task from queue [${claimed.id}]: ${claimed.instruction.slice(0, 80)}\x1b[0m`);
89
+ context.taskJson = JSON.stringify(botTask);
90
+ context.hasTask = true;
91
+ return { onSuccess: true, onFailure: false, ctx: JSON.stringify(context) };
87
92
  }
88
- } catch { /* ignore queue errors */ }
93
+ } catch (err) { if (process.env.WEAVER_VERBOSE) console.error('[receive-task] queue error:', err); }
89
94
 
90
95
  console.log('\x1b[33m→ No task found\x1b[0m');
91
96
  return { onSuccess: false, onFailure: true, ctx: JSON.stringify(context) };
@@ -40,7 +40,7 @@ function sendWebhook(
40
40
  headers,
41
41
  body,
42
42
  signal: AbortSignal.timeout(10_000),
43
- }).catch(() => {});
43
+ }).catch((err) => { if (process.env.WEAVER_VERBOSE) console.error('[send-notify] webhook failed:', err); });
44
44
  }
45
45
 
46
46
  /**