engineering-process 1.2.0__tar.gz → 1.2.2__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 (75) hide show
  1. {engineering_process-1.2.0/engineering_process.egg-info → engineering_process-1.2.2}/PKG-INFO +5 -1
  2. {engineering_process-1.2.0 → engineering_process-1.2.2}/README.md +4 -0
  3. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/__init__.py +1 -1
  4. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/distribution.py +8 -3
  5. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/publication_compat.py +6 -1
  6. {engineering_process-1.2.0 → engineering_process-1.2.2/engineering_process.egg-info}/PKG-INFO +5 -1
  7. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process.egg-info/SOURCES.txt +1 -0
  8. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/finish-change/SKILL.md +6 -0
  9. {engineering_process-1.2.0 → engineering_process-1.2.2}/pyproject.toml +1 -1
  10. {engineering_process-1.2.0 → engineering_process-1.2.2}/templates/PULL_REQUEST_TEMPLATE.md +2 -0
  11. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_automation.py +11 -0
  12. engineering_process-1.2.2/tests/test_distribution.py +54 -0
  13. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_publication_compat.py +36 -0
  14. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_skills.py +2 -0
  15. {engineering_process-1.2.0 → engineering_process-1.2.2}/LICENSE +0 -0
  16. {engineering_process-1.2.0 → engineering_process-1.2.2}/MANIFEST.in +0 -0
  17. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/__main__.py +0 -0
  18. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/_supervisor_contract.py +0 -0
  19. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/_supervisor_posix.py +0 -0
  20. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/_supervisor_windows.py +0 -0
  21. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/_windows_job.py +0 -0
  22. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/adoption.py +0 -0
  23. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/cli.py +0 -0
  24. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/commands.py +0 -0
  25. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/contracts.py +0 -0
  26. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/helper_launch.py +0 -0
  27. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/lifecycle.py +0 -0
  28. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/production_engineering.py +0 -0
  29. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/project.py +0 -0
  30. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/release.py +0 -0
  31. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/repository.py +0 -0
  32. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/requirements-dev.txt +0 -0
  33. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/requirements-runtime.txt +0 -0
  34. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/skills.py +0 -0
  35. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process/supervision.py +0 -0
  36. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process.egg-info/dependency_links.txt +0 -0
  37. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process.egg-info/entry_points.txt +0 -0
  38. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process.egg-info/requires.txt +0 -0
  39. {engineering_process-1.2.0 → engineering_process-1.2.2}/engineering_process.egg-info/top_level.txt +0 -0
  40. {engineering_process-1.2.0 → engineering_process-1.2.2}/process-graph.json +0 -0
  41. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/implement-change/SKILL.md +0 -0
  42. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/improve-process/SKILL.md +0 -0
  43. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/plan-change/SKILL.md +0 -0
  44. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/production-engineering/SKILL.md +0 -0
  45. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/production-engineering/invariants.json +0 -0
  46. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/review-change/SKILL.md +0 -0
  47. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/run-change/SKILL.md +0 -0
  48. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/start-change/SKILL.md +0 -0
  49. {engineering_process-1.2.0 → engineering_process-1.2.2}/process_assets/skills/verify-change/SKILL.md +0 -0
  50. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/change.schema.json +0 -0
  51. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/plan.schema.json +0 -0
  52. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/process-graph.schema.json +0 -0
  53. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/process-lock.schema.json +0 -0
  54. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/production-engineering.schema.json +0 -0
  55. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/project-legacy.schema.json +0 -0
  56. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/project.schema.json +0 -0
  57. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/receipt.schema.json +0 -0
  58. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/release-change.schema.json +0 -0
  59. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/release.schema.json +0 -0
  60. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/review.schema.json +0 -0
  61. {engineering_process-1.2.0 → engineering_process-1.2.2}/schemas/run.schema.json +0 -0
  62. {engineering_process-1.2.0 → engineering_process-1.2.2}/setup.cfg +0 -0
  63. {engineering_process-1.2.0 → engineering_process-1.2.2}/templates/AGENTS.process.md +0 -0
  64. {engineering_process-1.2.0 → engineering_process-1.2.2}/templates/adopt-process-windows-job.py +0 -0
  65. {engineering_process-1.2.0 → engineering_process-1.2.2}/templates/adopt-process.py +0 -0
  66. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_adoption.py +0 -0
  67. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_architecture.py +0 -0
  68. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_cli.py +0 -0
  69. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_commands.py +0 -0
  70. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_contracts.py +0 -0
  71. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_lifecycle.py +0 -0
  72. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_production_engineering.py +0 -0
  73. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_release.py +0 -0
  74. {engineering_process-1.2.0 → engineering_process-1.2.2}/tests/test_repository.py +0 -0
  75. {engineering_process-1.2.0 → engineering_process-1.2.2}/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.0
