cowork-flow 0.0.39 → 0.0.41

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 (24) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/template/.cowork-flow/.version +1 -1
  4. package/template/.cowork-flow/scripts/application/task_archive.py +148 -21
  5. package/template/.cowork-flow/scripts/application/task_lifecycle.py +3 -1
  6. package/template/.cowork-flow/scripts/commands/add_session.py +137 -124
  7. package/template/.cowork-flow/scripts/commands/task.py +1 -0
  8. package/template/.cowork-flow/scripts/commands/task_archive_commands.py +135 -49
  9. package/template/.cowork-flow/scripts/commands/task_navigation.py +145 -81
  10. package/template/.cowork-flow/scripts/commands/task_parser.py +1 -0
  11. package/template/.cowork-flow/scripts/common/core/paths.py +4 -1
  12. package/template/.cowork-flow/scripts/common/gates/gates.py +114 -108
  13. package/template/.cowork-flow/scripts/common/gates/validate_implementation.py +50 -28
  14. package/template/.cowork-flow/scripts/common/git/git_snapshot.py +14 -0
  15. package/template/.cowork-flow/spec/protocols/decision-review.md +28 -1
  16. package/template/.cowork-flow/spec/protocols/spec-maintenance.md +19 -0
  17. package/template/.cowork-flow/spec/protocols/tdd.md +13 -0
  18. package/template/.cowork-flow/spec/references/definition-of-done.md +1 -1
  19. package/template/.cowork-flow/spec/runtime/host-assets.json +0 -2
  20. package/template/.cowork-flow/spec/runtime/skill-registry.json +29 -1
  21. package/template/.cowork-flow/workflow.md +6 -2
  22. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  23. package/template/.zcode/scaffold/.cowork-flow/workflow.md +6 -2
  24. package/template/skills/doubt-review/SKILL.md +125 -0
@@ -28,6 +28,7 @@ STAGE_GATES = {
28
28
  "complexity",
29
29
  ),
