@rowansenne/workflow-run 0.25.0 → 0.27.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +60 -38
  3. package/bin/lib/artifact-context.mjs +46 -0
  4. package/bin/lib/core.mjs +5 -1
  5. package/bin/lib/docnexus.mjs +6 -6
  6. package/bin/lib/doctor.mjs +1 -2
  7. package/bin/lib/install.mjs +1 -1
  8. package/bin/lib/uninstall.mjs +3 -0
  9. package/bin/workflow-run.mjs +20 -29
  10. package/code-graph/SKILL.md.tpl +2 -7
  11. package/create-release-md-doc/SKILL.md.tpl +9 -8
  12. package/create-tech-md-doc/SKILL.md.tpl +9 -8
  13. package/database-query/SKILL.md.tpl +2 -0
  14. package/database-query/skill.json +2 -2
  15. package/dev/SKILL.md.tpl +42 -116
  16. package/dev/references/bug.md +30 -0
  17. package/dev/references/delivery-template.md +6 -6
  18. package/dev/references/delivery.md +48 -0
  19. package/dev/references/implementation.md +19 -0
  20. package/dev/references/plan-template.md +5 -3
  21. package/dev/references/planning.md +45 -0
  22. package/dev/references/preflight.md +35 -0
  23. package/dev/skill.json +2 -2
  24. package/md-to-wiki/SKILL.md.tpl +5 -1
  25. package/md-to-wiki/skill.json +2 -2
  26. package/package.json +2 -2
  27. package/project-review/SKILL.md.tpl +46 -72
  28. package/project-review/references/report.md.tpl +4 -4
  29. package/project-review/references/reporting.md +48 -0
  30. package/project-review/references/review-method.md +56 -0
  31. package/project-review/references/verification.md +30 -0
  32. package/shared/prd-fetch.md +1 -1
  33. package/workflow/SKILL.md.tpl +15 -16
  34. package/workflow/lib/artifact-model.mjs +83 -0
  35. package/workflow/lib/capabilities.mjs +80 -0
  36. package/workflow/lib/document-model.mjs +86 -0
  37. package/workflow/lib/meta-store.mjs +112 -0
  38. package/workflow/lib/project.mjs +218 -0
  39. package/workflow/lib/requirement-read.mjs +68 -0
  40. package/workflow/lib/requirements.mjs +219 -0
  41. package/workflow/lib/runs.mjs +144 -0
  42. package/workflow/lib/workspace-read.mjs +120 -0
  43. package/workflow/references/architecture.md +43 -0
  44. package/workflow/references/capabilities.md +39 -0
  45. package/workflow/references/codegraph.md +3 -3
  46. package/workflow/references/docnexus.md +1 -1
  47. package/workflow/references/requirements.md +25 -13
  48. package/workflow/references/runs.md +98 -0
  49. package/workflow/references/status.md +6 -3
  50. package/workflow/references/templates.md +11 -14
  51. package/workflow/scripts/artifacts.mjs +21 -80
  52. package/workflow/scripts/doc-sections.mjs +2 -55
  53. package/workflow/scripts/docnexus-layout.mjs +2 -24
  54. package/workflow/scripts/knowledge.mjs +4 -2
  55. package/workflow/scripts/lint-doc.mjs +2 -35
  56. package/workflow/scripts/meta-store.mjs +2 -0
  57. package/workflow/scripts/pr-desc.mjs +8 -6
  58. package/workflow/scripts/report.mjs +20 -68
  59. package/workflow/scripts/requirement.mjs +9 -402
  60. package/workflow/scripts/resolve.mjs +22 -190
  61. package/workflow/scripts/review-scope.mjs +2 -1
  62. package/workflow/scripts/run.mjs +37 -0
  63. package/workflow/scripts/tables.mjs +1 -1
  64. package/workflow/scripts/template.mjs +2 -6
  65. package/workflow/scripts/view.mjs +41 -21
