engineering-process 2.4.0__tar.gz → 2.6.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 (109) hide show
  1. {engineering_process-2.4.0/engineering_process.egg-info → engineering_process-2.6.0}/PKG-INFO +94 -4
  2. {engineering_process-2.4.0 → engineering_process-2.6.0}/README.md +93 -3
  3. engineering_process-2.6.0/RELEASE_NOTES.md +49 -0
  4. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/__init__.py +1 -1
  5. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/cli.py +198 -7
  6. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/commands.py +93 -16
  7. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/contracts.py +2 -0
  8. engineering_process-2.6.0/engineering_process/evidence.py +180 -0
  9. engineering_process-2.6.0/engineering_process/impact.py +231 -0
  10. engineering_process-2.6.0/engineering_process/lifecycle.py +1556 -0
  11. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/pr_description.py +229 -1
  12. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/project.py +60 -0
  13. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/release_notes.py +52 -5
  14. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/repository.py +69 -8
  15. {engineering_process-2.4.0 → engineering_process-2.6.0/engineering_process.egg-info}/PKG-INFO +94 -4
  16. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process.egg-info/SOURCES.txt +5 -0
  17. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/change-plan/SKILL.md +23 -0
  18. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/change-review/SKILL.md +17 -3
  19. engineering_process-2.6.0/process_assets/skills/change-verify/SKILL.md +131 -0
  20. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/process-improve/SKILL.md +24 -9
  21. {engineering_process-2.4.0 → engineering_process-2.6.0}/pyproject.toml +3 -1
  22. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/project.schema.json +90 -0
  23. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/receipt.schema.json +7 -5
  24. engineering_process-2.6.0/schemas/release-change.schema.json +79 -0
  25. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/release-notes-data.schema.json +44 -1
  26. engineering_process-2.6.0/schemas/release.schema.json +105 -0
  27. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/run.schema.json +4 -0
  28. engineering_process-2.6.0/schemas/verification-impact-selection.schema.json +143 -0
  29. engineering_process-2.6.0/schemas/verification-selection.schema.json +91 -0
  30. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_architecture.py +2 -0
  31. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_artifact_standards.py +384 -2
  32. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_automation.py +6 -1
  33. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_cli.py +117 -1
  34. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_commands.py +114 -3
  35. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_contracts.py +47 -0
  36. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_distribution.py +16 -0
  37. engineering_process-2.6.0/tests/test_impact.py +374 -0
  38. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_lifecycle.py +586 -11
  39. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_release.py +170 -18
  40. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_repository.py +63 -2
  41. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_review_contexts.py +125 -22
  42. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_skills.py +12 -1
  43. engineering_process-2.4.0/RELEASE_NOTES.md +0 -22
  44. engineering_process-2.4.0/engineering_process/lifecycle.py +0 -730
  45. engineering_process-2.4.0/process_assets/skills/change-verify/SKILL.md +0 -54
  46. engineering_process-2.4.0/schemas/release-change.schema.json +0 -20
  47. engineering_process-2.4.0/schemas/release.schema.json +0 -40
  48. {engineering_process-2.4.0 → engineering_process-2.6.0}/LICENSE +0 -0
  49. {engineering_process-2.4.0 → engineering_process-2.6.0}/MANIFEST.in +0 -0
  50. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/__main__.py +0 -0
  51. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/_supervisor_contract.py +0 -0
  52. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/_supervisor_posix.py +0 -0
  53. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/_supervisor_windows.py +0 -0
  54. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/_windows_job.py +0 -0
  55. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/adoption.py +0 -0
  56. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/artifact_standards.py +0 -0
  57. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/automation_name.py +0 -0
  58. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/distribution.py +0 -0
  59. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/helper_launch.py +0 -0
  60. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/issue.py +0 -0
  61. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/production_engineering.py +0 -0
  62. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/publication_compat.py +0 -0
  63. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/release.py +0 -0
  64. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/requirements-dev.txt +0 -0
  65. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/requirements-runtime.txt +0 -0
  66. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/review_contexts.py +0 -0
  67. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/skills.py +0 -0
  68. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/source_publication.py +0 -0
  69. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process/supervision.py +0 -0
  70. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process.egg-info/dependency_links.txt +0 -0
  71. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process.egg-info/entry_points.txt +0 -0
  72. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process.egg-info/requires.txt +0 -0
  73. {engineering_process-2.4.0 → engineering_process-2.6.0}/engineering_process.egg-info/top_level.txt +0 -0
  74. {engineering_process-2.4.0 → engineering_process-2.6.0}/process-graph.json +0 -0
  75. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/change-complete/SKILL.md +0 -0
  76. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/change-implement/SKILL.md +0 -0
  77. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/change-start/SKILL.md +0 -0
  78. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/deliver-change/SKILL.md +0 -0
  79. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/production-engineering/SKILL.md +0 -0
  80. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/skills/production-engineering/invariants.json +0 -0
  81. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/standards/automation-name.v1.json +0 -0
  82. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/standards/issue.v1.json +0 -0
  83. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/standards/pull-request.v1.json +0 -0
  84. {engineering_process-2.4.0 → engineering_process-2.6.0}/process_assets/standards/release-notes.v1.json +0 -0
  85. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/artifact-selection.schema.json +0 -0
  86. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/artifact-standard.schema.json +0 -0
  87. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/automation-name-data.schema.json +0 -0
  88. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/change.schema.json +0 -0
  89. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/issue-data.schema.json +0 -0
  90. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/plan.schema.json +0 -0
  91. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/pr-description-data.schema.json +0 -0
  92. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/process-graph.schema.json +0 -0
  93. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/process-lock.schema.json +0 -0
  94. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/production-engineering.schema.json +0 -0
  95. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/project-legacy.schema.json +0 -0
  96. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/renovate-preset.schema.json +0 -0
  97. {engineering_process-2.4.0 → engineering_process-2.6.0}/schemas/review.schema.json +0 -0
  98. {engineering_process-2.4.0 → engineering_process-2.6.0}/setup.cfg +0 -0
  99. {engineering_process-2.4.0 → engineering_process-2.6.0}/templates/AGENTS.process.md +0 -0
  100. {engineering_process-2.4.0 → engineering_process-2.6.0}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
  101. {engineering_process-2.4.0 → engineering_process-2.6.0}/templates/adopt-process-windows-job.py +0 -0
  102. {engineering_process-2.4.0 → engineering_process-2.6.0}/templates/adopt-process.py +0 -0
  103. {engineering_process-2.4.0 → engineering_process-2.6.0}/templates/renovate.json +0 -0
  104. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_adoption.py +0 -0
  105. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_production_engineering.py +0 -0
  106. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_publication_compat.py +0 -0
  107. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_publication_workflow.py +0 -0
  108. {engineering_process-2.4.0 → engineering_process-2.6.0}/tests/test_pypi_cache.py +0 -0
  109. {engineering_process-2.4.0 → engineering_process-2.6.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: 2.4.0
3
+ Version: 2.6.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
@@ -86,6 +86,32 @@ but cannot establish native conversation freshness or inspect removed history,
86
86
  other clones or other repositories. Keep native creation, non-inherited dispatch
87
87
  and effective settings evidence in the existing handoff.
88
88
 
89
+ Verification keeps two caller intents explicit. `change verify --profile PROFILE`
90
+ always refreshes the named profile. `change explain` is read-only; it shows the
91
+ accepted required profiles, valid prior evidence, remaining work, inapplicable
92
+ optional profiles, and blocked or unknown decisions. `change verify --remaining`
93
+ executes only remaining work and reuses a complete profile report only when the
94
+ candidate, contract/plan, project policy, process authority, runtime, and bounded
95
+ child environment match. Legacy evidence without that identity reruns. This is
96
+ whole-profile reuse: equal check IDs and ordered side effects are never merged, and
97
+ the lifecycle receipt still records only actual executions.
98
+
99
+ Reuse is stage-aware rather than a blanket cache: the contract and plan travel by
100
+ digest; valid whole-profile verification can satisfy a continuation request; review
101
+ and finish consume the same reports after rechecking the snapshot; and a new
102
+ implementation cycle invalidates verification. Operation-local calculation reuse is
103
+ allowed only before a mutation/concurrency boundary. Impact feedback and generated
104
+ documents remain derived artifacts and never replace final required profiles or
105
+ independent review.
106
+
107
+ Consumers that can prove complete changed-path coverage may opt selected required
108
+ profiles into final impact assurance with `impactProfiles.schemaVersion: 2` and
109
+ `finalProfiles`. Each opted profile must declare an explicit global unit whose paths
110
+ include the universal `**` pattern for cross-cutting reach. The lifecycle then
111
+ records the selected units as final
112
+ verification evidence; missing or unresolved coverage blocks rather than silently
113
+ falling back. Existing version 1 policies remain feedback-only.
114
+
89
115
  `processctl change review replace-reused` repairs only an initial pending assignment
90
116
  proven to reuse another change's agent context, before a submitted review or normal
91
117
  report file exists. It validates a fresh replacement and current verification,
@@ -156,6 +182,33 @@ Python 3.11 or newer and Git are required. A consumer owns .process/project.json
156
182
  "timeoutSeconds": 600
157
183
  }
