@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,177 @@
1
+ 'use strict';
2
+
3
+ const workflow = require('./workflow.js');
4
+ const workflowPolicy = require('./workflow-policy.js');
5
+ const workflowVerify = require('./workflow-verify.js');
6
+
7
+ function checkWorkflow(root, slug, st) {
8
+ const errors = [];
9
+ const warnings = [];
10
+ const wf = st.workflow;
11
+ if (!wf || !Array.isArray(wf.steps)) {
12
+ return {
13
+ ok: false,
14
+ status: null,
15
+ errors: [{ code: 'workflow-missing', message: 'workflow draft not found' }],
16
+ warnings,
17
+ nextAction: `devflow flow draft --slug=${slug}`,
18
+ };
19
+ }
20
+
21
+ checkProtectedGates(st, wf, errors);
22
+ checkInstalledSkills(root, wf, errors);
23
+ checkPendingWorkflowPolicy(st, errors);
24
+ checkVerifyStrength(st, wf, warnings);
25
+ checkRiskSignals(st, wf, warnings);
26
+
27
+ const ok = errors.length === 0 && warnings.length === 0;
28
+ return {
29
+ ok,
30
+ status: wf.status || null,
31
+ errors,
32
+ warnings,
33
+ nextAction: nextCheckAction(slug, st, errors, warnings),
34
+ confirmationCard: ok ? buildConfirmationCard(slug, wf) : null,
35
+ };
36
+ }
37
+
38
+ function buildConfirmationCard(slug, wf) {
39
+ const confirmCommand = `devflow flow confirm --slug=${slug}`;
40
+ return {
41
+ type: 'workflow_confirm',
42
+ title: '确认本次 workflow',
43
+ question: '是否确认本次 change workflow,并按当前快照执行?',
44
+ baseRecipe: wf.baseRecipe || null,
45
+ status: wf.status || null,
46
+ steps: (wf.steps || []).map((step) => ({
47
+ id: step.id,
48
+ skill: step.skill,
49
+ label: step.label || step.id,
50
+ status: step.status || null,
51
+ required: step.required === true,
52
+ optional: step.optional === true,
53
+ protected: step.protected === true,
54
+ source: step.source || null,
55
+ })),
56
+ primaryAction: {
57
+ id: 'confirm',
58
+ label: '确认 workflow',
59
+ command: confirmCommand,
60
+ },
61
+ secondaryActions: [
62
+ {
63
+ id: 'edit',
64
+ label: '调整编排',
65
+ command: `devflow flow picker --slug=${slug} --json`,
66
+ },
67
+ ],
68
+ };
69
+ }
70
+
71
+ function checkProtectedGates(st, wf, errors) {
72
+ const steps = wf.steps || [];
73
+ for (const gateId of workflow.PROTECTED_GATES) {
74
+ const step = steps.find((s) => s.id === gateId);
75
+ if (!step) {
76
+ errors.push({ code: 'missing-protected-gate', step: gateId, message: `missing protected workflow gate: ${gateId}` });
77
+ continue;
78
+ }
79
+ if (step.status === 'disabled') {
80
+ const summary = `禁用 required workflow step: ${gateId}`;
81
+ if (!hasAcceptedWorkflowRisk(st, summary)) {
82
+ errors.push({ code: 'disabled-protected-gate', step: gateId, message: `protected workflow gate is disabled: ${gateId}` });
83
+ }
84
+ }
85
+ if (!step.required || !step.protected) {
86
+ errors.push({ code: 'invalid-protected-gate', step: gateId, message: `protected workflow gate must be required and protected: ${gateId}` });
87
+ }
88
+ }
89
+ const order = steps.map((s) => s.id);
90
+ if (hasInvalidOrder(order, 'review', 'verify') || hasInvalidOrder(order, 'verify', 'deliver')) {
91
+ errors.push({ code: 'invalid-gate-order', message: 'protected workflow gates must keep review -> verify -> deliver order' });
92
+ }
93
+ }
94
+
95
+ function hasInvalidOrder(order, before, after) {
96
+ const beforeIdx = order.indexOf(before);
97
+ const afterIdx = order.indexOf(after);
98
+ return beforeIdx !== -1 && afterIdx !== -1 && beforeIdx > afterIdx;
99
+ }
100
+
101
+ function checkInstalledSkills(root, wf, errors) {
102
+ for (const step of wf.steps || []) {
103
+ if (step.status === 'disabled') continue;
104
+ if (!workflow.installedSkillExists(root, step.skill)) {
105
+ errors.push({ code: 'missing-skill', step: step.id, skill: step.skill, message: `workflow step references an uninstalled skill: ${step.skill}` });
106
+ }
107
+ }
108
+ }
109
+
110
+ function checkPendingWorkflowPolicy(st, errors) {
111
+ const pending = (st.checkpoints || []).filter((cp) => cp.type === 'workflow_policy' && (cp.status || 'pending') === 'pending');
112
+ for (const cp of pending) {
113
+ errors.push({
114
+ code: 'pending-workflow-policy',
115
+ checkpoint: cp.id,
116
+ message: cp.summary || 'pending workflow policy checkpoint',
117
+ });
118
+ }
119
+ }
120
+
121
+ function checkVerifyStrength(st, wf, warnings) {
122
+ const override = wf.verify && wf.verify.requiredReports;
123
+ if (!Array.isArray(override) || !override.length) return;
124
+ const missing = workflowVerify.missingDefaultReports(st.level, override);
125
+ if (missing.length) {
126
+ const summary = `降低 verify 要求: 缺少 ${missing.join(', ')}`;
127
+ if (hasAcceptedWorkflowRisk(st, summary)) return;
128
+ warnings.push({
129
+ code: 'verify-below-level-default',
130
+ missingReports: missing,
131
+ message: `verify reports are below ${st.level || 'default'} defaults: missing ${missing.join(', ')}`,
132
+ });
133
+ }
134
+ }
135
+
136
+ function checkRiskSignals(st, wf, warnings) {
137
+ const steps = wf.steps || [];
138
+ const open = (st.riskSignals || []).filter((risk) => (risk.status || 'open') === 'open');
139
+ for (const risk of open) {
140
+ const gate = workflowPolicy.RISK_STEP_GATES[risk.type];
141
+ if (!gate) continue;
142
+ const hasActiveStep = gate.steps.some((id) => {
143
+ const step = steps.find((s) => s.id === id);
144
+ return step && step.status !== 'disabled';
145
+ });
146
+ if (!hasActiveStep) {
147
+ const accepted = gate.steps.some((id) => hasAcceptedWorkflowRisk(st, `禁用风险相关 workflow step: ${id} (${risk.type})`));
148
+ if (accepted) continue;
149
+ warnings.push({
150
+ code: 'missing-risk-step',
151
+ riskSignal: risk.type,
152
+ recommendedSteps: gate.steps.slice(),
153
+ message: `open ${risk.type} risk signal has no active recommended workflow step: ${gate.steps.join(', ')}`,
154
+ });
155
+ }
156
+ }
157
+ }
158
+
159
+ function hasAcceptedWorkflowRisk(st, summary) {
160
+ return (st.checkpoints || []).some((cp) => (
161
+ cp.type === 'workflow_policy'
162
+ && cp.summary === summary
163
+ && cp.status === 'resolved'
164
+ && cp.decision === 'accept-risk'
165
+ ));
166
+ }
167
+
168
+ function nextCheckAction(slug, st, errors, warnings) {
169
+ if (errors.find((item) => item.code === 'workflow-missing')) return `devflow flow draft --slug=${slug}`;
170
+ if (errors.find((item) => item.code === 'pending-workflow-policy')) return `devflow checkpoint show --slug=${slug}`;
171
+ if (errors.length) return `devflow flow explain --slug=${slug}`;
172
+ if (warnings.find((item) => item.code === 'missing-risk-step')) return `devflow flow suggest --slug=${slug} --apply-draft`;
173
+ if (warnings.find((item) => item.code === 'verify-below-level-default')) return `devflow flow set-verify --slug=${slug} --reports=${workflowVerify.defaultRequiredReports(st.level).join(',')}`;
174
+ return `devflow flow confirm --slug=${slug}`;
175
+ }
176
+
177
+ module.exports = { checkWorkflow };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const state = require('./state.js');
4
+ const workflow = require('./workflow.js');
5
+
6
+ function draftRecommendedWorkflow(root, st, cfg = {}, opts = {}) {
7
+ if (!st) throw new Error('state is required');
8
+ if (!opts.force && st.workflow && Array.isArray(st.workflow.steps)) {
9
+ return { created: false, recipe: st.workflow.baseRecipe || null, reason: 'workflow already exists' };
10
+ }
11
+ const recommendation = opts.recipe
12
+ ? { id: opts.recipe, reason: opts.reason || 'selected by entry' }
13
+ : workflow.recommendRecipe(st, cfg || {});
14
+ const recipe = workflow.expandRecipe(recommendation.id, cfg || {});
15
+ validateInstalledSkills(root, recipe.steps);
16
+ st.workflow = workflow.createDraft({ recipe, state: st, reason: recommendation.reason });
17
+ state.logEvent(st, 'workflow.draft', {
18
+ recipe: recipe.id,
19
+ source: recipe.source,
20
+ reason: recommendation.reason,
21
+ auto: opts.auto === true,
22
+ });
23
+ return { created: true, recipe, reason: recommendation.reason };
24
+ }
25
+
26
+ function validateInstalledSkills(root, steps) {
27
+ for (const step of steps) {
28
+ if (!workflow.installedSkillExists(root, step.skill)) {
29
+ throw new Error(`skill is not installed in this project: ${step.skill}`);
30
+ }
31
+ }
32
+ }
33
+
34
+ module.exports = { draftRecommendedWorkflow };
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ const workflow = require('./workflow.js');
4
+ const workflowPolicy = require('./workflow-policy.js');
5
+
6
+ const GROUPS = [
7
+ { id: 'core', label: '核心流程' },
8
+ { id: 'quality', label: '专项检查' },
9
+ { id: 'delivery', label: '质量闸与交付' },
10
+ ];
11
+
12
+ const CORE_STEPS = ['requirement', 'design', 'plan', 'apply'];
13
+ const DELIVERY_STEPS = ['review', 'verify', 'deliver', 'archive'];
14
+ const QUALITY_CANDIDATES = [
15
+ { id: 'frontend-quality', skill: 'frontend-quality', label: 'frontend-quality', after: 'apply' },
16
+ { id: 'security-hardening', skill: 'security-hardening', label: 'security-hardening', after: 'apply' },
17
+ { id: 'dependency-upgrade', skill: 'dependency-upgrade', label: 'dependency-upgrade', before: 'plan' },
18
+ { id: 'ci-fix', skill: 'ci-fix', label: 'ci-fix', before: 'apply' },
19
+ ];
20
+
21
+ function buildPicker(root, slug, st) {
22
+ const wf = st.workflow;
23
+ if (!wf || !Array.isArray(wf.steps)) throw new Error('workflow draft not found. run devflow flow draft first.');
24
+
25
+ const activeSteps = new Map((wf.steps || []).map((step) => [step.id, step]));
26
+ const baseStepIds = new Set((wf.baseSteps || []).map((step) => step.id));
27
+ const riskReasons = recommendedStepReasons(st);
28
+ const groups = GROUPS.map((group) => ({ ...group, items: [] }));
29
+ const byGroup = new Map(groups.map((group) => [group.id, group]));
30
+
31
+ for (const step of wf.steps || []) {
32
+ const group = byGroup.get(groupForStep(step));
33
+ group.items.push(buildItem(root, slug, step, {
34
+ selected: step.status !== 'disabled',
35
+ recommended: baseStepIds.has(step.id) || riskReasons.has(step.id),
36
+ reason: riskReasons.get(step.id) || (baseStepIds.has(step.id) ? 'included by base recipe' : null),
37
+ }));
38
+ }
39
+
40
+ for (const candidate of QUALITY_CANDIDATES) {
41
+ if (activeSteps.has(candidate.id)) continue;
42
+ byGroup.get('quality').items.push(buildItem(root, slug, {
43
+ id: candidate.id,
44
+ skill: candidate.skill,
45
+ label: candidate.label,
46
+ optional: true,
47
+ required: false,
48
+ protected: false,
49
+ source: 'builtin',
50
+ status: 'available',
51
+ }, {
52
+ selected: false,
53
+ recommended: riskReasons.has(candidate.id),
54
+ reason: riskReasons.get(candidate.id) || null,
55
+ command: addCommand(slug, candidate),
56
+ }));
57
+ }
58
+
59
+ for (const group of groups) {
60
+ group.items.sort((a, b) => itemOrder(a) - itemOrder(b));
61
+ }
62
+
63
+ return {
64
+ type: 'workflow_picker_surface',
65
+ layout: 'linear-grouped',
66
+ baseRecipe: wf.baseRecipe || null,
67
+ status: wf.status || null,
68
+ currentStep: wf.currentStep || null,
69
+ groups,
70
+ actions: {
71
+ card: `devflow flow card --slug=${slug} --json`,
72
+ check: `devflow flow check --slug=${slug} --json`,
73
+ applySelection: `devflow flow apply-selection --slug=${slug} --selection=<json> --json`,
74
+ confirm: `devflow flow confirm --slug=${slug}`,
75
+ suggest: `devflow flow suggest --slug=${slug} --apply-draft`,
76
+ },
77
+ };
78
+ }
79
+
80
+ function buildItem(root, slug, step, opts = {}) {
81
+ const selected = opts.selected === true;
82
+ const locked = step.protected === true || workflow.PROTECTED_GATES.includes(step.id);
83
+ return {
84
+ step: step.id,
85
+ skill: step.skill,
86
+ label: step.label || step.id,
87
+ group: groupForStep(step),
88
+ selected,
89
+ recommended: opts.recommended === true,
90
+ reason: opts.reason || null,
91
+ required: step.required === true,
92
+ optional: step.optional === true,
93
+ protected: step.protected === true,
94
+ locked,
95
+ source: step.source || null,
96
+ status: step.status || null,
97
+ installed: workflow.installedSkillExists(root, step.skill),
98
+ command: opts.command || commandForStep(slug, step, selected, locked),
99
+ };
100
+ }
101
+
102
+ function commandForStep(slug, step, selected, locked) {
103
+ if (locked) return null;
104
+ if (selected && step.optional) {
105
+ return `devflow flow disable-step ${step.id} --slug=${slug} --reason="..."`;
106
+ }
107
+ return null;
108
+ }
109
+
110
+ function addCommand(slug, candidate) {
111
+ const anchor = candidate.after ? `--after=${candidate.after}` : `--before=${candidate.before}`;
112
+ return `devflow flow add-step ${candidate.skill} --slug=${slug} ${anchor}`;
113
+ }
114
+
115
+ function recommendedStepReasons(st) {
116
+ const out = new Map();
117
+ const open = (st.riskSignals || []).filter((risk) => (risk.status || 'open') === 'open');
118
+ for (const risk of open) {
119
+ const gate = workflowPolicy.RISK_STEP_GATES[risk.type];
120
+ if (!gate) continue;
121
+ for (const stepId of gate.steps || []) {
122
+ out.set(stepId, `open ${risk.type} risk signal`);
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+
128
+ function groupForStep(step) {
129
+ if (DELIVERY_STEPS.includes(step.id) || step.protected) return 'delivery';
130
+ if (QUALITY_CANDIDATES.some((candidate) => candidate.id === step.id) || step.optional) return 'quality';
131
+ if (CORE_STEPS.includes(step.id)) return 'core';
132
+ return 'core';
133
+ }
134
+
135
+ function itemOrder(item) {
136
+ const order = [
137
+ 'requirement',
138
+ 'design',
139
+ 'plan',
140
+ 'apply',
141
+ 'dependency-upgrade',
142
+ 'ci-fix',
143
+ 'frontend-quality',
144
+ 'security-hardening',
145
+ 'review',
146
+ 'verify',
147
+ 'deliver',
148
+ 'archive',
149
+ ];
150
+ const idx = order.indexOf(item.step);
151
+ return idx === -1 ? order.length : idx;
152
+ }
153
+
154
+ module.exports = { buildPicker };
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ const workflowVerify = require('./workflow-verify.js');
4
+
5
+ const RISK_STEP_GATES = {
6
+ frontend_change: {
7
+ steps: ['frontend-quality'],
8
+ label: '前端质量检查',
9
+ },
10
+ security_sensitive: {
11
+ steps: ['security-hardening'],
12
+ label: '安全专项检查',
13
+ },
14
+ dependency_change: {
15
+ steps: ['dependency-upgrade'],
16
+ label: '依赖升级检查',
17
+ },
18
+ ci_failure: {
19
+ steps: ['ci-fix'],
20
+ label: 'CI 修复检查',
21
+ },
22
+ };
23
+
24
+ function evaluateWorkflowChange(st, change) {
25
+ const action = change && change.action;
26
+ if (action === 'add-step') {
27
+ return safe('adding checks does not weaken workflow');
28
+ }
29
+
30
+ if (action === 'set-verify') {
31
+ const missing = workflowVerify.missingDefaultReports(st.level, change.requiredReports || change.reports || []);
32
+ if (missing.length) {
33
+ return checkpointRequired({
34
+ summary: `降低 verify 要求: 缺少 ${missing.join(', ')}`,
35
+ risks: ['降低 verify required reports 会减少本次 change 的验证证据。'],
36
+ });
37
+ }
38
+ return auditOnly('verify required reports are equal or stronger than level default');
39
+ }
40
+
41
+ if (action === 'move-step') {
42
+ const step = findStep(st.workflow, change.stepId);
43
+ if (isProtected(step)) {
44
+ return rejected(`cannot move protected workflow gate: ${change.stepId}`, [
45
+ 'protected gates must keep review -> verify -> deliver ordering.',
46
+ ]);
47
+ }
48
+ return safe('moving a non-gate step is audit-only');
49
+ }
50
+
51
+ if (action === 'disable-step') {
52
+ const step = findStep(st.workflow, change.stepId);
53
+ if (isProtected(step) || step.required) {
54
+ return checkpointRequired({
55
+ summary: `禁用 required workflow step: ${change.stepId}`,
56
+ risks: ['禁用 required step 可能绕过 review/verify/deliver 或必要前置产物。'],
57
+ });
58
+ }
59
+
60
+ const risk = matchedOpenRisk(st, change.stepId);
61
+ if (risk) {
62
+ return checkpointRequired({
63
+ summary: `禁用风险相关 workflow step: ${change.stepId} (${risk.type})`,
64
+ risks: [`当前存在 ${risk.type} 风险信号,禁用 ${risk.label} 会降低本次 change 的验证覆盖。`],
65
+ });
66
+ }
67
+
68
+ return auditOnly('disabling optional step without mapped open risk');
69
+ }
70
+
71
+ return safe('no policy rule matched');
72
+ }
73
+
74
+ function matchedOpenRisk(st, stepId) {
75
+ const open = (st.riskSignals || []).filter((r) => (r.status || 'open') === 'open');
76
+ for (const r of open) {
77
+ const gate = RISK_STEP_GATES[r.type];
78
+ if (gate && gate.steps.includes(stepId)) return { type: r.type, label: gate.label };
79
+ }
80
+ return null;
81
+ }
82
+
83
+ function findStep(workflow, id) {
84
+ const step = workflow && Array.isArray(workflow.steps)
85
+ ? workflow.steps.find((s) => s.id === id)
86
+ : null;
87
+ if (!step) return { id, missing: true };
88
+ return step;
89
+ }
90
+
91
+ function isProtected(step) {
92
+ return !!(step && (step.protected || step.required || ['review', 'verify', 'deliver'].includes(step.id)));
93
+ }
94
+
95
+ function safe(reason) {
96
+ return { decision: 'safe', reason };
97
+ }
98
+
99
+ function auditOnly(reason) {
100
+ return { decision: 'audit-only', reason };
101
+ }
102
+
103
+ function checkpointRequired({ summary, risks }) {
104
+ return {
105
+ decision: 'checkpoint-required',
106
+ summary,
107
+ question: '这个 workflow 调整会提高风险,是否接受本次 change 的风险变化?',
108
+ risks,
109
+ };
110
+ }
111
+
112
+ function rejected(message, risks) {
113
+ return { decision: 'rejected', message, risks };
114
+ }
115
+
116
+ module.exports = {
117
+ RISK_STEP_GATES,
118
+ evaluateWorkflowChange,
119
+ };
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ const workflowVerify = require('./workflow-verify.js');
4
+
5
+ const INTENT_RULES = [
6
+ {
7
+ id: 'frontend-quality',
8
+ kind: 'step',
9
+ skill: 'frontend-quality',
10
+ patterns: [/前端/, /页面/, /\bui\b/i, /样式/, /交互/, /视觉/, /浏览器/, /react/i, /vue/i, /next/i, /vite/i],
11
+ reason: 'intent mentions frontend or UI quality risk',
12
+ },
13
+ {
14
+ id: 'security-hardening',
15
+ kind: 'step',
16
+ skill: 'security-hardening',
17
+ patterns: [/安全/, /权限/, /鉴权/, /认证/, /登录/, /token/i, /cookie/i, /auth/i, /加密/, /校验/],
18
+ reason: 'intent mentions security-sensitive behavior',
19
+ },
20
+ {
21
+ id: 'dependency-upgrade',
22
+ kind: 'recipe',
23
+ recipe: 'dependency-upgrade',
24
+ patterns: [/依赖/, /升级/, /package-lock/i, /pnpm-lock/i, /yarn\.lock/i, /go\.sum/i, /pom\.xml/i],
25
+ reason: 'intent mentions dependency changes',
26
+ },
27
+ {
28
+ id: 'ci-fix',
29
+ kind: 'recipe',
30
+ recipe: 'ci-fix',
31
+ patterns: [/\bci\b/i, /构建/, /流水线/, /jenkins/i, /github actions/i, /gitlab ci/i, /失败/],
32
+ reason: 'intent mentions CI or build failure',
33
+ },
34
+ ];
35
+
36
+ const VERIFY_RULES = [
37
+ {
38
+ id: 'frontend-e2e',
39
+ reports: ['e2e-test.md'],
40
+ patterns: [/核心链路/, /主链路/, /关键路径/, /端到端/, /\be2e\b/i],
41
+ reason: 'intent mentions core path or e2e verification',
42
+ },
43
+ ];
44
+
45
+ const RISK_SIGNAL_RULES = {
46
+ frontend_change: {
47
+ step: { id: 'frontend-quality', skill: 'frontend-quality' },
48
+ reports: ['e2e-test.md'],
49
+ reason: 'open frontend_change risk signal',
50
+ },
51
+ security_sensitive: {
52
+ step: { id: 'security-hardening', skill: 'security-hardening' },
53
+ reports: ['contract-test.md'],
54
+ reason: 'open security_sensitive risk signal',
55
+ },
56
+ dependency_change: {
57
+ recipe: 'dependency-upgrade',
58
+ reason: 'open dependency_change risk signal',
59
+ },
60
+ ci_failure: {
61
+ recipe: 'ci-fix',
62
+ reason: 'open ci_failure risk signal',
63
+ },
64
+ };
65
+
66
+ function suggestWorkflowOverrides(intent, workflow, options = {}) {
67
+ const text = String(intent || '').trim();
68
+ const existing = new Set(((workflow && workflow.steps) || []).map((s) => s.id));
69
+ const suggestedSteps = new Set(existing);
70
+ const suggestedRecipes = new Set();
71
+ const suggestions = [];
72
+ let after = existing.has('apply') ? 'apply' : null;
73
+
74
+ for (const rule of INTENT_RULES) {
75
+ if (!matches(rule, text)) continue;
76
+ if (rule.kind === 'recipe') {
77
+ pushRecipeSuggestion(suggestions, suggestedRecipes, rule.recipe, rule.reason);
78
+ continue;
79
+ }
80
+ after = pushStepSuggestion(suggestions, suggestedSteps, { id: rule.id, skill: rule.skill, after, reason: rule.reason }) || after;
81
+ }
82
+
83
+ const currentReports = workflowVerify.requiredReportsForState({
84
+ level: options.level,
85
+ workflow,
86
+ });
87
+ let reportBase = currentReports;
88
+ for (const rule of VERIFY_RULES) {
89
+ if (!matches(rule, text)) continue;
90
+ const suggestion = buildVerifySuggestion(reportBase, rule.reports, rule.reason);
91
+ if (suggestion) {
92
+ suggestions.push(suggestion);
93
+ reportBase = suggestion.reports;
94
+ }
95
+ }
96
+
97
+ for (const risk of openRiskSignals(options.riskSignals)) {
98
+ const rule = RISK_SIGNAL_RULES[risk.type];
99
+ if (!rule) continue;
100
+ if (rule.recipe) pushRecipeSuggestion(suggestions, suggestedRecipes, rule.recipe, rule.reason);
101
+ if (rule.step) {
102
+ after = pushStepSuggestion(suggestions, suggestedSteps, {
103
+ id: rule.step.id,
104
+ skill: rule.step.skill,
105
+ after,
106
+ reason: rule.reason,
107
+ }) || after;
108
+ }
109
+ if (rule.reports) {
110
+ const suggestion = buildVerifySuggestion(reportBase, rule.reports, rule.reason);
111
+ if (suggestion) {
112
+ suggestions.push(suggestion);
113
+ reportBase = suggestion.reports;
114
+ }
115
+ }
116
+ }
117
+
118
+ return suggestions;
119
+ }
120
+
121
+ function matches(rule, text) {
122
+ if (!text) return false;
123
+ return rule.patterns.some((pattern) => pattern.test(text));
124
+ }
125
+
126
+ function withReports(base, extra) {
127
+ const out = workflowVerify.normalizeReportList(base);
128
+ const seen = new Set(out);
129
+ for (const report of workflowVerify.normalizeReportList(extra)) {
130
+ if (!seen.has(report)) {
131
+ out.push(report);
132
+ seen.add(report);
133
+ }
134
+ }
135
+ return out;
136
+ }
137
+
138
+ function buildVerifySuggestion(baseReports, extraReports, reason) {
139
+ const nextReports = withReports(baseReports, extraReports);
140
+ if (nextReports.length === baseReports.length) return null;
141
+ return {
142
+ type: 'set-verify',
143
+ reports: nextReports,
144
+ reportArgs: nextReports.map(workflowVerify.reportKindFromName),
145
+ reason,
146
+ };
147
+ }
148
+
149
+ function pushStepSuggestion(suggestions, suggestedSteps, { id, skill, after, reason }) {
150
+ if (suggestedSteps.has(id)) return null;
151
+ suggestions.push({
152
+ type: 'add-step',
153
+ step: id,
154
+ skill,
155
+ after,
156
+ reason,
157
+ });
158
+ suggestedSteps.add(id);
159
+ return id;
160
+ }
161
+
162
+ function pushRecipeSuggestion(suggestions, suggestedRecipes, recipe, reason) {
163
+ if (suggestedRecipes.has(recipe)) return;
164
+ suggestions.push({
165
+ type: 'recipe',
166
+ recipe,
167
+ reason,
168
+ });
169
+ suggestedRecipes.add(recipe);
170
+ }
171
+
172
+ function openRiskSignals(riskSignals) {
173
+ return (riskSignals || []).filter((risk) => (risk.status || 'open') === 'open');
174
+ }
175
+
176
+ module.exports = {
177
+ INTENT_RULES,
178
+ VERIFY_RULES,
179
+ RISK_SIGNAL_RULES,
180
+ suggestWorkflowOverrides,
181
+ };