engineering-process 1.2.6__tar.gz → 2.0.0__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 (77) hide show
  1. {engineering_process-1.2.6/engineering_process.egg-info → engineering_process-2.0.0}/PKG-INFO +21 -5
  2. {engineering_process-1.2.6 → engineering_process-2.0.0}/README.md +20 -4
  3. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/__init__.py +1 -1
  4. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/skills.py +15 -0
  5. {engineering_process-1.2.6 → engineering_process-2.0.0/engineering_process.egg-info}/PKG-INFO +21 -5
  6. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process.egg-info/SOURCES.txt +8 -8
  7. {engineering_process-1.2.6 → engineering_process-2.0.0}/process-graph.json +23 -23
  8. {engineering_process-1.2.6/process_assets/skills/finish-change → engineering_process-2.0.0/process_assets/skills/change-complete}/SKILL.md +5 -4
  9. {engineering_process-1.2.6/process_assets/skills/implement-change → engineering_process-2.0.0/process_assets/skills/change-implement}/SKILL.md +3 -3
  10. {engineering_process-1.2.6/process_assets/skills/plan-change → engineering_process-2.0.0/process_assets/skills/change-plan}/SKILL.md +2 -2
  11. {engineering_process-1.2.6/process_assets/skills/review-change → engineering_process-2.0.0/process_assets/skills/change-review}/SKILL.md +15 -6
  12. {engineering_process-1.2.6/process_assets/skills/start-change → engineering_process-2.0.0/process_assets/skills/change-start}/SKILL.md +2 -2
  13. {engineering_process-1.2.6/process_assets/skills/verify-change → engineering_process-2.0.0/process_assets/skills/change-verify}/SKILL.md +3 -3
  14. {engineering_process-1.2.6/process_assets/skills/run-change → engineering_process-2.0.0/process_assets/skills/deliver-change}/SKILL.md +16 -15
  15. {engineering_process-1.2.6/process_assets/skills/improve-process → engineering_process-2.0.0/process_assets/skills/process-improve}/SKILL.md +2 -2
  16. {engineering_process-1.2.6 → engineering_process-2.0.0}/pyproject.toml +9 -9
  17. {engineering_process-1.2.6 → engineering_process-2.0.0}/templates/AGENTS.process.md +1 -1
  18. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_adoption.py +83 -3
  19. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_distribution.py +19 -0
  20. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_skills.py +64 -19
  21. {engineering_process-1.2.6 → engineering_process-2.0.0}/LICENSE +0 -0
  22. {engineering_process-1.2.6 → engineering_process-2.0.0}/MANIFEST.in +0 -0
  23. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/__main__.py +0 -0
  24. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/_supervisor_contract.py +0 -0
  25. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/_supervisor_posix.py +0 -0
  26. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/_supervisor_windows.py +0 -0
  27. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/_windows_job.py +0 -0
  28. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/adoption.py +0 -0
  29. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/cli.py +0 -0
  30. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/commands.py +0 -0
  31. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/contracts.py +0 -0
  32. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/distribution.py +0 -0
  33. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/helper_launch.py +0 -0
  34. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/lifecycle.py +0 -0
  35. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/production_engineering.py +0 -0
  36. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/project.py +0 -0
  37. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/publication_compat.py +0 -0
  38. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/release.py +0 -0
  39. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/repository.py +0 -0
  40. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/requirements-dev.txt +0 -0
  41. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/requirements-runtime.txt +0 -0
  42. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process/supervision.py +0 -0
  43. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process.egg-info/dependency_links.txt +0 -0
  44. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process.egg-info/entry_points.txt +0 -0
  45. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process.egg-info/requires.txt +0 -0
  46. {engineering_process-1.2.6 → engineering_process-2.0.0}/engineering_process.egg-info/top_level.txt +0 -0
  47. {engineering_process-1.2.6 → engineering_process-2.0.0}/process_assets/skills/production-engineering/SKILL.md +0 -0
  48. {engineering_process-1.2.6 → engineering_process-2.0.0}/process_assets/skills/production-engineering/invariants.json +0 -0
  49. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/change.schema.json +0 -0
  50. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/plan.schema.json +0 -0
  51. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/process-graph.schema.json +0 -0
  52. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/process-lock.schema.json +0 -0
  53. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/production-engineering.schema.json +0 -0
  54. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/project-legacy.schema.json +0 -0
  55. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/project.schema.json +0 -0
  56. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/receipt.schema.json +0 -0
  57. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/release-change.schema.json +0 -0
  58. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/release.schema.json +0 -0
  59. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/review.schema.json +0 -0
  60. {engineering_process-1.2.6 → engineering_process-2.0.0}/schemas/run.schema.json +0 -0
  61. {engineering_process-1.2.6 → engineering_process-2.0.0}/setup.cfg +0 -0
  62. {engineering_process-1.2.6 → engineering_process-2.0.0}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
  63. {engineering_process-1.2.6 → engineering_process-2.0.0}/templates/adopt-process-windows-job.py +0 -0
  64. {engineering_process-1.2.6 → engineering_process-2.0.0}/templates/adopt-process.py +0 -0
  65. {engineering_process-1.2.6 → engineering_process-2.0.0}/templates/renovate.json +0 -0
  66. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_architecture.py +0 -0
  67. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_automation.py +0 -0
  68. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_cli.py +0 -0
  69. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_commands.py +0 -0
  70. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_contracts.py +0 -0
  71. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_lifecycle.py +0 -0
  72. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_production_engineering.py +0 -0
  73. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_publication_compat.py +0 -0
  74. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_pypi_cache_horizon.py +0 -0
  75. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_release.py +0 -0
  76. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_repository.py +0 -0
  77. {engineering_process-1.2.6 → engineering_process-2.0.0}/tests/test_supervisor_posix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 1.2.6
3
+ Version: 2.0.0
4
4
  Summary: A small agent-neutral engineering lifecycle with managed adoption
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/phuongnse/engineering-process
@@ -33,11 +33,27 @@ Portable skills explain what to do. processctl owns state transitions and curren
33
33
  evidence. Each consumer owns its product rules, exact commands, merge policy, and
34
34
  release decisions.
35
35
 