158
184
  ]
185
+ },
186
+ "impactProfiles": {
187
+ "schemaVersion": 1,
188
+ "profiles": {
189
+ "development": [
190
+ {
191
+ "id": "unit-tests",
192
+ "run": ["python", "-m", "unittest", "tests.test_orders"],
193
+ "timeoutSeconds": 600,
194
+ "paths": ["src/orders/**", "tests/test_orders.py"]
195
+ },
196
+ {
197
+ "id": "cross-cutting",
198
+ "run": ["python", "-m", "unittest"],
199
+ "timeoutSeconds": 900,
200
+ "paths": ["**/process-policy.json"]
201
+ }
202
+ ],
203
+ "review": [
204
+ {
205
+ "id": "package",
206
+ "run": ["python", "-m", "build"],
207
+ "timeoutSeconds": 600,
208
+ "paths": ["src/**", "pyproject.toml"]
209
+ }
210
+ ]
211
+ }
159
212
  }
160
213
  }
161
214
 
@@ -502,6 +555,41 @@ Register implementation and run every required profile:
502
555
  processctl change verify --change-id change-123 --profile development
503
556
  processctl change verify --change-id change-123 --profile review
504
557
 
558
+ When continuing an incomplete verification, inspect and execute the necessary set:
559
+
560
+ processctl change explain --change-id change-123
561
+ processctl change verify --change-id change-123 --remaining
562
+
563
+ The explicit `--profile` form remains a refresh and is never silently converted to
564
+ reuse.
565
+
566
+ For fast feedback, consumers may declare a versioned `impactProfiles` policy in
567
+ `.process/project.json` and run only units related to the candidate paths:
568
+
569
+ processctl change explain --change-id change-123 --impact
570
+ processctl change verify --change-id change-123 --affected --affected-profile development
571
+
572
+ Each impact unit owns an exact command and path-pattern coverage. Matching is
573
+ explicit and deterministic; overlapping units all run. A unit with
574
+ `scope: "global"` may subsume narrower units only when its declared paths include
575
+ the universal `**` pattern; a narrower pattern never covers unrelated paths. Every
576
+ changed path must resolve for
577
+ each requested profile. Missing policy, unsupported policy, or an unmapped path is
578
+ `unresolved`/`unavailable`: the process launches nothing and returns the action
579
+ `inspect-diff-and-update-impact-policy`. The agent must inspect the diff and
580
+ dependency reach, update the consumer-owned mapping or obtain an owner decision,
581
+ then repeat the explanation. It must not use a full profile as a fallback.
582
+
583
+ Affected execution under a version 1 policy is feedback evidence only and never
584
+ advances the lifecycle or satisfies a required profile. A consumer that can prove
585
+ complete coverage may use schema version 2 with `finalProfiles`; `--remaining` then
586
+ records the selected units as `impact-assurance` evidence. Each opted profile needs
587
+ an explicit global unit with the universal `**` pattern for cross-cutting reach, and
588
+ unresolved final coverage
589
+ blocks rather than falling back silently. Explicit `--profile` remains the full
590
+ refresh. Consumers that do not adopt the opt-in retain the existing final boundary;
591
+ the affected command does not guess a policy for them.
592
+
505
593
  Assign an independent reviewer and submit its report:
