@atlashub/smartstack-cli 3.7.0 → 3.9.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/dist/index.js +365 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/templates/agents/action.md +1 -0
- package/templates/agents/ba-writer.md +33 -0
- package/templates/agents/explore-codebase.md +1 -0
- package/templates/agents/explore-docs.md +1 -0
- package/templates/agents/fix-grammar.md +1 -0
- package/templates/agents/snipper.md +1 -0
- package/templates/skills/admin/SKILL.md +6 -0
- package/templates/skills/ai-prompt/SKILL.md +32 -136
- package/templates/skills/ai-prompt/steps/step-01-implementation.md +122 -0
- package/templates/skills/apex/SKILL.md +120 -0
- package/templates/skills/apex/_shared.md +86 -0
- package/templates/skills/apex/references/agent-teams-protocol.md +164 -0
- package/templates/skills/apex/references/smartstack-layers.md +173 -0
- package/templates/skills/apex/steps/step-00-init.md +156 -0
- package/templates/skills/apex/steps/step-01-analyze.md +169 -0
- package/templates/skills/apex/steps/step-02-plan.md +160 -0
- package/templates/skills/apex/steps/step-03-execute.md +166 -0
- package/templates/skills/apex/steps/step-04-validate.md +138 -0
- package/templates/skills/apex/steps/step-05-examine.md +124 -0
- package/templates/skills/apex/steps/step-06-resolve.md +105 -0
- package/templates/skills/apex/steps/step-07-tests.md +130 -0
- package/templates/skills/apex/steps/step-08-run-tests.md +115 -0
- package/templates/skills/application/SKILL.md +10 -0
- package/templates/skills/application/references/backend-controller-hierarchy.md +58 -0
- package/templates/skills/application/references/backend-entity-seeding.md +72 -0
- package/templates/skills/application/references/backend-verification.md +88 -0
- package/templates/skills/application/references/frontend-verification.md +111 -0
- package/templates/skills/application/references/nav-fallback-procedure.md +200 -0
- package/templates/skills/application/references/provider-template.md +134 -0
- package/templates/skills/application/references/test-frontend.md +73 -0
- package/templates/skills/application/references/test-prerequisites.md +72 -0
- package/templates/skills/application/steps/step-01-navigation.md +7 -198
- package/templates/skills/application/steps/step-03b-provider.md +4 -128
- package/templates/skills/application/steps/step-04-backend.md +20 -350
- package/templates/skills/application/steps/step-05-frontend.md +12 -101
- package/templates/skills/application/steps/step-07-tests.md +12 -132
- package/templates/skills/business-analyse/SKILL.md +11 -2
- package/templates/skills/business-analyse/html/ba-interactive.html +3214 -2246
- package/templates/skills/business-analyse/html/build-html.js +77 -0
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +130 -0
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +22 -0
- package/templates/skills/business-analyse/html/src/scripts/03-render-cadrage.js +208 -0
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +211 -0
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +554 -0
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +110 -0
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +90 -0
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +45 -0
- package/templates/skills/business-analyse/html/src/scripts/09-export.js +168 -0
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +171 -0
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +161 -0
- package/templates/skills/business-analyse/html/src/styles/01-variables.css +38 -0
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +101 -0
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +62 -0
- package/templates/skills/business-analyse/html/src/styles/04-cards.css +196 -0
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +325 -0
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +230 -0
- package/templates/skills/business-analyse/html/src/styles/07-comments.css +184 -0
- package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +241 -0
- package/templates/skills/business-analyse/html/src/template.html +623 -0
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +78 -0
- package/templates/skills/business-analyse/references/cadrage-vibe-coding.md +97 -0
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +92 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +121 -0
- package/templates/skills/business-analyse/references/deploy-modes.md +49 -0
- package/templates/skills/business-analyse/references/handoff-file-templates.md +119 -0
- package/templates/skills/business-analyse/references/handoff-mappings.md +81 -0
- package/templates/skills/business-analyse/references/html-data-mapping.md +10 -2
- package/templates/skills/business-analyse/references/init-schema-deployment.md +65 -0
- package/templates/skills/business-analyse/references/review-data-mapping.md +363 -0
- package/templates/skills/business-analyse/references/spec-auto-inference.md +57 -0
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +85 -0
- package/templates/skills/business-analyse/references/ui-resource-cards.md +110 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +55 -0
- package/templates/skills/business-analyse/steps/step-00-init.md +35 -68
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +5 -194
- package/templates/skills/business-analyse/steps/step-03a-data.md +6 -49
- package/templates/skills/business-analyse/steps/step-03b-ui.md +12 -178
- package/templates/skills/business-analyse/steps/step-03d-validate.md +3 -48
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +9 -104
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +25 -441
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +19 -187
- package/templates/skills/business-analyse/steps/step-06-review.md +277 -0
- package/templates/skills/cc-agent/references/agent-behavior-patterns.md +95 -0
- package/templates/skills/cc-agent/steps/step-02-generate.md +5 -78
- package/templates/skills/check-version/SKILL.md +7 -0
- package/templates/skills/controller/references/controller-code-templates.md +159 -0
- package/templates/skills/controller/references/permission-sync-templates.md +152 -0
- package/templates/skills/controller/steps/step-03-generate.md +6 -158
- package/templates/skills/controller/steps/step-04-perms.md +5 -144
- package/templates/skills/debug/SKILL.md +7 -0
- package/templates/skills/explore/SKILL.md +6 -0
- package/templates/skills/feature-full/SKILL.md +39 -142
- package/templates/skills/feature-full/steps/step-01-implementation.md +120 -0
- package/templates/skills/gitflow/references/init-config-template.md +135 -0
- package/templates/skills/gitflow/references/init-name-normalization.md +103 -0
- package/templates/skills/gitflow/references/plan-template.md +69 -0
- package/templates/skills/gitflow/references/start-efcore-preflight.md +70 -0
- package/templates/skills/gitflow/references/start-local-config.md +110 -0
- package/templates/skills/gitflow/steps/step-init.md +18 -289
- package/templates/skills/gitflow/steps/step-plan.md +6 -63
- package/templates/skills/gitflow/steps/step-start.md +16 -126
- package/templates/skills/mcp/SKILL.md +9 -213
- package/templates/skills/mcp/steps/step-01-healthcheck.md +108 -0
- package/templates/skills/mcp/steps/step-02-tools.md +73 -0
- package/templates/skills/notification/SKILL.md +7 -0
- package/templates/skills/quick-search/SKILL.md +5 -0
- package/templates/skills/ralph-loop/SKILL.md +99 -381
- package/templates/skills/ralph-loop/references/category-rules.md +259 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +182 -0
- package/templates/skills/ralph-loop/references/task-transform-legacy.md +259 -0
- package/templates/skills/ralph-loop/references/team-orchestration.md +189 -0
- package/templates/skills/ralph-loop/steps/step-00-init.md +111 -383
- package/templates/skills/ralph-loop/steps/step-01-task.md +79 -896
- package/templates/skills/ralph-loop/steps/step-02-execute.md +68 -680
- package/templates/skills/ralph-loop/steps/step-03-commit.md +47 -277
- package/templates/skills/ralph-loop/steps/step-04-check.md +124 -607
- package/templates/skills/ralph-loop/steps/step-05-report.md +68 -367
- package/templates/skills/refactor/SKILL.md +12 -176
- package/templates/skills/refactor/steps/step-01-discover.md +60 -0
- package/templates/skills/refactor/steps/step-02-execute.md +67 -0
- package/templates/skills/review-code/SKILL.md +19 -257
- package/templates/skills/review-code/steps/step-01-smartstack.md +96 -0
- package/templates/skills/review-code/steps/step-02-detailed-review.md +80 -0
- package/templates/skills/review-code/steps/step-03-react.md +44 -0
- package/templates/skills/ui-components/SKILL.md +7 -0
- package/templates/skills/utils/SKILL.md +6 -0
- package/templates/skills/validate/SKILL.md +6 -0
- package/templates/skills/validate-feature/SKILL.md +8 -0
- package/templates/skills/workflow/SKILL.md +40 -118
- package/templates/skills/workflow/steps/step-01-implementation.md +84 -0
|
@@ -1,974 +1,157 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: step-01-task
|
|
3
|
-
description: Load or create tasks from prd.json (v2
|
|
3
|
+
description: Load or create tasks from prd.json (v3 unified or v2 legacy)
|
|
4
4
|
next_step: steps/step-02-execute.md
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Step 1: Load Task
|
|
8
8
|
|
|
9
|
-
> **MODULE TRANSITION CHECK
|
|
10
|
-
> Before applying the "Only Read Once" rule, check for module transition marker:
|
|
9
|
+
> **MODULE TRANSITION CHECK:** Before "Only Read Once" rule, check for module transition:
|
|
11
10
|
|
|
12
11
|
```javascript
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.log(`🔄 Module transition detected: ${transitionData.fromModule} → ${transitionData.toModule}`);
|
|
19
|
-
console.log(` Reloading step-01 to initialize new module...`);
|
|
20
|
-
|
|
21
|
-
// Delete flag (consume it)
|
|
22
|
-
deleteFile(moduleChangedPath);
|
|
23
|
-
|
|
24
|
-
// PROCEED with step-01 execution (skip "Only Read Once" rule below)
|
|
25
|
-
// This is the EXCEPTION case for multi-module workflows
|
|
12
|
+
if (fileExists('.ralph/module-changed.json')) {
|
|
13
|
+
const t = readJSON('.ralph/module-changed.json');
|
|
14
|
+
console.log(`Module transition: ${t.fromModule} → ${t.toModule}`);
|
|
15
|
+
deleteFile('.ralph/module-changed.json');
|
|
16
|
+
// PROCEED (exception to "Only Read Once" rule)
|
|
26
17
|
}
|
|
27
18
|
```
|
|
28
19
|
|
|
29
|
-
> **CONTEXT OPTIMIZATION:**
|
|
30
|
-
>
|
|
31
|
-
>
|
|
32
|
-
> **DO NOT re-read this file for iterations > 1 of the same module.**
|
|
33
|
-
>
|
|
34
|
-
> **EXCEPTION:** When transitioning between modules (multi-module workflows), step-01 MUST be re-read
|
|
35
|
-
> to load the next module's PRD. This is signaled by `.ralph/module-changed.json` file (checked above).
|
|
36
|
-
>
|
|
37
|
-
> If you are re-reading this file on iteration > 1 AND no module transition detected, STOP and go to step-04 section 5 instead.
|
|
20
|
+
> **CONTEXT OPTIMIZATION:** Read ONCE per module (first iteration).
|
|
21
|
+
> Iterations > 1 within same module → use COMPACT LOOP in step-04.
|
|
22
|
+
> Re-read ONLY on module transition (module-changed.json detected above).
|
|
38
23
|
|
|
39
24
|
## YOUR TASK:
|
|
40
25
|
|
|
41
|
-
Load
|
|
42
|
-
|
|
43
|
-
**ULTRA THINK about task decomposition.**
|
|
26
|
+
Load task from prd.json or create initial task breakdown with categories, dependencies, and acceptance criteria.
|
|
44
27
|
|
|
45
28
|
---
|
|
46
29
|
|
|
47
|
-
##
|
|
48
|
-
|
|
49
|
-
### 0. Multi-Module Queue Check
|
|
50
|
-
|
|
51
|
-
**✅ ALWAYS check for modules-queue.json, even on iteration > 1:**
|
|
52
|
-
|
|
53
|
-
> **CRITICAL:** This section MUST execute even when step-01 is re-read for module transitions.
|
|
54
|
-
> The module-changed.json flag (checked above) ensures this section runs when needed.
|
|
30
|
+
## 0. Multi-Module Queue Check
|
|
55
31
|
|
|
56
32
|
```javascript
|
|
57
33
|
const queuePath = '.ralph/modules-queue.json';
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (hasQueue) {
|
|
34
|
+
if (fileExists(queuePath)) {
|
|
61
35
|
const queue = readJSON(queuePath);
|
|
62
36
|
const currentModule = queue.modules[queue.currentIndex];
|
|
37
|
+
console.log(`Multi-module: ${currentModule.code} (${queue.currentIndex + 1}/${queue.totalModules})`);
|
|
63
38
|
|
|
64
|
-
console.log(`
|
|
65
|
-
╔══════════════════════════════════════════════════════════════════╗
|
|
66
|
-
║ Multi-Module Workflow Detected ║
|
|
67
|
-
╠══════════════════════════════════════════════════════════════════╣
|
|
68
|
-
║ Current Module: ${currentModule.code} (${queue.currentIndex + 1}/${queue.totalModules}) ║
|
|
69
|
-
║ Status: ${currentModule.status} ║
|
|
70
|
-
║ PRD File: ${currentModule.prdFile} ║
|
|
71
|
-
╚══════════════════════════════════════════════════════════════════╝
|
|
72
|
-
`);
|
|
73
|
-
|
|
74
|
-
// ✅ FIX #3: RESTORE multi-module state variables
|
|
75
|
-
// These variables persist across step files and are critical for multi-module coordination
|
|
76
39
|
{modules_queue} = queue;
|
|
77
40
|
{current_module} = currentModule.code;
|
|
78
41
|
|
|
79
|
-
//
|
|
80
|
-
// On module transition, step-04 already loaded it, but we verify here
|
|
42
|
+
// Verify PRD matches current module
|
|
81
43
|
const currentPrd = readJSON('.ralph/prd.json');
|
|
82
|
-
|
|
83
|
-
// Verify PRD matches current module (sanity check)
|
|
84
|
-
if (currentPrd.metadata && currentPrd.metadata.moduleCode !== currentModule.code) {
|
|
85
|
-
console.log(`⚠️ PRD mismatch detected. Loading correct PRD for module ${currentModule.code}...`);
|
|
86
|
-
|
|
87
|
-
// Load current module's PRD
|
|
44
|
+
if (currentPrd.metadata?.moduleCode !== currentModule.code) {
|
|
88
45
|
const modulePrd = readJSON(currentModule.prdFile);
|
|
89
46
|
|
|
90
|
-
//
|
|
91
|
-
if (modulePrd
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
47
|
+
// v3 FAST PATH: unified PRD from ss derive-prd (tasks pre-computed)
|
|
48
|
+
if (modulePrd.$version === '3.0.0') {
|
|
49
|
+
// Inject runtime fields and use directly
|
|
50
|
+
modulePrd.status = 'in_progress';
|
|
51
|
+
modulePrd.feature = `${modulePrd.project?.module || currentModule.code} (${modulePrd.project?.application || 'app'})`;
|
|
52
|
+
modulePrd.created = modulePrd.created || new Date().toISOString();
|
|
53
|
+
modulePrd.updated_at = new Date().toISOString();
|
|
54
|
+
modulePrd.config = modulePrd.config || { max_iterations: {max_iterations}, completion_promise: "{completion_promise}", current_iteration: 1 };
|
|
55
|
+
modulePrd.metadata.branch = modulePrd.metadata.branch || getCurrentBranch();
|
|
56
|
+
modulePrd.metadata.project_path = modulePrd.metadata.project_path || process.cwd();
|
|
57
|
+
modulePrd.metadata.mcp_servers = modulePrd.metadata.mcp_servers || { smartstack: true, context7: true };
|
|
58
|
+
modulePrd.history = modulePrd.history || [];
|
|
59
|
+
writeJSON('.ralph/prd.json', modulePrd);
|
|
60
|
+
}
|
|
61
|
+
// LEGACY: FORMAT A (no $version) — transform + warning
|
|
62
|
+
else if (modulePrd.project && modulePrd.requirements && !modulePrd.$version) {
|
|
63
|
+
console.warn('⚠ DEPRECATED: FORMAT A prd.json detected. Re-run `ss derive-prd` to generate v3 format.');
|
|
64
|
+
writeJSON('.ralph/prd.json', transformPrdJsonToRalphV2(modulePrd, currentModule.code));
|
|
65
|
+
}
|
|
66
|
+
// v2 legacy or already runtime format
|
|
67
|
+
else {
|
|
98
68
|
writeJSON('.ralph/prd.json', modulePrd);
|
|
99
|
-
console.log(`✅ Loaded PRD for module: ${currentModule.code}`);
|
|
100
69
|
}
|
|
101
|
-
} else {
|
|
102
|
-
console.log(`✅ PRD already loaded for module: ${currentModule.code}`);
|
|
103
70
|
}
|
|
104
|
-
|
|
105
|
-
console.log(`State restored: modules_queue and current_module set`);
|
|
106
71
|
} else {
|
|
107
|
-
// Single-module workflow
|
|
108
72
|
{modules_queue} = null;
|
|
109
73
|
{current_module} = null;
|
|
110
74
|
}
|
|
111
75
|
```
|
|
112
76
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
The PrdJson format (from `ss derive-prd`) has this structure:
|
|
116
|
-
```
|
|
117
|
-
{ version, source, project, requirements: { useCases[], functionalRequirements[] },
|
|
118
|
-
businessRules[], architecture: { entities[], apiEndpoints[], sections[] },
|
|
119
|
-
implementation: { filesToCreate: { domain[], application[], infrastructure[], api[], frontend[], tests[] } },
|
|
120
|
-
seedData }
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
```javascript
|
|
124
|
-
function transformPrdJsonToRalphV2(prdJson, moduleCode) {
|
|
125
|
-
const tasks = [];
|
|
126
|
-
let taskId = 1;
|
|
127
|
-
|
|
128
|
-
// Layer processing order (SmartStack convention)
|
|
129
|
-
const layerOrder = [
|
|
130
|
-
{ key: "domain", category: "domain" },
|
|
131
|
-
{ key: "infrastructure", category: "infrastructure" },
|
|
132
|
-
{ key: "application", category: "application" },
|
|
133
|
-
{ key: "api", category: "api" },
|
|
134
|
-
{ key: "frontend", category: "frontend" },
|
|
135
|
-
{ key: "seedData", category: "infrastructure" },
|
|
136
|
-
{ key: "tests", category: "test" }
|
|
137
|
-
];
|
|
138
|
-
|
|
139
|
-
// Support BOTH formats: nested (from ss derive-prd) and flat (LLM-generated)
|
|
140
|
-
const filesToCreate = prdJson.implementation?.filesToCreate
|
|
141
|
-
|| prdJson.filesToCreate
|
|
142
|
-
|| {};
|
|
143
|
-
|
|
144
|
-
// 1. Generate tasks from implementation.filesToCreate (primary source)
|
|
145
|
-
// CRITICAL: Frontend and i18n tasks are CONSOLIDATED into ONE module-level task each.
|
|
146
|
-
// These use MCP scaffolding tools, NOT manual file-by-file generation.
|
|
147
|
-
const lastIdByCategory = {};
|
|
148
|
-
const frontendFiles = []; // Collect frontend files for consolidated task
|
|
149
|
-
const i18nFiles = []; // Collect i18n files for consolidated task
|
|
150
|
-
|
|
151
|
-
for (const layer of layerOrder) {
|
|
152
|
-
const files = filesToCreate[layer.key] || [];
|
|
153
|
-
|
|
154
|
-
// FRONTEND & I18N: Collect files, do NOT create individual tasks
|
|
155
|
-
if (layer.category === "frontend") {
|
|
156
|
-
frontendFiles.push(...files);
|
|
157
|
-
continue; // Skip individual task creation
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
for (const fileSpec of files) {
|
|
161
|
-
// Check if this is an i18n file (category "infrastructure" but path contains i18n)
|
|
162
|
-
if (fileSpec.path?.includes('i18n/') || fileSpec.path?.includes('/locales/')) {
|
|
163
|
-
i18nFiles.push(fileSpec);
|
|
164
|
-
continue; // Skip individual task creation
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const depIds = [];
|
|
168
|
-
// Depend on last task in same category (sequential within layer)
|
|
169
|
-
if (lastIdByCategory[layer.category]) {
|
|
170
|
-
depIds.push(lastIdByCategory[layer.category]);
|
|
171
|
-
}
|
|
172
|
-
// Cross-layer: api depends on last application task
|
|
173
|
-
if (layer.category === "api" && lastIdByCategory["application"]) {
|
|
174
|
-
depIds.push(lastIdByCategory["application"]);
|
|
175
|
-
}
|
|
176
|
-
if (layer.category === "test" && lastIdByCategory["api"]) {
|
|
177
|
-
depIds.push(lastIdByCategory["api"]);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Build acceptance criteria from linked FRs and UCs
|
|
181
|
-
// Support both nested (requirements.functionalRequirements) and flat (functionalRequirements) formats
|
|
182
|
-
const allFRs = prdJson.requirements?.functionalRequirements || prdJson.functionalRequirements || [];
|
|
183
|
-
const linkedFRs = (fileSpec.linkedFRs || [])
|
|
184
|
-
.map(frId => {
|
|
185
|
-
const fr = allFRs.find(f => f.id === frId);
|
|
186
|
-
return fr ? fr.statement : frId;
|
|
187
|
-
});
|
|
188
|
-
let criteria = linkedFRs.length > 0
|
|
189
|
-
? `Implements: ${linkedFRs.join("; ")}`
|
|
190
|
-
: `File ${fileSpec.path} created and compiles correctly`;
|
|
191
|
-
|
|
192
|
-
// Enhance acceptance criteria for API tasks with permission enforcement
|
|
193
|
-
const permMatrix = prdJson.architecture?.permissionMatrix || prdJson.permissionMatrix;
|
|
194
|
-
if (layer.category === "api" && permMatrix?.permissions?.length > 0) {
|
|
195
|
-
criteria += "; MANDATORY: [RequirePermission] attributes on every endpoint (NOT [Authorize])";
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
tasks.push({
|
|
199
|
-
id: taskId,
|
|
200
|
-
description: fileSpec.description
|
|
201
|
-
? `[${layer.category}] ${fileSpec.description} → ${fileSpec.path}`
|
|
202
|
-
: `[${layer.category}] Create ${fileSpec.type}: ${fileSpec.path}`,
|
|
203
|
-
status: "pending",
|
|
204
|
-
category: layer.category,
|
|
205
|
-
dependencies: [...new Set(depIds)],
|
|
206
|
-
acceptance_criteria: criteria,
|
|
207
|
-
started_at: null,
|
|
208
|
-
completed_at: null,
|
|
209
|
-
iteration: null,
|
|
210
|
-
commit_hash: null,
|
|
211
|
-
files_changed: { created: [fileSpec.path], modified: [] },
|
|
212
|
-
validation: null,
|
|
213
|
-
error: null,
|
|
214
|
-
module: moduleCode
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
lastIdByCategory[layer.category] = taskId;
|
|
218
|
-
taskId++;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 1b. Create CONSOLIDATED frontend task (ONE task per module, uses MCP tools)
|
|
223
|
-
if (frontendFiles.length > 0) {
|
|
224
|
-
const apiDepId = lastIdByCategory["api"] || lastIdByCategory["application"];
|
|
225
|
-
const allLinkedUCs = [...new Set(frontendFiles.flatMap(f => f.linkedUCs || []))];
|
|
226
|
-
const allLinkedWireframes = [...new Set(frontendFiles.flatMap(f => f.linkedWireframes || []))];
|
|
227
|
-
const allPaths = frontendFiles.map(f => f.path);
|
|
228
|
-
|
|
229
|
-
tasks.push({
|
|
230
|
-
id: taskId,
|
|
231
|
-
description: `[frontend] Generate COMPLETE frontend for module ${moduleCode} via MCP scaffold tools (${frontendFiles.length} files: pages, components, hooks, API client)`,
|
|
232
|
-
status: "pending",
|
|
233
|
-
category: "frontend",
|
|
234
|
-
dependencies: apiDepId ? [apiDepId] : [],
|
|
235
|
-
acceptance_criteria: [
|
|
236
|
-
"MCP scaffold_api_client called → API client + types generated",
|
|
237
|
-
"MCP scaffold_routes called → routes.tsx updated with nested routes inside Layout wrapper",
|
|
238
|
-
"Pages match wireframes: " + allLinkedWireframes.join(", "),
|
|
239
|
-
"SmartTable for lists (NOT HTML tables), EntityCard for grids (NOT custom divs)",
|
|
240
|
-
"CSS variables ONLY (NO hardcoded Tailwind colors like bg-blue-600)",
|
|
241
|
-
"useNavigate + useParams for routing, NOT window.location",
|
|
242
|
-
"Loading/error/empty states on all pages",
|
|
243
|
-
"4-language i18n keys (fr, en, it, de)",
|
|
244
|
-
"npm run typecheck passes"
|
|
245
|
-
].join("; "),
|
|
246
|
-
started_at: null,
|
|
247
|
-
completed_at: null,
|
|
248
|
-
iteration: null,
|
|
249
|
-
commit_hash: null,
|
|
250
|
-
files_changed: { created: allPaths, modified: [] },
|
|
251
|
-
validation: null,
|
|
252
|
-
error: null,
|
|
253
|
-
module: moduleCode,
|
|
254
|
-
_frontendMeta: {
|
|
255
|
-
wireframes: allLinkedWireframes,
|
|
256
|
-
linkedUCs: allLinkedUCs,
|
|
257
|
-
filesToCreate: frontendFiles
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
lastIdByCategory["frontend"] = taskId;
|
|
262
|
-
taskId++;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 1c. Create CONSOLIDATED i18n task (ONE task per module)
|
|
266
|
-
if (i18nFiles.length > 0) {
|
|
267
|
-
tasks.push({
|
|
268
|
-
id: taskId,
|
|
269
|
-
description: `[i18n] Generate i18n translations for module ${moduleCode} (4 languages: fr, en, it, de)`,
|
|
270
|
-
status: "pending",
|
|
271
|
-
category: "i18n",
|
|
272
|
-
dependencies: lastIdByCategory["frontend"] ? [lastIdByCategory["frontend"]] : [],
|
|
273
|
-
acceptance_criteria: "4 JSON files (fr, en, it, de) with identical keys; all UI labels translated",
|
|
274
|
-
started_at: null,
|
|
275
|
-
completed_at: null,
|
|
276
|
-
iteration: null,
|
|
277
|
-
commit_hash: null,
|
|
278
|
-
files_changed: { created: i18nFiles.map(f => f.path), modified: [] },
|
|
279
|
-
validation: null,
|
|
280
|
-
error: null,
|
|
281
|
-
module: moduleCode
|
|
282
|
-
});
|
|
283
|
-
lastIdByCategory["i18n"] = taskId;
|
|
284
|
-
taskId++;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// 1d. GUARDRAIL: Inject missing layer tasks when filesToCreate is incomplete
|
|
288
|
-
// This handles LLM-generated PRDs that omit layers present in the source data.
|
|
289
|
-
// Uses architecture data (entities, apiEndpoints, sections, wireframes) as fallback.
|
|
290
|
-
|
|
291
|
-
const entities = prdJson.architecture?.entities || prdJson.entities || [];
|
|
292
|
-
const apiEndpoints = prdJson.architecture?.apiEndpoints || prdJson.apiEndpoints || [];
|
|
293
|
-
const sections = prdJson.architecture?.sections || prdJson.sections || [];
|
|
294
|
-
const wireframes = prdJson.specification?.uiWireframes || prdJson.uiWireframes || [];
|
|
295
|
-
const dashboards = prdJson.specification?.dashboards || prdJson.dashboards || [];
|
|
296
|
-
|
|
297
|
-
const hasDomainTasks = tasks.some(t => t.category === 'domain');
|
|
298
|
-
const hasInfraTasks = tasks.some(t => t.category === 'infrastructure');
|
|
299
|
-
const hasApiTasks = tasks.some(t => t.category === 'api');
|
|
300
|
-
const hasFrontendTasks = frontendFiles.length > 0; // Already handled in 1b
|
|
301
|
-
const hasTestTasks = tasks.some(t => t.category === 'test');
|
|
302
|
-
|
|
303
|
-
// INJECT consolidated infrastructure task if missing
|
|
304
|
-
if (!hasInfraTasks && entities.length > 0) {
|
|
305
|
-
const domainDepId = lastIdByCategory["domain"];
|
|
306
|
-
tasks.push({
|
|
307
|
-
id: taskId,
|
|
308
|
-
description: `[infrastructure] Create EF Core configurations, services, and seed data for module ${moduleCode} (${entities.length} entities)`,
|
|
309
|
-
status: "pending",
|
|
310
|
-
category: "infrastructure",
|
|
311
|
-
dependencies: domainDepId ? [domainDepId] : [],
|
|
312
|
-
acceptance_criteria: `EF Core configs for all ${entities.length} entities; Service implementations; DbSet in ExtensionsDbContext; DI registration; Seed data`,
|
|
313
|
-
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
314
|
-
files_changed: { created: [], modified: [] },
|
|
315
|
-
validation: null, error: null, module: moduleCode
|
|
316
|
-
});
|
|
317
|
-
lastIdByCategory["infrastructure"] = taskId;
|
|
318
|
-
taskId++;
|
|
319
|
-
console.log(`⚠️ GUARDRAIL: Injected missing [infrastructure] task from ${entities.length} entities`);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// INJECT consolidated API task if missing
|
|
323
|
-
if (!hasApiTasks && apiEndpoints.length > 0) {
|
|
324
|
-
const appDepId = lastIdByCategory["application"] || lastIdByCategory["infrastructure"];
|
|
325
|
-
const permMatrix = prdJson.architecture?.permissionMatrix || prdJson.permissionMatrix;
|
|
326
|
-
const hasPermissions = permMatrix?.permissions?.length > 0;
|
|
327
|
-
|
|
328
|
-
tasks.push({
|
|
329
|
-
id: taskId,
|
|
330
|
-
description: `[api] Create API controllers for module ${moduleCode} (${apiEndpoints.length} endpoints)`,
|
|
331
|
-
status: "pending",
|
|
332
|
-
category: "api",
|
|
333
|
-
dependencies: appDepId ? [appDepId] : [],
|
|
334
|
-
acceptance_criteria: [
|
|
335
|
-
`Controllers for all ${apiEndpoints.length} endpoints`,
|
|
336
|
-
hasPermissions
|
|
337
|
-
? "MANDATORY: [RequirePermission(Permissions.{Action})] on EVERY endpoint (NOT generic [Authorize])"
|
|
338
|
-
: "Authorization attributes",
|
|
339
|
-
"Swagger docs"
|
|
340
|
-
].join("; "),
|
|
341
|
-
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
342
|
-
files_changed: { created: [], modified: [] },
|
|
343
|
-
validation: null, error: null, module: moduleCode
|
|
344
|
-
});
|
|
345
|
-
lastIdByCategory["api"] = taskId;
|
|
346
|
-
taskId++;
|
|
347
|
-
console.log(`⚠️ GUARDRAIL: Injected missing [api] task from ${apiEndpoints.length} endpoints`);
|
|
348
|
-
}
|
|
77
|
+
## PrdJson → Ralph v2 Transformation (DEPRECATED)
|
|
349
78
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const apiDepId = lastIdByCategory["api"] || lastIdByCategory["application"];
|
|
354
|
-
|
|
355
|
-
// Derive frontend file list from available data sources
|
|
356
|
-
const derivedFrontendFiles = [];
|
|
357
|
-
|
|
358
|
-
// From wireframes → pages
|
|
359
|
-
for (const wf of wireframes) {
|
|
360
|
-
const screenId = wf.screen || wf.id;
|
|
361
|
-
derivedFrontendFiles.push({
|
|
362
|
-
path: `web/src/pages/${moduleCode}/${screenId}.tsx`,
|
|
363
|
-
type: screenId.includes('dashboard') ? 'DashboardPage' : 'Page',
|
|
364
|
-
linkedWireframes: [screenId],
|
|
365
|
-
linkedUCs: wf.linkedUCs || [],
|
|
366
|
-
});
|
|
367
|
-
}
|
|
79
|
+
> **DEPRECATED:** This transformation is a fallback for old FORMAT A PRDs (pre-v3.9.0).
|
|
80
|
+
> New PRDs generated by `ss derive-prd` use unified v3 format with pre-computed tasks.
|
|
81
|
+
> This function will be removed in a future release.
|
|
368
82
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
derivedFrontendFiles.push({
|
|
374
|
-
path: `web/src/pages/${moduleCode}/${dashId}.tsx`,
|
|
375
|
-
type: 'DashboardPage',
|
|
376
|
-
linkedWireframes: [dashId],
|
|
377
|
-
dashboardRef: dashId,
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// From API endpoints → API client service
|
|
383
|
-
if (apiEndpoints.length > 0) {
|
|
384
|
-
derivedFrontendFiles.push({
|
|
385
|
-
path: `web/src/services/api/${moduleCode}Api.ts`,
|
|
386
|
-
type: 'ApiService',
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const allWireframeIds = derivedFrontendFiles.flatMap(f => f.linkedWireframes || []);
|
|
391
|
-
|
|
392
|
-
tasks.push({
|
|
393
|
-
id: taskId,
|
|
394
|
-
description: `[frontend] Generate COMPLETE frontend for module ${moduleCode} via MCP scaffold tools (${derivedFrontendFiles.length} files: pages, components, hooks, API client)`,
|
|
395
|
-
status: "pending",
|
|
396
|
-
category: "frontend",
|
|
397
|
-
dependencies: apiDepId ? [apiDepId] : [],
|
|
398
|
-
acceptance_criteria: [
|
|
399
|
-
"MCP scaffold_api_client called → API client + types generated",
|
|
400
|
-
"MCP scaffold_routes called → routes.tsx updated with nested routes inside Layout wrapper",
|
|
401
|
-
allWireframeIds.length > 0 ? "Pages match wireframes: " + allWireframeIds.join(", ") : "Pages created for all UI sections",
|
|
402
|
-
"SmartTable for lists (NOT HTML tables), EntityCard for grids (NOT custom divs)",
|
|
403
|
-
"CSS variables ONLY (NO hardcoded Tailwind colors like bg-blue-600)",
|
|
404
|
-
"useNavigate + useParams for routing, NOT window.location",
|
|
405
|
-
"Loading/error/empty states on all pages",
|
|
406
|
-
"4-language i18n keys (fr, en, it, de)",
|
|
407
|
-
"npm run typecheck passes"
|
|
408
|
-
].join("; "),
|
|
409
|
-
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
410
|
-
files_changed: { created: derivedFrontendFiles.map(f => f.path), modified: [] },
|
|
411
|
-
validation: null, error: null, module: moduleCode,
|
|
412
|
-
_frontendMeta: {
|
|
413
|
-
wireframes: allWireframeIds,
|
|
414
|
-
filesToCreate: derivedFrontendFiles,
|
|
415
|
-
source: "guardrail-derived"
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
lastIdByCategory["frontend"] = taskId;
|
|
419
|
-
taskId++;
|
|
420
|
-
console.log(`⚠️ GUARDRAIL: Injected missing [frontend] task derived from ${wireframes.length} wireframes + ${dashboards.length} dashboards + ${apiEndpoints.length} endpoints`);
|
|
421
|
-
|
|
422
|
-
// Also inject i18n task if not already present
|
|
423
|
-
if (i18nFiles.length === 0) {
|
|
424
|
-
tasks.push({
|
|
425
|
-
id: taskId,
|
|
426
|
-
description: `[i18n] Generate i18n translations for module ${moduleCode} (4 languages: fr, en, it, de)`,
|
|
427
|
-
status: "pending",
|
|
428
|
-
category: "i18n",
|
|
429
|
-
dependencies: [lastIdByCategory["frontend"]],
|
|
430
|
-
acceptance_criteria: "4 JSON files (fr, en, it, de) with identical keys; all UI labels translated",
|
|
431
|
-
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
432
|
-
files_changed: { created: [], modified: [] },
|
|
433
|
-
validation: null, error: null, module: moduleCode
|
|
434
|
-
});
|
|
435
|
-
lastIdByCategory["i18n"] = taskId;
|
|
436
|
-
taskId++;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// INJECT consolidated test task if missing
|
|
441
|
-
if (!hasTestTasks && entities.length > 0) {
|
|
442
|
-
const apiDepId = lastIdByCategory["api"] || lastIdByCategory["application"];
|
|
443
|
-
tasks.push({
|
|
444
|
-
id: taskId,
|
|
445
|
-
description: `[test] Create unit and integration tests for module ${moduleCode}`,
|
|
446
|
-
status: "pending",
|
|
447
|
-
category: "test",
|
|
448
|
-
dependencies: apiDepId ? [apiDepId] : [],
|
|
449
|
-
acceptance_criteria: "Domain unit tests + service unit tests + controller integration tests; dotnet test passes; coverage >= 80%",
|
|
450
|
-
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
451
|
-
files_changed: { created: [], modified: [] },
|
|
452
|
-
validation: null, error: null, module: moduleCode
|
|
453
|
-
});
|
|
454
|
-
lastIdByCategory["test"] = taskId;
|
|
455
|
-
taskId++;
|
|
456
|
-
console.log(`⚠️ GUARDRAIL: Injected missing [test] task`);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// 1e. GUARDRAIL: Inject seedData tasks when core seed data exists but filesToCreate.seedData is empty
|
|
460
|
-
// This is the MOST CRITICAL guardrail — without core seed data, the application has:
|
|
461
|
-
// - No navigation menu entries
|
|
462
|
-
// - No RBAC permissions in the database
|
|
463
|
-
// - No role-permission mappings
|
|
464
|
-
// - Controllers accessible to ANY authenticated user
|
|
465
|
-
const coreSeedData = prdJson.seedData?.core
|
|
466
|
-
|| prdJson.seedDataCore
|
|
467
|
-
|| prdJson.specification?.seedDataCore;
|
|
468
|
-
const hasSeedDataTasks = (filesToCreate["seedData"]?.length ?? 0) > 0;
|
|
469
|
-
const hasSeedDataInTasks = tasks.some(t =>
|
|
470
|
-
t.description?.includes("SeedData") || t.description?.includes("seed data"));
|
|
471
|
-
|
|
472
|
-
if (coreSeedData && !hasSeedDataTasks && !hasSeedDataInTasks) {
|
|
473
|
-
const infraDepId = lastIdByCategory["infrastructure"] || lastIdByCategory["domain"];
|
|
474
|
-
|
|
475
|
-
// Derive navigation/permissions/roles from coreSeedData
|
|
476
|
-
const navModules = coreSeedData.navigationModules || coreSeedData.navigation || [];
|
|
477
|
-
const permissions = coreSeedData.permissions || [];
|
|
478
|
-
const rolePerms = coreSeedData.rolePermissions || [];
|
|
479
|
-
|
|
480
|
-
// Inject consolidated core seedData task
|
|
481
|
-
tasks.push({
|
|
482
|
-
id: taskId,
|
|
483
|
-
description: `[infrastructure] Create core seed data files for module ${moduleCode}: NavigationModuleSeedData, PermissionsSeedData, RolesSeedData, SeedConstants`,
|
|
484
|
-
status: "pending",
|
|
485
|
-
category: "infrastructure",
|
|
486
|
-
dependencies: infraDepId ? [infraDepId] : [],
|
|
487
|
-
acceptance_criteria: [
|
|
488
|
-
`NavigationModuleSeedData.cs with ${navModules.length} navigation module(s)`,
|
|
489
|
-
`PermissionsSeedData.cs with ${permissions.length} permission(s) from seedData.core`,
|
|
490
|
-
`RolesSeedData.cs with ${rolePerms.length} role-permission mapping(s)`,
|
|
491
|
-
"SeedConstants.cs with deterministic GUIDs",
|
|
492
|
-
"All seed data classes are static with GetSeedData() method"
|
|
493
|
-
].join("; "),
|
|
494
|
-
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
495
|
-
files_changed: { created: [
|
|
496
|
-
`src/Infrastructure/Persistence/Seeding/Data/${moduleCode}/NavigationModuleSeedData.cs`,
|
|
497
|
-
`src/Infrastructure/Persistence/Seeding/Data/${moduleCode}/PermissionsSeedData.cs`,
|
|
498
|
-
`src/Infrastructure/Persistence/Seeding/Data/${moduleCode}/RolesSeedData.cs`,
|
|
499
|
-
"src/Infrastructure/Persistence/Seeding/Data/SeedConstants.cs"
|
|
500
|
-
], modified: [] },
|
|
501
|
-
validation: null, error: null, module: moduleCode,
|
|
502
|
-
_seedDataMeta: {
|
|
503
|
-
source: "guardrail-derived",
|
|
504
|
-
coreSeedData,
|
|
505
|
-
navRoute: prdJson.project?.navRoute
|
|
506
|
-
|| (permissions?.[0]?.path?.split('.').slice(0, -1).join('.'))
|
|
507
|
-
|| `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
|
|
508
|
-
contextCode: prdJson.project?.context
|
|
509
|
-
|| coreSeedData.navigationModules?.[0]?.route?.split('/')?.[1]
|
|
510
|
-
|| 'business',
|
|
511
|
-
appCode: prdJson.project?.application
|
|
512
|
-
|| coreSeedData.navigationModules?.[0]?.route?.split('/')?.[2],
|
|
513
|
-
appLabels: prdJson.project?.labels || null
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
lastIdByCategory["infrastructure"] = taskId;
|
|
517
|
-
taskId++;
|
|
518
|
-
console.log(`GUARDRAIL: Injected missing [infrastructure] core seed data task from seedData.core (${navModules.length} nav, ${permissions.length} perms, ${rolePerms.length} roles)`);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// 2. Add IClientSeedDataProvider task for client projects (ExtensionsDbContext)
|
|
522
|
-
// This is MANDATORY - without it, core seed data (navigation, permissions, roles) is dead code
|
|
523
|
-
const hasClientSeedData = filesToCreate["seedData"]?.some(f =>
|
|
524
|
-
f.type === "IClientSeedDataProvider" || f.path?.includes("SeedDataProvider"));
|
|
525
|
-
const hasProviderInTasks = tasks.some(t =>
|
|
526
|
-
t.description?.includes("IClientSeedDataProvider") || t.description?.includes("SeedDataProvider"));
|
|
527
|
-
|
|
528
|
-
// Trigger when: seedData files exist OR coreSeedData exists (fallback for incomplete PRDs)
|
|
529
|
-
if (!hasClientSeedData && !hasProviderInTasks &&
|
|
530
|
-
(filesToCreate["seedData"]?.length > 0 || coreSeedData)) {
|
|
531
|
-
tasks.push({
|
|
532
|
-
id: taskId,
|
|
533
|
-
description: `[infrastructure] Create IClientSeedDataProvider to inject core seed data (navigation, permissions, roles) at runtime`,
|
|
534
|
-
status: "pending",
|
|
535
|
-
category: "infrastructure",
|
|
536
|
-
dependencies: [lastIdByCategory["infrastructure"] || lastIdByCategory["domain"]].filter(Boolean),
|
|
537
|
-
acceptance_criteria: [
|
|
538
|
-
"Implements IClientSeedDataProvider with SeedNavigationAsync, SeedPermissionsAsync, SeedRolePermissionsAsync",
|
|
539
|
-
"Registered in DI: services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()",
|
|
540
|
-
"All methods are idempotent (check existence before inserting)",
|
|
541
|
-
"Consumes seed data from NavigationModuleSeedData, PermissionsSeedData, RolesSeedData"
|
|
542
|
-
].join("; "),
|
|
543
|
-
started_at: null, completed_at: null, iteration: null, commit_hash: null,
|
|
544
|
-
files_changed: { created: ["src/Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs"], modified: ["src/Infrastructure/DependencyInjection.cs"] },
|
|
545
|
-
validation: null, error: null, module: moduleCode,
|
|
546
|
-
_providerMeta: {
|
|
547
|
-
consumesSeedDataFiles: [
|
|
548
|
-
`Infrastructure/Persistence/Seeding/Data/${moduleCode}/NavigationModuleSeedData.cs`,
|
|
549
|
-
`Infrastructure/Persistence/Seeding/Data/${moduleCode}/PermissionsSeedData.cs`,
|
|
550
|
-
`Infrastructure/Persistence/Seeding/Data/${moduleCode}/RolesSeedData.cs`
|
|
551
|
-
],
|
|
552
|
-
navRoute: prdJson.project?.navRoute
|
|
553
|
-
|| `business.${prdJson.project?.application || 'app'}.${moduleCode}`,
|
|
554
|
-
contextCode: prdJson.project?.context || 'business',
|
|
555
|
-
appCode: prdJson.project?.application
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
lastIdByCategory["infrastructure"] = taskId;
|
|
559
|
-
taskId++;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// 3. Add a final validation task
|
|
563
|
-
tasks.push({
|
|
564
|
-
id: taskId,
|
|
565
|
-
description: `[validation] Build, test, and MCP validate module ${moduleCode}`,
|
|
566
|
-
status: "pending",
|
|
567
|
-
category: "validation",
|
|
568
|
-
dependencies: Object.values(lastIdByCategory),
|
|
569
|
-
acceptance_criteria: "dotnet build succeeds, dotnet test passes, MCP validate_conventions clean",
|
|
570
|
-
started_at: null,
|
|
571
|
-
completed_at: null,
|
|
572
|
-
iteration: null,
|
|
573
|
-
commit_hash: null,
|
|
574
|
-
files_changed: { created: [], modified: [] },
|
|
575
|
-
validation: null,
|
|
576
|
-
error: null,
|
|
577
|
-
module: moduleCode
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
return {
|
|
581
|
-
$version: "2.0.0",
|
|
582
|
-
feature: `${prdJson.project?.module || moduleCode} (${prdJson.project?.application || "app"})`,
|
|
583
|
-
status: "in_progress",
|
|
584
|
-
created: new Date().toISOString(),
|
|
585
|
-
updated_at: new Date().toISOString(),
|
|
586
|
-
metadata: {
|
|
587
|
-
cli_version: "unknown",
|
|
588
|
-
branch: getCurrentBranch(),
|
|
589
|
-
project_path: process.cwd(),
|
|
590
|
-
mcp_servers: { smartstack: true, context7: true },
|
|
591
|
-
module: moduleCode
|
|
592
|
-
},
|
|
593
|
-
config: {
|
|
594
|
-
max_iterations: {max_iterations},
|
|
595
|
-
completion_promise: "{completion_promise}",
|
|
596
|
-
current_iteration: 1
|
|
597
|
-
},
|
|
598
|
-
source: {
|
|
599
|
-
type: "ba-handoff",
|
|
600
|
-
handoff_path: null,
|
|
601
|
-
feature_json_path: prdJson.source?.featureJson || null,
|
|
602
|
-
module: moduleCode
|
|
603
|
-
},
|
|
604
|
-
tasks: tasks,
|
|
605
|
-
history: []
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
**If no queue exists:** proceed with standard logic below (backward compatible).
|
|
83
|
+
See [references/task-transform-legacy.md](../references/task-transform-legacy.md) for the full `transformPrdJsonToRalphV2()` function including:
|
|
84
|
+
- Layer-ordered task generation (domain → infra → app → api → frontend → seedData → tests)
|
|
85
|
+
- Consolidated frontend + i18n tasks
|
|
86
|
+
- Guardrails 1d-1j (inject missing infrastructure, API, frontend, tests, seed data, migration)
|
|
611
87
|
|
|
612
88
|
---
|
|
613
89
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
**If `.ralph/prd.json` exists:**
|
|
617
|
-
- Read and parse the file
|
|
618
|
-
- Verify `$version` is `"2.0.0"` (if not, STOP - run step-00 migration first)
|
|
619
|
-
- Find the next eligible task (see step 5)
|
|
620
|
-
- Skip to step 5
|
|
90
|
+
## 1. Check for Existing prd.json
|
|
621
91
|
|
|
622
|
-
|
|
623
|
-
|
|
92
|
+
If `.ralph/prd.json` exists:
|
|
93
|
+
1. Read the PRD
|
|
94
|
+
2. **v3 FAST PATH:** If `$version === "3.0.0"` AND `tasks[]` exists:
|
|
95
|
+
- Inject runtime fields if missing (status, config, feature, created, updated_at, history)
|
|
96
|
+
- Write back to `.ralph/prd.json`
|
|
97
|
+
- Skip directly to section 5 (find next task)
|
|
98
|
+
3. **v2 legacy:** If `$version === "2.0.0"` → find next eligible task (section 5), skip to section 5
|
|
99
|
+
4. **FORMAT A (deprecated):** If `.project && .requirements && !.$version` → run `transformPrdJsonToRalphV2()` → section 5
|
|
624
100
|
|
|
625
|
-
|
|
101
|
+
If `.ralph/prd.json` does not exist: continue to section 2.
|
|
626
102
|
|
|
627
|
-
|
|
103
|
+
## 2. Analyze Task Description
|
|
628
104
|
|
|
629
|
-
|
|
630
|
-
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
- What dependencies exist between tasks?
|
|
105
|
+
Check for BA handoff source (priority):
|
|
106
|
+
1. Per-module PRD files (handled by section 0)
|
|
107
|
+
2. Single prd.json already present
|
|
108
|
+
3. Markdown handoff: `find . -path "*development-handoff*" -name "*.md"`
|
|
109
|
+
4. Direct task from `{task_description}`
|
|
635
110
|
|
|
636
|
-
**
|
|
111
|
+
Generate **3-30 subtasks** by category (domain → infrastructure → application → api → frontend → i18n → test → validation).
|
|
637
112
|
|
|
638
|
-
|
|
639
|
-
# Priority 1: Per-module PRD files (from ss derive-prd / BA handoff)
|
|
640
|
-
# These are already handled by Section 0 if modules-queue.json exists
|
|
641
|
-
PRD_MODULE=$(ls .ralph/prd-*.json 2>/dev/null | head -1)
|
|
113
|
+
## 3. Create prd.json
|
|
642
114
|
|
|
643
|
-
|
|
644
|
-
PRD_SINGLE=".ralph/prd.json"
|
|
645
|
-
|
|
646
|
-
# Priority 3: Markdown handoff document
|
|
647
|
-
HANDOFF=$(find . -path "*development-handoff*" -name "*.md" | head -1)
|
|
648
|
-
BA_OUTPUT=$(find . -path ".claude/output/ba/*" -name "4-*.md" | head -1)
|
|
649
|
-
SOURCE_PATH=${HANDOFF:-$BA_OUTPUT}
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
**If prd-*.json files exist but no modules-queue.json:**
|
|
653
|
-
- This means step-00 didn't create a queue (single prd file)
|
|
654
|
-
- Copy the single `prd-*.json` to `prd.json` and transform if needed
|
|
655
|
-
|
|
656
|
-
**If markdown handoff found:**
|
|
657
|
-
- Read the handoff document
|
|
658
|
-
- Derive tasks from its specifications (per-layer breakdown)
|
|
659
|
-
- Set `source.type = "ba-handoff"` and `source.handoff_path`
|
|
660
|
-
- Look for feature.json in the same directory
|
|
661
|
-
|
|
662
|
-
**If no handoff:**
|
|
663
|
-
- Generate task breakdown from `{task_description}`
|
|
664
|
-
- Set `source = null`
|
|
665
|
-
|
|
666
|
-
**Generate task breakdown:**
|
|
667
|
-
|
|
668
|
-
For `{task_description}`, identify **3-30 subtasks** organized by category.
|
|
669
|
-
|
|
670
|
-
**Task categories (use SmartStack layer order):**
|
|
671
|
-
|
|
672
|
-
| Category | Description | Examples |
|
|
673
|
-
|----------|-------------|---------|
|
|
674
|
-
| `domain` | Domain entities and value objects | Create entities, enums, interfaces |
|
|
675
|
-
| `application` | CQRS handlers, DTOs, validators | Commands, Queries, DTOs, FluentValidation |
|
|
676
|
-
| `infrastructure` | EF Core, external services | DbContext configs, migrations, services |
|
|
677
|
-
| `api` | Controllers, middleware | REST endpoints, filters, middleware |
|
|
678
|
-
| `frontend` | React pages, components, hooks | Pages, API services, React Query hooks |
|
|
679
|
-
| `i18n` | Translations | Translation JSON files |
|
|
680
|
-
| `test` | Unit and integration tests | xUnit tests, React testing library |
|
|
681
|
-
| `validation` | Build, lint, MCP checks | dotnet build, pnpm build, MCP validate |
|
|
682
|
-
| `other` | Anything else | Documentation, configuration |
|
|
683
|
-
|
|
684
|
-
**Example for "implement user authentication":**
|
|
685
|
-
```json
|
|
686
|
-
{
|
|
687
|
-
"tasks": [
|
|
688
|
-
{
|
|
689
|
-
"id": 1,
|
|
690
|
-
"description": "Create User entity in SmartStack.Domain/Entities/Business/",
|
|
691
|
-
"status": "pending",
|
|
692
|
-
"category": "domain",
|
|
693
|
-
"dependencies": [],
|
|
694
|
-
"acceptance_criteria": "Entity compiles with all required properties per handoff spec"
|
|
695
|
-
},
|
|
696
|
-
{
|
|
697
|
-
"id": 2,
|
|
698
|
-
"description": "Add User DbSet to ApplicationDbContext and create EF Core configuration",
|
|
699
|
-
"status": "pending",
|
|
700
|
-
"category": "infrastructure",
|
|
701
|
-
"dependencies": [1],
|
|
702
|
-
"acceptance_criteria": "DbContext compiles, configuration maps all properties"
|
|
703
|
-
},
|
|
704
|
-
{
|
|
705
|
-
"id": 3,
|
|
706
|
-
"description": "Create EF Core migration for User table",
|
|
707
|
-
"status": "pending",
|
|
708
|
-
"category": "infrastructure",
|
|
709
|
-
"dependencies": [2],
|
|
710
|
-
"acceptance_criteria": "Migration applies without errors, 3 files present"
|
|
711
|
-
},
|
|
712
|
-
{
|
|
713
|
-
"id": 4,
|
|
714
|
-
"description": "Implement IPasswordService and IJwtService",
|
|
715
|
-
"status": "pending",
|
|
716
|
-
"category": "application",
|
|
717
|
-
"dependencies": [1],
|
|
718
|
-
"acceptance_criteria": "Services compile, follow existing patterns"
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
"id": 5,
|
|
722
|
-
"description": "Create CQRS Commands: LoginCommand + RegisterCommand with handlers",
|
|
723
|
-
"status": "pending",
|
|
724
|
-
"category": "application",
|
|
725
|
-
"dependencies": [4],
|
|
726
|
-
"acceptance_criteria": "Handlers compile, validators defined"
|
|
727
|
-
},
|
|
728
|
-
{
|
|
729
|
-
"id": 6,
|
|
730
|
-
"description": "Create AuthController with login/register endpoints",
|
|
731
|
-
"status": "pending",
|
|
732
|
-
"category": "api",
|
|
733
|
-
"dependencies": [5],
|
|
734
|
-
"acceptance_criteria": "Endpoints respond correctly, Swagger displays them"
|
|
735
|
-
},
|
|
736
|
-
{
|
|
737
|
-
"id": 7,
|
|
738
|
-
"description": "Write unit tests for User entity and validators",
|
|
739
|
-
"status": "pending",
|
|
740
|
-
"category": "test",
|
|
741
|
-
"dependencies": [5],
|
|
742
|
-
"acceptance_criteria": "All tests pass, >80% coverage on new code"
|
|
743
|
-
},
|
|
744
|
-
{
|
|
745
|
-
"id": 8,
|
|
746
|
-
"description": "Run dotnet build, dotnet test, MCP validate_conventions",
|
|
747
|
-
"status": "pending",
|
|
748
|
-
"category": "validation",
|
|
749
|
-
"dependencies": [6, 7],
|
|
750
|
-
"acceptance_criteria": "Build succeeds, all tests pass, MCP validation clean"
|
|
751
|
-
}
|
|
752
|
-
]
|
|
753
|
-
}
|
|
754
|
-
```
|
|
755
|
-
|
|
756
|
-
**Task count validation:**
|
|
757
|
-
- MINIMUM: 3 tasks (anything less is too coarse)
|
|
758
|
-
- MAXIMUM: 30 tasks (anything more needs re-grouping)
|
|
759
|
-
- If over 30, merge related subtasks into larger units
|
|
760
|
-
|
|
761
|
-
### 3. Create prd.json (v2)
|
|
762
|
-
|
|
763
|
-
**Write `.ralph/prd.json`:**
|
|
764
|
-
|
|
765
|
-
```json
|
|
766
|
-
{
|
|
767
|
-
"$version": "2.0.0",
|
|
768
|
-
"feature": "{task_description}",
|
|
769
|
-
"status": "in_progress",
|
|
770
|
-
"created": "{ISO_TIMESTAMP}",
|
|
771
|
-
"updated_at": "{ISO_TIMESTAMP}",
|
|
772
|
-
"metadata": {metadata},
|
|
773
|
-
"config": {
|
|
774
|
-
"max_iterations": {max_iterations},
|
|
775
|
-
"completion_promise": "{completion_promise}",
|
|
776
|
-
"current_iteration": 1
|
|
777
|
-
},
|
|
778
|
-
"source": {source_object_or_null},
|
|
779
|
-
"tasks": [{generated_tasks_with_all_fields}],
|
|
780
|
-
"history": []
|
|
781
|
-
}
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
**IMPORTANT: Every task MUST have ALL fields:**
|
|
785
|
-
```json
|
|
786
|
-
{
|
|
787
|
-
"id": 1,
|
|
788
|
-
"description": "...",
|
|
789
|
-
"status": "pending",
|
|
790
|
-
"category": "domain|application|infrastructure|api|frontend|i18n|test|validation|other",
|
|
791
|
-
"dependencies": [],
|
|
792
|
-
"acceptance_criteria": "...",
|
|
793
|
-
"started_at": null,
|
|
794
|
-
"completed_at": null,
|
|
795
|
-
"iteration": null,
|
|
796
|
-
"commit_hash": null,
|
|
797
|
-
"files_changed": { "created": [], "modified": [] },
|
|
798
|
-
"validation": null,
|
|
799
|
-
"error": null
|
|
800
|
-
}
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
**Initialize `.ralph/progress.txt`:**
|
|
115
|
+
Write `.ralph/prd.json` with all tasks (every task MUST have: id, description, status, category, dependencies, acceptance_criteria, started_at, completed_at, iteration, commit_hash, files_changed, validation, error).
|
|
804
116
|
|
|
117
|
+
Initialize `.ralph/progress.txt`:
|
|
805
118
|
```markdown
|
|
806
119
|
# Ralph Loop Progress
|
|
807
|
-
|
|
808
120
|
## Task: {task_description}
|
|
809
121
|
## Started: {timestamp}
|
|
810
|
-
## Schema:
|
|
811
|
-
## Source: {source_type or "direct"}
|
|
812
|
-
|
|
813
|
-
---
|
|
814
|
-
|
|
815
|
-
## Iteration 1
|
|
816
|
-
|
|
817
|
-
### Context
|
|
818
|
-
Starting fresh implementation.
|
|
819
|
-
|
|
820
|
-
### Learnings
|
|
821
|
-
(To be updated after each iteration)
|
|
122
|
+
## Schema: v3.0.0
|
|
822
123
|
```
|
|
823
124
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
**After creation, verify:**
|
|
827
|
-
|
|
828
|
-
```javascript
|
|
829
|
-
// Check all tasks have required fields
|
|
830
|
-
const requiredFields = ['id', 'description', 'status', 'category',
|
|
831
|
-
'dependencies', 'acceptance_criteria', 'started_at', 'completed_at',
|
|
832
|
-
'iteration', 'commit_hash', 'files_changed', 'validation', 'error'];
|
|
833
|
-
|
|
834
|
-
for (const task of prd.tasks) {
|
|
835
|
-
for (const field of requiredFields) {
|
|
836
|
-
if (!(field in task)) {
|
|
837
|
-
ERROR: "Task ${task.id} missing field: ${field}";
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Check dependency references are valid
|
|
843
|
-
const taskIds = prd.tasks.map(t => t.id);
|
|
844
|
-
for (const task of prd.tasks) {
|
|
845
|
-
for (const dep of task.dependencies) {
|
|
846
|
-
if (!taskIds.includes(dep)) {
|
|
847
|
-
ERROR: "Task ${task.id} depends on non-existent task ${dep}";
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
// Check no circular dependencies
|
|
853
|
-
// (simple: a task cannot depend on a task with higher or equal id)
|
|
854
|
-
for (const task of prd.tasks) {
|
|
855
|
-
for (const dep of task.dependencies) {
|
|
856
|
-
if (dep >= task.id) {
|
|
857
|
-
ERROR: "Task ${task.id} has forward/circular dependency on task ${dep}";
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
// Check task count
|
|
863
|
-
if (prd.tasks.length < 3 || prd.tasks.length > 30) {
|
|
864
|
-
WARNING: "Task count ${prd.tasks.length} outside recommended range (3-30)";
|
|
865
|
-
}
|
|
866
|
-
```
|
|
125
|
+
## 4. Validate Task Integrity
|
|
867
126
|
|
|
868
|
-
|
|
127
|
+
- All tasks have required fields
|
|
128
|
+
- Dependency references are valid (exist, no forward/circular)
|
|
129
|
+
- Task count 3-30
|
|
869
130
|
|
|
870
|
-
|
|
131
|
+
## 5. Find Current Task
|
|
871
132
|
|
|
872
133
|
```javascript
|
|
873
134
|
function findNextTask(tasks) {
|
|
874
135
|
for (const task of tasks) {
|
|
875
136
|
if (task.status !== 'pending') continue;
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const depsOk = task.dependencies.every(depId => {
|
|
879
|
-
const dep = tasks.find(t => t.id === depId);
|
|
880
|
-
return dep && dep.status === 'completed';
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
// Check if any dependency failed (→ block this task)
|
|
884
|
-
const depsBlocked = task.dependencies.some(depId => {
|
|
885
|
-
const dep = tasks.find(t => t.id === depId);
|
|
137
|
+
const depsBlocked = task.dependencies.some(d => {
|
|
138
|
+
const dep = tasks.find(t => t.id === d);
|
|
886
139
|
return dep && (dep.status === 'failed' || dep.status === 'blocked');
|
|
887
140
|
});
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
task.status = 'blocked';
|
|
891
|
-
task.error = `Blocked by failed dependency`;
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
|
|
141
|
+
if (depsBlocked) { task.status = 'blocked'; task.error = 'Blocked by failed dependency'; continue; }
|
|
142
|
+
const depsOk = task.dependencies.every(d => tasks.find(t => t.id === d)?.status === 'completed');
|
|
895
143
|
if (depsOk) return task;
|
|
896
144
|
}
|
|
897
|
-
return null;
|
|
145
|
+
return null;
|
|
898
146
|
}
|
|
899
|
-
|
|
900
|
-
const currentTask = findNextTask(prd.tasks);
|
|
901
147
|
```
|
|
902
148
|
|
|
903
|
-
|
|
904
|
-
- Check if all tasks are completed → skip to step-05 (report)
|
|
905
|
-
- Check if remaining tasks are all blocked → report as partial, step-05
|
|
906
|
-
- Otherwise → unexpected state, STOP
|
|
149
|
+
If no eligible task: all completed → step-05, all blocked → step-05, else STOP.
|
|
907
150
|
|
|
908
|
-
|
|
909
|
-
```
|
|
910
|
-
{current_task_id} = currentTask.id
|
|
911
|
-
{current_task_description} = currentTask.description
|
|
912
|
-
{current_task_category} = currentTask.category
|
|
913
|
-
{current_task_criteria} = currentTask.acceptance_criteria
|
|
914
|
-
{tasks_completed} = tasks.filter(t => t.status === 'completed').length
|
|
915
|
-
{tasks_total} = tasks.length
|
|
916
|
-
{tasks_blocked} = tasks.filter(t => t.status === 'blocked').length
|
|
917
|
-
```
|
|
918
|
-
|
|
919
|
-
### 6. Read Previous Progress
|
|
920
|
-
|
|
921
|
-
**If iteration > 1:**
|
|
922
|
-
|
|
923
|
-
Read `.ralph/progress.txt` to understand:
|
|
924
|
-
- What was done in previous iterations
|
|
925
|
-
- Key learnings and context
|
|
926
|
-
- Files that were modified
|
|
927
|
-
- Issues encountered
|
|
928
|
-
|
|
929
|
-
**Also read `history[]` from prd.json for structured context.**
|
|
930
|
-
|
|
931
|
-
### 7. Show Task Status
|
|
932
|
-
|
|
933
|
-
**Display current state:**
|
|
151
|
+
## 6. Show Task Status
|
|
934
152
|
|
|
935
153
|
```
|
|
936
|
-
|
|
937
|
-
║ ITERATION {current_iteration} / {max_iterations} ║
|
|
938
|
-
║ {modules_queue ? "Module " + (currentIndex+1) + "/" + totalModules + ": " + current_module : ""} ║
|
|
939
|
-
╠══════════════════════════════════════════════════════════════════╣
|
|
940
|
-
║ Progress: {tasks_completed} / {tasks_total} tasks complete ║
|
|
941
|
-
║ Blocked: {tasks_blocked} ║
|
|
942
|
-
║ ║
|
|
943
|
-
║ Current Task: ║
|
|
944
|
-
║ [{current_task_id}] {current_task_description} ║
|
|
945
|
-
║ Category: {current_task_category} ║
|
|
946
|
-
║ Criteria: {current_task_criteria} ║
|
|
947
|
-
╠══════════════════════════════════════════════════════════════════╣
|
|
948
|
-
║ Previous Learnings: ║
|
|
949
|
-
║ {summary from progress.txt} ║
|
|
950
|
-
╚══════════════════════════════════════════════════════════════════╝
|
|
951
|
-
|
|
952
|
-
-> Executing task...
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
---
|
|
956
|
-
|
|
957
|
-
## OUTPUT FORMAT:
|
|
958
|
-
|
|
959
|
-
```
|
|
960
|
-
Task Loaded:
|
|
961
|
-
|
|
962
|
-
| Field | Value |
|
|
963
|
-
|-------|-------|
|
|
964
|
-
| Iteration | {current_iteration} / {max_iterations} |
|
|
965
|
-
| Task ID | {current_task_id} |
|
|
966
|
-
| Task | {current_task_description} |
|
|
967
|
-
| Category | {current_task_category} |
|
|
968
|
-
| Criteria | {current_task_criteria} |
|
|
969
|
-
| Progress | {tasks_completed} / {tasks_total} |
|
|
970
|
-
| Blocked | {tasks_blocked} |
|
|
971
|
-
|
|
154
|
+
[{iteration}/{max}] {completed}/{total} done | Task [{id}] {description} ({category})
|
|
972
155
|
-> Executing...
|
|
973
156
|
```
|
|
974
157
|
|