@guanlin4924/devflow-pi 0.1.92 → 0.1.94

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.
Files changed (156) hide show
  1. package/README.md +6 -6
  2. package/package.json +1 -1
  3. package/plugins/devflow-core/extensions/devflow-core.ts +87 -33
  4. package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +3 -3
  5. package/plugins/devflow-core/runtime/hooks/hook_common.py +65 -11
  6. package/plugins/devflow-core/runtime/hooks/start.py +13 -13
  7. package/plugins/devflow-core/skills/wireframe-proposal/SKILL.md +2 -2
  8. package/plugins/devflow-core/skills/wireframe-validate/SKILL.md +4 -4
  9. package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +19 -19
  10. package/plugins/devflow-core/skills/workflow-orchestrator/agents/openai.yaml +2 -2
  11. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/acceptance_test_runner.py +5 -5
  12. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +9 -9
  13. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/api_test_matrix.py +2 -2
  14. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_assessment.rb +1 -1
  15. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_brief.rb +1 -1
  16. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_feasibility.rb +1 -1
  17. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_invalidation.rb +5 -5
  18. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_model_commit.rb +4 -4
  19. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_model_validate.py +1 -1
  20. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/business_source_resolver.rb +4 -4
  21. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/checker_adapter.rb +7 -7
  22. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/control_io.rb +51 -7
  23. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/cross_track_consistency_check.rb +1 -1
  24. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +6 -6
  25. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_runtime.rb +3 -3
  26. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +5 -5
  27. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_workflow.rb +5 -5
  28. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +13 -13
  29. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/machine_evidence.rb +3 -3
  30. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/manage_lessons.rb +7 -7
  31. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_recover.rb +2 -2
  32. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_run.rb +3 -3
  33. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/phase_ownership.rb +6 -6
  34. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/repair_control_plane.rb +1 -1
  35. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_coverage.rb +1 -1
  36. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_graph.rb +3 -3
  37. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/review_preflight.py +3 -3
  38. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/review_run.py +18 -18
  39. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/runtime_context_collector.rb +3 -3
  40. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/session_rollover.rb +4 -4
  41. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/static_runtime_context_collector.rb +2 -2
  42. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +5 -5
  43. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/tdd_evidence.py +3 -3
  44. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_command_policy.py +2 -2
  45. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/test_task_binding_check.py +3 -3
  46. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/update_triage_route.rb +3 -3
  47. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +6 -6
  48. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_commit.rb +5 -5
  49. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_validate.py +1 -1
  50. package/plugins/devflow-core/skills/workflow-orchestrator/templates/acceptance-remediation.yaml +12 -12
  51. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +205 -205
  52. package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +3 -3
  53. package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +10 -10
  54. package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +14 -14
  55. package/plugins/devflow-core/skills/workflow-project-context/assets/agents-md-template.md +7 -7
  56. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-backend-spec-template.md +4 -4
  57. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-frontend-spec-template.md +4 -4
  58. package/plugins/devflow-core/skills/workflow-project-context/assets/codebase-navigation-template.md +1 -1
  59. package/plugins/devflow-core/skills/workflow-project-context/assets/context-domain-index-template.md +4 -4
  60. package/plugins/devflow-core/skills/workflow-project-context/assets/context-spec-index-template.md +10 -10
  61. package/plugins/devflow-core/skills/workflow-project-context/assets/execution-contract-template.md +2 -2
  62. package/plugins/devflow-core/skills/workflow-project-context/assets/navigation-index-template.md +18 -18
  63. package/plugins/devflow-core/skills/workflow-project-context/assets/project-initialization-template.md +32 -32
  64. package/plugins/devflow-core/skills/workflow-project-context/assets/rules-index-template.md +30 -30
  65. package/plugins/devflow-core/skills/workflow-project-context/assets/state-template.md +7 -7
  66. package/plugins/devflow-core/skills/workflow-project-context/references/navigation-guide.md +37 -37
  67. package/plugins/devflow-core/skills/workflow-project-context/scripts/validate_initialization.py +22 -17
  68. package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +4 -4
  69. package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +8 -8
  70. package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +5 -5
  71. package/plugins/devflow-design/skills/workflow-architecture-feasibility/SKILL.md +1 -1
  72. package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +39 -39
  73. package/plugins/devflow-design/skills/workflow-backend-tech-spec/assets/backend-spec-template.md +10 -10
  74. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/affected-module-exploration.md +2 -2
  75. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/code-explore-guide.md +1 -1
  76. package/plugins/devflow-design/skills/workflow-backend-tech-spec/references/project-rule-contract.md +20 -20
  77. package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +8 -8
  78. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/index.md +3 -3
  79. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/accessibility.md +2 -2
  80. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/component-library.md +2 -2
  81. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/design-systems.md +1 -1
  82. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/responsive-grid.md +2 -2
  83. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/stack-guidelines.md +6 -6
  84. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/recommendations/ux-patterns.md +2 -2
  85. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tokens/project-tokens.md +1 -1
  86. package/plugins/devflow-design/skills/workflow-design-rule-pack/assets/design/tools/ui-ux-pro-max/SKILL.md +12 -12
  87. package/plugins/devflow-design/skills/workflow-design-rule-pack/scripts/install.py +2 -2
  88. package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +30 -30
  89. package/plugins/devflow-design/skills/workflow-feature-breakdown/assets/output-template.md +2 -2
  90. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +41 -41
  91. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/assets/frontend-spec-template.md +15 -15
  92. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/affected-module-exploration.md +2 -2
  93. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/references/project-rule-contract.md +25 -25
  94. package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +13 -13
  95. package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +23 -23
  96. package/plugins/devflow-design/skills/workflow-ui-spec/assets/ui-spec-template.md +3 -3
  97. package/plugins/devflow-implementation/agents/implementation-worker.md +2 -2
  98. package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +9 -9
  99. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/lesson-format.md +2 -2
  100. package/plugins/devflow-implementation/skills/workflow-bug-fix/references/root-cause-ladder.md +1 -1
  101. package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +17 -17
  102. package/plugins/devflow-implementation/skills/workflow-change-plan/references/code-conventions.md +1 -1
  103. package/plugins/devflow-implementation/skills/workflow-change-plan/references/output-template.md +3 -3
  104. package/plugins/devflow-implementation/skills/workflow-change-plan/references/project-rule-contract.md +16 -16
  105. package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +38 -38
  106. package/plugins/devflow-implementation/skills/workflow-execute-phase/references/execute-phase.md +1 -1
  107. package/plugins/devflow-intel/agents/project-scout.md +3 -1
  108. package/plugins/devflow-intel/extensions/devflow-intel.ts +20 -19
  109. package/plugins/devflow-intel/skills/code-index-query/SKILL.md +8 -6
  110. package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +12 -12
  111. package/plugins/devflow-intel/skills/workflow-code-index/references/discovery.md +2 -2
  112. package/plugins/devflow-intel/skills/workflow-code-index/references/query-guide.md +1 -1
  113. package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +8 -8
  114. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/discovery.py +2 -2
  115. package/plugins/devflow-intel/skills/workflow-code-index/scripts/core/scanner.py +4 -1
  116. package/plugins/devflow-intel/skills/workflow-code-index/scripts/validators/project_binding_check.py +4 -4
  117. package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +14 -14
  118. package/plugins/devflow-intel/skills/workflow-context-sync/scripts/context_sync_cursor.py +2 -2
  119. package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +26 -26
  120. package/plugins/devflow-intel/skills/workflow-update-navigation/references/incremental-update-guide.md +2 -2
  121. package/plugins/devflow-quality/skills/code-review/SKILL.md +13 -13
  122. package/plugins/devflow-quality/skills/code-review/references/spec-checklist.md +1 -1
  123. package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +8 -8
  124. package/plugins/devflow-quality/skills/workflow-gen-test-guide/SKILL.md +6 -6
  125. package/plugins/devflow-quality/skills/workflow-gen-test-guide/legacy/SKILL.md +14 -14
  126. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +23 -23
  127. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/codebase-discovery.md +9 -9
  128. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/references/project-dsl-contract.md +1 -1
  129. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/compile_dsl.py +2 -2
  130. package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/scripts/discover_codebases.py +2 -2
  131. package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +15 -15
  132. package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +5 -5
  133. package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +1 -1
  134. package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +10 -10
  135. package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +25 -25
  136. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +49 -49
  137. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/assets/baseline-template.md +22 -22
  138. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/antipatterns.md +3 -3
  139. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/challenge-review-guide.md +3 -3
  140. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/project-context-guide.md +11 -11
  141. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/questioning.md +1 -1
  142. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/references/requirement-review-checklist.md +4 -4
  143. package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +16 -16
  144. package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +6 -6
  145. package/src/cli.ts +15 -0
  146. package/src/constants.ts +1 -1
  147. package/src/initialization-store.ts +3 -2
  148. package/src/project-context.ts +36 -14
  149. package/src/project-documents.ts +61 -61
  150. package/src/project-initializer.ts +23 -9
  151. package/src/project-layout.ts +217 -0
  152. package/src/runtime.ts +3 -3
  153. package/templates/commands/start.md +4 -4
  154. package/templates/omp/AGENTS.md +1 -1
  155. package/templates/omp/hook-config.json +7 -7
  156. package/templates/omp/index-policy.json +8 -8
