@popoverai/dotrequirements 0.23.0 → 0.24.1

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 (235) hide show
  1. package/README.md +169 -22
  2. package/dist/cli.js +121 -60
  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-initial.d.ts +11 -0
  36. package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
  37. package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
  38. package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
  39. package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
  40. package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
  41. package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
  42. package/dist/codebase-to-spec/prompts/specifier.js +100 -0
  43. package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
  44. package/dist/codebase-to-spec/prompts/style-check.js +78 -0
  45. package/dist/codebase-to-spec/schemas.d.ts +257 -0
  46. package/dist/codebase-to-spec/schemas.js +183 -0
  47. package/dist/codebase-to-spec/skill-install.d.ts +57 -0
  48. package/dist/codebase-to-spec/skill-install.js +79 -0
  49. package/dist/codebase-to-spec/slice.d.ts +49 -0
  50. package/dist/codebase-to-spec/slice.js +111 -0
  51. package/dist/codebase-to-spec/specifier.d.ts +60 -0
  52. package/dist/codebase-to-spec/specifier.js +79 -0
  53. package/dist/codebase-to-spec/style-check.d.ts +29 -0
  54. package/dist/codebase-to-spec/style-check.js +33 -0
  55. package/dist/codebase-to-spec/summary.d.ts +51 -0
  56. package/dist/codebase-to-spec/summary.js +183 -0
  57. package/dist/codebase-to-spec/validate.d.ts +46 -0
  58. package/dist/codebase-to-spec/validate.js +130 -0
  59. package/dist/commands/acceptance-test.d.ts +6 -0
  60. package/dist/commands/{browsertest.js → acceptance-test.js} +36 -29
  61. package/dist/commands/ai-setup.d.ts +5 -0
  62. package/dist/commands/ai-setup.js +441 -0
  63. package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
  64. package/dist/commands/codebase-to-spec/compose.js +57 -0
  65. package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
  66. package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
  67. package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
  68. package/dist/commands/codebase-to-spec/fan-out.js +77 -0
  69. package/dist/commands/codebase-to-spec/index.d.ts +9 -0
  70. package/dist/commands/codebase-to-spec/index.js +135 -0
  71. package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
  72. package/dist/commands/codebase-to-spec/pack.js +76 -0
  73. package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
  74. package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
  75. package/dist/commands/codebase-to-spec/present.d.ts +21 -0
  76. package/dist/commands/codebase-to-spec/present.js +92 -0
  77. package/dist/commands/codebase-to-spec/run.d.ts +20 -0
  78. package/dist/commands/codebase-to-spec/run.js +85 -0
  79. package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
  80. package/dist/commands/codebase-to-spec/skill-install.js +51 -0
  81. package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
  82. package/dist/commands/codebase-to-spec/specify-area.js +82 -0
  83. package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
  84. package/dist/commands/codebase-to-spec/style-check.js +42 -0
  85. package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
  86. package/dist/commands/codebase-to-spec/validate.js +38 -0
  87. package/dist/commands/create-requirement-document.d.ts +2 -0
  88. package/dist/commands/create-requirement-document.js +41 -0
  89. package/dist/commands/finalize.js +7 -7
  90. package/dist/commands/get.d.ts +2 -0
  91. package/dist/commands/get.js +55 -0
  92. package/dist/commands/init.js +132 -117
  93. package/dist/commands/link.js +27 -27
  94. package/dist/commands/list.d.ts +6 -0
  95. package/dist/commands/list.js +43 -0
  96. package/dist/commands/mcp.js +1 -1
  97. package/dist/commands/prepare.js +4 -4
  98. package/dist/commands/pull.js +116 -121
  99. package/dist/commands/push.js +106 -112
  100. package/dist/commands/report.d.ts +6 -2
  101. package/dist/commands/report.js +177 -122
  102. package/dist/commands/requirements-for.d.ts +2 -0
  103. package/dist/commands/requirements-for.js +29 -0
  104. package/dist/commands/review-test.d.ts +2 -0
  105. package/dist/commands/review-test.js +75 -0
  106. package/dist/commands/search.d.ts +6 -0
  107. package/dist/commands/search.js +39 -0
  108. package/dist/commands/style-check.d.ts +7 -0
  109. package/dist/commands/style-check.js +75 -0
  110. package/dist/commands/tests-for.d.ts +2 -0
  111. package/dist/commands/tests-for.js +80 -0
  112. package/dist/commands/validate.d.ts +6 -0
  113. package/dist/commands/validate.js +72 -0
  114. package/dist/config.js +1 -1
  115. package/dist/convex.d.ts +34 -22
  116. package/dist/convex.js +38 -22
  117. package/dist/harness/cache.d.ts +1 -5
  118. package/dist/harness/cache.js +49 -59
  119. package/dist/harness/convexReporting.d.ts +1 -1
  120. package/dist/harness/convexReporting.js +9 -7
  121. package/dist/harness/coverageCache.js +3 -3
  122. package/dist/harness/finalize.js +59 -46
  123. package/dist/harness/index.d.ts +6 -7
  124. package/dist/harness/index.js +9 -10
  125. package/dist/harness/prepare.js +6 -5
  126. package/dist/harness/requirementsLoader.d.ts +2 -2
  127. package/dist/harness/requirementsLoader.js +13 -35
  128. package/dist/harness/tracking.js +18 -18
  129. package/dist/harness/types.d.ts +1 -1
  130. package/dist/mcp/convexClient.d.ts +0 -39
  131. package/dist/mcp/convexClient.js +2 -107
  132. package/dist/mcp/handlers/authoring.d.ts +1 -1
  133. package/dist/mcp/handlers/authoring.js +30 -234
  134. package/dist/mcp/handlers/debug.d.ts +2 -3
  135. package/dist/mcp/handlers/debug.js +10 -10
  136. package/dist/mcp/handlers/get.d.ts +1 -1
  137. package/dist/mcp/handlers/get.js +11 -10
  138. package/dist/mcp/handlers/index.d.ts +20 -20
  139. package/dist/mcp/handlers/index.js +10 -10
  140. package/dist/mcp/handlers/list.d.ts +4 -33
  141. package/dist/mcp/handlers/list.js +16 -38
  142. package/dist/mcp/handlers/push.d.ts +1 -1
  143. package/dist/mcp/handlers/push.js +28 -18
  144. package/dist/mcp/handlers/report.d.ts +16 -0
  145. package/dist/mcp/handlers/report.js +134 -0
  146. package/dist/mcp/handlers/review.d.ts +1 -1
  147. package/dist/mcp/handlers/review.js +40 -59
  148. package/dist/mcp/handlers/search.d.ts +1 -1
  149. package/dist/mcp/handlers/search.js +7 -9
  150. package/dist/mcp/handlers/test-mapping.d.ts +1 -1
  151. package/dist/mcp/handlers/test-mapping.js +14 -14
  152. package/dist/mcp/handlers/types.d.ts +3 -3
  153. package/dist/mcp/handlers/types.js +2 -2
  154. package/dist/mcp/index.d.ts +1 -1
  155. package/dist/mcp/index.js +147 -167
  156. package/dist/push/core.d.ts +2 -2
  157. package/dist/push/core.js +20 -20
  158. package/dist/push/index.d.ts +1 -1
  159. package/dist/push/index.js +2 -2
  160. package/dist/requirements/cloud-ai.d.ts +57 -0
  161. package/dist/requirements/cloud-ai.js +104 -0
  162. package/dist/requirements/cloud-coverage.d.ts +41 -0
  163. package/dist/requirements/cloud-coverage.js +60 -0
  164. package/dist/requirements/coverage.d.ts +45 -0
  165. package/dist/requirements/coverage.js +114 -0
  166. package/dist/{mcp → requirements}/grep.d.ts +10 -1
  167. package/dist/{mcp → requirements}/grep.js +89 -44
  168. package/dist/{mcp/requirements.d.ts → requirements/index.d.ts} +19 -3
  169. package/dist/{mcp/requirements.js → requirements/index.js} +54 -35
  170. package/dist/requirements/style-guide.d.ts +67 -0
  171. package/dist/requirements/style-guide.js +299 -0
  172. package/dist/{mcp → requirements}/testCodeExtractor.js +24 -26
  173. package/dist/schema/browser.d.ts +8 -8
  174. package/dist/schema/browser.js +13 -15
  175. package/dist/schema/builder.d.ts +1 -1
  176. package/dist/schema/builder.js +13 -44
  177. package/dist/schema/conversions.d.ts +2 -2
  178. package/dist/schema/conversions.js +11 -11
  179. package/dist/schema/index.d.ts +9 -9
  180. package/dist/schema/index.js +15 -15
  181. package/dist/schema/parser-core.d.ts +1 -1
  182. package/dist/schema/parser-core.js +23 -22
  183. package/dist/schema/parser.d.ts +3 -3
  184. package/dist/schema/parser.js +27 -31
  185. package/dist/schema/resolver.d.ts +1 -1
  186. package/dist/schema/resolver.js +9 -9
  187. package/dist/schema/scenario.d.ts +1 -1
  188. package/dist/schema/scenario.js +1 -1
  189. package/dist/schema/schemas.d.ts +3 -3
  190. package/dist/schema/schemas.js +41 -28
  191. package/dist/schema/test-schema.js +27 -27
  192. package/dist/templates/context-file-section.md +3 -2
  193. package/dist/templates/example-requirements.js +1 -1
  194. package/dist/templates/example-requirements.ts +3 -1
  195. package/dist/templates/requirements-readme.js +1 -1
  196. package/dist/templates/requirements-readme.ts +1 -1
  197. package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
  198. package/dist/utils/brand.js +3 -3
  199. package/dist/utils/browser-launch.js +4 -4
  200. package/dist/utils/context-file.d.ts +1 -1
  201. package/dist/utils/context-file.js +26 -26
  202. package/dist/utils/env.js +7 -7
  203. package/dist/utils/gitignore.js +7 -7
  204. package/dist/utils/oauth-callback-server.d.ts +1 -1
  205. package/dist/utils/oauth-callback-server.js +27 -25
  206. package/dist/utils/oauth-flow.js +32 -29
  207. package/dist/utils/project-discovery.d.ts +3 -3
  208. package/dist/utils/project-discovery.js +18 -17
  209. package/dist/utils/project-name.js +8 -8
  210. package/dist/utils/project-selector.d.ts +1 -1
  211. package/dist/utils/project-selector.js +24 -21
  212. package/dist/utils/project-settings.d.ts +1 -1
  213. package/dist/utils/project-settings.js +24 -22
  214. package/dist/utils/templates.js +6 -6
  215. package/package.json +3 -2
  216. package/dist/commands/browsertest.d.ts +0 -6
  217. package/dist/commands/login.d.ts +0 -12
  218. package/dist/commands/login.js +0 -117
  219. package/dist/commands/logout.d.ts +0 -5
  220. package/dist/commands/logout.js +0 -17
  221. package/dist/commands/mcp-setup.d.ts +0 -5
  222. package/dist/commands/mcp-setup.js +0 -431
  223. package/dist/commands/test.d.ts +0 -6
  224. package/dist/commands/test.js +0 -78
  225. package/dist/mcp/handlers/coverage.d.ts +0 -44
  226. package/dist/mcp/handlers/coverage.js +0 -105
  227. package/dist/mcp/types.d.ts +0 -27
  228. package/dist/mcp/types.js +0 -2
  229. package/dist/utils/local-project.d.ts +0 -31
  230. package/dist/utils/local-project.js +0 -33
  231. package/dist/utils/token-refresh.d.ts +0 -24
  232. package/dist/utils/token-refresh.js +0 -69
  233. package/dist/utils/token-storage.d.ts +0 -31
  234. package/dist/utils/token-storage.js +0 -57
  235. /package/dist/{mcp → requirements}/testCodeExtractor.d.ts +0 -0
