claude-autopm 1.27.0 → 1.28.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 +40 -0
- package/autopm/.claude/scripts/pm/prd-new.js +292 -2
- package/autopm/.claude/scripts/pm/template-list.js +119 -0
- package/autopm/.claude/scripts/pm/template-new.js +344 -0
- package/autopm/.claude/templates/prds/README.md +334 -0
- package/autopm/.claude/templates/prds/api-feature.md +306 -0
- package/autopm/.claude/templates/prds/bug-fix.md +413 -0
- package/autopm/.claude/templates/prds/data-migration.md +483 -0
- package/autopm/.claude/templates/prds/documentation.md +439 -0
- package/autopm/.claude/templates/prds/ui-feature.md +365 -0
- package/lib/template-engine.js +347 -0
- package/package.json +1 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: {{id}}
|
|
3
|
+
title: {{title}}
|
|
4
|
+
type: prd
|
|
5
|
+
status: draft
|
|
6
|
+
priority: {{priority}}
|
|
7
|
+
created: {{timestamp}}
|
|
8
|
+
author: {{author}}
|
|
9
|
+
timeline: {{timeline}}
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# PRD: {{title}}
|
|
13
|
+
|
|
14
|
+
## Executive Summary
|
|
15
|
+
|
|
16
|
+
Design and implement {{title}} - a {{component_type}} component for {{feature_purpose}}.
|
|
17
|
+
|
|
18
|
+
**Component Type**: {{component_type}}
|
|
19
|
+
**Target Platform**: {{platform}}
|
|
20
|
+
|
|
21
|
+
## Problem Statement
|
|
22
|
+
|
|
23
|
+
### Background
|
|
24
|
+
{{problem}}
|
|
25
|
+
|
|
26
|
+
### User Need
|
|
27
|
+
Users need to {{user_need}} in order to {{user_goal}}.
|
|
28
|
+
|
|
29
|
+
### Current Pain Points
|
|
30
|
+
{{#if pain_points}}
|
|
31
|
+
{{#each pain_points}}
|
|
32
|
+
- {{this}}
|
|
33
|
+
{{/each}}
|
|
34
|
+
{{/if}}
|
|
35
|
+
|
|
36
|
+
## User Stories (INVEST Criteria)
|
|
37
|
+
|
|
38
|
+
Following INVEST principles (Independent, Negotiable, Valuable, Estimable, Small, Testable):
|
|
39
|
+
|
|
40
|
+
- As a **{{user_role}}**, I want to **{{user_action}}** so that **{{user_benefit}}**
|
|
41
|
+
|
|
42
|
+
{{#if additional_stories}}
|
|
43
|
+
{{#each additional_stories}}
|
|
44
|
+
- As a **{{role}}**, I want to **{{action}}** so that **{{benefit}}**
|
|
45
|
+
{{/each}}
|
|
46
|
+
{{/if}}
|
|
47
|
+
|
|
48
|
+
## UI/UX Requirements
|
|
49
|
+
|
|
50
|
+
### Component Specifications
|
|
51
|
+
- **Type**: {{component_type}} (Page/Modal/Widget/Form/Dashboard)
|
|
52
|
+
- **Location**: {{component_location}}
|
|
53
|
+
- **Interaction Pattern**: {{interaction_pattern}}
|
|
54
|
+
- **Responsive Breakpoints**: Mobile (320px+), Tablet (768px+), Desktop (1024px+)
|
|
55
|
+
|
|
56
|
+
### Design Assets
|
|
57
|
+
- **Wireframes**: {{wireframe_link}} <!-- e.g. https://figma.com/file/xyz or /assets/wireframes/homepage.png -->
|
|
58
|
+
- **Mockups**: {{design_link}} <!-- e.g. https://invisionapp.com/mockup/abc or /assets/mockups/modal.jpg -->
|
|
59
|
+
- **Design System**: {{design_system}} <!-- e.g. https://company.com/design-system or /docs/design-system.md -->
|
|
60
|
+
- **Brand Guidelines**: {{brand_guidelines}} <!-- e.g. https://company.com/brand-guidelines.pdf or /assets/brand-guidelines.pdf -->
|
|
61
|
+
|
|
62
|
+
### Accessibility (WCAG 2.1 Level AA - 2025 Compliance)
|
|
63
|
+
|
|
64
|
+
#### Perceivable
|
|
65
|
+
- [ ] **Text Alternatives**: All non-text content has alt text
|
|
66
|
+
- [ ] **Color Contrast**: Minimum 4.5:1 ratio for normal text, 3:1 for large text
|
|
67
|
+
- [ ] **Resize Text**: Content readable at 200% zoom
|
|
68
|
+
- [ ] **Images of Text**: Avoided unless essential
|
|
69
|
+
|
|
70
|
+
#### Operable
|
|
71
|
+
- [ ] **Keyboard Accessible**: All functionality via keyboard (no mouse required)
|
|
72
|
+
- [ ] **Focus Indicators**: Visible focus states (outline, highlight)
|
|
73
|
+
- [ ] **No Keyboard Trap**: Focus can move away from component
|
|
74
|
+
- [ ] **Skip Links**: Skip to main content option
|
|
75
|
+
- [ ] **Tab Order**: Logical navigation flow
|
|
76
|
+
|
|
77
|
+
#### Understandable
|
|
78
|
+
- [ ] **Clear Labels**: Form inputs have associated labels
|
|
79
|
+
- [ ] **Error Identification**: Errors clearly described
|
|
80
|
+
- [ ] **Consistent Navigation**: UI patterns consistent across app
|
|
81
|
+
- [ ] **Predictable**: Components behave as expected
|
|
82
|
+
|
|
83
|
+
#### Robust
|
|
84
|
+
- [ ] **Valid HTML**: Semantic markup, no errors
|
|
85
|
+
- [ ] **ARIA Labels**: Proper ARIA attributes where needed
|
|
86
|
+
- [ ] **Screen Reader Testing**: NVDA, JAWS, VoiceOver compatible
|
|
87
|
+
- [ ] **Browser Compatibility**: Chrome, Firefox, Safari, Edge
|
|
88
|
+
|
|
89
|
+
### Responsive Design
|
|
90
|
+
|
|
91
|
+
**Mobile First Approach** (320px - 767px):
|
|
92
|
+
- [ ] Touch-friendly targets (min 44x44px)
|
|
93
|
+
- [ ] Single column layout
|
|
94
|
+
- [ ] Simplified navigation
|
|
95
|
+
- [ ] Optimized images
|
|
96
|
+
|
|
97
|
+
**Tablet** (768px - 1023px):
|
|
98
|
+
- [ ] Two column layout where appropriate
|
|
99
|
+
- [ ] Enhanced navigation
|
|
100
|
+
- [ ] Adaptive components
|
|
101
|
+
|
|
102
|
+
**Desktop** (1024px+):
|
|
103
|
+
- [ ] Multi-column layouts
|
|
104
|
+
- [ ] Full feature set
|
|
105
|
+
- [ ] Hover states
|
|
106
|
+
- [ ] Advanced interactions
|
|
107
|
+
|
|
108
|
+
## Technical Requirements
|
|
109
|
+
|
|
110
|
+
### Frontend Stack
|
|
111
|
+
- **Framework**: {{frontend_framework}} (React 18+/Vue 3+/Angular 17+)
|
|
112
|
+
- **State Management**: {{state_management}} (Redux/Zustand/Pinia/NgRx)
|
|
113
|
+
- **Styling**: {{styling_approach}} (CSS-in-JS/Sass/Tailwind/CSS Modules)
|
|
114
|
+
- **Build Tool**: {{build_tool}} (Vite/Webpack 5/Parcel)
|
|
115
|
+
|
|
116
|
+
### Component Architecture
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
{{component_name}}/
|
|
120
|
+
├── index.jsx/tsx # Main component
|
|
121
|
+
├── styles.module.css # Component styles
|
|
122
|
+
├── hooks/
|
|
123
|
+
│ ├── useComponentState.js
|
|
124
|
+
│ └── useComponentEffects.js
|
|
125
|
+
├── utils/
|
|
126
|
+
│ └── helpers.js
|
|
127
|
+
├── types/
|
|
128
|
+
│ └── index.ts # TypeScript definitions
|
|
129
|
+
├── __tests__/
|
|
130
|
+
│ ├── index.test.jsx
|
|
131
|
+
│ ├── integration.test.jsx
|
|
132
|
+
│ └── a11y.test.jsx # Accessibility tests
|
|
133
|
+
└── README.md
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### API Integration
|
|
137
|
+
- **Endpoints**: {{api_endpoints}}
|
|
138
|
+
- **Loading States**:
|
|
139
|
+
- Skeleton loaders for content
|
|
140
|
+
- Spinners for actions
|
|
141
|
+
- Progress indicators for long operations
|
|
142
|
+
- **Error Handling**:
|
|
143
|
+
- Toast notifications
|
|
144
|
+
- Inline error messages
|
|
145
|
+
- Fallback UI components
|
|
146
|
+
- **Caching Strategy**: {{cache_strategy}}
|
|
147
|
+
|
|
148
|
+
### Performance Requirements
|
|
149
|
+
|
|
150
|
+
**Core Web Vitals (2025 Standards)**:
|
|
151
|
+
- [ ] **LCP (Largest Contentful Paint)**: < 2.5s
|
|
152
|
+
- [ ] **FID (First Input Delay)**: < 100ms
|
|
153
|
+
- [ ] **CLS (Cumulative Layout Shift)**: < 0.1
|
|
154
|
+
- [ ] **INP (Interaction to Next Paint)**: < 200ms
|
|
155
|
+
- [ ] **TTFB (Time to First Byte)**: < 600ms
|
|
156
|
+
|
|
157
|
+
**Bundle Size**:
|
|
158
|
+
- [ ] Component bundle: < 50KB (gzipped)
|
|
159
|
+
- [ ] Total page size: < 500KB (initial load)
|
|
160
|
+
- [ ] Code splitting implemented
|
|
161
|
+
- [ ] Lazy loading for routes
|
|
162
|
+
|
|
163
|
+
**Runtime Performance**:
|
|
164
|
+
- [ ] 60 FPS scrolling
|
|
165
|
+
- [ ] Smooth animations (requestAnimationFrame)
|
|
166
|
+
- [ ] Debounced inputs (300ms)
|
|
167
|
+
- [ ] Virtualized lists (for 100+ items)
|
|
168
|
+
|
|
169
|
+
**Lighthouse Scores (Mobile)**:
|
|
170
|
+
- Performance: > 90
|
|
171
|
+
- Accessibility: 100
|
|
172
|
+
- Best Practices: > 95
|
|
173
|
+
- SEO: > 90
|
|
174
|
+
|
|
175
|
+
## Testing Requirements (TDD Approach)
|
|
176
|
+
|
|
177
|
+
### Unit Tests (Jest/Vitest)
|
|
178
|
+
- [ ] Component rendering (all states)
|
|
179
|
+
- [ ] User interactions (click, input, submit)
|
|
180
|
+
- [ ] State management (updates, side effects)
|
|
181
|
+
- [ ] Edge cases (empty states, errors)
|
|
182
|
+
- [ ] Utility functions (pure logic)
|
|
183
|
+
- **Coverage Target**: 100% for new code
|
|
184
|
+
|
|
185
|
+
### Integration Tests
|
|
186
|
+
- [ ] API integration (success/error flows)
|
|
187
|
+
- [ ] Navigation flow (routing)
|
|
188
|
+
- [ ] Form submission (validation, submission)
|
|
189
|
+
- [ ] State persistence (localStorage/sessionStorage)
|
|
190
|
+
|
|
191
|
+
### Accessibility Tests
|
|
192
|
+
- [ ] axe-core automated testing
|
|
193
|
+
- [ ] Keyboard navigation testing
|
|
194
|
+
- [ ] Screen reader testing (NVDA, JAWS, VoiceOver)
|
|
195
|
+
- [ ] Color contrast validation
|
|
196
|
+
- [ ] ARIA attributes validation
|
|
197
|
+
|
|
198
|
+
### E2E Tests (Playwright/Cypress)
|
|
199
|
+
- [ ] User journey (complete flow)
|
|
200
|
+
- [ ] Cross-browser (Chrome, Firefox, Safari, Edge)
|
|
201
|
+
- [ ] Responsive design (mobile, tablet, desktop)
|
|
202
|
+
- [ ] Performance profiling
|
|
203
|
+
- [ ] Visual regression testing
|
|
204
|
+
|
|
205
|
+
### Visual Testing
|
|
206
|
+
- [ ] Chromatic/Percy visual regression
|
|
207
|
+
- [ ] Storybook component documentation
|
|
208
|
+
- [ ] Design system adherence
|
|
209
|
+
|
|
210
|
+
## Internationalization (i18n)
|
|
211
|
+
|
|
212
|
+
{{#if i18n_required}}
|
|
213
|
+
- [ ] Text externalized to language files
|
|
214
|
+
- [ ] RTL (Right-to-Left) support
|
|
215
|
+
- [ ] Date/time localization
|
|
216
|
+
- [ ] Number/currency formatting
|
|
217
|
+
- [ ] Pluralization rules
|
|
218
|
+
- **Languages**: {{supported_languages}}
|
|
219
|
+
{{/if}}
|
|
220
|
+
|
|
221
|
+
## Success Metrics (SMART Goals)
|
|
222
|
+
|
|
223
|
+
- **Usability**: {{usability_score}}/100 SUS (System Usability Scale) score
|
|
224
|
+
- **Adoption**: {{adoption_target}}% user engagement within {{adoption_timeframe}}
|
|
225
|
+
- **Performance**: {{lighthouse_target}}/100 Lighthouse score
|
|
226
|
+
- **Accessibility**: 100% WCAG 2.1 AA compliance
|
|
227
|
+
- **Error Rate**: < {{error_rate}}%
|
|
228
|
+
- **User Satisfaction**: {{nps_target}} NPS score
|
|
229
|
+
|
|
230
|
+
## User Feedback Collection
|
|
231
|
+
|
|
232
|
+
- [ ] Analytics integration (GA4/Mixpanel)
|
|
233
|
+
- [ ] Heatmap tracking (Hotjar/FullStory)
|
|
234
|
+
- [ ] User surveys (post-interaction)
|
|
235
|
+
- [ ] A/B testing framework
|
|
236
|
+
- [ ] Session replay analysis
|
|
237
|
+
|
|
238
|
+
## Implementation Plan
|
|
239
|
+
|
|
240
|
+
### Phase 1: Design & Setup (Week 1)
|
|
241
|
+
- [ ] UI/UX review and approval
|
|
242
|
+
- [ ] Component architecture design
|
|
243
|
+
- [ ] API contracts finalized
|
|
244
|
+
- [ ] Accessibility audit plan
|
|
245
|
+
- [ ] Development environment setup
|
|
246
|
+
|
|
247
|
+
### Phase 2: Development (Week 2-3)
|
|
248
|
+
- [ ] Write failing tests (TDD Red)
|
|
249
|
+
- [ ] Implement component logic (TDD Green)
|
|
250
|
+
- [ ] Apply styling and responsive design
|
|
251
|
+
- [ ] Accessibility implementation
|
|
252
|
+
- [ ] Code review and refactoring
|
|
253
|
+
|
|
254
|
+
### Phase 3: Testing (Week 4)
|
|
255
|
+
- [ ] Unit test completion (100% coverage)
|
|
256
|
+
- [ ] Integration testing
|
|
257
|
+
- [ ] Accessibility testing (manual + automated)
|
|
258
|
+
- [ ] Cross-browser testing
|
|
259
|
+
- [ ] Performance optimization
|
|
260
|
+
- [ ] Visual regression testing
|
|
261
|
+
|
|
262
|
+
### Phase 4: Release (Week 5)
|
|
263
|
+
- [ ] Staging deployment
|
|
264
|
+
- [ ] QA validation
|
|
265
|
+
- [ ] Usability testing
|
|
266
|
+
- [ ] Documentation finalization
|
|
267
|
+
- [ ] Production deployment
|
|
268
|
+
- [ ] Monitoring setup
|
|
269
|
+
- [ ] User feedback collection
|
|
270
|
+
|
|
271
|
+
## Browser & Device Support
|
|
272
|
+
|
|
273
|
+
### Desktop Browsers
|
|
274
|
+
- Chrome (latest 2 versions)
|
|
275
|
+
- Firefox (latest 2 versions)
|
|
276
|
+
- Safari (latest 2 versions)
|
|
277
|
+
- Edge (latest 2 versions)
|
|
278
|
+
|
|
279
|
+
### Mobile Browsers
|
|
280
|
+
- iOS Safari (latest 2 versions)
|
|
281
|
+
- Chrome Mobile (latest version)
|
|
282
|
+
- Samsung Internet (latest version)
|
|
283
|
+
|
|
284
|
+
### Device Testing
|
|
285
|
+
- [ ] iPhone (12, 13, 14, 15)
|
|
286
|
+
- [ ] iPad (9th gen, Pro)
|
|
287
|
+
- [ ] Android phones (Samsung, Pixel)
|
|
288
|
+
- [ ] Desktop (1920x1080, 2560x1440)
|
|
289
|
+
|
|
290
|
+
## Monitoring & Analytics
|
|
291
|
+
|
|
292
|
+
### Performance Monitoring
|
|
293
|
+
- Real User Monitoring (RUM)
|
|
294
|
+
- Core Web Vitals tracking
|
|
295
|
+
- Error tracking (Sentry/Rollbar)
|
|
296
|
+
- Bundle size monitoring
|
|
297
|
+
|
|
298
|
+
### User Analytics
|
|
299
|
+
- User flow tracking
|
|
300
|
+
- Feature usage metrics
|
|
301
|
+
- Conversion funnels
|
|
302
|
+
- Drop-off points
|
|
303
|
+
|
|
304
|
+
### Alerts
|
|
305
|
+
- Performance degradation (LCP > 3s)
|
|
306
|
+
- Error rate spike (> {{error_threshold}}%)
|
|
307
|
+
- Accessibility violations
|
|
308
|
+
- Browser compatibility issues
|
|
309
|
+
|
|
310
|
+
## Rollback Plan
|
|
311
|
+
|
|
312
|
+
### Rollback Triggers
|
|
313
|
+
- Accessibility compliance failure
|
|
314
|
+
- Critical UI bug affecting {{user_percentage}}% users
|
|
315
|
+
- Performance degradation > {{perf_degradation}}%
|
|
316
|
+
- Cross-browser compatibility issues
|
|
317
|
+
|
|
318
|
+
### Rollback Procedure
|
|
319
|
+
1. Feature flag toggle (immediate disable)
|
|
320
|
+
2. Revert deployment to previous version
|
|
321
|
+
3. Notify affected users
|
|
322
|
+
4. Root cause analysis
|
|
323
|
+
5. Fix and re-deploy
|
|
324
|
+
|
|
325
|
+
## Risks and Mitigation
|
|
326
|
+
|
|
327
|
+
### Technical Risks
|
|
328
|
+
| Risk | Impact | Probability | Mitigation |
|
|
329
|
+
|------|--------|-------------|------------|
|
|
330
|
+
| Browser compatibility issues | High | Medium | Cross-browser testing early |
|
|
331
|
+
| Performance degradation | High | Low | Performance budgets, monitoring |
|
|
332
|
+
| Accessibility violations | Critical | Low | Automated + manual testing |
|
|
333
|
+
|
|
334
|
+
### UX Risks
|
|
335
|
+
| Risk | Impact | Probability | Mitigation |
|
|
336
|
+
|------|--------|-------------|------------|
|
|
337
|
+
| Poor usability | High | Medium | User testing, feedback loops |
|
|
338
|
+
| Confusing navigation | Medium | Medium | A/B testing, analytics |
|
|
339
|
+
|
|
340
|
+
## Open Questions
|
|
341
|
+
|
|
342
|
+
- [ ] {{question_1}}
|
|
343
|
+
- [ ] {{question_2}}
|
|
344
|
+
- [ ] {{question_3}}
|
|
345
|
+
|
|
346
|
+
## Appendix
|
|
347
|
+
|
|
348
|
+
### Design Resources
|
|
349
|
+
- Design system: {{design_system_link}}
|
|
350
|
+
- Component library: {{component_library_link}}
|
|
351
|
+
- Icon library: {{icon_library}}
|
|
352
|
+
|
|
353
|
+
### References
|
|
354
|
+
- [WCAG 2.1 Guidelines](https://www.w3.org/TR/WCAG21/)
|
|
355
|
+
- [Web Vitals](https://web.dev/vitals/)
|
|
356
|
+
- [Accessibility Testing Guide](https://www.w3.org/WAI/test-evaluate/)
|
|
357
|
+
- [React Best Practices 2025](https://react.dev/learn)
|
|
358
|
+
|
|
359
|
+
### Changelog
|
|
360
|
+
- {{timestamp}}: Initial PRD created by {{author}}
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
*UI Feature PRD - Generated from template: ui-feature*
|
|
365
|
+
*Template follows 2025 best practices: WCAG 2.1 AA compliance, Core Web Vitals, TDD methodology, Mobile-first design*
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Engine - Pure Node.js Implementation
|
|
3
|
+
*
|
|
4
|
+
* NO external dependencies - uses only built-in Node.js modules
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Variable substitution: {{variable}}
|
|
8
|
+
* - Conditionals: {{#if variable}}...{{/if}}
|
|
9
|
+
* - Loops: {{#each items}}...{{/each}}
|
|
10
|
+
* - Auto-generated variables: id, timestamp, author, date
|
|
11
|
+
* - Template discovery: user custom overrides built-in
|
|
12
|
+
*
|
|
13
|
+
* @example Basic Usage
|
|
14
|
+
* ```javascript
|
|
15
|
+
* const TemplateEngine = require('./lib/template-engine');
|
|
16
|
+
* const engine = new TemplateEngine();
|
|
17
|
+
*
|
|
18
|
+
* // Find template
|
|
19
|
+
* const templatePath = engine.findTemplate('prds', 'api-feature');
|
|
20
|
+
*
|
|
21
|
+
* // Render with variables
|
|
22
|
+
* const rendered = engine.renderFile(templatePath, {
|
|
23
|
+
* title: 'User Authentication API',
|
|
24
|
+
* priority: 'P0',
|
|
25
|
+
* problem: 'Users cannot login securely'
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example Advanced Features
|
|
30
|
+
* ```javascript
|
|
31
|
+
* // Template with conditionals and loops
|
|
32
|
+
* const template = `
|
|
33
|
+
* # {{title}}
|
|
34
|
+
*
|
|
35
|
+
* {{#if description}}
|
|
36
|
+
* ## Description
|
|
37
|
+
* {{description}}
|
|
38
|
+
* {{/if}}
|
|
39
|
+
*
|
|
40
|
+
* ## Features
|
|
41
|
+
* {{#each features}}
|
|
42
|
+
* - {{this}}
|
|
43
|
+
* {{/each}}
|
|
44
|
+
* `;
|
|
45
|
+
*
|
|
46
|
+
* const result = engine.render(template, {
|
|
47
|
+
* title: 'My Feature',
|
|
48
|
+
* description: 'Feature description',
|
|
49
|
+
* features: ['Auth', 'API', 'UI']
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @module TemplateEngine
|
|
54
|
+
* @version 1.0.0
|
|
55
|
+
* @since v1.28.0
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
const fs = require('fs');
|
|
59
|
+
const path = require('path');
|
|
60
|
+
|
|
61
|
+
class TemplateEngine {
|
|
62
|
+
constructor(builtInDir, userDir) {
|
|
63
|
+
// For testing, allow custom built-in directory and user directory
|
|
64
|
+
// In production, use autopm/.claude/templates and .claude/templates
|
|
65
|
+
this.builtInDir = builtInDir || path.join(__dirname, '..', 'autopm', '.claude', 'templates');
|
|
66
|
+
this.userDir = userDir || path.join('.claude', 'templates');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Find template by name
|
|
71
|
+
* Priority: user templates > built-in templates
|
|
72
|
+
*
|
|
73
|
+
* @param {string} type - Template type (prds/epics/tasks)
|
|
74
|
+
* @param {string} name - Template name (without .md extension)
|
|
75
|
+
* @returns {string|null} - Path to template or null if not found
|
|
76
|
+
*/
|
|
77
|
+
findTemplate(type, name) {
|
|
78
|
+
const userPath = path.resolve(this.userDir, type, `${name}.md`);
|
|
79
|
+
const builtInPath = path.resolve(this.builtInDir, type, `${name}.md`);
|
|
80
|
+
|
|
81
|
+
if (fs.existsSync(userPath)) return userPath;
|
|
82
|
+
if (fs.existsSync(builtInPath)) return builtInPath;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* List all available templates of a given type
|
|
88
|
+
*
|
|
89
|
+
* @param {string} type - Template type (prds/epics/tasks)
|
|
90
|
+
* @returns {Array<{name: string, custom: boolean}>}
|
|
91
|
+
*/
|
|
92
|
+
listTemplates(type) {
|
|
93
|
+
const templates = [];
|
|
94
|
+
|
|
95
|
+
// Built-in templates
|
|
96
|
+
const builtInPath = path.join(this.builtInDir, type);
|
|
97
|
+
if (fs.existsSync(builtInPath)) {
|
|
98
|
+
const files = fs.readdirSync(builtInPath)
|
|
99
|
+
.filter(f => f.endsWith('.md'))
|
|
100
|
+
.map(f => ({ name: f.replace('.md', ''), custom: false }));
|
|
101
|
+
templates.push(...files);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// User custom templates
|
|
105
|
+
const userPath = path.join(this.userDir, type);
|
|
106
|
+
if (fs.existsSync(userPath)) {
|
|
107
|
+
const files = fs.readdirSync(userPath)
|
|
108
|
+
.filter(f => f.endsWith('.md'))
|
|
109
|
+
.map(f => ({ name: f.replace('.md', ''), custom: true }));
|
|
110
|
+
templates.push(...files);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return templates;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate auto variables
|
|
118
|
+
*
|
|
119
|
+
* @returns {Object} - Auto-generated variables
|
|
120
|
+
*/
|
|
121
|
+
generateAutoVariables() {
|
|
122
|
+
const now = new Date();
|
|
123
|
+
return {
|
|
124
|
+
id: '', // Will be set by generateId()
|
|
125
|
+
timestamp: now.toISOString(),
|
|
126
|
+
date: now.toISOString().split('T')[0],
|
|
127
|
+
author: process.env.USER || process.env.USERNAME || 'unknown'
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate sequential ID
|
|
133
|
+
*
|
|
134
|
+
* @param {string} prefix - ID prefix (prd/epic/task)
|
|
135
|
+
* @param {string} directory - Directory to scan for existing IDs
|
|
136
|
+
* @returns {string} - Next sequential ID
|
|
137
|
+
*/
|
|
138
|
+
generateId(prefix, directory) {
|
|
139
|
+
if (!fs.existsSync(directory)) {
|
|
140
|
+
return `${prefix}-001`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const files = fs.readdirSync(directory)
|
|
144
|
+
.filter(f => f.startsWith(`${prefix}-`) && f.endsWith('.md'));
|
|
145
|
+
|
|
146
|
+
if (files.length === 0) {
|
|
147
|
+
return `${prefix}-001`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Extract numbers and find max
|
|
151
|
+
const numbers = files
|
|
152
|
+
.map(f => {
|
|
153
|
+
const match = f.match(new RegExp(`${prefix}-(\\d+)\\.md`));
|
|
154
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
155
|
+
})
|
|
156
|
+
.filter(n => !isNaN(n));
|
|
157
|
+
|
|
158
|
+
const maxNum = Math.max(...numbers);
|
|
159
|
+
const nextNum = maxNum + 1;
|
|
160
|
+
|
|
161
|
+
return `${prefix}-${String(nextNum).padStart(3, '0')}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Render template with variables
|
|
166
|
+
*
|
|
167
|
+
* @param {string} template - Template string
|
|
168
|
+
* @param {Object} variables - Variables to substitute
|
|
169
|
+
* @returns {string} - Rendered template
|
|
170
|
+
*/
|
|
171
|
+
render(template, variables) {
|
|
172
|
+
// Auto-generate variables
|
|
173
|
+
const autoVars = this.generateAutoVariables();
|
|
174
|
+
|
|
175
|
+
// Generate ID if not provided
|
|
176
|
+
if (!variables.id && !autoVars.id) {
|
|
177
|
+
// Try to infer type and directory from variables
|
|
178
|
+
const type = variables.type || 'item';
|
|
179
|
+
const directory = `.claude/${type}s`;
|
|
180
|
+
autoVars.id = this.generateId(type, directory);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const allVars = { ...autoVars, ...variables };
|
|
184
|
+
|
|
185
|
+
let content = template;
|
|
186
|
+
|
|
187
|
+
// Process loops first ({{#each}}...{{/each}})
|
|
188
|
+
content = this.processLoops(content, allVars);
|
|
189
|
+
|
|
190
|
+
// Process conditionals ({{#if}}...{{/if}})
|
|
191
|
+
content = this.processConditionals(content, allVars);
|
|
192
|
+
|
|
193
|
+
// Simple variable substitution ({{variable}})
|
|
194
|
+
for (const [key, value] of Object.entries(allVars)) {
|
|
195
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
196
|
+
content = content.replace(regex, value || '');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Replace any remaining {{variables}} that weren't in allVars with empty string
|
|
200
|
+
content = content.replace(/\{\{(\w+)\}\}/g, '');
|
|
201
|
+
|
|
202
|
+
return content;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Render template from file
|
|
207
|
+
*
|
|
208
|
+
* @param {string} templatePath - Path to template file
|
|
209
|
+
* @param {Object} variables - Variables to substitute
|
|
210
|
+
* @returns {string} - Rendered template
|
|
211
|
+
*/
|
|
212
|
+
renderFile(templatePath, variables) {
|
|
213
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
214
|
+
return this.render(template, variables);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Process conditionals {{#if var}}...{{/if}}
|
|
219
|
+
*
|
|
220
|
+
* @param {string} content - Template content
|
|
221
|
+
* @param {Object} vars - Variables
|
|
222
|
+
* @returns {string} - Processed content
|
|
223
|
+
*/
|
|
224
|
+
processConditionals(content, vars) {
|
|
225
|
+
// Match {{#if variable}}...{{/if}} - process from inside out for nested conditionals
|
|
226
|
+
const ifRegex = /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/;
|
|
227
|
+
|
|
228
|
+
let result = content;
|
|
229
|
+
let changed = true;
|
|
230
|
+
let iterations = 0;
|
|
231
|
+
const maxIterations = 100;
|
|
232
|
+
|
|
233
|
+
// Keep processing until no more conditionals found (handles nesting)
|
|
234
|
+
while (changed && iterations < maxIterations) {
|
|
235
|
+
iterations++;
|
|
236
|
+
changed = false;
|
|
237
|
+
|
|
238
|
+
const match = result.match(ifRegex);
|
|
239
|
+
if (match) {
|
|
240
|
+
const [fullMatch, varName, innerContent] = match;
|
|
241
|
+
const varValue = vars[varName];
|
|
242
|
+
|
|
243
|
+
// If variable is truthy, keep inner content; otherwise remove
|
|
244
|
+
const replacement = varValue ? innerContent : '';
|
|
245
|
+
result = result.replace(fullMatch, replacement);
|
|
246
|
+
changed = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Process loops {{#each items}}...{{/each}}
|
|
255
|
+
*
|
|
256
|
+
* @param {string} content - Template content
|
|
257
|
+
* @param {Object} vars - Variables
|
|
258
|
+
* @returns {string} - Processed content
|
|
259
|
+
*/
|
|
260
|
+
processLoops(content, vars) {
|
|
261
|
+
// Match {{#each variable}}...{{/each}}
|
|
262
|
+
// Limit inner content to 10,000 characters to prevent ReDoS
|
|
263
|
+
const eachRegex = /\{\{#each\s+(\w+)\}\}([\s\S]{0,10000}?)\{\{\/each\}\}/g;
|
|
264
|
+
|
|
265
|
+
let result = content;
|
|
266
|
+
let match;
|
|
267
|
+
|
|
268
|
+
// Safety counter
|
|
269
|
+
let iterations = 0;
|
|
270
|
+
const maxIterations = 100;
|
|
271
|
+
|
|
272
|
+
while ((match = eachRegex.exec(content)) !== null && iterations < maxIterations) {
|
|
273
|
+
iterations++;
|
|
274
|
+
const [fullMatch, varName, template] = match;
|
|
275
|
+
const items = vars[varName];
|
|
276
|
+
|
|
277
|
+
if (!Array.isArray(items)) {
|
|
278
|
+
// Not an array, remove the loop
|
|
279
|
+
result = result.replace(fullMatch, '');
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Render each item
|
|
284
|
+
let rendered = '';
|
|
285
|
+
for (const item of items) {
|
|
286
|
+
if (typeof item === 'object' && item !== null) {
|
|
287
|
+
// Object: replace {{property}} with object properties
|
|
288
|
+
let itemRendered = template;
|
|
289
|
+
for (const [key, value] of Object.entries(item)) {
|
|
290
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
291
|
+
itemRendered = itemRendered.replace(regex, value || '');
|
|
292
|
+
}
|
|
293
|
+
rendered += itemRendered;
|
|
294
|
+
} else {
|
|
295
|
+
// Primitive: replace {{this}} with the value
|
|
296
|
+
rendered += template.replace(/\{\{this\}\}/g, item);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
result = result.replace(fullMatch, rendered);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Validate template
|
|
308
|
+
*
|
|
309
|
+
* @param {string} template - Template string
|
|
310
|
+
* @returns {Object} - {valid: boolean, errors: string[]}
|
|
311
|
+
*/
|
|
312
|
+
validate(template) {
|
|
313
|
+
const errors = [];
|
|
314
|
+
|
|
315
|
+
// Check frontmatter
|
|
316
|
+
if (!template.startsWith('---')) {
|
|
317
|
+
errors.push('Missing frontmatter');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Check required variables
|
|
321
|
+
const requiredVars = ['id', 'title', 'type'];
|
|
322
|
+
for (const varName of requiredVars) {
|
|
323
|
+
if (!template.includes(`{{${varName}}}`)) {
|
|
324
|
+
errors.push(`Missing required variable: {{${varName}}}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
valid: errors.length === 0,
|
|
330
|
+
errors
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Ensure template directory exists
|
|
336
|
+
*
|
|
337
|
+
* @param {string} type - Template type (prds/epics/tasks)
|
|
338
|
+
*/
|
|
339
|
+
ensureTemplateDir(type) {
|
|
340
|
+
const dir = path.join(this.userDir, type);
|
|
341
|
+
if (!fs.existsSync(dir)) {
|
|
342
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = TemplateEngine;
|