@xulthekl/team-flow 0.63.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 (53) 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 +28 -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/state-machine.md +4 -1
  15. 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" +2 -2
  16. package/gemini-extension.json +1 -1
  17. package/hooks/session-start +2 -2
  18. package/llms.txt +1 -1
  19. package/package.json +1 -1
  20. package/plugin.json +1 -1
  21. package/scripts/guard/checks/_fs-utils.mjs +18 -0
  22. package/scripts/guard/checks/arch-design-light.mjs +39 -0
  23. package/scripts/guard/checks/arch-merged-light.mjs +67 -0
  24. package/scripts/guard/checks/arch-snapshot-light.mjs +30 -0
  25. package/scripts/guard/checks/artifacts-planned.mjs +38 -0
  26. package/scripts/guard/checks/compound-writeback-light.mjs +45 -0
  27. package/scripts/guard/checks/cross-change-consistency-light.mjs +75 -0
  28. package/scripts/guard/checks/direct-short-path.mjs +52 -0
  29. package/scripts/guard/checks/direct-test-result.mjs +30 -0
  30. package/scripts/guard/checks/execution-plan-ready.mjs +7 -1
  31. package/scripts/guard/checks/execution-reviews-passed-light.mjs +28 -0
  32. package/scripts/guard/checks/lightweight-completion-evidence.mjs +27 -0
  33. package/scripts/guard/checks/specs-merged.mjs +25 -1
  34. package/scripts/guard/checks/test-matrix-complete.mjs +27 -1
  35. package/scripts/guard/checks/test-matrix-ready.mjs +28 -1
  36. package/scripts/guard/checks/test-merged-light.mjs +36 -0
  37. package/scripts/guard/guard.mjs +102 -12
  38. package/scripts/infer-workflow.mjs +35 -4
  39. package/scripts/lib/arch-merge.mjs +20 -4
  40. package/scripts/lib/cmd-execution.mjs +44 -1
  41. package/scripts/lib/cmd-state.mjs +94 -4
  42. package/scripts/lib/execution-plan.mjs +3 -1
  43. package/scripts/lib/state-loader.mjs +43 -0
  44. package/scripts/lib/surface-scan.mjs +156 -0
  45. package/scripts/lib/test-merge.mjs +10 -2
  46. package/scripts/team-flow.mjs +3 -3
  47. package/skills/clean-code/SKILL.md +1 -1
  48. package/skills/jarvis/SKILL.md +2 -0
  49. package/skills/release-archivist/SKILL.md +39 -13
  50. package/skills/session-handoff/SKILL.md +1 -0
  51. package/skills/test-strategy/SKILL.md +1 -1
  52. package/skills/workflow-start/SKILL.md +63 -5
  53. package/skills/workflow-start/references/routing-rules.md +4 -4
@@ -0,0 +1,30 @@
1
+ // scripts/guard/checks/direct-test-result.mjs — direct/lightweight 验证证据(§4.5 注记③)
2
+ // v0.64.0:guard 只读 tf test record 程序化证据,绝不在 guard 进程内现场跑测试
3
+ // (cmd-state spawnSync 10s 总超时会杀掉 guard,A-19)。证据机制与 tests-passing 同源。
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { readState } from '../../lib/state-loader.mjs';
7
+ import { parseStructuredTestResult } from './tests-passing.mjs';
8
+
9
+ const RECORD_HINT = 'run your verification command, then record it: tf test record <change-dir> --from <runner-output>';
10
+
11
+ export function checkDirectTestResult(changeDir) {
12
+ const state = readState(changeDir);
13
+ const parsed = parseStructuredTestResult(state.test_result);
14
+ if (!parsed || parsed.verdict !== 'pass') {
15
+ return { pass: false, failures: [`no passing programmatic test evidence recorded — ${RECORD_HINT}`] };
16
+ }
17
+ if (parsed.total == null || parsed.total <= 0) {
18
+ return { pass: false, failures: [`recorded evidence has total=0 (empty-run rejected) — ${RECORD_HINT}`] };
19
+ }
20
+ if (!state.test_evidence_path) {
21
+ return { pass: false, failures: [`test_evidence_path missing — ${RECORD_HINT}`] };
22
+ }
23
+ const evidenceAbs = path.isAbsolute(state.test_evidence_path)
24
+ ? state.test_evidence_path
25
+ : path.join(changeDir, state.test_evidence_path);
26
+ if (!fs.existsSync(evidenceAbs)) {
27
+ return { pass: false, failures: [`evidence file not found: ${state.test_evidence_path} — ${RECORD_HINT}`] };
28
+ }
29
+ return { pass: true, failures: [] };
30
+ }
@@ -7,7 +7,7 @@ import { readState } from '../../lib/state-loader.mjs';
7
7
  * A non-empty DP-4 field is not sufficient: it must name the current plan
