workstate-protocol 0.1.6__tar.gz → 0.1.7__tar.gz

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 (27) hide show
  1. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/PKG-INFO +2 -2
  2. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/README.md +1 -1
  3. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/pyproject.toml +1 -1
  4. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/__init__.py +0 -6
  5. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/bootstrap.py +48 -10
  6. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/branch_naming.py +13 -13
  7. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/handoff.py +3 -3
  8. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/hooks.py +1 -1
  9. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/paths.py +4 -19
  10. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/skills.py +2 -2
  11. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/PKG-INFO +2 -2
  12. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_bootstrap_manifest.py +1 -1
  13. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_branch_grammar_registry.py +7 -7
  14. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_branch_naming.py +45 -45
  15. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_handoff_schema.py +8 -8
  16. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_skill_manifest_real_skills.py +1 -1
  17. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/setup.cfg +0 -0
  18. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/compaction.py +0 -0
  19. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/env_aliases.py +0 -0
  20. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol/py.typed +0 -0
  21. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/SOURCES.txt +0 -0
  22. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/dependency_links.txt +0 -0
  23. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/requires.txt +0 -0
  24. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/src/workstate_protocol.egg-info/top_level.txt +0 -0
  25. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_env_aliases.py +0 -0
  26. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_package_metadata.py +0 -0
  27. {workstate_protocol-0.1.6 → workstate_protocol-0.1.7}/tests/test_plugin_override_schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workstate-protocol
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Typed cross-repo contracts for the Workstate system: handoff state, MCP I/O, hook events, skill manifests, bootstrap install manifest.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/darce/workstate
@@ -17,7 +17,7 @@ Requires-Dist: pytest>=8; extra == "dev"
17
17
 
18
18
  Single source of truth for cross-repo contracts in the Workstate system. Pydantic v2 is canonical; JSON Schema artifacts under `schemas/` are generated from the models so non-Python consumers (hook scripts, future TS/JS tooling) can validate without importing Python.
19
19
 
20
- ## Schemas (rolled out incrementally per founding plan 0001)
20
+ ## Schemas (rolled out incrementally per founding implementation note)
21
21
 
22
22
  | Status | Module | Schema |
23
23
  | --- | --- | --- |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Single source of truth for cross-repo contracts in the Workstate system. Pydantic v2 is canonical; JSON Schema artifacts under `schemas/` are generated from the models so non-Python consumers (hook scripts, future TS/JS tooling) can validate without importing Python.
4
4
 
5
- ## Schemas (rolled out incrementally per founding plan 0001)
5
+ ## Schemas (rolled out incrementally per founding implementation note)
6
6
 
7
7
  | Status | Module | Schema |
8
8
  | --- | --- | --- |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "workstate-protocol"