@@ -0,0 +1,144 @@
1
+ // 运行状态服务:阶段、完成校验和 revision;不依赖命令入口。
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { find, requirements } from './requirement-read.mjs';
6
+ import { parseBranch } from './artifact-model.mjs';
7
+ import { updateMeta } from './meta-store.mjs';
8
+ import { loadWorkspace } from './workspace-read.mjs';
9
+
10
+ export const FLOWS = {
11
+ feature: ['Step0', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8'],
12
+ bug: ['Step0', 'B1', 'B2', 'B3', 'B4', 'B5'],
13
+ review: ['R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7'],
14
+ tech: ['T0', 'T1', 'T2', 'T3', 'T4'],
15
+ release: ['L0', 'L1', 'L2', 'L3', 'L4'],
16
+ wiki: ['W0', 'W1', 'W2'],
17
+ };
18
+ export const STATUS_LABELS = { running: '进行中', waiting_user: '等待用户', blocked: '受阻', completed: '已完成', cancelled: '已取消' };
19
+ const terminal = (s) => s === 'completed' || s === 'cancelled';
20
+ const done = (s) => s === 'completed' || s === 'skipped';
21
+ const object = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
22
+ const timestamp = () => new Date().toISOString();
23
+
24
+ export function allRuns(root, workspace = loadWorkspace(root)) {
25
+ return workspace.runs.filter((run) => run.project === workspace.layout.project).map(({ project, ...run }) => run);
26
+ }
27
+
28
+ export const branchRuns = (root, branch, workspace) => allRuns(root, workspace).filter((run) => run.branch === branch);
29
+
30
+ export function runState(root, branch, workspace = loadWorkspace(root)) {
31
+ const runs = branchRuns(root, branch, workspace);
32
+ const current = runs.filter((r) => !terminal(r.status)).at(-1) ?? runs.at(-1) ?? null;
33
+ const next = !current ? null : current.status === 'waiting_user' ? `等待用户:${current.waiting_for}`
34
+ : current.status === 'blocked' ? `处理阻塞:${current.reason}`
35
+ : terminal(current.status) ? `${STATUS_LABELS[current.status]}:${current.kind}(${current.id})`
36
+ : `继续 ${current.stage}(${current.kind},运行 ${current.id})`;
37
+ return { branch, current, runs, next, ...(workspace.diagnostics.length ? { diagnostics: workspace.diagnostics } : {}) };
38
+ }
39
+
40
+ function applyCheckpoint(run, patch, at, reopen = true) {
41
+ if (!object(patch)) throw new Error('checkpoint 必须是 JSON 对象');
42
+ const allowed = ['stage', 'status', 'size', 'waiting_for', 'reason', 'steps', 'context', 'decision'];
43
+ for (const key of Object.keys(patch)) if (!allowed.includes(key)) throw new Error(`不支持修改字段:${key}`);
44
+ if (patch.status !== undefined && !['running', 'waiting_user', 'blocked'].includes(patch.status)) throw new Error('save 的状态只支持 running / waiting_user / blocked;完成用 complete,取消用 cancel');
45
+ if (patch.size !== undefined) {
46
+ if (!['S', 'M', 'L'].includes(patch.size)) throw new Error('size 必须是 S / M / L');
47
+ run.size = patch.size;
48
+ }
49
+ const stages = FLOWS[run.kind];
50
+ const stage = patch.stage ?? run.stage;
51
+ if (!stages.includes(stage)) throw new Error(`无效阶段:${stage}`);
52
+ const status = patch.status ?? run.status;
53
+ // 回到已完成的阶段时,后续完成标记失效;历史 checkpoint 仍保留。
54
+ if (reopen && patch.stage && (stages.indexOf(stage) < stages.indexOf(run.stage) || (status === 'running' && done(run.steps[stage].status)))) {
55
+ for (const name of stages.slice(stages.indexOf(stage))) run.steps[name] = { status: 'pending' };
56
+ run.decision = null;
57
+ }
58
+ if (patch.steps !== undefined) {
59
+ if (!object(patch.steps)) throw new Error('steps 必须是对象');
60
+ for (const [name, value] of Object.entries(patch.steps)) {
61
+ if (!stages.includes(name) || !object(value)) throw new Error(`无效步骤:${name}`);
62
+ if (!['pending', 'in_progress', 'completed', 'skipped'].includes(value.status)) throw new Error(`无效步骤状态:${name}`);
63
+ if (value.status === 'skipped' && !value.reason?.trim()) throw new Error(`跳过 ${name} 必须注明 reason`);
64
+ run.steps[name] = { ...value, updated_at: at, completed_at: value.status === 'completed' ? (run.steps[name].completed_at ?? at) : null };
65
+ }
66
+ }
67
+ if (patch.context !== undefined) {
68
+ if (!object(patch.context)) throw new Error('context 必须是对象');
69
+ run.context = { ...run.context, ...patch.context };
70
+ }
71
+ if (patch.decision !== undefined) {
72
+ if (!object(patch.decision) || !['fix', 'deliver'].includes(patch.decision.action) || patch.decision.confirmed !== true) throw new Error('decision 必须记录用户明确确认的 fix / deliver');
73
+ run.decision = { ...patch.decision, at };
74
+ }
75
+ run.stage = stage;
76
+ run.status = status;
77
+ run.waiting_for = status === 'waiting_user' ? (patch.waiting_for ?? run.waiting_for) : null;
78
+ run.reason = status === 'blocked' ? (patch.reason ?? run.reason) : null;
79
+ if (status === 'waiting_user' && !run.waiting_for?.trim()) throw new Error('等待用户时必须填写 waiting_for');
80
+ if (status === 'blocked' && !run.reason?.trim()) throw new Error('阻塞时必须填写 reason');
81
+ if (status === 'running' && run.steps[stage].status === 'pending') run.steps[stage] = { status: 'in_progress', updated_at: at };
82
+ run.updated_at = at;
83
+ run.history.push({ at, stage, status, steps: patch.steps ?? {}, ...(patch.decision ? { decision: run.decision } : {}) });
84
+ return run;
85
+ }
86
+
87
+ export function startRun(root, { branch, kind = 'feature', size = null, input = {}, dir = null }) {
88
+ if (!branch || !FLOWS[kind]) throw new Error('start 需要有效分支及 kind:feature / bug / review / tech / release / wiki');
89
+ if (['feature', 'bug'].includes(kind) && !['S', 'M', 'L'].includes(size)) throw new Error('完整 dev 流程需要 size:S / M / L;快速通道不创建运行记录');
90
+ const req = dir ? requirements(root).find((r) => r.abs === path.resolve(root, dir)) : find(root, branch);
91
+ if (!req || !req.branches.includes(branch)) throw new Error('当前分支尚未绑定指定需求,先确认需求名并运行 requirement.mjs bind;快速通道不要调用 start');
92
+ let selected; let resumed = false;
93
+ updateMeta(path.join(req.abs, 'meta.json'), (meta) => {
94
+ const runs = meta.runs ?? {};
95
+ selected = Object.values(runs).find((r) => r.branch === branch && r.kind === kind && !terminal(r.status));
96
+ if (selected) { resumed = true; return meta; }
97
+ const at = timestamp(); const id = randomUUID();
98
+ selected = applyCheckpoint({ id, kind, size, branch, revision: 1, status: 'running', stage: FLOWS[kind][0],
99
+ created_at: at, updated_at: at, completed_at: null, steps: Object.fromEntries(FLOWS[kind].map((s) => [s, { status: 'pending' }])),
100
+ context: {}, waiting_for: null, reason: null, decision: null, history: [] }, input, at);
101
+ return { ...meta, runs: { ...runs, [id]: selected }, updated: at };
102
+ }, { root });
103
+ return { ok: true, resumed, dir: req.dir, run: selected };
104
+ }
105
+
106
+ export function changeRun(root, { branch, id, revision, input = {}, action = 'save' }) {
107
+ if (!branch || !id || !['save', 'complete', 'cancel'].includes(action)) throw new Error('需要当前分支、运行 id 和有效动作');
108
+ const req = requirements(root).find((r) => Object.hasOwn(r.meta.runs ?? {}, id));
109
+ if (!req) throw new Error(`找不到运行:${id}`);
110
+ let result;
111
+ updateMeta(path.join(req.abs, 'meta.json'), (meta) => {
112
+ const run = structuredClone(meta.runs[id]);
113
+ if (run.branch !== branch) throw new Error('运行属于其他分支,请切换到对应分支后恢复,不要修改原运行的分支');
114
+ if (!Number.isInteger(revision) || run.revision !== revision) throw new Error(`运行版本冲突,重新读取状态后使用 --expect-revision ${run.revision}`);
115
+ if (terminal(run.status)) throw new Error('运行已结束,不再修改;新的工作请 start 新运行');
116
+ const at = timestamp();
117
+ if (action === 'cancel') {
118
+ if (!input.reason?.trim()) throw new Error('取消运行需要 reason');
119
+ run.status = 'cancelled'; run.reason = input.reason; run.waiting_for = null; run.ended_at = at;
120
+ run.history.push({ at, stage: run.stage, status: 'cancelled', reason: input.reason });
121
+ } else {
122
+ applyCheckpoint(run, input, at, action !== 'complete');
123
+ if (action === 'complete') {
124
+ const steps = FLOWS[run.kind];
125
+ if (run.status !== 'running' || run.stage !== steps.at(-1) || !steps.every((s) => done(run.steps[s].status)) || run.steps[steps.at(-1)].status !== 'completed') throw new Error('还有未完成步骤、待用户决定或阻塞,不能标记已完成');
126
+ if (['feature', 'bug'].includes(run.kind)) {
127
+ const required = run.kind === 'feature' ? ['F4', 'F5', 'F6', 'F7', 'F8'] : ['B4', 'B5'];
128
+ if (required.some((s) => run.steps[s].status !== 'completed') || run.decision?.action !== 'deliver' || !run.decision.confirmed) throw new Error('完整 dev 流程必须完成方案、实现、验证、审查和交付,且用户已选择交付');
129
+ }
130
+ const files = { feature: ['plan.md', 'review.md', 'delivery.md'], bug: ['plan.md', 'review.md', 'delivery.md'], review: ['review.md'], tech: ['tech-doc.md'], release: ['release-doc.md'] };
131
+ for (const name of files[run.kind] ?? []) {
132
+ const file = path.join(req.abs, name);
133
+ if (!fs.existsSync(file) || parseBranch(fs.readFileSync(file, 'utf8')) !== branch) throw new Error(`缺少当前分支的产物:${name}`);
134
+ }
135
+ run.status = 'completed'; run.completed_at = at; run.waiting_for = null; run.reason = null;
136
+ run.history.push({ at, stage: run.stage, status: 'completed' });
137
+ }
138
+ }
139
+ run.updated_at = at; run.revision++;
140
+ result = run;
141
+ return { ...meta, runs: { ...meta.runs, [id]: run }, updated: at };
142
+ }, { root });
143
+ return { ok: true, dir: req.dir, run: result };
144
+ }
@@ -0,0 +1,120 @@
1
+ // 单次命令的只读内存快照。工作区与 Git 使用相同解析器,消费者不再重读正文。
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { layout, REQ_DIR } from './project.mjs';
6
+ import { readRequirementDirectories } from './requirement-read.mjs';
7
+ import { FILES, LABELS, parseFile, parseBranch, parseWiki, parseDocnexus, headerDate, captureState, citesKnowledge, parseReview, parseDelivery } from './artifact-model.mjs';
8
+
9
+ const relative = (root, file) => path.relative(root, file).split(path.sep).join('/');
10
+ const git = (root, args) => spawnSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
11
+ export function localBranches(root) {
12
+ const r = git(root, ['for-each-ref', '--format=%(refname:short)', 'refs/heads']);
13
+ return r.status === 0 ? r.stdout.split('\n').filter(Boolean) : [];
14
+ }
15
+
16
+ function artifact(file, text, versions = {}, extra = {}) {
17
+ const parts = file.split('/');
18
+ if ((parts.length !== 5 && parts.length !== 6) || parts.slice(0, 3).join('/') !== REQ_DIR) return null;
19
+ const parsed = parseFile(parts.at(-1));
20
+ if (!parsed) return null;
21
+ const { kind } = parsed;
22
+ const version = parts.at(-1) === `${FILES[kind]}.md` ? versions?.[kind] ?? 1 : parsed.version;
23
+ return { kind, label: LABELS[kind], path: file, requirement: parts[3], project: parts.length === 6 ? parts[4] : null,
24
+ version, branch: parseBranch(text), date: headerDate(text), wiki: parseWiki(text), text,
25
+ knowledge: parseDocnexus(text) ?? [], capture: captureState(text), citesKnowledge: citesKnowledge(text),
26
+ ...(kind === 'review' ? { review: parseReview(text) } : {}), ...(kind === 'delivery' ? { delivery: parseDelivery(text) } : {}), ...extra };
27
+ }
28
+
29
+ export const byArtifactDate = (a, b) => `${a.date ?? ''}#${String(a.version).padStart(3, '0')}`.localeCompare(`${b.date ?? ''}#${String(b.version).padStart(3, '0')}`) || a.mtime - b.mtime;
30
+
31
+ // CLI 原有产物条目格式;统一模型内部另有正文与解析结果,避免改变正常命令的输出。
32
+ export function artifactEntry(a) {
33
+ const common = { kind: a.kind, label: a.label, path: a.path, ...(a.cite ? { cite: a.cite } : {}), requirement: a.requirement, project: a.project,
34
+ ...(a.source === 'worktree' ? { version: a.version } : {}), branch: a.branch, date: `${a.date ?? ''}#${String(a.version).padStart(3, '0')}`, mtime: a.mtime, wiki: a.wiki };
35
+ return a.source === 'git' ? { ...common, ref: a.ref, text: a.text } : common;
36
+ }
37
+
38
+ export function readGitArtifacts(root, ref, diagnostics = []) {
39
+ const head = git(root, ['rev-parse', '--verify', `${ref}^{commit}`]);
40
+ if (head.status !== 0) return [];
41
+ const commit = head.stdout.trim();
42
+ const ls = git(root, ['-c', 'core.quotePath=false', 'ls-tree', '-r', '--name-only', commit, '--', REQ_DIR]);
43
+ if (ls.status !== 0) return [];
44
+ const metas = new Map(); const out = [];
45
+ for (const file of ls.stdout.split('\n').filter(Boolean)) {
46
+ if (!parseFile(path.posix.basename(file))) continue;
47
+ const show = git(root, ['show', `${commit}:${file}`]);
48
+ if (show.status !== 0) continue;
49
+ const metaPath = `${path.posix.dirname(file)}/meta.json`;
50
+ if (!metas.has(metaPath)) {
51
+ const result = git(root, ['show', `${commit}:${metaPath}`]);
52
+ let meta = {};
53
+ if (result.status === 0) {
54
+ try {
55
+ meta = JSON.parse(result.stdout);
56
+ if (!meta || typeof meta !== 'object' || Array.isArray(meta)) throw new Error('元信息必须为对象');
57
+ } catch (e) { diagnostics.push({ code: 'invalid_meta', path: metaPath, ref, message: e.message }); meta = {}; }
58
+ }
59
+ metas.set(metaPath, meta);
60
+ }
61
+ const a = artifact(file, show.stdout, metas.get(metaPath).versions, { mtime: 0, source: 'git', ref, commit });
62
+ if (a) out.push(a);
63
+ }
64
+ return out.sort(byArtifactDate);
65
+ }
66
+
67
+ export function loadWorkspace(root, { allProjects = false, allBranches = false } = {}) {
68
+ root = path.resolve(root);
69
+ const L = layout(root); const diagnostics = [];
70
+ const requirements = readRequirementDirectories(root, L, { allProjects, diagnostics });
71
+ const artifacts = []; const runs = [];
72
+ for (const req of requirements) {
73
+ const found = new Set();
74
+ const index = Array.isArray(req.meta.artifacts) ? req.meta.artifacts.filter((item) => item && typeof item.file === 'string') : [];
75
+ if (req.meta.artifacts !== undefined && (!Array.isArray(req.meta.artifacts) || index.length !== req.meta.artifacts.length)) {
76
+ diagnostics.push({ code: 'invalid_index', path: `${req.dir}/meta.json`, message: '产物索引结构无效,请显式 sync;运行状态保留' });
77
+ }
78
+ for (const entry of fs.readdirSync(req.abs, { withFileTypes: true })) {
79
+ if (!entry.isFile() || !parseFile(entry.name)) continue;
80
+ const abs = path.join(req.abs, entry.name); const file = relative(root, abs);
81
+ try {
82
+ const a = artifact(file, fs.readFileSync(abs, 'utf8'), req.meta.versions, { mtime: fs.statSync(abs).mtimeMs, source: 'worktree', cite: L.ref(abs) });
83
+ found.add(entry.name);
84
+ artifacts.push(a);
85
+ const indexed = index.find((i) => i.file === entry.name);
86
+ if ((Array.isArray(req.meta.artifacts) && !indexed) || (indexed && (indexed.branch !== a.branch || indexed.date !== a.date
87
+ || JSON.stringify(indexed.wiki) !== JSON.stringify(a.wiki) || indexed.version !== a.version
88
+ || JSON.stringify(indexed.docnexus ?? []) !== JSON.stringify(a.knowledge)))) {
89
+ diagnostics.push({ code: 'stale_index', path: file, message: '文档与已登记索引不一致,请显式 sync' });
90
+ }
91
+ } catch (e) {
92
+ diagnostics.push({ code: 'unreadable_artifact', path: file, message: e.message });
93
+ }
94
+ }
95
+ for (const item of index) {
96
+ if (!found.has(item.file)) diagnostics.push({ code: 'missing_artifact', path: `${req.dir}/${item.file}`, message: '索引引用的产物不存在' });
97
+ }
98
+ for (const [id, run] of Object.entries(req.meta.runs ?? {})) {
99
+ if (!run || run.id !== id || typeof run.updated_at !== 'string' || typeof run.branch !== 'string' || !run.steps || typeof run.steps !== 'object' || Array.isArray(run.steps)
100
+ || typeof run.kind !== 'string' || typeof run.stage !== 'string' || !Array.isArray(run.history)
101
+ || Object.values(run.steps).some((step) => !step || !['pending', 'in_progress', 'completed', 'skipped'].includes(step.status))
102
+ || !['running', 'waiting_user', 'blocked', 'completed', 'cancelled'].includes(run.status)) {
103
+ diagnostics.push({ code: 'invalid_run', path: `${req.dir}/meta.json`, message: `运行 ${id} 结构无效,不能恢复` });
104
+ continue;
105
+ }
106
+ runs.push({ ...run, requirement: req.name, dir: req.dir, project: req.project });
107
+ }
108
+ }
109
+ artifacts.sort(byArtifactDate);
110
+ const scanned = allBranches ? localBranches(root) : [];
111
+ const seen = new Set(artifacts.map((a) => a.path));
112
+ for (const ref of scanned) for (const a of readGitArtifacts(root, ref, diagnostics)) {
113
+ if (!seen.has(a.path)) { seen.add(a.path); artifacts.push(a); }
114
+ }
115
+ runs.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
116
+ return { root, layout: L, requirements, artifacts, runs, diagnostics, scanned_branches: scanned };
117
+ }
118
+
119
+ // 展示可看全部项目;原有分支查询和效果报告默认只看当前项目。
120
+ export const projectArtifacts = (workspace, allProjects = false) => workspace.artifacts.filter((a) => allProjects || a.source === 'git' || a.project === workspace.layout.project);
@@ -0,0 +1,43 @@
1
+ # 内部模块边界与统一读取模型
2
+
3
+ 安装接入在 `bin/`;各 Skill 的 SKILL.md 与 references 定义流程步骤、确认点和调用时机;`workflow/lib/` 承担共享规则与数据读写;`workflow/scripts/` 保留既有命令和导出入口。数据库、代码图谱、wiki 的执行与凭据仍在各自能力目录。`shared/` 只分发参考文档,运行共享代码随 workflow 的 lib 一起安装。
4
+
5
+
6
+ dev / project-review 的 SKILL.md 只保留触发边界、门禁、步骤路由、必要命令和结果去向。方法细节按阶段保存在各自 references 中,进入阶段前只读对应小节,避免开工加载全部方法。F1~F8、B1~B5 与审查第 1~9 步编号不变;报告选择、快速通道、完成标记等关键约束在入口可见,具体执行仍由阶段说明和共享状态协议约束。
7
+
8
+ ## 共享核心
9
+
10
+ | 模块 | 职责 |
11
+ |---|---|
12
+ | capabilities | 必装 DocNexus 门槛、wiki / CodeGraph / 数据库的统一接入探测与通道选择 |
13
+ | project | 布局、路径、项目画像、本机环境与配置 |
14
+ | requirement-read | 需求目录和元信息读取、查询与分组 |
15
+ | requirements | 需求绑定、产物版本、归档、索引写入 |
16
+ | runs | 运行阶段、用户选择、revision、完成与取消 |
17
+ | meta-store | 元信息写锁与原子替换;写入前自动创建父目录,再按真实路径归一化加锁;读取不创建目录 |
18
+ | document-model | 标题、章节、表格、元信息等纯文本解析 |
19
+ | artifact-model | 产物种类、文件名、分支、日期、发布与沉淀、审查和交付解析 |
20
+ | workspace-read | 工作区/Git 数据采集,统一内存快照与诊断 |
21
+
22
+ lib 不依赖 scripts;纯模型不依赖 I/O。读侧不调用 sync 或推进运行,写侧在原有锁内重读元信息。布局配置不依赖需求或运行,避免形成循环依赖。`updateMeta` 负责创建写入所需的父目录,调用方负责需求绑定与流程前置校验;存储层建目录不代表需求已绑定。
23
+
24
+ ## 数据归属
25
+
26
+ - `meta.json`:需求名称、绑定分支、项目、已登记版本、runs 和扩展字段。
27
+ - Markdown:正文、文档头、wiki 发布记录、DocNexus 沉淀引用。
28
+ - `artifacts`、`latest`:由显式 sync 重建的索引,不能覆盖 runs;文档头不隐式改变需求绑定。
29
+ - 文件存在不代表运行已完成。只读查询不会创建需求目录或状态文件。
30
+
31
+ `loadWorkspace` 返回 requirements、artifacts、runs、diagnostics、layout 和 scanned_branches。产物条目包含来源、需求/项目、分支、类型、版本、日期、正文及解析结果。相同来源和查询范围内,按日期、版本、修改时间排序。当前项目、全部项目和 Git 分支是显式读取范围:view 使用全部项目,分支查询与效果报告默认当前项目;所有入口都忽略 other、tmp、DocNexus 知识目录。
32
+
33
+ 一次命令内只采集一份快照并传给各查询,正文不再次读取。快照是已读数据的内存表示,不是跨全部文件的事务锁;并发改动在下次采集时可见。不会新增持久索引或缓存文件。运行写入仍用 revision 与元信息锁。
34
+
35
+ `--all-branches` 继续按路径去重、工作区优先;分支中才有的文件从 Git 补入,版本取同一 Git 引用的 meta.json,不混用工作区版本。未迁移旧目录的 Git 产物仍可读取,版本缺失时按原文件名规则回退。
36
+
37
+ 损坏元信息、无效运行、索引不一致和引用文件缺失进入 diagnostics,状态/报告/视图显示诊断。可读正文保留展示,损坏运行不提供恢复;读取不会将坏元信息重建为空记录。`list` 也只读取;用户修复数据或显式 sync 后重新加载即可。
38
+
39
+ ## 能力边界
40
+
41
+ `lib/capabilities.mjs` 集中探测接入,`resolve.mjs status` 输出本轮能力快照(ready / blockers、通道、模板解析结果),`references/capabilities.md` 定义所有 Skill 共用的失败处理。CodeGraph 的库探测通过其 status 命令交给能力适配器,lib 不导入 Skill 执行脚本。新会话或环境变化时刷新,不持久化个人能力状态到需求元信息。
42
+
43
+ DocNexus 是必装组件:安装器缺失即接入,安装失败不报告完成;已有组件默认复用,`--with-docnexus` 强制更新。模板只从知识库读取,包内模板仅用于导入。知识查询失败不作为空结果继续。CodeGraph、wiki、数据库仍按共享规则选择增强通道;必需目标、需求绑定和语句拦截按各自语义处理。
@@ -0,0 +1,39 @@
1
+ # 开工能力与失败处理
2
+
3
+ 所有 Skill 共用本规则。开工在项目根运行一次:
4
+
5
+ ```sh
6
+ node <skills>/workflow/scripts/resolve.mjs status
7
+ ```
8
+
9
+ 保留输出作为本轮能力快照;同一项目的流程跳转、子 agent 直接复用,由主 agent 一并交接。新会话恢复、切换项目、配置或安装发生变化时重新探测。快照只放在会话上下文,不作为团队配置或固定状态写入 meta.json。
10
+
11
+ ## 开工门槛
12
+
13
+ `ready` 表示必装组件 DocNexus 已接入;为 false 时先报告 `blockers`,修复后再开始任务。不跳过知识库,不回退到内置模板。status / profile / report / view 等诊断、只读查看与安装修复仍可执行。
14
+
15
+ 开工时仅一次查看当前会话工具:有 wiki MCP 就将本轮 `wiki.channel` 选为 `mcp`、`wiki.ok` 视为 true,否则沿用输出的 `rest` 或空通道;MCP、子 agent 无法由脚本探测,不把 unknown 解释为不存在。wiki / 数据库的配置检查不代表远端连接、鉴权已通过。
16
+
17
+ ## 本轮能力选择
18
+
19
+ | 能力字段 | 选择 |
20
+ |---|---|
21
+ | `docnexus` | 必需:按 `run` 调用;知识检索无匹配是正常结果,调用失败必须修复后继续 |
22
+ | `codegraph.mode=script` | `cg.mjs impact / entry`,读链路可使用当前会话的 CodeGraph MCP 或已可用的 CLI |
23
+ | `codegraph.mode=cli` | 用 CodeGraph 原生查询命令;不要调用依赖 Node 库的 `cg.mjs impact / entry` |
24
+ | `codegraph.mode=source` | 直接搜索、读代码与调用链;不自动安装或建索引 |
25
+ | `codegraph.scan=true` | `cg.mjs mq / jobs / tables` 源码扫描可用,不要求索引或 Node 库 |
26
+ | `wiki.channel` | 用选定通道读 PRD或发布;无通道时读 PRD 可改用用户提供的内容,发布任务必须先配置 |
27
+ | `database.ok` | 配置存在时可做实际库核对;未配置则读 schema / DDL,注明「未与实际库核对」;用户明确要求查库时必须先配置 |
28
+ | `templates` | 只使用当前 Skill 所需条目的 `path`;缺失按 templates.md 处理,不能用其他模板凑数 |
29
+
30
+ 缺少可选增强时不试跑对应命令、不逐步重复提示安装;在最终产物汇总未覆盖项。没有调用方、没有知识命中不代表代码安全或业务事实不存在。
31
+
32
+ ## 执行中失败
33
+
34
+ - **DocNexus 或所需模板失败**:保留错误与进度,修复后重试;不降级,不把失败当作没有相关知识。已有运行按 runs.md 保存阻塞与原因。
35
+ - **可选增强失败**:保留原因,本轮换到已知可用通道或源码,不反复重试;PRD 拉取按 prd-fetch.md 回退,数据库核对标未验证。明确要求发布或查库时不能降级成已完成。
36
+ - **模板缺失**:`resolve.mjs template <skill> <id>` 输出最新的 path / expected / hint;统一按 templates.md 创建或导入,完成后刷新快照,不在各 Skill 复制分支。
37
+ - **其他错误**:按命令自身语义处理。退出码 2 不是统一的「未接入」:需求未绑定、缺模板、数据库语句拦截等不能当作可选增强跳过。
38
+
39
+ 知识写入、发布、数据库写操作仍遵守各流程的用户授权要求;组件必装不代表自动授权沉淀。
@@ -1,10 +1,10 @@
1
1
  # CodeGraph:用代码图谱读代码
2
2
 
3
- [CodeGraph](https://github.com/colbymchenry/codegraph) 把项目预先解析成本地的代码知识图谱(符号、调用边、继承 / 实现、Spring 等框架的路由),查询一次就拿到相关源码、调用路径和影响面,不用一个个文件 grep、Read。可用性看 `node <skills>/workflow/scripts/resolve.mjs status` 的 `codegraph.ok`(本项目有 `.codegraph/` 索引且 `codegraph` 命令可用)。不可用时照常用 grep / 读文件,不阻塞流程。
3
+ [CodeGraph](https://github.com/colbymchenry/codegraph) 把项目预先解析成本地的代码知识图谱(符号、调用边、继承 / 实现、Spring 等框架的路由),查询一次就拿到相关源码、调用路径和影响面,不用一个个文件 grep、Read。按开工能力快照的 `codegraph.mode` 选择 script / cli / source,统一规则见 `capabilities.md`。
4
4
 
5
5
  ## 优先用 code-graph skill 的脚本
6
6
 
7
- 影响面、入口、MQ、定时任务、表这几类问题,先用 `node <skills>/code-graph/scripts/cg.mjs`(见 `<skills>/code-graph/SKILL.md`):它直接调 CodeGraph 的库,一次给出改动符号 → 受影响的接口 / MQ 消费者 / 定时任务 / 测试 / 表 / 配置 key,并补上 CodeGraph 看不到的 MQ topic、定时任务和 MyBatis XML 里的表。
7
+ script 模式下,影响面、入口、MQ、定时任务、表这几类问题用 `node <skills>/code-graph/scripts/cg.mjs`(见 `<skills>/code-graph/SKILL.md`):它直接调 CodeGraph 的库,一次给出改动符号 → 受影响的接口 / MQ 消费者 / 定时任务 / 测试 / 表 / 配置 key,并补上 CodeGraph 看不到的 MQ topic、定时任务和 MyBatis XML 里的表。
8
8
 
9
9
  | 场景 | 命令 |
10
10
  |---|---|
@@ -13,7 +13,7 @@
13
13
  | 一个方法从哪些入口进来 | `cg.mjs entry <Class.method>` |
14
14
  | topic / 定时任务 / 表 | `cg.mjs mq` / `jobs` / `tables`(只扫源码,没有索引也能用) |
15
15
 
16
- `cg.mjs` 退出码 2(没有索引、找不到库、Node < 22.5)时,改用下面的 CodeGraph 原生工具或 grep。
16
+ cli 模式用下面的原生命令;source 模式直接读代码。执行失败按 capabilities.md 统一处理。
17
17
 
18
18
  ## 读代码:CodeGraph 原生工具
19
19
 
@@ -44,4 +44,4 @@ node <skills>/workflow/scripts/knowledge.mjs <skill> [--match 词1,词2] [--limi
44
44
  - 列表:每个分类下的文档(标题、摘要、`path`、更新时间,按更新时间倒序,不含正文)。`--match` 只按标题 / 摘要 / 标签 / 路径过滤,先用本次的模块名、业务名词,没结果再去掉 `--match` 看最近的几篇。
45
45
  - `--query`:在每个分类内 `recall`,结果放在各分类的 `recall` 字段,能找到标题里没写、正文里提到的内容。问题写成一句话,带上模块名和业务名词,例如「订单列表按创建时间筛选 时区 分页」。
46
46
  - 从列表和 recall 结果里挑与本次任务相关的读 `path`(普通 Markdown),引用时写 `workflow-run/docnexus/library/<path>`。需要跨分类找(例如别的团队的约定)时,仍可按 `docnexus-recall` 做不带分类的 `recall`。
47
- - 退出码 2 表示未接入 DocNexus,跳过即可;只读,不写入。
47
+ - 退出码 2 表示必装组件 DocNexus 不可用;按 capabilities.md 修复后继续,不跳过知识库参考。查询或 recall 失败也不能当作无相关知识;只读,不写入。
@@ -1,13 +1,15 @@
1
1
  # 产物按需求归档(dev / project-review / 技术方案 / 上线方案共用)
2
2
 
3
+ 本约定只适用于工作流的正式需求产物。普通聊天生成的报告、分析、方案、草稿、导出等辅助文件默认放在项目根的 `workflow-run/other/`(可按主题建子目录,需要时创建),即使当前分支已绑定需求,也不调用 `bind / path / sync` 或 `run.mjs`,不写需求 `meta.json`,不进入需求统计和文档视图。单独执行 project-review、技术方案或上线方案等工作流仍按本约定归档。源码、测试、配置和已有文件的维护、工作流指定的临时目录与知识库沿用各自路径;用户明确指定输出路径时以用户为准,不自动搬移已有文件。
4
+
3
5
  一个需求的全部产物放在同一个目录,整条链路一眼看全,`/workflow view` 也直接读这里。本工具的产出都在项目根的 `workflow-run/` 里(需求产物、DocNexus 工作区、项目模板、临时文件),不往项目的其他地方写。默认 `workflow-run/` 随代码提交;团队也可以让 `workflow-run/` 成为独立的文档仓库,见最后一节。两种用法的目录结构完全一样(下图),切换时文件不用挪。
4
6
 
5
7
  ```
6
8
  workflow-run/docs/requirements/<需求名>/<项目名>/
7
- ├── meta.json # 需求名、绑定的分支、各产物(种类、版本、日期、wiki 发布记录、DocNexus 沉淀)
8
- ├── plan.md # 开发方案(dev,L 规模或用户要求时)
9
- ├── delivery.md # 交付说明(dev)
10
- ├── review.md # 审查报告(project-review);复审也写这个文件,上一版归档(见「落盘」)
9
+ ├── meta.json # 运行记录 runs(阶段、步骤完成标记、等待决定、上下文、完成时间)及 需求名、绑定的分支、各产物(种类、版本、日期、wiki 发布记录、DocNexus 沉淀)
10
+ ├── plan.md # 方案(dev,非快速通道都写;S / M 简版;缺陷修复追加修复方案节)
11
+ ├── delivery.md # 交付说明(dev,完整流程审查后用户选择交付才生成;S 快速通道不落盘)
12
+ ├── review.md # 审查报告(独立 project-review 或 dev 完整流程的自查;S 快速通道不落盘);复审也写这个文件,上一版归档(见「落盘」)
11
13
  ├── tech-doc.md # 技术方案(create-tech-md-doc)
12
14
  └── release-doc.md # 上线方案(create-release-md-doc)
13
15
  ```
@@ -16,7 +18,9 @@ workflow-run/docs/requirements/<需求名>/<项目名>/
16
18
 
17
19
  这个目录写在项目根的 `.ignore` 里,不参与代码搜索(找历史决策用 DocNexus)。读写一律按下面脚本给出的路径,不要靠搜索找这些文件,也不要自己拼路径。两种用法下各 skill 的做法完全一样,区别都在脚本里。
18
20
 
19
- ## 需求名:流程开始或第一次落盘时问用户
21
+ ## 需求名:流程开始时问用户
22
+
23
+ dev 先按 Step 0 确定本次工作分支,再查询绑定和确认需求名。之后如果新建或切换分支,重新运行 `which`,不沿用旧分支的绑定结果或自动读到的名字;只有用户明确确认继续同一需求时,才把该名字用于新分支。
20
24
 
21
25
  ```
22
26
  node <skills>/workflow/scripts/requirement.mjs which
@@ -24,19 +28,24 @@ node <skills>/workflow/scripts/requirement.mjs which
24
28
 
25
29
  - `bound: true`:当前分支已绑定需求,直接用,不再问。
26
30
  - 退出码 2 且 `need_project`:独立文档仓库模式还没配项目名,按最后一节处理。
27
- - `bound: false`:**问用户这个需求叫什么**,一句话即可,例如「订单导出支持按渠道筛选」。把 `suggested_name`(由分支名生成)作为默认值给出;`recent` 里有相关的需求时,也让用户可以选择继续那一个(例如修 bug 的新分支属于原来的需求)。然后:
28
- - 用户给了名字或选了已有的需求:`requirement.mjs bind --name "<需求名>"`;
29
- - 用户说不用起名、随便:`requirement.mjs bind`,按分支名生成。
31
+ - `bound: false`:**问用户这个需求叫什么**,一句话即可,例如「订单导出支持按渠道筛选」。把 `suggested_name`(由分支名生成)作为默认值给出;`recent` 里有相关的需求时,也让用户可以选择继续那一个(例如修 bug 的新分支属于原来的需求)。先只确认名字,第一次需要落盘时再 `bind`:
32
+ - 落盘时用户给了名字或选了已有的需求:`requirement.mjs bind --name "<需求名>"`;
33
+ - 用户说不用起名、随便:`requirement.mjs bind`,按分支名生成;
34
+ - 用户给不出名字、也说不清这次要做什么:**不开始流程**(dev 停在 F1 之前)。
30
35
 
31
- 什么时候问:
32
- - dev:确定规模为 M / L、要落盘时(S 规模快速通道不落盘,不问);
33
- - 从 `/workflow` 完整链路开始时;
34
- - 其他 skill:第一次落盘时。
36
+ 什么时候问 / 绑定:
37
+ - dev:Step 0 就问(从 `/workflow` 进来时沿用已确认的名字,不再问);完整流程第一次落盘前 `bind`(S 快速通道只问不 bind);
38
+ - 从 `/workflow` 完整链路开始时:只问名字、不 bind,把名字交给接下来的 skill;
39
+ - 其他 skill:第一次落盘时问并 bind;对话里已经给过名字就直接用,不再问。
35
40
 
36
- 已经问过、绑定过的分支,后面的 skill 直接沿用。
41
+ 已经问过、绑定过的分支,后面的 skill 直接沿用;只问过、还没 bind 的,沿用对话里给出的名字。
37
42
 
38
43
  一个需求可以绑定多个分支(`bind` 在其他分支上用同一个名字即可)。
39
44
 
45
+ ## 运行状态
46
+
47
+ 完整流程在本文件的 `runs` 中按运行 id 保存进度,关联分支;同一需求可有多次运行。`status: completed` 和 `completed_at` 是本次运行已完成的明确标记,步骤也单独记录 completed。保存/恢复和各 skill 的记录时机见 `runs.md`;快速通道不创建记录。`sync` 只重建产物索引,保留 runs 和其他扩展字段;重新 bind 也不会清空历史。元信息采用原子写入与短时写锁,状态更新另有 revision 检查。
48
+
40
49
  ## 落盘
41
50
 
42
51
  ```
@@ -47,6 +56,7 @@ node <skills>/workflow/scripts/requirement.mjs path <plan|delivery|review|tech|r
47
56
  - 退出码 2:当前分支还没绑定需求,按上一节问用户需求名,`bind` 之后再运行一次。
48
57
  - `wiki_record` 非空:上一版已经发布到 wiki。新版的**第一行原样写这条记录**,md-to-wiki 再发布时会更新同一个页面,不会新建页面。
49
58
  - 写完运行 `requirement.mjs sync <path>`,更新 `meta.json`。md-to-wiki 发布后改了第一行的记录,或补写了 DocNexus 元信息行,也要再 sync 一次。
59
+ - 需求绑定由 `bind` 显式维护;`sync` 记录文档所属分支,但不从文档头隐式新增需求绑定。`list` 和状态/视图/报告只读,不自动 sync。文档与索引不一致、元信息损坏或引用文件缺失时会给出数据诊断;可读正文仍可展示,运行状态不根据文件存在推断完成。修复正文后显式 sync,损坏的元信息先人工核对修复。
50
60
  - sync 时新版内容和归档的上一版不同,才算新版本(`meta.json` 的 `versions` 加一);没写就放弃的话版本号不变。输出里 `archived` 非空时,告诉用户归档了哪些文件、在哪里(`workflow-run/tmp/old-artifacts/<需求名>/<项目名>/`,不提交),请用户确认不再需要后自己删除;**不要替用户删**。随代码或文档仓库提交过的旧版也能从 git 历史找回。
51
61
  - sync 输出里 `commit` 非空:按下一节「文档仓库的提交」处理。
52
62
 
@@ -70,6 +80,8 @@ npx @rowansenne/workflow-run docs-repo <团队文档仓库地址> [--project <
70
80
 
71
81
  文档仓库模式下,产物写在另一个 git 仓库里,不会随代码提交。sync 输出里 `commit` 非空时:
72
82
 
83
+ **dev F7 审查例外**:审查环节(独立子 agent 或当前会话自查)拿到 `commit` 信息后不询问、不执行文档提交,先交审查报告、等待用户选择修复或交付。选择交付进入 F8 后(完整流程;S 快速通道不落盘,没有待提交产物),再运行 `requirement.mjs pending` 获取当前待提交内容,按下面步骤询问;修复与复审期间继续延后,不改动 `commit_prompt` 配置。
84
+
73
85
  1. 把 `commit.files`(未提交的产物)和 `commit.suggested_message` 给用户看,问要不要提交。
74
86
  2. 用户确认后,按 `commit.commands` 在文档仓库里提交;推送另外问用户,用户同意后再 `git -C workflow-run push`。
75
87
  3. 用户不想现在提交就跳过,不再追问;之后随时可以运行 `requirement.mjs pending` 看还有哪些没提交。
@@ -0,0 +1,98 @@
1
+ # 完整流程的运行状态
2
+
3
+ 状态和需求绑定、产物索引统一保存在 `workflow-run/docs/requirements/<需求>/<项目>/meta.json` 的 `runs` 对象中,以运行 id 区分。每次运行有自己的分支、kind、规模、阶段、步骤状态、上下文、历史、revision 和完成时间;同一需求跨分支不会共用一个全局阶段。
4
+
5
+ **快速通道不 start、不 save、不 bind**,仍然只写既有运行反馈。`show` 只读,无绑定或旧需求没有 runs 时返回空列表,不创建目录,不根据文档存在推断“已完成”。
6
+
7
+ ## 命令与状态
8
+
9
+ 在业务项目根运行(`<skills>` 是当前安装目录):
10
+
11
+ ```bash
12
+ node <skills>/workflow/scripts/run.mjs show
13
+ node <skills>/workflow/scripts/run.mjs show --id <运行id>
14
+ node <skills>/workflow/scripts/run.mjs start --kind feature --size M --input workflow-run/tmp/checkpoint.json
15
+ node <skills>/workflow/scripts/run.mjs save --id <运行id> --expect-revision <revision> --input workflow-run/tmp/checkpoint.json
16
+ node <skills>/workflow/scripts/run.mjs complete --id <运行id> --expect-revision <revision> --input workflow-run/tmp/checkpoint.json
17
+ node <skills>/workflow/scripts/run.mjs cancel --id <运行id> --expect-revision <revision> --input workflow-run/tmp/checkpoint.json
18
+ ```
19
+
20
+ `--input` 读 JSON,`--input -` 也可读 stdin;不需要新 checkpoint 时可以省略。`--root` 指定业务项目根,`--branch` 默认当前分支;start 的 `--dir <需求目录>` 可明确指定已有产物所属需求,并校验该需求确实绑定此分支。代码开发/审查要先切换并核对工作分支,不能伪造验证结果;wiki 发布可明确使用来源产物的归属分支,不代表该分支代码已验证。输出 JSON,失败退出 1 且不写入半份状态。
21
+
22
+ | 对象 | 状态 |
23
+ |---|---|
24
+ | 运行 | `running` 进行中、`waiting_user` 等待用户、`blocked` 受阻、`completed` **已完成**、`cancelled` 已取消 |
25
+ | 每个步骤 | `pending` 未开始、`in_progress` 进行中、`completed` **已完成**、`skipped` 已跳过(必须说明 reason) |
26
+
27
+ `completed_at` 保存运行/步骤的完成时间。取消不算完成。完成或取消后原记录不可改,之后的新工作创建新运行;重复 start 同一需求、同分支、同 kind 的未结束运行会返回 `resumed: true`,不覆盖上下文。若用户明确开始新任务,应绑定新需求,或先确认取消旧运行,不能把旧运行的进度覆盖为新任务。
28
+
29
+ | kind | 步骤 | 首次创建时机 |
30
+ |---|---|---|
31
+ | `feature` | Step0、F1~F8 | F4 确定不走快速通道并 bind 后 |
32
+ | `bug` | Step0、B1~B5 | B4 确定不走快速通道并 bind 后 |
33
+ | `review` | R1~R7(project-review 第 1~7 步) | 独立审查需要落盘且绑定需求后;dev 内部审查不另开运行 |
34
+ | `tech` | T0~T4(技术方案 Step 0~4) | 确定需求和分支、绑定后 |
35
+ | `release` | L0~L4(上线方案 Step 0~4) | 确定需求和分支、绑定后 |
36
+ | `wiki` | W0~W2(发布 Step 0~2) | 已有需求产物的 wiki 发布;单独发布其他 Markdown 不强制绑定需求 |
37
+
38
+ “已完成”表示**本次运行范围已交付**,不是整个需求已经上线。dev 完成后,用户选择的技术方案、上线方案、wiki 发布是同一个 meta.json 中的新运行,不把已完成的 dev 改回未完成。提交、推送和知识沉淀仍按既有授权规则处理,不因 completed 自动执行;这些可选后续动作不阻止已完成的技能交付标记完成。
39
+
40
+ ## 写 checkpoint
41
+
42
+ 完整 dev 开始记录前已经完成的前置步骤,要把对话中确认的事实补入第一份 checkpoint;没有实际完成的步骤不能补成 completed。例如 F4 开始时:
43
+
44
+ ```json
45
+ {
46
+ "stage": "F4",
47
+ "status": "running",
48
+ "steps": {
49
+ "Step0": { "status": "completed" },
50
+ "F1": { "status": "completed" },
51
+ "F2": { "status": "completed" },
52
+ "F3": { "status": "completed", "note": "理解与验收已确认" }
53
+ },
54
+ "context": {
55
+ "summary": "本次要实现的行为",
56
+ "acceptance": ["可验证的验收项"],
57
+ "decisions": ["用户确认的业务决定"],
58
+ "scope": ["本次范围及排除项"],
59
+ "remaining": ["下一步要做的事"],
60
+ "baseline": "基线命令、结果和已有失败"
61
+ }
62
+ }
63
+ ```
64
+
65
+ 每完成一个步骤、发生阻塞、准备等待用户,或结束本轮会话前,save 一次;`context` 按顶层字段合并,列表字段更新时提供完整列表,不写密码、token。每一步可记录 `evidence`(命令、结果、产物路径、未验证原因)和 `note`;这些是已观察到的结果,不得用计划冒充。
66
+
67
+ 审查报告已交给用户、等待修复或交付时:
68
+
69
+ ```json
70
+ {
71
+ "stage": "F7",
72
+ "status": "waiting_user",
73
+ "waiting_for": "修复报告中的问题或交付",
74
+ "steps": { "F7": { "status": "completed", "evidence": ["review.md 已落盘并自检,报告已交给用户"] } },
75
+ "context": { "remaining": ["等待用户选择修复或交付"] }
76
+ }
77
+ ```
78
+
79
+ 前面的 F4~F6 必须已按实际进度保存,不能为凑齐示例而直接标完成。bug 的 B5 同时包含审查与交付,报告完成等待选择时 B5 仍为 in_progress,最后交付才标完成。
80
+
81
+ - 用户选修复:save `stage: F5`、`status: running`、`decision: {"action":"fix","confirmed":true}`;bug 回 B4。回到前面的阶段会自动重置该阶段及后续的完成标记、清除旧交付选择,旧 checkpoint 保留在 history;修复、验证和复审后再次等待选择。需求变化需回 F3,规模变化同时写 size。
82
+ - 用户明确选交付:save `stage: F8`、`status: running`、`decision: {"action":"deliver","confirmed":true}`;bug 留在 B5。只记录用户实际确认的选择,遗留风险写 context。
83
+ - 等方案确认:`status: waiting_user`,waiting_for 说明待确认方案;方案已写不代表用户已同意。
84
+ - 环境等阻塞:`status: blocked`、reason 说明原因,保留步骤与 remaining;解除后 save running。
85
+ - 取消:用 cancel,input 提供 reason,不能用 complete 把取消记成完成。
86
+
87
+ 交付产物、自检、摘要和反馈已处理后,把最后步骤标 completed,再运行 complete。complete 要求所有步骤 completed 或有原因的 skipped;完整 dev 的方案、实现、验证、审查、交付核心步骤不能跳过,用户必须已选择交付,并有当前分支的 plan.md、review.md、delivery.md。其他文档/审查运行也检查当前分支的对应文件。验证步骤完成可以包含如实记录的“未验证”,不等于所有测试都通过。
88
+
89
+ complete 不接受待用户确认或 blocked 的运行。还在处理本次已承诺的交付事项时不能提前完成;可选下一步不属于本次运行范围。状态写入具有 revision 检查和短时元信息写锁;版本冲突时重新 show,核对并合并进度,不盲目重试覆盖。写入用 workflow-run/tmp/locks/ 下的短时锁目录(不落在需求目录、不会随产物提交),目录名为元信息相对路径的 SHA-256,避免长中文路径超过文件名限制。每次写入持有唯一的 PID/UUID 标记;只回收超过 30 秒且进程已退出的旧标记,非空锁目录不能删除,释放时也只删除自己的标记。mkdir 后尚未写入标记就崩溃的空目录,过期后同样自动恢复。
90
+
91
+ ## 新会话恢复
92
+
93
+ 1. 先确认业务项目根和当前工作分支,再 show;多个运行时按用户的任务选 id,不能把其他分支的状态直接套过来。
94
+ 2. 读取 context、各步骤状态、waiting_for、reason、history 和关联文档,核对 git 改动、已有产物与证据是否仍适用。记录不是当前代码仍然正确的保证。
95
+ 3. 从保存的阶段继续,已完成的有效步骤不重复执行。waiting_user 时先取得对应答复;不能将“继续”直接当成“交付”或接受风险。代码已变化而验证/报告失效时,回退到实现或验证阶段并 save,再验证与复审。
96
+ 4. completed 运行只展示已完成及时间,不自动重启;需要新的工作时 start 新记录。旧需求没有 runs 时明确说明暂无可恢复状态,按文档和代码重建,经核对后再记录,不自动假定已完成。
97
+
98
+ `artifacts.mjs` 的 execution 和 `/workflow status` 使用同一份状态;有未结束运行时 next 优先显示等待、阻塞或当前阶段,不越过修复/交付选择推荐后续动作。`/workflow view` 即使尚无 Markdown 也展示已有运行,包含“已完成”标识和完成时间。
@@ -1,6 +1,6 @@
1
1
  # `/workflow status` 输出
2
2
 
3
- 先运行 `node <skills>/workflow/scripts/resolve.mjs status` 和 `node <skills>/workflow/scripts/artifacts.mjs`(当前分支的产物进度),前者按上表判定画像、wiki 配置、DocNexus(含缺失模板)、代码图谱、数据库连接(只给连接名、driver、是否只读,不含凭据),再据此填写下表。wiki MCP 脚本探测不到,需你自己看当前环境有没有 wiki 相关工具。
3
+ 复用开工时 `resolve.mjs status` 的输出,并运行 `node <skills>/workflow/scripts/artifacts.mjs`(当前分支的产物进度),`artifacts.mjs` 的 `execution` 给出当前运行、历史、阶段、已完成步骤、waiting_for、reason 和完成时间;没有运行时说明暂无可恢复状态,不根据文档齐全推断已完成。前者按 capabilities.md 读取本轮能力快照,展示 ready / blockers,再展示画像、wiki 配置、DocNexus(含缺失模板)、代码图谱、数据库连接(只给连接名、driver、是否只读,不含凭据),再据此填写下表。wiki MCP 脚本探测不到,需你自己看当前环境有没有 wiki 相关工具。
4
4
 
5
5
  ```
6
6
  # Workflow 状态
@@ -9,14 +9,17 @@
9
9
  |---|---|---|
10
10
  | 项目画像 | ✅ / ⚠️ 部分字段为空 | <空字段>;运行 /workflow profile |
11
11
  | wiki | ✅ / ❌ | 填写 md-to-wiki/wiki-config.json 或接入 wiki MCP |
12
- | DocNexus | ✅ / ❌ | 重跑 npx @rowansenne/workflow-run 并加 --with-docnexus;已接入但缺模板时列出缺的模板并提示 `npx @rowansenne/workflow-run seed-templates`;未接入时说明默认模板只读、不能定制,技术方案 / 上线方案要先接入(`templates_need_docnexus`);`stale_project_templates` 非空时提示这些项目模板已不再生效 |
12
+ | DocNexus(必装) | ✅ / ❌ 阻塞 | 缺失运行 npx @rowansenne/workflow-run 修复;缺模板按 templates.md;无默认模板降级 |
13
13
  | 数据库 | ✅ / ❌ | 重跑 `npx @rowansenne/workflow-run` 补配 |
14
14
  | 代码图谱 | ✅ / ⚠️ 已装未建索引 / ❌ | 重跑 npx @rowansenne/workflow-run 并加 --with-codegraph;已装 codegraph 但没有 .codegraph/ 时提示用户自己运行 codegraph init |
15
15
 
16
16
  ## 当前分支 <分支>
17
+ 运行:<id / 暂无记录> | 状态:<进行中 / 等待用户 / 受阻 / 已完成 / 已取消> | 当前阶段:<stage>
18
+ 已完成步骤:<steps 中 completed 的名称> | 完成时间:<completed_at / —>
19
+ 等待 / 阻塞:<waiting_for / reason / 无>
17
20
  | 环节 | 产物 | wiki |
18
21
  |---|---|---|
19
- | 开发方案 / 交付说明 / 审查报告 / 技术方案 / 上线方案 | <path 或「—」> | <pageId vN 或「未发布」> |
22
+ | 方案 / 交付说明 / 审查报告 / 技术方案 / 上线方案 | <path 或「—」> | <pageId vN 或「未发布」> |
20
23
 
21
24
  下一步:<artifacts.mjs 的 next>
22
25