8
8
  * revision, whose hashes, workflow, mode, and state summary remain current.
9
9
  */
10
- export function checkExecutionPlanReady(changeDir) {
10
+ export function checkExecutionPlanReady(changeDir, ctx = {}) {
11
11
  const plan = readPlan(changeDir);
12
12
  if (!plan) {
13
13
  return {
@@ -21,6 +21,12 @@ export function checkExecutionPlanReady(changeDir) {
21
21
  return { pass: false, failures: validation.failures };
22
22
  }
23
23
 
24
+ // v0.64.0(§4.5 注记⑤,R2 兼容性修复):planned 无 DP-4——豁免 DP-4 revision 精确引用,
25
+ // 判据 = plan 存在 + validatePlan 通过(内嵌 hash 与制品一致)。
26
+ if (ctx.variant === 'planned') {
27
+ return { pass: true, failures: [], reason: 'planned variant: DP-4 reference waived; plan file + hash validation suffice' };
28
+ }
29
+
24
30
  const state = readState(changeDir);
25
31
  const expectedRevision = `plan revision ${plan.revision}`;
26
32
  const decision = typeof state.dp_4_result === 'string' ? state.dp_4_result : '';
@@ -0,0 +1,28 @@
1
+ // scripts/guard/checks/execution-reviews-passed-light.mjs — planned 一次最终审查(§4.5)
2
+ // v0.64.0(A-05①):planned 无波次无逐波 receipt——判据 = 关门前存在非空(≥5 非空行)
3
+ // 最终审查记录 final-review.md;内容最低契约(B-01)防空文件桩。
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+
7
+ const MIN_LINES = 5;
8
+
9
+ export function checkExecutionReviewsPassedLight(changeDir) {
10
+ const p = join(changeDir, 'final-review.md');
11
+ if (!existsSync(p)) {
12
+ return {
13
+ pass: false,
14
+ failures: [
15
+ 'final-review.md missing — planned path requires ONE final review before closing '
16
+ + '(record verdict, scope, and findings; ≥5 lines)',
17
+ ],
18
+ };
19
+ }
20
+ const lines = readFileSync(p, 'utf-8').split('\n').filter(l => l.trim()).length;
21
+ if (lines < MIN_LINES) {
22
+ return {
23
+ pass: false,
24
+ failures: [`final-review.md too thin (${lines} non-empty lines, need ≥${MIN_LINES}) — record verdict + scope + findings`],
25
+ };
26
+ }
27
+ return { pass: true, failures: [] };
28
+ }
@@ -0,0 +1,27 @@
1
+ // scripts/guard/checks/lightweight-completion-evidence.mjs — lightweight 档完成证据(§4.5)
2
+ // v0.64.0:类 direct-test-result——证据文件须存在且非空(独立于 test_result 解析,
3
+ // 兜住「test_result 被清但文件还在」与「文件被删但记录还在」两种漂移)。
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { readState } from '../../lib/state-loader.mjs';
7
+
8
+ export function checkLightweightCompletionEvidence(changeDir) {
9
+ const state = readState(changeDir);
10
+ if (!state.test_evidence_path) {
11
+ return {
12
+ pass: false,
13
+ failures: ['completion evidence missing: test_evidence_path is null — record verification via tf test record before closing'],
14
+ };
15
+ }
16
+ const abs = path.isAbsolute(state.test_evidence_path)
17
+ ? state.test_evidence_path
18
+ : path.join(changeDir, state.test_evidence_path);
19
+ if (!fs.existsSync(abs)) {
20
+ return { pass: false, failures: [`completion evidence file not found: ${state.test_evidence_path}`] };
21
+ }
22
+ const content = fs.readFileSync(abs, 'utf-8').trim();
23
+ if (!content) {
24
+ return { pass: false, failures: [`completion evidence file is empty: ${state.test_evidence_path}`] };
25
+ }
26
+ return { pass: true, failures: [] };
27
+ }
@@ -34,11 +34,35 @@ function hasDeltaSpecs(changeDir) {
34
34
  * Passes when either spec_merged is recorded, or there are no delta specs to merge.
35
35
  * Blocks `executing → closing` when delta specs exist but spec-merger hasn't run.
36
36
  */
37
- export function checkSpecsMerged(changeDir) {
37
+ export function checkSpecsMerged(changeDir, ctx = {}) {
38
38
  const state = readState(changeDir);
39
39
  if (state.spec_merged === true || state.spec_merged === 'true') {
40
40
  return { pass: true, failures: [] };
41
41
  }
42
+ // v0.64.0(§4.5 注记⑦,B-02):planned 轻语义——无 specs/ 目录即过;
43
+ // 有 specs/ 但无 delta 头 = 手写完整形态 specs 永不合并 → FAIL(补 delta 头或移除)。
44
+ if (ctx.variant === 'planned') {
45
+ const specsDir = join(changeDir, 'specs');
46
+ if (!existsSync(specsDir)) {
47
+ return { pass: true, failures: [], reason: 'planned: no specs/ dir — merge N/A' };
48
+ }
49
+ if (!hasDeltaSpecs(changeDir)) {
50
+ return {
51
+ pass: false,
52
+ failures: [
53
+ 'specs/ exists but no ## ADDED/MODIFIED/REMOVED/RENAMED Requirements delta headers — '
54
+ + 'full-form specs would never merge; add delta headers (then spec-merger) or remove specs/',
55
+ ],
56
+ };
57
+ }
58
+ // 有 delta 头但未合并 → 落到下方统一失败
59
+ return {
60
+ pass: false,
61
+ failures: [
62
+ 'Delta specs exist in specs/ but spec-merger has not run (spec_merged not recorded). Run spec-merger to merge ADDED/MODIFIED/REMOVED/RENAMED requirements before closing.',
63
+ ],
64
+ };
65
+ }
42
66
  if (!hasDeltaSpecs(changeDir)) {
43
67
  return { pass: true, failures: [] };
44
68
  }
@@ -19,7 +19,7 @@ import {
19
19
  /**
20
20
  * Returns { pass, failures[], reason? }.
21
21
  */
22
- export function checkTestMatrixComplete(changeDir) {
22
+ export function checkTestMatrixComplete(changeDir, ctx = {}) {
23
23
  const state = readState(changeDir);
24
24
 
25
25
  // Step 1 — legacy:v0.32.0 之前初始化的 change(无 schema_version 打戳)保留旧行为。
@@ -40,6 +40,32 @@ export function checkTestMatrixComplete(changeDir) {
40
40
  return { pass: true, failures: [], reason: `explicitly skipped: ${state.test_matrix_skip_reason}` };
41
41
  }
42
42
 
43
+ // v0.64.0(§4.5 注记②,D4 无契约轻量判据):direct/planned/quick/lightweight
44
+ // 无契约 → 跳过 Step 3 契约声明检查;矩阵存在非空 +(若已记录)hash 一致即可。
45
+ const isLight = ctx.variant === 'direct' || ctx.variant === 'planned'
46
+ || ctx.workflow === 'quick' || ctx.workflow === 'lightweight';
47
+ if (isLight) {
48
+ const matrix = readTestMatrixFile(changeDir);
49
+ if (!matrix.exists || matrix.empty) {
50
+ return {
51
+ pass: false,
52
+ failures: [
53
+ !matrix.exists
54
+ ? 'test-matrix.md missing — light path requires the matrix file itself (no contract)'
55
+ : 'test-matrix.md is empty — must contain at least a Summary section and Cases table',
56
+ 'or skip explicitly: tf state set <dir> test_matrix_skipped true + test_matrix_skip_reason "<reason>"',
57
+ ],
58
+ };
59
+ }
60
+ if (state.test_matrix_hash) {
61
+ const computedLight = computeTestMatrixHash(changeDir);
62
+ if (computedLight !== state.test_matrix_hash) {
63
+ return { pass: false, failures: ['test-matrix.md changed since recorded hash — run tf state rebuild'] };
64
+ }
65
+ }
66
+ return { pass: true, failures: [] };
67
+ }
68
+
43
69
  // Step 3 — 非存量 change 的契约必须声明矩阵(v0.13:内容型豁免删除)。
44
70
  const contract = contractDeclaresTestMatrix(changeDir);
45
71
  if (!contract.exists) {
@@ -17,7 +17,7 @@ import {
17
17
  /**
18
18
  * Returns { pass, failures[], reason? }.
19
19
  */
20
- export function checkTestMatrixReady(changeDir) {
20
+ export function checkTestMatrixReady(changeDir, ctx = {}) {
21
21
  const state = readState(changeDir);
22
22
 
23
23
  // Exemption 1:legacy change(v0.32.0 之前初始化,无 schema_version 打戳)。
@@ -25,6 +25,33 @@ export function checkTestMatrixReady(changeDir) {
25
25
  return { pass: true, failures: [], reason: 'legacy change — initialized before v0.32.0' };
26
26
  }
27
27
 
28
+ // v0.64.0(§4.5 注记②,D4 无契约轻量判据):direct/planned/quick/lightweight
29
+ // 无 execution-contract——判据降为「test-matrix.md 存在非空 OR 显式 skip 附理由」,
30
+ // 不要求契约声明段。skip 仍须附理由(拒 state set 自清为常规通过路径)。
31
+ const isLight = ctx.variant === 'direct' || ctx.variant === 'planned'
32
+ || ctx.workflow === 'quick' || ctx.workflow === 'lightweight';
33
+ if (isLight) {
34
+ if (state.test_matrix_skipped === 'true') {
35
+ if (skipMissingReason(state)) {
36
+ return { pass: false, failures: [SKIP_REASON_HINT] };
37
+ }
38
+ return { pass: true, failures: [], reason: `explicitly skipped: ${state.test_matrix_skip_reason}` };
39
+ }
40
+ const matrix = readTestMatrixFile(changeDir);
41
+ if (matrix.exists && !matrix.empty) {
42
+ return { pass: true, failures: [] };
43
+ }
44
+ return {
45
+ pass: false,
46
+ failures: [
47
+ matrix.exists
48
+ ? 'test-matrix.md is empty — add a Summary + Cases table (light path has no contract section)'
49
+ : 'test-matrix.md missing — create it (light path: matrix file alone is sufficient, no contract needed)',
50
+ 'or skip explicitly: tf state set <dir> test_matrix_skipped true + test_matrix_skip_reason "<reason>"',
51
+ ],
52
+ };
53
+ }
54
+
28
55
  // Exemption 2:显式 skip(必须附理由)。
29
56
  if (state.test_matrix_skipped === 'true') {
30
57
  // v2.1 §6.3 skip 禁令:glaf4-delegation 模式禁止 skip(唯一例外 TEST_BOOTSTRAP)
@@ -0,0 +1,36 @@
1
+ // scripts/guard/checks/test-merged-light.mjs — 测试台账轻回写(§4,横展发现的同型全局态)
2
+ // v0.64.0:无测试触碰 → 过;有 → docs/test-ledger/** 须含归因 change:<name>
3
+ //(写入走 tf test-merge --light,同 sink 同 rewriteIndex 单写入口)。
4
+ import { readState } from '../../lib/state-loader.mjs';
5
+ import { scanArchitectureSurface } from '../../lib/surface-scan.mjs';
6
+ import { filesContainDir } from './_fs-utils.mjs';
7
+ import { join } from 'node:path';
8
+
9
+ export function checkTestMergedLight(changeDir) {
10
+ const state = readState(changeDir);
11
+ const changeName = state.change_name || changeDir.split('/').filter(Boolean).pop();
12
+ const scan = scanArchitectureSurface(changeDir, state);
13
+
14
+ if (scan.error === 'no-baseline' || scan.error === 'diff-failed') {
15
+ return { pass: false, failures: [`surface scan failed (${scan.error}) — cannot determine touched tests, fail-closed`] };
16
+ }
17
+ if (scan.error === 'aggregate-list-missing') {
18
+ // 测试判定不依赖聚合清单——降级为仅用文件清单分类(清单缺失不阻塞本维度)
19
+ // 但 scanArchitectureSurface 在清单缺失时不返回 files……此时保守 FAIL:
20
+ return { pass: false, failures: ['aggregate list missing (.team-flow/aggregate-dirs.txt) — shared scan precondition unmet, fail-closed'] };
21
+ }
22
+
23
+ if (scan.testFiles.length === 0) {
24
+ return { pass: true, failures: [], reason: 'no test files touched — test ledger writeback N/A' };
25
+ }
26
+
27
+ const ledger = join(scan.projectRoot, 'docs', 'test-ledger');
28
+ const anchor = `change:${changeName}`;
29
+ if (filesContainDir(ledger, anchor) || filesContainDir(ledger, changeName)) {
30
+ return { pass: true, failures: [] };
31
+ }
32
+ return {
33
+ pass: false,
34
+ failures: [`tests touched but docs/test-ledger/** has no '${anchor}' entry — run: tf test-merge --light <change-dir>`],
35
+ };
36
+ }
@@ -21,6 +21,18 @@ import { checkArchReadiness } from './checks/arch-readiness.mjs';
21
21
  import { checkArchSnapshot } from './checks/arch-snapshot.mjs';
22
22
  import { checkArchMerged } from './checks/arch-merged.mjs';
23
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';
24
36
 
25
37
  // Transition matrix: <from>:<to> → required check dimensions
26
38
  const TRANSITION_CHECKS = {
@@ -113,12 +125,52 @@ const WORKFLOW_TRANSITION_CHECKS = {
113
125
 
114
126
  const TRANSITION_WORKFLOW_REQUIREMENTS = {
115
127
  'exploring:bridging': ['hotfix'],
116
- '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'],
117
131
  };
118
132
 
119
- 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) {
120
170
  const allowed = TRANSITION_WORKFLOW_REQUIREMENTS[key];
121
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: [] };
122
174
  return {
123
175
  pass: false,
124
176
  checks: [{
@@ -129,7 +181,19 @@ function checkWorkflowAllowed(key, workflow) {
129
181
  };
130
182
  }
131
183
 
132
- 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
+ }
133
197
  return WORKFLOW_TRANSITION_CHECKS[workflow]?.[key] ?? TRANSITION_CHECKS[key];
134
198
  }
135
199
 
@@ -138,13 +202,16 @@ async function main() {
138
202
  options: {
139
203
  json: { type: 'boolean', default: false },
140
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: '' },
141
208
  },
142
209
  allowPositionals: true,
143
210
  });
144
211
 
145
212
  const subcommand = positionals[0];
146
213
  if (subcommand !== 'check') {
147
- 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]');
148
215
  process.exit(2);
149
216
  }
150
217
 
@@ -153,20 +220,27 @@ async function main() {
153
220
  const toState = positionals[3];
154
221
  const useJson = values.json;
155
222
  const workflow = values.workflow;
223
+ const workflowVariant = values['workflow-variant'] || null;
224
+ const plannedArch = values['planned-arch'] === 'true';
156
225
 
157
- 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'];
158
228
  if (!VALID_WORKFLOWS.includes(workflow)) {
159
229
  console.error(`Invalid workflow: ${workflow}. Must be one of: ${VALID_WORKFLOWS.join(', ')}`);
160
230
  process.exit(2);
161
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
+ }
162
236
 
163
237
  if (!changeDir || !fromState || !toState) {
164
- 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]');
165
239
  process.exit(2);
166
240
  }
167
241
 
168
242
  const key = `${fromState}:${toState}`;
169
- const dimensions = resolveDimensions(key, workflow);
243
+ const dimensions = resolveDimensions(key, workflow, { variant: workflowVariant, plannedArch });
170
244
 
171
245
  if (!dimensions) {
172
246
  const valid = Object.keys(TRANSITION_CHECKS).join(', ');
@@ -176,7 +250,7 @@ async function main() {
176
250
  process.exit(1);
177
251
  }
178
252
 
179
- const workflowCheck = checkWorkflowAllowed(key, workflow);
253
+ const workflowCheck = checkWorkflowAllowed(key, workflow, workflowVariant);
180
254
  if (!workflowCheck.pass) {
181
255
  if (useJson) {
182
256
  console.log(JSON.stringify({ pass: false, checks: workflowCheck.checks }, null, 2));
@@ -198,6 +272,10 @@ async function main() {
198
272
  process.exit(0);
199
273
  }
200
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
+
201
279
  const CHECK_RUNNERS = {
202
280
  'artifacts-exist': (dir) => checkArtifactsExist(dir),
203
281
  'schema-valid': async (dir) => (await import('./checks/schema-valid.mjs')).checkSchemaValid(dir),
@@ -205,20 +283,32 @@ async function main() {
205
283
  'contract-current': (dir) => checkContractCurrent(dir),
206
284
  'tasks-complete': (dir) => checkTasksComplete(dir),
207
285
  'tests-passing': (dir) => checkTestsPassing(dir),
208
- 'specs-merged': (dir) => checkSpecsMerged(dir),
286
+ 'specs-merged': (dir) => checkSpecsMerged(dir, CTX),
209
287
  'dp-gate-passed': (dir) => checkDpGate(dir, fromState, toState),
210
288
  'dp3-approved': (dir) => checkDp3Approved(dir),
211
- 'execution-plan-ready': (dir) => checkExecutionPlanReady(dir),
289
+ 'execution-plan-ready': (dir) => checkExecutionPlanReady(dir, CTX),
212
290
  'execution-reviews-passed': (dir) => checkExecutionReviewsPassed(dir),
213
291
  'arch-design': (dir) => checkArchDesign(dir),
214
292
  'compound-captured': (dir) => checkCompoundCaptured(dir),
215
- 'test-matrix-complete': (dir) => checkTestMatrixComplete(dir),
216
- 'test-matrix-ready': (dir) => checkTestMatrixReady(dir),
293
+ 'test-matrix-complete': (dir) => checkTestMatrixComplete(dir, CTX),
294
+ 'test-matrix-ready': (dir) => checkTestMatrixReady(dir, CTX),
217
295
  'gates-probed': (dir) => checkGatesProbed(dir),
218
296
  'arch-readiness': (dir) => checkArchReadiness(dir),
219
297
  'arch-snapshot': (dir) => checkArchSnapshot(dir),
220
298
  'arch-merged': (dir) => checkArchMerged(dir),
221
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),
222
312
  };
223
313
 
224
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}`);