506
594
 
507
595
  processctl change review start \
@@ -588,9 +676,11 @@ At any point:
588
676
  ## Release to consumer PR
589
677
 
590
678
  Each release includes [reviewed release contents](RELEASE_NOTES.md) generated from
591
- the canonical manifest: shipped features/fixes, their source issues or changes, and
592
- upgrade guidance. The release PR reviews this file; the GitHub Release publishes the
593
- same contents. See [the release procedure](RELEASING.md) for authoring and checks.
679
+ the canonical manifest: one record per shipped feature/fix, its source issue, the
680
+ problem and affected paths, the application action, compatibility impact, important
681
+ notes, and upgrade guidance. The release PR reviews this file; the GitHub Release
682
+ publishes the same contents. See [the release procedure](RELEASING.md) for authoring
683
+ and checks.
594
684
 
595
685
  This producer's release identity inputs and text assets declared by
596
686
  `tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
@@ -62,6 +62,32 @@ but cannot establish native conversation freshness or inspect removed history,
62
62
  other clones or other repositories. Keep native creation, non-inherited dispatch
63
63
  and effective settings evidence in the existing handoff.
64
64
 
65
+ Verification keeps two caller intents explicit. `change verify --profile PROFILE`
66
+ always refreshes the named profile. `change explain` is read-only; it shows the
67
+ accepted required profiles, valid prior evidence, remaining work, inapplicable
68
+ optional profiles, and blocked or unknown decisions. `change verify --remaining`
69
+ executes only remaining work and reuses a complete profile report only when the
70
+ candidate, contract/plan, project policy, process authority, runtime, and bounded
71
+ child environment match. Legacy evidence without that identity reruns. This is
72
+ whole-profile reuse: equal check IDs and ordered side effects are never merged, and
73
+ the lifecycle receipt still records only actual executions.
74
+
75
+ Reuse is stage-aware rather than a blanket cache: the contract and plan travel by
76
+ digest; valid whole-profile verification can satisfy a continuation request; review
77
+ and finish consume the same reports after rechecking the snapshot; and a new
78
+ implementation cycle invalidates verification. Operation-local calculation reuse is
79
+ allowed only before a mutation/concurrency boundary. Impact feedback and generated
80
+ documents remain derived artifacts and never replace final required profiles or
81
+ independent review.
82
+
83
+ Consumers that can prove complete changed-path coverage may opt selected required
84
+ profiles into final impact assurance with `impactProfiles.schemaVersion: 2` and
85
+ `finalProfiles`. Each opted profile must declare an explicit global unit whose paths
86
+ include the universal `**` pattern for cross-cutting reach. The lifecycle then
87
+ records the selected units as final
88
+ verification evidence; missing or unresolved coverage blocks rather than silently
89
+ falling back. Existing version 1 policies remain feedback-only.
90
+
65
91
  `processctl change review replace-reused` repairs only an initial pending assignment
66
92
  proven to reuse another change's agent context, before a submitted review or normal
67
93
  report file exists. It validates a fresh replacement and current verification,
@@ -132,6 +158,33 @@ Python 3.11 or newer and Git are required. A consumer owns .process/project.json
132
158
  "timeoutSeconds": 600
133
159
  }
134
160
  ]
161
+ },
162
+ "impactProfiles": {
163
+ "schemaVersion": 1,
164
+ "profiles": {
165
+ "development": [
166
+ {
167
+ "id": "unit-tests",
168
+ "run": ["python", "-m", "unittest", "tests.test_orders"],
169
+ "timeoutSeconds": 600,
170
+ "paths": ["src/orders/**", "tests/test_orders.py"]
171
+ },
172
+ {
173
+ "id": "cross-cutting",
174
+ "run": ["python", "-m", "unittest"],
175
+ "timeoutSeconds": 900,
176
+ "paths": ["**/process-policy.json"]
177
+ }
178
+ ],
179
+ "review": [
180
+ {
181
+ "id": "package",
182
+ "run": ["python", "-m", "build"],
183
+ "timeoutSeconds": 600,
184
+ "paths": ["src/**", "pyproject.toml"]
185
+ }
186
+ ]
187
+ }
135
188
  }
136
189
  }
137
190
 
@@ -478,6 +531,41 @@ Register implementation and run every required profile:
478
531
  processctl change verify --change-id change-123 --profile development
479
532
  processctl change verify --change-id change-123 --profile review
480
533
 
534
+ When continuing an incomplete verification, inspect and execute the necessary set:
535
+
536
+ processctl change explain --change-id change-123
537
+ processctl change verify --change-id change-123 --remaining
538
+
539
+ The explicit `--profile` form remains a refresh and is never silently converted to
540
+ reuse.
541
+
542
+ For fast feedback, consumers may declare a versioned `impactProfiles` policy in
543
+ `.process/project.json` and run only units related to the candidate paths:
544
+
545
+ processctl change explain --change-id change-123 --impact
546
+ processctl change verify --change-id change-123 --affected --affected-profile development
547
+
548
+ Each impact unit owns an exact command and path-pattern coverage. Matching is
549
+ explicit and deterministic; overlapping units all run. A unit with
550
+ `scope: "global"` may subsume narrower units only when its declared paths include
551
+ the universal `**` pattern; a narrower pattern never covers unrelated paths. Every
552
+ changed path must resolve for
553
+ each requested profile. Missing policy, unsupported policy, or an unmapped path is
554
+ `unresolved`/`unavailable`: the process launches nothing and returns the action
555
+ `inspect-diff-and-update-impact-policy`. The agent must inspect the diff and
556
+ dependency reach, update the consumer-owned mapping or obtain an owner decision,
557
+ then repeat the explanation. It must not use a full profile as a fallback.
558
+
559
+ Affected execution under a version 1 policy is feedback evidence only and never
560
+ advances the lifecycle or satisfies a required profile. A consumer that can prove
561
+ complete coverage may use schema version 2 with `finalProfiles`; `--remaining` then
562
+ records the selected units as `impact-assurance` evidence. Each opted profile needs
563
+ an explicit global unit with the universal `**` pattern for cross-cutting reach, and
564
+ unresolved final coverage
565
+ blocks rather than falling back silently. Explicit `--profile` remains the full
566
+ refresh. Consumers that do not adopt the opt-in retain the existing final boundary;
567
+ the affected command does not guess a policy for them.
568
+
481
569
  Assign an independent reviewer and submit its report:
482
570
 
483
571
  processctl change review start \
@@ -564,9 +652,11 @@ At any point:
564
652
  ## Release to consumer PR
565
653
 
566
654
  Each release includes [reviewed release contents](RELEASE_NOTES.md) generated from
567
- the canonical manifest: shipped features/fixes, their source issues or changes, and
568
- upgrade guidance. The release PR reviews this file; the GitHub Release publishes the
569
- same contents. See [the release procedure](RELEASING.md) for authoring and checks.
655
+ the canonical manifest: one record per shipped feature/fix, its source issue, the
656
+ problem and affected paths, the application action, compatibility impact, important
657
+ notes, and upgrade guidance. The release PR reviews this file; the GitHub Release
658
+ publishes the same contents. See [the release procedure](RELEASING.md) for authoring
659
+ and checks.
570
660
 
571
661
  This producer's release identity inputs and text assets declared by
572
662
  `tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
