@chenguangyao/devflow-kit 0.1.43

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 (198) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/LICENSE +21 -0
  3. package/README.md +539 -0
  4. package/bin/devflow.js +9 -0
  5. package/docs/RFC-001-devflow-kit.md +617 -0
  6. package/docs/RFC-002-workflow-kernel.md +134 -0
  7. package/docs/enterprise-integration-supplement.md +274 -0
  8. package/docs/internal-gitlab-setup.md +426 -0
  9. package/docs/marketplace-skills.md +231 -0
  10. package/docs/migration-from-arb.md +232 -0
  11. package/docs/tooling-overview.md +774 -0
  12. package/docs/workflow-orchestration.md +695 -0
  13. package/docs/workflow-ui-prototype.html +271 -0
  14. package/package.json +52 -0
  15. package/schemas/config.schema.json +51 -0
  16. package/schemas/delta.schema.json +22 -0
  17. package/schemas/state.schema.json +130 -0
  18. package/schemas/status-surface.schema.json +197 -0
  19. package/schemas/workflow-confirmation-surface.schema.json +70 -0
  20. package/schemas/workflow-picker.schema.json +94 -0
  21. package/scripts/postinstall.js +101 -0
  22. package/scripts/render-workflow-ui-prototype.js +271 -0
  23. package/skills/apply/SKILL.md +313 -0
  24. package/skills/apply/references/discipline-checklist.md +145 -0
  25. package/skills/apply/references/subagent-implementer-prompt.md +113 -0
  26. package/skills/apply/references/subagent-orchestration.md +150 -0
  27. package/skills/apply/references/subagent-reviewer-prompt.md +180 -0
  28. package/skills/apply/references/tdd-loop.md +287 -0
  29. package/skills/apply/references/when-plan-is-wrong.md +279 -0
  30. package/skills/apply/references/worktree-swarm.md +292 -0
  31. package/skills/archive/SKILL.md +229 -0
  32. package/skills/archive/references/conflict-resolution.md +336 -0
  33. package/skills/archive/references/knowledge-deposit.md +381 -0
  34. package/skills/archive/references/spec-merge.md +365 -0
  35. package/skills/brainstorm/SKILL.md +123 -0
  36. package/skills/brainstorm/references/proposal-template.md +244 -0
  37. package/skills/brainstorm/references/question-catalog.md +168 -0
  38. package/skills/brainstorm/references/session-template.md +184 -0
  39. package/skills/ci-fix/SKILL.md +63 -0
  40. package/skills/ci-fix/references/loop.md +25 -0
  41. package/skills/code-review/SKILL.md +279 -0
  42. package/skills/code-review/references/escalation-playbook.md +192 -0
  43. package/skills/code-review/references/language-cheatsheets/go.md +175 -0
  44. package/skills/code-review/references/language-cheatsheets/java-spring-mybatis.md +246 -0
  45. package/skills/code-review/references/language-cheatsheets/python.md +170 -0
  46. package/skills/code-review/references/language-cheatsheets/vue.md +199 -0
  47. package/skills/code-review/references/output-template.md +275 -0
  48. package/skills/code-review/references/review-checklist.md +251 -0
  49. package/skills/complexity-grading/SKILL.md +259 -0
  50. package/skills/deliver/SKILL.md +271 -0
  51. package/skills/deliver/references/delivery-modes.md +299 -0
  52. package/skills/deliver/references/notify.md +359 -0
  53. package/skills/deliver/references/pr-description.md +319 -0
  54. package/skills/dependency-upgrade/SKILL.md +57 -0
  55. package/skills/dependency-upgrade/references/risk-matrix.md +38 -0
  56. package/skills/df-orchestrator/SKILL.md +407 -0
  57. package/skills/df-orchestrator/references/complexity-grading.md +177 -0
  58. package/skills/df-orchestrator/references/escalation-matrix.md +191 -0
  59. package/skills/df-orchestrator/references/routing-rules.md +290 -0
  60. package/skills/df-orchestrator/references/workflow-state-machine.md +208 -0
  61. package/skills/frontend-quality/SKILL.md +61 -0
  62. package/skills/frontend-quality/references/checklist.md +35 -0
  63. package/skills/handoff-resume/SKILL.md +59 -0
  64. package/skills/handoff-resume/references/handoff-template.md +54 -0
  65. package/skills/plan/SKILL.md +166 -0
  66. package/skills/plan/references/task-breakdown.md +207 -0
  67. package/skills/plan/references/task-sequencing.md +143 -0
  68. package/skills/plan/references/task-template.md +248 -0
  69. package/skills/requirement-analysis/SKILL.md +499 -0
  70. package/skills/requirement-analysis/references/acceptance-criteria.md +183 -0
  71. package/skills/requirement-analysis/references/code-recon.md +151 -0
  72. package/skills/requirement-analysis/references/edge-case-catalog.md +164 -0
  73. package/skills/requirement-analysis/references/requirement-template.md +339 -0
  74. package/skills/requirement-analysis/references/scope-negotiation.md +162 -0
  75. package/skills/security-hardening/SKILL.md +60 -0
  76. package/skills/security-hardening/references/checklist.md +42 -0
  77. package/skills/tech-spec/SKILL.md +388 -0
  78. package/skills/tech-spec/references/api-contract-design.md +172 -0
  79. package/skills/tech-spec/references/decision-records.md +110 -0
  80. package/skills/tech-spec/references/design-template.md +301 -0
  81. package/skills/tech-spec/references/rollout-and-rollback.md +203 -0
  82. package/skills/tech-spec/references/spec-delta-conventions.md +250 -0
  83. package/skills/tech-spec/references/transaction-patterns.md +212 -0
  84. package/skills/test-spec/SKILL.md +219 -0
  85. package/skills/test-spec/references/coverage-strategy.md +218 -0
  86. package/skills/test-spec/references/edge-case-to-test.md +143 -0
  87. package/skills/test-spec/references/test-case-template.md +276 -0
  88. package/skills/verify/SKILL.md +232 -0
  89. package/skills/verify/references/nfr-verification.md +292 -0
  90. package/skills/verify/references/report-templates.md +510 -0
  91. package/skills/verify/references/self-test-guide.md +240 -0
  92. package/skills/verify/references/verify-rollback-map.md +247 -0
  93. package/src/cli/commands/_helpers.js +108 -0
  94. package/src/cli/commands/_submit.js +718 -0
  95. package/src/cli/commands/apply.js +198 -0
  96. package/src/cli/commands/archive.js +180 -0
  97. package/src/cli/commands/checkpoint.js +113 -0
  98. package/src/cli/commands/deliver.js +377 -0
  99. package/src/cli/commands/deploy.js +504 -0
  100. package/src/cli/commands/design.js +158 -0
  101. package/src/cli/commands/disable.js +21 -0
  102. package/src/cli/commands/doctor.js +178 -0
  103. package/src/cli/commands/enable.js +21 -0
  104. package/src/cli/commands/flow.js +645 -0
  105. package/src/cli/commands/help.js +93 -0
  106. package/src/cli/commands/ingest.js +602 -0
  107. package/src/cli/commands/init.js +341 -0
  108. package/src/cli/commands/knowledge.js +523 -0
  109. package/src/cli/commands/logs.js +43 -0
  110. package/src/cli/commands/new.js +202 -0
  111. package/src/cli/commands/plan.js +49 -0
  112. package/src/cli/commands/propose.js +27 -0
  113. package/src/cli/commands/provider.js +698 -0
  114. package/src/cli/commands/report.js +143 -0
  115. package/src/cli/commands/requirement.js +227 -0
  116. package/src/cli/commands/review.js +301 -0
  117. package/src/cli/commands/skills.js +457 -0
  118. package/src/cli/commands/status.js +925 -0
  119. package/src/cli/commands/switch.js +27 -0
  120. package/src/cli/commands/sync.js +47 -0
  121. package/src/cli/commands/test.js +366 -0
  122. package/src/cli/commands/uninstall.js +32 -0
  123. package/src/cli/commands/update.js +74 -0
  124. package/src/cli/commands/verify.js +354 -0
  125. package/src/cli/commands/worktree.js +78 -0
  126. package/src/cli/index.js +72 -0
  127. package/src/cli/parse-args.js +102 -0
  128. package/src/core/autodetect.js +271 -0
  129. package/src/core/change.js +208 -0
  130. package/src/core/checkpoint.js +217 -0
  131. package/src/core/config.js +60 -0
  132. package/src/core/delta.js +290 -0
  133. package/src/core/markers.js +59 -0
  134. package/src/core/paths.js +173 -0
  135. package/src/core/plan-tasks.js +36 -0
  136. package/src/core/project-routing.js +285 -0
  137. package/src/core/projects.js +200 -0
  138. package/src/core/state.js +200 -0
  139. package/src/core/workflow-check.js +177 -0
  140. package/src/core/workflow-init.js +34 -0
  141. package/src/core/workflow-picker.js +154 -0
  142. package/src/core/workflow-policy.js +119 -0
  143. package/src/core/workflow-suggest.js +181 -0
  144. package/src/core/workflow-verify.js +88 -0
  145. package/src/core/workflow.js +433 -0
  146. package/src/core/worktree.js +241 -0
  147. package/src/knowledge/categories.js +107 -0
  148. package/src/knowledge/classify.js +125 -0
  149. package/src/knowledge/deposit.js +414 -0
  150. package/src/knowledge/migrate.js +149 -0
  151. package/src/knowledge/mr.js +219 -0
  152. package/src/knowledge/query.js +131 -0
  153. package/src/knowledge/registry.js +151 -0
  154. package/src/knowledge/sync.js +179 -0
  155. package/src/providers/base.js +74 -0
  156. package/src/providers/drivers/api-yapi.js +78 -0
  157. package/src/providers/drivers/ci-jenkins.js +109 -0
  158. package/src/providers/drivers/intake-confluence.js +544 -0
  159. package/src/providers/drivers/kb-git.js +549 -0
  160. package/src/providers/drivers/kb-weknora.js +472 -0
  161. package/src/providers/drivers/notify-smtp.js +515 -0
  162. package/src/providers/drivers/observability-oss.js +43 -0
  163. package/src/providers/drivers/observability-sls.js +50 -0
  164. package/src/providers/lifecycle.js +135 -0
  165. package/src/providers/loader.js +132 -0
  166. package/src/providers/local.js +190 -0
  167. package/src/providers/userconfig.js +283 -0
  168. package/src/reports/aggregate.js +185 -0
  169. package/src/reports/coverage.js +163 -0
  170. package/src/reports/detect.js +143 -0
  171. package/src/reports/parse.js +236 -0
  172. package/src/templates/files/ci/github.yml +38 -0
  173. package/src/templates/files/ci/gitlab.yml +27 -0
  174. package/src/templates/files/design.md +63 -0
  175. package/src/templates/files/ide/devflow-workflow.md +58 -0
  176. package/src/templates/files/ide/project-overview-reference.md +1 -0
  177. package/src/templates/files/ide/project-overview.md +27 -0
  178. package/src/templates/files/knowledge-index.json +17 -0
  179. package/src/templates/files/knowledge.md +28 -0
  180. package/src/templates/files/meta.json +8 -0
  181. package/src/templates/files/plan.md +38 -0
  182. package/src/templates/files/proposal.md +33 -0
  183. package/src/templates/files/reports/contract-test.md +40 -0
  184. package/src/templates/files/reports/e2e-test.md +30 -0
  185. package/src/templates/files/reports/integration-test.md +36 -0
  186. package/src/templates/files/reports/joint-test.md +58 -0
  187. package/src/templates/files/reports/perf.md +24 -0
  188. package/src/templates/files/reports/regression.md +20 -0
  189. package/src/templates/files/reports/remote-test.md +55 -0
  190. package/src/templates/files/reports/self-test.md +43 -0
  191. package/src/templates/files/reports/smoke-test.md +22 -0
  192. package/src/templates/files/reports/unit-test.md +36 -0
  193. package/src/templates/files/requirement.md +51 -0
  194. package/src/templates/files/review.md +38 -0
  195. package/src/templates/files/tests.md +36 -0
  196. package/src/templates/files/verify.md +32 -0
  197. package/src/templates/index.js +21 -0
  198. package/src/utils/log.js +37 -0