3
+ Version: 1.2.2
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
@@ -422,6 +422,10 @@ review identities.
422
422
  It rejects missing, repeated, misplaced, hidden, unordered, or unsupported visible
423
423
  structure. Completion checkboxes belong only to the Completion gate section. Ready
424
424
  pull requests must have every checkbox checked; drafts may retain unchecked work.
425
+ One trailing `Refs ISSUE.` line remains optional. A ready, contract-identified final
426
+ consumer adoption may instead use `Closes ISSUE, closes OWNER/REPOSITORY#NUMBER.` with
427
+ the complete keyword/reference syntax repeated for every issue; drafts cannot close
428
+ issues. Producer and intermediate pull requests do not close release-source issues.
425
429
  The managed template never solicits execution identity,
426
430
  and authors plus independent review keep it out of free-form values. The validator is
427
431
  a positive grammar for public fields; it deliberately does not guess identities from
@@ -398,6 +398,10 @@ review identities.
398
398
  It rejects missing, repeated, misplaced, hidden, unordered, or unsupported visible
399
399
  structure. Completion checkboxes belong only to the Completion gate section. Ready
400
400
  pull requests must have every checkbox checked; drafts may retain unchecked work.
401
+ One trailing `Refs ISSUE.` line remains optional. A ready, contract-identified final
402
+ consumer adoption may instead use `Closes ISSUE, closes OWNER/REPOSITORY#NUMBER.` with
403
+ the complete keyword/reference syntax repeated for every issue; drafts cannot close
404
+ issues. Producer and intermediate pull requests do not close release-source issues.
401
405
  The managed template never solicits execution identity,
402
406
  and authors plus independent review keep it out of free-form values. The validator is
403
407
  a positive grammar for public fields; it deliberately does not guess identities from
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "1.2.0"
3
+ VERSION = "1.2.2"
@@ -65,12 +65,17 @@ def skill_names(root: Path) -> tuple[str, ...]:
65
65
  return names
66
66
 
67
67
 
68
+ def _ordered_paths(root: Path, paths: Iterable[Path]) -> list[Path]:
69
+ return sorted(paths, key=lambda path: path.relative_to(root).as_posix())
70
+
71
+
68
72
  def _asset_paths(root: Path) -> Iterable[tuple[str, Path]]:
69
73
  skill_directory = skills_root(root)
70
- for path in sorted(skill_directory.rglob("*")):
74
+ for path in _ordered_paths(skill_directory, skill_directory.rglob("*")):
71
75
  if path.is_file():
72
76
  yield f"skills/{path.relative_to(skill_directory).as_posix()}", path
73
- for path in sorted(schemas_root(root).glob("*.json")):
77
+ schema_directory = schemas_root(root)
78
+ for path in _ordered_paths(schema_directory, schema_directory.glob("*.json")):
74
79
  yield f"schemas/{path.name}", path
75
80
  for name in (
76
81
  "AGENTS.process.md",
@@ -110,7 +115,7 @@ def distribution_digest(root: Path) -> str:
110
115
 
111
116
  def skill_digest(path: Path) -> str:
112
117
  digest = hashlib.sha256()
113
- for file_path in sorted(path.rglob("*")):
118
+ for file_path in _ordered_paths(path, path.rglob("*")):
114
119
  if not file_path.is_file() or file_path.name == ".engineering-process.json":
115
120
  continue
116
121
  relative = file_path.relative_to(path).as_posix().encode("utf-8")
@@ -53,8 +53,9 @@ PR_CHECKS = (
53
53
  )
54
54
  FENCE = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$")
55
55
  NON_MARKDOWN_LINE_SEPARATOR = re.compile("[\v\f\x85\u2028\u2029]")
56
+ ISSUE_TARGET = r"(?:[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)?#[1-9][0-9]*"
56
57
  ISSUE_REFERENCE = re.compile(
57
- r"^Refs (?:[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+)?#[1-9][0-9]*\.$"
58
+ rf"^(?:Refs {ISSUE_TARGET}|Closes {ISSUE_TARGET}(?:, closes {ISSUE_TARGET})*)\.$"
58
59
  )
59
60
 
60
61
 
@@ -232,6 +233,10 @@ def _pull_request_body_issues(body: str, state: str) -> list[str]:
232
233
  and reference_positions[0] <= checklist_positions[-1]
233
234
  ):
234
235
  issues.append("pull request body issue reference must follow the checklist")
