@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.
- package/README.md +169 -22
- package/dist/cli.js +121 -60
- package/dist/codebase-to-spec/budget.d.ts +53 -0
- package/dist/codebase-to-spec/budget.js +80 -0
- package/dist/codebase-to-spec/cache.d.ts +49 -0
- package/dist/codebase-to-spec/cache.js +54 -0
- package/dist/codebase-to-spec/claude.d.ts +69 -0
- package/dist/codebase-to-spec/claude.js +126 -0
- package/dist/codebase-to-spec/compose.d.ts +49 -0
- package/dist/codebase-to-spec/compose.js +124 -0
- package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
- package/dist/codebase-to-spec/edit-loop.js +195 -0
- package/dist/codebase-to-spec/editor.d.ts +54 -0
- package/dist/codebase-to-spec/editor.js +74 -0
- package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
- package/dist/codebase-to-spec/exit-codes.js +58 -0
- package/dist/codebase-to-spec/fan-out.d.ts +63 -0
- package/dist/codebase-to-spec/fan-out.js +215 -0
- package/dist/codebase-to-spec/interactive.d.ts +30 -0
- package/dist/codebase-to-spec/interactive.js +48 -0
- package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
- package/dist/codebase-to-spec/outline-review-loop.js +187 -0
- package/dist/codebase-to-spec/pack.d.ts +51 -0
- package/dist/codebase-to-spec/pack.js +127 -0
- package/dist/codebase-to-spec/planner.d.ts +41 -0
- package/dist/codebase-to-spec/planner.js +76 -0
- package/dist/codebase-to-spec/present.d.ts +94 -0
- package/dist/codebase-to-spec/present.js +288 -0
- package/dist/codebase-to-spec/progress.d.ts +33 -0
- package/dist/codebase-to-spec/progress.js +28 -0
- package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
- package/dist/codebase-to-spec/prompts/editor.js +57 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
- package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
- package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
- package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
- package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/specifier.js +100 -0
- package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/style-check.js +78 -0
- package/dist/codebase-to-spec/schemas.d.ts +257 -0
- package/dist/codebase-to-spec/schemas.js +183 -0
- package/dist/codebase-to-spec/skill-install.d.ts +57 -0
- package/dist/codebase-to-spec/skill-install.js +79 -0
- package/dist/codebase-to-spec/slice.d.ts +49 -0
- package/dist/codebase-to-spec/slice.js +111 -0
- package/dist/codebase-to-spec/specifier.d.ts +60 -0
- package/dist/codebase-to-spec/specifier.js +79 -0
- package/dist/codebase-to-spec/style-check.d.ts +29 -0
- package/dist/codebase-to-spec/style-check.js +33 -0
- package/dist/codebase-to-spec/summary.d.ts +51 -0
- package/dist/codebase-to-spec/summary.js +183 -0
- package/dist/codebase-to-spec/validate.d.ts +46 -0
- package/dist/codebase-to-spec/validate.js +130 -0
- package/dist/commands/acceptance-test.d.ts +6 -0
- package/dist/commands/{browsertest.js → acceptance-test.js} +36 -29
- package/dist/commands/ai-setup.d.ts +5 -0
- package/dist/commands/ai-setup.js +441 -0
- package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose.js +57 -0
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
- package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
- package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
- package/dist/commands/codebase-to-spec/fan-out.js +77 -0
- package/dist/commands/codebase-to-spec/index.d.ts +9 -0
- package/dist/commands/codebase-to-spec/index.js +135 -0
- package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
- package/dist/commands/codebase-to-spec/pack.js +76 -0
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
- package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
- package/dist/commands/codebase-to-spec/present.d.ts +21 -0
- package/dist/commands/codebase-to-spec/present.js +92 -0
- package/dist/commands/codebase-to-spec/run.d.ts +20 -0
- package/dist/commands/codebase-to-spec/run.js +85 -0
- package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
- package/dist/commands/codebase-to-spec/skill-install.js +51 -0
- package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
- package/dist/commands/codebase-to-spec/specify-area.js +82 -0
- package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
- package/dist/commands/codebase-to-spec/style-check.js +42 -0
- package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
- package/dist/commands/codebase-to-spec/validate.js +38 -0
- package/dist/commands/create-requirement-document.d.ts +2 -0
- package/dist/commands/create-requirement-document.js +41 -0
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +55 -0
- package/dist/commands/init.js +132 -117
- package/dist/commands/link.js +27 -27
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/pull.js +116 -121
- package/dist/commands/push.js +106 -112
- package/dist/commands/report.d.ts +6 -2
- package/dist/commands/report.js +177 -122
- package/dist/commands/requirements-for.d.ts +2 -0
- package/dist/commands/requirements-for.js +29 -0
- package/dist/commands/review-test.d.ts +2 -0
- package/dist/commands/review-test.js +75 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.js +39 -0
- package/dist/commands/style-check.d.ts +7 -0
- package/dist/commands/style-check.js +75 -0
- package/dist/commands/tests-for.d.ts +2 -0
- package/dist/commands/tests-for.js +80 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +72 -0
- package/dist/config.js +1 -1
- package/dist/convex.d.ts +34 -22
- package/dist/convex.js +38 -22
- package/dist/harness/cache.d.ts +1 -5
- package/dist/harness/cache.js +49 -59
- package/dist/harness/convexReporting.d.ts +1 -1
- package/dist/harness/convexReporting.js +9 -7
- package/dist/harness/coverageCache.js +3 -3
- package/dist/harness/finalize.js +59 -46
- package/dist/harness/index.d.ts +6 -7
- package/dist/harness/index.js +9 -10
- package/dist/harness/prepare.js +6 -5
- package/dist/harness/requirementsLoader.d.ts +2 -2
- package/dist/harness/requirementsLoader.js +13 -35
- package/dist/harness/tracking.js +18 -18
- package/dist/harness/types.d.ts +1 -1
- package/dist/mcp/convexClient.d.ts +0 -39
- package/dist/mcp/convexClient.js +2 -107
- package/dist/mcp/handlers/authoring.d.ts +1 -1
- package/dist/mcp/handlers/authoring.js +30 -234
- package/dist/mcp/handlers/debug.d.ts +2 -3
- package/dist/mcp/handlers/debug.js +10 -10
- package/dist/mcp/handlers/get.d.ts +1 -1
- package/dist/mcp/handlers/get.js +11 -10
- package/dist/mcp/handlers/index.d.ts +20 -20
- package/dist/mcp/handlers/index.js +10 -10
- package/dist/mcp/handlers/list.d.ts +4 -33
- package/dist/mcp/handlers/list.js +16 -38
- package/dist/mcp/handlers/push.d.ts +1 -1
- package/dist/mcp/handlers/push.js +28 -18
- package/dist/mcp/handlers/report.d.ts +16 -0
- package/dist/mcp/handlers/report.js +134 -0
- package/dist/mcp/handlers/review.d.ts +1 -1
- package/dist/mcp/handlers/review.js +40 -59
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +7 -9
- package/dist/mcp/handlers/test-mapping.d.ts +1 -1
- package/dist/mcp/handlers/test-mapping.js +14 -14
- package/dist/mcp/handlers/types.d.ts +3 -3
- package/dist/mcp/handlers/types.js +2 -2
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +147 -167
- package/dist/push/core.d.ts +2 -2
- package/dist/push/core.js +20 -20
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +2 -2
- package/dist/requirements/cloud-ai.d.ts +57 -0
- package/dist/requirements/cloud-ai.js +104 -0
- package/dist/requirements/cloud-coverage.d.ts +41 -0
- package/dist/requirements/cloud-coverage.js +60 -0
- package/dist/requirements/coverage.d.ts +45 -0
- package/dist/requirements/coverage.js +114 -0
- package/dist/{mcp → requirements}/grep.d.ts +10 -1
- package/dist/{mcp → requirements}/grep.js +89 -44
- package/dist/{mcp/requirements.d.ts → requirements/index.d.ts} +19 -3
- package/dist/{mcp/requirements.js → requirements/index.js} +54 -35
- package/dist/requirements/style-guide.d.ts +67 -0
- package/dist/requirements/style-guide.js +299 -0
- package/dist/{mcp → requirements}/testCodeExtractor.js +24 -26
- package/dist/schema/browser.d.ts +8 -8
- package/dist/schema/browser.js +13 -15
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -44
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +11 -11
- package/dist/schema/index.d.ts +9 -9
- package/dist/schema/index.js +15 -15
- package/dist/schema/parser-core.d.ts +1 -1
- package/dist/schema/parser-core.js +23 -22
- package/dist/schema/parser.d.ts +3 -3
- package/dist/schema/parser.js +27 -31
- package/dist/schema/resolver.d.ts +1 -1
- package/dist/schema/resolver.js +9 -9
- package/dist/schema/scenario.d.ts +1 -1
- package/dist/schema/scenario.js +1 -1
- package/dist/schema/schemas.d.ts +3 -3
- package/dist/schema/schemas.js +41 -28
- package/dist/schema/test-schema.js +27 -27
- package/dist/templates/context-file-section.md +3 -2
- package/dist/templates/example-requirements.js +1 -1
- package/dist/templates/example-requirements.ts +3 -1
- package/dist/templates/requirements-readme.js +1 -1
- package/dist/templates/requirements-readme.ts +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
- package/dist/utils/brand.js +3 -3
- package/dist/utils/browser-launch.js +4 -4
- package/dist/utils/context-file.d.ts +1 -1
- package/dist/utils/context-file.js +26 -26
- package/dist/utils/env.js +7 -7
- package/dist/utils/gitignore.js +7 -7
- package/dist/utils/oauth-callback-server.d.ts +1 -1
- package/dist/utils/oauth-callback-server.js +27 -25
- package/dist/utils/oauth-flow.js +32 -29
- package/dist/utils/project-discovery.d.ts +3 -3
- package/dist/utils/project-discovery.js +18 -17
- package/dist/utils/project-name.js +8 -8
- package/dist/utils/project-selector.d.ts +1 -1
- package/dist/utils/project-selector.js +24 -21
- package/dist/utils/project-settings.d.ts +1 -1
- package/dist/utils/project-settings.js +24 -22
- package/dist/utils/templates.js +6 -6
- package/package.json +3 -2
- package/dist/commands/browsertest.d.ts +0 -6
- package/dist/commands/login.d.ts +0 -12
- package/dist/commands/login.js +0 -117
- package/dist/commands/logout.d.ts +0 -5
- package/dist/commands/logout.js +0 -17
- package/dist/commands/mcp-setup.d.ts +0 -5
- package/dist/commands/mcp-setup.js +0 -431
- package/dist/commands/test.d.ts +0 -6
- package/dist/commands/test.js +0 -78
- package/dist/mcp/handlers/coverage.d.ts +0 -44
- package/dist/mcp/handlers/coverage.js +0 -105
- package/dist/mcp/types.d.ts +0 -27
- package/dist/mcp/types.js +0 -2
- package/dist/utils/local-project.d.ts +0 -31
- package/dist/utils/local-project.js +0 -33
- package/dist/utils/token-refresh.d.ts +0 -24
- package/dist/utils/token-refresh.js +0 -69
- package/dist/utils/token-storage.d.ts +0 -31
- package/dist/utils/token-storage.js +0 -57
- /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
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import * as
|
|
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,
|
|
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:
|
|
21
|
-
plugins: [
|
|
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 =
|
|
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 ===
|
|
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
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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,
|
|
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
|
|
146
|
-
// Skip files that can't be read
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
146
|
+
catch { }
|
|
149
147
|
}
|
|
150
148
|
return matchingFiles;
|
|
151
149
|
}
|
package/dist/schema/browser.d.ts
CHANGED
|
@@ -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 {
|
|
7
|
-
export type {
|
|
8
|
-
export {
|
|
9
|
-
export
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export
|
|
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
|
package/dist/schema/browser.js
CHANGED
|
@@ -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,
|
|
15
|
+
MetadataSchema,
|
|
16
|
+
// Prefix/key utilities
|
|
17
|
+
normalizePrefix, ParsedCriterionSchema, parseRequirementKey, REQUIREMENT_KEY_PATTERN,
|
|
10
18
|
// Patterns (for external validation)
|
|
11
|
-
REQUIREMENT_PREFIX_PATTERN,
|
|
19
|
+
REQUIREMENT_PREFIX_PATTERN, RequirementKeySchema, RequirementNodeSchema, RequirementPrefixSchema, RequirementsFileSchema,
|
|
12
20
|
// Validation
|
|
13
|
-
ValidationError, validateMetadata, validateRequirementNode, validateRequirementsFile,
|
|
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
|
|
23
|
+
export { buildScenarioFromRequirements, requirementTreeToScenario, } from "./scenario.js";
|
|
26
24
|
//# sourceMappingURL=browser.js.map
|
package/dist/schema/builder.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build Markdown requirements files from structured data.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
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.
|
package/dist/schema/builder.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build Markdown requirements files from structured data.
|
|
3
3
|
*/
|
|
4
|
-
import YAML from
|
|
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 ===
|
|
59
|
-
const depth = position.split(
|
|
60
|
-
const indent =
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
74
|
+
markdown += "\n";
|
|
106
75
|
}
|
|
107
|
-
return markdown.trimEnd()
|
|
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 {
|
|
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?:
|
|
19
|
+
metadata?: unknown;
|
|
20
20
|
externalLinks?: {
|
|
21
21
|
jira?: string;
|
|
22
22
|
notion?: string;
|