heraspec 0.1.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/LICENSE +22 -0
- package/README.md +57 -0
- package/bin/heraspec.js +3803 -0
- package/bin/heraspec.js.map +7 -0
- package/dist/core/templates/skills/CHANGELOG.md +117 -0
- package/dist/core/templates/skills/README-template.md +58 -0
- package/dist/core/templates/skills/README.md +36 -0
- package/dist/core/templates/skills/content-optimization-skill.md +104 -0
- package/dist/core/templates/skills/data/charts.csv +26 -0
- package/dist/core/templates/skills/data/colors.csv +97 -0
- package/dist/core/templates/skills/data/landing.csv +31 -0
- package/dist/core/templates/skills/data/pages-proposed.csv +22 -0
- package/dist/core/templates/skills/data/pages.csv +10 -0
- package/dist/core/templates/skills/data/products.csv +97 -0
- package/dist/core/templates/skills/data/prompts.csv +24 -0
- package/dist/core/templates/skills/data/stacks/flutter.csv +53 -0
- package/dist/core/templates/skills/data/stacks/html-tailwind.csv +56 -0
- package/dist/core/templates/skills/data/stacks/nextjs.csv +53 -0
- package/dist/core/templates/skills/data/stacks/react-native.csv +52 -0
- package/dist/core/templates/skills/data/stacks/react.csv +54 -0
- package/dist/core/templates/skills/data/stacks/svelte.csv +54 -0
- package/dist/core/templates/skills/data/stacks/swiftui.csv +51 -0
- package/dist/core/templates/skills/data/stacks/vue.csv +50 -0
- package/dist/core/templates/skills/data/styles.csv +59 -0
- package/dist/core/templates/skills/data/typography.csv +58 -0
- package/dist/core/templates/skills/data/ux-guidelines.csv +100 -0
- package/dist/core/templates/skills/documents-skill.md +114 -0
- package/dist/core/templates/skills/e2e-test-skill.md +119 -0
- package/dist/core/templates/skills/integration-test-skill.md +118 -0
- package/dist/core/templates/skills/module-codebase-skill.md +110 -0
- package/dist/core/templates/skills/scripts/CODE_EXPLANATION.md +394 -0
- package/dist/core/templates/skills/scripts/SEARCH_ALGORITHMS_COMPARISON.md +421 -0
- package/dist/core/templates/skills/scripts/SEARCH_MODES_GUIDE.md +238 -0
- package/dist/core/templates/skills/scripts/core.py +385 -0
- package/dist/core/templates/skills/scripts/search.py +73 -0
- package/dist/core/templates/skills/suggestion-skill.md +118 -0
- package/dist/core/templates/skills/templates/accessibility-checklist.md +40 -0
- package/dist/core/templates/skills/templates/example-prompt-full-theme.md +333 -0
- package/dist/core/templates/skills/templates/page-types-guide.md +338 -0
- package/dist/core/templates/skills/templates/pages-proposed-summary.md +273 -0
- package/dist/core/templates/skills/templates/pre-delivery-checklist.md +42 -0
- package/dist/core/templates/skills/templates/prompt-template-full-theme.md +313 -0
- package/dist/core/templates/skills/templates/responsive-design.md +40 -0
- package/dist/core/templates/skills/ui-ux-skill.md +584 -0
- package/dist/core/templates/skills/unit-test-skill.md +111 -0
- package/dist/index.js +1736 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1736 @@
|
|
|
1
|
+
import { createRequire } from 'module'; const require = createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/core/config.ts
|
|
4
|
+
var HERASPEC_DIR_NAME = "heraspec";
|
|
5
|
+
var SPECS_DIR_NAME = "specs";
|
|
6
|
+
var CHANGES_DIR_NAME = "changes";
|
|
7
|
+
var ARCHIVES_DIR_NAME = "archives";
|
|
8
|
+
var SKILLS_DIR_NAME = "skills";
|
|
9
|
+
var PROJECT_TYPES = [
|
|
10
|
+
"wordpress-plugin",
|
|
11
|
+
"wordpress-theme",
|
|
12
|
+
"perfex-module",
|
|
13
|
+
"laravel-package",
|
|
14
|
+
"node-service",
|
|
15
|
+
"generic-webapp",
|
|
16
|
+
"backend-api",
|
|
17
|
+
"frontend-app",
|
|
18
|
+
"multi-stack"
|
|
19
|
+
];
|
|
20
|
+
var SKILLS = {
|
|
21
|
+
"wordpress-plugin": [
|
|
22
|
+
"admin-settings-page",
|
|
23
|
+
"custom-post-type",
|
|
24
|
+
"shortcode",
|
|
25
|
+
"rest-endpoint",
|
|
26
|
+
"ajax-handler",
|
|
27
|
+
"activation-hook",
|
|
28
|
+
"deactivation-hook",
|
|
29
|
+
"admin-menu-item",
|
|
30
|
+
"meta-box",
|
|
31
|
+
"taxonomy"
|
|
32
|
+
],
|
|
33
|
+
"wordpress-theme": [
|
|
34
|
+
"theme-setup",
|
|
35
|
+
"custom-post-type",
|
|
36
|
+
"template-part",
|
|
37
|
+
"widget-area",
|
|
38
|
+
"customizer-setting",
|
|
39
|
+
"theme-option"
|
|
40
|
+
],
|
|
41
|
+
"perfex-module": [
|
|
42
|
+
"module-codebase",
|
|
43
|
+
"module-registration",
|
|
44
|
+
"permission-group",
|
|
45
|
+
"admin-menu-item",
|
|
46
|
+
"login-hook",
|
|
47
|
+
"database-table",
|
|
48
|
+
"api-endpoint"
|
|
49
|
+
],
|
|
50
|
+
"laravel-package": [
|
|
51
|
+
"service-provider",
|
|
52
|
+
"config-file",
|
|
53
|
+
"artisan-command",
|
|
54
|
+
"migration",
|
|
55
|
+
"model",
|
|
56
|
+
"controller",
|
|
57
|
+
"middleware",
|
|
58
|
+
"route"
|
|
59
|
+
],
|
|
60
|
+
"node-service": [
|
|
61
|
+
"express-route",
|
|
62
|
+
"middleware",
|
|
63
|
+
"database-model",
|
|
64
|
+
"service-layer",
|
|
65
|
+
"api-endpoint",
|
|
66
|
+
"background-job"
|
|
67
|
+
],
|
|
68
|
+
"generic-webapp": [
|
|
69
|
+
"page",
|
|
70
|
+
"component",
|
|
71
|
+
"api-endpoint",
|
|
72
|
+
"database-table",
|
|
73
|
+
"authentication",
|
|
74
|
+
"authorization"
|
|
75
|
+
],
|
|
76
|
+
"backend-api": [
|
|
77
|
+
"endpoint",
|
|
78
|
+
"middleware",
|
|
79
|
+
"authentication",
|
|
80
|
+
"authorization",
|
|
81
|
+
"database-model",
|
|
82
|
+
"validation"
|
|
83
|
+
],
|
|
84
|
+
"frontend-app": [
|
|
85
|
+
"page",
|
|
86
|
+
"component",
|
|
87
|
+
"route",
|
|
88
|
+
"store",
|
|
89
|
+
"service",
|
|
90
|
+
"hook"
|
|
91
|
+
],
|
|
92
|
+
"multi-stack": [
|
|
93
|
+
"cross-platform-feature",
|
|
94
|
+
"api-contract",
|
|
95
|
+
"shared-type",
|
|
96
|
+
"integration-point"
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
var HERASPEC_MARKERS = {
|
|
100
|
+
PROJECT_MD: "project.md",
|
|
101
|
+
AGENTS_MD: "AGENTS.heraspec.md",
|
|
102
|
+
CONFIG_YAML: "config.yaml",
|
|
103
|
+
PROPOSAL_MD: "proposal.md",
|
|
104
|
+
TASKS_MD: "tasks.md",
|
|
105
|
+
DESIGN_MD: "design.md"
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/core/schemas/base.schema.ts
|
|
109
|
+
import { z } from "zod";
|
|
110
|
+
var ProjectTypeSchema = z.enum([
|
|
111
|
+
"wordpress-plugin",
|
|
112
|
+
"wordpress-theme",
|
|
113
|
+
"perfex-module",
|
|
114
|
+
"laravel-package",
|
|
115
|
+
"node-service",
|
|
116
|
+
"generic-webapp",
|
|
117
|
+
"backend-api",
|
|
118
|
+
"frontend-app",
|
|
119
|
+
"multi-stack"
|
|
120
|
+
]);
|
|
121
|
+
var SpecMetaSchema = z.object({
|
|
122
|
+
projectType: z.union([
|
|
123
|
+
ProjectTypeSchema,
|
|
124
|
+
z.array(ProjectTypeSchema)
|
|
125
|
+
]).optional(),
|
|
126
|
+
domain: z.string().optional(),
|
|
127
|
+
stack: z.union([
|
|
128
|
+
z.string(),
|
|
129
|
+
z.array(z.string())
|
|
130
|
+
]).optional()
|
|
131
|
+
}).optional();
|
|
132
|
+
var ScenarioSchema = z.object({
|
|
133
|
+
name: z.string(),
|
|
134
|
+
steps: z.array(z.string()).min(1)
|
|
135
|
+
});
|
|
136
|
+
var RequirementSchema = z.object({
|
|
137
|
+
id: z.string().optional(),
|
|
138
|
+
name: z.string(),
|
|
139
|
+
description: z.string(),
|
|
140
|
+
scenarios: z.array(ScenarioSchema).optional(),
|
|
141
|
+
constraints: z.array(z.string()).optional()
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// src/core/schemas/spec.schema.ts
|
|
145
|
+
import { z as z2 } from "zod";
|
|
146
|
+
var SpecSchema = z2.object({
|
|
147
|
+
name: z2.string().min(1),
|
|
148
|
+
meta: SpecMetaSchema,
|
|
149
|
+
overview: z2.string().min(1),
|
|
150
|
+
requirements: z2.array(RequirementSchema).min(1),
|
|
151
|
+
metadata: z2.object({
|
|
152
|
+
version: z2.string().default("1.0.0"),
|
|
153
|
+
format: z2.literal("heraspec"),
|
|
154
|
+
sourcePath: z2.string().optional()
|
|
155
|
+
}).optional()
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// src/core/schemas/change.schema.ts
|
|
159
|
+
import { z as z3 } from "zod";
|
|
160
|
+
var DeltaTypeSchema = z3.enum(["ADDED", "MODIFIED", "REMOVED"]);
|
|
161
|
+
var DeltaRequirementSchema = z3.object({
|
|
162
|
+
type: DeltaTypeSchema,
|
|
163
|
+
requirement: z3.object({
|
|
164
|
+
id: z3.string().optional(),
|
|
165
|
+
name: z3.string(),
|
|
166
|
+
description: z3.string(),
|
|
167
|
+
scenarios: z3.array(z3.object({
|
|
168
|
+
name: z3.string(),
|
|
169
|
+
steps: z3.array(z3.string())
|
|
170
|
+
})).optional(),
|
|
171
|
+
constraints: z3.array(z3.string()).optional()
|
|
172
|
+
})
|
|
173
|
+
});
|
|
174
|
+
var ChangeSchema = z3.object({
|
|
175
|
+
name: z3.string(),
|
|
176
|
+
proposal: z3.string().min(1),
|
|
177
|
+
tasks: z3.array(z3.string()).optional(),
|
|
178
|
+
design: z3.string().optional()
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// src/core/init.ts
|
|
182
|
+
import ora from "ora";
|
|
183
|
+
import chalk from "chalk";
|
|
184
|
+
import path2 from "path";
|
|
185
|
+
|
|
186
|
+
// src/utils/file-system.ts
|
|
187
|
+
import { promises as fs } from "fs";
|
|
188
|
+
import path from "path";
|
|
189
|
+
var FileSystemUtils = class {
|
|
190
|
+
static async createDirectory(dirPath) {
|
|
191
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
192
|
+
}
|
|
193
|
+
static async fileExists(filePath) {
|
|
194
|
+
try {
|
|
195
|
+
await fs.access(filePath);
|
|
196
|
+
return true;
|
|
197
|
+
} catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
static async readFile(filePath) {
|
|
202
|
+
return await fs.readFile(filePath, "utf-8");
|
|
203
|
+
}
|
|
204
|
+
static async writeFile(filePath, content) {
|
|
205
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
206
|
+
}
|
|
207
|
+
static async readDirectory(dirPath) {
|
|
208
|
+
return await fs.readdir(dirPath);
|
|
209
|
+
}
|
|
210
|
+
static async stat(filePath) {
|
|
211
|
+
return await fs.stat(filePath);
|
|
212
|
+
}
|
|
213
|
+
static async copyFile(src, dest) {
|
|
214
|
+
await fs.copyFile(src, dest);
|
|
215
|
+
}
|
|
216
|
+
static async copyDirectory(src, dest) {
|
|
217
|
+
await fs.mkdir(dest, { recursive: true });
|
|
218
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
const srcPath = path.join(src, entry.name);
|
|
221
|
+
const destPath = path.join(dest, entry.name);
|
|
222
|
+
if (entry.isDirectory()) {
|
|
223
|
+
await this.copyDirectory(srcPath, destPath);
|
|
224
|
+
} else {
|
|
225
|
+
await fs.copyFile(srcPath, destPath);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
static async removeFile(filePath) {
|
|
230
|
+
await fs.unlink(filePath);
|
|
231
|
+
}
|
|
232
|
+
static async removeDirectory(dirPath, recursive = true) {
|
|
233
|
+
if (typeof fs.rm === "function") {
|
|
234
|
+
await fs.rm(dirPath, { recursive, force: true });
|
|
235
|
+
} else {
|
|
236
|
+
await fs.rmdir(dirPath, { recursive });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
static async moveFile(src, dest) {
|
|
240
|
+
await fs.rename(src, dest);
|
|
241
|
+
}
|
|
242
|
+
static joinPath(...segments) {
|
|
243
|
+
return path.join(...segments);
|
|
244
|
+
}
|
|
245
|
+
static resolvePath(...segments) {
|
|
246
|
+
return path.resolve(...segments);
|
|
247
|
+
}
|
|
248
|
+
static getDirectoryName(filePath) {
|
|
249
|
+
return path.dirname(filePath);
|
|
250
|
+
}
|
|
251
|
+
static getBaseName(filePath) {
|
|
252
|
+
return path.basename(filePath);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/core/templates/index.ts
|
|
257
|
+
var PROJECT_TEMPLATE = `# HeraSpec Project
|
|
258
|
+
|
|
259
|
+
## Overview
|
|
260
|
+
Describe your project here.
|
|
261
|
+
|
|
262
|
+
## Project Types
|
|
263
|
+
- wordpress-plugin
|
|
264
|
+
- wordpress-theme
|
|
265
|
+
- perfex-module
|
|
266
|
+
- laravel-package
|
|
267
|
+
- node-service
|
|
268
|
+
- generic-webapp
|
|
269
|
+
- backend-api
|
|
270
|
+
- frontend-app
|
|
271
|
+
- multi-stack
|
|
272
|
+
|
|
273
|
+
## Tech Stack
|
|
274
|
+
List your technologies here (e.g., PHP 8.1, WordPress 6.0, Laravel 10, etc.)
|
|
275
|
+
|
|
276
|
+
## Conventions
|
|
277
|
+
Define coding standards, architectural patterns, and conventions to follow.
|
|
278
|
+
|
|
279
|
+
`;
|
|
280
|
+
var AGENTS_TEMPLATE = `# HeraSpec \u2014 AI Agent Instructions
|
|
281
|
+
|
|
282
|
+
This document defines the workflow for AI agents working with HeraSpec.
|
|
283
|
+
|
|
284
|
+
## Core Workflow
|
|
285
|
+
|
|
286
|
+
### Step 1 \u2014 Create a Change
|
|
287
|
+
|
|
288
|
+
**When creating changes, ALWAYS read heraspec/project.md first to understand:**
|
|
289
|
+
- Project types being used
|
|
290
|
+
- Tech stack and conventions
|
|
291
|
+
- Existing architecture patterns
|
|
292
|
+
- Coding standards
|
|
293
|
+
|
|
294
|
+
**Then scaffold:**
|
|
295
|
+
- \`heraspec/changes/<slug>/\` - Create proposal.md, tasks.md, design.md (optional)
|
|
296
|
+
- \`heraspec/specs/<slug>/\` - Create delta specs here (NOT inside changes folder)
|
|
297
|
+
|
|
298
|
+
**If user asks to create changes based on project.md:**
|
|
299
|
+
1. Read \`heraspec/project.md\` thoroughly
|
|
300
|
+
2. Identify all features/capabilities mentioned
|
|
301
|
+
3. Create separate changes for each major feature
|
|
302
|
+
4. Ensure each change follows project.md conventions
|
|
303
|
+
5. Use correct project types and skills from project.md
|
|
304
|
+
|
|
305
|
+
### Step 2 \u2014 Refine Specs
|
|
306
|
+
- Update delta specs in \`heraspec/specs/<slug>/\`
|
|
307
|
+
- Never modify source-of-truth specs directly
|
|
308
|
+
|
|
309
|
+
### Step 3 \u2014 Approval
|
|
310
|
+
- Wait for user: "Specs approved."
|
|
311
|
+
|
|
312
|
+
### Step 4 \u2014 Implementation
|
|
313
|
+
|
|
314
|
+
**CRITICAL: When implementing tasks, ALWAYS use Skills system:**
|
|
315
|
+
|
|
316
|
+
1. **Read task line** to identify skill:
|
|
317
|
+
\`\`\`markdown
|
|
318
|
+
## 1. Perfex module \u2013 Category Management (projectType: perfex-module, skill: module-codebase)
|
|
319
|
+
- [ ] 1.1 Create module structure
|
|
320
|
+
\`\`\`
|
|
321
|
+
|
|
322
|
+
2. **Find skill folder**:
|
|
323
|
+
- Project-specific: \`heraspec/skills/<project-type>/<skill-name>/\`
|
|
324
|
+
- Cross-cutting: \`heraspec/skills/<skill-name>/\`
|
|
325
|
+
|
|
326
|
+
3. **Read skill.md**:
|
|
327
|
+
- Understand purpose, steps, inputs, outputs
|
|
328
|
+
- Follow tone, rules, and limitations
|
|
329
|
+
- Check available templates and scripts
|
|
330
|
+
|
|
331
|
+
4. **Use skill resources**:
|
|
332
|
+
- Run scripts from \`scripts/\` folder if needed
|
|
333
|
+
- Use templates from \`templates/\` folder
|
|
334
|
+
- Reference examples from \`examples/\` folder
|
|
335
|
+
|
|
336
|
+
5. **Implement following skill.md guidance**:
|
|
337
|
+
- Follow step-by-step process
|
|
338
|
+
- Use correct naming conventions
|
|
339
|
+
- Apply code style rules
|
|
340
|
+
- Respect limitations
|
|
341
|
+
|
|
342
|
+
**Example workflow:**
|
|
343
|
+
- Task: \`(projectType: perfex-module, skill: module-codebase)\`
|
|
344
|
+
- Agent reads: \`heraspec/skills/perfex-module/module-codebase/skill.md\`
|
|
345
|
+
- Agent follows: Steps, uses templates, runs scripts
|
|
346
|
+
- Agent implements: According to skill.md guidelines
|
|
347
|
+
|
|
348
|
+
**Special case - UI/UX skill:**
|
|
349
|
+
- Task: \`(skill: ui-ux)\`
|
|
350
|
+
- Agent reads: \`heraspec/skills/ui-ux/skill.md\`
|
|
351
|
+
- Agent MUST use search scripts before implementing:
|
|
352
|
+
\`\`\`bash
|
|
353
|
+
# Search for design intelligence
|
|
354
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "<keyword>" --domain <domain>
|
|
355
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "<keyword>" --stack <stack>
|
|
356
|
+
\`\`\`
|
|
357
|
+
- Agent synthesizes search results
|
|
358
|
+
- Agent implements with proper colors, fonts, styles from search results
|
|
359
|
+
- Agent verifies with pre-delivery checklist
|
|
360
|
+
|
|
361
|
+
- Follow tasks.md
|
|
362
|
+
- Mark tasks completed: \`- [x]\`
|
|
363
|
+
|
|
364
|
+
### Step 5 \u2014 Archive
|
|
365
|
+
- Run: \`heraspec archive <slug> --yes\`
|
|
366
|
+
- This merges delta specs into source specs
|
|
367
|
+
- Moves change folder to archives
|
|
368
|
+
|
|
369
|
+
## Spec Format
|
|
370
|
+
|
|
371
|
+
Specs must include:
|
|
372
|
+
- \`## Meta\` section with project type, domain, stack
|
|
373
|
+
- \`## Purpose\`
|
|
374
|
+
- \`## Requirements\` with scenarios
|
|
375
|
+
|
|
376
|
+
## Delta Spec Format
|
|
377
|
+
|
|
378
|
+
Delta specs use:
|
|
379
|
+
- \`## ADDED Requirements\`
|
|
380
|
+
- \`## MODIFIED Requirements\`
|
|
381
|
+
- \`## REMOVED Requirements\`
|
|
382
|
+
|
|
383
|
+
## Tasks Format
|
|
384
|
+
|
|
385
|
+
Tasks grouped by project type and skill:
|
|
386
|
+
\`\`\`
|
|
387
|
+
## 1. WordPress plugin \u2013 admin settings page (projectType: wordpress-plugin, skill: admin-settings-page)
|
|
388
|
+
- [ ] Task description
|
|
389
|
+
\`\`\`
|
|
390
|
+
|
|
391
|
+
## Skills System
|
|
392
|
+
|
|
393
|
+
**CRITICAL: Always use Skills when implementing tasks!**
|
|
394
|
+
|
|
395
|
+
### How Skills Work
|
|
396
|
+
|
|
397
|
+
1. **Tasks reference skills**: \`(projectType: perfex-module, skill: module-codebase)\`
|
|
398
|
+
2. **Find skill folder**:
|
|
399
|
+
- Project-specific: \`heraspec/skills/<project-type>/<skill-name>/\`
|
|
400
|
+
- Cross-cutting: \`heraspec/skills/<skill-name>/\`
|
|
401
|
+
3. **Read skill.md**: Understand purpose, steps, inputs, outputs, rules
|
|
402
|
+
4. **Use skill resources**: Scripts, templates, examples
|
|
403
|
+
5. **Implement following skill.md**: Follow step-by-step process
|
|
404
|
+
|
|
405
|
+
### Skill Discovery
|
|
406
|
+
|
|
407
|
+
- List all skills: Check \`heraspec/skills/\` directory
|
|
408
|
+
- Project-specific skills: \`heraspec/skills/<project-type>/\`
|
|
409
|
+
- Cross-cutting skills: \`heraspec/skills/<skill-name>/\` (root level)
|
|
410
|
+
|
|
411
|
+
### When Change Has Multiple Skills
|
|
412
|
+
|
|
413
|
+
**Important**: Each task group uses ONE skill. When working on a task group, agent MUST use that skill's skill.md.
|
|
414
|
+
|
|
415
|
+
Example with multiple skills in one change:
|
|
416
|
+
\`\`\`
|
|
417
|
+
## 1. Perfex module \u2013 Feature (projectType: perfex-module, skill: module-codebase)
|
|
418
|
+
- [ ] Task 1.1 Create module structure
|
|
419
|
+
- [ ] Task 1.2 Configure registration
|
|
420
|
+
|
|
421
|
+
## 2. UI/UX \u2013 Admin Interface (skill: ui-ux)
|
|
422
|
+
- [ ] Task 2.1 Design color palette
|
|
423
|
+
- [ ] Task 2.2 Create component styles
|
|
424
|
+
|
|
425
|
+
## 3. Documents \u2013 User Guide (skill: documents)
|
|
426
|
+
- [ ] Task 3.1 Write technical docs
|
|
427
|
+
\`\`\`
|
|
428
|
+
|
|
429
|
+
**Agent workflow:**
|
|
430
|
+
1. **For task group 1** (module-codebase):
|
|
431
|
+
- Read: \`heraspec/skills/perfex-module/module-codebase/skill.md\`
|
|
432
|
+
- Follow: Module codebase process
|
|
433
|
+
- Use: Module codebase templates/scripts
|
|
434
|
+
- Implement: Tasks 1.1, 1.2
|
|
435
|
+
|
|
436
|
+
2. **For task group 2** (ui-ux):
|
|
437
|
+
- Read: \`heraspec/skills/ui-ux/skill.md\`
|
|
438
|
+
- Follow: UI/UX process
|
|
439
|
+
- Use: UI/UX templates/scripts
|
|
440
|
+
- Implement: Tasks 2.1, 2.2
|
|
441
|
+
|
|
442
|
+
3. **For task group 3** (documents):
|
|
443
|
+
- Read: \`heraspec/skills/documents/skill.md\`
|
|
444
|
+
- Follow: Documents process
|
|
445
|
+
- Use: Documents templates/scripts
|
|
446
|
+
- Implement: Task 3.1
|
|
447
|
+
|
|
448
|
+
**Key rule**: Switch skill.md when switching task groups!
|
|
449
|
+
|
|
450
|
+
## Rules
|
|
451
|
+
|
|
452
|
+
1. **Specs first, tasks second, implementation last.**
|
|
453
|
+
2. **Always use Skills**: When task has skill tag, MUST read and follow skill.md
|
|
454
|
+
3. Never modify source-of-truth specs directly.
|
|
455
|
+
4. Delta specs go in \`heraspec/specs/<slug>/\` (NOT in changes folder).
|
|
456
|
+
5. Always wait for approval before implementation.
|
|
457
|
+
6. **One skill per task group**: Each task group should use one skill consistently.
|
|
458
|
+
|
|
459
|
+
`;
|
|
460
|
+
var SKILLS_SECTION_TEMPLATE = `## Skills System
|
|
461
|
+
|
|
462
|
+
**CRITICAL: When implementing tasks, ALWAYS use Skills system:**
|
|
463
|
+
|
|
464
|
+
1. **Read task line** to identify skill:
|
|
465
|
+
\`\`\`markdown
|
|
466
|
+
## 1. Perfex module \u2013 Category Management (projectType: perfex-module, skill: module-codebase)
|
|
467
|
+
- [ ] 1.1 Create module structure
|
|
468
|
+
\`\`\`
|
|
469
|
+
|
|
470
|
+
2. **Find skill folder**:
|
|
471
|
+
- Project-specific: \`heraspec/skills/<project-type>/<skill-name>/\`
|
|
472
|
+
- Cross-cutting: \`heraspec/skills/<skill-name>/\`
|
|
473
|
+
|
|
474
|
+
3. **Read skill.md**:
|
|
475
|
+
- Understand purpose, steps, inputs, outputs
|
|
476
|
+
- Follow tone, rules, and limitations
|
|
477
|
+
- Check available templates and scripts
|
|
478
|
+
|
|
479
|
+
4. **Use skill resources**:
|
|
480
|
+
- Run scripts from \`scripts/\` folder if needed
|
|
481
|
+
- Use templates from \`templates/\` folder
|
|
482
|
+
- Reference examples from \`examples/\` folder
|
|
483
|
+
|
|
484
|
+
5. **Implement following skill.md guidance**:
|
|
485
|
+
- Follow step-by-step process
|
|
486
|
+
- Use correct naming conventions
|
|
487
|
+
- Apply code style rules
|
|
488
|
+
- Respect limitations
|
|
489
|
+
|
|
490
|
+
**Example workflow:**
|
|
491
|
+
- Task: \`(projectType: perfex-module, skill: module-codebase)\`
|
|
492
|
+
- Agent reads: \`heraspec/skills/perfex-module/module-codebase/skill.md\`
|
|
493
|
+
- Agent follows: Steps, uses templates, runs scripts
|
|
494
|
+
- Agent implements: According to skill.md guidelines
|
|
495
|
+
|
|
496
|
+
**Special case - UI/UX skill:**
|
|
497
|
+
- Task: \`(skill: ui-ux)\`
|
|
498
|
+
- Agent reads: \`heraspec/skills/ui-ux/skill.md\`
|
|
499
|
+
- Agent MUST use search scripts before implementing:
|
|
500
|
+
\`\`\`bash
|
|
501
|
+
# Search for design intelligence (BM25 - default, fast)
|
|
502
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "<keyword>" --domain <domain>
|
|
503
|
+
|
|
504
|
+
# For better semantic results, use Vector or Hybrid mode:
|
|
505
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "<keyword>" --domain <domain> --mode vector
|
|
506
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "<keyword>" --domain <domain> --mode hybrid
|
|
507
|
+
|
|
508
|
+
# Stack-specific search
|
|
509
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "<keyword>" --stack <stack>
|
|
510
|
+
\`\`\`
|
|
511
|
+
- **Search Modes:**
|
|
512
|
+
- **BM25 (default)**: Fast keyword-based search, zero dependencies
|
|
513
|
+
- **Vector**: Semantic search, ~15-20% better results (requires: pip install sentence-transformers scikit-learn)
|
|
514
|
+
- **Hybrid**: Best of both, ~25% better results (requires: pip install sentence-transformers scikit-learn)
|
|
515
|
+
- Agent synthesizes search results
|
|
516
|
+
- Agent implements with proper colors, fonts, styles from search results
|
|
517
|
+
- Agent verifies with pre-delivery checklist
|
|
518
|
+
|
|
519
|
+
### Skill Discovery
|
|
520
|
+
|
|
521
|
+
- List all skills: Check \`heraspec/skills/\` directory
|
|
522
|
+
- Project-specific skills: \`heraspec/skills/<project-type>/\`
|
|
523
|
+
- Cross-cutting skills: \`heraspec/skills/<skill-name>/\` (root level)
|
|
524
|
+
|
|
525
|
+
### When Change Has Multiple Skills
|
|
526
|
+
|
|
527
|
+
**Important**: Each task group uses ONE skill. When working on a task group, agent MUST use that skill's skill.md.
|
|
528
|
+
|
|
529
|
+
Example with multiple skills in one change:
|
|
530
|
+
\`\`\`
|
|
531
|
+
## 1. Perfex module \u2013 Feature (projectType: perfex-module, skill: module-codebase)
|
|
532
|
+
- [ ] Task 1.1 Create module structure
|
|
533
|
+
- [ ] Task 1.2 Configure registration
|
|
534
|
+
|
|
535
|
+
## 2. UI/UX \u2013 Admin Interface (skill: ui-ux)
|
|
536
|
+
- [ ] Task 2.1 Design color palette
|
|
537
|
+
- [ ] Task 2.2 Create component styles
|
|
538
|
+
|
|
539
|
+
## 3. Documents \u2013 User Guide (skill: documents)
|
|
540
|
+
- [ ] Task 3.1 Write technical docs
|
|
541
|
+
\`\`\`
|
|
542
|
+
|
|
543
|
+
**Agent workflow:**
|
|
544
|
+
1. **For task group 1** (module-codebase):
|
|
545
|
+
- Read: \`heraspec/skills/perfex-module/module-codebase/skill.md\`
|
|
546
|
+
- Follow: Module codebase process
|
|
547
|
+
- Use: Module codebase templates/scripts
|
|
548
|
+
- Implement: Tasks 1.1, 1.2
|
|
549
|
+
|
|
550
|
+
2. **For task group 2** (ui-ux):
|
|
551
|
+
- Read: \`heraspec/skills/ui-ux/skill.md\`
|
|
552
|
+
- Follow: UI/UX process
|
|
553
|
+
- Use: UI/UX templates/scripts
|
|
554
|
+
- Implement: Tasks 2.1, 2.2
|
|
555
|
+
|
|
556
|
+
3. **For task group 3** (documents):
|
|
557
|
+
- Read: \`heraspec/skills/documents/skill.md\`
|
|
558
|
+
- Follow: Documents process
|
|
559
|
+
- Use: Documents templates/scripts
|
|
560
|
+
- Implement: Task 3.1
|
|
561
|
+
|
|
562
|
+
**Key rule**: Switch skill.md when switching task groups!
|
|
563
|
+
`;
|
|
564
|
+
var CONFIG_TEMPLATE = `# HeraSpec Configuration
|
|
565
|
+
|
|
566
|
+
projectTypes:
|
|
567
|
+
- wordpress-plugin
|
|
568
|
+
# Add other project types as needed
|
|
569
|
+
|
|
570
|
+
defaultDomain: global
|
|
571
|
+
|
|
572
|
+
`;
|
|
573
|
+
var PROPOSAL_TEMPLATE = `# Change Proposal: <slug>
|
|
574
|
+
|
|
575
|
+
## Purpose
|
|
576
|
+
Describe why this change is needed.
|
|
577
|
+
|
|
578
|
+
## Scope
|
|
579
|
+
What will be changed?
|
|
580
|
+
|
|
581
|
+
## Project Types
|
|
582
|
+
- wordpress-plugin
|
|
583
|
+
- perfex-module
|
|
584
|
+
|
|
585
|
+
## Impact
|
|
586
|
+
What parts of the system will be affected?
|
|
587
|
+
|
|
588
|
+
`;
|
|
589
|
+
var TASKS_TEMPLATE = `# Tasks
|
|
590
|
+
|
|
591
|
+
## 1. WordPress plugin \u2013 feature name (projectType: wordpress-plugin, skill: admin-settings-page)
|
|
592
|
+
- [ ] Task 1.1
|
|
593
|
+
- [ ] Task 1.2
|
|
594
|
+
|
|
595
|
+
## 2. Perfex module \u2013 feature name (projectType: perfex-module, skill: module-registration)
|
|
596
|
+
- [ ] Task 2.1
|
|
597
|
+
|
|
598
|
+
`;
|
|
599
|
+
var TemplateManager = class {
|
|
600
|
+
static getProjectTemplate() {
|
|
601
|
+
return PROJECT_TEMPLATE;
|
|
602
|
+
}
|
|
603
|
+
static getAgentsTemplate() {
|
|
604
|
+
return AGENTS_TEMPLATE;
|
|
605
|
+
}
|
|
606
|
+
static getConfigTemplate() {
|
|
607
|
+
return CONFIG_TEMPLATE;
|
|
608
|
+
}
|
|
609
|
+
static getProposalTemplate(slug) {
|
|
610
|
+
return PROPOSAL_TEMPLATE.replace("<slug>", slug);
|
|
611
|
+
}
|
|
612
|
+
static getTasksTemplate() {
|
|
613
|
+
return TASKS_TEMPLATE;
|
|
614
|
+
}
|
|
615
|
+
static getSkillsSection() {
|
|
616
|
+
return SKILLS_SECTION_TEMPLATE;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
// src/core/init.ts
|
|
621
|
+
var InitCommand = class {
|
|
622
|
+
async execute(targetPath = ".") {
|
|
623
|
+
const resolvedPath = path2.resolve(targetPath);
|
|
624
|
+
const heraspecPath = path2.join(resolvedPath, HERASPEC_DIR_NAME);
|
|
625
|
+
const alreadyInitialized = await FileSystemUtils.fileExists(
|
|
626
|
+
path2.join(heraspecPath, HERASPEC_MARKERS.PROJECT_MD)
|
|
627
|
+
);
|
|
628
|
+
const spinner = ora({
|
|
629
|
+
text: alreadyInitialized ? "Updating HeraSpec..." : "Initializing HeraSpec...",
|
|
630
|
+
color: "blue"
|
|
631
|
+
}).start();
|
|
632
|
+
try {
|
|
633
|
+
await FileSystemUtils.createDirectory(heraspecPath);
|
|
634
|
+
await FileSystemUtils.createDirectory(path2.join(heraspecPath, SPECS_DIR_NAME));
|
|
635
|
+
await FileSystemUtils.createDirectory(path2.join(heraspecPath, CHANGES_DIR_NAME));
|
|
636
|
+
await FileSystemUtils.createDirectory(path2.join(heraspecPath, ARCHIVES_DIR_NAME));
|
|
637
|
+
await FileSystemUtils.createDirectory(path2.join(heraspecPath, SKILLS_DIR_NAME));
|
|
638
|
+
const skillsReadmePath = path2.join(heraspecPath, SKILLS_DIR_NAME, "README.md");
|
|
639
|
+
if (!await FileSystemUtils.fileExists(skillsReadmePath)) {
|
|
640
|
+
const skillsReadme = await this.getSkillsReadmeTemplate();
|
|
641
|
+
await FileSystemUtils.writeFile(skillsReadmePath, skillsReadme);
|
|
642
|
+
}
|
|
643
|
+
const uiuxGuidePath = path2.join(heraspecPath, SKILLS_DIR_NAME, "UI_UX_SKILL_QUICK_REFERENCE.md");
|
|
644
|
+
if (!await FileSystemUtils.fileExists(uiuxGuidePath)) {
|
|
645
|
+
const uiuxGuide = await this.getUIUXQuickReference();
|
|
646
|
+
await FileSystemUtils.writeFile(uiuxGuidePath, uiuxGuide);
|
|
647
|
+
}
|
|
648
|
+
await this.createTemplateFiles(heraspecPath, alreadyInitialized);
|
|
649
|
+
const agentsPath = path2.join(resolvedPath, HERASPEC_MARKERS.AGENTS_MD);
|
|
650
|
+
await this.updateAgentsFile(agentsPath, alreadyInitialized);
|
|
651
|
+
await this.updateRelatedMarkdownFiles(resolvedPath);
|
|
652
|
+
spinner.succeed(
|
|
653
|
+
chalk.green(
|
|
654
|
+
alreadyInitialized ? "HeraSpec updated successfully" : "HeraSpec initialized successfully"
|
|
655
|
+
)
|
|
656
|
+
);
|
|
657
|
+
console.log();
|
|
658
|
+
console.log(chalk.cyan("Next steps:"));
|
|
659
|
+
console.log(
|
|
660
|
+
chalk.gray("1. Review and update heraspec/project.md with your project details")
|
|
661
|
+
);
|
|
662
|
+
console.log(
|
|
663
|
+
chalk.gray('2. Create your first change: "Create a HeraSpec change to..."')
|
|
664
|
+
);
|
|
665
|
+
console.log(
|
|
666
|
+
chalk.gray("3. List changes: heraspec list")
|
|
667
|
+
);
|
|
668
|
+
} catch (error) {
|
|
669
|
+
spinner.fail(chalk.red(`Error: ${error.message}`));
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
async createTemplateFiles(heraspecPath, skipExisting) {
|
|
674
|
+
const projectMdPath = path2.join(heraspecPath, HERASPEC_MARKERS.PROJECT_MD);
|
|
675
|
+
const configYamlPath = path2.join(heraspecPath, HERASPEC_MARKERS.CONFIG_YAML);
|
|
676
|
+
if (!await FileSystemUtils.fileExists(projectMdPath) || !skipExisting) {
|
|
677
|
+
await FileSystemUtils.writeFile(
|
|
678
|
+
projectMdPath,
|
|
679
|
+
TemplateManager.getProjectTemplate()
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
if (!await FileSystemUtils.fileExists(configYamlPath) || !skipExisting) {
|
|
683
|
+
await FileSystemUtils.writeFile(
|
|
684
|
+
configYamlPath,
|
|
685
|
+
TemplateManager.getConfigTemplate()
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
async updateAgentsFile(agentsPath, alreadyInitialized) {
|
|
690
|
+
const skillsSectionMarker = "## Skills System";
|
|
691
|
+
const skillsSectionEndMarker = "**Key rule**: Switch skill.md when switching task groups!";
|
|
692
|
+
if (!alreadyInitialized) {
|
|
693
|
+
await FileSystemUtils.writeFile(
|
|
694
|
+
agentsPath,
|
|
695
|
+
TemplateManager.getAgentsTemplate()
|
|
696
|
+
);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
let existingContent = "";
|
|
700
|
+
if (await FileSystemUtils.fileExists(agentsPath)) {
|
|
701
|
+
existingContent = await FileSystemUtils.readFile(agentsPath);
|
|
702
|
+
}
|
|
703
|
+
if (existingContent.includes(skillsSectionMarker)) {
|
|
704
|
+
const skillsSection = await this.getSkillsSection();
|
|
705
|
+
const updatedContent = this.replaceSkillsSection(existingContent, skillsSection);
|
|
706
|
+
await FileSystemUtils.writeFile(agentsPath, updatedContent);
|
|
707
|
+
} else {
|
|
708
|
+
const skillsSection = await this.getSkillsSection();
|
|
709
|
+
const updatedContent = this.appendSkillsSection(existingContent, skillsSection);
|
|
710
|
+
await FileSystemUtils.writeFile(agentsPath, updatedContent);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
replaceSkillsSection(existingContent, newSkillsSection) {
|
|
714
|
+
const startMarker = "## Skills System";
|
|
715
|
+
const startIndex = existingContent.indexOf(startMarker);
|
|
716
|
+
if (startIndex === -1) {
|
|
717
|
+
return this.appendSkillsSection(existingContent, newSkillsSection);
|
|
718
|
+
}
|
|
719
|
+
let endIndex = existingContent.indexOf("\n## ", startIndex + startMarker.length);
|
|
720
|
+
if (endIndex === -1) {
|
|
721
|
+
endIndex = existingContent.length;
|
|
722
|
+
}
|
|
723
|
+
const before = existingContent.substring(0, startIndex).trimEnd();
|
|
724
|
+
const after = existingContent.substring(endIndex);
|
|
725
|
+
return before + "\n\n" + newSkillsSection + (after.trimStart().startsWith("\n") ? "" : "\n\n") + after;
|
|
726
|
+
}
|
|
727
|
+
appendSkillsSection(existingContent, skillsSection) {
|
|
728
|
+
const rulesMarker = "\n## Rules\n";
|
|
729
|
+
const rulesIndex = existingContent.indexOf(rulesMarker);
|
|
730
|
+
if (rulesIndex !== -1) {
|
|
731
|
+
const before = existingContent.substring(0, rulesIndex).trimEnd();
|
|
732
|
+
const after = existingContent.substring(rulesIndex);
|
|
733
|
+
return before + "\n\n" + skillsSection + "\n\n" + after;
|
|
734
|
+
}
|
|
735
|
+
const rulesMarker2 = "## Rules";
|
|
736
|
+
const rulesIndex2 = existingContent.indexOf(rulesMarker2);
|
|
737
|
+
if (rulesIndex2 !== -1 && rulesIndex2 > 0) {
|
|
738
|
+
const before = existingContent.substring(0, rulesIndex2).trimEnd();
|
|
739
|
+
const after = existingContent.substring(rulesIndex2);
|
|
740
|
+
return before + "\n\n" + skillsSection + "\n\n" + after;
|
|
741
|
+
}
|
|
742
|
+
return existingContent.trimEnd() + "\n\n" + skillsSection;
|
|
743
|
+
}
|
|
744
|
+
async getSkillsSection() {
|
|
745
|
+
return TemplateManager.getSkillsSection();
|
|
746
|
+
}
|
|
747
|
+
async getSkillsReadmeTemplate() {
|
|
748
|
+
return `# Skills Directory
|
|
749
|
+
|
|
750
|
+
This directory contains reusable skills for HeraSpec projects.
|
|
751
|
+
|
|
752
|
+
## What Are Skills?
|
|
753
|
+
|
|
754
|
+
Skills are reusable patterns and workflows that help AI agents implement tasks consistently. Each skill contains:
|
|
755
|
+
|
|
756
|
+
- **skill.md**: Complete guide on how to use the skill
|
|
757
|
+
- **templates/**: Reusable templates
|
|
758
|
+
- **scripts/**: Automation scripts
|
|
759
|
+
- **examples/**: Good and bad examples
|
|
760
|
+
|
|
761
|
+
## How Agents Use Skills
|
|
762
|
+
|
|
763
|
+
When a task has a skill tag:
|
|
764
|
+
\`\`\`markdown
|
|
765
|
+
## 1. Feature (projectType: perfex-module, skill: module-codebase)
|
|
766
|
+
- [ ] Task 1.1
|
|
767
|
+
\`\`\`
|
|
768
|
+
|
|
769
|
+
The agent will:
|
|
770
|
+
1. Find skill folder: \`heraspec/skills/perfex-module/module-codebase/\`
|
|
771
|
+
2. Read \`skill.md\` to understand process
|
|
772
|
+
3. Use templates and scripts from skill folder
|
|
773
|
+
4. Follow guidelines in skill.md
|
|
774
|
+
|
|
775
|
+
## Available Skills
|
|
776
|
+
|
|
777
|
+
Run \`heraspec skill list\` to see all available skills.
|
|
778
|
+
|
|
779
|
+
## UI/UX Skill - Creating Full Theme Packages
|
|
780
|
+
|
|
781
|
+
The **UI/UX skill** is particularly useful for creating complete website themes with multiple pages.
|
|
782
|
+
|
|
783
|
+
### Quick Start
|
|
784
|
+
|
|
785
|
+
When you need to create a full website package, use prompts like:
|
|
786
|
+
|
|
787
|
+
\`\`\`
|
|
788
|
+
T\u1EA1o g\xF3i website \u0111\u1EA7y \u0111\u1EE7 cho [PRODUCT_TYPE] v\u1EDBi style [STYLE_KEYWORDS].
|
|
789
|
+
S\u1EED d\u1EE5ng skill ui-ux v\u1EDBi hybrid mode \u0111\u1EC3 search design intelligence.
|
|
790
|
+
T\u1EA1o c\xE1c trang: home, about, [other pages].
|
|
791
|
+
Stack: [html-tailwind/react/nextjs].
|
|
792
|
+
\u0110\u1EA3m b\u1EA3o responsive, accessible, consistent design system.
|
|
793
|
+
\`\`\`
|
|
794
|
+
|
|
795
|
+
### Prompt Templates
|
|
796
|
+
|
|
797
|
+
For detailed prompt examples and templates, see:
|
|
798
|
+
- **Example Prompts**: \`heraspec/skills/ui-ux/templates/example-prompt-full-theme.md\`
|
|
799
|
+
- **Prompt Templates**: \`heraspec/skills/ui-ux/templates/prompt-template-full-theme.md\`
|
|
800
|
+
|
|
801
|
+
These templates include:
|
|
802
|
+
- Ready-to-use prompts for different website types (E-commerce, SaaS, Service, Blog, Portfolio)
|
|
803
|
+
- Step-by-step instructions
|
|
804
|
+
- Search command examples
|
|
805
|
+
- Best practices
|
|
806
|
+
|
|
807
|
+
### Search Modes
|
|
808
|
+
|
|
809
|
+
UI/UX skill supports 3 search modes:
|
|
810
|
+
- **BM25 (default)**: Fast keyword-based search, zero dependencies
|
|
811
|
+
- **Vector**: Semantic search, ~15-20% better results (requires: \`pip install sentence-transformers scikit-learn\`)
|
|
812
|
+
- **Hybrid**: Best of both, ~25% better results (requires: \`pip install sentence-transformers scikit-learn\`)
|
|
813
|
+
|
|
814
|
+
**Usage:**
|
|
815
|
+
\`\`\`bash
|
|
816
|
+
# BM25 (default)
|
|
817
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "minimalism" --domain style
|
|
818
|
+
|
|
819
|
+
# Vector (semantic)
|
|
820
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "elegant dark theme" --domain style --mode vector
|
|
821
|
+
|
|
822
|
+
# Hybrid (best)
|
|
823
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "modern minimal design" --domain style --mode hybrid
|
|
824
|
+
\`\`\`
|
|
825
|
+
|
|
826
|
+
### Multi-Page Support
|
|
827
|
+
|
|
828
|
+
Default page set includes:
|
|
829
|
+
1. Home
|
|
830
|
+
2. About
|
|
831
|
+
3. Post Details
|
|
832
|
+
4. Category
|
|
833
|
+
5. Pricing
|
|
834
|
+
6. FAQ
|
|
835
|
+
7. Contact
|
|
836
|
+
8. Product Category (e-commerce)
|
|
837
|
+
9. Product Details (e-commerce)
|
|
838
|
+
|
|
839
|
+
Search page types:
|
|
840
|
+
\`\`\`bash
|
|
841
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "home homepage" --domain pages
|
|
842
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "pricing plans" --domain pages
|
|
843
|
+
\`\`\`
|
|
844
|
+
|
|
845
|
+
### Adding UI/UX Skill to Your Project
|
|
846
|
+
|
|
847
|
+
1. Copy skill from HeraSpec core:
|
|
848
|
+
\`\`\`bash
|
|
849
|
+
# Copy UI/UX skill
|
|
850
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/ui-ux-skill.md heraspec/skills/ui-ux/
|
|
851
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/scripts heraspec/skills/ui-ux/
|
|
852
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/data heraspec/skills/ui-ux/
|
|
853
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/templates heraspec/skills/ui-ux/
|
|
854
|
+
\`\`\`
|
|
855
|
+
|
|
856
|
+
2. Or use \`heraspec skill add ui-ux\` (if available)
|
|
857
|
+
|
|
858
|
+
3. Read \`heraspec/skills/ui-ux/ui-ux-skill.md\` for complete documentation
|
|
859
|
+
|
|
860
|
+
## Creating New Skills
|
|
861
|
+
|
|
862
|
+
1. Create skill folder structure
|
|
863
|
+
2. Write \`skill.md\` following the template
|
|
864
|
+
3. Add templates, scripts, examples as needed
|
|
865
|
+
|
|
866
|
+
See \`docs/SKILLS_STRUCTURE_PROPOSAL.md\` for detailed structure.
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
async getUIUXQuickReference() {
|
|
870
|
+
return `# UI/UX Skill - Quick Reference Guide
|
|
871
|
+
|
|
872
|
+
Quick guide for creating prompts to build full theme packages with multiple pages using the ui-ux skill.
|
|
873
|
+
|
|
874
|
+
## \u{1F4CB} Basic Prompt Template
|
|
875
|
+
|
|
876
|
+
\`\`\`
|
|
877
|
+
Create a complete website package for [PRODUCT_TYPE] with the following requirements:
|
|
878
|
+
|
|
879
|
+
**Project Information:**
|
|
880
|
+
- Product type: [SaaS / E-commerce / Service / Portfolio / etc.]
|
|
881
|
+
- Style: [minimal / elegant / modern / bold / etc.]
|
|
882
|
+
- Industry: [Healthcare / Fintech / Beauty / etc.]
|
|
883
|
+
- Stack: [html-tailwind / react / nextjs / etc.]
|
|
884
|
+
- Pages to create: home, about, [add other pages if needed]
|
|
885
|
+
|
|
886
|
+
**Process:**
|
|
887
|
+
1. Use skill ui-ux to search design intelligence with hybrid mode
|
|
888
|
+
2. Create shared components first (Header, Footer, Button, Card)
|
|
889
|
+
3. Implement pages in order
|
|
890
|
+
4. Ensure consistency in colors, typography, spacing
|
|
891
|
+
5. Verify with pre-delivery checklist
|
|
892
|
+
|
|
893
|
+
**Quality Requirements:**
|
|
894
|
+
- \u2705 Consistent design system
|
|
895
|
+
- \u2705 Responsive (320px, 768px, 1024px, 1440px)
|
|
896
|
+
- \u2705 Accessible (WCAG AA minimum)
|
|
897
|
+
- \u2705 Performance optimized
|
|
898
|
+
\`\`\`
|
|
899
|
+
|
|
900
|
+
## \u{1F3AF} Specific Prompt Examples
|
|
901
|
+
|
|
902
|
+
### E-Commerce
|
|
903
|
+
\`\`\`
|
|
904
|
+
Create a complete website package for an online fashion store.
|
|
905
|
+
|
|
906
|
+
Product type: E-commerce Luxury
|
|
907
|
+
Style: elegant, premium, sophisticated
|
|
908
|
+
Stack: Next.js with Tailwind CSS
|
|
909
|
+
Pages: home, about, product category, product details, cart, checkout, thank you, faq, contact
|
|
910
|
+
|
|
911
|
+
Use skill ui-ux with hybrid mode. Focus on conversion optimization.
|
|
912
|
+
\`\`\`
|
|
913
|
+
|
|
914
|
+
### SaaS
|
|
915
|
+
\`\`\`
|
|
916
|
+
Create a complete website package for a project management SaaS platform.
|
|
917
|
+
|
|
918
|
+
Product type: SaaS (General)
|
|
919
|
+
Style: modern, clean, professional
|
|
920
|
+
Stack: React with Tailwind CSS
|
|
921
|
+
Pages: home, about, pricing, features, faq, contact, login, register, dashboard
|
|
922
|
+
|
|
923
|
+
Use skill ui-ux with hybrid mode. Ensure professional and trustworthy.
|
|
924
|
+
\`\`\`
|
|
925
|
+
|
|
926
|
+
### Service Business
|
|
927
|
+
\`\`\`
|
|
928
|
+
Create a complete website package for a healthcare service.
|
|
929
|
+
|
|
930
|
+
Product type: Beauty & Wellness Service
|
|
931
|
+
Style: elegant, minimal, soft, professional
|
|
932
|
+
Stack: html-tailwind
|
|
933
|
+
Pages: home, about, services, blog listing, post details, category, pricing, faq, contact
|
|
934
|
+
|
|
935
|
+
Use skill ui-ux with hybrid mode. Focus on trust and credibility.
|
|
936
|
+
\`\`\`
|
|
937
|
+
|
|
938
|
+
## \u{1F50D} Search Modes
|
|
939
|
+
|
|
940
|
+
### BM25 (Default)
|
|
941
|
+
\`\`\`bash
|
|
942
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "minimalism" --domain style
|
|
943
|
+
\`\`\`
|
|
944
|
+
- \u2705 Fast, zero dependencies
|
|
945
|
+
- \u2705 Best for exact keyword matches
|
|
946
|
+
|
|
947
|
+
### Vector (Semantic)
|
|
948
|
+
\`\`\`bash
|
|
949
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "elegant dark theme" --domain style --mode vector
|
|
950
|
+
\`\`\`
|
|
951
|
+
- \u2705 Understands meaning and synonyms
|
|
952
|
+
- \u2705 ~15-20% better results
|
|
953
|
+
- \u26A0\uFE0F Requires: \`pip install sentence-transformers scikit-learn\`
|
|
954
|
+
|
|
955
|
+
### Hybrid (Best)
|
|
956
|
+
\`\`\`bash
|
|
957
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "modern minimal design" --domain style --mode hybrid
|
|
958
|
+
\`\`\`
|
|
959
|
+
- \u2705 Combines BM25 + Vector
|
|
960
|
+
- \u2705 ~25% better results
|
|
961
|
+
- \u26A0\uFE0F Requires: \`pip install sentence-transformers scikit-learn\`
|
|
962
|
+
|
|
963
|
+
## \u{1F4C4} Default Page Set
|
|
964
|
+
|
|
965
|
+
When creating a "complete website package", the default set includes 9 pages:
|
|
966
|
+
|
|
967
|
+
1. **Home** - Main homepage
|
|
968
|
+
2. **About** - Company/story page
|
|
969
|
+
3. **Post Details** - Blog/article detail
|
|
970
|
+
4. **Category** - Blog/category listing
|
|
971
|
+
5. **Pricing** - Pricing plans
|
|
972
|
+
6. **FAQ** - Frequently asked questions
|
|
973
|
+
7. **Contact** - Contact form
|
|
974
|
+
8. **Product Category** - E-commerce category (if applicable)
|
|
975
|
+
9. **Product Details** - E-commerce product detail (if applicable)
|
|
976
|
+
|
|
977
|
+
## \u{1F527} Search Page Types
|
|
978
|
+
|
|
979
|
+
\`\`\`bash
|
|
980
|
+
# Home page
|
|
981
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "home homepage" --domain pages
|
|
982
|
+
|
|
983
|
+
# About page
|
|
984
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "about company story" --domain pages
|
|
985
|
+
|
|
986
|
+
# Pricing page
|
|
987
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "pricing plans tiers" --domain pages
|
|
988
|
+
|
|
989
|
+
# E-commerce pages
|
|
990
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "product-category shop catalog" --domain pages
|
|
991
|
+
python3 heraspec/skills/ui-ux/scripts/search.py "product-detail single-product" --domain pages
|
|
992
|
+
\`\`\`
|
|
993
|
+
|
|
994
|
+
## \u{1F4DA} Detailed Documentation
|
|
995
|
+
|
|
996
|
+
After copying UI/UX skill to your project, see:
|
|
997
|
+
- \`heraspec/skills/ui-ux/ui-ux-skill.md\` - Complete skill documentation
|
|
998
|
+
- \`heraspec/skills/ui-ux/templates/example-prompt-full-theme.md\` - Detailed prompt examples
|
|
999
|
+
- \`heraspec/skills/ui-ux/templates/prompt-template-full-theme.md\` - Copy-paste templates
|
|
1000
|
+
|
|
1001
|
+
## \u{1F4A1} Tips
|
|
1002
|
+
|
|
1003
|
+
1. **Always mention "skill ui-ux"** - Agent will know to use this skill
|
|
1004
|
+
2. **Encourage using hybrid mode** - Best results
|
|
1005
|
+
3. **List all pages clearly** - Agent knows exact scope
|
|
1006
|
+
4. **Require consistency** - Ensures unified design system
|
|
1007
|
+
5. **Mention pre-delivery checklist** - Agent will verify before delivering
|
|
1008
|
+
|
|
1009
|
+
## \u{1F680} Quick Start
|
|
1010
|
+
|
|
1011
|
+
1. Copy UI/UX skill to project:
|
|
1012
|
+
\`\`\`bash
|
|
1013
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/ui-ux-skill.md heraspec/skills/ui-ux/
|
|
1014
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/scripts heraspec/skills/ui-ux/
|
|
1015
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/data heraspec/skills/ui-ux/
|
|
1016
|
+
cp -r /path/to/HeraSpec/src/core/templates/skills/templates heraspec/skills/ui-ux/
|
|
1017
|
+
\`\`\`
|
|
1018
|
+
|
|
1019
|
+
2. Use prompt template from above
|
|
1020
|
+
|
|
1021
|
+
3. Agent will automatically:
|
|
1022
|
+
- Search design intelligence with skill ui-ux
|
|
1023
|
+
- Create shared components
|
|
1024
|
+
- Implement each page
|
|
1025
|
+
- Verify with checklist
|
|
1026
|
+
`;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Update related markdown files in the project (README.md, etc.)
|
|
1030
|
+
*/
|
|
1031
|
+
async updateRelatedMarkdownFiles(projectPath) {
|
|
1032
|
+
const readmePath = path2.join(projectPath, "README.md");
|
|
1033
|
+
if (await FileSystemUtils.fileExists(readmePath)) {
|
|
1034
|
+
await this.updateReadmeFile(readmePath);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Update README.md with HeraSpec information
|
|
1039
|
+
*/
|
|
1040
|
+
async updateReadmeFile(readmePath) {
|
|
1041
|
+
const existingContent = await FileSystemUtils.readFile(readmePath);
|
|
1042
|
+
const heraspecSection = this.getHeraSpecReadmeSection();
|
|
1043
|
+
const sectionMarkers = [
|
|
1044
|
+
"## HeraSpec",
|
|
1045
|
+
"## HeraSpec Development",
|
|
1046
|
+
"### HeraSpec",
|
|
1047
|
+
"### HeraSpec Development",
|
|
1048
|
+
"<!-- HeraSpec Section -->"
|
|
1049
|
+
];
|
|
1050
|
+
let hasHeraSpecSection = false;
|
|
1051
|
+
let sectionStartIndex = -1;
|
|
1052
|
+
let sectionEndIndex = -1;
|
|
1053
|
+
for (const marker of sectionMarkers) {
|
|
1054
|
+
const index = existingContent.indexOf(marker);
|
|
1055
|
+
if (index !== -1) {
|
|
1056
|
+
hasHeraSpecSection = true;
|
|
1057
|
+
sectionStartIndex = index;
|
|
1058
|
+
sectionEndIndex = existingContent.indexOf("\n## ", index + marker.length);
|
|
1059
|
+
if (sectionEndIndex === -1) {
|
|
1060
|
+
sectionEndIndex = existingContent.indexOf("\n### ", index + marker.length);
|
|
1061
|
+
}
|
|
1062
|
+
if (sectionEndIndex === -1) {
|
|
1063
|
+
sectionEndIndex = existingContent.length;
|
|
1064
|
+
}
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (hasHeraSpecSection && sectionStartIndex !== -1) {
|
|
1069
|
+
const before = existingContent.substring(0, sectionStartIndex).trimEnd();
|
|
1070
|
+
const after = existingContent.substring(sectionEndIndex);
|
|
1071
|
+
const updatedContent = before + "\n\n" + heraspecSection + (after.trimStart().startsWith("\n") ? "" : "\n\n") + after;
|
|
1072
|
+
await FileSystemUtils.writeFile(readmePath, updatedContent);
|
|
1073
|
+
} else {
|
|
1074
|
+
const insertBeforeMarkers = [
|
|
1075
|
+
"\n## Development",
|
|
1076
|
+
"\n## Setup",
|
|
1077
|
+
"\n## Contributing",
|
|
1078
|
+
"\n## Installation",
|
|
1079
|
+
"\n## Getting Started"
|
|
1080
|
+
];
|
|
1081
|
+
let inserted = false;
|
|
1082
|
+
for (const marker of insertBeforeMarkers) {
|
|
1083
|
+
const index = existingContent.indexOf(marker);
|
|
1084
|
+
if (index !== -1) {
|
|
1085
|
+
const before = existingContent.substring(0, index).trimEnd();
|
|
1086
|
+
const after = existingContent.substring(index);
|
|
1087
|
+
const updatedContent = before + "\n\n" + heraspecSection + "\n\n" + after;
|
|
1088
|
+
await FileSystemUtils.writeFile(readmePath, updatedContent);
|
|
1089
|
+
inserted = true;
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (!inserted) {
|
|
1094
|
+
const updatedContent = existingContent.trimEnd() + "\n\n" + heraspecSection;
|
|
1095
|
+
await FileSystemUtils.writeFile(readmePath, updatedContent);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Get HeraSpec section content for README.md
|
|
1101
|
+
*/
|
|
1102
|
+
getHeraSpecReadmeSection() {
|
|
1103
|
+
return `<!-- HeraSpec Section -->
|
|
1104
|
+
## HeraSpec Development
|
|
1105
|
+
|
|
1106
|
+
This project uses [HeraSpec](https://github.com/your-org/heraspec) for spec-driven development.
|
|
1107
|
+
|
|
1108
|
+
### Quick Start
|
|
1109
|
+
|
|
1110
|
+
\`\`\`bash
|
|
1111
|
+
# Initialize HeraSpec (if not already done)
|
|
1112
|
+
heraspec init
|
|
1113
|
+
|
|
1114
|
+
# List active changes
|
|
1115
|
+
heraspec list
|
|
1116
|
+
|
|
1117
|
+
# View a change
|
|
1118
|
+
heraspec show <change-name>
|
|
1119
|
+
|
|
1120
|
+
# Validate changes
|
|
1121
|
+
heraspec validate <change-name>
|
|
1122
|
+
\`\`\`
|
|
1123
|
+
|
|
1124
|
+
### Project Structure
|
|
1125
|
+
|
|
1126
|
+
- \`heraspec/project.md\` - Project overview and configuration
|
|
1127
|
+
- \`heraspec/specs/\` - Source of truth specifications
|
|
1128
|
+
- \`heraspec/changes/\` - Active changes in progress
|
|
1129
|
+
- \`heraspec/skills/\` - Reusable skills for AI agents
|
|
1130
|
+
- \`AGENTS.heraspec.md\` - AI agent instructions
|
|
1131
|
+
|
|
1132
|
+
### Working with Changes
|
|
1133
|
+
|
|
1134
|
+
1. **Create a change**: Ask AI to create a HeraSpec change, or create manually
|
|
1135
|
+
2. **Refine specs**: Review and update delta specs in \`heraspec/specs/<change-name>/\`
|
|
1136
|
+
3. **Implement**: Follow tasks in \`heraspec/changes/<change-name>/tasks.md\`
|
|
1137
|
+
4. **Archive**: Run \`heraspec archive <change-name> --yes\` when complete
|
|
1138
|
+
|
|
1139
|
+
### Skills
|
|
1140
|
+
|
|
1141
|
+
Add skills to your project:
|
|
1142
|
+
|
|
1143
|
+
\`\`\`bash
|
|
1144
|
+
# List available skills
|
|
1145
|
+
heraspec skill list
|
|
1146
|
+
|
|
1147
|
+
# Add a skill
|
|
1148
|
+
heraspec skill add ui-ux
|
|
1149
|
+
heraspec skill add unit-test
|
|
1150
|
+
|
|
1151
|
+
# View skill details
|
|
1152
|
+
heraspec skill show ui-ux
|
|
1153
|
+
\`\`\`
|
|
1154
|
+
|
|
1155
|
+
For more information, see the [HeraSpec documentation](https://github.com/your-org/heraspec/docs).
|
|
1156
|
+
|
|
1157
|
+
---
|
|
1158
|
+
|
|
1159
|
+
*This section is automatically updated by \`heraspec init\`. Last updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}*`;
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
// src/core/list.ts
|
|
1164
|
+
import path3 from "path";
|
|
1165
|
+
var ListCommand = class {
|
|
1166
|
+
async execute(targetPath = ".", mode = "changes") {
|
|
1167
|
+
const heraspecPath = path3.join(targetPath, HERASPEC_DIR_NAME);
|
|
1168
|
+
if (mode === "changes") {
|
|
1169
|
+
await this.listChanges(heraspecPath);
|
|
1170
|
+
} else {
|
|
1171
|
+
await this.listSpecs(heraspecPath);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
async listChanges(heraspecPath) {
|
|
1175
|
+
const changesDir = path3.join(heraspecPath, CHANGES_DIR_NAME);
|
|
1176
|
+
try {
|
|
1177
|
+
await FileSystemUtils.stat(changesDir);
|
|
1178
|
+
} catch {
|
|
1179
|
+
console.log('No HeraSpec changes directory found. Run "heraspec init" first.');
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const entries = await FileSystemUtils.readDirectory(changesDir);
|
|
1183
|
+
const changeDirs = [];
|
|
1184
|
+
for (const entry of entries) {
|
|
1185
|
+
const entryPath = path3.join(changesDir, entry);
|
|
1186
|
+
const stats = await FileSystemUtils.stat(entryPath);
|
|
1187
|
+
if (stats.isDirectory() && entry !== ARCHIVES_DIR_NAME) {
|
|
1188
|
+
changeDirs.push(entry);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (changeDirs.length === 0) {
|
|
1192
|
+
console.log("No active changes found.");
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
changeDirs.sort();
|
|
1196
|
+
console.log("\nActive changes:");
|
|
1197
|
+
console.log("\u2500".repeat(50));
|
|
1198
|
+
for (const change of changeDirs) {
|
|
1199
|
+
console.log(` \u2022 ${change}`);
|
|
1200
|
+
}
|
|
1201
|
+
console.log();
|
|
1202
|
+
}
|
|
1203
|
+
async listSpecs(heraspecPath) {
|
|
1204
|
+
const specsDir = path3.join(heraspecPath, SPECS_DIR_NAME);
|
|
1205
|
+
try {
|
|
1206
|
+
await FileSystemUtils.stat(specsDir);
|
|
1207
|
+
} catch {
|
|
1208
|
+
console.log('No HeraSpec specs directory found. Run "heraspec init" first.');
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const specs = await this.findSpecFiles(specsDir, "");
|
|
1212
|
+
if (specs.length === 0) {
|
|
1213
|
+
console.log("No specs found.");
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
specs.sort();
|
|
1217
|
+
console.log("\nSpecs:");
|
|
1218
|
+
console.log("\u2500".repeat(50));
|
|
1219
|
+
for (const spec of specs) {
|
|
1220
|
+
console.log(` \u2022 ${spec}`);
|
|
1221
|
+
}
|
|
1222
|
+
console.log();
|
|
1223
|
+
}
|
|
1224
|
+
async findSpecFiles(dir, prefix) {
|
|
1225
|
+
const specs = [];
|
|
1226
|
+
const entries = await FileSystemUtils.readDirectory(dir);
|
|
1227
|
+
for (const entry of entries) {
|
|
1228
|
+
const entryPath = path3.join(dir, entry);
|
|
1229
|
+
const stats = await FileSystemUtils.stat(entryPath);
|
|
1230
|
+
if (stats.isDirectory()) {
|
|
1231
|
+
const subSpecs = await this.findSpecFiles(
|
|
1232
|
+
entryPath,
|
|
1233
|
+
prefix ? `${prefix}/${entry}` : entry
|
|
1234
|
+
);
|
|
1235
|
+
specs.push(...subSpecs);
|
|
1236
|
+
} else if (entry === "spec.md") {
|
|
1237
|
+
specs.push(prefix || "global");
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return specs;
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
// src/core/archive.ts
|
|
1245
|
+
import path4 from "path";
|
|
1246
|
+
import ora2 from "ora";
|
|
1247
|
+
import chalk2 from "chalk";
|
|
1248
|
+
|
|
1249
|
+
// src/core/parsers/markdown-parser.ts
|
|
1250
|
+
var MarkdownParser = class _MarkdownParser {
|
|
1251
|
+
lines;
|
|
1252
|
+
constructor(content) {
|
|
1253
|
+
this.lines = _MarkdownParser.normalizeContent(content).split("\n");
|
|
1254
|
+
}
|
|
1255
|
+
static normalizeContent(content) {
|
|
1256
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1257
|
+
}
|
|
1258
|
+
parseSpec(name) {
|
|
1259
|
+
const sections = this.parseSections();
|
|
1260
|
+
const meta = this.parseMeta(sections);
|
|
1261
|
+
const purpose = this.findSection(sections, "Purpose")?.content.join("\n").trim() || "";
|
|
1262
|
+
const requirementsSection = this.findSection(sections, "Requirements");
|
|
1263
|
+
if (!purpose) {
|
|
1264
|
+
throw new Error("Spec must have a Purpose section");
|
|
1265
|
+
}
|
|
1266
|
+
if (!requirementsSection) {
|
|
1267
|
+
throw new Error("Spec must have a Requirements section");
|
|
1268
|
+
}
|
|
1269
|
+
const requirements = this.parseRequirements(requirementsSection);
|
|
1270
|
+
return {
|
|
1271
|
+
name,
|
|
1272
|
+
meta,
|
|
1273
|
+
overview: purpose.trim(),
|
|
1274
|
+
requirements,
|
|
1275
|
+
metadata: {
|
|
1276
|
+
version: "1.0.0",
|
|
1277
|
+
format: "heraspec"
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
parseDeltaSpec(content) {
|
|
1282
|
+
const sections = this.parseSections();
|
|
1283
|
+
const added = this.findSection(sections, "ADDED Requirements");
|
|
1284
|
+
const modified = this.findSection(sections, "MODIFIED Requirements");
|
|
1285
|
+
const removed = this.findSection(sections, "REMOVED Requirements");
|
|
1286
|
+
return {
|
|
1287
|
+
added: added ? this.parseDeltaRequirements(added) : [],
|
|
1288
|
+
modified: modified ? this.parseDeltaRequirements(modified) : [],
|
|
1289
|
+
removed: removed ? this.parseDeltaRequirements(removed) : []
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
parseMeta(sections) {
|
|
1293
|
+
const metaSection = this.findSection(sections, "Meta");
|
|
1294
|
+
if (!metaSection) {
|
|
1295
|
+
return void 0;
|
|
1296
|
+
}
|
|
1297
|
+
const meta = {};
|
|
1298
|
+
const content = metaSection.content.join("\n");
|
|
1299
|
+
const projectTypeMatch = content.match(/project type:\s*(.+)/i);
|
|
1300
|
+
if (projectTypeMatch) {
|
|
1301
|
+
const types = projectTypeMatch[1].split("|").map((t) => t.trim());
|
|
1302
|
+
meta.projectType = types.length === 1 ? types[0] : types;
|
|
1303
|
+
}
|
|
1304
|
+
const domainMatch = content.match(/domain:\s*(.+)/i);
|
|
1305
|
+
if (domainMatch) {
|
|
1306
|
+
meta.domain = domainMatch[1].trim();
|
|
1307
|
+
}
|
|
1308
|
+
const stackMatch = content.match(/stack:\s*(.+)/i);
|
|
1309
|
+
if (stackMatch) {
|
|
1310
|
+
const stacks = stackMatch[1].split("|").map((s) => s.trim());
|
|
1311
|
+
meta.stack = stacks.length === 1 ? stacks[0] : stacks;
|
|
1312
|
+
}
|
|
1313
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
1314
|
+
}
|
|
1315
|
+
parseSections() {
|
|
1316
|
+
const sections = [];
|
|
1317
|
+
let currentSection = null;
|
|
1318
|
+
for (const line of this.lines) {
|
|
1319
|
+
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
1320
|
+
if (headerMatch) {
|
|
1321
|
+
if (currentSection) {
|
|
1322
|
+
sections.push(currentSection);
|
|
1323
|
+
}
|
|
1324
|
+
currentSection = {
|
|
1325
|
+
level: headerMatch[1].length,
|
|
1326
|
+
title: headerMatch[2].trim(),
|
|
1327
|
+
content: []
|
|
1328
|
+
};
|
|
1329
|
+
} else if (currentSection) {
|
|
1330
|
+
currentSection.content.push(line);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
if (currentSection) {
|
|
1334
|
+
sections.push(currentSection);
|
|
1335
|
+
}
|
|
1336
|
+
return sections;
|
|
1337
|
+
}
|
|
1338
|
+
findSection(sections, title) {
|
|
1339
|
+
return sections.find(
|
|
1340
|
+
(s) => s.title.toLowerCase() === title.toLowerCase() || s.title.toLowerCase().includes(title.toLowerCase())
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
parseRequirements(section) {
|
|
1344
|
+
const requirements = [];
|
|
1345
|
+
const lines = section.content;
|
|
1346
|
+
let currentRequirement = null;
|
|
1347
|
+
let inRequirement = false;
|
|
1348
|
+
let inScenario = false;
|
|
1349
|
+
let currentScenario = null;
|
|
1350
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1351
|
+
const line = lines[i];
|
|
1352
|
+
const reqMatch = line.match(/^###\s+Requirement:\s*(.+)$/i);
|
|
1353
|
+
if (reqMatch) {
|
|
1354
|
+
if (currentRequirement) {
|
|
1355
|
+
requirements.push(currentRequirement);
|
|
1356
|
+
}
|
|
1357
|
+
currentRequirement = {
|
|
1358
|
+
name: reqMatch[1].trim(),
|
|
1359
|
+
description: "",
|
|
1360
|
+
scenarios: []
|
|
1361
|
+
};
|
|
1362
|
+
inRequirement = true;
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
const scenarioMatch = line.match(/^####\s+Scenario:\s*(.+)$/i);
|
|
1366
|
+
if (scenarioMatch && currentRequirement) {
|
|
1367
|
+
if (currentScenario) {
|
|
1368
|
+
currentRequirement.scenarios.push(currentScenario);
|
|
1369
|
+
}
|
|
1370
|
+
currentScenario = {
|
|
1371
|
+
name: scenarioMatch[1].trim(),
|
|
1372
|
+
steps: []
|
|
1373
|
+
};
|
|
1374
|
+
inScenario = true;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
if (currentScenario && line.match(/^-\s*(GIVEN|WHEN|THEN|AND|BUT)\s+/i)) {
|
|
1378
|
+
const step = line.replace(/^-\s*/, "").trim();
|
|
1379
|
+
currentScenario.steps.push(step);
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
if (currentRequirement && !inScenario) {
|
|
1383
|
+
const trimmed = line.trim();
|
|
1384
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
1385
|
+
currentRequirement.description += (currentRequirement.description ? "\n" : "") + trimmed;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (currentScenario && currentRequirement) {
|
|
1390
|
+
currentRequirement.scenarios.push(currentScenario);
|
|
1391
|
+
}
|
|
1392
|
+
if (currentRequirement) {
|
|
1393
|
+
requirements.push(currentRequirement);
|
|
1394
|
+
}
|
|
1395
|
+
return requirements;
|
|
1396
|
+
}
|
|
1397
|
+
parseDeltaRequirements(section) {
|
|
1398
|
+
return this.parseRequirements(section);
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
// src/core/archive.ts
|
|
1403
|
+
var ArchiveCommand = class {
|
|
1404
|
+
async execute(changeName, options) {
|
|
1405
|
+
if (!changeName) {
|
|
1406
|
+
console.error("Error: Please specify a change name");
|
|
1407
|
+
console.log("Usage: heraspec archive <change-name> [--yes]");
|
|
1408
|
+
process.exitCode = 1;
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
const changePath = path4.join(".", HERASPEC_DIR_NAME, CHANGES_DIR_NAME, changeName);
|
|
1412
|
+
if (!await FileSystemUtils.fileExists(changePath)) {
|
|
1413
|
+
console.error(`Error: Change "${changeName}" not found`);
|
|
1414
|
+
process.exitCode = 1;
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
if (!options?.yes) {
|
|
1418
|
+
console.log(`
|
|
1419
|
+
This will archive "${changeName}" and merge delta specs into source specs.`);
|
|
1420
|
+
console.log("This action cannot be undone.\n");
|
|
1421
|
+
console.error("Error: Please use --yes flag to confirm");
|
|
1422
|
+
process.exitCode = 1;
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const spinner = ora2({
|
|
1426
|
+
text: `Archiving change "${changeName}"...`,
|
|
1427
|
+
color: "blue"
|
|
1428
|
+
}).start();
|
|
1429
|
+
try {
|
|
1430
|
+
await this.mergeDeltaSpecs(changePath, changeName);
|
|
1431
|
+
const specsDir = path4.join(
|
|
1432
|
+
".",
|
|
1433
|
+
HERASPEC_DIR_NAME,
|
|
1434
|
+
SPECS_DIR_NAME,
|
|
1435
|
+
changeName
|
|
1436
|
+
);
|
|
1437
|
+
if (await FileSystemUtils.fileExists(specsDir)) {
|
|
1438
|
+
await FileSystemUtils.removeDirectory(specsDir, true);
|
|
1439
|
+
}
|
|
1440
|
+
const archiveDir = path4.join(
|
|
1441
|
+
".",
|
|
1442
|
+
HERASPEC_DIR_NAME,
|
|
1443
|
+
CHANGES_DIR_NAME,
|
|
1444
|
+
ARCHIVES_DIR_NAME
|
|
1445
|
+
);
|
|
1446
|
+
await FileSystemUtils.createDirectory(archiveDir);
|
|
1447
|
+
const datePrefix = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1448
|
+
const archivePath = path4.join(archiveDir, `${datePrefix}-${changeName}`);
|
|
1449
|
+
await FileSystemUtils.createDirectory(archivePath);
|
|
1450
|
+
await this.moveChangeToArchive(changePath, archivePath);
|
|
1451
|
+
await FileSystemUtils.removeDirectory(changePath, true);
|
|
1452
|
+
spinner.succeed(chalk2.green(`Change "${changeName}" archived successfully`));
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
spinner.fail(chalk2.red(`Error: ${error.message}`));
|
|
1455
|
+
throw error;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
async mergeDeltaSpecs(changePath, changeName) {
|
|
1459
|
+
const deltaSpecsDir = path4.join(
|
|
1460
|
+
".",
|
|
1461
|
+
HERASPEC_DIR_NAME,
|
|
1462
|
+
SPECS_DIR_NAME,
|
|
1463
|
+
changeName
|
|
1464
|
+
);
|
|
1465
|
+
if (!await FileSystemUtils.fileExists(deltaSpecsDir)) {
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
const deltaSpecs = await this.findDeltaSpecFiles(deltaSpecsDir);
|
|
1469
|
+
for (const deltaSpec of deltaSpecs) {
|
|
1470
|
+
const relativePath = path4.relative(deltaSpecsDir, deltaSpec.path);
|
|
1471
|
+
const targetSpecPath = path4.join(
|
|
1472
|
+
".",
|
|
1473
|
+
HERASPEC_DIR_NAME,
|
|
1474
|
+
SPECS_DIR_NAME,
|
|
1475
|
+
relativePath
|
|
1476
|
+
);
|
|
1477
|
+
const deltaContent = await FileSystemUtils.readFile(deltaSpec.path);
|
|
1478
|
+
const parser = new MarkdownParser(deltaContent);
|
|
1479
|
+
const delta = parser.parseDeltaSpec(deltaContent);
|
|
1480
|
+
let targetContent = "";
|
|
1481
|
+
if (await FileSystemUtils.fileExists(targetSpecPath)) {
|
|
1482
|
+
targetContent = await FileSystemUtils.readFile(targetSpecPath);
|
|
1483
|
+
}
|
|
1484
|
+
const mergedContent = this.mergeDeltaIntoSpec(targetContent, delta, deltaSpec.name);
|
|
1485
|
+
await FileSystemUtils.createDirectory(path4.dirname(targetSpecPath));
|
|
1486
|
+
await FileSystemUtils.writeFile(targetSpecPath, mergedContent);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
mergeDeltaIntoSpec(existingContent, delta, specName) {
|
|
1490
|
+
let merged = existingContent || `# Spec: ${specName}
|
|
1491
|
+
|
|
1492
|
+
## Purpose
|
|
1493
|
+
|
|
1494
|
+
## Requirements
|
|
1495
|
+
|
|
1496
|
+
`;
|
|
1497
|
+
if (delta.added.length > 0) {
|
|
1498
|
+
merged += "\n## ADDED Requirements\n\n";
|
|
1499
|
+
for (const req of delta.added) {
|
|
1500
|
+
merged += `### Requirement: ${req.name}
|
|
1501
|
+
${req.description}
|
|
1502
|
+
|
|
1503
|
+
`;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return merged;
|
|
1507
|
+
}
|
|
1508
|
+
async moveChangeToArchive(sourcePath, archivePath) {
|
|
1509
|
+
const entries = await FileSystemUtils.readDirectory(sourcePath);
|
|
1510
|
+
for (const entry of entries) {
|
|
1511
|
+
const sourceEntry = path4.join(sourcePath, entry);
|
|
1512
|
+
const archiveEntry = path4.join(archivePath, entry);
|
|
1513
|
+
const stats = await FileSystemUtils.stat(sourceEntry);
|
|
1514
|
+
if (stats.isDirectory()) {
|
|
1515
|
+
await FileSystemUtils.createDirectory(archiveEntry);
|
|
1516
|
+
await this.moveChangeToArchive(sourceEntry, archiveEntry);
|
|
1517
|
+
} else {
|
|
1518
|
+
await FileSystemUtils.moveFile(sourceEntry, archiveEntry);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async findDeltaSpecFiles(dir, prefix = "") {
|
|
1523
|
+
const specs = [];
|
|
1524
|
+
const entries = await FileSystemUtils.readDirectory(dir);
|
|
1525
|
+
for (const entry of entries) {
|
|
1526
|
+
const entryPath = path4.join(dir, entry);
|
|
1527
|
+
const stats = await FileSystemUtils.stat(entryPath);
|
|
1528
|
+
if (stats.isDirectory()) {
|
|
1529
|
+
const subSpecs = await this.findDeltaSpecFiles(
|
|
1530
|
+
entryPath,
|
|
1531
|
+
prefix ? `${prefix}/${entry}` : entry
|
|
1532
|
+
);
|
|
1533
|
+
specs.push(...subSpecs);
|
|
1534
|
+
} else if (entry.endsWith(".md")) {
|
|
1535
|
+
specs.push({
|
|
1536
|
+
name: prefix || path4.basename(entry, ".md"),
|
|
1537
|
+
path: entryPath
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return specs;
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
|
|
1545
|
+
// src/core/validation/validator.ts
|
|
1546
|
+
import { readFileSync } from "fs";
|
|
1547
|
+
import path5 from "path";
|
|
1548
|
+
var Validator = class {
|
|
1549
|
+
strictMode;
|
|
1550
|
+
constructor(strictMode = false) {
|
|
1551
|
+
this.strictMode = strictMode;
|
|
1552
|
+
}
|
|
1553
|
+
async validateSpec(filePath) {
|
|
1554
|
+
const errors = [];
|
|
1555
|
+
const warnings = [];
|
|
1556
|
+
try {
|
|
1557
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1558
|
+
const parser = new MarkdownParser(content);
|
|
1559
|
+
const specName = this.extractNameFromPath(filePath);
|
|
1560
|
+
const spec = parser.parseSpec(specName);
|
|
1561
|
+
const result = SpecSchema.safeParse(spec);
|
|
1562
|
+
if (!result.success) {
|
|
1563
|
+
result.error.errors.forEach((err) => {
|
|
1564
|
+
errors.push(`${err.path.join(".")}: ${err.message}`);
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
if (spec.requirements.length === 0) {
|
|
1568
|
+
errors.push("Spec must have at least one requirement");
|
|
1569
|
+
}
|
|
1570
|
+
for (const req of spec.requirements) {
|
|
1571
|
+
if (!req.description || req.description.trim().length === 0) {
|
|
1572
|
+
errors.push(`Requirement "${req.name}" must have a description`);
|
|
1573
|
+
}
|
|
1574
|
+
if (!req.scenarios || req.scenarios.length === 0) {
|
|
1575
|
+
warnings.push(`Requirement "${req.name}" has no scenarios`);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return {
|
|
1579
|
+
valid: errors.length === 0,
|
|
1580
|
+
errors,
|
|
1581
|
+
warnings
|
|
1582
|
+
};
|
|
1583
|
+
} catch (error) {
|
|
1584
|
+
return {
|
|
1585
|
+
valid: false,
|
|
1586
|
+
errors: [error instanceof Error ? error.message : "Unknown error"],
|
|
1587
|
+
warnings: []
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async validateChange(changePath) {
|
|
1592
|
+
const errors = [];
|
|
1593
|
+
const warnings = [];
|
|
1594
|
+
const suggestions = [];
|
|
1595
|
+
const proposalPath = path5.join(changePath, "proposal.md");
|
|
1596
|
+
if (!await FileSystemUtils.fileExists(proposalPath)) {
|
|
1597
|
+
errors.push({
|
|
1598
|
+
message: "Change must have a proposal.md file",
|
|
1599
|
+
path: proposalPath,
|
|
1600
|
+
suggestion: `Create proposal.md in ${changePath} with: # Change Proposal: [name]
|
|
1601
|
+
|
|
1602
|
+
## Purpose
|
|
1603
|
+
[Description]
|
|
1604
|
+
|
|
1605
|
+
## Scope
|
|
1606
|
+
[What will change]`,
|
|
1607
|
+
autoFixable: false
|
|
1608
|
+
});
|
|
1609
|
+
suggestions.push(`Create proposal.md file at ${proposalPath}`);
|
|
1610
|
+
}
|
|
1611
|
+
const tasksPath = path5.join(changePath, "tasks.md");
|
|
1612
|
+
if (!await FileSystemUtils.fileExists(tasksPath)) {
|
|
1613
|
+
warnings.push({
|
|
1614
|
+
message: "Change has no tasks.md file",
|
|
1615
|
+
path: tasksPath,
|
|
1616
|
+
suggestion: `Create tasks.md with implementation tasks grouped by project type and skill`,
|
|
1617
|
+
autoFixable: false
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
const changeName = path5.basename(changePath);
|
|
1621
|
+
const specsDir = path5.join(
|
|
1622
|
+
".",
|
|
1623
|
+
HERASPEC_DIR_NAME,
|
|
1624
|
+
SPECS_DIR_NAME,
|
|
1625
|
+
changeName
|
|
1626
|
+
);
|
|
1627
|
+
if (await FileSystemUtils.fileExists(specsDir)) {
|
|
1628
|
+
const deltaSpecs = await this.findDeltaSpecs(specsDir);
|
|
1629
|
+
for (const specPath of deltaSpecs) {
|
|
1630
|
+
const report = await this.validateDeltaSpec(specPath);
|
|
1631
|
+
errors.push(...report.errors);
|
|
1632
|
+
warnings.push(...report.warnings);
|
|
1633
|
+
if (report.suggestions) {
|
|
1634
|
+
suggestions.push(...report.suggestions);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
} else {
|
|
1638
|
+
warnings.push({
|
|
1639
|
+
message: "No delta specs found for this change",
|
|
1640
|
+
suggestion: `Create delta specs in ${specsDir}/spec.md using ADDED/MODIFIED/REMOVED sections`,
|
|
1641
|
+
autoFixable: false
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
return {
|
|
1645
|
+
valid: errors.length === 0,
|
|
1646
|
+
errors,
|
|
1647
|
+
warnings,
|
|
1648
|
+
suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
async validateDeltaSpec(filePath) {
|
|
1652
|
+
const errors = [];
|
|
1653
|
+
const warnings = [];
|
|
1654
|
+
const suggestions = [];
|
|
1655
|
+
try {
|
|
1656
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1657
|
+
const parser = new MarkdownParser(content);
|
|
1658
|
+
const delta = parser.parseDeltaSpec(content);
|
|
1659
|
+
if (delta.added.length === 0 && delta.modified.length === 0 && delta.removed.length === 0) {
|
|
1660
|
+
warnings.push({
|
|
1661
|
+
message: "Delta spec has no changes",
|
|
1662
|
+
suggestion: "Add at least one section: ## ADDED Requirements, ## MODIFIED Requirements, or ## REMOVED Requirements",
|
|
1663
|
+
autoFixable: false
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
if (!content.includes("## ADDED") && !content.includes("## MODIFIED") && !content.includes("## REMOVED")) {
|
|
1667
|
+
warnings.push({
|
|
1668
|
+
message: "Delta spec may not follow proper format",
|
|
1669
|
+
suggestion: "Use sections: ## ADDED Requirements, ## MODIFIED Requirements, ## REMOVED Requirements",
|
|
1670
|
+
autoFixable: false
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
return {
|
|
1674
|
+
valid: errors.length === 0,
|
|
1675
|
+
errors,
|
|
1676
|
+
warnings,
|
|
1677
|
+
suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
1678
|
+
};
|
|
1679
|
+
} catch (error) {
|
|
1680
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1681
|
+
const suggestion = this.getParseErrorSuggestion(errorMessage);
|
|
1682
|
+
return {
|
|
1683
|
+
valid: false,
|
|
1684
|
+
errors: [{
|
|
1685
|
+
message: errorMessage,
|
|
1686
|
+
suggestion,
|
|
1687
|
+
autoFixable: false
|
|
1688
|
+
}],
|
|
1689
|
+
warnings: [],
|
|
1690
|
+
suggestions: suggestion ? [suggestion] : void 0
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
extractNameFromPath(filePath) {
|
|
1695
|
+
const baseName = path5.basename(filePath, ".md");
|
|
1696
|
+
return baseName === "spec" ? path5.basename(path5.dirname(filePath)) : baseName;
|
|
1697
|
+
}
|
|
1698
|
+
async findDeltaSpecs(dir) {
|
|
1699
|
+
const specs = [];
|
|
1700
|
+
const entries = await FileSystemUtils.readDirectory(dir);
|
|
1701
|
+
for (const entry of entries) {
|
|
1702
|
+
const entryPath = path5.join(dir, entry);
|
|
1703
|
+
const stats = await FileSystemUtils.stat(entryPath);
|
|
1704
|
+
if (stats.isDirectory()) {
|
|
1705
|
+
const subSpecs = await this.findDeltaSpecs(entryPath);
|
|
1706
|
+
specs.push(...subSpecs);
|
|
1707
|
+
} else if (entry.endsWith(".md")) {
|
|
1708
|
+
specs.push(entryPath);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return specs;
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
export {
|
|
1715
|
+
ARCHIVES_DIR_NAME,
|
|
1716
|
+
ArchiveCommand,
|
|
1717
|
+
CHANGES_DIR_NAME,
|
|
1718
|
+
ChangeSchema,
|
|
1719
|
+
DeltaRequirementSchema,
|
|
1720
|
+
DeltaTypeSchema,
|
|
1721
|
+
HERASPEC_DIR_NAME,
|
|
1722
|
+
HERASPEC_MARKERS,
|
|
1723
|
+
InitCommand,
|
|
1724
|
+
ListCommand,
|
|
1725
|
+
PROJECT_TYPES,
|
|
1726
|
+
ProjectTypeSchema,
|
|
1727
|
+
RequirementSchema,
|
|
1728
|
+
SKILLS,
|
|
1729
|
+
SKILLS_DIR_NAME,
|
|
1730
|
+
SPECS_DIR_NAME,
|
|
1731
|
+
ScenarioSchema,
|
|
1732
|
+
SpecMetaSchema,
|
|
1733
|
+
SpecSchema,
|
|
1734
|
+
Validator
|
|
1735
|
+
};
|
|
1736
|
+
//# sourceMappingURL=index.js.map
|