ai-delivery-workflow 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -6
- package/bin/ai-delivery.mjs +2 -24
- package/docs/CLI-PARAMETER-REFERENCE.zh-CN.md +6 -67
- package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +1 -1
- package/docs/FILE-REFERENCE.zh-CN.md +6 -28
- package/docs/MAINTENANCE-VERIFICATION.zh-CN.md +2 -2
- package/docs/PROJECT-MANUAL.zh-CN.md +23 -38
- package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +8 -10
- package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +6 -16
- package/docs/agents/workflow-manager-acceptance-matrix.md +9 -15
- package/docs/agents/workflow-manager-current-state.md +12 -15
- package/lib/communication-confirmation.mjs +177 -0
- package/lib/delivery-state.mjs +59 -910
- package/lib/fs-utils.mjs +0 -16
- package/lib/project-bootstrap.mjs +288 -61
- package/lib/project-installer.mjs +49 -181
- package/lib/project-repair.mjs +217 -0
- package/lib/toml-hooks.mjs +2 -4
- package/lib/workflow-manager-governance.mjs +3 -47
- package/lib/workspace.mjs +2 -3
- package/package.json +2 -2
- package/skills/ai-delivery-bootstrap/SKILL.md +5 -1
- package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +1 -1
- package/skills/ai-delivery-checkpoint-task/SKILL.md +2 -4
- package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +4 -2
- package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +2 -8
- package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +26 -46
- package/skills/ai-delivery-close-version/SKILL.md +13 -10
- package/skills/ai-delivery-close-version/references/version-closeout-contract.md +2 -1
- package/skills/ai-delivery-define-product/SKILL.md +6 -3
- package/skills/ai-delivery-design-architecture/SKILL.md +10 -5
- package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-gate.yaml +2 -0
- package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +2 -0
- package/skills/ai-delivery-design-architecture/references/architecture-contract.md +4 -0
- package/skills/ai-delivery-design-experience/SKILL.md +8 -6
- package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +2 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +3 -0
- package/skills/ai-delivery-design-experience/references/experience-contract.md +3 -13
- package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +1 -1
- package/skills/ai-delivery-design-tests/SKILL.md +4 -0
- package/skills/ai-delivery-develop-iteration/SKILL.md +6 -1
- package/skills/ai-delivery-develop-iteration/assets/development-template/candidate-manifest.yaml +2 -0
- package/skills/ai-delivery-execute-work-package/SKILL.md +4 -2
- package/skills/ai-delivery-orchestrate/SKILL.md +7 -7
- package/skills/ai-delivery-orchestrate/assets/codebase-template/AGENTS.md +14 -0
- package/skills/ai-delivery-orchestrate/assets/codebase-template/CODEBASE_GUIDE.md +124 -0
- package/skills/ai-delivery-orchestrate/assets/codebase-template/docs/CODEBASE_INDEX.md +87 -0
- package/skills/ai-delivery-orchestrate/assets/communication-template/confirmation-examples.yaml +67 -0
- package/skills/ai-delivery-orchestrate/assets/communication-template/confirmation-summary.yaml +42 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/iterations/ITER-0001/02-solution-design/product-impact-assessment.yaml +42 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +2 -8
- package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +4 -5
- package/skills/ai-delivery-orchestrate/references/codebase-document-contract.md +91 -0
- package/skills/ai-delivery-orchestrate/references/communication-confirmation-contract.md +52 -0
- package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +11 -20
- package/skills/ai-delivery-orchestrate/references/product-impact-assessment-contract.md +87 -0
- package/skills/ai-delivery-orchestrate/references/workflow-model.md +1 -3
- package/skills/ai-delivery-orchestrate/scripts/confirmation-state.mjs +30 -0
- package/skills/ai-delivery-orchestrate-release/SKILL.md +3 -1
- package/skills/ai-delivery-plan-iteration/SKILL.md +6 -1
- package/skills/ai-delivery-prepare-platform/SKILL.md +1 -1
- package/skills/ai-delivery-prepare-release/SKILL.md +2 -0
- package/skills/ai-delivery-prepare-release/assets/release-template/release-manifest.yaml +0 -2
- package/skills/ai-delivery-review-change/SKILL.md +3 -1
- package/skills/ai-delivery-validate-artifacts/SKILL.md +10 -7
- package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +2 -1
- package/skills/ai-delivery-verify-candidate/SKILL.md +5 -2
- package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +1 -1
- package/docs/PROTOTYPE-MIGRATION.zh-CN.md +0 -164
- package/docs/VIEWER-MAINTENANCE.zh-CN.md +0 -150
- package/docs/VIEWER-USER-GUIDE.zh-CN.md +0 -113
- package/lib/project-migration.mjs +0 -1193
- package/lib/prototype-migration.mjs +0 -713
- package/skills/ai-delivery-design-experience/assets/experience-template/LEGACY-COMPATIBILITY.md +0 -16
- package/skills/ai-delivery-design-experience/assets/experience-template/canvas-catalog.csv +0 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/canvas-pages.csv +0 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/page-catalog.csv +0 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-file-terminals.csv +0 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-files.csv +0 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +0 -76
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +0 -24
package/lib/fs-utils.mjs
CHANGED
|
@@ -35,19 +35,3 @@ export function assertPathInside(root, target) {
|
|
|
35
35
|
if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) return target;
|
|
36
36
|
throw new Error(`Path resolves outside the project: ${target} -> ${realAncestor}`);
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
export function resolveContainedRelativePath(root, relative, allowedPrefixes = []) {
|
|
40
|
-
const value = String(relative || "");
|
|
41
|
-
if (!value || value.includes("\\") || value.includes("\0")
|
|
42
|
-
|| path.posix.isAbsolute(value) || path.posix.normalize(value) !== value
|
|
43
|
-
|| value === ".." || value.startsWith("../")) {
|
|
44
|
-
throw new Error(`Invalid migration path: ${value || "missing"}.`);
|
|
45
|
-
}
|
|
46
|
-
if (allowedPrefixes.length && !allowedPrefixes.some((prefix) =>
|
|
47
|
-
value === prefix || value.startsWith(`${prefix}/`))) {
|
|
48
|
-
throw new Error(`Migration path is outside its allowed area: ${value}.`);
|
|
49
|
-
}
|
|
50
|
-
const target = path.resolve(root, ...value.split("/"));
|
|
51
|
-
assertPathInside(root, target);
|
|
52
|
-
return target;
|
|
53
|
-
}
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
stableValue,
|
|
9
9
|
withFormalStateReadLock,
|
|
10
10
|
} from "./delivery-state.mjs";
|
|
11
|
+
import { readConfirmationSummary } from "./communication-confirmation.mjs";
|
|
11
12
|
import { inspectGitRepository, readWorkspaceConfiguration } from "./workspace.mjs";
|
|
12
13
|
|
|
13
14
|
const IGNORED_DIRECTORIES = new Set([
|
|
14
15
|
".git",
|
|
15
16
|
".agents",
|
|
16
17
|
".codex",
|
|
17
|
-
".ai-delivery",
|
|
18
18
|
".workflow",
|
|
19
19
|
".idea",
|
|
20
20
|
".vscode",
|
|
@@ -33,6 +33,50 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
33
33
|
"target",
|
|
34
34
|
]);
|
|
35
35
|
const VALID_NEXT_ACTION_OWNERS = new Set(["agent", "user", "external"]);
|
|
36
|
+
const CODEBASE_DOCUMENTATION_FILES = [
|
|
37
|
+
{ path: "AGENTS.md", kind: "agent-entrypoint" },
|
|
38
|
+
{ path: "CODEBASE_GUIDE.md", kind: "maintainer-guide" },
|
|
39
|
+
{ path: "docs/CODEBASE_INDEX.md", kind: "navigation-index" },
|
|
40
|
+
];
|
|
41
|
+
const CODEBASE_DOCUMENTATION_STATUS = new Set(["observed", "approved", "stale"]);
|
|
42
|
+
|
|
43
|
+
export function inspectConfirmationSummaries(root) {
|
|
44
|
+
const base = path.join(root, ".workflow", "iterations");
|
|
45
|
+
const summaries = [];
|
|
46
|
+
const errors = [];
|
|
47
|
+
if (!fs.existsSync(base)) return { status: "absent", summaries, errors };
|
|
48
|
+
const visit = (directory) => {
|
|
49
|
+
let entries;
|
|
50
|
+
try { entries = fs.readdirSync(directory, { withFileTypes: true }); } catch (error) {
|
|
51
|
+
errors.push(`${toPosix(path.relative(root, directory))}: ${error.message}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const absolute = path.join(directory, entry.name);
|
|
56
|
+
if (entry.isDirectory()) visit(absolute);
|
|
57
|
+
else if (entry.isFile() && entry.name === "confirmation-summary.yaml") {
|
|
58
|
+
try {
|
|
59
|
+
const result = readConfirmationSummary(absolute);
|
|
60
|
+
summaries.push({
|
|
61
|
+
path: toPosix(path.relative(root, absolute)),
|
|
62
|
+
checksum: sha256File(absolute),
|
|
63
|
+
summary_id: result.summary.summary_id,
|
|
64
|
+
version: result.summary.version,
|
|
65
|
+
frontier: result.validation.frontier,
|
|
66
|
+
blocked_proposals: result.validation.blocked_proposals,
|
|
67
|
+
valid: result.validation.valid,
|
|
68
|
+
errors: result.validation.errors,
|
|
69
|
+
});
|
|
70
|
+
if (!result.validation.valid) errors.push(...result.validation.errors.map((item) => `${toPosix(path.relative(root, absolute))}: ${item}`));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
errors.push(`${toPosix(path.relative(root, absolute))}: ${error.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
visit(base);
|
|
78
|
+
return { status: errors.length ? "invalid" : summaries.length ? "valid" : "absent", summaries, errors };
|
|
79
|
+
}
|
|
36
80
|
|
|
37
81
|
const SOURCE_EXTENSIONS = new Map([
|
|
38
82
|
[".c", "C"],
|
|
@@ -94,19 +138,6 @@ const WORKFLOW_REQUIRED_FILES = [
|
|
|
94
138
|
".workflow/standards/index.yaml",
|
|
95
139
|
".gitattributes",
|
|
96
140
|
];
|
|
97
|
-
const LEGACY_WORKFLOW_REQUIRED_FILES = [
|
|
98
|
-
".ai-delivery/workspace.yaml",
|
|
99
|
-
".ai-delivery/install-manifest.json",
|
|
100
|
-
".codex/config.toml",
|
|
101
|
-
"AGENTS.md",
|
|
102
|
-
".ai-delivery/state/state.mjs",
|
|
103
|
-
".ai-delivery/hooks/hook-event.mjs",
|
|
104
|
-
"delivery/project.yaml",
|
|
105
|
-
"delivery/workflow-state.yaml",
|
|
106
|
-
"delivery/artifact-registry.yaml",
|
|
107
|
-
"delivery/standards-baseline.yaml",
|
|
108
|
-
"standards/index.yaml",
|
|
109
|
-
];
|
|
110
141
|
const ESSENTIAL_STARTUP_SKILLS = [
|
|
111
142
|
"ai-delivery-bootstrap",
|
|
112
143
|
"ai-delivery-checkpoint-task",
|
|
@@ -269,6 +300,199 @@ function scanProjectFiles(root, maxFiles = 10_000) {
|
|
|
269
300
|
};
|
|
270
301
|
}
|
|
271
302
|
|
|
303
|
+
function hasSensitiveContent(text) {
|
|
304
|
+
return /BEGIN (?:RSA|OPENSSH|EC|DSA) PRIVATE KEY|(?:AKIA|ASIA)[A-Z0-9]{16}|(?:ghp|github_pat)_[A-Za-z0-9_]{20,}/.test(text)
|
|
305
|
+
|| /(?:password|secret|token|api[_-]?key)\s*[:=]\s*["'][^"']{8,}["']/i.test(text);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function documentationMetadata(text) {
|
|
309
|
+
const status = text.match(/(?:^|\|)\s*(?:状态|项目状态)\s*\|\s*`?([^|\n`]+)`?/m)?.[1]?.trim().toLowerCase() || null;
|
|
310
|
+
const sourceCommit = text.match(/(?:适用代码提交|对应代码提交|source_commit)\s*\|\s*`?([^|\n`]+)`?/m)?.[1]?.trim()
|
|
311
|
+
|| text.match(/`source_commit`\s*\|\s*`?([^|\n`]+)`?/m)?.[1]?.trim()
|
|
312
|
+
|| null;
|
|
313
|
+
const lastReviewed = text.match(/(?:最后核对|last_reviewed)\s*\|\s*`?([^|\n`]+)`?/m)?.[1]?.trim()
|
|
314
|
+
|| text.match(/`last_reviewed`\s*\|\s*`?([^|\n`]+)`?/m)?.[1]?.trim()
|
|
315
|
+
|| null;
|
|
316
|
+
return { status, source_commit: sourceCommit, last_reviewed: lastReviewed };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function documentationPathEntries(text) {
|
|
320
|
+
const entries = [];
|
|
321
|
+
let header = null;
|
|
322
|
+
for (const [index, line] of text.split(/\r?\n/).entries()) {
|
|
323
|
+
if (!line.trim().startsWith("|")) continue;
|
|
324
|
+
const cells = line.split("|").slice(1, -1).map((cell) => cell.trim());
|
|
325
|
+
if (!cells.length) continue;
|
|
326
|
+
if (cells.some((cell) => /^(路径|文件)$/.test(cell))) {
|
|
327
|
+
header = cells;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (!header || cells.every((cell) => /^:?-{3,}:?$/.test(cell))) continue;
|
|
331
|
+
const pathIndex = header.findIndex((cell) => /^(路径|文件)$/.test(cell));
|
|
332
|
+
if (pathIndex < 0 || !cells[pathIndex]) continue;
|
|
333
|
+
entries.push({ value: cells[pathIndex].replaceAll("`", "").trim(), line: index + 1 });
|
|
334
|
+
}
|
|
335
|
+
return entries;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function documentationLinks(text) {
|
|
339
|
+
return [...text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)].map((match) => match[1].trim());
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function inspectCodebaseDocumentation(root, git = inspectGitRepository(root)) {
|
|
343
|
+
const files = [];
|
|
344
|
+
const errors = [];
|
|
345
|
+
const warnings = [];
|
|
346
|
+
const missing = [];
|
|
347
|
+
const invalid = [];
|
|
348
|
+
const stale = [];
|
|
349
|
+
const statuses = [];
|
|
350
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
351
|
+
return {
|
|
352
|
+
status: "missing",
|
|
353
|
+
files: [],
|
|
354
|
+
missing: CODEBASE_DOCUMENTATION_FILES.map((item) => item.path),
|
|
355
|
+
invalid: [],
|
|
356
|
+
stale: [],
|
|
357
|
+
sensitive_file_count: 0,
|
|
358
|
+
errors: [],
|
|
359
|
+
warnings: ["代码仓库目录不存在,无法发现项目说明书和索引。"],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const expected of CODEBASE_DOCUMENTATION_FILES) {
|
|
364
|
+
const absolute = path.join(root, expected.path);
|
|
365
|
+
if (!fs.existsSync(absolute)) {
|
|
366
|
+
missing.push(expected.path);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
let stat;
|
|
370
|
+
try {
|
|
371
|
+
stat = fs.lstatSync(absolute);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
invalid.push({ path: expected.path, reason: `无法读取文件:${error.code || error.message}` });
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (stat.isSymbolicLink()) {
|
|
377
|
+
invalid.push({ path: expected.path, reason: "说明文件不能是符号链接" });
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (!stat.isFile()) {
|
|
381
|
+
invalid.push({ path: expected.path, reason: "说明路径必须是普通文件" });
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
let text;
|
|
385
|
+
try {
|
|
386
|
+
text = fs.readFileSync(absolute, "utf8");
|
|
387
|
+
} catch (error) {
|
|
388
|
+
invalid.push({ path: expected.path, reason: `无法读取文件:${error.code || error.message}` });
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const metadata = documentationMetadata(text);
|
|
392
|
+
const record = { path: expected.path, kind: expected.kind, ...metadata, bytes: stat.size };
|
|
393
|
+
files.push(record);
|
|
394
|
+
if (expected.kind !== "agent-entrypoint") {
|
|
395
|
+
if (!metadata.status) invalid.push({ path: expected.path, reason: "缺少 status/状态字段" });
|
|
396
|
+
if (!metadata.source_commit) invalid.push({ path: expected.path, reason: "缺少 source_commit/适用代码提交字段" });
|
|
397
|
+
if (!metadata.last_reviewed) invalid.push({ path: expected.path, reason: "缺少 last_reviewed/最后核对字段" });
|
|
398
|
+
}
|
|
399
|
+
if (metadata.status && !CODEBASE_DOCUMENTATION_STATUS.has(metadata.status)) {
|
|
400
|
+
invalid.push({ path: expected.path, reason: `status 无效:${metadata.status}` });
|
|
401
|
+
}
|
|
402
|
+
if (metadata.status) statuses.push(metadata.status);
|
|
403
|
+
if (hasSensitiveContent(text)) {
|
|
404
|
+
invalid.push({ path: expected.path, reason: "疑似包含凭据、私钥或敏感值" });
|
|
405
|
+
}
|
|
406
|
+
if (git?.head && metadata.source_commit && !/^<.*>$/.test(metadata.source_commit)
|
|
407
|
+
&& metadata.source_commit !== "-" && metadata.source_commit !== git.head) {
|
|
408
|
+
stale.push({ path: expected.path, reason: `source_commit ${metadata.source_commit} 与当前 HEAD ${git.head} 不一致` });
|
|
409
|
+
}
|
|
410
|
+
if (expected.kind === "maintainer-guide") {
|
|
411
|
+
for (const heading of ["项目概览", "快速开始", "仓库地图", "工程分层", "工程约定", "变更工作流", "风险和保护边界", "文档维护"]) {
|
|
412
|
+
if (!new RegExp(`^## .*${heading}`, "m").test(text)) {
|
|
413
|
+
invalid.push({ path: expected.path, reason: `缺少章节:${heading}` });
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (expected.kind === "navigation-index") {
|
|
418
|
+
for (const field of ["路径", "职责", "主要入口", "关联测试", "风险", "状态", "source_commit", "last_reviewed"]) {
|
|
419
|
+
if (!text.includes(field)) invalid.push({ path: expected.path, reason: `缺少索引字段:${field}` });
|
|
420
|
+
}
|
|
421
|
+
for (const entry of documentationPathEntries(text)) {
|
|
422
|
+
const value = entry.value;
|
|
423
|
+
if (!value || value === "-" || /^<.*>$/.test(value) || value.includes("*")) continue;
|
|
424
|
+
if (path.isAbsolute(value) || value.split("/").includes("..")) {
|
|
425
|
+
invalid.push({ path: expected.path, reason: `第 ${entry.line} 行路径越界:${value}` });
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const target = path.resolve(root, value);
|
|
429
|
+
const relative = path.relative(root, target);
|
|
430
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
431
|
+
invalid.push({ path: expected.path, reason: `第 ${entry.line} 行路径越界:${value}` });
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (!fs.existsSync(target)) {
|
|
435
|
+
invalid.push({ path: expected.path, reason: `第 ${entry.line} 行引用路径不存在:${value}` });
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (fs.lstatSync(target).isSymbolicLink()) {
|
|
439
|
+
invalid.push({ path: expected.path, reason: `第 ${entry.line} 行引用符号链接:${value}` });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const seenPaths = new Set();
|
|
444
|
+
for (const entry of documentationPathEntries(text)) {
|
|
445
|
+
if (/^<.*>$/.test(entry.value) || entry.value === "-" || entry.value.includes("*")) continue;
|
|
446
|
+
if (seenPaths.has(entry.value)) {
|
|
447
|
+
invalid.push({ path: expected.path, reason: `第 ${entry.line} 行重复索引路径:${entry.value}` });
|
|
448
|
+
}
|
|
449
|
+
seenPaths.add(entry.value);
|
|
450
|
+
}
|
|
451
|
+
for (const link of documentationLinks(text)) {
|
|
452
|
+
const targetValue = link.split(/[?#]/, 1)[0];
|
|
453
|
+
if (!targetValue || targetValue.startsWith("#") || /^[a-z][a-z0-9+.-]*:/i.test(targetValue)) continue;
|
|
454
|
+
if (path.isAbsolute(targetValue) || targetValue.split("/").includes("..")) {
|
|
455
|
+
invalid.push({ path: expected.path, reason: `Markdown 链接越界:${link}` });
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const target = path.resolve(root, path.dirname(expected.path), targetValue);
|
|
459
|
+
const relative = path.relative(root, target);
|
|
460
|
+
if (relative.startsWith("..") || path.isAbsolute(relative) || !fs.existsSync(target)) {
|
|
461
|
+
invalid.push({ path: expected.path, reason: `Markdown 链接路径不存在:${link}` });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (missing.length) warnings.push(`缺少代码仓库说明文件:${missing.join(", ")}`);
|
|
467
|
+
if (invalid.length) errors.push(...invalid.map((item) => `${item.path}: ${item.reason}`));
|
|
468
|
+
if (stale.length) warnings.push(...stale.map((item) => `${item.path}: ${item.reason}`));
|
|
469
|
+
const hasTemplatePlaceholders = files.some((file) => file.source_commit?.startsWith("<") || file.status === null);
|
|
470
|
+
if (new Set(statuses).size > 1) {
|
|
471
|
+
errors.push(`代码仓库说明文档状态冲突:${[...new Set(statuses)].join(", ")}`);
|
|
472
|
+
}
|
|
473
|
+
const status = invalid.length || errors.length
|
|
474
|
+
? "invalid"
|
|
475
|
+
: stale.length
|
|
476
|
+
? "stale"
|
|
477
|
+
: missing.length
|
|
478
|
+
? "missing"
|
|
479
|
+
: statuses.some((value) => value === "stale")
|
|
480
|
+
? "stale"
|
|
481
|
+
: hasTemplatePlaceholders || statuses.some((value) => value === "observed")
|
|
482
|
+
? "observed"
|
|
483
|
+
: "approved";
|
|
484
|
+
return {
|
|
485
|
+
status,
|
|
486
|
+
files,
|
|
487
|
+
missing,
|
|
488
|
+
invalid,
|
|
489
|
+
stale,
|
|
490
|
+
sensitive_file_count: invalid.filter((item) => item.reason.includes("敏感值")).length,
|
|
491
|
+
errors,
|
|
492
|
+
warnings,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
272
496
|
function gitState(root) {
|
|
273
497
|
const run = (args) => spawnSync("git", args, { cwd: root, encoding: "utf8", windowsHide: true });
|
|
274
498
|
const topLevel = run(["rev-parse", "--show-toplevel"]);
|
|
@@ -306,13 +530,13 @@ function normalizeInspectedScope(value, fallbackScopeId = null) {
|
|
|
306
530
|
}
|
|
307
531
|
|
|
308
532
|
function inspectWorkflowScopes(workflowState, errors) {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
: normalizeInspectedScope(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
533
|
+
if (Number(workflowState?.schema_version) !== 2
|
|
534
|
+
|| !workflowState?.development_state || !workflowState?.release_state) {
|
|
535
|
+
errors.push("formal state must use schema 2 with development_state and release_state scopes");
|
|
536
|
+
return { development: normalizeInspectedScope(null), release: normalizeInspectedScope(null) };
|
|
537
|
+
}
|
|
538
|
+
const development = normalizeInspectedScope(workflowState.development_state);
|
|
539
|
+
const release = normalizeInspectedScope(workflowState.release_state);
|
|
316
540
|
|
|
317
541
|
for (const [scopeName, scope] of [["development", development], ["release", release]]) {
|
|
318
542
|
for (const conflict of findIncompatibleNodeStates(scope)) {
|
|
@@ -505,23 +729,15 @@ export function inspectProjectState(target) {
|
|
|
505
729
|
const codebase = scanProjectFiles(codeRoot);
|
|
506
730
|
const errors = [];
|
|
507
731
|
const currentIndicators = WORKFLOW_REQUIRED_FILES.filter((file) => exists(root, file));
|
|
508
|
-
const
|
|
509
|
-
const currentStateIndicators = WORKFLOW_REQUIRED_FILES.filter((file) =>
|
|
510
|
-
file.startsWith(".workflow/delivery/") || file === ".workflow/standards/index.yaml")
|
|
511
|
-
.filter((file) => exists(root, file));
|
|
512
|
-
const legacyStateIndicators = LEGACY_WORKFLOW_REQUIRED_FILES.filter((file) =>
|
|
513
|
-
file.startsWith("delivery/") || file === "standards/index.yaml")
|
|
732
|
+
const unsupportedIndicators = [".ai-delivery", ".ai-delivery-task", "delivery", "standards"]
|
|
514
733
|
.filter((file) => exists(root, file));
|
|
515
|
-
const
|
|
516
|
-
const
|
|
517
|
-
const
|
|
518
|
-
const workflowIndicators = requiredFiles.filter((file) => exists(root, file));
|
|
519
|
-
const missingFiles = requiredFiles.filter((file) => !exists(root, file));
|
|
734
|
+
const workflowRoot = ".workflow";
|
|
735
|
+
const workflowIndicators = WORKFLOW_REQUIRED_FILES.filter((file) => exists(root, file));
|
|
736
|
+
const missingFiles = WORKFLOW_REQUIRED_FILES.filter((file) => !exists(root, file));
|
|
520
737
|
const installation = {
|
|
521
|
-
detected: currentIndicators.length > 0 ||
|
|
522
|
-
complete: workflowIndicators.length ===
|
|
738
|
+
detected: currentIndicators.length > 0 || unsupportedIndicators.length > 0,
|
|
739
|
+
complete: workflowIndicators.length === WORKFLOW_REQUIRED_FILES.length,
|
|
523
740
|
workflow_root: workflowRoot,
|
|
524
|
-
legacy_layout: legacyLayout,
|
|
525
741
|
present_files: workflowIndicators,
|
|
526
742
|
missing_files: missingFiles,
|
|
527
743
|
installed_skill_count: 0,
|
|
@@ -556,25 +772,13 @@ export function inspectProjectState(target) {
|
|
|
556
772
|
dependency_errors: [],
|
|
557
773
|
};
|
|
558
774
|
|
|
559
|
-
if (
|
|
560
|
-
errors.push("
|
|
561
|
-
}
|
|
562
|
-
if (currentStateIndicators.length > 0 && legacyStateIndicators.length > 0) {
|
|
563
|
-
errors.push("同时检测到 .workflow 和旧版 delivery/standards 状态;必须人工确定唯一权威根。");
|
|
775
|
+
if (unsupportedIndicators.length) {
|
|
776
|
+
errors.push(`检测到不受支持的旧工作流路径:${unsupportedIndicators.join(", ")}。当前项目只接受 .workflow/ 布局。`);
|
|
564
777
|
}
|
|
565
778
|
|
|
566
779
|
if (installation.complete) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
if (currentStateIndicators.length > 0 && legacyStateIndicators.length > 0) {
|
|
571
|
-
errors.push("同时检测到 .workflow 和旧版 delivery/standards 状态;必须人工确定唯一权威根。");
|
|
572
|
-
}
|
|
573
|
-
const manifest = readStructuredFile(path.join(
|
|
574
|
-
root,
|
|
575
|
-
legacyLayout ? ".ai-delivery/install-manifest.json" : ".workflow/config/install-manifest.json",
|
|
576
|
-
), "json", errors);
|
|
577
|
-
const stateRoot = path.join(root, workflowRoot);
|
|
780
|
+
const manifest = readStructuredFile(path.join(root, ".workflow/config/install-manifest.json"), "json", errors);
|
|
781
|
+
const stateRoot = path.join(root, ".workflow");
|
|
578
782
|
let project = null;
|
|
579
783
|
let workflowState = null;
|
|
580
784
|
let registry = null;
|
|
@@ -583,13 +787,10 @@ export function inspectProjectState(target) {
|
|
|
583
787
|
workflowState = readStructuredFile(path.join(stateRoot, "delivery", "workflow-state.yaml"), "yaml", errors);
|
|
584
788
|
registry = readStructuredFile(path.join(stateRoot, "delivery", "artifact-registry.yaml"), "yaml", errors);
|
|
585
789
|
};
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
} catch (error) {
|
|
591
|
-
errors.push(error.message);
|
|
592
|
-
}
|
|
790
|
+
try {
|
|
791
|
+
withFormalStateReadLock(root, readFormalState);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
errors.push(error.message);
|
|
593
794
|
}
|
|
594
795
|
readStructuredFile(path.join(stateRoot, "delivery", "standards-baseline.yaml"), "yaml", errors);
|
|
595
796
|
readStructuredFile(path.join(stateRoot, "standards", "index.yaml"), "yaml", errors);
|
|
@@ -630,8 +831,19 @@ export function inspectProjectState(target) {
|
|
|
630
831
|
}
|
|
631
832
|
}
|
|
632
833
|
|
|
834
|
+
const codeGit = inspectGitRepository(codeRoot);
|
|
835
|
+
const documentation = inspectCodebaseDocumentation(codeRoot, codeGit);
|
|
836
|
+
const confirmations = inspectConfirmationSummaries(root);
|
|
837
|
+
if (confirmations.errors.length) errors.push(...confirmations.errors);
|
|
838
|
+
codebase.summary.documentation = documentation;
|
|
839
|
+
codebase.summary.confirmations = confirmations;
|
|
840
|
+
errors.push(...documentation.errors);
|
|
841
|
+
const documentationWarnings = documentation.warnings;
|
|
842
|
+
|
|
633
843
|
let state;
|
|
634
|
-
if (
|
|
844
|
+
if (errors.length) {
|
|
845
|
+
state = "workflow-inconsistent";
|
|
846
|
+
} else if (!installation.detected) {
|
|
635
847
|
state = codebase.summary.has_code ? "codebase-uninitialized" : "empty-uninitialized";
|
|
636
848
|
} else if (!installation.complete || errors.length) {
|
|
637
849
|
state = "workflow-inconsistent";
|
|
@@ -641,7 +853,6 @@ export function inspectProjectState(target) {
|
|
|
641
853
|
state = "workflow-ready";
|
|
642
854
|
}
|
|
643
855
|
const recommendation = recommendationFor(state, workflow, runtime);
|
|
644
|
-
const codeGit = inspectGitRepository(codeRoot);
|
|
645
856
|
const workflowGit = inspectGitRepository(root);
|
|
646
857
|
const syncWarnings = [];
|
|
647
858
|
if (workflowGit.sync_status === "uncommitted") {
|
|
@@ -662,11 +873,12 @@ export function inspectProjectState(target) {
|
|
|
662
873
|
workflow,
|
|
663
874
|
runtime,
|
|
664
875
|
codebase: codebase.summary,
|
|
876
|
+
confirmations,
|
|
665
877
|
git: codeGit,
|
|
666
878
|
workflow_git: workflowGit,
|
|
667
879
|
recommendation,
|
|
668
880
|
errors,
|
|
669
|
-
warnings: [...codebase.warnings, ...syncWarnings],
|
|
881
|
+
warnings: [...codebase.warnings, ...documentationWarnings, ...syncWarnings],
|
|
670
882
|
};
|
|
671
883
|
}
|
|
672
884
|
|
|
@@ -738,6 +950,10 @@ function adoptionMarkdown(initialState, scan, result) {
|
|
|
738
950
|
if (!artifactTypes.some((type) => /architecture.*baseline/.test(type))) gaps.push("缺少经确认的架构基线。");
|
|
739
951
|
if (scan.summary.has_code && !scan.summary.categories.test) gaps.push("未发现可识别的自动化测试文件,需人工核对测试入口。");
|
|
740
952
|
if (!result.git.repository) gaps.push("当前目录不是有效 Git 仓库,分支和提交身份不可用。");
|
|
953
|
+
const documentation = result.codebase.documentation;
|
|
954
|
+
if (documentation?.status === "missing") gaps.push("代码仓库说明书或导航索引缺失,需要在维护者确认后补齐。");
|
|
955
|
+
if (documentation?.status === "invalid") gaps.push("代码仓库说明书或导航索引存在结构、路径或敏感内容问题,Bootstrap 已阻止将其视为可信上下文。");
|
|
956
|
+
if (documentation?.status === "stale") gaps.push("代码仓库说明书或导航索引相对当前提交过期,需要更新 source_commit 并重新审阅。");
|
|
741
957
|
return `# 存量项目接入评估
|
|
742
958
|
|
|
743
959
|
## 探测结论
|
|
@@ -749,6 +965,14 @@ function adoptionMarkdown(initialState, scan, result) {
|
|
|
749
965
|
- 检测技术:${scan.summary.technologies.join("、") || "未识别"}
|
|
750
966
|
- 排除的敏感文件:${scan.summary.sensitive_file_count}
|
|
751
967
|
|
|
968
|
+
## 代码仓库说明书与导航
|
|
969
|
+
|
|
970
|
+
- 状态:\`${documentation?.status || "missing"}\`
|
|
971
|
+
- 已发现:${(documentation?.files || []).filter((item) => item.status !== "missing").map((item) => item.path).join("、") || "无"}
|
|
972
|
+
- 缺失:${(documentation?.missing || []).join("、") || "无"}
|
|
973
|
+
- 失效:${(documentation?.invalid || []).map((item) => item.path).join("、") || "无"}
|
|
974
|
+
- 过期:${(documentation?.stale || []).map((item) => item.path).join("、") || "无"}
|
|
975
|
+
|
|
752
976
|
## 上下文边界
|
|
753
977
|
|
|
754
978
|
本报告只描述代码和仓库的可观察现状。代码行为不是已批准产品需求,自动归纳内容均保持 \`draft\`,必须通过产品和架构流程核对后才能成为正式基线。
|
|
@@ -814,6 +1038,7 @@ function writeBootstrapArtifacts(root, initialState, result, sourceScan = null)
|
|
|
814
1038
|
detected_at: result.detected_at,
|
|
815
1039
|
repository: result.git,
|
|
816
1040
|
codebase: scan.summary,
|
|
1041
|
+
documentation: result.codebase.documentation,
|
|
817
1042
|
command_candidates: packageCommands(root, scan.files),
|
|
818
1043
|
constraints: [
|
|
819
1044
|
"Observed code is not an approved product requirement.",
|
|
@@ -870,6 +1095,8 @@ function writeBootstrapArtifacts(root, initialState, result, sourceScan = null)
|
|
|
870
1095
|
workflow: result.workflow,
|
|
871
1096
|
runtime: result.runtime,
|
|
872
1097
|
codebase: result.codebase,
|
|
1098
|
+
documentation: result.codebase.documentation,
|
|
1099
|
+
confirmations: result.confirmations,
|
|
873
1100
|
git: result.git,
|
|
874
1101
|
recommendation: result.recommendation,
|
|
875
1102
|
errors: result.errors,
|