@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,299 @@
1
+ /**
2
+ * Style-guide template generator. Produces the markdown document that both
3
+ * the MCP `create_requirement_document` tool and the CLI
4
+ * `dotreq create-requirement-document` verb return.
5
+ *
6
+ * The output is a full ready-to-paste-into-a-PR markdown document that
7
+ * describes the requirements format, embeds a worked example, and includes
8
+ * style principles for writing requirements and tests. Discovered label /
9
+ * key patterns from the calling workspace and (optional) project-owner-
10
+ * supplied custom guidance are interpolated into the body.
11
+ *
12
+ * If the project has a `.requirements/STYLE.md`, callers can pass its
13
+ * contents via `localStyleGuide` to use that body in place of the bundled
14
+ * defaults. See {@link readLocalStyleGuide} for the lookup helper.
15
+ */
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ /**
19
+ * Conventional location of a project's STYLE.md, relative to the workspace
20
+ * root. Exported so callers (init, push/pull, tests) can reference one place.
21
+ */
22
+ export const STYLE_MD_PATH = ".requirements/STYLE.md";
23
+ /**
24
+ * Read `.requirements/STYLE.md` if the workspace has one. Returns the file
25
+ * contents on success, `null` if the file is absent. Empty / whitespace-only
26
+ * files are treated as absent so the bundled defaults still apply.
27
+ */
28
+ export function readLocalStyleGuide(workspaceRoot) {
29
+ const fullPath = join(workspaceRoot, STYLE_MD_PATH);
30
+ if (!existsSync(fullPath)) {
31
+ return null;
32
+ }
33
+ const contents = readFileSync(fullPath, "utf-8");
34
+ return contents.trim().length > 0 ? contents : null;
35
+ }
36
+ /**
37
+ * Build the body of the bundled default style guide. This is the content
38
+ * that lives inside the "# Requirements File Template" preamble — the
39
+ * format syntax, key/label conventions, style principles, and example
40
+ * requirements — with discovered patterns and custom guidance interpolated.
41
+ *
42
+ * Exposed separately from {@link generateStyleGuide} so callers can:
43
+ * - scaffold `.requirements/STYLE.md` with the bundled defaults at init time
44
+ * - swap the body wholesale (via `localStyleGuide`) without losing the
45
+ * wrapping preamble + "Next Steps" footer
46
+ */
47
+ export function generateStyleGuideBody(params) {
48
+ const { requirements, customStyleGuidance } = params;
49
+ const labels = new Set();
50
+ const keyPrefixes = new Set();
51
+ for (const req of requirements) {
52
+ if (req.label?.trim()) {
53
+ labels.add(req.label);
54
+ }
55
+ if (req.path.length === 0 && req.id) {
56
+ const lastDashIndex = req.id.lastIndexOf("-");
57
+ if (lastDashIndex > 0) {
58
+ keyPrefixes.add(req.id.substring(0, lastDashIndex));
59
+ }
60
+ }
61
+ }
62
+ const discoveredLabels = Array.from(labels).sort();
63
+ const discoveredPrefixes = Array.from(keyPrefixes).sort();
64
+ let labelGuidance;
65
+ if (discoveredLabels.length > 0) {
66
+ const labelList = discoveredLabels
67
+ .slice(0, 10)
68
+ .map((l) => `"${l}"`)
69
+ .join(", ");
70
+ const more = discoveredLabels.length > 10
71
+ ? ` (and ${discoveredLabels.length - 10} more)`
72
+ : "";
73
+ labelGuidance = `**Existing labels in this codebase:** ${labelList}${more}
74
+
75
+ **Use these existing labels** to maintain consistency. If you're unsure which labels to use for a new requirement, ask the user.`;
76
+ }
77
+ else {
78
+ labelGuidance = `**No existing requirements found in this codebase.**
79
+
80
+ **Default to unlabeled requirements** (\`0. → content\`). If the user wants labels, ask them which format they prefer. Do not choose an opinionated framework like Given/When/Then without explicit user consent.`;
81
+ }
82
+ let keyGuidance;
83
+ if (discoveredPrefixes.length > 0) {
84
+ const prefixList = discoveredPrefixes.map((p) => `"${p}"`).join(", ");
85
+ keyGuidance = `**Existing requirement key prefixes in this codebase:** ${prefixList}
86
+
87
+ **Match the existing pattern** when creating new requirement keys. Use the same domain prefixes and sequential numbering style.`;
88
+ }
89
+ else {
90
+ keyGuidance = `**No existing requirements found in this codebase.**
91
+
92
+ **Use concise domain prefixes** like \`AUTH-1\`, \`LOGIN-1\`, etc. Start numbering at 1 and increment sequentially.`;
93
+ }
94
+ let userStyleGuidance = "";
95
+ if (customStyleGuidance?.trim()) {
96
+ userStyleGuidance = `
97
+
98
+ ## User-Provided Style Guidelines
99
+
100
+ The following style guidelines were provided by the project owner. When these conflict with the defaults above, prioritize the user's guidelines.
101
+
102
+ ${customStyleGuidance.trim()}`;
103
+ }
104
+ const template = `---
105
+ document:
106
+ title: "Example Requirements"
107
+ ---
108
+
109
+ # Example Requirements
110
+
111
+ This template demonstrates the dotrequirements Markdown format and style guidelines.
112
+
113
+ ## Syntax Overview
114
+
115
+ **File naming:** Use \`*.requirements.md\` pattern (colocated: \`auth.requirements.md\` or centralized: \`.requirements/auth.requirements.md\`)
116
+
117
+ **Block format:**
118
+ \`\`\`dotrequirements
119
+ KEY: Root requirement content
120
+ 0. → First criterion (unlabeled)
121
+ 1. Label → Second criterion (with label)
122
+ 1.0. → Nested criterion (unlabeled)
123
+ \`\`\`
124
+
125
+ - First line: \`KEY: content\` (requirement key and description)
126
+ - Criteria: \`position. Label → content\` or \`position. → content\` (unlabeled)
127
+ - Position: \`0\`, \`1\`, \`2\` (top-level) or \`0.0\`, \`1.0\` (nested) - defines hierarchy
128
+ - Delimiter: \`→\` or \`->\` separates optional label from content
129
+
130
+ ## Requirement Keys: Concise and Sequential
131
+
132
+ ${keyGuidance}
133
+
134
+ **Key format:** \`DOMAIN-FEATURE-N\` where N is sequential (1, 2, 3...)
135
+
136
+ **Best practices:**
137
+ 1. **Concise domains** - Use short, clear prefixes
138
+ - ✅ \`AUTHZ-1\` (authorization)
139
+ - ✅ \`AUTH-1\` (authentication)
140
+ - ❌ \`AUTHORIZATION-1\` (too verbose)
141
+ - ❌ \`REQ-IDENTITY-ACCESS-AUTHZ-1\` (too nested)
142
+
143
+ 2. **Sequential, 1-indexed numbering** - Start at 1, no padding
144
+ - ✅ \`LOGIN-1\`, \`LOGIN-2\`, \`LOGIN-3\`
145
+ - ❌ \`LOGIN-0\` (don't use 0-indexing for requirement IDs)
146
+ - ❌ \`LOGIN-001\` (no zero-padding)
147
+
148
+ 3. **Unique across project** - Each key must be unique in the entire project
149
+
150
+ 4. **Match existing patterns** - Check existing requirements first and follow their convention
151
+
152
+ ## Labels: Match Your Codebase or Ask the User
153
+
154
+ ${labelGuidance}
155
+
156
+ ## Style Principles for Requirements
157
+
158
+ **1. Use Concrete Examples**: Replace vague language with specific, testable conditions.
159
+ - ❌ "users can log in" or "works properly"
160
+ - ✅ "When a registered user provides valid credentials, they are authenticated"
161
+
162
+ **2. Write Natural, Concise Prose**: Avoid terseness and verbosity. Use declarative style (not "should").
163
+ - ❌ "registered user with valid credentials is authenticated" (too terse)
164
+ - ❌ "A registered user, whose account was created on Tuesday and whose life story is as follows..." (too verbose)
165
+ - ✅ "When a registered user provides valid credentials, they are authenticated"
166
+
167
+ **3. Keep Arrange/Act/Assert in Mind**: Well-written requirements describe preconditions, trigger, and result.
168
+ - ✅ "When [preconditions:] a registered user [trigger:] provides valid credentials, [result:] they are authenticated"
169
+
170
+ **4. Be Framework Neutral**: Don't prescribe Given/When/Then vs AC vs other formats - focus on content quality.
171
+
172
+ **5. Use Named Personas**: Establish personas in parent requirements, reuse in children.
173
+ - ✅ Parent: "A registered user, Jamie, can log in normally" → Child: "When Jamie provides valid credentials, they are authenticated"
174
+
175
+ **6. Use User-Centric Language**: Describe user experience, not technical internals.
176
+ - ❌ "they are redirected to app.dotrequirements.io/redirect/dashboard"
177
+ - ✅ "they are automatically brought to the dashboard"
178
+
179
+ **7. Single Action Per Requirement**: Don't chain multiple actions with "and then".
180
+ - ❌ "When Robin provides credentials, requests an OTP, then provides the OTP..."
181
+ - ✅ Break into separate requirements for each action
182
+
183
+ **8. Each Requirement Should Be Independent**: Requirements within a block should be independently testable. If they share preconditions or form a sequence, either nest them or restate context.
184
+ - ❌ "0. → When Jordan submits signup, an account is created" / "1. → Welcome email is sent" / "2. → Dashboard appears"
185
+ - ✅ Option A: Restate context - "1. → When Jordan submits signup, a welcome email is sent"
186
+ - ✅ Option B: Use nesting - "0. When → Jordan submits signup" / " 0.0. Then → an account is created"
187
+ - Test: Can you understand what's being tested by reading just one requirement, or do you need to read its siblings?
188
+
189
+ **9. Focus on Behavior, Not Design**: Describe what happens, not UI specifics.
190
+ - ❌ "enters valid credentials into two single-line input fields and presses a green button"
191
+ - ✅ "provides valid credentials"
192
+
193
+ **10. Focus on Outcomes, Not Implementation**: User perspective, even for technical requirements.
194
+ - ❌ "When the app requests that Twilio send Casey an OTP from /email/POST endpoint..."
195
+ - ✅ "When Casey requests an email OTP..."
196
+ - Note: Even technical requirements can be user-centric: "95 percent of users experience under 1 second of delay"
197
+
198
+ **11. Decompose Large Requirements**: If it can't be validated with a single test, break it down.
199
+ ${userStyleGuidance}
200
+
201
+ ## Example Requirements
202
+
203
+ **Unlabeled (recommended default):**
204
+ \`\`\`dotrequirements
205
+ AUTH-LOGIN-1: A registered user, Jamie, can log in to their account
206
+ 0. → When Jamie provides their registered email and correct password, they are authenticated and brought to their dashboard
207
+ 1. → When Jamie provides an incorrect password, they see an error message and remain on the login page
208
+ 2. → When Jamie's account has been deactivated, they see a message explaining their account status
209
+ \`\`\`
210
+
211
+ **With labels (example only - DO NOT use opinionated formats like Given/When/Then without asking the user first):**
212
+ \`\`\`dotrequirements
213
+ PAYMENT-REFUND-1: A customer, Alex, receives a refund after returning an item
214
+ 0. Given → Alex purchased a laptop from the store 10 days ago
215
+ 1. Given → Alex initiates a return through their order history
216
+ 2. When → Alex's returned laptop is received and inspected at the warehouse
217
+ 3. Then → Alex receives a refund to their original payment method within 5 business days
218
+ 4. Then → Alex receives an email confirmation with the refund amount and expected timeline
219
+ \`\`\`
220
+
221
+ ## Referencing Requirements in Tests
222
+
223
+ **Use \`requirement()\` as the test description** - it returns a string:
224
+
225
+ \`\`\`typescript
226
+ import { describe, it, expect } from 'vitest';
227
+ import { requirement } from '@popoverai/dotrequirements/test';
228
+
229
+ describe(requirement('AUTH-LOGIN-1'), () => {
230
+ describe(requirement('AUTH-LOGIN-1.given'), () => {
231
+ // Arrange: Create Jamie's account
232
+ });
233
+
234
+ describe(requirement('AUTH-LOGIN-1.when'), () => {
235
+ // Act: Submit login with valid credentials
236
+
237
+ it(requirement('AUTH-LOGIN-1.then'), () => {
238
+ // Assert: Jamie is authenticated
239
+ });
240
+ });
241
+ });
242
+ \`\`\`
243
+
244
+ **Test Style Principles:**
245
+
246
+ **1. Use requirement() AS the description**: Don't put requirement() inside test body or in comments.
247
+ - ✅ \`test(requirement('AUTH-LOGIN-1'), () => { /* test code */ })\`
248
+ - ✅ \`it(requirement('LOGIN-1.then'), () => { /* assert */ })\`
249
+ - ❌ \`test("user can log in", () => { requirement('AUTH-LOGIN-1'); /* test code */ })\`
250
+ - ❌ \`// LOGIN-1: User can log in\` (comment instead of using requirement() as description)
251
+
252
+ **2. Comments describe the TEST, not the requirement**: Don't copy requirement text verbatim.
253
+ - ✅ \`describe(requirement('REQ-1.0'), () => { // registered user, valid credentials\`
254
+ - ❌ \`describe(requirement('REQ-1.0'), () => { // 0. When a registered user provides valid credentials, they are authenticated\` (verbatim copy is a red flag)
255
+ - Note: Comments should reflect what the test actually does, not just repeat what the requirement says
256
+
257
+ **3. Structure tests to match requirements**: Nest describe/it blocks for structured requirements.
258
+ - ✅ For structured requirements: \`describe(requirement('LOGIN-1.given'))\` nested with \`describe(requirement('LOGIN-1.when'))\` and \`it(requirement('LOGIN-1.then'))\`
259
+ - ✅ For simple requirements: \`test(requirement('AUTH-LOGIN-1'), () => { /* arrange, act, assert all in one */ })\`
260
+
261
+ **Path formats:**
262
+ - \`requirement('AUTH-LOGIN-1')\` - root requirement
263
+ - \`requirement('AUTH-LOGIN-1.0')\` - by numeric position
264
+ - \`requirement('AUTH-LOGIN-1.given')\` - by label (case-insensitive)
265
+ - \`requirement('AUTH-LOGIN-1.given#1')\` - disambiguate duplicate labels`;
266
+ return template;
267
+ }
268
+ /**
269
+ * Build a complete style-guide document for the calling workspace.
270
+ * Returns a single markdown string suitable for printing to stdout or
271
+ * wrapping in an MCP text response.
272
+ *
273
+ * When `localStyleGuide` is provided and non-empty, that content replaces
274
+ * the bundled default body. The preamble and "Next Steps" footer are
275
+ * always applied.
276
+ */
277
+ export function generateStyleGuide(params) {
278
+ const { filePath = ".requirements/example.requirements.md", localStyleGuide, } = params;
279
+ const body = localStyleGuide?.trim()
280
+ ? localStyleGuide
281
+ : generateStyleGuideBody(params);
282
+ return `# Requirements File Template
283
+
284
+ Here's a comprehensive template for \`${filePath}\` with format and style guidance:
285
+
286
+ \`\`\`markdown
287
+ ${body}
288
+ \`\`\`
289
+
290
+ ## Next Steps
291
+
292
+ 1. **Create file**: Save this template as \`${filePath}\` and edit it for your feature
293
+ 2. **Refine style** (optional): Run \`style-check\` (or call the \`style_check\` MCP tool) for AI feedback
294
+ 3. **Validate syntax**: Run \`validate\` (or call the \`validate\` MCP tool) to verify format
295
+ 4. **Push to cloud**: Run \`dotrequirements push\` (or call the \`push_requirements\` MCP tool) to sync
296
+
297
+ **Note**: Requirements files can be colocated with code (\`src/auth.requirements.md\`) or centralized in \`.requirements/\` directory.`;
298
+ }
299
+ //# sourceMappingURL=style-guide.js.map
@@ -4,30 +4,32 @@
4
4
  * Finds requirement() call references and extracts the enclosing
