@dzhechkov/skills-idea2prd 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 +21 -0
- package/README.md +71 -0
- package/bin/cli.js +5 -0
- package/package.json +49 -0
- package/sources.json +25 -0
- package/src/cli.js +108 -0
- package/src/commands/doctor.js +340 -0
- package/src/commands/init.js +168 -0
- package/src/commands/list.js +146 -0
- package/src/commands/remove.js +182 -0
- package/src/commands/update.js +170 -0
- package/src/utils.js +154 -0
- package/templates/.claude/commands/idea2prd-manual.md +35 -0
- package/templates/.claude/skills/explore/SKILL.md +218 -0
- package/templates/.claude/skills/explore/references/questioning-techniques.md +151 -0
- package/templates/.claude/skills/explore/references/task-brief-templates.md +355 -0
- package/templates/.claude/skills/goap-research-ed25519/SKILL.md +418 -0
- package/templates/.claude/skills/goap-research-ed25519/references/ed25519-verification.md +658 -0
- package/templates/.claude/skills/goap-research-ed25519/references/research-actions.md +544 -0
- package/templates/.claude/skills/goap-research-ed25519/references/source-evaluation.md +560 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +662 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +720 -0
- package/templates/.claude/skills/idea2prd-manual/SKILL.md +695 -0
- package/templates/.claude/skills/idea2prd-manual/references/adr-catalog.md +288 -0
- package/templates/.claude/skills/idea2prd-manual/references/c4-model.md +277 -0
- package/templates/.claude/skills/idea2prd-manual/references/completion-checklist-template.md +446 -0
- package/templates/.claude/skills/idea2prd-manual/references/ddd-patterns.md +261 -0
- package/templates/.claude/skills/idea2prd-manual/references/fitness-functions-catalog.md +414 -0
- package/templates/.claude/skills/idea2prd-manual/references/pseudocode-style.md +404 -0
- package/templates/.claude/skills/idea2prd-manual/scripts/ai_context_builder.py +491 -0
- package/templates/.claude/skills/idea2prd-manual/scripts/c4_generator.py +311 -0
- package/templates/.claude/skills/idea2prd-manual/scripts/fitness_validator.py +451 -0
- package/templates/.claude/skills/idea2prd-manual/scripts/pseudocode_generator.py +430 -0
- package/templates/.claude/skills/problem-solver-enhanced/SKILL.md +565 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
pseudocode_generator.py - Генератор pseudocode для агрегатов и сервисов
|
|
4
|
+
|
|
5
|
+
Используется в Phase 4.5 для создания pseudocode файлов на основе
|
|
6
|
+
DDD Tactical design (Phase 4).
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python pseudocode_generator.py --input tactical.json --output docs/pseudocode/
|
|
10
|
+
|
|
11
|
+
Input Format (tactical.json):
|
|
12
|
+
{
|
|
13
|
+
"aggregates": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Order",
|
|
16
|
+
"boundedContext": "OrderManagement",
|
|
17
|
+
"state": [
|
|
18
|
+
{"name": "id", "type": "OrderId"},
|
|
19
|
+
{"name": "status", "type": "OrderStatus"}
|
|
20
|
+
],
|
|
21
|
+
"methods": [
|
|
22
|
+
{
|
|
23
|
+
"name": "placeOrder",
|
|
24
|
+
"type": "command",
|
|
25
|
+
"params": [
|
|
26
|
+
{"name": "items", "type": "List<OrderItem>"},
|
|
27
|
+
{"name": "customer", "type": "Customer"}
|
|
28
|
+
],
|
|
29
|
+
"returns": "OrderId",
|
|
30
|
+
"description": "Place a new order",
|
|
31
|
+
"businessRules": [
|
|
32
|
+
"Items cannot be empty",
|
|
33
|
+
"Customer must be verified",
|
|
34
|
+
"All items must be in stock"
|
|
35
|
+
],
|
|
36
|
+
"emits": ["OrderPlacedEvent"]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"services": [
|
|
42
|
+
{
|
|
43
|
+
"name": "PricingService",
|
|
44
|
+
"boundedContext": "Pricing",
|
|
45
|
+
"methods": [...]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
import json
|
|
52
|
+
import argparse
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
from typing import Dict, List, Any
|
|
55
|
+
from dataclasses import dataclass
|
|
56
|
+
from datetime import datetime
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class MethodParam:
|
|
61
|
+
name: str
|
|
62
|
+
type: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Method:
|
|
67
|
+
name: str
|
|
68
|
+
type: str # command, query, factory
|
|
69
|
+
params: List[MethodParam]
|
|
70
|
+
returns: str
|
|
71
|
+
description: str
|
|
72
|
+
business_rules: List[str]
|
|
73
|
+
emits: List[str]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class StateField:
|
|
78
|
+
name: str
|
|
79
|
+
type: str
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class Aggregate:
|
|
84
|
+
name: str
|
|
85
|
+
bounded_context: str
|
|
86
|
+
state: List[StateField]
|
|
87
|
+
methods: List[Method]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class Service:
|
|
92
|
+
name: str
|
|
93
|
+
bounded_context: str
|
|
94
|
+
methods: List[Method]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class PseudocodeGenerator:
|
|
98
|
+
"""Generates pseudocode from DDD tactical design."""
|
|
99
|
+
|
|
100
|
+
TEMPLATE_AGGREGATE = '''// File: {filename}
|
|
101
|
+
// Generated: {timestamp}
|
|
102
|
+
// Bounded Context: {bounded_context}
|
|
103
|
+
|
|
104
|
+
AGGREGATE {name}
|
|
105
|
+
|
|
106
|
+
STATE:
|
|
107
|
+
{state_fields}
|
|
108
|
+
END STATE
|
|
109
|
+
|
|
110
|
+
{methods}
|
|
111
|
+
END AGGREGATE
|
|
112
|
+
'''
|
|
113
|
+
|
|
114
|
+
TEMPLATE_SERVICE = '''// File: {filename}
|
|
115
|
+
// Generated: {timestamp}
|
|
116
|
+
// Bounded Context: {bounded_context}
|
|
117
|
+
|
|
118
|
+
SERVICE {name}
|
|
119
|
+
|
|
120
|
+
{methods}
|
|
121
|
+
END SERVICE
|
|
122
|
+
'''
|
|
123
|
+
|
|
124
|
+
TEMPLATE_METHOD = ''' //========================================
|
|
125
|
+
// {method_type}: {description}
|
|
126
|
+
//========================================
|
|
127
|
+
FUNCTION {name}({params}) -> {returns}:
|
|
128
|
+
|
|
129
|
+
// Pre-conditions
|
|
130
|
+
{validations}
|
|
131
|
+
|
|
132
|
+
// Main logic
|
|
133
|
+
{logic}
|
|
134
|
+
|
|
135
|
+
// Post-conditions
|
|
136
|
+
{postconditions}
|
|
137
|
+
{events}
|
|
138
|
+
RETURN {return_value}
|
|
139
|
+
END FUNCTION
|
|
140
|
+
'''
|
|
141
|
+
|
|
142
|
+
def __init__(self, output_dir: Path):
|
|
143
|
+
self.output_dir = output_dir
|
|
144
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
|
|
146
|
+
def generate_from_json(self, input_file: Path) -> Dict[str, str]:
|
|
147
|
+
"""Generate pseudocode files from JSON input."""
|
|
148
|
+
with open(input_file, 'r', encoding='utf-8') as f:
|
|
149
|
+
data = json.load(f)
|
|
150
|
+
|
|
151
|
+
generated_files = {}
|
|
152
|
+
|
|
153
|
+
# Process aggregates
|
|
154
|
+
for agg_data in data.get('aggregates', []):
|
|
155
|
+
aggregate = self._parse_aggregate(agg_data)
|
|
156
|
+
filename = f"{aggregate.name}Aggregate.pseudo"
|
|
157
|
+
content = self._generate_aggregate(aggregate)
|
|
158
|
+
self._write_file(filename, content)
|
|
159
|
+
generated_files[filename] = content
|
|
160
|
+
|
|
161
|
+
# Process services
|
|
162
|
+
for svc_data in data.get('services', []):
|
|
163
|
+
service = self._parse_service(svc_data)
|
|
164
|
+
filename = f"{service.name}.pseudo"
|
|
165
|
+
content = self._generate_service(service)
|
|
166
|
+
self._write_file(filename, content)
|
|
167
|
+
generated_files[filename] = content
|
|
168
|
+
|
|
169
|
+
# Generate index
|
|
170
|
+
index_content = self._generate_index(generated_files)
|
|
171
|
+
self._write_file('INDEX.md', index_content)
|
|
172
|
+
generated_files['INDEX.md'] = index_content
|
|
173
|
+
|
|
174
|
+
return generated_files
|
|
175
|
+
|
|
176
|
+
def _parse_aggregate(self, data: Dict) -> Aggregate:
|
|
177
|
+
"""Parse aggregate from JSON."""
|
|
178
|
+
return Aggregate(
|
|
179
|
+
name=data['name'],
|
|
180
|
+
bounded_context=data.get('boundedContext', 'Unknown'),
|
|
181
|
+
state=[StateField(s['name'], s['type']) for s in data.get('state', [])],
|
|
182
|
+
methods=[self._parse_method(m) for m in data.get('methods', [])]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _parse_service(self, data: Dict) -> Service:
|
|
186
|
+
"""Parse service from JSON."""
|
|
187
|
+
return Service(
|
|
188
|
+
name=data['name'],
|
|
189
|
+
bounded_context=data.get('boundedContext', 'Unknown'),
|
|
190
|
+
methods=[self._parse_method(m) for m in data.get('methods', [])]
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def _parse_method(self, data: Dict) -> Method:
|
|
194
|
+
"""Parse method from JSON."""
|
|
195
|
+
return Method(
|
|
196
|
+
name=data['name'],
|
|
197
|
+
type=data.get('type', 'command'),
|
|
198
|
+
params=[MethodParam(p['name'], p['type']) for p in data.get('params', [])],
|
|
199
|
+
returns=data.get('returns', 'void'),
|
|
200
|
+
description=data.get('description', ''),
|
|
201
|
+
business_rules=data.get('businessRules', []),
|
|
202
|
+
emits=data.get('emits', [])
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def _generate_aggregate(self, aggregate: Aggregate) -> str:
|
|
206
|
+
"""Generate pseudocode for aggregate."""
|
|
207
|
+
state_fields = '\n'.join(
|
|
208
|
+
f" {f.name}: {f.type}"
|
|
209
|
+
for f in aggregate.state
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
methods = '\n'.join(
|
|
213
|
+
self._generate_method(m)
|
|
214
|
+
for m in aggregate.methods
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return self.TEMPLATE_AGGREGATE.format(
|
|
218
|
+
filename=f"{aggregate.name}Aggregate.pseudo",
|
|
219
|
+
timestamp=datetime.now().isoformat(),
|
|
220
|
+
bounded_context=aggregate.bounded_context,
|
|
221
|
+
name=aggregate.name,
|
|
222
|
+
state_fields=state_fields,
|
|
223
|
+
methods=methods
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def _generate_service(self, service: Service) -> str:
|
|
227
|
+
"""Generate pseudocode for service."""
|
|
228
|
+
methods = '\n'.join(
|
|
229
|
+
self._generate_method(m)
|
|
230
|
+
for m in service.methods
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return self.TEMPLATE_SERVICE.format(
|
|
234
|
+
filename=f"{service.name}.pseudo",
|
|
235
|
+
timestamp=datetime.now().isoformat(),
|
|
236
|
+
bounded_context=service.bounded_context,
|
|
237
|
+
name=service.name,
|
|
238
|
+
methods=methods
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def _generate_method(self, method: Method) -> str:
|
|
242
|
+
"""Generate pseudocode for a single method."""
|
|
243
|
+
# Format parameters
|
|
244
|
+
params = ', '.join(f"{p.name}: {p.type}" for p in method.params)
|
|
245
|
+
|
|
246
|
+
# Generate validations from business rules
|
|
247
|
+
validations = self._generate_validations(method.business_rules)
|
|
248
|
+
|
|
249
|
+
# Generate placeholder logic
|
|
250
|
+
logic = self._generate_logic_placeholder(method)
|
|
251
|
+
|
|
252
|
+
# Generate postconditions
|
|
253
|
+
postconditions = self._generate_postconditions(method)
|
|
254
|
+
|
|
255
|
+
# Generate event emissions
|
|
256
|
+
events = self._generate_events(method.emits)
|
|
257
|
+
|
|
258
|
+
# Determine return value
|
|
259
|
+
return_value = 'result' if method.returns != 'void' else ''
|
|
260
|
+
if return_value == '' and method.returns == 'void':
|
|
261
|
+
# Remove RETURN line for void methods
|
|
262
|
+
return_template = self.TEMPLATE_METHOD.replace(
|
|
263
|
+
' RETURN {return_value}\n', ''
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
return_template = self.TEMPLATE_METHOD
|
|
267
|
+
|
|
268
|
+
return return_template.format(
|
|
269
|
+
method_type=method.type.upper(),
|
|
270
|
+
description=method.description,
|
|
271
|
+
name=method.name,
|
|
272
|
+
params=params,
|
|
273
|
+
returns=method.returns,
|
|
274
|
+
validations=validations,
|
|
275
|
+
logic=logic,
|
|
276
|
+
postconditions=postconditions,
|
|
277
|
+
events=events,
|
|
278
|
+
return_value=return_value
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def _generate_validations(self, rules: List[str]) -> str:
|
|
282
|
+
"""Generate VALIDATE statements from business rules."""
|
|
283
|
+
if not rules:
|
|
284
|
+
return " // No explicit validations"
|
|
285
|
+
|
|
286
|
+
validations = []
|
|
287
|
+
for rule in rules:
|
|
288
|
+
# Convert natural language rule to pseudocode
|
|
289
|
+
validation = self._rule_to_validation(rule)
|
|
290
|
+
validations.append(f" VALIDATE {validation}")
|
|
291
|
+
|
|
292
|
+
return '\n'.join(validations)
|
|
293
|
+
|
|
294
|
+
def _rule_to_validation(self, rule: str) -> str:
|
|
295
|
+
"""Convert business rule to validation pseudocode."""
|
|
296
|
+
rule_lower = rule.lower()
|
|
297
|
+
|
|
298
|
+
# Pattern matching for common rules
|
|
299
|
+
if 'cannot be empty' in rule_lower or 'not empty' in rule_lower:
|
|
300
|
+
subject = rule.split()[0].lower()
|
|
301
|
+
return f"{subject} IS NOT empty ELSE throw ValidationError(\"{rule}\")"
|
|
302
|
+
|
|
303
|
+
if 'must be' in rule_lower:
|
|
304
|
+
parts = rule.split('must be')
|
|
305
|
+
subject = parts[0].strip().lower().replace(' ', '_')
|
|
306
|
+
condition = parts[1].strip()
|
|
307
|
+
return f"{subject}.is{condition.title().replace(' ', '')} ELSE throw ValidationError(\"{rule}\")"
|
|
308
|
+
|
|
309
|
+
if 'must have' in rule_lower:
|
|
310
|
+
parts = rule.split('must have')
|
|
311
|
+
subject = parts[0].strip().lower().replace(' ', '_')
|
|
312
|
+
what = parts[1].strip()
|
|
313
|
+
return f"{subject}.has{what.title().replace(' ', '')} ELSE throw ValidationError(\"{rule}\")"
|
|
314
|
+
|
|
315
|
+
# Default: just use the rule as-is
|
|
316
|
+
return f"/* {rule} */"
|
|
317
|
+
|
|
318
|
+
def _generate_logic_placeholder(self, method: Method) -> str:
|
|
319
|
+
"""Generate placeholder logic based on method type."""
|
|
320
|
+
if method.type == 'command':
|
|
321
|
+
return ''' // TODO: Implement business logic
|
|
322
|
+
// 1. Validate current state
|
|
323
|
+
// 2. Apply business rules
|
|
324
|
+
// 3. Update state
|
|
325
|
+
result = PROCESS(params)'''
|
|
326
|
+
|
|
327
|
+
elif method.type == 'query':
|
|
328
|
+
return ''' // TODO: Implement query logic
|
|
329
|
+
result = FETCH data based on criteria'''
|
|
330
|
+
|
|
331
|
+
elif method.type == 'factory':
|
|
332
|
+
return ''' // TODO: Implement creation logic
|
|
333
|
+
entity = CREATE new entity with params
|
|
334
|
+
result = entity'''
|
|
335
|
+
|
|
336
|
+
return ''' // TODO: Implement logic
|
|
337
|
+
result = PROCESS(params)'''
|
|
338
|
+
|
|
339
|
+
def _generate_postconditions(self, method: Method) -> str:
|
|
340
|
+
"""Generate postcondition checks."""
|
|
341
|
+
if method.returns == 'void':
|
|
342
|
+
return " // State updated"
|
|
343
|
+
|
|
344
|
+
return f" ENSURE result IS valid"
|
|
345
|
+
|
|
346
|
+
def _generate_events(self, events: List[str]) -> str:
|
|
347
|
+
"""Generate event emission statements."""
|
|
348
|
+
if not events:
|
|
349
|
+
return ""
|
|
350
|
+
|
|
351
|
+
lines = ["\n // Emit domain events"]
|
|
352
|
+
for event in events:
|
|
353
|
+
lines.append(f" EMIT {event}(")
|
|
354
|
+
lines.append(f" // Event data")
|
|
355
|
+
lines.append(f" timestamp: NOW()")
|
|
356
|
+
lines.append(f" )")
|
|
357
|
+
|
|
358
|
+
return '\n'.join(lines)
|
|
359
|
+
|
|
360
|
+
def _generate_index(self, files: Dict[str, str]) -> str:
|
|
361
|
+
"""Generate index file for all pseudocode."""
|
|
362
|
+
lines = [
|
|
363
|
+
"# Pseudocode Index",
|
|
364
|
+
"",
|
|
365
|
+
f"Generated: {datetime.now().isoformat()}",
|
|
366
|
+
"",
|
|
367
|
+
"## Files",
|
|
368
|
+
"",
|
|
369
|
+
"| File | Type | Methods |",
|
|
370
|
+
"|------|------|---------|"
|
|
371
|
+
]
|
|
372
|
+
|
|
373
|
+
for filename, content in files.items():
|
|
374
|
+
if filename == 'INDEX.md':
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
file_type = 'Aggregate' if 'AGGREGATE' in content else 'Service'
|
|
378
|
+
method_count = content.count('FUNCTION ')
|
|
379
|
+
lines.append(f"| {filename} | {file_type} | {method_count} |")
|
|
380
|
+
|
|
381
|
+
lines.extend([
|
|
382
|
+
"",
|
|
383
|
+
"## Usage with Claude Code",
|
|
384
|
+
"",
|
|
385
|
+
"```bash",
|
|
386
|
+
"# Implement specific aggregate",
|
|
387
|
+
"claude \"Implement OrderAggregate from @docs/pseudocode/OrderAggregate.pseudo\"",
|
|
388
|
+
"",
|
|
389
|
+
"# Implement all",
|
|
390
|
+
"claude \"Implement all aggregates from docs/pseudocode/ in TypeScript\"",
|
|
391
|
+
"```"
|
|
392
|
+
])
|
|
393
|
+
|
|
394
|
+
return '\n'.join(lines)
|
|
395
|
+
|
|
396
|
+
def _write_file(self, filename: str, content: str):
|
|
397
|
+
"""Write content to file."""
|
|
398
|
+
filepath = self.output_dir / filename
|
|
399
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
400
|
+
f.write(content)
|
|
401
|
+
print(f"Generated: {filepath}")
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def main():
|
|
405
|
+
parser = argparse.ArgumentParser(
|
|
406
|
+
description='Generate pseudocode from DDD tactical design'
|
|
407
|
+
)
|
|
408
|
+
parser.add_argument(
|
|
409
|
+
'--input', '-i',
|
|
410
|
+
type=Path,
|
|
411
|
+
required=True,
|
|
412
|
+
help='Input JSON file with tactical design'
|
|
413
|
+
)
|
|
414
|
+
parser.add_argument(
|
|
415
|
+
'--output', '-o',
|
|
416
|
+
type=Path,
|
|
417
|
+
default=Path('docs/pseudocode'),
|
|
418
|
+
help='Output directory for pseudocode files'
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
args = parser.parse_args()
|
|
422
|
+
|
|
423
|
+
generator = PseudocodeGenerator(args.output)
|
|
424
|
+
files = generator.generate_from_json(args.input)
|
|
425
|
+
|
|
426
|
+
print(f"\nGenerated {len(files)} files in {args.output}")
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
if __name__ == '__main__':
|
|
430
|
+
main()
|