36
+ Start delivery work with [deliver-change](process_assets/skills/deliver-change/SKILL.md).
37
+ It selects the current phase from lifecycle state; callers do not select a phase skill:
38
+
39
+ deliver-change
40
+ change-start -> change-plan -> change-implement -> change-verify
41
+ -> change-review -> change-complete
42
+
43
+ [process-improve](process_assets/skills/process-improve/SKILL.md) handles a reusable
44
+ process problem, then returns delivery to the same lifecycle.
45
+ [production-engineering](process_assets/skills/production-engineering/SKILL.md) supplies
46
+ the invariant floor consulted from planning through independent review. Both are
47
+ reachable specializations; neither advances lifecycle state. `change-complete` calls
48
+ the existing `processctl change finish` command.
49
+
50
+ For migration from the previous skill identifiers, see [Versioning](VERSIONING.md#skill-namespace-migration).
51
+
36
52
  ## Architecture
37
53
 
38
54
  The distribution has four live parts:
39
55
 
40
- 1. Managed skills under process_assets/skills, all reachable from run-change.
56
+ 1. Managed skills under process_assets/skills, all reachable from deliver-change.
41
57
  2. One processctl state machine under engineering_process/lifecycle.py.
42
58
  3. JSON Schemas that are loaded directly by the runtime.
43
59
  4. One adoption transaction that synchronizes managed skills and configuration from
@@ -175,7 +191,7 @@ This assessment is not a production certificate. Production still requires the
175
191
  consumer's immutable readiness pack, every required capability in `enforced` state,
176
192
  fresh consumer-owned verification on the exact candidate, and independent review.
177
193
 
178
- For each ordinary change, `run-change` first surfaces this readiness view. The accepted
194
+ For each ordinary change, `deliver-change` first surfaces this readiness view. The accepted
179
195
  request and consumer rules determine which capabilities are affected. Every change
180
196
  retains the project's baseline `requiredProfiles`; start and plan add any conditional
181
197
  evidence profiles needed by affected capabilities and include a planned gap only when
@@ -184,7 +200,7 @@ floor. A planned-to-enforced promotion is a reviewed consumer source diff with f
184
200
  evidence. Unrelated planned gaps remain visible but do not block development, and no
185
201
  skill chooses product priorities or changes readiness automatically.
186
202
 
187
- When a consumer incident exposes a reusable process gap, `improve-process` first keeps
203
+ When a consumer incident exposes a reusable process gap, `process-improve` first keeps
188
204
  the consumer safe, then prepares a sanitized GitHub issue draft from that checkout.
189
205
  It deduplicates by consumer/process-version/invariant, requires owner authorization
190
206
  before `gh issue create`, and uses an accepted issue as the later process change source
@@ -352,7 +368,7 @@ URL; without it, the review remains pending. Earlier plan and review documents r
352
368
  readable, and their runs remain registrable or finishable with the version selected by
353
369
  the authority that started the relevant phase.
354
370
 
355
- The [finding priority definitions](process_assets/skills/review-change/SKILL.md#finding-priority)
371
+ The [finding priority definitions](process_assets/skills/change-review/SKILL.md#finding-priority)
356
372
  are the canonical P0-P3 impact convention for this process, including examples and
357
373
  their relationship to blocking decisions.
358
374
 
@@ -9,11 +9,27 @@ Portable skills explain what to do. processctl owns state transitions and curren
9
9
  evidence. Each consumer owns its product rules, exact commands, merge policy, and
10
10
  release decisions.
11
11
 
12
+ Start delivery work with [deliver-change](process_assets/skills/deliver-change/SKILL.md).
13
+ It selects the current phase from lifecycle state; callers do not select a phase skill:
14
+
15
+ deliver-change
16
+ change-start -> change-plan -> change-implement -> change-verify
17
+ -> change-review -> change-complete
18
+
19
+ [process-improve](process_assets/skills/process-improve/SKILL.md) handles a reusable
20
+ process problem, then returns delivery to the same lifecycle.
21
+ [production-engineering](process_assets/skills/production-engineering/SKILL.md) supplies
22
+ the invariant floor consulted from planning through independent review. Both are
23
+ reachable specializations; neither advances lifecycle state. `change-complete` calls
24
+ the existing `processctl change finish` command.
25
+
26
+ For migration from the previous skill identifiers, see [Versioning](VERSIONING.md#skill-namespace-migration).
27
+
12
28
  ## Architecture
13
29
 
14
30
  The distribution has four live parts:
15
31
 
16
- 1. Managed skills under process_assets/skills, all reachable from run-change.
32
+ 1. Managed skills under process_assets/skills, all reachable from deliver-change.
17
33
  2. One processctl state machine under engineering_process/lifecycle.py.
18
34
  3. JSON Schemas that are loaded directly by the runtime.
19
35
  4. One adoption transaction that synchronizes managed skills and configuration from
@@ -151,7 +167,7 @@ This assessment is not a production certificate. Production still requires the
151
167
  consumer's immutable readiness pack, every required capability in `enforced` state,
152
168
  fresh consumer-owned verification on the exact candidate, and independent review.
153
169
 
154
- For each ordinary change, `run-change` first surfaces this readiness view. The accepted
170
+ For each ordinary change, `deliver-change` first surfaces this readiness view. The accepted
155
171
  request and consumer rules determine which capabilities are affected. Every change
156
172
  retains the project's baseline `requiredProfiles`; start and plan add any conditional
157
173
  evidence profiles needed by affected capabilities and include a planned gap only when
@@ -160,7 +176,7 @@ floor. A planned-to-enforced promotion is a reviewed consumer source diff with f
160
176
  evidence. Unrelated planned gaps remain visible but do not block development, and no
161
177
  skill chooses product priorities or changes readiness automatically.
162
178
 
163
- When a consumer incident exposes a reusable process gap, `improve-process` first keeps
179
+ When a consumer incident exposes a reusable process gap, `process-improve` first keeps
164
180
  the consumer safe, then prepares a sanitized GitHub issue draft from that checkout.
165
181
  It deduplicates by consumer/process-version/invariant, requires owner authorization
166
182
  before `gh issue create`, and uses an accepted issue as the later process change source
@@ -328,7 +344,7 @@ URL; without it, the review remains pending. Earlier plan and review documents r
328
344
  readable, and their runs remain registrable or finishable with the version selected by
329
345
  the authority that started the relevant phase.
330
346
 
331
- The [finding priority definitions](process_assets/skills/review-change/SKILL.md#finding-priority)
347
+ The [finding priority definitions](process_assets/skills/change-review/SKILL.md#finding-priority)
332
348
  are the canonical P0-P3 impact convention for this process, including examples and
333
349
  their relationship to blocking decisions.
334
350
 
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "1.2.6"
3
+ VERSION = "2.0.0"
@@ -90,6 +90,21 @@ def validate_skills(
90
90
  graph = load_and_validate(
91
91
  graph_path, "process-graph", schema_root=schemas_root(process_root)
92
92
  )
93
+ owners = {state["id"]: state["ownerSkill"] for state in graph["states"]}
94
+ if len(owners) != len(graph["states"]):
95
+ raise ProcessError("process graph state ids must be unique")
96
+ for state in graph["states"]:
97
+ for transition in state["transitions"]:
98
+ destination = transition["nextState"]
99
+ skill = transition["nextSkill"]
100
+ if (destination is None) != (skill is None):
101
+ raise ProcessError("process graph terminal transitions must have both targets null")
102
+ if destination is not None and destination not in owners:
103
+ raise ProcessError(f"process graph references missing state: {destination}")
104
+ if skill is not None and skill not in names:
105
+ raise ProcessError(
106
+ f"process graph transition from {state['id']} references missing skill: {skill}"
107
+ )
93
108
  routed = {graph["entrySkill"]}
94
109
  routed.update(state["ownerSkill"] for state in graph["states"])
95
110
  routed.update(graph.get("specializations", {}).values())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 1.2.6
3
+ Version: 2.0.0
4
4
  Summary: A small agent-neutral engineering lifecycle with managed adoption
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/phuongnse/engineering-process
@@ -33,11 +33,27 @@ Portable skills explain what to do. processctl owns state transitions and curren
33
33
  evidence. Each consumer owns its product rules, exact commands, merge policy, and
34
34
  release decisions.
35
35
 
36
+ Start delivery work with [deliver-change](process_assets/skills/deliver-change/SKILL.md).
37
+ It selects the current phase from lifecycle state; callers do not select a phase skill:
38
+
39
+ deliver-change
40
+ change-start -> change-plan -> change-implement -> change-verify
41
+ -> change-review -> change-complete
42
+
43
+ [process-improve](process_assets/skills/process-improve/SKILL.md) handles a reusable
44
+ process problem, then returns delivery to the same lifecycle.
45
+ [production-engineering](process_assets/skills/production-engineering/SKILL.md) supplies
46
+ the invariant floor consulted from planning through independent review. Both are
47
+ reachable specializations; neither advances lifecycle state. `change-complete` calls
48
+ the existing `processctl change finish` command.
49
+
50
+ For migration from the previous skill identifiers, see [Versioning](VERSIONING.md#skill-namespace-migration).
51
+
36
52
  ## Architecture
37
53
 
38
54
  The distribution has four live parts:
39
55
 
40
- 1. Managed skills under process_assets/skills, all reachable from run-change.
56
+ 1. Managed skills under process_assets/skills, all reachable from deliver-change.
41
57
  2. One processctl state machine under engineering_process/lifecycle.py.
42
58
  3. JSON Schemas that are loaded directly by the runtime.
43
59
  4. One adoption transaction that synchronizes managed skills and configuration from
@@ -175,7 +191,7 @@ This assessment is not a production certificate. Production still requires the
175
191
  consumer's immutable readiness pack, every required capability in `enforced` state,
176
192
  fresh consumer-owned verification on the exact candidate, and independent review.
177
193
 
178
- For each ordinary change, `run-change` first surfaces this readiness view. The accepted
194
+ For each ordinary change, `deliver-change` first surfaces this readiness view. The accepted
179
195
  request and consumer rules determine which capabilities are affected. Every change
180
196
  retains the project's baseline `requiredProfiles`; start and plan add any conditional
181
197
  evidence profiles needed by affected capabilities and include a planned gap only when
@@ -184,7 +200,7 @@ floor. A planned-to-enforced promotion is a reviewed consumer source diff with f
184
200
  evidence. Unrelated planned gaps remain visible but do not block development, and no
185
201
  skill chooses product priorities or changes readiness automatically.
186
202
 
187
- When a consumer incident exposes a reusable process gap, `improve-process` first keeps
203
+ When a consumer incident exposes a reusable process gap, `process-improve` first keeps
188
204
  the consumer safe, then prepares a sanitized GitHub issue draft from that checkout.
189
205
  It deduplicates by consumer/process-version/invariant, requires owner authorization
190
206
  before `gh issue create`, and uses an accepted issue as the later process change source
@@ -352,7 +368,7 @@ URL; without it, the review remains pending. Earlier plan and review documents r
352
368
  readable, and their runs remain registrable or finishable with the version selected by
353
369
  the authority that started the relevant phase.
354
370
 
355
- The [finding priority definitions](process_assets/skills/review-change/SKILL.md#finding-priority)
371
+ The [finding priority definitions](process_assets/skills/change-review/SKILL.md#finding-priority)
356
372
  are the canonical P0-P3 impact convention for this process, including examples and
357
373
  their relationship to blocking decisions.
358
374
 
@@ -31,16 +31,16 @@ engineering_process.egg-info/dependency_links.txt
31
31
  engineering_process.egg-info/entry_points.txt
32
32
  engineering_process.egg-info/requires.txt
33
33
  engineering_process.egg-info/top_level.txt
34
- process_assets/skills/finish-change/SKILL.md
35
- process_assets/skills/implement-change/SKILL.md
36
- process_assets/skills/improve-process/SKILL.md
37
- process_assets/skills/plan-change/SKILL.md
34
+ process_assets/skills/change-complete/SKILL.md
35
+ process_assets/skills/change-implement/SKILL.md
36
+ process_assets/skills/change-plan/SKILL.md
37
+ process_assets/skills/change-review/SKILL.md
38
+ process_assets/skills/change-start/SKILL.md
39
+ process_assets/skills/change-verify/SKILL.md
40
+ process_assets/skills/deliver-change/SKILL.md
41
+ process_assets/skills/process-improve/SKILL.md
38
42
  process_assets/skills/production-engineering/SKILL.md
39
43
  process_assets/skills/production-engineering/invariants.json
40
- process_assets/skills/review-change/SKILL.md
41
- process_assets/skills/run-change/SKILL.md
42
- process_assets/skills/start-change/SKILL.md
43
- process_assets/skills/verify-change/SKILL.md
44
44
  schemas/change.schema.json
45
45
  schemas/plan.schema.json
46
46
  schemas/process-graph.schema.json
@@ -1,83 +1,83 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
- "entrySkill": "run-change",
3
+ "entrySkill": "deliver-change",
4
4
  "specializations": {
5
- "processChange": "improve-process",
5
+ "processChange": "process-improve",
6
6
  "productionEngineering": "production-engineering"
7
7
  },
8
8
  "states": [
9
9
  {
10
10
  "id": "unregistered",
11
- "ownerSkill": "start-change",
11
+ "ownerSkill": "change-start",
12
12
  "commands": ["change start"],
13
13
  "transitions": [
14
- {"result": "success", "nextState": "specified", "nextSkill": "plan-change"}
14
+ {"result": "success", "nextState": "specified", "nextSkill": "change-plan"}
15
15
  ]
16
16
  },
17
17
  {
18
18
  "id": "specified",
19
- "ownerSkill": "plan-change",
19
+ "ownerSkill": "change-plan",
20
20
  "commands": ["change plan"],
21
21
  "transitions": [
22
- {"result": "success", "nextState": "planned", "nextSkill": "implement-change"}
22
+ {"result": "success", "nextState": "planned", "nextSkill": "change-implement"}
23
23
  ]
24
24
  },
25
25
  {
26
26
  "id": "planned",
27
- "ownerSkill": "implement-change",
27
+ "ownerSkill": "change-implement",
28
28
  "commands": ["change implement"],
29
29
  "transitions": [
30
- {"result": "success", "nextState": "implementing", "nextSkill": "verify-change"}
30
+ {"result": "success", "nextState": "implementing", "nextSkill": "change-verify"}
31
31
  ]
32
32
  },
33
33
  {
34
34
  "id": "implementing",
35
- "ownerSkill": "verify-change",
35
+ "ownerSkill": "change-verify",
36
36
  "commands": ["change implement", "change verify"],
37
37
  "transitions": [
38
- {"result": "profile-passed", "nextState": "implementing", "nextSkill": "verify-change"},
39
- {"result": "all-passed", "nextState": "verified", "nextSkill": "review-change"}
38
+ {"result": "profile-passed", "nextState": "implementing", "nextSkill": "change-verify"},
39
+ {"result": "all-passed", "nextState": "verified", "nextSkill": "change-review"}
40
40
  ]
41
41
  },
42
42
  {
43
43
  "id": "verified",
44
- "ownerSkill": "review-change",
44
+ "ownerSkill": "change-review",
45
45
  "commands": ["change implement", "change review start"],
46
46
  "transitions": [
47
- {"result": "assigned", "nextState": "review-pending", "nextSkill": "review-change"},
48
- {"result": "source-changed", "nextState": "implementing", "nextSkill": "implement-change"}
47
+ {"result": "assigned", "nextState": "review-pending", "nextSkill": "change-review"},
48
+ {"result": "source-changed", "nextState": "implementing", "nextSkill": "change-implement"}
49
49
  ]
50
50
  },
51
51
  {
52
52
  "id": "review-pending",
53
- "ownerSkill": "review-change",
53
+ "ownerSkill": "change-review",
54
54
  "commands": ["change implement", "change review submit"],
55
55
  "transitions": [
56
- {"result": "approved", "nextState": "approved", "nextSkill": "finish-change"},
57
- {"result": "changes-requested", "nextState": "changes-requested", "nextSkill": "implement-change"},
58
- {"result": "source-changed", "nextState": "implementing", "nextSkill": "implement-change"}
56
+ {"result": "approved", "nextState": "approved", "nextSkill": "change-complete"},
57
+ {"result": "changes-requested", "nextState": "changes-requested", "nextSkill": "change-implement"},
58
+ {"result": "source-changed", "nextState": "implementing", "nextSkill": "change-implement"}
59
59
  ]
60
60
  },
61
61
  {
62
62
  "id": "changes-requested",
63
- "ownerSkill": "implement-change",
63
+ "ownerSkill": "change-implement",
64
64
  "commands": ["change implement"],
65
65
  "transitions": [
66
- {"result": "success", "nextState": "implementing", "nextSkill": "verify-change"}
66
+ {"result": "success", "nextState": "implementing", "nextSkill": "change-verify"}
67
67
  ]
68
68
  },
69
69
  {
70
70
  "id": "approved",
71
- "ownerSkill": "finish-change",
71
+ "ownerSkill": "change-complete",
72
72
  "commands": ["change finish", "change implement"],
73
73
  "transitions": [
74
74
  {"result": "success", "nextState": null, "nextSkill": null},
75
- {"result": "source-changed", "nextState": "implementing", "nextSkill": "implement-change"}
75
+ {"result": "source-changed", "nextState": "implementing", "nextSkill": "change-implement"}
76
76
  ]
77
77
  },
78
78
  {
79
79
  "id": "blocked",
80
- "ownerSkill": "run-change",
80
+ "ownerSkill": "deliver-change",
81
81
  "commands": [],
82
82
  "transitions": []
83
83
  }
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: finish-change
3
- description: Complete an approved change only while its verification and independent review still match the current repository snapshot.
2
+ name: change-complete
3
+ description: Complete an approved change when routed by deliver-change, only while verification and independent review still match the repository snapshot.
4
4
  ---
5
5
 
6
- # Finish a change
6
+ # Complete a change
7
7
 
8
8
  Confirm the lifecycle is approved, every required profile passed, every blocking
9
9
  finding is closed, every non-blocking finding has its required disposition, and the
@@ -11,7 +11,8 @@ repository still matches the reviewed snapshot. Then run:
11
11
 
12
12
  processctl change finish --change-id ID --actor ACTOR --context CONTEXT
13
13
 
14
- The command writes one bounded completion receipt and marks the run completed.
14
+ The existing `change finish` CLI operation writes one bounded completion receipt and
15
+ marks the run completed.
15
16
  Completion does not itself grant merge, deployment, or release authority; those
16
17
  remain project-owned operations. Never report completion from prose alone.
17
18
 
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: implement-change
3
- description: Implement the accepted plan or resolve blocking review findings without changing the contract implicitly.
2
+ name: change-implement
3
+ description: Implement the accepted plan or resolve blocking review findings when routed by deliver-change, without changing the contract implicitly.
4
4
  ---
5
5
 
6
6
  # Implement a change
@@ -29,4 +29,4 @@ gaps merely because they are listed.
29
29
 
30
30
  When evidence exposes a contract gap, stop and ask the project owner to supersede the
31
31
  contract. Do not make review prose into new scope. When implementation is ready,
32
- route to **verify-change**.
32
+ route to **change-verify**.
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: plan-change
3
- description: Produce the smallest implementation plan that covers every accepted outcome and its verification boundary.
2
+ name: change-plan
3
+ description: Plan the registered change and its verification boundary when deliver-change routes a specified change.
4
4
  ---
5
5
 
6
6
  # Plan a change
@@ -1,15 +1,24 @@
1
1
  ---
2
- name: review-change
3
- description: Review the exact verified snapshot from an actor and context independent of its implementation.
2
+ name: change-review
3
+ description: Review the exact verified snapshot from an independent actor and context when routed by deliver-change.
4
4
  ---
5
5
 
6
6
  # Review a change
7
7
 
8
8
  The reviewer must not share either actor identity or execution context with an
9
- implementer in the current cycle. Start the assignment:
9
+ implementer in the current cycle. Read `processctl change status --change-id ID`.
10
+ When the phase is `verified`, start the assignment:
10
11
 
11
12
  processctl change review start --change-id ID --actor REVIEWER --context REVIEW_CONTEXT
12
13
 
14
+ When the phase is `review-pending`, resume the existing assignment; do not run
15
+ `change review start` again. Read `.process/runs/ID/run.json` for its `cycle` and
16
+ `reviewAssignment`, including the assigned reviewer, checkpoint, and
17
+ `reportSchemaVersion`. Continue with the assigned independent actor/context and the
18
+ existing report path, `.process/runs/ID/review-CYCLE.json`. If that reviewer is
19
+ unavailable, report the pending assignment as a blocker; never impersonate its
20
+ identity or create a replacement assignment from another context.
21
+
13
22
  Review the accepted contract, plan, complete diff, focused tests, and verification
14
23
  evidence. The first pass is comprehensive within that frozen contract. Every finding
15
24
  maps to one accepted criterion and records priority, origin, severity, and location.
@@ -41,7 +50,7 @@ consider consumer evidence that the lifecycle cannot observe. Every schema-versi
41
50
  report classifies `processImprovement` as `none`, `consumer-specific`, or
42
51
  `shared-process` and gives a concrete rationale. Consumer-specific behavior stays in
43
52
  the consumer. For `shared-process`, keep the assignment `review-pending` and route the
44
- candidate through **improve-process**. Submit only after an existing or owner-authorized
53
+ candidate through **process-improve**. Submit only after an existing or owner-authorized
45
54
  issue supplies the stable HTTPS `recordUrl`; the review itself remains read-only.
46
55
 
47
56
  Read the consumer readiness result and repository rules. Check the complete diff for
@@ -56,8 +65,8 @@ Validate and submit the report:
56
65
  processctl contract validate --kind review REPORT_PATH
57
66
  processctl change review submit --change-id ID --review REPORT_PATH
58
67
 
59
- Review is read-only. Requested changes route back to **implement-change**; approval
60
- routes to **finish-change**. Keep the same independent reviewer for corrections.
68
+ Review is read-only. Requested changes route back to **change-implement**; approval
69
+ routes to **change-complete**. Keep the same independent reviewer for corrections.
61
70
  Follow-up scope is only carried findings, remediation diffs, and regressions against
62
71
  the frozen contract. A new blocker must be either remediation-caused or a P0/P1 late
63
72
  violation with a rationale. After two correction cycles, another changes-requested
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: start-change
3
- description: Turn an accepted request into a bounded change contract before planning or implementation begins.
2
+ name: change-start
3
+ description: Turn an accepted request into a bounded change contract when deliver-change routes a new change to lifecycle start.
4
4
  ---
5
5
 
6
6
  # Start a change
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: verify-change
3
- description: Run the project-owned verification profiles and bind their results to one unchanged repository snapshot.
2
+ name: change-verify
3
+ description: Run the project-owned verification profiles on one unchanged repository snapshot when routed by deliver-change.
4
4
  ---
5
5
 
6
6
  # Verify a change
@@ -23,4 +23,4 @@ planned capability whose gap remains open. A readiness promotion is valid only w
23
23
  all evidence named by that capability passes on this same snapshot.
24
24
 
25
25
  When all required profiles pass on the same snapshot, the lifecycle becomes verified;
26
- route to **review-change**.
26
+ route to **change-review**.
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: run-change
3
- description: Route a non-trivial repository change through the complete engineering process from an accepted contract to independently reviewed completion.
2
+ name: deliver-change
3
+ description: Drive a repository change through the governed engineering lifecycle, from an accepted contract to independently reviewed completion.
4
4
  ---
5
5
 
6
- # Run a change
6
+ # Deliver a change
7
7
 
8
8
  Use this as the only entry point for delivery work. Run `processctl project validate
9
9
  --json` first. When readiness is present, report its stage, immutable pack versions,
@@ -11,21 +11,22 @@ enforced floor, and planned gaps. Planned gaps guide future work but do not beco
11
11
  scope of the current change unless the accepted request selects one; never choose the
12
12
  product roadmap autonomously.
13
13
 
14
- Inspect processctl change status when a change already exists, then route exactly one
15
- current phase:
16
-
17
- 1. No run: use **start-change**.
18
- 2. specified: use **plan-change**.
19
- 3. planned or changes-requested: use **implement-change**.
20
- 4. implementing: use **verify-change**.
21
- 5. verified or review-pending: use **review-change**; if the repository changed after
22
- evidence was recorded, use **implement-change** to open a new cycle.
23
- 6. approved: use **finish-change**; a later repository change also reopens through
24
- **implement-change**.
14
+ The six **change-*** skills are lifecycle routes selected here. Inspect processctl
15
+ change status when a change already exists, then route exactly one current phase:
16
+
17
+ 1. No run: use **change-start**.
18
+ 2. specified: use **change-plan**.
19
+ 3. planned or changes-requested: use **change-implement**.
20
+ 4. implementing: use **change-verify**.
21
+ 5. verified or review-pending: use **change-review**, resuming the existing assignment
22
+ when review is pending; if the repository changed after evidence was recorded,
23
+ use **change-implement** to open a new cycle.
24
+ 6. approved: use **change-complete**; a later repository change also reopens through
25
+ **change-implement**.
25
26
  7. blocked: stop. The current contract cannot merge; the owner may narrow or
26
27
  supersede it, but no correction-limit stop can waive independent review.
27
28
 
28
- When changing this process itself, first use **improve-process** to prove the request
29
+ When changing this process itself, first use **process-improve** to prove the request
29
30
  came from a real consumer incident or need; the change still follows the same six
30
31
  phases afterward.
31
32
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: improve-process
2
+ name: process-improve
3
3
  description: Change the shared process only in response to evidence from a real consumer and prefer removing complexity over adding governance.
4
4
  ---
5
5
 
@@ -68,7 +68,7 @@ Find the smallest reusable correction. Prefer, in order:
68
68
  4. add a new gate only when the consumer evidence proves the other options cannot
69
69
  protect the required invariant.
70
70
 
71
- Use **run-change** for the actual work and require the same independent final review
71
+ Use **deliver-change** for the actual work and require the same independent final review
72
72
  as any consumer change. Track cross-repository discussion in ordinary issues or pull
73
73
  requests; do not create a second lifecycle or evidence federation. The process never
74
74
  self-publishes or self-merges: the owner retains release and adoption authority, and
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-process"
7
- version = "1.2.6"
7
+ version = "2.0.0"
8
8
  description = "A small agent-neutral engineering lifecycle with managed adoption"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -60,15 +60,15 @@ dev = {file = ["engineering_process/requirements-dev.txt"]}
60
60
  "schemas/review.schema.json",
61
61
  "schemas/run.schema.json",
62
62
  ]
63
- "share/engineering-process/skills/finish-change" = ["process_assets/skills/finish-change/SKILL.md"]
64
- "share/engineering-process/skills/implement-change" = ["process_assets/skills/implement-change/SKILL.md"]
65
- "share/engineering-process/skills/improve-process" = ["process_assets/skills/improve-process/SKILL.md"]
66
- "share/engineering-process/skills/plan-change" = ["process_assets/skills/plan-change/SKILL.md"]
63
+ "share/engineering-process/skills/change-complete" = ["process_assets/skills/change-complete/SKILL.md"]
64
+ "share/engineering-process/skills/change-implement" = ["process_assets/skills/change-implement/SKILL.md"]
65
+ "share/engineering-process/skills/change-plan" = ["process_assets/skills/change-plan/SKILL.md"]
66
+ "share/engineering-process/skills/change-review" = ["process_assets/skills/change-review/SKILL.md"]
67
+ "share/engineering-process/skills/change-start" = ["process_assets/skills/change-start/SKILL.md"]
68
+ "share/engineering-process/skills/change-verify" = ["process_assets/skills/change-verify/SKILL.md"]
69
+ "share/engineering-process/skills/deliver-change" = ["process_assets/skills/deliver-change/SKILL.md"]
70
+ "share/engineering-process/skills/process-improve" = ["process_assets/skills/process-improve/SKILL.md"]
67
71
  "share/engineering-process/skills/production-engineering" = [
68
72
  "process_assets/skills/production-engineering/SKILL.md",
69
73
  "process_assets/skills/production-engineering/invariants.json",
70
74
  ]
71
- "share/engineering-process/skills/review-change" = ["process_assets/skills/review-change/SKILL.md"]
72
- "share/engineering-process/skills/run-change" = ["process_assets/skills/run-change/SKILL.md"]
73
- "share/engineering-process/skills/start-change" = ["process_assets/skills/start-change/SKILL.md"]
74
- "share/engineering-process/skills/verify-change" = ["process_assets/skills/verify-change/SKILL.md"]
@@ -1,7 +1,7 @@
1
1
  <!-- engineering-process:start -->
2
2
  ## Engineering process
3
3
 
4
- For non-trivial delivery work, enter through the managed run-change skill and follow
4
+ For non-trivial delivery work, enter through the managed deliver-change skill and follow
5
5
  the processctl lifecycle: start, plan, implement, verify, independent review, finish.
6
6
 
7
7
  This repository owns product decisions, domain rules, exact argument-array commands,
@@ -17,6 +17,11 @@ from engineering_process.contracts import ProcessError, read_json
17
17
 
18
18
 
19
19
  PROCESS_ROOT = Path(__file__).resolve().parent.parent
20
+ PREVIOUS_SKILLS = (
21
+ "run-change", "start-change", "plan-change", "implement-change",
22
+ "verify-change", "review-change", "finish-change", "improve-process",
23
+ "production-engineering",
24
+ )
20
25
 
21
26
 
22
27
  def write_json(path: Path, value: object) -> None:
@@ -135,6 +140,80 @@ class AdoptionTests(unittest.TestCase):
135
140
  def tearDown(self) -> None:
136
141
  self.temporary.cleanup()
137
142
 
143
+ def previous_catalog_consumer(self) -> Path:
144
+ consumer = (self.root / "previous-catalog").resolve()
145
+ write_json(consumer / ".process" / "project.json", read_json(self.root / ".process" / "project.json"))
146
+ managed = []
147
+ for name in PREVIOUS_SKILLS:
148
+ directory = consumer / ".agents" / "skills" / name
149
+ directory.mkdir(parents=True)
150
+ (directory / "SKILL.md").write_bytes(
151
+ f"---\nname: {name}\ndescription: Previous release skill.\n---\n".encode("utf-8")
152
+ )
153
+ write_json(directory / ".engineering-process.json", {
154
+ "schemaVersion": 1, "managedBy": "engineering-process",
155
+ "version": "1.2.6", "digest": "sha256:" + "0" * 64,
156
+ })
157
+ (directory / "consumer-notes.md").write_bytes(b"consumer owned\n")
158
+ managed.extend(f".agents/skills/{name}/{file}" for file in ("SKILL.md", ".engineering-process.json"))
159
+ invariant = Path(".agents/skills/production-engineering/invariants.json")
160
+ (consumer / invariant).write_bytes(
161
+ (PROCESS_ROOT / "process_assets" / "skills" / "production-engineering" / "invariants.json").read_bytes()
162
+ )
163
+ managed.append(invariant.as_posix())
164
+ write_json(consumer / ".process" / "process.lock", {
165
+ "schemaVersion": 2,
166
+ "process": {"package": "engineering-process", "version": "1.2.6", "digest": "sha256:" + "0" * 64},
167
+ "requirementsDigest": "sha256:" + "1" * 64,
168
+ "skills": list(PREVIOUS_SKILLS), "managedFiles": managed,
169
+ })
170
+ (consumer / "AGENTS.md").write_bytes(
171
+ b"# Consumer rules\n\n<!-- engineering-process:start -->\nUse run-change.\n<!-- engineering-process:end -->\n"
172
+ )
173
+ return consumer
174
+
175
+ def test_previous_catalog_migrates_all_eight_names_and_is_idempotent(self) -> None:
176
+ consumer = self.previous_catalog_consumer()
177
+ self.assertEqual("applied", apply_adoption(consumer, PROCESS_ROOT, self.requirements)["status"])
178
+ lock = read_json(consumer / ".process" / "process.lock")
179
+ expected = {path.name for path in (PROCESS_ROOT / "process_assets" / "skills").iterdir() if path.is_dir()}
180
+ self.assertEqual(expected, set(lock["skills"]))
181
+ installed = consumer / ".agents" / "skills"
182
+ self.assertEqual(expected, {path.name for path in installed.iterdir() if (path / "SKILL.md").is_file()})
183
+ for name in PREVIOUS_SKILLS:
184
+ with self.subTest(name=name):
185
+ self.assertEqual(b"consumer owned\n", (installed / name / "consumer-notes.md").read_bytes())
186
+ if name != "production-engineering":
187
+ for filename in ("SKILL.md", ".engineering-process.json"):
188
+ self.assertFalse((installed / name / filename).exists())
189
+ self.assertNotIn(f".agents/skills/{name}/{filename}", lock["managedFiles"])
190
+ instructions = (consumer / "AGENTS.md").read_text(encoding="utf-8")
191
+ self.assertIn("# Consumer rules", instructions)
192
+ self.assertIn("deliver-change", instructions)
193
+ self.assertNotIn("run-change", instructions)
194
+ self.assertEqual("passed", check_adoption(consumer, PROCESS_ROOT, self.requirements)["status"])
195
+ self.assertEqual("unchanged", apply_adoption(consumer, PROCESS_ROOT, self.requirements)["status"])
196
+
197
+ def test_previous_catalog_is_restored_after_rename_cleanup_failure(self) -> None:
198
+ consumer = self.previous_catalog_consumer()
199
+ before = {path.relative_to(consumer): path.read_bytes() for path in consumer.rglob("*") if path.is_file()}
200
+ original_unlink = Path.unlink
201
+ failed = False
202
+
203
+ def fail_old_skill_removal(path: Path, *args: object, **kwargs: object) -> None:
204
+ nonlocal failed
205
+ if not failed and path == consumer / ".agents" / "skills" / "run-change" / "SKILL.md":
206
+ failed = True
207
+ raise OSError("injected old-skill cleanup failure")
208
+ original_unlink(path, *args, **kwargs)
209
+
210
+ with mock.patch.object(Path, "unlink", fail_old_skill_removal), self.assertRaisesRegex(ProcessError, "was rolled back"):
211
+ apply_adoption(consumer, PROCESS_ROOT, self.requirements)
212
+ self.assertTrue(failed)
213
+ self.assertEqual(before, {path.relative_to(consumer): path.read_bytes() for path in consumer.rglob("*") if path.is_file()})
214
+ self.assertEqual("applied", apply_adoption(consumer, PROCESS_ROOT, self.requirements)["status"])
215
+ self.assertEqual("passed", check_adoption(consumer, PROCESS_ROOT, self.requirements)["status"])
216
+
138
217
  def test_legacy_consumer_converges_and_second_apply_is_noop(self) -> None:
139
218
  first = apply_adoption(
140
219
  self.root, PROCESS_ROOT, self.requirements, requirements_source=self.requirements
@@ -160,7 +239,8 @@ class AdoptionTests(unittest.TestCase):
160
239
  self.assertFalse((self.root / ".process" / "automation.json").exists())
161
240
  self.assertFalse((self.root / ".process" / "adoption-migrations").exists())
162
241
  self.assertTrue((self.root / ".agents" / "skills" / "consumer-owned" / "SKILL.md").is_file())
163
- self.assertTrue((self.root / ".agents" / "skills" / "improve-process" / "SKILL.md").is_file())
242
+ self.assertTrue((self.root / ".agents" / "skills" / "process-improve" / "SKILL.md").is_file())
243
+ self.assertFalse((self.root / ".agents" / "skills" / "run-change" / "SKILL.md").exists())
164
244
  self.assertEqual(
165
245
  (
166
246
  PROCESS_ROOT
@@ -204,7 +284,7 @@ class AdoptionTests(unittest.TestCase):
204
284
  lock = read_json(self.root / ".process" / "process.lock")
205
285
  self.assertEqual(2, lock["schemaVersion"])
206
286
  self.assertEqual(VERSION, lock["process"]["version"])
207
- self.assertIn(".agents/skills/run-change/SKILL.md", lock["managedFiles"])
287
+ self.assertIn(".agents/skills/deliver-change/SKILL.md", lock["managedFiles"])
208
288
  self.assertIn(".process/adopt-process-windows-job.py", lock["managedFiles"])
209
289
 
210
290
  second = apply_adoption(self.root, PROCESS_ROOT, self.requirements)
@@ -220,7 +300,7 @@ class AdoptionTests(unittest.TestCase):
220
300
  apply_adoption(self.root, PROCESS_ROOT, self.requirements)
221
301
 
222
302
  def test_consumer_owned_skill_name_collision_fails_closed(self) -> None:
223
- collision = self.root / ".agents" / "skills" / "improve-process" / "SKILL.md"
303
+ collision = self.root / ".agents" / "skills" / "process-improve" / "SKILL.md"
224
304
  collision.parent.mkdir(parents=True)
225
305
  collision.write_text("consumer skill\n", encoding="utf-8")
226
306
  with self.assertRaisesRegex(ProcessError, "consumer-owned path collides"):
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import hashlib
4
4
  from pathlib import Path
5
5
  import tempfile
6
+ import tomllib
6
7
  import unittest
7
8
 
8
9
  from engineering_process.distribution import distribution_digest, skill_digest
@@ -20,6 +21,24 @@ def framed_digest(entries: list[tuple[str, bytes]]) -> str:
20
21
 
21
22
 
22
23
  class DistributionTests(unittest.TestCase):
24
+ def test_packaged_skill_assets_match_the_source_catalog(self) -> None:
25
+ root = Path(__file__).resolve().parent.parent
26
+ metadata = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
27
+ declared = {
28
+ target: set(paths)
29
+ for target, paths in metadata["tool"]["setuptools"]["data-files"].items()
30
+ if target.startswith("share/engineering-process/skills/")
31
+ }
32
+ expected = {
33
+ f"share/engineering-process/skills/{directory.name}": {
34
+ path.relative_to(root).as_posix()
35
+ for path in directory.rglob("*") if path.is_file()
36
+ }
37
+ for directory in (root / "process_assets" / "skills").iterdir()
38
+ if directory.is_dir()
39
+ }
40
+ self.assertEqual(expected, declared)
41
+
23
42
  def test_digests_follow_canonical_relative_posix_order(self) -> None:
24
43
  entries = [
25
44
  ("skills/sample/SKILL.md", b"skill\n"),
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from copy import deepcopy
3
4
  from pathlib import Path
5
+ import re
4
6
  import unittest
7
+ from unittest import mock
5
8
 
6
- from engineering_process.contracts import read_json
9
+ from engineering_process.contracts import ProcessError, read_json
7
10
  from engineering_process.skills import validate_skills
8
11
 
9
12
 
@@ -13,21 +16,63 @@ ROOT = Path(__file__).resolve().parent.parent
13
16
  class SkillTests(unittest.TestCase):
14
17
  def test_distribution_has_one_complete_reachable_skill_graph(self) -> None:
15
18
  result = validate_skills(ROOT / "process_assets" / "skills", process_root=ROOT)
16
- self.assertEqual("run-change", result["entrySkill"])
19
+ self.assertEqual("deliver-change", result["entrySkill"])
17
20
  expected = {
18
- "finish-change",
19
- "implement-change",
20
- "improve-process",
21
- "plan-change",
21
+ "change-complete",
22
+ "change-implement",
23
+ "process-improve",
24
+ "change-plan",
22
25
  "production-engineering",
23
- "review-change",
24
- "run-change",
25
- "start-change",
26
- "verify-change",
26
+ "change-review",
27
+ "deliver-change",
28
+ "change-start",
29
+ "change-verify",
27
30
  }
28
31
  self.assertEqual(expected, set(result["skills"]))
29
32
  self.assertEqual(len(expected), result["count"])
30
33
 
34
+ def test_graph_rejects_missing_and_partial_transition_targets(self) -> None:
35
+ graph = read_json(ROOT / "process-graph.json")
36
+ for destination, skill in (
37
+ ("specified", "missing-skill"),
38
+ ("missing-state", "change-plan"),
39
+ (None, "change-plan"),
40
+ ("specified", None),
41
+ ):
42
+ invalid = deepcopy(graph)
43
+ start = next(state for state in invalid["states"] if state["id"] == "unregistered")
44
+ start["transitions"][0].update(nextState=destination, nextSkill=skill)
45
+ with self.subTest(destination=destination, skill=skill), mock.patch(
46
+ "engineering_process.skills.load_and_validate", return_value=invalid
47
+ ), self.assertRaisesRegex(ProcessError, "process graph"):
48
+ validate_skills(ROOT / "process_assets" / "skills", process_root=ROOT)
49
+
50
+ def test_changed_source_routes_through_implementation_before_verification(self) -> None:
51
+ graph = read_json(ROOT / "process-graph.json")
52
+ for state in graph["states"]:
53
+ if state["id"] in {"verified", "review-pending", "approved"}:
54
+ transition = next(item for item in state["transitions"] if item["result"] == "source-changed")
55
+ with self.subTest(state=state["id"]):
56
+ self.assertEqual("implementing", transition["nextState"])
57
+ self.assertEqual("change-implement", transition["nextSkill"])
58
+ validate_skills(ROOT / "process_assets" / "skills", process_root=ROOT)
59
+
60
+ def test_graph_rejects_ambiguous_state_owners(self) -> None:
61
+ graph = read_json(ROOT / "process-graph.json")
62
+ graph["states"][-1]["id"] = "specified"
63
+ with mock.patch(
64
+ "engineering_process.skills.load_and_validate", return_value=graph
65
+ ), self.assertRaisesRegex(ProcessError, "state ids must be unique"):
66
+ validate_skills(ROOT / "process_assets" / "skills", process_root=ROOT)
67
+
68
+ def test_readme_skill_links_resolve(self) -> None:
69
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
70
+ links = re.findall(r"\]\((process_assets/skills/[^)#]+)(?:#[^)]*)?\)", readme)
71
+ self.assertTrue(links)
72
+ for relative in links:
73
+ with self.subTest(relative=relative):
74
+ self.assertTrue((ROOT / relative).is_file())
75
+
31
76
  def test_graph_exposes_only_the_six_phase_cli(self) -> None:
32
77
  graph = read_json(ROOT / "process-graph.json")
33
78
  commands = {
@@ -50,10 +95,10 @@ class SkillTests(unittest.TestCase):
50
95
  def test_existing_skill_graph_guides_readiness_without_global_blocking(self) -> None:
51
96
  roots = ROOT / "process_assets" / "skills"
52
97
  required = {
53
- "run-change": ("processctl project validate", "never choose the\nproduct roadmap autonomously"),
54
- "start-change": ("affected\nenforced capability", "Unrelated planned gaps remain visible and non-blocking"),
55
- "plan-change": ("do not add unrelated planned gaps", "checklist edit alone is not evidence"),
56
- "implement-change": (
98
+ "deliver-change": ("processctl project validate", "never choose the\nproduct roadmap autonomously"),
99
+ "change-start": ("affected\nenforced capability", "Unrelated planned gaps remain visible and non-blocking"),
100
+ "change-plan": ("do not add unrelated planned gaps", "checklist edit alone is not evidence"),
101
+ "change-implement": (
57
102
  "Never auto-promote",
58
103
  "do not work unrelated planned\ngaps",
59
104
  "production-engineering",
@@ -65,8 +110,8 @@ class SkillTests(unittest.TestCase):
65
110
  "Approval is impossible",
66
111
  "readiness declaration",
67
112
  ),
68
- "verify-change": ("do not run every planned production gate", "same snapshot"),
69
- "review-change": (
113
+ "change-verify": ("do not run every planned production gate", "same snapshot"),
114
+ "change-review": (
70
115
  "Do not block the change merely because unrelated planned capabilities",
71
116
  "Priority records impact if the finding remains unresolved",
72
117
  "never omit an\nobservation merely to reach approval",
@@ -74,14 +119,14 @@ class SkillTests(unittest.TestCase):
74
119
  "bounded `processSignals`",
75
120
  "keep the assignment `review-pending`",
76
121
  ),
77
- "finish-change": (
122
+ "change-complete": (
78
123
  "Finish never edits\nreadiness",
79
124
  "remaining planned\ngaps",
80
125
  "owner and stable record URL",
81
126
  "contract-identified final consumer adoption",
82
127
  "Closes ISSUE, closes ISSUE",
83
128
  ),
84
- "improve-process": (
129
+ "process-improve": (
85
130
  "new immutable pack version",
86
131
  "pending schema-version 7 review",
87
132
  "never\nself-publishes or self-merges",
@@ -94,7 +139,7 @@ class SkillTests(unittest.TestCase):
94
139
  self.assertIn(" ".join(fragment.split()), " ".join(text.split()))
95
140
 
96
141
  def test_improve_process_supports_owner_authorized_issue_handoff(self) -> None:
97
- text = (ROOT / "process_assets" / "skills" / "improve-process" / "SKILL.md").read_text(encoding="utf-8")
142
+ text = (ROOT / "process_assets" / "skills" / "process-improve" / "SKILL.md").read_text(encoding="utf-8")
98
143
  for fragment in (
99
144
  "fix or safely block the current consumer change",
100
145
  "gh issue list --repo phuongnse/engineering-process",