@lawrence369/loop-cli 0.1.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 (105) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -0
  4. package/dist/agent/activity.d.ts +64 -0
  5. package/dist/agent/activity.js +265 -0
  6. package/dist/agent/launcher.d.ts +42 -0
  7. package/dist/agent/launcher.js +243 -0
  8. package/dist/agent/pty-session.d.ts +113 -0
  9. package/dist/agent/pty-session.js +490 -0
  10. package/dist/agent/ready-detector.d.ts +46 -0
  11. package/dist/agent/ready-detector.js +86 -0
  12. package/dist/agent/wrapper.d.ts +18 -0
  13. package/dist/agent/wrapper.js +110 -0
  14. package/dist/bin/lclaude.d.ts +3 -0
  15. package/dist/bin/lclaude.js +7 -0
  16. package/dist/bin/lcodex.d.ts +3 -0
  17. package/dist/bin/lcodex.js +7 -0
  18. package/dist/bin/lgemini.d.ts +3 -0
  19. package/dist/bin/lgemini.js +7 -0
  20. package/dist/bus/daemon.d.ts +56 -0
  21. package/dist/bus/daemon.js +135 -0
  22. package/dist/bus/event-bus.d.ts +105 -0
  23. package/dist/bus/event-bus.js +157 -0
  24. package/dist/bus/message.d.ts +48 -0
  25. package/dist/bus/message.js +129 -0
  26. package/dist/bus/queue.d.ts +50 -0
  27. package/dist/bus/queue.js +100 -0
  28. package/dist/bus/store.d.ts +88 -0
  29. package/dist/bus/store.js +212 -0
  30. package/dist/bus/subscriber.d.ts +76 -0
  31. package/dist/bus/subscriber.js +187 -0
  32. package/dist/config/index.d.ts +8 -0
  33. package/dist/config/index.js +72 -0
  34. package/dist/config/schema.d.ts +18 -0
  35. package/dist/config/schema.js +58 -0
  36. package/dist/core/conversation.d.ts +34 -0
  37. package/dist/core/conversation.js +289 -0
  38. package/dist/core/engine.d.ts +40 -0
  39. package/dist/core/engine.js +288 -0
  40. package/dist/core/loop.d.ts +33 -0
  41. package/dist/core/loop.js +209 -0
  42. package/dist/core/protocol.d.ts +60 -0
  43. package/dist/core/protocol.js +162 -0
  44. package/dist/core/scoring.d.ts +34 -0
  45. package/dist/core/scoring.js +69 -0
  46. package/dist/index.d.ts +3 -0
  47. package/dist/index.js +408 -0
  48. package/dist/orchestrator/daemon.d.ts +74 -0
  49. package/dist/orchestrator/daemon.js +294 -0
  50. package/dist/orchestrator/group.d.ts +73 -0
  51. package/dist/orchestrator/group.js +166 -0
  52. package/dist/orchestrator/ipc-server.d.ts +60 -0
  53. package/dist/orchestrator/ipc-server.js +166 -0
  54. package/dist/orchestrator/scheduler.d.ts +32 -0
  55. package/dist/orchestrator/scheduler.js +95 -0
  56. package/dist/plan/context.d.ts +8 -0
  57. package/dist/plan/context.js +42 -0
  58. package/dist/plan/decisions.d.ts +18 -0
  59. package/dist/plan/decisions.js +143 -0
  60. package/dist/plan/shared-plan.d.ts +33 -0
  61. package/dist/plan/shared-plan.js +211 -0
  62. package/dist/skills/executor.d.ts +7 -0
  63. package/dist/skills/executor.js +11 -0
  64. package/dist/skills/loader.d.ts +16 -0
  65. package/dist/skills/loader.js +80 -0
  66. package/dist/skills/registry.d.ts +13 -0
  67. package/dist/skills/registry.js +54 -0
  68. package/dist/terminal/adapter.d.ts +61 -0
  69. package/dist/terminal/adapter.js +42 -0
  70. package/dist/terminal/detect.d.ts +30 -0
  71. package/dist/terminal/detect.js +77 -0
  72. package/dist/terminal/iterm2-adapter.d.ts +19 -0
  73. package/dist/terminal/iterm2-adapter.js +120 -0
  74. package/dist/terminal/pty-adapter.d.ts +18 -0
  75. package/dist/terminal/pty-adapter.js +84 -0
  76. package/dist/terminal/terminal-adapter.d.ts +17 -0
  77. package/dist/terminal/terminal-adapter.js +94 -0
  78. package/dist/terminal/tmux-adapter.d.ts +18 -0
  79. package/dist/terminal/tmux-adapter.js +127 -0
  80. package/dist/ui/banner.d.ts +3 -0
  81. package/dist/ui/banner.js +145 -0
  82. package/dist/ui/colors.d.ts +41 -0
  83. package/dist/ui/colors.js +65 -0
  84. package/dist/ui/dashboard.d.ts +32 -0
  85. package/dist/ui/dashboard.js +138 -0
  86. package/dist/ui/input.d.ts +10 -0
  87. package/dist/ui/input.js +96 -0
  88. package/dist/ui/interactive.d.ts +13 -0
  89. package/dist/ui/interactive.js +230 -0
  90. package/dist/ui/renderer.d.ts +33 -0
  91. package/dist/ui/renderer.js +106 -0
  92. package/dist/utils/ansi.d.ts +11 -0
  93. package/dist/utils/ansi.js +16 -0
  94. package/dist/utils/fs.d.ts +34 -0
  95. package/dist/utils/fs.js +115 -0
  96. package/dist/utils/lock.d.ts +12 -0
  97. package/dist/utils/lock.js +116 -0
  98. package/dist/utils/process.d.ts +31 -0
  99. package/dist/utils/process.js +111 -0
  100. package/dist/utils/pty-filter.d.ts +31 -0
  101. package/dist/utils/pty-filter.js +187 -0
  102. package/package.json +71 -0
  103. package/skills/loop/SKILL.md +19 -0
  104. package/skills/plan/SKILL.md +9 -0
  105. package/skills/review/SKILL.md +14 -0
