@atlashub/smartstack-cli 3.33.0 → 3.35.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/.documentation/agents.html +5 -1
- package/.documentation/apex.html +644 -0
- package/.documentation/business-analyse.html +81 -1
- package/.documentation/cli-commands.html +5 -1
- package/.documentation/commands.html +5 -1
- package/.documentation/efcore.html +5 -1
- package/.documentation/gitflow.html +5 -1
- package/.documentation/hooks.html +5 -1
- package/.documentation/index.html +60 -2
- package/.documentation/init.html +414 -1
- package/.documentation/installation.html +5 -1
- package/.documentation/ralph-loop.html +365 -216
- package/.documentation/test-web.html +5 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +7 -24
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -2
- package/templates/agents/ba-writer.md +142 -15
- package/templates/mcp-scaffolding/controller.cs.hbs +5 -1
- package/templates/skills/apex/SKILL.md +9 -3
- package/templates/skills/apex/_shared.md +49 -4
- package/templates/skills/{ralph-loop → apex}/references/core-seed-data.md +20 -11
- package/templates/skills/{ralph-loop → apex}/references/error-classification.md +2 -1
- package/templates/skills/apex/references/post-checks.md +463 -3
- package/templates/skills/apex/references/smartstack-api.md +76 -8
- package/templates/skills/apex/references/smartstack-frontend.md +74 -1
- package/templates/skills/apex/references/smartstack-layers.md +21 -3
- package/templates/skills/apex/steps/step-00-init.md +121 -1
- package/templates/skills/apex/steps/step-01-analyze.md +58 -0
- package/templates/skills/apex/steps/step-02-plan.md +36 -0
- package/templates/skills/apex/steps/step-03-execute.md +114 -7
- package/templates/skills/apex/steps/step-04-examine.md +116 -2
- package/templates/skills/business-analyse/SKILL.md +31 -20
- package/templates/skills/business-analyse/_module-loop.md +68 -9
- package/templates/skills/business-analyse/_shared.md +80 -21
- package/templates/skills/business-analyse/questionnaire/00-application.md +4 -2
- package/templates/skills/business-analyse/questionnaire/00b-project.md +85 -0
- package/templates/skills/business-analyse/references/deploy-modes.md +69 -0
- package/templates/skills/business-analyse/references/team-orchestration.md +158 -7
- package/templates/skills/business-analyse/schemas/application-schema.json +15 -1
- package/templates/skills/business-analyse/schemas/project-schema.json +490 -0
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +2 -1
- package/templates/skills/business-analyse/steps/step-00-init.md +220 -38
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +184 -5
- package/templates/skills/business-analyse/steps/step-01b-applications.md +423 -0
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +23 -6
- package/templates/skills/business-analyse/steps/step-03c-compile.md +14 -2
- package/templates/skills/business-analyse/steps/step-03d-validate.md +32 -7
- package/templates/skills/business-analyse/steps/step-04a-collect.md +111 -0
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +296 -103
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +46 -14
- package/templates/skills/documentation/SKILL.md +92 -2
- package/templates/skills/ralph-loop/SKILL.md +14 -17
- package/templates/skills/ralph-loop/references/category-rules.md +63 -683
- package/templates/skills/ralph-loop/references/compact-loop.md +188 -428
- package/templates/skills/ralph-loop/references/section-splitting.md +439 -0
- package/templates/skills/ralph-loop/references/team-orchestration.md +13 -14
- package/templates/skills/ralph-loop/steps/step-01-task.md +27 -0
- package/templates/skills/ralph-loop/steps/step-02-execute.md +80 -691
- package/templates/skills/ralph-loop/steps/step-03-commit.md +38 -79
- package/templates/skills/ralph-loop/steps/step-04-check.md +39 -58
- package/templates/skills/ralph-loop/steps/step-05-report.md +31 -123
- package/scripts/health-check.sh +0 -168
- package/scripts/postinstall.js +0 -18
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Compact Loop —
|
|
1
|
+
# Compact Loop — Delegate to /apex
|
|
2
2
|
|
|
3
3
|
> **Loaded by:** step-04 section 5 (after first full iteration)
|
|
4
|
-
> **Purpose:** Execute ALL remaining tasks autonomously
|
|
4
|
+
> **Purpose:** Execute ALL remaining tasks autonomously by delegating to /apex.
|
|
5
5
|
> **EXECUTION GUARANTEE:** This loop runs until ALL tasks are done, max iterations, or dead-end.
|
|
6
6
|
> **ABSOLUTE RULES:**
|
|
7
7
|
> - NEVER stop the loop
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
> - NEVER ask for confirmation
|
|
10
10
|
> - NEVER re-read step files
|
|
11
11
|
> - NEVER pause between iterations
|
|
12
|
+
> - NEVER generate code directly — delegate to /apex
|
|
12
13
|
> - Execute A → B → C → D → back to step-04 section 1 → repeat
|
|
13
14
|
|
|
14
15
|
---
|
|
@@ -22,12 +23,122 @@ Display compact progress:
|
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
25
|
-
##
|
|
26
|
+
## A0. Section-Split Mode (checked BEFORE standard batching)
|
|
26
27
|
|
|
27
28
|
```javascript
|
|
28
29
|
const prd = readJSON('.ralph/prd.json');
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
if (prd._sectionSplit?.enabled) {
|
|
32
|
+
// SECTION-SPLIT: Delegate per-phase instead of per-category batch
|
|
33
|
+
|
|
34
|
+
// Find next pending phase with dependencies met
|
|
35
|
+
const nextPhase = prd._sectionSplit.phases.find(p => p.status === 'pending');
|
|
36
|
+
|
|
37
|
+
if (!nextPhase) {
|
|
38
|
+
// All phases done — check if master PRD tasks are all complete
|
|
39
|
+
goto CHECK_COMPLETION;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const depsOk = nextPhase.dependsOn.every(depIdx =>
|
|
43
|
+
prd._sectionSplit.phases[depIdx].status === 'completed'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!depsOk) {
|
|
47
|
+
// Phase blocked — should not happen with topological sort
|
|
48
|
+
console.warn(`Phase ${nextPhase.phase} blocked — dependencies incomplete`);
|
|
49
|
+
goto CHECK_COMPLETION;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const phaseLabel = nextPhase.type === 'foundation'
|
|
53
|
+
? `Phase 0: Foundation (${nextPhase.entities.length} entities + migration)`
|
|
54
|
+
: `Phase ${nextPhase.phase}: Section "${nextPhase.sectionCode}" (${nextPhase.entities.length} entities)`;
|
|
55
|
+
console.log(`[{iteration}/{max}] SECTION SPLIT: ${phaseLabel}`);
|
|
56
|
+
|
|
57
|
+
// INVOKE /apex -d {nextPhase.prdFile}
|
|
58
|
+
// Apex sees a normal (smaller) PRD and executes normally
|
|
59
|
+
|
|
60
|
+
// After apex returns: merge results back into master PRD
|
|
61
|
+
// Read references/section-splitting.md sections 7-9
|
|
62
|
+
const phasePrd = readJSON(nextPhase.prdFile);
|
|
63
|
+
for (const phaseTask of phasePrd.tasks) {
|
|
64
|
+
const masterTask = prd.tasks.find(t => t.id === phaseTask.id);
|
|
65
|
+
if (masterTask) {
|
|
66
|
+
masterTask.status = phaseTask.status;
|
|
67
|
+
masterTask.completed_at = phaseTask.completed_at;
|
|
68
|
+
masterTask.commit_hash = phaseTask.commit_hash;
|
|
69
|
+
masterTask.files_changed = phaseTask.files_changed;
|
|
70
|
+
masterTask.error = phaseTask.error;
|
|
71
|
+
masterTask.validation = phaseTask.validation;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Handle phase failure
|
|
76
|
+
if (phasePrd.tasks.some(t => t.status === 'failed')) {
|
|
77
|
+
const retryCount = nextPhase._retryCount || 0;
|
|
78
|
+
if (retryCount < 2) {
|
|
79
|
+
nextPhase.status = 'pending';
|
|
80
|
+
nextPhase._retryCount = retryCount + 1;
|
|
81
|
+
console.log(`Phase ${nextPhase.phase} had failures — retry ${retryCount + 1}/2`);
|
|
82
|
+
// Reset failed tasks in phase PRD for retry
|
|
83
|
+
for (const task of phasePrd.tasks) {
|
|
84
|
+
if (task.status === 'failed') { task.status = 'pending'; task.error = null; }
|
|
85
|
+
}
|
|
86
|
+
writeJSON(nextPhase.prdFile, phasePrd);
|
|
87
|
+
} else {
|
|
88
|
+
nextPhase.status = 'failed';
|
|
89
|
+
console.error(`Phase ${nextPhase.phase} failed after 2 retries`);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
nextPhase.status = 'completed';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
prd._sectionSplit.currentPhase = nextPhase.phase;
|
|
96
|
+
writeJSON('.ralph/prd.json', prd);
|
|
97
|
+
|
|
98
|
+
// → Skip to section C (commit PRD state), then D (loop back)
|
|
99
|
+
goto COMMIT_PRD;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> **IMPORTANT:** When `_sectionSplit.enabled`, the standard category-based batching (section A below)
|
|
104
|
+
> is SKIPPED. The loop delegates per-phase to apex instead of per-category-batch.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## A. Find Eligible Tasks
|
|
109
|
+
|
|
110
|
+
> **Note:** This section is SKIPPED when `prd._sectionSplit?.enabled` (handled by A0 above).
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const MAX_RETRIES = 3;
|
|
114
|
+
|
|
115
|
+
// RETRY: Reset failed tasks to pending if retries remain (max 3 attempts per task)
|
|
116
|
+
for (const task of prd.tasks) {
|
|
117
|
+
if (task.status !== 'failed') continue;
|
|
118
|
+
const retryCount = task._retryCount || 0;
|
|
119
|
+
if (retryCount < MAX_RETRIES) {
|
|
120
|
+
task.status = 'pending';
|
|
121
|
+
task._retryCount = retryCount + 1;
|
|
122
|
+
task.error = null;
|
|
123
|
+
console.log(`RETRY [${task.id}] attempt ${retryCount + 1}/${MAX_RETRIES}: ${task.description.substring(0, 60)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Unblock tasks whose dependencies are no longer failed (due to retries above)
|
|
128
|
+
for (const task of prd.tasks) {
|
|
129
|
+
if (task.status !== 'blocked') continue;
|
|
130
|
+
const stillBlocked = task.dependencies.some(depId => {
|
|
131
|
+
const dep = prd.tasks.find(t => t.id === depId);
|
|
132
|
+
return dep && (dep.status === 'failed' || dep.status === 'blocked');
|
|
133
|
+
});
|
|
134
|
+
if (!stillBlocked) {
|
|
135
|
+
task.status = 'pending';
|
|
136
|
+
task.error = null;
|
|
137
|
+
console.log(`UNBLOCKED [${task.id}]: dependencies resolved`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Block pending tasks whose dependencies are still failed/blocked
|
|
31
142
|
for (const task of prd.tasks) {
|
|
32
143
|
if (task.status !== 'pending') continue;
|
|
33
144
|
const depsBlocked = task.dependencies.some(depId => {
|
|
@@ -37,6 +148,8 @@ for (const task of prd.tasks) {
|
|
|
37
148
|
if (depsBlocked) { task.status = 'blocked'; task.error = 'Blocked by failed dependency'; }
|
|
38
149
|
}
|
|
39
150
|
|
|
151
|
+
writeJSON('.ralph/prd.json', prd);
|
|
152
|
+
|
|
40
153
|
// Find ALL eligible tasks (dependencies met)
|
|
41
154
|
const eligible = prd.tasks.filter(task => {
|
|
42
155
|
if (task.status !== 'pending') return false;
|
|
@@ -63,463 +176,110 @@ Batch: {batch.length} [{firstCategory}] → {batch.map(t => `[${t.id}] ${t.descr
|
|
|
63
176
|
|
|
64
177
|
---
|
|
65
178
|
|
|
66
|
-
## B.
|
|
67
|
-
|
|
68
|
-
**For EACH task in batch:**
|
|
69
|
-
|
|
70
|
-
1. Mark `task.status = 'in_progress'`, `task.started_at = now`
|
|
71
|
-
2. **Read `references/category-rules.md`** for category-specific rules
|
|
72
|
-
2bis. **For frontend tasks:** Also read back the original **feature.json** (via `prd.source.featurePath`) to access wireframe `componentMapping`, `columnDefs`, `layout`, `emptyState`, `rowActions`, and `i18n` keys that the PRD does NOT carry forward. The PRD only has the file list — the behavioral spec is in feature.json.
|
|
73
|
-
3. Implement the task following SmartStack conventions
|
|
74
|
-
4. Track `files_created` and `files_modified`
|
|
75
|
-
|
|
76
|
-
**Execution cycle per task:**
|
|
77
|
-
1. Generate code following category-rules.md
|
|
78
|
-
2. **BUILD verification (BLOCKING per task):**
|
|
79
|
-
- Backend (domain/infra/app/api): `dotnet build --no-restore` → MUST exit 0
|
|
80
|
-
- Frontend: `npm run typecheck` → MUST exit 0
|
|
81
|
-
3. If build fails → **CLASSIFY ERROR FIRST** (read `references/error-classification.md`):
|
|
82
|
-
- **Category A/B (assembly/package):** Run `dotnet add package {Name}` → `dotnet restore` → rebuild. Do NOT edit source code.
|
|
83
|
-
- **Category C (DI):** Edit DI registration file only → rebuild.
|
|
84
|
-
- **Category D (migration):** Run migration commands → rebuild.
|
|
85
|
-
- **Category E (config):** Edit config file → rebuild.
|
|
86
|
-
- **Category F (code):** Fix source code → rebuild → loop until pass.
|
|
87
|
-
- **If 2+ rebuild attempts fail on same error and Category F was chosen:** Re-classify — likely wrong category.
|
|
88
|
-
4. **ARTIFACT verification (BLOCKING per task):**
|
|
89
|
-
- `files_created` MUST NOT be empty for code-generating tasks
|
|
90
|
-
- Category-specific checks (see below)
|
|
91
|
-
- IF no artifacts produced → task.status = 'failed', task.error = 'No artifacts created'
|
|
92
|
-
5. Verify acceptance criteria from task description
|
|
93
|
-
6. If failed after 3 attempts: `task.status = 'failed'`, `task.error = reason`, continue to next in batch
|
|
94
|
-
|
|
95
|
-
**Category-specific artifact checks (step 4 above):**
|
|
96
|
-
|
|
97
|
-
| Category | Artifact Check | FAIL if |
|
|
98
|
-
|----------|---------------|---------|
|
|
99
|
-
| `domain` | Entity .cs files exist in Domain/ | 0 files created |
|
|
100
|
-
| `infrastructure` with migration | `Migrations/` folder has .cs files | No migration files |
|
|
101
|
-
| `infrastructure` with seed data | Seed data .cs files exist | 0 files created |
|
|
102
|
-
| `application` | Service + DTO + Validator files exist | 0 files created |
|
|
103
|
-
| `api` | Controller .cs files exist + GetAll supports `?search=` | 0 files created OR GetAll without search param |
|
|
104
|
-
| `frontend` | .tsx page files exist + FK fields use EntityLookup (not plain text) | 0 files created OR FK Guid field as `<input type="text">` |
|
|
105
|
-
| `test` | Test project dir exists AND contains test .cs files | No test project or 0 test files |
|
|
106
|
-
| `validation` | `dotnet test` exit 0 AND `dotnet build` exit 0 | Either command fails |
|
|
107
|
-
|
|
108
|
-
**Category-specific triggers:**
|
|
109
|
-
|
|
110
|
-
| Category | Action |
|
|
111
|
-
|----------|--------|
|
|
112
|
-
| `infrastructure` with `_migrationMeta` | Migration sequence: `suggest_migration` MCP → `dotnet ef migrations add` → cleanup corrupted artifacts (`for d in src/*/bin?Debug; do [ -d "$d" ] && rm -rf "$d"; done`) → `dotnet ef database update` → `dotnet build` |
|
|
113
|
-
| `infrastructure` with seed data keywords | **MANDATORY:** Read `references/core-seed-data.md` → implement templates → `dotnet build` |
|
|
114
|
-
| `frontend` | MCP-first: `scaffold_api_client` → `scaffold_routes` (outputFormat: clientRoutes) → **wire to App.tsx** → **LOAD `/ui-components` skill** before creating ANY page → create pages using skill patterns (SmartTable, EntityCard, CSS variables, StatusBadge) → ALL visible text via `t()` from first line → `npm run typecheck && npm run lint` → **run frontend POST-CHECKs (section 3bis below)** |
|
|
115
|
-
| `test` | **Create tests:** `scaffold_tests` MCP → **Run:** `dotnet test --verbosity normal` → **Fix loop:** if fail → fix SOURCE CODE → rebuild → retest → repeat until 100% pass |
|
|
116
|
-
| `validation` | `dotnet clean && dotnet restore && dotnet build` → `dotnet test` (full) → `validate_conventions` MCP |
|
|
117
|
-
|
|
118
|
-
**Post-batch verification (BLOCKING — ALL must pass before commit):**
|
|
119
|
-
|
|
120
|
-
1. **Backend build:**
|
|
121
|
-
```bash
|
|
122
|
-
dotnet build --no-restore
|
|
123
|
-
```
|
|
124
|
-
If FAIL → fix → rebuild → DO NOT commit until exit 0
|
|
125
|
-
|
|
126
|
-
2. **Tests (if test tasks in batch OR after infrastructure batch):**
|
|
127
|
-
```bash
|
|
128
|
-
TEST_PROJECT="tests/$(basename $(pwd)).Tests.Unit"
|
|
129
|
-
dotnet test "$TEST_PROJECT" --no-build --verbosity normal
|
|
130
|
-
```
|
|
131
|
-
If FAIL → fix source code (NOT tests) → rebuild → retest → loop until 100% pass
|
|
132
|
-
|
|
133
|
-
2bis. **Runtime assembly check (if api/validation tasks in batch):**
|
|
134
|
-
```bash
|
|
135
|
-
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
136
|
-
if [ -n "$API_PROJECT" ]; then
|
|
137
|
-
dotnet run --project "$API_PROJECT" --urls "http://localhost:5098" > /tmp/ralph-startup-check.log 2>&1 &
|
|
138
|
-
CHECK_PID=$!
|
|
139
|
-
sleep 5
|
|
140
|
-
if ! kill -0 $CHECK_PID 2>/dev/null; then
|
|
141
|
-
CRASH_OUTPUT=$(cat /tmp/ralph-startup-check.log 2>/dev/null)
|
|
142
|
-
echo "RUNTIME ASSEMBLY ERROR DETECTED"
|
|
143
|
-
# Classify per references/error-classification.md
|
|
144
|
-
# If Category A: dotnet add package → rebuild → DO NOT count as iteration failure
|
|
145
|
-
fi
|
|
146
|
-
kill $CHECK_PID 2>/dev/null
|
|
147
|
-
wait $CHECK_PID 2>/dev/null
|
|
148
|
-
rm -f /tmp/ralph-startup-check.log
|
|
149
|
-
fi
|
|
150
|
-
```
|
|
151
|
-
If FAIL → classify error per `references/error-classification.md` → apply fix → rebuild → verify again
|
|
152
|
-
|
|
153
|
-
2ter. **DI completeness (if application tasks in batch):**
|
|
154
|
-
Verify `Application/DependencyInjection.cs` registers all validators and services:
|
|
155
|
-
- Count validators in `Validators/` folder
|
|
156
|
-
- Verify `AddValidatorsFromAssemblyContaining` is present
|
|
157
|
-
- If empty/TODO → FAIL → fix DI registration → rebuild
|
|
158
|
-
|
|
159
|
-
3. **Frontend (if frontend/i18n tasks in batch):**
|
|
160
|
-
```bash
|
|
161
|
-
npm run typecheck && npm run lint
|
|
162
|
-
```
|
|
163
|
-
If FAIL → fix → re-check → loop until pass
|
|
164
|
-
|
|
165
|
-
3bis. **Frontend POST-CHECKs (BLOCKING — if frontend tasks in batch):**
|
|
166
|
-
|
|
167
|
-
> **CRITICAL:** These checks enforce /ui-components compliance, i18n, and route conventions.
|
|
168
|
-
> Without them, generated code silently regresses to raw HTML tables, hardcoded colors, and English-only text.
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
# Resolve paths dynamically (handles web/ prefix)
|
|
172
|
-
PAGE_DIR=$(find . -path "*/src/pages" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
173
|
-
HOOK_DIR=$(find . -path "*/src/hooks" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
174
|
-
COMP_DIR=$(find . -path "*/src/components" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
175
|
-
WEB_SRC=$(find . -name "App.tsx" -not -path "*/node_modules/*" -exec dirname {} \; 2>/dev/null | head -1)
|
|
176
|
-
|
|
177
|
-
if [ -n "$PAGE_DIR" ]; then
|
|
178
|
-
# BLOCKING: No raw HTML <table> — MUST use SmartTable/DataTable
|
|
179
|
-
RAW_TABLES=$(grep -rPn '<table[\s>]|<thead[\s>]|<tbody[\s>]' "$PAGE_DIR" --include="*.tsx" 2>/dev/null)
|
|
180
|
-
if [ -n "$RAW_TABLES" ]; then
|
|
181
|
-
echo "BLOCKING: Raw HTML <table> detected — MUST use SmartTable or DataTable"
|
|
182
|
-
echo "$RAW_TABLES" | head -5
|
|
183
|
-
# FIX REQUIRED before commit
|
|
184
|
-
fi
|
|
185
|
-
|
|
186
|
-
# BLOCKING: No hardcoded Tailwind colors — MUST use CSS variables
|
|
187
|
-
HARDCODED_COLORS=$(grep -rPn '(?:bg|text|border|ring)-(?:red|blue|green|yellow|orange|purple|pink|indigo|teal|cyan|emerald|violet|fuchsia|rose|amber|lime|sky|slate|gray|zinc|neutral|stone)-\d{2,3}' "$PAGE_DIR" "$COMP_DIR" --include="*.tsx" 2>/dev/null | head -10)
|
|
188
|
-
if [ -n "$HARDCODED_COLORS" ]; then
|
|
189
|
-
echo "BLOCKING: Hardcoded Tailwind colors — MUST use CSS variables (bg-[var(--bg-secondary)])"
|
|
190
|
-
echo "$HARDCODED_COLORS" | head -5
|
|
191
|
-
# FIX REQUIRED before commit
|
|
192
|
-
fi
|
|
193
|
-
|
|
194
|
-
# BLOCKING: No window.confirm() — use ConfirmDialog component
|
|
195
|
-
BAD_CONFIRM=$(grep -rPn 'window\.confirm\s*\(' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | grep -v '//')
|
|
196
|
-
if [ -n "$BAD_CONFIRM" ]; then
|
|
197
|
-
echo "BLOCKING: window.confirm() detected — use ConfirmDialog component with i18n"
|
|
198
|
-
echo "$BAD_CONFIRM"
|
|
199
|
-
# FIX REQUIRED before commit
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
# BLOCKING: No hardcoded English UI text in JSX
|
|
203
|
-
HARDCODED_TEXT=$(grep -rPn '>\s*(Create|Edit|Delete|Save|Cancel|Search|Loading|Error|No .* found|Are you sure|Submit|Back|Actions|Status)\s*<' "$PAGE_DIR" --include="*.tsx" 2>/dev/null | grep -v test | grep -v '//' | head -10)
|
|
204
|
-
if [ -n "$HARDCODED_TEXT" ]; then
|
|
205
|
-
echo "BLOCKING: Hardcoded English text — ALL visible text MUST use t() from useTranslation()"
|
|
206
|
-
echo "$HARDCODED_TEXT" | head -5
|
|
207
|
-
# FIX REQUIRED before commit
|
|
208
|
-
fi
|
|
209
|
-
|
|
210
|
-
# BLOCKING: No modals/drawers for forms — forms are full pages
|
|
211
|
-
MODAL_IMPORTS=$(grep -rPn "import.*\b(Modal|Dialog|Drawer|Popup|Sheet)\b" "$PAGE_DIR" --include="*.tsx" 2>/dev/null)
|
|
212
|
-
if [ -n "$MODAL_IMPORTS" ]; then
|
|
213
|
-
echo "BLOCKING: Form modals/dialogs detected — forms MUST be full pages with own URL"
|
|
214
|
-
echo "$MODAL_IMPORTS"
|
|
215
|
-
# FIX REQUIRED before commit
|
|
216
|
-
fi
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
if [ -n "$HOOK_DIR" ]; then
|
|
220
|
-
# BLOCKING: Hooks must use i18n for error messages
|
|
221
|
-
HARDCODED_ERRORS=$(grep -rPn "(setError|throw new Error)\(['\"][A-Z]" "$HOOK_DIR" --include="*.ts" --include="*.tsx" 2>/dev/null | grep -v "t(" | head -10)
|
|
222
|
-
if [ -n "$HARDCODED_ERRORS" ]; then
|
|
223
|
-
echo "BLOCKING: Hardcoded error messages in hooks — MUST use t('{mod}:errors.xxx')"
|
|
224
|
-
echo "$HARDCODED_ERRORS" | head -5
|
|
225
|
-
# FIX REQUIRED before commit
|
|
226
|
-
fi
|
|
227
|
-
fi
|
|
228
|
-
|
|
229
|
-
# BLOCKING: i18n file structure — 4 languages with module-specific JSON files
|
|
230
|
-
if [ -n "$WEB_SRC" ]; then
|
|
231
|
-
I18N_DIR="$WEB_SRC/i18n/locales"
|
|
232
|
-
if [ -d "$I18N_DIR" ]; then
|
|
233
|
-
for LANG in fr en it de; do
|
|
234
|
-
if [ ! -d "$I18N_DIR/$LANG" ]; then
|
|
235
|
-
echo "BLOCKING: Missing i18n locale directory: $I18N_DIR/$LANG"
|
|
236
|
-
# FIX REQUIRED before commit
|
|
237
|
-
fi
|
|
238
|
-
MODULE_JSONS=$(find "$I18N_DIR/$LANG" -name "*.json" ! -name "common.json" ! -name "navigation.json" 2>/dev/null)
|
|
239
|
-
if [ -z "$MODULE_JSONS" ]; then
|
|
240
|
-
echo "BLOCKING: No module translation files in $I18N_DIR/$LANG/"
|
|
241
|
-
# FIX REQUIRED before commit
|
|
242
|
-
fi
|
|
243
|
-
done
|
|
244
|
-
else
|
|
245
|
-
echo "BLOCKING: Missing i18n/locales directory"
|
|
246
|
-
# FIX REQUIRED before commit
|
|
247
|
-
fi
|
|
248
|
-
fi
|
|
249
|
-
|
|
250
|
-
# Cross-validate route conventions (frontend vs backend)
|
|
251
|
-
APP_TSX=$(find . -name "App.tsx" -not -path "*/node_modules/*" 2>/dev/null | head -1)
|
|
252
|
-
API_CONTROLLERS=$(find . -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
253
|
-
if [ -n "$APP_TSX" ] && [ -n "$API_CONTROLLERS" ]; then
|
|
254
|
-
API_SEGMENTS=$(grep -ohP '\[Route\("api/([^"]+)"\)\]' $API_CONTROLLERS | sed 's/\[Route("api\///;s/")\]//' | tr '/' '\n' | sort -u)
|
|
255
|
-
CLIENT_PATHS=$(grep -ohP "path:\s*['\"]([^'\"]+)['\"]" "$APP_TSX" | sed "s/path:\s*['\"]//;s/['\"]//" | sort -u)
|
|
256
|
-
for SEG in $API_SEGMENTS; do
|
|
257
|
-
if echo "$SEG" | grep -qP '[a-z]+-[a-z]+'; then
|
|
258
|
-
NO_HYPHEN=$(echo "$SEG" | tr -d '-')
|
|
259
|
-
if echo "$CLIENT_PATHS" | grep -q "$NO_HYPHEN"; then
|
|
260
|
-
echo "BLOCKING: Frontend uses '$NO_HYPHEN' but backend uses '$SEG' (kebab-case mismatch)"
|
|
261
|
-
# FIX REQUIRED before commit
|
|
262
|
-
fi
|
|
263
|
-
fi
|
|
264
|
-
done
|
|
265
|
-
fi
|
|
266
|
-
```
|
|
267
|
-
# BLOCKING: No custom entity hooks wrapping services (test-v4-014 root cause)
|
|
268
|
-
if [ -n "$HOOK_DIR" ]; then
|
|
269
|
-
CUSTOM_HOOKS=$(find "$HOOK_DIR" -name "use*.ts" -o -name "use*.tsx" 2>/dev/null | grep -iv "Preferences" | grep -v "node_modules")
|
|
270
|
-
if [ -n "$CUSTOM_HOOKS" ]; then
|
|
271
|
-
echo "BLOCKING: Custom entity hooks detected — pages MUST call services directly"
|
|
272
|
-
echo "$CUSTOM_HOOKS"
|
|
273
|
-
echo "Pattern: page useCallback → service.getAll() → setData(result.items)"
|
|
274
|
-
echo "FORBIDDEN: useEmployees(), useProjects() etc. wrapping services with useState/useEffect"
|
|
275
|
-
# FIX REQUIRED: delete custom hooks, move API calls into page components
|
|
276
|
-
fi
|
|
277
|
-
fi
|
|
278
|
-
|
|
279
|
-
# BLOCKING: No direct array types in services — MUST use PaginatedResult<T>
|
|
280
|
-
if [ -n "$WEB_SRC" ]; then
|
|
281
|
-
SVC_DIR=$(find "$WEB_SRC" -path "*/services" -not -path "*/node_modules/*" -type d 2>/dev/null | head -1)
|
|
282
|
-
if [ -n "$SVC_DIR" ]; then
|
|
283
|
-
DIRECT_ARRAYS=$(grep -rPn 'api\.get<[A-Z]\w+\[\]>' "$SVC_DIR" --include="*.ts" 2>/dev/null)
|
|
284
|
-
if [ -n "$DIRECT_ARRAYS" ]; then
|
|
285
|
-
echo "BLOCKING: Direct array response types — MUST use PaginatedResult<T>"
|
|
286
|
-
echo "$DIRECT_ARRAYS"
|
|
287
|
-
echo "Pattern: api.get<PaginatedResult<Employee>>(url) then .items in page"
|
|
288
|
-
# FIX REQUIRED before commit
|
|
289
|
-
fi
|
|
290
|
-
fi
|
|
291
|
-
fi
|
|
292
|
-
|
|
293
|
-
# BLOCKING: lazy() imports MUST have Suspense boundary in App.tsx
|
|
294
|
-
if [ -n "$APP_TSX" ]; then
|
|
295
|
-
HAS_LAZY=$(grep -c 'lazy(' "$APP_TSX" 2>/dev/null)
|
|
296
|
-
HAS_SUSPENSE=$(grep -c 'Suspense' "$APP_TSX" 2>/dev/null)
|
|
297
|
-
if [ "$HAS_LAZY" -gt 0 ] && [ "$HAS_SUSPENSE" -eq 0 ]; then
|
|
298
|
-
echo "BLOCKING: lazy() imports without <Suspense> boundary in App.tsx"
|
|
299
|
-
echo "Every lazy() page MUST be wrapped: <Suspense fallback={<PageLoader />}>"
|
|
300
|
-
# FIX REQUIRED before commit
|
|
301
|
-
fi
|
|
302
|
-
fi
|
|
303
|
-
|
|
304
|
-
If ANY POST-CHECK fails → fix the code → re-run ALL checks → loop until ALL pass.
|
|
305
|
-
**NEVER commit with failing POST-CHECKs.**
|
|
306
|
-
|
|
307
|
-
3ter. **Dependency audit (if frontend tasks in batch):**
|
|
308
|
-
```bash
|
|
309
|
-
cd {frontend_dir}
|
|
310
|
-
npm ls --depth=0 2>&1 | grep "MISSING" && echo "FAIL: missing deps" && exit 1
|
|
311
|
-
```
|
|
312
|
-
If FAIL → `npm install {missing_packages}` → re-check → loop until pass
|
|
313
|
-
|
|
314
|
-
4. **MCP validation:**
|
|
315
|
-
`mcp__smartstack__validate_conventions` ONCE for the whole batch
|
|
316
|
-
|
|
317
|
-
5. **DB validation (if infrastructure/migration tasks in batch):**
|
|
318
|
-
```bash
|
|
319
|
-
# Quick check: pending model changes after batch?
|
|
320
|
-
INFRA_PROJECT=$(ls src/*Infrastructure*/*.csproj 2>/dev/null | head -1)
|
|
321
|
-
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
322
|
-
if [ -n "$INFRA_PROJECT" ] && [ -n "$API_PROJECT" ]; then
|
|
323
|
-
dotnet ef migrations has-pending-model-changes \
|
|
324
|
-
--project "$INFRA_PROJECT" \
|
|
325
|
-
--startup-project "$API_PROJECT"
|
|
326
|
-
if [ $? -ne 0 ]; then
|
|
327
|
-
echo "MIGRATION MISSING: Model has uncommitted changes"
|
|
328
|
-
# Create fix task for missing migration
|
|
329
|
-
fi
|
|
330
|
-
|
|
331
|
-
# If batch included migration tasks: verify migration applies on real SQL Server
|
|
332
|
-
DB_NAME="SmartStack_Ralph_Check_${iteration}"
|
|
333
|
-
CONN_STRING="Server=(localdb)\\MSSQLLocalDB;Database=$DB_NAME;Integrated Security=true;TrustServerCertificate=true;Connect Timeout=120;"
|
|
334
|
-
dotnet ef database update \
|
|
335
|
-
--connection "$CONN_STRING" \
|
|
336
|
-
--project "$INFRA_PROJECT" \
|
|
337
|
-
--startup-project "$API_PROJECT"
|
|
338
|
-
if [ $? -ne 0 ]; then
|
|
339
|
-
echo "MIGRATION BROKEN: Cannot apply migrations on SQL Server"
|
|
340
|
-
# Fix migration → rebuild → DO NOT commit until this passes
|
|
341
|
-
fi
|
|
342
|
-
|
|
343
|
-
# Cleanup temp database
|
|
344
|
-
sqlcmd -S "(localdb)\MSSQLLocalDB" -Q "IF DB_ID('$DB_NAME') IS NOT NULL BEGIN ALTER DATABASE [$DB_NAME] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [$DB_NAME]; END" 2>/dev/null
|
|
345
|
-
fi
|
|
346
|
-
```
|
|
347
|
-
If FAIL → migration is broken → fix → rebuild → DO NOT commit
|
|
348
|
-
|
|
349
|
-
5bis. **Navigation section route CRUD suffix check (BLOCKING — if seed data tasks in batch):**
|
|
350
|
-
```bash
|
|
351
|
-
# Detect /list or /detail/:id in navigation section routes
|
|
352
|
-
SEED_NAV_FILES=$(find src/ -path "*/Seeding/Data/*" -name "*NavigationSeedData.cs" 2>/dev/null)
|
|
353
|
-
if [ -n "$SEED_NAV_FILES" ]; then
|
|
354
|
-
CRUD_ROUTES=$(grep -rn 'Route.*=.*\/list\b\|Route.*=.*\/detail' $SEED_NAV_FILES 2>/dev/null | grep -v '//.*Route')
|
|
355
|
-
if [ -n "$CRUD_ROUTES" ]; then
|
|
356
|
-
echo "BLOCKING: Navigation section routes contain /list or /detail/:id suffixes"
|
|
357
|
-
echo "Convention: list → module route (no suffix), detail → module route + /:id"
|
|
358
|
-
echo "$CRUD_ROUTES"
|
|
359
|
-
# FIX REQUIRED before commit
|
|
360
|
-
fi
|
|
361
|
-
fi
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
5ter. **Core Seed Data Integrity (BLOCKING — if seed data tasks in batch):**
|
|
365
|
-
```bash
|
|
366
|
-
# Verify NavigationApplicationSeedData.cs exists
|
|
367
|
-
APP_SEED=$(find . -path "*/Seeding/Data/NavigationApplicationSeedData.cs" 2>/dev/null | head -1)
|
|
368
|
-
if [ -z "$APP_SEED" ]; then
|
|
369
|
-
echo "❌ BLOCKING: NavigationApplicationSeedData.cs NOT FOUND"
|
|
370
|
-
echo "Without this: nav_Applications empty → menu invisible → modules inaccessible"
|
|
371
|
-
echo "Fix: Generate from core-seed-data.md section 1b"
|
|
372
|
-
# Create fix task
|
|
373
|
-
fi
|
|
374
|
-
|
|
375
|
-
# Verify no hardcoded placeholders in provider
|
|
376
|
-
PROVIDER=$(find . -path "*/Seeding/*SeedDataProvider.cs" 2>/dev/null | head -1)
|
|
377
|
-
if [ -n "$PROVIDER" ]; then
|
|
378
|
-
if grep -qE '\{appLabel_|\{appDesc_|\{appIcon\}|\{ApplicationGuid\}' "$PROVIDER" 2>/dev/null; then
|
|
379
|
-
echo "❌ BLOCKING: SeedDataProvider has unresolved placeholders"
|
|
380
|
-
echo "Fix: Use NavigationApplicationSeedData.GetApplicationEntry()"
|
|
381
|
-
fi
|
|
382
|
-
fi
|
|
383
|
-
|
|
384
|
-
# Verify ApplicationRolesSeedData references real GUID
|
|
385
|
-
ROLES_SEED=$(find . -path "*/Seeding/Data/ApplicationRolesSeedData.cs" 2>/dev/null | head -1)
|
|
386
|
-
if [ -n "$ROLES_SEED" ]; then
|
|
387
|
-
if grep -q '{ApplicationGuid}' "$ROLES_SEED" 2>/dev/null; then
|
|
388
|
-
echo "❌ BLOCKING: ApplicationRolesSeedData still has {ApplicationGuid} placeholder"
|
|
389
|
-
echo "Fix: Replace with NavigationApplicationSeedData.ApplicationId"
|
|
390
|
-
fi
|
|
391
|
-
fi
|
|
392
|
-
|
|
393
|
-
# Quick startup test to verify seed data runs without exceptions
|
|
394
|
-
API_PROJECT=$(ls src/*Api*/*.csproj 2>/dev/null | head -1)
|
|
395
|
-
if [ -n "$API_PROJECT" ] && [ -n "$APP_SEED" ]; then
|
|
396
|
-
echo "Running seed data startup test..."
|
|
397
|
-
dotnet run --project "$API_PROJECT" --urls "http://localhost:0" -- --environment Development > /tmp/ralph-seed-check.log 2>&1 &
|
|
398
|
-
SEED_PID=$!
|
|
399
|
-
sleep 10
|
|
400
|
-
if ! kill -0 $SEED_PID 2>/dev/null; then
|
|
401
|
-
SEED_LOG=$(cat /tmp/ralph-seed-check.log 2>/dev/null | tail -20)
|
|
402
|
-
echo "❌ BLOCKING: Application crashed during startup — seed data likely failed"
|
|
403
|
-
echo "Last 20 lines: $SEED_LOG"
|
|
404
|
-
# Do NOT continue to frontend/test — fix seed data first
|
|
405
|
-
else
|
|
406
|
-
echo "✓ Seed data startup test passed"
|
|
407
|
-
kill $SEED_PID 2>/dev/null
|
|
408
|
-
wait $SEED_PID 2>/dev/null
|
|
409
|
-
fi
|
|
410
|
-
rm -f /tmp/ralph-seed-check.log
|
|
411
|
-
fi
|
|
412
|
-
```
|
|
413
|
-
If FAIL → fix seed data → rebuild → DO NOT continue to frontend/test tasks
|
|
414
|
-
|
|
415
|
-
**Error resolution cycle:**
|
|
416
|
-
1. Read the FULL error output (not just first line)
|
|
417
|
-
2. Identify root cause: file path + line number
|
|
418
|
-
3. Fix the issue in source code
|
|
419
|
-
4. Rebuild/retest
|
|
420
|
-
5. If still failing → repeat from step 1
|
|
421
|
-
6. **NEVER** commit with failing build or tests
|
|
179
|
+
## B. Delegate Batch to /apex
|
|
422
180
|
|
|
423
|
-
|
|
181
|
+
> **Ralph NEVER generates code.** All code generation, MCP calls, POST-CHECKs,
|
|
182
|
+
> and build verification are handled by apex.
|
|
183
|
+
|
|
184
|
+
### B1. Mark Batch In-Progress
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
for (const task of batch) {
|
|
188
|
+
task.status = 'in_progress';
|
|
189
|
+
task.started_at = new Date().toISOString();
|
|
190
|
+
}
|
|
191
|
+
writeJSON('.ralph/prd.json', prd);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### B2. Invoke /apex
|
|
195
|
+
|
|
196
|
+
**INVOKE `/apex -d .ralph/prd.json`**
|
|
424
197
|
|
|
425
|
-
|
|
198
|
+
Apex handles everything for the current module:
|
|
199
|
+
- Reads PRD, extracts context (context_code, app_name, module_code, entities, sections)
|
|
200
|
+
- Executes ALL layers: domain → infrastructure → migration → application → api → seed data → frontend → tests
|
|
201
|
+
- Runs full POST-CHECKs (50 checks from `references/post-checks.md`)
|
|
202
|
+
- Commits per layer (atomic commits)
|
|
203
|
+
- Validates with MCP (`validate_conventions`)
|
|
204
|
+
- Updates task statuses in the PRD file directly
|
|
426
205
|
|
|
427
|
-
|
|
206
|
+
> **FLAGS:** `-d` implies `-a` (auto, no user confirmation) and `-e` (economy, no nested teams).
|
|
428
207
|
|
|
429
|
-
|
|
430
|
-
> In test-v4-005, the compact loop executed 44 tasks but NEVER updated prd.json status.
|
|
431
|
-
> This broke ALL downstream guardrails (module completeness check never triggered).
|
|
208
|
+
### B3. Verify Post-Apex Results
|
|
432
209
|
|
|
433
210
|
```javascript
|
|
434
|
-
|
|
435
|
-
const
|
|
211
|
+
// Re-read PRD after apex execution
|
|
212
|
+
const updatedPrd = readJSON('.ralph/prd.json');
|
|
213
|
+
const batchIds = batch.map(t => t.id);
|
|
436
214
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
215
|
+
const completed = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'completed').length;
|
|
216
|
+
const failed = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'failed').length;
|
|
217
|
+
const pending = updatedPrd.tasks.filter(t => batchIds.includes(t.id) && t.status === 'pending').length;
|
|
218
|
+
|
|
219
|
+
if (failed > 0) {
|
|
220
|
+
console.log(`WARNING: ${failed} tasks failed during apex execution`);
|
|
221
|
+
// Failed tasks will be retried in next loop iteration if retries remain
|
|
442
222
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
223
|
+
|
|
224
|
+
if (pending > 0) {
|
|
225
|
+
console.log(`INFO: ${pending} tasks still pending — apex may not have reached them`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(`Apex completed: ${completed}/${batchIds.length} tasks`);
|
|
446
229
|
```
|
|
447
230
|
|
|
448
|
-
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## C. Commit PRD State
|
|
234
|
+
|
|
235
|
+
### C1. Update Progress File (MANDATORY)
|
|
449
236
|
|
|
450
237
|
```javascript
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
const
|
|
238
|
+
const prdCheck = readJSON('.ralph/prd.json');
|
|
239
|
+
const totalCompleted = prdCheck.tasks.filter(t => t.status === 'completed').length;
|
|
240
|
+
const total = prdCheck.tasks.length;
|
|
454
241
|
|
|
455
|
-
// Append to progress.txt (NOT overwrite)
|
|
456
242
|
appendFile('.ralph/progress.txt',
|
|
457
|
-
`Iteration ${
|
|
458
|
-
`${
|
|
459
|
-
` Files: ${batch.reduce((acc, t) => acc + (t.files_changed?.created?.length || 0), 0)} created\n`
|
|
243
|
+
`Iteration ${prdCheck.config.current_iteration}: Delegated to /apex — ` +
|
|
244
|
+
`${completed}/${batchIds.length} batch tasks completed. ${totalCompleted}/${total} overall.\n`
|
|
460
245
|
);
|
|
461
246
|
```
|
|
462
247
|
|
|
463
|
-
###
|
|
248
|
+
### C2. Increment Iteration
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
prdCheck.config.current_iteration++;
|
|
252
|
+
prdCheck.updated_at = new Date().toISOString();
|
|
253
|
+
writeJSON('.ralph/prd.json', prdCheck);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### C3. Git Commit (PRD state only — apex already committed code)
|
|
464
257
|
|
|
465
258
|
```bash
|
|
466
|
-
# Stage all changed files from batch
|
|
467
|
-
git add {all_files_from_batch}
|
|
468
259
|
git add .ralph/prd.json .ralph/progress.txt
|
|
469
260
|
[ -f .ralph/modules-queue.json ] && git add .ralph/modules-queue.json
|
|
470
261
|
|
|
471
262
|
git commit -m "$(cat <<'EOF'
|
|
472
|
-
|
|
263
|
+
chore(ralph): update PRD state — iteration {iteration}
|
|
473
264
|
|
|
474
|
-
Tasks: {
|
|
475
|
-
|
|
476
|
-
{module ? "Module: " + module : ""}
|
|
265
|
+
Tasks: {completed}/{batch_size} completed via /apex
|
|
266
|
+
Overall: {totalCompleted}/{total}
|
|
477
267
|
|
|
478
268
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
479
269
|
EOF
|
|
480
270
|
)"
|
|
481
|
-
|
|
482
|
-
COMMIT_HASH=$(git rev-parse --short HEAD)
|
|
483
271
|
```
|
|
484
272
|
|
|
485
|
-
### C4.
|
|
273
|
+
### C4. PRD Sync Verification (HARD CHECK)
|
|
486
274
|
|
|
487
275
|
```javascript
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (task.commit_hash === 'pending-commit') task.commit_hash = COMMIT_HASH;
|
|
491
|
-
}
|
|
492
|
-
prd.history.push({
|
|
493
|
-
iteration: prd.config.current_iteration - 1,
|
|
494
|
-
task_ids: batch.map(t => t.id),
|
|
495
|
-
action: 'batch-completed',
|
|
496
|
-
timestamp: now,
|
|
497
|
-
commit_hash: COMMIT_HASH,
|
|
498
|
-
notes: "{summary}"
|
|
499
|
-
});
|
|
500
|
-
writeJSON('.ralph/prd.json', prd);
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
### C5. PRD Sync Verification (HARD CHECK)
|
|
504
|
-
|
|
505
|
-
> **MANDATORY:** Verify prd.json reflects reality before looping back.
|
|
276
|
+
const prdVerify = readJSON('.ralph/prd.json');
|
|
277
|
+
const actuallyCompleted = prdVerify.tasks.filter(t => batchIds.includes(t.id) && t.status === 'completed');
|
|
506
278
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (actuallyCompleted.length !== batch.filter(t => t.status !== 'failed').length) {
|
|
513
|
-
console.error('PRD SYNC ERROR: Tasks executed but not marked completed in prd.json');
|
|
514
|
-
console.error(`Expected: ${batch.filter(t => t.status !== 'failed').length} completed, Got: ${actuallyCompleted.length}`);
|
|
515
|
-
// Force re-write
|
|
516
|
-
for (const task of batch) {
|
|
517
|
-
const prdTask = prdCheck.tasks.find(t => t.id === task.id);
|
|
518
|
-
if (prdTask && task.status === 'completed') prdTask.status = 'completed';
|
|
519
|
-
if (prdTask) prdTask.commit_hash = COMMIT_HASH;
|
|
520
|
-
}
|
|
521
|
-
writeJSON('.ralph/prd.json', prdCheck);
|
|
522
|
-
console.log('PRD SYNC REPAIRED');
|
|
279
|
+
if (actuallyCompleted.length !== completed) {
|
|
280
|
+
console.error('PRD SYNC ERROR: Mismatch between apex results and prd.json');
|
|
281
|
+
// Force re-read — apex may have written after our read
|
|
282
|
+
// DO NOT overwrite — apex is the source of truth for task statuses
|
|
523
283
|
}
|
|
524
284
|
```
|
|
525
285
|
|