localptp 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 (186) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +159 -0
  3. package/dist/cli.d.ts +24 -0
  4. package/dist/cli.js +344 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/config.d.ts +14 -0
  7. package/dist/commands/config.js +65 -0
  8. package/dist/commands/config.js.map +1 -0
  9. package/dist/commands/context.d.ts +12 -0
  10. package/dist/commands/context.js +176 -0
  11. package/dist/commands/context.js.map +1 -0
  12. package/dist/commands/doctor.d.ts +16 -0
  13. package/dist/commands/doctor.js +54 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/index.d.ts +13 -0
  16. package/dist/commands/index.js +70 -0
  17. package/dist/commands/index.js.map +1 -0
  18. package/dist/commands/init.d.ts +15 -0
  19. package/dist/commands/init.js +46 -0
  20. package/dist/commands/init.js.map +1 -0
  21. package/dist/commands/plan.d.ts +17 -0
  22. package/dist/commands/plan.js +170 -0
  23. package/dist/commands/plan.js.map +1 -0
  24. package/dist/commands/resume.d.ts +17 -0
  25. package/dist/commands/resume.js +75 -0
  26. package/dist/commands/resume.js.map +1 -0
  27. package/dist/commands/review.d.ts +17 -0
  28. package/dist/commands/review.js +67 -0
  29. package/dist/commands/review.js.map +1 -0
  30. package/dist/commands/run.d.ts +24 -0
  31. package/dist/commands/run.js +65 -0
  32. package/dist/commands/run.js.map +1 -0
  33. package/dist/commands/step.d.ts +44 -0
  34. package/dist/commands/step.js +50 -0
  35. package/dist/commands/step.js.map +1 -0
  36. package/dist/commands/summarize.d.ts +17 -0
  37. package/dist/commands/summarize.js +276 -0
  38. package/dist/commands/summarize.js.map +1 -0
  39. package/dist/commands/task.d.ts +13 -0
  40. package/dist/commands/task.js +53 -0
  41. package/dist/commands/task.js.map +1 -0
  42. package/dist/core/activePointer.d.ts +28 -0
  43. package/dist/core/activePointer.js +84 -0
  44. package/dist/core/activePointer.js.map +1 -0
  45. package/dist/core/approval.d.ts +12 -0
  46. package/dist/core/approval.js +34 -0
  47. package/dist/core/approval.js.map +1 -0
  48. package/dist/core/configManager.d.ts +32 -0
  49. package/dist/core/configManager.js +177 -0
  50. package/dist/core/configManager.js.map +1 -0
  51. package/dist/core/contextBuilder.d.ts +40 -0
  52. package/dist/core/contextBuilder.js +406 -0
  53. package/dist/core/contextBuilder.js.map +1 -0
  54. package/dist/core/memoryLoader.d.ts +4 -0
  55. package/dist/core/memoryLoader.js +35 -0
  56. package/dist/core/memoryLoader.js.map +1 -0
  57. package/dist/core/memoryManager.d.ts +41 -0
  58. package/dist/core/memoryManager.js +181 -0
  59. package/dist/core/memoryManager.js.map +1 -0
  60. package/dist/core/memoryPolicy.d.ts +23 -0
  61. package/dist/core/memoryPolicy.js +73 -0
  62. package/dist/core/memoryPolicy.js.map +1 -0
  63. package/dist/core/modelClient.d.ts +37 -0
  64. package/dist/core/modelClient.js +160 -0
  65. package/dist/core/modelClient.js.map +1 -0
  66. package/dist/core/patchManager.d.ts +89 -0
  67. package/dist/core/patchManager.js +801 -0
  68. package/dist/core/patchManager.js.map +1 -0
  69. package/dist/core/plannerJson.d.ts +23 -0
  70. package/dist/core/plannerJson.js +118 -0
  71. package/dist/core/plannerJson.js.map +1 -0
  72. package/dist/core/promptManager.d.ts +16 -0
  73. package/dist/core/promptManager.js +35 -0
  74. package/dist/core/promptManager.js.map +1 -0
  75. package/dist/core/prompts.d.ts +8 -0
  76. package/dist/core/prompts.js +18 -0
  77. package/dist/core/prompts.js.map +1 -0
  78. package/dist/core/repoIndexer.d.ts +21 -0
  79. package/dist/core/repoIndexer.js +557 -0
  80. package/dist/core/repoIndexer.js.map +1 -0
  81. package/dist/core/reviewEngine.d.ts +53 -0
  82. package/dist/core/reviewEngine.js +229 -0
  83. package/dist/core/reviewEngine.js.map +1 -0
  84. package/dist/core/runLoop.d.ts +26 -0
  85. package/dist/core/runLoop.js +103 -0
  86. package/dist/core/runLoop.js.map +1 -0
  87. package/dist/core/runStep.d.ts +42 -0
  88. package/dist/core/runStep.js +586 -0
  89. package/dist/core/runStep.js.map +1 -0
  90. package/dist/core/safetyManager.d.ts +7 -0
  91. package/dist/core/safetyManager.js +202 -0
  92. package/dist/core/safetyManager.js.map +1 -0
  93. package/dist/core/sessionManager.d.ts +35 -0
  94. package/dist/core/sessionManager.js +265 -0
  95. package/dist/core/sessionManager.js.map +1 -0
  96. package/dist/core/stopConditions.d.ts +24 -0
  97. package/dist/core/stopConditions.js +18 -0
  98. package/dist/core/stopConditions.js.map +1 -0
  99. package/dist/core/taskManager.d.ts +26 -0
  100. package/dist/core/taskManager.js +312 -0
  101. package/dist/core/taskManager.js.map +1 -0
  102. package/dist/core/testRunner.d.ts +27 -0
  103. package/dist/core/testRunner.js +71 -0
  104. package/dist/core/testRunner.js.map +1 -0
  105. package/dist/prompts/coder.d.ts +3 -0
  106. package/dist/prompts/coder.js +46 -0
  107. package/dist/prompts/coder.js.map +1 -0
  108. package/dist/prompts/planner.d.ts +3 -0
  109. package/dist/prompts/planner.js +44 -0
  110. package/dist/prompts/planner.js.map +1 -0
  111. package/dist/prompts/reviewer.d.ts +3 -0
  112. package/dist/prompts/reviewer.js +41 -0
  113. package/dist/prompts/reviewer.js.map +1 -0
  114. package/dist/prompts/summarizer.d.ts +3 -0
  115. package/dist/prompts/summarizer.js +65 -0
  116. package/dist/prompts/summarizer.js.map +1 -0
  117. package/dist/prompts/testFixer.d.ts +3 -0
  118. package/dist/prompts/testFixer.js +33 -0
  119. package/dist/prompts/testFixer.js.map +1 -0
  120. package/dist/repl/approver.d.ts +15 -0
  121. package/dist/repl/approver.js +21 -0
  122. package/dist/repl/approver.js.map +1 -0
  123. package/dist/repl/dispatch.d.ts +48 -0
  124. package/dist/repl/dispatch.js +164 -0
  125. package/dist/repl/dispatch.js.map +1 -0
  126. package/dist/repl/loop.d.ts +14 -0
  127. package/dist/repl/loop.js +124 -0
  128. package/dist/repl/loop.js.map +1 -0
  129. package/dist/repl/tokenize.d.ts +13 -0
  130. package/dist/repl/tokenize.js +56 -0
  131. package/dist/repl/tokenize.js.map +1 -0
  132. package/dist/templates/memory.d.ts +13 -0
  133. package/dist/templates/memory.js +32 -0
  134. package/dist/templates/memory.js.map +1 -0
  135. package/dist/types/config.d.ts +235 -0
  136. package/dist/types/config.js +66 -0
  137. package/dist/types/config.js.map +1 -0
  138. package/dist/types/context.d.ts +78 -0
  139. package/dist/types/context.js +141 -0
  140. package/dist/types/context.js.map +1 -0
  141. package/dist/types/index.d.ts +117 -0
  142. package/dist/types/index.js +24 -0
  143. package/dist/types/index.js.map +1 -0
  144. package/dist/types/model.d.ts +35 -0
  145. package/dist/types/model.js +16 -0
  146. package/dist/types/model.js.map +1 -0
  147. package/dist/types/patch.d.ts +38 -0
  148. package/dist/types/patch.js +11 -0
  149. package/dist/types/patch.js.map +1 -0
  150. package/dist/types/plan.d.ts +86 -0
  151. package/dist/types/plan.js +27 -0
  152. package/dist/types/plan.js.map +1 -0
  153. package/dist/types/review.d.ts +33 -0
  154. package/dist/types/review.js +24 -0
  155. package/dist/types/review.js.map +1 -0
  156. package/dist/types/run.d.ts +34 -0
  157. package/dist/types/run.js +2 -0
  158. package/dist/types/run.js.map +1 -0
  159. package/dist/types/session.d.ts +21 -0
  160. package/dist/types/session.js +2 -0
  161. package/dist/types/session.js.map +1 -0
  162. package/dist/types/summary.d.ts +65 -0
  163. package/dist/types/summary.js +26 -0
  164. package/dist/types/summary.js.map +1 -0
  165. package/dist/types/task.d.ts +31 -0
  166. package/dist/types/task.js +10 -0
  167. package/dist/types/task.js.map +1 -0
  168. package/dist/types/test.d.ts +16 -0
  169. package/dist/types/test.js +2 -0
  170. package/dist/types/test.js.map +1 -0
  171. package/dist/utils/fs.d.ts +13 -0
  172. package/dist/utils/fs.js +65 -0
  173. package/dist/utils/fs.js.map +1 -0
  174. package/dist/utils/gitRoot.d.ts +5 -0
  175. package/dist/utils/gitRoot.js +21 -0
  176. package/dist/utils/gitRoot.js.map +1 -0
  177. package/dist/utils/logger.d.ts +15 -0
  178. package/dist/utils/logger.js +60 -0
  179. package/dist/utils/logger.js.map +1 -0
  180. package/dist/utils/paths.d.ts +13 -0
  181. package/dist/utils/paths.js +24 -0
  182. package/dist/utils/paths.js.map +1 -0
  183. package/dist/utils/tokenEstimate.d.ts +5 -0
  184. package/dist/utils/tokenEstimate.js +8 -0
  185. package/dist/utils/tokenEstimate.js.map +1 -0
  186. package/package.json +44 -0
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Task Manager (HLD-SRD §3.5).
3
+ *
4
+ * Creates `/ai/tasks/YYYY-MM-DD_HHMM_slug.md` files, parses them into a typed
5
+ * `Task`, and rewrites ONLY the `## Subtasks` block on edit (heading-section
6
+ * discipline) so user content elsewhere is preserved. The model's free-form
7
+ * task text is stored verbatim in `## Goal`; the filename slug is sanitized to
8
+ * ASCII-kebab.
9
+ */
10
+ import { promises as fs } from "node:fs";
11
+ import path from "node:path";
12
+ import { ensureDir } from "../utils/fs.js";
13
+ const SUBTASKS_HEADING = "Subtasks";
14
+ // ---------------------------------------------------------------------------
15
+ // Slug
16
+ // ---------------------------------------------------------------------------
17
+ /**
18
+ * Slugify free task text: drop diacritics, lower-case, keep [a-z0-9], collapse
19
+ * runs to single hyphens, take the first ~6 words. Empty result → `task`.
20
+ */
21
+ export function slugify(text) {
22
+ const ascii = text
23
+ .normalize("NFKD")
24
+ .replace(/[̀-ͯ]/g, "") // strip combining diacritics
25
+ .toLowerCase();
26
+ const words = ascii
27
+ .split(/[^a-z0-9]+/)
28
+ .filter((w) => w.length > 0)
29
+ .slice(0, 6);
30
+ const slug = words.join("-");
31
+ return slug.length > 0 ? slug : "task";
32
+ }
33
+ // ---------------------------------------------------------------------------
34
+ // Timestamp
35
+ // ---------------------------------------------------------------------------
36
+ function pad(n) {
37
+ return String(n).padStart(2, "0");
38
+ }
39
+ /** `YYYY-MM-DD` */
40
+ function dateStamp(d) {
41
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
42
+ }
43
+ /** `HHMM` */
44
+ function timeStamp(d) {
45
+ return `${pad(d.getHours())}${pad(d.getMinutes())}`;
46
+ }
47
+ /** `YYYY-MM-DD HH:MM` for the `Created:` line. */
48
+ function createdStamp(d) {
49
+ return `${dateStamp(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Section helpers (heading-delimited, level-aware)
53
+ // ---------------------------------------------------------------------------
54
+ /**
55
+ * Replace the body of a `## <heading>` section with `newBody`, preserving the
56
+ * heading line and everything outside the section. If the heading is absent,
57
+ * append a fresh section. Section ends at the next heading of same-or-higher
58
+ * level.
59
+ */
60
+ function replaceSection(md, heading, newBody) {
61
+ const lines = md.split(/\r?\n/);
62
+ const headingLc = heading.toLowerCase();
63
+ let start = -1;
64
+ let level = 0;
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const m = /^(#{1,6})\s+(.*?)\s*$/.exec(lines[i]);
67
+ if (m && m[2].toLowerCase() === headingLc) {
68
+ start = i;
69
+ level = m[1].length;
70
+ break;
71
+ }
72
+ }
73
+ if (start === -1) {
74
+ const sep = md.length > 0 && !md.endsWith("\n") ? "\n" : "";
75
+ return `${md}${sep}\n${"#".repeat(2)} ${heading}\n${newBody}\n`;
76
+ }
77
+ // Find the end of the section body.
78
+ let end = lines.length;
79
+ for (let i = start + 1; i < lines.length; i++) {
80
+ const m = /^(#{1,6})\s+/.exec(lines[i]);
81
+ if (m && m[1].length <= level) {
82
+ end = i;
83
+ break;
84
+ }
85
+ }
86
+ const headingLine = lines[start];
87
+ const before = lines.slice(0, start);
88
+ const after = lines.slice(end);
89
+ const rebuilt = [...before, headingLine, newBody, ...after];
90
+ return rebuilt.join("\n");
91
+ }
92
+ /** Trimmed body of a `## <heading>` section, or undefined. */
93
+ function sectionBody(md, heading) {
94
+ const lines = md.split(/\r?\n/);
95
+ const headingLc = heading.toLowerCase();
96
+ let start = -1;
97
+ let level = 0;
98
+ for (let i = 0; i < lines.length; i++) {
99
+ const m = /^(#{1,6})\s+(.*?)\s*$/.exec(lines[i]);
100
+ if (m && m[2].toLowerCase() === headingLc) {
101
+ start = i + 1;
102
+ level = m[1].length;
103
+ break;
104
+ }
105
+ }
106
+ if (start === -1)
107
+ return undefined;
108
+ const collected = [];
109
+ for (let i = start; i < lines.length; i++) {
110
+ const m = /^(#{1,6})\s+/.exec(lines[i]);
111
+ if (m && m[1].length <= level)
112
+ break;
113
+ collected.push(lines[i]);
114
+ }
115
+ const body = collected.join("\n").trim();
116
+ return body.length > 0 ? body : undefined;
117
+ }
118
+ // ---------------------------------------------------------------------------
119
+ // Subtask serialization
120
+ // ---------------------------------------------------------------------------
121
+ const RISK_VALUES = ["low", "medium", "high"];
122
+ function normalizeRisk(raw) {
123
+ const v = (raw ?? "").trim().toLowerCase();
124
+ return RISK_VALUES.includes(v) ? v : "medium";
125
+ }
126
+ function statusToCheckbox(status) {
127
+ return status === "done" ? "x" : " ";
128
+ }
129
+ /**
130
+ * Render the subtasks list. Each subtask is a checkbox line carrying its id,
131
+ * title and risk; an optional indented `likelyFiles:` line follows. The
132
+ * checkbox form keeps the tolerant 0001_03 parser working.
133
+ */
134
+ function renderSubtasks(subtasks) {
135
+ if (subtasks.length === 0)
136
+ return "";
137
+ const out = [];
138
+ for (const s of subtasks) {
139
+ const box = statusToCheckbox(s.status);
140
+ out.push(`- [${box}] ${s.id}: ${s.title} (risk: ${s.risk})`);
141
+ if (s.description.trim().length > 0) {
142
+ out.push(` description: ${s.description.trim()}`);
143
+ }
144
+ if (s.likelyFiles.length > 0) {
145
+ out.push(` likelyFiles: ${s.likelyFiles.join(", ")}`);
146
+ }
147
+ if (s.acceptanceCriteria && s.acceptanceCriteria.length > 0) {
148
+ out.push(` acceptanceCriteria: ${s.acceptanceCriteria.join("; ")}`);
149
+ }
150
+ }
151
+ return out.join("\n");
152
+ }
153
+ function parseList(raw, sep) {
154
+ return raw
155
+ .split(sep)
156
+ .map((s) => s.trim())
157
+ .filter((s) => s.length > 0);
158
+ }
159
+ const CHECKBOX_RE = /^\s*[-*]\s+\[( |x|X)\]\s*(.*)$/;
160
+ const ATTR_RE = /^\s+([a-zA-Z]+)\s*:\s*(.*)$/;
161
+ /** Parse the `## Subtasks` block into typed Subtasks. */
162
+ function parseSubtasks(md) {
163
+ const body = sectionBody(md, SUBTASKS_HEADING);
164
+ if (body === undefined)
165
+ return [];
166
+ const lines = body.split(/\r?\n/);
167
+ const subtasks = [];
168
+ let current;
169
+ let index = 0;
170
+ for (const line of lines) {
171
+ const cb = CHECKBOX_RE.exec(line);
172
+ if (cb) {
173
+ index += 1;
174
+ const done = cb[1].toLowerCase() === "x";
175
+ let rest = cb[2].trim();
176
+ // Risk: trailing `(risk: low)`.
177
+ let risk = "medium";
178
+ const riskM = /\(risk:\s*([a-zA-Z]+)\s*\)\s*$/i.exec(rest);
179
+ if (riskM) {
180
+ risk = normalizeRisk(riskM[1]);
181
+ rest = rest.slice(0, riskM.index).trim();
182
+ }
183
+ // Id prefix: `step-N:`.
184
+ let id = `step-${index}`;
185
+ let title = rest;
186
+ const idM = /^([A-Za-z][\w-]*)\s*:\s*(.*)$/.exec(rest);
187
+ if (idM) {
188
+ id = idM[1];
189
+ title = idM[2].trim();
190
+ }
191
+ const status = done ? "done" : "pending";
192
+ current = {
193
+ id,
194
+ title,
195
+ description: "",
196
+ status,
197
+ risk,
198
+ likelyFiles: [],
199
+ };
200
+ subtasks.push(current);
201
+ continue;
202
+ }
203
+ const attr = ATTR_RE.exec(line);
204
+ if (attr && current) {
205
+ const key = attr[1].toLowerCase();
206
+ const value = attr[2].trim();
207
+ if (key === "likelyfiles")
208
+ current.likelyFiles = parseList(value, ",");
209
+ else if (key === "acceptancecriteria")
210
+ current.acceptanceCriteria = parseList(value, ";");
211
+ else if (key === "description")
212
+ current.description = value;
213
+ }
214
+ }
215
+ return subtasks;
216
+ }
217
+ // ---------------------------------------------------------------------------
218
+ // Status / title parsing
219
+ // ---------------------------------------------------------------------------
220
+ function parseStatus(md) {
221
+ const m = /^Status:\s*(active|done|blocked)\s*$/im.exec(md);
222
+ return m?.[1]?.toLowerCase() ?? "active";
223
+ }
224
+ function parseTitle(md) {
225
+ const m = /^#\s+Task:\s*(.*?)\s*$/m.exec(md);
226
+ return m?.[1] ?? "";
227
+ }
228
+ /** Build the §3.5 markdown for a new task. */
229
+ function renderTaskFile(text, now) {
230
+ return [
231
+ `# Task: ${text}`,
232
+ "",
233
+ `Created: ${createdStamp(now)}`,
234
+ "Status: active",
235
+ "",
236
+ "## Goal",
237
+ text,
238
+ "",
239
+ "## Background",
240
+ "",
241
+ "## Requirements",
242
+ "",
243
+ "## Non-goals",
244
+ "",
245
+ "## Acceptance Criteria",
246
+ "",
247
+ "## Subtasks",
248
+ "",
249
+ "## Tests",
250
+ "- npm run typecheck",
251
+ "- npm test",
252
+ "",
253
+ ].join("\n");
254
+ }
255
+ /**
256
+ * Create a new task file under `tasksDir`. Never overwrites: on a filename
257
+ * collision (same timestamp+slug) appends `-2`, `-3`, … Returns the parsed Task.
258
+ */
259
+ export async function createTask(tasksDir, text, opts = {}) {
260
+ const now = opts.now ?? new Date();
261
+ await ensureDir(tasksDir);
262
+ const base = `${dateStamp(now)}_${timeStamp(now)}_${slugify(text)}`;
263
+ const content = renderTaskFile(text, now);
264
+ let attempt = 0;
265
+ // First try the bare name, then -2, -3, … using O_EXCL to avoid races/overwrite.
266
+ for (;;) {
267
+ const name = attempt === 0 ? `${base}.md` : `${base}-${attempt + 1}.md`;
268
+ const full = path.join(tasksDir, name);
269
+ try {
270
+ await fs.writeFile(full, content, { encoding: "utf8", flag: "wx" });
271
+ return parseTaskString(full, content);
272
+ }
273
+ catch (err) {
274
+ if (err.code === "EEXIST") {
275
+ attempt += 1;
276
+ continue;
277
+ }
278
+ throw err;
279
+ }
280
+ }
281
+ }
282
+ /** Parse a task markdown string into a typed Task. */
283
+ export function parseTaskString(filePath, md) {
284
+ return {
285
+ path: filePath,
286
+ title: parseTitle(md),
287
+ status: parseStatus(md),
288
+ goal: sectionBody(md, "Goal") ?? "",
289
+ subtasks: parseSubtasks(md),
290
+ raw: md,
291
+ };
292
+ }
293
+ /** Read + parse a task file from disk. */
294
+ export async function parseTask(filePath) {
295
+ const md = await fs.readFile(filePath, "utf8");
296
+ return parseTaskString(filePath, md);
297
+ }
298
+ /**
299
+ * Return a Task whose `## Subtasks` block (in `raw`) is rewritten to `subtasks`,
300
+ * preserving all other content. Pure: callers persist via `serializeTask`.
301
+ */
302
+ export function setSubtasks(task, subtasks) {
303
+ const body = renderSubtasks(subtasks);
304
+ const sectionContent = body.length > 0 ? `\n${body}\n` : "\n";
305
+ const raw = replaceSection(task.raw, SUBTASKS_HEADING, sectionContent);
306
+ return { ...task, subtasks, raw };
307
+ }
308
+ /** The markdown to persist for a Task (its `raw`). */
309
+ export function serializeTask(task) {
310
+ return task.raw;
311
+ }
312
+ //# sourceMappingURL=taskManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taskManager.js","sourceRoot":"","sources":["../../src/core/taskManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { TestResult } from "../types/test.js";
2
+ /** A command to run: an executable plus its argument vector. */
3
+ export type TestCommand = [file: string, args: string[]];
4
+ /**
5
+ * Split a configured command string (e.g. `npm run test`) into a `[file, args]`
6
+ * tuple by whitespace. This is intentionally simple (no quote handling) — the
7
+ * MVP config commands are plain `npm …` invocations. An empty/blank string
8
+ * yields undefined so the caller can skip it.
9
+ */
10
+ export declare function splitCommand(command: string): TestCommand | undefined;
11
+ export interface RunTestsOptions {
12
+ /**
13
+ * Working directory to run each command in. Defaults to the process cwd, but
14
+ * the `step` command passes the resolved repository ROOT so tests run against
15
+ * the same tree the patch was applied to (the invocation cwd may be a
16
+ * subdirectory of the repo root).
17
+ */
18
+ cwd?: string;
19
+ }
20
+ /**
21
+ * Run each command in order, report-only. Resolves to one `TestResult` per
22
+ * command (in order). A non-zero exit is captured, not thrown; a command that
23
+ * cannot even be spawned (ENOENT) is reported with exitCode 127.
24
+ */
25
+ export declare function runTests(commands: TestCommand[], opts?: RunTestsOptions): Promise<TestResult[]>;
26
+ /** Format a single result as a `✓`/`✗` report line. */
27
+ export declare function formatTestResult(r: TestResult): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Test Runner (HLD-SRD §3.11) — report-only.
3
+ *
4
+ * Runs each configured command via `execa` with an explicit argv ARRAY (never a
5
+ * shell string) so there is no shell-injection surface, captures
6
+ * `{command, exitCode, stdout, stderr, durationMs}`, and prints a `✓`/`✗` line.
7
+ * It NEVER retries and NEVER rolls back an applied patch on failure — the
8
+ * auto-fix loop is 0001_06.
9
+ *
10
+ * A command is given as a `[file, args]` tuple; the caller (the `step` command)
11
+ * splits configured command strings into tuples (config carries strings like
12
+ * `npm test`; the splitter is a thin helper here so commands stay arg-array).
13
+ */
14
+ import { execa } from "execa";
15
+ /**
16
+ * Split a configured command string (e.g. `npm run test`) into a `[file, args]`
17
+ * tuple by whitespace. This is intentionally simple (no quote handling) — the
18
+ * MVP config commands are plain `npm …` invocations. An empty/blank string
19
+ * yields undefined so the caller can skip it.
20
+ */
21
+ export function splitCommand(command) {
22
+ const parts = command.trim().split(/\s+/).filter((p) => p.length > 0);
23
+ if (parts.length === 0)
24
+ return undefined;
25
+ return [parts[0], parts.slice(1)];
26
+ }
27
+ /**
28
+ * Run each command in order, report-only. Resolves to one `TestResult` per
29
+ * command (in order). A non-zero exit is captured, not thrown; a command that
30
+ * cannot even be spawned (ENOENT) is reported with exitCode 127.
31
+ */
32
+ export async function runTests(commands, opts = {}) {
33
+ const results = [];
34
+ for (const [file, args] of commands) {
35
+ const display = [file, ...args].join(" ");
36
+ const started = Date.now();
37
+ try {
38
+ const r = await execa(file, args, {
39
+ reject: false,
40
+ all: false,
41
+ stripFinalNewline: false,
42
+ ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
43
+ });
44
+ results.push({
45
+ command: display,
46
+ exitCode: r.exitCode ?? 1,
47
+ stdout: r.stdout ?? "",
48
+ stderr: r.stderr ?? "",
49
+ durationMs: Date.now() - started,
50
+ });
51
+ }
52
+ catch (err) {
53
+ // execa with reject:false should not throw on a non-zero exit; this is a
54
+ // spawn failure (e.g. the executable is missing).
55
+ results.push({
56
+ command: display,
57
+ exitCode: 127,
58
+ stdout: "",
59
+ stderr: err instanceof Error ? err.message : String(err),
60
+ durationMs: Date.now() - started,
61
+ });
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+ /** Format a single result as a `✓`/`✗` report line. */
67
+ export function formatTestResult(r) {
68
+ const mark = r.exitCode === 0 ? "✓" : "✗";
69
+ return `${mark} ${r.command} (exit ${r.exitCode}, ${r.durationMs}ms)`;
70
+ }
71
+ //# sourceMappingURL=testRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testRunner.js","sourceRoot":"","sources":["../../src/core/testRunner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAM9B;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAYD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,QAAuB,EACvB,OAAwB,EAAE;IAE1B,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE;gBAChC,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,KAAK;gBACV,iBAAiB,EAAE,KAAK;gBACxB,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACrD,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC;gBACX,OAAO,EAAE,OAAO;gBAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC;gBACzB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE;gBACtB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE;gBACtB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;aACjC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yEAAyE;YACzE,kDAAkD;YAClD,OAAO,CAAC,IAAI,CAAC;gBACX,OAAO,EAAE,OAAO;gBAChB,QAAQ,EAAE,GAAG;gBACb,MAAM,EAAE,EAAE;gBACV,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACxD,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;aACjC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,gBAAgB,CAAC,CAAa;IAC5C,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1C,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,KAAK,CAAC;AACxE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const CODER_SYSTEM = "You are a careful coding assistant working inside an existing codebase.\n\nRules:\n- Work in small, safe patches.\n- Do not rewrite unrelated code.\n- Preserve existing behavior unless explicitly instructed.\n- Prefer existing project patterns.\n- If context is insufficient, request exact files instead of guessing.\n- Do not modify risky areas unless explicitly required.\n- Return machine-parseable output when requested.\n\nYou are acting as the CODER. Implement ONLY the current subtask with the\nsmallest safe change. Do not refactor unrelated code and do not touch files\noutside the change.\n\nReturn your output in EXACTLY ONE of these two forms \u2014 nothing else, no prose,\nno markdown fences:\n\n1. A single unified diff in git format (the kind `git apply` accepts). Use\n `diff --git a/<path> b/<path>` headers, `---`/`+++` file lines, and\n `@@` hunks. Paths are repo-relative. This is the normal response.\n\n2. If \u2014 and only if \u2014 you cannot safely produce a patch without seeing more of\n the codebase, return a single JSON object instead of a diff:\n\n {\"status\": \"needs_context\", \"files\": [\"path/you/need.ts\"], \"reason\": \"why\"}\n\nNever invent file contents you have not been shown; prefer a `needs_context`\nrequest over guessing.";
2
+ /** Render the coder user message from an assembled context string. */
3
+ export declare function renderCoderUser(context: string): string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Coder prompt template (HLD-SRD §3.10, §10.1 base + §10.2 output contract).
3
+ *
4
+ * The system prompt embeds the §10.1 base coding rules and instructs the model,
5
+ * acting as the CODER, to make the smallest safe change for the current subtask
6
+ * and return its output in EXACTLY one of two machine-parseable shapes (§10.2):
7
+ * 1. a single unified diff (git format) and nothing else, OR
8
+ * 2. a `{ "status": "needs_context", ... }` JSON object when it cannot safely
9
+ * patch without more files.
10
+ *
11
+ * Returning a unified diff is the normal path; the `needs_context` object is the
12
+ * escape hatch so a context-starved model never guesses (HLD-SRD §10.1: "If
13
+ * context is insufficient, request exact files instead of guessing.").
14
+ */
15
+ import { CODER_SYSTEM_PROMPT } from "../core/prompts.js";
16
+ export const CODER_SYSTEM = `${CODER_SYSTEM_PROMPT}
17
+
18
+ You are acting as the CODER. Implement ONLY the current subtask with the
19
+ smallest safe change. Do not refactor unrelated code and do not touch files
20
+ outside the change.
21
+
22
+ Return your output in EXACTLY ONE of these two forms — nothing else, no prose,
23
+ no markdown fences:
24
+
25
+ 1. A single unified diff in git format (the kind \`git apply\` accepts). Use
26
+ \`diff --git a/<path> b/<path>\` headers, \`---\`/\`+++\` file lines, and
27
+ \`@@\` hunks. Paths are repo-relative. This is the normal response.
28
+
29
+ 2. If — and only if — you cannot safely produce a patch without seeing more of
30
+ the codebase, return a single JSON object instead of a diff:
31
+
32
+ {"status": "needs_context", "files": ["path/you/need.ts"], "reason": "why"}
33
+
34
+ Never invent file contents you have not been shown; prefer a \`needs_context\`
35
+ request over guessing.`;
36
+ /** Render the coder user message from an assembled context string. */
37
+ export function renderCoderUser(context) {
38
+ return `${context}
39
+
40
+ ---
41
+
42
+ Implement the current subtask now. Return ONLY a unified diff (git format) that
43
+ \`git apply\` can apply, OR a single {"status":"needs_context","files":[...],
44
+ "reason":"..."} JSON object if you need more files. No prose, no fences.`;
45
+ }
46
+ //# sourceMappingURL=coder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coder.js","sourceRoot":"","sources":["../../src/prompts/coder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,mBAAmB;;;;;;;;;;;;;;;;;;;uBAmB3B,CAAC;AAExB,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,GAAG,OAAO;;;;;;yEAMsD,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const PLANNER_SYSTEM = "You are a careful coding assistant working inside an existing codebase.\n\nRules:\n- Work in small, safe patches.\n- Do not rewrite unrelated code.\n- Preserve existing behavior unless explicitly instructed.\n- Prefer existing project patterns.\n- If context is insufficient, request exact files instead of guessing.\n- Do not modify risky areas unless explicitly required.\n- Return machine-parseable output when requested.\n\nYou are acting as the PLANNER. Decompose the user's task into a small, ordered\nlist of concrete subtasks that a coding model can execute one at a time. Order\nsubtasks by dependency. Prefer the smallest safe decomposition. Mark risky\nsubtasks (migrations, auth, billing, deletes, config) with a higher risk level.\n\nReturn ONLY a single JSON object \u2014 no prose, no markdown fences \u2014 with this shape:\n\n{\n \"summary\": \"one-paragraph plan summary\",\n \"subtasks\": [\n {\n \"id\": \"step-1\",\n \"title\": \"short imperative title\",\n \"description\": \"what to do and why\",\n \"risk\": \"low|medium|high\",\n \"likelyFiles\": [\"path/one.ts\"],\n \"acceptanceCriteria\": [\"observable done condition\"]\n }\n ],\n \"risks\": [\"overall risk note\"],\n \"questions\": [\"open question for the user\"]\n}";
2
+ /** Render the planner user message from an assembled context string. */
3
+ export declare function renderPlannerUser(context: string): string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Planner prompt template (HLD-SRD §3.9, §10.1).
3
+ *
4
+ * The system prompt embeds the §10.1 base coding rules and instructs the model
5
+ * to decompose the task into ordered subtasks. The user framing wraps the
6
+ * Context Builder's assembled context and demands ONLY the §3.9 planner JSON
7
+ * object — listing every required key so a local model returns a parseable shape.
8
+ */
9
+ import { CODER_SYSTEM_PROMPT } from "../core/prompts.js";
10
+ export const PLANNER_SYSTEM = `${CODER_SYSTEM_PROMPT}
11
+
12
+ You are acting as the PLANNER. Decompose the user's task into a small, ordered
13
+ list of concrete subtasks that a coding model can execute one at a time. Order
14
+ subtasks by dependency. Prefer the smallest safe decomposition. Mark risky
15
+ subtasks (migrations, auth, billing, deletes, config) with a higher risk level.
16
+
17
+ Return ONLY a single JSON object — no prose, no markdown fences — with this shape:
18
+
19
+ {
20
+ "summary": "one-paragraph plan summary",
21
+ "subtasks": [
22
+ {
23
+ "id": "step-1",
24
+ "title": "short imperative title",
25
+ "description": "what to do and why",
26
+ "risk": "low|medium|high",
27
+ "likelyFiles": ["path/one.ts"],
28
+ "acceptanceCriteria": ["observable done condition"]
29
+ }
30
+ ],
31
+ "risks": ["overall risk note"],
32
+ "questions": ["open question for the user"]
33
+ }`;
34
+ /** Render the planner user message from an assembled context string. */
35
+ export function renderPlannerUser(context) {
36
+ return `${context}
37
+
38
+ ---
39
+
40
+ Produce the plan now. Return ONLY the JSON object described in your
41
+ instructions, with the keys: summary, subtasks (each with id, title,
42
+ description, risk, likelyFiles, acceptanceCriteria), risks, questions.`;
43
+ }
44
+ //# sourceMappingURL=planner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.js","sourceRoot":"","sources":["../../src/prompts/planner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;EAuBlD,CAAC;AAEH,wEAAwE;AACxE,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,GAAG,OAAO;;;;;;uEAMoD,CAAC;AACxE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const REVIEWER_SYSTEM = "You are a careful coding assistant working inside an existing codebase.\n\nRules:\n- Work in small, safe patches.\n- Do not rewrite unrelated code.\n- Preserve existing behavior unless explicitly instructed.\n- Prefer existing project patterns.\n- If context is insufficient, request exact files instead of guessing.\n- Do not modify risky areas unless explicitly required.\n- Return machine-parseable output when requested.\n\nYou are acting as the REVIEWER. Review the provided unified diff against the task\nand session. Your review is ADVISORY ONLY: do NOT propose a patch, do NOT edit or\nrevert code \u2014 only describe what you find. Look for correctness/blocking issues,\nsmaller non-blocking suggestions, missing tests, and scope creep (changes outside\nthe stated task).\n\nReturn ONLY a single JSON object \u2014 no prose, no markdown fences \u2014 with this shape:\n\n{\n \"summary\": \"one-paragraph summary of the diff and your overall read\",\n \"blocking\": [\"issue that should block merge\"],\n \"nonBlocking\": [\"smaller suggestion that need not block\"],\n \"missingTests\": [\"behavior that should be tested but is not\"],\n \"scopeCreep\": [\"change outside the stated task scope\"],\n \"recommendation\": \"approve | request-changes | needs-discussion (with a short why)\"\n}";
2
+ /** Render the reviewer user message from an assembled context string. */
3
+ export declare function renderReviewerUser(context: string): string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Reviewer prompt template (HLD-SRD §3.12, §10.1).
3
+ *
4
+ * The system prompt embeds the §10.1 base coding rules and instructs the model,
5
+ * acting as the REVIEWER, to review a unified diff and return a single JSON
6
+ * report object (§3.12). Review is ADVISORY — the reviewer never edits, reverts,
7
+ * or blocks code; it only describes what it finds. The user framing wraps the
8
+ * Context Builder's assembled context (which already carries the diff) and
9
+ * demands ONLY the report JSON with every required key so a local model returns
10
+ * a parseable shape.
11
+ */
12
+ import { CODER_SYSTEM_PROMPT } from "../core/prompts.js";
13
+ export const REVIEWER_SYSTEM = `${CODER_SYSTEM_PROMPT}
14
+
15
+ You are acting as the REVIEWER. Review the provided unified diff against the task
16
+ and session. Your review is ADVISORY ONLY: do NOT propose a patch, do NOT edit or
17
+ revert code — only describe what you find. Look for correctness/blocking issues,
18
+ smaller non-blocking suggestions, missing tests, and scope creep (changes outside
19
+ the stated task).
20
+
21
+ Return ONLY a single JSON object — no prose, no markdown fences — with this shape:
22
+
23
+ {
24
+ "summary": "one-paragraph summary of the diff and your overall read",
25
+ "blocking": ["issue that should block merge"],
26
+ "nonBlocking": ["smaller suggestion that need not block"],
27
+ "missingTests": ["behavior that should be tested but is not"],
28
+ "scopeCreep": ["change outside the stated task scope"],
29
+ "recommendation": "approve | request-changes | needs-discussion (with a short why)"
30
+ }`;
31
+ /** Render the reviewer user message from an assembled context string. */
32
+ export function renderReviewerUser(context) {
33
+ return `${context}
34
+
35
+ ---
36
+
37
+ Review the diff above now. Return ONLY the JSON object described in your
38
+ instructions, with the keys: summary, blocking, nonBlocking, missingTests,
39
+ scopeCreep, recommendation. Do not propose or apply any patch.`;
40
+ }
41
+ //# sourceMappingURL=reviewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewer.js","sourceRoot":"","sources":["../../src/prompts/reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,mBAAmB;;;;;;;;;;;;;;;;;EAiBnD,CAAC;AAEH,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,GAAG,OAAO;;;;;;+DAM4C,CAAC;AAChE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const SUMMARIZER_SYSTEM = "You are a careful coding assistant working inside an existing codebase.\n\nRules:\n- Work in small, safe patches.\n- Do not rewrite unrelated code.\n- Preserve existing behavior unless explicitly instructed.\n- Prefer existing project patterns.\n- If context is insufficient, request exact files instead of guessing.\n- Do not modify risky areas unless explicitly required.\n- Return machine-parseable output when requested.\n\nYou are acting as the SUMMARIZER. Review the work done in this session and\nproduce a compact, structured summary for memory updates.\n\nReturn ONLY a single JSON object \u2014 no prose, no markdown fences \u2014 with this shape:\n\n{\n \"sessionUpdate\": {\n \"currentState\": \"one concise paragraph of what was accomplished\",\n \"filesChanged\": [\"list\", \"of\", \"files\", \"touched\"],\n \"decisions\": [\"brief decision taken\"],\n \"risks\": [\"brief risk or open issue\"]\n },\n \"memoryUpdates\": [\n {\n \"changeType\": \"<ONLY one of the allowed types listed below>\",\n \"content\": \"one concise line describing the change\"\n }\n ],\n \"nextStep\": \"the single most important next action\"\n}\n\nAllowed changeType values (ONLY these \u2014 the system uses this to determine which\nmemory file to update; you must NOT specify a file name):\n - file-responsibility \u2192 file ownership / responsibility changes\n - api-behavior \u2192 API contract or endpoint behavior changes\n - data-model \u2192 data structure or schema changes\n - architectural-decision \u2192 significant design decisions\n - external-integration \u2192 third-party or external service integration changes\n - testing-process \u2192 testing strategy or process changes\n - risk \u2192 risks, bugs found, or known issues\n\nRules:\n- Keep every entry CONCISE \u2014 one brief line per memoryUpdates entry.\n- Omit empty arrays (include only what applies).\n- Do NOT include a changeType outside the allowed list above.\n- Do NOT suggest a file path in your response.\n- If no Git diff is present, summarize task/session progress only.";
2
+ /** Render the summarizer user message from an assembled context string. */
3
+ export declare function renderSummarizerUser(context: string): string;