/** * .trellis/spec/shared/project-context.md「产品画像」节读写(兼容读取旧 OpenSpec/project.md)。 */ import * as fs from "node:fs"; import * as path from "node:path"; import { projectMdWritePath, resolveProjectMdPath } from "./openspec-paths"; export const PROJECT_MD_TEMPLATE = `# 项目领域知识 ## 产品画像 - _(未选择)_ ## 技术栈 - 后端:Java / C# / Python(按产品线选定) - 数据库:Oracle / MySQL / PostgreSQL / SQL Server - 构建:Maven/Gradle(Cosmic)/ MSBuild(企业版 C#) - 元数据:FKERNELXML / fdata / 设计器导出 ## 关键概念 - 单据(Bill):业务数据载体 - 表单(Form):UI 载体 - 操作(Operation):业务逻辑入口 - 转换(Convert / BOTP):单据间转换 - 工作流(Workflow):审批流程 - 插件:表单/列表/操作/报表/转换/校验(Cosmic Java 或企业版 C#/IronPython) ## 常用产品 ID(仅参考,写入「产品画像」列表用 \`- id\`) | id | 含义 | |----|------| | cosmic-java | 金蝶AI苍穹/金蝶AI星瀚/金蝶AI套件 Cosmic Java | | enterprise-csharp | 企业版 C# | | enterprise-ironpython | 企业版 IronPython | | enterprise-openapi | 企业版 OpenAPI | `; function readProjectMdContent(cwd: string): string | undefined { const p = resolveProjectMdPath(cwd); if (!p) return undefined; try { return fs.readFileSync(p, "utf8"); } catch { return undefined; } } function extractSection(md: string, heading: string): string | undefined { const re = new RegExp(`(?<=^|\n)## ${heading}\\s*\n([\\s\\S]*?)(?=\n## |\n# |$)`); return md.match(re)?.[1]; } /** 解析「产品画像」列表项(`- id`);忽略占位与注释行语义由调用方处理。 */ export function readProductsFromProjectMd(cwd: string): string[] { const md = readProjectMdContent(cwd); if (!md) return []; const section = extractSection(md, "产品画像"); if (!section) return []; const products: string[] = []; for (const line of section.split("\n")) { const m = line.match(/^\s*-\s+(.+)$/); if (!m) continue; const raw = m[1].trim(); if (!raw || raw.startsWith("_") || raw === "(未选择)") continue; const colon = raw.lastIndexOf(":"); const productId = colon >= 0 ? raw.slice(colon + 1).trim() : raw; if (productId) products.push(productId); } return products; } export function writeProductsToProjectMd(cwd: string, products: string[]): void { const writePath = projectMdWritePath(cwd); const md = readProjectMdContent(cwd) ?? PROJECT_MD_TEMPLATE; const lines: string[] = products.length === 0 ? ["- _(未选择)_"] : products.map(id => `- ${id}`); const newBlock = [ "", "", "", "", ...lines, ].join("\n"); const replaced = md.replace( /(?<=\n)(## 产品画像\s*\n)([\s\S]*?)(?=\n## |\n# |$)/, (_, header: string) => `${header}${newBlock}\n`, ); fs.mkdirSync(path.dirname(writePath), { recursive: true }); fs.writeFileSync(writePath, replaced, "utf8"); }