cowork-flow 0.0.47 → 0.0.49

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 (90) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +102 -23
  3. package/package.json +8 -3
  4. package/src/cli.js +6 -0
  5. package/src/commands/init.js +0 -83
  6. package/src/commands/source-refresh.js +40 -0
  7. package/src/commands/sync.js +6 -0
  8. package/src/commands/update.js +59 -6
  9. package/src/lib/copy-template.js +200 -50
  10. package/src/lib/host-assets.js +367 -64
  11. package/src/lib/plan-applier.js +47 -0
  12. package/template/.claude/hooks/inject-workflow-state.py +7 -0
  13. package/template/.codex/hooks/inject-workflow-state.py +7 -0
  14. package/template/.codex/hooks.json +1 -1
  15. package/template/.cowork-flow/.version +1 -1
  16. package/template/.cowork-flow/config.yaml +0 -11
  17. package/template/.cowork-flow/scripts/adapters/cli/init_developer.py +0 -1
  18. package/template/.cowork-flow/scripts/adapters/cli/subagent.py +7 -4
  19. package/template/.cowork-flow/scripts/adapters/cli/task.py +8 -484
  20. package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +15 -134
  21. package/template/.cowork-flow/scripts/adapters/cli/task_context_commands.py +7 -1
  22. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +388 -0
  23. package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +163 -2
  24. package/template/.cowork-flow/scripts/adapters/git/git_context.py +16 -193
  25. package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +170 -84
  26. package/template/.cowork-flow/scripts/infra/config.py +0 -17
  27. package/template/.cowork-flow/scripts/infra/developer_profile.py +0 -95
  28. package/template/.cowork-flow/scripts/infra/paths.py +0 -79
  29. package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
  30. package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
  31. package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
  32. package/template/.cowork-flow/scripts/kernel/workflow_route.py +15 -15
  33. package/template/.cowork-flow/scripts/run.py +0 -5
  34. package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
  35. package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
  36. package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
  37. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +186 -69
  38. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
  39. package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
  40. package/template/.cowork-flow/scripts/services/readiness.py +3 -170
  41. package/template/.cowork-flow/scripts/services/task_archive.py +115 -24
  42. package/template/.cowork-flow/scripts/services/task_context.py +103 -365
  43. package/template/.cowork-flow/scripts/services/task_creation.py +40 -5
  44. package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
  45. package/template/.cowork-flow/scripts/services/task_routing.py +80 -67
  46. package/template/.cowork-flow/scripts/services/task_utils.py +0 -146
  47. package/template/.cowork-flow/scripts/services/workflow_runtime.py +1 -1
  48. package/template/.cowork-flow/spec/contracts/capabilities.md +23 -0
  49. package/template/.cowork-flow/spec/contracts/decision-anchor.md +2 -2
  50. package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
  51. package/template/.cowork-flow/spec/contracts/index.md +1 -0
  52. package/template/.cowork-flow/spec/contracts/party-mode-v2-board.md +20 -0
  53. package/template/.cowork-flow/spec/contracts/plan-binding.md +50 -0
  54. package/template/.cowork-flow/spec/contracts/skill-owned-actions.md +31 -7
  55. package/template/.cowork-flow/spec/guides/index.md +3 -1
  56. package/template/.cowork-flow/spec/index.md +5 -4
  57. package/template/.cowork-flow/spec/references/definition-of-done.md +2 -3
  58. package/template/.cowork-flow/spec/references/orchestration-patterns.md +1 -1
  59. package/template/.cowork-flow/spec/runtime/contract-registry.json +14 -0
  60. package/template/.cowork-flow/spec/runtime/host-assets.json +73 -3
  61. package/template/.cowork-flow/spec/runtime/index.md +1 -1
  62. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +150 -8
  63. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  64. package/template/skills/batch-execution/SKILL.md +37 -3
  65. package/template/skills/batch-execution/scripts/batch_action.py +4 -0
  66. package/template/skills/batch-execution/scripts/batch_execution.py +120 -499
  67. package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
  68. package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
  69. package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
  70. package/template/skills/brainstorming/SKILL.md +8 -7
  71. package/template/skills/cowork-flow/SKILL.md +3 -0
  72. package/template/skills/party-mode/scripts/party_action_contract.py +113 -0
  73. package/template/skills/party-mode/scripts/party_actions.py +95 -0
  74. package/template/skills/party-mode/scripts/party_board_store.py +148 -0
  75. package/template/skills/party-mode/scripts/party_mode_v2.py +345 -317
  76. package/template/skills/party-mode/scripts/party_reports.py +177 -0
  77. package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
  78. package/template/skills/runtime-health/SKILL.md +14 -2
  79. package/template/skills/runtime-health/scripts/doctor.py +278 -41
  80. package/template/skills/task-planning/SKILL.md +105 -61
  81. package/template/skills/task-review/SKILL.md +4 -4
  82. package/template/skills/task-review/scripts/review_check.py +277 -20
  83. package/template/skills/test-first/SKILL.md +18 -3
  84. package/template/skills/test-first/manifest.json +8 -4
  85. package/template/.cowork-flow/changes/archive/.gitkeep +0 -1
  86. package/template/.cowork-flow/scripts/adapters/cli/add_session.py +0 -465
  87. package/template/.cowork-flow/scripts/adapters/cli/change.py +0 -412
  88. package/template/.cowork-flow/workspace/index.md +0 -102
  89. package/template/.zcode/scaffold/AGENTS.md +0 -145
  90. package/template/.zcode/scaffold/CLAUDE.md +0 -5
