@xulthekl/team-flow 0.23.0 → 0.24.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 (54) 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/CHANGELOG.md +50 -0
  9. package/GEMINI.md +1 -1
  10. package/INSTALL.md +1 -1
  11. package/README.md +1 -1
  12. package/docs/README_en.md +1 -1
  13. package/docs/solutions/INDEX.md +5 -0
  14. package/docs/solutions/cross-phase/2026-07-28-no-summary.md +17 -0
  15. package/gemini-extension.json +1 -1
  16. package/hooks/pre-tool-use-guard +9 -9
  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/compound-captured.mjs +70 -0
  22. package/scripts/guard/guard.mjs +3 -1
  23. package/scripts/lib/cmd-state.mjs +2 -0
  24. package/scripts/lib/solutions-promote.mjs +1 -1
  25. package/scripts/lib/state-loader.mjs +5 -0
  26. package/skills/bug-investigator/SKILL.md +1 -1
  27. package/skills/build-executor/SKILL.md +19 -19
  28. package/skills/build-executor/implementer-prompt.md +1 -1
  29. package/skills/build-executor/references/execution-modes.md +6 -6
  30. package/skills/build-executor/task-reviewer-prompt.md +1 -1
  31. package/skills/code-reviewer/SKILL.md +2 -2
  32. package/skills/code-reviewer/code-reviewer-prompt.md +1 -1
  33. package/skills/contract-builder/SKILL.md +6 -6
  34. package/skills/need-explorer/SKILL.md +2 -2
  35. package/skills/release-archivist/SKILL.md +12 -12
  36. package/skills/release-archivist/references/closing-procedures.md +8 -8
  37. package/skills/spec-merger/SKILL.md +2 -2
  38. package/skills/spec-writer/SKILL.md +3 -3
  39. package/skills/workflow-bootstrap/SKILL.md +13 -2
  40. package/skills/workflow-bootstrap/references/b1-reconnaissance.md +13 -7
  41. package/skills/workflow-bootstrap/scripts/recon-probe.sh +122 -1
  42. package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +1 -1
  43. package/skills/workflow-orchestrator/references/s4-split-validate.md +1 -1
  44. package/skills/workflow-orchestrator/references/s5-monitoring.md +1 -1
  45. package/skills/workflow-start/SKILL.md +16 -16
  46. package/skills/workflow-start/references/routing-rules.md +17 -17
  47. package/tests/lib/guard-compound-captured.test.mjs +92 -0
  48. package/tests/lib/guard-specs-merged.test.mjs +2 -0
  49. package/tests/lib/guard-tests-passing.test.mjs +2 -0
  50. package/tests/lib/guard.test.mjs +2 -0
  51. package/tests/lib/solutions-capture.test.mjs +108 -0
  52. package/tests/lib/solutions-index-gen.test.mjs +147 -0
  53. package/tests/lib/solutions-inject.test.mjs +115 -0
  54. package/tests/lib/solutions-promote.test.mjs +200 -0
@@ -15,14 +15,14 @@ Do NOT invoke for: general coding tasks outside team-flow changes, casual questi
15
15
 
16
16
  ## States
17
17
 
18
- `exploring` → `specifying` → `bridging` → `approved-for-build` → `executing` → `closing`, with `debugging` side-path from `executing`, and `abandoned` as terminal. If a transition is ambiguous, run `npx --yes --package @xulthekl/team-flow@0.23.0 tf runtime asset read docs/state-machine.md`.
18
+ `exploring` → `specifying` → `bridging` → `approved-for-build` → `executing` → `closing`, with `debugging` side-path from `executing`, and `abandoned` as terminal. If a transition is ambiguous, run `npx --yes --package @xulthekl/team-flow@0.24.0 tf runtime asset read docs/state-machine.md`.
19
19
 
20
20
  ## Initialization
21
21
 
22
- 1. **Update check**: Run `npx --yes --package @xulthekl/team-flow@0.23.0 tf runtime check-update`. Exit 0 → continue. Exit 1 → non-blocking upgrade reminder. Exit 2 → skip.
22
+ 1. **Update check**: Run `npx --yes --package @xulthekl/team-flow@0.24.0 tf runtime check-update`. Exit 0 → continue. Exit 1 → non-blocking upgrade reminder. Exit 2 → skip.
23
23
  2. **Inspect change folder**: Check for `proposal.md`, `specs/`, `design.md`, `tasks.md`, `execution-contract.md`. Answer: Is the change fuzzy? Artifacts missing/unstable? Contract exist? User approved contract? Execution in progress or blocked? In verification/wrap-up?
