@synergenius/flow-weaver-pack-weaver 0.8.3 → 0.9.3

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 (265) 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/assistant-core.d.ts +25 -0
  6. package/dist/bot/assistant-core.d.ts.map +1 -0
  7. package/dist/bot/assistant-core.js +265 -0
  8. package/dist/bot/assistant-core.js.map +1 -0
  9. package/dist/bot/assistant-tools.d.ts +9 -0
  10. package/dist/bot/assistant-tools.d.ts.map +1 -0
  11. package/dist/bot/assistant-tools.js +602 -0
  12. package/dist/bot/assistant-tools.js.map +1 -0
  13. package/dist/bot/audit-logger.d.ts.map +1 -1
  14. package/dist/bot/audit-logger.js +9 -5
  15. package/dist/bot/audit-logger.js.map +1 -1
  16. package/dist/bot/audit-store.d.ts.map +1 -1
  17. package/dist/bot/audit-store.js +3 -11
  18. package/dist/bot/audit-store.js.map +1 -1
  19. package/dist/bot/bot-manager.d.ts +49 -0
  20. package/dist/bot/bot-manager.d.ts.map +1 -0
  21. package/dist/bot/bot-manager.js +279 -0
  22. package/dist/bot/bot-manager.js.map +1 -0
  23. package/dist/bot/child-process-tracker.d.ts +6 -0
  24. package/dist/bot/child-process-tracker.d.ts.map +1 -0
  25. package/dist/bot/child-process-tracker.js +35 -0
  26. package/dist/bot/child-process-tracker.js.map +1 -0
  27. package/dist/bot/cli-provider.d.ts.map +1 -1
  28. package/dist/bot/cli-provider.js +13 -8
  29. package/dist/bot/cli-provider.js.map +1 -1
  30. package/dist/bot/conversation-store.d.ts +40 -0
  31. package/dist/bot/conversation-store.d.ts.map +1 -0
  32. package/dist/bot/conversation-store.js +182 -0
  33. package/dist/bot/conversation-store.js.map +1 -0
  34. package/dist/bot/cost-store.d.ts.map +1 -1
  35. package/dist/bot/cost-store.js +10 -14
  36. package/dist/bot/cost-store.js.map +1 -1
  37. package/dist/bot/error-guide.d.ts +10 -0
  38. package/dist/bot/error-guide.d.ts.map +1 -0
  39. package/dist/bot/error-guide.js +34 -0
  40. package/dist/bot/error-guide.js.map +1 -0
  41. package/dist/bot/genesis-store.d.ts.map +1 -1
  42. package/dist/bot/genesis-store.js +11 -20
  43. package/dist/bot/genesis-store.js.map +1 -1
  44. package/dist/bot/index.d.ts +3 -0
  45. package/dist/bot/index.d.ts.map +1 -1
  46. package/dist/bot/index.js +3 -0
  47. package/dist/bot/index.js.map +1 -1
  48. package/dist/bot/knowledge-store.d.ts +17 -0
  49. package/dist/bot/knowledge-store.d.ts.map +1 -0
  50. package/dist/bot/knowledge-store.js +53 -0
  51. package/dist/bot/knowledge-store.js.map +1 -0
  52. package/dist/bot/pipeline-runner.d.ts.map +1 -1
  53. package/dist/bot/pipeline-runner.js +8 -1
  54. package/dist/bot/pipeline-runner.js.map +1 -1
  55. package/dist/bot/retry-utils.d.ts +19 -0
  56. package/dist/bot/retry-utils.d.ts.map +1 -0
  57. package/dist/bot/retry-utils.js +64 -0
  58. package/dist/bot/retry-utils.js.map +1 -0
  59. package/dist/bot/run-store.d.ts.map +1 -1
  60. package/dist/bot/run-store.js +2 -10
  61. package/dist/bot/run-store.js.map +1 -1
  62. package/dist/bot/runner.d.ts.map +1 -1
  63. package/dist/bot/runner.js +24 -3
  64. package/dist/bot/runner.js.map +1 -1
  65. package/dist/bot/safe-json.d.ts +32 -0
  66. package/dist/bot/safe-json.d.ts.map +1 -0
  67. package/dist/bot/safe-json.js +56 -0
  68. package/dist/bot/safe-json.js.map +1 -0
  69. package/dist/bot/safe-path.d.ts +18 -0
  70. package/dist/bot/safe-path.d.ts.map +1 -0
  71. package/dist/bot/safe-path.js +40 -0
  72. package/dist/bot/safe-path.js.map +1 -0
  73. package/dist/bot/session-state.d.ts.map +1 -1
  74. package/dist/bot/session-state.js +3 -1
  75. package/dist/bot/session-state.js.map +1 -1
  76. package/dist/bot/steering.js +1 -1
  77. package/dist/bot/steering.js.map +1 -1
  78. package/dist/bot/step-executor.d.ts +10 -5
  79. package/dist/bot/step-executor.d.ts.map +1 -1
  80. package/dist/bot/step-executor.js +252 -3
  81. package/dist/bot/step-executor.js.map +1 -1
  82. package/dist/bot/system-prompt.d.ts +1 -1
  83. package/dist/bot/system-prompt.d.ts.map +1 -1
  84. package/dist/bot/system-prompt.js +69 -43
  85. package/dist/bot/system-prompt.js.map +1 -1
  86. package/dist/bot/task-decomposer.d.ts +24 -0
  87. package/dist/bot/task-decomposer.d.ts.map +1 -0
  88. package/dist/bot/task-decomposer.js +75 -0
  89. package/dist/bot/task-decomposer.js.map +1 -0
  90. package/dist/bot/task-queue.d.ts +17 -4
  91. package/dist/bot/task-queue.d.ts.map +1 -1
  92. package/dist/bot/task-queue.js +102 -14
  93. package/dist/bot/task-queue.js.map +1 -1
  94. package/dist/bot/terminal-renderer.d.ts +60 -0
  95. package/dist/bot/terminal-renderer.d.ts.map +1 -0
  96. package/dist/bot/terminal-renderer.js +205 -0
  97. package/dist/bot/terminal-renderer.js.map +1 -0
  98. package/dist/bot/types.d.ts +7 -0
  99. package/dist/bot/types.d.ts.map +1 -1
  100. package/dist/bot/weaver-tools.d.ts +18 -0
  101. package/dist/bot/weaver-tools.d.ts.map +1 -0
  102. package/dist/bot/weaver-tools.js +215 -0
  103. package/dist/bot/weaver-tools.js.map +1 -0
  104. package/dist/cli-bridge.d.ts.map +1 -1
  105. package/dist/cli-bridge.js +10 -3
  106. package/dist/cli-bridge.js.map +1 -1
  107. package/dist/cli-handlers.d.ts +15 -1
  108. package/dist/cli-handlers.d.ts.map +1 -1
  109. package/dist/cli-handlers.js +742 -28
  110. package/dist/cli-handlers.js.map +1 -1
  111. package/dist/handlers/on-bot-completed.d.ts +21 -0
  112. package/dist/handlers/on-bot-completed.d.ts.map +1 -0
  113. package/dist/handlers/on-bot-completed.js +28 -0
  114. package/dist/handlers/on-bot-completed.js.map +1 -0
  115. package/dist/handlers/on-execution-failure.d.ts +23 -0
  116. package/dist/handlers/on-execution-failure.d.ts.map +1 -0
  117. package/dist/handlers/on-execution-failure.js +28 -0
  118. package/dist/handlers/on-execution-failure.js.map +1 -0
  119. package/dist/handlers/scheduled-run.d.ts +24 -0
  120. package/dist/handlers/scheduled-run.d.ts.map +1 -0
  121. package/dist/handlers/scheduled-run.js +25 -0
  122. package/dist/handlers/scheduled-run.js.map +1 -0
  123. package/dist/index.d.ts +3 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +4 -0
  126. package/dist/index.js.map +1 -1
  127. package/dist/mcp-tools.js +2 -2
  128. package/dist/mcp-tools.js.map +1 -1
  129. package/dist/node-types/abort-task.d.ts.map +1 -1
  130. package/dist/node-types/abort-task.js +4 -3
  131. package/dist/node-types/abort-task.js.map +1 -1
  132. package/dist/node-types/agent-execute.d.ts +38 -0
  133. package/dist/node-types/agent-execute.d.ts.map +1 -0
  134. package/dist/node-types/agent-execute.js +256 -0
  135. package/dist/node-types/agent-execute.js.map +1 -0
  136. package/dist/node-types/bot-report.d.ts +5 -3
  137. package/dist/node-types/bot-report.d.ts.map +1 -1
  138. package/dist/node-types/bot-report.js +39 -7
  139. package/dist/node-types/bot-report.js.map +1 -1
  140. package/dist/node-types/build-context.d.ts +3 -3
  141. package/dist/node-types/build-context.d.ts.map +1 -1
  142. package/dist/node-types/build-context.js +108 -24
  143. package/dist/node-types/build-context.js.map +1 -1
  144. package/dist/node-types/detect-provider.d.ts +2 -2
  145. package/dist/node-types/detect-provider.d.ts.map +1 -1
  146. package/dist/node-types/detect-provider.js +3 -1
  147. package/dist/node-types/detect-provider.js.map +1 -1
  148. package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
  149. package/dist/node-types/exec-validate-retry.js +43 -6
  150. package/dist/node-types/exec-validate-retry.js.map +1 -1
  151. package/dist/node-types/execute-plan.d.ts.map +1 -1
  152. package/dist/node-types/execute-plan.js +31 -8
  153. package/dist/node-types/execute-plan.js.map +1 -1
  154. package/dist/node-types/execute-target.d.ts.map +1 -1
  155. package/dist/node-types/execute-target.js +3 -1
  156. package/dist/node-types/execute-target.js.map +1 -1
  157. package/dist/node-types/fix-errors.d.ts.map +1 -1
  158. package/dist/node-types/fix-errors.js +21 -5
  159. package/dist/node-types/fix-errors.js.map +1 -1
  160. package/dist/node-types/genesis-observe.d.ts.map +1 -1
  161. package/dist/node-types/genesis-observe.js +3 -1
  162. package/dist/node-types/genesis-observe.js.map +1 -1
  163. package/dist/node-types/genesis-report.js +4 -1
  164. package/dist/node-types/genesis-report.js.map +1 -1
  165. package/dist/node-types/git-ops.d.ts.map +1 -1
  166. package/dist/node-types/git-ops.js +98 -4
  167. package/dist/node-types/git-ops.js.map +1 -1
  168. package/dist/node-types/index.d.ts +2 -0
  169. package/dist/node-types/index.d.ts.map +1 -1
  170. package/dist/node-types/index.js +2 -0
  171. package/dist/node-types/index.js.map +1 -1
  172. package/dist/node-types/load-config.d.ts +2 -2
  173. package/dist/node-types/load-config.d.ts.map +1 -1
  174. package/dist/node-types/load-config.js.map +1 -1
  175. package/dist/node-types/plan-task.d.ts.map +1 -1
  176. package/dist/node-types/plan-task.js +14 -2
  177. package/dist/node-types/plan-task.js.map +1 -1
  178. package/dist/node-types/read-workflow.js +8 -2
  179. package/dist/node-types/read-workflow.js.map +1 -1
  180. package/dist/node-types/receive-task.d.ts.map +1 -1
  181. package/dist/node-types/receive-task.js +35 -26
  182. package/dist/node-types/receive-task.js.map +1 -1
  183. package/dist/node-types/send-notify.js +2 -1
  184. package/dist/node-types/send-notify.js.map +1 -1
  185. package/dist/node-types/validate-gate.d.ts +18 -0
  186. package/dist/node-types/validate-gate.d.ts.map +1 -0
  187. package/dist/node-types/validate-gate.js +96 -0
  188. package/dist/node-types/validate-gate.js.map +1 -0
  189. package/dist/workflows/genesis-task.d.ts +20 -12
  190. package/dist/workflows/genesis-task.d.ts.map +1 -1
  191. package/dist/workflows/genesis-task.js +20 -12
  192. package/dist/workflows/genesis-task.js.map +1 -1
  193. package/dist/workflows/weaver-agent.d.ts +35 -0
  194. package/dist/workflows/weaver-agent.d.ts.map +1 -0
  195. package/dist/workflows/weaver-agent.js +777 -0
  196. package/dist/workflows/weaver-agent.js.map +1 -0
  197. package/dist/workflows/weaver-bot-batch.d.ts +19 -26
  198. package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
  199. package/dist/workflows/weaver-bot-batch.js +1043 -27
  200. package/dist/workflows/weaver-bot-batch.js.map +1 -1
  201. package/dist/workflows/weaver-bot.d.ts +21 -35
  202. package/dist/workflows/weaver-bot.d.ts.map +1 -1
  203. package/dist/workflows/weaver-bot.js +1119 -36
  204. package/dist/workflows/weaver-bot.js.map +1 -1
  205. package/flowweaver.manifest.json +113 -2
  206. package/package.json +5 -2
  207. package/src/bot/ai-client.ts +180 -19
  208. package/src/bot/assistant-core.ts +306 -0
  209. package/src/bot/assistant-tools.ts +605 -0
  210. package/src/bot/audit-logger.ts +6 -5
  211. package/src/bot/audit-store.ts +3 -12
  212. package/src/bot/bot-manager.ts +293 -0
  213. package/src/bot/child-process-tracker.ts +40 -0
  214. package/src/bot/cli-provider.ts +13 -8
  215. package/src/bot/conversation-store.ts +222 -0
  216. package/src/bot/cost-store.ts +11 -12
  217. package/src/bot/error-guide.ts +34 -0
  218. package/src/bot/genesis-store.ts +11 -17
  219. package/src/bot/index.ts +5 -0
  220. package/src/bot/knowledge-store.ts +59 -0
  221. package/src/bot/pipeline-runner.ts +7 -1
  222. package/src/bot/retry-utils.ts +76 -0
  223. package/src/bot/run-store.ts +2 -11
  224. package/src/bot/runner.ts +26 -3
  225. package/src/bot/safe-json.ts +76 -0
  226. package/src/bot/safe-path.ts +44 -0
  227. package/src/bot/session-state.ts +2 -1
  228. package/src/bot/steering.ts +1 -1
  229. package/src/bot/step-executor.ts +313 -5
  230. package/src/bot/system-prompt.ts +70 -47
  231. package/src/bot/task-decomposer.ts +100 -0
  232. package/src/bot/task-queue.ts +119 -15
  233. package/src/bot/terminal-renderer.ts +241 -0
  234. package/src/bot/types.ts +8 -0
  235. package/src/bot/weaver-tools.ts +225 -0
  236. package/src/cli-bridge.ts +14 -3
  237. package/src/cli-handlers.ts +760 -29
  238. package/src/handlers/on-bot-completed.ts +48 -0
  239. package/src/handlers/on-execution-failure.ts +42 -0
  240. package/src/handlers/scheduled-run.ts +42 -0
  241. package/src/index.ts +5 -0
  242. package/src/mcp-tools.ts +2 -2
  243. package/src/node-types/abort-task.ts +5 -4
  244. package/src/node-types/agent-execute.ts +306 -0
  245. package/src/node-types/bot-report.ts +40 -9
  246. package/src/node-types/build-context.ts +112 -25
  247. package/src/node-types/detect-provider.ts +4 -3
  248. package/src/node-types/exec-validate-retry.ts +47 -8
  249. package/src/node-types/execute-plan.ts +32 -8
  250. package/src/node-types/execute-target.ts +2 -1
  251. package/src/node-types/fix-errors.ts +20 -5
  252. package/src/node-types/genesis-observe.ts +2 -1
  253. package/src/node-types/genesis-report.ts +1 -1
  254. package/src/node-types/git-ops.ts +93 -4
  255. package/src/node-types/index.ts +2 -0
  256. package/src/node-types/load-config.ts +3 -3
  257. package/src/node-types/plan-task.ts +15 -3
  258. package/src/node-types/read-workflow.ts +2 -2
  259. package/src/node-types/receive-task.ts +31 -26
  260. package/src/node-types/send-notify.ts +1 -1
  261. package/src/node-types/validate-gate.ts +112 -0
  262. package/src/workflows/genesis-task.ts +20 -12
  263. package/src/workflows/weaver-agent.ts +799 -0
  264. package/src/workflows/weaver-bot-batch.ts +1049 -27
  265. package/src/workflows/weaver-bot.ts +1123 -36
