speccore 6.16.0 → 6.17.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/commands/about.js +2 -2
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +4 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/dev.js +30 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doc2spec.js +10 -7
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/core/analyze-engine.d.ts +16 -0
- package/dist/core/analyze-engine.d.ts.map +1 -1
- package/dist/core/analyze-engine.js +400 -1
- package/dist/core/analyze-engine.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ exports.runAnalysis = runAnalysis;
|
|
|
4
4
|
exports.analyzeSingleFeature = analyzeSingleFeature;
|
|
5
5
|
exports.analyzeSingleTypedDoc = analyzeSingleTypedDoc;
|
|
6
6
|
exports.supplementAnalysis = supplementAnalysis;
|
|
7
|
+
exports.generateSpecsFromRequirements = generateSpecsFromRequirements;
|
|
7
8
|
/**
|
|
8
9
|
* analyze-engine — 统一分析引擎
|
|
9
10
|
*
|
|
@@ -690,7 +691,15 @@ function scanCompleteness(content) {
|
|
|
690
691
|
if (!hasDataModel) {
|
|
691
692
|
issues.push({ severity: 'info', category: '内容完整性', message: '未找到数据模型/数据表描述。如需数据库变更,建议补充。' });
|
|
692
693
|
}
|
|
693
|
-
|
|
694
|
+
// 去重:同一 message 只保留第一次出现(避免多文档内容重复扫描导致重复告警)
|
|
695
|
+
const seen = new Set();
|
|
696
|
+
return issues.filter(issue => {
|
|
697
|
+
const key = issue.message;
|
|
698
|
+
if (seen.has(key))
|
|
699
|
+
return false;
|
|
700
|
+
seen.add(key);
|
|
701
|
+
return true;
|
|
702
|
+
});
|
|
694
703
|
}
|
|
695
704
|
async function analyzeArchitectureImpact(content) {
|
|
696
705
|
const impact = { modules: [], newDependencies: [], risks: [], apis: [] };
|
|
@@ -1532,4 +1541,394 @@ async function detectPlatformsFromConstitution() {
|
|
|
1532
1541
|
catch { }
|
|
1533
1542
|
return ['app', 'h5', 'miniapp', 'admin']; // 默认四端
|
|
1534
1543
|
}
|
|
1544
|
+
/**
|
|
1545
|
+
* 从需求文档内容中提取结构化信息,生成有实质内容的 Spec 文件。
|
|
1546
|
+
* 用于 analyze --auto 模式,替代 init 创建的空模板。
|
|
1547
|
+
*/
|
|
1548
|
+
async function generateSpecsFromRequirements(reqPaths, iteration, specDir) {
|
|
1549
|
+
// 1. 读取所有需求内容
|
|
1550
|
+
const allContent = [];
|
|
1551
|
+
for (const p of reqPaths) {
|
|
1552
|
+
if (await (0, fs_extra_1.pathExists)(p)) {
|
|
1553
|
+
allContent.push(await (0, fs_extra_1.readFile)(p, 'utf-8'));
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
const fullContent = allContent.join('\n\n---\n\n');
|
|
1557
|
+
if (fullContent.trim().length < 20) {
|
|
1558
|
+
logger_1.logger.warn(' ⚠️ 需求文档内容过少,无法生成有效 Spec 文件');
|
|
1559
|
+
return { files: [], summary: { total: 0, withContent: 0, skipped: 0 } };
|
|
1560
|
+
}
|
|
1561
|
+
// 2. 提取结构化信息
|
|
1562
|
+
const apis = extractApis(fullContent);
|
|
1563
|
+
const features = extractFeatures(fullContent);
|
|
1564
|
+
const dataModels = extractDataModels(fullContent);
|
|
1565
|
+
const businessRules = extractBusinessRules(fullContent);
|
|
1566
|
+
const archImpact = await analyzeArchitectureImpact(fullContent);
|
|
1567
|
+
const platforms = await detectPlatformsFromConstitution();
|
|
1568
|
+
const now = new Date().toISOString().split('T')[0];
|
|
1569
|
+
// 3. 生成各 Spec 文件
|
|
1570
|
+
const files = [];
|
|
1571
|
+
// REQUIREMENT.md — 结构化需求规格
|
|
1572
|
+
files.push({
|
|
1573
|
+
filename: 'REQUIREMENT.md',
|
|
1574
|
+
content: buildRequirementSpec(iteration, now, features, apis, dataModels, businessRules),
|
|
1575
|
+
});
|
|
1576
|
+
// TECH.md — 技术方案
|
|
1577
|
+
files.push({
|
|
1578
|
+
filename: 'TECH.md',
|
|
1579
|
+
content: buildTechSpec(iteration, now, apis, dataModels, archImpact, platforms, features),
|
|
1580
|
+
});
|
|
1581
|
+
// TEST.md — 测试计划
|
|
1582
|
+
files.push({
|
|
1583
|
+
filename: 'TEST.md',
|
|
1584
|
+
content: buildTestSpec(iteration, now, features, apis),
|
|
1585
|
+
});
|
|
1586
|
+
// REVIEW.md — 评审清单
|
|
1587
|
+
files.push({
|
|
1588
|
+
filename: 'REVIEW.md',
|
|
1589
|
+
content: buildReviewSpec(iteration, now, apis, archImpact),
|
|
1590
|
+
});
|
|
1591
|
+
// RISK.md — 风险评估
|
|
1592
|
+
files.push({
|
|
1593
|
+
filename: 'RISK.md',
|
|
1594
|
+
content: buildRiskSpec(iteration, now, archImpact),
|
|
1595
|
+
});
|
|
1596
|
+
// DEPS.md — 依赖清单
|
|
1597
|
+
files.push({
|
|
1598
|
+
filename: 'DEPS.md',
|
|
1599
|
+
content: buildDepsSpec(iteration, now, archImpact),
|
|
1600
|
+
});
|
|
1601
|
+
// MONITOR.md — 监控指标
|
|
1602
|
+
files.push({
|
|
1603
|
+
filename: 'MONITOR.md',
|
|
1604
|
+
content: buildMonitorSpec(iteration, now, apis, features),
|
|
1605
|
+
});
|
|
1606
|
+
// 4. 写入文件(覆盖空模板,不覆盖已有实质内容的文件)
|
|
1607
|
+
let withContent = 0;
|
|
1608
|
+
let skipped = 0;
|
|
1609
|
+
await (0, fs_extra_1.ensureDir)(specDir);
|
|
1610
|
+
for (const f of files) {
|
|
1611
|
+
const filePath = (0, path_1.join)(specDir, f.filename);
|
|
1612
|
+
// 如果文件已存在且有实质内容(>50 非模板字符),跳过
|
|
1613
|
+
if (await (0, fs_extra_1.pathExists)(filePath)) {
|
|
1614
|
+
const existing = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
|
|
1615
|
+
const meaningful = stripTemplateNoise(existing);
|
|
1616
|
+
if (meaningful.length > 50) {
|
|
1617
|
+
skipped++;
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
await (0, fs_extra_1.writeFile)(filePath, f.content);
|
|
1622
|
+
withContent++;
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
files,
|
|
1626
|
+
summary: { total: files.length, withContent, skipped },
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
// ── 信息提取工具函数 ──
|
|
1630
|
+
function extractApis(content) {
|
|
1631
|
+
const apis = [];
|
|
1632
|
+
// 从表格中提取: | GET | /api/xxx | 说明 |
|
|
1633
|
+
const tableRegex = /\|\s*(GET|POST|PUT|DELETE|PATCH|get|post|put|delete|patch)\s*\|\s*(\/[^\s|]+)\s*\|\s*([^|\n]*)\|/g;
|
|
1634
|
+
let m;
|
|
1635
|
+
while ((m = tableRegex.exec(content)) !== null) {
|
|
1636
|
+
apis.push({ method: m[1].toUpperCase(), path: m[2].trim(), desc: m[3].trim() });
|
|
1637
|
+
}
|
|
1638
|
+
// 从行内提取: POST /api/xxx
|
|
1639
|
+
const inlineRegex = /(GET|POST|PUT|DELETE|PATCH)\s+(\/api\/[^\s,,。]+)/gi;
|
|
1640
|
+
while ((m = inlineRegex.exec(content)) !== null) {
|
|
1641
|
+
const path = m[2].trim();
|
|
1642
|
+
if (!apis.some(a => a.path === path)) {
|
|
1643
|
+
apis.push({ method: m[1].toUpperCase(), path, desc: '' });
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
// 去重
|
|
1647
|
+
const seen = new Set();
|
|
1648
|
+
return apis.filter(a => {
|
|
1649
|
+
const key = `${a.method}:${a.path}`;
|
|
1650
|
+
if (seen.has(key))
|
|
1651
|
+
return false;
|
|
1652
|
+
seen.add(key);
|
|
1653
|
+
return true;
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
function extractFeatures(content) {
|
|
1657
|
+
const features = [];
|
|
1658
|
+
// 从 ## / ### 标题提取功能模块
|
|
1659
|
+
const lines = content.split('\n');
|
|
1660
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1661
|
+
const line = lines[i];
|
|
1662
|
+
const headingMatch = line.match(/^#{2,3}\s+(.+)$/);
|
|
1663
|
+
if (headingMatch) {
|
|
1664
|
+
const name = headingMatch[1].trim();
|
|
1665
|
+
// 跳过通用标题
|
|
1666
|
+
if (/^(需求|功能|接口|附录|目录|概述|背景|目标|范围|非功能)/.test(name))
|
|
1667
|
+
continue;
|
|
1668
|
+
if (/^(测试|评审|风险|依赖|监控|技术)/.test(name))
|
|
1669
|
+
continue;
|
|
1670
|
+
// 取后续 1-2 行作为描述
|
|
1671
|
+
let desc = '';
|
|
1672
|
+
for (let j = i + 1; j < Math.min(i + 4, lines.length); j++) {
|
|
1673
|
+
if (lines[j].match(/^#/))
|
|
1674
|
+
break;
|
|
1675
|
+
if (lines[j].trim())
|
|
1676
|
+
desc += lines[j].trim() + ' ';
|
|
1677
|
+
}
|
|
1678
|
+
if (name.length > 1 && name.length < 30) {
|
|
1679
|
+
features.push({ name, desc: desc.slice(0, 100).trim() });
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
// 去重(按名称)
|
|
1684
|
+
const seen = new Set();
|
|
1685
|
+
return features.filter(f => {
|
|
1686
|
+
if (seen.has(f.name))
|
|
1687
|
+
return false;
|
|
1688
|
+
seen.add(f.name);
|
|
1689
|
+
return true;
|
|
1690
|
+
}).slice(0, 20); // 最多 20 个功能模块
|
|
1691
|
+
}
|
|
1692
|
+
function extractDataModels(content) {
|
|
1693
|
+
const models = [];
|
|
1694
|
+
// 检测数据表关键词
|
|
1695
|
+
const tablePatterns = [
|
|
1696
|
+
/(?:表名|数据表|实体|模型)[::]\s*(\w+)/gi,
|
|
1697
|
+
/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?(\w+)/gi,
|
|
1698
|
+
/(?:user|order|product|item|payment|auth|log|config|setting)s?\b/gi,
|
|
1699
|
+
];
|
|
1700
|
+
const tables = new Set();
|
|
1701
|
+
for (const pattern of tablePatterns) {
|
|
1702
|
+
let m;
|
|
1703
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
1704
|
+
tables.add(m[1] || m[0]);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
for (const t of tables) {
|
|
1708
|
+
models.push({ table: t, fields: '—', desc: '从需求推导' });
|
|
1709
|
+
}
|
|
1710
|
+
return models.slice(0, 15);
|
|
1711
|
+
}
|
|
1712
|
+
function extractBusinessRules(content) {
|
|
1713
|
+
const rules = [];
|
|
1714
|
+
// 匹配 R-XX 格式的业务规则编号
|
|
1715
|
+
const ruleRegex = /R-\d{2,4}[-.]?\d{0,2}[::]*\s*(.+)/g;
|
|
1716
|
+
let m;
|
|
1717
|
+
while ((m = ruleRegex.exec(content)) !== null) {
|
|
1718
|
+
rules.push(m[1].trim());
|
|
1719
|
+
}
|
|
1720
|
+
// 匹配「必须」「不允许」「应当」等规则描述
|
|
1721
|
+
const mustRegex = /(?:必须|不允许|应当|不能|需要|确保)\s*(.{5,60})/g;
|
|
1722
|
+
while ((m = mustRegex.exec(content)) !== null) {
|
|
1723
|
+
const rule = m[1].trim().replace(/[。,,.]+$/, '');
|
|
1724
|
+
if (rule.length > 5 && !rules.some(r => r.includes(rule))) {
|
|
1725
|
+
rules.push(rule);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
return rules.slice(0, 15);
|
|
1729
|
+
}
|
|
1730
|
+
function stripTemplateNoise(content) {
|
|
1731
|
+
// 移除模板占位符后计算有效内容长度
|
|
1732
|
+
return content
|
|
1733
|
+
.replace(/_待填充_|_待补充_|_待 AI 分析_|_待定_|_待导入_/g, '')
|
|
1734
|
+
.replace(/\|\s*:---[\s|:-]*\|/g, '') // 表格分隔行
|
|
1735
|
+
.replace(/\|\s*\|\s*\|/g, '') // 空表格行
|
|
1736
|
+
.replace(/^#+\s.*$/gm, '') // 标题行
|
|
1737
|
+
.replace(/^>.*$/gm, '') // 引用行
|
|
1738
|
+
.replace(/\s/g, '')
|
|
1739
|
+
.trim();
|
|
1740
|
+
}
|
|
1741
|
+
// ── Spec 文件内容构建器 ──
|
|
1742
|
+
function buildRequirementSpec(iter, now, features, apis, models, rules) {
|
|
1743
|
+
let md = `# 需求规格说明书\n\n> 迭代: ${iter} | 生成: ${now} | 由 analyze --auto 自动提取\n\n`;
|
|
1744
|
+
md += `## 1. 功能模块清单\n\n`;
|
|
1745
|
+
if (features.length > 0) {
|
|
1746
|
+
md += `| # | 功能模块 | 描述 |\n| :--- | :--- | :--- |\n`;
|
|
1747
|
+
features.forEach((f, i) => { md += `| ${i + 1} | ${f.name} | ${f.desc || '—'} |\n`; });
|
|
1748
|
+
}
|
|
1749
|
+
else {
|
|
1750
|
+
md += `_需求文档中未检测到明确的功能模块标题,建议补充功能章节。_\n`;
|
|
1751
|
+
}
|
|
1752
|
+
md += `\n## 2. 接口清单\n\n`;
|
|
1753
|
+
if (apis.length > 0) {
|
|
1754
|
+
md += `| 方法 | 路径 | 说明 |\n| :--- | :--- | :--- |\n`;
|
|
1755
|
+
apis.forEach(a => { md += `| ${a.method} | \`${a.path}\` | ${a.desc || '—'} |\n`; });
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
md += `_需求文档中未检测到接口定义,建议补充 API 规格。_\n`;
|
|
1759
|
+
}
|
|
1760
|
+
md += `\n## 3. 数据模型\n\n`;
|
|
1761
|
+
if (models.length > 0) {
|
|
1762
|
+
md += `| 实体/表 | 关键字段 | 说明 |\n| :--- | :--- | :--- |\n`;
|
|
1763
|
+
models.forEach(m => { md += `| ${m.table} | ${m.fields} | ${m.desc} |\n`; });
|
|
1764
|
+
}
|
|
1765
|
+
else {
|
|
1766
|
+
md += `_需求文档中未检测到数据模型描述。_\n`;
|
|
1767
|
+
}
|
|
1768
|
+
md += `\n## 4. 业务规则\n\n`;
|
|
1769
|
+
if (rules.length > 0) {
|
|
1770
|
+
rules.forEach((r, i) => { md += `${i + 1}. ${r}\n`; });
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
md += `_需求文档中未检测到明确业务规则。_\n`;
|
|
1774
|
+
}
|
|
1775
|
+
return md;
|
|
1776
|
+
}
|
|
1777
|
+
function buildTechSpec(iter, now, apis, models, archImpact, platforms, features) {
|
|
1778
|
+
let md = `# 技术方案\n\n> 迭代: ${iter} | 生成: ${now} | 由 analyze --auto 自动提取\n\n`;
|
|
1779
|
+
// 架构
|
|
1780
|
+
md += `## 1. 整体架构\n\n`;
|
|
1781
|
+
md += `基于需求分析,系统涉及以下端: ${platforms.join('、') || '未配置'}\n\n`;
|
|
1782
|
+
if (archImpact.modules.length > 0) {
|
|
1783
|
+
md += `**架构影响**: ${archImpact.modules.join('; ')}\n\n`;
|
|
1784
|
+
}
|
|
1785
|
+
// API 设计
|
|
1786
|
+
md += `## 2. API 设计\n\n`;
|
|
1787
|
+
if (apis.length > 0) {
|
|
1788
|
+
md += `| 方法 | 路径 | 说明 | 所属模块 |\n| :--- | :--- | :--- | :--- |\n`;
|
|
1789
|
+
apis.forEach(a => {
|
|
1790
|
+
const mod = guessModule(a.path, features);
|
|
1791
|
+
md += `| ${a.method} | \`${a.path}\` | ${a.desc || '—'} | ${mod} |\n`;
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
else {
|
|
1795
|
+
md += `_未检测到 API 定义,需根据需求补充。_\n`;
|
|
1796
|
+
}
|
|
1797
|
+
// 数据库
|
|
1798
|
+
md += `\n## 3. 数据库设计\n\n`;
|
|
1799
|
+
if (models.length > 0) {
|
|
1800
|
+
md += `| 表名 | 说明 |\n| :--- | :--- |\n`;
|
|
1801
|
+
models.forEach(m => { md += `| \`${m.table}\` | ${m.desc} |\n`; });
|
|
1802
|
+
md += `\n> 💡 详细字段设计需在开发阶段补充 DDL。\n`;
|
|
1803
|
+
}
|
|
1804
|
+
else {
|
|
1805
|
+
md += `_需求中未检测到数据模型,需根据功能需求推导。_\n`;
|
|
1806
|
+
}
|
|
1807
|
+
// 中间件
|
|
1808
|
+
if (archImpact.newDependencies.length > 0) {
|
|
1809
|
+
md += `\n## 4. 中间件与外部依赖\n\n`;
|
|
1810
|
+
md += `| 依赖 | 用途 |\n| :--- | :--- |\n`;
|
|
1811
|
+
archImpact.newDependencies.forEach(d => { md += `| ${d} | 需求文档提及 |\n`; });
|
|
1812
|
+
}
|
|
1813
|
+
return md;
|
|
1814
|
+
}
|
|
1815
|
+
function buildTestSpec(iter, now, features, apis) {
|
|
1816
|
+
let md = `# 测试计划\n\n> 迭代: ${iter} | 生成: ${now} | 由 analyze --auto 自动提取\n\n`;
|
|
1817
|
+
// 单元测试
|
|
1818
|
+
md += `## 1. 单元测试\n\n`;
|
|
1819
|
+
if (features.length > 0) {
|
|
1820
|
+
features.forEach(f => {
|
|
1821
|
+
md += `- [ ] ${f.name}: 核心逻辑覆盖\n`;
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
else {
|
|
1825
|
+
md += `- [ ] 核心模块覆盖\n`;
|
|
1826
|
+
}
|
|
1827
|
+
// 接口测试
|
|
1828
|
+
md += `\n## 2. 接口测试\n\n`;
|
|
1829
|
+
if (apis.length > 0) {
|
|
1830
|
+
md += `| 接口 | 测试场景 | 预期结果 |\n| :--- | :--- | :--- |\n`;
|
|
1831
|
+
apis.forEach(a => {
|
|
1832
|
+
md += `| \`${a.method} ${a.path}\` | 正常请求 | 200 响应 |\n`;
|
|
1833
|
+
md += `| \`${a.method} ${a.path}\` | 缺少必填参数 | 400 错误 |\n`;
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
md += `_未检测到接口定义,需补充接口测试用例。_\n`;
|
|
1838
|
+
}
|
|
1839
|
+
// E2E
|
|
1840
|
+
md += `\n## 3. E2E 端到端测试\n\n`;
|
|
1841
|
+
if (features.length >= 2) {
|
|
1842
|
+
md += `- [ ] 核心业务流程: ${features.slice(0, 3).map(f => f.name).join(' → ')}\n`;
|
|
1843
|
+
}
|
|
1844
|
+
md += `- [ ] 异常流程: 网络超时、并发冲突、权限不足\n`;
|
|
1845
|
+
// 性能
|
|
1846
|
+
md += `\n## 4. 性能测试\n\n`;
|
|
1847
|
+
md += `- [ ] 接口响应时间 < 500ms (P99)\n`;
|
|
1848
|
+
md += `- [ ] 并发用户数 ≥ 100\n`;
|
|
1849
|
+
return md;
|
|
1850
|
+
}
|
|
1851
|
+
function buildReviewSpec(iter, now, apis, archImpact) {
|
|
1852
|
+
let md = `# 评审检查清单\n\n> 迭代: ${iter} | 生成: ${now} | 由 analyze --auto 自动提取\n\n`;
|
|
1853
|
+
md += `## 安全\n\n`;
|
|
1854
|
+
apis.forEach(a => {
|
|
1855
|
+
const authCheck = a.method === 'POST' || a.method === 'PUT' || a.method === 'DELETE' ? '鉴权 + 参数校验' : '鉴权检查';
|
|
1856
|
+
md += `- [ ] \`${a.path}\` — ${authCheck}\n`;
|
|
1857
|
+
});
|
|
1858
|
+
if (apis.length === 0)
|
|
1859
|
+
md += `- [ ] 接口鉴权完整性\n`;
|
|
1860
|
+
md += `\n## 质量\n\n`;
|
|
1861
|
+
md += `- [ ] 幂等性处理(POST/PUT 接口)\n`;
|
|
1862
|
+
md += `- [ ] 事务一致性(涉及多表操作)\n`;
|
|
1863
|
+
md += `- [ ] 错误处理与友好提示\n`;
|
|
1864
|
+
md += `- [ ] 日志规范(关键操作记录)\n`;
|
|
1865
|
+
if (archImpact.risks.length > 0) {
|
|
1866
|
+
md += `\n## 风险相关\n\n`;
|
|
1867
|
+
archImpact.risks.forEach(r => { md += `- [ ] ${r}\n`; });
|
|
1868
|
+
}
|
|
1869
|
+
return md;
|
|
1870
|
+
}
|
|
1871
|
+
function buildRiskSpec(iter, now, archImpact) {
|
|
1872
|
+
let md = `# 风险评估\n\n> 迭代: ${iter} | 生成: ${now} | 由 analyze --auto 自动提取\n\n`;
|
|
1873
|
+
md += `## 风险矩阵\n\n`;
|
|
1874
|
+
if (archImpact.risks.length > 0) {
|
|
1875
|
+
md += `| 风险 | 可能性 | 影响 | 缓解措施 |\n| :--- | :--- | :--- | :--- |\n`;
|
|
1876
|
+
archImpact.risks.forEach(r => {
|
|
1877
|
+
md += `| ${r} | 中 | 中 | 需评审确认 |\n`;
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
else {
|
|
1881
|
+
md += `_从需求中未检测到明显风险项,建议在评审中确认。_\n`;
|
|
1882
|
+
}
|
|
1883
|
+
md += `\n## 回滚方案\n\n`;
|
|
1884
|
+
md += `1. 触发条件: 核心接口错误率 > 5% 或数据不一致\n`;
|
|
1885
|
+
md += `2. 回滚步骤: 回退至上一稳定版本镜像 + 数据库回滚脚本\n`;
|
|
1886
|
+
md += `3. 验证方式: 冒烟测试通过 + 监控指标恢复正常\n`;
|
|
1887
|
+
return md;
|
|
1888
|
+
}
|
|
1889
|
+
function buildDepsSpec(iter, now, archImpact) {
|
|
1890
|
+
let md = `# 依赖清单\n\n> 迭代: ${iter} | 生成: ${now} | 由 analyze --auto 自动提取\n\n`;
|
|
1891
|
+
md += `## 上游依赖\n\n`;
|
|
1892
|
+
if (archImpact.newDependencies.length > 0) {
|
|
1893
|
+
md += `| 服务 | 用途 | 备注 |\n| :--- | :--- | :--- |\n`;
|
|
1894
|
+
archImpact.newDependencies.forEach(d => { md += `| ${d} | 需求文档提及 | 需确认版本和 SLA |\n`; });
|
|
1895
|
+
}
|
|
1896
|
+
else {
|
|
1897
|
+
md += `_从需求中未检测到外部依赖,需评审确认。_\n`;
|
|
1898
|
+
}
|
|
1899
|
+
md += `\n## 下游影响\n\n`;
|
|
1900
|
+
md += `_需根据 API 变更评估下游消费方影响。_\n`;
|
|
1901
|
+
return md;
|
|
1902
|
+
}
|
|
1903
|
+
function buildMonitorSpec(iter, now, apis, features) {
|
|
1904
|
+
let md = `# 监控指标\n\n> 迭代: ${iter} | 生成: ${now} | 由 analyze --auto 自动提取\n\n`;
|
|
1905
|
+
md += `## 业务指标\n\n`;
|
|
1906
|
+
md += `| 指标 | 阈值 | 级别 |\n| :--- | :--- | :--- |\n`;
|
|
1907
|
+
md += `| 接口成功率 | < 99.9% | P1 |\n`;
|
|
1908
|
+
md += `| P99 延迟 | > 1000ms | P2 |\n`;
|
|
1909
|
+
if (features.length > 0) {
|
|
1910
|
+
md += `| 核心功能可用率 | < 99.5% | P1 |\n`;
|
|
1911
|
+
}
|
|
1912
|
+
md += `\n## 告警规则\n\n`;
|
|
1913
|
+
md += `| 规则 | 条件 | 通知 |\n| :--- | :--- | :--- |\n`;
|
|
1914
|
+
md += `| 接口错误率突增 | 5 分钟内错误率 > 1% | 企微/钉钉 |\n`;
|
|
1915
|
+
md += `| 响应时间劣化 | P99 > 2s 持续 3 分钟 | 企微/钉钉 |\n`;
|
|
1916
|
+
if (apis.length > 0) {
|
|
1917
|
+
md += `| 关键接口异常 | \`${apis[0].path}\` 连续失败 3 次 | 电话告警 |\n`;
|
|
1918
|
+
}
|
|
1919
|
+
return md;
|
|
1920
|
+
}
|
|
1921
|
+
function guessModule(apiPath, features) {
|
|
1922
|
+
// 简单启发式:API 路径关键词匹配功能模块
|
|
1923
|
+
const segments = apiPath.split('/').filter(Boolean);
|
|
1924
|
+
for (const f of features) {
|
|
1925
|
+
const nameLower = f.name.toLowerCase();
|
|
1926
|
+
for (const seg of segments) {
|
|
1927
|
+
if (nameLower.includes(seg.toLowerCase()) || seg.toLowerCase().includes(nameLower.slice(0, 4))) {
|
|
1928
|
+
return f.name;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
return segments[1] || '—';
|
|
1933
|
+
}
|
|
1535
1934
|
//# sourceMappingURL=analyze-engine.js.map
|