24
- 3. **Overlay recovery scan**: Run `npx --yes --package @xulthekl/team-flow@0.23.0 tf handoff list <change-dir> --json` and `npx --yes --package @xulthekl/team-flow@0.23.0 tf checkpoint list <change-dir> --json`. A `result-ready` handoff requires explicit review and `npx --yes --package @xulthekl/team-flow@0.23.0 tf handoff resolve` before resuming the affected work. An `active` handoff is non-blocking side work. Show a non-stale checkpoint as recovery context; show a stale checkpoint only as historical evidence.
25
- 4. **Execution-control recovery scan**: For `approved-for-build`, `executing`, `debugging`, or `closing`, run `npx --yes --package @xulthekl/team-flow@0.23.0 tf execution show <change-dir> --json`. Treat only `current: true` plus `waves[].eligible: true` as permission to start a wave; report plan revision, mode, next eligible wave, and every wave's receipt/blockers. A missing, invalid, or stale plan blocks implementation and routes to `build-executor`; do not infer progress from chat history.
24
+ 3. **Overlay recovery scan**: Run `npx --yes --package @xulthekl/team-flow@0.24.0 tf handoff list <change-dir> --json` and `npx --yes --package @xulthekl/team-flow@0.24.0 tf checkpoint list <change-dir> --json`. A `result-ready` handoff requires explicit review and `npx --yes --package @xulthekl/team-flow@0.24.0 tf handoff resolve` before resuming the affected work. An `active` handoff is non-blocking side work. Show a non-stale checkpoint as recovery context; show a stale checkpoint only as historical evidence.
25
+ 4. **Execution-control recovery scan**: For `approved-for-build`, `executing`, `debugging`, or `closing`, run `npx --yes --package @xulthekl/team-flow@0.24.0 tf execution show <change-dir> --json`. Treat only `current: true` plus `waves[].eligible: true` as permission to start a wave; report plan revision, mode, next eligible wave, and every wave's receipt/blockers. A missing, invalid, or stale plan blocks implementation and routes to `build-executor`; do not infer progress from chat history.
26
26
 
27
27
  ## DP-0: User Confirmation Gate
28
28
 
@@ -36,17 +36,17 @@ Ask (manual path): change name + one-sentence intent, known constraints, related
36
36
 
37
37
  After confirmation:
38
38
  ```bash
39
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_decisions "<summary>"
40
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_result confirmed
41
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_confirmed true
42
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ)
39
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_decisions "<summary>"
40
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_result confirmed
41
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_confirmed true
42
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ)
43
43
  ```
44
44
 
45
45
  Config-aware routing: check `artifacts.order` and `artifacts.skip` from project config.
46
46
 
47
47
  ## Mode Detection
48
48
 
49
- If workflow is `auto`/`null`/unset: run `npx --yes --package @xulthekl/team-flow@0.23.0 tf runtime infer <change-dir>`. Inference: **hotfix** (≤2 tasks, ≤2 files, no schema/API/new modules), **tweak** (≤4 tasks, config/doc only), **full** (anything larger). Persist with `npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <dir> workflow <mode>`.
49
+ If workflow is `auto`/`null`/unset: run `npx --yes --package @xulthekl/team-flow@0.24.0 tf runtime infer <change-dir>`. Inference: **hotfix** (≤2 tasks, ≤2 files, no schema/API/new modules), **tweak** (≤4 tasks, config/doc only), **full** (anything larger). Persist with `npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <dir> workflow <mode>`.
50
50
 
51
51
  Validate mode against artifact content. If hotfix/tweak criteria not met → upgrade to `full` and output reason. Don't overwrite explicit mode unless user asks.
52
52
 
@@ -59,19 +59,19 @@ Change is fuzzy, scope unclear, comparing options, no stable change name.
59
59
  Guard: `arch_design_decision` in `.team-flow.yaml` is `null` → must run before spec-writer. Dispatch `architecture-design` as sub-agent; after return, run reasonableness check and write yaml. Full protocol in `references/routing-rules.md`「Route to architecture-design」.
60
60
 
61
61
  ### Route to spec-writer
62
- Guard: `npx --yes --package @xulthekl/team-flow@0.23.0 tf runtime guard check <dir> exploring specifying --json` → fail = BLOCK. **arch_design_decision must not be null** → fail = BLOCK (v0.9 §26). User knows what they want, artifacts missing/incomplete.
62
+ Guard: `npx --yes --package @xulthekl/team-flow@0.24.0 tf runtime guard check <dir> exploring specifying --json` → fail = BLOCK. **arch_design_decision must not be null** → fail = BLOCK (v0.9 §26). User knows what they want, artifacts missing/incomplete.
63
63
 
64
64
  ### Route to contract-builder
65
65
  Guard: `... check <dir> specifying bridging --json` → fail = BLOCK. Artifacts exist, implementation requested, contract missing/stale. Include `DP-3: 契约批准`.
66
66
 
67
67
  ### Route to build-executor
