cowork-flow 0.0.48 → 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 (43) hide show
  1. package/README.md +16 -1
  2. package/package.json +6 -3
  3. package/src/cli.js +6 -0
  4. package/src/commands/source-refresh.js +40 -0
  5. package/src/lib/copy-template.js +130 -1
  6. package/src/lib/host-assets.js +216 -71
  7. package/template/.codex/hooks.json +1 -1
  8. package/template/.cowork-flow/.version +1 -1
  9. package/template/.cowork-flow/scripts/adapters/cli/task.py +0 -2
  10. package/template/.cowork-flow/scripts/adapters/cli/task_archive_commands.py +12 -0
  11. package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +18 -133
  12. package/template/.cowork-flow/scripts/adapters/cli/task_navigation.py +82 -1
  13. package/template/.cowork-flow/scripts/adapters/host/host_manifest.py +98 -114
  14. package/template/.cowork-flow/scripts/infra/skill_manifest.py +23 -3
  15. package/template/.cowork-flow/scripts/infra/storage/operation_log.py +40 -0
  16. package/template/.cowork-flow/scripts/infra/storage/state_store.py +356 -1
  17. package/template/.cowork-flow/scripts/services/context_discovery.py +156 -0
  18. package/template/.cowork-flow/scripts/services/context_jsonl.py +119 -0
  19. package/template/.cowork-flow/scripts/services/context_paths.py +186 -0
  20. package/template/.cowork-flow/scripts/services/lifecycle_checks.py +23 -4
  21. package/template/.cowork-flow/scripts/services/lifecycle_policy.py +128 -0
  22. package/template/.cowork-flow/scripts/services/plan_binding.py +146 -0
  23. package/template/.cowork-flow/scripts/services/task_archive.py +100 -14
  24. package/template/.cowork-flow/scripts/services/task_context.py +49 -479
  25. package/template/.cowork-flow/scripts/services/task_creation.py +31 -27
  26. package/template/.cowork-flow/scripts/services/task_lifecycle.py +155 -18
  27. package/template/.cowork-flow/spec/contracts/error-output-as-data.md +8 -0
  28. package/template/.cowork-flow/spec/runtime/index.md +1 -1
  29. package/template/.cowork-flow/spec/schemas/host-assets.schema.json +32 -6
  30. package/template/.zcode/.zcode-plugin/plugin.json +1 -1
  31. package/template/skills/batch-execution/scripts/batch_execution.py +111 -629
  32. package/template/skills/batch-execution/scripts/batch_recovery.py +94 -0
  33. package/template/skills/batch-execution/scripts/batch_state_machine.py +201 -0
  34. package/template/skills/batch-execution/scripts/batch_verification.py +326 -0
  35. package/template/skills/cowork-flow/SKILL.md +3 -0
  36. package/template/skills/party-mode/scripts/party_actions.py +95 -0
  37. package/template/skills/party-mode/scripts/party_mode_v2.py +129 -205
  38. package/template/skills/party-mode/scripts/party_reports.py +177 -0
  39. package/template/skills/party-mode/scripts/party_state_machine.py +189 -0
  40. package/template/skills/runtime-health/scripts/doctor.py +204 -15
  41. package/template/skills/task-review/scripts/review_check.py +135 -2
  42. package/template/skills/test-first/SKILL.md +18 -3
  43. package/template/skills/test-first/manifest.json +8 -4
@@ -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",
@@ -316,6 +318,124 @@ def _is_code_file(path: str) -> bool:
316
318
  return Path(path).suffix in CODE_SUFFIXES
317
319
 
318
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
+
319
439
  def _python_test_names(content: str) -> list[str]:
320
440
  try:
321
441
  tree = ast.parse(content.lstrip("\ufeff"))
@@ -403,7 +523,11 @@ def _test_intent_issues(test_signals: dict[str, Any]) -> list[dict[str, str]]:
403
523
  return issues
404
524
 
405
525
 
406
- def _verification_hints(changed_files: list[str], test_signals: dict[str, Any]) -> list[str]:
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]:
407
531
  hints: list[str] = []
408
532
  code_files = [
409
533
  path
@@ -417,6 +541,8 @@ def _verification_hints(changed_files: list[str], test_signals: dict[str, Any])
417
541
  )
418
542
  if test_signals["shallowAssertionSignals"]:
419
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.")
420
546
  return hints
421
547
 
422
548
 
@@ -425,6 +551,7 @@ def build_report(repo_root: Path, task_dir: Path) -> dict[str, Any]:
425
551
  changed_files = collect_changed_paths(repo_root)
426
552
  dev_type = _infer_dev_type(task_data, changed_files)
427
553
  test_signals = _test_intent_signals(repo_root, changed_files)
554
+ complexity_signals = _complexity_signals(repo_root, changed_files)
428
555
  scope_facts, scope_issues = _scope_facts_with_issues(
429
556
  repo_root,
430
557
  task_dir,
@@ -443,6 +570,7 @@ def build_report(repo_root: Path, task_dir: Path) -> dict[str, Any]:
443
570
  "normalizedIssues": [
444
571
  *scope_issues,
445
572
  *_test_intent_issues(test_signals),
573
+ *_complexity_issues(complexity_signals),
446
574
  ],
447
575
  "specSources": _spec_sources(
448
576
  repo_root,
@@ -451,7 +579,12 @@ def build_report(repo_root: Path, task_dir: Path) -> dict[str, Any]:
451
579
  changed_files=changed_files,
452
580
  ),
453
581
  "testIntentSignals": test_signals,
454
- "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
+ ),
455
588
  "notes": [
456
589
  "This report is advisory and does not mutate task state.",
457
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
  }