@electric-agent/agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/agents/clarifier.d.ts +16 -0
  2. package/dist/agents/clarifier.d.ts.map +1 -0
  3. package/dist/agents/clarifier.js +158 -0
  4. package/dist/agents/clarifier.js.map +1 -0
  5. package/dist/agents/coder.d.ts +14 -0
  6. package/dist/agents/coder.d.ts.map +1 -0
  7. package/dist/agents/coder.js +126 -0
  8. package/dist/agents/coder.js.map +1 -0
  9. package/dist/agents/planner.d.ts +6 -0
  10. package/dist/agents/planner.d.ts.map +1 -0
  11. package/dist/agents/planner.js +69 -0
  12. package/dist/agents/planner.js.map +1 -0
  13. package/dist/agents/prompts.d.ts +9 -0
  14. package/dist/agents/prompts.d.ts.map +1 -0
  15. package/dist/agents/prompts.js +231 -0
  16. package/dist/agents/prompts.js.map +1 -0
  17. package/dist/cli/headless.d.ts +9 -0
  18. package/dist/cli/headless.d.ts.map +1 -0
  19. package/dist/cli/headless.js +506 -0
  20. package/dist/cli/headless.js.map +1 -0
  21. package/dist/cli/serve.d.ts +6 -0
  22. package/dist/cli/serve.d.ts.map +1 -0
  23. package/dist/cli/serve.js +113 -0
  24. package/dist/cli/serve.js.map +1 -0
  25. package/dist/engine/message-parser.d.ts +8 -0
  26. package/dist/engine/message-parser.d.ts.map +1 -0
  27. package/dist/engine/message-parser.js +106 -0
  28. package/dist/engine/message-parser.js.map +1 -0
  29. package/dist/engine/orchestrator.d.ts +50 -0
  30. package/dist/engine/orchestrator.d.ts.map +1 -0
  31. package/dist/engine/orchestrator.js +492 -0
  32. package/dist/engine/orchestrator.js.map +1 -0
  33. package/dist/engine/stdio-adapter.d.ts +24 -0
  34. package/dist/engine/stdio-adapter.d.ts.map +1 -0
  35. package/dist/engine/stdio-adapter.js +139 -0
  36. package/dist/engine/stdio-adapter.js.map +1 -0
  37. package/dist/engine/stream-adapter.d.ts +45 -0
  38. package/dist/engine/stream-adapter.d.ts.map +1 -0
  39. package/dist/engine/stream-adapter.js +154 -0
  40. package/dist/engine/stream-adapter.js.map +1 -0
  41. package/dist/find-env.d.ts +3 -0
  42. package/dist/find-env.d.ts.map +1 -0
  43. package/dist/find-env.js +16 -0
  44. package/dist/find-env.js.map +1 -0
  45. package/dist/git/index.d.ts +114 -0
  46. package/dist/git/index.d.ts.map +1 -0
  47. package/dist/git/index.js +434 -0
  48. package/dist/git/index.js.map +1 -0
  49. package/dist/hooks/block-bash.d.ts +7 -0
  50. package/dist/hooks/block-bash.d.ts.map +1 -0
  51. package/dist/hooks/block-bash.js +15 -0
  52. package/dist/hooks/block-bash.js.map +1 -0
  53. package/dist/hooks/dependency-guard.d.ts +7 -0
  54. package/dist/hooks/dependency-guard.d.ts.map +1 -0
  55. package/dist/hooks/dependency-guard.js +43 -0
  56. package/dist/hooks/dependency-guard.js.map +1 -0
  57. package/dist/hooks/guardrail-inject.d.ts +17 -0
  58. package/dist/hooks/guardrail-inject.d.ts.map +1 -0
  59. package/dist/hooks/guardrail-inject.js +69 -0
  60. package/dist/hooks/guardrail-inject.js.map +1 -0
  61. package/dist/hooks/import-validation.d.ts +7 -0
  62. package/dist/hooks/import-validation.d.ts.map +1 -0
  63. package/dist/hooks/import-validation.js +192 -0
  64. package/dist/hooks/import-validation.js.map +1 -0
  65. package/dist/hooks/index.d.ts +15 -0
  66. package/dist/hooks/index.d.ts.map +1 -0
  67. package/dist/hooks/index.js +42 -0
  68. package/dist/hooks/index.js.map +1 -0
  69. package/dist/hooks/migration-validation.d.ts +9 -0
  70. package/dist/hooks/migration-validation.d.ts.map +1 -0
  71. package/dist/hooks/migration-validation.js +62 -0
  72. package/dist/hooks/migration-validation.js.map +1 -0
  73. package/dist/hooks/schema-consistency.d.ts +12 -0
  74. package/dist/hooks/schema-consistency.d.ts.map +1 -0
  75. package/dist/hooks/schema-consistency.js +72 -0
  76. package/dist/hooks/schema-consistency.js.map +1 -0
  77. package/dist/hooks/write-protection.d.ts +7 -0
  78. package/dist/hooks/write-protection.d.ts.map +1 -0
  79. package/dist/hooks/write-protection.js +33 -0
  80. package/dist/hooks/write-protection.js.map +1 -0
  81. package/dist/index.d.ts +3 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +37 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/progress/reporter.d.ts +15 -0
  86. package/dist/progress/reporter.d.ts.map +1 -0
  87. package/dist/progress/reporter.js +133 -0
  88. package/dist/progress/reporter.js.map +1 -0
  89. package/dist/scaffold/index.d.ts +23 -0
  90. package/dist/scaffold/index.d.ts.map +1 -0
  91. package/dist/scaffold/index.js +315 -0
  92. package/dist/scaffold/index.js.map +1 -0
  93. package/dist/tools/build.d.ts +3 -0
  94. package/dist/tools/build.d.ts.map +1 -0
  95. package/dist/tools/build.js +84 -0
  96. package/dist/tools/build.js.map +1 -0
  97. package/dist/tools/playbook.d.ts +14 -0
  98. package/dist/tools/playbook.d.ts.map +1 -0
  99. package/dist/tools/playbook.js +239 -0
  100. package/dist/tools/playbook.js.map +1 -0
  101. package/dist/tools/server.d.ts +3 -0
  102. package/dist/tools/server.d.ts.map +1 -0
  103. package/dist/tools/server.js +13 -0
  104. package/dist/tools/server.js.map +1 -0
  105. package/dist/working-memory/errors.d.ts +14 -0
  106. package/dist/working-memory/errors.d.ts.map +1 -0
  107. package/dist/working-memory/errors.js +89 -0
  108. package/dist/working-memory/errors.js.map +1 -0
  109. package/dist/working-memory/session.d.ts +12 -0
  110. package/dist/working-memory/session.d.ts.map +1 -0
  111. package/dist/working-memory/session.js +71 -0
  112. package/dist/working-memory/session.js.map +1 -0
  113. package/package.json +50 -0
  114. package/playbooks/electric-app-guardrails/SKILL.md +255 -0
  115. package/template/.env.example +2 -0
  116. package/template/Caddyfile +11 -0
  117. package/template/docker-compose.yml +47 -0
  118. package/template/drizzle.config.ts +12 -0
  119. package/template/postgres.conf +4 -0
  120. package/template/src/components/ClientOnly.tsx +27 -0
  121. package/template/src/db/index.ts +7 -0
  122. package/template/src/db/schema.ts +14 -0
  123. package/template/src/db/utils.ts +31 -0
  124. package/template/src/db/zod-schemas.ts +14 -0
  125. package/template/src/lib/electric-proxy.ts +59 -0
  126. package/template/tests/helpers/schema-test-utils.ts +106 -0
  127. package/template/vitest.config.ts +7 -0