5
5
  * meaningful code block (function, describe, test, etc.)
6
6
  */
7
- import { parse } from '@babel/parser';
8
- import traverse from '@babel/traverse';
9
- import * as t from '@babel/types';
10
- import * as fs from 'fs';
7
+ import * as fs from "node:fs";
8
+ import { parse } from "@babel/parser";
9
+ import traverse from "@babel/traverse";
10
+ import * as t from "@babel/types";
11
11
  /**
12
12
  * Find all test code blocks that reference a specific requirement
13
13
  */
14
14
  export function findTestCodeForRequirement(filePath, requirementId) {
15
15
  try {
16
- const code = fs.readFileSync(filePath, 'utf-8');
16
+ const code = fs.readFileSync(filePath, "utf-8");
17
17
  const results = [];
18
18
  // Parse with TypeScript and JSX support
19
19
  const ast = parse(code, {
20
- sourceType: 'module',
21
- plugins: ['typescript', 'jsx'],
20
+ sourceType: "module",
21
+ plugins: ["typescript", "jsx"],
22
22
  errorRecovery: true,
23
23
  });
24
24
  // Handle both ES module and CommonJS exports
25
- const traverseFunc = typeof traverse === 'function' ? traverse : traverse.default;
25
+ const traverseFunc =
26
+ // biome-ignore lint/suspicious/noExplicitAny: @babel/traverse CJS default-export interop (callable function vs namespace import)
27
+ typeof traverse === "function" ? traverse : traverse.default;
26
28
  traverseFunc(ast, {
27
29
  CallExpression(path) {
28
30
  // Check if this is a requirement('ID', ...) call
29
31
  const callee = path.node.callee;
30
- if (t.isIdentifier(callee) && callee.name === 'requirement') {
32
+ if (t.isIdentifier(callee) && callee.name === "requirement") {
31
33
  const args = path.node.arguments;
32
34
  // Extract all string literal arguments (supports multi-requirement calls)
33
35
  const refIds = [];
@@ -39,12 +41,11 @@ export function findTestCodeForRequirement(filePath, requirementId) {
39
41
  // Check if any argument matches the requirement we're looking for
40
42
  // Match exact ID or if it's a child (e.g., searching for REQ-123 matches REQ-123.0)
41
43
  for (const refId of refIds) {
42
- const isMatch = refId === requirementId ||
43
- refId.startsWith(`${requirementId}.`);
44
+ const isMatch = refId === requirementId || refId.startsWith(`${requirementId}.`);
44
45
  if (isMatch) {
45
46
  // Find the meaningful enclosing block
46
47
  const contextNode = findMeaningfulParent(path);
47
- if (contextNode && contextNode.node.loc) {
48
+ if (contextNode?.node.loc) {
48
49
  const { start, end } = contextNode.node.loc;
49
50
  const nodeStart = contextNode.node.start ?? 0;
50
51
  const nodeEnd = contextNode.node.end ?? code.length;
@@ -91,15 +92,15 @@ function findMeaningfulParent(path) {
91
92
  const callee = node.callee;
92
93
  // Common test framework function names
93
94
  const testFunctionNames = [
94
- 'describe',
95
- 'it',
96
- 'test',
97
- 'suite',
98
- 'context',
99
- 'beforeEach',
100
- 'afterEach',
101
- 'beforeAll',
102
- 'afterAll',
95
+ "describe",
96
+ "it",
97
+ "test",
98
+ "suite",
99
+ "context",
100
+ "beforeEach",
101
+ "afterEach",
102
+ "beforeAll",
103
+ "afterAll",
103
104
  ];
104
105
  if (t.isIdentifier(callee) && testFunctionNames.includes(callee.name)) {
105
106
  return current;
@@ -125,7 +126,7 @@ export function findFilesWithRequirement(files, requirementId) {
125
126
  const matchingFiles = [];
126
127
  for (const file of files) {
127
128
  try {
128
- const code = fs.readFileSync(file, 'utf-8');
129
+ const code = fs.readFileSync(file, "utf-8");
129
130
  // Check for exact match or child references (e.g., AUTH-VALID-LOGIN or AUTH-VALID-LOGIN.0)
130
131
  // Simple string search first for performance
131
132
  // Patterns match both single and multi-arg calls:
@@ -142,10 +143,7 @@ export function findFilesWithRequirement(files, requirementId) {
142
143
  matchingFiles.push(file);
143
144
  }
144
145
  }
145
- catch (error) {
146
- // Skip files that can't be read
147
- continue;
148
- }
146
+ catch { }
149
147
  }
150
148
  return matchingFiles;
151
149
  }
@@ -3,12 +3,12 @@
3
3
  * This file excludes Node.js-specific functionality (file I/O, fs module).
4
4
  * Use this entry point when importing from browser/web environments.
5
5
  */
6
- export { MetadataSchema, RequirementNodeSchema, RequirementsFileSchema, ParsedCriterionSchema, RequirementPrefixSchema, RequirementKeySchema, REQUIREMENT_PREFIX_PATTERN, REQUIREMENT_KEY_PATTERN, ValidationError, validateMetadata, validateRequirementNode, validateRequirementsFile, validatePrefix, validateKey, normalizePrefix, parseRequirementKey, buildRequirementKey, } from './schemas.js';
7
- export type { Metadata, RequirementNode, RequirementsFile, ParsedCriterion, RequirementPrefix, RequirementKey, } from './schemas.js';
8
- export { DEFAULT_DELIMITER, buildRequirementsMarkdown, buildRequirementMarkdown, } from './builder.js';
9
- export type { ConvexRequirement, } from './conversions.js';
10
- export { convexToRequirements, requirementsToConvex, buildMetadata, extractRequirementKeys, groupByRoot, constructKey, parseKey, } from './conversions.js';
11
- export { DELIMITER_PATTERN, parseCriterionLine, parseRootLine, parseRequirementBlock, extractRequirementBlocks, parseRequirementBlocksFromMarkdown, flattenRequirementTree, findRequirementById, getAllRequirements, } from './parser-core.js';
12
- export { buildScenarioFromRequirements, requirementTreeToScenario, } from './scenario.js';
13
- export type { Scenario, ScenarioStep, ScenarioAssertionSource, BuildScenarioOptions, } from './scenario.js';
6
+ export { buildRequirementMarkdown, buildRequirementsMarkdown, DEFAULT_DELIMITER, } from "./builder.js";
7
+ export type { ConvexRequirement } from "./conversions.js";
8
+ export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
9
+ export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, } from "./parser-core.js";
10
+ export type { Metadata, ParsedCriterion, RequirementKey, RequirementNode, RequirementPrefix, RequirementsFile, } from "./schemas.js";
11
+ export { buildRequirementKey, MetadataSchema, normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN, REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema, ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
12
+ export type { BuildScenarioOptions, Scenario, ScenarioAssertionSource, ScenarioStep, } from "./scenario.js";
13
+ export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
14
14
  //# sourceMappingURL=browser.d.ts.map
@@ -3,24 +3,22 @@
3
3
  * This file excludes Node.js-specific functionality (file I/O, fs module).
4
4
  * Use this entry point when importing from browser/web environments.
5
5
  */
6
+ // Building (uses yaml package - browser-safe)
7
+ export { buildRequirementMarkdown, buildRequirementsMarkdown, DEFAULT_DELIMITER, } from "./builder.js";
8
+ export { buildMetadata, constructKey, convexToRequirements, extractRequirementKeys, groupByRoot, parseKey, requirementsToConvex, } from "./conversions.js";
9
+ // Parser core (pure TypeScript - browser-safe, no fs dependency)
10
+ // These functions parse markdown strings directly without file I/O
11
+ export { DELIMITER_PATTERN, extractRequirementBlocks, findRequirementById, flattenRequirementTree, getAllRequirements, parseCriterionLine, parseRequirementBlock, parseRequirementBlocksFromMarkdown, parseRootLine, } from "./parser-core.js";
6
12
  // Schemas and types (uses zod - browser-safe)
7
- export {
13
+ export { buildRequirementKey,
8
14
  // Zod schemas
9
- MetadataSchema, RequirementNodeSchema, RequirementsFileSchema, ParsedCriterionSchema, RequirementPrefixSchema, RequirementKeySchema,
15
+ MetadataSchema,
16
+ // Prefix/key utilities
17
+ normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN,
10
18
  // Patterns (for external validation)
11
- REQUIREMENT_PREFIX_PATTERN, REQUIREMENT_KEY_PATTERN,
19
+ REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema,
12
20
  // Validation
13
- ValidationError, validateMetadata, validateRequirementNode, validateRequirementsFile, validatePrefix, validateKey,
14
- // Prefix/key utilities
15
- normalizePrefix, parseRequirementKey, buildRequirementKey, } from './schemas.js';
16
- // Building (uses yaml package - browser-safe)
17
- export { DEFAULT_DELIMITER, buildRequirementsMarkdown, buildRequirementMarkdown, } from './builder.js';
18
- export { convexToRequirements, requirementsToConvex, buildMetadata, extractRequirementKeys, groupByRoot, constructKey, parseKey, } from './conversions.js';
19
- // Parser core (pure TypeScript - browser-safe, no fs dependency)
20
- // These functions parse markdown strings directly without file I/O
21
- export { DELIMITER_PATTERN, parseCriterionLine, parseRootLine, parseRequirementBlock, extractRequirementBlocks, parseRequirementBlocksFromMarkdown, flattenRequirementTree, findRequirementById, getAllRequirements, } from './parser-core.js';
22
- // NOTE: parser.ts and resolver.ts are excluded because they use Node.js 'fs' module.
23
- // Use parser-core.ts functions above for browser/Convex environments.
21
+ ValidationError, validateKey, validateMetadata, validatePrefix, validateRequirementNode, validateRequirementsFile, } from "./schemas.js";
24
22
  // Scenario building (pure TypeScript - browser-safe, used by Convex Node actions)
25
- export { buildScenarioFromRequirements, requirementTreeToScenario, } from './scenario.js';
23
+ export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
26
24
  //# sourceMappingURL=browser.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Build Markdown requirements files from structured data.
3
3
  */
4
- import { RequirementNode, Metadata } from './schemas.js';
4
+ import type { Metadata, RequirementNode } from "./schemas.js";
5
5
  /**
6
6
  * Default delimiter for requirements.
7
7
  * Can be overridden for organization-specific preferences.
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Build Markdown requirements files from structured data.
3
3
  */
4
- import YAML from 'yaml';
4
+ import YAML from "yaml";
5
5
  /**
6
6
  * Default delimiter for requirements.
7
7
  * Can be overridden for organization-specific preferences.
8
8
  */
9
- export const DEFAULT_DELIMITER = '→';
9
+ export const DEFAULT_DELIMITER = "→";
10
10
  /**
11
11
  * Build YAML frontmatter from metadata.
12
12
  */
@@ -17,51 +17,20 @@ function buildFrontmatter(metadata) {
17
17
  });
18
18
  return `---\n${yamlStr.trim()}\n---`;
19
19
  }
20
- /**
21
- * Build a dotrequirements block from a requirement node.
22
- * Formats children with explicit position paths and proper indentation.
23
- */
24
- function buildRequirementBlock(node, delimiter = DEFAULT_DELIMITER) {
25
- // First line: root requirement with delimiter
26
- // Include label if it's not empty (empty string means unlabeled)
27
- const rootLabel = node.label
28
- ? `${node.label.charAt(0).toUpperCase() + node.label.slice(1)} `
29
- : '';
30
- let block = `${rootLabel}${delimiter} ${node.content}\n`;
31
- // Recursively build criteria lines
32
- function addCriteria(children, parentPosition = '') {
33
- children.forEach((child, index) => {
34
- const position = parentPosition === '' ? `${index}` : `${parentPosition}.${index}`;
35
- const depth = position.split('.').length - 1;
36
- const indent = ' '.repeat(depth + 1); // 2 spaces per level
37
- // Include label if present, otherwise just delimiter
38
- const labelPart = child.label
39
- ? `${child.label.charAt(0).toUpperCase() + child.label.slice(1)} `
40
- : '';
41
- block += `${indent}${position}. ${labelPart}${delimiter} ${child.content}\n`;
42
- // Recursively add grandchildren
43
- if (child.children && child.children.length > 0) {
44
- addCriteria(child.children, position);
45
- }
46
- });
47
- }
48
- addCriteria(node.children);
49
- return block.trimEnd();
50
- }
51
20
  /**
52
21
  * Build the children portion of a requirement block (without the root line).
53
22
  */
54
23
  function buildChildrenBlock(children) {
55
- let block = '';
56
- function addChildren(nodes, parentPosition = '') {
24
+ let block = "";
25
+ function addChildren(nodes, parentPosition = "") {
57
26
  nodes.forEach((child, index) => {
58
- const position = parentPosition === '' ? `${index}` : `${parentPosition}.${index}`;
59
- const depth = position.split('.').length - 1;
60
- const indent = ' '.repeat(depth + 1); // 2 spaces per level
27
+ const position = parentPosition === "" ? `${index}` : `${parentPosition}.${index}`;
28
+ const depth = position.split(".").length - 1;
29
+ const indent = " ".repeat(depth + 1); // 2 spaces per level
61
30
  // Include label if present, otherwise just delimiter
62
31
  const labelPart = child.label
63
32
  ? `${child.label.charAt(0).toUpperCase() + child.label.slice(1)} `
64
- : '';
33
+ : "";
65
34
  block += `${indent}${position}. ${labelPart}→ ${child.content}\n`;
66
35
  // Recursively add grandchildren
67
36
  if (child.children && child.children.length > 0) {
@@ -79,14 +48,14 @@ function buildRequirementSection(node, title) {
79
48
  const displayTitle = title || node.content;
80
49
  // Heading now just has the title, no key
81
50
  let section = `## ${displayTitle}\n\n`;
82
- section += '```dotrequirements\n';
51
+ section += "```dotrequirements\n";
83
52
  // First line of block has the key
84
53
  section += `${node.id}: ${node.content}\n`;
85
54
  // Then add children
86
55
  if (node.children && node.children.length > 0) {
87
56
  section += buildChildrenBlock(node.children);
88
57
  }
89
- section += '\n```\n';
58
+ section += "\n```\n";
90
59
  return section;
91
60
  }
92
61
  /**
@@ -94,7 +63,7 @@ function buildRequirementSection(node, title) {
94
63
  */
95
64
  export function buildRequirementsMarkdown(metadata, requirements, documentTitle) {
96
65
  let markdown = buildFrontmatter(metadata);
97
- markdown += '\n\n';
66
+ markdown += "\n\n";
98
67
  // Add document title if provided
99
68
  if (documentTitle) {
100
69
  markdown += `# ${documentTitle}\n\n`;
@@ -102,9 +71,9 @@ export function buildRequirementsMarkdown(metadata, requirements, documentTitle)
102
71
  // Add each requirement section
103
72
  for (const req of requirements) {
104
73
  markdown += buildRequirementSection(req);
105
- markdown += '\n';
74
+ markdown += "\n";
106
75
  }
107
- return markdown.trimEnd() + '\n';
76
+ return `${markdown.trimEnd()}\n`;
108
77
  }
109
78
  /**
110
79
  * Build Markdown for a single requirement (for testing/debugging).
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Convert between Convex flat representation and hierarchical RequirementNode trees.
3
3
  */
4
- import { RequirementNode, Metadata } from './schemas.js';
4
+ import type { Metadata, RequirementNode } from "./schemas.js";
5
5
  /**
6
6
  * Convex requirement type (flat structure with position paths).
7
7
  * This mirrors the Convex database schema.
@@ -16,7 +16,7 @@ export interface ConvexRequirement {
16
16
  projectId: string;
17
17
  rootId?: string;
18
18
  position?: string;
19
- metadata?: any;
19
+ metadata?: unknown;
20
20
  externalLinks?: {
21
21
  jira?: string;
22
22
  notion?: string;