@@ -5,9 +5,11 @@ import path from "node:path";
5
5
  import { PROJECT_SCHEMA_VERSION } from "./constants.ts";
6
6
  import { InitializationStore } from "./initialization-store.ts";
7
7
  import { projectControlDatabasePath } from "./paths.ts";
8
+ import { DEVFLOW_MANIFEST, migrateLegacyProjectLayout, PLANNING_DIRECTORY, projectLayout } from "./project-layout.ts";
8
9
 
9
10
  export interface ProjectManifest {
10
11
  schemaVersion: number;
12
+ layoutVersion: number;
11
13
  projectId: string;
12
14
  name: string;
13
15
  createdAt: string;
@@ -23,11 +25,12 @@ export interface ProjectContext {
23
25
  }
24
26
 
25
27
  const PROJECT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
28
+ const PROJECT_LAYOUT_VERSION = 2;
26
29
  const CONTROL_BOUNDARY_PATHS = [
27
- ".planning/STATE.md",
28
- ".planning/.devflow.lock",
29
- ".planning/workflows",
30
- ".planning/artifacts",
30
+ `${PLANNING_DIRECTORY}/STATE.md`,
31
+ `${PLANNING_DIRECTORY}/.devflow.lock`,
32
+ `${PLANNING_DIRECTORY}/workflows`,
33
+ `${PLANNING_DIRECTORY}/artifacts`,
31
34
  ];
32
35
 
33
36
  async function assertNoSymlinkDescendants(target: string): Promise<void> {
@@ -58,7 +61,7 @@ async function canonicalDirectory(candidate: string): Promise<string> {
58
61
  async function nearestManifest(start: string): Promise<string | null> {
59
62
  let current = await canonicalDirectory(start);
60
63
  while (true) {
61
- const manifest = path.join(current, ".devflow", "project.json");
64
+ const manifest = path.join(current, DEVFLOW_MANIFEST);
62
65
  if (await exists(manifest)) return current;
63
66
  const parent = path.dirname(current);
64
67
  if (parent === current) return null;
@@ -95,18 +98,21 @@ function validateManifest(value: unknown, manifestPath: string): ProjectManifest
95
98
  if (record.schemaVersion !== PROJECT_SCHEMA_VERSION) {
96
99
  throw new Error(`Unsupported project schema in ${manifestPath}: ${String(record.schemaVersion)}`);
97
100
  }
101
+ if (record.layoutVersion !== undefined && record.layoutVersion !== 1 && record.layoutVersion !== PROJECT_LAYOUT_VERSION) {
102
+ throw new Error(`Unsupported project layout in ${manifestPath}: ${String(record.layoutVersion)}`);
103
+ }
98
104
  if (typeof record.projectId !== "string" || !PROJECT_ID_PATTERN.test(record.projectId)) {
99
105
  throw new Error(`Invalid projectId in ${manifestPath}`);
100
106
  }
101
107
  if (typeof record.name !== "string" || !record.name || typeof record.createdAt !== "string") {
102
108
  throw new Error(`Incomplete project manifest: ${manifestPath}`);
103
109
  }
104
- return record as unknown as ProjectManifest;
110
+ return { ...record, layoutVersion: Number(record.layoutVersion ?? 1) } as unknown as ProjectManifest;
105
111
  }
106
112
 
107
113
  export async function assertPlanningBoundary(root: string): Promise<string> {
108
114
  const canonicalRoot = await canonicalDirectory(root);
109
- const planningRoot = path.join(canonicalRoot, ".planning");
115
+ const planningRoot = projectLayout(canonicalRoot).planningRoot;
110
116
  const stat = await lstat(planningRoot).catch(() => null);
111
117
  if (stat?.isSymbolicLink()) throw new Error(`Refusing symlinked planning root: ${planningRoot}`);
112
118
  if (stat && !stat.isDirectory()) throw new Error(`Planning root is not a directory: ${planningRoot}`);
@@ -133,8 +139,16 @@ export async function assertPlanningBoundary(root: string): Promise<string> {
133
139
 
134
140
  export async function loadProjectContext(root: string): Promise<ProjectContext> {
135
141
  const canonicalRoot = await canonicalDirectory(root);
136
- const manifestPath = path.join(canonicalRoot, ".devflow", "project.json");
137
- const manifest = validateManifest(JSON.parse(await readFile(manifestPath, "utf8")), manifestPath);
142
+ const manifestPath = projectLayout(canonicalRoot).manifestPath;
143
+ const manifestValue = JSON.parse(await readFile(manifestPath, "utf8")) as Record<string, unknown>;
144
+ validateManifest(manifestValue, manifestPath);
145
+ await migrateLegacyProjectLayout(canonicalRoot);
146
+ if (manifestValue.layoutVersion !== PROJECT_LAYOUT_VERSION) {
147
+ manifestValue.layoutVersion = PROJECT_LAYOUT_VERSION;
148
+ await atomicWriteJson(manifestPath, manifestValue);
149
+ }
150
+ const manifest = validateManifest(manifestValue, manifestPath);
151
+ await mkdir(projectLayout(canonicalRoot).planningRoot, { recursive: true });
138
152
  const planningRoot = await assertPlanningBoundary(canonicalRoot);
139
153
  if (!(await exists(projectControlDatabasePath(manifest.projectId)))) throw new Error("Project initialization database is not registered; run explicit project initialization first");
140
154
  return attachInitializationStore({ root: canonicalRoot, manifestPath, manifest, planningRoot });
@@ -160,22 +174,30 @@ async function atomicWriteJson(target: string, value: unknown): Promise<void> {
160
174
 
161
175
  export async function initializeProject(root: string): Promise<{ context: ProjectContext; created: boolean }> {
162
176
  const canonicalRoot = await canonicalDirectory(root);
163
- const devflowDir = path.join(canonicalRoot, ".devflow");
164
- const planningRoot = path.join(canonicalRoot, ".planning");
165
- await assertPlanningBoundary(canonicalRoot);
177
+ const { devflowRoot: devflowDir, manifestPath, planningRoot } = projectLayout(canonicalRoot);
166
178
  await mkdir(devflowDir, { recursive: true });
167
- await mkdir(planningRoot, { recursive: true });
168
- const manifestPath = path.join(devflowDir, "project.json");
179
+ await assertPlanningBoundary(canonicalRoot);
169
180
  if (await exists(manifestPath)) {
170
181
  return { context: await loadProjectContext(canonicalRoot), created: false };
171
182
  }
172
183
  const manifest: ProjectManifest = {
173
184
  schemaVersion: PROJECT_SCHEMA_VERSION,
185
+ // Establish a v1-compatible pending identity while legacy material is
186
+ // assessed; only commit v2 after migration succeeds.
187
+ layoutVersion: 1,
174
188
  projectId: randomUUID(),
175
189
  name: path.basename(canonicalRoot),
176
190
  createdAt: new Date().toISOString(),
177
191
  };
178
192
  await atomicWriteJson(manifestPath, manifest);
193
+ // Explicit initialization establishes project identity before attempting a
194
+ // legacy-layout migration. This prevents a tracked v1 project with no local
195
+ // manifest from being permanently skipped, while active/conflicting legacy
196
+ // control state still fails closed in migrateLegacyProjectLayout.
197
+ await migrateLegacyProjectLayout(canonicalRoot);
198
+ manifest.layoutVersion = PROJECT_LAYOUT_VERSION;
199
+ await atomicWriteJson(manifestPath, manifest);
200
+ await mkdir(planningRoot, { recursive: true });
179
201
  const bootstrapStore = await InitializationStore.open({ root: canonicalRoot, manifest });
180
202
  try { await bootstrapStore.registerWorkspace(); } finally { bootstrapStore.close(); }
181
203
  return {
@@ -152,9 +152,9 @@ function classifyDocument(document: SourceDocument): DocumentClassification {
152
152
  function shouldInspectDocument(relativePath: string): boolean {
153
153
  if (!/\.(?:md|mdx|txt|adoc|rst|yaml|yml|json)$/i.test(relativePath)) return false;
154
154
  if (/^(?:\.git|node_modules|dist|build|coverage|vendor|target)\//.test(relativePath)) return false;
155
- if (/^\.planning\/code-index\//.test(relativePath)) return false;
156
- if (/^\.context\/requirements\/_template\//.test(relativePath)) return false;
157
- return /^(?:README|docs\/|features\/|requirements\/|\.context\/|\.planning\/requirements\/|\.planning\/features\/|test-environment-guide|skit\/docs\/|text\/)/i.test(relativePath);
155
+ if (/^\.devflow\/planning\/code-index\//.test(relativePath)) return false;
156
+ if (/^\.devflow\/context\/requirements\/_template\//.test(relativePath)) return false;
157
+ return /^(?:README|docs\/|features\/|requirements\/|\.devflow\/context\/|\.devflow\/planning\/requirements\/|\.devflow\/planning\/features\/|test-environment-guide|skit\/docs\/|text\/)/i.test(relativePath);
158
158
  }
159
159
  async function discoverSourceDocuments(root: string): Promise<DocumentKnowledge> {
160
160
  const documents: SourceDocument[] = [];
@@ -232,7 +232,7 @@ function knowledgePagePath(type: KnowledgePageType, titleOrId: string): string {
232
232
  incident: "incidents",
233
233
  lesson: "lessons",
234
234
  } satisfies Record<KnowledgePageType, string>)[type];
235
- return `.context/knowledge/${folder}/${slug(titleOrId)}.md`;
235
+ return `.devflow/context/knowledge/${folder}/${slug(titleOrId)}.md`;
236
236
  }
237
237
  function sourceTable(items: Array<{ source: SourceDocument; classification: DocumentClassification }>): string {
238
238
  if (!items.length) return "未发现可归类的已有文档。";
@@ -305,24 +305,24 @@ function renderDocuments(context: ProjectContext, facts: Fact[], intro: string,
305
305
  const capabilities = facts.filter(fact => fact.kind === "capability");
306
306
  const repositories = [...new Set(facts.map(fact => fact.repository).filter(Boolean))].sort();
307
307
  const outputs: Array<{ targetPath: string; body: string }> = [];
308
- outputs.push({ targetPath: ".planning/PROJECT.md", body: [
308
+ outputs.push({ targetPath: ".devflow/planning/PROJECT.md", body: [
309
309
  `# ${context.manifest.name}`, "", intro, "", "## 代码库组成", "",
310
310
  table(["代码库", "文件数量", "已观察能力"], repositories.map(repository => [repository, assets.filter(fact => fact.repository === repository).length, [...new Set(facts.filter(fact => fact.repository === repository && ["api", "page", "data_model", "integration"].includes(fact.kind)).map(fact => fact.kind))].join("、") || "未发现"])),
311
311
  "", "## 技术概况", "", table(["声明", "来源类型"], technologies.slice(0, 100).map(fact => [fact.value.name ?? fact.value.extension, fact.value.declaration ?? "文件统计"])),
312
312
  "", "## 已知边界", "", "当前信息来自本地静态扫描。接口声明不证明服务可访问,页面声明不证明运行时渲染,命令声明不表示已经执行。", "",
313
313
  ].join("\n") });
314
- outputs.push({ targetPath: ".planning/toolchain.md", body: ["# 工具链", "", intro, "", table(["代码库", "名称", "命令", "工作目录", "执行状态"], commands.map(fact => [fact.repository, fact.value.name, fact.value.command, fact.value.workingDirectory || ".", "仅发现,未执行"])), ""].join("\n") });
315
- outputs.push({ targetPath: ".planning/navigation/INDEX.md", body: ["# 项目导航", "", intro, "", table(["代码库", "导航说明", "扫描文件数"], repositories.map(repository => [repository, `.planning/navigation/${repository.replace(/[^\p{L}\p{N}._-]/gu, "-")}.md`, assets.filter(fact => fact.repository === repository).length])), ""].join("\n") });
314
+ outputs.push({ targetPath: ".devflow/planning/toolchain.md", body: ["# 工具链", "", intro, "", table(["代码库", "名称", "命令", "工作目录", "执行状态"], commands.map(fact => [fact.repository, fact.value.name, fact.value.command, fact.value.workingDirectory || ".", "仅发现,未执行"])), ""].join("\n") });
315
+ outputs.push({ targetPath: ".devflow/planning/navigation/INDEX.md", body: ["# 项目导航", "", intro, "", table(["代码库", "导航说明", "扫描文件数"], repositories.map(repository => [repository, `.devflow/planning/navigation/${repository.replace(/[^\p{L}\p{N}._-]/gu, "-")}.md`, assets.filter(fact => fact.repository === repository).length])), ""].join("\n") });
316
316
  for (const repository of repositories) {
317
317
  const local = facts.filter(fact => fact.repository === repository);
318
- outputs.push({ targetPath: `.planning/navigation/${repository.replace(/[^\p{L}\p{N}._-]/gu, "-")}.md`, body: [
318
+ outputs.push({ targetPath: `.devflow/planning/navigation/${repository.replace(/[^\p{L}\p{N}._-]/gu, "-")}.md`, body: [
319
319
  `# ${repository} 导航`, "", intro, "", "## 文件与入口", "",
320
320
  table(["路径", "类型"], local.filter(fact => fact.kind === "asset").slice(0, 200).map(fact => [fact.value.path, fact.value.extension || "无扩展名"])),
321
321
  "", "## 已发现能力", "", table(["类型", "对象"], local.filter(fact => ["api", "page", "data_model", "integration"].includes(fact.kind)).slice(0, 200).map(fact => [fact.kind, fact.value.path ?? fact.value.route ?? fact.value.name ?? fact.value.host ?? "未确认"])), "",
322
322
  ].join("\n") });
323
323
  }
324
- outputs.push({ targetPath: ".planning/rules/INDEX.md", body: ["# 项目规则来源", "", intro, "", table(["代码库", "规则文件", "性质"], rules.map(fact => [fact.repository, fact.value.path, fact.value.explicit ? "显式规则" : "惯例候选"])), "", "未登记的惯例候选不会自动成为强制规则。", ""].join("\n") });
325
- outputs.push({ targetPath: ".planning/initialization/report.md", body: [
324
+ outputs.push({ targetPath: ".devflow/planning/rules/INDEX.md", body: ["# 项目规则来源", "", intro, "", table(["代码库", "规则文件", "性质"], rules.map(fact => [fact.repository, fact.value.path, fact.value.explicit ? "显式规则" : "惯例候选"])), "", "未登记的惯例候选不会自动成为强制规则。", ""].join("\n") });
325
+ outputs.push({ targetPath: ".devflow/planning/initialization/report.md", body: [
326
326
  "# 项目初始化报告", "", intro, "", "## 数据可用范围", "",
327
327
  table(["采集器", "支持范围", "限制"], capabilities.map(fact => [fact.objectKey, Array.isArray(fact.value.supported) ? fact.value.supported.join("、") : "未确认", Array.isArray(fact.value.limitations) ? fact.value.limitations.join(";") : "未确认"])),
328
328
  "", "## 分项采集状态", "", table(["代码库", "专题", "状态", "诊断数"], collections.map(item => [item.repository || "全局", item.kind, item.status, item.diagnostics?.length ?? 0])),
@@ -383,13 +383,13 @@ function requirementTemplateBody(context: ProjectContext, type: KnowledgeDocumen
383
383
  related_apis: [],
384
384
  managed: true,
385
385
  });
386
- 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`;
386
+ return `${header}# ${documentTitle(type)}\n\n本文件是需求级文档模板。创建具体需求时复制到 \`.devflow/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`;
387
387
  }
388
388
 
389
389
  function inboxRequirementBody(context: ProjectContext, source: SourceDocument, classification: DocumentClassification, snapshotId: string, generation?: string): Array<{ targetPath: string; body: string }> {
390
390
  const requirementId = classification.requirementId ?? source.id;
391
391
  const folder = `${requirementId}-${slug(source.title)}`;
392
- const base = `.context/requirements/_inbox/${folder}`;
392
+ const base = `.devflow/context/requirements/_inbox/${folder}`;
393
393
  const documentType = classification.documentType === "unknown" ? "requirements" : classification.documentType;
394
394
  const body = `${frontmatter({ type: documentType === "requirements" ? "requirement-spec" : `requirement-${documentType}`, scope: "requirement", project: context.manifest.name, requirement_id: requirementId, status: "inbox", source_snapshot: snapshotId, code_index_generation: generation, source_documents: [source.path], managed: true })}# ${source.title}\n\n本文档由初始化阶段从已有资料归档而来,尚未确认是否为正式需求。\n\n## 来源摘要\n\n${source.excerpt || "无可用摘要。"}\n\n## 来源文档\n\n${table(["来源", "标题", "内容哈希"], [[source.path, source.title, source.contentHash]])}\n\n## 归类信息\n\n- 类型:${classification.documentType}\n- 置信度:${classification.confidence}\n- 理由:${classification.reasons.join(";")}\n\n## 待确认\n\n- 是否转为正式 REQ 编号\n- 是否拆分为多个需求\n- 是否补齐产品、技术、API 与用例文档\n`;
395
395
  return [
@@ -449,7 +449,7 @@ function buildKnowledgePages(knowledge: DocumentKnowledge, facts: Fact[]): Knowl
449
449
  const allSourceRefs = [...new Set([...pages.values()].flatMap(page => page.sourceRefs).filter(ref => ref.startsWith("source-")))];
450
450
  pages.set("synthesis-project-overview", {
451
451
  id: "synthesis-project-overview",
452
- path: ".context/knowledge/synthesis/project-overview.md",
452
+ path: ".devflow/context/knowledge/synthesis/project-overview.md",
453
453
  type: "synthesis",
454
454
  title: "项目知识总览",
455
455
  status: "draft",
@@ -513,7 +513,7 @@ function bugKnowledgeTemplates(context: ProjectContext, snapshotId: string, gene
513
513
  const common = { project: context.manifest.name, status: "template", source_snapshot: snapshotId, code_index_generation: generation, managed: true };
514
514
  return [
515
515
  {
516
- targetPath: ".context/knowledge/bugs/_template.md",
516
+ targetPath: ".devflow/context/knowledge/bugs/_template.md",
517
517
  body: `${frontmatter({ ...common, type: "bug-report", id: "BUG-TEMPLATE", title: "BUG 模板", source_refs: [], related_requirements: [], related_code_facts: [], related_fixes: [], regression_tests: [], confidence: "manual" })}# BUG 模板
518
518
 
519
519
  ## Symptom
@@ -546,7 +546,7 @@ function bugKnowledgeTemplates(context: ProjectContext, snapshotId: string, gene
546
546
  `,
547
547
  },
548
548
  {
549
- targetPath: ".context/knowledge/fixes/_template.md",
549
+ targetPath: ".devflow/context/knowledge/fixes/_template.md",
550
550
  body: `${frontmatter({ ...common, type: "bug-fix", id: "FIX-TEMPLATE", title: "FIX 模板", bug_refs: [], changed_code_facts: [], regression_tests: [], confidence: "manual" })}# FIX 模板
551
551
 
552
552
  ## Change Summary
@@ -571,7 +571,7 @@ function bugKnowledgeTemplates(context: ProjectContext, snapshotId: string, gene
571
571
  `,
572
572
  },
573
573
  {
574
- targetPath: ".context/knowledge/incidents/_template.md",
574
+ targetPath: ".devflow/context/knowledge/incidents/_template.md",
575
575
  body: `${frontmatter({ ...common, type: "incident", id: "INC-TEMPLATE", title: "INCIDENT 模板", related_bugs: [], related_fixes: [], related_code_facts: [], confidence: "manual" })}# INCIDENT 模板
576
576
 
577
577
  ## Timeline
@@ -596,7 +596,7 @@ function bugKnowledgeTemplates(context: ProjectContext, snapshotId: string, gene
596
596
  `,
597
597
  },
598
598
  {
599
- targetPath: ".context/knowledge/lessons/_template.md",
599
+ targetPath: ".devflow/context/knowledge/lessons/_template.md",
600
600
  body: `${frontmatter({ ...common, type: "lesson", id: "LESSON-TEMPLATE", title: "LESSON 模板", related_bugs: [], related_fixes: [], related_code_facts: [], confidence: "manual" })}# LESSON 模板
601
601
 
602
602
  ## Lesson
@@ -633,9 +633,9 @@ function initializationInterfaceContracts(): InterfaceContract[] {
633
633
  name: "scan",
634
634
  purpose: "执行项目初始化扫描并发布上下文快照。",
635
635
  request: { action: "scan", mode: "full|fast", repositories: [{ name: "app", path: ".", role: "frontend|backend|shared|other" }], requiredKinds: FACT_KINDS, documentMode: "template|none", requestId: "stable-id" },
636
- response: { runId: "uuid", snapshotId: "uuid", completeness: "complete|partial", database: "<control.db>", projectId: "uuid", workspaceId: "uuid", planningRoot: ".planning", diagnostics: [], documents: [{ targetPath: ".context/project/api.md", status: "complete|conflict|failed" }] },
636
+ response: { runId: "uuid", snapshotId: "uuid", completeness: "complete|partial", database: "<control.db>", projectId: "uuid", workspaceId: "uuid", planningRoot: ".devflow/planning", diagnostics: [], documents: [{ targetPath: ".devflow/context/project/api.md", status: "complete|conflict|failed" }] },
637
637
  reads: ["source tree", ".devflow/project.json"],
638
- writes: ["control.db", ".planning/code-index/", ".planning/document-index.json", ".planning/knowledge-index.json", ".context/", ".planning/PROJECT.md"],
638
+ writes: ["control.db", ".devflow/planning/code-index/", ".devflow/planning/document-index.json", ".devflow/planning/knowledge-index.json", ".devflow/context/", ".devflow/planning/PROJECT.md"],
639
639
  },
640
640
  {
641
641
  name: "query",
@@ -657,9 +657,9 @@ function initializationInterfaceContracts(): InterfaceContract[] {
657
657
  name: "generate-docs",
658
658
  purpose: "针对已发布快照重试生成文档投影。",
659
659
  request: { action: "generate-docs", snapshotId: "uuid" },
660
- response: [{ targetPath: ".context/project/api.md", status: "complete|conflict|failed", outputPath: "path" }],
661
- reads: ["control.db", ".planning/code-index/", "source documents"],
662
- writes: [".context/", ".planning/document-index.json", ".planning/knowledge-index.json"],
660
+ response: [{ targetPath: ".devflow/context/project/api.md", status: "complete|conflict|failed", outputPath: "path" }],
661
+ reads: ["control.db", ".devflow/planning/code-index/", "source documents"],
662
+ writes: [".devflow/context/", ".devflow/planning/document-index.json", ".devflow/planning/knowledge-index.json"],
663
663
  },
664
664
  {
665
665
  name: "config",
@@ -712,7 +712,7 @@ function initializationInterfaceContracts(): InterfaceContract[] {
712
712
  {
713
713
  name: "import-legacy",
714
714
  purpose: "把旧版初始化产物导入数据库控制面。",
715
- request: { action: "import-legacy", sources: [".planning/PROJECT.md"], requestId: "stable-id" },
715
+ request: { action: "import-legacy", sources: [".devflow/planning/PROJECT.md"], requestId: "stable-id" },
716
716
  response: { runId: "uuid", snapshotId: "uuid", imports: [] },
717
717
  reads: ["legacy initialization files"],
718
718
  writes: ["control.db"],
@@ -720,7 +720,7 @@ function initializationInterfaceContracts(): InterfaceContract[] {
720
720
  {
721
721
  name: "export",
722
722
  purpose: "把指定初始化快照导出为兼容文件。",
723
- request: { action: "export", snapshotId: "uuid", targetPath: ".planning/project-context-export.json" },
723
+ request: { action: "export", snapshotId: "uuid", targetPath: ".devflow/planning/project-context-export.json" },
724
724
  response: { path: "path", contentHash: "sha256:..." },
725
725
  reads: ["control.db"],
726
726
  writes: ["targetPath"],
@@ -744,7 +744,7 @@ function initializationInterfaceContracts(): InterfaceContract[] {
744
744
  {
745
745
  name: "backup",
746
746
  purpose: "备份初始化控制数据库和内容状态。",
747
- request: { action: "backup", targetPath: ".planning/backups/initialization.db" },
747
+ request: { action: "backup", targetPath: ".devflow/planning/backups/initialization.db" },
748
748
  response: { backup: "absolute-path" },
749
749
  reads: ["control.db", "managed initialization documents"],
750
750
  writes: ["targetPath", "targetPath.manifest.json"],
@@ -752,7 +752,7 @@ function initializationInterfaceContracts(): InterfaceContract[] {
752
752
  {
753
753
  name: "restore",
754
754
  purpose: "从已验证备份恢复初始化控制数据库。",
755
- request: { action: "restore", targetPath: ".planning/backups/initialization.db" },
755
+ request: { action: "restore", targetPath: ".devflow/planning/backups/initialization.db" },
756
756
  response: { restored: true, database: "control.db" },
757
757
  reads: ["targetPath", "targetPath.manifest.json"],
758
758
  writes: ["control.db"],
@@ -803,7 +803,7 @@ function initializationInterfaceContracts(): InterfaceContract[] {
803
803
  gaps: [],
804
804
  recommendedNextAction: "query|diff|generate-docs|scan|human-review|none",
805
805
  },
806
- reads: [".planning/interface-index.json", ".planning/knowledge-index.json", ".planning/code-index/", ".context/knowledge/", "control interfaces"],
806
+ reads: [".devflow/planning/interface-index.json", ".devflow/planning/knowledge-index.json", ".devflow/planning/code-index/", ".devflow/context/knowledge/", "control interfaces"],
807
807
  writes: ["initialization projections through tools only"],
808
808
  },
809
809
  ],
@@ -818,11 +818,11 @@ function initializationInterfaceContracts(): InterfaceContract[] {
818
818
  actions: [
819
819
  {
820
820
  name: "scan/validate/audit/query",
821
- purpose: "生成并查询 .planning/code-index/ 机器代码事实。",
822
- request: { action: "scan|validate|audit|query", index: ".planning/code-index", root: "." },
821
+ purpose: "生成并查询 .devflow/planning/code-index/ 机器代码事实。",
822
+ request: { action: "scan|validate|audit|query", index: ".devflow/planning/code-index", root: "." },
823
823
  response: { generation: "uuid", files: "jsonl", symbols: "jsonl", relations: "jsonl", diagnostics: "jsonl" },
824
824
  reads: ["source tree"],
825
- writes: [".planning/code-index/"],
825
+ writes: [".devflow/planning/code-index/"],
826
826
  },
827
827
  ],
828
828
  },
@@ -846,18 +846,18 @@ function initializationInterfaceContracts(): InterfaceContract[] {
846
846
  },
847
847
  {
848
848
  id: "artifact-knowledge-index",
849
- name: ".planning/knowledge-index.json",
849
+ name: ".devflow/planning/knowledge-index.json",
850
850
  kind: "artifact",
851
- path: ".planning/knowledge-index.json",
851
+ path: ".devflow/planning/knowledge-index.json",
852
852
  purpose: "知识页、来源文档、需求、代码事实和预留 BUG 节点的桥接索引。",
853
853
  approval: "read",
854
854
  actions: [
855
855
  {
856
856
  name: "read",
857
857
  purpose: "让 LLM 或工具快速发现知识层节点和绑定关系。",
858
- request: { path: ".planning/knowledge-index.json" },
859
- response: { schemaVersion: 1, bundleRoot: ".context/knowledge", pages: [], reservedBugKnowledgeNodes: [] },
860
- reads: [".planning/knowledge-index.json"],
858
+ request: { path: ".devflow/planning/knowledge-index.json" },
859
+ response: { schemaVersion: 1, bundleRoot: ".devflow/context/knowledge", pages: [], reservedBugKnowledgeNodes: [] },
860
+ reads: [".devflow/planning/knowledge-index.json"],
861
861
  writes: [],
862
862
  },
863
863
  ],
@@ -912,8 +912,8 @@ function renderInterfaceOutputs(context: ProjectContext, snapshot: { id?: unknow
912
912
  const generation = codeIndexGeneration(snapshot);
913
913
  const contracts = initializationInterfaceContracts();
914
914
  const outputs: Array<{ targetPath: string; body: string }> = [];
915
- outputs.push({ targetPath: ".context/knowledge/interfaces/index.md", body: `${frontmatter({ type: "interface-index", title: "初始化接口目录", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 初始化接口目录\n\n${contracts.map(contract => `- [${contract.name}](./${slug(contract.id)}.md) — ${contract.purpose}`).join("\n")}\n` });
916
- for (const contract of contracts) outputs.push({ targetPath: `.context/knowledge/interfaces/${slug(contract.id)}.md`, body: renderInterfaceContract(context, contract, snapshotId, generation) });
915
+ outputs.push({ targetPath: ".devflow/context/knowledge/interfaces/index.md", body: `${frontmatter({ type: "interface-index", title: "初始化接口目录", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 初始化接口目录\n\n${contracts.map(contract => `- [${contract.name}](./${slug(contract.id)}.md) — ${contract.purpose}`).join("\n")}\n` });
916
+ for (const contract of contracts) outputs.push({ targetPath: `.devflow/context/knowledge/interfaces/${slug(contract.id)}.md`, body: renderInterfaceContract(context, contract, snapshotId, generation) });
917
917
  return { outputs, contracts };
918
918
  }
919
919
 
@@ -923,9 +923,9 @@ function renderOpenWikiKnowledge(context: ProjectContext, facts: Fact[], snapsho
923
923
  const pages = buildKnowledgePages(knowledge, facts);
924
924
  const sourceByRef = new Map(knowledge.sources.map(source => [sourcePageId(source), source] as const));
925
925
  const outputs: Array<{ targetPath: string; body: string }> = [];
926
- outputs.push({ targetPath: ".context/knowledge/index.md", body: `${frontmatter({ type: "knowledge-bundle-index", title: `${context.manifest.name} Knowledge`, project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# ${context.manifest.name} Knowledge\n\n## Sources\n\n${pages.filter(page => page.type === "source-summary").map(page => `- [${page.title}](./sources/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Entities\n\n${pages.filter(page => page.type === "entity").map(page => `- [${page.title}](./entities/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Concepts\n\n${pages.filter(page => page.type === "concept").map(page => `- [${page.title}](./concepts/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Requirements\n\n${pages.filter(page => page.type === "requirement").map(page => `- [${page.title}](./requirements/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Synthesis\n\n${pages.filter(page => page.type === "synthesis").map(page => `- [${page.title}](./synthesis/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Bugs / Fixes / Incidents / Lessons\n\n- [BUG 模板](./bugs/_template.md)\n- [FIX 模板](./fixes/_template.md)\n- [INCIDENT 模板](./incidents/_template.md)\n- [LESSON 模板](./lessons/_template.md)\n` });
927
- outputs.push({ targetPath: ".context/knowledge/log.md", body: `${frontmatter({ type: "knowledge-log", title: "Knowledge Operation Log", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# Knowledge Operation Log\n\n- 初始化生成 OpenWiki 风格知识层。\n- 原始文档保持在项目原位置,source-summary 记录摘要和引用。\n- 代码事实来自 \`.planning/code-index/\`,桥接关系写入 \`.planning/knowledge-index.json\`。\n` });
928
- outputs.push({ targetPath: ".context/knowledge/raw/README.md", body: `${frontmatter({ type: "raw-source-readme", title: "Raw Source Policy", project: context.manifest.name, status: "draft", managed: true })}# Raw Source Policy\n\n本目录保留给后续需要复制或归档的原始资料。当前初始化默认不复制原文,只在 source-summary 中记录原路径和内容哈希,避免制造第二份权威原文。\n` });
926
+ outputs.push({ targetPath: ".devflow/context/knowledge/index.md", body: `${frontmatter({ type: "knowledge-bundle-index", title: `${context.manifest.name} Knowledge`, project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# ${context.manifest.name} Knowledge\n\n## Sources\n\n${pages.filter(page => page.type === "source-summary").map(page => `- [${page.title}](./sources/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Entities\n\n${pages.filter(page => page.type === "entity").map(page => `- [${page.title}](./entities/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Concepts\n\n${pages.filter(page => page.type === "concept").map(page => `- [${page.title}](./concepts/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Requirements\n\n${pages.filter(page => page.type === "requirement").map(page => `- [${page.title}](./requirements/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Synthesis\n\n${pages.filter(page => page.type === "synthesis").map(page => `- [${page.title}](./synthesis/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Bugs / Fixes / Incidents / Lessons\n\n- [BUG 模板](./bugs/_template.md)\n- [FIX 模板](./fixes/_template.md)\n- [INCIDENT 模板](./incidents/_template.md)\n- [LESSON 模板](./lessons/_template.md)\n` });
927
+ outputs.push({ targetPath: ".devflow/context/knowledge/log.md", body: `${frontmatter({ type: "knowledge-log", title: "Knowledge Operation Log", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# Knowledge Operation Log\n\n- 初始化生成 OpenWiki 风格知识层。\n- 原始文档保持在项目原位置,source-summary 记录摘要和引用。\n- 代码事实来自 \`.devflow/planning/code-index/\`,桥接关系写入 \`.devflow/planning/knowledge-index.json\`。\n` });
928
+ outputs.push({ targetPath: ".devflow/context/knowledge/raw/README.md", body: `${frontmatter({ type: "raw-source-readme", title: "Raw Source Policy", project: context.manifest.name, status: "draft", managed: true })}# Raw Source Policy\n\n本目录保留给后续需要复制或归档的原始资料。当前初始化默认不复制原文,只在 source-summary 中记录原路径和内容哈希,避免制造第二份权威原文。\n` });
929
929
  outputs.push(...bugKnowledgeTemplates(context, snapshotId, generation));
930
930
  for (const page of pages) outputs.push({ targetPath: page.path, body: renderKnowledgePage(context, page, sourceByRef, snapshotId, generation) });
931
931
  return { outputs, pages };
@@ -935,12 +935,12 @@ function renderKnowledgeDocuments(context: ProjectContext, facts: Fact[], intro:
935
935
  const snapshotId = String(snapshot.id ?? "");
936
936
  const generation = codeIndexGeneration(snapshot);
937
937
  const outputs: Array<{ targetPath: string; body: string }> = [];
938
- 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` });
939
- outputs.push({ targetPath: ".context/spec/index.md", body: `${frontmatter({ type: "compat-spec-index", scope: "project", project: context.manifest.name, status: "compat", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 兼容 Spec 入口\n\n本文件是项目初始化的兼容入口,用于旧流程查找 \`.context/spec/index.md\`。新的权威入口如下:\n\n- 项目知识库:[../index.md](../index.md)\n- 项目级文档:[../project/index.md](../project/index.md)\n- OpenWiki 风格知识层:[../knowledge/index.md](../knowledge/index.md)\n- 初始化接口目录:[../knowledge/interfaces/index.md](../knowledge/interfaces/index.md)\n- 文档索引:\`.planning/document-index.json\`\n- 知识索引:\`.planning/knowledge-index.json\`\n- 接口索引:\`.planning/interface-index.json\`\n- 代码索引:\`.planning/code-index/\`\n\n此兼容入口不表示需求、设计或 API 已批准;人工确认前所有生成内容保持 draft/compat 状态。\n` });
940
- 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` });
941
- for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.context/project/${type}.md`, body: projectKnowledgeBody(context, type, facts, intro, snapshotId, knowledge, generation) });
942
- 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` });
943
- for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.context/requirements/_template/${type === "requirements" ? "requirement" : type}.md`, body: requirementTemplateBody(context, type, snapshotId, generation) });
938
+ outputs.push({ targetPath: ".devflow/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- 新需求使用 [.devflow/context/requirements/_template/index.md](/requirements/_template/index.md) 作为文档包模板。\n\n## 机器索引\n\n- 文档索引:\`.devflow/planning/document-index.json\`\n- 代码索引:\`.devflow/planning/code-index/\`\n` });
939
+ outputs.push({ targetPath: ".devflow/context/spec/index.md", body: `${frontmatter({ type: "compat-spec-index", scope: "project", project: context.manifest.name, status: "compat", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 兼容 Spec 入口\n\n本文件是项目初始化的兼容入口,用于旧流程查找 \`.devflow/context/spec/index.md\`。新的权威入口如下:\n\n- 项目知识库:[../index.md](../index.md)\n- 项目级文档:[../project/index.md](../project/index.md)\n- OpenWiki 风格知识层:[../knowledge/index.md](../knowledge/index.md)\n- 初始化接口目录:[../knowledge/interfaces/index.md](../knowledge/interfaces/index.md)\n- 文档索引:\`.devflow/planning/document-index.json\`\n- 知识索引:\`.devflow/planning/knowledge-index.json\`\n- 接口索引:\`.devflow/planning/interface-index.json\`\n- 代码索引:\`.devflow/planning/code-index/\`\n\n此兼容入口不表示需求、设计或 API 已批准;人工确认前所有生成内容保持 draft/compat 状态。\n` });
940
+ outputs.push({ targetPath: ".devflow/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` });
941
+ for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.devflow/context/project/${type}.md`, body: projectKnowledgeBody(context, type, facts, intro, snapshotId, knowledge, generation) });
942
+ outputs.push({ targetPath: ".devflow/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` });
943
+ for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.devflow/context/requirements/_template/${type === "requirements" ? "requirement" : type}.md`, body: requirementTemplateBody(context, type, snapshotId, generation) });
944
944
  for (const item of knowledge.classifications.filter(item => item.scope === "requirement" && item.confidence !== "exact").slice(0, 100)) {
945
945
  const source = knowledge.sources.find(source => source.id === item.sourceDocumentId);
946
946
  if (source) outputs.push(...inboxRequirementBody(context, source, item, snapshotId, generation));
@@ -952,21 +952,21 @@ function renderDocumentIndex(context: ProjectContext, snapshot: { id?: unknown;
952
952
  const snapshotId = String(snapshot.id ?? "");
953
953
  const generation = codeIndexGeneration(snapshot);
954
954
  const documents = outputs
955
- .filter(output => output.targetPath.startsWith(".context/"))
955
+ .filter(output => output.targetPath.startsWith(".devflow/context/"))
956
956
  .map(output => {
957
957
  const parts = output.targetPath.split("/");
958
958
  const file = parts.at(-1) ?? "";
959
- const scope: IndexedDocumentScope = parts[1] === "project" ? "project" : parts[1] === "requirements" ? "requirement" : parts[1] === "knowledge" ? "knowledge" : "project";
960
- const type = output.targetPath === ".context/index.md" ? "knowledge-index"
961
- : output.targetPath === ".context/knowledge/index.md" ? "knowledge-bundle-index"
962
- : scope === "knowledge" ? `knowledge-${parts[2] === "log.md" ? "log" : parts[2] ?? "page"}`
959
+ const scope: IndexedDocumentScope = parts[2] === "project" ? "project" : parts[2] === "requirements" ? "requirement" : parts[2] === "knowledge" ? "knowledge" : "project";
960
+ const type = output.targetPath === ".devflow/context/index.md" ? "knowledge-index"
961
+ : output.targetPath === ".devflow/context/knowledge/index.md" ? "knowledge-bundle-index"
962
+ : scope === "knowledge" ? `knowledge-${parts[3] === "log.md" ? "log" : parts[3] ?? "page"}`
963
963
  : scope === "project" ? `project-${file.replace(/\.md$/, "")}`
964
964
  : `requirement-${file.replace(/^requirement\.md$/, "requirements").replace(/\.md$/, "")}`;
965
965
  return {
966
966
  path: output.targetPath,
967
967
  type,
968
968
  scope,
969
- requirementId: scope === "requirement" ? (parts[2] === "_template" ? "REQ-TEMPLATE" : parts[2]) : undefined,
969
+ requirementId: scope === "requirement" ? (parts[3] === "_template" ? "REQ-TEMPLATE" : parts[3]) : undefined,
970
970
  status: parts.includes("_template") ? "template" : parts.includes("_inbox") ? "inbox" : "draft",
971
971
  managed: true,
972
972
  sourceSnapshot: snapshotId,
@@ -974,7 +974,7 @@ function renderDocumentIndex(context: ProjectContext, snapshot: { id?: unknown;
974
974
  };
975
975
  });
976
976
  return {
977
- targetPath: ".planning/document-index.json",
977
+ targetPath: ".devflow/planning/document-index.json",
978
978
  body: `${JSON.stringify({
979
979
  schemaVersion: 2,
980
980
  project: context.manifest.name,
@@ -993,14 +993,14 @@ function renderKnowledgeIndex(context: ProjectContext, snapshot: { id?: unknown;
993
993
  const snapshotId = String(snapshot.id ?? "");
994
994
  const generation = codeIndexGeneration(snapshot);
995
995
  return {
996
- targetPath: ".planning/knowledge-index.json",
996
+ targetPath: ".devflow/planning/knowledge-index.json",
997
997
  body: `${JSON.stringify({
998
998
  schemaVersion: 1,
999
999
  project: context.manifest.name,
1000
1000
  projectId: context.manifest.projectId,
1001
1001
  sourceSnapshot: snapshotId,
1002
1002
  codeIndexGeneration: generation ?? null,
1003
- bundleRoot: ".context/knowledge",
1003
+ bundleRoot: ".devflow/context/knowledge",
1004
1004
  sourceDocuments: knowledge.sources,
1005
1005
  classifications: knowledge.classifications,
1006
1006
  pages: pages.map(page => ({
@@ -1015,10 +1015,10 @@ function renderKnowledgeIndex(context: ProjectContext, snapshot: { id?: unknown;
1015
1015
  confidence: page.confidence,
1016
1016
  })),
1017
1017
  reservedBugKnowledgeNodes: [
1018
- { id: "BUG-TEMPLATE", path: ".context/knowledge/bugs/_template.md", type: "bug-report", purpose: "问题现象、影响、复现、根因和回归绑定模板" },
1019
- { id: "FIX-TEMPLATE", path: ".context/knowledge/fixes/_template.md", type: "bug-fix", purpose: "修复动作、变更原因、变更文件和回归覆盖模板" },
1020
- { id: "INC-TEMPLATE", path: ".context/knowledge/incidents/_template.md", type: "incident", purpose: "事故时间线、影响、缓解和后续行动模板" },
1021
- { id: "LESSON-TEMPLATE", path: ".context/knowledge/lessons/_template.md", type: "lesson", purpose: "可复用排障经验和检查规则模板" },
1018
+ { id: "BUG-TEMPLATE", path: ".devflow/context/knowledge/bugs/_template.md", type: "bug-report", purpose: "问题现象、影响、复现、根因和回归绑定模板" },
1019
+ { id: "FIX-TEMPLATE", path: ".devflow/context/knowledge/fixes/_template.md", type: "bug-fix", purpose: "修复动作、变更原因、变更文件和回归覆盖模板" },
1020
+ { id: "INC-TEMPLATE", path: ".devflow/context/knowledge/incidents/_template.md", type: "incident", purpose: "事故时间线、影响、缓解和后续行动模板" },
1021
+ { id: "LESSON-TEMPLATE", path: ".devflow/context/knowledge/lessons/_template.md", type: "lesson", purpose: "可复用排障经验和检查规则模板" },
1022
1022
  ],
1023
1023
  interfaces: interfaces.map(item => ({ id: item.id, name: item.name, kind: item.kind, path: item.path, purpose: item.purpose, approval: item.approval, actions: item.actions.map(action => action.name) })),
1024
1024
  }, null, 2)}\n`,
@@ -1029,7 +1029,7 @@ function renderInterfaceIndex(context: ProjectContext, snapshot: { id?: unknown;
1029
1029
  const snapshotId = String(snapshot.id ?? "");
1030
1030
  const generation = codeIndexGeneration(snapshot);
1031
1031
  return {
1032
- targetPath: ".planning/interface-index.json",
1032
+ targetPath: ".devflow/planning/interface-index.json",
1033
1033
  body: `${JSON.stringify({
1034
1034
  schemaVersion: 1,
1035
1035
  project: context.manifest.name,
@@ -1068,7 +1068,7 @@ export async function generateProjectDocuments(context: ProjectContext, snapshot
1068
1068
  } while (after);
1069
1069
  const run = runId ? store.getRun(runId) as { collections?: Array<{ repository?: string; kind?: string; status?: string; diagnostics?: unknown[] }> } | null : null;
1070
1070
  const collections = run?.collections ?? [];
1071
- const batchJob = store.createDocumentJob(snapshotId, { targetPath: ".planning/initialization/report.md", ownership: "generated", templateVersion: TEMPLATE_VERSION, modelRole: options.organizer ? `${options.organizer.role}:${options.organizer.model}` : options.mode, input: facts.map(fact => fact.objectKey) });
1071
+ const batchJob = store.createDocumentJob(snapshotId, { targetPath: ".devflow/planning/initialization/report.md", ownership: "generated", templateVersion: TEMPLATE_VERSION, modelRole: options.organizer ? `${options.organizer.role}:${options.organizer.model}` : options.mode, input: facts.map(fact => fact.objectKey) });
1072
1072
  let organized;
1073
1073
  try { organized = await narrative(context, "project-initialization", facts, options.mode, options.organizer); }
1074
1074
  catch (error) { store.failDocumentJob(batchJob, error instanceof Error ? error.message : String(error)); throw error; }
@@ -4,8 +4,9 @@ import path from "node:path";
4
4
 
5
5
  import { FACT_KINDS, InitializationStore, type FactInput, type FactKind, type ScanConfigInput } from "./initialization-store.ts";
6
6
  import type { ProjectContext } from "./project-context.ts";
7
+ import { DEVFLOW_SCAN_EXCLUDES, PLANNING_DIRECTORY } from "./project-layout.ts";
7
8
 
8
- const DEFAULT_EXCLUDED = new Set([".git", ".devflow", ".planning", "node_modules", "dist", "build", "coverage", "target", "vendor", ".next", ".nuxt"]);
9
+ const DEFAULT_EXCLUDED = new Set([".git", ".devflow", ".planning", ".context", "node_modules", "dist", "build", "coverage", "target", "vendor", ".next", ".nuxt"]);
9
10
  const TEXT_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".go", ".java", ".kt", ".rs", ".php", ".sql", ".json", ".yaml", ".yml", ".toml", ".md"]);
10
11
 
11
12
  type SourceFile = { repository: string; path: string; absolute: string; contentHash: string; size: number; text?: string };
@@ -78,7 +79,7 @@ async function scan(context: ProjectContext, config: ScanConfigInput): Promise<{
78
79
  const diagnostics: ScanDiagnostic[] = [];
79
80
  const repositoryVersions: Array<{ repository: string; revision: string | null }> = [];
80
81
  const includes = config.includes ?? ["**/*"];
81
- const excludes = config.excludes ?? [".git/**", ".planning/**", ".devflow/**"];
82
+ const excludes = [...new Set([...DEVFLOW_SCAN_EXCLUDES, ...(config.excludes ?? [])])];
82
83
  for (const repository of config.repositories) {
83
84
  const root = path.resolve(context.root, repository.relativePath);
84
85
  if (!path.isAbsolute(repository.relativePath) && root !== context.root && !root.startsWith(`${context.root}${path.sep}`)) throw new Error(`Repository escapes project root: ${repository.relativePath}`);
@@ -217,26 +218,39 @@ async function runProcess(argv: string[], cwd: string, label = path.basename(arg
217
218
 
218
219
  async function buildCodeIndex(context: ProjectContext, config: ScanConfigInput): Promise<Record<string, unknown>> {
219
220
  const script = path.resolve(import.meta.dir, "../plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py");
220
- const output = `.planning/.code-index-pending-${randomUUID()}`;
221
+ const output = `${PLANNING_DIRECTORY}/.code-index-pending-${randomUUID()}`;
221
222
  const codebases = config.repositories.flatMap(repository => ["--codebase", `${repository.name}:${path.resolve(context.root, repository.relativePath)}:${repository.role}`]);
222
223
  const scopeArgs = [
223
224
  ...(config.includes ?? ["**/*"]).flatMap(pattern => ["--include", pattern]),
224
- ...(config.excludes ?? [".git/**", ".planning/**", ".devflow/**"]).flatMap(pattern => ["--exclude", pattern]),
225
+ ...([...new Set([...DEVFLOW_SCAN_EXCLUDES, ...(config.excludes ?? [])])]).flatMap(pattern => ["--exclude", pattern]),
225
226
  "--max-file-size", String(config.maxFileSize ?? 2 * 1024 * 1024),
226
227
  ];
227
228
  await runProcess(["python3", script, "scan", "--root", context.root, "--output", output, ...scopeArgs, ...codebases], context.root, "code-index scan");
228
- await runProcess(["python3", script, "validate", "--index", output, "--level", "publish"], context.root, "code-index validate");
229
- await runProcess(["python3", script, "audit", "--root", context.root, "--index", output, "--skill-root", path.resolve(import.meta.dir, "../plugins/devflow-intel/skills"), "--level", "publish"], context.root, "code-index audit");
229
+ try {
230
+ await runProcess(["python3", script, "validate", "--index", output, "--level", "publish"], context.root, "code-index validate");
231
+ await runProcess(["python3", script, "audit", "--root", context.root, "--index", output, "--skill-root", path.resolve(import.meta.dir, "../plugins/devflow-intel/skills"), "--level", "publish"], context.root, "code-index audit");
232
+ } catch (error) {
233
+ const message = error instanceof Error ? error.message : String(error);
234
+ return {
235
+ path: `${PLANNING_DIRECTORY}/code-index`,
236
+ stagingPath: output,
237
+ status: "degraded",
238
+ reason: "code_index_publish_failed",
239
+ fallback: "bounded_source_search",
240
+ diagnostics: [message],
241
+ };
242
+ }
230
243
  const snapshotPath = path.join(context.root, output, "SNAPSHOT.json");
231
244
  const bytes = await readFile(snapshotPath); const snapshot = JSON.parse(bytes.toString("utf8")) as Record<string, unknown>;
232
245
  if (!snapshot.generation || !snapshot.immutablePath) throw new Error("Code index did not publish an immutable generation");
233
246
  const workspace = (snapshot.workspace ?? {}) as Record<string, unknown>;
234
- return { path: ".planning/code-index", stagingPath: output, generation: snapshot.generation, immutablePath: snapshot.immutablePath, snapshotHash: digest(bytes), contentManifestHash: workspace.contentManifestHash, repositoryRevision: workspace.repositoryRevision, status: "complete" };
247
+ return { path: `${PLANNING_DIRECTORY}/code-index`, stagingPath: output, generation: snapshot.generation, immutablePath: snapshot.immutablePath, snapshotHash: digest(bytes), contentManifestHash: workspace.contentManifestHash, repositoryRevision: workspace.repositoryRevision, status: "complete" };
235
248
  }
236
249
 
237
250
  async function promoteCodeIndex(context: ProjectContext, indexRef: Record<string, unknown>): Promise<() => Promise<void>> {
251
+ if (indexRef.status !== "complete") return async () => {};
238
252
  const stagingPath = typeof indexRef.stagingPath === "string" ? path.resolve(context.root, indexRef.stagingPath) : null;
239
- const target = path.resolve(context.root, ".planning/code-index");
253
+ const target = path.resolve(context.root, PLANNING_DIRECTORY, "code-index");
240
254
  if (!stagingPath) return async () => {};
241
255
  const previous = `${target}.previous-${randomUUID()}`;
242
256
  const existing = await stat(target).catch(() => null);
@@ -340,7 +354,7 @@ export async function runProjectInitialization(context: ProjectContext, input: {
340
354
  } catch (error) {
341
355
  const message = error instanceof Error ? error.message : String(error);
342
356
  diagnostics.push(`Document generation failed independently: ${message}`);
343
- documents = [{ targetPath: ".planning/initialization/report.md", status: "failed", error: message }];
357
+ documents = [{ targetPath: ".devflow/planning/initialization/report.md", status: "failed", error: message }];
344
358
  }
345
359
  }
346
360
  return {