68
- Contract exists and approved, contract matches artifacts. Include `DP-4: 执行模式选择`: propose waves, run `npx --yes --package @xulthekl/team-flow@0.23.0 tf execution recommend <change-dir> [--wave ...]`, show the user every available mode plus evidence and the recommendation, then obtain a clear selection. The command saves a current receipt; before the first implementation edit, `build-executor` must run `npx --yes --package @xulthekl/team-flow@0.23.0 tf execution plan <change-dir> --mode <selected> --confirm ...` (and `--acknowledge-recommendation` when the selected mode differs from the recommendation) using matching artifacts, contract, and waves, then `npx --yes --package @xulthekl/team-flow@0.23.0 tf execution show <change-dir> --json`; report the saved revision, selected mode, recommendation alignment, ordered waves, and actual concurrent-dispatch capability. A revision must repeat recommend and confirmation. Do not transition to `executing` until `show` reports `current: true`; then run `... check <dir> approved-for-build executing --json` → fail = BLOCK.
68
+ Contract exists and approved, contract matches artifacts. Include `DP-4: 执行模式选择`: propose waves, run `npx --yes --package @xulthekl/team-flow@0.24.0 tf execution recommend <change-dir> [--wave ...]`, show the user every available mode plus evidence and the recommendation, then obtain a clear selection. The command saves a current receipt; before the first implementation edit, `build-executor` must run `npx --yes --package @xulthekl/team-flow@0.24.0 tf execution plan <change-dir> --mode <selected> --confirm ...` (and `--acknowledge-recommendation` when the selected mode differs from the recommendation) using matching artifacts, contract, and waves, then `npx --yes --package @xulthekl/team-flow@0.24.0 tf execution show <change-dir> --json`; report the saved revision, selected mode, recommendation alignment, ordered waves, and actual concurrent-dispatch capability. A revision must repeat recommend and confirmation. Do not transition to `executing` until `show` reports `current: true`; then run `... check <dir> approved-for-build executing --json` → fail = BLOCK.
69
69
 
70
70
  ### Route to bug-investigator
71
71
  Execution hit blockage: test failure, unexpected behavior, build error, task cannot proceed. After debugging, route back to build-executor.
72
72
 
73
73
  ### Route to code-reviewer
74
- The current planned wave is implemented and ready for spec-compliance + code-quality verification. A reviewer must write an `npx --yes --package @xulthekl/team-flow@0.23.0 tf execution review <change-dir> --wave <id> --base <sha> --head <sha> --report <path> --verdict <pass|fail>` receipt before any dependent wave or closing transition.
74
+ The current planned wave is implemented and ready for spec-compliance + code-quality verification. A reviewer must write an `npx --yes --package @xulthekl/team-flow@0.24.0 tf execution review <change-dir> --wave <id> --base <sha> --head <sha> --report <path> --verdict <pass|fail>` receipt before any dependent wave or closing transition.
75
75
 
76
76
  ### Route to release-archivist
77
77
  Guard: `... check <dir> executing closing --json` → fail = BLOCK. Implementation complete, verification complete/nearly complete. Include `DP-7: 归档确认`.
@@ -90,10 +90,10 @@ uncertainty. Do not create a prototype handoff or enter a prototype worktree
90
90
  until the user confirms. After confirmation:
91
91
 
92
92
  ```bash
93
- npx --yes --package @xulthekl/team-flow@0.23.0 tf handoff create <change-dir> \
93
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf handoff create <change-dir> \
94
94
  --type prototype --objective "<confirmed objective>" \
95
95
  --expected-output "<expected evidence>" --acceptance "<completion criterion>"
96
- npx --yes --package @xulthekl/team-flow@0.23.0 tf isolate <change-dir> prototype-<handoff-id>
96
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf isolate <change-dir> prototype-<handoff-id>
97
97
  ```
98
98
 
99
99
  Never suggest or enter this route automatically for backend, CLI, configuration,
@@ -107,7 +107,7 @@ work.
107
107
  - **Hotfix**: Route to contract-builder (minimal), skip need-explorer + spec-writer, guard check `exploring bridging --workflow hotfix`, then `bridging -> approved-for-build`, after DP-3 → build-executor (recommend, show, and confirm an execution mode), after → release-archivist (lightweight). Hotfix may skip `proposal.md`, `design.md`, `tasks.md`, and `specs/`, but it still requires a fresh minimal `execution-contract.md`, DP-3 approval, and a current execution plan before build. **architecture-design 不豁免**(v0.9 §26):同样过 architecture-design 子代理判断门,快速判定是否涉及架构变更(hotfix 可能正是架构缺陷导致)
108
108
  - **Tweak**: Route to build-executor (direct edit), skip need-explorer + spec-writer + contract-builder, guard check `exploring approved-for-build --workflow tweak`, after → release-archivist (lightweight). **architecture-design 不豁免**(v0.9 §26):同样过 architecture-design 子代理判断门
109
109
 
110
- Post-transition: 💡 `npx --yes --package @xulthekl/team-flow@0.23.0 tf inject <change-dir>` to update phase-guard artifacts.
110
+ Post-transition: 💡 `npx --yes --package @xulthekl/team-flow@0.24.0 tf inject <change-dir>` to update phase-guard artifacts.
111
111
 
