@atlashub/smartstack-cli 4.27.0 → 4.29.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.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/templates/skills/apex/references/core-seed-data.md +27 -4
  3. package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +29 -7
  4. package/templates/skills/apex/references/post-checks.md +324 -0
  5. package/templates/skills/apex/references/smartstack-frontend.md +23 -8
  6. package/templates/skills/apex/references/smartstack-layers.md +53 -6
  7. package/templates/skills/apex/steps/step-02-plan.md +9 -0
  8. package/templates/skills/apex/steps/step-03-execute.md +49 -3
  9. package/templates/skills/apex/steps/step-04-examine.md +4 -0
  10. package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +33 -0
  11. package/templates/skills/business-analyse/questionnaire/01-context.md +12 -12
  12. package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +45 -45
  13. package/templates/skills/business-analyse/questionnaire/03-data-ui.md +39 -39
  14. package/templates/skills/business-analyse/questionnaire/05-cross-module.md +32 -32
  15. package/templates/skills/business-analyse/questionnaire.md +11 -11
  16. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +17 -0
  17. package/templates/skills/business-analyse/references/spec-auto-inference.md +12 -7
  18. package/templates/skills/business-analyse/steps/step-00-init.md +2 -2
  19. package/templates/skills/business-analyse/steps/step-01-cadrage.md +3 -3
  20. package/templates/skills/business-analyse/steps/step-02-structure.md +22 -8
  21. package/templates/skills/business-analyse/steps/step-03-specify.md +22 -15
  22. package/templates/skills/controller/references/mcp-scaffold-workflow.md +20 -0
  23. package/templates/skills/derive-prd/references/handoff-file-templates.md +25 -1
  24. package/templates/skills/derive-prd/references/handoff-seeddata-generation.md +3 -1
  25. package/templates/skills/ralph-loop/references/category-completeness.md +125 -0
  26. package/templates/skills/ralph-loop/references/compact-loop.md +66 -10
  27. package/templates/skills/ralph-loop/references/module-transition.md +60 -0
  28. package/templates/skills/ralph-loop/steps/step-04-check.md +207 -12
  29. package/templates/skills/ralph-loop/steps/step-05-report.md +205 -14
@@ -27,6 +27,27 @@ if (fileExists(queuePath)) {
27
27
 
28
28
  // All categories present — module is complete
29
29
  // Continue below...
30
+
31
+ // HANDOFF FILE GATE (double-security — step-04 §1.8 is primary, this is defense-in-depth)
32
+ // The build gate does NOT detect missing files if nothing references them (no compile error).
33
+ const ftc = prd.implementation?.filesToCreate;
34
+ if (ftc) {
35
+ let ftcMissing = 0;
36
+ const ftcDetails = [];
37
+ for (const [cat, files] of Object.entries(ftc)) {
38
+ for (const file of (files || [])) {
39
+ if (!fileExists(file.path || file)) {
40
+ ftcMissing++;
41
+ ftcDetails.push(`${cat}: ${(file.path || file).split('/').pop()}`);
42
+ }
43
+ }
44
+ }
45
+ if (ftcMissing > 0) {
46
+ console.error(`BLOCKING: ${ftcMissing} handoff files missing — cannot advance module`);
47
+ ftcDetails.forEach(d => console.error(` ${d}`));
48
+ return; // Back to compact loop — remediation tasks already injected by category-completeness
49
+ }
50
+ }
30
51
  }
31
52
  ```
32
53
 
@@ -36,6 +57,36 @@ if (fileExists(queuePath)) {
36
57
 
37
58
  ```javascript
