Files
16gagent/research/pr-37/pr-37-domain-intelligence-delivery-planning.test.mjs
T
2026-06-06 10:40:48 +08:00

831 lines
34 KiB
JavaScript

/**
* 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");
});
});