@@ -7,6 +7,7 @@ import argparse
7
7
  import ast
8
8
  import json
9
9
  import re
10
+ import subprocess
10
11
  import sys
11
12
  from pathlib import Path
12
13
  from typing import Any
@@ -35,6 +36,7 @@ from runtime.session_state import is_main_session
35
36
  PROTECTED_PREFIX = "Protected workflow/spec file changed outside main session: "
36
37
  UNLISTED_PREFIX = "Modified file not listed in implement.jsonl: "
37
38
  TEST_SUFFIXES = {".py", ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs"}
39
+ FUNCTION_LINE_SOFT_LIMIT = 60
38
40
  CODE_SUFFIXES = {
39
41
  ".py",
40
42
  ".js",
@@ -119,12 +121,71 @@ def _infer_dev_type(task_data: dict[str, Any], changed_files: list[str]) -> str:
119
121
  return ""
120
122
 
121
123
 
124
+ def _normalized_issue(
125
+ *,
126
+ code: str,
127
+ severity: str,
128
+ path: str,
129
+ message: str,
130
+ source: str,
131
+ **extra: str,
132
+ ) -> dict[str, str]:
133
+ issue = {
134
+ "code": code,
135
+ "severity": severity,
136
+ "path": path,
137
+ "message": message,
138
+ "source": source,
139
+ }
140
+ issue.update(extra)
141
+ return issue
142
+
143
+
144
+ def _lifecycle_issue_fact(issue: Any) -> dict[str, str]:
145
+ return _normalized_issue(
146
+ code=str(getattr(issue, "code", "") or "lifecycle_issue"),
147
+ severity="error",
148
+ path=str(getattr(issue, "path", "") or ""),
149
+ message=str(getattr(issue, "message", "") or ""),
150
+ source="lifecycle",
151
+ )
152
+
153
+
154
+ def _fallback_lifecycle_issue(
155
+ *,
156
+ code: str,
157
+ path: str,
158
+ message: str,
159
+ ) -> dict[str, str]:
160
+ return _normalized_issue(
161
+ code=code,
162
+ severity="error",
163
+ path=path,
164
+ message=message,
165
+ source="lifecycle",
166
+ )
167
+
168
+
122
169
  def _scope_facts(
123
170
  repo_root: Path,
124
171
  task_dir: Path,
125
172
  *,
126
173
  allow_spec_file_modifications: bool,
127
174
  ) -> dict[str, Any]:
175
+ scope_facts, _ = _scope_facts_with_issues(
176
+ repo_root,
177
+ task_dir,
178
+ allow_spec_file_modifications=allow_spec_file_modifications,
179
+ )
180
+ return scope_facts
181
+
182
+
183
+ def _scope_facts_with_issues(
184
+ repo_root: Path,
185
+ task_dir: Path,
186
+ *,
187
+ allow_spec_file_modifications: bool,
188
+ ) -> tuple[dict[str, Any], list[dict[str, str]]]:
128
189
  result = LifecycleCheckRunner(repo_root).review(
129
190
  task_dir,
130
191
  allow_spec_file_modifications=allow_spec_file_modifications,
@@ -132,19 +193,62 @@ def _scope_facts(
132
193
  protected: list[str] = []
133
194
  unlisted: list[str] = []
134
195
  context_issues: list[str] = []
135
- for message in result.blockers:
136
- if message.startswith(PROTECTED_PREFIX):
137
- protected.append(message.removeprefix(PROTECTED_PREFIX))
138
- elif message.startswith(UNLISTED_PREFIX):
139
- unlisted.append(message.removeprefix(UNLISTED_PREFIX))
140
- else:
141
- context_issues.append(message)
142
- return {
143
- "unlistedChangedFiles": unlisted,
144
- "protectedWorkflowFiles": protected,
145
- "contextIssues": context_issues,
146
- "lifecycleMessages": list(result.blockers),
147
- }
196
+ normalized_issues: list[dict[str, str]] = []
197
+ issues = tuple(getattr(result, "issues", ()) or ())
198
+ if issues:
199
+ for issue in issues:
200
+ code = str(getattr(issue, "code", "") or "")
201
+ path = getattr(issue, "path", None)
202
+ message = str(getattr(issue, "message", "") or "")
203
+ normalized_issues.append(_lifecycle_issue_fact(issue))
204
+ if code == "protected_workflow_file":
205
+ protected.append(str(path or message.removeprefix(PROTECTED_PREFIX)))
206
+ elif code == "unlisted_changed_file":
207
+ normalized = str(path or message.removeprefix(UNLISTED_PREFIX))
208
+ if normalized not in protected:
209
+ unlisted.append(normalized)
210
+ else:
211
+ context_issues.append(message)
212
+ else:
213
+ for message in result.blockers:
214
+ if message.startswith(PROTECTED_PREFIX):
215
+ path = message.removeprefix(PROTECTED_PREFIX)
216
+ protected.append(path)
217
+ normalized_issues.append(
218
+ _fallback_lifecycle_issue(
219
+ code="protected_workflow_file",
220
+ path=path,
221
+ message=message,
222
+ )
223
+ )
224
+ elif message.startswith(UNLISTED_PREFIX):
225
+ path = message.removeprefix(UNLISTED_PREFIX)
226
+ unlisted.append(path)
227
+ normalized_issues.append(
228
+ _fallback_lifecycle_issue(
229
+ code="unlisted_changed_file",
230
+ path=path,
231
+ message=message,
232
+ )
233
+ )
234
+ else:
235
+ context_issues.append(message)
236
+ normalized_issues.append(
237
+ _fallback_lifecycle_issue(
238
+ code="lifecycle_context_issue",
239
+ path="",
240
+ message=message,
241
+ )
242
+ )
243
+ return (
244
+ {
245
+ "unlistedChangedFiles": unlisted,
246
+ "protectedWorkflowFiles": protected,
247
+ "contextIssues": context_issues,
248
+ "lifecycleMessages": list(result.blockers),
249
+ },
250
+ normalized_issues,
251
+ )
148
252
 
149
253
 
150
254
  def _check_context_sources(repo_root: Path, task_dir: Path) -> list[dict[str, Any]]:
@@ -214,6 +318,124 @@ def _is_code_file(path: str) -> bool:
214
318
  return Path(path).suffix in CODE_SUFFIXES
215
319
 
216
320
 
321
+ def _python_function_lengths(content: str) -> dict[str, dict[str, int]]:
322
+ try:
323
+ tree = ast.parse(content.lstrip("\ufeff"))
324
+ except SyntaxError:
325
+ return {}
326
+
327
+ functions: dict[str, dict[str, int]] = {}
328
+
329
+ def visit_nodes(nodes: list[ast.stmt], prefix: tuple[str, ...]) -> None:
330
+ for node in nodes:
331
+ if isinstance(node, ast.ClassDef):
332
+ visit_nodes(node.body, (*prefix, node.name))
333
+ continue
334
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
335
+ end_lineno = getattr(node, "end_lineno", node.lineno)
336
+ name = ".".join((*prefix, node.name))
337
+ functions[name] = {
338
+ "line": node.lineno,
339
+ "lines": end_lineno - node.lineno + 1,
340
+ }
341
+ visit_nodes(node.body, (*prefix, node.name))
342
+
343
+ visit_nodes(tree.body, ())
344
+ return functions
345
+
346
+
347
+ def _git_baseline_text(repo_root: Path, path: str) -> str | None:
348
+ result = subprocess.run(
349
+ ["git", "show", f"HEAD:{path}"],
350
+ cwd=repo_root,
351
+ capture_output=True,
352
+ text=True,
353
+ encoding="utf-8",
354
+ check=False,
355
+ )
356
+ if result.returncode != 0:
357
+ return None
358
+ return result.stdout
359
+
360
+
361
+ def _complexity_signals(repo_root: Path, changed_files: list[str]) -> list[dict[str, str]]:
362
+ signals: list[dict[str, str]] = []
363
+ for raw_path in changed_files:
364
+ normalized = raw_path.replace("\\", "/").removeprefix("./")
365
+ if Path(normalized).suffix != ".py":
366
+ continue
367
+ full_path = repo_root / normalized
368
+ if not full_path.is_file():
369
+ continue
370
+ try:
371
+ current_functions = _python_function_lengths(
372
+ full_path.read_text(encoding="utf-8")
373
+ )
374
+ except (OSError, UnicodeDecodeError):
375
+ continue
376
+ baseline_text = _git_baseline_text(repo_root, normalized)
377
+ baseline_functions = (
378
+ _python_function_lengths(baseline_text) if baseline_text is not None else {}
379
+ )
380
+ for function_name, current in sorted(current_functions.items()):
381
+ current_lines = current["lines"]
382
+ if current_lines <= FUNCTION_LINE_SOFT_LIMIT:
383
+ continue
384
+ baseline_lines = baseline_functions.get(function_name, {}).get("lines", 0)
385
+ budget_baseline = (
386
+ baseline_lines if baseline_lines > FUNCTION_LINE_SOFT_LIMIT else 0
387
+ )
388
+ if budget_baseline == 0:
389
+ code = "complexity_new_long_function"
390
+ message = (
391
+ f"Function {function_name} is {current_lines} lines; "
392
+ f"soft budget is {FUNCTION_LINE_SOFT_LIMIT} lines."
393
+ )
394
+ elif current_lines > baseline_lines:
395
+ code = "complexity_hotspot_grew"
396
+ message = (
397
+ f"Function {function_name} grew from {baseline_lines} "
398
+ f"to {current_lines} lines; split responsibilities or record intent."
399
+ )
400
+ else:
401
+ code = "complexity_existing_hotspot"
402
+ message = (
403
+ f"Existing hotspot {function_name} remains {current_lines} lines; "
404
+ "warning only for reviewer awareness."
405
+ )
406
+ signals.append(
407
+ {
408
+ "code": code,
409
+ "file": normalized,
410
+ "function": function_name,
411
+ "line": str(current["line"]),
412
+ "baselineLines": str(budget_baseline),
413
+ "currentLines": str(current_lines),
414
+ "limitLines": str(FUNCTION_LINE_SOFT_LIMIT),
415
+ "reason": message,
416
+ }
417
+ )
418
+ return signals
419
+
420
+
421
+ def _complexity_issues(signals: list[dict[str, str]]) -> list[dict[str, str]]:
422
+ return [
423
+ _normalized_issue(
424
+ code=signal["code"],
425
+ severity="warning",
426
+ path=signal["file"],
427
+ message=signal["reason"],
428
+ source="complexity",
429
+ function=signal["function"],
430
+ line=signal["line"],
431
+ baselineLines=signal["baselineLines"],
432
+ currentLines=signal["currentLines"],
433
+ limitLines=signal["limitLines"],
434
+ )
435
+ for signal in signals
436
+ ]
437
+
438
+
217
439
  def _python_test_names(content: str) -> list[str]:
218
440
  try:
219
441
  tree = ast.parse(content.lstrip("\ufeff"))
@@ -284,7 +506,28 @@ def _test_intent_signals(repo_root: Path, changed_files: list[str]) -> dict[str,
284
506
  }
285
507
 
286
508
 
287
- def _verification_hints(changed_files: list[str], test_signals: dict[str, Any]) -> list[str]:
509
+ def _test_intent_issues(test_signals: dict[str, Any]) -> list[dict[str, str]]:
510
+ issues: list[dict[str, str]] = []
511
+ for signal in test_signals["shallowAssertionSignals"]:
512
+ issues.append(
513
+ _normalized_issue(
514
+ code="shallow_assertion_signal",
515
+ severity="warning",
516
+ path=str(signal.get("file") or ""),
517
+ message=str(signal.get("reason") or "shallow test intent signal"),
518
+ source="test-intent",
519
+ testName=str(signal.get("testName") or ""),
520
+ signal=str(signal.get("signal") or ""),
521
+ )
522
+ )
523
+ return issues
524
+
525
+
526
+ def _verification_hints(
527
+ changed_files: list[str],
528
+ test_signals: dict[str, Any],
529
+ complexity_signals: list[dict[str, str]],
530
+ ) -> list[str]:
288
531
  hints: list[str] = []
289
532
  code_files = [
290
533
  path
@@ -298,6 +541,8 @@ def _verification_hints(changed_files: list[str], test_signals: dict[str, Any])
298
541
  )
299
542
  if test_signals["shallowAssertionSignals"]:
300
543
  hints.append("Review shallowAssertionSignals before accepting test intent.")
544
+ if complexity_signals:
545
+ hints.append("Review complexity soft-budget warnings before accepting large functions.")
301
546
  return hints
302
547
 
303
548
 
@@ -306,6 +551,12 @@ def build_report(repo_root: Path, task_dir: Path) -> dict[str, Any]:
306
551
  changed_files = collect_changed_paths(repo_root)
307
552
  dev_type = _infer_dev_type(task_data, changed_files)
308
553
  test_signals = _test_intent_signals(repo_root, changed_files)
554
+ complexity_signals = _complexity_signals(repo_root, changed_files)
555
+ scope_facts, scope_issues = _scope_facts_with_issues(
556
+ repo_root,
557
+ task_dir,
558
+ allow_spec_file_modifications=is_main_session(repo_root),
559
+ )
309
560
  return {
310
561
  "reportVersion": 1,
311
562
  "mode": "advisory",
@@ -315,11 +566,12 @@ def build_report(repo_root: Path, task_dir: Path) -> dict[str, Any]:
315
566
  "devType": dev_type,
316
567
  },
317
568
  "changedFiles": changed_files,
318
- "scopeFacts": _scope_facts(
319
- repo_root,
320
- task_dir,
321
- allow_spec_file_modifications=is_main_session(repo_root),
322
- ),
569
+ "scopeFacts": scope_facts,
570
+ "normalizedIssues": [
571
+ *scope_issues,
572
+ *_test_intent_issues(test_signals),
573
+ *_complexity_issues(complexity_signals),
574
+ ],
323
575
  "specSources": _spec_sources(
324
576
  repo_root,
325
577
  task_dir,
@@ -327,7 +579,12 @@ def build_report(repo_root: Path, task_dir: Path) -> dict[str, Any]:
327
579
  changed_files=changed_files,
328
580
  ),
329
581
  "testIntentSignals": test_signals,
330
- "verificationHints": _verification_hints(changed_files, test_signals),
582
+ "complexitySignals": complexity_signals,
583
+ "verificationHints": _verification_hints(
584
+ changed_files,
585
+ test_signals,
586
+ complexity_signals,
587
+ ),
331
588
  "notes": [
332
589
  "This report is advisory and does not mutate task state.",
333
590
  "Do not treat natural-language spec review as a runtime lifecycle gate.",
@@ -9,9 +9,13 @@ Use this Skill to practice test-first implementation inside the active cowork-fl
9
9
 
10
10
  ## Trigger
11
11
 
12
- Use for behavior, state, protocol, CLI, data-format, permission, or error-handling changes when a failing test is the clearest way to pin expected behavior. For docs/comment/format-only work, skip TDD and run the smallest relevant verification command.
12
+ Use for behavior, state, protocol, CLI, data-format, permission, or error-handling changes. Behavior changes default to a red test before implementation: first pin the expected behavior with the smallest meaningful failing test, then make the minimum code change needed to turn it green.
13
13
 
14
- ## Red-Green-Refactor
14
+ For docs/comment/format-only work, skip TDD and run the smallest relevant verification command.
15
+
16
+ ## Red test before implementation
17
+
18
+ Do not modify production code before the red test for a behavior change unless a documented exception applies. The normal path is:
15
19
 
16
20
  1. Map the behavior to a `decision-anchor.md` acceptance ID when one exists.
17
21
  2. Write the smallest meaningful test that fails because the target behavior is missing or wrong.
@@ -20,6 +24,17 @@ Use for behavior, state, protocol, CLI, data-format, permission, or error-handli
20
24
  5. Run the same test to green, then run directly dependent tests.
21
25
  6. Refactor only after the behavior is pinned green.
22
26
 
27
+ Tests added after implementation are regression tests, not evidence that a red-green cycle happened. They may still be useful, but do not describe them as test-first work unless the red failure was observed before the implementation change.
28
+
29
+ ## Exceptions
30
+
31
+ If red-first is skipped, state the reason before or with the implementation summary and name the substitute verification. Acceptable reasons are limited to:
32
+
33
+ - docs/comment/format-only changes where no behavior changes.
34
+ - an existing precise failing test already reproduces the target behavior.
35
+ - the behavior cannot be reproduced locally after a focused attempt; describe the attempted command and observed result.
36
+ - no usable test entry point exists; explain the gap and use the narrowest manual or static verification available.
37
+
23
38
  ## Output
24
39
 
25
40
  Do not write TDD evidence objects to `check.jsonl`, do not create `tdd.jsonl`, and do not create TDD exemption records. Report the exact red/green commands in the agent response or task review narrative when useful; the workflow does not validate them as separate evidence artifacts.
@@ -32,6 +47,6 @@ Do not use these excuses to skip meaningful behavior tests:
32
47
  |---|---|---|
33
48
  | "This logic is simple, no test needed" | Simple logic still breaks on edge cases and later edits. | Write the smallest behavior test, or state the concrete non-test verification used. |
34
49
  | "Other tests already cover this" | Invisible coverage is hard to review. | Name the exact existing test command and behavior it covers. |
35
- | "I'll add tests after implementation" | Post-implementation tests can miss the original failure mode. | Prefer red first, then green when behavior is new or risky. |
50
+ | "I'll add tests after implementation" | Post-implementation tests can miss the original failure mode and is not test-first. | Red first, then green; if this already happened, call the new test a regression test. |
36
51
  | "It looks correct" | Visual inspection misses scenarios that tests can pin down. | Turn expected behavior and edge cases into assertions. |
37
52
  | "This is internal only" | Internal behavior still affects callers and workflow state. | Test through the public entry point, or directly test the narrow internal contract. |
@@ -3,10 +3,14 @@
3
3
  "skill": "test-first",
4
4
  "context": [
5
5
  {
6
- "contexts": ["implement", "check"],
7
- "devTypes": ["test"],
8
- "pathPatterns": ["test/**", "tests/**", "**/*.test.js", "**/*_test.py"],
9
- "reason": "Auto-routed Test First guide"
6
+ "contexts": [
7
+ "implement",
8
+ "check"
9
+ ],
10
+ "devTypes": [
11
+ "*"
12
+ ],
13
+ "reason": "Auto-routed context-only red-green guide"
10
14
  }
11
15
  ]
12
16
  }