7
- version = "0.1.6"
7
+ version = "0.1.7"
8
8
  description = "Typed cross-repo contracts for the Workstate system: handoff state, MCP I/O, hook events, skill manifests, bootstrap install manifest."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -37,10 +37,7 @@ from .paths import (
37
37
  DOCS_MIRROR_DIR,
38
38
  HARNESS_CONTRACT_RELPATH,
39
39
  INSTRUCTIONS_RELPATH,
40
- LEGACY_DOCS_MIRROR_DIR,
41
- LEGACY_RUNTIME_ROOT_DIRNAME,
42
40
  RULES_DIR,
43
- RUNTIME_PATH_RENAMES,
44
41
  RUNTIME_ROOT_DIRNAME,
45
42
  docs_mirror_path,
46
43
  runtime_root_path,
@@ -59,14 +56,11 @@ __all__ = [
59
56
  "HandoffState",
60
57
  "HandoffStatus",
61
58
  "INSTRUCTIONS_RELPATH",
62
- "LEGACY_DOCS_MIRROR_DIR",
63
- "LEGACY_RUNTIME_ROOT_DIRNAME",
64
59
  "OverlayConfigEntry",
65
60
  "OverlaySurface",
66
61
  "PostToolUseEvent",
67
62
  "PreToolUseEvent",
68
63
  "RULES_DIR",
69
- "RUNTIME_PATH_RENAMES",
70
64
  "RUNTIME_ROOT_DIRNAME",
71
65
  "SessionStartEvent",
72
66
  "SkillManifest",
@@ -1,4 +1,4 @@
1
- """Bootstrap install manifest schema (Schema #5 from founding plan 0001).
1
+ """Bootstrap install manifest schema (Schema #5 from founding implementation note).
2
2
 
3
3
  The wire shape ``workstate-bootstrap`` writes to
4
4
  ``<target>/.workstate-overlay.json``. Captures the contract between
@@ -27,14 +27,16 @@ class OverlaySurface(BaseModel):
27
27
 
28
28
  model_config = ConfigDict(extra="allow")
29
29
 
30
- path: str = Field(description="Repo-relative surface path (e.g. '.claude/skills/handoff-lifecycle').")
30
+ path: str = Field(
31
+ description="Repo-relative surface path (e.g. '.claude/skills/handoff-lifecycle')."
32
+ )
31
33
  source: Literal["shared", "local", "overlapping", "generated", "lifecycle"] = Field(
32
34
  description=(
33
35
  "Origin tier: 'shared' (symlinked from workstate-system), "
34
36
  "'local' (target-owned), 'overlapping' (target overrides shared), "
35
37
  "'generated' (per-agent surface produced by generate_agent_workflows.py "
36
- "during install — Plan 0002 step 1), "
37
- "'lifecycle' (Plan 0009 hoisted Make fragment + runner package "
38
+ "during install — implementation note step 1), "
39
+ "'lifecycle' (implementation note hoisted Make fragment + runner package "
38
40
  "— copied, not symlinked, so the consumer can run `make context` "
39
41
  "without the workstate-system packaging tree)."
40
42
  ),
@@ -112,7 +114,9 @@ class PluginOverrideManifest(BaseModel):
112
114
 
113
115
  schema_version: Literal[1] = 1
114
116
  plugin: PluginComponentName
115
- components: PluginOverrideComponents = Field(default_factory=PluginOverrideComponents)
117
+ components: PluginOverrideComponents = Field(
118
+ default_factory=PluginOverrideComponents
119
+ )
116
120
 
117
121
 
118
122
  class ReplaceCommandOp(BaseModel):
@@ -240,10 +244,24 @@ class BootstrapManifest(BaseModel):
240
244
  model_config = ConfigDict(extra="allow")
241
245
 
242
246
  schema_version: int = Field(ge=1, description="Manifest schema version.")
243
- remote_url: str = Field(min_length=1)
244
- remote_ref: str = Field(min_length=1)
245
- remote_sha: Sha40 = Field(
246
- description="Resolved 40-char git SHA at install time.",
247
+ source_kind: Literal["git_overlay", "package"] = Field(
248
+ default="git_overlay",
249
+ description=(
250
+ "Overlay delivery source: 'git_overlay' (a clone checked out at "
251
+ "remote_ref) or 'package' (an installed workstate-system "
252
+ "distribution). Defaults to 'git_overlay' so manifests written "
253
+ "before WS-PKG-DELIVERY-01 validate unchanged."
254
+ ),
255
+ )
256
+ remote_url: str | None = Field(default=None, min_length=1)
257
+ remote_ref: str | None = Field(default=None, min_length=1)
258
+ remote_sha: Sha40 | None = Field(
259
+ default=None,
260
+ description="Resolved 40-char git SHA at install time (git_overlay source).",
261
+ )
262
+ package_version: str | None = Field(
263
+ default=None,
264
+ description="Installed workstate-system distribution version (package source).",
247
265
  )
248
266
  surfaces: list[OverlaySurface] = Field(default_factory=list)
249
267
  configs: list[OverlayConfigEntry] = Field(default_factory=list)
@@ -261,7 +279,27 @@ class BootstrapManifest(BaseModel):
261
279
  default=None,
262
280
  description=(
263
281
  "Optional explicit plugin override root recorded by bootstrap so "
264
- "later doctor/update/repair runs can reuse a non-default APD-03 "
282
+ "later doctor/update/repair runs can reuse a non-default WORKSTATE-REF-03 "
265
283
  "override location."
266
284
  ),
267
285
  )
286
+
287
+ @model_validator(mode="after")
288
+ def _check_source_provenance(self) -> "BootstrapManifest":
289
+ """Each delivery source requires its own provenance fields.
290
+
291
+ ``git_overlay`` (the default, so pre-WS-PKG-DELIVERY-01 manifests keep
292
+ validating) requires the git ``remote_*`` triple; ``package`` requires
293
+ the installed distribution ``package_version``.
294
+ """
295
+ if self.source_kind == "git_overlay":
296
+ missing = [
297
+ name
298
+ for name in ("remote_url", "remote_ref", "remote_sha")
299
+ if not getattr(self, name)
300
+ ]
301
+ if missing:
302
+ raise ValueError("git_overlay manifest requires " + ", ".join(missing))
303
+ elif self.source_kind == "package" and not self.package_version:
304
+ raise ValueError("package manifest requires package_version")
305
+ return self
@@ -4,13 +4,13 @@ This module is the SOLE owner of ``TASK_REF_RE``,
4
4
  ``derive_task_ref_candidates`` and ``format_suggested_branch_name``.
5
5
  Every gate (post-checkout warn, PreToolUse block, pre-commit hard gate,
6
6
  pre-push mirror) imports from here. ``workstate_handoff_mcp`` re-exports
7
- the same objects without redefinition. See plan 0006 for context.
7
+ the same objects without redefinition. See implementation note for context.
8
8
 
9
9
  Case convention
10
10
  ---------------
11
11
 
12
- Branches are lowercase (``feature/ahmcp-37-foo``). Task refs in the
13
- Workstate handoff task table are uppercase (``AHMCP-37``).
12
+ Branches are lowercase (``feature/WORKSTATE-37-foo``). Task refs in the
13
+ Workstate handoff task table are uppercase (``WORKSTATE-REF-37``).
14
14
  ``derive_task_ref_candidates`` returns lowercase candidates; callers
15
15
  ``.upper()`` each candidate before intersecting against the live task
16
16
  table. ``format_suggested_branch_name`` accepts task refs in either
@@ -26,7 +26,7 @@ from dataclasses import dataclass, field
26
26
 
27
27
  __all__ = [
28
28
  "BRANCH_GRAMMAR_REGISTRY",
29
- "BranchClassification",
29
+ "BranWORKSTATElassification",
30
30
  "BranchGrammarEntry",
31
31
  "TASK_REF_RE",
32
32
  "__protocol_version__",
@@ -51,7 +51,7 @@ TASK_REF_RE: re.Pattern[str] = re.compile(
51
51
  # >=2 hyphen-separated lowercase / digit segments; each segment is
52
52
  # non-empty. The alternation enforces a `<prefix>-<rest>` shape so
53
53
  # single-word branches like ``feature/foo`` are rejected and
54
- # conventional task refs (``feature/ahmcp-37``,
54
+ # conventional task refs (``feature/WORKSTATE-37``,
55
55
  # ``feature/maint-dirty-br-01``) still match.
56
56
  r"(?P<task_ref>[a-z0-9]+(?:-[a-z0-9]+)+)"
57
57
  r"$"
@@ -69,8 +69,8 @@ def derive_task_ref_candidates(branch_name: str) -> list[str]:
69
69
 
70
70
  Examples
71
71
  --------
72
- >>> derive_task_ref_candidates("feature/ahmcp-37-branch-naming-enforcement")
73
- ['ahmcp-37-branch-naming-enforcement', 'ahmcp-37']
72
+ >>> derive_task_ref_candidates("feature/WORKSTATE-37-branch-naming-enforcement")
73
+ ['WORKSTATE-37-branch-naming-enforcement', 'WORKSTATE-37']
74
74
  >>> derive_task_ref_candidates("feature/maint-dirty-br-01")
75
75
  ['maint-dirty-br-01', 'maint-dirty-br']
76
76
  >>> derive_task_ref_candidates("fix/foo")
@@ -107,7 +107,7 @@ def select_task_ref_candidate(
107
107
  when both the base and the follow-up are registered. When the
108
108
  registry is non-empty but no candidate intersects, returns
109
109
  ``None`` — the strict invariant is that we never name a candidate
110
- that is absent from a populated registry (AHMCP65-BR-02).
110
+ that is absent from a populated registry (WORKSTATE65-BR-02).
111
111
  - If ``known_task_refs`` is empty or ``None`` (no registry context
112
112
  available at all), falls back to the shortest digit-bearing
113
113
  prefix. This is the no-context degradation path that keeps
@@ -152,7 +152,7 @@ def _has_digit(value: str) -> bool:
152
152
 
153
153
 
154
154
  # ---------------------------------------------------------------------------
155
- # Branch grammar registry (Plan 0010 Slice 6)
155
+ # Branch grammar registry (implementation note implementation note)
156
156
  # ---------------------------------------------------------------------------
157
157
  #
158
158
  # Canonical pattern (``feature/<task-ref>``) plus each documented
@@ -170,7 +170,7 @@ class BranchGrammarEntry:
170
170
 
171
171
 
172
172
  @dataclass(frozen=True)
173
- class BranchClassification:
173
+ class BranWORKSTATElassification:
174
174
  kind: str
175
175
  branch: str
176
176
 
@@ -193,8 +193,8 @@ BRANCH_GRAMMAR_REGISTRY: tuple[BranchGrammarEntry, ...] = (
193
193
  )
194
194
 
195
195
 
196
- def classify_branch(name: str) -> BranchClassification | None:
197
- """Return the :class:`BranchClassification` for ``name`` or ``None``.
196
+ def classify_branch(name: str) -> BranWORKSTATElassification | None:
197
+ """Return the :class:`BranWORKSTATElassification` for ``name`` or ``None``.
198
198
 
199
199
  Unknown patterns return ``None`` (fail-closed); each consumer
200
200
  decides whether unknown means warn or block.
@@ -202,7 +202,7 @@ def classify_branch(name: str) -> BranchClassification | None:
202
202
 
203
203
  for entry in BRANCH_GRAMMAR_REGISTRY:
204
204
  if entry.regex.match(name):
205
- return BranchClassification(kind=entry.kind, branch=name)
205
+ return BranWORKSTATElassification(kind=entry.kind, branch=name)
206
206
  return None
207
207
 
208
208
 
@@ -1,4 +1,4 @@
1
- """Handoff state schemas (Schema #1 from founding plan 0001).
1
+ """Handoff state schemas (Schema #1 from founding implementation note).
2
2
 
3
3
  These are the cross-repo contract types for handoff state. They model
4
4
  the wire shape that ``mcp-workstate-handoff`` exposes to MCP clients and
@@ -23,13 +23,13 @@ from pydantic import BaseModel, ConfigDict, Field, StringConstraints
23
23
  # Primitive identifiers and enums
24
24
  # ---------------------------------------------------------------------------
25
25
 
26
- # A task_ref is a short identifier like "E17-14" or "MAINT-7" or "AAA-1".
26
+ # A task_ref is a short identifier like "WORKSTATE-REF-17-14" or "WORKSTATE-REF-7" or "AAA-1".
27
27
  # We require non-empty string with a permissive character class so domain
28
28
  # tools can introduce new prefixes without re-releasing the protocol.
29
29
  TaskRef = Annotated[
30
30
  str,
31
31
  StringConstraints(strip_whitespace=True, min_length=1, max_length=128),
32
- Field(description="Short task identifier, e.g. 'E17-14' or 'MAINT-7'."),
32
+ Field(description="Short task identifier, e.g. 'WORKSTATE-REF-17-14' or 'WORKSTATE-REF-7'."),
33
33
  ]
34
34
 
35
35
 
@@ -1,4 +1,4 @@
1
- """Hook event payload schemas (Schema #3 from founding plan 0001).
1
+ """Hook event payload schemas (Schema #3 from founding implementation note).
2
2
 
3
3
  Models the JSON Claude Code delivers to hook scripts on stdin. Each
4
4
  event type is modeled separately rather than as a discriminated union
@@ -1,13 +1,9 @@
1
1
  """Canonical Workstate runtime + doc-mirror path roots — single source of truth.
2
2
 
3
- The runtime install directory and the mirrored docs/contracts path were renamed
4
- from the legacy ``.agentic/`` / ``docs/agentic/`` to ``.workstate/`` /
5
- ``docs/workstate/`` (Plan 0013 Slice D, completing Plan 0011 §11 items #3/#4).
6
-
7
- Every package resolves these through this module so the names live in exactly
8
- one place: a future path change is a one-line flip here, not a repo-wide sweep.
9
- The ``LEGACY_*`` names and :data:`RUNTIME_PATH_RENAMES` exist so bootstrap can
10
- detect and migrate an old checkout forward for one release.
3
+ The runtime install directory is ``.workstate/`` and the mirrored docs/contracts
4
+ path is ``docs/workstate/``. Every package resolves these through this module so
5
+ the names live in exactly one place: a future path change is a one-line flip
6
+ here, not a repo-wide sweep.
11
7
  """
12
8
 
13
9
  from __future__ import annotations
@@ -17,18 +13,10 @@ from pathlib import Path
17
13
  # Runtime install root — bootstrap materializes overlay surfaces and the remote
18
14
  # clone under ``<target>/.workstate/``.
19
15
  RUNTIME_ROOT_DIRNAME = ".workstate"
20
- LEGACY_RUNTIME_ROOT_DIRNAME = ".agentic"
21
16
 
22
17
  # Mirrored docs / contracts path — the SHARED_SURFACES consumed at install time
23
18
  # (rules, contracts, templates) live under ``docs/workstate/``.
24
19
  DOCS_MIRROR_DIR = "docs/workstate"
25
- LEGACY_DOCS_MIRROR_DIR = "docs/agentic"
26
-
27
- # Ordered (legacy -> canonical) pairs for migration / detection sweeps.
28
- RUNTIME_PATH_RENAMES: tuple[tuple[str, str], ...] = (
29
- (LEGACY_RUNTIME_ROOT_DIRNAME, RUNTIME_ROOT_DIRNAME),
30
- (LEGACY_DOCS_MIRROR_DIR, DOCS_MIRROR_DIR),
31
- )
32
20
 
33
21
  # Common derived locations under the canonical docs mirror.
34
22
  CONTRACTS_DIR = f"{DOCS_MIRROR_DIR}/contracts"
@@ -41,10 +29,7 @@ __all__ = [
41
29
  "DOCS_MIRROR_DIR",
42
30
  "HARNESS_CONTRACT_RELPATH",
43
31
  "INSTRUCTIONS_RELPATH",
44
- "LEGACY_DOCS_MIRROR_DIR",
45
- "LEGACY_RUNTIME_ROOT_DIRNAME",
46
32
  "RULES_DIR",
47
- "RUNTIME_PATH_RENAMES",
48
33
  "RUNTIME_ROOT_DIRNAME",
49
34
  "docs_mirror_path",
50
35
  "runtime_root_path",
@@ -1,7 +1,7 @@
1
- """Skill manifest schema (Schema #4 from founding plan 0001).
1
+ """Skill manifest schema (Schema #4 from founding implementation note).
2
2
 
3
3
  Structured contract for the canonical neutral skill layout
4
- ``skills/<slug>/skill.yaml`` (Plan 0002 step 1). The pre-Plan-0002
4
+ ``skills/<slug>/skill.yaml`` (implementation note step 1). The pre-Plan-0002
5
5
  ``.claude/skills/<slug>/SKILL.md`` frontmatter is now a *generated*
6
6
  artifact in target repos and is no longer a source-of-truth surface.
7
7
  Enforces the Tier 1 / Tier 2 / Tier 3 portability boundary by
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workstate-protocol
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Typed cross-repo contracts for the Workstate system: handoff state, MCP I/O, hook events, skill manifests, bootstrap install manifest.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/darce/workstate
@@ -17,7 +17,7 @@ Requires-Dist: pytest>=8; extra == "dev"
17
17
 
18
18
  Single source of truth for cross-repo contracts in the Workstate system. Pydantic v2 is canonical; JSON Schema artifacts under `schemas/` are generated from the models so non-Python consumers (hook scripts, future TS/JS tooling) can validate without importing Python.
19
19
 
20
- ## Schemas (rolled out incrementally per founding plan 0001)
20
+ ## Schemas (rolled out incrementally per founding implementation note)
21
21
 
22
22
  | Status | Module | Schema |
23
23
  | --- | --- | --- |
@@ -1,6 +1,6 @@
1
1
  """Schema tests for BootstrapManifest mcp_servers field.
2
2
 
3
- AHMCP-50 slice 1b adds a ``mcp_servers: list[str]`` field to the manifest
3
+ WORKSTATE-REF-50 implementation note adds a ``mcp_servers: list[str]`` field to the manifest
4
4
  so the bootstrap ledger can carry the previously-managed server names.
5
5
  ``sync_mcp_configs(prune_removed_managed=True)`` reads this list to
6
6
  distinguish managed launchers (subject to prune) from third-party ones
@@ -1,4 +1,4 @@
1
- """Plan 0010 Slice 6 — branch-grammar registry."""
1
+ """implementation note implementation note — branch-grammar registry."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -16,11 +16,11 @@ def test_registry_includes_documented_exception_kinds() -> None:
16
16
  @pytest.mark.parametrize(
17
17
  "branch,expected_kind",
18
18
  [
19
- ("feature/ahmcp-37-branch-naming-enforcement", "feature"),
19
+ ("feature/WORKSTATE-37-branch-naming-enforcement", "feature"),
20
20
  ("release/0.9.1", "release"),
21
- ("hotfix/ahmcp-99-fix-bug", "hotfix"),
21
+ ("hotfix/WORKSTATE-99-fix-bug", "hotfix"),
22
22
  ("maint/cleanup-stale-rows", "maint"),
23
- ("revert/ahmcp-12-bad-merge", "revert"),
23
+ ("revert/WORKSTATE-12-bad-merge", "revert"),
24
24
  ],
25
25
  )
26
26
  def test_classify_branch_returns_correct_kind(branch: str, expected_kind: str) -> None:
@@ -34,11 +34,11 @@ def test_classify_branch_returns_correct_kind(branch: str, expected_kind: str) -
34
34
  @pytest.mark.parametrize(
35
35
  "branch",
36
36
  [
37
- "feature/ahmcp-37-branch-naming-enforcement",
37
+ "feature/WORKSTATE-37-branch-naming-enforcement",
38
38
  "release/0.9.1",
39
- "hotfix/ahmcp-99-fix-bug",
39
+ "hotfix/WORKSTATE-99-fix-bug",
40
40
  "maint/cleanup-stale-rows",
41
- "revert/ahmcp-12-bad-merge",
41
+ "revert/WORKSTATE-12-bad-merge",
42
42
  ],
43
43
  )
44
44
  def test_is_allowed_branch_passes_for_documented_patterns(branch: str) -> None:
@@ -1,6 +1,6 @@
1
1
  """Tests for the canonical branch-naming rule.
2
2
 
3
- Plan 0006 Slice 1: ``workstate_protocol.branch_naming`` exposes
3
+ implementation note implementation note: ``workstate_protocol.branch_naming`` exposes
4
4
  ``TASK_REF_RE``, ``derive_task_ref_candidates``,
5
5
  ``format_suggested_branch_name``, and ``__protocol_version__`` as the
6
6
  single source of truth for branch-naming validation across every gate.
@@ -23,8 +23,8 @@ from workstate_protocol.branch_naming import (
23
23
 
24
24
 
25
25
  POSITIVE_CORPUS = (
26
- "feature/ahmcp-35",
27
- "feature/ahmcp-37-branch-naming-enforcement",
26
+ "feature/WORKSTATE-35",
27
+ "feature/WORKSTATE-37-branch-naming-enforcement",
28
28
  "feature/maint-dirty-br-01",
29
29
  "feature/maint-archive-stale-20260502",
30
30
  "feature/plan-0006",
@@ -43,11 +43,11 @@ NEGATIVE_CORPUS = (
43
43
  "feature/no-digits-here",
44
44
  "hotfix/x",
45
45
  "release/v1",
46
- "feature/AHMCP-37", # uppercase
46
+ "feature/WORKSTATE-REF-37", # uppercase
47
47
  "feature/-x", # leading hyphen
48
48
  "feature/123-foo", # leading digit segment
49
49
  "feature/", # empty task ref
50
- "FEATURE/ahmcp-37", # uppercase prefix
50
+ "FEATURE/WORKSTATE-37", # uppercase prefix
51
51
  "",
52
52
  )
53
53
 
@@ -74,15 +74,15 @@ def test_protocol_version_marker_is_string() -> None:
74
74
  @pytest.mark.parametrize(
75
75
  "branch,expected",
76
76
  [
77
- # Walk every digit-bearing prefix from longest to shortest. ``ahmcp`` is
77
+ # Walk every digit-bearing prefix from longest to shortest. ``WORKSTATE`` is
78
78
  # dropped because it has no digit.
79
- ("feature/ahmcp-37-branch-naming-enforcement", [
80
- "ahmcp-37-branch-naming-enforcement",
81
- "ahmcp-37-branch-naming",
82
- "ahmcp-37-branch",
83
- "ahmcp-37",
79
+ ("feature/WORKSTATE-37-branch-naming-enforcement", [
80
+ "WORKSTATE-37-branch-naming-enforcement",
81
+ "WORKSTATE-37-branch-naming",
82
+ "WORKSTATE-37-branch",
83
+ "WORKSTATE-37",
84
84
  ]),
85
- ("feature/ahmcp-37", ["ahmcp-37"]),
85
+ ("feature/WORKSTATE-37", ["WORKSTATE-37"]),
86
86
  # Only the full ref carries a digit; shorter prefixes are digit-less.
87
87
  ("feature/maint-dirty-br-01", ["maint-dirty-br-01"]),
88
88
  ("feature/plan-0006", ["plan-0006"]),
@@ -105,7 +105,7 @@ def test_derive_returns_lowercase_for_conforming() -> None:
105
105
  The derivation MUST stay lowercase so that intersection logic owns the
106
106
  case conversion rather than guessing it here."""
107
107
  candidates = derive_task_ref_candidates(
108
- "feature/ahmcp-37-branch-naming-enforcement"
108
+ "feature/WORKSTATE-37-branch-naming-enforcement"
109
109
  )
110
110
  assert all(c == c.lower() for c in candidates)
111
111
 
@@ -113,11 +113,11 @@ def test_derive_returns_lowercase_for_conforming() -> None:
113
113
  @pytest.mark.parametrize(
114
114
  "task_ref,slug,expected",
115
115
  [
116
- ("AHMCP-37", None, "feature/ahmcp-37"),
117
- ("ahmcp-37", None, "feature/ahmcp-37"),
118
- ("AHMCP-37", "branch-naming-enforcement",
119
- "feature/ahmcp-37-branch-naming-enforcement"),
120
- ("MAINT-DIRTY-BR-01", None, "feature/maint-dirty-br-01"),
116
+ ("WORKSTATE-REF-37", None, "feature/WORKSTATE-37"),
117
+ ("WORKSTATE-37", None, "feature/WORKSTATE-37"),
118
+ ("WORKSTATE-REF-37", "branch-naming-enforcement",
119
+ "feature/WORKSTATE-37-branch-naming-enforcement"),
120
+ ("WORKSTATE-REF-DIRTY-BR-01", None, "feature/maint-dirty-br-01"),
121
121
  ("PLAN-0006", "rollout", "feature/plan-0006-rollout"),
122
122
  ],
123
123
  )
@@ -145,61 +145,61 @@ def test_select_task_ref_candidate_no_registry_returns_shortest_prefix() -> None
145
145
  """
146
146
  assert (
147
147
  select_task_ref_candidate(
148
- "feature/ahmcp-63-fu-tighten-compaction-defaults", known_task_refs=None
148
+ "feature/WORKSTATE-63-fu-tighten-compaction-defaults", known_task_refs=None
149
149
  )
150
- == "AHMCP-63"
150
+ == "WORKSTATE-REF-63"
151
151
  )
152
152
  assert (
153
153
  select_task_ref_candidate(
154
- "feature/ahmcp-63-fu-tighten-compaction-defaults", known_task_refs=()
154
+ "feature/WORKSTATE-63-fu-tighten-compaction-defaults", known_task_refs=()
155
155
  )
156
- == "AHMCP-63"
156
+ == "WORKSTATE-REF-63"
157
157
  )
158
158
  assert (
159
159
  select_task_ref_candidate(
160
- "feature/ahmcp-37-branch-naming-enforcement", known_task_refs=set()
160
+ "feature/WORKSTATE-37-branch-naming-enforcement", known_task_refs=set()
161
161
  )
162
- == "AHMCP-37"
162
+ == "WORKSTATE-REF-37"
163
163
  )
164
164
  assert (
165
165
  select_task_ref_candidate("feature/maint-dirty-br-01", known_task_refs=None)
166
- == "MAINT-DIRTY-BR-01"
166
+ == "WORKSTATE-REF-DIRTY-BR-01"
167
167
  )
168
168
 
169
169
 
170
170
  def test_select_task_ref_candidate_longest_registered_wins() -> None:
171
171
  """With both the base and a follow-up registered, the longer registered
172
- candidate wins. This is the AHMCP-63 vs AHMCP-63-FU-... case that
172
+ candidate wins. This is the WORKSTATE-REF-63 vs WORKSTATE-REF-63-FU-... case that
173
173
  motivated the selector.
174
174
  """
175
- branch = "feature/ahmcp-63-fu-tighten-compaction-defaults"
176
- known = {"AHMCP-63", "AHMCP-63-FU-TIGHTEN-COMPACTION-DEFAULTS"}
175
+ branch = "feature/WORKSTATE-63-fu-tighten-compaction-defaults"
176
+ known = {"WORKSTATE-REF-63", "WORKSTATE-REF-63-FU-TIGHTEN-COMTASKCTION-DEFAULTS"}
177
177
  assert (
178
178
  select_task_ref_candidate(branch, known_task_refs=known)
179
- == "AHMCP-63-FU-TIGHTEN-COMPACTION-DEFAULTS"
179
+ == "WORKSTATE-REF-63-FU-TIGHTEN-COMTASKCTION-DEFAULTS"
180
180
  )
181
181
 
182
182
 
183
183
  def test_select_task_ref_candidate_only_base_registered_picks_base() -> None:
184
184
  """When only the base ref is registered, no longer candidate intersects
185
185
  and the base wins (it is itself a registered candidate)."""
186
- branch = "feature/ahmcp-63-fu-tighten-compaction-defaults"
186
+ branch = "feature/WORKSTATE-63-fu-tighten-compaction-defaults"
187
187
  assert (
188
- select_task_ref_candidate(branch, known_task_refs={"AHMCP-63"})
189
- == "AHMCP-63"
188
+ select_task_ref_candidate(branch, known_task_refs={"WORKSTATE-REF-63"})
189
+ == "WORKSTATE-REF-63"
190
190
  )
191
191
 
192
192
 
193
193
  def test_select_task_ref_candidate_no_intersection_returns_none() -> None:
194
194
  """If ``known_task_refs`` is non-empty but no candidate intersects, the
195
195
  selector returns ``None`` rather than naming a candidate absent from a
196
- populated registry (AHMCP65-BR-02 invariant: "no resolver path should
196
+ populated registry (WORKSTATE65-BR-02 invariant: "no resolver path should
197
197
  return a candidate absent from a non-empty registry"). The no-context
198
198
  shortest-prefix fallback applies only when the registry is genuinely
199
199
  empty / unavailable, not when it answered with unrelated rows."""
200
- branch = "feature/ahmcp-63-fu-tighten-compaction-defaults"
200
+ branch = "feature/WORKSTATE-63-fu-tighten-compaction-defaults"
201
201
  assert (
202
- select_task_ref_candidate(branch, known_task_refs={"DCMCP-02", "AHMCP-99"})
202
+ select_task_ref_candidate(branch, known_task_refs={"WORKSTATE-REF-02", "WORKSTATE-REF-99"})
203
203
  is None
204
204
  )
205
205
 
@@ -208,14 +208,14 @@ def test_select_task_ref_candidate_case_insensitive_intersection() -> None:
208
208
  """``derive_task_ref_candidates`` returns lowercase; registry refs are
209
209
  canonically uppercase. The selector normalizes both sides so a
210
210
  lowercase or mixed-case registry still resolves correctly."""
211
- branch = "feature/ahmcp-63-fu-example"
211
+ branch = "feature/WORKSTATE-63-fu-example"
212
212
  assert (
213
- select_task_ref_candidate(branch, known_task_refs={"ahmcp-63-fu-example"})
214
- == "AHMCP-63-FU-EXAMPLE"
213
+ select_task_ref_candidate(branch, known_task_refs={"WORKSTATE-63-fu-example"})
214
+ == "WORKSTATE-REF-63-FU-EXAMPLE"
215
215
  )
216
216
  assert (
217
- select_task_ref_candidate(branch, known_task_refs={"Ahmcp-63-Fu-Example"})
218
- == "AHMCP-63-FU-EXAMPLE"
217
+ select_task_ref_candidate(branch, known_task_refs={"WORKSTATE-63-Fu-Example"})
218
+ == "WORKSTATE-REF-63-FU-EXAMPLE"
219
219
  )
220
220
 
221
221
 
@@ -227,22 +227,22 @@ def test_select_task_ref_candidate_non_conforming_branch_returns_none() -> None:
227
227
  assert select_task_ref_candidate("main") is None
228
228
  assert select_task_ref_candidate("") is None
229
229
  assert (
230
- select_task_ref_candidate("fix/foo", known_task_refs={"AHMCP-1"}) is None
230
+ select_task_ref_candidate("fix/foo", known_task_refs={"WORKSTATE-REF-1"}) is None
231
231
  )
232
232
 
233
233
 
234
234
  def test_select_task_ref_candidate_single_segment_unchanged() -> None:
235
- """Single-segment refs like ``MAINT-DIRTY-BR-01`` (one task ref, no
235
+ """Single-segment refs like ``WORKSTATE-REF-DIRTY-BR-01`` (one task ref, no
236
236
  follow-up suffix possible) resolve identically with or without
237
237
  registry context."""
238
238
  branch = "feature/maint-dirty-br-01"
239
239
  assert (
240
- select_task_ref_candidate(branch, known_task_refs={"MAINT-DIRTY-BR-01"})
241
- == "MAINT-DIRTY-BR-01"
240
+ select_task_ref_candidate(branch, known_task_refs={"WORKSTATE-REF-DIRTY-BR-01"})
241
+ == "WORKSTATE-REF-DIRTY-BR-01"
242
242
  )
243
243
  assert (
244
244
  select_task_ref_candidate(branch, known_task_refs=None)
245
- == "MAINT-DIRTY-BR-01"
245
+ == "WORKSTATE-REF-DIRTY-BR-01"
246
246
  )
247
247
 
248
248
 
@@ -16,18 +16,18 @@ from workstate_protocol import (
16
16
 
17
17
 
18
18
  def test_active_task_minimal_roundtrip() -> None:
19
- a = ActiveTask(task_ref="E17-14", objective="probe")
19
+ a = ActiveTask(task_ref="WORKSTATE-REF-17-14", objective="probe")
20
20
  assert a.status is HandoffStatus.in_progress
21
21
  assert a.task_plan_path is None
22
22
  assert a.task_plan() is None
23
23
  dumped = a.model_dump()
24
- assert dumped["task_ref"] == "E17-14"
24
+ assert dumped["task_ref"] == "WORKSTATE-REF-17-14"
25
25
  assert dumped["status"] == "in_progress"
26
26
 
27
27
 
28
28
  def test_active_task_with_plan_metadata() -> None:
29
29
  a = ActiveTask(
30
- task_ref="E17-14",
30
+ task_ref="WORKSTATE-REF-17-14",
31
31
  objective="probe",
32
32
  target_branch="feature/e17-14",
33
33
  target_worktree_path="/tmp/worktree",
@@ -48,7 +48,7 @@ def test_active_task_allows_extra_fields_for_passthrough() -> None:
48
48
  # The handoff DB row has columns we don't model yet; they must
49
49
  # round-trip rather than blow up.
50
50
  raw = {
51
- "task_ref": "E17-14",
51
+ "task_ref": "WORKSTATE-REF-17-14",
52
52
  "objective": "probe",
53
53
  "revision": 3,
54
54
  "updated_at": "2026-04-25T12:00:00Z",
@@ -135,12 +135,12 @@ def test_handoff_status_is_constrained() -> None:
135
135
 
136
136
  def test_turn_range_and_structured_summary_roundtrip() -> None:
137
137
  summary = StructuredSummary(
138
- compaction_id="C-AHMCP-34-0001",
138
+ compaction_id="C-WORKSTATE-REF-34-0001",
139
139
  session_id="session-123",
140
140
  harness="codex",
141
- task_ref="AHMCP-34",
141
+ task_ref="WORKSTATE-REF-34",
142
142
  turn_range=TurnRange(start_turn=1, end_turn=42),
143
- decisions=[{"decision_id": "scope_intake_AHMCP-34_trigger_choice", "slug": "trigger-choice"}],
143
+ decisions=[{"decision_id": "scope_intake_WORKSTATE-34_trigger_choice", "slug": "trigger-choice"}],
144
144
  findings_fixed=["F-1"],
145
145
  findings_opened=["F-2"],
146
146
  tests_verified=["pytest tests/test_schema_migrations.py -q"],
@@ -154,7 +154,7 @@ def test_turn_range_and_structured_summary_roundtrip() -> None:
154
154
  assert dumped["harness"] == "codex"
155
155
  restored = StructuredSummary.model_validate(dumped)
156
156
  assert restored.turn_range.end_turn == 42
157
- assert restored.decisions[0].decision_id == "scope_intake_AHMCP-34_trigger_choice"
157
+ assert restored.decisions[0].decision_id == "scope_intake_WORKSTATE-34_trigger_choice"
158
158
 
159
159
 
160
160
  def test_compaction_summary_schema_artifact_exists() -> None:
@@ -1,7 +1,7 @@
1
1
  """Loads real harness skills from workstate-system to confirm the
2
2
  SkillManifest contract matches what is actually shipped.
3
3
 
4
- After plan 0002 step 1, the canonical layout is
4
+ After implementation note step 1, the canonical layout is
5
5
  ``packages/workstate-system/skills/<slug>/{skill.yaml, body.md}``.
6
6
  The Claude-namespaced ``.claude/skills/<slug>/SKILL.md`` is a generated
7
7
  artifact in target repos, not source. This test reads the neutral