@@ -0,0 +1,645 @@
1
+ 'use strict';
2
+
3
+ const log = require('../../utils/log.js');
4
+ const config = require('../../core/config.js');
5
+ const state = require('../../core/state.js');
6
+ const checkpoint = require('../../core/checkpoint.js');
7
+ const workflow = require('../../core/workflow.js');
8
+ const workflowPolicy = require('../../core/workflow-policy.js');
9
+ const workflowSuggest = require('../../core/workflow-suggest.js');
10
+ const workflowVerify = require('../../core/workflow-verify.js');
11
+ const workflowCheck = require('../../core/workflow-check.js');
12
+ const workflowInit = require('../../core/workflow-init.js');
13
+ const workflowPicker = require('../../core/workflow-picker.js');
14
+ const helpers = require('./_helpers.js');
15
+
16
+ async function run({ sub = null, positional = [], flags = {}, cwd }) {
17
+ const root = cwd || process.cwd();
18
+ const action = sub || positional[0] || 'current';
19
+ const args = sub ? positional : positional.slice(1);
20
+ const cfg = (await config.read(root)) || {};
21
+ if (action === 'validate') {
22
+ try { return await validate(root, cfg); }
23
+ catch (e) { log.error(e.message); process.exitCode = 2; return; }
24
+ }
25
+ if (action === 'preview') {
26
+ try { return await preview(root, cfg, args[0] || flags.recipe); }
27
+ catch (e) { log.error(e.message); process.exitCode = 2; return; }
28
+ }
29
+ const slug = await helpers.resolveSlug(root, flags, args);
30
+ if (!slug) { process.exitCode = 1; return; }
31
+ const st = await helpers.loadStateOrFail(root, slug);
32
+ if (!st) { process.exitCode = 1; return; }
33
+
34
+ try {
35
+ if (action === 'recommend') return await recommend(root, slug, st, cfg, flags);
36
+ if (action === 'draft') return await draft(root, slug, st, cfg, flags);
37
+ if (action === 'add-step') return await addStep(root, slug, st, args[0], flags);
38
+ if (action === 'disable-step') return await disableStep(root, slug, st, args[0], flags);
39
+ if (action === 'move-step') return await moveStep(root, slug, st, args[0], flags);
40
+ if (action === 'set-verify') return await setVerify(root, slug, st, flags);
41
+ if (action === 'suggest') return await suggest(root, slug, st, flags);
42
+ if (action === 'diff') return diff(st, flags);
43
+ if (action === 'picker') return picker(root, slug, st, flags);
44
+ if (action === 'apply-selection') return await applySelection(root, slug, st, flags);
45
+ if (action === 'card') return card(root, slug, st, flags);
46
+ if (action === 'check') return check(root, slug, st, flags);
47
+ if (action === 'confirm') return await confirm(root, slug, st);
48
+ if (action === 'current') return current(st);
49
+ if (action === 'next') return next(st);
50
+ if (action === 'explain') return explain(st, flags);
51
+ if (action === 'use') return await draft(root, slug, st, cfg, { ...flags, recipe: args[0] || flags.recipe, confirm: true });
52
+ usage();
53
+ process.exitCode = 2;
54
+ } catch (e) {
55
+ log.error(e.message);
56
+ process.exitCode = e.code === 'WORKFLOW_POLICY' ? 1 : 2;
57
+ }
58
+ }
59
+
60
+ async function suggest(root, slug, st, flags) {
61
+ const intent = flags.intent || flags.i || flags.text || '';
62
+ if (intent === true) throw new Error('usage: devflow flow suggest --intent="..." --slug=<slug>');
63
+ const suggestions = workflowSuggest.suggestWorkflowOverrides(intent, st.workflow, {
64
+ level: st.level,
65
+ riskSignals: st.riskSignals,
66
+ });
67
+ const applyDraft = flags['apply-draft'] === true || flags.applyDraft === true;
68
+ const shouldConfirm = flags.confirm === true;
69
+ log.raw('suggested overrides');
70
+ if (!suggestions.length) {
71
+ log.raw(' - no workflow override suggested');
72
+ log.dim('state unchanged.');
73
+ return;
74
+ }
75
+ let overrideCount = 0;
76
+ for (const item of suggestions) {
77
+ if (item.type === 'add-step') {
78
+ overrideCount += 1;
79
+ log.raw(` - add-step ${item.step}: ${item.reason}`);
80
+ log.raw(` command: devflow flow add-step ${item.skill} --slug=${slug}${item.after ? ` --after=${item.after}` : ''}`);
81
+ } else if (item.type === 'set-verify') {
82
+ overrideCount += 1;
83
+ log.raw(` - set-verify: ${item.reason}`);
84
+ log.raw(` command: devflow flow set-verify --slug=${slug} --reports=${item.reportArgs.join(',')}`);
85
+ } else if (item.type === 'recipe') {
86
+ log.raw(` - recipe ${item.recipe}: ${item.reason}`);
87
+ log.raw(` command: devflow flow draft --slug=${slug} --recipe=${item.recipe}`);
88
+ }
89
+ }
90
+ if (applyDraft) {
91
+ const recipeCount = suggestions.filter((item) => item.type === 'recipe').length;
92
+ if (shouldConfirm && recipeCount) {
93
+ log.error('cannot confirm suggested workflow with unapplied recipe suggestions.');
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+ const applied = await applySuggestedDraftOverrides(root, slug, st, suggestions);
98
+ if (applied.length) {
99
+ state.logEvent(st, 'workflow.suggest.apply_draft', { applied: applied.map((item) => item.type) });
100
+ await state.write(root, slug, st);
101
+ log.ok(`applied safe overrides to workflow draft: ${applied.map((item) => item.label).join(', ')}`);
102
+ }
103
+ if (recipeCount) {
104
+ log.dim('recipe suggestions were not applied; run the draft command explicitly to replace the workflow draft.');
105
+ }
106
+ if (!applied.length && !recipeCount) log.dim('no safe draft override to apply.');
107
+ if (shouldConfirm) {
108
+ if (!applied.length) {
109
+ log.error('cannot confirm suggested workflow: no safe draft overrides were applied.');
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ const confirmed = await confirm(root, slug, st);
114
+ if (!confirmed) return;
115
+ st.workflow.audit.push({ ts: new Date().toISOString(), event: 'workflow.suggest.confirm', applied: applied.map((item) => item.type) });
116
+ state.logEvent(st, 'workflow.suggest.confirm', { applied: applied.map((item) => item.type), currentStep: st.workflow.currentStep });
117
+ await state.write(root, slug, st);
118
+ log.ok(`workflow confirmed from suggested draft: currentStep=${st.workflow.currentStep || '-'}`);
119
+ }
120
+ return;
121
+ }
122
+ if (overrideCount) {
123
+ log.dim('apply these commands to update the draft; suggest does not mutate state.');
124
+ } else {
125
+ log.dim('recipe suggestions replace the draft only if you run the command explicitly.');
126
+ }
127
+ }
128
+
129
+ async function applySuggestedDraftOverrides(root, slug, st, suggestions) {
130
+ const applied = [];
131
+ for (const item of suggestions) {
132
+ if (item.type === 'add-step') {
133
+ if (!workflow.installedSkillExists(root, item.skill)) throw new Error(`skill is not installed in this project: ${item.skill}`);
134
+ if (await enforcePolicyOrCheckpoint(root, slug, st, workflowPolicy.evaluateWorkflowChange(st, {
135
+ action: 'add-step',
136
+ stepId: item.step,
137
+ skill: item.skill,
138
+ }))) continue;
139
+ const step = workflow.addStep(st.workflow, { skill: item.skill, id: item.step, after: item.after });
140
+ state.logEvent(st, 'workflow.step.add', { step: step.id, skill: item.skill, after: item.after || null, before: null, source: 'suggest' });
141
+ applied.push({ type: 'add-step', label: step.id });
142
+ } else if (item.type === 'set-verify') {
143
+ if (await enforcePolicyOrCheckpoint(root, slug, st, workflowPolicy.evaluateWorkflowChange(st, {
144
+ action: 'set-verify',
145
+ requiredReports: item.reports,
146
+ }))) continue;
147
+ workflow.setVerifyReports(st.workflow, { requiredReports: item.reports, reason: item.reason });
148
+ state.logEvent(st, 'workflow.verify.set_reports', { requiredReports: item.reports, reason: item.reason, source: 'suggest' });
149
+ applied.push({ type: 'set-verify', label: 'verify reports' });
150
+ }
151
+ }
152
+ return applied;
153
+ }
154
+
155
+ async function recommend(root, slug, st, cfg) {
156
+ const rec = workflow.recommendRecipe(st, cfg);
157
+ const recipe = workflow.expandRecipe(rec.id, cfg);
158
+ log.raw(`推荐流程: ${recipe.label} (${recipe.id})`);
159
+ log.raw(`原因: ${rec.reason}`);
160
+ log.raw('步骤:');
161
+ renderSteps(recipe.steps);
162
+ log.dim(`next: devflow flow draft --slug=${slug} --recipe=${recipe.id}`);
163
+ }
164
+
165
+ async function validate(root, cfg) {
166
+ const ids = new Set(Object.keys(workflow.BUILTIN_RECIPES));
167
+ for (const id of Object.keys((cfg.workflows && cfg.workflows.recipes) || {})) ids.add(id);
168
+ for (const id of ids) {
169
+ const recipe = workflow.expandRecipe(id, cfg);
170
+ validateInstalledSkills(root, recipe.steps);
171
+ }
172
+ log.ok(`workflow recipes valid: ${ids.size}`);
173
+ }
174
+
175
+ async function preview(root, cfg, id) {
176
+ if (!id) throw new Error('usage: devflow flow preview <recipe>');
177
+ const recipe = workflow.expandRecipe(id, cfg);
178
+ validateInstalledSkills(root, recipe.steps);
179
+ log.raw(`${recipe.label} (${recipe.id}) source=${recipe.source}`);
180
+ renderSteps(recipe.steps);
181
+ }
182
+
183
+ async function draft(root, slug, st, cfg, flags) {
184
+ const recommendation = flags.recipe
185
+ ? { id: flags.recipe, reason: flags.reason || 'selected by user' }
186
+ : workflow.recommendRecipe(st, cfg);
187
+ const { recipe } = workflowInit.draftRecommendedWorkflow(root, st, cfg, {
188
+ recipe: recommendation.id,
189
+ reason: recommendation.reason,
190
+ force: true,
191
+ });
192
+ if (flags.confirm) {
193
+ workflow.confirmWorkflow(st.workflow);
194
+ state.logEvent(st, 'workflow.confirm', { recipe: recipe.id, currentStep: st.workflow.currentStep });
195
+ }
196
+ await state.write(root, slug, st);
197
+ log.ok(`${flags.confirm ? 'workflow confirmed' : 'workflow draft'}: ${recipe.label} (${recipe.id})`);
198
+ renderSteps(st.workflow.steps);
199
+ }
200
+
201
+ async function addStep(root, slug, st, skill, flags) {
202
+ if (!skill) throw new Error('usage: devflow flow add-step <skill> --after=<step>');
203
+ if (!workflow.installedSkillExists(root, skill)) throw new Error(`skill is not installed in this project: ${skill}`);
204
+ if (await enforcePolicyOrCheckpoint(root, slug, st, workflowPolicy.evaluateWorkflowChange(st, {
205
+ action: 'add-step',
206
+ stepId: flags.id || skill,
207
+ skill,
208
+ }))) return;
209
+ const step = workflow.addStep(st.workflow, { skill, id: flags.id, after: flags.after, before: flags.before });
210
+ state.logEvent(st, 'workflow.step.add', { step: step.id, skill, after: flags.after || null, before: flags.before || null });
211
+ await state.write(root, slug, st);
212
+ log.ok(`workflow step added: ${step.id}`);
213
+ }
214
+
215
+ async function disableStep(root, slug, st, stepId, flags) {
216
+ if (!stepId) throw new Error('usage: devflow flow disable-step <step> --reason="..."');
217
+ const policyResult = workflowPolicy.evaluateWorkflowChange(st, { action: 'disable-step', stepId });
218
+ if (await enforcePolicyOrCheckpoint(root, slug, st, policyResult)) return;
219
+ const allowRequired = policyResult.decision === 'checkpoint-required'
220
+ && !!findWorkflowPolicyCheckpoint(st, policyResult.summary, 'resolved', 'accept-risk');
221
+ try {
222
+ const step = workflow.disableStep(st.workflow, { stepId, reason: flags.reason, allowRequired });
223
+ state.logEvent(st, 'workflow.step.disable', { step: step.id, reason: flags.reason });
224
+ await state.write(root, slug, st);
225
+ log.ok(`workflow step disabled: ${step.id}`);
226
+ } catch (e) {
227
+ if (e.code !== 'WORKFLOW_POLICY') throw e;
228
+ const cp = checkpoint.addCheckpoint(st, {
229
+ type: 'workflow_policy',
230
+ phase: 'workflow',
231
+ summary: `禁用 required workflow step: ${stepId}`,
232
+ question: '这个调整会弱化流程质量闸,是否接受风险?',
233
+ options: [
234
+ { id: 'keep', label: '保留当前流程', command: `devflow status --slug=${slug}` },
235
+ { id: 'accept-risk', label: '接受风险', command: `devflow checkpoint resolve --id=<checkpoint-id> --decision=accept-risk` },
236
+ ],
237
+ nextAction: `devflow status --slug=${slug}`,
238
+ risks: ['禁用 required step 可能绕过 review/verify/deliver 或必要前置产物。'],
239
+ evidence: ['state.json#workflow'],
240
+ });
241
+ cp.options = cp.options.map((opt) => opt.id === 'accept-risk'
242
+ ? { ...opt, command: `devflow checkpoint resolve --id=${cp.id} --decision=accept-risk` }
243
+ : opt);
244
+ state.logEvent(st, 'checkpoint.add', { id: cp.id, type: cp.type, phase: cp.phase, step: stepId });
245
+ await state.write(root, slug, st);
246
+ log.error(e.message);
247
+ log.raw('');
248
+ log.raw(checkpoint.renderNextStepCard(cp));
249
+ process.exitCode = 1;
250
+ }
251
+ }
252
+
253
+ async function moveStep(root, slug, st, stepId, flags) {
254
+ if (!stepId) throw new Error('usage: devflow flow move-step <step> --after=<step>');
255
+ const policyResult = workflowPolicy.evaluateWorkflowChange(st, { action: 'move-step', stepId, after: flags.after, before: flags.before });
256
+ if (policyResult.decision === 'rejected' && ['review', 'verify', 'deliver'].includes(stepId)) {
257
+ throw Object.assign(new Error(policyResult.message), { code: 'WORKFLOW_POLICY' });
258
+ }
259
+ const step = workflow.moveStep(st.workflow, { stepId, after: flags.after, before: flags.before });
260
+ state.logEvent(st, 'workflow.step.move', { step: step.id, after: flags.after || null, before: flags.before || null });
261
+ await state.write(root, slug, st);
262
+ log.ok(`workflow step moved: ${step.id}`);
263
+ }
264
+
265
+ async function setVerify(root, slug, st, flags) {
266
+ const raw = flags.reports || flags.report || flags.requiredReports || flags['required-reports'];
267
+ if (!raw || raw === true) throw new Error('usage: devflow flow set-verify --reports=unit,smoke --slug=<slug>');
268
+ const requiredReports = workflowVerify.normalizeReportList(raw);
269
+ const reason = flags.reason && flags.reason !== true ? flags.reason : '';
270
+ if (await enforcePolicyOrCheckpoint(root, slug, st, workflowPolicy.evaluateWorkflowChange(st, {
271
+ action: 'set-verify',
272
+ requiredReports,
273
+ }))) return;
274
+ workflow.setVerifyReports(st.workflow, { requiredReports, reason });
275
+ state.logEvent(st, 'workflow.verify.set_reports', { requiredReports, reason: reason || null });
276
+ await state.write(root, slug, st);
277
+ log.ok(`workflow verify reports set: ${requiredReports.join(', ')}`);
278
+ }
279
+
280
+ async function enforcePolicyOrCheckpoint(root, slug, st, result, options = {}) {
281
+ if (!result || result.decision === 'safe' || result.decision === 'audit-only') return false;
282
+ if (result.decision === 'rejected') {
283
+ const e = new Error(result.message || 'workflow policy rejected this change');
284
+ e.code = 'WORKFLOW_POLICY';
285
+ throw e;
286
+ }
287
+ if (result.decision !== 'checkpoint-required') return false;
288
+ const accepted = findWorkflowPolicyCheckpoint(st, result.summary, 'resolved', 'accept-risk');
289
+ if (accepted) {
290
+ state.logEvent(st, 'workflow.policy.accept_risk', { checkpoint: accepted.id, summary: result.summary });
291
+ return false;
292
+ }
293
+ const pending = findWorkflowPolicyCheckpoint(st, result.summary, 'pending');
294
+ if (pending) {
295
+ renderWorkflowPolicyCheckpoint(slug, pending, result.summary, options);
296
+ process.exitCode = 1;
297
+ return true;
298
+ }
299
+ const cp = checkpoint.addCheckpoint(st, {
300
+ type: 'workflow_policy',
301
+ phase: 'workflow',
302
+ summary: result.summary,
303
+ question: result.question || '这个调整会弱化流程质量闸,是否接受风险?',
304
+ options: [
305
+ { id: 'keep', label: '保留当前流程', command: `devflow status --slug=${slug}` },
306
+ { id: 'accept-risk', label: '接受风险', command: `devflow checkpoint resolve --id=<checkpoint-id> --decision=accept-risk` },
307
+ ],
308
+ nextAction: `devflow status --slug=${slug}`,
309
+ risks: result.risks || ['workflow policy requires confirmation.'],
310
+ evidence: ['state.json#workflow'],
311
+ });
312
+ cp.options = cp.options.map((opt) => opt.id === 'accept-risk'
313
+ ? { ...opt, command: `devflow checkpoint resolve --id=${cp.id} --decision=accept-risk` }
314
+ : opt);
315
+ state.logEvent(st, 'checkpoint.add', { id: cp.id, type: cp.type, phase: cp.phase, summary: result.summary });
316
+ await state.write(root, slug, st);
317
+ renderWorkflowPolicyCheckpoint(slug, cp, result.summary, options);
318
+ process.exitCode = 1;
319
+ return true;
320
+ }
321
+
322
+ function renderWorkflowPolicyCheckpoint(slug, cp, summary, options = {}) {
323
+ if (options.json === true) {
324
+ log.raw(JSON.stringify(buildWorkflowPolicyConfirmationSurface(slug, cp), null, 2));
325
+ return;
326
+ }
327
+ log.error(summary || cp.summary || 'workflow policy checkpoint required');
328
+ log.raw('');
329
+ log.raw(checkpoint.renderNextStepCard(cp));
330
+ }
331
+
332
+ function buildWorkflowPolicyConfirmationSurface(slug, cp) {
333
+ return {
334
+ ok: false,
335
+ type: 'workflow_policy_confirmation_surface',
336
+ slug,
337
+ checkpoint: checkpoint.summarizeCheckpoint(cp),
338
+ confirmationCard: checkpoint.buildConfirmationCard(cp, {
339
+ type: 'workflow_policy_confirm',
340
+ title: '确认 workflow 风险',
341
+ primaryActionId: 'accept-risk',
342
+ }),
343
+ actions: {
344
+ show: `devflow checkpoint show --slug=${slug} --json`,
345
+ card: `devflow flow card --slug=${slug} --json`,
346
+ keep: `devflow status --slug=${slug}`,
347
+ acceptRisk: (cp.options || []).find((opt) => opt.id === 'accept-risk')?.command || `devflow checkpoint resolve --id=${cp.id} --decision=accept-risk`,
348
+ },
349
+ nextAction: `devflow checkpoint show --slug=${slug} --json`,
350
+ };
351
+ }
352
+
353
+ function findWorkflowPolicyCheckpoint(st, summary, status, decision) {
354
+ const checkpoints = Array.isArray(st.checkpoints) ? st.checkpoints : [];
355
+ for (let i = checkpoints.length - 1; i >= 0; i -= 1) {
356
+ const cp = checkpoints[i];
357
+ if (cp.type !== 'workflow_policy') continue;
358
+ if (cp.summary !== summary) continue;
359
+ if ((cp.status || 'pending') !== status) continue;
360
+ if (decision && cp.decision !== decision) continue;
361
+ return cp;
362
+ }
363
+ return null;
364
+ }
365
+
366
+ function diff(st, flags = {}) {
367
+ const d = workflow.diffWorkflow(st.workflow);
368
+ if (flags.json === true) {
369
+ log.raw(JSON.stringify({
370
+ baseRecipe: st.workflow.baseRecipe || null,
371
+ status: st.workflow.status || null,
372
+ ...d,
373
+ }, null, 2));
374
+ return;
375
+ }
376
+ log.raw('workflow diff');
377
+ log.raw(`added: ${d.added.length ? d.added.join(', ') : '-'}`);
378
+ log.raw(`disabled: ${d.disabled.length ? d.disabled.join(', ') : '-'}`);
379
+ log.raw(`moved: ${d.moved.length ? d.moved.join(', ') : '-'}`);
380
+ log.raw(`verify reports: ${d.verifyReports.length ? d.verifyReports.join(', ') : '-'}`);
381
+ }
382
+
383
+ function check(root, slug, st, flags = {}) {
384
+ const result = workflowCheck.checkWorkflow(root, slug, st);
385
+ if (flags.json === true) {
386
+ log.raw(JSON.stringify(result, null, 2));
387
+ } else {
388
+ renderCheckResult('workflow check', result);
389
+ }
390
+ if (!result.ok) process.exitCode = 1;
391
+ }
392
+
393
+ function card(root, slug, st, flags = {}) {
394
+ const result = buildWorkflowConfirmationSurface(root, slug, st);
395
+ if (flags.json === true) {
396
+ log.raw(JSON.stringify(result, null, 2));
397
+ } else {
398
+ renderCheckResult('workflow card', result);
399
+ }
400
+ if (!result.ok) process.exitCode = 1;
401
+ }
402
+
403
+ function buildWorkflowConfirmationSurface(root, slug, st) {
404
+ const result = workflowCheck.checkWorkflow(root, slug, st);
405
+ return {
406
+ type: 'workflow_confirmation_surface',
407
+ slug,
408
+ baseRecipe: st.workflow && st.workflow.baseRecipe ? st.workflow.baseRecipe : null,
409
+ actions: workflowActions(slug),
410
+ ...result,
411
+ };
412
+ }
413
+
414
+ function workflowActions(slug) {
415
+ return {
416
+ picker: `devflow flow picker --slug=${slug} --json`,
417
+ applySelection: `devflow flow apply-selection --slug=${slug} --selection='<json>' --json`,
418
+ check: `devflow flow check --slug=${slug} --json`,
419
+ confirm: `devflow flow confirm --slug=${slug}`,
420
+ };
421
+ }
422
+
423
+ function picker(root, slug, st, flags = {}) {
424
+ const result = workflowPicker.buildPicker(root, slug, st);
425
+ if (flags.json === true) {
426
+ log.raw(JSON.stringify(result, null, 2));
427
+ return;
428
+ }
429
+ log.raw(`workflow picker: ${result.baseRecipe ? result.baseRecipe.label : '-'} status=${result.status || '-'}`);
430
+ for (const group of result.groups) {
431
+ log.raw(`${group.label}:`);
432
+ for (const item of group.items) {
433
+ const box = item.selected ? '[x]' : '[ ]';
434
+ const tags = [];
435
+ if (item.locked) tags.push('locked');
436
+ if (item.recommended && !item.selected) tags.push('recommended');
437
+ log.raw(` ${box} ${item.step} -> ${item.skill}${tags.length ? ` (${tags.join(', ')})` : ''}`);
438
+ }
439
+ }
440
+ log.raw(`next: ${result.actions.check}`);
441
+ }
442
+
443
+ async function applySelection(root, slug, st, flags = {}) {
444
+ const selection = parseSelection(flags.selection || flags.input || flags.items);
445
+ const applied = { added: [], disabled: [], skipped: [] };
446
+
447
+ for (const item of selection.items) {
448
+ if (!item || !item.step) {
449
+ applied.skipped.push({ step: null, reason: 'missing step' });
450
+ continue;
451
+ }
452
+ const existing = (st.workflow.steps || []).find((step) => step.id === item.step);
453
+ if (item.selected === true && !existing) {
454
+ const skill = item.skill || item.step;
455
+ if (!workflow.installedSkillExists(root, skill)) throw new Error(`skill is not installed in this project: ${skill}`);
456
+ if (await enforcePolicyOrCheckpoint(root, slug, st, workflowPolicy.evaluateWorkflowChange(st, {
457
+ action: 'add-step',
458
+ stepId: item.step,
459
+ skill,
460
+ }), { json: flags.json === true })) return;
461
+ const step = workflow.addStep(st.workflow, {
462
+ skill,
463
+ id: item.step,
464
+ after: item.after || null,
465
+ before: item.before || null,
466
+ });
467
+ state.logEvent(st, 'workflow.step.add', { step: step.id, skill, after: item.after || null, before: item.before || null, source: 'selection' });
468
+ applied.added.push(step.id);
469
+ continue;
470
+ }
471
+
472
+ if (item.selected === false && existing && existing.status !== 'disabled') {
473
+ const policyResult = workflowPolicy.evaluateWorkflowChange(st, { action: 'disable-step', stepId: item.step });
474
+ if (await enforcePolicyOrCheckpoint(root, slug, st, policyResult, { json: flags.json === true })) return;
475
+ const allowRequired = policyResult.decision === 'checkpoint-required'
476
+ && !!findWorkflowPolicyCheckpoint(st, policyResult.summary, 'resolved', 'accept-risk');
477
+ const reason = item.reason || 'disabled from workflow picker';
478
+ const step = workflow.disableStep(st.workflow, { stepId: item.step, reason, allowRequired });
479
+ state.logEvent(st, 'workflow.step.disable', { step: step.id, reason, source: 'selection' });
480
+ applied.disabled.push(step.id);
481
+ continue;
482
+ }
483
+
484
+ applied.skipped.push({ step: item.step, reason: 'no change' });
485
+ }
486
+
487
+ await state.write(root, slug, st);
488
+ const result = {
489
+ ...applied,
490
+ nextAction: `devflow flow card --slug=${slug} --json`,
491
+ };
492
+ if (flags.json === true) {
493
+ log.raw(JSON.stringify(result, null, 2));
494
+ return result;
495
+ }
496
+ log.ok(`workflow selection applied: added=${applied.added.length} disabled=${applied.disabled.length}`);
497
+ log.dim(`next: ${result.nextAction}`);
498
+ return result;
499
+ }
500
+
501
+ function parseSelection(raw) {
502
+ if (!raw || raw === true) throw new Error('usage: devflow flow apply-selection --selection=<json> --slug=<slug>');
503
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
504
+ const items = Array.isArray(parsed) ? parsed : parsed.items;
505
+ if (!Array.isArray(items)) throw new Error('workflow selection must contain an items array');
506
+ return { items };
507
+ }
508
+
509
+ async function confirm(root, slug, st) {
510
+ const result = workflowCheck.checkWorkflow(root, slug, st);
511
+ if (!result.ok) {
512
+ renderCheckResult('workflow confirm blocked', result);
513
+ process.exitCode = 1;
514
+ return false;
515
+ }
516
+ workflow.confirmWorkflow(st.workflow);
517
+ state.logEvent(st, 'workflow.confirm', { recipe: st.workflow.baseRecipe && st.workflow.baseRecipe.id, currentStep: st.workflow.currentStep });
518
+ await state.write(root, slug, st);
519
+ log.ok(`workflow confirmed: currentStep=${st.workflow.currentStep || '-'}`);
520
+ return true;
521
+ }
522
+
523
+ function renderCheckResult(label, result) {
524
+ log.raw(`${label}: ${result.ok ? 'ok' : 'blocked'}`);
525
+ for (const item of result.errors) log.raw(`error: ${item.code} - ${item.message}`);
526
+ for (const item of result.warnings) log.raw(`warning: ${item.code} - ${item.message}`);
527
+ if (result.ok && result.confirmationCard) helpers.renderWorkflowConfirmationCard(result.confirmationCard);
528
+ log.raw(`next: ${result.nextAction || '-'}`);
529
+ }
530
+
531
+ function current(st) {
532
+ if (!st.workflow) {
533
+ log.dim('no workflow draft. run devflow flow draft.');
534
+ return;
535
+ }
536
+ log.raw(`workflow: ${st.workflow.baseRecipe.label} (${st.workflow.baseRecipe.id}) status=${st.workflow.status}`);
537
+ renderSteps(st.workflow.steps);
538
+ }
539
+
540
+ function next(st) {
541
+ if (!st.workflow) {
542
+ log.dim('no workflow draft. run devflow flow draft.');
543
+ return;
544
+ }
545
+ const step = st.workflow.steps.find((s) => s.id === st.workflow.currentStep)
546
+ || st.workflow.steps.find((s) => s.status !== 'disabled');
547
+ log.raw(`next: ${step ? `${step.id} (${step.skill})` : '-'}`);
548
+ }
549
+
550
+ function explain(st, flags = {}) {
551
+ if (!st.workflow) {
552
+ log.dim('no workflow draft. run devflow flow draft.');
553
+ return;
554
+ }
555
+ const info = explainWorkflow(st);
556
+ if (flags.json === true) {
557
+ log.raw(JSON.stringify(info, null, 2));
558
+ return;
559
+ }
560
+ log.raw(`base recipe: ${st.workflow.baseRecipe.label} (${st.workflow.baseRecipe.id}) source=${st.workflow.baseRecipe.source}`);
561
+ if (st.workflow.baseRecipe.reason) log.raw(`reason: ${st.workflow.baseRecipe.reason}`);
562
+ log.raw('steps:');
563
+ for (const s of info.steps) {
564
+ const flags = [];
565
+ if (s.protected) flags.push('protected');
566
+ if (s.required) flags.push('required');
567
+ if (s.optional) flags.push('optional');
568
+ if (s.status === 'disabled') flags.push(`disabled: ${s.disabledReason || '-'}`);
569
+ log.raw(` - ${s.id}: skill=${s.skill} source=${s.source}${flags.length ? ` [${flags.join(', ')}]` : ''}`);
570
+ }
571
+ const requiredReports = info.verify && info.verify.requiredReports;
572
+ if (Array.isArray(requiredReports) && requiredReports.length) {
573
+ log.raw(`verify required reports: ${requiredReports.join(', ')}`);
574
+ if (info.verify.reason) log.raw(`verify reason: ${info.verify.reason}`);
575
+ }
576
+ const openRisks = info.openRiskSignals;
577
+ if (openRisks.length) {
578
+ log.raw('open risk signals:');
579
+ for (const risk of openRisks) {
580
+ log.raw(` - ${risk.type}: ${risk.reason || '-'}`);
581
+ }
582
+ }
583
+ const overrides = info.overrides || [];
584
+ if (overrides.length) {
585
+ log.raw('overrides:');
586
+ for (const item of overrides) {
587
+ log.raw(` - ${formatOverride(item)}`);
588
+ }
589
+ }
590
+ }
591
+
592
+ function explainWorkflow(st) {
593
+ const wf = st.workflow || {};
594
+ return {
595
+ baseRecipe: wf.baseRecipe || null,
596
+ status: wf.status || null,
597
+ currentStep: wf.currentStep || null,
598
+ steps: wf.steps || [],
599
+ verify: wf.verify || null,
600
+ openRiskSignals: (st.riskSignals || []).filter((risk) => (risk.status || 'open') === 'open'),
601
+ overrides: wf.overrides || [],
602
+ audit: wf.audit || [],
603
+ };
604
+ }
605
+
606
+ function formatOverride(item) {
607
+ if (item.type === 'add-step') {
608
+ const anchor = item.after ? ` after=${item.after}` : item.before ? ` before=${item.before}` : '';
609
+ return `add-step ${item.step}${anchor}`;
610
+ }
611
+ if (item.type === 'disable-step') {
612
+ return `disable-step ${item.step} reason=${item.reason || '-'}`;
613
+ }
614
+ if (item.type === 'move-step') {
615
+ const anchor = item.after ? ` after=${item.after}` : item.before ? ` before=${item.before}` : '';
616
+ return `move-step ${item.step}${anchor}`;
617
+ }
618
+ if (item.type === 'set-verify') {
619
+ return `set-verify ${(item.requiredReports || []).join(', ')}`;
620
+ }
621
+ return item.type || 'unknown';
622
+ }
623
+
624
+ function renderSteps(steps) {
625
+ for (const s of steps) {
626
+ const tags = [];
627
+ if (s.required) tags.push('required');
628
+ if (s.optional) tags.push('optional');
629
+ if (s.protected) tags.push('protected');
630
+ if (s.status === 'disabled') tags.push('disabled');
631
+ log.raw(` - ${s.id} -> ${s.skill}${tags.length ? ` (${tags.join(', ')})` : ''}`);
632
+ }
633
+ }
634
+
635
+ function validateInstalledSkills(root, steps) {
636
+ for (const s of steps) {
637
+ if (!workflow.installedSkillExists(root, s.skill)) throw new Error(`skill is not installed in this project: ${s.skill}`);
638
+ }
639
+ }
640
+
641
+ function usage() {
642
+ log.error('usage: devflow flow <recommend|draft|suggest|add-step|disable-step|move-step|set-verify|diff|picker|apply-selection|card|check|confirm|current|next|explain|validate|preview|use>');
643
+ }
644
+
645
+ module.exports = { run };