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,202 @@
1
+ /**
2
+ * Safety Manager — enforcement of HLD-SRD §11/§13 (pure).
3
+ *
4
+ * `evaluate(plan, config, root)` is a PURE function over a parsed `PatchPlan`
5
+ * plus config: no fs, no git, fully unit-testable. It returns a `SafetyVerdict`
6
+ * the `step` command acts on — `refuse` stops with the matching message;
7
+ * `allow` may carry `needsConfirm` prompts (`risky-path`, `delete`).
8
+ *
9
+ * Gate ordering (fail-fast, before any write — design.md "Safety gate ordering"):
10
+ * 1. binary patch → refuse
11
+ * 2. any path escaping root → refuse (§13)
12
+ * 3. any ignored/generated file → refuse (§3.10)
13
+ * 4. touched files > maxChangedFilesPerStep → refuse (§11)
14
+ * 5. any risky-path match (incl `.env*`) → needsConfirm += 'risky-path' (§13)
15
+ * 6. any delete → needsConfirm += 'delete' (§13)
16
+ *
17
+ * The git-dependent gates (working-tree-safe, `git apply --check`) live in the
18
+ * command because they need the repo; `evaluate` is deliberately git-free.
19
+ *
20
+ * Path normalization (so the risky/ignore globs match regardless of diff form):
21
+ * strip a leading `a/`/`b/` prefix, convert `\` to `/`, drop `./` and a leading
22
+ * `/`. Globs are matched dotfile-aware so `.env*` hits `.env` and `.env.local`.
23
+ */
24
+ import path from "node:path";
25
+ // ---------------------------------------------------------------------------
26
+ // Path normalization + glob matching
27
+ // ---------------------------------------------------------------------------
28
+ /** Repo-relative POSIX form for matching. The plan paths are ALREADY prefix-
29
+ * stripped by `parsePatch`, so this does NOT re-strip an `a/`/`b/` prefix (that
30
+ * would corrupt a real path whose first segment is literally `a`/`b`). Absolute
31
+ * markers are caught by the escape gate, which runs first. */
32
+ function normalize(p) {
33
+ let s = p.replace(/\\/g, "/").replace(/^\.\//, "");
34
+ while (s.startsWith("/"))
35
+ s = s.slice(1);
36
+ return s;
37
+ }
38
+ /** Lexical root-escape detection (mirrors patchManager's lexical checks). */
39
+ function escapesRootLexically(p) {
40
+ const raw = p.replace(/\\/g, "/");
41
+ if (path.isAbsolute(p) || /^[A-Za-z]:[\\/]/.test(p) || raw.startsWith("/")) {
42
+ return true;
43
+ }
44
+ const stripped = raw.replace(/^[ab]\//, "").replace(/^\.\//, "");
45
+ const normalized = path.posix.normalize(stripped);
46
+ return normalized === ".." || normalized.startsWith("../");
47
+ }
48
+ /**
49
+ * Convert a glob to a RegExp, dotfile-aware. Supports `**` (any path span,
50
+ * including across `/`), `*` (any run within a segment, including a leading
51
+ * dot), and `?`. A leading `**` followed by a slash may match zero leading
52
+ * segments so a `auth` glob matches `auth/x` as well as `src/auth/x`.
53
+ */
54
+ function globToRegExp(glob) {
55
+ const g = glob.replace(/\\/g, "/");
56
+ let re = "";
57
+ for (let i = 0; i < g.length; i++) {
58
+ const c = g[i];
59
+ if (c === "*") {
60
+ if (g[i + 1] === "*") {
61
+ // `**` — match any characters including `/`.
62
+ i += 1;
63
+ if (g[i + 1] === "/") {
64
+ // `**/` — also allow zero leading segments.
65
+ i += 1;
66
+ re += "(?:.*/)?";
67
+ }
68
+ else {
69
+ re += ".*";
70
+ }
71
+ }
72
+ else {
73
+ // `*` — any run within a segment (no `/`).
74
+ re += "[^/]*";
75
+ }
76
+ }
77
+ else if (c === "?") {
78
+ re += "[^/]";
79
+ }
80
+ else if (".+^${}()|[]\\".includes(c)) {
81
+ re += "\\" + c;
82
+ }
83
+ else {
84
+ re += c;
85
+ }
86
+ }
87
+ return new RegExp(`^${re}$`);
88
+ }
89
+ /**
90
+ * Secret/`.env*` patterns that are ALWAYS risky (HLD-SRD §13: no secret
91
+ * auto-modify) regardless of `config.safety.riskyPaths`. A user who customizes
92
+ * `riskyPaths` and drops `.env*` must still get the secret confirmation — the
93
+ * guarantee is enforced here, not left to config.
94
+ */
95
+ const ALWAYS_RISKY = [".env*", "**/.env*"];
96
+ /** Does any configured (or always-risky) glob match the normalized path? */
97
+ function isRiskyPath(rel, riskyPaths) {
98
+ const basename = rel.split("/").pop() ?? rel;
99
+ for (const glob of [...ALWAYS_RISKY, ...riskyPaths]) {
100
+ const re = globToRegExp(glob);
101
+ if (re.test(rel))
102
+ return true;
103
+ // A bare-name / basename glob (e.g. `.env*`) should also match the file's
104
+ // basename anywhere in the tree (e.g. `config/.env.local`).
105
+ if (!glob.includes("/") && re.test(basename))
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+ /** Ignore semantics shared with the indexer / patchManager. */
111
+ function isIgnored(rel, config) {
112
+ const parts = rel.split("/");
113
+ const basename = parts[parts.length - 1];
114
+ const baseline = new Set([
115
+ ".git",
116
+ "node_modules",
117
+ "dist",
118
+ "build",
119
+ ".next",
120
+ "coverage",
121
+ ".ai-orchestrator",
122
+ ]);
123
+ for (const seg of parts) {
124
+ if (baseline.has(seg))
125
+ return true;
126
+ }
127
+ for (const pat of config.ignore) {
128
+ if (rel === pat)
129
+ return true;
130
+ if (rel.startsWith(pat + "/"))
131
+ return true;
132
+ if (basename === pat)
133
+ return true;
134
+ if (parts.includes(pat))
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // evaluate
141
+ // ---------------------------------------------------------------------------
142
+ /**
143
+ * Evaluate a parsed plan against the safety config. Pure — `root` is accepted
144
+ * for parity with the wider seam but not dereferenced (no fs/git here).
145
+ */
146
+ export function evaluate(plan, config, _root) {
147
+ const reasons = [];
148
+ // 1. Binary.
149
+ if (plan.isBinary) {
150
+ return {
151
+ decision: "refuse",
152
+ needsConfirm: [],
153
+ reasons: ["Binary patch not supported (§13)."],
154
+ };
155
+ }
156
+ // 2. Path escape (§13).
157
+ for (const p of plan.touchedFiles) {
158
+ if (escapesRootLexically(p)) {
159
+ return {
160
+ decision: "refuse",
161
+ needsConfirm: [],
162
+ reasons: [`Path escapes the repository root: ${p} (§13).`],
163
+ };
164
+ }
165
+ }
166
+ // 3. Ignored / generated (§3.10).
167
+ for (const p of plan.touchedFiles) {
168
+ const rel = normalize(p);
169
+ if (isIgnored(rel, config)) {
170
+ return {
171
+ decision: "refuse",
172
+ needsConfirm: [],
173
+ reasons: [`Patch touches an ignored/generated file: ${rel} (§3.10).`],
174
+ };
175
+ }
176
+ }
177
+ // 4. Changed-file cap (§11).
178
+ if (plan.touchedFiles.length > config.safety.maxChangedFilesPerStep) {
179
+ return {
180
+ decision: "refuse",
181
+ needsConfirm: [],
182
+ reasons: [
183
+ `Too many changed files: ${plan.touchedFiles.length} > ` +
184
+ `maxChangedFilesPerStep (${config.safety.maxChangedFilesPerStep}) (§11).`,
185
+ ],
186
+ };
187
+ }
188
+ // 5. Risky path (§13) — second confirmation, never auto-approved.
189
+ const needsConfirm = [];
190
+ const riskyHit = plan.touchedFiles.some((p) => isRiskyPath(normalize(p), config.safety.riskyPaths));
191
+ if (riskyHit) {
192
+ needsConfirm.push("risky-path");
193
+ reasons.push("Patch touches a configured risky path (§13).");
194
+ }
195
+ // 6. Delete (§13) — explicit deletion confirmation.
196
+ if (plan.deletes.length > 0) {
197
+ needsConfirm.push("delete");
198
+ reasons.push("Patch deletes one or more files (§13).");
199
+ }
200
+ return { decision: "allow", needsConfirm, reasons };
201
+ }
202
+ //# sourceMappingURL=safetyManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safetyManager.js","sourceRoot":"","sources":["../../src/core/safetyManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { Session } from "../types/session.js";
2
+ import type { Task, TaskStatus } from "../types/task.js";
3
+ export interface CreateSessionOptions {
4
+ now?: Date;
5
+ }
6
+ /** Build a typed Session from a markdown string. */
7
+ export declare function parseSessionString(filePath: string, md: string): Session;
8
+ /**
9
+ * Create a session file for `task` under `sessionsDir`. Never overwrites: a
10
+ * filename collision appends `-2`, `-3`, …
11
+ */
12
+ export declare function createSession(sessionsDir: string, task: Task, opts?: CreateSessionOptions): Promise<Session>;
13
+ export interface SessionPatch {
14
+ status?: TaskStatus;
15
+ currentState?: string;
16
+ nextStep?: string;
17
+ /** Lines written into `## Risks / Not Verified`. */
18
+ risks?: string[];
19
+ /** Lines appended to `## Decisions`. */
20
+ decisions?: string[];
21
+ objective?: string;
22
+ }
23
+ /**
24
+ * Apply a section-scoped patch to a session and persist it. `risks` overwrites
25
+ * the Risks section; `decisions` is appended to the Decisions section, keeping
26
+ * any prior decisions. The `Updated:` line is refreshed.
27
+ */
28
+ export declare function updateSession(session: Session, patch: SessionPatch, opts?: CreateSessionOptions): Promise<Session>;
29
+ /** Read + parse a session from disk. */
30
+ export declare function loadSession(filePath: string): Promise<Session>;
31
+ /**
32
+ * List sessions newest-first (by mtime; name tiebreak). Each entry is a parsed
33
+ * Session carrying status + Next-Step preview. A missing dir → empty array.
34
+ */
35
+ export declare function listSessions(sessionsDir: string): Promise<Session[]>;
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Session Manager (HLD-SRD §3.6).
3
+ *
4
+ * Creates `/ai/sessions/YYYY-MM-DD_HHMM_slug_session.md` files paired to a task,
5
+ * applies section-scoped updates (Current State / Next Step / Risks / Decisions),
6
+ * loads a session back into a typed `Session`, and lists sessions newest-first
7
+ * with a Next-Step preview. Section edits preserve content elsewhere.
8
+ */
9
+ import { promises as fs } from "node:fs";
10
+ import path from "node:path";
11
+ import { ensureDir } from "../utils/fs.js";
12
+ // ---------------------------------------------------------------------------
13
+ // Timestamp helpers
14
+ // ---------------------------------------------------------------------------
15
+ function pad(n) {
16
+ return String(n).padStart(2, "0");
17
+ }
18
+ function dateStamp(d) {
19
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
20
+ }
21
+ function timeStamp(d) {
22
+ return `${pad(d.getHours())}${pad(d.getMinutes())}`;
23
+ }
24
+ function fullStamp(d) {
25
+ return `${dateStamp(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
26
+ }
27
+ // ---------------------------------------------------------------------------
28
+ // Section helpers (level-aware, shared shape with taskManager)
29
+ // ---------------------------------------------------------------------------
30
+ function replaceSection(md, heading, newBody) {
31
+ const lines = md.split(/\r?\n/);
32
+ const headingLc = heading.toLowerCase();
33
+ let start = -1;
34
+ let level = 0;
35
+ for (let i = 0; i < lines.length; i++) {
36
+ const m = /^(#{1,6})\s+(.*?)\s*$/.exec(lines[i]);
37
+ if (m && m[2].toLowerCase() === headingLc) {
38
+ start = i;
39
+ level = m[1].length;
40
+ break;
41
+ }
42
+ }
43
+ if (start === -1) {
44
+ const sep = md.length > 0 && !md.endsWith("\n") ? "\n" : "";
45
+ return `${md}${sep}\n## ${heading}\n${newBody}\n`;
46
+ }
47
+ let end = lines.length;
48
+ for (let i = start + 1; i < lines.length; i++) {
49
+ const m = /^(#{1,6})\s+/.exec(lines[i]);
50
+ if (m && m[1].length <= level) {
51
+ end = i;
52
+ break;
53
+ }
54
+ }
55
+ const headingLine = lines[start];
56
+ const before = lines.slice(0, start);
57
+ const after = lines.slice(end);
58
+ return [...before, headingLine, newBody, ...after].join("\n");
59
+ }
60
+ function sectionBody(md, heading) {
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 + 1;
69
+ level = m[1].length;
70
+ break;
71
+ }
72
+ }
73
+ if (start === -1)
74
+ return undefined;
75
+ const collected = [];
76
+ for (let i = start; i < lines.length; i++) {
77
+ const m = /^(#{1,6})\s+/.exec(lines[i]);
78
+ if (m && m[1].length <= level)
79
+ break;
80
+ collected.push(lines[i]);
81
+ }
82
+ const body = collected.join("\n").trim();
83
+ return body.length > 0 ? body : undefined;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Field parsing
87
+ // ---------------------------------------------------------------------------
88
+ function parseTaskRef(md) {
89
+ const m = /^Task:\s*(.*?)\s*$/m.exec(md);
90
+ return m?.[1] ?? "";
91
+ }
92
+ function parseStatus(md) {
93
+ const m = /^Status:\s*(active|done|blocked)\s*$/im.exec(md);
94
+ return m?.[1]?.toLowerCase() ?? "active";
95
+ }
96
+ function parseTitle(md) {
97
+ const m = /^#\s+Session:\s*(.*?)\s*$/m.exec(md);
98
+ return m?.[1] ?? "";
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Slug from task path
102
+ // ---------------------------------------------------------------------------
103
+ /** Strip the `YYYY-MM-DD_HHMM_` prefix and `.md` suffix from a task basename. */
104
+ function slugFromTaskPath(taskPath) {
105
+ const base = path.basename(taskPath).replace(/\.md$/i, "");
106
+ const m = /^\d{4}-\d{2}-\d{2}_\d{4}_(.*)$/.exec(base);
107
+ return m?.[1] ?? base;
108
+ }
109
+ function renderSessionFile(task, now) {
110
+ const stamp = fullStamp(now);
111
+ return [
112
+ `# Session: ${task.title}`,
113
+ "",
114
+ `Created: ${stamp}`,
115
+ `Updated: ${stamp}`,
116
+ `Task: ${task.path}`,
117
+ "Status: active",
118
+ "",
119
+ "## Objective",
120
+ task.goal,
121
+ "",
122
+ "## Current State",
123
+ "Not started.",
124
+ "",
125
+ "## Files Inspected",
126
+ "| File | Reason | Findings |",
127
+ "|---|---|---|",
128
+ "",
129
+ "## Changes Made",
130
+ "| File | Change |",
131
+ "|---|---|",
132
+ "",
133
+ "## Decisions",
134
+ "",
135
+ "## Risks / Not Verified",
136
+ "",
137
+ "## Tests",
138
+ "- Not run yet",
139
+ "",
140
+ "## Next Step",
141
+ "Run `localptp plan` to decompose the task.",
142
+ "",
143
+ ].join("\n");
144
+ }
145
+ /** Build a typed Session from a markdown string. */
146
+ export function parseSessionString(filePath, md) {
147
+ return {
148
+ path: filePath,
149
+ taskPath: parseTaskRef(md),
150
+ status: parseStatus(md),
151
+ objective: sectionBody(md, "Objective") ?? "",
152
+ currentState: sectionBody(md, "Current State") ?? "",
153
+ nextStep: sectionBody(md, "Next Step") ?? "",
154
+ raw: md,
155
+ };
156
+ }
157
+ /**
158
+ * Create a session file for `task` under `sessionsDir`. Never overwrites: a
159
+ * filename collision appends `-2`, `-3`, …
160
+ */
161
+ export async function createSession(sessionsDir, task, opts = {}) {
162
+ const now = opts.now ?? new Date();
163
+ await ensureDir(sessionsDir);
164
+ const slug = slugFromTaskPath(task.path);
165
+ const base = `${dateStamp(now)}_${timeStamp(now)}_${slug}_session`;
166
+ const content = renderSessionFile(task, now);
167
+ let attempt = 0;
168
+ for (;;) {
169
+ const name = attempt === 0 ? `${base}.md` : `${base}-${attempt + 1}.md`;
170
+ const full = path.join(sessionsDir, name);
171
+ try {
172
+ await fs.writeFile(full, content, { encoding: "utf8", flag: "wx" });
173
+ return parseSessionString(full, content);
174
+ }
175
+ catch (err) {
176
+ if (err.code === "EEXIST") {
177
+ attempt += 1;
178
+ continue;
179
+ }
180
+ throw err;
181
+ }
182
+ }
183
+ }
184
+ function asBullets(items) {
185
+ return items.map((i) => `- ${i}`).join("\n");
186
+ }
187
+ /**
188
+ * Apply a section-scoped patch to a session and persist it. `risks` overwrites
189
+ * the Risks section; `decisions` is appended to the Decisions section, keeping
190
+ * any prior decisions. The `Updated:` line is refreshed.
191
+ */
192
+ export async function updateSession(session, patch, opts = {}) {
193
+ const now = opts.now ?? new Date();
194
+ let raw = session.raw;
195
+ if (patch.objective !== undefined) {
196
+ raw = replaceSection(raw, "Objective", `\n${patch.objective}\n`);
197
+ }
198
+ if (patch.currentState !== undefined) {
199
+ raw = replaceSection(raw, "Current State", `\n${patch.currentState}\n`);
200
+ }
201
+ if (patch.nextStep !== undefined) {
202
+ raw = replaceSection(raw, "Next Step", `\n${patch.nextStep}\n`);
203
+ }
204
+ if (patch.risks !== undefined) {
205
+ const body = patch.risks.length > 0 ? `\n${asBullets(patch.risks)}\n` : "\n";
206
+ raw = replaceSection(raw, "Risks / Not Verified", body);
207
+ }
208
+ if (patch.decisions !== undefined && patch.decisions.length > 0) {
209
+ const existing = sectionBody(raw, "Decisions");
210
+ const merged = existing
211
+ ? `${existing}\n${asBullets(patch.decisions)}`
212
+ : asBullets(patch.decisions);
213
+ raw = replaceSection(raw, "Decisions", `\n${merged}\n`);
214
+ }
215
+ if (patch.status !== undefined) {
216
+ raw = raw.replace(/^Status:\s*.*$/m, `Status: ${patch.status}`);
217
+ }
218
+ // Refresh the Updated: line.
219
+ raw = raw.replace(/^Updated:\s*.*$/m, `Updated: ${fullStamp(now)}`);
220
+ await fs.writeFile(session.path, raw, "utf8");
221
+ return parseSessionString(session.path, raw);
222
+ }
223
+ /** Read + parse a session from disk. */
224
+ export async function loadSession(filePath) {
225
+ const md = await fs.readFile(filePath, "utf8");
226
+ return parseSessionString(filePath, md);
227
+ }
228
+ /**
229
+ * List sessions newest-first (by mtime; name tiebreak). Each entry is a parsed
230
+ * Session carrying status + Next-Step preview. A missing dir → empty array.
231
+ */
232
+ export async function listSessions(sessionsDir) {
233
+ let entries;
234
+ try {
235
+ entries = await fs.readdir(sessionsDir, { withFileTypes: true });
236
+ }
237
+ catch {
238
+ return [];
239
+ }
240
+ const items = [];
241
+ for (const entry of entries) {
242
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md"))
243
+ continue;
244
+ const full = path.join(sessionsDir, entry.name);
245
+ try {
246
+ const stat = await fs.stat(full);
247
+ const md = await fs.readFile(full, "utf8");
248
+ items.push({
249
+ session: parseSessionString(full, md),
250
+ mtimeMs: stat.mtimeMs,
251
+ name: entry.name,
252
+ });
253
+ }
254
+ catch {
255
+ // Skip unreadable entries.
256
+ }
257
+ }
258
+ items.sort((a, b) => {
259
+ if (b.mtimeMs !== a.mtimeMs)
260
+ return b.mtimeMs - a.mtimeMs;
261
+ return b.name.localeCompare(a.name);
262
+ });
263
+ return items.map((i) => i.session);
264
+ }
265
+ //# sourceMappingURL=sessionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionManager.js","sourceRoot":"","sources":["../../src/core/sessionManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Stop conditions for the `run` loop (HLD-SRD §11.2; 0001_06).
3
+ *
4
+ * `decideStop` is a PURE function over the latest `StepOutcome`, the loop state,
5
+ * and the config. It returns a named `StopReason` to stop, or `null` to
6
+ * continue. Keeping it pure makes every §11.2 condition independently
7
+ * unit-testable with scripted outcomes and keeps the loop driver thin.
8
+ *
9
+ * Decision order (design.md "decideStop"):
10
+ * 1. a stopReason bubbled up from `runStep` (safety refusal, approval denied,
11
+ * unparseable, patch-invalid, repeated-failure, …) — return it verbatim;
12
+ * 2. no pending subtask AND nothing applied → `acceptance-met` (the MVP
13
+ * acceptance heuristic: no pending subtasks remain);
14
+ * 3. tests still failing AND the fix-attempt budget is exhausted →
15
+ * `repeated-failure` (a safety net; `runStep` normally sets this itself,
16
+ * and it also covers the `maxFailedFixAttempts = 0` boundary);
17
+ * 4. otherwise → `null` (continue).
18
+ *
19
+ * `iteration-cap-exceeded` is produced by the loop driver on cap fall-through,
20
+ * not here; but if a caller ever sets it as a stopReason, branch 1 bubbles it.
21
+ */
22
+ import type { AppConfig } from "../types/config.js";
23
+ import type { LoopState, StepOutcome, StopReason } from "../types/run.js";
24
+ export declare function decideStop(outcome: StepOutcome, _state: LoopState, config: AppConfig): StopReason | null;
@@ -0,0 +1,18 @@
1
+ function testsFailing(outcome) {
2
+ return outcome.testResults.some((r) => r.exitCode !== 0);
3
+ }
4
+ export function decideStop(outcome, _state, config) {
5
+ // 1. A stop reason bubbled up from the step core wins.
6
+ if (outcome.stopReason !== undefined)
7
+ return outcome.stopReason;
8
+ // 2. No pending subtask and nothing applied → acceptance met.
9
+ if (outcome.subtaskId === null && !outcome.applied)
10
+ return "acceptance-met";
11
+ // 3. Tests still failing after the fix budget is exhausted → repeated failure.
12
+ if (testsFailing(outcome) && outcome.fixAttempts >= config.safety.maxFailedFixAttempts) {
13
+ return "repeated-failure";
14
+ }
15
+ // 4. Continue.
16
+ return null;
17
+ }
18
+ //# sourceMappingURL=stopConditions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stopConditions.js","sourceRoot":"","sources":["../../src/core/stopConditions.ts"],"names":[],"mappings":"AAwBA,SAAS,YAAY,CAAC,OAAoB;IACxC,OAAO,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,OAAoB,EACpB,MAAiB,EACjB,MAAiB;IAEjB,uDAAuD;IACvD,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,UAAU,CAAC;IAEhE,8DAA8D;IAC9D,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,OAAO,gBAAgB,CAAC;IAE5E,+EAA+E;IAC/E,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC;QACvF,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,eAAe;IACf,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { Task, Subtask } from "../types/task.js";
2
+ /**
3
+ * Slugify free task text: drop diacritics, lower-case, keep [a-z0-9], collapse
4
+ * runs to single hyphens, take the first ~6 words. Empty result → `task`.
5
+ */
6
+ export declare function slugify(text: string): string;
7
+ export interface CreateTaskOptions {
8
+ /** Injectable clock for deterministic tests. */
9
+ now?: Date;
10
+ }
11
+ /**
12
+ * Create a new task file under `tasksDir`. Never overwrites: on a filename
13
+ * collision (same timestamp+slug) appends `-2`, `-3`, … Returns the parsed Task.
14
+ */
15
+ export declare function createTask(tasksDir: string, text: string, opts?: CreateTaskOptions): Promise<Task>;
16
+ /** Parse a task markdown string into a typed Task. */
17
+ export declare function parseTaskString(filePath: string, md: string): Task;
18
+ /** Read + parse a task file from disk. */
19
+ export declare function parseTask(filePath: string): Promise<Task>;
20
+ /**
21
+ * Return a Task whose `## Subtasks` block (in `raw`) is rewritten to `subtasks`,
22
+ * preserving all other content. Pure: callers persist via `serializeTask`.
23
+ */
24
+ export declare function setSubtasks(task: Task, subtasks: Subtask[]): Task;
25
+ /** The markdown to persist for a Task (its `raw`). */
26
+ export declare function serializeTask(task: Task): string;