38
59
  if (missing.length === 0) {
60
+ // MANDATORY BUILD+TEST GATE before advancing module
61
+ // (audit ba-002: module marked "100% complete" despite build errors and security flaws)
62
+ const buildOk = execSync('dotnet build --no-restore --verbosity quiet').exitCode === 0;
63
+ if (!buildOk) {
64
+ console.error(`BLOCKING: Module ${currentModule.code} build fails — cannot advance to next module`);
65
+ const maxId = Math.max(...prd.tasks.map(t => parseInt(t.id.replace(/\D/g,''))||0));
66
+ prd.tasks.push({
67
+ id: `GATE-${maxId+1}`, description: `BLOCKING: Fix build for module ${currentModule.code}`,
68
+ status: 'pending', category: 'validation', dependencies: [],
69
+ acceptance_criteria: 'dotnet build passes with 0 errors'
70
+ });
71
+ writeJSON(currentPrdPath, prd);
72
+ return; // Back to compact loop — do NOT advance
73
+ }
74
+
75
+ const testOk = execSync('dotnet test --no-build --verbosity quiet').exitCode === 0;
76
+ if (!testOk) {
77
+ console.error(`BLOCKING: Module ${currentModule.code} tests fail — cannot advance to next module`);
78
+ const maxId = Math.max(...prd.tasks.map(t => parseInt(t.id.replace(/\D/g,''))||0));
79
+ prd.tasks.push({
80
+ id: `GATE-${maxId+1}`, description: `BLOCKING: Fix tests for module ${currentModule.code}`,
81
+ status: 'pending', category: 'validation', dependencies: [],
82
+ acceptance_criteria: 'dotnet test passes with 0 failures'
83
+ });
84
+ writeJSON(currentPrdPath, prd);
85
+ return; // Back to compact loop — do NOT advance
86
+ }
87
+
88
+ console.log(`MODULE GATE PASSED: ${currentModule.code} — build OK, tests OK`);
89
+
39
90
  // Mark current module done
40
91
  currentModule.status = 'completed';
41
92
  queue.completedModules++;
@@ -68,6 +119,15 @@ if (missing.length === 0) {
68
119
 
69
120
  console.log(`MODULE COMPLETE: ${currentModule.code} → NEXT: ${queue.modules[nextIndex].code}`);
70
121
 
122
+ // CONTEXT REFRESH: Force /compact between modules to prevent context degradation
123
+ // (audit ba-002: quality degraded progressively — module 2 had more errors than module 1
124
+ // because patterns from module 1 were lost during context compaction)
125
+ console.log('Forcing /compact for fresh context before next module...');
126
+ // The /compact command clears accumulated tool results and intermediate reasoning,
127
+ // preserving only key decisions and the current state. This ensures the next module
128
+ // starts with a clean context window instead of degraded fragments from the previous module.
129
+ // INVOKE /compact
130
+
71
131
  // Return to step-01 to load next module's tasks
72
132
  // (step-01 will detect module-changed.json and load next PRD)
73
133
  return GO_TO_STEP_01;
@@ -33,24 +33,80 @@ const tasksTotal = prd.tasks.length;
33
33
  const allDone = (tasksCompleted + tasksSkipped) === tasksTotal;
34
34
  ```
35
35
 
36
- ### 1.5. Lightweight Sanity Check
36
+ ### 1.5. Build & Test Gate (BLOCKING)
37
37
 
38
- > **Note:** Full build verification, POST-CHECKs, and test runs are handled by apex.
39
- > Ralph only does a lightweight sanity check to detect regressions between apex invocations.
38
+ > **LESSON LEARNED (audit ba-002):** A non-blocking sanity build allowed "92/92 COMPLETE" with
39
+ > code that had compilation errors and security flaws. The build gate MUST be BLOCKING.
40
40
 
41
41
  ```bash
42
- # Quick build check (quiet mode)
42
+ # STEP 1: Build gate (BLOCKING — must pass before ANY further iteration)
43
43
  dotnet build --no-restore --verbosity quiet
44
44
  BUILD_RC=$?
45
45
  # Note: WSL bin\Debug cleanup handled by PostToolUse hook (wsl-dotnet-cleanup.sh)
46
46
  if [ $BUILD_RC -ne 0 ]; then
47
- echo "BUILD REGRESSION detected between apex invocations"
48
- # Inject fix task — apex will handle the actual fix
49
- prd.tasks.push({ id: maxId+1, description: "Fix build regression",
47
+ echo "BLOCKING: BUILD FAILED cannot advance until fixed"
48
+ # Inject fix task with HIGH priority — apex will handle the actual fix
49
+ prd.tasks.push({ id: maxId+1, description: "BLOCKING: Fix build regression — dotnet build fails",
50
50
  status: "pending", category: "validation", dependencies: [],
51
- acceptance_criteria: "dotnet build passes" });
51
+ acceptance_criteria: "dotnet build passes with 0 errors" });
52
52
  writeJSON(currentPrdPath, prd);
53
+ # DO NOT mark module complete — force compact loop to fix first
53
54
  fi
55
+
56
+ # STEP 2: Test gate (BLOCKING — runs only if build passes)
57
+ if [ $BUILD_RC -eq 0 ]; then
58
+ dotnet test --no-build --verbosity quiet
59
+ TEST_RC=$?
60
+ if [ $TEST_RC -ne 0 ]; then
61
+ echo "BLOCKING: TESTS FAILED — cannot advance until fixed"
62
+ prd.tasks.push({ id: maxId+2, description: "BLOCKING: Fix test regression — dotnet test fails",
63
+ status: "pending", category: "validation", dependencies: [],
64
+ acceptance_criteria: "dotnet test passes with 0 failures" });
65
+ writeJSON(currentPrdPath, prd);
66
+ fi
67
+ fi
68
+ ```
69
+
70
+ ### 1.6. MCP Security Gate (post-module validation)
71
+
72
+ > **LESSON LEARNED (audit ba-002):** Apex POST-CHECKs validate structure (TenantId present,
73
+ > [RequirePermission] present) but miss SEMANTIC errors (Read permission on write endpoints,
74
+ > self-approval, cross-tenant FK references). This gate catches those.
75
+
76
+ ```javascript
77
+ // Run ONLY when all tasks for current module are complete (avoid wasting tokens mid-loop)
78
+ const moduleTasksDone = prd.tasks.filter(t => t.status === 'completed').length === prd.tasks.length;
79
+
80
+ if (moduleTasksDone && BUILD_RC === 0) {
81
+ // 1. Validate security (catches tenant isolation gaps, permission issues)
82
+ const securityResult = await mcp__smartstack__validate_security();
83
+
84
+ // 2. Review code quality (catches logic errors, exception inconsistencies)
85
+ const reviewResult = await mcp__smartstack__review_code();
86
+
87
+ // 3. SEMANTIC PERMISSION CHECK: write endpoints must not use Read permissions
88
+ // This check catches the ba-002 bug where Cancel (POST) used Permissions.*.Read
89
+ const permissionIssues = [];
90
+ for (const ctrlFile of glob('src/**/Controllers/**/*Controller.cs')) {
91
+ const content = readFile(ctrlFile);
92
+ // Find POST/PUT/DELETE endpoints with Read permission
93
+ const writeEndpoints = content.matchAll(/\[(HttpPost|HttpPut|HttpDelete|HttpPatch)[^\]]*\][^[]*\[RequirePermission\(([^\)]+)\)\]/gs);
94
+ for (const match of writeEndpoints) {
95
+ if (match[2].includes('.Read')) {
96
+ permissionIssues.push(`${ctrlFile}: ${match[1]} endpoint uses Read permission — should be Update/Delete/Create`);
97
+ }
98
+ }
99
+ }
100
+
101
+ if (permissionIssues.length > 0) {
102
+ console.error('BLOCKING: Write endpoints with Read permissions detected:');
103
+ permissionIssues.forEach(i => console.error(` ${i}`));
104
+ prd.tasks.push({ id: maxId+3, description: "BLOCKING: Fix permission mismatch — write endpoints using Read permission",
105
+ status: "pending", category: "validation", dependencies: [],
106
+ acceptance_criteria: "All POST/PUT/DELETE endpoints use appropriate write permissions (Create/Update/Delete)" });
107
+ writeJSON(currentPrdPath, prd);
108
+ }
109
+ }
54
110
  ```
55
111
 
56
112
  ### 1.7. Category Completeness Check (RUNS EVERY ITERATION)
@@ -71,6 +127,125 @@ if (guardrailsNeeded.length > 0) {
71
127
  // Artifact verification is handled inside checkCategoryCompleteness() — see reference file.
72
128
  ```
73
129
 
130
+ ### 1.8. Handoff File Gate (BLOCKING)
131
+
132
+ > **LESSON LEARNED (audit ba-002):** Category completeness checked "at least one file per category"
133
+ > but 4/17 API endpoints were missing because individual files were never reconciled against the handoff contract.
134
+
135
+ ```javascript
136
+ // BLOCKING: Reconcile prd.implementation.filesToCreate against disk
137
+ const filesToCreate = prd.implementation?.filesToCreate;
138
+ if (filesToCreate) {
139
+ let handoffDeclared = 0, handoffPresent = 0;
140
+ const missingByCategory = {};
141
+
142
+ for (const [category, files] of Object.entries(filesToCreate)) {
143
+ for (const file of (files || [])) {
144
+ handoffDeclared++;
145
+ const filePath = file.path || file;
146
+ if (fileExists(filePath)) {
147
+ handoffPresent++;
148
+ } else {
149
+ if (!missingByCategory[category]) missingByCategory[category] = [];
150
+ missingByCategory[category].push(filePath);
151
+ }
152
+ }
153
+ }
154
+
155
+ const coveragePct = handoffDeclared > 0 ? Math.round((handoffPresent / handoffDeclared) * 100) : 100;
156
+ console.log(`Handoff file coverage: ${handoffPresent}/${handoffDeclared} (${coveragePct}%)`);
157
+
158
+ if (coveragePct < 100) {
159
+ console.error(`BLOCKING: Handoff coverage ${coveragePct}% — ${handoffDeclared - handoffPresent} files missing`);
160
+ for (const [cat, paths] of Object.entries(missingByCategory)) {
161
+ console.error(` ${cat}: ${paths.length} missing`);
162
+ }
163
+ // category-completeness.md injects remediation tasks — do NOT advance module
164
+ }
165
+ }
166
+ ```
167
+
168
+ ### 1.9. Business Rule Coverage Gate (BLOCKING)
169
+
170
+ > **LESSON LEARNED (audit ba-002):** 5/22 business rules were not implemented despite all tasks
171
+ > being marked COMPLETE. Task-level tracking ("file created?") missed rule-level coverage
172
+ > ("does the code actually implement BR-007?").
173
+
174
+ ```javascript
175
+ // BLOCKING: Verify each BR from brToCodeMapping has an implementation
176
+ const brMapping = prd.implementation?.brToCodeMapping;
177
+ if (brMapping) {
178
+ const brMissing = [];
179
+ const brNotTested = [];
180
+
181
+ for (const br of brMapping) {
182
+ // Check if the component file exists
183
+ const componentFile = br.component || br.file;
184
+ const methodName = br.method || br.function;
185
+
186
+ if (!componentFile || !fileExists(componentFile)) {
187
+ brMissing.push({ id: br.id, rule: br.rule, component: componentFile, reason: 'file not found' });
188
+ continue;
189
+ }
190
+
191
+ // Check if the method exists in the file
192
+ if (methodName) {
193
+ const content = readFile(componentFile);
194
+ if (!content.includes(methodName)) {
195
+ brMissing.push({ id: br.id, rule: br.rule, component: componentFile, reason: `method ${methodName} not found` });
196
+ continue;
197
+ }
198
+ }
199
+
200
+ // Check test coverage (WARNING only — test category handles enforcement)
201
+ if (br.testFile && !fileExists(br.testFile)) {
202
+ brNotTested.push({ id: br.id, rule: br.rule, testFile: br.testFile });
203
+ }
204
+ }
205
+
206
+ if (brMissing.length > 0) {
207
+ console.error(`BLOCKING: ${brMissing.length}/${brMapping.length} business rules NOT implemented:`);
208
+ for (const br of brMissing) {
209
+ console.error(` ${br.id}: ${br.rule} — ${br.reason}`);
210
+ }
211
+
212
+ // Inject remediation tasks for missing BRs
213
+ let maxIdNum = Math.max(...prd.tasks.map(t => {
214
+ const num = parseInt(t.id.replace(/[^0-9]/g, ''), 10);
215
+ return isNaN(num) ? 0 : num;
216
+ }), 0);
217
+ const prefix = prd.tasks[0]?.id?.replace(/[0-9]+$/, '') || 'BR-';
218
+
219
+ for (const br of brMissing) {
220
+ const alreadyExists = prd.tasks.some(t =>
221
+ t.description.includes('[BR-REMEDIATION]') && t.description.includes(br.id)
222
+ );
223
+ if (alreadyExists) continue;
224
+
225
+ maxIdNum++;
226
+ prd.tasks.push({
227
+ id: `${prefix}${String(maxIdNum).padStart(3, '0')}`,
228
+ description: `[BR-REMEDIATION] Implement ${br.id}: ${br.rule}`,
229
+ status: 'pending',
230
+ category: 'application',
231
+ dependencies: [],
232
+ acceptance_criteria: `Business rule ${br.id} implemented in ${br.component} with method ${br.method || 'TBD'}`,
233
+ started_at: null, completed_at: null, iteration: null,
234
+ commit_hash: null, files_changed: [], validation: null, error: null
235
+ });
236
+ }
237
+ writeJSON(currentPrdPath, prd);
238
+ }
239
+
240
+ if (brNotTested.length > 0) {
241
+ console.warn(`WARNING: ${brNotTested.length}/${brMapping.length} business rules have no test file:`);
242
+ for (const br of brNotTested) {
243
+ console.warn(` ${br.id}: ${br.rule} — expected test: ${br.testFile}`);
244
+ }
245
+ }
246
+ }
247
+ ```
248
+
74
249
  ## 2. Check Iteration Limit
75
250
 
76
251
  If `current_iteration > max_iterations`:
@@ -123,10 +298,30 @@ if (fileExists(queuePath)) {
123
298
  console.log(`Module ${currentModule.code}: missing categories ${missing.join(', ')} — continuing loop`);
124
299
  // Fall through to section 5 (compact loop)
125
300
  } else {
126
- // All categories complete — advance to next module
127
- // (detailed logic in references/module-transition.md)
128
- // MANDATORY: If this was the LAST module, ALWAYS proceed to step-05-report.md.
129
- // NEVER stop without generating the final report.
301
+ // All categories complete — verify handoff files before advancing
302
+ const ftc = prd.implementation?.filesToCreate;
303
+ if (ftc) {
304
+ let ftcMissing = 0;
305
+ for (const [cat, files] of Object.entries(ftc)) {
306
+ for (const file of (files || [])) {
307
+ if (!fileExists(file.path || file)) ftcMissing++;
308
+ }
309
+ }
310
+ if (ftcMissing > 0) {
311
+ console.error(`BLOCKING: ${ftcMissing} handoff files still missing — cannot advance module`);
312
+ // Fall through to section 5 (compact loop) — remediation tasks injected by 1.8
313
+ } else {
314
+ // Handoff 100% + all categories complete — advance to next module
315
+ // (detailed logic in references/module-transition.md)
316
+ // MANDATORY: If this was the LAST module, ALWAYS proceed to step-05-report.md.
317
+ // NEVER stop without generating the final report.
318
+ }
319
+ } else {
320
+ // No handoff contract — advance to next module (manual mode)
321
+ // (detailed logic in references/module-transition.md)
322
+ // MANDATORY: If this was the LAST module, ALWAYS proceed to step-05-report.md.
323
+ // NEVER stop without generating the final report.
324
+ }
130
325
  }
131
326
  }
