@atlashub/smartstack-cli 3.7.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +365 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/templates/agents/action.md +1 -0
- package/templates/agents/ba-writer.md +33 -0
- package/templates/agents/explore-codebase.md +1 -0
- package/templates/agents/explore-docs.md +1 -0
- package/templates/agents/fix-grammar.md +1 -0
- package/templates/agents/snipper.md +1 -0
- package/templates/skills/admin/SKILL.md +6 -0
- package/templates/skills/ai-prompt/SKILL.md +32 -136
- package/templates/skills/ai-prompt/steps/step-01-implementation.md +122 -0
- package/templates/skills/apex/SKILL.md +120 -0
- package/templates/skills/apex/_shared.md +86 -0
- package/templates/skills/apex/references/agent-teams-protocol.md +164 -0
- package/templates/skills/apex/references/smartstack-layers.md +173 -0
- package/templates/skills/apex/steps/step-00-init.md +156 -0
- package/templates/skills/apex/steps/step-01-analyze.md +169 -0
- package/templates/skills/apex/steps/step-02-plan.md +160 -0
- package/templates/skills/apex/steps/step-03-execute.md +166 -0
- package/templates/skills/apex/steps/step-04-validate.md +138 -0
- package/templates/skills/apex/steps/step-05-examine.md +124 -0
- package/templates/skills/apex/steps/step-06-resolve.md +105 -0
- package/templates/skills/apex/steps/step-07-tests.md +130 -0
- package/templates/skills/apex/steps/step-08-run-tests.md +115 -0
- package/templates/skills/application/SKILL.md +10 -0
- package/templates/skills/application/references/backend-controller-hierarchy.md +58 -0
- package/templates/skills/application/references/backend-entity-seeding.md +72 -0
- package/templates/skills/application/references/backend-verification.md +88 -0
- package/templates/skills/application/references/frontend-verification.md +111 -0
- package/templates/skills/application/references/nav-fallback-procedure.md +200 -0
- package/templates/skills/application/references/provider-template.md +134 -0
- package/templates/skills/application/references/test-frontend.md +73 -0
- package/templates/skills/application/references/test-prerequisites.md +72 -0
- package/templates/skills/application/steps/step-01-navigation.md +7 -198
- package/templates/skills/application/steps/step-03b-provider.md +4 -128
- package/templates/skills/application/steps/step-04-backend.md +20 -350
- package/templates/skills/application/steps/step-05-frontend.md +12 -101
- package/templates/skills/application/steps/step-07-tests.md +12 -132
- package/templates/skills/business-analyse/SKILL.md +11 -2
- package/templates/skills/business-analyse/html/ba-interactive.html +3214 -2246
- package/templates/skills/business-analyse/html/build-html.js +77 -0
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +130 -0
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +22 -0
- package/templates/skills/business-analyse/html/src/scripts/03-render-cadrage.js +208 -0
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +211 -0
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +554 -0
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +110 -0
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +90 -0
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +45 -0
- package/templates/skills/business-analyse/html/src/scripts/09-export.js +168 -0
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +171 -0
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +161 -0
- package/templates/skills/business-analyse/html/src/styles/01-variables.css +38 -0
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +101 -0
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +62 -0
- package/templates/skills/business-analyse/html/src/styles/04-cards.css +196 -0
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +325 -0
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +230 -0
- package/templates/skills/business-analyse/html/src/styles/07-comments.css +184 -0
- package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +241 -0
- package/templates/skills/business-analyse/html/src/template.html +623 -0
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +78 -0
- package/templates/skills/business-analyse/references/cadrage-vibe-coding.md +97 -0
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +92 -0
- package/templates/skills/business-analyse/references/deploy-data-build.md +121 -0
- package/templates/skills/business-analyse/references/deploy-modes.md +49 -0
- package/templates/skills/business-analyse/references/handoff-file-templates.md +119 -0
- package/templates/skills/business-analyse/references/handoff-mappings.md +81 -0
- package/templates/skills/business-analyse/references/html-data-mapping.md +10 -2
- package/templates/skills/business-analyse/references/init-schema-deployment.md +65 -0
- package/templates/skills/business-analyse/references/review-data-mapping.md +363 -0
- package/templates/skills/business-analyse/references/spec-auto-inference.md +57 -0
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +85 -0
- package/templates/skills/business-analyse/references/ui-resource-cards.md +110 -0
- package/templates/skills/business-analyse/references/validate-incremental-html.md +55 -0
- package/templates/skills/business-analyse/steps/step-00-init.md +35 -68
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +5 -194
- package/templates/skills/business-analyse/steps/step-03a-data.md +6 -49
- package/templates/skills/business-analyse/steps/step-03b-ui.md +12 -178
- package/templates/skills/business-analyse/steps/step-03d-validate.md +3 -48
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +9 -104
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +25 -441
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +19 -187
- package/templates/skills/business-analyse/steps/step-06-review.md +277 -0
- package/templates/skills/cc-agent/references/agent-behavior-patterns.md +95 -0
- package/templates/skills/cc-agent/steps/step-02-generate.md +5 -78
- package/templates/skills/check-version/SKILL.md +7 -0
- package/templates/skills/controller/references/controller-code-templates.md +159 -0
- package/templates/skills/controller/references/permission-sync-templates.md +152 -0
- package/templates/skills/controller/steps/step-03-generate.md +6 -158
- package/templates/skills/controller/steps/step-04-perms.md +5 -144
- package/templates/skills/debug/SKILL.md +7 -0
- package/templates/skills/explore/SKILL.md +6 -0
- package/templates/skills/feature-full/SKILL.md +39 -142
- package/templates/skills/feature-full/steps/step-01-implementation.md +120 -0
- package/templates/skills/gitflow/references/init-config-template.md +135 -0
- package/templates/skills/gitflow/references/init-name-normalization.md +103 -0
- package/templates/skills/gitflow/references/plan-template.md +69 -0
- package/templates/skills/gitflow/references/start-efcore-preflight.md +70 -0
- package/templates/skills/gitflow/references/start-local-config.md +110 -0
- package/templates/skills/gitflow/steps/step-init.md +18 -289
- package/templates/skills/gitflow/steps/step-plan.md +6 -63
- package/templates/skills/gitflow/steps/step-start.md +16 -126
- package/templates/skills/mcp/SKILL.md +9 -213
- package/templates/skills/mcp/steps/step-01-healthcheck.md +108 -0
- package/templates/skills/mcp/steps/step-02-tools.md +73 -0
- package/templates/skills/notification/SKILL.md +7 -0
- package/templates/skills/quick-search/SKILL.md +5 -0
- package/templates/skills/ralph-loop/SKILL.md +99 -381
- package/templates/skills/ralph-loop/references/category-rules.md +259 -0
- package/templates/skills/ralph-loop/references/compact-loop.md +182 -0
- package/templates/skills/ralph-loop/references/task-transform-legacy.md +259 -0
- package/templates/skills/ralph-loop/references/team-orchestration.md +189 -0
- package/templates/skills/ralph-loop/steps/step-00-init.md +111 -383
- package/templates/skills/ralph-loop/steps/step-01-task.md +79 -896
- package/templates/skills/ralph-loop/steps/step-02-execute.md +68 -680
- package/templates/skills/ralph-loop/steps/step-03-commit.md +47 -277
- package/templates/skills/ralph-loop/steps/step-04-check.md +124 -607
- package/templates/skills/ralph-loop/steps/step-05-report.md +68 -367
- package/templates/skills/refactor/SKILL.md +12 -176
- package/templates/skills/refactor/steps/step-01-discover.md +60 -0
- package/templates/skills/refactor/steps/step-02-execute.md +67 -0
- package/templates/skills/review-code/SKILL.md +19 -257
- package/templates/skills/review-code/steps/step-01-smartstack.md +96 -0
- package/templates/skills/review-code/steps/step-02-detailed-review.md +80 -0
- package/templates/skills/review-code/steps/step-03-react.md +44 -0
- package/templates/skills/ui-components/SKILL.md +7 -0
- package/templates/skills/utils/SKILL.md +6 -0
- package/templates/skills/validate/SKILL.md +6 -0
- package/templates/skills/validate-feature/SKILL.md +8 -0
- package/templates/skills/workflow/SKILL.md +40 -118
- package/templates/skills/workflow/steps/step-01-implementation.md +84 -0
package/dist/index.js
CHANGED
|
@@ -126749,6 +126749,334 @@ mcpCommand.command("remove").description("Unregister MCP servers from CLI and VS
|
|
|
126749
126749
|
var import_fs_extra9 = __toESM(require_lib());
|
|
126750
126750
|
var import_path11 = require("path");
|
|
126751
126751
|
|
|
126752
|
+
// src/utils/task-generator.ts
|
|
126753
|
+
var LAYER_ORDER = [
|
|
126754
|
+
{ key: "domain", category: "domain" },
|
|
126755
|
+
{ key: "infrastructure", category: "infrastructure" },
|
|
126756
|
+
{ key: "application", category: "application" },
|
|
126757
|
+
{ key: "api", category: "api" },
|
|
126758
|
+
{ key: "frontend", category: "frontend" },
|
|
126759
|
+
{ key: "seedData", category: "infrastructure" },
|
|
126760
|
+
{ key: "tests", category: "test" }
|
|
126761
|
+
];
|
|
126762
|
+
function createTask(ctx, category, description, deps, acceptanceCriteria, filesCreated = [], meta) {
|
|
126763
|
+
const task = {
|
|
126764
|
+
id: ctx.nextId++,
|
|
126765
|
+
description,
|
|
126766
|
+
status: "pending",
|
|
126767
|
+
category,
|
|
126768
|
+
dependencies: [...new Set(deps)],
|
|
126769
|
+
acceptance_criteria: acceptanceCriteria,
|
|
126770
|
+
started_at: null,
|
|
126771
|
+
completed_at: null,
|
|
126772
|
+
iteration: null,
|
|
126773
|
+
commit_hash: null,
|
|
126774
|
+
files_changed: { created: filesCreated, modified: [] },
|
|
126775
|
+
validation: null,
|
|
126776
|
+
error: null,
|
|
126777
|
+
module: ctx.moduleCode,
|
|
126778
|
+
...meta ?? {}
|
|
126779
|
+
};
|
|
126780
|
+
ctx.lastIdByCategory[category] = task.id;
|
|
126781
|
+
return task;
|
|
126782
|
+
}
|
|
126783
|
+
function generateFilesTasks(prd, ctx, frontendFiles, i18nFiles, testFiles) {
|
|
126784
|
+
const tasks = [];
|
|
126785
|
+
const filesToCreate = prd.implementation?.filesToCreate ?? {};
|
|
126786
|
+
const allFRs = prd.requirements?.functionalRequirements ?? [];
|
|
126787
|
+
const permMatrix = prd.architecture?.permissionMatrix;
|
|
126788
|
+
for (const layer of LAYER_ORDER) {
|
|
126789
|
+
const files = filesToCreate[layer.key] ?? [];
|
|
126790
|
+
if (layer.category === "frontend") {
|
|
126791
|
+
frontendFiles.push(...files);
|
|
126792
|
+
continue;
|
|
126793
|
+
}
|
|
126794
|
+
if (layer.category === "test") {
|
|
126795
|
+
testFiles.push(...files);
|
|
126796
|
+
continue;
|
|
126797
|
+
}
|
|
126798
|
+
for (const fileSpec of files) {
|
|
126799
|
+
if (fileSpec.path?.includes("i18n/") || fileSpec.path?.includes("/locales/")) {
|
|
126800
|
+
i18nFiles.push(fileSpec);
|
|
126801
|
+
continue;
|
|
126802
|
+
}
|
|
126803
|
+
const depIds = [];
|
|
126804
|
+
if (ctx.lastIdByCategory[layer.category]) depIds.push(ctx.lastIdByCategory[layer.category]);
|
|
126805
|
+
if (layer.category === "api" && ctx.lastIdByCategory["application"]) depIds.push(ctx.lastIdByCategory["application"]);
|
|
126806
|
+
const linkedFRs = (fileSpec.linkedFRs ?? []).map(
|
|
126807
|
+
(frId) => allFRs.find((f) => f.id === frId)?.statement ?? frId
|
|
126808
|
+
);
|
|
126809
|
+
let criteria = linkedFRs.length > 0 ? `Implements: ${linkedFRs.join("; ")}` : `File ${fileSpec.path} created and compiles`;
|
|
126810
|
+
if (layer.category === "api" && (permMatrix?.permissions?.length ?? 0) > 0) {
|
|
126811
|
+
criteria += "; MANDATORY: [RequirePermission] on every endpoint (NOT [Authorize])";
|
|
126812
|
+
}
|
|
126813
|
+
const description = fileSpec.description ? `[${layer.category}] ${fileSpec.description} \u2192 ${fileSpec.path}` : `[${layer.category}] Create ${fileSpec.type}: ${fileSpec.path}`;
|
|
126814
|
+
tasks.push(createTask(ctx, layer.category, description, depIds, criteria, [fileSpec.path]));
|
|
126815
|
+
}
|
|
126816
|
+
}
|
|
126817
|
+
return tasks;
|
|
126818
|
+
}
|
|
126819
|
+
function generateFrontendTask(prd, ctx, frontendFiles) {
|
|
126820
|
+
if (frontendFiles.length === 0) return [];
|
|
126821
|
+
const apiDepId = ctx.lastIdByCategory["api"] ?? ctx.lastIdByCategory["application"];
|
|
126822
|
+
const ctxName = capitalize(prd.project?.application ? "Business" : "Business");
|
|
126823
|
+
const app = prd.project?.application ?? ctx.moduleCode;
|
|
126824
|
+
const wireframes = [...new Set(frontendFiles.flatMap((f) => f.linkedWireframes ?? []))];
|
|
126825
|
+
return [createTask(
|
|
126826
|
+
ctx,
|
|
126827
|
+
"frontend",
|
|
126828
|
+
`[frontend] Generate COMPLETE frontend for module ${ctx.moduleCode} via MCP (${frontendFiles.length} files)`,
|
|
126829
|
+
apiDepId ? [apiDepId] : [],
|
|
126830
|
+
`Pages in src/pages/${ctxName}/${app}/${ctx.moduleCode}/; MCP scaffold_api_client + scaffold_routes; SmartTable for lists; CSS variables ONLY; 4-language i18n; npm run typecheck passes`,
|
|
126831
|
+
frontendFiles.map((f) => f.path),
|
|
126832
|
+
{
|
|
126833
|
+
_frontendMeta: {
|
|
126834
|
+
wireframes,
|
|
126835
|
+
filesToCreate: frontendFiles.map((f) => ({ path: f.path, type: f.type }))
|
|
126836
|
+
}
|
|
126837
|
+
}
|
|
126838
|
+
)];
|
|
126839
|
+
}
|
|
126840
|
+
function generateI18nTask(ctx, i18nFiles) {
|
|
126841
|
+
if (i18nFiles.length === 0) return [];
|
|
126842
|
+
const frontendDepId = ctx.lastIdByCategory["frontend"];
|
|
126843
|
+
return [createTask(
|
|
126844
|
+
ctx,
|
|
126845
|
+
"i18n",
|
|
126846
|
+
`[i18n] Generate i18n for module ${ctx.moduleCode} (4 languages)`,
|
|
126847
|
+
frontendDepId ? [frontendDepId] : [],
|
|
126848
|
+
"4 JSON files (fr, en, it, de) with identical keys",
|
|
126849
|
+
i18nFiles.map((f) => f.path)
|
|
126850
|
+
)];
|
|
126851
|
+
}
|
|
126852
|
+
function generateTestTasks(prd, ctx, testFiles) {
|
|
126853
|
+
const tasks = [];
|
|
126854
|
+
const allFRs = prd.requirements?.functionalRequirements ?? [];
|
|
126855
|
+
for (const fileSpec of testFiles) {
|
|
126856
|
+
const depIds = [];
|
|
126857
|
+
if (ctx.lastIdByCategory["test"]) depIds.push(ctx.lastIdByCategory["test"]);
|
|
126858
|
+
const migDepId = ctx.lastIdByCategory["migration"] ?? ctx.lastIdByCategory["api"] ?? ctx.lastIdByCategory["application"];
|
|
126859
|
+
if (migDepId) depIds.push(migDepId);
|
|
126860
|
+
const linkedFRs = (fileSpec.linkedFRs ?? []).map(
|
|
126861
|
+
(frId) => allFRs.find((f) => f.id === frId)?.statement ?? frId
|
|
126862
|
+
);
|
|
126863
|
+
const criteria = linkedFRs.length > 0 ? `Implements: ${linkedFRs.join("; ")}` : `File ${fileSpec.path} created and compiles`;
|
|
126864
|
+
const description = fileSpec.description ? `[test] ${fileSpec.description} \u2192 ${fileSpec.path}` : `[test] Create ${fileSpec.type}: ${fileSpec.path}`;
|
|
126865
|
+
tasks.push(createTask(ctx, "test", description, depIds, criteria, [fileSpec.path]));
|
|
126866
|
+
}
|
|
126867
|
+
return tasks;
|
|
126868
|
+
}
|
|
126869
|
+
function applyGuardrails(prd, ctx, tasks, frontendFiles, i18nFiles) {
|
|
126870
|
+
const injected = [];
|
|
126871
|
+
const filesToCreate = prd.implementation?.filesToCreate ?? {};
|
|
126872
|
+
const testFileSpecs = filesToCreate["tests"] ?? [];
|
|
126873
|
+
const entities = prd.architecture?.entities ?? [];
|
|
126874
|
+
const apiEndpoints = prd.architecture?.apiEndpoints ?? [];
|
|
126875
|
+
const sections = prd.architecture?.sections ?? [];
|
|
126876
|
+
const permMatrix = prd.architecture?.permissionMatrix;
|
|
126877
|
+
if (!tasks.some((t) => t.category === "infrastructure") && entities.length > 0) {
|
|
126878
|
+
injected.push(createTask(
|
|
126879
|
+
ctx,
|
|
126880
|
+
"infrastructure",
|
|
126881
|
+
`[infrastructure] Create EF Core configs and seed data for ${ctx.moduleCode} (${entities.length} entities)`,
|
|
126882
|
+
ctx.lastIdByCategory["domain"] ? [ctx.lastIdByCategory["domain"]] : [],
|
|
126883
|
+
`EF configs for ${entities.length} entities; DbSet in ExtensionsDbContext; DI registration`
|
|
126884
|
+
));
|
|
126885
|
+
}
|
|
126886
|
+
if (!tasks.some((t) => t.category === "api") && apiEndpoints.length > 0) {
|
|
126887
|
+
const depId = ctx.lastIdByCategory["application"] ?? ctx.lastIdByCategory["infrastructure"];
|
|
126888
|
+
const permCriteria = (permMatrix?.permissions?.length ?? 0) > 0 ? "[RequirePermission] on every endpoint" : "Authorization";
|
|
126889
|
+
injected.push(createTask(
|
|
126890
|
+
ctx,
|
|
126891
|
+
"api",
|
|
126892
|
+
`[api] Create controllers for ${ctx.moduleCode} (${apiEndpoints.length} endpoints)`,
|
|
126893
|
+
depId ? [depId] : [],
|
|
126894
|
+
`${apiEndpoints.length} endpoints; ${permCriteria}; Swagger`
|
|
126895
|
+
));
|
|
126896
|
+
}
|
|
126897
|
+
if (frontendFiles.length === 0 && !tasks.some((t) => t.category === "frontend") && (sections.length > 0 || apiEndpoints.length > 0)) {
|
|
126898
|
+
const ctxName = "Business";
|
|
126899
|
+
const app = prd.project?.application ?? ctx.moduleCode;
|
|
126900
|
+
const derivedFiles = sections.map((s) => ({
|
|
126901
|
+
path: `web/src/pages/${ctxName}/${app}/${ctx.moduleCode}/${s.code}.tsx`,
|
|
126902
|
+
type: "Page"
|
|
126903
|
+
}));
|
|
126904
|
+
if (apiEndpoints.length > 0) {
|
|
126905
|
+
derivedFiles.push({ path: `web/src/services/api/${ctx.moduleCode}Api.ts`, type: "ApiService" });
|
|
126906
|
+
}
|
|
126907
|
+
const depId = ctx.lastIdByCategory["api"] ?? ctx.lastIdByCategory["application"];
|
|
126908
|
+
injected.push(createTask(
|
|
126909
|
+
ctx,
|
|
126910
|
+
"frontend",
|
|
126911
|
+
`[frontend] Generate COMPLETE frontend for ${ctx.moduleCode} via MCP (${derivedFiles.length} files)`,
|
|
126912
|
+
depId ? [depId] : [],
|
|
126913
|
+
`Pages in src/pages/${ctxName}/${app}/${ctx.moduleCode}/; SmartTable for lists; CSS variables ONLY; 4-language i18n; npm run typecheck passes`,
|
|
126914
|
+
derivedFiles.map((f) => f.path),
|
|
126915
|
+
{
|
|
126916
|
+
_frontendMeta: {
|
|
126917
|
+
wireframes: sections.map((s) => s.code),
|
|
126918
|
+
filesToCreate: derivedFiles,
|
|
126919
|
+
source: "guardrail-derived"
|
|
126920
|
+
}
|
|
126921
|
+
}
|
|
126922
|
+
));
|
|
126923
|
+
if (i18nFiles.length === 0 && !tasks.some((t) => t.category === "i18n")) {
|
|
126924
|
+
injected.push(createTask(
|
|
126925
|
+
ctx,
|
|
126926
|
+
"i18n",
|
|
126927
|
+
`[i18n] Generate i18n for ${ctx.moduleCode} (4 languages)`,
|
|
126928
|
+
[ctx.lastIdByCategory["frontend"]],
|
|
126929
|
+
"4 JSON files (fr, en, it, de) with identical keys"
|
|
126930
|
+
));
|
|
126931
|
+
}
|
|
126932
|
+
}
|
|
126933
|
+
if (testFileSpecs.length === 0 && !tasks.some((t) => t.category === "test") && !injected.some((t) => t.category === "test") && entities.length > 0) {
|
|
126934
|
+
ctx.lastIdByCategory["_needsGuardrailTest"] = 1;
|
|
126935
|
+
}
|
|
126936
|
+
const coreSeedData = prd.seedData?.core;
|
|
126937
|
+
const hasSeedDataInTasks = [...tasks, ...injected].some(
|
|
126938
|
+
(t) => t.description?.includes("SeedData") || t.description?.includes("seed data")
|
|
126939
|
+
);
|
|
126940
|
+
if (coreSeedData && coreSeedData.length > 0 && !hasSeedDataInTasks) {
|
|
126941
|
+
const infraDepId = ctx.lastIdByCategory["infrastructure"] ?? ctx.lastIdByCategory["domain"];
|
|
126942
|
+
const navCount = coreSeedData.filter((s) => s.entity === "NavigationModule").reduce((n, s) => n + (s.count ?? 1), 0);
|
|
126943
|
+
const permCount = coreSeedData.filter((s) => s.entity === "Permission").reduce((n, s) => n + (s.count ?? 1), 0);
|
|
126944
|
+
const roleCount = coreSeedData.filter((s) => s.entity === "RolePermission").reduce((n, s) => n + (s.count ?? 1), 0);
|
|
126945
|
+
const navRoute = `business.${(prd.project?.application ?? "app").toLowerCase()}.${ctx.moduleCode.toLowerCase()}`;
|
|
126946
|
+
injected.push(createTask(
|
|
126947
|
+
ctx,
|
|
126948
|
+
"infrastructure",
|
|
126949
|
+
`[infrastructure] Create core seed data for ${ctx.moduleCode}: NavigationModuleSeedData, PermissionsSeedData, RolesSeedData`,
|
|
126950
|
+
infraDepId ? [infraDepId] : [],
|
|
126951
|
+
`NavigationModuleSeedData (${navCount} nav); PermissionsSeedData (${permCount} perms); RolesSeedData (${roleCount} roles); SeedConstants; dotnet build passes`,
|
|
126952
|
+
[],
|
|
126953
|
+
{
|
|
126954
|
+
_seedDataMeta: {
|
|
126955
|
+
source: "guardrail-derived",
|
|
126956
|
+
coreSeedData,
|
|
126957
|
+
navRoute,
|
|
126958
|
+
contextCode: "business",
|
|
126959
|
+
appCode: prd.project?.application,
|
|
126960
|
+
appLabels: null
|
|
126961
|
+
}
|
|
126962
|
+
}
|
|
126963
|
+
));
|
|
126964
|
+
}
|
|
126965
|
+
const hasProvider = [...tasks, ...injected].some(
|
|
126966
|
+
(t) => t.description?.includes("IClientSeedDataProvider") || t.description?.includes("SeedDataProvider")
|
|
126967
|
+
);
|
|
126968
|
+
const seedDataFiles = filesToCreate["seedData"] ?? [];
|
|
126969
|
+
if (!hasProvider && (seedDataFiles.length > 0 || coreSeedData && coreSeedData.length > 0)) {
|
|
126970
|
+
const depId = ctx.lastIdByCategory["infrastructure"] ?? ctx.lastIdByCategory["domain"];
|
|
126971
|
+
const navRoute = `business.${(prd.project?.application ?? "app").toLowerCase()}.${ctx.moduleCode.toLowerCase()}`;
|
|
126972
|
+
injected.push(createTask(
|
|
126973
|
+
ctx,
|
|
126974
|
+
"infrastructure",
|
|
126975
|
+
"[infrastructure] Create IClientSeedDataProvider for core seed data injection",
|
|
126976
|
+
depId ? [depId] : [],
|
|
126977
|
+
"SeedNavigationAsync + SeedPermissionsAsync + SeedRolePermissionsAsync; DI registered; idempotent",
|
|
126978
|
+
[],
|
|
126979
|
+
{
|
|
126980
|
+
_providerMeta: {
|
|
126981
|
+
navRoute,
|
|
126982
|
+
contextCode: "business",
|
|
126983
|
+
appCode: prd.project?.application
|
|
126984
|
+
}
|
|
126985
|
+
}
|
|
126986
|
+
));
|
|
126987
|
+
}
|
|
126988
|
+
const allTasks = [...tasks, ...injected];
|
|
126989
|
+
const hasEntities = entities.length > 0 || allTasks.some((t) => t.category === "domain");
|
|
126990
|
+
const hasMigration = allTasks.some((t) => t.description?.toLowerCase().includes("migration"));
|
|
126991
|
+
if (hasEntities && !hasMigration) {
|
|
126992
|
+
const depId = ctx.lastIdByCategory["infrastructure"];
|
|
126993
|
+
injected.push(createTask(
|
|
126994
|
+
ctx,
|
|
126995
|
+
"infrastructure",
|
|
126996
|
+
`[infrastructure] Create EF Core migration for ${ctx.moduleCode}`,
|
|
126997
|
+
depId ? [depId] : [],
|
|
126998
|
+
"MCP suggest_migration \u2192 name; dotnet ef migrations add; dotnet ef database update; dotnet build passes",
|
|
126999
|
+
[],
|
|
127000
|
+
{
|
|
127001
|
+
_migrationMeta: {
|
|
127002
|
+
entities: entities.map((e) => e.name),
|
|
127003
|
+
moduleCode: ctx.moduleCode
|
|
127004
|
+
}
|
|
127005
|
+
}
|
|
127006
|
+
));
|
|
127007
|
+
ctx.lastIdByCategory["migration"] = ctx.nextId - 1;
|
|
127008
|
+
}
|
|
127009
|
+
if (ctx.lastIdByCategory["_needsGuardrailTest"]) {
|
|
127010
|
+
delete ctx.lastIdByCategory["_needsGuardrailTest"];
|
|
127011
|
+
const migDepId = ctx.lastIdByCategory["migration"] ?? ctx.lastIdByCategory["api"] ?? ctx.lastIdByCategory["application"];
|
|
127012
|
+
injected.push(createTask(
|
|
127013
|
+
ctx,
|
|
127014
|
+
"test",
|
|
127015
|
+
`[test] Create unit and integration tests for ${ctx.moduleCode}`,
|
|
127016
|
+
migDepId ? [migDepId] : [],
|
|
127017
|
+
"Domain + service + controller tests; dotnet test passes; coverage >= 80%"
|
|
127018
|
+
));
|
|
127019
|
+
}
|
|
127020
|
+
return injected;
|
|
127021
|
+
}
|
|
127022
|
+
function generateValidationTask(ctx) {
|
|
127023
|
+
const allCategoryTails = Object.values(ctx.lastIdByCategory).filter((id) => id != null);
|
|
127024
|
+
return createTask(
|
|
127025
|
+
ctx,
|
|
127026
|
+
"validation",
|
|
127027
|
+
`[validation] Build, test, and MCP validate module ${ctx.moduleCode}`,
|
|
127028
|
+
allCategoryTails,
|
|
127029
|
+
"dotnet build succeeds; dotnet test passes; MCP validate_conventions clean"
|
|
127030
|
+
);
|
|
127031
|
+
}
|
|
127032
|
+
function generateTasks(prd) {
|
|
127033
|
+
const moduleCode = prd.project.module;
|
|
127034
|
+
const ctx = {
|
|
127035
|
+
moduleCode,
|
|
127036
|
+
lastIdByCategory: {},
|
|
127037
|
+
nextId: 1
|
|
127038
|
+
};
|
|
127039
|
+
const frontendFiles = [];
|
|
127040
|
+
const i18nFiles = [];
|
|
127041
|
+
const testFiles = [];
|
|
127042
|
+
const fileTasks = generateFilesTasks(prd, ctx, frontendFiles, i18nFiles, testFiles);
|
|
127043
|
+
const frontendTask = generateFrontendTask(prd, ctx, frontendFiles);
|
|
127044
|
+
const i18nTask = generateI18nTask(ctx, i18nFiles);
|
|
127045
|
+
const coreTasks = [...fileTasks, ...frontendTask, ...i18nTask];
|
|
127046
|
+
const guardrailTasks = applyGuardrails(prd, ctx, coreTasks, frontendFiles, i18nFiles);
|
|
127047
|
+
const testTasks = generateTestTasks(prd, ctx, testFiles);
|
|
127048
|
+
const allTasks = [...coreTasks, ...guardrailTasks, ...testTasks];
|
|
127049
|
+
const validationTask = generateValidationTask(ctx);
|
|
127050
|
+
return [...allTasks, validationTask];
|
|
127051
|
+
}
|
|
127052
|
+
function validateTaskIntegrity(tasks) {
|
|
127053
|
+
const errors = [];
|
|
127054
|
+
const taskIds = new Set(tasks.map((t) => t.id));
|
|
127055
|
+
for (const task of tasks) {
|
|
127056
|
+
for (const depId of task.dependencies) {
|
|
127057
|
+
if (!taskIds.has(depId)) {
|
|
127058
|
+
errors.push(`Task ${task.id}: dependency ${depId} does not exist`);
|
|
127059
|
+
}
|
|
127060
|
+
}
|
|
127061
|
+
for (const depId of task.dependencies) {
|
|
127062
|
+
if (depId >= task.id) {
|
|
127063
|
+
errors.push(`Task ${task.id}: forward dependency to ${depId}`);
|
|
127064
|
+
}
|
|
127065
|
+
}
|
|
127066
|
+
if (!task.description) errors.push(`Task ${task.id}: missing description`);
|
|
127067
|
+
if (!task.category) errors.push(`Task ${task.id}: missing category`);
|
|
127068
|
+
if (!task.acceptance_criteria) errors.push(`Task ${task.id}: missing acceptance_criteria`);
|
|
127069
|
+
if (!task.module) errors.push(`Task ${task.id}: missing module`);
|
|
127070
|
+
}
|
|
127071
|
+
if (tasks.length < 2) {
|
|
127072
|
+
errors.push(`Only ${tasks.length} task(s) generated (minimum 2 expected)`);
|
|
127073
|
+
}
|
|
127074
|
+
return errors;
|
|
127075
|
+
}
|
|
127076
|
+
function capitalize(s) {
|
|
127077
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
127078
|
+
}
|
|
127079
|
+
|
|
126752
127080
|
// src/utils/prd-extractor.ts
|
|
126753
127081
|
function extractPrd(moduleFeature, featureJsonPath, namespace) {
|
|
126754
127082
|
const { metadata, analysis, specification, handoff } = moduleFeature;
|
|
@@ -126938,6 +127266,26 @@ function validatePrdCompleteness(prd, feature) {
|
|
|
126938
127266
|
}
|
|
126939
127267
|
return warnings;
|
|
126940
127268
|
}
|
|
127269
|
+
function extractUnifiedPrd(moduleFeature, featureJsonPath, namespace, cliVersion) {
|
|
127270
|
+
const prdJson = extractPrd(moduleFeature, featureJsonPath, namespace);
|
|
127271
|
+
const tasks = generateTasks(prdJson);
|
|
127272
|
+
return {
|
|
127273
|
+
$version: "3.0.0",
|
|
127274
|
+
source: prdJson.source,
|
|
127275
|
+
project: prdJson.project,
|
|
127276
|
+
requirements: prdJson.requirements,
|
|
127277
|
+
businessRules: prdJson.businessRules,
|
|
127278
|
+
architecture: prdJson.architecture,
|
|
127279
|
+
implementation: prdJson.implementation,
|
|
127280
|
+
seedData: prdJson.seedData,
|
|
127281
|
+
tasks,
|
|
127282
|
+
metadata: {
|
|
127283
|
+
generatedBy: "ss-derive-prd",
|
|
127284
|
+
cliVersion: cliVersion ?? "unknown",
|
|
127285
|
+
moduleCode: prdJson.project.module
|
|
127286
|
+
}
|
|
127287
|
+
};
|
|
127288
|
+
}
|
|
126941
127289
|
function getPrdFileCounts(prd) {
|
|
126942
127290
|
const ftc = prd.implementation.filesToCreate;
|
|
126943
127291
|
return {
|
|
@@ -127028,7 +127376,14 @@ Processing module: ${source_default.bold(moduleName)}`));
|
|
|
127028
127376
|
continue;
|
|
127029
127377
|
}
|
|
127030
127378
|
}
|
|
127379
|
+
let cliVersion = "unknown";
|
|
127380
|
+
try {
|
|
127381
|
+
const pkg2 = import_fs_extra9.default.readJsonSync((0, import_path11.join)(__dirname, "..", "..", "package.json"));
|
|
127382
|
+
cliVersion = pkg2.version ?? "unknown";
|
|
127383
|
+
} catch {
|
|
127384
|
+
}
|
|
127031
127385
|
const relativePath = (0, import_path11.relative)(process.cwd(), entry.path);
|
|
127386
|
+
const unified = extractUnifiedPrd(featureJson, relativePath, namespace, cliVersion);
|
|
127032
127387
|
const prd = extractPrd(featureJson, relativePath, namespace);
|
|
127033
127388
|
let outputPath;
|
|
127034
127389
|
if (options.output && !entry.isApplicationMode) {
|
|
@@ -127036,7 +127391,7 @@ Processing module: ${source_default.bold(moduleName)}`));
|
|
|
127036
127391
|
} else {
|
|
127037
127392
|
outputPath = (0, import_path11.join)(ralphDir, `prd-${moduleName}.json`);
|
|
127038
127393
|
}
|
|
127039
|
-
await import_fs_extra9.default.writeJson(outputPath,
|
|
127394
|
+
await import_fs_extra9.default.writeJson(outputPath, unified, { spaces: 2 });
|
|
127040
127395
|
totalGenerated++;
|
|
127041
127396
|
const completenessWarnings = validatePrdCompleteness(prd, featureJson);
|
|
127042
127397
|
if (completenessWarnings.length > 0) {
|
|
@@ -127050,13 +127405,21 @@ Processing module: ${source_default.bold(moduleName)}`));
|
|
|
127050
127405
|
continue;
|
|
127051
127406
|
}
|
|
127052
127407
|
}
|
|
127408
|
+
const taskErrors = validateTaskIntegrity(unified.tasks);
|
|
127409
|
+
if (taskErrors.length > 0) {
|
|
127410
|
+
console.log(source_default.yellow(` Task integrity warnings:`));
|
|
127411
|
+
for (const te of taskErrors) {
|
|
127412
|
+
console.log(source_default.yellow(` - ${te}`));
|
|
127413
|
+
}
|
|
127414
|
+
}
|
|
127053
127415
|
const relOutput = (0, import_path11.relative)(process.cwd(), outputPath);
|
|
127054
|
-
console.log(source_default.green(` Generated: ${relOutput}`));
|
|
127416
|
+
console.log(source_default.green(` Generated: ${relOutput} (unified v3)`));
|
|
127055
127417
|
console.log(source_default.gray(` UCs: ${prd.requirements.useCases.length} | FRs: ${prd.requirements.functionalRequirements.length} | BRs: ${prd.businessRules.length}`));
|
|
127056
127418
|
console.log(source_default.gray(` Endpoints: ${prd.architecture.apiEndpoints.length} | Sections: ${prd.architecture.sections.length}`));
|
|
127057
127419
|
const counts = getPrdFileCounts(prd);
|
|
127058
127420
|
const totalFiles = Object.values(counts).reduce((s, n) => s + n, 0);
|
|
127059
127421
|
console.log(source_default.gray(` Files to create: ${totalFiles} (domain: ${counts.domain}, app: ${counts.application}, infra: ${counts.infrastructure}, api: ${counts.api}, frontend: ${counts.frontend}, seed: ${counts.seedData}, tests: ${counts.tests})`));
|
|
127422
|
+
console.log(source_default.gray(` Tasks: ${unified.tasks.length} (${unified.tasks.filter((t) => t.category === "validation").length} validation)`));
|
|
127060
127423
|
console.log(source_default.gray(` BR mappings: ${prd.implementation.brToCodeMapping.length}`));
|
|
127061
127424
|
}
|
|
127062
127425
|
console.log();
|