@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
|
@@ -72,16 +72,27 @@ For each module, anticipate the navigation structure:
|
|
|
72
72
|
For each section:
|
|
73
73
|
- code (kebab-case, e.g., "list", "detail", "dashboard")
|
|
74
74
|
- label (display name)
|
|
75
|
+
- sectionType: primary | functional | view | embedded
|
|
76
|
+
- permissionMode: crud | custom | read-only | inherit
|
|
75
77
|
- resources: [
|
|
76
78
|
{ code, type (SmartTable|SmartForm|SmartCard|SmartKanban|SmartDashboard|SmartFilter), label }
|
|
77
79
|
]
|
|
78
80
|
```
|
|
79
81
|
|
|
82
|
+
**Section classification rules:**
|
|
83
|
+
|
|
84
|
+
| sectionType | permissionMode | When to use | Examples |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `primary` | `crud` | Main entry point of the module, visible in menu | list |
|
|
87
|
+
| `functional` | `crud` or `custom` | Independent functional zone with own access control | approve, import, planning |
|
|
88
|
+
| `view` | `inherit` | Subordinate view reached from a primary section | detail, edit |
|
|
89
|
+
| `embedded` | `read-only` | Widget or tab embedded in another section | dashboard (when embedded in module) |
|
|
90
|
+
|
|
80
91
|
Common patterns:
|
|
81
|
-
- **Data-centric module**: list (
|
|
82
|
-
- **Workflow module**: list + detail +
|
|
83
|
-
- **Reporting module**: dashboard (
|
|
84
|
-
- **Full module**: list + detail + dashboard
|
|
92
|
+
- **Data-centric module**: list (`primary`/`crud`) + detail (`view`/`inherit`)
|
|
93
|
+
- **Workflow module**: list (`primary`/`crud`) + detail (`view`/`inherit`) + approve (`functional`/`custom`)
|
|
94
|
+
- **Reporting module**: dashboard (`primary`/`read-only`) + detail (`view`/`inherit`)
|
|
95
|
+
- **Full module**: list (`primary`/`crud`) + detail (`view`/`inherit`) + dashboard (`embedded`/`read-only`)
|
|
85
96
|
|
|
86
97
|
### 4. Dependency Graph
|
|
87
98
|
|
|
@@ -111,6 +122,9 @@ For EACH identified element, ask yourself:
|
|
|
111
122
|
- Does this module need a dashboard?
|
|
112
123
|
- Is the list/detail pattern sufficient or are there other views?
|
|
113
124
|
- Are there workflow steps that need dedicated sections?
|
|
125
|
+
- Does this section need its own access control? If not → `view` or `embedded`
|
|
126
|
+
- Is this a read-only view (dashboard, balances, statistics)? If yes → `permissionMode: read-only`
|
|
127
|
+
- Is this section reached by clicking a row in another section? If yes → always `view`
|
|
114
128
|
|
|
115
129
|
**Resources:**
|
|
116
130
|
- Is SmartTable the right component for this list?
|
|
@@ -159,8 +173,8 @@ Write via ba-writer:
|
|
|
159
173
|
"priority": "must",
|
|
160
174
|
"entities": ["Employee", "Contract"],
|
|
161
175
|
"anticipatedSections": [
|
|
162
|
-
{ "code": "list", "label": "Liste", "resources": [{ "code": "employees-grid", "type": "SmartTable" }] },
|
|
163
|
-
{ "code": "detail", "label": "Fiche", "resources": [{ "code": "employee-form", "type": "SmartForm" }] }
|
|
176
|
+
{ "code": "list", "label": "Liste", "sectionType": "primary", "permissionMode": "crud", "resources": [{ "code": "employees-grid", "type": "SmartTable" }] },
|
|
177
|
+
{ "code": "detail", "label": "Fiche", "sectionType": "view", "permissionMode": "inherit", "resources": [{ "code": "employee-form", "type": "SmartForm" }] }
|
|
164
178
|
]
|
|
165
179
|
}
|
|
166
180
|
],
|
|
@@ -123,6 +123,10 @@ Define the permission matrix:
|
|
|
123
123
|
}
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
+
**Auto-detection rules:**
|
|
127
|
+
- If the cadrage mentions "export", "Excel", "CSV", or "télécharger" → automatically add `.export` permission and an export use case (UC-{PREFIX}-EXPORT)
|
|
128
|
+
- If the cadrage mentions "import", "importer", "upload" → automatically add `.import` permission and an import use case (UC-{PREFIX}-IMPORT)
|
|
129
|
+
|
|
126
130
|
### F. Interface Specs — Delegated to /ba-design-ui
|
|
127
131
|
|
|
128
132
|
> **Screen specifications are NOT produced in this step.**
|
|
@@ -163,6 +167,9 @@ Before advancing to step 04, verify:
|
|
|
163
167
|
- [ ] All entity relationships reference existing entities
|
|
164
168
|
- [ ] All UC business rule references exist
|
|
165
169
|
- [ ] All permission paths follow the convention
|
|
170
|
+
- [ ] Sections with `sectionType: view` do NOT appear in `permissionPaths` (they inherit from module)
|
|
171
|
+
- [ ] Sections with `permissionMode: read-only` only have `.read` in `permissionPaths` (no create/update/delete)
|
|
172
|
+
- [ ] Sections with `permissionMode: inherit` have ZERO entries in `permissionPaths`
|
|
166
173
|
|
|
167
174
|
## Transition
|
|
168
175
|
|
|
@@ -123,6 +123,26 @@ if (actualNavRoute !== permission_path) {
|
|
|
123
123
|
console.warn(` Got: "${actualNavRoute}"`);
|
|
124
124
|
// Warning only — proceed
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
// Validate segment count
|
|
128
|
+
const segments = actualNavRoute ? actualNavRoute.split('.').length : 0;
|
|
129
|
+
if (segments < 2) {
|
|
130
|
+
console.error(`BLOCKING: NavRoute "${actualNavRoute}" has only ${segments} segment(s) — minimum 2 required`);
|
|
131
|
+
console.error(` Format: "app.module" (2 segments) or "app.module.section" (3 segments)`);
|
|
132
|
+
STOP;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If entity is in a section subfolder, NavRoute must have 3+ segments
|
|
136
|
+
// Check: controller path has 3+ levels after Controllers/ → section-level
|
|
137
|
+
const controllerDir = controllerCall.controllerFile.replace(/[^/]*$/, '');
|
|
138
|
+
const depthAfterControllers = controllerDir.split('Controllers/')[1]?.split('/').filter(Boolean).length || 0;
|
|
139
|
+
if (depthAfterControllers >= 2 && segments < 3) {
|
|
140
|
+
console.warn(`WARNING: Controller is in a section subfolder (depth=${depthAfterControllers})`);
|
|
141
|
+
console.warn(` but NavRoute "${actualNavRoute}" has only ${segments} segments`);
|
|
142
|
+
console.warn(` Expected 3 segments: "app.module.section"`);
|
|
143
|
+
console.warn(` A 2-segment NavRoute on a section controller causes API 404s`);
|
|
144
|
+
// Warning — developer should verify and fix
|
|
145
|
+
}
|
|
126
146
|
```
|
|
127
147
|
|
|
128
148
|
---
|
|
@@ -30,6 +30,12 @@ From `usecases.json > useCases[]`:
|
|
|
30
30
|
|
|
31
31
|
Include: Service per UC cluster, DTOs for API contracts, Validators (FluentValidation), Query handlers
|
|
32
32
|
|
|
33
|
+
**Validator generation rules:**
|
|
34
|
+
- Every entity with Create and/or Update use cases MUST have a corresponding Validator
|
|
35
|
+
- Validators MUST be registered in DI (`services.AddScoped<IValidator<CreateXxxDto>, CreateXxxValidator>()`)
|
|
36
|
+
- Validators MUST be injected into controllers/services that handle POST/PUT operations
|
|
37
|
+
- NO TODO/placeholder comments allowed in Validators — all validation rules from business rules (BR-VAL-*) must be implemented
|
|
38
|
+
|
|
33
39
|
## 4.3 Infrastructure Files
|
|
34
40
|
|
|
35
41
|
From `entities.json > entities[]`:
|
|
@@ -48,7 +54,8 @@ Generated from `usecases.json` + `entities.json`:
|
|
|
48
54
|
|
|
49
55
|
```json
|
|
50
56
|
"api": [
|
|
51
|
-
{ "path": "src/API/Controllers/{ApplicationName}/{EntityName}Controller.cs", "type": "ApiController", "linkedUCs": [], "linkedFRs": [], "module": "{moduleCode}" }
|
|
57
|
+
{ "path": "src/API/Controllers/{ApplicationName}/{EntityName}Controller.cs", "type": "ApiController", "navRoute": "{app-kebab}.{module-kebab}", "isSection": false, "linkedUCs": [], "linkedFRs": [], "module": "{moduleCode}" },
|
|
58
|
+
{ "path": "src/API/Controllers/{ApplicationName}/{ModuleName}/{SectionEntityName}Controller.cs", "type": "ApiController", "navRoute": "{app-kebab}.{module-kebab}.{section-kebab}", "isSection": true, "linkedUCs": [], "linkedFRs": [], "module": "{moduleCode}" }
|
|
52
59
|
]
|
|
53
60
|
```
|
|
54
61
|
|
|
@@ -80,6 +87,23 @@ From `screens.json > screens[]` and `usecases.json > useCases[]`:
|
|
|
80
87
|
|
|
81
88
|
**Dashboard acceptance criteria:** Chart library (Recharts), chart types matching spec, KPI cards, filters, CSS variables, responsive layout, wireframe-matching positions.
|
|
82
89
|
|
|
90
|
+
## 4.5b Notification Files (CONDITIONAL)
|
|
91
|
+
|
|
92
|
+
> Generated only when `lifeCycles[].transitions[].effects[]` contains entries with `type: "notification"`.
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
"notifications": [
|
|
96
|
+
{ "path": "src/Application/Notifications/{ApplicationName}/{ModuleName}/{NotificationName}Notification.cs", "type": "Notification", "linkedUCs": [], "module": "{moduleCode}", "description": "Notification triggered by lifecycle transition" },
|
|
97
|
+
{ "path": "src/Application/Notifications/{ApplicationName}/{ModuleName}/{NotificationName}NotificationHandler.cs", "type": "NotificationHandler", "linkedUCs": [], "module": "{moduleCode}", "description": "Handler that sends in-app/email notification" }
|
|
98
|
+
]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Generation rules:**
|
|
102
|
+
- One Notification + Handler pair per unique `notification` effect in lifecycle transitions
|
|
103
|
+
- NotificationName derived from transition: `{Entity}{TransitionName}` (e.g., `OrderApproved`)
|
|
104
|
+
- Handler must use `INotificationService` for in-app and `IEmailService` for email type
|
|
105
|
+
- Notification must include: recipient resolution, template reference, and payload mapping
|
|
106
|
+
|
|
83
107
|
## 4.6 SeedData Files
|
|
84
108
|
|
|
85
109
|
**OBLIGATORY: 2 app-level CORE + per module CORE (NavigationModule + NavigationSections + Permissions + Roles) + business per module:**
|
|
@@ -127,7 +127,9 @@ const seedDataCore = {
|
|
|
127
127
|
? `/${toKebabCase(appCode)}/${toKebabCase(m.code)}/:id`
|
|
128
128
|
: `/${toKebabCase(appCode)}/${toKebabCase(m.code)}/${toKebabCase(s.code)}`,
|
|
129
129
|
displayOrder: (j + 1) * 10,
|
|
130
|
-
navigation: s.code === "detail" ? "hidden" : "visible"
|
|
130
|
+
navigation: s.code === "detail" ? "hidden" : "visible",
|
|
131
|
+
// Propagate permissionMode for section-level permission generation
|
|
132
|
+
permissionMode: s.permissionMode || (s.code === "detail" ? "inherit" : s.code === "dashboard" ? "read-only" : "crud")
|
|
131
133
|
}))
|
|
132
134
|
);
|
|
133
135
|
|
|
@@ -191,6 +191,129 @@ for (const [cat, check] of Object.entries(artifactChecks)) {
|
|
|
191
191
|
}
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
+
## Entity-Level File Completeness Check (BLOCKING)
|
|
195
|
+
|
|
196
|
+
> **LESSON LEARNED (audit ba-002):** Artifact verification checked "at least one file per category"
|
|
197
|
+
> but never reconciled against the **handoff contract** (`prd.implementation.filesToCreate`).
|
|
198
|
+
> Result: 4/17 API endpoints were missing but the category showed "complete" because *some* controllers existed.
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// BLOCKING: Verify EVERY file from prd.implementation.filesToCreate exists on disk
|
|
202
|
+
const filesToCreate = prd.implementation?.filesToCreate;
|
|
203
|
+
if (filesToCreate) {
|
|
204
|
+
const handoffMissing = [];
|
|
205
|
+
const handoffPresent = [];
|
|
206
|
+
|
|
207
|
+
for (const [category, files] of Object.entries(filesToCreate)) {
|
|
208
|
+
for (const file of (files || [])) {
|
|
209
|
+
const filePath = file.path || file;
|
|
210
|
+
if (fileExists(filePath)) {
|
|
211
|
+
handoffPresent.push({ category, path: filePath });
|
|
212
|
+
} else {
|
|
213
|
+
handoffMissing.push({ category, path: filePath });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const totalHandoff = handoffPresent.length + handoffMissing.length;
|
|
219
|
+
const coveragePct = totalHandoff > 0 ? Math.round((handoffPresent.length / totalHandoff) * 100) : 100;
|
|
220
|
+
console.log(`Handoff file coverage: ${handoffPresent.length}/${totalHandoff} (${coveragePct}%)`);
|
|
221
|
+
|
|
222
|
+
if (handoffMissing.length > 0) {
|
|
223
|
+
console.error(`BLOCKING: ${handoffMissing.length} files from handoff contract missing on disk`);
|
|
224
|
+
|
|
225
|
+
// Group missing files by category for targeted remediation
|
|
226
|
+
const missingByCategory = {};
|
|
227
|
+
for (const m of handoffMissing) {
|
|
228
|
+
if (!missingByCategory[m.category]) missingByCategory[m.category] = [];
|
|
229
|
+
missingByCategory[m.category].push(m.path);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Inject remediation tasks for each missing file
|
|
233
|
+
let maxIdNum = Math.max(...prd.tasks.map(t => {
|
|
234
|
+
const num = parseInt(t.id.replace(/[^0-9]/g, ''), 10);
|
|
235
|
+
return isNaN(num) ? 0 : num;
|
|
236
|
+
}), 0);
|
|
237
|
+
const prefix = prd.tasks[0]?.id?.replace(/[0-9]+$/, '') || 'HNDOFF-';
|
|
238
|
+
|
|
239
|
+
for (const [cat, paths] of Object.entries(missingByCategory)) {
|
|
240
|
+
for (const p of paths) {
|
|
241
|
+
// Skip if a remediation task already exists for this file
|
|
242
|
+
const alreadyExists = prd.tasks.some(t =>
|
|
243
|
+
t.description.includes(p) && t.description.includes('[HANDOFF-REMEDIATION]')
|
|
244
|
+
);
|
|
245
|
+
if (alreadyExists) continue;
|
|
246
|
+
|
|
247
|
+
maxIdNum++;
|
|
248
|
+
prd.tasks.push({
|
|
249
|
+
id: `${prefix}${String(maxIdNum).padStart(3, '0')}`,
|
|
250
|
+
description: `[HANDOFF-REMEDIATION] Create missing file: ${p}`,
|
|
251
|
+
status: 'pending',
|
|
252
|
+
category: cat,
|
|
253
|
+
dependencies: [],
|
|
254
|
+
acceptance_criteria: `File ${p} exists on disk and compiles`,
|
|
255
|
+
started_at: null, completed_at: null, iteration: null,
|
|
256
|
+
commit_hash: null, files_changed: [], validation: null, error: null
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
console.error(` ${cat}: ${paths.length} missing — ${paths.map(p => p.split('/').pop()).join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
writeJSON(currentPrdPath, prd);
|
|
263
|
+
}
|
|
264
|
+
} // end filesToCreate check
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Type Reference Verification (Cross-File Integrity)
|
|
268
|
+
|
|
269
|
+
> **LESSON LEARNED (audit ba-002):** Artifact verification counted files but didn't check
|
|
270
|
+
> that types referenced in code actually exist. `EmployeeListDto` was used in services
|
|
271
|
+
> but the file was missing — a "phantom reference" that artifact file-counting missed.
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
// After artifact checks, verify cross-file type references for completed categories
|
|
275
|
+
if (completedCats.has('application') && completedCats.has('api')) {
|
|
276
|
+
const serviceFiles = glob('src/**/Services/**/*Service.cs');
|
|
277
|
+
const dtoFiles = glob('src/**/DTOs/**/*.cs');
|
|
278
|
+
const entityFiles = glob('src/**/Domain/**/*.cs');
|
|
279
|
+
|
|
280
|
+
// Extract all DTO type names from actual files
|
|
281
|
+
const existingTypes = new Set();
|
|
282
|
+
for (const f of [...dtoFiles, ...entityFiles]) {
|
|
283
|
+
const content = readFile(f);
|
|
284
|
+
const typeMatches = content.matchAll(/(?:class|record|struct|enum|interface)\s+(\w+)/g);
|
|
285
|
+
for (const m of typeMatches) existingTypes.add(m[1]);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check service files for references to types that don't exist
|
|
289
|
+
const phantomRefs = [];
|
|
290
|
+
for (const f of serviceFiles) {
|
|
291
|
+
const content = readFile(f);
|
|
292
|
+
// Look for Dto/Entity type references in return types and method params
|
|
293
|
+
const typeRefs = content.matchAll(/(?:<|,\s*|\(|new\s+)(\w+(?:Dto|Entity|Exception))\b/g);
|
|
294
|
+
for (const m of typeRefs) {
|
|
295
|
+
if (!existingTypes.has(m[1]) && !m[1].startsWith('I')) {
|
|
296
|
+
phantomRefs.push({ file: f, type: m[1] });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (phantomRefs.length > 0) {
|
|
302
|
+
console.error(`PHANTOM TYPE REFERENCES DETECTED (${phantomRefs.length}):`);
|
|
303
|
+
phantomRefs.forEach(r => console.error(` ${r.file}: references ${r.type} — TYPE DOES NOT EXIST`));
|
|
304
|
+
|
|
305
|
+
// Reset application category tasks to pending — code references non-existent types
|
|
306
|
+
prd.tasks.filter(t => t.category === 'application' && t.status === 'completed')
|
|
307
|
+
.forEach(t => {
|
|
308
|
+
t.status = 'pending';
|
|
309
|
+
t.error = `Phantom type references: ${phantomRefs.map(r=>r.type).join(', ')}`;
|
|
310
|
+
t.completed_at = null;
|
|
311
|
+
});
|
|
312
|
+
writeJSON(currentPrdPath, prd);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
194
317
|
---
|
|
195
318
|
|
|
196
319
|
## Key Rules
|
|
@@ -198,4 +321,6 @@ for (const [cat, check] of Object.entries(artifactChecks)) {
|
|
|
198
321
|
- **Inject EVERY iteration:** Not just once during load
|
|
199
322
|
- **Frontend depends on seedData:** Not just API
|
|
200
323
|
- **Check artifacts:** Mark tasks pending if files don't exist
|
|
324
|
+
- **Check type references:** Verify types used in code actually exist (phantom reference detection)
|
|
325
|
+
- **Check handoff files:** Verify EVERY file from `prd.implementation.filesToCreate` exists on disk
|
|
201
326
|
- **Never skip:** This is the blocker for "missing frontend/test" failures
|
|
@@ -289,6 +289,35 @@ if (pending > 0) {
|
|
|
289
289
|
console.log(`Apex completed: ${completed}/${batchIds.length} tasks`);
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
+
### B3b. Post-Batch Build Verification (BLOCKING)
|
|
293
|
+
|
|
294
|
+
> **LESSON LEARNED (audit ba-002):** Without build checks between batches, compilation
|
|
295
|
+
> errors from early batches propagate silently through all subsequent batches.
|
|
296
|
+
> The final "Build PASS" was never actually verified.
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# Quick build check after each batch — BLOCKING if fails
|
|
300
|
+
dotnet build --no-restore --verbosity quiet
|
|
301
|
+
if [ $? -ne 0 ]; then
|
|
302
|
+
echo "BLOCKING: Build fails after batch [${firstCategory}]"
|
|
303
|
+
# Inject immediate fix task — will be picked up next iteration
|
|
304
|
+
fi
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
if (BUILD_FAILED) {
|
|
309
|
+
const maxId = Math.max(...updatedPrd.tasks.map(t => parseInt(t.id.replace(/\D/g,''))||0));
|
|
310
|
+
updatedPrd.tasks.push({
|
|
311
|
+
id: `FIX-${maxId+1}`,
|
|
312
|
+
description: `BLOCKING: Fix build regression after ${firstCategory} batch`,
|
|
313
|
+
status: 'pending', category: 'validation', dependencies: [],
|
|
314
|
+
acceptance_criteria: 'dotnet build passes with 0 errors'
|
|
315
|
+
});
|
|
316
|
+
writeJSON(currentPrdPath, updatedPrd);
|
|
317
|
+
// Continue to section C (commit) then D (loop) — fix task will be picked up next iteration
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
292
321
|
---
|
|
293
322
|
|
|
294
323
|
## C. Commit PRD State
|
|
@@ -310,27 +339,54 @@ appendFile('.ralph/progress.txt',
|
|
|
310
339
|
|
|
311
340
|
After apex returns, check for newly skipped tasks:
|
|
312
341
|
|
|
342
|
+
> **LESSON LEARNED (audit ba-002):** Individual skips in critical categories (api, test, domain,
|
|
343
|
+
> infrastructure) allowed 4/17 endpoints and 74% stub tests to pass undetected. The old logic
|
|
344
|
+
> only blocked when ALL tasks in a category were skipped — individual skips slipped through.
|
|
345
|
+
|
|
313
346
|
```javascript
|
|
347
|
+
const CRITICAL_CATEGORIES = ['api', 'test', 'domain', 'infrastructure'];
|
|
348
|
+
const MAX_SKIP_RETRIES = 3;
|
|
349
|
+
|
|
314
350
|
const newlySkipped = prdCheck.tasks.filter(t =>
|
|
315
351
|
batchIds.includes(t.id) && t.status === 'skipped'
|
|
316
352
|
);
|
|
317
353
|
if (newlySkipped.length > 0) {
|
|
318
354
|
console.warn(`⚠ ${newlySkipped.length} tasks were SKIPPED by apex:`);
|
|
319
|
-
newlySkipped.forEach(t => console.warn(` - ${t.id}: ${t.description}`));
|
|
355
|
+
newlySkipped.forEach(t => console.warn(` - ${t.id}: ${t.description} [${t.category}]`));
|
|
320
356
|
|
|
321
|
-
// If ALL tasks in a category were skipped → BLOCKING, reset for retry
|
|
322
357
|
const skippedCategories = [...new Set(newlySkipped.map(t => t.category))];
|
|
323
358
|
for (const cat of skippedCategories) {
|
|
359
|
+
const isCritical = CRITICAL_CATEGORIES.includes(cat);
|
|
324
360
|
const allInCat = prdCheck.tasks.filter(t => t.category === cat);
|
|
325
361
|
const allSkipped = allInCat.every(t => t.status === 'skipped');
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
362
|
+
|
|
363
|
+
if (isCritical) {
|
|
364
|
+
// CRITICAL CATEGORY: Block on INDIVIDUAL skips (not just when ALL are skipped)
|
|
365
|
+
const skippedInCat = newlySkipped.filter(t => t.category === cat);
|
|
366
|
+
for (const t of skippedInCat) {
|
|
367
|
+
const retryCount = t._skipRetryCount || 0;
|
|
368
|
+
if (retryCount < MAX_SKIP_RETRIES) {
|
|
369
|
+
console.error(`BLOCKING: Task ${t.id} skipped in critical category "${cat}" — reset to pending (retry ${retryCount + 1}/${MAX_SKIP_RETRIES})`);
|
|
370
|
+
t.status = 'pending';
|
|
371
|
+
t._skipRetryCount = retryCount + 1;
|
|
372
|
+
t._retryCount = (t._retryCount || 0) + 1;
|
|
373
|
+
t.error = `Skipped in critical category "${cat}" — auto-retry ${retryCount + 1}/${MAX_SKIP_RETRIES}`;
|
|
374
|
+
} else {
|
|
375
|
+
console.error(`FAILED: Task ${t.id} skipped ${MAX_SKIP_RETRIES} times in critical category "${cat}" — marking failed`);
|
|
376
|
+
t.status = 'failed';
|
|
377
|
+
t.error = `Skipped ${MAX_SKIP_RETRIES} times in critical category "${cat}" — max retries exhausted`;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
// NON-CRITICAL CATEGORY: Block only when ALL tasks in category are skipped (existing behavior)
|
|
382
|
+
if (allSkipped) {
|
|
383
|
+
console.error(`BLOCKING: ALL tasks in category "${cat}" were skipped — investigate root cause`);
|
|
384
|
+
allInCat.forEach(t => {
|
|
385
|
+
t.status = 'pending';
|
|
386
|
+
t._retryCount = (t._retryCount || 0) + 1;
|
|
387
|
+
t.error = `All tasks in category "${cat}" skipped — auto-retry`;
|
|
388
|
+
});
|
|
389
|
+
}
|
|
334
390
|
}
|
|
335
391
|
}
|
|
336
392
|
writeJSON(currentPrdPath, prdCheck);
|
|
@@ -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;
|