@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,354 @@
1
+ 'use strict';
2
+ const fs = require('fs/promises');
3
+ const fsSync = require('fs');
4
+ const path = require('path');
5
+ const log = require('../../utils/log.js');
6
+ const state = require('../../core/state.js');
7
+ const change = require('../../core/change.js');
8
+ const checkpoint = require('../../core/checkpoint.js');
9
+ const helpers = require('./_helpers.js');
10
+ const aggregate = require('../../reports/aggregate.js');
11
+ const workflowVerify = require('../../core/workflow-verify.js');
12
+
13
+ const RISK_VERIFY_GATES = {
14
+ frontend_change: {
15
+ label: '前端改动',
16
+ anyOf: ['joint-test.md', 'e2e-test.md'],
17
+ evidence: ['reports/test-report.md#joint', 'reports/test-report.md#e2e'],
18
+ hint: 'run devflow test joint/e2e and include browser or screenshot evidence',
19
+ },
20
+ dependency_change: {
21
+ label: '依赖升级',
22
+ allOf: ['unit-test.md', 'integration-test.md'],
23
+ evidence: ['reports/test-report.md#unit', 'reports/test-report.md#integration'],
24
+ hint: 'run unit + integration compatibility tests and document rollback in deliver notes',
25
+ },
26
+ security_sensitive: {
27
+ label: '安全敏感改动',
28
+ anyOf: ['contract-test.md', 'integration-test.md', 'e2e-test.md', 'self-test.md'],
29
+ evidence: ['reports/test-report.md#contract', 'reports/test-report.md#integration', 'reports/test-report.md#e2e', 'reports/test-report.md#self-test'],
30
+ hint: 'add security verification evidence or an explicit owner-approved risk acceptance',
31
+ },
32
+ };
33
+
34
+ async function run({ flags = {}, positional = [], cwd }) {
35
+ const root = cwd || process.cwd();
36
+ const isFinalize = positional[0] === 'finalize' || flags.finalize;
37
+ const slugPositional = isFinalize ? positional.slice(1) : positional;
38
+ const slug = await helpers.resolveSlug(root, flags, slugPositional);
39
+ if (!slug) { process.exitCode = 1; return; }
40
+ const st = await helpers.loadStateOrFail(root, slug);
41
+ if (!st) { process.exitCode = 1; return; }
42
+ if (helpers.blockWorkflowStep(root, st, slug, 'verify', 'verify')) return;
43
+
44
+ if (isFinalize) {
45
+ return finalize(root, slug, st, flags);
46
+ }
47
+
48
+ const pendingVerifyStart = checkpoint.latestPendingByType(st, 'verify_start');
49
+ if (pendingVerifyStart) {
50
+ if (flags.force === true) {
51
+ const reason = flags.reason;
52
+ if (!reason || reason === true) {
53
+ log.error('verify --force requires --reason="..." when bypassing verify_start checkpoint');
54
+ process.exitCode = 2;
55
+ return;
56
+ }
57
+ checkpoint.resolveCheckpoint(st, pendingVerifyStart.id, 'force-start-verify', reason);
58
+ state.logEvent(st, 'checkpoint.resolve', { id: pendingVerifyStart.id, decision: 'force-start-verify' });
59
+ state.logEvent(st, 'verify.force_start', { checkpointId: pendingVerifyStart.id, reason });
60
+ log.warn(`verify start checkpoint bypassed — reason: "${reason}"`);
61
+ } else {
62
+ state.logEvent(st, 'verify.blocked_pending_start_confirmation', { checkpointId: pendingVerifyStart.id });
63
+ await state.write(root, slug, st);
64
+ log.error('verify blocked: pending verify_start checkpoint.');
65
+ log.raw('');
66
+ log.raw(checkpoint.renderNextStepCard(pendingVerifyStart));
67
+ log.raw('');
68
+ log.dim(`confirm first: devflow checkpoint resolve --id=${pendingVerifyStart.id} --decision=start-verify`);
69
+ log.dim(`override (audited): devflow verify --slug=${slug} --force --reason="..."`);
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ }
74
+
75
+ // Gate: review must be completed (or force-passed) before verify may start.
76
+ const review = (st.phases && st.phases.review) || {};
77
+ const skipGate = flags['skip-review-gate'] === true || flags.skipReviewGate === true;
78
+ if (!skipGate) {
79
+ if (review.status === 'blocked') {
80
+ log.error('verify blocked: review is at status=blocked.');
81
+ log.dim(`fix MUSTs + devflow review --slug=${slug} --round --continue-rounds, or devflow review --slug=${slug} --force-pass --reason="..."`);
82
+ log.dim(`override (audited): devflow verify --slug=${slug} --skip-review-gate --reason="..."`);
83
+ process.exitCode = 1; return;
84
+ }
85
+ if (review.status !== 'completed') {
86
+ const rounds = (review.rounds || []).length;
87
+ if (rounds === 0) {
88
+ log.error(`verify blocked: no review rounds recorded. run "devflow review --slug=${slug} --round" first.`);
89
+ } else {
90
+ const last = review.rounds[rounds - 1];
91
+ log.error(`verify blocked: last review round outcome=${last.outcome}. status=${review.status}`);
92
+ log.dim('run more rounds until pass, or force-pass with reason.');
93
+ }
94
+ log.dim(`override (audited): devflow verify --slug=${slug} --skip-review-gate --reason="..."`);
95
+ process.exitCode = 1; return;
96
+ }
97
+ } else {
98
+ const reason = flags.reason;
99
+ if (!reason || reason === true) {
100
+ log.error('--skip-review-gate requires --reason="..." (audited)');
101
+ process.exitCode = 2; return;
102
+ }
103
+ state.logEvent(st, 'verify.skip_review_gate', { reason });
104
+ log.warn(`review gate skipped — reason: "${reason}"`);
105
+ }
106
+
107
+ await helpers.writePhaseArtifact(root, slug, st, 'verify.md', 'verify.md', {});
108
+ state.setPhase(st, 'review', 'completed');
109
+ state.setPhase(st, 'verify', 'in_progress');
110
+ state.logEvent(st, 'verify scaffold');
111
+ await state.write(root, slug, st);
112
+ log.dim(`run "devflow test <kind> --slug=${slug}" for each enabled kind, then "devflow verify finalize --slug=${slug}"`);
113
+ }
114
+
115
+ async function finalize(root, slug, st, flags = {}) {
116
+ const reportsDir = path.join(change.resolveChangeDir(root, slug), 'reports');
117
+ const required = workflowVerify.requiredReportsForState(st);
118
+ const missing = [];
119
+ for (const r of required) {
120
+ if (!reportExists(reportsDir, r)) missing.push(r);
121
+ }
122
+ if (missing.length) {
123
+ const cp = ensureVerificationEvidenceCheckpoint(st, missing, slug);
124
+ const missingText = missing.map(reportDisplayName).join(', ');
125
+ if (flags.force === true) {
126
+ const reason = flags.reason;
127
+ if (!reason || reason === true) {
128
+ log.error('verify finalize --force requires --reason="..."');
129
+ process.exitCode = 2;
130
+ return;
131
+ }
132
+ checkpoint.resolveCheckpoint(st, cp.id, 'accept_missing_evidence', reason);
133
+ state.logEvent(st, 'checkpoint.resolve', { id: cp.id, decision: 'accept_missing_evidence' });
134
+ state.logEvent(st, 'verify.force_missing_reports', { missing, reason });
135
+ log.warn(`verify finalize: force-accepted missing report sections — ${missingText}; reason: "${reason}"`);
136
+ } else {
137
+ state.logEvent(st, 'verify.finalize_blocked_missing_reports', { missing, checkpoint: cp.id });
138
+ await state.write(root, slug, st);
139
+ log.error(`verify finalize blocked. missing report sections: ${missingText}`);
140
+ log.dim(`hint: run "devflow test unit --slug=${slug}" / "devflow test integration --slug=${slug}" / "devflow test joint --slug=${slug}" / "devflow report self-test --slug=${slug}" etc.`);
141
+ log.raw('');
142
+ log.raw(checkpoint.renderNextStepCard(cp));
143
+ log.dim(`override (audited): devflow verify finalize --slug=${slug} --force --reason="..."`);
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ }
148
+ const riskProblems = evaluateRiskVerifyGates(st, reportsDir);
149
+ if (riskProblems.length) {
150
+ const cp = ensureRiskAcceptanceCheckpoint(st, riskProblems, slug);
151
+ if (flags.force === true) {
152
+ const reason = flags.reason;
153
+ if (!reason || reason === true) {
154
+ log.error('verify finalize --force requires --reason="..." when accepting open risk signals');
155
+ process.exitCode = 2;
156
+ return;
157
+ }
158
+ checkpoint.resolveCheckpoint(st, cp.id, 'accept-risk', reason);
159
+ state.logEvent(st, 'checkpoint.resolve', { id: cp.id, decision: 'accept-risk' });
160
+ for (const p of riskProblems) {
161
+ state.acceptRiskSignal(st, p.type, { reason, evidence: p.expectedEvidence });
162
+ }
163
+ state.logEvent(st, 'verify.force_risk_signals', { riskSignals: riskProblems.map((p) => p.type), reason });
164
+ log.warn(`verify finalize: force-accepted risk signals — ${riskProblems.map((p) => p.type).join(', ')}; reason: "${reason}"`);
165
+ } else {
166
+ state.logEvent(st, 'verify.finalize_blocked_risk_signals', {
167
+ riskSignals: riskProblems.map((p) => ({ type: p.type, missing: p.missing, anyOf: p.anyOf })),
168
+ checkpoint: cp.id,
169
+ });
170
+ await state.write(root, slug, st);
171
+ log.error(`verify finalize blocked. unresolved risk signals: ${riskProblems.map((p) => p.type).join(', ')}`);
172
+ for (const p of riskProblems) log.dim(` - ${p.label}: ${p.hint}`);
173
+ log.raw('');
174
+ log.raw(checkpoint.renderNextStepCard(cp));
175
+ log.dim(`override (audited): devflow verify finalize --slug=${slug} --force --reason="..."`);
176
+ process.exitCode = 1;
177
+ return;
178
+ }
179
+ } else {
180
+ resolveSatisfiedRiskSignals(st, reportsDir);
181
+ }
182
+ const failedReports = [];
183
+ const reportFiles = await listReportFiles(reportsDir);
184
+ for (const r of reportFiles) {
185
+ const meta = readReportMeta(path.join(reportsDir, r));
186
+ if (meta.status === 'fail' || meta.status === 'blocked') {
187
+ failedReports.push({ file: r, status: meta.status, failureType: meta.failureType || meta.failure_type || '-' });
188
+ }
189
+ }
190
+ const aggregateReport = aggregate.readAggregate(reportsDir);
191
+ for (const r of aggregateReport.reports) {
192
+ if (r.status === 'fail' || r.status === 'blocked') {
193
+ failedReports.push({ file: `test-report.md#${r.kind}`, status: r.status, failureType: r.failureType || '-' });
194
+ }
195
+ }
196
+ if (failedReports.length) {
197
+ const detail = failedReports.map((r) => `${r.file}:${r.status}${r.failureType !== '-' ? `(${r.failureType})` : ''}`).join(', ');
198
+ if (flags.force === true) {
199
+ const reason = flags.reason;
200
+ if (!reason || reason === true) {
201
+ log.error('verify finalize --force requires --reason="..." when accepting failed reports');
202
+ process.exitCode = 2;
203
+ return;
204
+ }
205
+ state.logEvent(st, 'verify.force_failed_reports', { failedReports, reason });
206
+ log.warn(`verify finalize: force-accepted failed reports — ${detail}; reason: "${reason}"`);
207
+ } else {
208
+ state.logEvent(st, 'verify.finalize_blocked_failed_reports', { failedReports });
209
+ await state.write(root, slug, st);
210
+ log.error(`verify finalize blocked. failed reports: ${detail}`);
211
+ const hasEnv = failedReports.some((r) => r.failureType === 'env_error');
212
+ const hasCode = failedReports.some((r) => r.failureType === 'code_error');
213
+ if (hasCode) log.dim('next: fix code via apply, rerun the failed test, then verify finalize.');
214
+ if (hasEnv) log.dim('next: fix/start test environment or browser/API tooling, rerun the failed test, then verify finalize.');
215
+ log.dim(`override (audited): devflow verify finalize --slug=${slug} --force --reason="..."`);
216
+ process.exitCode = 1;
217
+ return;
218
+ }
219
+ }
220
+ state.setPhase(st, 'verify', 'completed');
221
+ helpers.completeWorkflowStep(st, 'verify');
222
+ state.passCheckpoint(st, 'verify-finalize');
223
+ state.logEvent(st, 'verify finalize ok');
224
+ await state.write(root, slug, st);
225
+ log.ok('verify: all required report sections present.');
226
+ log.dim(`next: devflow deliver --slug=${slug}`);
227
+ }
228
+
229
+ function evaluateRiskVerifyGates(st, reportsDir) {
230
+ const out = [];
231
+ for (const sig of state.openRiskSignals(st, Object.keys(RISK_VERIFY_GATES))) {
232
+ const gate = RISK_VERIFY_GATES[sig.type];
233
+ const missing = (gate.allOf || []).filter((name) => !reportExists(reportsDir, name));
234
+ const anyOf = gate.anyOf || [];
235
+ const anySatisfied = !anyOf.length || anyOf.some((name) => reportExists(reportsDir, name));
236
+ if (missing.length || !anySatisfied) {
237
+ out.push({
238
+ type: sig.type,
239
+ label: gate.label,
240
+ missing,
241
+ anyOf: anySatisfied ? [] : anyOf,
242
+ expectedEvidence: gate.evidence,
243
+ hint: gate.hint,
244
+ });
245
+ }
246
+ }
247
+ return out;
248
+ }
249
+
250
+ function resolveSatisfiedRiskSignals(st, reportsDir) {
251
+ for (const sig of state.openRiskSignals(st, Object.keys(RISK_VERIFY_GATES))) {
252
+ const gate = RISK_VERIFY_GATES[sig.type];
253
+ const missing = (gate.allOf || []).filter((name) => !reportExists(reportsDir, name));
254
+ const anyOf = gate.anyOf || [];
255
+ const anySatisfied = !anyOf.length || anyOf.some((name) => reportExists(reportsDir, name));
256
+ if (!missing.length && anySatisfied) {
257
+ state.resolveRiskSignal(st, sig.type, { evidence: gate.evidence, reason: 'verify evidence satisfied' });
258
+ }
259
+ }
260
+ }
261
+
262
+ function ensureRiskAcceptanceCheckpoint(st, problems, slug) {
263
+ const existing = checkpoint.latestPendingByType(st, 'risk_acceptance');
264
+ if (existing) return existing;
265
+ const summary = `风险信号缺少验证证据:${problems.map((p) => p.label).join(', ')}`;
266
+ const nextCommands = problems.flatMap((p) => p.expectedEvidence || []);
267
+ const cp = checkpoint.addCheckpoint(st, {
268
+ type: 'risk_acceptance',
269
+ phase: 'verify',
270
+ summary,
271
+ question: '补齐风险验证证据,还是带原因接受风险?',
272
+ options: [
273
+ { id: 'add-evidence', label: '补齐风险证据', command: 'devflow test <kind> --slug=<slug>' },
274
+ { id: 'accept-risk', label: '接受风险', command: `devflow verify finalize --slug=${slug} --force --reason="..."` },
275
+ ],
276
+ nextAction: 'devflow test <kind> --slug=<slug>',
277
+ evidence: nextCommands,
278
+ risks: problems.map((p) => `${p.label}: ${p.hint}`),
279
+ });
280
+ state.logEvent(st, 'checkpoint.add', { id: cp.id, type: cp.type, phase: cp.phase });
281
+ return cp;
282
+ }
283
+
284
+ function ensureVerificationEvidenceCheckpoint(st, missing, slug) {
285
+ const existing = checkpoint.latestPendingByType(st, 'verification_evidence');
286
+ if (existing) return existing;
287
+ const cp = checkpoint.addCheckpoint(st, {
288
+ type: 'verification_evidence',
289
+ phase: 'verify',
290
+ summary: `缺少必需验证报告:${missing.map(reportDisplayName).join(', ')}`,
291
+ question: '补齐验证报告,还是带原因接受缺失证据风险?',
292
+ options: [
293
+ { id: 'add-reports', label: '补齐报告', command: `devflow test <kind> --slug=${slug} / devflow report self-test --slug=${slug}` },
294
+ { id: 'accept-risk', label: '接受缺失证据风险', command: `devflow verify finalize --slug=${slug} --force --reason="..."` },
295
+ ],
296
+ nextAction: `devflow test <kind> --slug=${slug} / devflow report self-test --slug=${slug}`,
297
+ evidence: missing.map((name) => `reports/${reportDisplayName(name)}`),
298
+ risks: ['缺少验证报告时交付,后续 reviewer 和发布人无法复查实际验证证据。'],
299
+ });
300
+ state.logEvent(st, 'checkpoint.add', { id: cp.id, type: cp.type, phase: cp.phase });
301
+ return cp;
302
+ }
303
+
304
+ function readReportMeta(file) {
305
+ let txt = '';
306
+ try { txt = fsSync.readFileSync(file, 'utf8'); } catch (_) { return {}; }
307
+ if (!txt.startsWith('---')) return {};
308
+ const end = txt.indexOf('\n---', 3);
309
+ if (end < 0) return {};
310
+ const yaml = txt.slice(3, end).trim();
311
+ const out = {};
312
+ for (const line of yaml.split('\n')) {
313
+ const m = /^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(line.trim());
314
+ if (m) out[m[1]] = m[2].trim();
315
+ }
316
+ return out;
317
+ }
318
+
319
+ async function listReportFiles(reportsDir) {
320
+ let entries = [];
321
+ try { entries = await fs.readdir(reportsDir); } catch (_) { return []; }
322
+ return entries
323
+ .filter((name) => name.endsWith('.md'))
324
+ .filter((name) => !['submit.md', 'test-report.md'].includes(name))
325
+ .sort();
326
+ }
327
+
328
+ function reportExists(reportsDir, name) {
329
+ if (fsSync.existsSync(path.join(reportsDir, name))) return true;
330
+ const kind = reportKindFromName(name);
331
+ const aggregateReport = aggregate.readAggregate(reportsDir);
332
+ return aggregateReport.reports.some((r) => r.kind === kind);
333
+ }
334
+
335
+ function reportKindFromName(name) {
336
+ if (name === 'self-test.md') return 'self-test';
337
+ return name.replace(/-test\.md$/, '').replace(/\.md$/, '');
338
+ }
339
+
340
+ function reportDisplayName(name) {
341
+ return `test-report.md#${reportKindFromName(name)}`;
342
+ }
343
+
344
+ module.exports = {
345
+ run,
346
+ _internals: {
347
+ readReportMeta,
348
+ listReportFiles,
349
+ reportExists,
350
+ reportKindFromName,
351
+ reportDisplayName,
352
+ evaluateRiskVerifyGates,
353
+ },
354
+ };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ const log = require('../../utils/log.js');
4
+ const wt = require('../../core/worktree.js');
5
+ const change = require('../../core/change.js');
6
+
7
+ async function run({ sub, positional = [], flags = {}, cwd }) {
8
+ const root = cwd || process.cwd();
9
+ const action = sub || positional.shift();
10
+ switch (action) {
11
+ case 'create': return create(root, positional, flags);
12
+ case 'list': return list(root, positional, flags);
13
+ case 'cleanup': return cleanup(root, positional, flags);
14
+ case undefined:
15
+ case 'help': return help();
16
+ default:
17
+ log.error(`unknown subcommand: ${action}`);
18
+ help();
19
+ process.exitCode = 2;
20
+ }
21
+ }
22
+
23
+ async function create(root, positional, flags) {
24
+ const slug = positional[0] || flags.slug || (await change.getCurrent(root));
25
+ if (!slug) { log.error('usage: devflow worktree create <slug> [--task=N] [--base=branch]'); process.exitCode = 2; return; }
26
+ try {
27
+ const r = await wt.create(root, slug, {
28
+ task: flags.task,
29
+ base: flags.base,
30
+ title: flags.title,
31
+ });
32
+ if (r.created) log.ok(`created worktree: ${path.relative(root, r.path)} branch=${r.branch}`);
33
+ else log.info(`worktree exists: ${path.relative(root, r.path)} branch=${r.branch}`);
34
+ log.dim(`cd ${path.relative(root, r.path)} # then code & commit on this branch`);
35
+ } catch (e) {
36
+ log.error(e.message);
37
+ process.exitCode = 1;
38
+ }
39
+ }
40
+
41
+ async function list(root, positional, flags) {
42
+ const slug = positional[0] || flags.slug;
43
+ const items = await wt.list(root, slug);
44
+ if (!items.length) { log.info('no worktrees recorded'); return; }
45
+ for (const it of items) {
46
+ const tag = it.gitTracked ? 'git✓' : 'orphan';
47
+ log.raw(` ${it.slug.padEnd(26)} ${String(it.task).padEnd(10)} ${it.status.padEnd(11)} ${tag.padEnd(7)} ${path.relative(root, it.path)}`);
48
+ }
49
+ }
50
+
51
+ async function cleanup(root, positional, flags) {
52
+ const slug = positional[0] || flags.slug || (await change.getCurrent(root));
53
+ if (!slug) { log.error('usage: devflow worktree cleanup <slug> [--task=N] [--keep-branch]'); process.exitCode = 2; return; }
54
+ try {
55
+ const r = await wt.cleanup(root, slug, {
56
+ task: flags.task,
57
+ keepBranch: flags['keep-branch'] === true || flags.keepBranch === true,
58
+ force: flags.force === false ? false : true,
59
+ });
60
+ log.ok(`removed worktree (branch ${r.kept ? 'kept' : 'deleted'}: ${r.branch})`);
61
+ } catch (e) {
62
+ log.error(e.message);
63
+ process.exitCode = 1;
64
+ }
65
+ }
66
+
67
+ function help() {
68
+ log.raw(`devflow worktree <create|list|cleanup> [args]
69
+
70
+ create [<slug>] [--task=N] [--base=branch] [--title=...]
71
+ create .devflow-worktrees/<slug>[/task-N]/ + branch devflow/<slug>[/task-N]
72
+ list [<slug>] list worktrees (cross-checked with git worktree)
73
+ cleanup [<slug>] [--task=N] [--keep-branch]
74
+ git worktree remove + delete branch (unless --keep-branch)
75
+ `);
76
+ }
77
+
78
+ module.exports = { run };
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ const log = require('../utils/log.js');
4
+ const { parse } = require('./parse-args.js');
5
+
6
+ const VERSION = require('../../package.json').version;
7
+
8
+ const commands = {
9
+ init: require('./commands/init.js'),
10
+ ingest: require('./commands/ingest.js'),
11
+ new: require('./commands/new.js'),
12
+ propose: require('./commands/propose.js'),
13
+ requirement: require('./commands/requirement.js'),
14
+ design: require('./commands/design.js'),
15
+ plan: require('./commands/plan.js'),
16
+ apply: require('./commands/apply.js'),
17
+ review: require('./commands/review.js'),
18
+ verify: require('./commands/verify.js'),
19
+ deploy: require('./commands/deploy.js'),
20
+ deliver: require('./commands/deliver.js'),
21
+ archive: require('./commands/archive.js'),
22
+ checkpoint: require('./commands/checkpoint.js'),
23
+ status: require('./commands/status.js'),
24
+ doctor: require('./commands/doctor.js'),
25
+ enable: require('./commands/enable.js'),
26
+ disable: require('./commands/disable.js'),
27
+ provider: require('./commands/provider.js'),
28
+ knowledge: require('./commands/knowledge.js'),
29
+ skills: require('./commands/skills.js'),
30
+ test: require('./commands/test.js'),
31
+ report: require('./commands/report.js'),
32
+ flow: require('./commands/flow.js'),
33
+ sync: require('./commands/sync.js'),
34
+ logs: require('./commands/logs.js'),
35
+ switch: require('./commands/switch.js'),
36
+ update: require('./commands/update.js'),
37
+ uninstall: require('./commands/uninstall.js'),
38
+ worktree: require('./commands/worktree.js'),
39
+ help: require('./commands/help.js'),
40
+ };
41
+
42
+ async function main(argv) {
43
+ const args = parse(argv);
44
+
45
+ if (args.flags.version || args.command === 'version') {
46
+ log.raw(`@chenguangyao/devflow-kit ${VERSION} (cmd: devflow / dfk)`);
47
+ return;
48
+ }
49
+ if (!args.command || args.flags.help || args.command === 'help') {
50
+ if (args.command === 'help' && args.positional[0]) {
51
+ return commands.help.run({ topic: args.positional[0] });
52
+ }
53
+ return commands.help.run({});
54
+ }
55
+
56
+ const cmd = commands[args.command];
57
+ if (!cmd) {
58
+ log.error(`unknown command: ${args.command}`);
59
+ log.dim(`run "devflow help" to see available commands`);
60
+ process.exitCode = 2;
61
+ return;
62
+ }
63
+
64
+ await cmd.run({
65
+ sub: args.sub,
66
+ positional: args.positional,
67
+ flags: args.flags,
68
+ cwd: process.cwd(),
69
+ });
70
+ }
71
+
72
+ module.exports = { main };
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+ /**
3
+ * Minimal argv parser. No external dependency.
4
+ *
5
+ * Supports:
6
+ * devflow <command> [subcommand] [positional...] [--flag] [--key=value] [--key value] [-k value]
7
+ *
8
+ * Returns:
9
+ * { command, sub, positional: [...], flags: { ... } }
10
+ */
11
+
12
+ function parse(argv) {
13
+ const result = { command: null, sub: null, positional: [], flags: {} };
14
+ const tokens = argv.slice();
15
+
16
+ if (tokens.length === 0) return result;
17
+
18
+ // First non-flag token is the command
19
+ let i = 0;
20
+ while (i < tokens.length && tokens[i].startsWith('-')) {
21
+ consumeFlag(tokens, i, result.flags);
22
+ i = advance(tokens, i);
23
+ }
24
+ if (i < tokens.length) {
25
+ result.command = tokens[i++];
26
+ }
27
+
28
+ // Optional subcommand: also non-flag (commands like `devflow provider list`, `devflow knowledge query`)
29
+ if (i < tokens.length && !tokens[i].startsWith('-')) {
30
+ // Heuristic: if the command is a known multi-level command, second token is subcommand
31
+ const multiLevel = new Set(['provider', 'knowledge', 'skills', 'switch', 'sync', 'test', 'report', 'worktree', 'logs', 'flow']);
32
+ if (multiLevel.has(result.command)) {
33
+ result.sub = tokens[i++];
34
+ }
35
+ }
36
+
37
+ while (i < tokens.length) {
38
+ const t = tokens[i];
39
+ if (t.startsWith('-')) {
40
+ consumeFlag(tokens, i, result.flags);
41
+ i = advance(tokens, i);
42
+ } else {
43
+ result.positional.push(t);
44
+ i++;
45
+ }
46
+ }
47
+ return result;
48
+ }
49
+
50
+ function consumeFlag(tokens, i, flags) {
51
+ const t = tokens[i];
52
+ let key, val;
53
+ if (t.startsWith('--')) {
54
+ const eq = t.indexOf('=');
55
+ if (eq !== -1) {
56
+ key = t.slice(2, eq);
57
+ val = t.slice(eq + 1);
58
+ } else {
59
+ key = t.slice(2);
60
+ const next = tokens[i + 1];
61
+ if (next !== undefined && !next.startsWith('-')) {
62
+ val = next;
63
+ } else {
64
+ val = true;
65
+ }
66
+ }
67
+ } else {
68
+ key = t.slice(1);
69
+ const next = tokens[i + 1];
70
+ if (next !== undefined && !next.startsWith('-')) {
71
+ val = next;
72
+ } else {
73
+ val = true;
74
+ }
75
+ }
76
+ // boolean negation: --no-foo
77
+ if (typeof val !== 'string' && key.startsWith('no-')) {
78
+ const k = key.slice(3);
79
+ flags[k] = false;
80
+ flags[camel(k)] = false;
81
+ } else {
82
+ flags[key] = val;
83
+ flags[camel(key)] = val;
84
+ }
85
+ }
86
+
87
+ function camel(s) {
88
+ return s.replace(/-([a-zA-Z0-9])/g, (_, c) => c.toUpperCase());
89
+ }
90
+
91
+ function advance(tokens, i) {
92
+ const t = tokens[i];
93
+ if (t.startsWith('--') && t.indexOf('=') !== -1) return i + 1;
94
+ if (t.startsWith('-')) {
95
+ const next = tokens[i + 1];
96
+ if (next !== undefined && !next.startsWith('-')) return i + 2;
97
+ return i + 1;
98
+ }
99
+ return i + 1;
100
+ }
101
+
102
+ module.exports = { parse };