112
112
  ## Staleness Detection
113
113
 
@@ -124,7 +124,7 @@ Use content inspection, not timestamps.
124
124
  ## Guardrails
125
125
 
126
126
  - No implementation before planning artifacts or contract exist
127
- - No implementation for full/hotfix without a current `npx --yes --package @xulthekl/team-flow@0.23.0 tf execution plan`; no state transition based on an unverified DP-4 string
127
+ - No implementation for full/hotfix without a current `npx --yes --package @xulthekl/team-flow@0.24.0 tf execution plan`; no state transition based on an unverified DP-4 string
128
128
  - No "continue" without state inspection
129
129
  - No implementation past stale contract
130
130
  - No implementation past bug without investigation
@@ -32,17 +32,17 @@ artifacts: # required 时必填
32
32
  - `null`(未判定)→ **BLOCK**
33
33
 
34
34
  ```bash
35
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> arch_design_decision "<required|skipped>"
36
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> arch_design_reason "<reason>"
37
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> arch_design_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ)
35
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> arch_design_decision "<required|skipped>"
36
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> arch_design_reason "<reason>"
37
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> arch_design_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ)
38
38
  # if required:
39
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> arch_design_artifacts "architecture/architecture.md,architecture/database.md,architecture/api.md"
39
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> arch_design_artifacts "architecture/architecture.md,architecture/database.md,architecture/api.md"
40
40
  ```
41
41
 
42
42
  **hotfix / tweak 不豁免**:同样过 architecture-design 子代理判断门。
43
43
 
44
44
  ## Route to spec-writer
45
- Guard: `npx --yes --package @xulthekl/team-flow@0.23.0 tf runtime guard check <dir> exploring specifying --json` → fail = BLOCK.
45
+ Guard: `npx --yes --package @xulthekl/team-flow@0.24.0 tf runtime guard check <dir> exploring specifying --json` → fail = BLOCK.
46
46
  **arch_design_decision must not be null** → fail = BLOCK(architecture-design gate not passed,v0.9 §26)。
47
47
  User knows what they want, artifacts missing/incomplete.
48
48
 
@@ -55,12 +55,12 @@ Contract exists and approved, contract matches artifacts. Include DP-4: 执行
55
55
 
56
56
  Propose waves, run:
57
57
  ```bash
58
- npx --yes --package @xulthekl/team-flow@0.23.0 tf execution recommend <change-dir> [--wave ...]
58
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf execution recommend <change-dir> [--wave ...]
59
59
  ```
60
60
  Show every available mode plus evidence and recommendation, obtain clear selection. Then:
61
61
  ```bash
62
- npx --yes --package @xulthekl/team-flow@0.23.0 tf execution plan <change-dir> --mode <selected> --confirm ...
63
- npx --yes --package @xulthekl/team-flow@0.23.0 tf execution show <change-dir> --json
62
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf execution plan <change-dir> --mode <selected> --confirm ...
63
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf execution show <change-dir> --json
64
64
  ```
65
65
  Do not transition to `executing` until `show` reports `current: true`.
66
66
  Guard: `... check <dir> approved-for-build executing --json` → fail = BLOCK.
@@ -71,7 +71,7 @@ Execution hit blockage: test failure, unexpected behavior, build error. After de
71
71
  ## Route to code-reviewer
72
72
  Current planned wave implemented and ready for review. Reviewer must write:
73
73
  ```bash
74
- npx --yes --package @xulthekl/team-flow@0.23.0 tf execution review <change-dir> --wave <id> --base <sha> --head <sha> --report <path> --verdict <pass|fail>
74
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf execution review <change-dir> --wave <id> --base <sha> --head <sha> --report <path> --verdict <pass|fail>
75
75
  ```
76
76
 
77
77
  ## Route to release-archivist
@@ -93,10 +93,10 @@ User explicitly requests, bug-investigator escalates after 3+ failures AND user
93
93
 
94
94
  When the user's brief explicitly contains UI/screen/interaction/layout/UX uncertainty, ask once whether a prototype would reduce uncertainty. After confirmation:
95
95
  ```bash
96
- npx --yes --package @xulthekl/team-flow@0.23.0 tf handoff create <change-dir> \
96
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf handoff create <change-dir> \
97
97
  --type prototype --objective "<confirmed objective>" \
98
98
  --expected-output "<expected evidence>" --acceptance "<completion criterion>"
99
- npx --yes --package @xulthekl/team-flow@0.23.0 tf isolate <change-dir> prototype-<handoff-id>
99
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf isolate <change-dir> prototype-<handoff-id>
100
100
  ```
101
101
  Never suggest for backend/CLI/config/internal-refactor work. Never pass `--force`.
102
102
 
@@ -130,17 +130,17 @@ else:
130
130
 