132
327
  ```
@@ -63,24 +63,136 @@ if (fileExists(queuePath)) {
63
63
  }
64
64
  ```
65
65
 
66
- ### 1d. Artifact Verification (Post-Execution Validation)
66
+ ### 1d. Handoff Reconciliation (Post-Execution Validation)
67
+
68
+ > **LESSON LEARNED (audit ba-002):** Simple "missing files" warnings were ignored.
69
+ > Structured reconciliation tables make gaps impossible to miss.
67
70
 
68
71
  ```javascript
69
- // Verify key artifacts exist on disk
70
- const allFiles = prd.implementation?.filesToCreate || {};
71
- const missingFiles = [];
72
- for (const cat of Object.keys(allFiles)) {
73
- for (const file of allFiles[cat] || []) {
74
- if (!fileExists(file.path)) missingFiles.push(file.path);
72
+ // === RECONCILIATION 1: Handoff File Coverage ===
73
+ const filesToCreate = prd.implementation?.filesToCreate || {};
74
+ const handoffReconciliation = { categories: [], totalDeclared: 0, totalPresent: 0, missingFiles: [] };
75
+
76
+ for (const [cat, files] of Object.entries(filesToCreate)) {
77
+ let declared = 0, present = 0;
78
+ for (const file of (files || [])) {
79
+ declared++;
80
+ const filePath = file.path || file;
81
+ if (fileExists(filePath)) {
82
+ present++;
83
+ } else {
84
+ handoffReconciliation.missingFiles.push({ category: cat, path: filePath });
85
+ }
75
86
  }
87
+ handoffReconciliation.categories.push({
88
+ category: cat, declared, present, missing: declared - present,
89
+ coverage: declared > 0 ? Math.round((present / declared) * 100) : 100
90
+ });
91
+ handoffReconciliation.totalDeclared += declared;
92
+ handoffReconciliation.totalPresent += present;
76
93
  }
77
- if (missingFiles.length > 0) {
78
- console.warn(`WARNING: ${missingFiles.length} files declared in PRD but not found on disk`);
79
- for (const f of missingFiles.slice(0, 10)) {
80
- console.warn(` MISSING: ${f}`);
94
+
95
+ const totalHandoffCoverage = handoffReconciliation.totalDeclared > 0
96
+ ? Math.round((handoffReconciliation.totalPresent / handoffReconciliation.totalDeclared) * 100)
97
+ : 100;
98
+
99
+ // === RECONCILIATION 2: Business Rule Coverage ===
100
+ const brMapping = prd.implementation?.brToCodeMapping || [];
101
+ const brReconciliation = { total: brMapping.length, implemented: 0, missing: [], notTested: [] };
102
+
103
+ for (const br of brMapping) {
104
+ const componentFile = br.component || br.file;
105
+ const methodName = br.method || br.function;
106
+
107
+ if (!componentFile || !fileExists(componentFile)) {
108
+ brReconciliation.missing.push({ id: br.id, rule: br.rule, reason: 'file not found' });
109
+ continue;
110
+ }
111
+ if (methodName) {
112
+ const content = readFile(componentFile);
113
+ if (!content.includes(methodName)) {
114
+ brReconciliation.missing.push({ id: br.id, rule: br.rule, reason: `method ${methodName} not found` });
115
+ continue;
116
+ }
117
+ }
118
+ brReconciliation.implemented++;
119
+
120
+ if (br.testFile && !fileExists(br.testFile)) {
121
+ brReconciliation.notTested.push({ id: br.id, rule: br.rule });
122
+ }
123
+ }
124
+
125
+ // === RECONCILIATION 3: API Endpoint Coverage ===
126
+ const apiEndpoints = prd.implementation?.apiEndpointSummary || [];
127
+ const apiReconciliation = { total: apiEndpoints.length, found: 0, missing: [] };
128
+
129
+ const ctrlFilesForReconciliation = glob('src/**/Controllers/**/*Controller.cs');
130
+ for (const ep of apiEndpoints) {
131
+ const opName = ep.operation || ep.name;
132
+ let found = false;
133
+ for (const f of ctrlFilesForReconciliation) {
134
+ const content = readFile(f);
135
+ if (content.includes(opName)) { found = true; break; }
81
136
  }
82
- if (missingFiles.length > 10) {
83
- console.warn(` ... and ${missingFiles.length - 10} more`);
137
+ if (found) {
138
+ apiReconciliation.found++;
139
+ } else {
140
+ apiReconciliation.missing.push({ operation: opName, method: ep.method, path: ep.path });
141
+ }
142
+ }
143
+
144
+ // Log summary
145
+ console.log(`Handoff files: ${handoffReconciliation.totalPresent}/${handoffReconciliation.totalDeclared} (${totalHandoffCoverage}%)`);
146
+ console.log(`Business rules: ${brReconciliation.implemented}/${brReconciliation.total} implemented`);
147
+ console.log(`API endpoints: ${apiReconciliation.found}/${apiReconciliation.total} found`);
148
+ ```
149
+
150
+ ### 1e. Final Build & Test Verification (TRUTH CHECK)
151
+
152
+ > **LESSON LEARNED (audit ba-002):** "92/92 COMPLETE" + "Build PASS" was reported but the code
153
+ > had compilation errors and critical security flaws. The report MUST reflect actual build/test
154
+ > state, not just PRD task statuses.
155
+
156
+ ```bash
157
+ # REAL build verification — overrides any previous "Build PASS" claim
158
+ dotnet build --no-restore --verbosity quiet
159
+ FINAL_BUILD_RC=$?
160
+
161
+ FINAL_TEST_RC=-1
162
+ if [ $FINAL_BUILD_RC -eq 0 ]; then
163
+ dotnet test --no-build --verbosity quiet
164
+ FINAL_TEST_RC=$?
165
+ fi
166
+ ```
167
+
168
+ ```javascript
169
+ // Override PRD-based build/test status with REAL results
170
+ const buildStatus = FINAL_BUILD_RC === 0 ? 'PASS' : 'FAIL';
171
+ const testStatus = FINAL_TEST_RC === 0 ? 'PASS' : (FINAL_TEST_RC === -1 ? 'SKIPPED (build failed)' : 'FAIL');
172
+
173
+ // CRITICAL: If build fails despite all tasks "completed", flag this prominently
174
+ if (FINAL_BUILD_RC !== 0 && stats.tasks.completed === stats.tasks.total) {
175
+ console.error('INTEGRITY WARNING: All tasks marked COMPLETE but build FAILS');
176
+ console.error('This indicates phantom task completion — review code quality');
177
+ stats.buildIntegrityWarning = true;
178
+ }
179
+ ```
180
+
181
+ ### 1f. MCP Security Scan (Final Gate)
182
+
183
+ ```javascript
184
+ // Quick MCP security validation on the final codebase
185
+ const securityScan = await mcp__smartstack__validate_security();
186
+ const conventionScan = await mcp__smartstack__validate_conventions();
187
+
188
+ // Check for write endpoints with Read permissions (semantic permission check)
189
+ const permissionWarnings = [];
190
+ const ctrlFiles = glob('src/**/Controllers/**/*Controller.cs');
191
+ for (const f of ctrlFiles) {
192
+ const content = readFile(f);
193
+ const writeWithRead = content.matchAll(/\[(HttpPost|HttpPut|HttpDelete|HttpPatch)[^\]]*\][^[]*\[RequirePermission\([^\)]*\.Read\)\]/gs);
194
+ for (const m of writeWithRead) {
195
+ permissionWarnings.push(`${f}: ${m[1]} uses Read permission`);
84
196
  }
85
197
  }
86
198
  ```
