@guanlin4924/devflow-pi 0.1.69 → 0.1.70
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 +1 -1
- package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +2 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/adapters/python_ast.py +2 -0
- package/plugins/devflow-intel/skills/workflow-code-index/scripts/code_index.py +8 -3
- package/src/constants.ts +1 -1
- package/src/project-documents.ts +2 -1
- package/src/project-initializer.ts +31 -3
package/package.json
CHANGED
|
@@ -209,6 +209,8 @@ Run the database validator with identifiers returned by the scan:
|
|
|
209
209
|
devflow_skill_script(script="project-context-db-validate", args=["--database", "<control.db>", "--project-id", "<project-id>", "--workspace-id", "<workspace-id>", "--run-id", "<run-id>"])
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
+
The `--database` value must come from `devflow_project_context(action="scan").database` or the loaded project context `controlDatabasePath`. Never guess `./control.db`, `.devflow/control.db`, `.planning/code-index/index.sqlite`, an OMP agent database, or any SQLite found by searching user directories. Code-index SQLite is not the initialization control database and does not contain the initialization `schema_meta` table.
|
|
213
|
+
|
|
212
214
|
Completion requires a stable immutable input, matching context snapshot, accounted required categories, evidence for observed facts, a registered immutable index in full mode, revision-safe pointer publication, and separate document statuses. Workflow `STATE` and other non-initialization authorities must remain unchanged.
|
|
213
215
|
|
|
214
216
|
Report partial, unsupported, skipped, failed, and unknown coverage honestly. Initialization success does not mean tests passed, services ran, designs were approved, or requirements were delivered.
|
|
@@ -116,6 +116,8 @@ class _PythonVisitor(ast.NodeVisitor):
|
|
|
116
116
|
|
|
117
117
|
@staticmethod
|
|
118
118
|
def _signature(node):
|
|
119
|
+
if isinstance(node, ast.ClassDef):
|
|
120
|
+
return f"{node.name}()"
|
|
119
121
|
arguments = [argument.arg for argument in [*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs]]
|
|
120
122
|
if node.args.vararg:
|
|
121
123
|
arguments.append(f"*{node.args.vararg.arg}")
|
|
@@ -97,13 +97,15 @@ def _publish_snapshot(staging: Path, output: Path, counts: Dict[str, int], works
|
|
|
97
97
|
os.close(directory_fd)
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
def _workspace_state(root: Path, files: List[dict], changed: Set[str]) -> Dict[str, object]:
|
|
100
|
+
def _workspace_state(root: Path, files: List[dict], changed: Set[str], discovered_paths: Iterable[str] | None = None) -> Dict[str, object]:
|
|
101
101
|
indexed_paths = sorted(record["path"] for record in files)
|
|
102
|
+
discovered = sorted(set(discovered_paths or indexed_paths))
|
|
102
103
|
source_manifest_hash = _hash_lines(f"{record['path']}\0{record['content_hash']}" for record in files)
|
|
103
104
|
return {
|
|
104
105
|
"repositoryRevision": _git_output(root, ["rev-parse", "HEAD"]),
|
|
105
106
|
"contentManifestHash": source_manifest_hash,
|
|
106
107
|
"indexedPathsHash": _hash_lines(indexed_paths),
|
|
108
|
+
"discoveredPathsHash": _hash_lines(discovered),
|
|
107
109
|
"dirtyPathsAtBuild": sorted(changed),
|
|
108
110
|
"extractors": sorted(set(record.get("parser", "unknown") for record in files)),
|
|
109
111
|
"capabilities": {
|
|
@@ -210,12 +212,14 @@ def command_scan(args) -> int:
|
|
|
210
212
|
|
|
211
213
|
files, symbols, relations, diagnostics = _existing_after_removal(existing, changed)
|
|
212
214
|
indexed_paths = {record["path"] for record in files}
|
|
215
|
+
discovered_paths: Set[str] = set()
|
|
213
216
|
|
|
214
217
|
for codebase in codebases:
|
|
215
218
|
if not codebase.root.exists():
|
|
216
219
|
diagnostics.append(_diagnostic(codebase.name, "", 1, "warning", "skipped_file", f"codebase root does not exist: {codebase.root}", "discovery@1"))
|
|
217
220
|
continue
|
|
218
221
|
all_files = scan_files(codebase.root, excludes=args.exclude)
|
|
222
|
+
discovered_paths.update(_workspace_rel(root, path) for path in all_files)
|
|
219
223
|
if changed:
|
|
220
224
|
all_files = [path for path in all_files if _workspace_rel(root, path) in changed]
|
|
221
225
|
manifests = [path for path in all_files if path.name in {"package.json", "go.mod", "Cargo.toml", "pyproject.toml", "requirements.txt", "composer.json", "pubspec.yaml"}]
|
|
@@ -280,7 +284,7 @@ def command_scan(args) -> int:
|
|
|
280
284
|
"relations": len(relations),
|
|
281
285
|
"diagnostics": len(diagnostics),
|
|
282
286
|
},
|
|
283
|
-
_workspace_state(root, files, changed),
|
|
287
|
+
_workspace_state(root, files, changed, discovered_paths),
|
|
284
288
|
)
|
|
285
289
|
finally:
|
|
286
290
|
shutil.rmtree(staging, ignore_errors=True)
|
|
@@ -406,7 +410,8 @@ def _query_is_fresh(root: Path, snapshot: Dict[str, object], files: List[dict])
|
|
|
406
410
|
indexed_paths = sorted(record["path"] for record in files)
|
|
407
411
|
if not any(path.startswith("../") for path in indexed_paths):
|
|
408
412
|
current_paths = sorted(_workspace_rel(root, item) for item in scan_files(root))
|
|
409
|
-
|
|
413
|
+
expected_paths_hash = workspace.get("discoveredPathsHash") or workspace.get("indexedPathsHash")
|
|
414
|
+
if _hash_lines(current_paths) != expected_paths_hash:
|
|
410
415
|
return False
|
|
411
416
|
return True
|
|
412
417
|
|
package/src/constants.ts
CHANGED
package/src/project-documents.ts
CHANGED
|
@@ -629,7 +629,7 @@ function initializationInterfaceContracts(): InterfaceContract[] {
|
|
|
629
629
|
name: "scan",
|
|
630
630
|
purpose: "执行项目初始化扫描并发布上下文快照。",
|
|
631
631
|
request: { action: "scan", mode: "full|fast", repositories: [{ name: "app", path: ".", role: "frontend|backend|shared|other" }], requiredKinds: FACT_KINDS, documentMode: "template|none", requestId: "stable-id" },
|
|
632
|
-
response: { runId: "uuid", snapshotId: "uuid", completeness: "complete|partial", diagnostics: [], documents: [{ targetPath: ".context/project/api.md", status: "complete|conflict|failed" }] },
|
|
632
|
+
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" }] },
|
|
633
633
|
reads: ["source tree", ".devflow/project.json"],
|
|
634
634
|
writes: ["control.db", ".planning/code-index/", ".planning/document-index.json", ".planning/knowledge-index.json", ".context/", ".planning/PROJECT.md"],
|
|
635
635
|
},
|
|
@@ -932,6 +932,7 @@ function renderKnowledgeDocuments(context: ProjectContext, facts: Fact[], intro:
|
|
|
932
932
|
const generation = codeIndexGeneration(snapshot);
|
|
933
933
|
const outputs: Array<{ targetPath: string; body: string }> = [];
|
|
934
934
|
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` });
|
|
935
|
+
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` });
|
|
935
936
|
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` });
|
|
936
937
|
for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.context/project/${type}.md`, body: projectKnowledgeBody(context, type, facts, intro, snapshotId, knowledge, generation) });
|
|
937
938
|
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` });
|
|
@@ -10,7 +10,17 @@ const TEXT_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".g
|
|
|
10
10
|
|
|
11
11
|
type SourceFile = { repository: string; path: string; absolute: string; contentHash: string; size: number; text?: string };
|
|
12
12
|
type ScanDiagnostic = { repository: string; path: string; reason: "symlink" | "excluded" | "too_large" | "unreadable"; message?: string };
|
|
13
|
-
export type InitializationProgress = {
|
|
13
|
+
export type InitializationProgress = {
|
|
14
|
+
runId: string;
|
|
15
|
+
snapshotId?: string;
|
|
16
|
+
completeness?: "complete" | "partial";
|
|
17
|
+
diagnostics: string[];
|
|
18
|
+
documents?: Array<{ targetPath: string; status: string; outputPath?: string; error?: string }>;
|
|
19
|
+
database: string;
|
|
20
|
+
projectId: string;
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
planningRoot: string;
|
|
23
|
+
};
|
|
14
24
|
|
|
15
25
|
function digest(value: string | Uint8Array): string { return `sha256:${createHash("sha256").update(value).digest("hex")}`; }
|
|
16
26
|
function extension(file: string): string { return path.extname(file).toLowerCase(); }
|
|
@@ -189,7 +199,15 @@ export async function runProjectInitialization(context: ProjectContext, input: {
|
|
|
189
199
|
const inputSnapshot = store.createInputSnapshot(workspace.id, storedConfig.id, manifest, false);
|
|
190
200
|
const run = store.startRun({ workspaceId: workspace.id, configId: storedConfig.id, inputSnapshotId: inputSnapshot.id, requestId: input.requestId });
|
|
191
201
|
activeRun = run;
|
|
192
|
-
if (run.reused) return {
|
|
202
|
+
if (run.reused) return {
|
|
203
|
+
runId: run.id,
|
|
204
|
+
snapshotId: store.getRun(run.id)?.publishedSnapshotId as string | undefined,
|
|
205
|
+
diagnostics,
|
|
206
|
+
database: context.controlDatabasePath,
|
|
207
|
+
projectId: context.manifest.projectId,
|
|
208
|
+
workspaceId: context.workspaceId,
|
|
209
|
+
planningRoot: context.planningRoot,
|
|
210
|
+
};
|
|
193
211
|
const grouped = collect(files, context, config); const required = new Set(config.requiredKinds ?? FACT_KINDS);
|
|
194
212
|
for (const repository of config.repositories) for (const kind of FACT_KINDS) {
|
|
195
213
|
const repositoryFacts = grouped.get(kind)!.filter(fact => fact.repository === repository.name);
|
|
@@ -241,7 +259,17 @@ export async function runProjectInitialization(context: ProjectContext, input: {
|
|
|
241
259
|
documents = [{ targetPath: ".planning/initialization/report.md", status: "failed", error: message }];
|
|
242
260
|
}
|
|
243
261
|
}
|
|
244
|
-
return {
|
|
262
|
+
return {
|
|
263
|
+
runId: run.id,
|
|
264
|
+
snapshotId: published.snapshotId,
|
|
265
|
+
completeness: result.snapshot.completeness,
|
|
266
|
+
diagnostics,
|
|
267
|
+
documents,
|
|
268
|
+
database: context.controlDatabasePath,
|
|
269
|
+
projectId: context.manifest.projectId,
|
|
270
|
+
workspaceId: context.workspaceId,
|
|
271
|
+
planningRoot: context.planningRoot,
|
|
272
|
+
};
|
|
245
273
|
} catch (error) {
|
|
246
274
|
const message = error instanceof Error ? error.message : String(error);
|
|
247
275
|
diagnostics.push(message);
|