236
+ if state == "draft" and any(
237
+ lines[position].startswith("Closes ") for position in reference_positions
238
+ ):
239
+ issues.append("draft pull request cannot close issues")
235
240
  field_patterns = tuple(
236
241
  re.compile(rf"^- {re.escape(field)}:\s*.*$")
237
242
  for fields in PR_FIELDS.values()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 1.2.0
3
+ Version: 1.2.2
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
@@ -422,6 +422,10 @@ review identities.
422
422
  It rejects missing, repeated, misplaced, hidden, unordered, or unsupported visible
423
423
  structure. Completion checkboxes belong only to the Completion gate section. Ready
424
424
  pull requests must have every checkbox checked; drafts may retain unchecked work.
425
+ One trailing `Refs ISSUE.` line remains optional. A ready, contract-identified final
426
+ consumer adoption may instead use `Closes ISSUE, closes OWNER/REPOSITORY#NUMBER.` with
427
+ the complete keyword/reference syntax repeated for every issue; drafts cannot close
428
+ issues. Producer and intermediate pull requests do not close release-source issues.
425
429
  The managed template never solicits execution identity,
426
430
  and authors plus independent review keep it out of free-form values. The validator is
427
431
  a positive grammar for public fields; it deliberately does not guess identities from
@@ -63,6 +63,7 @@ tests/test_automation.py
63
63
  tests/test_cli.py
64
64
  tests/test_commands.py
65
65
  tests/test_contracts.py
66
+ tests/test_distribution.py
66
67
  tests/test_lifecycle.py
67
68
  tests/test_production_engineering.py
68
69
  tests/test_publication_compat.py
@@ -15,6 +15,12 @@ The command writes one bounded completion receipt and marks the run completed.
15
15
  Completion does not itself grant merge, deployment, or release authority; those
16
16
  remain project-owned operations. Never report completion from prose alone.
17
17
 
18
+ Use a normal trailing `Refs ISSUE.` line for producer and intermediate pull requests.
19
+ Only a contract-identified final consumer adoption that has verified the released
20
+ behavior may use `Closes ISSUE, closes ISSUE.` after the completed public checklist.
21
+ Repeat the full keyword and local or `OWNER/REPOSITORY#NUMBER` reference for every
22
+ issue; never close source issues merely because the producer release merged.
23
+
18
24
  Report the readiness capabilities protected or advanced and the remaining planned
19
25
  gaps after completion. Carry the owner and stable record URL for every accepted-risk
20
26
  or tracked-follow-up disposition into the durable handoff. Finish never edits
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-process"
7
- version = "1.2.0"
7
+ version = "1.2.2"
8
8
  description = "A small agent-neutral engineering lifecycle with managed adoption"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -34,4 +34,6 @@ tracked-follow-up entries need an owner and durable HTTPS record. -->
34
34
  - [ ] Required profiles pass on the reviewed snapshot.
35
35
  - [ ] Independent review approved with no blocking finding.
36
36
  - [ ] Every non-blocking finding has a recorded disposition.
37
+ <!-- Producer/intermediate PR: Refs ISSUE. Final verified consumer adoption only:
38
+ Closes ISSUE, closes OWNER/REPOSITORY#NUMBER. Repeat the keyword for every issue. -->
37
39
  <!-- engineering-process:pr-description:end -->
@@ -55,6 +55,8 @@ class AutomationTests(unittest.TestCase):
55
55
  template.index("## Completion gate"),
56
56
  )
57
57
  self.assertIn("Every non-blocking finding has a recorded disposition.", template)
58
+ self.assertIn("Final verified consumer adoption only", template)
59
+ self.assertIn("Closes ISSUE, closes OWNER/REPOSITORY#NUMBER", template)
58
60
 
59
61
  def test_renovate_process_rule_materializes_the_complete_adoption(self) -> None:
60
62
  config = json.loads((ROOT / ".github" / "renovate.json").read_text(encoding="utf-8"))
@@ -104,6 +106,15 @@ class AutomationTests(unittest.TestCase):
104
106
  )
105
107
  self.assertIn("needs: [metadata, publish]", workflow)
106
108
  self.assertIn("Verify PyPI exposes the exact built hashes", workflow)
109
+ visibility = workflow.split(
110
+ " - name: Verify PyPI exposes the exact built hashes\n", maxsplit=1
111
+ )[1].split(" - name: Create short-lived release token\n", maxsplit=1)[0]
112
+ self.assertIn("for attempt in range(12)", visibility)
113
+ self.assertIn('"Accept": "application/vnd.pypi.simple.v1+json"', visibility)
114
+ self.assertIn(
115
+ "if actual == expected and simple_actual == expected:", visibility
116
+ )
117
+ self.assertIn("PyPI Simple API did not return PEP 691 JSON", visibility)
107
118
  self.assertIn("SOURCE_DATE_EPOCH", workflow)