@@ -138,7 +250,86 @@ Write to `.ralph/reports/{feature-slug}.md`:
138
250
 
139
251
  {end if}
140
252
 
141
- ## Test Metrics
253
+ {if handoffReconciliation.totalDeclared > 0:}
254
+ ## Handoff Reconciliation
255
+
256
+ ### File Coverage by Category
257
+ | Category | Declared | Present | Missing | Coverage |
258
+ |----------|----------|---------|---------|----------|
259
+ {for each cat in handoffReconciliation.categories:}
260
+ | {category} | {declared} | {present} | {missing} | {coverage}% |
261
+ {end for}
262
+
263
+ **Total: {totalPresent}/{totalDeclared} ({totalHandoffCoverage}%)**
264
+
265
+ {if handoffReconciliation.missingFiles.length > 0:}
266
+ ### Missing Files
267
+ {for each mf in handoffReconciliation.missingFiles:}
268
+ - **{mf.category}**: `{mf.path}`
269
+ {end for}
270
+ {end if}
271
+ {end if}
272
+
273
+ {if brReconciliation.total > 0:}
274
+ ### Business Rule Coverage
275
+ | Metric | Value |
276
+ |--------|-------|
277
+ | Total Rules | {brReconciliation.total} |
278
+ | Implemented | {brReconciliation.implemented} |
279
+ | Missing | {brReconciliation.missing.length} |
280
+ | Not Tested | {brReconciliation.notTested.length} |
281
+
282
+ {if brReconciliation.missing.length > 0:}
283
+ **Missing BR implementations:**
284
+ {for each br in brReconciliation.missing:}
285
+ - `{br.id}`: {br.rule} — {br.reason}
286
+ {end for}
287
+ {end if}
288
+ {end if}
289
+
290
+ {if apiReconciliation.total > 0:}
291
+ ### API Endpoint Coverage
292
+ | Metric | Value |
293
+ |--------|-------|
294
+ | Total Endpoints | {apiReconciliation.total} |
295
+ | Found | {apiReconciliation.found} |
296
+ | Missing | {apiReconciliation.missing.length} |
297
+
298
+ {if apiReconciliation.missing.length > 0:}
299
+ **Missing API endpoints:**
300
+ {for each ep in apiReconciliation.missing:}
301
+ - `{ep.method} {ep.path}` — operation: `{ep.operation}`
302
+ {end for}
303
+ {end if}
304
+ {end if}
305
+
306
+ ## Build & Test Verification (Actual)
307
+
308
+ > These results come from REAL `dotnet build` and `dotnet test` execution,
309
+ > not from PRD task statuses.
310
+
311
+ | Check | Result |
312
+ |-------|--------|
313
+ | `dotnet build` | {buildStatus} |
314
+ | `dotnet test` | {testStatus} |
315
+ | MCP validate_security | {securityScan.status} |
316
+ | MCP validate_conventions | {conventionScan.status} |
317
+ | Permission semantic check | {permissionWarnings.length === 0 ? 'PASS' : `${permissionWarnings.length} issues`} |
318
+
319
+ {if stats.buildIntegrityWarning:}
320
+ > **INTEGRITY WARNING:** All tasks marked COMPLETE but the build FAILS.
321
+ > This means some generated code references types or methods that don't exist.
322
+ > Review the build output and fix before considering this feature done.
323
+ {end if}
324
+
325
+ {if permissionWarnings.length > 0:}
326
+ > **PERMISSION WARNINGS:** Write endpoints using Read permissions detected:
327
+ {for each warning:}
328
+ > - {warning}
329
+ {end for}
330
+ {end if}
331
+
332
+ ## Test Metrics (PRD-based)
142
333
  | Metric | Value |
143
334
  |--------|-------|
144
335
  | Test Tasks | {testTasks.length} |