engineering-process 2.1.0__tar.gz → 2.3.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 (100) hide show
  1. {engineering_process-2.1.0 → engineering_process-2.3.0}/MANIFEST.in +1 -0
  2. {engineering_process-2.1.0/engineering_process.egg-info → engineering_process-2.3.0}/PKG-INFO +57 -8
  3. {engineering_process-2.1.0 → engineering_process-2.3.0}/README.md +56 -7
  4. engineering_process-2.3.0/RELEASE_NOTES.md +23 -0
  5. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/__init__.py +1 -1
  6. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_posix.py +10 -4
  7. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_windows.py +2 -1
  8. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/adoption.py +10 -3
  9. engineering_process-2.3.0/engineering_process/artifact_standards.py +155 -0
  10. engineering_process-2.3.0/engineering_process/automation_name.py +31 -0
  11. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/cli.py +77 -1
  12. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/commands.py +6 -0
  13. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/contracts.py +7 -0
  14. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/distribution.py +5 -2
  15. engineering_process-2.3.0/engineering_process/issue.py +112 -0
  16. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/lifecycle.py +36 -0
  17. engineering_process-2.1.0/engineering_process/publication_compat.py → engineering_process-2.3.0/engineering_process/pr_description.py +99 -138
  18. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/project.py +5 -0
  19. engineering_process-2.3.0/engineering_process/publication_compat.py +58 -0
  20. engineering_process-2.3.0/engineering_process/release_notes.py +76 -0
  21. engineering_process-2.3.0/engineering_process/source_publication.py +133 -0
  22. {engineering_process-2.1.0 → engineering_process-2.3.0/engineering_process.egg-info}/PKG-INFO +57 -8
  23. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/SOURCES.txt +18 -0
  24. engineering_process-2.3.0/process_assets/skills/change-complete/SKILL.md +79 -0
  25. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-review/SKILL.md +9 -2
  26. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-start/SKILL.md +10 -0
  27. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-verify/SKILL.md +17 -0
  28. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/deliver-change/SKILL.md +26 -0
  29. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/production-engineering/SKILL.md +7 -0
  30. engineering_process-2.3.0/process_assets/standards/automation-name.v1.json +16 -0
  31. engineering_process-2.3.0/process_assets/standards/issue.v1.json +78 -0
  32. engineering_process-2.3.0/process_assets/standards/pull-request.v1.json +129 -0
  33. engineering_process-2.3.0/process_assets/standards/release-notes.v1.json +34 -0
  34. {engineering_process-2.1.0 → engineering_process-2.3.0}/pyproject.toml +14 -1
  35. engineering_process-2.3.0/schemas/artifact-selection.schema.json +67 -0
  36. engineering_process-2.3.0/schemas/artifact-standard.schema.json +407 -0
  37. engineering_process-2.3.0/schemas/automation-name-data.schema.json +35 -0
  38. engineering_process-2.3.0/schemas/issue-data.schema.json +44 -0
  39. engineering_process-2.3.0/schemas/pr-description-data.schema.json +63 -0
  40. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/project.schema.json +8 -0
  41. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/receipt.schema.json +20 -2
  42. engineering_process-2.3.0/schemas/release-notes-data.schema.json +103 -0
  43. engineering_process-2.3.0/schemas/renovate-preset.schema.json +30 -0
  44. engineering_process-2.3.0/templates/PULL_REQUEST_TEMPLATE.md +36 -0
  45. {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/renovate.json +1 -1
  46. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_adoption.py +69 -0
  47. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_architecture.py +7 -1
  48. engineering_process-2.3.0/tests/test_artifact_standards.py +413 -0
  49. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_cli.py +1 -0
  50. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_commands.py +100 -2
  51. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_contracts.py +81 -0
  52. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_distribution.py +13 -1
  53. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_lifecycle.py +171 -1
  54. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_publication_compat.py +101 -0
  55. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_supervisor_posix.py +35 -0
  56. engineering_process-2.1.0/RELEASE_NOTES.md +0 -23
  57. engineering_process-2.1.0/process_assets/skills/change-complete/SKILL.md +0 -39
  58. engineering_process-2.1.0/templates/PULL_REQUEST_TEMPLATE.md +0 -39
  59. {engineering_process-2.1.0 → engineering_process-2.3.0}/LICENSE +0 -0
  60. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/__main__.py +0 -0
  61. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_contract.py +0 -0
  62. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_windows_job.py +0 -0
  63. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/helper_launch.py +0 -0
  64. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/production_engineering.py +0 -0
  65. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/release.py +0 -0
  66. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/repository.py +0 -0
  67. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/requirements-dev.txt +0 -0
  68. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/requirements-runtime.txt +0 -0
  69. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/skills.py +0 -0
  70. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/supervision.py +0 -0
  71. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/dependency_links.txt +0 -0
  72. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/entry_points.txt +0 -0
  73. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/requires.txt +0 -0
  74. {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/top_level.txt +0 -0
  75. {engineering_process-2.1.0 → engineering_process-2.3.0}/process-graph.json +0 -0
  76. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-implement/SKILL.md +0 -0
  77. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-plan/SKILL.md +0 -0
  78. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/process-improve/SKILL.md +0 -0
  79. {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/production-engineering/invariants.json +0 -0
  80. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/change.schema.json +0 -0
  81. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/plan.schema.json +0 -0
  82. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/process-graph.schema.json +0 -0
  83. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/process-lock.schema.json +0 -0
  84. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/production-engineering.schema.json +0 -0
  85. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/project-legacy.schema.json +0 -0
  86. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/release-change.schema.json +0 -0
  87. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/release.schema.json +0 -0
  88. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/review.schema.json +0 -0
  89. {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/run.schema.json +0 -0
  90. {engineering_process-2.1.0 → engineering_process-2.3.0}/setup.cfg +0 -0
  91. {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/AGENTS.process.md +0 -0
  92. {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/adopt-process-windows-job.py +0 -0
  93. {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/adopt-process.py +0 -0
  94. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_automation.py +0 -0
  95. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_production_engineering.py +0 -0
  96. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_publication_workflow.py +0 -0
  97. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_pypi_cache_horizon.py +0 -0
  98. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_release.py +0 -0
  99. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_repository.py +0 -0
  100. {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_skills.py +0 -0
@@ -1,5 +1,6 @@
1
1
  include process-graph.json
2
2
  include RELEASE_NOTES.md
3
3
  recursive-include process_assets/skills SKILL.md *.json
4
+ recursive-include process_assets/standards *.json
4
5
  recursive-include schemas *.json
5
6
  recursive-include templates *.md *.py *.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 2.1.0
3
+ Version: 2.3.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
@@ -73,10 +73,31 @@ The coordinator hands review to an actual reviewer. A new agent review starts in
73
73
  fresh context with the accepted source artifacts and no suggested verdict; the same
74
74
  reviewer continues corrections. The reviewer inspects the change and authors its
75
75
  own report. The runner's existing task/session interaction and returned result make
76
- that work inspectable. Sharing a model, provider, or account is allowed; this is a
76
+ that work inspectable. Sharing a provider or account is allowed; this is a
77
77
  workflow for independent judgment, not authenticated identity or merge enforcement.
78
78
  See [change-review](process_assets/skills/change-review/SKILL.md) for the handoff.
79
79
 
80
+ Every delegated agent and reviewer follows the active user-selected model and
81
+ reasoning effort under [deliver-change's settings rule](process_assets/skills/deliver-change/SKILL.md#preserve-agent-execution-settings).
82
+ It covers fresh agents and resumed sessions, requires native runtime confirmation,
83
+ and permits no autonomous upgrade or downgrade. The portable guidance does not make
84
+ processctl a provider runtime or a model-quality evaluator.
85
+
86
+ Consumers using the packaged publication compatibility policy can enable
87
+ `lifecycle.publication.required: true` in `.process/project.json` after adopting a
88
+ version that supports it. Start then rejects an invalid branch before creating a
89
+ run. Finish checks the current branch, head subject, and pinned comparison-base-to-head
90
+ range, and writes publication metadata in a version 2 receipt. Version 1 receipts and
91
+ consumers without the opt-in remain supported. Missing pinned bases and mutations
92
+ during the check fail completion.
93
+
94
+ PR readiness additionally requires the selected ready-state body/title and exact
95
+ head/range checks. Follow the [completion preflight](process_assets/skills/change-complete/SKILL.md)
96
+ and repeat those checks in required CI. This repository's existing
97
+ `verification/verify_publication.py --pull-request` uses the actual PR metadata and
98
+ selected consumer standard; local lifecycle completion alone does not establish
99
+ external PR readiness. Integration-branch pushes remain consumer-owned operations.
100
+
80
101
  Runtime architecture is enforced by semantic fitness functions, not module or source-
81
102
  line quotas. Every module has an explicit dependency layer, imports point toward lower
82
103
  layers, the internal graph remains acyclic, and lifecycle.py alone owns state
@@ -122,6 +143,20 @@ Commands are argument arrays, never shell strings. Each command has a finite tim
122
143
  Output has a hard aggregate budget; evidence stores byte counts and hashes, never raw
123
144
  stdout or stderr that could contain secrets.
124
145
 
146
+ For a fresh checkout of this repository, create a virtual environment with a supported
147
+ Python and install `engineering_process/requirements-runtime.txt`,
148
+ `engineering_process/requirements-dev.txt`, and
149
+ `engineering_process/requirements-build.txt` through that interpreter. Use
150
+ `.venv/Scripts/python.exe` on Windows or `.venv/bin/python` on POSIX to invoke the
151
+ source CLI and verification scripts. Other consumers own their runtime paths and
152
+ setup commands; installed consumers can use their environment's `processctl` entry
153
+ point without a source checkout.
154
+
155
+ The bounded runner prepends the invoking Python executable's directory to child
156
+ `PATH`. Bare commands therefore search that environment before the inherited path,
157
+ while declared argument arrays, explicit executable paths, secret filtering and
158
+ remaining path entries are preserved. This alignment does not install dependencies.
159
+
125
160
  A failed profile also reports a safe `diagnostic` descriptor containing only its
126
161
  validated profile identifier, check identifier, canonical one-based profile position,
127
162
  and a fixed `processctl` argument array. The position remains unambiguous even when a
@@ -270,6 +305,13 @@ The transaction writes only managed surfaces:
270
305
 
271
306
  It removes obsolete skills named by the previous process lock and preserves
272
307
  consumer-owned skills and instructions. Applying the same version twice is a no-op.
308
+ Consumer-owned `.process/standards.json` selections and override definitions are also
309
+ preserved; the managed PR template follows the effective supported standard. See
310
+ [consumer document standards](ARTIFACT_STANDARDS.md).
311
+ [Issue records](ARTIFACT_STANDARDS.md#issues) use the same default-and-override
312
+ mechanism for open requests and closed evidence while tracker actions remain consumer-owned.
313
+ [Automation naming](ARTIFACT_STANDARDS.md#automation-names) uses the same versioned
314
+ selection and override mechanism; consumers apply it in their bootstrap and provider checks.
273
315
  The legacy managed runner can enter 1.0 directly, so consumers do not need a chain of
274
316
  per-version migration documents. The same transaction deletes the retired migration
275
317
  directory and standing automation policy; the Windows Job Object helper remains a
@@ -482,7 +524,7 @@ supported review schemas; ordinary legacy non-blocking observations remain reada
482
524
 
483
525
  ### Public pull-request evidence
484
526
 
485
- The managed pull-request template keeps public assurance separate from local
527
+ The default pull-request standard keeps public assurance separate from local
486
528
  lifecycle identity. Its five sections and labeled fields are ordered and stable:
487
529
  outcome and scope; source, risk, compatibility, and stack; profiles, snapshot, and
488
530
  completion receipt; verdict, cycles, blocking status, and non-blocking dispositions;
@@ -492,10 +534,15 @@ handle, or local `.process/runs` path. Those values remain in lifecycle state, w
492
534
  they enforce self-review rejection but do not pretend to be provider-authenticated
493
535
  review identities.
494
536
 
495
- `processctl publication validate-pr` checks that public contract deterministically.
537
+ The template and validator derive this contract from the same versioned definition.
538
+ Consumers can select a supported override through `.process/standards.json`; see
539
+ [generation, verification and custom-format boundaries](ARTIFACT_STANDARDS.md).
540
+ `processctl publication validate-pr` checks that selected contract deterministically.
496
541
  It rejects missing, repeated, misplaced, hidden, unordered, or unsupported visible
497
542
  structure. Completion checkboxes belong only to the Completion gate section. Ready
498
- pull requests must have every checkbox checked; drafts may retain unchecked work.
543
+ pull requests must have every checkbox checked and no unresolved default placeholder
544
+ values; drafts may retain pending fields and unchecked work. The author/coordinator
545
+ replaces them with actual evidence before ready/merge; the reviewer supplies the verdict.
499
546
  One trailing `Refs ISSUE.` line remains optional. A ready, contract-identified final
500
547
  consumer adoption may instead use `Closes ISSUE, closes OWNER/REPOSITORY#NUMBER.` with
501
548
  the complete keyword/reference syntax repeated for every issue; drafts cannot close
@@ -555,17 +602,19 @@ authorizes that merge.
555
602
  This repository opts in through .github/renovate.json, so it receives the same
556
603
  adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
557
604
 
558
- The optional [Renovate preset](templates/renovate.json) is generated from the
559
- canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
605
+ The optional [Renovate preset](templates/renovate.json) and public template are generated
606
+ from the packaged PR standard. It supplies only `prHeader` and `prBodyTemplate`;
560
607
  dependency selection, supported platforms, schedules, major-update approval,
561
608
  commands, draft policy, and merge authority remain consumer-owned. Regenerate it
562
609
  with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
610
+ Consumers overriding that standard can generate a matching preset with
611
+ `processctl artifact renovate-preset`; wire it through their own Renovate configuration.
563
612
 
564
613
  Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
565
614
  to their existing `extends` array, replacing `COMMIT_SHA` with the full source
566
615
  commit of a verified release that contains the preset. Remove obsolete inline
567
616
  `prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
568
- The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
617
+ The default preset targets the draft grammar used by 1.2.4 and this distribution;
569
618
  it does not claim compatibility with earlier publication contracts. A future
570
619
  grammar change must preserve this adapter or ship an explicit consumer migration.
571
620
 
@@ -49,10 +49,31 @@ The coordinator hands review to an actual reviewer. A new agent review starts in
49
49
  fresh context with the accepted source artifacts and no suggested verdict; the same
50
50
  reviewer continues corrections. The reviewer inspects the change and authors its
51
51
  own report. The runner's existing task/session interaction and returned result make
52
- that work inspectable. Sharing a model, provider, or account is allowed; this is a
52
+ that work inspectable. Sharing a provider or account is allowed; this is a
53
53
  workflow for independent judgment, not authenticated identity or merge enforcement.
54
54
  See [change-review](process_assets/skills/change-review/SKILL.md) for the handoff.
55
55
 
56
+ Every delegated agent and reviewer follows the active user-selected model and
57
+ reasoning effort under [deliver-change's settings rule](process_assets/skills/deliver-change/SKILL.md#preserve-agent-execution-settings).
58
+ It covers fresh agents and resumed sessions, requires native runtime confirmation,
59
+ and permits no autonomous upgrade or downgrade. The portable guidance does not make
60
+ processctl a provider runtime or a model-quality evaluator.
61
+
62
+ Consumers using the packaged publication compatibility policy can enable
63
+ `lifecycle.publication.required: true` in `.process/project.json` after adopting a
64
+ version that supports it. Start then rejects an invalid branch before creating a
65
+ run. Finish checks the current branch, head subject, and pinned comparison-base-to-head
66
+ range, and writes publication metadata in a version 2 receipt. Version 1 receipts and
67
+ consumers without the opt-in remain supported. Missing pinned bases and mutations
68
+ during the check fail completion.
69
+
70
+ PR readiness additionally requires the selected ready-state body/title and exact
71
+ head/range checks. Follow the [completion preflight](process_assets/skills/change-complete/SKILL.md)
72
+ and repeat those checks in required CI. This repository's existing
73
+ `verification/verify_publication.py --pull-request` uses the actual PR metadata and
74
+ selected consumer standard; local lifecycle completion alone does not establish
75
+ external PR readiness. Integration-branch pushes remain consumer-owned operations.
76
+
56
77
  Runtime architecture is enforced by semantic fitness functions, not module or source-
57
78
  line quotas. Every module has an explicit dependency layer, imports point toward lower
58
79
  layers, the internal graph remains acyclic, and lifecycle.py alone owns state
@@ -98,6 +119,20 @@ Commands are argument arrays, never shell strings. Each command has a finite tim
98
119
  Output has a hard aggregate budget; evidence stores byte counts and hashes, never raw
99
120
  stdout or stderr that could contain secrets.
100
121
 
122
+ For a fresh checkout of this repository, create a virtual environment with a supported
123
+ Python and install `engineering_process/requirements-runtime.txt`,
124
+ `engineering_process/requirements-dev.txt`, and
125
+ `engineering_process/requirements-build.txt` through that interpreter. Use
126
+ `.venv/Scripts/python.exe` on Windows or `.venv/bin/python` on POSIX to invoke the
127
+ source CLI and verification scripts. Other consumers own their runtime paths and
128
+ setup commands; installed consumers can use their environment's `processctl` entry
129
+ point without a source checkout.
130
+
131
+ The bounded runner prepends the invoking Python executable's directory to child
132
+ `PATH`. Bare commands therefore search that environment before the inherited path,
133
+ while declared argument arrays, explicit executable paths, secret filtering and
134
+ remaining path entries are preserved. This alignment does not install dependencies.
135
+
101
136
  A failed profile also reports a safe `diagnostic` descriptor containing only its
102
137
  validated profile identifier, check identifier, canonical one-based profile position,
103
138
  and a fixed `processctl` argument array. The position remains unambiguous even when a
@@ -246,6 +281,13 @@ The transaction writes only managed surfaces:
246
281
 
247
282
  It removes obsolete skills named by the previous process lock and preserves
248
283
  consumer-owned skills and instructions. Applying the same version twice is a no-op.
284
+ Consumer-owned `.process/standards.json` selections and override definitions are also
285
+ preserved; the managed PR template follows the effective supported standard. See
286
+ [consumer document standards](ARTIFACT_STANDARDS.md).
287
+ [Issue records](ARTIFACT_STANDARDS.md#issues) use the same default-and-override
288
+ mechanism for open requests and closed evidence while tracker actions remain consumer-owned.
289
+ [Automation naming](ARTIFACT_STANDARDS.md#automation-names) uses the same versioned
290
+ selection and override mechanism; consumers apply it in their bootstrap and provider checks.
249
291
  The legacy managed runner can enter 1.0 directly, so consumers do not need a chain of
250
292
  per-version migration documents. The same transaction deletes the retired migration
251
293
  directory and standing automation policy; the Windows Job Object helper remains a
@@ -458,7 +500,7 @@ supported review schemas; ordinary legacy non-blocking observations remain reada
458
500
 
459
501
  ### Public pull-request evidence
460
502
 
461
- The managed pull-request template keeps public assurance separate from local
503
+ The default pull-request standard keeps public assurance separate from local
462
504
  lifecycle identity. Its five sections and labeled fields are ordered and stable:
463
505
  outcome and scope; source, risk, compatibility, and stack; profiles, snapshot, and
464
506
  completion receipt; verdict, cycles, blocking status, and non-blocking dispositions;
@@ -468,10 +510,15 @@ handle, or local `.process/runs` path. Those values remain in lifecycle state, w
468
510
  they enforce self-review rejection but do not pretend to be provider-authenticated
469
511
  review identities.
470
512
 
471
- `processctl publication validate-pr` checks that public contract deterministically.
513
+ The template and validator derive this contract from the same versioned definition.
514
+ Consumers can select a supported override through `.process/standards.json`; see
515
+ [generation, verification and custom-format boundaries](ARTIFACT_STANDARDS.md).
516
+ `processctl publication validate-pr` checks that selected contract deterministically.
472
517
  It rejects missing, repeated, misplaced, hidden, unordered, or unsupported visible
473
518
  structure. Completion checkboxes belong only to the Completion gate section. Ready
474
- pull requests must have every checkbox checked; drafts may retain unchecked work.
519
+ pull requests must have every checkbox checked and no unresolved default placeholder
520
+ values; drafts may retain pending fields and unchecked work. The author/coordinator
521
+ replaces them with actual evidence before ready/merge; the reviewer supplies the verdict.
475
522
  One trailing `Refs ISSUE.` line remains optional. A ready, contract-identified final
476
523
  consumer adoption may instead use `Closes ISSUE, closes OWNER/REPOSITORY#NUMBER.` with
477
524
  the complete keyword/reference syntax repeated for every issue; drafts cannot close
@@ -531,17 +578,19 @@ authorizes that merge.
531
578
  This repository opts in through .github/renovate.json, so it receives the same
532
579
  adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
533
580
 
534
- The optional [Renovate preset](templates/renovate.json) is generated from the
535
- canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
581
+ The optional [Renovate preset](templates/renovate.json) and public template are generated
582
+ from the packaged PR standard. It supplies only `prHeader` and `prBodyTemplate`;
536
583
  dependency selection, supported platforms, schedules, major-update approval,
537
584
  commands, draft policy, and merge authority remain consumer-owned. Regenerate it
538
585
  with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
586
+ Consumers overriding that standard can generate a matching preset with
587
+ `processctl artifact renovate-preset`; wire it through their own Renovate configuration.
539
588
 
540
589
  Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
541
590
  to their existing `extends` array, replacing `COMMIT_SHA` with the full source
542
591
  commit of a verified release that contains the preset. Remove obsolete inline
543
592
  `prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
544
- The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
593
+ The default preset targets the draft grammar used by 1.2.4 and this distribution;
545
594
  it does not claim compatibility with earlier publication contracts. A future
546
595
  grammar change must preserve this adapter or ship an explicit consumer migration.
547
596
 
@@ -0,0 +1,23 @@
1
+ # Engineering Process v2.3.0
2
+
3
+ Changes since v2.2.0.
4
+
5
+ ## Features
6
+
7
+ - Generate and verify open and closed issue records from a versioned default or consumer\-owned override while keeping tracker actions and product workflow consumer\-owned\. ([#182](https://github.com/phuongnse/engineering-process/issues/182))
8
+ - Opt into publication checks at lifecycle start and completion\, with pinned source metadata in version 2 receipts and exact PR head\/body checks before readiness\; existing consumers and version 1 receipts remain supported\. ([#180](https://github.com/phuongnse/engineering-process/issues/180))
9
+
10
+ ## Fixes
11
+
12
+ - Require delegated agents and reviewers to preserve the active user\-selected model and reasoning effort\, with native runtime confirmation on spawn and resume and no autonomous model changes\. ([#182](https://github.com/phuongnse/engineering-process/issues/182))
13
+ - Resolve bare child commands through the invoking Python environment in fresh sessions\, preserving installed dependencies\, declared argument arrays\, explicit executables and environment filtering\. ([#181](https://github.com/phuongnse/engineering-process/issues/181))
14
+
15
+ ## Upgrade and compatibility
16
+
17
+ Merge the complete hash-locked package/adoption PR, update the local and CI environments to the selected version, and start a fresh agent session.
18
+
19
+ Consumer CI, naming conventions and branch-protection settings remain consumer-owned; adoption does not configure them automatically.
20
+
21
+ See [versioning and compatibility](https://github.com/phuongnse/engineering-process/blob/v2.3.0/VERSIONING.md) and [adoption guidance](https://github.com/phuongnse/engineering-process/blob/v2.3.0/SELF_HOSTING.md).
22
+
23
+ [Full change comparison](https://github.com/phuongnse/engineering-process/compare/v2.2.0...v2.3.0)
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "2.1.0"
3
+ VERSION = "2.3.0"
@@ -7,6 +7,7 @@ import ctypes
7
7
  import os
8
8
  from pathlib import Path
9
9
  import secrets
10
+ import shutil
10
11
  import signal
11
12
  import subprocess
12
13
  import sys
@@ -35,9 +36,13 @@ def _enable_subreaper() -> None:
35
36
 
36
37
 
37
38
  def _ps_process_table() -> tuple[dict[int, int], str | None]:
39
+ # Supervision must not depend on the consumer's PATH.
40
+ application = shutil.which("ps", path=os.defpath)
41
+ if application is None:
42
+ return {}, "process table snapshot could not start"
38
43
  try:
39
44
  result = subprocess.run(
40
- ["ps", "-axo", "pid=,ppid="],
45
+ [application, "-axo", "pid=,ppid="],
41
46
  stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
42
47
  timeout=PROCESS_TABLE_TIMEOUT_SECONDS, check=False, text=True,
43
48
  )
@@ -190,7 +195,7 @@ class PosixProcessSupervisor:
190
195
  if not command or "\x00" in command:
191
196
  raise OSError("command executable is invalid")
192
197
  supplied = Path(command)
193
- if supplied.is_absolute() or supplied.parent != Path("."):
198
+ if supplied.is_absolute() or os.path.dirname(command):
194
199
  candidate = supplied if supplied.is_absolute() else working_directory / supplied
195
200
  try:
196
201
  resolved = candidate.resolve(strict=True)
@@ -198,7 +203,8 @@ class PosixProcessSupervisor:
198
203
  raise OSError(f"executable is unavailable: {command}") from error
199
204
  if not resolved.is_file() or not os.access(resolved, os.X_OK):
200
205
  raise OSError(f"executable is unavailable: {command}")
201
- return resolved
206
+ # The symlink path may select a virtual environment.
207
+ return candidate.absolute()
202
208
  for raw_directory in environment.get("PATH", "").split(os.pathsep):
203
209
  directory = Path(raw_directory or ".")
204
210
  if not directory.is_absolute():
@@ -209,7 +215,7 @@ class PosixProcessSupervisor:
209
215
  except OSError:
210
216
  continue
211
217
  if resolved.is_file() and os.access(resolved, os.X_OK):
212
- return resolved
218
+ return candidate.absolute()
213
219
  raise OSError(f"executable is unavailable: {command}")
214
220
 
215
221
  def spawn(
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import ntpath
6
7
  import os
7
8
  from pathlib import Path
8
9
  import subprocess
@@ -99,7 +100,7 @@ def resolve_windows_application(
99
100
  raise OSError("command executable is invalid")
100
101
  separator = os.pathsep if path_separator is None else path_separator
101
102
  supplied = Path(command)
102
- explicit_path = supplied.is_absolute() or supplied.parent != Path(".")
103
+ explicit_path = supplied.is_absolute() or bool(ntpath.dirname(command))
103
104
  suffix = supplied.suffix.casefold()
104
105
  if suffix in _UNSUPPORTED_SHELL_SUFFIXES:
105
106
  raise OSError(
@@ -12,6 +12,7 @@ import tempfile
12
12
  from typing import Any, Callable
13
13
 
14
14
  from . import VERSION
15
+ from .artifact_standards import load_standard_catalog
15
16
  from .contracts import ProcessError, read_json, validate_document
16
17
  from .distribution import (
17
18
  distribution_digest,
@@ -21,6 +22,7 @@ from .distribution import (
21
22
  skills_root,
22
23
  )
23
24
  from .project import normalize_project, project_path
25
+ from .pr_description import render_template
24
26
 
25
27
 
26
28
  MAX_REQUIREMENTS_BYTES = 2_000_000
@@ -253,9 +255,8 @@ def _expected_files(
253
255
  "utf-8"
254
256
  )
255
257
 
256
- pull_request_template = (
257
- process_root / "templates" / "PULL_REQUEST_TEMPLATE.md"
258
- ).read_text(encoding="utf-8")
258
+ standards = load_standard_catalog(project_root, process_root)
259
+ pull_request_template = render_template(standards.resolve("pull-request"), process_root=process_root)
259
260
  pull_request_path = project_root / ".github" / "PULL_REQUEST_TEMPLATE.md"
260
261
  existing_pull_request = (
261
262
  pull_request_path.read_text(encoding="utf-8")
@@ -301,6 +302,12 @@ def _expected_files(
301
302
  writes[Path(".process/process.lock")] = (
302
303
  json.dumps(lock, indent=2, sort_keys=True) + "\n"
303
304
  ).encode("utf-8")
305
+ collisions = standards.consumer_files.intersection(writes.keys() | deletions)
306
+ if collisions:
307
+ raise ProcessError(
308
+ "consumer standard files conflict with managed adoption paths; move the definitions and update the selection: "
309
+ + ", ".join(sorted(path.as_posix() for path in collisions))
310
+ )
304
311
  total = sum(len(value) for value in writes.values())
305
312
  if total > MAX_MANAGED_BYTES:
306
313
  raise ProcessError("managed adoption output exceeds its aggregate limit")
@@ -0,0 +1,155 @@
1
+ """Resolve one versioned artifact contract for both generation and verification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import os
7
+ from pathlib import Path
8
+ import re
9
+ from typing import Any
10
+
11
+ from .contracts import ProcessError, digest_json, load_and_validate
12
+ from .distribution import schemas_root
13
+ from .repository import STATE_PREFIXES, _git
14
+
15
+
16
+ IDENTIFIER = re.compile(r"[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?")
17
+ MAX_DOCUMENT_BYTES = 1_000_000
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ArtifactStandard:
22
+ document: dict[str, Any]
23
+ source: str
24
+
25
+ @property
26
+ def rules(self) -> dict[str, Any]:
27
+ return self.document["rules"]
28
+
29
+ @property
30
+ def metadata(self) -> dict[str, Any]:
31
+ return {
32
+ **{key: self.document[key] for key in ("id", "version", "artifact", "adapter")},
33
+ "digest": digest_json(self.document),
34
+ "source": self.source,
35
+ }
36
+
37
+ def unresolved(self, value: str) -> bool:
38
+ # These are reserved values in an owned protocol, not prose classification.
39
+ return value.strip().casefold() in {
40
+ item.strip().casefold() for item in self.document.get("pendingValues", [])
41
+ }
42
+
43
+
44
+ def _consumer_file(root: Path, relative: str) -> Path:
45
+ path = root / relative
46
+ if not path.resolve().is_relative_to(root):
47
+ raise ProcessError(f"standard path escapes the consumer repository: {relative}")
48
+ if path.resolve() != path or Path(relative).as_posix() != relative:
49
+ raise ProcessError(f"standard path must be canonical without links: {relative}")
50
+ if any(part in {"..", ".git"} for part in Path(relative).parts):
51
+ raise ProcessError(f"standard path is not an owned repository file: {relative}")
52
+ current = root
53
+ for part in Path(relative).parts:
54
+ current /= part
55
+ if current.is_symlink():
56
+ raise ProcessError(f"standard paths cannot traverse symlinks: {relative}")
57
+ if not path.is_file():
58
+ raise ProcessError(f"consumer standard file is missing: {relative}")
59
+ encoded = os.fsencode(Path(relative).as_posix())
60
+ if any(encoded.startswith(prefix) for prefix in STATE_PREFIXES):
61
+ raise ProcessError(f"standard file is excluded from lifecycle snapshots: {relative}")
62
+ inventory = _git(root, ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", relative])
63
+ if encoded not in inventory.split(b"\0"):
64
+ raise ProcessError(f"standard file must be included in the consumer Git snapshot: {relative}")
65
+ return path
66
+
67
+
68
+ def _unique(values: list[str], description: str) -> None:
69
+ if len(values) != len(set(values)):
70
+ raise ProcessError(f"artifact standard requires unique {description}")
71
+
72
+
73
+ def _validate_relations(document: dict[str, Any]) -> None:
74
+ _unique([value.strip().casefold() for value in document.get("pendingValues", [])], "pending values")
75
+ rules = document["rules"]
76
+ if document["adapter"] == "pr-description":
77
+ sections = rules["sections"]
78
+ _unique([section["heading"] for section in sections], "section headings")
79
+ for kind in ("fields", "checks"):
80
+ entries = [entry for section in sections for entry in section[kind]]
81
+ _unique([entry["id"] for entry in entries], f"{kind} ids")
82
+ _unique([entry["label"] for entry in entries], f"{kind} labels")
83
+ elif document["adapter"] == "release-notes":
84
+ _unique([group["type"] for group in rules["groups"]], "change types")
85
+ _unique([section["id"] for section in rules["sections"]], "release section ids")
86
+ _unique([item["heading"] for item in rules["groups"] + rules["sections"]], "release headings")
87
+ elif document["adapter"] == "issue":
88
+ for state, definition in rules["states"].items():
89
+ sections = definition["sections"]
90
+ _unique([section["heading"] for section in sections], f"{state} issue section headings")
91
+ for kind in ("fields", "checks"):
92
+ entries = [entry for section in sections for entry in section[kind]]
93
+ _unique([entry["id"] for entry in entries], f"{state} issue {kind} ids")
94
+ _unique([entry["label"] for entry in entries], f"{state} issue {kind} labels")
95
+
96
+
97
+ def read_document(path: Path) -> bytes:
98
+ with path.open("rb") as stream:
99
+ data = stream.read(MAX_DOCUMENT_BYTES + 1)
100
+ if len(data) > MAX_DOCUMENT_BYTES:
101
+ raise ProcessError("artifact body exceeds its size limit")
102
+ data.decode("utf-8")
103
+ return data
104
+
105
+
106
+ @dataclass(frozen=True)
107
+ class StandardCatalog:
108
+ """One selection snapshot for adapter resolution and adoption ownership checks."""
109
+
110
+ project_root: Path | None
111
+ process_root: Path
112
+ selections: dict[str, Any]
113
+ consumer_files: frozenset[Path]
114
+
115
+ def resolve(self, artifact: str) -> ArtifactStandard:
116
+ if len(artifact) > 128 or not IDENTIFIER.fullmatch(artifact):
117
+ raise ProcessError("artifact must be a canonical identifier")
118
+ selection = self.selections.get(artifact, {"builtin": f"{artifact}@1"})
119
+ if "path" in selection:
120
+ path = _consumer_file(self.project_root, selection["path"])
121
+ source = selection["path"]
122
+ else:
123
+ name, version = selection["builtin"].rsplit("@", 1)
124
+ path = self.process_root / "process_assets" / "standards" / f"{name}.v{version}.json"
125
+ source = selection["builtin"]
126
+ if path.is_symlink() or not path.is_file():
127
+ raise ProcessError(f"unsupported packaged artifact standard: {source}")
128
+ document = load_and_validate(path, "artifact-standard", schema_root=schemas_root(self.process_root))
129
+ if document["artifact"] != artifact:
130
+ raise ProcessError(f"selected standard is for {document['artifact']}, not {artifact}")
131
+ if "builtin" in selection and (name != artifact or document["version"] != int(version)):
132
+ raise ProcessError("packaged standard identity does not match its selection")
133
+ _validate_relations(document)
134
+ return ArtifactStandard(document, source)
135
+
136
+
137
+ def load_standard_catalog(project_root: Path | None, process_root: Path) -> StandardCatalog:
138
+ selections: dict[str, Any] = {}
139
+ consumer_files: set[Path] = set()
140
+ if project_root is not None:
141
+ project_root = project_root.resolve()
142
+ selector = project_root / ".process" / "standards.json"
143
+ if selector.exists() or selector.is_symlink():
144
+ document = load_and_validate(
145
+ _consumer_file(project_root, ".process/standards.json"),
146
+ "artifact-selection", schema_root=schemas_root(process_root),
147
+ )
148
+ selections = document["artifacts"]
149
+ consumer_files = {Path(".process/standards.json")}
150
+ consumer_files.update(Path(value["path"]) for value in selections.values() if "path" in value)
151
+ return StandardCatalog(project_root, process_root, selections, frozenset(consumer_files))
152
+
153
+
154
+ def resolve_standard(project_root: Path | None, process_root: Path, artifact: str) -> ArtifactStandard:
155
+ return load_standard_catalog(project_root, process_root).resolve(artifact)
@@ -0,0 +1,31 @@
1
+ """Compose automation names from a selected convention and consumer-owned components."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from .artifact_standards import ArtifactStandard
7
+ from .contracts import ProcessError, validate_document
8
+ from .distribution import distribution_root, schemas_root
9
+
10
+
11
+ def render_name(
12
+ standard: ArtifactStandard,
13
+ data: dict[str, Any],
14
+ *,
15
+ state: str = "ready",
16
+ process_root: Path | None = None,
17
+ ) -> str:
18
+ if standard.document["adapter"] != "automation-name":
19
+ raise ProcessError("selected standard requires a different name adapter")
20
+ validate_document(data, "automation-name-data", schema_root=schemas_root(distribution_root(process_root)))
21
+ if state not in {"draft", "ready"}:
22
+ raise ProcessError("artifact state must be draft or ready")
23
+ rules = standard.rules
24
+ if set(data["components"]) != set(rules["components"]):
25
+ raise ProcessError("name components must exactly match the selected standard")
26
+ name = rules["separator"].join(data["components"][key] for key in rules["components"])
27
+ if rules["case"] == "lower":
28
+ name = name.lower()
29
+ if len(name) > rules["maxLength"]:
30
+ raise ProcessError("automation name exceeds the selected length limit")
31
+ return name + "\n"