@xulthekl/team-flow 0.62.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +2 -2
- package/.github/workflows/ci.yml +2 -0
- package/CHANGELOG.md +67 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/docs/README_en.md +1 -1
- package/docs/decision-points.md +8 -0
- package/docs/state-machine.md +4 -1
- package/docs/team-flow /344/275/277/347/224/250/350/257/264/346/230/216/357/274/210/347/240/224/345/217/221/345/233/242/351/230/237/347/211/210/357/274/211.md" +4 -4
- package/gemini-extension.json +1 -1
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/guard/checks/_fs-utils.mjs +18 -0
- package/scripts/guard/checks/arch-design-light.mjs +39 -0
- package/scripts/guard/checks/arch-merged-light.mjs +67 -0
- package/scripts/guard/checks/arch-snapshot-light.mjs +30 -0
- package/scripts/guard/checks/artifacts-planned.mjs +38 -0
- package/scripts/guard/checks/compound-writeback-light.mjs +45 -0
- package/scripts/guard/checks/contract-fresh.mjs +48 -4
- package/scripts/guard/checks/cross-change-consistency-light.mjs +75 -0
- package/scripts/guard/checks/direct-short-path.mjs +52 -0
- package/scripts/guard/checks/direct-test-result.mjs +30 -0
- package/scripts/guard/checks/execution-plan-ready.mjs +7 -1
- package/scripts/guard/checks/execution-reviews-passed-light.mjs +28 -0
- package/scripts/guard/checks/gates-probed.mjs +175 -0
- package/scripts/guard/checks/lightweight-completion-evidence.mjs +27 -0
- package/scripts/guard/checks/specs-merged.mjs +25 -1
- package/scripts/guard/checks/test-matrix-complete.mjs +27 -1
- package/scripts/guard/checks/test-matrix-ready.mjs +28 -1
- package/scripts/guard/checks/test-merged-light.mjs +36 -0
- package/scripts/guard/guard.mjs +119 -14
- package/scripts/infer-workflow.mjs +35 -4
- package/scripts/lib/arch-merge.mjs +20 -4
- package/scripts/lib/cmd-execution.mjs +44 -1
- package/scripts/lib/cmd-state.mjs +98 -4
- package/scripts/lib/execution-plan.mjs +3 -1
- package/scripts/lib/state-loader.mjs +55 -0
- package/scripts/lib/surface-scan.mjs +156 -0
- package/scripts/lib/test-merge.mjs +10 -2
- package/scripts/team-flow.mjs +3 -3
- package/skills/build-executor/SKILL.md +6 -11
- package/skills/build-executor/references/wave-delivery-selfcheck.md +92 -0
- package/skills/clean-code/SKILL.md +1 -1
- package/skills/code-reviewer/SKILL.md +4 -0
- package/skills/contract-builder/SKILL.md +21 -0
- package/skills/contract-builder/references/bridging-gate-dry-run.md +89 -0
- package/skills/contract-builder/references/freeze-and-errata.md +81 -0
- package/skills/jarvis/SKILL.md +2 -0
- package/skills/release-archivist/SKILL.md +48 -13
- package/skills/session-handoff/SKILL.md +1 -0
- package/skills/spec-writer/SKILL.md +3 -0
- package/skills/spec-writer/references/facts-referencing.md +64 -0
- package/skills/test-strategy/SKILL.md +1 -1
- package/skills/workflow-start/SKILL.md +64 -5
- package/skills/workflow-start/references/routing-rules.md +4 -4
package/scripts/guard/guard.mjs
CHANGED
|
@@ -16,10 +16,23 @@ import { checkExecutionReviewsPassed } from './checks/execution-reviews-passed.m
|
|
|
16
16
|
import { checkCompoundCaptured } from './checks/compound-captured.mjs';
|
|
17
17
|
import { checkTestMatrixComplete } from './checks/test-matrix-complete.mjs';
|
|
18
18
|
import { checkTestMatrixReady } from './checks/test-matrix-ready.mjs';
|
|
19
|
+
import { checkGatesProbed } from './checks/gates-probed.mjs';
|
|
19
20
|
import { checkArchReadiness } from './checks/arch-readiness.mjs';
|
|
20
21
|
import { checkArchSnapshot } from './checks/arch-snapshot.mjs';
|
|
21
22
|
import { checkArchMerged } from './checks/arch-merged.mjs';
|
|
22
23
|
import { checkDelegationStatus } from './checks/delegation-status.mjs';
|
|
24
|
+
// v0.64.0 P1(spec-superflow 2.0 实施计划 §4.5):direct/planned/lightweight 短路径与轻量全局态闸门
|
|
25
|
+
import { checkDirectShortPath } from './checks/direct-short-path.mjs';
|
|
26
|
+
import { checkDirectTestResult } from './checks/direct-test-result.mjs';
|
|
27
|
+
import { checkLightweightCompletionEvidence } from './checks/lightweight-completion-evidence.mjs';
|
|
28
|
+
import { checkArtifactsPlanned } from './checks/artifacts-planned.mjs';
|
|
29
|
+
import { checkArchDesignLight } from './checks/arch-design-light.mjs';
|
|
30
|
+
import { checkArchSnapshotLight } from './checks/arch-snapshot-light.mjs';
|
|
31
|
+
import { checkArchMergedLight } from './checks/arch-merged-light.mjs';
|
|
32
|
+
import { checkCompoundWritebackLight } from './checks/compound-writeback-light.mjs';
|
|
33
|
+
import { checkTestMergedLight } from './checks/test-merged-light.mjs';
|
|
34
|
+
import { checkCrossChangeConsistencyLight } from './checks/cross-change-consistency-light.mjs';
|
|
35
|
+
import { checkExecutionReviewsPassedLight } from './checks/execution-reviews-passed-light.mjs';
|
|
23
36
|
|
|
24
37
|
// Transition matrix: <from>:<to> → required check dimensions
|
|
25
38
|
const TRANSITION_CHECKS = {
|
|
@@ -39,7 +52,12 @@ const TRANSITION_CHECKS = {
|
|
|
39
52
|
// 任何能写 dp_3_result 的主体(含夜间替身的 HOLD 落盘)都能让 change 呈现"已批准"假象,
|
|
40
53
|
// 从而在无真实批准的前提下跨过 DP-3 硬门。本维度要求取值以 "approved" 开头
|
|
41
54
|
// (判据实现见 checks/dp3-approved.mjs)。
|
|
42
|
-
|
|
55
|
+
// v0.63.0(workflow-feedback 20260923-013114 S2):**gates-probed 新增**——契约的 G 类闸门
|
|
56
|
+
// 必须在 bridging 期跑过 dry-run(预期 FAIL = RED 基线)并留档,把 design.md R-7 长期存在的
|
|
57
|
+
// 「闸门已实测可跑」文字自证物化为机械证据。检查五件:evidence 存在 / 首行 RED 基线 /
|
|
58
|
+
// CONTRACT_HASH 值新鲜 / 契约 Gate Registry 的 id 逐 id 覆盖 / **段缺失即 FAIL(fail-closed)**。
|
|
59
|
+
// hotfix 走 WORKFLOW_TRANSITION_CHECKS 自有覆盖(下方),tweak 由 skip 回退阀放行。
|
|
60
|
+
'bridging:approved-for-build': ['artifacts-exist', 'schema-valid', 'contract-fresh', 'dp-gate-passed', 'dp3-approved', 'gates-probed'],
|
|
43
61
|
// v0.13 §49:test-matrix-ready 门禁前移——full 模式进入 executing 前强制测试准备度
|
|
44
62
|
//(矩阵存在非空 OR 显式 skip 附理由;legacy 豁免)。hotfix/tweak 沿用 §45.3 豁免,不挂。
|
|
45
63
|
'approved-for-build:executing': ['artifacts-exist', 'contract-fresh', 'dp-gate-passed', 'execution-plan-ready', 'test-matrix-ready'],
|
|
@@ -51,7 +69,15 @@ const TRANSITION_CHECKS = {
|
|
|
51
69
|
// 且 arch-merge 在状态机上无任何锚点(VALID_STATES 无 closed、closing→closed 不存在)。
|
|
52
70
|
// B' 把 arch-merge 前移到本转换**之前**,本维度即其前置条件。
|
|
53
71
|
// hotfix/tweak 沿用既有豁免口径(WORKFLOW_TRANSITION_CHECKS 各自列维度,天然不挂)。
|
|
54
|
-
|
|
72
|
+
// v0.63.0(feedback 20260923-013114 S3):**contract-fresh 新增**——S3「DP-3 后规划制品冻结」
|
|
73
|
+
// 的反查锚。原状 closing 无 contract-fresh,反查实际依赖 execution-plan-ready(validatePlan
|
|
74
|
+
// 比对 plan 内嵌的 artifacts_hash/contract_hash),而该比对可被 `tf execution refresh-hash`
|
|
75
|
+
// 一键刷平(refreshPlanHash 直接改写 plan JSON、revision 不升)→ 把 gate-affecting 变更
|
|
76
|
+
// 伪记为陈述性勘误后,closing 全维 PASS、wave receipt 亦不失效(wave_fingerprint 不含
|
|
77
|
+
// artifacts_hash)→ **伪绿静默通过**。本维度直接比对 state.artifacts_hash 与制品实算值,
|
|
78
|
+
// refresh-hash 无法清屏(它只改 plan JSON,不碰 state)——故为 refresh-hash 之外的独立锚。
|
|
79
|
+
// hotfix/tweak 不挂:二者跳过 spec-writer、无 planning 四件,冻结机制本身 N/A(设计 §3.9)。
|
|
80
|
+
'executing:closing': ['contract-fresh', 'tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured', 'test-matrix-complete', 'arch-snapshot', 'delegation-status', 'arch-merged'],
|
|
55
81
|
|
|
56
82
|
// Debugging side-path
|
|
57
83
|
'executing:debugging': [],
|
|
@@ -99,12 +125,52 @@ const WORKFLOW_TRANSITION_CHECKS = {
|
|
|
99
125
|
|
|
100
126
|
const TRANSITION_WORKFLOW_REQUIREMENTS = {
|
|
101
127
|
'exploring:bridging': ['hotfix'],
|
|
102
|
-
|
|
128
|
+
// v0.64.0(§4.5 注记①):quick/lightweight 短路径 + planned(full 变体)共用该捷径;
|
|
129
|
+
// checkWorkflowAllowed 经 variant 参数放行 planned。
|
|
130
|
+
'exploring:approved-for-build': ['tweak', 'quick', 'lightweight'],
|
|
103
131
|
};
|
|
104
132
|
|
|
105
|
-
|
|
133
|
+
// v0.64.0(§4.5①):direct / lightweight 短路径维度表。
|
|
134
|
+
// 必须显式列出关键跳——未列出的 key 会回落 full 基表(contract-fresh 等对轻路径恒 FAIL)。
|
|
135
|
+
const QUICK_TRANSITION_CHECKS = {
|
|
136
|
+
quick: {
|
|
137
|
+
'exploring:approved-for-build': ['direct-short-path'],
|
|
138
|
+
'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready'],
|
|
139
|
+
'executing:closing': ['direct-short-path', 'direct-test-result', 'test-matrix-complete'],
|
|
140
|
+
'executing:debugging': [],
|
|
141
|
+
'debugging:executing': ['direct-test-result'],
|
|
142
|
+
},
|
|
143
|
+
lightweight: {
|
|
144
|
+
'exploring:approved-for-build': ['direct-short-path'],
|
|
145
|
+
'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready'],
|
|
146
|
+
'executing:closing': ['direct-short-path', 'direct-test-result', 'lightweight-completion-evidence', 'test-matrix-complete'],
|
|
147
|
+
'executing:debugging': [],
|
|
148
|
+
'debugging:executing': ['direct-test-result'],
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// v0.64.0(§4.5②):planned(workflow=full + variant=planned)维度表。
|
|
153
|
+
// 无契约/无 DP-3/无逐波审查;回写四灯 + 最终审查 + 测试矩阵在 closing 一次性收口。
|
|
154
|
+
const PLANNED_TRANSITION_CHECKS = {
|
|
155
|
+
// 注:不挂 schema-valid——该 checker 强制 canonical specs/<capability>/spec.md,
|
|
156
|
+
// 与 planned「specs 按需」冲突(P1 实测修正,同步方案 §4.5);反空壳由 artifacts-planned
|
|
157
|
+
// (proposal≥10 行 + checkbox)+ 最终审查内容契约承担。
|
|
158
|
+
'exploring:approved-for-build': ['artifacts-planned'],
|
|
159
|
+
'approved-for-build:executing': ['execution-plan-ready', 'test-matrix-ready'],
|
|
160
|
+
'executing:closing': [
|
|
161
|
+
'execution-plan-ready', 'execution-reviews-passed-light', 'test-matrix-complete',
|
|
162
|
+
'tests-passing', 'arch-merged-light', 'compound-writeback-light', 'test-merged-light',
|
|
163
|
+
'cross-change-consistency-light', 'delegation-status', 'specs-merged', 'tasks-complete',
|
|
164
|
+
],
|
|
165
|
+
'executing:debugging': [],
|
|
166
|
+
'debugging:executing': ['execution-plan-ready', 'tests-passing'],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
function checkWorkflowAllowed(key, workflow, variant) {
|
|
106
170
|
const allowed = TRANSITION_WORKFLOW_REQUIREMENTS[key];
|
|
107
171
|
if (!allowed || allowed.includes(workflow)) return { pass: true, checks: [] };
|
|
172
|
+
// planned(full 变体)走同一捷径,但其维度由 PLANNED 表管辖(§4.5)
|
|
173
|
+
if (variant === 'planned' && key === 'exploring:approved-for-build') return { pass: true, checks: [] };
|
|
108
174
|
return {
|
|
109
175
|
pass: false,
|
|
110
176
|
checks: [{
|
|
@@ -115,7 +181,19 @@ function checkWorkflowAllowed(key, workflow) {
|
|
|
115
181
|
};
|
|
116
182
|
}
|
|
117
183
|
|
|
118
|
-
function resolveDimensions(key, workflow) {
|
|
184
|
+
function resolveDimensions(key, workflow, opts = {}) {
|
|
185
|
+
const { variant, plannedArch } = opts;
|
|
186
|
+
if (workflow === 'full' && variant === 'planned') {
|
|
187
|
+
let dims = PLANNED_TRANSITION_CHECKS[key] ?? TRANSITION_CHECKS[key];
|
|
188
|
+
// G3:planned_arch=true 时在规划段追加轻架构两维(§4.5 条件行)
|
|
189
|
+
if (plannedArch && (key === 'exploring:approved-for-build' || key === 'approved-for-build:executing')) {
|
|
190
|
+
dims = [...dims, 'arch-design-light', 'arch-snapshot-light'];
|
|
191
|
+
}
|
|
192
|
+
return dims;
|
|
193
|
+
}
|
|
194
|
+
if (workflow === 'quick' || workflow === 'lightweight') {
|
|
195
|
+
return QUICK_TRANSITION_CHECKS[workflow]?.[key] ?? TRANSITION_CHECKS[key];
|
|
196
|
+
}
|
|
119
197
|
return WORKFLOW_TRANSITION_CHECKS[workflow]?.[key] ?? TRANSITION_CHECKS[key];
|
|
120
198
|
}
|
|
121
199
|
|
|
@@ -124,13 +202,16 @@ async function main() {
|
|
|
124
202
|
options: {
|
|
125
203
|
json: { type: 'boolean', default: false },
|
|
126
204
|
workflow: { type: 'string', default: 'full' },
|
|
205
|
+
// v0.64.0 P1:cmd-state 透传的前门变体(§4.5 注记①)
|
|
206
|
+
'workflow-variant': { type: 'string', default: '' },
|
|
207
|
+
'planned-arch': { type: 'string', default: '' },
|
|
127
208
|
},
|
|
128
209
|
allowPositionals: true,
|
|
129
210
|
});
|
|
130
211
|
|
|
131
212
|
const subcommand = positionals[0];
|
|
132
213
|
if (subcommand !== 'check') {
|
|
133
|
-
console.error('Usage: guard.mjs check <change-dir> <from-state> <to-state> [--json] [--workflow <mode>]');
|
|
214
|
+
console.error('Usage: guard.mjs check <change-dir> <from-state> <to-state> [--json] [--workflow <mode>] [--workflow-variant <direct|planned|legacy>] [--planned-arch true]');
|
|
134
215
|
process.exit(2);
|
|
135
216
|
}
|
|
136
217
|
|
|
@@ -139,20 +220,27 @@ async function main() {
|
|
|
139
220
|
const toState = positionals[3];
|
|
140
221
|
const useJson = values.json;
|
|
141
222
|
const workflow = values.workflow;
|
|
223
|
+
const workflowVariant = values['workflow-variant'] || null;
|
|
224
|
+
const plannedArch = values['planned-arch'] === 'true';
|
|
142
225
|
|
|
143
|
-
|
|
226
|
+
// F1 校正(§3.3):加 quick/lightweight——direct/planned 是 variant 不是 workflow 值
|
|
227
|
+
const VALID_WORKFLOWS = ['full', 'hotfix', 'tweak', 'quick', 'lightweight'];
|
|
144
228
|
if (!VALID_WORKFLOWS.includes(workflow)) {
|
|
145
229
|
console.error(`Invalid workflow: ${workflow}. Must be one of: ${VALID_WORKFLOWS.join(', ')}`);
|
|
146
230
|
process.exit(2);
|
|
147
231
|
}
|
|
232
|
+
if (workflowVariant && !['direct', 'planned', 'legacy'].includes(workflowVariant)) {
|
|
233
|
+
console.error(`Invalid workflow variant: ${workflowVariant}. Must be one of: direct, planned, legacy`);
|
|
234
|
+
process.exit(2);
|
|
235
|
+
}
|
|
148
236
|
|
|
149
237
|
if (!changeDir || !fromState || !toState) {
|
|
150
|
-
console.error('Usage: guard.mjs check <change-dir> <from-state> <to-state> [--json]');
|
|
238
|
+
console.error('Usage: guard.mjs check <change-dir> <from-state> <to-state> [--json] [--workflow <mode>] [--workflow-variant <direct|planned|legacy>] [--planned-arch true]');
|
|
151
239
|
process.exit(2);
|
|
152
240
|
}
|
|
153
241
|
|
|
154
242
|
const key = `${fromState}:${toState}`;
|
|
155
|
-
const dimensions = resolveDimensions(key, workflow);
|
|
243
|
+
const dimensions = resolveDimensions(key, workflow, { variant: workflowVariant, plannedArch });
|
|
156
244
|
|
|
157
245
|
if (!dimensions) {
|
|
158
246
|
const valid = Object.keys(TRANSITION_CHECKS).join(', ');
|
|
@@ -162,7 +250,7 @@ async function main() {
|
|
|
162
250
|
process.exit(1);
|
|
163
251
|
}
|
|
164
252
|
|
|
165
|
-
const workflowCheck = checkWorkflowAllowed(key, workflow);
|
|
253
|
+
const workflowCheck = checkWorkflowAllowed(key, workflow, workflowVariant);
|
|
166
254
|
if (!workflowCheck.pass) {
|
|
167
255
|
if (useJson) {
|
|
168
256
|
console.log(JSON.stringify({ pass: false, checks: workflowCheck.checks }, null, 2));
|
|
@@ -184,6 +272,10 @@ async function main() {
|
|
|
184
272
|
process.exit(0);
|
|
185
273
|
}
|
|
186
274
|
|
|
275
|
+
// 变体上下文:需要 variant/plannedArch 的 runner(test-matrix-*、execution-plan-ready、
|
|
276
|
+
// specs-merged、direct-short-path 的轻量分支)从闭包 CTX 读取,保持 runner(dir) 签名兼容。
|
|
277
|
+
const CTX = { workflow, variant: workflowVariant, plannedArch };
|
|
278
|
+
|
|
187
279
|
const CHECK_RUNNERS = {
|
|
188
280
|
'artifacts-exist': (dir) => checkArtifactsExist(dir),
|
|
189
281
|
'schema-valid': async (dir) => (await import('./checks/schema-valid.mjs')).checkSchemaValid(dir),
|
|
@@ -191,19 +283,32 @@ async function main() {
|
|
|
191
283
|
'contract-current': (dir) => checkContractCurrent(dir),
|
|
192
284
|
'tasks-complete': (dir) => checkTasksComplete(dir),
|
|
193
285
|
'tests-passing': (dir) => checkTestsPassing(dir),
|
|
194
|
-
'specs-merged': (dir) => checkSpecsMerged(dir),
|
|
286
|
+
'specs-merged': (dir) => checkSpecsMerged(dir, CTX),
|
|
195
287
|
'dp-gate-passed': (dir) => checkDpGate(dir, fromState, toState),
|
|
196
288
|
'dp3-approved': (dir) => checkDp3Approved(dir),
|
|
197
|
-
'execution-plan-ready': (dir) => checkExecutionPlanReady(dir),
|
|
289
|
+
'execution-plan-ready': (dir) => checkExecutionPlanReady(dir, CTX),
|
|
198
290
|
'execution-reviews-passed': (dir) => checkExecutionReviewsPassed(dir),
|
|
199
291
|
'arch-design': (dir) => checkArchDesign(dir),
|
|
200
292
|
'compound-captured': (dir) => checkCompoundCaptured(dir),
|
|
201
|
-
'test-matrix-complete': (dir) => checkTestMatrixComplete(dir),
|
|
202
|
-
'test-matrix-ready': (dir) => checkTestMatrixReady(dir),
|
|
293
|
+
'test-matrix-complete': (dir) => checkTestMatrixComplete(dir, CTX),
|
|
294
|
+
'test-matrix-ready': (dir) => checkTestMatrixReady(dir, CTX),
|
|
295
|
+
'gates-probed': (dir) => checkGatesProbed(dir),
|
|
203
296
|
'arch-readiness': (dir) => checkArchReadiness(dir),
|
|
204
297
|
'arch-snapshot': (dir) => checkArchSnapshot(dir),
|
|
205
298
|
'arch-merged': (dir) => checkArchMerged(dir),
|
|
206
299
|
'delegation-status': (dir) => checkDelegationStatus(dir),
|
|
300
|
+
// v0.64.0 P1(§3.3 清单,11 个新 runner)
|
|
301
|
+
'direct-short-path': (dir) => checkDirectShortPath(dir, CTX),
|
|
302
|
+
'direct-test-result': (dir) => checkDirectTestResult(dir),
|
|
303
|
+
'lightweight-completion-evidence': (dir) => checkLightweightCompletionEvidence(dir),
|
|
304
|
+
'artifacts-planned': (dir) => checkArtifactsPlanned(dir),
|
|
305
|
+
'arch-design-light': (dir) => checkArchDesignLight(dir),
|
|
306
|
+
'arch-snapshot-light': (dir) => checkArchSnapshotLight(dir),
|
|
307
|
+
'arch-merged-light': (dir) => checkArchMergedLight(dir),
|
|
308
|
+
'compound-writeback-light': (dir) => checkCompoundWritebackLight(dir),
|
|
309
|
+
'test-merged-light': (dir) => checkTestMergedLight(dir),
|
|
310
|
+
'cross-change-consistency-light': (dir) => checkCrossChangeConsistencyLight(dir),
|
|
311
|
+
'execution-reviews-passed-light': (dir) => checkExecutionReviewsPassedLight(dir),
|
|
207
312
|
};
|
|
208
313
|
|
|
209
314
|
const checks = [];
|
|
@@ -57,11 +57,14 @@ function inferMode(changeDir) {
|
|
|
57
57
|
const state = readState(changeDir);
|
|
58
58
|
|
|
59
59
|
// Explicit override: honor any non-auto, non-null workflow value
|
|
60
|
+
// v0.64.0(§3.2 F1):VALID_WORKFLOWS 扩为五值,quick/lightweight 亦为合法显式档位。
|
|
60
61
|
if (state.workflow && state.workflow !== 'auto') {
|
|
61
|
-
const valid = ['hotfix', 'tweak', 'full'];
|
|
62
|
+
const valid = ['hotfix', 'tweak', 'full', 'quick', 'lightweight'];
|
|
62
63
|
if (valid.includes(state.workflow)) {
|
|
63
64
|
return {
|
|
64
65
|
mode: state.workflow,
|
|
66
|
+
// 双通道(§3.2):显式 workflow 已定时不再建议前门(显式 > 推断)
|
|
67
|
+
suggested_path: null,
|
|
65
68
|
explicit: true,
|
|
66
69
|
reason: `workflow explicitly set to '${state.workflow}' in .team-flow.yaml; skipping auto-detection`,
|
|
67
70
|
};
|
|
@@ -85,6 +88,20 @@ function inferMode(changeDir) {
|
|
|
85
88
|
'新增 capability', 'new capability',
|
|
86
89
|
]);
|
|
87
90
|
|
|
91
|
+
// v0.64.0(§3.2 双通道):前门建议信号。
|
|
92
|
+
// hasSchemaChange 已含 api/schema/接口 关键词(F-06 去重:不新建同词判定);
|
|
93
|
+
// arch/aggregate/cross_module/uncertainty 为净新增信号(现状 infer 无)。
|
|
94
|
+
const hasArchAggregate = hasKeyword(combined, [
|
|
95
|
+
'聚合', 'aggregate', 'architecture', '架构', '限界上下文', 'bounded context', 'cqrs',
|
|
96
|
+
]);
|
|
97
|
+
const hasCrossModule = hasKeyword(combined, [
|
|
98
|
+
'cross-module', '跨模块', '跨服务', 'multi-repo', '多仓',
|
|
99
|
+
]);
|
|
100
|
+
// 架构 surface 信号 → 建议 planned(轻架构);不再直接推 full(D1)
|
|
101
|
+
const archPathSignal = hasSchemaChange || hasArchAggregate;
|
|
102
|
+
// 跨模块 / 高不确定性 → full(D1:full 只留重场景)
|
|
103
|
+
const fullSignal = hasCrossModule || (taskCount > 4 && fileCount > 6);
|
|
104
|
+
|
|
88
105
|
const allExts = files.map(f => {
|
|
89
106
|
const parts = f.split('.');
|
|
90
107
|
return parts[parts.length - 1].toLowerCase();
|
|
@@ -96,6 +113,7 @@ function inferMode(changeDir) {
|
|
|
96
113
|
if (taskCount === 0 && fileCount === 0) {
|
|
97
114
|
return {
|
|
98
115
|
mode: 'full',
|
|
116
|
+
suggested_path: null,
|
|
99
117
|
explicit: false,
|
|
100
118
|
reason: 'no planning artifacts detected → full (safe default)',
|
|
101
119
|
};
|
|
@@ -105,8 +123,9 @@ function inferMode(changeDir) {
|
|
|
105
123
|
if (taskCount <= 2 && fileCount <= 2 && !hasSchemaChange && !hasNewModule) {
|
|
106
124
|
return {
|
|
107
125
|
mode: 'hotfix',
|
|
126
|
+
suggested_path: 'direct',
|
|
108
127
|
explicit: false,
|
|
109
|
-
reason: `≤2 tasks, ≤2 files, no schema/API/new-module keywords → hotfix`,
|
|
128
|
+
reason: `≤2 tasks, ≤2 files, no schema/API/new-module keywords → hotfix (trivial → suggest path direct; hotfix retained when reusing an existing contract)`,
|
|
110
129
|
};
|
|
111
130
|
}
|
|
112
131
|
|
|
@@ -114,16 +133,28 @@ function inferMode(changeDir) {
|
|
|
114
133
|
if (taskCount <= 4 && configDocOnly && !hasSchemaChange && !hasNewModule) {
|
|
115
134
|
return {
|
|
116
135
|
mode: 'tweak',
|
|
136
|
+
suggested_path: 'direct',
|
|
137
|
+
explicit: false,
|
|
138
|
+
reason: `≤4 tasks, only config/doc files, no schema/API/new-module keywords → tweak (trivial → suggest path direct)`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Arch/API/DB/聚合信号 → 建议 planned(轻架构)而非 full(§3.2 D1 修正)
|
|
143
|
+
if (archPathSignal && !fullSignal) {
|
|
144
|
+
return {
|
|
145
|
+
mode: 'full',
|
|
146
|
+
suggested_path: 'planned',
|
|
117
147
|
explicit: false,
|
|
118
|
-
reason:
|
|
148
|
+
reason: `${taskCount} tasks, ${fileCount} files${hasSchemaChange ? ', schema/API change detected' : ''}${hasArchAggregate ? ', arch/aggregate keywords detected' : ''} → suggest path planned (light architecture); front door: workflow start --path planned (new change) or tf state upgrade <dir> planned (existing change)`,
|
|
119
149
|
};
|
|
120
150
|
}
|
|
121
151
|
|
|
122
152
|
// Default
|
|
123
153
|
return {
|
|
124
154
|
mode: 'full',
|
|
155
|
+
suggested_path: null,
|
|
125
156
|
explicit: false,
|
|
126
|
-
reason: `${taskCount} tasks, ${fileCount} files${codeFileCount > 0 ? ` (${codeFileCount} code files)` : ''}${hasSchemaChange ? ', schema/API change detected' : ''}${hasNewModule ? ', new module detected' : ''} → full`,
|
|
157
|
+
reason: `${taskCount} tasks, ${fileCount} files${codeFileCount > 0 ? ` (${codeFileCount} code files)` : ''}${hasSchemaChange ? ', schema/API change detected' : ''}${hasNewModule ? ', new module detected' : ''}${hasCrossModule ? ', cross-module detected' : ''} → full`,
|
|
127
158
|
};
|
|
128
159
|
}
|
|
129
160
|
|
|
@@ -584,16 +584,29 @@ function updateIndex(globalArchDir, changeName) {
|
|
|
584
584
|
|
|
585
585
|
/* ============ Step 1: 预检 ============ */
|
|
586
586
|
|
|
587
|
-
function preCheck(changeDir, archDir, projectRoot) {
|
|
587
|
+
function preCheck(changeDir, archDir, projectRoot, light = false) {
|
|
588
588
|
const conflicts = [];
|
|
589
589
|
if (!existsSync(archDir)) {
|
|
590
590
|
return { pass: false, conflicts: ['architecture/ directory not found in change'] };
|
|
591
591
|
}
|
|
592
|
-
|
|
592
|
+
// v0.64.0(§4 D8,--light):light 模式持久源只要求 architecture.md(聚合注册行 +
|
|
593
|
+
// 演进日志段 = marker 区投影唯一输入);api.md/database.md 触及时才要求(调用方按
|
|
594
|
+
// touched surface 判定,此处仅对已存在文件做非空校验——缺失即该 sink 无增量,
|
|
595
|
+
// 生成式重建会安全保留旧表而非抹除)。full 模式维持三件套硬要求。
|
|
596
|
+
const required = light ? ['architecture.md'] : ['architecture.md', 'database.md', 'api.md'];
|
|
597
|
+
for (const f of required) {
|
|
593
598
|
const fp = join(archDir, f);
|
|
594
599
|
if (!existsSync(fp)) conflicts.push(`architecture/${f} missing`);
|
|
595
600
|
else if (readFileSync(fp, 'utf-8').trim().length === 0) conflicts.push(`architecture/${f} empty`);
|
|
596
601
|
}
|
|
602
|
+
if (light) {
|
|
603
|
+
for (const f of ['database.md', 'api.md']) {
|
|
604
|
+
const fp = join(archDir, f);
|
|
605
|
+
if (existsSync(fp) && readFileSync(fp, 'utf-8').trim().length === 0) {
|
|
606
|
+
conflicts.push(`architecture/${f} empty (present but blank — delete it or fill the delta)`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
597
610
|
return { pass: conflicts.length === 0, conflicts };
|
|
598
611
|
}
|
|
599
612
|
|
|
@@ -705,6 +718,7 @@ export function run(args = {}) {
|
|
|
705
718
|
for (let i = 0; i < args.length; i++) {
|
|
706
719
|
if (args[i] === '--project-root' && args[i + 1] && !args[i + 1].startsWith('--')) parsed.projectRoot = args[++i];
|
|
707
720
|
else if (args[i] === '--dry-run') parsed.dryRun = true;
|
|
721
|
+
else if (args[i] === '--light') parsed.light = true;
|
|
708
722
|
else if (!args[i].startsWith('--')) parsed._.push(args[i]);
|
|
709
723
|
}
|
|
710
724
|
args = parsed;
|
|
@@ -712,8 +726,9 @@ export function run(args = {}) {
|
|
|
712
726
|
|
|
713
727
|
const changeDir = args._?.[0] || args.changeDir;
|
|
714
728
|
const dryRun = args.dryRun || false;
|
|
729
|
+
const light = args.light || false; // v0.64.0 §4 D8:planned 轻回写入口
|
|
715
730
|
if (!changeDir) {
|
|
716
|
-
console.error('Usage: tf arch-merge <change-dir> [--project-root <path>] [--dry-run]');
|
|
731
|
+
console.error('Usage: tf arch-merge <change-dir> [--project-root <path>] [--dry-run] [--light]');
|
|
717
732
|
process.exit(1);
|
|
718
733
|
}
|
|
719
734
|
|
|
@@ -733,6 +748,7 @@ export function run(args = {}) {
|
|
|
733
748
|
console.log(` Source: ${archDir}`);
|
|
734
749
|
console.log(` Target: ${globalArchDir}`);
|
|
735
750
|
if (dryRun) console.log(` Mode: DRY-RUN`);
|
|
751
|
+
if (light) console.log(` Mode: LIGHT(minimal persistent source: architecture.md + present deltas;同 sink 同生成器)`);
|
|
736
752
|
|
|
737
753
|
// 写锁(硬门禁 3)
|
|
738
754
|
const lockPath = acquireLock(projectRoot);
|
|
@@ -740,7 +756,7 @@ export function run(args = {}) {
|
|
|
740
756
|
if (!existsSync(globalArchDir)) makeDir(globalArchDir);
|
|
741
757
|
|
|
742
758
|
// Step 1: 预检
|
|
743
|
-
const preCheckResult = preCheck(changeDir, archDir, projectRoot);
|
|
759
|
+
const preCheckResult = preCheck(changeDir, archDir, projectRoot, light);
|
|
744
760
|
if (!preCheckResult.pass) {
|
|
745
761
|
console.error(' Pre-check FAILED:');
|
|
746
762
|
for (const c of preCheckResult.conflicts) console.error(` - ${c}`);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
2
4
|
import { createPlan, describeWaves, EXECUTION_MODES, readPlan, recordReview, refreshPlanHash, validatePlan, writePlan } from './execution-plan.mjs';
|
|
3
5
|
import {
|
|
4
6
|
createRecommendationReceipt,
|
|
@@ -6,6 +8,7 @@ import {
|
|
|
6
8
|
writeRecommendationReceipt,
|
|
7
9
|
} from './execution-recommendation.mjs';
|
|
8
10
|
import { readState, writeState } from './state-loader.mjs';
|
|
11
|
+
import { parseTaskLine } from './md-normalize.mjs';
|
|
9
12
|
|
|
10
13
|
const SUBCOMMANDS = ['recommend', 'plan', 'show', 'revise', 'review', 'refresh-hash'];
|
|
11
14
|
|
|
@@ -17,6 +20,8 @@ export async function run(args) {
|
|
|
17
20
|
reason: { type: 'string' },
|
|
18
21
|
wave: { type: 'string', multiple: true },
|
|
19
22
|
confirm: { type: 'boolean', default: false },
|
|
23
|
+
// v0.64.0(实施计划 D7):planned 前门的 tasks.md 派生通道——跳过 recommend/receipt 仪式
|
|
24
|
+
derive: { type: 'boolean', default: false },
|
|
20
25
|
'acknowledge-recommendation': { type: 'boolean', default: false },
|
|
21
26
|
base: { type: 'string' },
|
|
22
27
|
head: { type: 'string' },
|
|
@@ -58,10 +63,47 @@ export async function run(args) {
|
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
// v0.64.0(D7 派生):tasks.md checkbox → 单 wave(serial),仅 planned --derive 通道使用
|
|
67
|
+
function deriveWavesFromTasks(changeDir) {
|
|
68
|
+
const tasksPath = join(changeDir, 'tasks.md');
|
|
69
|
+
if (!existsSync(tasksPath)) throw new Error('--derive requires tasks.md (planned front door: proposal.md + tasks.md)');
|
|
70
|
+
const tasks = readFileSync(tasksPath, 'utf-8')
|
|
71
|
+
.split('\n')
|
|
72
|
+
.map(line => parseTaskLine(line)?.text)
|
|
73
|
+
.map(t => (typeof t === 'string' ? t.trim() : ''))
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
if (tasks.length === 0) throw new Error('tasks.md has no checkboxes to derive a plan from');
|
|
76
|
+
return [{ id: 'w1', strategy: 'serial', tasks, depends_on: [] }];
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
function createAndPrintPlan(changeDir, values, revise) {
|
|
62
|
-
requireMode(values.mode);
|
|
63
80
|
requireOption(values.reason, '--reason');
|
|
64
81
|
requireSafeReason(values.reason);
|
|
82
|
+
// planned 派生通道(v0.64.0 D7):无 recommendation/receipt/selection——validatePlan 对
|
|
83
|
+
// workflow_variant=planned 豁免三件要求;state.execution_* 由 writeExecutionSummary 同步。
|
|
84
|
+
if (values.derive) {
|
|
85
|
+
if (revise) throw new Error('--derive creates the initial plan; use "tf execution revise" for revisions');
|
|
86
|
+
const st = readState(changeDir);
|
|
87
|
+
if (st.workflow_variant !== 'planned') {
|
|
88
|
+
throw new Error('--derive is only valid for the planned front door (workflow_variant=planned)');
|
|
89
|
+
}
|
|
90
|
+
if (!values.confirm) throw new Error('--derive requires --confirm');
|
|
91
|
+
const mode = values.mode || 'inline';
|
|
92
|
+
requireMode(mode);
|
|
93
|
+
const waves = values.wave?.length ? parseWaves(values.wave) : deriveWavesFromTasks(changeDir);
|
|
94
|
+
const plan = createPlan(changeDir, {
|
|
95
|
+
mode,
|
|
96
|
+
source: 'tasks-derived',
|
|
97
|
+
rationale: values.reason,
|
|
98
|
+
waves,
|
|
99
|
+
});
|
|
100
|
+
const saved = writePlan(changeDir, plan);
|
|
101
|
+
writeExecutionSummary(changeDir, saved);
|
|
102
|
+
print(values.json, { ok: true, plan: saved },
|
|
103
|
+
`Execution plan revision ${saved.revision} recorded (${saved.mode}, derived from tasks.md).`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
requireMode(values.mode);
|
|
65
107
|
const waves = parseWaves(values.wave);
|
|
66
108
|
const existing = readPlan(changeDir);
|
|
67
109
|
if (revise) {
|
|
@@ -236,6 +278,7 @@ function printHelp() {
|
|
|
236
278
|
console.log(`Usage:
|
|
237
279
|
tf execution recommend <dir> [--wave <id>:<strategy>:<task,...>[:<depends-on,...>]] [--json]
|
|
238
280
|
tf execution plan <dir> --mode <mode> --confirm --reason <text> --wave <id>:<strategy>:<task,...>[:<depends-on,...>] [--acknowledge-recommendation]
|
|
281
|
+
tf execution plan <dir> --derive --confirm --reason <text> [--mode <mode>] # planned only: derive single wave from tasks.md
|
|
239
282
|
tf execution show <dir> [--json]
|
|
240
283
|
tf execution revise <dir> --mode sdd --confirm --reason <text> --wave <id>:<strategy>:<task,...>[:<depends-on,...>] [--acknowledge-recommendation]
|
|
241
284
|
tf execution review <dir> --wave <id> --base <sha> --head <sha> --report <path> --verdict pass|fail [--repo <path>] [--tests-total N --tests-passed N --tests-failed N]
|
|
@@ -17,6 +17,13 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
17
17
|
// schema_version 不可设置(仅 init 打戳,防止伪造豁免身份,v0.13 §48.1)。
|
|
18
18
|
const SETTABLE_FIELDS = [
|
|
19
19
|
'workflow', 'batches_completed', 'spec_merged',
|
|
20
|
+
// v0.64.0 P0(实施计划 §3.3):前门双维度。三环齐全(本列表 + state-loader
|
|
21
|
+
// BUILTIN_DEFAULTS + writeState 序列化分支),缺一即「回显成功却零写入」。
|
|
22
|
+
// workflow_variant 走通用通道仅 exploring 态可写(下方 set 分支强制);
|
|
23
|
+
// 升档豁免 = tf state upgrade 专用子命令(不经本通道),写入打
|
|
24
|
+
// variant_source=upgrade + variant_direction=up,guard 据此 fail-closed。
|
|
25
|
+
'workflow_variant', 'variant_source', 'variant_direction',
|
|
26
|
+
'planned_arch', 'model_profile',
|
|
20
27
|
'dp_0_decisions', 'dp_0_confirmed', 'dp_0_timestamp', 'dp_0_result',
|
|
21
28
|
// v0.59.0(P4 实测):dp_{1,2,3,5,6,7}_decisions / _confirmed 共 12 个字段已移除——
|
|
22
29
|
// 它们虽在旧白名单内,但 writeState 无对应序列化分支,tf state set 会**回显成功却零写入**
|
|
@@ -45,6 +52,12 @@ const SETTABLE_FIELDS = [
|
|
|
45
52
|
'tasks_skipped', 'tasks_skip_reason',
|
|
46
53
|
// Arch merge gate (v0.53.0 §110.2:arch-merged guard 维度的显式跳过键,须附理由)
|
|
47
54
|
'arch_merge_skipped', 'arch_merge_skip_reason',
|
|
55
|
+
// Bridging gates dry-run gate (v0.63.0;feedback 20260923-013114 S2)
|
|
56
|
+
// 三键必须与 state-loader BUILTIN_DEFAULTS + writeState 序列化分支同步注册,
|
|
57
|
+
// 缺任一环即「回显成功却零写入」(v0.59.0 同型教训,见本文件 :20-24 注释)。
|
|
58
|
+
'gates_probed_skipped', 'gates_probed_skip_reason', 'gates_probed_na',
|
|
59
|
+
// v0.64.0(§4 arch-design-light):轻架构说明显式跳过键(须附理由)
|
|
60
|
+
'arch_design_light_skipped', 'arch_design_light_skip_reason',
|
|
48
61
|
];
|
|
49
62
|
|
|
50
63
|
export async function run(args) {
|
|
@@ -62,7 +75,7 @@ export async function run(args) {
|
|
|
62
75
|
|
|
63
76
|
if (!changeDir) {
|
|
64
77
|
console.error('Usage: tf state <subcommand> <change-dir> [arg]');
|
|
65
|
-
console.error('Subcommands: init, check, transition, get, rebuild, set');
|
|
78
|
+
console.error('Subcommands: init, check, transition, get, rebuild, set, upgrade');
|
|
66
79
|
process.exit(2);
|
|
67
80
|
}
|
|
68
81
|
|
|
@@ -76,7 +89,7 @@ export async function run(args) {
|
|
|
76
89
|
// Unknown subcommand: report a usage error (exit 2) BEFORE the BUG-B
|
|
77
90
|
// state-file existence check, so a bad subcommand is not masked by the
|
|
78
91
|
// "No state file" error (which would return exit 1 instead of exit 2).
|
|
79
|
-
const KNOWN_SUBS = ['init', 'check', 'transition', 'get', 'rebuild', 'set'];
|
|
92
|
+
const KNOWN_SUBS = ['init', 'check', 'transition', 'get', 'rebuild', 'set', 'upgrade'];
|
|
80
93
|
if (!KNOWN_SUBS.includes(sub)) {
|
|
81
94
|
console.error(`Unknown subcommand: ${sub}. Valid: ${KNOWN_SUBS.join(', ')}`);
|
|
82
95
|
process.exit(2);
|
|
@@ -108,6 +121,12 @@ export async function run(args) {
|
|
|
108
121
|
const state = readState(changeDir);
|
|
109
122
|
if (!stateFileExisted) {
|
|
110
123
|
state.schema_version = 1;
|
|
124
|
+
// v0.64.0 P0(§4 扫描基线):init 打戳 base_sha(架构 surface 扫描的 git 基线)。
|
|
125
|
+
// 非 git 环境 / 尚无提交 → 保持 null,扫描时按 §4 退化参照(origin → FAIL)。
|
|
126
|
+
try {
|
|
127
|
+
const rev = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: changeDir, encoding: 'utf-8', timeout: 3_000 });
|
|
128
|
+
if (rev.status === 0 && rev.stdout?.trim()) state.base_sha = rev.stdout.trim();
|
|
129
|
+
} catch { /* 保持 null */ }
|
|
111
130
|
}
|
|
112
131
|
state.artifacts_hash = hash;
|
|
113
132
|
state.contract_hash = ch;
|
|
@@ -166,7 +185,16 @@ export async function run(args) {
|
|
|
166
185
|
const rawWorkflow = state.workflow || 'full';
|
|
167
186
|
// Normalize: guard only accepts full/hotfix/tweak, not "auto"
|
|
168
187
|
const workflow = rawWorkflow === 'auto' ? 'full' : rawWorkflow;
|
|
169
|
-
|
|
188
|
+
// v0.64.0 P1(§3.3/§4.5 注记①):透传前门变体——guard 据此选 quick/planned 维度表。
|
|
189
|
+
// 这是 workflow_variant 到达 resolveDimensions 的唯一管道(workflow-start 是 skill,不是调用方)。
|
|
190
|
+
const guardArgs = [guardScript, 'check', changeDir, fromState, toState, '--json', '--workflow', workflow];
|
|
191
|
+
if (state.workflow_variant != null && state.workflow_variant !== 'null') {
|
|
192
|
+
guardArgs.push('--workflow-variant', String(state.workflow_variant));
|
|
193
|
+
}
|
|
194
|
+
if (state.planned_arch === true || state.planned_arch === 'true') {
|
|
195
|
+
guardArgs.push('--planned-arch', 'true');
|
|
196
|
+
}
|
|
197
|
+
const guardResult = spawnSync('node', guardArgs, {
|
|
170
198
|
cwd: join(__dirname, '..', '..'),
|
|
171
199
|
timeout: 10_000,
|
|
172
200
|
});
|
|
@@ -278,6 +306,28 @@ export async function run(args) {
|
|
|
278
306
|
}
|
|
279
307
|
process.exit(1);
|
|
280
308
|
}
|
|
309
|
+
// v0.64.0 P0 写限(§3.3/§5.3):workflow_variant 通用通道仅 exploring 态可写,
|
|
310
|
+
// 防关门段自降档清屏;升档唯一豁免 = tf state upgrade 专用子命令(不走本通道)。
|
|
311
|
+
if (field === 'workflow_variant') {
|
|
312
|
+
const cur = readState(changeDir);
|
|
313
|
+
if (cur.state !== 'exploring') {
|
|
314
|
+
console.error(
|
|
315
|
+
`⛔ 'workflow_variant' 仅可在 exploring 态通过本通道写入(当前: ${cur.state})。\n` +
|
|
316
|
+
` 升档唯一机械入口: tf state upgrade <change-dir> <planned|full>(原子改写 variant_source=upgrade + 回退 approved-for-build + 重算 hash;降档拒绝)`
|
|
317
|
+
);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
if (value !== 'null' && !['direct', 'planned', 'legacy'].includes(value)) {
|
|
321
|
+
console.error(`⛔ Invalid workflow_variant: '${value}'. Must be one of: direct, planned, legacy, null`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (field === 'model_profile') {
|
|
326
|
+
if (!['mechanical', 'standard', 'strong', 'review'].includes(value)) {
|
|
327
|
+
console.error(`⛔ Invalid model_profile: '${value}'. Must be one of: mechanical, standard, strong, review`);
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
281
331
|
updateField(changeDir, field, value);
|
|
282
332
|
if (values.json) {
|
|
283
333
|
console.log(JSON.stringify({ ok: true, field, value }));
|
|
@@ -286,8 +336,52 @@ export async function run(args) {
|
|
|
286
336
|
}
|
|
287
337
|
break;
|
|
288
338
|
}
|
|
339
|
+
case 'upgrade': {
|
|
340
|
+
// v0.64.0(实施计划 §5.3 D9,升档唯一机械入口):
|
|
341
|
+
// tf state upgrade <change-dir> <planned|full>
|
|
342
|
+
// 校验方向向上 → 原子写 variant_source=upgrade + variant_direction=up →
|
|
343
|
+
// state 回退 approved-for-build(重跑目标档闸门)→ 重算 artifacts_hash(B-10)。
|
|
344
|
+
// 保留:磁盘代码/产物、test_result + test_evidence_path、test_matrix_hash。
|
|
345
|
+
const toVariant = arg;
|
|
346
|
+
if (!['planned', 'full'].includes(toVariant)) {
|
|
347
|
+
console.error('Usage: tf state upgrade <change-dir> <planned|full> (downgrades are forbidden)');
|
|
348
|
+
process.exit(2);
|
|
349
|
+
}
|
|
350
|
+
const ORDER = { direct: 0, planned: 1 };
|
|
351
|
+
const cur = readState(changeDir);
|
|
352
|
+
const from = cur.workflow_variant;
|
|
353
|
+
// 档位序:direct(0) < planned(1) < full/legacy(2)。upgrade full = 置 legacy + workflow full
|
|
354
|
+
//(variant 枚举无 'full',full 档由 workflow=full + legacy 全量仪式表达,方案 §5.3)。
|
|
355
|
+
const fromOrder = from === 'legacy' || from == null ? 2 : (ORDER[from] ?? 0);
|
|
356
|
+
const targetOrder = toVariant === 'full' ? 2 : ORDER[toVariant];
|
|
357
|
+
if (fromOrder >= targetOrder) {
|
|
358
|
+
console.error(`⛔ Upgrade ${from ?? 'null'} -> ${toVariant} is not upward (downgrades are forbidden)`);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
cur.workflow_variant = toVariant === 'full' ? 'legacy' : toVariant;
|
|
362
|
+
cur.variant_source = 'upgrade';
|
|
363
|
+
cur.variant_direction = 'up';
|
|
364
|
+
if (toVariant === 'full') cur.workflow = 'full';
|
|
365
|
+
// 已过规划段则回退 approved-for-build 重跑目标档闸门;exploring 态无需回退
|
|
366
|
+
const ROLLBACK_STATES = ['specifying', 'bridging', 'executing', 'debugging', 'closing'];
|
|
367
|
+
if (ROLLBACK_STATES.includes(cur.state)) {
|
|
368
|
+
const prev = cur.state;
|
|
369
|
+
cur.state = 'approved-for-build';
|
|
370
|
+
cur.last_transition_from = prev;
|
|
371
|
+
cur.last_transition_to = 'approved-for-build';
|
|
372
|
+
cur.last_transition = new Date().toISOString();
|
|
373
|
+
}
|
|
374
|
+
cur.artifacts_hash = computeArtifactsHash(changeDir); // B-10 重算,防 doctor/check 恒报不一致
|
|
375
|
+
writeState(changeDir, cur);
|
|
376
|
+
if (values.json) {
|
|
377
|
+
console.log(JSON.stringify({ ok: true, from: from ?? 'null', to: cur.workflow_variant, state: cur.state, variant_source: 'upgrade' }));
|
|
378
|
+
} else {
|
|
379
|
+
console.log(`✅ Upgraded workflow_variant: ${from ?? 'null'} -> ${cur.workflow_variant} (source=upgrade, state=${cur.state}, artifacts_hash recomputed)`);
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
289
383
|
default:
|
|
290
|
-
console.error(`Unknown subcommand: ${sub}. Valid: init, check, transition, get, rebuild, set`);
|
|
384
|
+
console.error(`Unknown subcommand: ${sub}. Valid: init, check, transition, get, rebuild, set, upgrade`);
|
|
291
385
|
process.exit(2);
|
|
292
386
|
}
|
|
293
387
|
}
|
|
@@ -107,7 +107,9 @@ export function validatePlan(changeDir, plan) {
|
|
|
107
107
|
if (state.revision != null && plan?.revision !== state.revision) {
|
|
108
108
|
failures.push('execution plan revision does not match state');
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
// v0.64.0(§4.5 注记⑤配套):planned(workflow=full+variant=planned)无 recommendation/
|
|
111
|
+
// selection 仪式——plan 由 tasks.md 派生,不走 execution-recommendation 流程;豁免三件要求。
|
|
112
|
+
if (plan?.workflow !== 'tweak' && state.workflow_variant !== 'planned') {
|
|
111
113
|
if (plan?.recommendation === undefined) failures.push('execution plan recommendation is required for full/hotfix');
|
|
112
114
|
if (plan?.recommendation_receipt === undefined) failures.push('execution plan recommendation receipt is required for full/hotfix');
|
|
113
115
|
if (plan?.selection === undefined) failures.push('execution plan selection is required for full/hotfix');
|