131
131
  After confirmation:
132
132
  ```bash
133
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_decisions "<summary>"
134
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_result confirmed
135
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_confirmed true
136
- npx --yes --package @xulthekl/team-flow@0.23.0 tf state set <change-dir> dp_0_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ)
133
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_decisions "<summary>"
134
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_result confirmed
135
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_confirmed true
136
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf state set <change-dir> dp_0_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ)
137
137
  ```
138
138
 
139
139
  ## Mode Detection
140
140
 
141
141
  If workflow is `auto`/`null`/unset:
142
142
  ```bash
143
- npx --yes --package @xulthekl/team-flow@0.23.0 tf runtime infer <change-dir>
143
+ npx --yes --package @xulthekl/team-flow@0.24.0 tf runtime infer <change-dir>
144
144
  ```
145
145
  Inference: **hotfix** (≤2 tasks, ≤2 files, no schema/API/new modules), **tweak** (≤4 tasks, config/doc only), **full** (anything larger).
146
146
 
@@ -152,4 +152,4 @@ Inference: **hotfix** (≤2 tasks, ≤2 files, no schema/API/new modules), **twe
152
152
  - **Brief drift (advisory, v0.9)**: change-brief.md 的 `plan_hash` 与当前 plan.md 不一致 → 提示回 orchestrator 重新分发;**不**阻断、**不**进 artifacts_hash、**不**混入上述三条产物互查
153
153
 
154
154
  ## Post-transition
