@popoverai/dotrequirements 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +478 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +82 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +355 -0
- package/dist/commands/link.d.ts +15 -0
- package/dist/commands/link.js +156 -0
- package/dist/commands/login.d.ts +12 -0
- package/dist/commands/login.js +117 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/mcp-setup.d.ts +5 -0
- package/dist/commands/mcp-setup.js +367 -0
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +10 -0
- package/dist/commands/pull.d.ts +7 -0
- package/dist/commands/pull.js +171 -0
- package/dist/commands/push.d.ts +11 -0
- package/dist/commands/push.js +310 -0
- package/dist/commands/test.d.ts +6 -0
- package/dist/commands/test.js +78 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +16 -0
- package/dist/convex.d.ts +56 -0
- package/dist/convex.js +58 -0
- package/dist/harness/cache.d.ts +135 -0
- package/dist/harness/cache.js +342 -0
- package/dist/harness/convexReporting.d.ts +15 -0
- package/dist/harness/convexReporting.js +136 -0
- package/dist/harness/coverageCache.d.ts +30 -0
- package/dist/harness/coverageCache.js +70 -0
- package/dist/harness/finalize.d.ts +48 -0
- package/dist/harness/finalize.js +299 -0
- package/dist/harness/index.d.ts +70 -0
- package/dist/harness/index.js +103 -0
- package/dist/harness/localReporting.d.ts +6 -0
- package/dist/harness/localReporting.js +49 -0
- package/dist/harness/prepare.d.ts +41 -0
- package/dist/harness/prepare.js +83 -0
- package/dist/harness/requirementsLoader.d.ts +45 -0
- package/dist/harness/requirementsLoader.js +201 -0
- package/dist/harness/tracking.d.ts +49 -0
- package/dist/harness/tracking.js +179 -0
- package/dist/harness/types.d.ts +12 -0
- package/dist/harness/types.js +6 -0
- package/dist/mcp/convexClient.d.ts +43 -0
- package/dist/mcp/convexClient.js +101 -0
- package/dist/mcp/grep.d.ts +24 -0
- package/dist/mcp/grep.js +261 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +1758 -0
- package/dist/mcp/requirements.d.ts +47 -0
- package/dist/mcp/requirements.js +141 -0
- package/dist/mcp/testCodeExtractor.d.ts +22 -0
- package/dist/mcp/testCodeExtractor.js +152 -0
- package/dist/mcp/types.d.ts +27 -0
- package/dist/mcp/types.js +2 -0
- package/dist/schema/browser.d.ts +12 -0
- package/dist/schema/browser.js +24 -0
- package/dist/schema/builder.d.ts +25 -0
- package/dist/schema/builder.js +125 -0
- package/dist/schema/conversions.d.ts +69 -0
- package/dist/schema/conversions.js +201 -0
- package/dist/schema/index.d.ts +14 -0
- package/dist/schema/index.js +24 -0
- package/dist/schema/parser-core.d.ts +61 -0
- package/dist/schema/parser-core.js +247 -0
- package/dist/schema/parser.d.ts +44 -0
- package/dist/schema/parser.js +295 -0
- package/dist/schema/resolver.d.ts +66 -0
- package/dist/schema/resolver.js +185 -0
- package/dist/schema/schemas.d.ts +312 -0
- package/dist/schema/schemas.js +258 -0
- package/dist/schema/test-schema.d.ts +5 -0
- package/dist/schema/test-schema.js +81 -0
- package/dist/templates/antigravity-gemini.md +3 -0
- package/dist/templates/antigravity-overview-rule.md +3 -0
- package/dist/templates/antigravity-test-rule.md +3 -0
- package/dist/templates/behavioral-core.md +25 -0
- package/dist/templates/claude-code-overview-skill.md +6 -0
- package/dist/templates/claude-code-skill.md +6 -0
- package/dist/templates/claude-code-test-skill.md +6 -0
- package/dist/templates/codex-agents.md +3 -0
- package/dist/templates/codex-overview-agents.md +3 -0
- package/dist/templates/codex-test-agents.md +3 -0
- package/dist/templates/cursor-overview-rule.mdc +5 -0
- package/dist/templates/cursor-rule.mdc +5 -0
- package/dist/templates/cursor-test-rule.mdc +5 -0
- package/dist/templates/example-requirements.d.ts +8 -0
- package/dist/templates/example-requirements.js +88 -0
- package/dist/templates/example-requirements.ts +88 -0
- package/dist/templates/overview-core.md +27 -0
- package/dist/templates/requirements-readme.d.ts +5 -0
- package/dist/templates/requirements-readme.js +31 -0
- package/dist/templates/requirements-readme.ts +30 -0
- package/dist/templates/test-writing-core.md +72 -0
- package/dist/utils/brand.d.ts +5 -0
- package/dist/utils/brand.js +8 -0
- package/dist/utils/browser-launch.d.ts +19 -0
- package/dist/utils/browser-launch.js +36 -0
- package/dist/utils/detect-existing-project.d.ts +5 -0
- package/dist/utils/detect-existing-project.js +34 -0
- package/dist/utils/env.d.ts +19 -0
- package/dist/utils/env.js +56 -0
- package/dist/utils/gitignore.d.ts +7 -0
- package/dist/utils/gitignore.js +29 -0
- package/dist/utils/local-project.d.ts +31 -0
- package/dist/utils/local-project.js +33 -0
- package/dist/utils/oauth-callback-server.d.ts +28 -0
- package/dist/utils/oauth-callback-server.js +156 -0
- package/dist/utils/oauth-flow.d.ts +22 -0
- package/dist/utils/oauth-flow.js +120 -0
- package/dist/utils/project-discovery.d.ts +57 -0
- package/dist/utils/project-discovery.js +146 -0
- package/dist/utils/project-name.d.ts +8 -0
- package/dist/utils/project-name.js +48 -0
- package/dist/utils/project-selector.d.ts +25 -0
- package/dist/utils/project-selector.js +69 -0
- package/dist/utils/prompts.d.ts +33 -0
- package/dist/utils/prompts.js +60 -0
- package/dist/utils/templates.d.ts +29 -0
- package/dist/utils/templates.js +67 -0
- package/dist/utils/token-refresh.d.ts +24 -0
- package/dist/utils/token-refresh.js +69 -0
- package/dist/utils/token-storage.d.ts +31 -0
- package/dist/utils/token-storage.js +57 -0
- package/package.json +82 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for Markdown requirements format.
|
|
3
|
+
* TypeScript types are inferred from these schemas using z.infer<>.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
/**
|
|
7
|
+
* Requirement prefix format: uppercase letters and hyphens, 1-30 chars.
|
|
8
|
+
* First char must be a letter.
|
|
9
|
+
* Examples: "LOGIN", "AUTH", "SYNC-CLI-CREATE", "ADMIN-PROJECT"
|
|
10
|
+
*/
|
|
11
|
+
export declare const REQUIREMENT_PREFIX_PATTERN: RegExp;
|
|
12
|
+
/**
|
|
13
|
+
* Requirement key format: PREFIX-number.
|
|
14
|
+
* Examples: "LOGIN-1", "AUTH-42", "USAGE-0"
|
|
15
|
+
*/
|
|
16
|
+
export declare const REQUIREMENT_KEY_PATTERN: RegExp;
|
|
17
|
+
/**
|
|
18
|
+
* Zod schema for requirement prefix.
|
|
19
|
+
* Accepts any case input but validates the uppercase form.
|
|
20
|
+
*/
|
|
21
|
+
export declare const RequirementPrefixSchema: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
22
|
+
export type RequirementPrefix = z.infer<typeof RequirementPrefixSchema>;
|
|
23
|
+
/**
|
|
24
|
+
* Zod schema for requirement key (PREFIX-number).
|
|
25
|
+
* Normalizes to uppercase and strips leading zeros from numbers.
|
|
26
|
+
*/
|
|
27
|
+
export declare const RequirementKeySchema: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
28
|
+
export type RequirementKey = z.infer<typeof RequirementKeySchema>;
|
|
29
|
+
/**
|
|
30
|
+
* Normalize a prefix to uppercase.
|
|
31
|
+
* Does not validate - use validatePrefix for validation.
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizePrefix(prefix: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Validate a prefix string.
|
|
36
|
+
* Returns the normalized (uppercase) prefix or throws ValidationError.
|
|
37
|
+
*/
|
|
38
|
+
export declare function validatePrefix(prefix: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Validate a requirement key string.
|
|
41
|
+
* Returns the normalized (uppercase) key or throws ValidationError.
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateKey(key: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Parse a requirement key into prefix and number parts.
|
|
46
|
+
* Returns null if the key doesn't match the expected format.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseRequirementKey(key: string): {
|
|
49
|
+
prefix: string;
|
|
50
|
+
number: number;
|
|
51
|
+
} | null;
|
|
52
|
+
/**
|
|
53
|
+
* Build a requirement key from prefix and number.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildRequirementKey(prefix: string, number: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* Metadata block at the top of every requirements file
|
|
58
|
+
*/
|
|
59
|
+
export declare const MetadataSchema: z.ZodObject<{
|
|
60
|
+
projectId: z.ZodString;
|
|
61
|
+
projectSlug: z.ZodOptional<z.ZodString>;
|
|
62
|
+
pulledAt: z.ZodOptional<z.ZodString>;
|
|
63
|
+
version: z.ZodNumber;
|
|
64
|
+
document: z.ZodOptional<z.ZodObject<{
|
|
65
|
+
id: z.ZodOptional<z.ZodString>;
|
|
66
|
+
title: z.ZodString;
|
|
67
|
+
defaultPrefix: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, "strip", z.ZodTypeAny, {
|
|
69
|
+
title: string;
|
|
70
|
+
id?: string | undefined;
|
|
71
|
+
defaultPrefix?: string | undefined;
|
|
72
|
+
}, {
|
|
73
|
+
title: string;
|
|
74
|
+
id?: string | undefined;
|
|
75
|
+
defaultPrefix?: string | undefined;
|
|
76
|
+
}>>;
|
|
77
|
+
}, "strip", z.ZodTypeAny, {
|
|
78
|
+
version: number;
|
|
79
|
+
projectId: string;
|
|
80
|
+
projectSlug?: string | undefined;
|
|
81
|
+
pulledAt?: string | undefined;
|
|
82
|
+
document?: {
|
|
83
|
+
title: string;
|
|
84
|
+
id?: string | undefined;
|
|
85
|
+
defaultPrefix?: string | undefined;
|
|
86
|
+
} | undefined;
|
|
87
|
+
}, {
|
|
88
|
+
version: number;
|
|
89
|
+
projectId: string;
|
|
90
|
+
projectSlug?: string | undefined;
|
|
91
|
+
pulledAt?: string | undefined;
|
|
92
|
+
document?: {
|
|
93
|
+
title: string;
|
|
94
|
+
id?: string | undefined;
|
|
95
|
+
defaultPrefix?: string | undefined;
|
|
96
|
+
} | undefined;
|
|
97
|
+
}>;
|
|
98
|
+
export type Metadata = z.infer<typeof MetadataSchema>;
|
|
99
|
+
/**
|
|
100
|
+
* Convex document ID format: 32 lowercase alphanumeric characters starting with 'j'
|
|
101
|
+
*/
|
|
102
|
+
export declare const CONVEX_ID_PATTERN: RegExp;
|
|
103
|
+
/**
|
|
104
|
+
* Result of validating a file for push readiness.
|
|
105
|
+
*/
|
|
106
|
+
export type PushValidationResult = {
|
|
107
|
+
valid: true;
|
|
108
|
+
action: 'create' | 'update';
|
|
109
|
+
warning?: string;
|
|
110
|
+
} | {
|
|
111
|
+
valid: false;
|
|
112
|
+
reason: string;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Validate metadata for push readiness.
|
|
116
|
+
* Returns whether the file can be pushed and what action will be taken.
|
|
117
|
+
*
|
|
118
|
+
* SYNC-CLI-CREATE-1: Files without document.id create new documents
|
|
119
|
+
* SYNC-CLI-EDIT-1: Files with valid document.id update existing documents
|
|
120
|
+
*/
|
|
121
|
+
export declare function validateForPush(metadata: Metadata): PushValidationResult;
|
|
122
|
+
/**
|
|
123
|
+
* Individual requirement node in the tree.
|
|
124
|
+
* Each node has an ID, label, content, and optional children.
|
|
125
|
+
*/
|
|
126
|
+
export declare const RequirementNodeSchema: z.ZodType<RequirementNode>;
|
|
127
|
+
export type RequirementNode = {
|
|
128
|
+
id: string;
|
|
129
|
+
label: string;
|
|
130
|
+
content: string;
|
|
131
|
+
children: RequirementNode[];
|
|
132
|
+
metadata?: {
|
|
133
|
+
convexId?: string;
|
|
134
|
+
rootId?: string;
|
|
135
|
+
position?: string;
|
|
136
|
+
updatedAt?: number;
|
|
137
|
+
externalLinks?: {
|
|
138
|
+
jira?: string;
|
|
139
|
+
notion?: string;
|
|
140
|
+
};
|
|
141
|
+
prefix?: string;
|
|
142
|
+
index?: number;
|
|
143
|
+
documentId?: string;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Complete requirements file structure.
|
|
148
|
+
* Top-level keys are either "_meta" or requirement IDs.
|
|
149
|
+
*/
|
|
150
|
+
export declare const RequirementsFileSchema: z.ZodObject<{
|
|
151
|
+
_meta: z.ZodObject<{
|
|
152
|
+
projectId: z.ZodString;
|
|
153
|
+
projectSlug: z.ZodOptional<z.ZodString>;
|
|
154
|
+
pulledAt: z.ZodOptional<z.ZodString>;
|
|
155
|
+
version: z.ZodNumber;
|
|
156
|
+
document: z.ZodOptional<z.ZodObject<{
|
|
157
|
+
id: z.ZodOptional<z.ZodString>;
|
|
158
|
+
title: z.ZodString;
|
|
159
|
+
defaultPrefix: z.ZodOptional<z.ZodString>;
|
|
160
|
+
}, "strip", z.ZodTypeAny, {
|
|
161
|
+
title: string;
|
|
162
|
+
id?: string | undefined;
|
|
163
|
+
defaultPrefix?: string | undefined;
|
|
164
|
+
}, {
|
|
165
|
+
title: string;
|
|
166
|
+
id?: string | undefined;
|
|
167
|
+
defaultPrefix?: string | undefined;
|
|
168
|
+
}>>;
|
|
169
|
+
}, "strip", z.ZodTypeAny, {
|
|
170
|
+
version: number;
|
|
171
|
+
projectId: string;
|
|
172
|
+
projectSlug?: string | undefined;
|
|
173
|
+
pulledAt?: string | undefined;
|
|
174
|
+
document?: {
|
|
175
|
+
title: string;
|
|
176
|
+
id?: string | undefined;
|
|
177
|
+
defaultPrefix?: string | undefined;
|
|
178
|
+
} | undefined;
|
|
179
|
+
}, {
|
|
180
|
+
version: number;
|
|
181
|
+
projectId: string;
|
|
182
|
+
projectSlug?: string | undefined;
|
|
183
|
+
pulledAt?: string | undefined;
|
|
184
|
+
document?: {
|
|
185
|
+
title: string;
|
|
186
|
+
id?: string | undefined;
|
|
187
|
+
defaultPrefix?: string | undefined;
|
|
188
|
+
} | undefined;
|
|
189
|
+
}>;
|
|
190
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
191
|
+
_meta: z.ZodObject<{
|
|
192
|
+
projectId: z.ZodString;
|
|
193
|
+
projectSlug: z.ZodOptional<z.ZodString>;
|
|
194
|
+
pulledAt: z.ZodOptional<z.ZodString>;
|
|
195
|
+
version: z.ZodNumber;
|
|
196
|
+
document: z.ZodOptional<z.ZodObject<{
|
|
197
|
+
id: z.ZodOptional<z.ZodString>;
|
|
198
|
+
title: z.ZodString;
|
|
199
|
+
defaultPrefix: z.ZodOptional<z.ZodString>;
|
|
200
|
+
}, "strip", z.ZodTypeAny, {
|
|
201
|
+
title: string;
|
|
202
|
+
id?: string | undefined;
|
|
203
|
+
defaultPrefix?: string | undefined;
|
|
204
|
+
}, {
|
|
205
|
+
title: string;
|
|
206
|
+
id?: string | undefined;
|
|
207
|
+
defaultPrefix?: string | undefined;
|
|
208
|
+
}>>;
|
|
209
|
+
}, "strip", z.ZodTypeAny, {
|
|
210
|
+
version: number;
|
|
211
|
+
projectId: string;
|
|
212
|
+
projectSlug?: string | undefined;
|
|
213
|
+
pulledAt?: string | undefined;
|
|
214
|
+
document?: {
|
|
215
|
+
title: string;
|
|
216
|
+
id?: string | undefined;
|
|
217
|
+
defaultPrefix?: string | undefined;
|
|
218
|
+
} | undefined;
|
|
219
|
+
}, {
|
|
220
|
+
version: number;
|
|
221
|
+
projectId: string;
|
|
222
|
+
projectSlug?: string | undefined;
|
|
223
|
+
pulledAt?: string | undefined;
|
|
224
|
+
document?: {
|
|
225
|
+
title: string;
|
|
226
|
+
id?: string | undefined;
|
|
227
|
+
defaultPrefix?: string | undefined;
|
|
228
|
+
} | undefined;
|
|
229
|
+
}>;
|
|
230
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
231
|
+
_meta: z.ZodObject<{
|
|
232
|
+
projectId: z.ZodString;
|
|
233
|
+
projectSlug: z.ZodOptional<z.ZodString>;
|
|
234
|
+
pulledAt: z.ZodOptional<z.ZodString>;
|
|
235
|
+
version: z.ZodNumber;
|
|
236
|
+
document: z.ZodOptional<z.ZodObject<{
|
|
237
|
+
id: z.ZodOptional<z.ZodString>;
|
|
238
|
+
title: z.ZodString;
|
|
239
|
+
defaultPrefix: z.ZodOptional<z.ZodString>;
|
|
240
|
+
}, "strip", z.ZodTypeAny, {
|
|
241
|
+
title: string;
|
|
242
|
+
id?: string | undefined;
|
|
243
|
+
defaultPrefix?: string | undefined;
|
|
244
|
+
}, {
|
|
245
|
+
title: string;
|
|
246
|
+
id?: string | undefined;
|
|
247
|
+
defaultPrefix?: string | undefined;
|
|
248
|
+
}>>;
|
|
249
|
+
}, "strip", z.ZodTypeAny, {
|
|
250
|
+
version: number;
|
|
251
|
+
projectId: string;
|
|
252
|
+
projectSlug?: string | undefined;
|
|
253
|
+
pulledAt?: string | undefined;
|
|
254
|
+
document?: {
|
|
255
|
+
title: string;
|
|
256
|
+
id?: string | undefined;
|
|
257
|
+
defaultPrefix?: string | undefined;
|
|
258
|
+
} | undefined;
|
|
259
|
+
}, {
|
|
260
|
+
version: number;
|
|
261
|
+
projectId: string;
|
|
262
|
+
projectSlug?: string | undefined;
|
|
263
|
+
pulledAt?: string | undefined;
|
|
264
|
+
document?: {
|
|
265
|
+
title: string;
|
|
266
|
+
id?: string | undefined;
|
|
267
|
+
defaultPrefix?: string | undefined;
|
|
268
|
+
} | undefined;
|
|
269
|
+
}>;
|
|
270
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
271
|
+
export type RequirementsFile = z.infer<typeof RequirementsFileSchema> & {
|
|
272
|
+
[key: string]: any;
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Parsed criterion from Markdown (position. Label: content format)
|
|
276
|
+
* Example: "0. Given: user has valid credentials"
|
|
277
|
+
*/
|
|
278
|
+
export declare const ParsedCriterionSchema: z.ZodObject<{
|
|
279
|
+
position: z.ZodString;
|
|
280
|
+
label: z.ZodString;
|
|
281
|
+
content: z.ZodString;
|
|
282
|
+
}, "strip", z.ZodTypeAny, {
|
|
283
|
+
label: string;
|
|
284
|
+
content: string;
|
|
285
|
+
position: string;
|
|
286
|
+
}, {
|
|
287
|
+
label: string;
|
|
288
|
+
content: string;
|
|
289
|
+
position: string;
|
|
290
|
+
}>;
|
|
291
|
+
export type ParsedCriterion = z.infer<typeof ParsedCriterionSchema>;
|
|
292
|
+
/**
|
|
293
|
+
* Validation error with context
|
|
294
|
+
*/
|
|
295
|
+
export declare class ValidationError extends Error {
|
|
296
|
+
path?: string | undefined;
|
|
297
|
+
cause?: unknown | undefined;
|
|
298
|
+
constructor(message: string, path?: string | undefined, cause?: unknown | undefined);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Validate metadata block
|
|
302
|
+
*/
|
|
303
|
+
export declare function validateMetadata(data: unknown): Metadata;
|
|
304
|
+
/**
|
|
305
|
+
* Validate requirement node structure
|
|
306
|
+
*/
|
|
307
|
+
export declare function validateRequirementNode(data: unknown, path: string): RequirementNode;
|
|
308
|
+
/**
|
|
309
|
+
* Validate complete requirements file
|
|
310
|
+
*/
|
|
311
|
+
export declare function validateRequirementsFile(data: unknown): RequirementsFile;
|
|
312
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schemas for Markdown requirements format.
|
|
3
|
+
* TypeScript types are inferred from these schemas using z.infer<>.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { fromZodError } from 'zod-validation-error';
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Prefix and Key Validation
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Requirement prefix format: uppercase letters and hyphens, 1-30 chars.
|
|
12
|
+
* First char must be a letter.
|
|
13
|
+
* Examples: "LOGIN", "AUTH", "SYNC-CLI-CREATE", "ADMIN-PROJECT"
|
|
14
|
+
*/
|
|
15
|
+
export const REQUIREMENT_PREFIX_PATTERN = /^[A-Z][A-Z-]{0,29}$/;
|
|
16
|
+
/**
|
|
17
|
+
* Requirement key format: PREFIX-number.
|
|
18
|
+
* Examples: "LOGIN-1", "AUTH-42", "USAGE-0"
|
|
19
|
+
*/
|
|
20
|
+
export const REQUIREMENT_KEY_PATTERN = /^[A-Z][A-Z-]*-[0-9]+$/;
|
|
21
|
+
/**
|
|
22
|
+
* Zod schema for requirement prefix.
|
|
23
|
+
* Accepts any case input but validates the uppercase form.
|
|
24
|
+
*/
|
|
25
|
+
export const RequirementPrefixSchema = z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1, 'Prefix must be at least 1 character')
|
|
28
|
+
.max(30, 'Prefix must be at most 30 characters')
|
|
29
|
+
.transform((val) => val.toUpperCase())
|
|
30
|
+
.refine((val) => REQUIREMENT_PREFIX_PATTERN.test(val), 'Prefix must start with a letter and contain only uppercase letters and hyphens');
|
|
31
|
+
/**
|
|
32
|
+
* Zod schema for requirement key (PREFIX-number).
|
|
33
|
+
* Normalizes to uppercase and strips leading zeros from numbers.
|
|
34
|
+
*/
|
|
35
|
+
export const RequirementKeySchema = z
|
|
36
|
+
.string()
|
|
37
|
+
.refine((val) => REQUIREMENT_KEY_PATTERN.test(val.toUpperCase()), 'Key must be in format PREFIX-number (e.g., LOGIN-1, AUTH-42)')
|
|
38
|
+
.transform((val) => {
|
|
39
|
+
const upper = val.toUpperCase();
|
|
40
|
+
// Normalize leading zeros: LOGIN-01 -> LOGIN-1, LOGIN-007 -> LOGIN-7
|
|
41
|
+
return upper.replace(/-0+(\d+)$/, '-$1');
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Normalize a prefix to uppercase.
|
|
45
|
+
* Does not validate - use validatePrefix for validation.
|
|
46
|
+
*/
|
|
47
|
+
export function normalizePrefix(prefix) {
|
|
48
|
+
return prefix.toUpperCase();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate a prefix string.
|
|
52
|
+
* Returns the normalized (uppercase) prefix or throws ValidationError.
|
|
53
|
+
*/
|
|
54
|
+
export function validatePrefix(prefix) {
|
|
55
|
+
const result = RequirementPrefixSchema.safeParse(prefix);
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
const validationError = fromZodError(result.error);
|
|
58
|
+
throw new ValidationError(`Invalid prefix "${prefix}": ${validationError.message}`, 'prefix', result.error);
|
|
59
|
+
}
|
|
60
|
+
return result.data;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate a requirement key string.
|
|
64
|
+
* Returns the normalized (uppercase) key or throws ValidationError.
|
|
65
|
+
*/
|
|
66
|
+
export function validateKey(key) {
|
|
67
|
+
const result = RequirementKeySchema.safeParse(key);
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
const validationError = fromZodError(result.error);
|
|
70
|
+
throw new ValidationError(`Invalid key "${key}": ${validationError.message}`, 'key', result.error);
|
|
71
|
+
}
|
|
72
|
+
return result.data;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse a requirement key into prefix and number parts.
|
|
76
|
+
* Returns null if the key doesn't match the expected format.
|
|
77
|
+
*/
|
|
78
|
+
export function parseRequirementKey(key) {
|
|
79
|
+
const normalized = key.toUpperCase();
|
|
80
|
+
// Find the last hyphen followed by digits - that's the number part
|
|
81
|
+
const match = normalized.match(/^(.+)-([0-9]+)$/);
|
|
82
|
+
if (!match)
|
|
83
|
+
return null;
|
|
84
|
+
const [, prefix, numStr] = match;
|
|
85
|
+
// Validate the prefix part
|
|
86
|
+
if (!REQUIREMENT_PREFIX_PATTERN.test(prefix))
|
|
87
|
+
return null;
|
|
88
|
+
return { prefix, number: parseInt(numStr, 10) };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build a requirement key from prefix and number.
|
|
92
|
+
*/
|
|
93
|
+
export function buildRequirementKey(prefix, number) {
|
|
94
|
+
const normalizedPrefix = prefix.toUpperCase();
|
|
95
|
+
if (!REQUIREMENT_PREFIX_PATTERN.test(normalizedPrefix)) {
|
|
96
|
+
throw new ValidationError(`Invalid prefix "${prefix}": must match pattern ${REQUIREMENT_PREFIX_PATTERN}`, 'prefix');
|
|
97
|
+
}
|
|
98
|
+
if (number < 0 || !Number.isInteger(number)) {
|
|
99
|
+
throw new ValidationError(`Invalid number "${number}": must be a non-negative integer`, 'number');
|
|
100
|
+
}
|
|
101
|
+
return `${normalizedPrefix}-${number}`;
|
|
102
|
+
}
|
|
103
|
+
// =============================================================================
|
|
104
|
+
// Metadata Schema
|
|
105
|
+
// =============================================================================
|
|
106
|
+
/**
|
|
107
|
+
* Metadata block at the top of every requirements file
|
|
108
|
+
*/
|
|
109
|
+
export const MetadataSchema = z.object({
|
|
110
|
+
projectId: z.string(),
|
|
111
|
+
projectSlug: z.string().optional(), // User-friendly slug (e.g., "quick-fox-123")
|
|
112
|
+
pulledAt: z.string().optional(), // ISO 8601 timestamp - required for CLI files, optional for IDE
|
|
113
|
+
version: z.number(),
|
|
114
|
+
// Optional document association (one file = one document)
|
|
115
|
+
document: z.object({
|
|
116
|
+
id: z.string().optional(), // Optional - filled by push when creating new document
|
|
117
|
+
title: z.string(),
|
|
118
|
+
// DOC-HEADER-11: Default prefix for new requirement keys in this document
|
|
119
|
+
defaultPrefix: z.string().optional(),
|
|
120
|
+
}).optional(),
|
|
121
|
+
});
|
|
122
|
+
// =============================================================================
|
|
123
|
+
// Push Readiness Validation
|
|
124
|
+
// =============================================================================
|
|
125
|
+
/**
|
|
126
|
+
* Convex document ID format: 32 lowercase alphanumeric characters starting with 'j'
|
|
127
|
+
*/
|
|
128
|
+
export const CONVEX_ID_PATTERN = /^j[a-z0-9]{31}$/;
|
|
129
|
+
/**
|
|
130
|
+
* Validate metadata for push readiness.
|
|
131
|
+
* Returns whether the file can be pushed and what action will be taken.
|
|
132
|
+
*
|
|
133
|
+
* SYNC-CLI-CREATE-1: Files without document.id create new documents
|
|
134
|
+
* SYNC-CLI-EDIT-1: Files with valid document.id update existing documents
|
|
135
|
+
*/
|
|
136
|
+
export function validateForPush(metadata) {
|
|
137
|
+
// Must have document section
|
|
138
|
+
if (!metadata.document) {
|
|
139
|
+
return {
|
|
140
|
+
valid: false,
|
|
141
|
+
reason: 'Missing document section. Add a document section with a title to push this file.'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Must have title
|
|
145
|
+
if (!metadata.document.title || metadata.document.title.trim() === '') {
|
|
146
|
+
return {
|
|
147
|
+
valid: false,
|
|
148
|
+
reason: 'Document section missing required title.'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Check document.id
|
|
152
|
+
if (!metadata.document.id) {
|
|
153
|
+
// No ID = create new document
|
|
154
|
+
return { valid: true, action: 'create' };
|
|
155
|
+
}
|
|
156
|
+
// Has ID - check if it's a valid Convex ID
|
|
157
|
+
if (CONVEX_ID_PATTERN.test(metadata.document.id)) {
|
|
158
|
+
// Valid Convex ID = update existing document
|
|
159
|
+
return { valid: true, action: 'update' };
|
|
160
|
+
}
|
|
161
|
+
// Invalid ID format - will create new document and overwrite
|
|
162
|
+
return {
|
|
163
|
+
valid: true,
|
|
164
|
+
action: 'create',
|
|
165
|
+
warning: `Invalid document ID '${metadata.document.id}' will be replaced with a new ID.`
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Individual requirement node in the tree.
|
|
170
|
+
* Each node has an ID, label, content, and optional children.
|
|
171
|
+
*/
|
|
172
|
+
export const RequirementNodeSchema = z.lazy(() => z.object({
|
|
173
|
+
id: z.string(), // e.g., "REQ123" or "REQ123.0.1"
|
|
174
|
+
label: z.string(), // e.g., "given", "when", "then", "requirement"
|
|
175
|
+
content: z.string(), // The actual requirement text
|
|
176
|
+
children: z.array(RequirementNodeSchema).default([]),
|
|
177
|
+
// Optional metadata preserved from Convex
|
|
178
|
+
metadata: z.object({
|
|
179
|
+
convexId: z.string().optional(), // Convex _id
|
|
180
|
+
rootId: z.string().optional(), // Convex rootId
|
|
181
|
+
position: z.string().optional(), // Convex position path
|
|
182
|
+
updatedAt: z.number().optional(),
|
|
183
|
+
externalLinks: z.object({
|
|
184
|
+
jira: z.string().optional(),
|
|
185
|
+
notion: z.string().optional(),
|
|
186
|
+
}).optional(),
|
|
187
|
+
// New schema fields for bidirectional sync
|
|
188
|
+
prefix: z.string().optional(), // e.g., "REQ"
|
|
189
|
+
index: z.number().optional(), // e.g., 123
|
|
190
|
+
documentId: z.string().optional(), // Convex document ID
|
|
191
|
+
}).optional(),
|
|
192
|
+
}));
|
|
193
|
+
/**
|
|
194
|
+
* Complete requirements file structure.
|
|
195
|
+
* Top-level keys are either "_meta" or requirement IDs.
|
|
196
|
+
*/
|
|
197
|
+
export const RequirementsFileSchema = z.object({
|
|
198
|
+
_meta: MetadataSchema,
|
|
199
|
+
// All other keys are requirement roots
|
|
200
|
+
// We'll validate the structure separately since Zod doesn't handle
|
|
201
|
+
// arbitrary keys with specific structure well
|
|
202
|
+
}).passthrough(); // Allow additional keys
|
|
203
|
+
/**
|
|
204
|
+
* Parsed criterion from Markdown (position. Label: content format)
|
|
205
|
+
* Example: "0. Given: user has valid credentials"
|
|
206
|
+
*/
|
|
207
|
+
export const ParsedCriterionSchema = z.object({
|
|
208
|
+
position: z.string(), // e.g., "0", "1.0", "2.3.1"
|
|
209
|
+
label: z.string(), // e.g., "given", "when", "then"
|
|
210
|
+
content: z.string(), // The criterion text
|
|
211
|
+
});
|
|
212
|
+
/**
|
|
213
|
+
* Validation error with context
|
|
214
|
+
*/
|
|
215
|
+
export class ValidationError extends Error {
|
|
216
|
+
path;
|
|
217
|
+
cause;
|
|
218
|
+
constructor(message, path, cause) {
|
|
219
|
+
super(message);
|
|
220
|
+
this.path = path;
|
|
221
|
+
this.cause = cause;
|
|
222
|
+
this.name = 'ValidationError';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Validate metadata block
|
|
227
|
+
*/
|
|
228
|
+
export function validateMetadata(data) {
|
|
229
|
+
const result = MetadataSchema.safeParse(data);
|
|
230
|
+
if (!result.success) {
|
|
231
|
+
const validationError = fromZodError(result.error);
|
|
232
|
+
throw new ValidationError(`Invalid metadata block:\n${validationError.message}`, '_meta', result.error);
|
|
233
|
+
}
|
|
234
|
+
return result.data;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Validate requirement node structure
|
|
238
|
+
*/
|
|
239
|
+
export function validateRequirementNode(data, path) {
|
|
240
|
+
const result = RequirementNodeSchema.safeParse(data);
|
|
241
|
+
if (!result.success) {
|
|
242
|
+
const validationError = fromZodError(result.error);
|
|
243
|
+
throw new ValidationError(`Invalid requirement node structure at ${path}:\n${validationError.message}`, path, result.error);
|
|
244
|
+
}
|
|
245
|
+
return result.data;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Validate complete requirements file
|
|
249
|
+
*/
|
|
250
|
+
export function validateRequirementsFile(data) {
|
|
251
|
+
const result = RequirementsFileSchema.safeParse(data);
|
|
252
|
+
if (!result.success) {
|
|
253
|
+
const validationError = fromZodError(result.error);
|
|
254
|
+
throw new ValidationError(`Invalid requirements file structure:\n${validationError.message}`, undefined, result.error);
|
|
255
|
+
}
|
|
256
|
+
return result.data;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick test to verify the new YAML schema is valid and round-trips correctly.
|
|
3
|
+
*/
|
|
4
|
+
import { buildRequirementsMarkdown, parseRequirementsFile, buildMetadata } from './index.js';
|
|
5
|
+
// Create test data
|
|
6
|
+
const metadata = buildMetadata('test-project-123', 1);
|
|
7
|
+
const testRequirements = [
|
|
8
|
+
{
|
|
9
|
+
id: 'REQ123',
|
|
10
|
+
label: 'requirement',
|
|
11
|
+
content: 'User can authenticate',
|
|
12
|
+
children: [
|
|
13
|
+
{
|
|
14
|
+
id: 'REQ123.0',
|
|
15
|
+
label: 'given',
|
|
16
|
+
content: 'user has valid credentials',
|
|
17
|
+
children: [],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'REQ123.1',
|
|
21
|
+
label: 'when',
|
|
22
|
+
content: 'user submits login form',
|
|
23
|
+
children: [
|
|
24
|
+
{
|
|
25
|
+
id: 'REQ123.1.0',
|
|
26
|
+
label: 'then',
|
|
27
|
+
content: 'system validates credentials',
|
|
28
|
+
children: [],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'REQ123.1.1',
|
|
32
|
+
label: 'then',
|
|
33
|
+
content: 'system creates session',
|
|
34
|
+
children: [],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'REQ123.2',
|
|
40
|
+
label: 'then',
|
|
41
|
+
content: 'user is redirected to dashboard',
|
|
42
|
+
children: [],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
console.log('=== Building Markdown ===\n');
|
|
48
|
+
const markdown = buildRequirementsMarkdown(metadata, testRequirements);
|
|
49
|
+
console.log(markdown);
|
|
50
|
+
console.log('\n=== Parsing Markdown back ===\n');
|
|
51
|
+
try {
|
|
52
|
+
const { metadata: parsedMeta, requirements: parsedReqs } = parseRequirementsFile(markdown);
|
|
53
|
+
console.log('Metadata:', JSON.stringify(parsedMeta, null, 2));
|
|
54
|
+
console.log('\nRequirements tree:');
|
|
55
|
+
function printTree(node, indent = 0) {
|
|
56
|
+
const spaces = ' '.repeat(indent);
|
|
57
|
+
console.log(`${spaces}${node.id}: ${node.label} ā ${node.content}`);
|
|
58
|
+
for (const child of node.children) {
|
|
59
|
+
printTree(child, indent + 1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const req of parsedReqs) {
|
|
63
|
+
printTree(req);
|
|
64
|
+
}
|
|
65
|
+
console.log('\nā
Round-trip successful!');
|
|
66
|
+
// Verify structure
|
|
67
|
+
console.log('\n=== Verifying structure ===');
|
|
68
|
+
const req = parsedReqs[0];
|
|
69
|
+
console.log(`Root ID: ${req.id}`);
|
|
70
|
+
console.log(`Root label: ${req.label}`);
|
|
71
|
+
console.log(`Root content: ${req.content}`);
|
|
72
|
+
console.log(`Children count: ${req.children.length}`);
|
|
73
|
+
console.log(`First child ID: ${req.children[0].id}`);
|
|
74
|
+
console.log(`Second child has ${req.children[1].children.length} grandchildren`);
|
|
75
|
+
console.log(`First grandchild ID: ${req.children[1].children[0].id}`);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('ā Failed to parse Markdown:', error);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=test-schema.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Requirements Capture
|
|
2
|
+
|
|
3
|
+
ALWAYS ask about capturing requirements when the user requests new functionality (not bug fixes or refactoring).
|
|
4
|
+
|
|
5
|
+
Ask: "Should we capture what this should do before we build it?"
|
|
6
|
+
|
|
7
|
+
If yes, use the MCP tools for requirements writing.
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
1. **Ask questions** only if there are major gaps in expected behavior
|
|
12
|
+
2. **Use requirements as alignment** - Get assumptions on paper quickly instead of playing twenty questions
|
|
13
|
+
3. **Think MVP** - Don't expand scope without consulting the user
|
|
14
|
+
4. **After building** - Suggest testing against the requirements using the project's standard testing tools
|
|
15
|
+
|
|
16
|
+
Be helpful, consultative, and encouraging; not obstructive.
|
|
17
|
+
|
|
18
|
+
## MCP Tools for Requirements
|
|
19
|
+
|
|
20
|
+
- `create_requirement_document` - Get template with codebase-aware format guidance and style principles
|
|
21
|
+
- `validate_requirements` - Check syntax before pushing (works offline)
|
|
22
|
+
- `style_check` - Get AI feedback on writing quality and clarity
|
|
23
|
+
- `push_requirements` - Sync to cloud (shows diff preview, then confirms)
|
|
24
|
+
|
|
25
|
+
Note: Users can also explicitly trigger this workflow with `/capture-requirements`.
|