@guanlin4924/devflow-pi 0.1.63 → 0.1.64

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanlin4924/devflow-pi",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
4
4
  "type": "module",
5
5
  "description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
6
6
  "license": "UNLICENSED",
package/src/constants.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PRODUCT_NAME = "Devflow Pi";
2
- export const PRODUCT_VERSION = "0.1.63";
2
+ export const PRODUCT_VERSION = "0.1.64";
3
3
  export const PROJECT_SCHEMA_VERSION = 1;
4
4
  export const OMP_RUNTIME_VERSION = "17.2.7";
5
5
 
@@ -15,6 +15,16 @@ export type DocumentGenerationResult = { targetPath: string; status: "complete"
15
15
 
16
16
  type Fact = { kind: string; repository: string; objectKey: string; value: Record<string, unknown>; evidenceLevel: string };
17
17
  const TEMPLATE_VERSION = "project-context-v2";
18
+ const KNOWLEDGE_DOCUMENT_TYPES = [
19
+ "requirements",
20
+ "product-design",
21
+ "architecture",
22
+ "frontend-tech",
23
+ "backend-tech",
24
+ "test-cases",
25
+ "api",
26
+ ] as const;
27
+ type KnowledgeDocumentType = typeof KNOWLEDGE_DOCUMENT_TYPES[number];
18
28
 
19
29
  function markdownCell(value: unknown): string {
20
30
  return String(value ?? "未确认").replaceAll("|", "\\|").replace(/[\r\n]+/g, " ");
@@ -32,6 +42,33 @@ function safeNarrative(value: string): string {
32
42
  function table(headers: string[], rows: unknown[][]): string {
33
43
  return [`| ${headers.join(" | ")} |`, `|${headers.map(() => "---").join("|")}|`, ...rows.map(row => `| ${row.map(markdownCell).join(" | ")} |`)].join("\n");
34
44
  }
45
+ function yamlString(value: string): string { return JSON.stringify(value); }
46
+ function frontmatter(fields: Record<string, string | boolean | string[] | null | undefined>): string {
47
+ const lines = ["---"];
48
+ for (const [key, value] of Object.entries(fields)) {
49
+ if (value === undefined || value === null) continue;
50
+ if (Array.isArray(value)) lines.push(`${key}: [${value.map(yamlString).join(", ")}]`);
51
+ else if (typeof value === "boolean") lines.push(`${key}: ${value ? "true" : "false"}`);
52
+ else lines.push(`${key}: ${yamlString(value)}`);
53
+ }
54
+ lines.push("---", "");
55
+ return lines.join("\n");
56
+ }
57
+ function documentTitle(type: KnowledgeDocumentType): string {
58
+ return ({
59
+ "requirements": "需求文档",
60
+ "product-design": "产品与交互设计",
61
+ "architecture": "架构设计",
62
+ "frontend-tech": "前端技术方案",
63
+ "backend-tech": "后端技术方案",
64
+ "test-cases": "用例与验收",
65
+ "api": "API 文档",
66
+ } satisfies Record<KnowledgeDocumentType, string>)[type];
67
+ }
68
+ function codeIndexGeneration(snapshot: { indexRef?: unknown }): string | undefined {
69
+ const indexRef = snapshot.indexRef as Record<string, unknown> | undefined;
70
+ return typeof indexRef?.generation === "string" ? indexRef.generation : undefined;
71
+ }
35
72
 
36
73
  async function narrative(context: ProjectContext, document: string, facts: Fact[], mode: DocumentGenerationMode, organizer?: LowTierDocumentOrganizer): Promise<{ text: string; modelRole?: string }> {
37
74
  const knownFacts = facts.slice(0, 80).map(fact => `${fact.kind}: ${Object.values(fact.value).filter(value => typeof value === "string" || typeof value === "number").slice(0, 3).join(" / ")}`);
@@ -76,6 +113,98 @@ function renderDocuments(context: ProjectContext, facts: Fact[], intro: string,
76
113
  return outputs;
77
114
  }
78
115
 
116
+ function projectKnowledgeBody(context: ProjectContext, type: KnowledgeDocumentType, facts: Fact[], intro: string, snapshotId: string, generation?: string): string {
117
+ const apis = facts.filter(fact => fact.kind === "api");
118
+ const pages = facts.filter(fact => fact.kind === "page");
119
+ const models = facts.filter(fact => fact.kind === "data_model");
120
+ const tech = facts.filter(fact => fact.kind === "technology");
121
+ const commands = facts.filter(fact => fact.kind === "toolchain");
122
+ const integrations = facts.filter(fact => fact.kind === "integration");
123
+ const repositoryNames = [...new Set(facts.map(fact => fact.repository).filter(Boolean))].sort();
124
+ const header = frontmatter({
125
+ type: `project-${type}`,
126
+ scope: "project",
127
+ project: context.manifest.name,
128
+ status: "draft",
129
+ source_snapshot: snapshotId,
130
+ code_index_generation: generation,
131
+ managed: true,
132
+ });
133
+ const evidence = [
134
+ "",
135
+ "## 证据绑定",
136
+ "",
137
+ `- 初始化快照:${snapshotId}`,
138
+ `- 代码索引 generation:${generation ?? "未生成或已跳过"}`,
139
+ "- 本文档由项目初始化创建骨架,人工确认前均为 draft。",
140
+ "",
141
+ ].join("\n");
142
+ if (type === "requirements") return `${header}# 项目总体需求\n\n${intro}\n\n## 项目范围\n\n${table(["代码库", "扫描事实数"], repositoryNames.map(repository => [repository, facts.filter(fact => fact.repository === repository).length]))}\n\n## 已观察业务能力\n\n${table(["类型", "对象"], [...pages, ...apis, ...models].slice(0, 80).map(fact => [fact.kind, fact.value.route ?? fact.value.path ?? fact.value.name ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 项目目标与非目标\n- 用户角色与核心流程\n- 业务约束与优先级\n${evidence}`;
143
+ if (type === "product-design") return `${header}# 项目产品与交互设计\n\n${intro}\n\n## 已发现页面/入口\n\n${table(["代码库", "页面或入口"], pages.slice(0, 100).map(fact => [fact.repository, fact.value.route ?? fact.value.componentPath ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 信息架构\n- 页面状态与异常态\n- 文案、权限与可访问性约束\n${evidence}`;
144
+ if (type === "architecture") return `${header}# 项目架构设计\n\n${intro}\n\n## 已观察结构\n\n${table(["代码库", "事实类型", "对象"], facts.filter(fact => ["api", "data_model", "integration", "capability"].includes(fact.kind)).slice(0, 120).map(fact => [fact.repository, fact.kind, fact.value.path ?? fact.value.name ?? fact.value.host ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 系统边界与部署关系\n- 模块职责与依赖方向\n- 数据流、风险与降级策略\n${evidence}`;
145
+ if (type === "frontend-tech") return `${header}# 项目前端技术方案\n\n${intro}\n\n## 已观察前端技术\n\n${table(["声明", "来源"], tech.filter(fact => /react|vue|vite|router|zustand|tailwind|typescript|eslint|vitest/i.test(String(fact.value.name ?? fact.value.extension ?? ""))).slice(0, 120).map(fact => [fact.value.name ?? fact.value.extension, fact.value.declaration ?? fact.value.path ?? "文件统计"]))}\n\n## 待人工补充\n\n- 路由、状态管理与组件边界\n- API 调用约定\n- 构建、测试与发布规则\n${evidence}`;
146
+ if (type === "backend-tech") return `${header}# 项目后端技术方案\n\n${intro}\n\n## 已观察后端事实\n\n${table(["类型", "对象"], [...apis, ...models, ...integrations].slice(0, 120).map(fact => [fact.kind, fact.value.path ?? fact.value.name ?? fact.value.host ?? fact.objectKey]))}\n\n## 待人工补充\n\n- handler/service/repository 分层\n- 数据库迁移与任务调度规则\n- 鉴权、错误码、日志与监控\n${evidence}`;
147
+ if (type === "test-cases") return `${header}# 项目用例与验收\n\n${intro}\n\n## 已发现测试/命令线索\n\n${table(["代码库", "名称", "命令或路径"], commands.slice(0, 120).map(fact => [fact.repository, fact.value.name ?? "test-definition", fact.value.command ?? fact.value.testDefinition ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 核心主流程验收用例\n- 异常路径与权限用例\n- 回归范围与自动化策略\n${evidence}`;
148
+ return `${header}# 项目 API 文档\n\n${intro}\n\n## 已发现 API 声明\n\n${table(["代码库", "方法", "路径"], apis.slice(0, 160).map(fact => [fact.repository, fact.value.method ?? "未确认", fact.value.path ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 认证与公共请求头\n- 通用响应结构与错误码\n- 兼容性与废弃策略\n${evidence}`;
149
+ }
150
+
151
+ function requirementTemplateBody(context: ProjectContext, type: KnowledgeDocumentType, snapshotId: string, generation?: string): string {
152
+ const header = frontmatter({
153
+ type: `requirement-${type}`,
154
+ scope: "requirement",
155
+ project: context.manifest.name,
156
+ requirement_id: "REQ-TEMPLATE",
157
+ status: "template",
158
+ source_snapshot: snapshotId,
159
+ code_index_generation: generation,
160
+ related_paths: [],
161
+ related_apis: [],
162
+ managed: true,
163
+ });
164
+ return `${header}# ${documentTitle(type)}\n\n本文件是需求级文档模板。创建具体需求时复制到 \`.context/requirements/REQ-xxx-短名/${type === "requirements" ? "requirement" : type}.md\`,并把 frontmatter 中的 \`requirement_id\`、\`status\`、关联路径和 API 补全。\n\n## 当前结论\n\n- 待补充。\n\n## 证据与约束\n\n- 初始化快照:${snapshotId}\n- 代码索引 generation:${generation ?? "未生成或已跳过"}\n- 需求级内容必须引用项目级文档和实际源码证据,不能只凭推断确认。\n\n## 待补充\n\n- 背景与目标\n- 范围与非目标\n- 设计决策\n- 验收条件\n`;
165
+ }
166
+
167
+ function renderKnowledgeDocuments(context: ProjectContext, facts: Fact[], intro: string, snapshot: { id?: unknown; indexRef?: unknown }): Array<{ targetPath: string; body: string }> {
168
+ const snapshotId = String(snapshot.id ?? "");
169
+ const generation = codeIndexGeneration(snapshot);
170
+ const outputs: Array<{ targetPath: string; body: string }> = [];
171
+ outputs.push({ targetPath: ".context/index.md", body: `${frontmatter({ type: "knowledge-index", scope: "project", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# ${context.manifest.name} 项目知识库\n\n## 项目级文档\n\n${KNOWLEDGE_DOCUMENT_TYPES.map(type => `- [${documentTitle(type)}](/project/${type}.md)`).join("\n")}\n\n## 需求级文档\n\n- 新需求使用 [.context/requirements/_template/index.md](/requirements/_template/index.md) 作为文档包模板。\n\n## 机器索引\n\n- 文档索引:\`.planning/document-index.json\`\n- 代码索引:\`.planning/code-index/\`\n` });
172
+ outputs.push({ targetPath: ".context/project/index.md", body: `${frontmatter({ type: "project-document-index", scope: "project", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 项目级文档\n\n${KNOWLEDGE_DOCUMENT_TYPES.map(type => `- [${documentTitle(type)}](./${type}.md)`).join("\n")}\n` });
173
+ for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.context/project/${type}.md`, body: projectKnowledgeBody(context, type, facts, intro, snapshotId, generation) });
174
+ outputs.push({ targetPath: ".context/requirements/_template/index.md", body: `${frontmatter({ type: "requirement-document-index", scope: "requirement", project: context.manifest.name, requirement_id: "REQ-TEMPLATE", status: "template", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 需求级文档模板\n\n${KNOWLEDGE_DOCUMENT_TYPES.map(type => `- [${documentTitle(type)}](./${type === "requirements" ? "requirement" : type}.md)`).join("\n")}\n` });
175
+ for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.context/requirements/_template/${type === "requirements" ? "requirement" : type}.md`, body: requirementTemplateBody(context, type, snapshotId, generation) });
176
+ return outputs;
177
+ }
178
+
179
+ function renderDocumentIndex(context: ProjectContext, snapshot: { id?: unknown; indexRef?: unknown }, outputs: Array<{ targetPath: string }>): { targetPath: string; body: string } {
180
+ const snapshotId = String(snapshot.id ?? "");
181
+ const generation = codeIndexGeneration(snapshot);
182
+ const documents = outputs
183
+ .filter(output => output.targetPath.startsWith(".context/"))
184
+ .map(output => {
185
+ const parts = output.targetPath.split("/");
186
+ const file = parts.at(-1) ?? "";
187
+ const scope = parts[1] === "project" ? "project" : parts[1] === "requirements" ? "requirement" : "project";
188
+ const type = output.targetPath === ".context/index.md" ? "knowledge-index"
189
+ : scope === "project" ? `project-${file.replace(/\.md$/, "")}`
190
+ : `requirement-${file.replace(/^requirement\.md$/, "requirements").replace(/\.md$/, "")}`;
191
+ return {
192
+ path: output.targetPath,
193
+ type,
194
+ scope,
195
+ requirementId: scope === "requirement" ? (parts[2] === "_template" ? "REQ-TEMPLATE" : parts[2]) : undefined,
196
+ status: parts.includes("_template") ? "template" : "draft",
197
+ managed: true,
198
+ sourceSnapshot: snapshotId,
199
+ codeIndexGeneration: generation ?? null,
200
+ };
201
+ });
202
+ return {
203
+ targetPath: ".planning/document-index.json",
204
+ body: `${JSON.stringify({ schemaVersion: 1, project: context.manifest.name, projectId: context.manifest.projectId, sourceSnapshot: snapshotId, codeIndexGeneration: generation ?? null, documents }, null, 2)}\n`,
205
+ };
206
+ }
207
+
79
208
  export async function generateProjectDocuments(context: ProjectContext, snapshotId: string, options: { mode: DocumentGenerationMode; organizer?: LowTierDocumentOrganizer }): Promise<DocumentGenerationResult[]> {
80
209
  const store = await InitializationStore.open(context);
81
210
  try {
@@ -92,7 +221,10 @@ export async function generateProjectDocuments(context: ProjectContext, snapshot
92
221
  let organized;
93
222
  try { organized = await narrative(context, "project-initialization", facts, options.mode, options.organizer); }
94
223
  catch (error) { store.failDocumentJob(batchJob, error instanceof Error ? error.message : String(error)); throw error; }
95
- const outputs = renderDocuments(context, facts, organized.text, collections); const results: DocumentGenerationResult[] = [];
224
+ const snapshotMeta = (store.queryContext({ snapshotId, limit: 1 }) as { snapshot: { id?: unknown; indexRef?: unknown } }).snapshot;
225
+ const outputs = [...renderDocuments(context, facts, organized.text, collections), ...renderKnowledgeDocuments(context, facts, organized.text, snapshotMeta)];
226
+ outputs.push(renderDocumentIndex(context, snapshotMeta, outputs));
227
+ const results: DocumentGenerationResult[] = [];
96
228
  for (const output of outputs) {
97
229
  const job = store.createDocumentJob(snapshotId, { targetPath: output.targetPath, ownership: "generated", templateVersion: TEMPLATE_VERSION, modelRole: organized.modelRole, input: facts.map(fact => fact.objectKey) });
98
230
  try {