@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.
Files changed (64) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor-plugin/marketplace.json +1 -1
  6. package/.cursor-plugin/plugin.json +1 -1
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/.github/workflows/ci.yml +2 -0
  9. package/CHANGELOG.md +67 -0
  10. package/GEMINI.md +1 -1
  11. package/INSTALL.md +1 -1
  12. package/README.md +1 -1
  13. package/docs/README_en.md +1 -1
  14. package/docs/decision-points.md +8 -0
  15. package/docs/state-machine.md +4 -1
  16. 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
  17. package/gemini-extension.json +1 -1
  18. package/hooks/session-start +2 -2
  19. package/llms.txt +1 -1
  20. package/package.json +1 -1
  21. package/plugin.json +1 -1
  22. package/scripts/guard/checks/_fs-utils.mjs +18 -0
  23. package/scripts/guard/checks/arch-design-light.mjs +39 -0
  24. package/scripts/guard/checks/arch-merged-light.mjs +67 -0
  25. package/scripts/guard/checks/arch-snapshot-light.mjs +30 -0
  26. package/scripts/guard/checks/artifacts-planned.mjs +38 -0
  27. package/scripts/guard/checks/compound-writeback-light.mjs +45 -0
  28. package/scripts/guard/checks/contract-fresh.mjs +48 -4
  29. package/scripts/guard/checks/cross-change-consistency-light.mjs +75 -0
  30. package/scripts/guard/checks/direct-short-path.mjs +52 -0
  31. package/scripts/guard/checks/direct-test-result.mjs +30 -0
  32. package/scripts/guard/checks/execution-plan-ready.mjs +7 -1
  33. package/scripts/guard/checks/execution-reviews-passed-light.mjs +28 -0
  34. package/scripts/guard/checks/gates-probed.mjs +175 -0
  35. package/scripts/guard/checks/lightweight-completion-evidence.mjs +27 -0
  36. package/scripts/guard/checks/specs-merged.mjs +25 -1
  37. package/scripts/guard/checks/test-matrix-complete.mjs +27 -1
  38. package/scripts/guard/checks/test-matrix-ready.mjs +28 -1
  39. package/scripts/guard/checks/test-merged-light.mjs +36 -0
  40. package/scripts/guard/guard.mjs +119 -14
  41. package/scripts/infer-workflow.mjs +35 -4
  42. package/scripts/lib/arch-merge.mjs +20 -4
  43. package/scripts/lib/cmd-execution.mjs +44 -1
  44. package/scripts/lib/cmd-state.mjs +98 -4
  45. package/scripts/lib/execution-plan.mjs +3 -1
  46. package/scripts/lib/state-loader.mjs +55 -0
  47. package/scripts/lib/surface-scan.mjs +156 -0
  48. package/scripts/lib/test-merge.mjs +10 -2
  49. package/scripts/team-flow.mjs +3 -3
  50. package/skills/build-executor/SKILL.md +6 -11
  51. package/skills/build-executor/references/wave-delivery-selfcheck.md +92 -0
  52. package/skills/clean-code/SKILL.md +1 -1
  53. package/skills/code-reviewer/SKILL.md +4 -0
  54. package/skills/contract-builder/SKILL.md +21 -0
  55. package/skills/contract-builder/references/bridging-gate-dry-run.md +89 -0
  56. package/skills/contract-builder/references/freeze-and-errata.md +81 -0
  57. package/skills/jarvis/SKILL.md +2 -0
  58. package/skills/release-archivist/SKILL.md +48 -13
  59. package/skills/session-handoff/SKILL.md +1 -0
  60. package/skills/spec-writer/SKILL.md +3 -0
  61. package/skills/spec-writer/references/facts-referencing.md +64 -0
  62. package/skills/test-strategy/SKILL.md +1 -1
  63. package/skills/workflow-start/SKILL.md +64 -5
  64. package/skills/workflow-start/references/routing-rules.md +4 -4
@@ -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
- 'bridging:approved-for-build': ['artifacts-exist', 'schema-valid', 'contract-fresh', 'dp-gate-passed', 'dp3-approved'],
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
- 'executing:closing': ['tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured', 'test-matrix-complete', 'arch-snapshot', 'delegation-status', 'arch-merged'],
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
- 'exploring:approved-for-build': ['tweak'],
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
- function checkWorkflowAllowed(key, workflow) {
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
- const VALID_WORKFLOWS = ['full', 'hotfix', 'tweak'];
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: `≤4 tasks, only config/doc files, no schema/API/new-module keywords → tweak`,
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
- for (const f of ['architecture.md', 'database.md', 'api.md']) {
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
- const guardResult = spawnSync('node', [guardScript, 'check', changeDir, fromState, toState, '--json', '--workflow', workflow], {
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
- if (plan?.workflow !== 'tweak') {
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');