/** * PR-37 Domain Intelligence & Delivery Planning — Test Suite * * Part M:全覆盖测试 * * 覆盖: * A — Domain Registry * B — Domain Classification * C — Industry Epic Generation * D — Industry Feature Generation * E — Domain Validation * F — Sprint Planning * G — Release Planning * H — Batch Planning * I — Capacity Planning * J — Roadmap Alignment * K — Factory Integration * Z — Regression (SF-01, SF-02, PR-35, PR-36) */ import { describe, it } from "node:test"; import assert from "node:assert/strict"; // ─── Domain Registry (Part A) ────────────────────── import { getDomainRegistry, getDomain, listDomains, getDomainsByCategory, searchDomains, DOMAINS, DOMAIN_CATEGORY, } from "../src/pr-37-domain-intelligence/domain-registry.mjs"; // ─── Domain Intelligence (Parts B, C, D, E) ──────── import { classifyProduct, } from "../src/pr-37-domain-intelligence/domain-classifier.mjs"; import { generateIndustryEpics, resetEpicCounter, describeDomain, listDomainEpics, } from "../src/pr-37-domain-intelligence/industry-epic-generator.mjs"; import { generateIndustryFeatures, resetFeatureCounter, detectFeatureType, } from "../src/pr-37-domain-intelligence/industry-feature-generator.mjs"; import { validateEpics, validateFeatures, } from "../src/pr-37-domain-intelligence/domain-validator.mjs"; import { createDomainIntelligenceFactory } from "../src/pr-37-domain-intelligence/index.mjs"; // ─── Delivery Planning (Parts F, G, H, I, J) ────── import { generateSprintPlan, defaultSprintConfig, } from "../src/pr-37-delivery-planning/sprint-planner.mjs"; import { generateReleasePlan, } from "../src/pr-37-delivery-planning/release-planner.mjs"; import { generateBatchPlan, } from "../src/pr-37-delivery-planning/batch-planner.mjs"; import { generateCapacityPlan, defaultCapacityConfig, } from "../src/pr-37-delivery-planning/capacity-planner.mjs"; import { alignRoadmapWithPlans, } from "../src/pr-37-delivery-planning/roadmap-aligner.mjs"; import { createDeliveryPlanningFactory, PR37_ARTIFACTS, } from "../src/pr-37-delivery-planning/index.mjs"; // ─── Regression Suite ────────────────────────────── import { createFactoryRegistry } from "../src/software-factory-core/factory-registry.mjs"; import { createFactoryInput } from "../src/software-factory-core/factory-contract.mjs"; import { registerSF01 } from "../src/sf-01-product-strategy/index.mjs"; import { registerSF02 } from "../src/sf-02-requirement-engineering/index.mjs"; // ═════════════════════════════════════════════════════ // Test Fixtures // ═════════════════════════════════════════════════════ function mockStories(count = 30) { return Array.from({ length: count }, (_, i) => ({ id: `us-${i + 1}`, statement: `As a user, I want feature ${i + 1}, so that I can work better.`, role: "用户", goal: `feature ${i + 1}`, value: "better work", priority: i < 10 ? "Must Have" : i < 20 ? "Should Have" : "Could Have", storyPoints: i < 10 ? 3 : i < 20 ? 2 : 1, featureId: `ft-${Math.floor(i / 5) + 1}`, epicId: `ep-${Math.floor(i / 8) + 1}`, })); } function mockEpics(count = 4) { return Array.from({ length: count }, (_, i) => ({ id: `ep-${i + 1}`, title: ["财务管理", "采购管理", "库存管理", "销售管理"][i] || `Epic ${i + 1}`, description: `Description for epic ${i + 1}`, category: "管理", objective: `Objective ${i + 1}`, })); } function mockFeatures(count = 6) { return Array.from({ length: count }, (_, i) => ({ id: `ft-${i + 1}`, title: `Feature ${i + 1}`, description: `Description for feature ${i + 1}`, epicId: `ep-${Math.floor(i / 2) + 1}`, featureType: "CRUD", })); } function mockMVPScope() { return { mvp: { label: "MVP", epics: 2, features: 2, userStories: 10, totalStoryPoints: 30 }, v1: { label: "V1", epics: 4, features: 4, userStories: 20, totalStoryPoints: 50 }, v2: { label: "V2", epics: 4, features: 6, userStories: 30, totalStoryPoints: 60 }, enterprise: { label: "Enterprise", epics: 4, features: 6, userStories: 30 }, global: { label: "Global", epics: 4, features: 6, userStories: 30 }, phases: [ { name: "mvp", scope: "最小可行产品", duration: 3, budget: 300000 }, { name: "v1", scope: "第一完整版本", duration: 4, budget: 500000 }, { name: "v2", scope: "功能增强版本", duration: 3, budget: 300000 }, ], }; } function mockPriors(stories) { return stories.map(s => ({ itemId: s.id, itemType: "story", category: s.priority, score: s.priority === "Must Have" ? 100 : s.priority === "Should Have" ? 70 : 40, })); } // ═════════════════════════════════════════════════════ // A — Domain Registry // ═════════════════════════════════════════════════════ describe("PR-37 Part A — Domain Registry", () => { it("registers exactly 16 domains", () => { const domains = listDomains(); assert.equal(domains.length, 16); }); it("each domain has all 10 knowledge dimensions", () => { for (const name of listDomains()) { const d = getDomain(name); assert.ok(d.name); assert.ok(d.displayName); assert.ok(d.description); assert.ok(d.coreProcesses.length >= 3, `${name}: coreProcesses`); assert.ok(d.coreEntities.length >= 3, `${name}: coreEntities`); assert.ok(d.coreEpics.length >= 5, `${name}: coreEpics`); assert.ok(Object.keys(d.coreFeatures).length >= 3, `${name}: coreFeatures`); assert.ok(d.coreKPIs.length >= 3, `${name}: coreKPIs`); assert.ok(d.coreRoles.length >= 3, `${name}: coreRoles`); assert.ok(d.terminology.length >= 2, `${name}: terminology`); assert.ok(d.constraints.length >= 1, `${name}: constraints`); } }); it("ERP domain has 10 core epics", () => { const erp = getDomain("ERP"); assert.equal(erp.coreEpics.length, 10); const titles = erp.coreEpics.map(e => e.title); assert.ok(titles.includes("财务管理")); assert.ok(titles.includes("采购管理")); assert.ok(titles.includes("库存管理")); assert.ok(titles.includes("生产管理")); }); it("MES domain has 10 core epics", () => { const mes = getDomain("MES"); assert.equal(mes.coreEpics.length, 10); const titles = mes.coreEpics.map(e => e.title); assert.ok(titles.includes("生产执行")); assert.ok(titles.includes("质量管理")); assert.ok(titles.includes("设备管理")); assert.ok(titles.includes("工单管理")); }); it("CRM domain has 9 core epics", () => { const crm = getDomain("CRM"); assert.ok(crm.coreEpics.length >= 7); const titles = crm.coreEpics.map(e => e.title); assert.ok(titles.includes("客户管理")); assert.ok(titles.includes("商机管理")); assert.ok(titles.includes("销售漏斗")); }); it("AI Platform domain has 8 core epics", () => { const ai = getDomain("AI Platform"); assert.ok(ai.coreEpics.length >= 6); const titles = ai.coreEpics.map(e => e.title); assert.ok(titles.includes("知识库管理")); assert.ok(titles.includes("Agent管理")); }); it("getDomain returns null for unknown domain", () => { assert.equal(getDomain("UnknownDomain"), null); }); it("searchDomains finds by keyword", () => { const results = searchDomains("MES"); assert.ok(results.length >= 1); assert.equal(results[0].name, "MES"); }); it("getDomainsByCategory filters correctly", () => { const enterprise = getDomainsByCategory(DOMAIN_CATEGORY.ENTERPRISE_SOFTWARE); assert.ok(enterprise.length >= 3); }); }); // ═════════════════════════════════════════════════════ // B — Domain Classification // ═════════════════════════════════════════════════════ describe("PR-37 Part B — Domain Classification", () => { it("classifies ERP system correctly", () => { const result = classifyProduct({ productName: "ERP系统", description: "企业资源计划" }); assert.equal(result.primaryDomain, "ERP"); assert.ok(result.confidenceScore >= 0.3); assert.ok(result.matchedKeywords.length >= 1); }); it("classifies MES system correctly", () => { const result = classifyProduct({ productName: "工业MES系统", description: "制造执行系统" }); assert.equal(result.primaryDomain, "MES"); assert.ok(result.confidenceScore >= 0.5); }); it("classifies AI customer service platform", () => { const result = classifyProduct({ productName: "AI客服平台", description: "智能客服系统" }); assert.equal(result.primaryDomain, "AI Platform"); }); it("classifies CRM system", () => { const result = classifyProduct({ productName: "CRM系统", description: "客户关系管理" }); assert.equal(result.primaryDomain, "CRM"); }); it("classifies cross-border ecommerce with secondary", () => { const result = classifyProduct({ productName: "跨境电商平台", description: "跨境电商" }); assert.equal(result.primaryDomain, "E-Commerce"); assert.ok(result.secondaryDomain); }); it("classifies industrial AI quality inspection", () => { const result = classifyProduct({ productName: "工业AI质检", description: "智能制造AI质检" }); assert.equal(result.primaryDomain, "MES"); }); it("classifies WMS warehouse", () => { const result = classifyProduct({ productName: "WMS仓库", description: "仓储管理" }); assert.equal(result.primaryDomain, "WMS"); }); it("returns null for empty input", () => { const result = classifyProduct({ productName: "", description: "" }); assert.equal(result.primaryDomain, null); assert.equal(result.confidenceScore, 0); assert.ok(result.classificationReason); }); it("returns null for null input", () => { const result = classifyProduct(null); assert.equal(result.primaryDomain, null); assert.equal(result.confidenceScore, 0); }); }); // ═════════════════════════════════════════════════════ // C — Industry Epic Generation // ═════════════════════════════════════════════════════ describe("PR-37 Part C — Industry Epic Generation", () => { it("generates ERP-specific epics with domain vocabulary", () => { resetEpicCounter(); const epics = generateIndustryEpics("ERP", { productName: "ERP系统" }); assert.equal(epics.length, 10); const titles = epics.map(e => e.title); assert.ok(titles.includes("财务管理")); assert.ok(titles.includes("采购管理")); assert.ok(titles.includes("生产管理")); // Should NOT have generic "内容管理" assert.ok(!titles.includes("内容管理")); }); it("generates MES-specific epics", () => { resetEpicCounter(); const epics = generateIndustryEpics("MES", { productName: "工业MES系统" }); assert.equal(epics.length, 10); const titles = epics.map(e => e.title); assert.ok(titles.includes("质量管理")); assert.ok(titles.includes("设备管理")); assert.ok(titles.includes("追溯管理")); }); it("generates AI Platform epics", () => { resetEpicCounter(); const epics = generateIndustryEpics("AI Platform", { productName: "AI客服平台" }); assert.ok(epics.length >= 6); const titles = epics.map(e => e.title); assert.ok(titles.includes("知识库管理")); assert.ok(titles.includes("Agent管理")); }); it("each epic has all required fields", () => { resetEpicCounter(); const epics = generateIndustryEpics("ERP", { productName: "ERP系统" }); for (const epic of epics) { assert.ok(epic.id.startsWith("ep-")); assert.ok(epic.title); assert.ok(epic.description); assert.ok(epic.category); assert.ok(epic.objective); assert.ok(epic.successMetric); } }); it("falls back to generic template for unknown domain", () => { resetEpicCounter(); const epics = generateIndustryEpics("量子计算", { productName: "量子计算机" }); assert.ok(epics.length >= 5); // Generic epics shouldn't have ERP-specific ones const titles = epics.map(e => e.title); assert.ok(!titles.includes("财务管理")); }); }); // ═════════════════════════════════════════════════════ // D — Industry Feature Generation // ═════════════════════════════════════════════════════ describe("PR-37 Part D — Industry Feature Generation", () => { it("generates ERP features from domain registry", () => { resetEpicCounter(); resetFeatureCounter(); const epics = generateIndustryEpics("ERP", { productName: "ERP系统" }); const features = generateIndustryFeatures("ERP", epics); assert.ok(features.length >= 50); const titles = features.map(f => f.title); assert.ok(titles.includes("总账管理")); assert.ok(titles.includes("供应商管理")); }); it("detects feature types correctly", () => { assert.equal(detectFeatureType("数据分析"), "analytics"); assert.equal(detectFeatureType("审批流程"), "workflow"); assert.equal(detectFeatureType("统计分析"), "analytics"); assert.equal(detectFeatureType("消息推送"), "notification"); assert.equal(detectFeatureType("搜索"), "search"); assert.equal(detectFeatureType("系统设置"), "configuration"); assert.equal(detectFeatureType("用户管理"), "CRUD"); }); it("each feature references correct epicId", () => { resetEpicCounter(); resetFeatureCounter(); const epics = generateIndustryEpics("ERP", { productName: "ERP系统" }); const features = generateIndustryFeatures("ERP", epics); const epicIds = new Set(epics.map(e => e.id)); for (const f of features) { assert.ok(epicIds.has(f.epicId), `Feature ${f.title} epicId ${f.epicId} not in epics`); } }); it("each feature has id with ft- prefix", () => { resetEpicCounter(); resetFeatureCounter(); const epics = generateIndustryEpics("MES", { productName: "MES系统" }); const features = generateIndustryFeatures("MES", epics); for (const f of features) { assert.ok(f.id.startsWith("ft-")); } }); }); // ═════════════════════════════════════════════════════ // E — Domain Validation // ═════════════════════════════════════════════════════ describe("PR-37 Part E — Domain Validation", () => { it("validates ERP epics with 100% coverage", () => { resetEpicCounter(); const epics = generateIndustryEpics("ERP", { productName: "ERP系统" }); const result = validateEpics(epics, "ERP"); assert.equal(result.overallVerdict, "pass"); assert.equal(result.coverageScore, 100); assert.equal(result.completenessScore, 100); }); it("validates MES epics with 100% coverage", () => { resetEpicCounter(); const epics = generateIndustryEpics("MES", { productName: "MES系统" }); const result = validateEpics(epics, "MES"); assert.equal(result.overallVerdict, "pass"); assert.equal(result.coverageScore, 100); }); it("detects generic epics as low coverage for ERP", () => { const genericEpics = [ { title: "用户管理" }, { title: "内容管理" }, { title: "业务流程" }, ]; const result = validateEpics(genericEpics, "ERP"); assert.ok(result.coverageScore < 100); assert.ok(result.missingEpics.length > 0); assert.equal(result.overallVerdict, "fail"); }); it("validates features consistency", () => { resetEpicCounter(); resetFeatureCounter(); const epics = generateIndustryEpics("ERP", { productName: "ERP系统" }); const features = generateIndustryFeatures("ERP", epics); const result = validateFeatures(features, "ERP"); assert.ok(result.consistencyScore >= 70); }); it("handles unknown domain gracefully", () => { const result = validateEpics([{ title: "Test" }], "UnknownDomain"); assert.equal(result.coverageScore, 0); assert.equal(result.overallVerdict, "fail"); }); }); // ═════════════════════════════════════════════════════ // F — Sprint Planning // ═════════════════════════════════════════════════════ describe("PR-37 Part F — Sprint Planning", () => { it("generates sprints from Must Have stories", () => { const stories = mockStories(30); const priors = mockPriors(stories); const result = generateSprintPlan({ stories, dependencies: [], priorities: priors, config: defaultSprintConfig({ teamVelocity: 20 }), }); assert.ok(result.sprints.length >= 1, `Expected >=1 sprints, got ${result.sprints.length}`); assert.ok(result.metrics.totalSprints >= 1); }); it("respects sprint capacity", () => { const stories = mockStories(30); const priors = mockPriors(stories); const result = generateSprintPlan({ stories, dependencies: [], priorities: priors, config: defaultSprintConfig({ teamVelocity: 15, maxStoryPointsPerSprint: 15 }), }); for (const sprint of result.sprints) { assert.ok(sprint.totalStoryPoints <= 15 + 5, // allow small overflow `Sprint ${sprint.sprintNumber}: ${sprint.totalStoryPoints} pts > 20`); } }); it("each sprint has required fields", () => { const result = generateSprintPlan({ stories: mockStories(20), dependencies: [], priorities: mockPriors(mockStories(20)), }); for (const s of result.sprints) { assert.ok(s.sprintNumber); assert.ok(s.sprintGoal); assert.ok(s.startDate); assert.ok(s.endDate); assert.ok(Array.isArray(s.storyIds)); assert.ok(s.totalStoryPoints >= 0); assert.ok(Array.isArray(s.risks)); } }); it("handles single story", () => { const stories = [{ id: "us-1", statement: "test", priority: "Must Have", storyPoints: 3, featureId: "ft-1", epicId: "ep-1" }]; const priors = [{ itemId: "us-1", itemType: "story", category: "Must Have", score: 100 }]; const result = generateSprintPlan({ stories, dependencies: [], priorities: priors }); assert.equal(result.sprints.length, 1); assert.equal(result.sprints[0].storyIds.length, 1); }); it("handles empty stories", () => { const result = generateSprintPlan({ stories: [], dependencies: [], priorities: [] }); assert.equal(result.sprints.length, 0); assert.equal(result.metrics.totalSprints, 0); }); it("respects dependency order", () => { const stories = [ { id: "us-1", statement: "base", priority: "Must Have", storyPoints: 5, featureId: "ft-1", epicId: "ep-1" }, { id: "us-2", statement: "dependent", priority: "Must Have", storyPoints: 5, featureId: "ft-1", epicId: "ep-1" }, { id: "us-3", statement: "filler", priority: "Should Have", storyPoints: 3, featureId: "ft-1", epicId: "ep-1" }, ]; const priors = [ { itemId: "us-1", itemType: "story", category: "Must Have", score: 100 }, { itemId: "us-2", itemType: "story", category: "Must Have", score: 95 }, { itemId: "us-3", itemType: "story", category: "Should Have", score: 70 }, ]; // us-2 depends on us-1 (us-2 → us-1 means us-2 requires us-1 first) const deps = { edges: [{ from: "us-2", to: "us-1", critical: true }] }; const result = generateSprintPlan({ stories, dependencies: deps, priorities: priors, config: defaultSprintConfig({ teamVelocity: 8 }) }); // Both us-1 and us-2 should be in the sprint plan const allIds = result.sprints.flatMap(s => s.storyIds); assert.ok(allIds.includes("us-1"), "us-1 should be planned"); assert.ok(allIds.includes("us-2"), "us-2 should be planned"); // us-2 must not be assigned before us-1 (dependency constraint) const s1 = result.sprints.find(s => s.storyIds.includes("us-1")); const s2 = result.sprints.find(s => s.storyIds.includes("us-2")); assert.ok(s1.sprintNumber <= s2.sprintNumber, `us-1 (Sprint ${s1.sprintNumber}) must not be after us-2 (Sprint ${s2.sprintNumber})`); }); }); // ═════════════════════════════════════════════════════ // G — Release Planning // ═════════════════════════════════════════════════════ describe("PR-37 Part G — Release Planning", () => { it("generates 5 releases from mvpScope", () => { const result = generateReleasePlan({ mvpScope: mockMVPScope(), priorities: mockPriors(mockStories(30)), dependencies: [], stories: mockStories(30), epics: mockEpics(4), features: mockFeatures(6), }); assert.equal(result.releases.length, 5); const versions = result.releases.map(r => r.version); assert.ok(versions.includes("1.0.0")); assert.ok(versions.includes("2.0.0")); }); it("R1.0 (MVP) contains Must Have stories", () => { const stories = mockStories(30); const result = generateReleasePlan({ mvpScope: mockMVPScope(), priorities: mockPriors(stories), dependencies: [], stories, epics: mockEpics(4), features: mockFeatures(6), }); const r1 = result.releases[0]; assert.ok(r1.features.storyCount > 0); assert.ok(r1.validationGates.length >= 3); }); it("each release has validation gates and goals", () => { const result = generateReleasePlan({ mvpScope: mockMVPScope(), priorities: mockPriors(mockStories(30)), dependencies: [], stories: mockStories(30), epics: mockEpics(4), features: mockFeatures(6), }); for (const release of result.releases) { assert.ok(release.version); assert.ok(release.name); assert.ok(Array.isArray(release.validationGates)); assert.ok(release.releaseGoals); } }); it("handles null mvpScope gracefully", () => { const result = generateReleasePlan({ mvpScope: null, priorities: [], dependencies: [], stories: [], epics: [], features: [], }); assert.ok(result.releases); }); }); // ═════════════════════════════════════════════════════ // H — Batch Planning // ═════════════════════════════════════════════════════ describe("PR-37 Part H — Batch Planning", () => { it("generates batches with dependency levels", () => { const stories = [ { id: "us-1", statement: "no deps", priority: "Must Have", storyPoints: 3, featureId: "ft-1", epicId: "ep-1" }, { id: "us-2", statement: "dep on 1", priority: "Must Have", storyPoints: 2, featureId: "ft-1", epicId: "ep-1" }, { id: "us-3", statement: "dep on 2", priority: "Should Have", storyPoints: 1, featureId: "ft-1", epicId: "ep-1" }, ]; const deps = { edges: [ { from: "us-2", to: "us-1", critical: true }, { from: "us-3", to: "us-2", critical: true }, ], }; const result = generateBatchPlan({ stories, dependencies: deps, priorities: mockPriors(stories), sprints: { sprints: [] }, releases: { releases: [] }, }); assert.ok(result.batches.length >= 1); // Should have at least a DevelopmentBatch with dependency levels const devBatch = result.batches.find(b => b.batchType === "DevelopmentBatch"); assert.ok(devBatch); }); it("generates all 4 batch types when data is sufficient", () => { const stories = mockStories(20); const sp = generateSprintPlan({ stories, dependencies: [], priorities: mockPriors(stories) }); const rp = generateReleasePlan({ mvpScope: mockMVPScope(), priorities: mockPriors(stories), dependencies: [], stories, epics: mockEpics(4), features: mockFeatures(6), }); const result = generateBatchPlan({ stories, dependencies: [], priorities: mockPriors(stories), sprints: sp, releases: rp.releases, }); assert.ok(result.batches.length >= 1); const types = new Set(result.batches.map(b => b.batchType)); assert.ok(types.has("DevelopmentBatch")); }); }); // ═════════════════════════════════════════════════════ // I — Capacity Planning // ═════════════════════════════════════════════════════ describe("PR-37 Part I — Capacity Planning", () => { it("calculates capacity from sprint plan", () => { const stories = mockStories(20); const sp = generateSprintPlan({ stories, dependencies: [], priorities: mockPriors(stories) }); const cp = generateCapacityPlan({ sprints: sp.sprints, releases: [] }); assert.ok(cp.totalEffort > 0); assert.ok(cp.totalCapacity > 0); assert.ok(cp.capacityUtilization.includes("%")); }); it("delivery forecast provides realistic date", () => { const stories = mockStories(20); const sp = generateSprintPlan({ stories, dependencies: [], priorities: mockPriors(stories) }); const cp = generateCapacityPlan({ sprints: sp.sprints, releases: [] }); assert.ok(cp.deliveryForecast.realistic); assert.notEqual(cp.deliveryForecast.realistic, "N/A"); }); it("handles empty sprint list gracefully", () => { const cp = generateCapacityPlan({ sprints: [], releases: [] }); assert.equal(cp.totalEffort, 0); }); }); // ═════════════════════════════════════════════════════ // J — Roadmap Alignment // ═════════════════════════════════════════════════════ describe("PR-37 Part J — Roadmap Alignment", () => { it("aligns roadmap with sprint and release plans", () => { const stories = mockStories(30); const sp = generateSprintPlan({ stories, dependencies: [], priorities: mockPriors(stories) }); const rp = generateReleasePlan({ mvpScope: mockMVPScope(), priorities: mockPriors(stories), dependencies: [], stories, epics: mockEpics(4), features: mockFeatures(6), }); const bp = generateBatchPlan({ stories, dependencies: [], priorities: mockPriors(stories), sprints: sp.sprints, releases: rp.releases }); const result = alignRoadmapWithPlans({ roadmap: mockMVPScope(), sprintPlan: sp, releasePlan: rp, batchPlan: bp, mvpScope: mockMVPScope(), }); assert.ok(["aligned", "partial", "misaligned"].includes(result.overallVerdict)); assert.ok(result.alignmentScore >= 0 && result.alignmentScore <= 100); assert.ok(result.summary); }); }); // ═════════════════════════════════════════════════════ // K — Factory Integration // ═════════════════════════════════════════════════════ describe("PR-37 Part K — Factory Integration", () => { it("createDomainIntelligenceFactory produces epics + features + validation", () => { const result = createDomainIntelligenceFactory({ productName: "ERP系统" }); assert.equal(result.classification.primaryDomain, "ERP"); assert.ok(result.epics.length >= 8); assert.ok(result.features.length >= 50); assert.ok(result.validation); assert.equal(result.validation.overallVerdict, "pass"); }); it("createDomainIntelligenceFactory accepts domainOverride", () => { const result = createDomainIntelligenceFactory( { productName: "神秘系统" }, { domainOverride: "MES" } ); assert.equal(result.classification.primaryDomain, "MES"); assert.equal(result.classification.confidenceScore, 1.0); }); it("createDeliveryPlanningFactory produces 4 plans", () => { const stories = mockStories(30); const sf02Data = { userStories: stories, epics: mockEpics(4), features: mockFeatures(6), dependencies: { nodes: [], edges: [] }, priorities: mockPriors(stories), mvpScope: mockMVPScope(), roadmap: mockMVPScope().phases, }; const result = createDeliveryPlanningFactory(sf02Data); assert.equal(result.status, "passed"); assert.ok(result.sprintPlan.sprints.length >= 1); assert.ok(result.releasePlan.releases.length >= 1); assert.ok(result.batchPlan.batches.length >= 1); assert.ok(result.capacityPlan.totalEffort > 0); assert.ok(result.alignmentReport); assert.ok(["aligned", "partial", "misaligned"].includes(result.alignmentReport.overallVerdict)); }); it("createDeliveryPlanningFactory fails gracefully with no data", () => { const result = createDeliveryPlanningFactory(null); assert.equal(result.status, "failed"); assert.ok(result.error); }); it("PR37_ARTIFACTS has all artifact types", () => { const types = Object.values(PR37_ARTIFACTS); assert.ok(types.includes("sprint-plan")); assert.ok(types.includes("release-plan")); assert.ok(types.includes("batch-plan")); assert.ok(types.includes("capacity-plan")); assert.ok(types.includes("alignment-report")); }); }); // ═════════════════════════════════════════════════════ // Z — Regression: SF-01, SF-02, PR-35, PR-36 // ═════════════════════════════════════════════════════ describe("PR-37 Part Z — Regression", () => { it("SF-01 factory still works after PR-37", () => { const registry = createFactoryRegistry(); registerSF01(registry); const input = createFactoryInput({ factoryId: "SF-01", parameters: { idea: "AI客服平台" } }); const output = registry.execute("SF-01", input); assert.equal(output.status, "passed"); }); it("SF-02 factory still works after PR-37", () => { const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); const sf01Out = registry.execute("SF-01", createFactoryInput({ factoryId: "SF-01", parameters: { idea: "AI客服平台" } })); const sf02Out = registry.execute("SF-02", createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Out.artifacts })); assert.equal(sf02Out.status, "passed"); assert.ok(sf02Out.artifacts.length >= 10); }); it("SF-01 → SF-02 → PR-37 full pipeline", () => { const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); const sf01Out = registry.execute("SF-01", createFactoryInput({ factoryId: "SF-01", parameters: { idea: "ERP系统" } })); const sf02Out = registry.execute("SF-02", createFactoryInput({ factoryId: "SF-02", upstreamArtifacts: sf01Out.artifacts })); // Extract SF-02 data const stories = JSON.parse(sf02Out.artifacts.find(a => a.metadata?.artifactType === "user-story-catalog").content); const epics = JSON.parse(sf02Out.artifacts.find(a => a.metadata?.artifactType === "epic-catalog").content); const features = JSON.parse(sf02Out.artifacts.find(a => a.metadata?.artifactType === "feature-catalog").content); const depGraph = JSON.parse(sf02Out.artifacts.find(a => a.metadata?.artifactType === "dependency-graph").content); const priMatrix = JSON.parse(sf02Out.artifacts.find(a => a.metadata?.artifactType === "priority-matrix").content); const mvpScope = JSON.parse(sf02Out.artifacts.find(a => a.metadata?.artifactType === "mvp-scope").content); // Build pri entries from priMatrix const priEntries = []; const catMap = { mustHave: "Must Have", shouldHave: "Should Have", couldHave: "Could Have", wontHave: "Won't Have" }; if (priMatrix?.story) { for (const [cat, ids] of Object.entries(priMatrix.story)) { if (cat === "summary") continue; for (const id of ids) priEntries.push({ itemId: id, itemType: "story", category: catMap[cat] || cat, score: cat === "mustHave" ? 100 : cat === "shouldHave" ? 70 : 40 }); } } // PR-37 Domain Intelligence const diResult = createDomainIntelligenceFactory({ productName: "ERP系统" }); assert.equal(diResult.classification.primaryDomain, "ERP"); assert.ok(diResult.epics.length >= 8); assert.equal(diResult.validation.overallVerdict, "pass"); // PR-37 Delivery Planning const dpResult = createDeliveryPlanningFactory({ userStories: stories, epics, features, dependencies: depGraph, priorities: priEntries, mvpScope, roadmap: mvpScope.phases, }); assert.equal(dpResult.status, "passed"); assert.ok(dpResult.sprintPlan.sprints.length >= 1); assert.ok(dpResult.releasePlan.releases.length >= 1); assert.ok(dpResult.alignmentReport); }); it("PR-35/PR-36 factory registry topological order preserved", () => { const registry = createFactoryRegistry(); registerSF01(registry); registerSF02(registry); const order = registry.getTopologicalOrder(); const sf01Idx = order.indexOf("SF-01"); const sf02Idx = order.indexOf("SF-02"); assert.ok(sf01Idx < sf02Idx, "SF-01 must come before SF-02 in topological order"); }); });