@@ -0,0 +1,127 @@
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 { existsSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ /**
15
+ * The curated default ignore list. These patterns are applied in addition to
16
+ * `.gitignore` and any project-level `.dotrequirements-ignore`.
17
+ *
18
+ * Sized to remove obvious non-behavioral noise: generated code, package
19
+ * manifests/lockfiles, vendored libraries, build artifacts, large binaries.
20
+ */
21
+ export const DEFAULT_IGNORES = [
22
+ // Vendored dependencies
23
+ "node_modules/**",
24
+ "vendor/**",
25
+ ".pnp.*",
26
+ // Build outputs
27
+ "dist/**",
28
+ "build/**",
29
+ "out/**",
30
+ ".next/**",
31
+ ".nuxt/**",
32
+ ".svelte-kit/**",
33
+ ".turbo/**",
34
+ ".parcel-cache/**",
35
+ // Generated code
36
+ "**/*.generated.*",
37
+ "**/_generated/**",
38
+ "**/__generated__/**",
39
+ "**/*.pb.ts",
40
+ "**/*.pb.go",
41
+ // Lockfiles
42
+ "package-lock.json",
43
+ "pnpm-lock.yaml",
44
+ "yarn.lock",
45
+ "bun.lockb",
46
+ "composer.lock",
47
+ "Gemfile.lock",
48
+ "poetry.lock",
49
+ "uv.lock",
50
+ "Cargo.lock",
51
+ // Coverage and test artifacts
52
+ "coverage/**",
53
+ ".nyc_output/**",
54
+ ".next-cache/**",
55
+ // IDE / editor
56
+ ".idea/**",
57
+ ".vscode/**",
58
+ // OS files
59
+ ".DS_Store",
60
+ "Thumbs.db",
61
+ // Project caches we own
62
+ ".dotrequirements-cache/**",
63
+ ];
64
+ /**
65
+ * Read project-level ignore patterns from `.dotrequirements-ignore` (and
66
+ * `.dotreqignore` as a shorthand), if either exists. Returns an empty array if
67
+ * no file is present.
68
+ */
69
+ export function readProjectIgnores(projectRoot) {
70
+ const candidates = [".dotrequirements-ignore", ".dotreqignore"];
71
+ for (const name of candidates) {
72
+ const p = join(projectRoot, name);
73
+ if (existsSync(p)) {
74
+ const content = readFileSync(p, "utf-8");
75
+ return content
76
+ .split(/\r?\n/)
77
+ .map((line) => line.trim())
78
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
79
+ }
80
+ }
81
+ return [];
82
+ }
83
+ /**
84
+ * Run Repomix twice over the same scope: once compressed, once uncompressed.
85
+ *
86
+ * We use Repomix's programmatic API rather than `npx repomix` so the CLI does
87
+ * not require a separate install and so we can control config precisely.
88
+ *
89
+ * NOTE: Repomix's TypeScript API is invoked via `runCli`. We invoke it as the
90
+ * library exposes it; the surface is small enough that this wraps cleanly.
91
+ */
92
+ export async function runPack(options) {
93
+ const { projectRoot, paths, scope, extraIgnores = [] } = options;
94
+ const ignores = [
95
+ ...DEFAULT_IGNORES,
96
+ ...readProjectIgnores(projectRoot),
97
+ ...extraIgnores,
98
+ ];
99
+ const target = scope ? join(projectRoot, scope) : projectRoot;
100
+ // Import dynamically — Repomix is heavy and we don't want to load it at CLI
101
+ // boot time for unrelated commands.
102
+ const repomix = await import("repomix");
103
+ const { runCli } = repomix;
104
+ // Run compressed (overview)
105
+ await runCli([target], projectRoot, {
106
+ output: paths.overview,
107
+ style: "plain",
108
+ compress: true,
109
+ ignore: ignores.join(","),
110
+ });
111
+ // Run uncompressed (source)
112
+ await runCli([target], projectRoot, {
113
+ output: paths.source,
114
+ style: "plain",
115
+ compress: false,
116
+ ignore: ignores.join(","),
117
+ });
118
+ // Count "File:" headers in the uncompressed pack to report file count
119
+ const sourceContent = readFileSync(paths.source, "utf-8");
120
+ const fileCount = (sourceContent.match(/^File: /gm) ?? []).length;
121
+ return {
122
+ overviewPath: paths.overview,
123
+ sourcePath: paths.source,
124
+ fileCount,
125
+ };
126
+ }
127
+ //# sourceMappingURL=pack.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Planner functions (initial and revise).
3
+ *
4
+ * Two modes correspond to two places the planner is invoked:
5
+ * - initial: first outline from the compressed pack (CTS-PLAN-1)
6
+ * - revise: address reviewer feedback — works for both
7
+ * `requires-another-review` (CTS-PLAN-5) and
8
+ * `approved-with-revisions` (CTS-PLAN-4). The orchestration
9
+ * decides whether to loop back to the reviewer or proceed to
10
+ * fan-out.
11
+ *
12
+ * Each mode is stateless — a fresh claude -p session per call.
13
+ */
14
+ import { type ClaudeRunner } from "./claude.js";
15
+ import { type Outline, type OutlineReview } from "./schemas.js";
16
+ export interface PlannerContext {
17
+ /** Path to the compressed pack file (readable by the spawned agent). */
18
+ overviewPath: string;
19
+ /** Directories the agent should have read access to (e.g., the cache dir). */
20
+ addDirs: string[];
21
+ /** Optional claude runner override (for tests). */
22
+ runner?: ClaudeRunner;
23
+ /** Optional model override. */
24
+ model?: string;
25
+ }
26
+ /**
27
+ * Run the planner in initial mode and return a parsed outline.
28
+ * Requirement: CTS-PLAN-1
29
+ */
30
+ export declare function runInitialPlanner(ctx: PlannerContext): Promise<Outline>;
31
+ /**
32
+ * Run the planner in revise mode — produce a new outline that addresses the
33
+ * reviewer's output. Handles both verdicts that trigger another planner pass:
34
+ * `requires-another-review` (categorized findings, judgment-based) and
35
+ * `approved-with-revisions` (explicit revisions list). The same prompt handles
36
+ * both shapes; the prompt switches behavior based on what's in the review.
37
+ *
38
+ * Requirements: CTS-PLAN-4, CTS-PLAN-5
39
+ */
40
+ export declare function runRevisingPlanner(ctx: PlannerContext, priorOutline: Outline, review: OutlineReview): Promise<Outline>;
41
+ //# sourceMappingURL=planner.d.ts.map
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Planner functions (initial and revise).
3
+ *
4
+ * Two modes correspond to two places the planner is invoked:
5
+ * - initial: first outline from the compressed pack (CTS-PLAN-1)
6
+ * - revise: address reviewer feedback — works for both
7
+ * `requires-another-review` (CTS-PLAN-5) and
8
+ * `approved-with-revisions` (CTS-PLAN-4). The orchestration
9
+ * decides whether to loop back to the reviewer or proceed to
10
+ * fan-out.
11
+ *
12
+ * Each mode is stateless — a fresh claude -p session per call.
13
+ */
14
+ import { runClaude } from "./claude.js";
15
+ import { PLANNER_INITIAL_PROMPT } from "./prompts/planner-initial.js";
16
+ import { PLANNER_REVISE_PROMPT } from "./prompts/planner-revise.js";
17
+ import { OUTLINE_JSON_SCHEMA, parseOutline, } from "./schemas.js";
18
+ /**
19
+ * Run the planner in initial mode and return a parsed outline.
20
+ * Requirement: CTS-PLAN-1
21
+ */
22
+ export async function runInitialPlanner(ctx) {
23
+ const runner = ctx.runner ?? runClaude;
24
+ const result = await runner({
25
+ systemPrompt: PLANNER_INITIAL_PROMPT,
26
+ userMessage: `The compressed packed codebase is at: ${ctx.overviewPath}\n\nRead it and produce the outline JSON described in your system prompt.`,
27
+ model: ctx.model,
28
+ tools: ["Read"],
29
+ addDirs: ctx.addDirs,
30
+ jsonSchema: OUTLINE_JSON_SCHEMA,
31
+ });
32
+ if (result.exitCode !== 0) {
33
+ throw new Error(`Planner failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
34
+ }
35
+ return parseOutline(result.stdout);
36
+ }
37
+ /**
38
+ * Run the planner in revise mode — produce a new outline that addresses the
39
+ * reviewer's output. Handles both verdicts that trigger another planner pass:
40
+ * `requires-another-review` (categorized findings, judgment-based) and
41
+ * `approved-with-revisions` (explicit revisions list). The same prompt handles
42
+ * both shapes; the prompt switches behavior based on what's in the review.
43
+ *
44
+ * Requirements: CTS-PLAN-4, CTS-PLAN-5
45
+ */
46
+ export async function runRevisingPlanner(ctx, priorOutline, review) {
47
+ const runner = ctx.runner ?? runClaude;
48
+ const userMessage = [
49
+ `The compressed packed codebase is at: ${ctx.overviewPath}`,
50
+ ``,
51
+ `## Prior outline`,
52
+ `\`\`\`json`,
53
+ JSON.stringify(priorOutline, null, 2),
54
+ `\`\`\``,
55
+ ``,
56
+ `## Reviewer output`,
57
+ `\`\`\`json`,
58
+ JSON.stringify(review, null, 2),
59
+ `\`\`\``,
60
+ ``,
61
+ `Produce a revised outline JSON that addresses the reviewer's output. Output JSON only.`,
62
+ ].join("\n");
63
+ const result = await runner({
64
+ systemPrompt: PLANNER_REVISE_PROMPT,
65
+ userMessage,
66
+ model: ctx.model,
67
+ tools: ["Read"],
68
+ addDirs: ctx.addDirs,
69
+ jsonSchema: OUTLINE_JSON_SCHEMA,
70
+ });
71
+ if (result.exitCode !== 0) {
72
+ throw new Error(`Revising planner failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
73
+ }
74
+ return parseOutline(result.stdout);
75
+ }
76
+ //# sourceMappingURL=planner.js.map
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Present stage: write the final spec to .requirements/.
3
+ *
4
+ * - When the outline has 5+ areas, split into per-area files
5
+ * (`.requirements/<sanitized-area-name>.requirements.md`).
6
+ * - When the outline has fewer than 5 areas, write a single document at
7
+ * `.requirements/<sanitized-title>.requirements.md`.
8
+ * - Overwrite handling depends on interactive vs non-interactive mode.
9
+ *
10
+ * Requirements covered:
11
+ * - CTS-PRESENT-1: Final spec written to .requirements/, split when 5+ areas
12
+ * - CTS-PRESENT-2: Interactive overwrite prompt with three choices
13
+ * - CTS-PRESENT-3: Non-interactive overwrites require --overwrite or --skip-existing
14
+ */
15
+ import type { Outline } from "./schemas.js";
16
+ export declare const AREA_SPLIT_THRESHOLD = 5;
17
+ export type OverwritePolicy = "prompt" | "overwrite" | "skip-existing" | "fail-fast";
18
+ export interface PresentOptions {
19
+ outline: Outline;
20
+ finalSpecPath: string;
21
+ /** Project root (.requirements/ will be created under this). */
22
+ projectRoot: string;
23
+ overwritePolicy: OverwritePolicy;
24
+ }
25
+ export interface PresentResult {
26
+ /** Paths written under .requirements/ (or those that would have been written, in fail cases). */
27
+ outputPaths: string[];
28
+ /** Per-file action taken. */
29
+ actions: PresentAction[];
30
+ /** True if every file landed successfully. */
31
+ allWritten: boolean;
32
+ /** Reason for failure, if `allWritten` is false in fail-fast mode. */
33
+ conflictPath?: string;
34
+ }
35
+ export interface PresentAction {
36
+ path: string;
37
+ action: "created" | "overwrote" | "skipped" | "merged" | "refused";
38
+ }
39
+ /**
40
+ * Resolve the output path(s) the present stage would write to.
41
+ */
42
+ export declare function planPresentPaths(outline: Outline, projectRoot: string): {
43
+ mode: "single" | "split";
44
+ paths: {
45
+ area?: string;
46
+ outPath: string;
47
+ }[];
48
+ };
49
+ /**
50
+ * Split a composed spec into per-area sections.
51
+ *
52
+ * The compose stage writes:
53
+ *
54
+ * ---
55
+ * frontmatter
56
+ * ---
57
+ *
58
+ * # Title
59
+ *
60
+ * Summary.
61
+ *
62
+ * ---
63
+ *
64
+ * ## Area One
65
+ * ...
66
+ *
67
+ * ---
68
+ *
69
+ * ## Area Two
70
+ * ...
71
+ *
72
+ * We split on `\n---\n\n## ` and pair each chunk with its area heading.
73
+ */
74
+ export declare function splitComposedSpec(composed: string, outline: Outline): Map<string, string>;
75
+ /**
76
+ * Build a single-file requirements document from the composed spec.
77
+ * The composed spec IS this document (with the right frontmatter and structure).
78
+ * We just read it and return it.
79
+ */
80
+ export declare function buildSingleFileDoc(composedSpec: string): string;
81
+ /**
82
+ * Build a per-area requirements document for split mode.
83
+ */
84
+ export declare function buildAreaDoc(outline: Outline, areaName: string, areaBody: string): string;
85
+ /**
86
+ * Run the present stage.
87
+ *
88
+ * Returns a list of actions taken per path. In fail-fast mode, if any path
89
+ * would conflict, the function returns early with `conflictPath` set and
90
+ * `allWritten = false` — no files are written in this case (preserving the
91
+ * "no file overwritten without choice" guarantee from CTS-PRESENT-2.1).
92
+ */
93
+ export declare function runPresent(options: PresentOptions): Promise<PresentResult>;
94
+ //# sourceMappingURL=present.d.ts.map
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Present stage: write the final spec to .requirements/.
3
+ *
4
+ * - When the outline has 5+ areas, split into per-area files
5
+ * (`.requirements/<sanitized-area-name>.requirements.md`).
6
+ * - When the outline has fewer than 5 areas, write a single document at
7
+ * `.requirements/<sanitized-title>.requirements.md`.
8
+ * - Overwrite handling depends on interactive vs non-interactive mode.
9
+ *
10
+ * Requirements covered:
11
+ * - CTS-PRESENT-1: Final spec written to .requirements/, split when 5+ areas
12
+ * - CTS-PRESENT-2: Interactive overwrite prompt with three choices
13
+ * - CTS-PRESENT-3: Non-interactive overwrites require --overwrite or --skip-existing
14
+ */
15
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { dirname, join } from "node:path";
17
+ import { parseRequirementsFile } from "../schema/parser.js";
18
+ import { sanitizeAreaName } from "./fan-out.js";
19
+ import { promptOverwriteChoice } from "./interactive.js";
20
+ export const AREA_SPLIT_THRESHOLD = 5;
21
+ /**
22
+ * Resolve the output path(s) the present stage would write to.
23
+ */
24
+ export function planPresentPaths(outline, projectRoot) {
25
+ const requirementsDir = join(projectRoot, ".requirements");
26
+ if (outline.areas.length >= AREA_SPLIT_THRESHOLD) {
27
+ return {
28
+ mode: "split",
29
+ paths: outline.areas.map((area) => ({
30
+ area: area.name,
31
+ outPath: join(requirementsDir, `${sanitizeAreaName(area.name)}.requirements.md`),
32
+ })),
33
+ };
34
+ }
35
+ return {
36
+ mode: "single",
37
+ paths: [
38
+ {
39
+ outPath: join(requirementsDir, `${sanitizeAreaName(outline.title)}.requirements.md`),
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ /**
45
+ * Split a composed spec into per-area sections.
46
+ *
47
+ * The compose stage writes:
48
+ *
49
+ * ---
50
+ * frontmatter
51
+ * ---
52
+ *
53
+ * # Title
54
+ *
55
+ * Summary.
56
+ *
57
+ * ---
58
+ *
59
+ * ## Area One
60
+ * ...
61
+ *
62
+ * ---
63
+ *
64
+ * ## Area Two
65
+ * ...
66
+ *
67
+ * We split on `\n---\n\n## ` and pair each chunk with its area heading.
68
+ */
69
+ export function splitComposedSpec(composed, outline) {
70
+ const sections = new Map();
71
+ // Strip frontmatter (already used) and grab everything from the first '## ' onward.
72
+ const fmEnd = composed.indexOf("\n---\n", 4);
73
+ // Find the first H2.
74
+ const firstH2 = composed.indexOf("\n## ");
75
+ if (firstH2 === -1) {
76
+ // No areas in the composed spec — fall back to empty sections.
77
+ return sections;
78
+ }
79
+ const afterTitle = composed.slice(firstH2);
80
+ // Split on '\n---\n\n## ' so each chunk starts with '## <area name>'.
81
+ // (We split-include the first chunk by prepending it manually.)
82
+ const chunks = afterTitle.split(/\n---\n\n## /);
83
+ // The first chunk already starts with '\n## ' from the original; normalize:
84
+ chunks[0] = chunks[0].replace(/^\n?## /, "");
85
+ // Subsequent chunks start with the area heading text directly (we stripped '## ').
86
+ for (const chunk of chunks) {
87
+ const firstNewline = chunk.indexOf("\n");
88
+ if (firstNewline === -1)
89
+ continue;
90
+ const heading = chunk.slice(0, firstNewline).trim();
91
+ const body = chunk.slice(firstNewline + 1);
92
+ // Match heading to outline area (exact match by name).
93
+ const area = outline.areas.find((a) => a.name === heading);
94
+ if (area) {
95
+ sections.set(area.name, body.trim());
96
+ }
97
+ }
98
+ // Also handle the trivial case where compose wrote `## Area` immediately
99
+ // after the title (no `---` separator). Unused for current compose output
100
+ // but defensive.
101
+ // Suppress unused-var warning for fmEnd:
102
+ void fmEnd;
103
+ return sections;
104
+ }
105
+ /**
106
+ * Build a single-file requirements document from the composed spec.
107
+ * The composed spec IS this document (with the right frontmatter and structure).
108
+ * We just read it and return it.
109
+ */
110
+ export function buildSingleFileDoc(composedSpec) {
111
+ return composedSpec;
112
+ }
113
+ /**
114
+ * Build a per-area requirements document for split mode.
115
+ */
116
+ export function buildAreaDoc(outline, areaName, areaBody) {
117
+ const area = outline.areas.find((a) => a.name === areaName);
118
+ const escapedTitle = `${outline.title}: ${areaName}`
119
+ .replace(/\\/g, "\\\\")
120
+ .replace(/"/g, '\\"');
121
+ const parts = [
122
+ "---",
123
+ "document:",
124
+ ` title: "${escapedTitle}"`,
125
+ ` defaultPrefix: ${outline.defaultPrefix}`,
126
+ "---",
127
+ "",
128
+ `# ${outline.title}: ${areaName}`,
129
+ "",
130
+ ];
131
+ // The planner's drive-by area.description is intentionally not surfaced
132
+ // here. The specifier's deeper, persona-grounded area description (written
133
+ // into the partial body) is the one readers see.
134
+ void area;
135
+ parts.push(areaBody.trim());
136
+ parts.push("");
137
+ return parts.join("\n");
138
+ }
139
+ /**
140
+ * Apply an overwrite policy to determine what action to take for a path.
141
+ */
142
+ async function resolveOverwriteAction(policy, path) {
143
+ if (!existsSync(path)) {
144
+ // No conflict — caller will create.
145
+ return "overwrite";
146
+ }
147
+ switch (policy) {
148
+ case "overwrite":
149
+ return "overwrite";
150
+ case "skip-existing":
151
+ return "skip";
152
+ case "fail-fast":
153
+ return "refused";
154
+ case "prompt": {
155
+ const choice = await promptOverwriteChoice(path);
156
+ if (choice === "cancel")
157
+ return "refused";
158
+ return choice;
159
+ }
160
+ }
161
+ }
162
+ /**
163
+ * Merge new content into an existing requirements file.
164
+ *
165
+ * Strategy: append the new file's requirement blocks below the existing
166
+ * content, with a separator comment. Existing requirements are preserved
167
+ * verbatim.
168
+ */
169
+ function mergeContent(existingPath, newContent) {
170
+ const existing = readFileSync(existingPath, "utf-8").trimEnd();
171
+ // Strip frontmatter from newContent (it would conflict with existing frontmatter)
172
+ const fmMatch = newContent.match(/^---\n[\s\S]+?\n---\n+/);
173
+ const body = fmMatch
174
+ ? newContent.slice(fmMatch[0].length).trimStart()
175
+ : newContent;
176
+ return [
177
+ existing,
178
+ "",
179
+ "",
180
+ "<!-- Merged content appended by `dotrequirements cts present`. Review and integrate. -->",
181
+ "",
182
+ body,
183
+ ].join("\n");
184
+ }
185
+ /**
186
+ * Validate a path's content against the dotrequirements schema. Returns an
187
+ * error message string, or undefined if valid.
188
+ */
189
+ function validateContent(content) {
190
+ try {
191
+ parseRequirementsFile(content);
192
+ return undefined;
193
+ }
194
+ catch (err) {
195
+ return err instanceof Error ? err.message : String(err);
196
+ }
197
+ }
198
+ /**
199
+ * Run the present stage.
200
+ *
201
+ * Returns a list of actions taken per path. In fail-fast mode, if any path
202
+ * would conflict, the function returns early with `conflictPath` set and
203
+ * `allWritten = false` — no files are written in this case (preserving the
204
+ * "no file overwritten without choice" guarantee from CTS-PRESENT-2.1).
205
+ */
206
+ export async function runPresent(options) {
207
+ const { outline, finalSpecPath, projectRoot, overwritePolicy } = options;
208
+ const composedSpec = readFileSync(finalSpecPath, "utf-8");
209
+ const plan = planPresentPaths(outline, projectRoot);
210
+ // Compute the content for each output up front.
211
+ const planned = [];
212
+ if (plan.mode === "single") {
213
+ planned.push({
214
+ outPath: plan.paths[0].outPath,
215
+ content: buildSingleFileDoc(composedSpec),
216
+ });
217
+ }
218
+ else {
219
+ const sections = splitComposedSpec(composedSpec, outline);
220
+ for (const { area, outPath } of plan.paths) {
221
+ const body = sections.get(area) ??
222
+ "_(no content for this area was found in the composed spec)_";
223
+ planned.push({ outPath, content: buildAreaDoc(outline, area, body) });
224
+ }
225
+ }
226
+ // In fail-fast mode, scan for any conflict before writing anything.
227
+ if (overwritePolicy === "fail-fast") {
228
+ for (const { outPath } of planned) {
229
+ if (existsSync(outPath)) {
230
+ return {
231
+ outputPaths: planned.map((p) => p.outPath),
232
+ actions: [],
233
+ allWritten: false,
234
+ conflictPath: outPath,
235
+ };
236
+ }
237
+ }
238
+ }
239
+ const actions = [];
240
+ let allWritten = true;
241
+ for (const { outPath, content } of planned) {
242
+ const choice = await resolveOverwriteAction(overwritePolicy, outPath);
243
+ const dir = dirname(outPath);
244
+ if (!existsSync(dir))
245
+ mkdirSync(dir, { recursive: true });
246
+ if (choice === "refused") {
247
+ // In prompt mode, the user cancelled the prompt.
248
+ actions.push({ path: outPath, action: "refused" });
249
+ allWritten = false;
250
+ continue;
251
+ }
252
+ if (choice === "skip") {
253
+ actions.push({ path: outPath, action: "skipped" });
254
+ continue;
255
+ }
256
+ if (choice === "merge" && existsSync(outPath)) {
257
+ const merged = mergeContent(outPath, content);
258
+ const validationError = validateContent(merged);
259
+ if (validationError) {
260
+ // Fallback: leave the existing file alone and warn.
261
+ actions.push({ path: outPath, action: "skipped" });
262
+ allWritten = false;
263
+ continue;
264
+ }
265
+ writeFileSync(outPath, merged, "utf-8");
266
+ actions.push({ path: outPath, action: "merged" });
267
+ continue;
268
+ }
269
+ // Overwrite or create
270
+ const validationError = validateContent(content);
271
+ if (validationError) {
272
+ allWritten = false;
273
+ actions.push({ path: outPath, action: "refused" });
274
+ continue;
275
+ }
276
+ const action = existsSync(outPath)
277
+ ? "overwrote"
278
+ : "created";
279
+ writeFileSync(outPath, content, "utf-8");
280
+ actions.push({ path: outPath, action });
281
+ }
282
+ return {
283
+ outputPaths: planned.map((p) => p.outPath),
284
+ actions,
285
+ allWritten,
286
+ };
287
+ }
288
+ //# sourceMappingURL=present.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Structured progress emitter for codebase-to-spec.
3
+ *
4
+ * Emits one line per event to stdout. Wrappers (the skill, CI tooling) parse
5
+ * these lines to render progress in their own UX.
6
+ *
7
+ * Format: `[CTS] <stage>/<step> <message>` where <stage>/<step> identifies the
8
+ * pipeline location and <message> is human-readable.
9
+ *
10
+ * Requirements covered:
11
+ * - CTS-CLI-5: CLI emissions are part of the public contract (stable format)
12
+ * - CTS-OBSERVE-1: CLI emits structured progress as the pipeline runs
13
+ */
14
+ export type Stage = "pack" | "plan" | "outline-review" | "specify" | "compose" | "spec-review" | "present";
15
+ export interface ProgressEvent {
16
+ stage: Stage;
17
+ step?: string;
18
+ message: string;
19
+ /** Optional structured data — included on JSON lines but not on plain text lines. */
20
+ data?: Record<string, unknown>;
21
+ }
22
+ export interface ProgressEmitter {
23
+ emit(event: ProgressEvent): void;
24
+ }
25
+ export declare class StdoutProgress implements ProgressEmitter {
26
+ emit(event: ProgressEvent): void;
27
+ }
28
+ /** Capturing emitter for tests. */
29
+ export declare class CapturingProgress implements ProgressEmitter {
30
+ events: ProgressEvent[];
31
+ emit(event: ProgressEvent): void;
32
+ }
33
+ //# sourceMappingURL=progress.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Structured progress emitter for codebase-to-spec.
3
+ *
4
+ * Emits one line per event to stdout. Wrappers (the skill, CI tooling) parse
5
+ * these lines to render progress in their own UX.
6
+ *
7
+ * Format: `[CTS] <stage>/<step> <message>` where <stage>/<step> identifies the
8
+ * pipeline location and <message> is human-readable.
9
+ *
10
+ * Requirements covered:
11
+ * - CTS-CLI-5: CLI emissions are part of the public contract (stable format)
12
+ * - CTS-OBSERVE-1: CLI emits structured progress as the pipeline runs
13
+ */
14
+ import { stdout } from "node:process";
15
+ export class StdoutProgress {
16
+ emit(event) {
17
+ const stagePath = event.step ? `${event.stage}/${event.step}` : event.stage;
18
+ stdout.write(`[CTS] ${stagePath} ${event.message}\n`);
19
+ }
20
+ }
21
+ /** Capturing emitter for tests. */
22
+ export class CapturingProgress {
23
+ events = [];
24
+ emit(event) {
25
+ this.events.push(event);
26
+ }
27
+ }
28
+ //# sourceMappingURL=progress.js.map