@@ -1,27 +1,335 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
+ import { execSync } from 'node:child_process';
3
4
  import { runCommand } from '@synergenius/flow-weaver';
4
5
 
6
+ // ---------------------------------------------------------------------------
7
+ // Safety thresholds
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /** Refuse writes that shrink an existing file by more than this ratio (0-1). */
11
+ const MAX_SHRINK_RATIO = 0.5;
12
+
13
+ /** Minimum file size (bytes) before shrink guard kicks in. */
14
+ const SHRINK_GUARD_MIN_SIZE = 100;
15
+
16
+ /** Maximum number of files that can be written in a single plan. */
17
+ const MAX_FILES_PER_PLAN = 50;
18
+
19
+ /** Maximum shell command execution time (ms). */
20
+ const SHELL_TIMEOUT = 60_000;
21
+
22
+ /** Maximum file size for read/patch operations (1MB). */
23
+ const MAX_READ_SIZE = 1_048_576;
24
+
25
+ /** Maximum files returned by list-files. */
26
+ const MAX_LIST_FILES = 1000;
27
+
28
+ /** Shell commands that are NEVER allowed (destructive operations). */
29
+ const BLOCKED_SHELL_PATTERNS = [
30
+ /\brm\s+(-[a-z]*r|-[a-z]*f)[a-z]*\s/i, // rm with -r or -f flags
31
+ /\bgit\s+push\b/i, // git push (no remote ops)
32
+ /\bgit\s+reset\s+--hard\b/i, // git reset --hard
33
+ /\bnpm\s+publish\b/i, // npm publish
34
+ /\bcurl\b.*\|\s*(sh|bash)\b/i, // curl | sh/bash
35
+ /\bwget\b.*\|\s*(sh|bash)\b/i, // wget | sh/bash
36
+ /\bsudo\b/i, // sudo
37
+ /\bchmod\s+777\b/i, // chmod 777
38
+ /\bkill\s+-9\b/i, // kill -9
39
+ /\bmkfs\b/i, // format disk
40
+ /\bdd\s+if=/i, // dd (disk destroyer)
41
+ />\s*\/dev\/sd/i, // write to raw disk
42
+ ];
43
+
44
+ /** Track files written in this process to enforce the per-plan cap. */
45
+ let filesWrittenThisPlan = 0;
46
+
47
+ /** Reset the per-plan counter between plans. */
48
+ export function resetPlanFileCounter(): void {
49
+ filesWrittenThisPlan = 0;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Path safety
54
+ // ---------------------------------------------------------------------------
55
+
56
+ function assertSafePath(filePath: string, projectDir: string): void {
57
+ const resolved = path.resolve(projectDir, filePath);
58
+ if (!resolved.startsWith(path.resolve(projectDir))) {
59
+ throw new Error(
60
+ `Path traversal blocked: "${filePath}" resolves outside project directory.`,
61
+ );
62
+ }
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Write safety
67
+ // ---------------------------------------------------------------------------
68
+
69
+ interface WriteGuardResult {
70
+ allowed: boolean;
71
+ reason?: string;
72
+ }
73
+
74
+ function checkWriteSafety(filePath: string, content: string): WriteGuardResult {
75
+ // Guard 1: Empty content
76
+ if (!content || content.trim().length === 0) {
77
+ return {
78
+ allowed: false,
79
+ reason:
80
+ `Refusing to write empty content to ${path.basename(filePath)}. ` +
81
+ `Use read-file first, then write the complete modified file back.`,
82
+ };
83
+ }
84
+
85
+ // Guard 2: Per-plan file cap
86
+ if (filesWrittenThisPlan >= MAX_FILES_PER_PLAN) {
87
+ return {
88
+ allowed: false,
89
+ reason: `File write limit reached (${MAX_FILES_PER_PLAN} files per plan).`,
90
+ };
91
+ }
92
+
93
+ // Guard 3: Shrink detection
94
+ if (fs.existsSync(filePath)) {
95
+ const existingSize = fs.statSync(filePath).size;
96
+ const newSize = Buffer.byteLength(content, 'utf-8');
97
+ if (existingSize > SHRINK_GUARD_MIN_SIZE && newSize < existingSize * MAX_SHRINK_RATIO) {
98
+ const shrinkPct = Math.round((1 - newSize / existingSize) * 100);
99
+ return {
100
+ allowed: false,
101
+ reason:
102
+ `Refusing to write ${path.basename(filePath)}: new content (${newSize}B) ` +
103
+ `is ${shrinkPct}% smaller than existing (${existingSize}B). ` +
104
+ `Use read-file first, make targeted changes, write complete file back.`,
105
+ };
106
+ }
107
+ }
108
+
109
+ return { allowed: true };
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // Shell safety
114
+ // ---------------------------------------------------------------------------
115
+
116
+ function checkShellSafety(command: string): WriteGuardResult {
117
+ for (const pattern of BLOCKED_SHELL_PATTERNS) {
118
+ if (pattern.test(command)) {
119
+ return {
120
+ allowed: false,
121
+ reason: `Shell command blocked by safety policy: matches "${pattern.source}"`,
122
+ };
123
+ }
124
+ }
125
+ return { allowed: true };
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Main executor
130
+ // ---------------------------------------------------------------------------
131
+
132
+ export interface StepResult {
133
+ file?: string;
134
+ files?: string[];
135
+ created?: boolean;
136
+ output?: string;
137
+ blocked?: boolean;
138
+ blockReason?: string;
139
+ }
140
+
5
141
  export async function executeStep(
6
142
  step: { operation: string; args: Record<string, unknown> },
7
143
  projectDir: string,
8
- ): Promise<{ file?: string; files?: string[]; created?: boolean; output?: string }> {
144
+ ): Promise<StepResult> {
9
145
  const args = step.args;
10
146
  const file = args.file as string | undefined;
11
147
 
12
148
  switch (step.operation) {
149
+ // -----------------------------------------------------------------
150
+ // File write operations (with safety guards)
151
+ // -----------------------------------------------------------------
13
152
  case 'write-file':
14
153
  case 'create-workflow':
15
154
  case 'modify-source':
16
155
  case 'implement-node': {
17
- const filePath = path.resolve(projectDir, file!);
156
+ if (!file) {
157
+ return { blocked: true, blockReason: `${step.operation} requires a "file" argument.` };
158
+ }
159
+ assertSafePath(file, projectDir);
160
+ const filePath = path.resolve(projectDir, file);
161
+ const content = (args.content as string) ?? (args.body as string) ?? '';
162
+
163
+ const guard = checkWriteSafety(filePath, content);
164
+ if (!guard.allowed) {
165
+ return { file: filePath, blocked: true, blockReason: guard.reason };
166
+ }
167
+
18
168
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
169
  const existed = fs.existsSync(filePath);
20
- fs.writeFileSync(filePath, (args.content as string) ?? (args.body as string) ?? '', 'utf-8');
170
+ fs.writeFileSync(filePath, content, 'utf-8');
171
+ filesWrittenThisPlan++;
21
172
  return { file: filePath, created: !existed };
22
173
  }
23
- case 'read-file':
24
- return {};
174
+
175
+ // -----------------------------------------------------------------
176
+ // Patch file: surgical find-and-replace (no full rewrite needed)
177
+ // -----------------------------------------------------------------
178
+ case 'patch-file': {
179
+ if (!file) {
180
+ return { blocked: true, blockReason: 'patch-file requires a "file" argument.' };
181
+ }
182
+ assertSafePath(file, projectDir);
183
+ const filePath = path.resolve(projectDir, file);
184
+
185
+ if (!fs.existsSync(filePath)) {
186
+ return { blocked: true, blockReason: `File not found: ${file}` };
187
+ }
188
+
189
+ // File size guard
190
+ const fileSize = fs.statSync(filePath).size;
191
+ if (fileSize > MAX_READ_SIZE) {
192
+ return { blocked: true, blockReason: `File too large for patch-file (${fileSize} bytes, max ${MAX_READ_SIZE}).` };
193
+ }
194
+
195
+ let content = fs.readFileSync(filePath, 'utf-8');
196
+ const patches = (args.patches as Array<{ find: string; replace: string }>) ?? [];
197
+
198
+ if (!patches.length && args.find && args.replace !== undefined) {
199
+ patches.push({ find: args.find as string, replace: args.replace as string });
200
+ }
201
+
202
+ if (!patches.length) {
203
+ return { blocked: true, blockReason: 'patch-file requires "patches" array or "find"+"replace" args.' };
204
+ }
205
+
206
+ let applied = 0;
207
+ const notFound: string[] = [];
208
+
209
+ for (const patch of patches) {
210
+ if (content.includes(patch.find)) {
211
+ content = content.split(patch.find).join(patch.replace); // replaceAll without regex
212
+ applied++;
213
+ } else {
214
+ notFound.push(patch.find.substring(0, 60));
215
+ }
216
+ }
217
+
218
+ if (applied === 0) {
219
+ return {
220
+ file: filePath,
221
+ output: `No patches applied. Search strings not found: ${notFound.join('; ')}`,
222
+ };
223
+ }
224
+
225
+ fs.writeFileSync(filePath, content, 'utf-8');
226
+ filesWrittenThisPlan++;
227
+
228
+ const summary = `Applied ${applied}/${patches.length} patches` +
229
+ (notFound.length ? `. Not found: ${notFound.join('; ')}` : '');
230
+ return { file: filePath, output: summary };
231
+ }
232
+
233
+ // -----------------------------------------------------------------
234
+ // Read file: return content for AI context
235
+ // -----------------------------------------------------------------
236
+ case 'read-file': {
237
+ if (!file) {
238
+ return { output: '' };
239
+ }
240
+ assertSafePath(file, projectDir);
241
+ const filePath = path.resolve(projectDir, file);
242
+ if (!fs.existsSync(filePath)) {
243
+ return { output: `File not found: ${file}` };
244
+ }
245
+ if (fs.statSync(filePath).isDirectory()) {
246
+ const entries = fs.readdirSync(filePath, { encoding: 'utf-8' });
247
+ return { output: `"${file}" is a directory. Contents:\n${entries.join('\n')}` };
248
+ }
249
+ const fileSize = fs.statSync(filePath).size;
250
+ if (fileSize > MAX_READ_SIZE) {
251
+ return { output: `File too large to read (${fileSize} bytes, max ${MAX_READ_SIZE}). Use run-shell with head/tail instead.` };
252
+ }
253
+ const content = fs.readFileSync(filePath, 'utf-8');
254
+ return { file: filePath, output: content };
255
+ }
256
+
257
+ // -----------------------------------------------------------------
258
+ // Shell command: run arbitrary command with safety guards
259
+ // -----------------------------------------------------------------
260
+ case 'run-shell': {
261
+ const command = (args.command as string) ?? '';
262
+ if (!command.trim()) {
263
+ return { blocked: true, blockReason: 'run-shell requires a "command" argument.' };
264
+ }
265
+
266
+ const shellGuard = checkShellSafety(command);
267
+ if (!shellGuard.allowed) {
268
+ return { blocked: true, blockReason: shellGuard.reason };
269
+ }
270
+
271
+ try {
272
+ const output = execSync(command, {
273
+ cwd: projectDir,
274
+ encoding: 'utf-8',
275
+ timeout: SHELL_TIMEOUT,
276
+ maxBuffer: 1024 * 1024, // 1MB output cap
277
+ stdio: ['pipe', 'pipe', 'pipe'],
278
+ });
279
+ return { output: output.trim() };
280
+ } catch (err: unknown) {
281
+ // Shell commands that exit non-zero still return useful output
282
+ const execErr = err as { stdout?: string; stderr?: string; status?: number };
283
+ const stdout = (execErr.stdout ?? '').trim();
284
+ const stderr = (execErr.stderr ?? '').trim();
285
+ const combined = [stdout, stderr].filter(Boolean).join('\n');
286
+ return {
287
+ output: combined || (err instanceof Error ? err.message : String(err)),
288
+ };
289
+ }
290
+ }
291
+
292
+ // -----------------------------------------------------------------
293
+ // List files: glob-like directory listing
294
+ // -----------------------------------------------------------------
295
+ case 'list-files': {
296
+ const dir = (args.directory as string) ?? (args.dir as string) ?? '.';
297
+ const pattern = (args.pattern as string) ?? '';
298
+ assertSafePath(dir, projectDir);
299
+ const targetDir = path.resolve(projectDir, dir);
300
+
301
+ if (!fs.existsSync(targetDir)) {
302
+ return { output: `Directory not found: ${dir}` };
303
+ }
304
+
305
+ const entries = fs.readdirSync(targetDir, { recursive: true, encoding: 'utf-8' }) as string[];
306
+ let files = entries
307
+ .filter(e => {
308
+ if (e.includes('node_modules') || e.includes('.git')) return false;
309
+ const full = path.join(targetDir, e);
310
+ try { return fs.statSync(full).isFile(); } catch { return false; }
311
+ })
312
+ .sort();
313
+
314
+ if (pattern) {
315
+ try {
316
+ const regex = new RegExp(pattern);
317
+ files = files.filter(f => regex.test(f));
318
+ } catch {
319
+ return { output: `Invalid regex pattern: ${pattern}` };
320
+ }
321
+ }
322
+
323
+ if (files.length > MAX_LIST_FILES) {
324
+ files = files.slice(0, MAX_LIST_FILES);
325
+ }
326
+
327
+ return { files: files.map(f => path.join(dir, f)), output: files.join('\n') };
328
+ }
329
+
330
+ // -----------------------------------------------------------------
331
+ // Flow-weaver CLI commands (via programmatic API)
332
+ // -----------------------------------------------------------------
25
333
  default: {
26
334
  const result = await runCommand(step.operation, { ...args, cwd: projectDir });
27
335
  return {
@@ -177,9 +177,38 @@ Genesis is a 17-step self-evolving workflow engine:
177
177
 
178
178
  When stabilize mode is active, only fix-up operations are allowed: removeNode, removeConnection, implementNode. No new nodes or connections.
179
179
 
180
- ## Response Format
180
+ ## Tool Use
181
181
 
182
- Return ONLY valid JSON. No markdown, no code fences, no explanation outside the JSON structure. Your response must parse with JSON.parse() directly.`;
182
+ You have tools available: validate, read_file, patch_file, run_shell, list_files, write_file.
183
+
184
+ USE TOOLS to complete tasks. Do NOT describe what you would do — actually do it by calling tools. You can see tool results and decide your next action dynamically.
185
+
186
+ Workflow for fixing validation errors:
187
+ 1. Call validate(file) to see exact errors
188
+ 2. Call read_file(file) to see the code
189
+ 3. Call patch_file(file, patches) with exact find/replace strings
190
+ 4. Call validate(file) again to confirm fixes
191
+ 5. Repeat if errors remain
192
+
193
+ Rules:
194
+ - Always validate BEFORE and AFTER patching
195
+ - Always read a file before patching it (you need exact strings for find/replace)
196
+ - Use patch_file for modifications, write_file only for new files
197
+ - Be concise in your text responses — let tool results speak
198
+
199
+ Flow Weaver workflows are TypeScript. You can also help create supporting files in other formats (JSON configs, shell scripts, Markdown docs).
200
+
201
+ Before starting a task on a file, call recall(filename) to check if there is stored knowledge about known issues or patterns for that file.
202
+ After discovering something important (a pattern, a common fix, a gotcha), call learn(key, value) to store it for future tasks.
203
+
204
+ ## Teaching
205
+
206
+ When creating or modifying workflows, briefly explain your decisions:
207
+ - Why you chose a particular template or pattern (1 line)
208
+ - What each node does and why it is @expression vs standard (1 line)
209
+ - What the data flow looks like (1 line)
210
+ Do NOT lecture. Keep explanations short. The user is learning Flow Weaver by watching you work.
211
+ Example: "Using sequential template — best for linear pipelines. The validator is @expression (pure, no side effects). Data flows: input -> validate -> transform -> output."`;
183
212
  }
184
213
 
185
214
  export async function buildSystemPrompt(): Promise<string> {
@@ -205,11 +234,25 @@ export async function buildSystemPrompt(): Promise<string> {
205
234
 
206
235
  function formatBotOperations(cliCommands: CliCommandDoc[]): string {
207
236
  const packOps = [
237
+ '## File Operations',
208
238
  '- create-workflow: Create a new workflow file. args: { file, content }',
209
239
  '- implement-node: Write a node type implementation. args: { file, content }',
210
- '- modify-source: Direct source file modification. args: { file, content }',
211
- '- read-file: Read a file for context. args: { file }',
212
- '- write-file: Write a file. args: { file, content }',
240
+ '- write-file: Write a file. args: { file, content }. Content must be the COMPLETE file.',
241
+ '- read-file: Read a file and return its content. args: { file }',
242
+ '- patch-file: Surgical find-and-replace edits. args: { file, patches: [{ find: "old text", replace: "new text" }] }. PREFERRED for modifying existing files — no need to rewrite the entire file.',
243
+ '- list-files: List files in a directory. args: { directory, pattern? } (pattern is regex)',
244
+ '',
245
+ '## Shell Commands',
246
+ '- run-shell: Execute a shell command and return output. args: { command }. Use for: npx vitest, git status, grep, find, etc.',
247
+ ' Examples: { "command": "npx vitest run --reporter verbose" }, { "command": "npx flow-weaver validate src/workflow.ts --json" }',
248
+ ' Blocked: rm -rf, git push, npm publish, sudo, curl|sh (safety policy).',
249
+ '',
250
+ '## Best Practices',
251
+ 'PREFER patch-file over write-file for modifying existing files (surgical edits, no truncation risk).',
252
+ 'Use run-shell for running tests (npx vitest), validation (flow-weaver validate), and inspecting output.',
253
+ 'Use read-file to understand a file before modifying it.',
254
+ 'Use list-files to discover project structure.',
255
+ 'Writes that shrink a file by >50% or write empty content are automatically BLOCKED.',
213
256
  ];
214
257
 
215
258
  const fwOps = cliCommands
@@ -229,48 +272,28 @@ function formatBotOperations(cliCommands: CliCommandDoc[]): string {
229
272
  return [...packOps, ...fwOps].join('\n');
230
273
  }
231
274
 
232
- export function buildBotSystemPrompt(contextBundle?: string, cliCommands?: CliCommandDoc[]): string {
233
- const operationsList = formatBotOperations(cliCommands ?? []);
234
-
235
- const planSchema = `## Bot Plan Schema
236
-
237
- When asked to plan a task, return a JSON object with this structure:
238
- {
239
- "steps": [
240
- {
241
- "id": "step-1",
242
- "operation": "<operation>",
243
- "description": "What this step does",
244
- "args": { ... operation-specific arguments ... }
245
- }
246
- ],
247
- "summary": "Brief description of the overall plan"
248
- }
249
-
250
- Available operations:
251
- ${operationsList}`;
252
-
253
- const botInstructions = `## Bot Mode Instructions
254
-
255
- You are operating in autonomous bot mode. Your job is to plan and execute workflow creation or modification tasks.
256
-
257
- When planning:
258
- 1. Break the task into concrete, ordered steps using the plan schema above
259
- 2. For new workflows, plan: scaffold/create -> implement nodes -> compile -> validate
260
- 3. For modifications, plan: read current state -> modify -> compile -> validate
261
- 4. Each step is executed via the flow-weaver programmatic API
262
- 5. Use templates when they match the task
263
- 6. Prefer @expression nodes for deterministic operations
264
- 7. Use proper JSDoc annotations on all node types and workflows
265
- 8. Include visualization metadata (colors, icons, positions) on workflow nodes
266
-
267
- When fixing validation errors:
268
- 1. Read the error messages carefully
269
- 2. Map each error to a specific fix operation
270
- 3. Common fixes: add missing connections, fix port names, resolve type mismatches
271
- 4. Return a new plan with only the fix steps`;
272
-
273
- let prompt = planSchema + '\n\n' + botInstructions;
275
+ export function buildBotSystemPrompt(contextBundle?: string, _cliCommands?: CliCommandDoc[], projectDir?: string): string {
276
+ let prompt = `## Safety Policy
277
+
278
+ Writes that shrink a file by >50% or write empty content are automatically BLOCKED.
279
+ Blocked shell commands: rm -rf, git push, npm publish, sudo, curl|sh.
280
+ Always validate BEFORE and AFTER patching.
281
+ Always read a file before patching it (you need exact strings for find/replace).
282
+ Use patch_file for modifications, write_file only for new files.
283
+ Be concise in your text responses — let tool results speak.`;
284
+
285
+ // Load project plan file if it exists — this is the vision spec that guides all work
286
+ if (projectDir) {
287
+ try {
288
+ const fs = require('node:fs');
289
+ const path = require('node:path');
290
+ const planPath = path.resolve(projectDir, '.weaver-plan.md');
291
+ if (fs.existsSync(planPath)) {
292
+ const plan = fs.readFileSync(planPath, 'utf-8').trim();
293
+ prompt += '\n\n## Project Plan & Vision\n\nIMPORTANT: All work MUST align with this plan. If a task contradicts the plan, skip it and explain why.\n\n' + plan;
294
+ }
295
+ } catch { /* plan file not available */ }
296
+ }
274
297
 
275
298
  if (contextBundle) {
276
299
  prompt += '\n\n## Project Context\n\n' + contextBundle;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Auto task decomposition — splits broad tasks into per-file tasks.
3
+ *
4
+ * When a task says "fix all templates" or "validate everything",
5
+ * decompose it into one task per file. This gives the AI focused
6
+ * context and prevents one failure from blocking all files.
7
+ */
8
+
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+
12
+ export interface DecomposableTask {
13
+ id: string;
14
+ instruction: string;
15
+ mode?: string;
16
+ targets?: string[];
17
+ priority?: number;
18
+ }
19
+
20
+ export interface DecomposedResult {
21
+ decomposed: boolean;
22
+ tasks: DecomposableTask[];
23
+ }
24
+
25
+ // Patterns that suggest a broad task targeting multiple files
26
+ const BROAD_PATTERNS = [
27
+ /\b(all|every|each)\b.*\b(template|workflow|file|node.?type)s?\b/i,
28
+ /\bfix\b.*\bin\s+src\/(templates|node-types|workflows)\/?$/i,
29
+ /\bvalidat(e|ion)\b.*\b(all|every|each|src\/)\b/i,
30
+ ];
31
+
32
+ /**
33
+ * Check if a task should be decomposed into per-file tasks.
34
+ * Returns the original task unchanged if no decomposition is needed.
35
+ */
36
+ export function decomposeTask(
37
+ task: DecomposableTask,
38
+ projectDir: string,
39
+ ): DecomposedResult {
40
+ const instruction = task.instruction;
41
+
42
+ // Already has specific targets — don't decompose
43
+ if (task.targets && task.targets.length === 1) {
44
+ return { decomposed: false, tasks: [task] };
45
+ }
46
+
47
+ // Check if instruction matches broad patterns
48
+ const isBroad = BROAD_PATTERNS.some(p => p.test(instruction));
49
+ if (!isBroad) {
50
+ return { decomposed: false, tasks: [task] };
51
+ }
52
+
53
+ // Determine which directory to scan
54
+ let targetDir: string | undefined;
55
+ if (instruction.match(/template/i)) targetDir = 'src/templates';
56
+ else if (instruction.match(/node.?type/i)) targetDir = 'src/node-types';
57
+ else if (instruction.match(/workflow/i)) targetDir = 'src/workflows';
58
+
59
+ if (!targetDir) {
60
+ return { decomposed: false, tasks: [task] };
61
+ }
62
+
63
+ const absDir = path.resolve(projectDir, targetDir);
64
+ if (!fs.existsSync(absDir)) {
65
+ return { decomposed: false, tasks: [task] };
66
+ }
67
+
68
+ // List .ts files in the directory — only files that actually exist
69
+ let files: string[];
70
+ try {
71
+ files = fs.readdirSync(absDir)
72
+ .filter(f => f.endsWith('.ts') && !f.startsWith('index'))
73
+ .filter(f => fs.existsSync(path.resolve(absDir, f))) // verify file exists
74
+ .sort();
75
+ } catch {
76
+ return { decomposed: false, tasks: [task] };
77
+ }
78
+
79
+ if (files.length === 0 || files.length > 50) {
80
+ return { decomposed: false, tasks: [task] };
81
+ }
82
+
83
+ // Extract the verb from the original instruction for clean per-file instructions
84
+ const verb = instruction.match(/^(Fix|Validate|Add|Update|Review|Run|Check|Test|Improve)/i)?.[0] ?? 'Process';
85
+
86
+ // Create per-file tasks with clean, grammatical instructions
87
+ const tasks: DecomposableTask[] = files.map((file, i) => {
88
+ const filePath = path.join(targetDir!, file);
89
+
90
+ return {
91
+ id: `${task.id}-${i + 1}`,
92
+ instruction: `${verb} ${filePath}`,
93
+ mode: task.mode ?? 'modify',
94
+ targets: [filePath],
95
+ priority: task.priority ?? 0,
96
+ };
97
+ });
98
+
99
+ return { decomposed: true, tasks };
100
+ }