108
119
  self.assertIn("verification/normalize_sdist.py", workflow)
109
120
  self.assertIn("points to a different commit", workflow)
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from pathlib import Path
5
+ import tempfile
6
+ import unittest
7
+
8
+ from engineering_process.distribution import distribution_digest, skill_digest
9
+
10
+
11
+ def framed_digest(entries: list[tuple[str, bytes]]) -> str:
12
+ digest = hashlib.sha256()
13
+ for relative, data in entries:
14
+ encoded = relative.encode("utf-8")
15
+ digest.update(len(encoded).to_bytes(4, "big"))
16
+ digest.update(encoded)
17
+ digest.update(len(data).to_bytes(8, "big"))
18
+ digest.update(data)
19
+ return "sha256:" + digest.hexdigest()
20
+
21
+
22
+ class DistributionTests(unittest.TestCase):
23
+ def test_digests_follow_canonical_relative_posix_order(self) -> None:
24
+ entries = [
25
+ ("skills/sample/SKILL.md", b"skill\n"),
26
+ ("skills/sample/invariants.json", b"{}\n"),
27
+ ("schemas/sample.json", b"{}\n"),
28
+ ("templates/AGENTS.process.md", b"agents\n"),
29
+ ("templates/PULL_REQUEST_TEMPLATE.md", b"pull request\n"),
30
+ ("templates/adopt-process.py", b"adopt\n"),
31
+ ("templates/adopt-process-windows-job.py", b"windows\n"),
32
+ ("process-graph.json", b"{}\n"),
33
+ ]
34
+ with tempfile.TemporaryDirectory() as directory:
35
+ root = Path(directory)
36
+ for relative, data in entries:
37
+ path = root / relative.replace("skills/", "process_assets/skills/", 1)
38
+ path.parent.mkdir(parents=True, exist_ok=True)
39
+ path.write_bytes(data)
40
+
41
+ self.assertEqual(framed_digest(entries), distribution_digest(root))
42
+ self.assertEqual(
43
+ framed_digest(
44
+ [
45
+ ("SKILL.md", b"skill\n"),
46
+ ("invariants.json", b"{}\n"),
47
+ ]
48
+ ),
49
+ skill_digest(root / "process_assets" / "skills" / "sample"),
50
+ )
51
+
52
+
53
+ if __name__ == "__main__":
54
+ unittest.main()
@@ -290,6 +290,42 @@ class PublicationCompatibilityTests(unittest.TestCase):
290
290
  self.validate_body(repeated),
291
291
  )
292
292
 
293
+ def test_final_adoption_can_close_multiple_issues_only_when_ready(self) -> None:
294
+ closing = (
295
+ f"{CANONICAL_BODY}\n"
296
+ "Closes example/process#123, closes #456, closes other/repository#789.\n"
297
+ )
298
+ self.assertEqual([], self.validate_body(closing))
299
+ self.assertIn(
300
+ "draft pull request cannot close issues",
301
+ self.validate_body(closing, state="draft"),
302
+ )
303
+
304
+ unchecked = closing.replace(
305
+ "- [x] Required profiles", "- [ ] Required profiles", 1
306
+ )
307
+ self.assertIn(
308
+ "ready pull request has unchecked item: Required profiles pass on the reviewed snapshot.",
309
+ self.validate_body(unchecked),
310
+ )
311
+
312
+ for malformed in (
313
+ "Closes example/process#123, #456.",
314
+ "Closes example/process#123, Closes #456.",
315
+ "Fixes example/process#123.",
316
+ "Closes example/process#0.",
317
+ ):
318
+ with self.subTest(malformed=malformed):
319
+ issues = self.validate_body(f"{CANONICAL_BODY}\n{malformed}\n")
320
+ self.assertTrue(
321
+ any(
322
+ issue.startswith(
323
+ "pull request body has unsupported visible content at line "
324
+ )
325
+ for issue in issues
326
+ )
327
+ )
328
+
293
329
  def test_pull_request_ignores_template_comments(self) -> None:
294
330
  body = CANONICAL_BODY.replace(
295
331
  "- Outcome: Preserve the requested behavior.",
@@ -78,6 +78,8 @@ class SkillTests(unittest.TestCase):
78
78
  "Finish never edits\nreadiness",
79
79
  "remaining planned\ngaps",
80
80
  "owner and stable record URL",
81
+ "contract-identified final consumer adoption",
82
+ "Closes ISSUE, closes ISSUE",
81
83
  ),
82
84
  "improve-process": (
83
85
  "new immutable pack version",