30
30
  "task_complete": (
31
+ "implementation",
31
32
  "tdd_evidence",
32
33
  "test_intent",
33
34
  "runtime_rules",
@@ -37,9 +38,8 @@ STAGE_GATES = {
37
38
  }
38
39
 
39
40
 
40
- def build_default_registry() -> GateRegistry:
41
- registry = GateRegistry()
42
- for binding in (
41
+ def _validator_bindings() -> tuple[ValidatorBinding, ...]:
42
+ return (
43
43
  ValidatorBinding(
44
44
  key="runtime_rules",
45
45
  module="validate_rules",
@@ -79,10 +79,11 @@ def build_default_registry() -> GateRegistry:
79
79
  function="validate_complexity_signals",
80
80
  positional=("repo_root", "task_dir"),
81
81
  ),
82
- ):
83
- registry.register_validator(binding)
82
+ )
83
+
84
84
 
85
- definitions = (
85
+ def _gate_definitions() -> tuple[GateDefinition, ...]:
86
+ return (
86
87
  GateDefinition(
87
88
  id="runtime_rules",
88
89
  validator_key="runtime_rules",
@@ -94,7 +95,7 @@ def build_default_registry() -> GateRegistry:
94
95
  id="implementation",
95
96
  validator_key="implementation",
96
97
  required=True,
97
- block_message="Implementation gate blocked task review",
98
+ block_message="Implementation gate blocked lifecycle transition",
98
99
  warning_message="Implementation violations detected",
99
100
  ),
100
101
  GateDefinition(
@@ -124,7 +125,13 @@ def build_default_registry() -> GateRegistry:
124
125
  warning_message="Complexity review warnings",
125
126
  ),
126
127
  )
127
- for definition in definitions:
128
+
129
+
130
+ def build_default_registry() -> GateRegistry:
131
+ registry = GateRegistry()
132
+ for binding in _validator_bindings():
133
+ registry.register_validator(binding)
134
+ for definition in _gate_definitions():
128
135
  registry.register_gate(definition)
129
136
  return registry
130
137
 
@@ -151,61 +158,57 @@ def _pipeline_violation(
151
158
  ).to_dict()
152
159
 
153
160
 
161
+ def _normalize_violation(
162
+ raw_violation: object,
163
+ context: GateContext,
164
+ definition: GateDefinition,
165
+ ) -> dict:
166
+ if not isinstance(raw_violation, Mapping):
167
+ return _pipeline_violation(
168
+ rule_id="GATE-PROTOCOL-001",
169
+ context=context,
170
+ definition=definition,
171
+ message=f"Gate {definition.id} returned a non-object violation",
172
+ fix_hint="Return violation dictionaries with stable metadata.",
173
+ )
174
+ try:
175
+ return Violation.from_mapping(
176
+ raw_violation,
177
+ scope=context.scope,
178
+ gate_id=definition.id,
179
+ ).to_dict()
180
+ except ValueError as error:
181
+ return _pipeline_violation(
182
+ rule_id="GATE-PROTOCOL-001",
183
+ context=context,
184
+ definition=definition,
185
+ message=f"Gate {definition.id} returned invalid metadata: {error}",
186
+ fix_hint="Return violations with rule_id, severity, and message.",
187
+ )
188
+
189
+
154
190
  def _normalize_violations(
155
191
  raw_result: object,
156
192
  context: GateContext,
157
193
  definition: GateDefinition,
158
194
  ) -> list[dict]:
159
- if not isinstance(raw_result, list):
195
+ if isinstance(raw_result, list):
160
196
  return [
161
- _pipeline_violation(
162
- rule_id="GATE-PROTOCOL-001",
163
- context=context,
164
- definition=definition,
165
- message=(
166
- f"Gate {definition.id} returned "
167
- f"{type(raw_result).__name__}; expected a list of violations"
168
- ),
169
- fix_hint="Return list[dict] from the registered gate validator.",
170
- )
197
+ _normalize_violation(item, context, definition)
198
+ for item in raw_result
171
199
  ]
172
-
173
- normalized: list[dict] = []
174
- for raw_violation in raw_result:
175
- if not isinstance(raw_violation, Mapping):
176
- normalized.append(
177
- _pipeline_violation(
178
- rule_id="GATE-PROTOCOL-001",
179
- context=context,
180
- definition=definition,
181
- message=(
182
- f"Gate {definition.id} returned a non-object violation"
183
- ),
184
- fix_hint="Return violation dictionaries with stable metadata.",
185
- )
186
- )
187
- continue
188
- try:
189
- normalized.append(
190
- Violation.from_mapping(
191
- raw_violation,
192
- scope=context.scope,
193
- gate_id=definition.id,
194
- ).to_dict()
195
- )
196
- except ValueError as error:
197
- normalized.append(
198
- _pipeline_violation(
199
- rule_id="GATE-PROTOCOL-001",
200
- context=context,
201
- definition=definition,
202
- message=f"Gate {definition.id} returned invalid metadata: {error}",
203
- fix_hint=(
204
- "Return violations with rule_id, severity, and message."
205
- ),
206
- )
207
- )
208
- return normalized
200
+ return [
201
+ _pipeline_violation(
202
+ rule_id="GATE-PROTOCOL-001",
203
+ context=context,
204
+ definition=definition,
205
+ message=(
206
+ f"Gate {definition.id} returned "
207
+ f"{type(raw_result).__name__}; expected a list of violations"
208
+ ),
209
+ fix_hint="Return list[dict] from the registered gate validator.",
210
+ )
211
+ ]
209
212
 
210
213
 
211
214
  class GatePipeline:
@@ -214,62 +217,65 @@ class GatePipeline:
214
217
  def __init__(self, registry: GateRegistry | None = None) -> None:
215
218
  self.registry = registry or build_default_registry()
216
219
 
217
- def run(self, context: GateContext) -> GateResult:
218
- executions: list[GateExecution] = []
219
- priority_violations: list[dict] = []
220
- ordinary_violations: list[dict] = []
221
-
222
- for gate_id in STAGE_GATES[context.scope]:
223
- definition = self.registry.gate(gate_id)
224
- try:
225
- raw_result = self.registry.invoke(definition, context)
226
- except GateLoadError as error:
227
- violations = [
228
- _pipeline_violation(
229
- rule_id="GATE-LOAD-001",
230
- context=context,
231
- definition=definition,
232
- message=f"Gate {definition.id} could not load: {error}",
233
- fix_hint=(
234
- "Restore the required validator module and function."
235
- ),
236
- )
237
- ]
238
- except Exception as error:
239
- violations = [
240
- _pipeline_violation(
241
- rule_id="GATE-EXEC-001",
242
- context=context,
243
- definition=definition,
244
- message=f"Gate {definition.id} failed during execution: {error}",
245
- fix_hint=(
246
- "Fix the validator exception; lifecycle gates fail closed."
247
- ),
248
- )
249
- ]
250
- else:
251
- violations = _normalize_violations(
252
- raw_result,
253
- context,
254
- definition,
220
+ def _execute_gate(
221
+ self,
222
+ context: GateContext,
223
+ gate_id: str,
224
+ ) -> GateExecution:
225
+ definition = self.registry.gate(gate_id)
226
+ try:
227
+ raw_result = self.registry.invoke(definition, context)
228
+ except GateLoadError as error:
229
+ violations = [
230
+ _pipeline_violation(
231
+ rule_id="GATE-LOAD-001",
232
+ context=context,
233
+ definition=definition,
234
+ message=f"Gate {definition.id} could not load: {error}",
235
+ fix_hint="Restore the required validator module and function.",
255
236
  )
256
-
257
- gate_result = GateResult.from_violations(
237
+ ]
238
+ except Exception as error:
239
+ violations = [
240
+ _pipeline_violation(
241
+ rule_id="GATE-EXEC-001",
242
+ context=context,
243
+ definition=definition,
244
+ message=f"Gate {definition.id} failed during execution: {error}",
245
+ fix_hint="Fix the validator exception; lifecycle gates fail closed.",
246
+ )
247
+ ]
248
+ else:
249
+ violations = _normalize_violations(raw_result, context, definition)
250
+ return GateExecution(
251
+ definition=definition,
252
+ result=GateResult.from_violations(
258
253
  context.scope,
259
254
  violations,
260
255
  context.task_dir,
261
- )
262
- executions.append(
263
- GateExecution(
264
- definition=definition,
265
- result=gate_result,
266
- )
267
- )
268
- for violation in gate_result.violations:
269
- if str(violation.get("rule_id", "")).startswith("GATE-"):
270
- priority_violations.append(violation)
271
- else:
272
- ordinary_violations.append(violation)
256
+ ),
257
+ )
258
+
259
+ def run(self, context: GateContext) -> GateResult:
260
+ executions = [
261
+ self._execute_gate(context, gate_id)
262
+ for gate_id in STAGE_GATES[context.scope]
263
+ ]
264
+ violations = [
265
+ violation
266
+ for execution in executions
267
+ for violation in execution.result.violations
268
+ ]
269
+ priority_violations = [
270
+ violation
271
+ for violation in violations
272
+ if str(violation.get("rule_id", "")).startswith("GATE-")
273
+ ]
274
+ ordinary_violations = [
275
+ violation
276
+ for violation in violations
277
+ if not str(violation.get("rule_id", "")).startswith("GATE-")
278
+ ]
273
279
 
274
280
  return GateResult(
275
281
  scope=context.scope,
@@ -18,6 +18,8 @@ import subprocess
18
18
  import sys
19
19
  from pathlib import Path
20
20
 
21
+ from common.git.git_snapshot import collect_changed_paths
22
+
21
23
  try:
22
24
  from .validate_rules import (
23
25
  config_violation,
@@ -71,7 +73,15 @@ def validate_implementation(
71
73
  )
72
74
  )
73
75
  violations.extend(_check_premature_abstraction(repo_root, diff_output, rule_index))
74
- violations.extend(_check_unrequested_features(repo_root, diff_output, task_dir, rule_index))
76
+ violations.extend(
77
+ _check_unrequested_features(
78
+ repo_root,
79
+ diff_output,
80
+ task_dir,
81
+ rule_index,
82
+ modified_files=modified_files,
83
+ )
84
+ )
75
85
 
76
86
  return violations
77
87
 
@@ -86,38 +96,44 @@ def _missing_rule(repo_root: Path, rule_id: str) -> dict:
86
96
 
87
97
 
88
98
  def _get_modified_files(repo_root: Path) -> list[str]:
89
- """Get git-tracked modified files under repo_root."""
90
- try:
91
- result = subprocess.run(
92
- ["git", "diff", "--name-only", "--", "."],
93
- cwd=repo_root,
94
- capture_output=True,
95
- text=True,
96
- encoding="utf-8",
97
- errors="replace",
98
- )
99
- if result.returncode == 0:
100
- return [file_path for file_path in result.stdout.strip().split("\n") if file_path]
101
- return []
102
- except Exception:
103
- return []
99
+ """Get staged, unstaged, and untracked files under repo_root."""
100
+ changed_files = collect_changed_paths(repo_root)
101
+ return [
102
+ file_path
103
+ for file_path in changed_files
104
+ if _is_implementation_relevant(file_path)
105
+ ]
106
+
107
+
108
+ def _is_implementation_relevant(file_path: str) -> bool:
109
+ normalized = file_path.replace("\\", "/")
110
+ if not normalized.startswith(".cowork-flow/"):
111
+ return True
112
+ return (
113
+ normalized.startswith(".cowork-flow/spec/")
114
+ or normalized == ".cowork-flow/workflow.md"
115
+ )
104
116
 
105
117
 
106
118
  def _get_git_diff(repo_root: Path, files: list[str]) -> str:
107
- """Get git diff output"""
119
+ """Get unstaged and staged diff output for changed files."""
108
120
  if not files:
109
121
  return ""
110
122
 
111
123
  try:
112
- diff_result = subprocess.run(
113
- ["git", "diff", "--", *files],
114
- cwd=repo_root,
115
- capture_output=True,
116
- text=True,
117
- encoding="utf-8",
118
- errors="replace",
119
- )
120
- return diff_result.stdout
124
+ outputs = []
125
+ for extra_args in ((), ("--cached",)):
126
+ diff_result = subprocess.run(
127
+ ["git", "diff", *extra_args, "--", *files],
128
+ cwd=repo_root,
129
+ capture_output=True,
130
+ text=True,
131
+ encoding="utf-8",
132
+ errors="replace",
133
+ )
134
+ if diff_result.returncode == 0:
135
+ outputs.append(diff_result.stdout)
136
+ return "\n".join(outputs)
121
137
  except Exception:
122
138
  return ""
123
139
 
@@ -229,6 +245,8 @@ def _check_unrequested_features(
229
245
  diff_output: str,
230
246
  task_dir: Path,
231
247
  rule_index: dict[str, dict],
248
+ *,
249
+ modified_files: list[str] | None = None,
232
250
  ) -> list[dict]:
233
251
  """R-AG-005: Require modified files to be explicitly listed."""
234
252
  del diff_output
@@ -242,8 +260,12 @@ def _check_unrequested_features(
242
260
  return violations
243
261
 
244
262
  # Get modified project files (exclude .cowork-flow/ metadata)
245
- modified_files = _get_modified_files(repo_root)
246
- for file_path in modified_files:
263
+ changed_files = (
264
+ modified_files
265
+ if modified_files is not None
266
+ else _get_modified_files(repo_root)
267
+ )
268
+ for file_path in changed_files:
247
269
  # Skip .cowork-flow metadata files and template files
248
270
  if ".cowork-flow/" in file_path:
249
271
  continue
@@ -19,6 +19,14 @@ class ChangedFile:
19
19
 
20
20
  def collect_changed_files(repo_root: Path) -> list[ChangedFile]:
21
21
  """Return modified, staged, and untracked files from git status."""
22
+ prefix_rc, prefix_stdout, _ = _run_git_command(
23
+ ["rev-parse", "--show-prefix"],
24
+ cwd=repo_root,
25
+ )
26
+ if prefix_rc != 0:
27
+ return []
28
+ repo_prefix = prefix_stdout.strip().replace("\\", "/")
29
+
22
30
  rc, stdout, _ = _run_git_command(
23
31
  ["status", "--porcelain=v1", "-uall", "--", "."],
24
32
  cwd=repo_root,
@@ -32,6 +40,12 @@ def collect_changed_files(repo_root: Path) -> list[ChangedFile]:
32
40
  if parsed is None:
33
41
  continue
34
42
  path, statuses = parsed
43
+ if repo_prefix:
44
+ if not path.startswith(repo_prefix):
45
+ continue
46
+ path = path[len(repo_prefix):]
47
+ if not path:
48
+ continue
35
49
  changed.setdefault(path, set()).update(statuses)
36
50
 
37
51
  return [
@@ -2,18 +2,39 @@
2
2
 
3
3
  > Internal protocol enforced by workflow readiness; it is not a public Skill.
4
4
 
5
+ ## Purpose
6
+
7
+ `decision-review` is the mandatory L2 evidence gate for non-trivial decisions.
8
+ It preserves the adversarial doubt cycle from `doubt-review` while keeping the
9
+ runtime authority in `decision-review.jsonl`.
10
+
5
11
  ## Enforcement
6
12
 
7
13
  - L2 tasks: mandatory before task start.
8
14
  - L0/L1 tasks: advisory and never blocked by missing decision review evidence.
9
15
  - The runtime authority is `decision-review.jsonl`; prose notes do not satisfy the gate.
10
16
 
17
+ ## Doubt Cycle
18
+
19
+ Every accepted L2 decision review must be derived from this bounded cycle:
20
+
21
+ 1. CLAIM: state the decision and why being wrong would be costly.
22
+ 2. EXTRACT: isolate the minimal reviewable artifact or design statement.
23
+ 3. DOUBT: ask a fresh context to find problems in ARTIFACT + CONTRACT, not CLAIM.
24
+ 4. RECONCILE: classify each finding as contract misunderstanding, actionable,
25
+ accepted trade-off, or noise.
26
+ 5. STOP: stop after trivial or already-considered findings, three cycles, or the
27
+ user explicitly says enough.
28
+
29
+ The reviewer receives ARTIFACT + CONTRACT, not CLAIM. CLAIM is recorded for
30
+ traceability, but it must not bias the adversarial review prompt.
31
+
11
32
  ## Evidence
12
33
 
13
34
  Write one UTF-8 JSON object per line to `<task>/decision-review.jsonl`:
14
35
 
15
36
  ```json
16
- {"acceptanceId":"AC-007A","claim":"The selected direction is safe.","contract":"The invariant that must hold.","reviewerContext":"fresh","findings":[],"resolution":"accepted"}
37
+ {"acceptanceId":"AC-007A","claim":"The selected direction is safe.","contract":"The invariant that must hold.","reviewerContext":"fresh","findings":[],"resolution":"accepted","artifact":"Focused diff or design statement reviewed via ARTIFACT + CONTRACT.","reconciliation":[],"cycleCount":1}
17
38
  ```
18
39
 
19
40
  Required fields:
@@ -25,4 +46,10 @@ Required fields:
25
46
  - `findings`: JSON array
26
47
  - `resolution`: exactly `accepted`
27
48
 
49
+ Recommended fields:
50
+
51
+ - `artifact`: minimal EXTRACT artifact reviewed.
52
+ - `reconciliation`: how each finding was classified.
53
+ - `cycleCount`: number of completed doubt cycles, never greater than 3.
54
+
28
55
  Missing, malformed, stale-context, or unaccepted records block L2 task start.
@@ -8,6 +8,25 @@ Update `.cowork-flow/spec/` when a change introduces or changes a reusable comma
8
8
 
9
9
  Do not add one-off implementation narration. Prefer the narrowest existing spec and update its index when a new topic is introduced.
10
10
 
11
+ ## Choose Location
12
+
13
+ - `backend/` or `frontend/`: implementation contracts, signatures, examples, validation behavior, and test points.
14
+ - `guides/`: short thinking checklists and pointers to deeper specs.
15
+ - `contracts/`: cross-agent, runtime, host-adapter, or persisted-state contracts.
16
+ - `runtime/` or `schemas/`: machine-readable rules, registries, and schema-owned metadata.
17
+ - `workflow.md` or `AGENTS.md`: process rules, phase gates, collaboration rules, or task routing behavior.
18
+
19
+ ## Write
20
+
21
+ Keep each update concrete:
22
+
23
+ - State the trigger and scope.
24
+ - Show the contract or command shape.
25
+ - Include good/bad cases when useful.
26
+ - State tests or checks that protect the behavior.
27
+ - Update the matching `index.md` when adding a new topic.
28
+ - Check for duplicate guidance and remove stale wording before finishing.
29
+
11
30
  ## Output
12
31
 
13
32
  Every Host reports `specUpdates` as one of:
@@ -28,3 +28,16 @@ Write one JSON object per line to `<task>/tdd.jsonl` with:
28
28
  - `greenCommand`, `greenExitCode`, `broaderVerification`
29
29
 
30
30
  Only docs/comment/format-only work may use a documented `exemption`.
31
+
32
+ ## Anti-Rationalization
33
+
34
+ The following excuses do not exempt the TDD evidence requirement:
35
+
36
+ | Excuse | Why It Fails | Required Alternative |
37
+ |---|---|---|
38
+ | "This logic is simple, no test needed" | Simple logic still breaks on edge cases and later edits. | Write the smallest behavior test for the acceptance ID. |
39
+ | "Other tests already cover this" | Invisible coverage is not red-green evidence. | Point to the exact redCommand and acceptanceId in `tdd.jsonl`. |
40
+ | "I'll add tests after implementation" | Post-implementation tests are not a red-green cycle. | Red first, then green; record both commands. |
41
+ | "It looks correct" | Visual inspection misses scenarios that tests can pin down. | Turn the expected behavior and edge case into assertions. |
42
+ | "I've written similar tests" | Similar structure is not the same behavior. | Re-run the redCommand for this acceptanceId. |
43
+ | "This is internal only" | Internal behavior still affects callers and workflow state. | Test through the public entry point, or directly test the internal contract when that is the narrowest stable surface. |
@@ -10,7 +10,7 @@
10
10
  | G2 | L1/L2: tdd.jsonl 有 red-green evidence | ./cowork-flow/run task validate |
11
11
  | G3 | check phase 完成,无 unresolved blocker | check.jsonl status=pass |
12
12
  | G4 | 无新引入 lint/type/build 警告 | lint / build 命令通过 |
13
- | G5 | git diff 仅含任务相关变更 | git diff --name-only |
13
+ | G5 | Git status snapshot 仅含任务相关变更,包括 staged、unstaged、untracked | git status --porcelain=v1 -uall |
14
14
 
15
15
  ## L0 专属(docs/format/comments only)
16
16
 
@@ -190,7 +190,6 @@
190
190
  ".agents/skills/before-dev",
191
191
  ".agents/skills/check",
192
192
  ".agents/skills/continue",
193
- ".agents/skills/doubt-review",
194
193
  ".agents/skills/entry-boundary",
195
194
  ".agents/skills/finish-work",
196
195
  ".agents/skills/party-mode-v2",
@@ -201,7 +200,6 @@
201
200
  ".claude/skills/before-dev",
202
201
  ".claude/skills/check",
203
202
  ".claude/skills/continue",
204
- ".claude/skills/doubt-review",
205
203
  ".claude/skills/entry-boundary",
206
204
  ".claude/skills/finish-work",
207
205
  ".claude/skills/party-mode-v2",
@@ -149,12 +149,40 @@
149
149
  "source": ".cowork-flow/spec/protocols/decision-review.md",
150
150
  "managedPaths": []
151
151
  },
152
+ {
153
+ "id": "doubt-review",
154
+ "displayName": "Doubt Review",
155
+ "aliases": [
156
+ "adversarial-review"
157
+ ],
158
+ "kind": "protocol",
159
+ "visibility": "public",
160
+ "status": "active",
161
+ "statuses": [
162
+ "planning",
163
+ "in_progress",
164
+ "review",
165
+ "delegated_subtask"
166
+ ],
167
+ "intents": [
168
+ "doubt_review"
169
+ ],
170
+ "enforcement": "advisory",
171
+ "runtimeGate": null,
172
+ "runtimeCommand": null,
173
+ "evidenceArtifact": "decision-review.jsonl",
174
+ "source": "skills/doubt-review/SKILL.md",
175
+ "managedPaths": [
176
+ ".agents/skills/doubt-review/",
177
+ ".claude/skills/doubt-review/"
178
+ ]
179
+ },
152
180
  {
153
181
  "id": "game-design",
154
182
  "displayName": "Game Design",
155
183
  "aliases": [],
156
184
  "kind": "domain",
157
- "visibility": "internal",
185
+ "visibility": "public",
158
186
  "status": "active",
159
187
  "statuses": [
160
188
  "planning",
@@ -216,7 +216,7 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
216
216
  - 所有声明通过的验证都有命令输出依据。
217
217
  - 规格已更新,或明确判断无需更新。
218
218
  - 计划状态、任务状态、`change` 元数据不冲突。
219
- - 先归档,再记录会话,然后提交。
219
+ - 先归档并提交实现,再用真实实现提交哈希记录会话;会话元数据使用 follow-up metadata commit 提交。
220
220
  - 不纳入无关脏改。
221
221
 
222
222
  2. 顺序:
@@ -226,11 +226,15 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
226
226
  git diff --check
227
227
  npm run test:all
228
228
  ./.cowork-flow/run task archive <task-name>
229
- ./.cowork-flow/run add-session --title "<title>" --commit "-" --summary "<summary>"
230
229
  git status --short
231
230
  git add <expected files>
232
231
  git commit -m "<message>"
232
+ implementation_commit="$(git rev-parse HEAD)"
233
+ ./.cowork-flow/run add-session --title "<title>" --commit "$implementation_commit" --summary "<summary>"
234
+ git add .cowork-flow/workspace/<developer>/
235
+ git commit -m "docs(session): record <title>"
233
236
  ```
237
+ 第二个提交是明确的 follow-up metadata commit;session 中的 `commit` 始终指向前一个真实实现提交,不使用永久占位值。
234
238
  `task archive <task-name>` 会归档 task,并自动归档 `change.yaml.task`
235
239
  指向该 task 的 active change;无法通过 `change validate` 的 change 不会被自动归档。
236
240
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-flow",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "description": "AI 协作工作流引擎:任务生命周期、子代理调度、门禁与状态注入。将 cowork-flow 的完整工作流带入 ZCode。",
5
5
  "author": {
6
6
  "name": "fisklikewater"
@@ -216,7 +216,7 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
216
216
  - 所有声明通过的验证都有命令输出依据。
217
217
  - 规格已更新,或明确判断无需更新。
218
218
  - 计划状态、任务状态、`change` 元数据不冲突。
219
- - 先归档,再记录会话,然后提交。
219
+ - 先归档并提交实现,再用真实实现提交哈希记录会话;会话元数据使用 follow-up metadata commit 提交。
220
220
  - 不纳入无关脏改。
221
221
 
222
222
  2. 顺序:
@@ -226,11 +226,15 @@ L2 任务在 `task start` 前必须通过 readiness gate;同一 blocker 列表
226
226
  git diff --check
227
227
  npm run test:all
228
228
  ./.cowork-flow/run task archive <task-name>
229
- ./.cowork-flow/run add-session --title "<title>" --commit "-" --summary "<summary>"
230
229
  git status --short
231
230
  git add <expected files>
232
231
  git commit -m "<message>"
232
+ implementation_commit="$(git rev-parse HEAD)"
233
+ ./.cowork-flow/run add-session --title "<title>" --commit "$implementation_commit" --summary "<summary>"
234
+ git add .cowork-flow/workspace/<developer>/
235
+ git commit -m "docs(session): record <title>"
233
236
  ```
237
+ 第二个提交是明确的 follow-up metadata commit;session 中的 `commit` 始终指向前一个真实实现提交,不使用永久占位值。
234
238
  `task archive <task-name>` 会归档 task,并自动归档 `change.yaml.task`
235
239
  指向该 task 的 active change;无法通过 `change validate` 的 change 不会被自动归档。
236
240