@keel_flow/build-mode 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jglasskatz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ import type { Build } from "@keel_flow/schema";
2
+ export interface RecordApprovalOptions {
3
+ approvedBy?: string;
4
+ }
5
+ export declare function readSummary(slug: string, cwd: string): Promise<string>;
6
+ export declare function recordApproval(slug: string, opts: RecordApprovalOptions, cwd: string): Promise<Build>;
7
+ export declare function recordRejection(slug: string, reason: string, cwd: string): Promise<Build>;
8
+ export interface SummaryHashCheckResult {
9
+ matches: boolean;
10
+ expected: string;
11
+ actual: string;
12
+ }
13
+ export declare function checkSummaryHash(slug: string, cwd: string): Promise<SummaryHashCheckResult>;
14
+ //# sourceMappingURL=approve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.d.ts","sourceRoot":"","sources":["../src/approve.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI/C,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM5E;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,qBAAqB,EAC3B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,KAAK,CAAC,CAsBhB;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,KAAK,CAAC,CAahB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,sBAAsB,CAAC,CAMjC"}
@@ -0,0 +1,51 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { getBuildPaths, loadBuild, saveBuild, transitionTo } from "./state.js";
5
+ import { appendConversation } from "./draft.js";
6
+ function sha256(s) {
7
+ return createHash("sha256").update(s, "utf-8").digest("hex");
8
+ }
9
+ export async function readSummary(slug, cwd) {
10
+ const paths = getBuildPaths(slug, cwd);
11
+ if (!existsSync(paths.summary)) {
12
+ throw new Error(`No summary.md found for build ${slug}; run distillation first`);
13
+ }
14
+ return fs.readFile(paths.summary, "utf-8");
15
+ }
16
+ export async function recordApproval(slug, opts, cwd) {
17
+ const build = await loadBuild(slug, cwd);
18
+ if (build.state !== "awaiting-approval") {
19
+ throw new Error(`Cannot approve build in state ${build.state} (slug=${slug}); summary must be awaiting-approval`);
20
+ }
21
+ const summary = await readSummary(slug, cwd);
22
+ const summaryHash = sha256(summary);
23
+ const approved = transitionTo(build, "approved");
24
+ approved.approvedAt = new Date().toISOString();
25
+ approved.approvedSummaryHash = summaryHash;
26
+ if (opts.approvedBy) {
27
+ await appendConversation(slug, "system", `Approved by ${opts.approvedBy} at ${approved.approvedAt}`, cwd);
28
+ }
29
+ await saveBuild(approved, cwd);
30
+ return approved;
31
+ }
32
+ export async function recordRejection(slug, reason, cwd) {
33
+ const build = await loadBuild(slug, cwd);
34
+ if (build.state !== "awaiting-approval") {
35
+ throw new Error(`Cannot reject build in state ${build.state} (slug=${slug}); only awaiting-approval is rejectable`);
36
+ }
37
+ await appendConversation(slug, "rejection", reason, cwd);
38
+ const rejected = transitionTo(build, "rejected");
39
+ await saveBuild(rejected, cwd);
40
+ const backToDrafting = transitionTo(rejected, "drafting");
41
+ await saveBuild(backToDrafting, cwd);
42
+ return backToDrafting;
43
+ }
44
+ export async function checkSummaryHash(slug, cwd) {
45
+ const build = await loadBuild(slug, cwd);
46
+ const expected = build.approvedSummaryHash ?? "";
47
+ const summary = await readSummary(slug, cwd);
48
+ const actual = sha256(summary);
49
+ return { matches: expected === actual, expected, actual };
50
+ }
51
+ //# sourceMappingURL=approve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approve.js","sourceRoot":"","sources":["../src/approve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { ModelProvider } from "@keel_flow/runtime";
2
+ import type { TelemetryEmitter } from "@keel_flow/telemetry";
3
+ export declare class SummaryDriftError extends Error {
4
+ readonly expected: string;
5
+ readonly actual: string;
6
+ constructor(expected: string, actual: string);
7
+ }
8
+ export interface CodifyOptions {
9
+ slug: string;
10
+ cwd: string;
11
+ provider: ModelProvider;
12
+ model?: string;
13
+ telemetry?: TelemetryEmitter;
14
+ parentSessionId?: string;
15
+ }
16
+ export interface CodifyResult {
17
+ specPath: string;
18
+ spec: string;
19
+ }
20
+ export declare function codifySpec(opts: CodifyOptions): Promise<CodifyResult>;
21
+ //# sourceMappingURL=codify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codify.d.ts","sourceRoot":"","sources":["../src/codify.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAS7D,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAS7C;AA8BD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAqBD,wBAAsB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAuF3E"}
package/dist/codify.js ADDED
@@ -0,0 +1,132 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { getBuildPaths, loadBuild, saveBuild, transitionTo, } from "./state.js";
5
+ import { checkSummaryHash, readSummary } from "./approve.js";
6
+ export class SummaryDriftError extends Error {
7
+ expected;
8
+ actual;
9
+ constructor(expected, actual) {
10
+ super(`summary.md has been edited since approval (expected hash ${expected}, got ${actual}). ` +
11
+ "Re-run `keel build approve <slug>` to re-record the approval, or revert summary.md.");
12
+ this.name = "SummaryDriftError";
13
+ this.expected = expected;
14
+ this.actual = actual;
15
+ }
16
+ }
17
+ const CODIFY_SYSTEM_PROMPT = `You are the codification assistant for Keel's build mode.
18
+ Given an approved summary, the architecture map, and the active principles, you emit a build-ready spec.md that sub-agents can implement against.
19
+
20
+ Output a single markdown document with these sections, in this order:
21
+
22
+ # Spec: <topic>
23
+
24
+ ## Acceptance Criteria
25
+ - one bullet per testable behavior
26
+
27
+ ## Affected Files
28
+ - file path — what changes
29
+
30
+ ## Schema Additions
31
+ - (none) or a per-schema bullet list
32
+
33
+ ## Principles To Uphold
34
+ - principle-id — why it applies
35
+
36
+ ## Test Cases
37
+ - name — assertion
38
+
39
+ Hard constraints:
40
+ - Be specific: file paths, identifiers, exact assertions
41
+ - Do not invent decisions absent from the summary
42
+ - Reference principle ids exactly as listed in the principles input
43
+ - No preamble, no closing remarks — just the document`;
44
+ async function atomicWrite(path, content) {
45
+ await fs.mkdir(dirname(path), { recursive: true });
46
+ const tmpFile = `${path}.tmp-${process.pid}-${Date.now()}`;
47
+ await fs.writeFile(tmpFile, content, "utf-8");
48
+ await fs.rename(tmpFile, path);
49
+ }
50
+ async function readArchitectureBrief(cwd) {
51
+ const candidate = join(resolve(cwd), "architecture", "data.ts");
52
+ if (!existsSync(candidate))
53
+ return "(no architecture/data.ts found)";
54
+ return fs.readFile(candidate, "utf-8");
55
+ }
56
+ async function readPrinciplesBrief(cwd) {
57
+ const candidate = join(resolve(cwd), "principles", "index.ts");
58
+ if (!existsSync(candidate))
59
+ return "(no principles/index.ts found)";
60
+ return fs.readFile(candidate, "utf-8");
61
+ }
62
+ export async function codifySpec(opts) {
63
+ const { slug, cwd, provider, model, telemetry, parentSessionId } = opts;
64
+ const paths = getBuildPaths(slug, cwd);
65
+ const build = await loadBuild(slug, cwd);
66
+ if (build.state !== "approved") {
67
+ throw new Error(`codifySpec requires state=approved (slug=${slug}, current=${build.state})`);
68
+ }
69
+ const drift = await checkSummaryHash(slug, cwd);
70
+ if (!drift.matches) {
71
+ throw new SummaryDriftError(drift.expected, drift.actual);
72
+ }
73
+ const codifying = transitionTo(build, "codifying");
74
+ await saveBuild(codifying, cwd);
75
+ const summary = await readSummary(slug, cwd);
76
+ const architectureBrief = await readArchitectureBrief(cwd);
77
+ const principlesBrief = await readPrinciplesBrief(cwd);
78
+ const userPrompt = `Topic: ${build.topic}\n\n` +
79
+ `## Approved summary\n\n${summary}\n\n` +
80
+ `## Architecture map (architecture/data.ts excerpt)\n\n\`\`\`ts\n${architectureBrief.slice(0, 6000)}\n\`\`\`\n\n` +
81
+ `## Active principles (principles/index.ts excerpt)\n\n\`\`\`ts\n${principlesBrief.slice(0, 3000)}\n\`\`\`\n\n` +
82
+ `Produce the spec now.`;
83
+ const startedAt = Date.now();
84
+ let res;
85
+ try {
86
+ res = await provider.generate({
87
+ system: CODIFY_SYSTEM_PROMPT,
88
+ messages: [
89
+ {
90
+ role: "user",
91
+ content: [{ type: "text", text: userPrompt }],
92
+ },
93
+ ],
94
+ maxTokens: 2048,
95
+ model: model ?? provider.defaultModel,
96
+ });
97
+ }
98
+ catch (err) {
99
+ const recovered = transitionTo(codifying, "approved");
100
+ await saveBuild(recovered, cwd);
101
+ throw err;
102
+ }
103
+ const spec = res.content
104
+ .map((b) => (b.type === "text" ? b.text : ""))
105
+ .join("")
106
+ .trim();
107
+ if (spec.length === 0) {
108
+ throw new Error(`Codification produced no text (slug=${slug})`);
109
+ }
110
+ await atomicWrite(paths.spec, spec + "\n");
111
+ const executing = transitionTo({ ...codifying, specPath: build.specPath ?? paths.spec }, "executing");
112
+ executing.specPath = paths.spec;
113
+ await saveBuild(executing, cwd);
114
+ telemetry?.emit({
115
+ sessionId: parentSessionId ?? "",
116
+ parentEventId: null,
117
+ workspaceId: null,
118
+ triggeredBy: { kind: "user" },
119
+ kind: "build.spec-codified",
120
+ payload: {
121
+ slug,
122
+ specPath: paths.spec,
123
+ specChars: spec.length,
124
+ },
125
+ latencyMs: Date.now() - startedAt,
126
+ inputTokens: res.usage.inputTokens,
127
+ outputTokens: res.usage.outputTokens,
128
+ model: model ?? provider.defaultModel,
129
+ });
130
+ return { specPath: paths.spec, spec };
131
+ }
132
+ //# sourceMappingURL=codify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codify.js","sourceRoot":"","sources":["../src/codify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ModelProvider } from "@keel_flow/runtime";
2
+ import type { TelemetryEmitter } from "@keel_flow/telemetry";
3
+ export interface DistillOptions {
4
+ slug: string;
5
+ cwd: string;
6
+ provider: ModelProvider;
7
+ model?: string;
8
+ telemetry?: TelemetryEmitter;
9
+ parentSessionId?: string;
10
+ }
11
+ export interface DistillResult {
12
+ summary: string;
13
+ summaryHash: string;
14
+ }
15
+ export declare function distillSummary(opts: DistillOptions): Promise<DistillResult>;
16
+ //# sourceMappingURL=distill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distill.d.ts","sourceRoot":"","sources":["../src/distill.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AA2B7D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAaD,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA0EjF"}
@@ -0,0 +1,94 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { dirname } from "node:path";
4
+ import { getBuildPaths, loadBuild, saveBuild, transitionTo } from "./state.js";
5
+ import { readConversation } from "./draft.js";
6
+ const DISTILL_SYSTEM_PROMPT = `You are the distillation assistant for Keel's build mode.
7
+ You compress a planning conversation into a concise summary that the user will approve before any code is written.
8
+
9
+ Output a single markdown document with these sections, in this order:
10
+
11
+ ## Decisions
12
+ - one bullet per decision the conversation has converged on
13
+
14
+ ## Scope
15
+ **In scope**
16
+ - bullets
17
+ **Out of scope**
18
+ - bullets
19
+
20
+ ## Open Questions
21
+ - bullets
22
+
23
+ Hard constraints:
24
+ - ≤500 tokens total
25
+ - Do not invent decisions not present in the conversation
26
+ - If a section is empty, write "(none yet)" — do not omit the header
27
+ - No preamble, no closing remarks, no apologies — just the document`;
28
+ function sha256(s) {
29
+ return createHash("sha256").update(s, "utf-8").digest("hex");
30
+ }
31
+ async function atomicWrite(path, content) {
32
+ await fs.mkdir(dirname(path), { recursive: true });
33
+ const tmpFile = `${path}.tmp-${process.pid}-${Date.now()}`;
34
+ await fs.writeFile(tmpFile, content, "utf-8");
35
+ await fs.rename(tmpFile, path);
36
+ }
37
+ export async function distillSummary(opts) {
38
+ const { slug, cwd, provider, model, telemetry, parentSessionId } = opts;
39
+ const paths = getBuildPaths(slug, cwd);
40
+ const build = await loadBuild(slug, cwd);
41
+ if (build.state !== "drafting" && build.state !== "summarizing") {
42
+ throw new Error(`distillSummary requires state=drafting or state=summarizing (slug=${slug}, current=${build.state})`);
43
+ }
44
+ if (build.state === "drafting") {
45
+ const transitioning = transitionTo(build, "summarizing");
46
+ await saveBuild(transitioning, cwd);
47
+ }
48
+ const conversation = await readConversation(slug, cwd);
49
+ const userPrompt = `Topic: ${build.topic}\n\n` +
50
+ `Conversation transcript (markdown blocks per turn follow):\n\n${conversation}\n\n` +
51
+ `Produce the summary now.`;
52
+ const startedAt = Date.now();
53
+ const res = await provider.generate({
54
+ system: DISTILL_SYSTEM_PROMPT,
55
+ messages: [
56
+ {
57
+ role: "user",
58
+ content: [{ type: "text", text: userPrompt }],
59
+ },
60
+ ],
61
+ maxTokens: 1024,
62
+ model: model ?? provider.defaultModel,
63
+ });
64
+ const text = res.content
65
+ .map((b) => (b.type === "text" ? b.text : ""))
66
+ .join("")
67
+ .trim();
68
+ if (text.length === 0) {
69
+ throw new Error(`Distillation produced no text (slug=${slug})`);
70
+ }
71
+ await atomicWrite(paths.summary, text + "\n");
72
+ const summaryHash = sha256(text + "\n");
73
+ const summarized = transitionTo({ ...build, state: "summarizing", updatedAt: new Date().toISOString() }, "awaiting-approval");
74
+ await saveBuild(summarized, cwd);
75
+ telemetry?.emit({
76
+ sessionId: parentSessionId ?? "",
77
+ parentEventId: null,
78
+ workspaceId: null,
79
+ triggeredBy: { kind: "user" },
80
+ kind: "build.summary",
81
+ payload: {
82
+ slug,
83
+ summaryHash,
84
+ conversationChars: conversation.length,
85
+ summaryChars: text.length,
86
+ },
87
+ latencyMs: Date.now() - startedAt,
88
+ inputTokens: res.usage.inputTokens,
89
+ outputTokens: res.usage.outputTokens,
90
+ model: model ?? provider.defaultModel,
91
+ });
92
+ return { summary: text, summaryHash };
93
+ }
94
+ //# sourceMappingURL=distill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distill.js","sourceRoot":"","sources":["../src/distill.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export type ConversationRole = "user" | "assistant" | "system" | "rejection";
2
+ export declare function appendConversation(slug: string, role: ConversationRole, content: string, cwd: string): Promise<void>;
3
+ export declare function readConversation(slug: string, cwd: string): Promise<string>;
4
+ export declare function countTurns(conversation: string): number;
5
+ //# sourceMappingURL=draft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE7E,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIjF;AAED,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEvD"}
package/dist/draft.js ADDED
@@ -0,0 +1,21 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { getBuildPaths } from "./state.js";
5
+ export async function appendConversation(slug, role, content, cwd) {
6
+ const paths = getBuildPaths(slug, cwd);
7
+ await fs.mkdir(dirname(paths.conversation), { recursive: true });
8
+ const timestamp = new Date().toISOString();
9
+ const block = `\n## ${timestamp} — ${role}\n\n${content.trim()}\n`;
10
+ await fs.appendFile(paths.conversation, block, "utf-8");
11
+ }
12
+ export async function readConversation(slug, cwd) {
13
+ const paths = getBuildPaths(slug, cwd);
14
+ if (!existsSync(paths.conversation))
15
+ return "";
16
+ return fs.readFile(paths.conversation, "utf-8");
17
+ }
18
+ export function countTurns(conversation) {
19
+ return (conversation.match(/^## .* — (user|assistant)$/gm) ?? []).length;
20
+ }
21
+ //# sourceMappingURL=draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI3C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY,EACZ,IAAsB,EACtB,OAAe,EACf,GAAW;IAEX,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,KAAK,GAAG,QAAQ,SAAS,MAAM,IAAI,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;IACnE,MAAM,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,GAAW;IAC9D,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC;QAAE,OAAO,EAAE,CAAC;IAC/C,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,YAAoB;IAC7C,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,8BAA8B,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { TelemetryEmitter } from "@keel_flow/telemetry";
2
+ export interface ExecutorContext {
3
+ slug: string;
4
+ cwd: string;
5
+ spec: string;
6
+ specPath: string;
7
+ topic: string;
8
+ dryRun: boolean;
9
+ }
10
+ export interface ExecutorOutcome {
11
+ finalMessage: string;
12
+ toolCalls: number;
13
+ iterations: number;
14
+ verifyExitCode: number;
15
+ commits?: string[];
16
+ }
17
+ export type ExecutorFn = (ctx: ExecutorContext) => Promise<ExecutorOutcome>;
18
+ export interface ExecuteBuildOptions {
19
+ slug: string;
20
+ cwd: string;
21
+ executeFn: ExecutorFn;
22
+ telemetry?: TelemetryEmitter;
23
+ parentSessionId?: string;
24
+ dryRun?: boolean;
25
+ }
26
+ export interface ExecuteBuildResult {
27
+ resultPath: string;
28
+ verifyExitCode: number;
29
+ }
30
+ export declare function executeBuild(opts: ExecuteBuildOptions): Promise<ExecuteBuildResult>;
31
+ //# sourceMappingURL=execute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAS7D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAiEzF"}
@@ -0,0 +1,55 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { getBuildPaths, loadBuild, saveBuild, transitionTo, } from "./state.js";
3
+ import { writeResult } from "./result.js";
4
+ export async function executeBuild(opts) {
5
+ const { slug, cwd, executeFn, telemetry, parentSessionId, dryRun } = opts;
6
+ const paths = getBuildPaths(slug, cwd);
7
+ const build = await loadBuild(slug, cwd);
8
+ if (build.state !== "executing") {
9
+ throw new Error(`executeBuild requires state=executing (slug=${slug}, current=${build.state}). Run codify first.`);
10
+ }
11
+ const spec = await fs.readFile(paths.spec, "utf-8");
12
+ const startedAt = Date.now();
13
+ telemetry?.emit({
14
+ sessionId: parentSessionId ?? "",
15
+ parentEventId: null,
16
+ workspaceId: null,
17
+ triggeredBy: { kind: "user" },
18
+ kind: "build.executing",
19
+ payload: { slug, specPath: paths.spec, dryRun: dryRun ?? false },
20
+ });
21
+ const outcome = await executeFn({
22
+ slug,
23
+ cwd,
24
+ spec,
25
+ specPath: paths.spec,
26
+ topic: build.topic,
27
+ dryRun: dryRun ?? false,
28
+ });
29
+ const resultPath = await writeResult(slug, {
30
+ finalMessage: outcome.finalMessage,
31
+ toolCalls: outcome.toolCalls,
32
+ iterations: outcome.iterations,
33
+ verifyExitCode: outcome.verifyExitCode,
34
+ commits: outcome.commits ?? [],
35
+ }, cwd);
36
+ const complete = transitionTo(build, "complete");
37
+ complete.resultPath = resultPath;
38
+ await saveBuild(complete, cwd);
39
+ telemetry?.emit({
40
+ sessionId: parentSessionId ?? "",
41
+ parentEventId: null,
42
+ workspaceId: null,
43
+ triggeredBy: { kind: "user" },
44
+ kind: "build.complete",
45
+ payload: {
46
+ slug,
47
+ verifyExitCode: outcome.verifyExitCode,
48
+ iterations: outcome.iterations,
49
+ toolCalls: outcome.toolCalls,
50
+ },
51
+ latencyMs: Date.now() - startedAt,
52
+ });
53
+ return { resultPath, verifyExitCode: outcome.verifyExitCode };
54
+ }
55
+ //# sourceMappingURL=execute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AAEzC,OAAO,EACL,aAAa,EACb,SAAS,EACT,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAmC1C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAyB;IAC1D,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAE1E,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAEzC,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,+CAA+C,IAAI,aAAa,KAAK,CAAC,KAAK,sBAAsB,CAClG,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAEpD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,SAAS,EAAE,IAAI,CAAC;QACd,SAAS,EAAE,eAAe,IAAI,EAAE;QAChC,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,IAAI;QACjB,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QAC7B,IAAI,EAAE,iBAAiB;QACvB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,IAAI,KAAK,EAAE;KACjE,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC;QAC9B,IAAI;QACJ,GAAG;QACH,IAAI;QACJ,QAAQ,EAAE,KAAK,CAAC,IAAI;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,MAAM,IAAI,KAAK;KACxB,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,MAAM,WAAW,CAClC,IAAI,EACJ;QACE,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;KAC/B,EACD,GAAG,CACJ,CAAC;IAEF,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACjD,QAAQ,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,MAAM,SAAS,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAE/B,SAAS,EAAE,IAAI,CAAC;QACd,SAAS,EAAE,eAAe,IAAI,EAAE;QAChC,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,IAAI;QACjB,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QAC7B,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE;YACP,IAAI;YACJ,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B;QACD,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;KAClC,CAAC,CAAC;IAEH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,16 @@
1
+ export { slugFromTopic, canTransition, transitionTo, getBuildPaths, createBuild, loadBuild, tryLoadBuild, listBuilds, saveBuild, buildsRoot, } from "./state.js";
2
+ export type { BuildPaths } from "./state.js";
3
+ export { appendConversation, readConversation, countTurns, } from "./draft.js";
4
+ export type { ConversationRole } from "./draft.js";
5
+ export { distillSummary } from "./distill.js";
6
+ export type { DistillOptions, DistillResult } from "./distill.js";
7
+ export { recordApproval, recordRejection, checkSummaryHash, readSummary, } from "./approve.js";
8
+ export type { RecordApprovalOptions, SummaryHashCheckResult, } from "./approve.js";
9
+ export { codifySpec, SummaryDriftError } from "./codify.js";
10
+ export type { CodifyOptions, CodifyResult } from "./codify.js";
11
+ export { executeBuild } from "./execute.js";
12
+ export type { ExecuteBuildOptions, ExecuteBuildResult, ExecutorContext, ExecutorOutcome, ExecutorFn, } from "./execute.js";
13
+ export { writeResult } from "./result.js";
14
+ export type { WriteResultOptions } from "./result.js";
15
+ export type { Build, BuildState } from "@keel_flow/schema";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EACL,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,UAAU,GACX,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { slugFromTopic, canTransition, transitionTo, getBuildPaths, createBuild, loadBuild, tryLoadBuild, listBuilds, saveBuild, buildsRoot, } from "./state.js";
2
+ export { appendConversation, readConversation, countTurns, } from "./draft.js";
3
+ export { distillSummary } from "./distill.js";
4
+ export { recordApproval, recordRejection, checkSummaryHash, readSummary, } from "./approve.js";
5
+ export { codifySpec, SummaryDriftError } from "./codify.js";
6
+ export { executeBuild } from "./execute.js";
7
+ export { writeResult } from "./result.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,YAAY,EACZ,UAAU,EACV,SAAS,EACT,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EACL,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,WAAW,GACZ,MAAM,cAAc,CAAC;AAMtB,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG5D,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAS5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface WriteResultOptions {
2
+ finalMessage: string;
3
+ toolCalls: number;
4
+ iterations: number;
5
+ verifyExitCode: number;
6
+ commits: string[];
7
+ }
8
+ export declare function writeResult(slug: string, opts: WriteResultOptions, cwd: string): Promise<string>;
9
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../src/result.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AASD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,kBAAkB,EACxB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAiBjB"}
package/dist/result.js ADDED
@@ -0,0 +1,24 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { getBuildPaths } from "./state.js";
4
+ async function atomicWrite(path, content) {
5
+ await fs.mkdir(dirname(path), { recursive: true });
6
+ const tmpFile = `${path}.tmp-${process.pid}-${Date.now()}`;
7
+ await fs.writeFile(tmpFile, content, "utf-8");
8
+ await fs.rename(tmpFile, path);
9
+ }
10
+ export async function writeResult(slug, opts, cwd) {
11
+ const paths = getBuildPaths(slug, cwd);
12
+ const commitsList = opts.commits.length === 0
13
+ ? "(none)"
14
+ : opts.commits.map((c) => `- ${c}`).join("\n");
15
+ const body = `# Result: ${slug}\n\n` +
16
+ `Completed: ${new Date().toISOString()}\n\n` +
17
+ `## Verify\n\n- exitCode: ${opts.verifyExitCode}\n\n` +
18
+ `## Execution\n\n- iterations: ${opts.iterations}\n- toolCalls: ${opts.toolCalls}\n\n` +
19
+ `## Commits\n\n${commitsList}\n\n` +
20
+ `## Orchestrator Final Message\n\n${opts.finalMessage.trim()}\n`;
21
+ await atomicWrite(paths.result, body);
22
+ return paths.result;
23
+ }
24
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../src/result.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAU3C,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,OAAe;IACtD,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC3D,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAY,EACZ,IAAwB,EACxB,GAAW;IAEX,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,WAAW,GACf,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QACvB,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEnD,MAAM,IAAI,GACR,aAAa,IAAI,MAAM;QACvB,cAAc,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM;QAC5C,4BAA4B,IAAI,CAAC,cAAc,MAAM;QACrD,iCAAiC,IAAI,CAAC,UAAU,kBAAkB,IAAI,CAAC,SAAS,MAAM;QACtF,iBAAiB,WAAW,MAAM;QAClC,oCAAoC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC;IAEnE,MAAM,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtC,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type Build, type BuildState } from "@keel_flow/schema";
2
+ export interface BuildPaths {
3
+ dir: string;
4
+ conversation: string;
5
+ summary: string;
6
+ spec: string;
7
+ result: string;
8
+ state: string;
9
+ }
10
+ export declare function buildsRoot(cwd: string): string;
11
+ export declare function getBuildPaths(slug: string, cwd: string): BuildPaths;
12
+ export declare function slugFromTopic(topic: string, existing?: Set<string>): string;
13
+ export declare function canTransition(from: BuildState, to: BuildState): boolean;
14
+ export declare function transitionTo(build: Build, nextState: BuildState): Build;
15
+ export declare function saveBuild(build: Build, cwd: string): Promise<void>;
16
+ export declare function loadBuild(slug: string, cwd: string): Promise<Build>;
17
+ export declare function tryLoadBuild(slug: string, cwd: string): Promise<Build | null>;
18
+ export declare function listBuilds(cwd: string): Promise<Build[]>;
19
+ export declare function createBuild(args: {
20
+ slug: string;
21
+ topic: string;
22
+ }, cwd: string): Promise<Build>;
23
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAyB7E,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAUnE;AAMD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,GAAG,CAAC,MAAM,CAAa,GAAG,MAAM,CAetF;AAcD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,GAAG,OAAO,CAEvE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,GAAG,KAAK,CAWvE;AASD,wBAAsB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxE;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAKzE;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAInF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAY9D;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EACrC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,KAAK,CAAC,CA0BhB"}
package/dist/state.js ADDED
@@ -0,0 +1,149 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { BuildSchema } from "@keel_flow/schema";
5
+ const STOPWORDS = new Set([
6
+ "a",
7
+ "an",
8
+ "the",
9
+ "and",
10
+ "or",
11
+ "of",
12
+ "to",
13
+ "for",
14
+ "in",
15
+ "on",
16
+ "with",
17
+ "is",
18
+ "are",
19
+ "be",
20
+ "by",
21
+ "at",
22
+ "as",
23
+ "it",
24
+ "this",
25
+ "that",
26
+ ]);
27
+ export function buildsRoot(cwd) {
28
+ return join(resolve(cwd), ".keel", "builds");
29
+ }
30
+ export function getBuildPaths(slug, cwd) {
31
+ const dir = join(buildsRoot(cwd), slug);
32
+ return {
33
+ dir,
34
+ conversation: join(dir, "conversation.md"),
35
+ summary: join(dir, "summary.md"),
36
+ spec: join(dir, "spec.md"),
37
+ result: join(dir, "result.md"),
38
+ state: join(dir, "state.json"),
39
+ };
40
+ }
41
+ function normalizeWord(w) {
42
+ return w.toLowerCase().replace(/[^a-z0-9]/g, "");
43
+ }
44
+ export function slugFromTopic(topic, existing = new Set()) {
45
+ const tokens = topic
46
+ .split(/\s+/)
47
+ .map(normalizeWord)
48
+ .filter((w) => w.length > 0);
49
+ const meaningful = tokens.filter((w) => !STOPWORDS.has(w));
50
+ const source = meaningful.length > 0 ? meaningful : tokens;
51
+ const head = source.slice(0, 3);
52
+ const base = head.length > 0 ? head.join("-") : "build";
53
+ if (!existing.has(base))
54
+ return base;
55
+ let n = 2;
56
+ while (existing.has(`${base}-${n}`))
57
+ n += 1;
58
+ return `${base}-${n}`;
59
+ }
60
+ const LEGAL_TRANSITIONS = {
61
+ drafting: ["summarizing", "abandoned"],
62
+ summarizing: ["awaiting-approval", "drafting", "abandoned"],
63
+ "awaiting-approval": ["approved", "rejected", "abandoned"],
64
+ approved: ["codifying", "abandoned"],
65
+ rejected: ["drafting", "abandoned"],
66
+ codifying: ["executing", "approved", "abandoned"],
67
+ executing: ["complete", "abandoned"],
68
+ complete: ["abandoned"],
69
+ abandoned: [],
70
+ };
71
+ export function canTransition(from, to) {
72
+ return LEGAL_TRANSITIONS[from]?.includes(to) ?? false;
73
+ }
74
+ export function transitionTo(build, nextState) {
75
+ if (!canTransition(build.state, nextState)) {
76
+ throw new Error(`Illegal build-state transition: ${build.state} → ${nextState} (slug=${build.slug})`);
77
+ }
78
+ return {
79
+ ...build,
80
+ state: nextState,
81
+ updatedAt: new Date().toISOString(),
82
+ };
83
+ }
84
+ async function atomicWrite(path, content) {
85
+ await fs.mkdir(join(path, ".."), { recursive: true });
86
+ const tmpFile = `${path}.tmp-${process.pid}-${Date.now()}`;
87
+ await fs.writeFile(tmpFile, content, "utf-8");
88
+ await fs.rename(tmpFile, path);
89
+ }
90
+ export async function saveBuild(build, cwd) {
91
+ const paths = getBuildPaths(build.slug, cwd);
92
+ await fs.mkdir(paths.dir, { recursive: true });
93
+ await atomicWrite(paths.state, JSON.stringify(build, null, 2));
94
+ }
95
+ export async function loadBuild(slug, cwd) {
96
+ const paths = getBuildPaths(slug, cwd);
97
+ const raw = await fs.readFile(paths.state, "utf-8");
98
+ const parsed = JSON.parse(raw);
99
+ return BuildSchema.parse(parsed);
100
+ }
101
+ export async function tryLoadBuild(slug, cwd) {
102
+ const paths = getBuildPaths(slug, cwd);
103
+ if (!existsSync(paths.state))
104
+ return null;
105
+ return loadBuild(slug, cwd);
106
+ }
107
+ export async function listBuilds(cwd) {
108
+ const root = buildsRoot(cwd);
109
+ if (!existsSync(root))
110
+ return [];
111
+ const entries = await fs.readdir(root, { withFileTypes: true });
112
+ const out = [];
113
+ for (const entry of entries) {
114
+ if (!entry.isDirectory())
115
+ continue;
116
+ const build = await tryLoadBuild(entry.name, cwd);
117
+ if (build)
118
+ out.push(build);
119
+ }
120
+ out.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
121
+ return out;
122
+ }
123
+ export async function createBuild(args, cwd) {
124
+ const paths = getBuildPaths(args.slug, cwd);
125
+ const rel = (p) => p.startsWith(resolve(cwd))
126
+ ? p.slice(resolve(cwd).length + 1)
127
+ : p;
128
+ const now = new Date().toISOString();
129
+ const build = {
130
+ slug: args.slug,
131
+ topic: args.topic,
132
+ state: "drafting",
133
+ conversationPath: rel(paths.conversation),
134
+ summaryPath: rel(paths.summary),
135
+ specPath: null,
136
+ resultPath: null,
137
+ approvedAt: null,
138
+ approvedSummaryHash: null,
139
+ createdAt: now,
140
+ updatedAt: now,
141
+ };
142
+ await fs.mkdir(paths.dir, { recursive: true });
143
+ if (!existsSync(paths.conversation)) {
144
+ await fs.writeFile(paths.conversation, "", "utf-8");
145
+ }
146
+ await saveBuild(build, cwd);
147
+ return build;
148
+ }
149
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@keel_flow/build-mode",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.js",
18
+ "types": "./dist/index.d.ts"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "zod": "^3.23.0",
23
+ "@keel_flow/runtime": "0.2.0",
24
+ "@keel_flow/schema": "0.2.0",
25
+ "@keel_flow/telemetry": "0.2.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^25.9.1",
29
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
30
+ "@typescript-eslint/parser": "^8.0.0",
31
+ "eslint": "^9.0.0",
32
+ "typescript": "^5.5.0",
33
+ "vitest": "^2.0.0"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "vitest run",
39
+ "lint": "eslint src"
40
+ }
41
+ }