@@ -0,0 +1,166 @@
1
+ import { createServer } from "node:net";
2
+ import { unlinkSync } from "node:fs";
3
+ /**
4
+ * Unix domain socket IPC server.
5
+ *
6
+ * Protocol: newline-delimited JSON (each message is a JSON object
7
+ * followed by a newline character).
8
+ */
9
+ export class IpcServer {
10
+ socketPath;
11
+ handler;
12
+ server = null;
13
+ sockets = new Set();
14
+ constructor(socketPath, handler) {
15
+ this.socketPath = socketPath;
16
+ this.handler = handler;
17
+ }
18
+ /**
19
+ * Start listening on the Unix socket.
20
+ */
21
+ async start() {
22
+ // Remove stale socket file if it exists
23
+ try {
24
+ unlinkSync(this.socketPath);
25
+ }
26
+ catch {
27
+ // Ignore - file may not exist
28
+ }
29
+ return new Promise((resolve, reject) => {
30
+ this.server = createServer((socket) => this.handleConnection(socket));
31
+ this.server.on("error", (err) => {
32
+ reject(err);
33
+ });
34
+ this.server.listen(this.socketPath, () => {
35
+ resolve();
36
+ });
37
+ });
38
+ }
39
+ /**
40
+ * Stop the IPC server and close all connections.
41
+ */
42
+ async stop() {
43
+ // Close all client sockets
44
+ for (const socket of this.sockets) {
45
+ try {
46
+ socket.destroy();
47
+ }
48
+ catch {
49
+ // Ignore
50
+ }
51
+ }
52
+ this.sockets.clear();
53
+ // Close the server
54
+ if (this.server) {
55
+ return new Promise((resolve) => {
56
+ this.server.close(() => {
57
+ // Remove socket file
58
+ try {
59
+ unlinkSync(this.socketPath);
60
+ }
61
+ catch {
62
+ // Ignore
63
+ }
64
+ resolve();
65
+ });
66
+ });
67
+ }
68
+ // Remove socket file even if server wasn't created
69
+ try {
70
+ unlinkSync(this.socketPath);
71
+ }
72
+ catch {
73
+ // Ignore
74
+ }
75
+ }
76
+ /**
77
+ * Send a message to all connected clients.
78
+ */
79
+ broadcast(payload) {
80
+ const line = JSON.stringify(payload) + "\n";
81
+ for (const socket of this.sockets) {
82
+ if (socket.destroyed)
83
+ continue;
84
+ try {
85
+ socket.write(line);
86
+ }
87
+ catch {
88
+ // Ignore write errors
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Get the number of connected clients.
94
+ */
95
+ get clientCount() {
96
+ return this.sockets.size;
97
+ }
98
+ /**
99
+ * Handle a new socket connection.
100
+ */
101
+ handleConnection(socket) {
102
+ this.sockets.add(socket);
103
+ let buffer = "";
104
+ socket.on("data", (data) => {
105
+ buffer += data.toString("utf8");
106
+ // Process complete lines
107
+ const lines = buffer.split("\n");
108
+ buffer = lines.pop() ?? ""; // Keep incomplete last line in buffer
109
+ for (const line of lines) {
110
+ const trimmed = line.trim();
111
+ if (!trimmed)
112
+ continue;
113
+ this.processLine(trimmed, socket).catch((err) => {
114
+ const message = err instanceof Error ? err.message : String(err);
115
+ const errorResponse = {
116
+ success: false,
117
+ type: "ERROR",
118
+ error: message,
119
+ };
120
+ try {
121
+ socket.write(JSON.stringify(errorResponse) + "\n");
122
+ }
123
+ catch {
124
+ // Ignore write errors
125
+ }
126
+ });
127
+ }
128
+ });
129
+ socket.on("close", () => {
130
+ this.sockets.delete(socket);
131
+ });
132
+ socket.on("error", () => {
133
+ this.sockets.delete(socket);
134
+ });
135
+ }
136
+ /**
137
+ * Process a single JSON line from a client.
138
+ */
139
+ async processLine(line, socket) {
140
+ let req;
141
+ try {
142
+ req = JSON.parse(line);
143
+ }
144
+ catch {
145
+ const errorResponse = {
146
+ success: false,
147
+ type: "ERROR",
148
+ error: "Invalid JSON",
149
+ };
150
+ socket.write(JSON.stringify(errorResponse) + "\n");
151
+ return;
152
+ }
153
+ if (!req || typeof req !== "object" || !req.type) {
154
+ const errorResponse = {
155
+ success: false,
156
+ type: "ERROR",
157
+ error: "Missing request type",
158
+ };
159
+ socket.write(JSON.stringify(errorResponse) + "\n");
160
+ return;
161
+ }
162
+ const response = await this.handler(req);
163
+ socket.write(JSON.stringify(response) + "\n");
164
+ }
165
+ }
166
+ //# sourceMappingURL=ipc-server.js.map
@@ -0,0 +1,32 @@
1
+ import { SubscriberManager } from "../bus/subscriber.js";
2
+ /**
3
+ * Simple activity-aware task scheduler.
4
+ *
5
+ * Routes tasks to idle agents, preferring agents that have been idle
6
+ * the longest. Falls back to the least-recently-active agent if none
7
+ * are idle.
8
+ */
9
+ export declare class Scheduler {
10
+ private readonly subscriberManager;
11
+ constructor(subscriberManager: SubscriberManager);
12
+ /**
13
+ * Assign a task to the best available agent.
14
+ *
15
+ * @param task - Description of the task (for logging; not persisted here)
16
+ * @param preferredAgent - Optional subscriber ID or agent type to prefer
17
+ * @returns The subscriber ID of the assigned agent, or null if none available
18
+ */
19
+ assignTask(_task: string, preferredAgent?: string): Promise<string | null>;
20
+ /**
21
+ * Find the best idle agent to route work to.
22
+ *
23
+ * @returns The subscriber ID of an idle agent, or null if none available
24
+ */
25
+ routeToIdle(): Promise<string | null>;
26
+ /**
27
+ * From a list of active agents, pick the best one to receive work.
28
+ * Prefers idle agents; among idle agents, picks the one idle longest.
29
+ */
30
+ private routeToIdleFromList;
31
+ }
32
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Simple activity-aware task scheduler.
3
+ *
4
+ * Routes tasks to idle agents, preferring agents that have been idle
5
+ * the longest. Falls back to the least-recently-active agent if none
6
+ * are idle.
7
+ */
8
+ export class Scheduler {
9
+ subscriberManager;
10
+ constructor(subscriberManager) {
11
+ this.subscriberManager = subscriberManager;
12
+ }
13
+ /**
14
+ * Assign a task to the best available agent.
15
+ *
16
+ * @param task - Description of the task (for logging; not persisted here)
17
+ * @param preferredAgent - Optional subscriber ID or agent type to prefer
18
+ * @returns The subscriber ID of the assigned agent, or null if none available
19
+ */
20
+ async assignTask(_task, preferredAgent) {
21
+ const agents = await this.subscriberManager.list();
22
+ // Filter to active agents only
23
+ const active = [];
24
+ for (const [id, meta] of agents) {
25
+ if (meta.status === "active") {
26
+ active.push([id, meta]);
27
+ }
28
+ }
29
+ if (active.length === 0)
30
+ return null;
31
+ // If a preferred agent is specified, check it first
32
+ if (preferredAgent) {
33
+ // Try exact match
34
+ const exact = active.find(([id]) => id === preferredAgent);
35
+ if (exact)
36
+ return exact[0];
37
+ // Try nickname match
38
+ const byNickname = active.find(([, meta]) => meta.nickname === preferredAgent);
39
+ if (byNickname)
40
+ return byNickname[0];
41
+ // Try agent type match (pick idle one of that type)
42
+ const byType = active.filter(([, meta]) => meta.agent_type === preferredAgent);
43
+ if (byType.length > 0) {
44
+ const idle = byType.find(([, meta]) => meta.activity_state === "idle");
45
+ if (idle)
46
+ return idle[0];
47
+ return byType[0][0]; // Fallback to first of type
48
+ }
49
+ }
50
+ // Prefer idle agents
51
+ return this.routeToIdleFromList(active);
52
+ }
53
+ /**
54
+ * Find the best idle agent to route work to.
55
+ *
56
+ * @returns The subscriber ID of an idle agent, or null if none available
57
+ */
58
+ async routeToIdle() {
59
+ const agents = await this.subscriberManager.list();
60
+ const active = [];
61
+ for (const [id, meta] of agents) {
62
+ if (meta.status === "active") {
63
+ active.push([id, meta]);
64
+ }
65
+ }
66
+ return this.routeToIdleFromList(active);
67
+ }
68
+ /**
69
+ * From a list of active agents, pick the best one to receive work.
70
+ * Prefers idle agents; among idle agents, picks the one idle longest.
71
+ */
72
+ routeToIdleFromList(active) {
73
+ if (active.length === 0)
74
+ return null;
75
+ // Separate idle from working
76
+ const idle = active.filter(([, meta]) => meta.activity_state === "idle");
77
+ if (idle.length > 0) {
78
+ // Pick the agent that has been idle the longest (oldest last_activity)
79
+ idle.sort((a, b) => {
80
+ const aTime = a[1].last_activity ?? a[1].last_seen;
81
+ const bTime = b[1].last_activity ?? b[1].last_seen;
82
+ return (aTime ?? "").localeCompare(bTime ?? "");
83
+ });
84
+ return idle[0][0];
85
+ }
86
+ // No idle agents - pick least recently active (might finish soonest)
87
+ const sorted = [...active].sort((a, b) => {
88
+ const aTime = a[1].last_activity ?? a[1].last_seen;
89
+ const bTime = b[1].last_activity ?? b[1].last_seen;
90
+ return (aTime ?? "").localeCompare(bTime ?? "");
91
+ });
92
+ return sorted[0][0];
93
+ }
94
+ }
95
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Build a unified context string combining:
3
+ * - Last iteration score/feedback from the shared plan
4
+ * - Active decisions
5
+ * - File change log
6
+ */
7
+ export declare function buildContext(cwd: string): Promise<string>;
8
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { getExecutorContext } from "./shared-plan.js";
2
+ import { listDecisions } from "./decisions.js";
3
+ /**
4
+ * Build a unified context string combining:
5
+ * - Last iteration score/feedback from the shared plan
6
+ * - Active decisions
7
+ * - File change log
8
+ */
9
+ export async function buildContext(cwd) {
10
+ const sections = [];
11
+ // 1. Last iteration context from shared plan
12
+ const iterationContext = await getExecutorContext(cwd);
13
+ if (iterationContext) {
14
+ sections.push(iterationContext);
15
+ }
16
+ // 2. Active decisions
17
+ try {
18
+ const decisions = await listDecisions(cwd);
19
+ const active = decisions.filter((d) => d.status === "proposed" || d.status === "accepted");
20
+ if (active.length > 0) {
21
+ const decisionLines = [
22
+ "## Active Decisions",
23
+ "",
24
+ ];
25
+ for (const d of active) {
26
+ decisionLines.push(`- **[${d.status.toUpperCase()}] #${d.id}: ${d.title}**`);
27
+ if (d.decision) {
28
+ decisionLines.push(` ${d.decision}`);
29
+ }
30
+ }
31
+ sections.push(decisionLines.join("\n"));
32
+ }
33
+ }
34
+ catch {
35
+ // No decisions directory — skip
36
+ }
37
+ if (sections.length === 0) {
38
+ return "";
39
+ }
40
+ return sections.join("\n\n");
41
+ }
42
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,18 @@
1
+ export interface Decision {
2
+ id: number;
3
+ title: string;
4
+ status: "proposed" | "accepted" | "rejected" | "superseded";
5
+ context: string;
6
+ decision: string;
7
+ consequences: string;
8
+ date: string;
9
+ }
10
+ type DecisionStatus = Decision["status"];
11
+ /** Add a new decision to the decisions directory. */
12
+ export declare function addDecision(cwd: string, decision: Omit<Decision, "id" | "date">): Promise<Decision>;
13
+ /** List all decisions from the decisions directory. */
14
+ export declare function listDecisions(cwd: string): Promise<Decision[]>;
15
+ /** Resolve (update status of) a decision by ID. */
16
+ export declare function resolveDecision(cwd: string, id: number, status: DecisionStatus): Promise<void>;
17
+ export {};
18
+ //# sourceMappingURL=decisions.d.ts.map
@@ -0,0 +1,143 @@
1
+ import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import matter from "gray-matter";
4
+ // ── Helpers ─────────────────────────────────────────
5
+ function decisionsDir(cwd) {
6
+ return join(cwd, ".loop", "context", "decisions");
7
+ }
8
+ function slugify(title) {
9
+ const cleaned = title
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9]+/g, "-")
12
+ .replace(/^-+|-+$/g, "")
13
+ .replace(/-+/g, "-");
14
+ return cleaned || "decision";
15
+ }
16
+ function nextNumber(dir) {
17
+ if (!existsSync(dir))
18
+ return 1;
19
+ const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
20
+ const numbers = files.map((f) => {
21
+ const match = f.match(/^(\d{4})-/);
22
+ return match ? parseInt(match[1], 10) : 0;
23
+ });
24
+ return numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
25
+ }
26
+ function padId(id) {
27
+ return String(id).padStart(4, "0");
28
+ }
29
+ function isValidStatus(s) {
30
+ return s === "proposed" || s === "accepted" || s === "rejected" || s === "superseded";
31
+ }
32
+ function parseDecisionFile(filePath) {
33
+ try {
34
+ const content = readFileSync(filePath, "utf-8");
35
+ const parsed = matter(content);
36
+ const data = parsed.data;
37
+ const body = parsed.content;
38
+ // Extract ID from filename
39
+ const filename = filePath.split("/").pop() ?? "";
40
+ const idMatch = filename.match(/^(\d{4})-/);
41
+ const id = idMatch ? parseInt(idMatch[1], 10) : 0;
42
+ // Extract title from first heading
43
+ let title = "";
44
+ const titleMatch = body.match(/^#\s+(.+)$/m);
45
+ if (titleMatch) {
46
+ title = titleMatch[1].replace(/^DECISION\s+\d+:\s*/i, "").trim();
47
+ }
48
+ // Extract sections from body
49
+ const contextMatch = body.match(/Context:\n([\s\S]*?)(?=\nDecision:|\n#|$)/i);
50
+ const decisionMatch = body.match(/Decision:\n([\s\S]*?)(?=\nConsequences:|\nImplications:|\n#|$)/i);
51
+ const consequencesMatch = body.match(/(?:Consequences|Implications):\n([\s\S]*?)(?=\n#|$)/i);
52
+ const status = isValidStatus(data.status) ? data.status : "proposed";
53
+ const date = typeof data.date === "string"
54
+ ? data.date
55
+ : (body.match(/Date:\s*(.+)/)?.[1]?.trim() ?? new Date().toISOString().slice(0, 10));
56
+ return {
57
+ id,
58
+ title: title || (typeof data.title === "string" ? data.title : "(no title)"),
59
+ status,
60
+ context: contextMatch?.[1]?.trim() ?? "",
61
+ decision: decisionMatch?.[1]?.trim() ?? "",
62
+ consequences: consequencesMatch?.[1]?.trim() ?? "",
63
+ date,
64
+ };
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ // ── Public API ──────────────────────────────────────
71
+ /** Add a new decision to the decisions directory. */
72
+ export async function addDecision(cwd, decision) {
73
+ const dir = decisionsDir(cwd);
74
+ mkdirSync(dir, { recursive: true });
75
+ const id = nextNumber(dir);
76
+ const date = new Date().toISOString().slice(0, 10);
77
+ const slug = slugify(decision.title);
78
+ const filename = `${padId(id)}-${slug}.md`;
79
+ const filePath = join(dir, filename);
80
+ // Path traversal guard: ensure file stays within decisions dir
81
+ if (!resolve(filePath).startsWith(resolve(dir))) {
82
+ throw new Error("Invalid decision title: path traversal detected");
83
+ }
84
+ const content = `---\n` +
85
+ `status: ${decision.status}\n` +
86
+ `date: ${date}\n` +
87
+ `---\n` +
88
+ `# DECISION ${padId(id)}: ${decision.title}\n\n` +
89
+ `Date: ${date}\n\n` +
90
+ `Context:\n${decision.context || "What led to this decision?"}\n\n` +
91
+ `Decision:\n${decision.decision || "What is now considered true?"}\n\n` +
92
+ `Consequences:\n${decision.consequences || "What must follow from this?"}\n`;
93
+ writeFileSync(filePath, content, "utf-8");
94
+ return {
95
+ id,
96
+ title: decision.title,
97
+ status: decision.status,
98
+ context: decision.context,
99
+ decision: decision.decision,
100
+ consequences: decision.consequences,
101
+ date,
102
+ };
103
+ }
104
+ /** List all decisions from the decisions directory. */
105
+ export async function listDecisions(cwd) {
106
+ const dir = decisionsDir(cwd);
107
+ if (!existsSync(dir))
108
+ return [];
109
+ const files = readdirSync(dir)
110
+ .filter((f) => f.endsWith(".md"))
111
+ .sort();
112
+ const decisions = [];
113
+ for (const file of files) {
114
+ const filePath = join(dir, file);
115
+ const decision = parseDecisionFile(filePath);
116
+ if (decision) {
117
+ decisions.push(decision);
118
+ }
119
+ }
120
+ return decisions;
121
+ }
122
+ /** Resolve (update status of) a decision by ID. */
123
+ export async function resolveDecision(cwd, id, status) {
124
+ const dir = decisionsDir(cwd);
125
+ if (!existsSync(dir)) {
126
+ throw new Error(`Decisions directory not found: ${dir}`);
127
+ }
128
+ const prefix = padId(id);
129
+ const files = readdirSync(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".md"));
130
+ if (files.length === 0) {
131
+ throw new Error(`Decision ${id} not found`);
132
+ }
133
+ const filePath = join(dir, files[0]);
134
+ const content = readFileSync(filePath, "utf-8");
135
+ const parsed = matter(content);
136
+ // Update status in frontmatter
137
+ const data = parsed.data;
138
+ data.status = status;
139
+ data.resolved_at = new Date().toISOString();
140
+ const updated = matter.stringify(parsed.content, data);
141
+ writeFileSync(filePath, updated, "utf-8");
142
+ }
143
+ //# sourceMappingURL=decisions.js.map
@@ -0,0 +1,33 @@
1
+ export interface SharedPlan {
2
+ task: string;
3
+ iterations: IterationRecord[];
4
+ fileChangeLog: FileChange[];
5
+ }
6
+ export interface IterationRecord {
7
+ iteration: number;
8
+ executor: string;
9
+ reviewer: string;
10
+ score: number;
11
+ approved: boolean;
12
+ executorSummary: string;
13
+ reviewerFeedback: string;
14
+ timestamp: string;
15
+ }
16
+ export interface FileChange {
17
+ iteration: number;
18
+ file: string;
19
+ action: "created" | "modified" | "deleted";
20
+ }
21
+ /** Initialize a new shared plan file. */
22
+ export declare function initSharedPlan(cwd: string, task: string): Promise<void>;
23
+ /** Update the shared plan with a new iteration record. */
24
+ export declare function updateSharedPlan(cwd: string, record: IterationRecord, filesChanged: string[]): Promise<void>;
25
+ /** Generate a context snippet from the shared plan for the executor prompt. */
26
+ export declare function getExecutorContext(cwd: string): Promise<string>;
27
+ /** Generate a context snippet for the reviewer prompt. */
28
+ export declare function getReviewerContext(cwd: string): Promise<string>;
29
+ /** Clear the shared plan file. */
30
+ export declare function clearPlan(cwd: string): Promise<void>;
31
+ /** Show the raw plan content. */
32
+ export declare function showPlan(cwd: string): Promise<string>;
33
+ //# sourceMappingURL=shared-plan.d.ts.map