@atlashub/smartstack-cli 4.27.0 → 4.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/skills/apex/references/core-seed-data.md +27 -4
- package/templates/skills/apex/references/post-checks.md +282 -0
- package/templates/skills/apex/references/smartstack-layers.md +31 -0
- package/templates/skills/apex/steps/step-02-plan.md +9 -0
- package/templates/skills/apex/steps/step-03-execute.md +39 -2
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +33 -0
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +17 -0
- package/templates/skills/business-analyse/references/spec-auto-inference.md +12 -7
- package/templates/skills/business-analyse/steps/step-02-structure.md +20 -6
- package/templates/skills/business-analyse/steps/step-03-specify.md +7 -0
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +20 -0
- package/templates/skills/derive-prd/references/handoff-file-templates.md +25 -1
- package/templates/skills/derive-prd/references/handoff-seeddata-generation.md +3 -1
- package/templates/skills/ralph-loop/references/category-completeness.md +125 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +66 -10
- package/templates/skills/ralph-loop/references/module-transition.md +60 -0
- package/templates/skills/ralph-loop/steps/step-04-check.md +207 -12
- package/templates/skills/ralph-loop/steps/step-05-report.md +205 -14
|
@@ -33,24 +33,80 @@ const tasksTotal = prd.tasks.length;
|
|
|
33
33
|
const allDone = (tasksCompleted + tasksSkipped) === tasksTotal;
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
### 1.5.
|
|
36
|
+
### 1.5. Build & Test Gate (BLOCKING)
|
|
37
37
|
|
|
38
|
-
> **
|
|
39
|
-
>
|
|
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
|
-
#
|
|
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
|
|
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 —
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
|
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
|
-
//
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 (
|
|
83
|
-
|
|
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
|
-
|
|
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} |
|