@@ -0,0 +1,8 @@
1
+ import type { EngineEvent } from "@electric-agent/protocol";
2
+ /**
3
+ * Parse an SDK message into one or more EngineEvents.
4
+ * This mirrors the logic in processAgentMessage but produces structured events
5
+ * instead of console output.
6
+ */
7
+ export declare function sdkMessageToEvents(message: Record<string, unknown>, agent?: string): EngineEvent[];
8
+ //# sourceMappingURL=message-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-parser.d.ts","sourceRoot":"","sources":["../../src/engine/message-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAG3D;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,KAAK,CAAC,EAAE,MAAM,GACZ,WAAW,EAAE,CAqGf"}
@@ -0,0 +1,106 @@
1
+ import { ts } from "@electric-agent/protocol";
2
+ /**
3
+ * Parse an SDK message into one or more EngineEvents.
4
+ * This mirrors the logic in processAgentMessage but produces structured events
5
+ * instead of console output.
6
+ */
7
+ export function sdkMessageToEvents(message, agent) {
8
+ const events = [];
9
+ if (message.type === "assistant" && message.message?.content) {
10
+ const content = message.message.content;
11
+ for (const block of content) {
12
+ if ("text" in block && block.text) {
13
+ const text = block.text;
14
+ events.push({ type: "assistant_message", text, agent, ts: ts() });
15
+ }
16
+ else if ("thinking" in block && block.thinking) {
17
+ events.push({
18
+ type: "assistant_thinking",
19
+ text: block.thinking,
20
+ agent,
21
+ ts: ts(),
22
+ });
23
+ }
24
+ else if ("name" in block) {
25
+ const name = block.name;
26
+ const input = (block.input || {});
27
+ const tool_use_id = (block.id || `tool_${Date.now()}`);
28
+ events.push({
29
+ type: "pre_tool_use",
30
+ tool_name: name,
31
+ tool_use_id,
32
+ tool_input: input,
33
+ agent,
34
+ ts: ts(),
35
+ });
36
+ }
37
+ }
38
+ }
39
+ else if (message.type === "user" && message.message?.content) {
40
+ // SDK sends tool results as user messages with tool_result content blocks
41
+ const msgContent = message.message.content;
42
+ if (Array.isArray(msgContent)) {
43
+ for (const block of msgContent) {
44
+ if (typeof block === "object" &&
45
+ block &&
46
+ block.type === "tool_result") {
47
+ const b = block;
48
+ const tool_use_id = (b.tool_use_id || "");
49
+ const content = b.content;
50
+ const texts = [];
51
+ if (typeof content === "string") {
52
+ texts.push(content);
53
+ }
54
+ else if (Array.isArray(content)) {
55
+ for (const inner of content) {
56
+ if (typeof inner === "object" && inner && "text" in inner) {
57
+ texts.push(inner.text);
58
+ }
59
+ }
60
+ }
61
+ events.push({
62
+ type: "post_tool_use",
63
+ tool_use_id,
64
+ tool_response: texts.join("\n"),
65
+ agent,
66
+ ts: ts(),
67
+ });
68
+ }
69
+ }
70
+ }
71
+ }
72
+ else if (message.type === "result") {
73
+ const sub = String(message.subtype);
74
+ const cost = message.total_cost_usd;
75
+ const costStr = `(cost: $${cost?.toFixed(4) || "?"})`;
76
+ if (cost !== undefined) {
77
+ events.push({ type: "cost_update", totalCostUsd: cost, ts: ts() });
78
+ }
79
+ if (sub === "success") {
80
+ events.push({
81
+ type: "log",
82
+ level: "done",
83
+ message: `Agent completed ${costStr}`,
84
+ ts: ts(),
85
+ });
86
+ }
87
+ else if (sub.includes("max_turns")) {
88
+ events.push({
89
+ type: "log",
90
+ level: "task",
91
+ message: `Agent reached turn limit ${costStr}`,
92
+ ts: ts(),
93
+ });
94
+ }
95
+ else {
96
+ events.push({
97
+ type: "log",
98
+ level: "error",
99
+ message: `Agent stopped: ${sub} ${costStr}`,
100
+ ts: ts(),
101
+ });
102
+ }
103
+ }
104
+ return events;
105
+ }
106
+ //# sourceMappingURL=message-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-parser.js","sourceRoot":"","sources":["../../src/engine/message-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { EngineEvent } from "@electric-agent/protocol";
2
+ export interface OrchestratorCallbacks {
3
+ onEvent: (event: EngineEvent) => void | Promise<void>;
4
+ onClarificationNeeded: (questions: string[], summary: string) => Promise<string[]>;
5
+ onPlanReady: (plan: string) => Promise<"approve" | "revise" | "cancel">;
6
+ onRevisionRequested: () => Promise<string>;
7
+ onContinueNeeded: () => Promise<boolean>;
8
+ }
9
+ /**
10
+ * Check if the target directory already exists. If so, append a random 4-char hex suffix.
11
+ */
12
+ export declare function resolveProjectDir(baseDir: string, name: string): {
13
+ projectName: string;
14
+ projectDir: string;
15
+ };
16
+ /**
17
+ * Run the full "new project" flow.
18
+ */
19
+ export declare function runNew(opts: {
20
+ description: string;
21
+ projectName?: string;
22
+ baseDir?: string;
23
+ verbose?: boolean;
24
+ autoApprove?: boolean;
25
+ initGit?: boolean;
26
+ callbacks: OrchestratorCallbacks;
27
+ abortController?: AbortController;
28
+ /** If provided, create a GitHub repo and push the scaffold before planning */
29
+ gitRepoName?: string;
30
+ gitRepoVisibility?: "public" | "private";
31
+ }): Promise<{
32
+ sessionId?: string;
33
+ projectDir?: string;
34
+ }>;
35
+ /**
36
+ * Run a single iteration on an existing project.
37
+ */
38
+ export declare function runIterate(opts: {
39
+ projectDir: string;
40
+ userRequest: string;
41
+ verbose?: boolean;
42
+ callbacks: OrchestratorCallbacks;
43
+ abortController?: AbortController;
44
+ resumeSessionId?: string;
45
+ }): Promise<{
46
+ success: boolean;
47
+ errors: string[];
48
+ sessionId?: string;
49
+ }>;
50
+ //# sourceMappingURL=orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/engine/orchestrator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAW3D,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAGrD,qBAAqB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IAClF,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAA;IACvE,mBAAmB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1C,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CACxC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACV;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAQ7C;AAuCD;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,qBAAqB,CAAA;IAChC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;CACxC,GAAG,OAAO,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAyTvD;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,qBAAqB,CAAA;IAChC,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,eAAe,CAAC,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmFtE"}
@@ -0,0 +1,492 @@
1
+ import { execSync } from "node:child_process";
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { ts } from "@electric-agent/protocol";
6
+ import { evaluateDescription, inferProjectName } from "../agents/clarifier.js";
7
+ import { runCoder } from "../agents/coder.js";
8
+ import { runPlanner } from "../agents/planner.js";
9
+ import { createProgressReporter } from "../progress/reporter.js";
10
+ import { scaffold } from "../scaffold/index.js";
11
+ import { validatePlaybooks } from "../tools/playbook.js";
12
+ import { updateSession } from "../working-memory/session.js";
13
+ import { sdkMessageToEvents } from "./message-parser.js";
14
+ /**
15
+ * Check if the target directory already exists. If so, append a random 4-char hex suffix.
16
+ */
17
+ export function resolveProjectDir(baseDir, name) {
18
+ const candidate = path.resolve(baseDir, name);
19
+ if (!fs.existsSync(candidate)) {
20
+ return { projectName: name, projectDir: candidate };
21
+ }
22
+ const suffix = crypto.randomBytes(2).toString("hex");
23
+ const uniqueName = `${name}-${suffix}`;
24
+ return { projectName: uniqueName, projectDir: path.resolve(baseDir, uniqueName) };
25
+ }
26
+ function buildEnhancedDescription(original, questions, answers) {
27
+ let enhanced = original;
28
+ if (questions.length > 0) {
29
+ enhanced += "\n\nAdditional details:";
30
+ for (let i = 0; i < questions.length; i++) {
31
+ if (answers[i]) {
32
+ enhanced += `\n- ${questions[i]} ${answers[i]}`;
33
+ }
34
+ }
35
+ }
36
+ // Extra free-text answer beyond the numbered questions
37
+ const extra = answers[questions.length];
38
+ if (extra) {
39
+ enhanced += `\n\nExtra context from the user:\n${extra}`;
40
+ }
41
+ return enhanced;
42
+ }
43
+ /**
44
+ * Create an onMessage callback that parses SDK messages into EngineEvents.
45
+ */
46
+ function createMessageForwarder(callbacks, agent) {
47
+ return (msg) => {
48
+ const events = sdkMessageToEvents(msg, agent);
49
+ for (const event of events) {
50
+ callbacks.onEvent(event);
51
+ }
52
+ };
53
+ }
54
+ /**
55
+ * Run the full "new project" flow.
56
+ */
57
+ export async function runNew(opts) {
58
+ const { callbacks } = opts;
59
+ const emit = (event) => callbacks.onEvent(event);
60
+ const reporter = createReporterFromCallbacks(callbacks, opts.verbose);
61
+ // Step 0: Evaluate confidence, clarify if needed, and infer project name
62
+ // Run evaluation and name inference in parallel to save time
63
+ let description = opts.description;
64
+ let inferredName = opts.projectName || "";
65
+ emit({ type: "log", level: "plan", message: "Analyzing your description...", ts: ts() });
66
+ try {
67
+ // Run evaluation and name inference concurrently — both use the original description
68
+ const [evaluation, earlyName] = await Promise.all([
69
+ evaluateDescription(opts.description),
70
+ inferredName || inferProjectName(opts.description),
71
+ ]);
72
+ if (!inferredName)
73
+ inferredName = earlyName;
74
+ if (evaluation.confidence < 70) {
75
+ emit({
76
+ type: "log",
77
+ level: "plan",
78
+ message: `Confidence: ${evaluation.confidence}% — need more details before planning`,
79
+ ts: ts(),
80
+ });
81
+ emit({
82
+ type: "clarification_needed",
83
+ questions: evaluation.questions,
84
+ confidence: evaluation.confidence,
85
+ summary: evaluation.summary,
86
+ ts: ts(),
87
+ });
88
+ const answers = await callbacks.onClarificationNeeded(evaluation.questions, evaluation.summary);
89
+ description = buildEnhancedDescription(opts.description, evaluation.questions, answers);
90
+ emit({
91
+ type: "log",
92
+ level: "plan",
93
+ message: "Description enriched with your answers",
94
+ ts: ts(),
95
+ });
96
+ }
97
+ else {
98
+ emit({
99
+ type: "log",
100
+ level: "plan",
101
+ message: `Confidence: ${evaluation.confidence}% — description is clear`,
102
+ ts: ts(),
103
+ });
104
+ }
105
+ }
106
+ catch {
107
+ emit({ type: "log", level: "plan", message: "Skipping clarification step", ts: ts() });
108
+ if (!inferredName)
109
+ inferredName = await inferProjectName(description);
110
+ }
111
+ const baseDir = opts.baseDir || process.cwd();
112
+ // When the caller provides an explicit projectName (e.g. server/sandbox),
113
+ // use it as-is — no dedup suffix. A sprite has exactly one project, and
114
+ // the server-sent name is used for the GitHub repo. Only add entropy
115
+ // when the name was inferred and might collide on a developer's machine.
116
+ const { projectName, projectDir } = opts.projectName
117
+ ? { projectName: inferredName, projectDir: path.resolve(baseDir, inferredName) }
118
+ : resolveProjectDir(baseDir, inferredName);
119
+ emit({ type: "log", level: "plan", message: `Creating project: ${projectName}`, ts: ts() });
120
+ emit({ type: "log", level: "plan", message: `Description: ${description}`, ts: ts() });
121
+ // Step 1: Scaffold
122
+ emit({
123
+ type: "log",
124
+ level: "task",
125
+ message: "Scaffolding project from KPB template...",
126
+ ts: ts(),
127
+ });
128
+ const skipGit = opts.initGit === false;
129
+ const scaffoldResult = await scaffold(projectDir, { projectName, reporter, skipGit });
130
+ if (scaffoldResult.errors.length > 0) {
131
+ for (const err of scaffoldResult.errors) {
132
+ emit({ type: "log", level: "error", message: err, ts: ts() });
133
+ }
134
+ }
135
+ if (scaffoldResult.skippedInstall) {
136
+ emit({
137
+ type: "log",
138
+ level: "error",
139
+ message: "Dependency install failed. You may need to run 'pnpm install' manually.",
140
+ ts: ts(),
141
+ });
142
+ }
143
+ // Validate critical project structure before proceeding to planner/coder
144
+ const pkgExists = fs.existsSync(path.join(projectDir, "package.json"));
145
+ if (!pkgExists) {
146
+ emit({
147
+ type: "log",
148
+ level: "error",
149
+ message: "Critical: package.json missing after scaffold — cannot proceed",
150
+ ts: ts(),
151
+ });
152
+ emit({
153
+ type: "phase_complete",
154
+ phase: "scaffold",
155
+ success: false,
156
+ errors: ["package.json missing after scaffold"],
157
+ ts: ts(),
158
+ });
159
+ return { projectDir };
160
+ }
161
+ emit({ type: "log", level: "done", message: "Scaffold complete", ts: ts() });
162
+ // Step 1b: Validate playbooks
163
+ try {
164
+ validatePlaybooks(projectDir);
165
+ }
166
+ catch (e) {
167
+ emit({
168
+ type: "log",
169
+ level: "error",
170
+ message: e instanceof Error ? e.message : "Playbook validation failed",
171
+ ts: ts(),
172
+ });
173
+ emit({
174
+ type: "phase_complete",
175
+ phase: "scaffold",
176
+ success: false,
177
+ errors: ["Playbook validation failed"],
178
+ ts: ts(),
179
+ });
180
+ return { projectDir };
181
+ }
182
+ // Step 1c: Create GitHub repo and push scaffold (if repo config provided)
183
+ if (opts.gitRepoName) {
184
+ emit({
185
+ type: "log",
186
+ level: "task",
187
+ message: "Creating GitHub repo and pushing scaffold...",
188
+ ts: ts(),
189
+ });
190
+ gitAutoCommit(projectDir, "chore: initial scaffold", emit);
191
+ try {
192
+ const vis = opts.gitRepoVisibility || "private";
193
+ execSync(`gh repo create "${opts.gitRepoName}" --${vis} --source . --remote origin --push`, {
194
+ cwd: projectDir,
195
+ stdio: "pipe",
196
+ timeout: 60_000,
197
+ env: { ...process.env },
198
+ });
199
+ emit({
200
+ type: "log",
201
+ level: "done",
202
+ message: `GitHub repo created: ${opts.gitRepoName} (${vis})`,
203
+ ts: ts(),
204
+ });
205
+ }
206
+ catch (err) {
207
+ const msg = err instanceof Error ? err.message : "unknown error";
208
+ emit({ type: "log", level: "error", message: `Repo creation failed: ${msg}`, ts: ts() });
209
+ emit({ type: "session_end", success: false, ts: ts() });
210
+ return { projectDir };
211
+ }
212
+ }
213
+ // Step 2: Plan
214
+ emit({ type: "log", level: "plan", message: "Running planner agent...", ts: ts() });
215
+ const plannerForwarder = createMessageForwarder(callbacks, "planner");
216
+ let plan = await runPlanner(description, projectDir, reporter, plannerForwarder, opts.abortController);
217
+ // Step 3: Approve
218
+ if (!opts.autoApprove) {
219
+ emit({ type: "plan_ready", plan, ts: ts() });
220
+ let decision = await callbacks.onPlanReady(plan);
221
+ while (decision === "revise") {
222
+ const feedback = await callbacks.onRevisionRequested();
223
+ emit({
224
+ type: "log",
225
+ level: "plan",
226
+ message: "Re-running planner with feedback...",
227
+ ts: ts(),
228
+ });
229
+ plan = await runPlanner(`${description}\n\nRevision feedback: ${feedback}`, projectDir, reporter, plannerForwarder, opts.abortController);
230
+ emit({ type: "plan_ready", plan, ts: ts() });
231
+ decision = await callbacks.onPlanReady(plan);
232
+ }
233
+ if (decision === "cancel") {
234
+ emit({ type: "log", level: "error", message: "Cancelled by user", ts: ts() });
235
+ emit({
236
+ type: "session_end",
237
+ success: false,
238
+ ts: ts(),
239
+ });
240
+ return { projectDir };
241
+ }
242
+ }
243
+ // Step 4: Write plan and initialize session (parallel — independent operations)
244
+ const fsPromises = await import("node:fs/promises");
245
+ await Promise.all([
246
+ fsPromises.writeFile(path.join(projectDir, "PLAN.md"), plan, "utf-8"),
247
+ updateSession(projectDir, {
248
+ appName: projectName,
249
+ currentPhase: "generation",
250
+ currentTask: "Starting code generation",
251
+ buildStatus: "pending",
252
+ totalBuilds: 0,
253
+ totalErrors: 0,
254
+ escalations: 0,
255
+ }),
256
+ ]);
257
+ // Step 5: Run coder (with continuation on max turns / max budget)
258
+ emit({ type: "log", level: "task", message: "Running coder agent...", ts: ts() });
259
+ const coderForwarder = createMessageForwarder(callbacks, "coder");
260
+ let result = await runCoder(projectDir, undefined, reporter, coderForwarder, undefined, opts.abortController);
261
+ while (result.stopReason === "max_turns" || result.stopReason === "max_budget") {
262
+ emit({
263
+ type: "continue_needed",
264
+ reason: result.stopReason,
265
+ ts: ts(),
266
+ });
267
+ const shouldContinue = await callbacks.onContinueNeeded();
268
+ if (!shouldContinue) {
269
+ emit({
270
+ type: "log",
271
+ level: "done",
272
+ message: "Stopped by user. Run 'electric-agent iterate' to continue later.",
273
+ ts: ts(),
274
+ });
275
+ emit({ type: "session_end", success: true, ts: ts() });
276
+ return { sessionId: result.sessionId, projectDir };
277
+ }
278
+ emit({ type: "log", level: "task", message: "Continuing coder agent...", ts: ts() });
279
+ result = await runCoder(projectDir, "Continue where you left off. Keep working on the remaining unchecked tasks in PLAN.md.", reporter, coderForwarder, result.sessionId, opts.abortController);
280
+ }
281
+ if (result.success) {
282
+ // Auto-commit after successful generation
283
+ emit({ type: "log", level: "task", message: "Creating git commit...", ts: ts() });
284
+ const commitResult = gitAutoCommit(projectDir, "feat: initial app generation", emit);
285
+ if (commitResult) {
286
+ emit({
287
+ type: "git_checkpoint",
288
+ commitHash: commitResult,
289
+ message: "feat: initial app generation",
290
+ ts: ts(),
291
+ });
292
+ // Push to remote if one exists (e.g. repo was created during scaffold)
293
+ gitAutoPush(projectDir, emit);
294
+ }
295
+ emit({
296
+ type: "log",
297
+ level: "done",
298
+ message: `Project ${projectName} created successfully!`,
299
+ ts: ts(),
300
+ });
301
+ emit({ type: "log", level: "done", message: ` cd ${projectName}`, ts: ts() });
302
+ emit({ type: "log", level: "done", message: " electric-agent up", ts: ts() });
303
+ }
304
+ else {
305
+ emit({
306
+ type: "log",
307
+ level: "error",
308
+ message: `Generation completed with errors: ${result.errors.join(", ")}`,
309
+ ts: ts(),
310
+ });
311
+ emit({
312
+ type: "log",
313
+ level: "error",
314
+ message: "Run 'electric-agent iterate' to continue fixing issues",
315
+ ts: ts(),
316
+ });
317
+ }
318
+ emit({
319
+ type: "phase_complete",
320
+ phase: "generation",
321
+ success: result.success,
322
+ errors: result.errors,
323
+ ts: ts(),
324
+ });
325
+ emit({ type: "session_end", success: result.success, ts: ts() });
326
+ return { sessionId: result.sessionId, projectDir };
327
+ }
328
+ /**
329
+ * Run a single iteration on an existing project.
330
+ */
331
+ export async function runIterate(opts) {
332
+ const { callbacks, projectDir, userRequest } = opts;
333
+ const emit = (event) => callbacks.onEvent(event);
334
+ const reporter = createReporterFromCallbacks(callbacks, opts.verbose);
335
+ const messageForwarder = createMessageForwarder(callbacks, "coder");
336
+ const iterationPrompt = `The user wants the following change to the existing app:
337
+
338
+ ${userRequest}
339
+
340
+ Instructions:
341
+ 1. Consult ARCHITECTURE.md (injected into your context as <app-architecture>) to understand the app structure — do NOT scan the filesystem
342
+ 2. Read PLAN.md to see what was built and previous iterations
343
+ 3. Read "electric-app-guardrails" playbook FIRST for critical integration rules
344
+ 4. Use list_playbooks to discover relevant skills, then read only what you need for this change
345
+ 5. Add a new "## Iteration: ${userRequest.slice(0, 60)}" section to the bottom of PLAN.md with tasks for this change
346
+ 6. Read ONLY the specific source files you need to modify (consult ARCHITECTURE.md for exact paths)
347
+ 7. Implement the changes immediately — write the actual code, following the Drizzle Workflow order
348
+ 8. If schema changes are needed, run drizzle-kit generate && drizzle-kit migrate
349
+ 9. Mark tasks as done in PLAN.md after completing them
350
+ 10. Update ARCHITECTURE.md to reflect any changes (new entities, routes, components, styles, or contexts)
351
+ 11. Run the build tool ONCE after all changes are complete — not after each file
352
+
353
+ Do NOT just write a plan — implement the changes directly.`;
354
+ emit({ type: "log", level: "task", message: "Running coder with your request...", ts: ts() });
355
+ let result = await runCoder(projectDir, iterationPrompt, reporter, messageForwarder, opts.resumeSessionId, opts.abortController);
356
+ while (result.stopReason === "max_turns" || result.stopReason === "max_budget") {
357
+ emit({ type: "continue_needed", reason: result.stopReason, ts: ts() });
358
+ const shouldContinue = await callbacks.onContinueNeeded();
359
+ if (!shouldContinue) {
360
+ emit({
361
+ type: "log",
362
+ level: "done",
363
+ message: "Paused. You can continue this work in the next iteration.",
364
+ ts: ts(),
365
+ });
366
+ return { success: true, errors: [], sessionId: result.sessionId };
367
+ }
368
+ emit({ type: "log", level: "task", message: "Continuing coder agent...", ts: ts() });
369
+ result = await runCoder(projectDir, "Continue where you left off. Keep implementing the remaining changes.", reporter, messageForwarder, result.sessionId, opts.abortController);
370
+ }
371
+ if (result.success) {
372
+ // Auto-commit after successful iteration
373
+ emit({ type: "log", level: "task", message: "Creating git commit...", ts: ts() });
374
+ const commitMsg = `feat: ${userRequest.slice(0, 70)}`;
375
+ const commitResult = gitAutoCommit(projectDir, commitMsg, emit);
376
+ if (commitResult) {
377
+ emit({
378
+ type: "git_checkpoint",
379
+ commitHash: commitResult,
380
+ message: commitMsg,
381
+ ts: ts(),
382
+ });
383
+ }
384
+ emit({ type: "log", level: "done", message: "Changes applied successfully", ts: ts() });
385
+ }
386
+ else {
387
+ emit({
388
+ type: "log",
389
+ level: "error",
390
+ message: `Issues: ${result.errors.join(", ")}`,
391
+ ts: ts(),
392
+ });
393
+ }
394
+ return { success: result.success, errors: result.errors, sessionId: result.sessionId };
395
+ }
396
+ /**
397
+ * Stage all changes and commit. Returns the commit hash, or null if there were no changes.
398
+ */
399
+ function gitAutoCommit(projectDir, message, emit) {
400
+ // Check for .git directory before attempting git operations
401
+ if (!fs.existsSync(path.join(projectDir, ".git"))) {
402
+ emit({
403
+ type: "log",
404
+ level: "error",
405
+ message: "Skipping git commit — no .git directory found (git init may have failed during scaffold)",
406
+ ts: ts(),
407
+ });
408
+ return null;
409
+ }
410
+ try {
411
+ execSync("git add -A", { cwd: projectDir, stdio: "pipe" });
412
+ try {
413
+ execSync("git diff --cached --quiet", { cwd: projectDir, stdio: "pipe" });
414
+ emit({ type: "log", level: "done", message: "No changes to commit", ts: ts() });
415
+ return null;
416
+ }
417
+ catch {
418
+ // There are staged changes — proceed
419
+ }
420
+ const safeMsg = message.replace(/"/g, '\\"');
421
+ execSync(`git commit -m "${safeMsg}"`, { cwd: projectDir, stdio: "pipe" });
422
+ const hash = execSync("git rev-parse HEAD", {
423
+ cwd: projectDir,
424
+ encoding: "utf-8",
425
+ stdio: "pipe",
426
+ }).trim();
427
+ emit({ type: "log", level: "done", message: `Committed: ${message}`, ts: ts() });
428
+ return hash;
429
+ }
430
+ catch (err) {
431
+ const msg = err instanceof Error ? err.message : "unknown error";
432
+ emit({ type: "log", level: "error", message: `Git commit failed: ${msg}`, ts: ts() });
433
+ return null;
434
+ }
435
+ }
436
+ /**
437
+ * Push to remote origin if configured. Silently skips if no remote exists.
438
+ */
439
+ function gitAutoPush(projectDir, emit) {
440
+ try {
441
+ // Check if a remote origin exists
442
+ const remote = execSync("git remote get-url origin", {
443
+ cwd: projectDir,
444
+ encoding: "utf-8",
445
+ stdio: "pipe",
446
+ }).trim();
447
+ if (!remote) {
448
+ emit({
449
+ type: "log",
450
+ level: "verbose",
451
+ message: "No remote configured — skipping push",
452
+ ts: ts(),
453
+ });
454
+ return;
455
+ }
456
+ const branch = execSync("git branch --show-current", {
457
+ cwd: projectDir,
458
+ encoding: "utf-8",
459
+ stdio: "pipe",
460
+ }).trim();
461
+ emit({ type: "log", level: "task", message: `Pushing to origin/${branch}...`, ts: ts() });
462
+ const pushOutput = execSync(`git push -u origin ${branch} 2>&1`, {
463
+ cwd: projectDir,
464
+ encoding: "utf-8",
465
+ timeout: 60_000,
466
+ env: { ...process.env },
467
+ }).trim();
468
+ if (pushOutput) {
469
+ emit({ type: "log", level: "verbose", message: pushOutput, ts: ts() });
470
+ }
471
+ emit({ type: "log", level: "done", message: `Pushed to origin/${branch}`, ts: ts() });
472
+ }
473
+ catch (err) {
474
+ const detail = err?.stderr ||
475
+ err?.stdout ||
476
+ (err instanceof Error ? err.message : "Push failed");
477
+ emit({
478
+ type: "log",
479
+ level: "error",
480
+ message: `Git push failed: ${detail}`,
481
+ ts: ts(),
482
+ });
483
+ }
484
+ }
485
+ /**
486
+ * Create a ProgressReporter that works for both CLI and web by forwarding to the engine callback.
487
+ * The reporter is used by scaffold and other components that need it directly.
488
+ */
489
+ function createReporterFromCallbacks(_callbacks, verbose) {
490
+ return createProgressReporter({ verbose });
491
+ }
492
+ //# sourceMappingURL=orchestrator.js.map