155
- 💡 `npx --yes --package @xulthekl/team-flow@0.23.0 tf inject <change-dir>` to update phase-guard artifacts.
155
+ 💡 `npx --yes --package @xulthekl/team-flow@0.24.0 tf inject <change-dir>` to update phase-guard artifacts.
@@ -0,0 +1,92 @@
1
+ // tests/lib/guard-compound-captured.test.mjs
2
+ // v0.24.0: compound-captured guard — executing:closing must require learnings.md
3
+ // or explicit compound_skipped=true in state.
4
+
5
+ import { describe, it, before, after } from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { mkdtempSync, writeFileSync, rmSync, mkdirSync, existsSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { tmpdir } from 'node:os';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { dirname } from 'node:path';
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const ROOT = join(__dirname, '..', '..');
15
+
16
+ // Import the check function directly for unit-level testing.
17
+ const { checkCompoundCaptured } = await import(
18
+ join(ROOT, 'scripts', 'guard', 'checks', 'compound-captured.mjs')
19
+ );
20
+
21
+ function makeDir(prefix) {
22
+ return mkdtempSync(join(tmpdir(), `tf-compound-${prefix}-`));
23
+ }
24
+
25
+ function cleanup(dir) {
26
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
27
+ }
28
+
29
+ describe('compound-captured guard', () => {
30
+ describe('learnings.md existence', () => {
31
+ let dir;
32
+ before(() => { dir = makeDir('learnings'); });
33
+ after(() => cleanup(dir));
34
+
35
+ it('SHALL fail when no learnings.md and no compound_skipped', () => {
36
+ writeFileSync(join(dir, '.team-flow.yaml'), 'state: executing\nworkflow: full\n');
37
+ const result = checkCompoundCaptured(dir);
38
+ assert.equal(result.pass, false);
39
+ assert.ok(result.failures.length > 0);
40
+ assert.match(result.failures[0], /Compound capture missing/);
41
+ });
42
+
43
+ it('SHALL pass when learnings.md exists and is non-empty', () => {
44
+ writeFileSync(join(dir, 'learnings.md'), '# Session Learnings\n\n## Lesson 1\nTest.\n');
45
+ const result = checkCompoundCaptured(dir);
46
+ assert.equal(result.pass, true);
47
+ });
48
+
49
+ it('SHALL fail when learnings.md exists but is empty', () => {
50
+ writeFileSync(join(dir, 'learnings.md'), '');
51
+ const result = checkCompoundCaptured(dir);
52
+ assert.equal(result.pass, false);
53
+ assert.match(result.failures[0], /empty/);
54
+ });
55
+ });
56
+
57
+ describe('compound_skipped explicit opt-out', () => {
58
+ let dir;
59
+ before(() => { dir = makeDir('skip'); });
60
+ after(() => cleanup(dir));
61
+
62
+ it('SHALL pass when compound_skipped=true and no learnings.md', () => {
63
+ writeFileSync(join(dir, '.team-flow.yaml'), 'state: executing\nworkflow: full\ncompound_skipped: true\n');
64
+ const result = checkCompoundCaptured(dir);
65
+ assert.equal(result.pass, true);
66
+ });
67
+
68
+ it('SHALL fail when compound_skipped=false (not opted out)', () => {
69
+ writeFileSync(join(dir, '.team-flow.yaml'), 'state: executing\nworkflow: full\ncompound_skipped: false\n');
70
+ const result = checkCompoundCaptured(dir);
71
+ assert.equal(result.pass, false);
72
+ });
73
+ });
74
+
75
+ describe('no state file (backward compatibility)', () => {
76
+ let dir;
77
+ before(() => { dir = makeDir('no-state'); });
78
+ after(() => cleanup(dir));
79
+
80
+ it('SHALL fail when no state file and no learnings.md', () => {
81
+ // No .team-flow.yaml, no learnings.md — guard should still fail
82
+ const result = checkCompoundCaptured(dir);
83
+ assert.equal(result.pass, false);
84
+ });
85
+
86
+ it('SHALL pass when no state file but learnings.md exists', () => {
87
+ writeFileSync(join(dir, 'learnings.md'), '# Learnings\nContent.\n');
88
+ const result = checkCompoundCaptured(dir);
89
+ assert.equal(result.pass, true);
90
+ });
91
+ });
92
+ });
@@ -27,6 +27,8 @@ function makeChangeDir(withDelta) {
27
27
  ? '## ADDED Requirements\n\n### Requirement: New\n\nThe system SHALL do new.\n\n#### Scenario: New\n- **WHEN** x\n- **THEN** y\n'
28
28
  : '## Requirements\n\n### Requirement: Existing\n\nThe system SHALL exist.\n\n#### Scenario: Existing\n- **WHEN** a\n- **THEN** b\n';
29
29
  writeFileSync(join(dir, 'specs', 'test.md'), specsContent);
30
+ // v0.24.0: compound-captured dimension requires learnings.md
31
+ writeFileSync(join(dir, 'learnings.md'), '# Session Learnings\n\n## Lesson 1\nTest.\n');
30
32
  initializeGitRepository(dir);
31
33
  return dir;
32
34
  }
@@ -25,6 +25,8 @@ function makeChangeDir() {
25
25
  writeFileSync(join(dir, 'tasks.md'), '# Tasks\n\n- [x] Task 1\n- [x] Task 2\n');
26
26
  writeFileSync(join(dir, 'specs', 'test.md'), '## ADDED Requirements\n\n### Requirement: Test\n\nThe system SHALL test.\n\n#### Scenario: Test\n- **WHEN** test\n- **THEN** test\n');
27
27
  writeFileSync(join(dir, 'execution-contract.md'), '# Execution Contract\n\n## Intent Lock\nTest.\n');
28
+ // v0.24.0: compound-captured dimension requires learnings.md
29
+ writeFileSync(join(dir, 'learnings.md'), '# Session Learnings\n\n## Lesson 1\nTest.\n');
28
30
  initializeGitRepository(dir);
29
31
  return dir;
30
32
  }
@@ -312,6 +312,8 @@ describe('guard: execution control records', () => {
312
312
  function recordPassingClosingPrerequisites() {
313
313
  runNodeScript(CLI_PATH, ['state', 'set', dir, 'test_result', 'pass: unit tests']);
314
314
  runNodeScript(CLI_PATH, ['state', 'set', dir, 'spec_merged', 'true']);
315
+ // v0.24.0: compound-captured dimension requires learnings.md (or explicit skip)
316
+ writeFileSync(join(dir, 'learnings.md'), '# Session Learnings\n\n## Lesson 1\nCaptured during guard test.\n');
315
317
  }
316
318
 
317
319
  function writeReviewReport(name, content = 'Review completed without blocking findings.\n') {
@@ -0,0 +1,108 @@
1
+ // tests/lib/solutions-capture.test.mjs
2
+ // Tests for scripts/lib/solutions-capture.mjs — compound capture mechanism
3
+
4
+ import { describe, it, before, after, beforeEach } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { mkdtempSync, readFileSync, existsSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { dirname } from 'node:path';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = join(__dirname, '..', '..');
14
+
15
+ const { run: captureRun } = await import(join(ROOT, 'scripts', 'lib', 'solutions-capture.mjs'));
16
+
17
+ function makeDir() {
18
+ return mkdtempSync(join(tmpdir(), 'tf-sol-capture-'));
19
+ }
20
+
21
+ function cleanup(dir) {
22
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
23
+ }
24
+
25
+ describe('solutions-capture', () => {
26
+ let dir;
27
+
28
+ beforeEach(() => {
29
+ dir = makeDir();
30
+ });
31
+
32
+ after(() => {
33
+ // Clean up all temp dirs
34
+ cleanup(dir);
35
+ });
36
+
37
+ it('creates experience file in correct phase directory', () => {
38
+ const result = captureRun({
39
+ phase: 'prd', domain: 'auth', type: 'pitfall', severity: 'high',
40
+ summary: 'OAuth token refresh fails silently', dir,
41
+ });
42
+
43
+ assert.equal(result.phase, 'prd');
44
+ assert.equal(result.severity, 'high');
45
+ assert.match(result.file, /^prd\/\d{4}-\d{2}-\d{2}-.*\.md$/);
46
+
47
+ // Verify file exists and has frontmatter
48
+ const filePath = join(dir, result.file);
49
+ assert.ok(existsSync(filePath), 'experience file should exist');
50
+ const content = readFileSync(filePath, 'utf-8');
51
+ assert.match(content, /^---\nphase: prd\n/);
52
+ assert.match(content, /domain: auth/);
53
+ assert.match(content, /type: pitfall/);
54
+ assert.match(content, /severity: high/);
55
+ });
56
+
57
+ it('creates INDEX.md from scratch when missing', () => {
58
+ captureRun({
59
+ phase: 'build', domain: 'ci', type: 'pattern', severity: 'medium',
60
+ summary: 'Parallel test execution race condition', dir,
61
+ });
62
+
63
+ const indexPath = join(dir, 'INDEX.md');
64
+ assert.ok(existsSync(indexPath), 'INDEX.md should be created');
65
+ const content = readFileSync(indexPath, 'utf-8');
66
+ assert.match(content, /# Solutions Index/);
67
+ assert.match(content, /parallel test execution/i);
68
+ });
69
+
70
+ it('appends to existing INDEX.md', () => {
71
+ captureRun({ phase: 'build', domain: 'ci', type: 'pattern', severity: 'high', summary: 'First issue', dir });
72
+ captureRun({ phase: 'spec', domain: 'api', type: 'insight', severity: 'low', summary: 'Second issue', dir });
73
+
74
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
75
+ assert.match(content, /First issue/);
76
+ assert.match(content, /Second issue/);
77
+ });
78
+
79
+ it('handles Chinese characters in summary (slugify)', () => {
80
+ const result = captureRun({
81
+ phase: 'review', domain: 'frontend', type: 'pitfall', severity: 'high',
82
+ summary: '中文标题测试特殊字符', dir,
83
+ });
84
+
85
+ assert.ok(result.file.includes('review/'), 'file should be in review phase');
86
+ assert.ok(existsSync(join(dir, result.file)), 'file with Chinese slug should exist');
87
+ });
88
+
89
+ it('uses cross-phase as default when no phase specified', () => {
90
+ const result = captureRun({ dir });
91
+ assert.equal(result.phase, 'cross-phase');
92
+ assert.equal(result.domain, 'general');
93
+ assert.equal(result.severity, 'medium');
94
+ });
95
+
96
+ it('rejects invalid phase', () => {
97
+ // capture calls process.exit(1) on invalid phase
98
+ // We test by checking that the function does not create files
99
+ const origExit = process.exit;
100
+ let exitCode = null;
101
+ process.exit = (code) => { exitCode = code; };
102
+ try {
103
+ captureRun({ phase: 'invalid-phase', dir });
104
+ } catch { /* ignore */ }
105
+ process.exit = origExit;
106
+ assert.equal(exitCode, 1, 'should exit with code 1 for invalid phase');
107
+ });
108
+ });
@@ -0,0 +1,147 @@
1
+ // tests/lib/solutions-index-gen.test.mjs
2
+ // Tests for scripts/lib/solutions-index-gen.mjs — full INDEX.md rebuild
3
+
4
+ import { describe, it, before, after, beforeEach } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { mkdtempSync, writeFileSync, existsSync, rmSync, mkdirSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { dirname } from 'node:path';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = join(__dirname, '..', '..');
14
+
15
+ const { run: indexGenRun } = await import(join(ROOT, 'scripts', 'lib', 'solutions-index-gen.mjs'));
16
+
17
+ function makeDir() {
18
+ return mkdtempSync(join(tmpdir(), 'tf-sol-indexgen-'));
19
+ }
20
+
21
+ function cleanup(dir) {
22
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
23
+ }
24
+
25
+ function writeEntry(dir, phase, filename, fm) {
26
+ const phaseDir = join(dir, phase);
27
+ mkdirSync(phaseDir, { recursive: true });
28
+ const fmLines = Object.entries(fm).map(([k, v]) => `${k}: ${v}`).join('\n');
29
+ writeFileSync(join(phaseDir, filename), `---\n${fmLines}\n---\n\n${fm.summary || 'Body text'}\n`, 'utf-8');
30
+ }
31
+
32
+ describe('solutions-index-gen', () => {
33
+ let dir;
34
+
35
+ beforeEach(() => { dir = makeDir(); });
36
+ after(() => { cleanup(dir); });
37
+
38
+ it('rebuilds INDEX.md from phase directories', () => {
39
+ writeEntry(dir, 'prd', '2026-07-01-auth-issue.md', {
40
+ phase: 'prd', domain: 'auth', type: 'pitfall', severity: 'high', date: '2026-07-01',
41
+ });
42
+ writeEntry(dir, 'build', '2026-07-02-ci-pattern.md', {
43
+ phase: 'build', domain: 'ci', type: 'pattern', severity: 'medium', date: '2026-07-02',
44
+ });
45
+
46
+ indexGenRun({ dir });
47
+
48
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
49
+ assert.match(content, /# Solutions Index/);
50
+ assert.match(content, /auth-issue/);
51
+ assert.match(content, /ci-pattern/);
52
+ });
53
+
54
+ it('sorts by severity descending (high before medium before low)', () => {
55
+ writeEntry(dir, 'prd', 'low.md', {
56
+ phase: 'prd', domain: 'd1', type: 'pitfall', severity: 'low', date: '2026-07-03',
57
+ });
58
+ writeEntry(dir, 'prd', 'high.md', {
59
+ phase: 'prd', domain: 'd2', type: 'pitfall', severity: 'high', date: '2026-07-01',
60
+ });
61
+ writeEntry(dir, 'prd', 'medium.md', {
62
+ phase: 'prd', domain: 'd3', type: 'pitfall', severity: 'medium', date: '2026-07-02',
63
+ });
64
+
65
+ indexGenRun({ dir });
66
+
67
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
68
+ const highPos = content.indexOf('d2');
69
+ const medPos = content.indexOf('d3');
70
+ const lowPos = content.indexOf('d1');
71
+ assert.ok(highPos < medPos, 'high should appear before medium');
72
+ assert.ok(medPos < lowPos, 'medium should appear before low');
73
+ });
74
+
75
+ it('sorts by date descending within same severity', () => {
76
+ writeEntry(dir, 'prd', 'older.md', {
77
+ phase: 'prd', domain: 'auth', type: 'pitfall', severity: 'high', date: '2026-07-01',
78
+ });
79
+ writeEntry(dir, 'prd', 'newer.md', {
80
+ phase: 'prd', domain: 'auth', type: 'pitfall', severity: 'high', date: '2026-07-15',
81
+ });
82
+
83
+ indexGenRun({ dir });
84
+
85
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
86
+ const newerPos = content.indexOf('2026-07-15');
87
+ const olderPos = content.indexOf('2026-07-01');
88
+ assert.ok(newerPos < olderPos, 'newer date should appear before older date');
89
+ });
90
+
91
+ it('scans all 7 phase directories', () => {
92
+ const phases = ['prd', 'plan', 'prototype', 'spec', 'build', 'review', 'cross-phase'];
93
+ for (const phase of phases) {
94
+ writeEntry(dir, phase, `2026-07-01-${phase}-entry.md`, {
95
+ phase, domain: 'general', type: 'insight', severity: 'medium', date: '2026-07-01',
96
+ });
97
+ }
98
+
99
+ indexGenRun({ dir });
100
+
101
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
102
+ for (const phase of phases) {
103
+ assert.match(content, new RegExp(phase), `INDEX should contain ${phase} entries`);
104
+ }
105
+ });
106
+
107
+ it('skips non-.md files', () => {
108
+ mkdirSync(join(dir, 'build'), { recursive: true });
109
+ writeFileSync(join(dir, 'build', 'readme.txt'), 'Not a markdown file', 'utf-8');
110
+ writeEntry(dir, 'build', '2026-07-01-real.md', {
111
+ phase: 'build', domain: 'ci', type: 'pattern', severity: 'high', date: '2026-07-01',
112
+ });
113
+
114
+ indexGenRun({ dir });
115
+
116
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
117
+ assert.doesNotMatch(content, /readme\.txt/);
118
+ assert.match(content, /real\.md/);
119
+ });
120
+
121
+ it('truncates to 150 entries (drops lowest priority)', () => {
122
+ // Create 155 entries (all high severity, varying dates)
123
+ mkdirSync(join(dir, 'build'), { recursive: true });
124
+ for (let i = 0; i < 155; i++) {
125
+ const day = String((i % 28) + 1).padStart(2, '0');
126
+ writeEntry(dir, 'build', `entry-${String(i).padStart(3, '0')}.md`, {
127
+ phase: 'build', domain: `d${i}`, type: 'pattern', severity: 'high', date: `2026-07-${day}`,
128
+ });
129
+ }
130
+
131
+ indexGenRun({ dir });
132
+
133
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
134
+ const dataLines = content.split('\n').filter(l => l.startsWith('|') && !l.startsWith('| date') && !l.startsWith('|--'));
135
+ assert.equal(dataLines.length, 150, 'INDEX should be truncated to 150 entries');
136
+ });
137
+
138
+ it('handles empty solutions directory', () => {
139
+ // No phase directories exist
140
+ indexGenRun({ dir });
141
+
142
+ const content = readFileSync(join(dir, 'INDEX.md'), 'utf-8');
143
+ assert.match(content, /# Solutions Index/);
144
+ const dataLines = content.split('\n').filter(l => l.startsWith('|') && !l.startsWith('| date') && !l.startsWith('|--'));
145
+ assert.equal(dataLines.length, 0);
146
+ });
147
+ });