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,229 @@
1
+ /**
2
+ * Review Engine (HLD-SRD §3.12; 0001_06).
3
+ *
4
+ * Powers the advisory `review` command: collect the current Git diff, build a
5
+ * reviewer context, ask the model to review it, and tolerantly parse the JSON
6
+ * report — falling back to the raw text on a parse failure so a local model's
7
+ * imperfect output never blocks the human. The engine NEVER edits, reverts, or
8
+ * applies code; it only reads the diff and returns a report.
9
+ *
10
+ * The tolerant extractor mirrors the 0001_04 planner extractor: strict
11
+ * whole-output parse, else the FIRST balanced `{...}` block, validated against
12
+ * `reviewReportSchema` (whose array fields default `[]`). A total failure
13
+ * returns `null`, and the caller prints the raw review.
14
+ */
15
+ import { simpleGit } from "simple-git";
16
+ import { buildContext } from "./contextBuilder.js";
17
+ import { getPrompt } from "./promptManager.js";
18
+ import { loadMemoryFiles } from "./memoryLoader.js";
19
+ import { resolveActive } from "./activePointer.js";
20
+ import { parseTask } from "./taskManager.js";
21
+ import { loadSession } from "./sessionManager.js";
22
+ import { ConfigManager } from "./configManager.js";
23
+ import { layout } from "../utils/paths.js";
24
+ import { readIfExists } from "../utils/fs.js";
25
+ import { repoIndexSchema } from "../types/index.js";
26
+ import { parseActiveTask, parseActiveSession } from "../types/context.js";
27
+ import { reviewReportSchema } from "../types/review.js";
28
+ import path from "node:path";
29
+ /**
30
+ * Find the first balanced `{...}` substring, tracking string/escape state so
31
+ * braces inside strings do not unbalance the count. Mirrors the 0001_04
32
+ * planner extractor.
33
+ */
34
+ function firstBalancedObject(raw) {
35
+ const start = raw.indexOf("{");
36
+ if (start === -1)
37
+ return undefined;
38
+ let depth = 0;
39
+ let inString = false;
40
+ let escaped = false;
41
+ for (let i = start; i < raw.length; i++) {
42
+ const ch = raw[i];
43
+ if (inString) {
44
+ if (escaped)
45
+ escaped = false;
46
+ else if (ch === "\\")
47
+ escaped = true;
48
+ else if (ch === '"')
49
+ inString = false;
50
+ continue;
51
+ }
52
+ if (ch === '"')
53
+ inString = true;
54
+ else if (ch === "{")
55
+ depth += 1;
56
+ else if (ch === "}") {
57
+ depth -= 1;
58
+ if (depth === 0)
59
+ return raw.slice(start, i + 1);
60
+ }
61
+ }
62
+ return undefined;
63
+ }
64
+ /**
65
+ * Tolerantly extract + validate a review report from raw model output. Returns
66
+ * the parsed report, or `null` when nothing parseable + valid is found (the
67
+ * caller then prints the raw text).
68
+ */
69
+ export function tolerantParseReviewReport(raw) {
70
+ // 1. Strict whole-output parse.
71
+ try {
72
+ const obj = JSON.parse(raw);
73
+ if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
74
+ const r = reviewReportSchema.safeParse(obj);
75
+ if (r.success)
76
+ return r.data;
77
+ }
78
+ }
79
+ catch {
80
+ // fall through
81
+ }
82
+ // 2. First balanced {...} block.
83
+ const block = firstBalancedObject(raw);
84
+ if (block === undefined)
85
+ return null;
86
+ try {
87
+ const obj = JSON.parse(block);
88
+ const r = reviewReportSchema.safeParse(obj);
89
+ return r.success ? r.data : null;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ const EMPTY_INDEX = {
96
+ generatedAt: "",
97
+ root: "",
98
+ files: [],
99
+ };
100
+ async function loadIndex(orchestratorDir) {
101
+ const raw = await readIfExists(path.join(orchestratorDir, "index.json"));
102
+ if (raw === undefined)
103
+ return EMPTY_INDEX;
104
+ try {
105
+ const parsed = repoIndexSchema.safeParse(JSON.parse(raw));
106
+ return parsed.success ? parsed.data : EMPTY_INDEX;
107
+ }
108
+ catch {
109
+ return EMPTY_INDEX;
110
+ }
111
+ }
112
+ /**
113
+ * The current working-tree diff for the repo at `root`: staged + unstaged
114
+ * changes to tracked files (`git diff HEAD`). Returns "" when nothing changed
115
+ * or the directory is not a git repo.
116
+ */
117
+ export async function currentDiff(root) {
118
+ const git = simpleGit(root);
119
+ try {
120
+ if (!(await git.checkIsRepo()))
121
+ return "";
122
+ // `git diff HEAD` captures both staged and unstaged tracked changes.
123
+ return await git.diff(["HEAD"]);
124
+ }
125
+ catch {
126
+ // No commits yet, or git error — fall back to the plain working-tree diff.
127
+ try {
128
+ return await git.diff();
129
+ }
130
+ catch {
131
+ return "";
132
+ }
133
+ }
134
+ }
135
+ /** Parse the changed-file paths from a unified diff (`diff --git a/x b/x`). */
136
+ export function changedFilesOf(diff) {
137
+ const files = new Set();
138
+ const re = /^diff --git a\/(.+?) b\/(.+)$/gm;
139
+ let m;
140
+ while ((m = re.exec(diff)) !== null) {
141
+ files.add(m[2]);
142
+ }
143
+ return [...files];
144
+ }
145
+ /**
146
+ * Build the reviewer system + user prompts. The Context Builder assembles the
147
+ * task/session/memory; the diff (which it does not model) is appended to the
148
+ * user prompt so the reviewer sees exactly what changed.
149
+ */
150
+ export function buildReviewPrompt(inputs) {
151
+ const pkg = buildContext({
152
+ role: "reviewer",
153
+ config: inputs.config,
154
+ index: inputs.index,
155
+ memory: inputs.memory,
156
+ ...(inputs.taskRaw !== undefined ? { task: parseActiveTask(inputs.taskRaw) } : {}),
157
+ ...(inputs.sessionRaw !== undefined
158
+ ? { session: parseActiveSession(inputs.sessionRaw) }
159
+ : {}),
160
+ fileContents: {},
161
+ });
162
+ const changed = changedFilesOf(inputs.diff);
163
+ const reviewer = getPrompt("reviewer");
164
+ const userContext = pkg.userPrompt +
165
+ `\n\n## Changed files\n\n${changed.length > 0 ? changed.map((f) => `- ${f}`).join("\n") : "(none parsed)"}` +
166
+ `\n\n## Diff under review\n\n\`\`\`diff\n${inputs.diff}\n\`\`\``;
167
+ return {
168
+ systemPrompt: reviewer.system,
169
+ userPrompt: reviewer.renderUser(userContext),
170
+ };
171
+ }
172
+ /**
173
+ * Run the review: collect the diff, call the model, tolerantly parse the report.
174
+ * Returns `{ hadChanges: false }` when the diff is empty (the caller prints "No
175
+ * changes to review" and exits 0). A model error propagates to the caller.
176
+ */
177
+ export async function runReviewEngine(deps) {
178
+ const { detectGitRoot } = await import("../utils/gitRoot.js");
179
+ const git = await detectGitRoot(deps.cwd);
180
+ const root = git.root ?? deps.cwd;
181
+ const l = layout(root);
182
+ const diff = await currentDiff(root);
183
+ if (diff.trim().length === 0) {
184
+ return { hadChanges: false };
185
+ }
186
+ const config = await new ConfigManager(l.configFile).load();
187
+ const index = await loadIndex(l.orchestratorDir);
188
+ const memory = await loadMemoryFiles(root);
189
+ // Active task/session are optional context — review works without them.
190
+ let taskRaw;
191
+ let sessionRaw;
192
+ const active = await resolveActive(l.orchestratorDir);
193
+ if (active.kind === "ok") {
194
+ try {
195
+ taskRaw = (await parseTask(active.pointer.taskPath)).raw;
196
+ }
197
+ catch {
198
+ // ignore
199
+ }
200
+ try {
201
+ sessionRaw = (await loadSession(active.pointer.sessionPath)).raw;
202
+ }
203
+ catch {
204
+ // ignore
205
+ }
206
+ }
207
+ const { systemPrompt, userPrompt } = buildReviewPrompt({
208
+ config,
209
+ index,
210
+ memory,
211
+ ...(taskRaw !== undefined ? { taskRaw } : {}),
212
+ ...(sessionRaw !== undefined ? { sessionRaw } : {}),
213
+ diff,
214
+ });
215
+ // A §12 ModelClientError propagates unchanged — the caller maps it to a
216
+ // non-zero exit with the connectivity guidance.
217
+ const response = await deps.client.complete({
218
+ role: "reviewer",
219
+ systemPrompt,
220
+ userPrompt,
221
+ });
222
+ const report = tolerantParseReviewReport(response.content);
223
+ return {
224
+ hadChanges: true,
225
+ ...(report !== null ? { report } : {}),
226
+ raw: response.content,
227
+ };
228
+ }
229
+ //# sourceMappingURL=reviewEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewEngine.js","sourceRoot":"","sources":["../../src/core/reviewEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { AppConfig } from "../types/config.js";
2
+ import type { LoopState, StepOutcome, StopReason } from "../types/run.js";
3
+ /** Extra iterations beyond the pending count before the hard cap trips. */
4
+ export declare const ITERATION_SLACK = 5;
5
+ export interface RunLoopDeps {
6
+ /** Run one step; resolves to its outcome or throws on an initial-patch refusal. */
7
+ step: () => Promise<StepOutcome>;
8
+ config: AppConfig;
9
+ /** The number of pending subtasks at loop start (sets the cap). */
10
+ pendingCount: number;
11
+ /** Output sink (defaults to process.stdout.write); injectable for tests. */
12
+ print?: (line: string) => void;
13
+ }
14
+ export interface RunLoopResult {
15
+ /** The reason the loop stopped. */
16
+ stopReason: StopReason;
17
+ /** The accumulated loop state at stop. */
18
+ state: LoopState;
19
+ /** The number of steps that applied a patch. */
20
+ applied: number;
21
+ }
22
+ /** Map a thrown initial-patch error to the matching stop reason. */
23
+ export declare function errorToStopReason(err: unknown): StopReason;
24
+ /** A human-readable progress line for one completed step. */
25
+ export declare function progressLine(state: LoopState, outcome: StepOutcome): string;
26
+ export declare function runLoop(deps: RunLoopDeps): Promise<RunLoopResult>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * `run` loop driver (HLD-SRD §3.13, §11.2; 0001_06).
3
+ *
4
+ * Loops the shared step core over the active task's pending subtasks, printing a
5
+ * per-step progress line, consulting the pure `decideStop` after each step, and
6
+ * enforcing a HARD iteration cap (`pending + slack`) as an infinite-loop guard
7
+ * on top of `decideStop`. The actual step runner is injected so the loop is
8
+ * fully offline-testable with a scripted core.
9
+ *
10
+ * Initial-patch refusals THROW from the step core (preserving the 0001_05 `step`
11
+ * behavior); the loop catches them and maps them to the matching `StopReason`
12
+ * so a single `step` and a looped `run` report stop conditions identically.
13
+ */
14
+ import { decideStop } from "./stopConditions.js";
15
+ import { ModelClientError } from "../types/model.js";
16
+ import { CommandError, } from "./runStep.js";
17
+ import { PatchValidationError, PatchApplyError, WorkingTreeUnsafeError, } from "./patchManager.js";
18
+ /** Extra iterations beyond the pending count before the hard cap trips. */
19
+ export const ITERATION_SLACK = 5;
20
+ /** Map a thrown initial-patch error to the matching stop reason. */
21
+ export function errorToStopReason(err) {
22
+ if (err instanceof ModelClientError)
23
+ return "model-unavailable";
24
+ if (err instanceof WorkingTreeUnsafeError)
25
+ return "unsafe-tree";
26
+ if (err instanceof PatchValidationError || err instanceof PatchApplyError) {
27
+ return "patch-invalid";
28
+ }
29
+ if (err instanceof CommandError) {
30
+ const m = err.message.toLowerCase();
31
+ if (m.includes("did not return a valid unified diff"))
32
+ return "unparseable-output";
33
+ if (m.includes("too many changed files"))
34
+ return "broad-rewrite-requested";
35
+ if (m.includes("mid-merge") || m.includes("working tree"))
36
+ return "unsafe-tree";
37
+ if (m.includes("refusing to apply"))
38
+ return "risky-change";
39
+ return "patch-invalid";
40
+ }
41
+ // Unknown error — surface as a generic patch-invalid stop (the loop stops,
42
+ // the message is printed by the caller). Re-throwing would crash the run.
43
+ return "patch-invalid";
44
+ }
45
+ /** A human-readable progress line for one completed step. */
46
+ export function progressLine(state, outcome) {
47
+ const subtask = outcome.subtaskId ?? "(none)";
48
+ const applied = outcome.applied ? "applied" : "no-op";
49
+ const tests = outcome.testResults.length;
50
+ const failed = outcome.testResults.filter((r) => r.exitCode !== 0).length;
51
+ const testPart = tests === 0
52
+ ? "no tests"
53
+ : failed === 0
54
+ ? `${tests} test(s) passing`
55
+ : `${failed}/${tests} test(s) failing`;
56
+ const fixPart = outcome.fixAttempts > 0 ? `, ${outcome.fixAttempts} fix attempt(s)` : "";
57
+ return `Step ${state.iterations + 1}: ${subtask} — ${applied}, ${testPart}${fixPart}`;
58
+ }
59
+ export async function runLoop(deps) {
60
+ const print = deps.print ?? ((line) => process.stdout.write(line));
61
+ const state = { iterations: 0, consecutiveFailures: 0, filesChanged: 0 };
62
+ const cap = deps.pendingCount + ITERATION_SLACK;
63
+ let applied = 0;
64
+ while (state.iterations < cap) {
65
+ let outcome;
66
+ try {
67
+ outcome = await deps.step();
68
+ }
69
+ catch (err) {
70
+ const reason = errorToStopReason(err);
71
+ const message = err instanceof Error ? err.message : String(err);
72
+ print(`${message}\n`);
73
+ print(`Stopping run — ${reason}\n`);
74
+ return { stopReason: reason, state, applied };
75
+ }
76
+ print(progressLine(state, outcome) + "\n");
77
+ if (outcome.applied)
78
+ applied += 1;
79
+ const reason = decideStop(outcome, state, deps.config);
80
+ if (reason !== null) {
81
+ if (reason === "acceptance-met") {
82
+ print("Acceptance criteria met — no pending subtasks remain.\n");
83
+ print("Recommend: run `localptp summarize` (0001_07) to close the session.\n");
84
+ }
85
+ else {
86
+ print(`Stopping run — ${reason}\n`);
87
+ }
88
+ return { stopReason: reason, state, applied };
89
+ }
90
+ // Continue: advance the loop bookkeeping.
91
+ state.iterations += 1;
92
+ if (outcome.testResults.some((r) => r.exitCode !== 0)) {
93
+ state.consecutiveFailures += 1;
94
+ }
95
+ else {
96
+ state.consecutiveFailures = 0;
97
+ }
98
+ }
99
+ // Fell through the cap without a decideStop reason — the hard guard.
100
+ print(`Stopping run — iteration-cap-exceeded\n`);
101
+ return { stopReason: "iteration-cap-exceeded", state, applied };
102
+ }
103
+ //# sourceMappingURL=runLoop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runLoop.js","sourceRoot":"","sources":["../../src/core/runLoop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAI3B,2EAA2E;AAC3E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAqBjC,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,IAAI,GAAG,YAAY,gBAAgB;QAAE,OAAO,mBAAmB,CAAC;IAChE,IAAI,GAAG,YAAY,sBAAsB;QAAE,OAAO,aAAa,CAAC;IAChE,IAAI,GAAG,YAAY,oBAAoB,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;QAC1E,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,QAAQ,CAAC,qCAAqC,CAAC;YAAE,OAAO,oBAAoB,CAAC;QACnF,IAAI,CAAC,CAAC,QAAQ,CAAC,wBAAwB,CAAC;YAAE,OAAO,yBAAyB,CAAC;QAC3E,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC;YAAE,OAAO,aAAa,CAAC;QAChF,IAAI,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YAAE,OAAO,cAAc,CAAC;QAC3D,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,2EAA2E;IAC3E,0EAA0E;IAC1E,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAgB,EAAE,OAAoB;IACjE,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAC;IAC9C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;IACtD,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC;IACzC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC1E,MAAM,QAAQ,GACZ,KAAK,KAAK,CAAC;QACT,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,MAAM,KAAK,CAAC;YACZ,CAAC,CAAC,GAAG,KAAK,kBAAkB;YAC5B,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,kBAAkB,CAAC;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,WAAW,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;IACzF,OAAO,QAAQ,KAAK,CAAC,UAAU,GAAG,CAAC,KAAK,OAAO,MAAM,OAAO,KAAK,QAAQ,GAAG,OAAO,EAAE,CAAC;AACxF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAiB;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAc,EAAE,UAAU,EAAE,CAAC,EAAE,mBAAmB,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IACpF,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,GAAG,eAAe,CAAC;IAChD,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,OAAO,KAAK,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC;QAC9B,IAAI,OAAoB,CAAC;QACzB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;YACtB,KAAK,CAAC,kBAAkB,MAAM,IAAI,CAAC,CAAC;YACpC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAChD,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,CAAC;QAElC,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACvD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,IAAI,MAAM,KAAK,gBAAgB,EAAE,CAAC;gBAChC,KAAK,CAAC,yDAAyD,CAAC,CAAC;gBACjE,KAAK,CACH,uEAAuE,CACxE,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,kBAAkB,MAAM,IAAI,CAAC,CAAC;YACtC,CAAC;YACD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAChD,CAAC;QAED,0CAA0C;QAC1C,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;QACtB,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YACtD,KAAK,CAAC,mBAAmB,IAAI,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,mBAAmB,GAAG,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,KAAK,CAAC,yCAAyC,CAAC,CAAC;IACjD,OAAO,EAAE,UAAU,EAAE,wBAAwB,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAClE,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { type Approver } from "./approval.js";
2
+ import type { ModelClient } from "../types/model.js";
3
+ import type { AppConfig } from "../types/config.js";
4
+ import type { StepOutcome } from "../types/run.js";
5
+ /** A command error that carries an exit code (preserves 0001_05 `step` behavior). */
6
+ export declare class CommandError extends Error {
7
+ readonly exitCode: number;
8
+ constructor(message: string, exitCode?: number);
9
+ }
10
+ export interface NeedsContext {
11
+ files: string[];
12
+ reason: string;
13
+ }
14
+ export interface StepDeps {
15
+ cwd: string;
16
+ /** Injectable for tests; defaults to the real LM Studio client. */
17
+ clientFactory?: (config: AppConfig) => ModelClient;
18
+ /** Injectable approval seam; defaults to a TTY yes/no prompt. */
19
+ approve?: Approver;
20
+ /** Injectable clock for deterministic patch artifact names. */
21
+ now?: Date;
22
+ }
23
+ /**
24
+ * The canonical step result: the structured `StepOutcome` (read by the run loop)
25
+ * plus the extra fields the `step` command surfaces (`done`, `needsContext`,
26
+ * `patchPath`).
27
+ */
28
+ export interface StepCoreResult extends StepOutcome {
29
+ /** True when there was no pending subtask (task done). */
30
+ done: boolean;
31
+ /** Set when the model returned a `needs_context` request instead of a diff. */
32
+ needsContext?: NeedsContext;
33
+ /** The saved patch artifact path of the coder patch (when applied). */
34
+ patchPath?: string;
35
+ }
36
+ /**
37
+ * Detect a `needs_context` response. Tolerant of fences/prose: scans for a JSON
38
+ * object carrying `"status":"needs_context"` and parses it. Returns the request
39
+ * (files + reason), or undefined when the response is not a needs_context.
40
+ */
41
+ export declare function parseNeedsContext(raw: string): NeedsContext | undefined;
42
+ export declare function runStep(deps: StepDeps): Promise<StepCoreResult>;