@synergenius/flow-weaver-pack-weaver 0.9.0 → 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.
- package/dist/bot/ai-client.d.ts +22 -2
- package/dist/bot/ai-client.d.ts.map +1 -1
- package/dist/bot/ai-client.js +168 -20
- package/dist/bot/ai-client.js.map +1 -1
- package/dist/bot/assistant-core.d.ts +25 -0
- package/dist/bot/assistant-core.d.ts.map +1 -0
- package/dist/bot/assistant-core.js +265 -0
- package/dist/bot/assistant-core.js.map +1 -0
- package/dist/bot/assistant-tools.d.ts +9 -0
- package/dist/bot/assistant-tools.d.ts.map +1 -0
- package/dist/bot/assistant-tools.js +602 -0
- package/dist/bot/assistant-tools.js.map +1 -0
- package/dist/bot/audit-logger.d.ts.map +1 -1
- package/dist/bot/audit-logger.js +9 -5
- package/dist/bot/audit-logger.js.map +1 -1
- package/dist/bot/bot-manager.d.ts +49 -0
- package/dist/bot/bot-manager.d.ts.map +1 -0
- package/dist/bot/bot-manager.js +279 -0
- package/dist/bot/bot-manager.js.map +1 -0
- package/dist/bot/child-process-tracker.d.ts +6 -0
- package/dist/bot/child-process-tracker.d.ts.map +1 -0
- package/dist/bot/child-process-tracker.js +35 -0
- package/dist/bot/child-process-tracker.js.map +1 -0
- package/dist/bot/cli-provider.d.ts.map +1 -1
- package/dist/bot/cli-provider.js +13 -8
- package/dist/bot/cli-provider.js.map +1 -1
- package/dist/bot/conversation-store.d.ts +40 -0
- package/dist/bot/conversation-store.d.ts.map +1 -0
- package/dist/bot/conversation-store.js +182 -0
- package/dist/bot/conversation-store.js.map +1 -0
- package/dist/bot/error-guide.d.ts +10 -0
- package/dist/bot/error-guide.d.ts.map +1 -0
- package/dist/bot/error-guide.js +34 -0
- package/dist/bot/error-guide.js.map +1 -0
- package/dist/bot/knowledge-store.d.ts +17 -0
- package/dist/bot/knowledge-store.d.ts.map +1 -0
- package/dist/bot/knowledge-store.js +53 -0
- package/dist/bot/knowledge-store.js.map +1 -0
- package/dist/bot/retry-utils.d.ts +19 -0
- package/dist/bot/retry-utils.d.ts.map +1 -0
- package/dist/bot/retry-utils.js +64 -0
- package/dist/bot/retry-utils.js.map +1 -0
- package/dist/bot/runner.d.ts.map +1 -1
- package/dist/bot/runner.js +12 -1
- package/dist/bot/runner.js.map +1 -1
- package/dist/bot/session-state.d.ts.map +1 -1
- package/dist/bot/session-state.js +3 -1
- package/dist/bot/session-state.js.map +1 -1
- package/dist/bot/steering.js +1 -1
- package/dist/bot/steering.js.map +1 -1
- package/dist/bot/step-executor.d.ts +10 -5
- package/dist/bot/step-executor.d.ts.map +1 -1
- package/dist/bot/step-executor.js +252 -3
- package/dist/bot/step-executor.js.map +1 -1
- package/dist/bot/system-prompt.d.ts +1 -1
- package/dist/bot/system-prompt.d.ts.map +1 -1
- package/dist/bot/system-prompt.js +69 -43
- package/dist/bot/system-prompt.js.map +1 -1
- package/dist/bot/task-decomposer.d.ts +24 -0
- package/dist/bot/task-decomposer.d.ts.map +1 -0
- package/dist/bot/task-decomposer.js +75 -0
- package/dist/bot/task-decomposer.js.map +1 -0
- package/dist/bot/task-queue.d.ts +17 -4
- package/dist/bot/task-queue.d.ts.map +1 -1
- package/dist/bot/task-queue.js +95 -4
- package/dist/bot/task-queue.js.map +1 -1
- package/dist/bot/terminal-renderer.d.ts +60 -0
- package/dist/bot/terminal-renderer.d.ts.map +1 -0
- package/dist/bot/terminal-renderer.js +205 -0
- package/dist/bot/terminal-renderer.js.map +1 -0
- package/dist/bot/types.d.ts +7 -0
- package/dist/bot/types.d.ts.map +1 -1
- package/dist/bot/weaver-tools.d.ts +18 -0
- package/dist/bot/weaver-tools.d.ts.map +1 -0
- package/dist/bot/weaver-tools.js +215 -0
- package/dist/bot/weaver-tools.js.map +1 -0
- package/dist/cli-bridge.d.ts.map +1 -1
- package/dist/cli-bridge.js +5 -1
- package/dist/cli-bridge.js.map +1 -1
- package/dist/cli-handlers.d.ts +13 -1
- package/dist/cli-handlers.d.ts.map +1 -1
- package/dist/cli-handlers.js +616 -48
- package/dist/cli-handlers.js.map +1 -1
- package/dist/mcp-tools.js +2 -2
- package/dist/mcp-tools.js.map +1 -1
- package/dist/node-types/abort-task.d.ts.map +1 -1
- package/dist/node-types/abort-task.js +4 -3
- package/dist/node-types/abort-task.js.map +1 -1
- package/dist/node-types/agent-execute.d.ts +38 -0
- package/dist/node-types/agent-execute.d.ts.map +1 -0
- package/dist/node-types/agent-execute.js +256 -0
- package/dist/node-types/agent-execute.js.map +1 -0
- package/dist/node-types/bot-report.d.ts +5 -3
- package/dist/node-types/bot-report.d.ts.map +1 -1
- package/dist/node-types/bot-report.js +39 -7
- package/dist/node-types/bot-report.js.map +1 -1
- package/dist/node-types/build-context.d.ts +3 -3
- package/dist/node-types/build-context.d.ts.map +1 -1
- package/dist/node-types/build-context.js +108 -24
- package/dist/node-types/build-context.js.map +1 -1
- package/dist/node-types/detect-provider.d.ts +2 -2
- package/dist/node-types/detect-provider.d.ts.map +1 -1
- package/dist/node-types/detect-provider.js +3 -1
- package/dist/node-types/detect-provider.js.map +1 -1
- package/dist/node-types/exec-validate-retry.d.ts.map +1 -1
- package/dist/node-types/exec-validate-retry.js +43 -6
- package/dist/node-types/exec-validate-retry.js.map +1 -1
- package/dist/node-types/execute-plan.d.ts.map +1 -1
- package/dist/node-types/execute-plan.js +31 -8
- package/dist/node-types/execute-plan.js.map +1 -1
- package/dist/node-types/execute-target.d.ts.map +1 -1
- package/dist/node-types/execute-target.js +3 -1
- package/dist/node-types/execute-target.js.map +1 -1
- package/dist/node-types/fix-errors.d.ts.map +1 -1
- package/dist/node-types/fix-errors.js +21 -5
- package/dist/node-types/fix-errors.js.map +1 -1
- package/dist/node-types/genesis-observe.d.ts.map +1 -1
- package/dist/node-types/genesis-observe.js +3 -1
- package/dist/node-types/genesis-observe.js.map +1 -1
- package/dist/node-types/genesis-report.js +4 -1
- package/dist/node-types/genesis-report.js.map +1 -1
- package/dist/node-types/git-ops.d.ts.map +1 -1
- package/dist/node-types/git-ops.js +98 -4
- package/dist/node-types/git-ops.js.map +1 -1
- package/dist/node-types/index.d.ts +2 -0
- package/dist/node-types/index.d.ts.map +1 -1
- package/dist/node-types/index.js +2 -0
- package/dist/node-types/index.js.map +1 -1
- package/dist/node-types/load-config.d.ts +2 -2
- package/dist/node-types/load-config.d.ts.map +1 -1
- package/dist/node-types/load-config.js.map +1 -1
- package/dist/node-types/plan-task.d.ts.map +1 -1
- package/dist/node-types/plan-task.js +14 -2
- package/dist/node-types/plan-task.js.map +1 -1
- package/dist/node-types/read-workflow.js +8 -2
- package/dist/node-types/read-workflow.js.map +1 -1
- package/dist/node-types/receive-task.d.ts.map +1 -1
- package/dist/node-types/receive-task.js +35 -26
- package/dist/node-types/receive-task.js.map +1 -1
- package/dist/node-types/send-notify.js +2 -1
- package/dist/node-types/send-notify.js.map +1 -1
- package/dist/node-types/validate-gate.d.ts +18 -0
- package/dist/node-types/validate-gate.d.ts.map +1 -0
- package/dist/node-types/validate-gate.js +96 -0
- package/dist/node-types/validate-gate.js.map +1 -0
- package/dist/workflows/genesis-task.d.ts +20 -12
- package/dist/workflows/genesis-task.d.ts.map +1 -1
- package/dist/workflows/genesis-task.js +20 -12
- package/dist/workflows/genesis-task.js.map +1 -1
- package/dist/workflows/weaver-agent.d.ts +35 -0
- package/dist/workflows/weaver-agent.d.ts.map +1 -0
- package/dist/workflows/weaver-agent.js +777 -0
- package/dist/workflows/weaver-agent.js.map +1 -0
- package/dist/workflows/weaver-bot-batch.d.ts +19 -26
- package/dist/workflows/weaver-bot-batch.d.ts.map +1 -1
- package/dist/workflows/weaver-bot-batch.js +1043 -27
- package/dist/workflows/weaver-bot-batch.js.map +1 -1
- package/dist/workflows/weaver-bot.d.ts +21 -35
- package/dist/workflows/weaver-bot.d.ts.map +1 -1
- package/dist/workflows/weaver-bot.js +1119 -36
- package/dist/workflows/weaver-bot.js.map +1 -1
- package/flowweaver.manifest.json +21 -1
- package/package.json +5 -2
- package/src/bot/ai-client.ts +180 -19
- package/src/bot/assistant-core.ts +306 -0
- package/src/bot/assistant-tools.ts +605 -0
- package/src/bot/audit-logger.ts +6 -5
- package/src/bot/bot-manager.ts +293 -0
- package/src/bot/child-process-tracker.ts +40 -0
- package/src/bot/cli-provider.ts +13 -8
- package/src/bot/conversation-store.ts +222 -0
- package/src/bot/error-guide.ts +34 -0
- package/src/bot/knowledge-store.ts +59 -0
- package/src/bot/retry-utils.ts +76 -0
- package/src/bot/runner.ts +12 -1
- package/src/bot/session-state.ts +2 -1
- package/src/bot/steering.ts +1 -1
- package/src/bot/step-executor.ts +313 -5
- package/src/bot/system-prompt.ts +70 -47
- package/src/bot/task-decomposer.ts +100 -0
- package/src/bot/task-queue.ts +113 -7
- package/src/bot/terminal-renderer.ts +241 -0
- package/src/bot/types.ts +8 -0
- package/src/bot/weaver-tools.ts +225 -0
- package/src/cli-bridge.ts +7 -1
- package/src/cli-handlers.ts +625 -48
- package/src/mcp-tools.ts +2 -2
- package/src/node-types/abort-task.ts +5 -4
- package/src/node-types/agent-execute.ts +306 -0
- package/src/node-types/bot-report.ts +40 -9
- package/src/node-types/build-context.ts +112 -25
- package/src/node-types/detect-provider.ts +4 -3
- package/src/node-types/exec-validate-retry.ts +47 -8
- package/src/node-types/execute-plan.ts +32 -8
- package/src/node-types/execute-target.ts +2 -1
- package/src/node-types/fix-errors.ts +20 -5
- package/src/node-types/genesis-observe.ts +2 -1
- package/src/node-types/genesis-report.ts +1 -1
- package/src/node-types/git-ops.ts +93 -4
- package/src/node-types/index.ts +2 -0
- package/src/node-types/load-config.ts +3 -3
- package/src/node-types/plan-task.ts +15 -3
- package/src/node-types/read-workflow.ts +2 -2
- package/src/node-types/receive-task.ts +31 -26
- package/src/node-types/send-notify.ts +1 -1
- package/src/node-types/validate-gate.ts +112 -0
- package/src/workflows/genesis-task.ts +20 -12
- package/src/workflows/weaver-agent.ts +799 -0
- package/src/workflows/weaver-bot-batch.ts +1049 -27
- package/src/workflows/weaver-bot.ts +1123 -36
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as crypto from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
export interface KnowledgeEntry {
|
|
7
|
+
key: string;
|
|
8
|
+
value: string;
|
|
9
|
+
source: string;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class KnowledgeStore {
|
|
14
|
+
private filePath: string;
|
|
15
|
+
|
|
16
|
+
constructor(projectDir?: string) {
|
|
17
|
+
const dir = projectDir
|
|
18
|
+
? path.join(os.homedir(), '.weaver', 'projects', crypto.createHash('sha256').update(projectDir).digest('hex').slice(0, 8))
|
|
19
|
+
: path.join(os.homedir(), '.weaver');
|
|
20
|
+
this.filePath = path.join(dir, 'knowledge.ndjson');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
learn(key: string, value: string, source: string): void {
|
|
24
|
+
// Append entry to NDJSON file. If key exists, update it.
|
|
25
|
+
const entries = this.readAll().filter(e => e.key !== key);
|
|
26
|
+
entries.push({ key, value, source, createdAt: Date.now() });
|
|
27
|
+
this.writeAll(entries);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
recall(query: string): KnowledgeEntry[] {
|
|
31
|
+
// Fuzzy match: return entries whose key contains the query (case-insensitive)
|
|
32
|
+
const lower = query.toLowerCase();
|
|
33
|
+
return this.readAll().filter(e => e.key.toLowerCase().includes(lower) || e.value.toLowerCase().includes(lower));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
forget(key: string): void {
|
|
37
|
+
const entries = this.readAll().filter(e => e.key !== key);
|
|
38
|
+
this.writeAll(entries);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
list(): KnowledgeEntry[] {
|
|
42
|
+
return this.readAll();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private readAll(): KnowledgeEntry[] {
|
|
46
|
+
if (!fs.existsSync(this.filePath)) return [];
|
|
47
|
+
const content = fs.readFileSync(this.filePath, 'utf-8').trim();
|
|
48
|
+
if (!content) return [];
|
|
49
|
+
return content.split('\n').filter(Boolean).map(line => {
|
|
50
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
51
|
+
}).filter(Boolean) as KnowledgeEntry[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private writeAll(entries: KnowledgeEntry[]): void {
|
|
55
|
+
const dir = path.dirname(this.filePath);
|
|
56
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
57
|
+
fs.writeFileSync(this.filePath, entries.map(e => JSON.stringify(e)).join('\n') + (entries.length > 0 ? '\n' : ''));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry utilities for transient error handling with exponential backoff.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const TRANSIENT_STATUS_CODES = [429, 502, 503, 504];
|
|
6
|
+
const TRANSIENT_ERROR_CODES = ['ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'EPIPE', 'ENOTFOUND'];
|
|
7
|
+
const TRANSIENT_MESSAGES = ['rate limit', 'too many requests', 'overloaded', 'bad gateway', 'service unavailable'];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if an error is transient (retriable) vs permanent.
|
|
11
|
+
* Transient: network issues, rate limits, server errors.
|
|
12
|
+
* Permanent: auth failures, parse errors, validation errors.
|
|
13
|
+
*/
|
|
14
|
+
export function isTransientError(err: unknown): boolean {
|
|
15
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16
|
+
const lower = msg.toLowerCase();
|
|
17
|
+
|
|
18
|
+
// Check for HTTP status codes in message
|
|
19
|
+
for (const code of TRANSIENT_STATUS_CODES) {
|
|
20
|
+
if (msg.includes(String(code))) return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check for Node.js error codes
|
|
24
|
+
if (err instanceof Error && 'code' in err) {
|
|
25
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
26
|
+
if (code && TRANSIENT_ERROR_CODES.includes(code)) return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Also check message for error code strings (e.g. "ETIMEDOUT" in message)
|
|
30
|
+
for (const code of TRANSIENT_ERROR_CODES) {
|
|
31
|
+
if (msg.includes(code)) return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check for rate limit / overload messages
|
|
35
|
+
for (const phrase of TRANSIENT_MESSAGES) {
|
|
36
|
+
if (lower.includes(phrase)) return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check for exit code 143 (SIGTERM — likely our timeout killed the process)
|
|
40
|
+
if (msg.includes('exit code 143') || msg.includes('exited with code 143')) return true;
|
|
41
|
+
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run a function with exponential backoff retry on transient errors.
|
|
47
|
+
*/
|
|
48
|
+
export async function withRetry<T>(
|
|
49
|
+
fn: () => Promise<T>,
|
|
50
|
+
options?: {
|
|
51
|
+
maxRetries?: number;
|
|
52
|
+
baseDelayMs?: number;
|
|
53
|
+
multiplier?: number;
|
|
54
|
+
onRetry?: (attempt: number, delay: number, err: Error) => void;
|
|
55
|
+
},
|
|
56
|
+
): Promise<T> {
|
|
57
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
58
|
+
const baseDelay = options?.baseDelayMs ?? 5_000;
|
|
59
|
+
const multiplier = options?.multiplier ?? 3;
|
|
60
|
+
|
|
61
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
62
|
+
try {
|
|
63
|
+
return await fn();
|
|
64
|
+
} catch (err: unknown) {
|
|
65
|
+
const isLast = attempt >= maxRetries;
|
|
66
|
+
if (isLast || !isTransientError(err)) throw err;
|
|
67
|
+
|
|
68
|
+
const delay = baseDelay * Math.pow(multiplier, attempt);
|
|
69
|
+
options?.onRetry?.(attempt + 1, delay, err instanceof Error ? err : new Error(String(err)));
|
|
70
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Unreachable, but TypeScript needs it
|
|
75
|
+
throw new Error('withRetry: exhausted retries');
|
|
76
|
+
}
|
package/src/bot/runner.ts
CHANGED
|
@@ -224,6 +224,16 @@ export async function runWorkflow(
|
|
|
224
224
|
const summary = buildSummary(result);
|
|
225
225
|
const outcome = success ? 'completed' : 'failed';
|
|
226
226
|
|
|
227
|
+
// Extract stepLog from WeaverContext if available
|
|
228
|
+
let stepLog: import('./types.js').StepLogEntry[] | undefined;
|
|
229
|
+
try {
|
|
230
|
+
const ctxStr = result?.ctx as string | undefined;
|
|
231
|
+
if (ctxStr) {
|
|
232
|
+
const ctx = JSON.parse(ctxStr);
|
|
233
|
+
if (ctx.stepLogJson) stepLog = JSON.parse(ctx.stepLogJson);
|
|
234
|
+
}
|
|
235
|
+
} catch { /* stepLog extraction is best-effort */ }
|
|
236
|
+
|
|
227
237
|
await notifier({
|
|
228
238
|
type: 'workflow-complete',
|
|
229
239
|
workflowFile: absPath,
|
|
@@ -237,7 +247,7 @@ export async function runWorkflow(
|
|
|
237
247
|
recordRun(store, {
|
|
238
248
|
id: runId, workflowFile: absPath, startedAt, success, outcome: outcome as RunOutcome, summary,
|
|
239
249
|
functionName: execResult.functionName, executionTime: execResult.executionTime,
|
|
240
|
-
dryRun: false, provider: providerConfig.name, params: options?.params,
|
|
250
|
+
dryRun: false, provider: providerConfig.name, params: options?.params, stepLog,
|
|
241
251
|
}, verbose);
|
|
242
252
|
|
|
243
253
|
auditEmit('run-complete', { success, outcome, summary });
|
|
@@ -282,6 +292,7 @@ function recordRun(
|
|
|
282
292
|
outcome: RunOutcome; summary: string; functionName?: string;
|
|
283
293
|
executionTime?: number; dryRun: boolean; provider?: string;
|
|
284
294
|
params?: Record<string, unknown>;
|
|
295
|
+
stepLog?: import('./types.js').StepLogEntry[];
|
|
285
296
|
},
|
|
286
297
|
verbose: boolean,
|
|
287
298
|
): void {
|
package/src/bot/session-state.ts
CHANGED
|
@@ -40,7 +40,8 @@ export class SessionStore {
|
|
|
40
40
|
try {
|
|
41
41
|
if (!fs.existsSync(this.filePath)) return null;
|
|
42
42
|
return JSON.parse(fs.readFileSync(this.filePath, 'utf-8')) as SessionState;
|
|
43
|
-
} catch {
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (process.env.WEAVER_VERBOSE) process.stderr.write(`[weaver] session state load failed: ${err}\n`);
|
|
44
45
|
return null;
|
|
45
46
|
}
|
|
46
47
|
}
|
package/src/bot/steering.ts
CHANGED
|
@@ -13,7 +13,7 @@ export class SteeringController {
|
|
|
13
13
|
private controlPath: string;
|
|
14
14
|
|
|
15
15
|
constructor(controlDir?: string) {
|
|
16
|
-
const dir = controlDir ?? path.join(os.homedir(), '.weaver');
|
|
16
|
+
const dir = controlDir ?? process.env.WEAVER_STEERING_DIR ?? path.join(os.homedir(), '.weaver');
|
|
17
17
|
this.controlPath = path.join(dir, 'control.json');
|
|
18
18
|
}
|
|
19
19
|
|
package/src/bot/step-executor.ts
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
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,
|
|
170
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
171
|
+
filesWrittenThisPlan++;
|
|
21
172
|
return { file: filePath, created: !existed };
|
|
22
173
|
}
|
|
23
|
-
|
|
24
|
-
|
|
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 {
|
package/src/bot/system-prompt.ts
CHANGED
|
@@ -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
|
-
##
|
|
180
|
+
## Tool Use
|
|
181
181
|
|
|
182
|
-
|
|
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
|
-
'-
|
|
211
|
-
'- read-file: Read a file
|
|
212
|
-
'-
|
|
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,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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;
|