@@ -0,0 +1,49 @@
1
+ # Engineering Process v2.6.0
2
+
3
+ Changes since v2.5.0.
4
+
5
+ ## Features
6
+
7
+ - **Select and assure verification units from explicit change impact.** ([#205](https://github.com/phuongnse/engineering-process/issues/205))
8
+ - **Problem:** A consumer could only choose a whole profile or reuse a whole-profile result, so a small change lacked a standard way to run related verification units. When impact was unclear, an automatic full-suite fallback could hide that dependency reach had not been resolved, while explicit coverage could not yet satisfy a final required profile.
9
+ - **What changed:** Add versioned consumer-owned impactProfiles, deterministic changed-path resolution, change explain --impact, and change verify --affected. Every path must resolve to matching units or the command stops with an explicit re-analysis action; an explicit global pattern is the only broad selection rule. Add a schema-version 2 finalProfiles opt-in so complete explicit coverage can run selected units as final impact-assurance evidence. Policy or candidate changes, missing coverage and unresolved paths fail closed; schema-version 1 policies remain feedback-only.
10
+ - **Where:** `schemas/project.schema.json`, `schemas/verification-impact-selection.schema.json`, `schemas/verification-selection.schema.json`, `schemas/run.schema.json`, `schemas/receipt.schema.json`, `engineering_process/impact.py`, `engineering_process/repository.py`, `engineering_process/lifecycle.py`, `engineering_process/cli.py`, `engineering_process/project.py`, `process_assets/skills/change-verify/SKILL.md`, `process_assets/skills/change-plan/SKILL.md`, `SELF_HOSTING.md`, `README.md`, `tests/test_contracts.py`, `tests/test_impact.py`, `tests/test_lifecycle.py`, `tests/test_skills.py`
11
+ - **Apply:** Adopt the package release, add impactProfiles to the consumer project policy, map every changed path to one or more independently executable commands, and run change explain --impact before change verify --affected. Opt selected required profiles into schema version 2 finalProfiles only after reviewing complete path coverage and global cross-cutting units; then run change verify --remaining. Resolve every unresolved path by inspecting the diff/dependency reach and updating the consumer-owned mapping or recording an owner decision.
12
+ - **Compatibility:** Backward-compatible opt-in. Existing project files, explicit profile verification, whole-profile reuse, required lifecycle profiles, and final review/release evidence remain supported. Consumers without impactProfiles receive a clear unavailable result, and schema-version 1 policies remain feedback-only.
13
+ - **Notes:** Path patterns are a versioned consumer policy, not a process guess. Overlapping units all run; a global unit may subsume narrower units only when its declared paths include the explicit universal \*\* pattern, while narrower patterns never cover unrelated paths. Affected feedback does not advance the lifecycle or prove final assurance. Final assurance records execution mode and selection identity in the existing lifecycle evidence; it does not create a second lifecycle or infer coverage from filenames, labels, commands, or diagnostics.
14
+
15
+ ## Fixes
16
+
17
+ - **Render release records as readable Markdown without gratuitous escapes.** ([#171](https://github.com/phuongnse/engineering-process/issues/171))
18
+ - **Problem:** Generated release notes escaped every punctuation mark and padded ordinary code references, making otherwise complete issue-level records difficult to read.
19
+ - **What changed:** Escape only Markdown and HTML-sensitive metadata syntax, keep ordinary punctuation readable, and render simple owned references as normal inline code while retaining safe fencing for embedded backticks.
20
+ - **Where:** `engineering_process/release_notes.py`, `tests/test_release.py`, `release-changes/README.md`, `RELEASING.md`, `ARTIFACT_STANDARDS.md`, `RELEASE_NOTES.md`
21
+ - **Apply:** Use the normal prepare-release workflow and review the generated RELEASE\_NOTES.md against release.json before publication.
22
+ - **Compatibility:** No breaking change. Schema-v5 manifests and legacy release-note data remain readable; the selected release-notes@1 structure and published v2.5.0 body are not rewritten.
23
+ - **Notes:** This correction applies to subsequently generated release bodies. Published release artifacts remain immutable and must be corrected only through the owner's release policy.
24
+ - **Batch reusable verification evidence across lifecycle stages.** ([#198](https://github.com/phuongnse/engineering-process/issues/198))
25
+ - **Problem:** Continuation verification could make repeated reuse decisions and authority calculations one profile at a time even when several reports already covered the same unchanged candidate.
26
+ - **What changed:** Batch valid whole-profile reuse decisions into one canonical state write and reuse one process-authority digest across stable evidence comparisons. Preserve the selected runtime path spelling and virtual-environment symlinks across POSIX and Windows while retaining fresh candidate snapshots, runtime identity checks, explicit refresh semantics, and correction-cycle invalidation.
27
+ - **Where:** `engineering_process/evidence.py`, `engineering_process/lifecycle.py`, `engineering_process/pr_description.py`, `process_assets/skills/change-verify/SKILL.md`, `README.md`, `tests/test_lifecycle.py`, `tests/test_skills.py`
28
+ - **Apply:** Use change verify --remaining for continuation work. The process reuses only exact valid reports and records reuse without relaunching their child commands.
29
+ - **Compatibility:** No breaking change. Explicit profile verification remains an unconditional refresh; stale, legacy, or mismatched evidence reruns; final review and finish still require exact-snapshot evidence.
30
+ - **Notes:** The optimization is operation-scoped and does not add a persistent success cache or reuse evidence across a changed candidate, mutation boundary, or incompatible runtime. Runtime path normalization no longer dereferences the selected executable before constructing the bounded child PATH.
31
+ - **Make the full verification suite deterministic and expose bounded timing diagnostics.** ([#201](https://github.com/phuongnse/engineering-process/issues/201))
32
+ - **Problem:** The repository-owned full verification suite took about six minutes on Windows and review-context tests incorrectly reported stale evidence when run after the complete suite, while local output did not identify the slowest tests.
33
+ - **What changed:** Give review-context tests an isolated fixture owner, seed their verified states once per test class, use the same bounded child environment as the verification boundary, and report aggregate, slow-test, fixture-setup, and profile timing without changing profile exit semantics or deleting behavioral coverage. Lifecycle fixtures clone an immutable Git snapshot instead of copying a live .git directory, avoiding races with Git maintenance locks.
34
+ - **Where:** `tests/test_review_contexts.py`, `tests/test_lifecycle.py`, `verification/run_test_suite.py`
35
+ - **Apply:** Run the normal development and review profiles; use the bounded suite timing and slow-test summary to diagnose future cost, and do not replace final verification with a focused subset.
36
+ - **Compatibility:** No breaking change. Existing profile commands, required checks, skip behavior, process/Git/filesystem boundaries, and stored evidence semantics remain supported.
37
+ - **Notes:** The reviewed change evidence records repeated comparable self-consumer measurements and the bounded improvement method; it does not establish a universal latency target. Shared mutable checkouts and blanket mocks remain out of scope.
38
+
39
+ ## Upgrade and compatibility
40
+
41
+ Merge the complete hash-locked package/adoption PR, update the local and CI environments to the selected version, and start a fresh agent session.
42
+
43
+ Consumer CI, naming conventions and branch-protection settings remain consumer-owned; adoption does not configure them automatically.
44
+
45
+ No breaking changes are included. Read each change's Apply, Compatibility and Notes entry before adopting.
46
+
47
+ See [versioning and compatibility](https://github.com/phuongnse/engineering-process/blob/v2.6.0/VERSIONING.md) and [adoption guidance](https://github.com/phuongnse/engineering-process/blob/v2.6.0/SELF_HOSTING.md).
48
+
49
+ [Full change comparison](https://github.com/phuongnse/engineering-process/compare/v2.5.0...v2.6.0)
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "2.4.0"
3
+ VERSION = "2.6.0"
@@ -38,10 +38,14 @@ from .lifecycle import (
38
38
  start_change,
39
39
  start_review,
40
40
  submit_review,
41
+ resolve_impact_work,
42
+ resolve_verification_work,
43
+ verify_affected,
44
+ verify_remaining,
41
45
  verify_change,
42
46
  )
43
47
  from .issue import render_issue
44
- from .project import load_project, readiness_summary
48
+ from .project import impact_profiles, load_project, readiness_summary
45
49
  from .production_engineering import (
46
50
  validate_plan_assessments,
47
51
  validate_review_assessments,
@@ -53,7 +57,13 @@ from .publication_compat import (
53
57
  validate_range,
54
58
  )
55
59
  from .release import validate_release
56
- from .pr_description import body_issues, render_description, render_renovate_preset, render_template
60
+ from .pr_description import (
61
+ body_issues,
62
+ build_pr_description_data,
63
+ render_description,
64
+ render_renovate_preset,
65
+ render_template,
66
+ )
57
67
  from .release_notes import render_notes
58
68
  from .repository import repository_snapshot, same_checkpoint
59
69
  from .skills import validate_skills
@@ -87,9 +97,16 @@ def _result(command: str, status: str = "passed", **details: Any) -> dict[str, A
87
97
  return {"command": command, "status": status, **details}
88
98
 
89
99
 
90
- def _state_result(command: str, state: dict[str, Any], **details: Any) -> dict[str, Any]:
100
+ def _state_result(
101
+ command: str,
102
+ state: dict[str, Any],
103
+ *,
104
+ status: str = "passed",
105
+ **details: Any,
106
+ ) -> dict[str, Any]:
91
107
  return _result(
92
108
  command,
109
+ status=status,
93
110
  changeId=state["changeId"],
94
111
  phase=state["phase"],
95
112
  cycle=state["cycle"],
@@ -120,6 +137,7 @@ def command_project_validate(args: argparse.Namespace) -> Result:
120
137
  "project validate",
121
138
  project=project["project"],
122
139
  profiles=sorted(project["profiles"]),
140
+ impactProfiles=sorted(impact_profiles(project)),
123
141
  requiredProfiles=project["lifecycle"]["requiredProfiles"],
124
142
  readiness=readiness_summary(project),
125
143
  ), 0
@@ -311,7 +329,84 @@ def command_change_implement(args: argparse.Namespace) -> Result:
311
329
 
312
330
  def command_change_verify(args: argparse.Namespace) -> Result:
313
331
  process_root = _process_root(args)
332
+ if getattr(args, "affected", False):
333
+ selected_profiles = tuple(getattr(args, "affected_profile", []) or []) or None
334
+ state, selection, executions = verify_affected(
335
+ args.project_root,
336
+ process_root,
337
+ {},
338
+ args.change_id,
339
+ profiles=selected_profiles,
340
+ )
341
+ execution_status = executions[0]["status"] if executions else None
342
+ status = (
343
+ "passed"
344
+ if selection["status"] == "ready" and execution_status == "passed"
345
+ else selection["status"]
346
+ if selection["status"] != "ready"
347
+ else "failed"
348
+ )
349
+ return _state_result(
350
+ "change verify",
351
+ state,
352
+ status=status,
353
+ selection=selection,
354
+ execution="affected",
355
+ evidence="feedback-only; does not satisfy a required full profile",
356
+ executions=executions,
357
+ ), (0 if status == "passed" else 1)
358
+ if getattr(args, "affected_profile", []):
359
+ raise ProcessError("--affected-profile requires --affected")
314
360
  project = load_project(args.project_root, process_root)
361
+ if args.remaining:
362
+ state, selection = verify_remaining(
363
+ args.project_root, process_root, project, args.change_id
364
+ )
365
+ executions = []
366
+ failures = []
367
+ for profile in selection["executeProfiles"]:
368
+ report = state["verification"].get(profile)
369
+ if report is None:
370
+ failures.append(profile)
371
+ continue
372
+ if report["status"] != "passed":
373
+ failures.append(profile)
374
+ check_duration = sum(check["durationMs"] for check in report["checks"])
375
+ executions.append(
376
+ {
377
+ "profile": profile,
378
+ "status": report["status"],
379
+ "executionMode": report.get("executionMode", "full"),
380
+ "launchCount": len(report["checks"]),
381
+ "durationMs": report.get("durationMs", check_duration),
382
+ "checkDurationMs": check_duration,
383
+ "processOverheadMs": max(
384
+ 0, report.get("durationMs", check_duration) - check_duration
385
+ ),
386
+ }
387
+ )
388
+ contract = state.get("contract", {})
389
+ required_profiles = (
390
+ contract.get("document", {}).get("requiredProfiles", [])
391
+ if isinstance(contract, dict)
392
+ else []
393
+ )
394
+ verification = state.get("verification", {})
395
+ required_profiles_passed = bool(required_profiles) and all(
396
+ isinstance(verification.get(profile), dict)
397
+ and verification[profile].get("status") == "passed"
398
+ for profile in required_profiles
399
+ )
400
+ complete = state["phase"] == "verified" and required_profiles_passed
401
+ return _state_result(
402
+ "change verify",
403
+ state,
404
+ status="passed" if complete and not failures else "failed",
405
+ selection=selection,
406
+ execution="remaining",
407
+ executions=executions,
408
+ failures=failures,
409
+ ), (0 if complete and not failures else 1)
315
410
  state, report = verify_change(
316
411
  args.project_root, process_root, project, args.change_id, args.profile
317
412
  )
@@ -321,6 +416,27 @@ def command_change_verify(args: argparse.Namespace) -> Result:
321
416
  ), code
322
417
 
323
418
 
419
+ def command_change_explain(args: argparse.Namespace) -> Result:
420
+ process_root = _process_root(args)
421
+ if getattr(args, "impact", False):
422
+ profiles = (args.profile,) if args.profile else None
423
+ selection = resolve_impact_work(
424
+ args.project_root,
425
+ process_root,
426
+ {},
427
+ args.change_id,
428
+ profiles=profiles,
429
+ )
430
+ return _result("change explain", selection=selection), 0
431
+ if getattr(args, "profile", None):
432
+ raise ProcessError("--profile for change explain requires --impact")
433
+ project = load_project(args.project_root, process_root)
434
+ selection = resolve_verification_work(
435
+ args.project_root, process_root, project, args.change_id
436
+ )
437
+ return _result("change explain", selection=selection), 0
438
+
439
+
324
440
  def command_change_review_start(args: argparse.Namespace) -> Result:
325
441
  process_root = _process_root(args)
326
442
  state = start_review(
@@ -434,13 +550,37 @@ def command_artifact(args: argparse.Namespace) -> Result:
434
550
  f"{standard.document['adapter']} state must be "
435
551
  + " or ".join(sorted(allowed_states))
436
552
  )
437
- data = load_and_validate(args.data_file, standard.document["adapter"] + "-data", schema_root=schemas_root(process_root)) if args.data_file is not None else None
553
+ if getattr(args, "change_id", None) is not None and standard.document["adapter"] == "pr-description":
554
+ overrides = (
555
+ load_and_validate(
556
+ args.data_file,
557
+ "pr-description-data",
558
+ schema_root=schemas_root(process_root),
559
+ )
560
+ if args.data_file is not None
561
+ else None
562
+ )
563
+ data = build_pr_description_data(
564
+ args.project_root,
565
+ process_root,
566
+ args.change_id,
567
+ overrides=overrides,
568
+ standard=standard,
569
+ )
570
+ elif args.data_file is not None:
571
+ data = load_and_validate(
572
+ args.data_file,
573
+ standard.document["adapter"] + "-data",
574
+ schema_root=schemas_root(process_root),
575
+ )
576
+ else:
577
+ data = None
438
578
  if data is not None:
439
579
  renderer = {"pr-description": render_description, "release-notes": render_notes, "automation-name": render_name, "issue": render_issue}[standard.document["adapter"]]
440
580
  payload = renderer(standard, data, state=state, process_root=process_root).encode("utf-8")
441
581
  details["dataDigest"] = digest_json(data)
442
582
  elif operation == "render" or standard.document["adapter"] != "pr-description":
443
- raise ProcessError("this artifact operation requires --data-file")
583
+ raise ProcessError("this artifact operation requires --data-file or --change-id")
444
584
  if operation == "validate":
445
585
  actual = read_document(args.body_file)
446
586
  if data is None:
@@ -460,6 +600,38 @@ def command_artifact(args: argparse.Namespace) -> Result:
460
600
  return _result(f"artifact {operation}", status, **details, issues=issues), (1 if issues else 0)
461
601
 
462
602
 
603
+ def command_artifact_prepare_pr_data(args: argparse.Namespace) -> Result:
604
+ process_root = _process_root(args)
605
+ standard = resolve_standard(args.project_root, process_root, "pull-request")
606
+ overrides = (
607
+ load_and_validate(
608
+ args.data_file,
609
+ "pr-description-data",
610
+ schema_root=schemas_root(process_root),
611
+ )
612
+ if args.data_file is not None
613
+ else None
614
+ )
615
+ data = build_pr_description_data(
616
+ args.project_root,
617
+ process_root,
618
+ args.change_id,
619
+ overrides=overrides,
620
+ standard=standard,
621
+ )
622
+ payload = formatted_json_bytes(data)
623
+ details: dict[str, Any] = {
624
+ "changeId": args.change_id,
625
+ "dataDigest": digest_json(data),
626
+ }
627
+ if args.output is not None:
628
+ args.output.write_bytes(payload)
629
+ details["output"] = str(args.output)
630
+ else:
631
+ details["data"] = data
632
+ return _result("artifact prepare-pr-data", "passed", **details), 0
633
+
634
+
463
635
  def _add_common(parser: argparse.ArgumentParser, *, project: bool = True) -> None:
464
636
  if project:
465
637
  parser.add_argument("--project-root", type=_root, default=Path.cwd())
@@ -536,7 +708,21 @@ def build_parser() -> argparse.ArgumentParser:
536
708
  change_implement.add_argument("--change-id", required=True)
537
709
  change_verify = _leaf(change_commands, "verify", command_change_verify)
538
710
  change_verify.add_argument("--change-id", required=True)
539
- change_verify.add_argument("--profile", required=True)
711
+ verify_scope = change_verify.add_mutually_exclusive_group(required=True)
712
+ verify_scope.add_argument("--profile")
713
+ verify_scope.add_argument("--remaining", action="store_true")
714
+ verify_scope.add_argument("--affected", action="store_true")
715
+ change_verify.add_argument(
716
+ "--affected-profile",
717
+ action="append",
718
+ default=[],
719
+ help="limit --affected to one or more accepted profiles",
720
+ )
721
+
722
+ change_explain = _leaf(change_commands, "explain", command_change_explain)
723
+ change_explain.add_argument("--change-id", required=True)
724
+ change_explain.add_argument("--impact", action="store_true")
725
+ change_explain.add_argument("--profile")
540
726
 
541
727
  review = change_commands.add_parser("review")
542
728
  review_commands = review.add_subparsers(dest="review_command", required=True)
@@ -564,12 +750,17 @@ def build_parser() -> argparse.ArgumentParser:
564
750
 
565
751
  artifact = commands.add_parser("artifact", help="Generate and verify consumer-selected artifact standards")
566
752
  artifact_commands = artifact.add_subparsers(dest="artifact_command", required=True)
753
+ prepare_pr_data = _leaf(artifact_commands, "prepare-pr-data", command_artifact_prepare_pr_data)
754
+ prepare_pr_data.add_argument("--change-id", required=True)
755
+ prepare_pr_data.add_argument("--data-file", type=Path)
756
+ prepare_pr_data.add_argument("--output", type=Path)
567
757
  for name in ("show", "template", "renovate-preset", "render", "validate"):
568
758
  leaf = _leaf(artifact_commands, name, command_artifact)
569
759
  leaf.add_argument("--artifact", required=True)
570
760
  if name in {"render", "validate"}:
571
761
  leaf.add_argument("--state", choices=("draft", "ready", "open", "closed"))
572
- leaf.add_argument("--data-file", type=Path, required=name == "render")
762
+ leaf.add_argument("--change-id")
763
+ leaf.add_argument("--data-file", type=Path, required=False)
573
764
  if name == "validate":
574
765
  leaf.add_argument("--body-file", type=Path, required=True)
575
766
  else: