@popoverai/dotrequirements 0.22.0 → 0.24.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 (230) hide show
  1. package/README.md +173 -23
  2. package/dist/cli.js +121 -61
  3. package/dist/codebase-to-spec/budget.d.ts +53 -0
  4. package/dist/codebase-to-spec/budget.js +80 -0
  5. package/dist/codebase-to-spec/cache.d.ts +49 -0
  6. package/dist/codebase-to-spec/cache.js +54 -0
  7. package/dist/codebase-to-spec/claude.d.ts +69 -0
  8. package/dist/codebase-to-spec/claude.js +126 -0
  9. package/dist/codebase-to-spec/compose.d.ts +49 -0
  10. package/dist/codebase-to-spec/compose.js +124 -0
  11. package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
  12. package/dist/codebase-to-spec/edit-loop.js +195 -0
  13. package/dist/codebase-to-spec/editor.d.ts +54 -0
  14. package/dist/codebase-to-spec/editor.js +74 -0
  15. package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
  16. package/dist/codebase-to-spec/exit-codes.js +58 -0
  17. package/dist/codebase-to-spec/fan-out.d.ts +63 -0
  18. package/dist/codebase-to-spec/fan-out.js +215 -0
  19. package/dist/codebase-to-spec/interactive.d.ts +30 -0
  20. package/dist/codebase-to-spec/interactive.js +48 -0
  21. package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
  22. package/dist/codebase-to-spec/outline-review-loop.js +187 -0
  23. package/dist/codebase-to-spec/pack.d.ts +51 -0
  24. package/dist/codebase-to-spec/pack.js +127 -0
  25. package/dist/codebase-to-spec/planner.d.ts +41 -0
  26. package/dist/codebase-to-spec/planner.js +76 -0
  27. package/dist/codebase-to-spec/present.d.ts +94 -0
  28. package/dist/codebase-to-spec/present.js +288 -0
  29. package/dist/codebase-to-spec/progress.d.ts +33 -0
  30. package/dist/codebase-to-spec/progress.js +28 -0
  31. package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
  32. package/dist/codebase-to-spec/prompts/editor.js +57 -0
  33. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
  34. package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
  35. package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
  36. package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
  37. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
  38. package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
  39. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
  40. package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
  41. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
  42. package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
  43. package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
  44. package/dist/codebase-to-spec/prompts/specifier.js +100 -0
  45. package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
  46. package/dist/codebase-to-spec/prompts/style-check.js +78 -0
  47. package/dist/codebase-to-spec/schemas.d.ts +257 -0
  48. package/dist/codebase-to-spec/schemas.js +183 -0
  49. package/dist/codebase-to-spec/skill-install.d.ts +57 -0
  50. package/dist/codebase-to-spec/skill-install.js +79 -0
  51. package/dist/codebase-to-spec/slice.d.ts +49 -0
  52. package/dist/codebase-to-spec/slice.js +111 -0
  53. package/dist/codebase-to-spec/specifier.d.ts +60 -0
  54. package/dist/codebase-to-spec/specifier.js +79 -0
  55. package/dist/codebase-to-spec/style-check.d.ts +29 -0
  56. package/dist/codebase-to-spec/style-check.js +33 -0
  57. package/dist/codebase-to-spec/summary.d.ts +51 -0
  58. package/dist/codebase-to-spec/summary.js +183 -0
  59. package/dist/codebase-to-spec/validate.d.ts +46 -0
  60. package/dist/codebase-to-spec/validate.js +130 -0
  61. package/dist/commands/acceptance-test.d.ts +6 -0
  62. package/dist/commands/acceptance-test.js +212 -0
  63. package/dist/commands/ai-setup.d.ts +5 -0
  64. package/dist/commands/ai-setup.js +441 -0
  65. package/dist/commands/browsertest.d.ts +0 -1
  66. package/dist/commands/browsertest.js +51 -26
  67. package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
  68. package/dist/commands/codebase-to-spec/compose.js +57 -0
  69. package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
  70. package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
  71. package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
  72. package/dist/commands/codebase-to-spec/fan-out.js +77 -0
  73. package/dist/commands/codebase-to-spec/index.d.ts +9 -0
  74. package/dist/commands/codebase-to-spec/index.js +135 -0
  75. package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
  76. package/dist/commands/codebase-to-spec/pack.js +76 -0
  77. package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
  78. package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
  79. package/dist/commands/codebase-to-spec/present.d.ts +21 -0
  80. package/dist/commands/codebase-to-spec/present.js +92 -0
  81. package/dist/commands/codebase-to-spec/run.d.ts +20 -0
  82. package/dist/commands/codebase-to-spec/run.js +85 -0
  83. package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
  84. package/dist/commands/codebase-to-spec/skill-install.js +51 -0
  85. package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
  86. package/dist/commands/codebase-to-spec/specify-area.js +82 -0
  87. package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
  88. package/dist/commands/codebase-to-spec/style-check.js +42 -0
  89. package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
  90. package/dist/commands/codebase-to-spec/validate.js +38 -0
  91. package/dist/commands/create-requirement-document.d.ts +2 -0
  92. package/dist/commands/create-requirement-document.js +41 -0
  93. package/dist/commands/finalize.js +7 -7
  94. package/dist/commands/get.d.ts +2 -0
  95. package/dist/commands/get.js +55 -0
  96. package/dist/commands/init.js +132 -117
  97. package/dist/commands/link.js +27 -27
  98. package/dist/commands/list.d.ts +6 -0
  99. package/dist/commands/list.js +43 -0
  100. package/dist/commands/mcp-setup.js +159 -149
  101. package/dist/commands/mcp.js +1 -1
  102. package/dist/commands/prepare.js +4 -4
  103. package/dist/commands/pull.js +116 -121
  104. package/dist/commands/push.js +106 -112
  105. package/dist/commands/report.d.ts +6 -2
  106. package/dist/commands/report.js +177 -122
  107. package/dist/commands/requirements-for.d.ts +2 -0
  108. package/dist/commands/requirements-for.js +29 -0
  109. package/dist/commands/review-test.d.ts +2 -0
  110. package/dist/commands/review-test.js +75 -0
  111. package/dist/commands/search.d.ts +6 -0
  112. package/dist/commands/search.js +39 -0
  113. package/dist/commands/style-check.d.ts +7 -0
  114. package/dist/commands/style-check.js +75 -0
  115. package/dist/commands/test.js +53 -59
  116. package/dist/commands/tests-for.d.ts +2 -0
  117. package/dist/commands/tests-for.js +80 -0
  118. package/dist/commands/validate.d.ts +6 -0
  119. package/dist/commands/validate.js +72 -0
  120. package/dist/config.js +1 -1
  121. package/dist/convex.d.ts +34 -22
  122. package/dist/convex.js +38 -22
  123. package/dist/harness/cache.d.ts +1 -5
  124. package/dist/harness/cache.js +49 -59
  125. package/dist/harness/convexReporting.d.ts +1 -1
  126. package/dist/harness/convexReporting.js +9 -7
  127. package/dist/harness/coverageCache.js +3 -3
  128. package/dist/harness/finalize.js +59 -46
  129. package/dist/harness/index.d.ts +6 -7
  130. package/dist/harness/index.js +9 -10
  131. package/dist/harness/prepare.js +6 -5
  132. package/dist/harness/requirementsLoader.d.ts +2 -2
  133. package/dist/harness/requirementsLoader.js +13 -35
  134. package/dist/harness/tracking.js +18 -18
  135. package/dist/harness/types.d.ts +1 -1
  136. package/dist/mcp/convexClient.d.ts +0 -39
  137. package/dist/mcp/convexClient.js +2 -107
  138. package/dist/mcp/grep.d.ts +1 -1
  139. package/dist/mcp/grep.js +87 -42
  140. package/dist/mcp/handlers/authoring.d.ts +1 -1
  141. package/dist/mcp/handlers/authoring.js +30 -234
  142. package/dist/mcp/handlers/coverage.d.ts +1 -1
  143. package/dist/mcp/handlers/coverage.js +13 -15
  144. package/dist/mcp/handlers/debug.d.ts +2 -3
  145. package/dist/mcp/handlers/debug.js +10 -10
  146. package/dist/mcp/handlers/get.d.ts +1 -1
  147. package/dist/mcp/handlers/get.js +11 -10
  148. package/dist/mcp/handlers/index.d.ts +20 -20
  149. package/dist/mcp/handlers/index.js +10 -10
  150. package/dist/mcp/handlers/list.d.ts +4 -33
  151. package/dist/mcp/handlers/list.js +16 -38
  152. package/dist/mcp/handlers/push.d.ts +1 -1
  153. package/dist/mcp/handlers/push.js +28 -18
  154. package/dist/mcp/handlers/report.d.ts +16 -0
  155. package/dist/mcp/handlers/report.js +134 -0
  156. package/dist/mcp/handlers/review.d.ts +1 -1
  157. package/dist/mcp/handlers/review.js +40 -59
  158. package/dist/mcp/handlers/search.d.ts +1 -1
  159. package/dist/mcp/handlers/search.js +7 -9
  160. package/dist/mcp/handlers/test-mapping.d.ts +1 -1
  161. package/dist/mcp/handlers/test-mapping.js +14 -14
  162. package/dist/mcp/handlers/types.d.ts +3 -3
  163. package/dist/mcp/handlers/types.js +2 -2
  164. package/dist/mcp/index.d.ts +1 -1
  165. package/dist/mcp/index.js +156 -178
  166. package/dist/mcp/requirements.d.ts +2 -2
  167. package/dist/mcp/requirements.js +30 -30
  168. package/dist/mcp/testCodeExtractor.js +24 -26
  169. package/dist/mcp/types.d.ts +1 -1
  170. package/dist/push/core.d.ts +2 -2
  171. package/dist/push/core.js +20 -20
  172. package/dist/push/index.d.ts +1 -1
  173. package/dist/push/index.js +2 -2
  174. package/dist/requirements/cloud-ai.d.ts +57 -0
  175. package/dist/requirements/cloud-ai.js +104 -0
  176. package/dist/requirements/cloud-coverage.d.ts +41 -0
  177. package/dist/requirements/cloud-coverage.js +60 -0
  178. package/dist/requirements/coverage.d.ts +45 -0
  179. package/dist/requirements/coverage.js +114 -0
  180. package/dist/requirements/grep.d.ts +33 -0
  181. package/dist/requirements/grep.js +306 -0
  182. package/dist/requirements/index.d.ts +73 -0
  183. package/dist/requirements/index.js +174 -0
  184. package/dist/requirements/style-guide.d.ts +67 -0
  185. package/dist/requirements/style-guide.js +299 -0
  186. package/dist/requirements/testCodeExtractor.d.ts +22 -0
  187. package/dist/requirements/testCodeExtractor.js +150 -0
  188. package/dist/schema/browser.d.ts +8 -6
  189. package/dist/schema/browser.js +14 -14
  190. package/dist/schema/builder.d.ts +1 -1
  191. package/dist/schema/builder.js +13 -44
  192. package/dist/schema/conversions.d.ts +2 -2
  193. package/dist/schema/conversions.js +11 -11
  194. package/dist/schema/index.d.ts +9 -7
  195. package/dist/schema/index.js +15 -13
  196. package/dist/schema/parser-core.d.ts +1 -1
  197. package/dist/schema/parser-core.js +23 -22
  198. package/dist/schema/parser.d.ts +3 -3
  199. package/dist/schema/parser.js +27 -31
  200. package/dist/schema/resolver.d.ts +1 -1
  201. package/dist/schema/resolver.js +9 -9
  202. package/dist/schema/scenario.d.ts +91 -0
  203. package/dist/schema/scenario.js +82 -0
  204. package/dist/schema/schemas.d.ts +3 -3
  205. package/dist/schema/schemas.js +41 -28
  206. package/dist/schema/test-schema.js +27 -27
  207. package/dist/templates/context-file-section.md +3 -2
  208. package/dist/templates/example-requirements.js +1 -1
  209. package/dist/templates/example-requirements.ts +3 -1
  210. package/dist/templates/requirements-readme.js +1 -1
  211. package/dist/templates/requirements-readme.ts +1 -1
  212. package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
  213. package/dist/utils/brand.js +3 -3
  214. package/dist/utils/browser-launch.js +4 -4
  215. package/dist/utils/context-file.d.ts +1 -1
  216. package/dist/utils/context-file.js +26 -26
  217. package/dist/utils/env.js +7 -7
  218. package/dist/utils/gitignore.js +7 -7
  219. package/dist/utils/oauth-callback-server.d.ts +1 -1
  220. package/dist/utils/oauth-callback-server.js +27 -25
  221. package/dist/utils/oauth-flow.js +32 -29
  222. package/dist/utils/project-discovery.d.ts +3 -3
  223. package/dist/utils/project-discovery.js +18 -17
  224. package/dist/utils/project-name.js +8 -8
  225. package/dist/utils/project-selector.d.ts +1 -1
  226. package/dist/utils/project-selector.js +24 -21
  227. package/dist/utils/project-settings.d.ts +5 -4
  228. package/dist/utils/project-settings.js +28 -20
  229. package/dist/utils/templates.js +6 -6
  230. package/package.json +2 -1
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Specifier fan-out orchestrator.
3
+ *
4
+ * For each area in the outline, extract a slice, spawn a specifier worker,
5
+ * and write the resulting partial to the cache. Workers run in parallel up
6
+ * to a bounded concurrency limit. On per-area failure (no partial written,
7
+ * empty partial), the worker is retried once; on second failure, the area
8
+ * is recorded as failed for surfacing to the user.
9
+ *
10
+ * Requirements covered:
11
+ * - CTS-SPEC-1.2: bounded concurrent execution
12
+ * - CTS-SPEC-5: failed/empty specifier output is detected and retried
13
+ * - CTS-OBSERVE-2.0: failure details are logged with area name + cause
14
+ * - CTS-RESUME-1.2: existing non-empty partial means the specifier is skipped
15
+ */
16
+ import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
17
+ import { dirname } from "node:path";
18
+ import { extractSlice } from "./slice.js";
19
+ import { runSpecifier } from "./specifier.js";
20
+ export const DEFAULT_FAN_OUT_CONCURRENCY = 8;
21
+ /**
22
+ * Convert an area name to a stable filesystem-safe identifier.
23
+ * E.g., "Reading & Editing Files" → "reading-editing-files"
24
+ */
25
+ export function sanitizeAreaName(name) {
26
+ return (name
27
+ .replace(/[^A-Za-z0-9]+/g, "-")
28
+ .replace(/^-+|-+$/g, "")
29
+ .toLowerCase() || "area");
30
+ }
31
+ /**
32
+ * Run a bounded-concurrency loop over items. Workers pull items off a shared
33
+ * counter; at most `limit` workers run at a time.
34
+ */
35
+ async function withLimit(items, limit, fn) {
36
+ let next = 0;
37
+ async function worker() {
38
+ while (true) {
39
+ const i = next++;
40
+ if (i >= items.length)
41
+ return;
42
+ await fn(items[i], i);
43
+ }
44
+ }
45
+ const workerCount = Math.min(Math.max(1, limit), items.length);
46
+ const workers = Array.from({ length: workerCount }, () => worker());
47
+ await Promise.all(workers);
48
+ }
49
+ function partialIsUsable(path) {
50
+ if (!existsSync(path))
51
+ return false;
52
+ try {
53
+ return statSync(path).size > 0;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Run the fan-out: extract slices, invoke specifiers, retry on failure.
61
+ */
62
+ export async function runFanOut(options) {
63
+ const { outline, fullPackPath, partialPathFor, slicePathFor, validateCommand, styleCheckCommand, addDirs, concurrency = DEFAULT_FAN_OUT_CONCURRENCY, runner, model, progress, } = options;
64
+ if (!existsSync(fullPackPath)) {
65
+ throw new Error(`Missing full pack at ${fullPackPath}`);
66
+ }
67
+ // Sanitization is lossy ("Read & Write" and "read/write" both become
68
+ // "read-write"). If two areas collide, they'd share a partial file path and
69
+ // silently overwrite each other downstream. Catch this up front.
70
+ const sanitizedNames = new Map();
71
+ for (const area of outline.areas) {
72
+ const key = sanitizeAreaName(area.name);
73
+ const bucket = sanitizedNames.get(key) ?? [];
74
+ bucket.push(area.name);
75
+ sanitizedNames.set(key, bucket);
76
+ }
77
+ const collisions = [...sanitizedNames.entries()].filter(([, names]) => names.length > 1);
78
+ if (collisions.length > 0) {
79
+ const detail = collisions
80
+ .map(([key, names]) => ` "${key}" ← ${names.map((n) => `"${n}"`).join(", ")}`)
81
+ .join("\n");
82
+ throw new Error(`Outline contains area names that collide after sanitization. Rename them so each produces a unique partial file path:\n${detail}`);
83
+ }
84
+ const outcomes = outline.areas.map((area) => ({
85
+ area,
86
+ status: "failed",
87
+ attempts: 0,
88
+ partialPath: partialPathFor(sanitizeAreaName(area.name)),
89
+ }));
90
+ // Ensure partials directory exists for each area.
91
+ for (const outcome of outcomes) {
92
+ mkdirSync(dirname(outcome.partialPath), { recursive: true });
93
+ }
94
+ await withLimit(outcomes, concurrency, async (outcome) => {
95
+ const sanitized = sanitizeAreaName(outcome.area.name);
96
+ const slicePath = slicePathFor(sanitized);
97
+ // CTS-RESUME-1.2: skip if a non-empty partial exists from a prior run.
98
+ if (partialIsUsable(outcome.partialPath)) {
99
+ outcome.status = "skipped-resume";
100
+ outcome.attempts = 0;
101
+ progress?.emit({
102
+ stage: "specify",
103
+ step: outcome.area.prefix,
104
+ message: `(resume) skipping ${outcome.area.name} — partial exists`,
105
+ });
106
+ return;
107
+ }
108
+ // Extract this area's slice from the full pack.
109
+ if (outcome.area.files.length === 0) {
110
+ // No files assigned — write a stub partial so compose has something.
111
+ writeFileSync(outcome.partialPath, "_(no files assigned to this area)_\n", "utf-8");
112
+ outcome.status = "failed";
113
+ outcome.attempts = 0;
114
+ outcome.failureCause = "no files assigned in outline";
115
+ progress?.emit({
116
+ stage: "specify",
117
+ step: outcome.area.prefix,
118
+ message: `!! ${outcome.area.name} has no files; marking as failed`,
119
+ });
120
+ return;
121
+ }
122
+ let sliceMatched = 0;
123
+ try {
124
+ const sliceResult = extractSlice(fullPackPath, slicePath, outcome.area.files);
125
+ sliceMatched = sliceResult.matched;
126
+ }
127
+ catch (err) {
128
+ outcome.failureCause = `slice extraction failed: ${err instanceof Error ? err.message : String(err)}`;
129
+ progress?.emit({
130
+ stage: "specify",
131
+ step: outcome.area.prefix,
132
+ message: `!! ${outcome.failureCause}`,
133
+ });
134
+ return;
135
+ }
136
+ if (sliceMatched === 0) {
137
+ progress?.emit({
138
+ stage: "specify",
139
+ step: outcome.area.prefix,
140
+ message: `(warning) slice for ${outcome.area.name} matched 0 files; specifier will need to fall back to full-pack reads`,
141
+ });
142
+ }
143
+ // Attempt 1
144
+ progress?.emit({
145
+ stage: "specify",
146
+ step: outcome.area.prefix,
147
+ message: `Running specifier for ${outcome.area.name} (attempt 1/2)`,
148
+ });
149
+ outcome.attempts = 1;
150
+ const r1 = await runSpecifier({
151
+ outline,
152
+ area: outcome.area,
153
+ slicePath,
154
+ fullPackPath,
155
+ partialPath: outcome.partialPath,
156
+ validateCommand,
157
+ styleCheckCommand,
158
+ addDirs,
159
+ runner,
160
+ model,
161
+ });
162
+ if (r1.wrotePartial) {
163
+ outcome.status = "completed";
164
+ progress?.emit({
165
+ stage: "specify",
166
+ step: outcome.area.prefix,
167
+ message: `${outcome.area.name} done (${r1.partialBytes} bytes)`,
168
+ });
169
+ return;
170
+ }
171
+ // Attempt 2 (retry once on no/empty partial)
172
+ progress?.emit({
173
+ stage: "specify",
174
+ step: outcome.area.prefix,
175
+ message: `!! ${outcome.area.name} produced no partial; retrying (attempt 2/2)`,
176
+ });
177
+ outcome.attempts = 2;
178
+ const r2 = await runSpecifier({
179
+ outline,
180
+ area: outcome.area,
181
+ slicePath,
182
+ fullPackPath,
183
+ partialPath: outcome.partialPath,
184
+ validateCommand,
185
+ styleCheckCommand,
186
+ addDirs,
187
+ runner,
188
+ model,
189
+ });
190
+ if (r2.wrotePartial) {
191
+ outcome.status = "completed";
192
+ progress?.emit({
193
+ stage: "specify",
194
+ step: outcome.area.prefix,
195
+ message: `${outcome.area.name} done on retry (${r2.partialBytes} bytes)`,
196
+ });
197
+ return;
198
+ }
199
+ // Two failures — record and continue. CTS-SPEC-5 says other areas still proceed.
200
+ outcome.status = "failed";
201
+ outcome.failureCause =
202
+ r2.exitCode !== 0
203
+ ? `specifier exited with code ${r2.exitCode}: ${r2.stderr.slice(0, 200)}`
204
+ : "specifier wrote no partial (or empty partial) on both attempts";
205
+ writeFileSync(outcome.partialPath, `_(missing partial — specifier failed after retry: ${outcome.failureCause})_\n`, "utf-8");
206
+ progress?.emit({
207
+ stage: "specify",
208
+ step: outcome.area.prefix,
209
+ message: `!! ${outcome.area.name} failed: ${outcome.failureCause}`,
210
+ });
211
+ });
212
+ const allSucceeded = outcomes.every((o) => o.status === "completed" || o.status === "skipped-resume");
213
+ return { outcomes, allSucceeded };
214
+ }
215
+ //# sourceMappingURL=fan-out.js.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Interactive / non-interactive mode detection and overwrite prompting.
3
+ *
4
+ * Requirements covered:
5
+ * - CTS-CLI-4: CLI distinguishes interactive and non-interactive invocation modes
6
+ * - CTS-PRESENT-2: Overwrites in interactive mode prompt the Engineer
7
+ * - CTS-PRESENT-3: Overwrites in non-interactive mode require an explicit flag
8
+ */
9
+ export type OverwriteChoice = "overwrite" | "skip" | "merge";
10
+ export interface InteractiveMode {
11
+ /** True when running in interactive mode (TTY on stdin, or --interactive forced). */
12
+ isInteractive: boolean;
13
+ /** What the engineer chose for a given path (only used in interactive mode). */
14
+ prompt: (path: string) => Promise<OverwriteChoice | "cancel">;
15
+ }
16
+ /**
17
+ * Detect interactive mode. Defaults to TTY detection on stdin; can be forced
18
+ * by the caller via the `force` argument.
19
+ *
20
+ * @param force 'interactive' or 'non-interactive' to override TTY detection.
21
+ */
22
+ export declare function detectMode(force?: "interactive" | "non-interactive"): {
23
+ isInteractive: boolean;
24
+ };
25
+ /**
26
+ * Prompt the engineer for an overwrite decision in interactive mode.
27
+ * Returns the chosen action, or `'cancel'` if the engineer dismissed the prompt.
28
+ */
29
+ export declare function promptOverwriteChoice(path: string): Promise<OverwriteChoice | "cancel">;
30
+ //# sourceMappingURL=interactive.d.ts.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Interactive / non-interactive mode detection and overwrite prompting.
3
+ *
4
+ * Requirements covered:
5
+ * - CTS-CLI-4: CLI distinguishes interactive and non-interactive invocation modes
6
+ * - CTS-PRESENT-2: Overwrites in interactive mode prompt the Engineer
7
+ * - CTS-PRESENT-3: Overwrites in non-interactive mode require an explicit flag
8
+ */
9
+ import prompts from "prompts";
10
+ /**
11
+ * Detect interactive mode. Defaults to TTY detection on stdin; can be forced
12
+ * by the caller via the `force` argument.
13
+ *
14
+ * @param force 'interactive' or 'non-interactive' to override TTY detection.
15
+ */
16
+ export function detectMode(force) {
17
+ if (force === "interactive")
18
+ return { isInteractive: true };
19
+ if (force === "non-interactive")
20
+ return { isInteractive: false };
21
+ // TTY detection
22
+ return { isInteractive: !!process.stdin.isTTY };
23
+ }
24
+ /**
25
+ * Prompt the engineer for an overwrite decision in interactive mode.
26
+ * Returns the chosen action, or `'cancel'` if the engineer dismissed the prompt.
27
+ */
28
+ export async function promptOverwriteChoice(path) {
29
+ const response = await prompts({
30
+ type: "select",
31
+ name: "choice",
32
+ message: `Overwrite ${path}?`,
33
+ choices: [
34
+ { title: "Overwrite (replace the existing file)", value: "overwrite" },
35
+ { title: "Skip (leave the existing file untouched)", value: "skip" },
36
+ {
37
+ title: "Merge (keep both — append new requirements to existing)",
38
+ value: "merge",
39
+ },
40
+ ],
41
+ initial: 0,
42
+ });
43
+ if (response.choice === undefined) {
44
+ return "cancel";
45
+ }
46
+ return response.choice;
47
+ }
48
+ //# sourceMappingURL=interactive.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Outline review loop: planner → reviewer → revise → reviewer → ... → approved.
3
+ *
4
+ * The reviewer runs in a SINGLE stateful claude session across turns (so it
5
+ * can compare prior outlines to the latest one). The planner is stateless —
6
+ * each invocation is fresh.
7
+ *
8
+ * Requirements covered:
9
+ * - CTS-PLAN-2: stateful reviewer session
10
+ * - CTS-PLAN-3: approved → proceed
11
+ * - CTS-PLAN-4: approved-with-revisions → one revision pass, then proceed
12
+ * - CTS-PLAN-5: requires-another-review → revision loop
13
+ * - CTS-PLAN-6: max-turns + convergence nudge
14
+ */
15
+ import { type ClaudeRunner } from "./claude.js";
16
+ import type { ProgressEmitter } from "./progress.js";
17
+ import { type Outline, type OutlineReview } from "./schemas.js";
18
+ export declare const DEFAULT_OUTLINE_LOOP_MAX_TURNS = 3;
19
+ export interface OutlineReviewLoopOptions {
20
+ overviewPath: string;
21
+ addDirs: string[];
22
+ maxTurns?: number;
23
+ runner?: ClaudeRunner;
24
+ model?: string;
25
+ progress?: ProgressEmitter;
26
+ /** Override session id (for tests and reproducibility). Otherwise random. */
27
+ sessionId?: string;
28
+ }
29
+ export interface OutlineReviewLoopResult {
30
+ /** The final outline after the loop converges (or hits max-turns). */
31
+ outline: Outline;
32
+ /** Per-turn outline snapshots (turn 1 is the initial, then each revision). */
33
+ outlinesByTurn: Outline[];
34
+ /** Per-turn reviewer results. */
35
+ reviewsByTurn: OutlineReview[];
36
+ /** The final reviewer verdict. */
37
+ finalVerdict: OutlineReview["verdict"];
38
+ /** Number of review turns used (= length of reviewsByTurn). */
39
+ turnsUsed: number;
40
+ /** True if the loop hit max-turns without converging. */
41
+ hitMaxTurns: boolean;
42
+ }
43
+ /**
44
+ * Run the outline review loop end-to-end.
45
+ *
46
+ * Returns the final outline (after applying mechanical revisions if the
47
+ * verdict was `approved-with-revisions`), the per-turn trace, and the final
48
+ * verdict.
49
+ */
50
+ export declare function runOutlineReviewLoop(options: OutlineReviewLoopOptions): Promise<OutlineReviewLoopResult>;
51
+ //# sourceMappingURL=outline-review-loop.d.ts.map
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Outline review loop: planner → reviewer → revise → reviewer → ... → approved.
3
+ *
4
+ * The reviewer runs in a SINGLE stateful claude session across turns (so it
5
+ * can compare prior outlines to the latest one). The planner is stateless —
6
+ * each invocation is fresh.
7
+ *
8
+ * Requirements covered:
9
+ * - CTS-PLAN-2: stateful reviewer session
10
+ * - CTS-PLAN-3: approved → proceed
11
+ * - CTS-PLAN-4: approved-with-revisions → one revision pass, then proceed
12
+ * - CTS-PLAN-5: requires-another-review → revision loop
13
+ * - CTS-PLAN-6: max-turns + convergence nudge
14
+ */
15
+ import { randomUUID } from "node:crypto";
16
+ import { runClaude } from "./claude.js";
17
+ import { runInitialPlanner, runRevisingPlanner, } from "./planner.js";
18
+ import { OUTLINE_REVIEWER_PROMPT } from "./prompts/outline-reviewer.js";
19
+ import { OUTLINE_REVIEW_JSON_SCHEMA, parseOutlineReview, } from "./schemas.js";
20
+ export const DEFAULT_OUTLINE_LOOP_MAX_TURNS = 3;
21
+ function reviewerUserMessageInitial(overviewPath, outline) {
22
+ return [
23
+ `Initial outline review.`,
24
+ ``,
25
+ `The compressed packed codebase is at: ${overviewPath}`,
26
+ ``,
27
+ `## Outline`,
28
+ `\`\`\`json`,
29
+ JSON.stringify(outline, null, 2),
30
+ `\`\`\``,
31
+ ``,
32
+ `Read the codebase if you need to verify file paths or coverage gaps, then produce your critique JSON.`,
33
+ ].join("\n");
34
+ }
35
+ function reviewerUserMessageRevised(turn, overviewPath, outline, nudge) {
36
+ const parts = [
37
+ `Review turn ${turn}. The planner has revised the outline based on your prior feedback.`,
38
+ ``,
39
+ ];
40
+ if (nudge) {
41
+ parts.push(nudge, ``);
42
+ }
43
+ parts.push(`The codebase pack at ${overviewPath} is unchanged.`, ``, `## Revised outline`, `\`\`\`json`, JSON.stringify(outline, null, 2), `\`\`\``, ``, `Compare this outline to the prior one. Report on what was fixed, what is still open, and what (if anything) was introduced as a new issue. Then produce your critique JSON.`);
44
+ return parts.join("\n");
45
+ }
46
+ function convergenceNudge(turn, maxTurns) {
47
+ return `CONVERGENCE NUDGE: This is review turn ${turn} of ${maxTurns} maximum. Please prioritize convergence. Your job is still to only approve a genuinely-ready outline, but if it is close, lean toward \`approved-with-revisions\` over \`requires-another-review\`. Reserve \`requires-another-review\` for genuine structural problems that the planner has not addressed despite prior feedback.`;
48
+ }
49
+ async function callReviewer(args) {
50
+ const result = await args.runner({
51
+ systemPrompt: OUTLINE_REVIEWER_PROMPT,
52
+ userMessage: args.userMessage,
53
+ model: args.model,
54
+ tools: ["Read", "Grep"],
55
+ addDirs: args.addDirs,
56
+ jsonSchema: OUTLINE_REVIEW_JSON_SCHEMA,
57
+ session: {
58
+ kind: args.isFirstTurn ? "fresh" : "resume",
59
+ sessionId: args.sessionId,
60
+ },
61
+ });
62
+ if (result.exitCode !== 0) {
63
+ throw new Error(`Outline reviewer failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
64
+ }
65
+ // On parse failure, retry the same turn once before failing.
66
+ // CTS-PLAN-2 requirement: "on validation failure the CLI logs the parse error
67
+ // and retries the same turn once before failing the loop"
68
+ try {
69
+ return parseOutlineReview(result.stdout);
70
+ }
71
+ catch (err) {
72
+ // Retry once
73
+ const retry = await args.runner({
74
+ systemPrompt: OUTLINE_REVIEWER_PROMPT,
75
+ userMessage: `${args.userMessage}\n\n(Your prior output failed schema validation: ${err instanceof Error ? err.message : String(err)}. Please try again, emitting JSON only.)`,
76
+ model: args.model,
77
+ tools: ["Read", "Grep"],
78
+ addDirs: args.addDirs,
79
+ jsonSchema: OUTLINE_REVIEW_JSON_SCHEMA,
80
+ session: { kind: "resume", sessionId: args.sessionId },
81
+ });
82
+ if (retry.exitCode !== 0) {
83
+ throw new Error(`Outline reviewer retry failed (exit ${retry.exitCode}): ${retry.stderr || retry.stdout}`);
84
+ }
85
+ return parseOutlineReview(retry.stdout);
86
+ }
87
+ }
88
+ /**
89
+ * Run the outline review loop end-to-end.
90
+ *
91
+ * Returns the final outline (after applying mechanical revisions if the
92
+ * verdict was `approved-with-revisions`), the per-turn trace, and the final
93
+ * verdict.
94
+ */
95
+ export async function runOutlineReviewLoop(options) {
96
+ const { overviewPath, addDirs, maxTurns = DEFAULT_OUTLINE_LOOP_MAX_TURNS, runner = runClaude, model, progress, sessionId = randomUUID(), } = options;
97
+ const plannerCtx = { overviewPath, addDirs, runner, model };
98
+ // Turn 1 — initial planner + initial reviewer
99
+ progress?.emit({
100
+ stage: "plan",
101
+ step: "planner-initial",
102
+ message: "Generating initial outline",
103
+ });
104
+ const outline1 = await runInitialPlanner(plannerCtx);
105
+ const outlinesByTurn = [outline1];
106
+ const reviewsByTurn = [];
107
+ progress?.emit({
108
+ stage: "outline-review",
109
+ step: "turn-1",
110
+ message: `Reviewing initial outline (${outline1.areas.length} areas)`,
111
+ });
112
+ let currentOutline = outline1;
113
+ let review = await callReviewer({
114
+ runner,
115
+ sessionId,
116
+ isFirstTurn: true,
117
+ userMessage: reviewerUserMessageInitial(overviewPath, outline1),
118
+ addDirs,
119
+ model,
120
+ });
121
+ reviewsByTurn.push(review);
122
+ progress?.emit({
123
+ stage: "outline-review",
124
+ step: "turn-1",
125
+ message: `Verdict: ${review.verdict}`,
126
+ data: { verdict: review.verdict },
127
+ });
128
+ let turn = 1;
129
+ while (review.verdict === "requires-another-review" && turn < maxTurns) {
130
+ turn++;
131
+ progress?.emit({
132
+ stage: "plan",
133
+ step: `revise-turn-${turn}`,
134
+ message: "Revising outline based on critique",
135
+ });
136
+ currentOutline = await runRevisingPlanner(plannerCtx, currentOutline, review);
137
+ outlinesByTurn.push(currentOutline);
138
+ const nudge = turn >= maxTurns - 1 && turn !== maxTurns
139
+ ? convergenceNudge(turn, maxTurns)
140
+ : undefined;
141
+ progress?.emit({
142
+ stage: "outline-review",
143
+ step: `turn-${turn}`,
144
+ message: `Reviewing revised outline (turn ${turn}/${maxTurns})`,
145
+ data: { turn, maxTurns, nudge: !!nudge },
146
+ });
147
+ review = await callReviewer({
148
+ runner,
149
+ sessionId,
150
+ isFirstTurn: false,
151
+ userMessage: reviewerUserMessageRevised(turn, overviewPath, currentOutline, nudge),
152
+ addDirs,
153
+ model,
154
+ });
155
+ reviewsByTurn.push(review);
156
+ progress?.emit({
157
+ stage: "outline-review",
158
+ step: `turn-${turn}`,
159
+ message: `Verdict: ${review.verdict}`,
160
+ data: { verdict: review.verdict },
161
+ });
162
+ }
163
+ const hitMaxTurns = review.verdict === "requires-another-review" && turn >= maxTurns;
164
+ // If the verdict ended on approved-with-revisions, run the planner once
165
+ // more to apply the listed revisions. Same agent as the revise loop; the
166
+ // difference is that we proceed to fan-out instead of looping back to
167
+ // review.
168
+ if (review.verdict === "approved-with-revisions" &&
169
+ review.revisions.length > 0) {
170
+ progress?.emit({
171
+ stage: "plan",
172
+ step: "apply-revisions",
173
+ message: `Applying ${review.revisions.length} revision(s)`,
174
+ });
175
+ currentOutline = await runRevisingPlanner(plannerCtx, currentOutline, review);
176
+ outlinesByTurn.push(currentOutline);
177
+ }
178
+ return {
179
+ outline: currentOutline,
180
+ outlinesByTurn,
181
+ reviewsByTurn,
182
+ finalVerdict: review.verdict,
183
+ turnsUsed: turn,
184
+ hitMaxTurns,
185
+ };
186
+ }
187
+ //# sourceMappingURL=outline-review-loop.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pack stage: produce compressed and uncompressed views of a codebase.
3
+ *
4
+ * Wraps Repomix's programmatic API to write:
5
+ * - `.dotrequirements-cache/overview.txt` — compressed (tree-sitter signatures)
6
+ * - `.dotrequirements-cache/source.txt` — uncompressed (full source for slices)
7
+ *
8
+ * Requirements covered:
9
+ * - CTS-PACK-1: Pack stage produces compressed and uncompressed views
10
+ * - CTS-PACK-2: Engineer can extend the ignore list per project
11
+ */
12
+ import type { CachePaths } from "./cache.js";
13
+ /**
14
+ * The curated default ignore list. These patterns are applied in addition to
15
+ * `.gitignore` and any project-level `.dotrequirements-ignore`.
16
+ *
17
+ * Sized to remove obvious non-behavioral noise: generated code, package
18
+ * manifests/lockfiles, vendored libraries, build artifacts, large binaries.
19
+ */
20
+ export declare const DEFAULT_IGNORES: readonly string[];
21
+ /**
22
+ * Read project-level ignore patterns from `.dotrequirements-ignore` (and
23
+ * `.dotreqignore` as a shorthand), if either exists. Returns an empty array if
24
+ * no file is present.
25
+ */
26
+ export declare function readProjectIgnores(projectRoot: string): string[];
27
+ export interface PackOptions {
28
+ projectRoot: string;
29
+ paths: CachePaths;
30
+ /** Optional scope path (subdir of projectRoot) to limit which files are packed. */
31
+ scope?: string;
32
+ /** Additional ignore patterns (beyond defaults and project-level). */
33
+ extraIgnores?: string[];
34
+ }
35
+ export interface PackResult {
36
+ overviewPath: string;
37
+ sourcePath: string;
38
+ /** Number of files included after ignores. */
39
+ fileCount: number;
40
+ }
41
+ /**
42
+ * Run Repomix twice over the same scope: once compressed, once uncompressed.
43
+ *
44
+ * We use Repomix's programmatic API rather than `npx repomix` so the CLI does
45
+ * not require a separate install and so we can control config precisely.
46
+ *
47
+ * NOTE: Repomix's TypeScript API is invoked via `runCli`. We invoke it as the
48
+ * library exposes it; the surface is small enough that this wraps cleanly.
49
+ */
50
+ export declare function runPack(options: PackOptions): Promise<PackResult>;
51
+ //# sourceMappingURL=pack.d.ts.map