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,75 @@
1
+ /**
2
+ * `localptp resume` (HLD-SRD §3.6; CLI.md).
3
+ *
4
+ * Pure filesystem operation (no model call). Lists sessions newest-first with
5
+ * status + Next-Step preview, selects one (a 1-based index arg, else a stdin
6
+ * number under TTY), loads it, writes the active pointer, and prints the Next
7
+ * Step. No sessions → friendly message, exit zero. An out-of-range index, or a
8
+ * non-TTY run with no index, is an actionable error (non-zero exit) that
9
+ * activates nothing.
10
+ */
11
+ import { detectGitRoot } from "../utils/gitRoot.js";
12
+ import { layout } from "../utils/paths.js";
13
+ import { listSessions } from "../core/sessionManager.js";
14
+ import { writeActive } from "../core/activePointer.js";
15
+ class CommandError extends Error {
16
+ exitCode;
17
+ constructor(message, exitCode = 1) {
18
+ super(message);
19
+ this.name = "CommandError";
20
+ this.exitCode = exitCode;
21
+ }
22
+ }
23
+ export async function runResume(opts) {
24
+ const git = await detectGitRoot(opts.cwd);
25
+ const root = git.root ?? opts.cwd;
26
+ const l = layout(root);
27
+ const sessions = await listSessions(l.sessionsDir);
28
+ if (sessions.length === 0) {
29
+ // Friendly, exit zero.
30
+ return { sessions };
31
+ }
32
+ // Resolve the selection (1-based).
33
+ let index = opts.index;
34
+ if (index === undefined) {
35
+ const isTTY = opts.isTTY ?? Boolean(process.stdin.isTTY);
36
+ if (isTTY && opts.prompt) {
37
+ index = await opts.prompt(sessions);
38
+ }
39
+ if (index === undefined) {
40
+ throw new CommandError(`Specify a session index 1–${sessions.length}, e.g. \`localptp resume 1\`.`);
41
+ }
42
+ }
43
+ if (!Number.isInteger(index) || index < 1 || index > sessions.length) {
44
+ throw new CommandError(`Invalid selection: ${index}. Choose an index 1–${sessions.length}.`);
45
+ }
46
+ const selected = sessions[index - 1];
47
+ await writeActive(l.orchestratorDir, {
48
+ taskPath: selected.taskPath,
49
+ sessionPath: selected.path,
50
+ });
51
+ return { sessions, selected };
52
+ }
53
+ function shortStamp(sessionPath) {
54
+ const base = sessionPath.replace(/\\/g, "/").split("/").pop() ?? "";
55
+ const m = /^(\d{4}-\d{2}-\d{2})_(\d{4})/.exec(base);
56
+ return m ? `${m[1]}_${m[2]}` : base;
57
+ }
58
+ function nextPreview(session) {
59
+ const ns = session.nextStep.split(/\r?\n/)[0]?.trim() ?? "";
60
+ return ns.length > 0 ? ns : "(none)";
61
+ }
62
+ export function formatResumeResult(result) {
63
+ if (result.sessions.length === 0) {
64
+ return 'No sessions yet. Create a task with `localptp task "…"`.';
65
+ }
66
+ const lines = [];
67
+ result.sessions.forEach((s, i) => {
68
+ lines.push(` ${i + 1}) ${shortStamp(s.path)} (${s.status}, next: ${nextPreview(s)})`);
69
+ });
70
+ if (result.selected) {
71
+ lines.push(`Loaded. Next step: ${nextPreview(result.selected)}`);
72
+ }
73
+ return lines.join("\n");
74
+ }
75
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../src/commands/resume.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { AppConfig } from "../types/config.js";
2
+ import type { ModelClient } from "../types/model.js";
3
+ import type { ReviewReport } from "../types/review.js";
4
+ export interface ReviewOptions {
5
+ cwd: string;
6
+ json?: boolean;
7
+ /** Injectable for tests; defaults to the real LM Studio client. */
8
+ clientFactory?: (config: AppConfig) => ModelClient;
9
+ }
10
+ export interface ReviewResult {
11
+ hadChanges: boolean;
12
+ report?: ReviewReport;
13
+ raw?: string;
14
+ json: boolean;
15
+ }
16
+ export declare function runReview(opts: ReviewOptions): Promise<ReviewResult>;
17
+ export declare function formatReviewResult(result: ReviewResult): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `localptp review` (HLD-SRD §3.12; 0001_06).
3
+ *
4
+ * Advisory diff review: build a reviewer context over the current Git diff, ask
5
+ * the model to review it, tolerantly parse the structured report (summary /
6
+ * blocking / non-blocking / missing tests / scope creep / recommendation) and
7
+ * print it — falling back to the raw output on a parse failure. It MODIFIES NO
8
+ * CODE: it never edits, reverts, or applies anything.
9
+ *
10
+ * Exit behavior:
11
+ * - empty diff → "No changes to review", exit 0;
12
+ * - a §12 ModelClientError propagates (the CLI maps it to a non-zero exit with
13
+ * the connectivity guidance).
14
+ */
15
+ import { LmStudioClient } from "../core/modelClient.js";
16
+ import { ConfigManager } from "../core/configManager.js";
17
+ import { runReviewEngine } from "../core/reviewEngine.js";
18
+ import { detectGitRoot } from "../utils/gitRoot.js";
19
+ import { layout } from "../utils/paths.js";
20
+ function defaultClientFactory(config) {
21
+ return new LmStudioClient({
22
+ baseUrl: config.model.baseUrl,
23
+ model: config.model.model,
24
+ apiKey: config.model.apiKey,
25
+ temperature: config.model.temperature,
26
+ timeoutMs: config.model.timeoutMs,
27
+ });
28
+ }
29
+ export async function runReview(opts) {
30
+ const git = await detectGitRoot(opts.cwd);
31
+ const root = git.root ?? opts.cwd;
32
+ const l = layout(root);
33
+ const config = await new ConfigManager(l.configFile).load();
34
+ const client = (opts.clientFactory ?? defaultClientFactory)(config);
35
+ const result = await runReviewEngine({ cwd: opts.cwd, client });
36
+ return {
37
+ hadChanges: result.hadChanges,
38
+ ...(result.report !== undefined ? { report: result.report } : {}),
39
+ ...(result.raw !== undefined ? { raw: result.raw } : {}),
40
+ json: opts.json ?? false,
41
+ };
42
+ }
43
+ function bulletList(label, items) {
44
+ if (items.length === 0)
45
+ return `${label}: (none)`;
46
+ return `${label}:\n${items.map((i) => ` - ${i}`).join("\n")}`;
47
+ }
48
+ export function formatReviewResult(result) {
49
+ if (!result.hadChanges) {
50
+ return "No changes to review.";
51
+ }
52
+ if (result.report) {
53
+ const r = result.report;
54
+ return [
55
+ "=== Review ===",
56
+ `Summary: ${r.summary || "(none)"}`,
57
+ bulletList("Blocking", r.blocking),
58
+ bulletList("Non-blocking", r.nonBlocking),
59
+ bulletList("Missing tests", r.missingTests),
60
+ bulletList("Scope creep", r.scopeCreep),
61
+ `Recommendation: ${r.recommendation || "(none)"}`,
62
+ ].join("\n");
63
+ }
64
+ // Unparseable — print the raw review verbatim (never blocks).
65
+ return ["=== Review (unparsed) ===", result.raw ?? ""].join("\n");
66
+ }
67
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type Approver } from "../core/approval.js";
2
+ import type { AppConfig } from "../types/config.js";
3
+ import type { ModelClient } from "../types/model.js";
4
+ import type { StopReason } from "../types/run.js";
5
+ export interface RunOptions {
6
+ cwd: string;
7
+ json?: boolean;
8
+ /** Injectable for tests; defaults to the real LM Studio client. */
9
+ clientFactory?: (config: AppConfig) => ModelClient;
10
+ /** Injectable approval seam; defaults to a TTY yes/no prompt. */
11
+ approve?: Approver;
12
+ /** Injectable clock for deterministic patch artifact names. */
13
+ now?: Date;
14
+ }
15
+ export interface RunResult {
16
+ stopReason: StopReason;
17
+ /** Number of steps that applied a patch. */
18
+ applied: number;
19
+ /** Number of iterations the loop completed (continued past). */
20
+ iterations: number;
21
+ json: boolean;
22
+ }
23
+ export declare function run(opts: RunOptions): Promise<RunResult>;
24
+ export declare function formatRunResult(result: RunResult): string;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `localptp run` (HLD-SRD §3.13, §11.2; 0001_06).
3
+ *
4
+ * Automates the daily loop: it drives the shared `runStep` core over the active
5
+ * task's pending subtasks via the `runLoop` driver, pausing for approval at each
6
+ * diff (the core owns approval), printing a per-step progress line, and stopping
7
+ * on the first §11.2 stop condition with a clear reason. The step runner is the
8
+ * SAME core `step` uses, so one-shot and looped execution behave identically.
9
+ *
10
+ * `run` never re-implements the patch pipeline and never runs `summarize`; on
11
+ * acceptance it only RECOMMENDS summarizing (0001_07 owns the command).
12
+ */
13
+ import { detectGitRoot } from "../utils/gitRoot.js";
14
+ import { layout } from "../utils/paths.js";
15
+ import { ConfigManager } from "../core/configManager.js";
16
+ import { resolveActive } from "../core/activePointer.js";
17
+ import { parseTask } from "../core/taskManager.js";
18
+ import { runStep, CommandError } from "../core/runStep.js";
19
+ import { runLoop } from "../core/runLoop.js";
20
+ import { ttyApprove } from "../core/approval.js";
21
+ export async function run(opts) {
22
+ const approve = opts.approve ?? ttyApprove;
23
+ const git = await detectGitRoot(opts.cwd);
24
+ const root = git.root ?? opts.cwd;
25
+ const l = layout(root);
26
+ // Resolve the active task to count pending subtasks (sets the iteration cap).
27
+ const active = await resolveActive(l.orchestratorDir);
28
+ if (active.kind === "none") {
29
+ throw new CommandError('No active task. Create one first with `localptp task "…"`.');
30
+ }
31
+ if (active.kind === "missing-target") {
32
+ throw new CommandError(`The active pointer references a missing file: ${active.missing.join(", ")}. ` +
33
+ 'Create a new task with `localptp task "…"` or pick another with `localptp resume`.');
34
+ }
35
+ const config = await new ConfigManager(l.configFile).load();
36
+ const task = await parseTask(active.pointer.taskPath);
37
+ const pendingCount = task.subtasks.filter((s) => s.status === "pending").length;
38
+ const result = await runLoop({
39
+ config,
40
+ pendingCount,
41
+ step: () => runStep({
42
+ cwd: opts.cwd,
43
+ ...(opts.clientFactory !== undefined ? { clientFactory: opts.clientFactory } : {}),
44
+ approve,
45
+ ...(opts.now !== undefined ? { now: opts.now } : {}),
46
+ }),
47
+ });
48
+ return {
49
+ stopReason: result.stopReason,
50
+ applied: result.applied,
51
+ iterations: result.state.iterations,
52
+ json: opts.json ?? false,
53
+ };
54
+ }
55
+ export function formatRunResult(result) {
56
+ const lines = [
57
+ `Run stopped: ${result.stopReason}.`,
58
+ `Applied ${result.applied} patch(es) over ${result.iterations} step(s).`,
59
+ ];
60
+ if (result.stopReason === "acceptance-met") {
61
+ lines.push("Recommend: `localptp summarize` (0001_07) to close the session.");
62
+ }
63
+ return lines.join("\n");
64
+ }
65
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `localptp step` (HLD-SRD §3.10, §3.11, §3.13, §10.2, §11, §12.3, §13).
3
+ *
4
+ * A thin wrapper over the shared `runStep` core (`src/core/runStep.ts`): it runs
5
+ * ONE pending subtask end-to-end through the core state machine, then adapts the
6
+ * core's `StepCoreResult` to the command-facing `StepResult` for the CLI. The
7
+ * highest-stakes code (the patch apply pipeline + the bounded test-fix loop)
8
+ * lives in the core so `step` (one-shot) and `run` (looped) share identical
9
+ * behavior.
10
+ */
11
+ import { parseNeedsContext, CommandError, type NeedsContext } from "../core/runStep.js";
12
+ import type { Approver } from "../core/approval.js";
13
+ import type { AppConfig } from "../types/config.js";
14
+ import type { ModelClient } from "../types/model.js";
15
+ import type { TestResult } from "../types/test.js";
16
+ export { CommandError, parseNeedsContext };
17
+ export type { NeedsContext };
18
+ export interface StepOptions {
19
+ cwd: string;
20
+ json?: boolean;
21
+ /** Injectable for tests; defaults to the real LM Studio client. */
22
+ clientFactory?: (config: AppConfig) => ModelClient;
23
+ /** Injectable approval seam; defaults to a TTY yes/no prompt. */
24
+ approve?: Approver;
25
+ /** Injectable clock for deterministic patch artifact names. */
26
+ now?: Date;
27
+ }
28
+ export interface StepResult {
29
+ /** True once the patch was applied to the working tree. */
30
+ applied: boolean;
31
+ /** True when there was no pending subtask (task done). */
32
+ done: boolean;
33
+ /** Set when the model returned a `needs_context` request instead of a diff. */
34
+ needsContext?: NeedsContext;
35
+ /** The subtask this step ran (when one was pending). */
36
+ subtaskId?: string;
37
+ /** The saved patch artifact path (when applied). */
38
+ patchPath?: string;
39
+ /** Captured test results (empty when no tests configured or nothing applied). */
40
+ testResults: TestResult[];
41
+ json: boolean;
42
+ }
43
+ export declare function runStep(opts: StepOptions): Promise<StepResult>;
44
+ export declare function formatStepResult(result: StepResult): string;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `localptp step` (HLD-SRD §3.10, §3.11, §3.13, §10.2, §11, §12.3, §13).
3
+ *
4
+ * A thin wrapper over the shared `runStep` core (`src/core/runStep.ts`): it runs
5
+ * ONE pending subtask end-to-end through the core state machine, then adapts the
6
+ * core's `StepCoreResult` to the command-facing `StepResult` for the CLI. The
7
+ * highest-stakes code (the patch apply pipeline + the bounded test-fix loop)
8
+ * lives in the core so `step` (one-shot) and `run` (looped) share identical
9
+ * behavior.
10
+ */
11
+ import { runStep as runStepCore, parseNeedsContext, CommandError, } from "../core/runStep.js";
12
+ import { formatTestResult } from "../core/testRunner.js";
13
+ export { CommandError, parseNeedsContext };
14
+ export async function runStep(opts) {
15
+ const deps = {
16
+ cwd: opts.cwd,
17
+ ...(opts.clientFactory !== undefined ? { clientFactory: opts.clientFactory } : {}),
18
+ ...(opts.approve !== undefined ? { approve: opts.approve } : {}),
19
+ ...(opts.now !== undefined ? { now: opts.now } : {}),
20
+ };
21
+ const outcome = await runStepCore(deps);
22
+ return {
23
+ applied: outcome.applied,
24
+ done: outcome.done,
25
+ ...(outcome.needsContext !== undefined ? { needsContext: outcome.needsContext } : {}),
26
+ ...(outcome.subtaskId !== null ? { subtaskId: outcome.subtaskId } : {}),
27
+ ...(outcome.patchPath !== undefined ? { patchPath: outcome.patchPath } : {}),
28
+ testResults: outcome.testResults,
29
+ json: opts.json ?? false,
30
+ };
31
+ }
32
+ export function formatStepResult(result) {
33
+ if (result.done) {
34
+ return "No pending subtasks — the task is complete.";
35
+ }
36
+ if (result.needsContext) {
37
+ return `Model needs more context: ${result.needsContext.reason}`;
38
+ }
39
+ if (!result.applied) {
40
+ return "Nothing applied.";
41
+ }
42
+ const lines = [`Applied ${result.subtaskId}.`];
43
+ if (result.patchPath)
44
+ lines.push(`Patch saved: ${result.patchPath}`);
45
+ for (const r of result.testResults) {
46
+ lines.push(formatTestResult(r));
47
+ }
48
+ return lines.join("\n");
49
+ }
50
+ //# sourceMappingURL=step.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step.js","sourceRoot":"","sources":["../../src/commands/step.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,OAAO,IAAI,WAAW,EACtB,iBAAiB,EACjB,YAAY,GAGb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAMzD,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAC;AA8B3C,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAiB;IAC7C,MAAM,IAAI,GAAa;QACrB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrD,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,CAAC,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrF,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5E,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;KACzB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,OAAO,6CAA6C,CAAC;IACvD,CAAC;IACD,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,OAAO,6BAA6B,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;IACnE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,WAAW,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;IAC/C,IAAI,MAAM,CAAC,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IACrE,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ModelClient } from "../types/model.js";
2
+ import type { AppConfig } from "../types/config.js";
3
+ import type { Session } from "../types/session.js";
4
+ export interface SummarizeOptions {
5
+ cwd: string;
6
+ json?: boolean;
7
+ /** Injectable for tests; defaults to the real LM Studio client. */
8
+ clientFactory?: (config: AppConfig) => ModelClient;
9
+ }
10
+ export interface SummarizeResult {
11
+ session: Session;
12
+ updatedFiles: string[];
13
+ ignored: string[];
14
+ json: boolean;
15
+ }
16
+ export declare function runSummarize(opts: SummarizeOptions): Promise<SummarizeResult>;
17
+ export declare function formatSummarizeResult(result: SummarizeResult): string;
@@ -0,0 +1,276 @@
1
+ /**
2
+ * `localptp summarize` (HLD-SRD §3.4, §3.9, §12, §13; CLI.md; 0001_07).
3
+ *
4
+ * Closes the daily loop by folding finished work into durable `/ai` memory.
5
+ *
6
+ * Flow (side-effect ordering: session BEFORE memory, so a later memory-append
7
+ * failure leaves a re-runnable partial):
8
+ * 1. resolveActive() — none → error, non-zero exit.
9
+ * 2. Load config + task + session + memory + optional Git diff.
10
+ * 3. buildContext({ role: 'summarizer', task, session }).
11
+ * 4. ModelClient.complete(summarizer system + rendered user).
12
+ * §12 ModelClientError → propagates unchanged; NOTHING written.
13
+ * 5. tolerantExtract(content, summarizerSchema).
14
+ * fail → minimal session-only note + warning, non-zero exit; NO memory
15
+ * writes.
16
+ * 6. updateSession(sessionUpdate, nextStep) ← SESSION FIRST.
17
+ * 7. For each memoryUpdate: normalize(changeType) → POLICY target file →
18
+ * appendMemoryEntry(). Out-of-table changeType → ignored + warning.
19
+ * 8. Return { session, updatedFiles, ignored, json }.
20
+ *
21
+ * Security: the model NEVER picks the target file. The code resolves it from
22
+ * the POLICY table using the declared changeType (§13).
23
+ */
24
+ import path from "node:path";
25
+ import { detectGitRoot } from "../utils/gitRoot.js";
26
+ import { layout } from "../utils/paths.js";
27
+ import { readIfExists } from "../utils/fs.js";
28
+ import { ConfigManager } from "../core/configManager.js";
29
+ import { loadMemoryFiles } from "../core/memoryLoader.js";
30
+ import { buildContext } from "../core/contextBuilder.js";
31
+ import { resolveActive } from "../core/activePointer.js";
32
+ import { parseTask } from "../core/taskManager.js";
33
+ import { loadSession, updateSession } from "../core/sessionManager.js";
34
+ import { getPrompt } from "../core/promptManager.js";
35
+ import { LmStudioClient } from "../core/modelClient.js";
36
+ import { POLICY, normalize, headingFor } from "../core/memoryPolicy.js";
37
+ import { appendMemoryEntry } from "../core/memoryManager.js";
38
+ import { summarizerSchema } from "../types/summary.js";
39
+ import { parseActiveTask, parseActiveSession } from "../types/context.js";
40
+ import { repoIndexSchema } from "../types/index.js";
41
+ import { simpleGit } from "simple-git";
42
+ // ---------------------------------------------------------------------------
43
+ // Helpers
44
+ // ---------------------------------------------------------------------------
45
+ class CommandError extends Error {
46
+ exitCode;
47
+ constructor(message, exitCode = 1) {
48
+ super(message);
49
+ this.name = "CommandError";
50
+ this.exitCode = exitCode;
51
+ }
52
+ }
53
+ function defaultClientFactory(config) {
54
+ return new LmStudioClient({
55
+ baseUrl: config.model.baseUrl,
56
+ model: config.model.model,
57
+ apiKey: config.model.apiKey,
58
+ temperature: config.model.temperature,
59
+ timeoutMs: config.model.timeoutMs,
60
+ });
61
+ }
62
+ const EMPTY_INDEX = {
63
+ generatedAt: "",
64
+ root: "",
65
+ files: [],
66
+ };
67
+ async function loadIndex(orchestratorDir) {
68
+ const raw = await readIfExists(path.join(orchestratorDir, "index.json"));
69
+ if (raw === undefined)
70
+ return EMPTY_INDEX;
71
+ try {
72
+ const parsed = repoIndexSchema.safeParse(JSON.parse(raw));
73
+ return parsed.success ? parsed.data : EMPTY_INDEX;
74
+ }
75
+ catch {
76
+ return EMPTY_INDEX;
77
+ }
78
+ }
79
+ /** Current working-tree diff: "" when not a git repo or no changes. */
80
+ async function currentDiff(root) {
81
+ const git = simpleGit(root);
82
+ try {
83
+ if (!(await git.checkIsRepo()))
84
+ return "";
85
+ return await git.diff(["HEAD"]);
86
+ }
87
+ catch {
88
+ try {
89
+ return await git.diff();
90
+ }
91
+ catch {
92
+ return "";
93
+ }
94
+ }
95
+ }
96
+ /** First balanced `{...}` substring — string/escape aware. */
97
+ function firstBalancedObject(raw) {
98
+ const start = raw.indexOf("{");
99
+ if (start === -1)
100
+ return undefined;
101
+ let depth = 0;
102
+ let inString = false;
103
+ let escaped = false;
104
+ for (let i = start; i < raw.length; i++) {
105
+ const ch = raw[i];
106
+ if (inString) {
107
+ if (escaped)
108
+ escaped = false;
109
+ else if (ch === "\\")
110
+ escaped = true;
111
+ else if (ch === '"')
112
+ inString = false;
113
+ continue;
114
+ }
115
+ if (ch === '"')
116
+ inString = true;
117
+ else if (ch === "{")
118
+ depth += 1;
119
+ else if (ch === "}") {
120
+ depth -= 1;
121
+ if (depth === 0)
122
+ return raw.slice(start, i + 1);
123
+ }
124
+ }
125
+ return undefined;
126
+ }
127
+ /**
128
+ * Tolerantly extract a SummarizerOutput from raw model output.
129
+ * Returns null when no parseable + valid structure is found.
130
+ */
131
+ function tolerantExtractSummary(raw) {
132
+ // 1. Strict whole-output parse.
133
+ try {
134
+ const obj = JSON.parse(raw);
135
+ if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
136
+ const r = summarizerSchema.safeParse(obj);
137
+ if (r.success)
138
+ return r.data;
139
+ }
140
+ }
141
+ catch {
142
+ // fall through
143
+ }
144
+ // 2. First balanced {...} block.
145
+ const block = firstBalancedObject(raw);
146
+ if (block === undefined)
147
+ return null;
148
+ try {
149
+ const obj = JSON.parse(block);
150
+ const r = summarizerSchema.safeParse(obj);
151
+ return r.success ? r.data : null;
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ export async function runSummarize(opts) {
158
+ const git = await detectGitRoot(opts.cwd);
159
+ const root = git.root ?? opts.cwd;
160
+ const l = layout(root);
161
+ // 1. Resolve the active session (required).
162
+ const active = await resolveActive(l.orchestratorDir);
163
+ if (active.kind === "none") {
164
+ throw new CommandError('No active session. Create or resume a task first with `localptp task "…"` or `localptp resume`.');
165
+ }
166
+ if (active.kind === "missing-target") {
167
+ throw new CommandError(`The active pointer references a missing file: ${active.missing.join(", ")}. ` +
168
+ "Create a new task with `localptp task \"…\"` or pick another with `localptp resume`.");
169
+ }
170
+ const { taskPath, sessionPath } = active.pointer;
171
+ // 2. Load config, task, session, memory, and the optional Git diff.
172
+ const config = await new ConfigManager(l.configFile).load();
173
+ const task = await parseTask(taskPath);
174
+ const session = await loadSession(sessionPath);
175
+ const index = await loadIndex(l.orchestratorDir);
176
+ const memory = await loadMemoryFiles(root);
177
+ const diff = await currentDiff(root);
178
+ // 3. Build the summarizer context.
179
+ // Include the diff as additional context in the user prompt.
180
+ const pkg = buildContext({
181
+ role: "summarizer",
182
+ config,
183
+ index,
184
+ memory,
185
+ task: parseActiveTask(task.raw),
186
+ session: parseActiveSession(session.raw),
187
+ fileContents: {},
188
+ });
189
+ // Augment the user prompt with the diff (optional).
190
+ const diffSection = diff.trim().length > 0
191
+ ? `\n\n## Git Diff\n\n\`\`\`diff\n${diff}\n\`\`\``
192
+ : "\n\n## Git Diff\n\n(No uncommitted changes detected — summarize task/session progress only.)";
193
+ const summarizer = getPrompt("summarizer");
194
+ const userPromptWithDiff = pkg.userPrompt + diffSection;
195
+ // 4. Call the model — §12 ModelClientError propagates; NOTHING written yet.
196
+ const client = (opts.clientFactory ?? defaultClientFactory)(config);
197
+ const response = await client.complete({
198
+ role: "summarizer",
199
+ systemPrompt: summarizer.system,
200
+ userPrompt: summarizer.renderUser(userPromptWithDiff),
201
+ });
202
+ // 5. Tolerantly parse the response.
203
+ const summary = tolerantExtractSummary(response.content);
204
+ if (summary === null) {
205
+ // Minimal session-only update + warning + non-zero exit; NO memory writes.
206
+ const failedSession = await updateSession(session, {
207
+ currentState: "summary attempted; model output unparseable",
208
+ });
209
+ process.stderr.write("warning: summarize: model output could not be parsed into a valid summary. " +
210
+ "Session updated with a minimal note; memory files were NOT written.\n");
211
+ const err = new CommandError("summarize: model output could not be parsed into a valid summary. " +
212
+ "Session updated with a minimal note; no memory files were written.");
213
+ // Attach the partial result for callers that catch and inspect.
214
+ err.session = failedSession;
215
+ err.updatedFiles = [];
216
+ err.ignored = [];
217
+ err.json = opts.json ?? false;
218
+ throw err;
219
+ }
220
+ // 6. SESSION FIRST — update the session before any memory writes.
221
+ const updatedSession = await updateSession(session, {
222
+ currentState: summary.sessionUpdate.currentState,
223
+ nextStep: summary.nextStep,
224
+ decisions: summary.sessionUpdate.decisions,
225
+ risks: summary.sessionUpdate.risks,
226
+ });
227
+ // 7. Apply memory updates via the POLICY table.
228
+ const updatedFiles = [];
229
+ const ignored = [];
230
+ for (const update of summary.memoryUpdates) {
231
+ const key = normalize(update.changeType);
232
+ if (key === undefined) {
233
+ process.stderr.write(`warning: summarize: ignored update for unknown change type '${update.changeType}'\n`);
234
+ ignored.push(update.changeType);
235
+ continue;
236
+ }
237
+ const targetFile = POLICY[key];
238
+ const heading = headingFor(key);
239
+ if (targetFile === undefined || heading === undefined) {
240
+ // Should not happen if POLICY and HEADINGS are in sync — defensive.
241
+ process.stderr.write(`warning: summarize: no policy entry for key '${key}' (change type '${update.changeType}')\n`);
242
+ ignored.push(update.changeType);
243
+ continue;
244
+ }
245
+ const fullPath = l.memoryFile(targetFile);
246
+ await appendMemoryEntry(fullPath, heading, update.content);
247
+ if (!updatedFiles.includes(targetFile)) {
248
+ updatedFiles.push(targetFile);
249
+ }
250
+ }
251
+ // 8. Return the result.
252
+ return {
253
+ session: updatedSession,
254
+ updatedFiles,
255
+ ignored,
256
+ json: opts.json ?? false,
257
+ };
258
+ }
259
+ // ---------------------------------------------------------------------------
260
+ // Formatting
261
+ // ---------------------------------------------------------------------------
262
+ export function formatSummarizeResult(result) {
263
+ const parts = [];
264
+ if (result.updatedFiles.length > 0) {
265
+ parts.push(`Updated: ${result.updatedFiles.join(", ")}`);
266
+ }
267
+ else {
268
+ parts.push("No memory files updated.");
269
+ }
270
+ parts.push(`Session: ${result.session.status}`);
271
+ if (result.ignored.length > 0) {
272
+ parts.push(`Ignored (unknown change types): ${result.ignored.join(", ")}`);
273
+ }
274
+ return parts.join("; ");
275
+ }
276
+ //# sourceMappingURL=summarize.js.map