napkinstack 0.6.1__tar.gz → 0.6.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 (28) hide show
  1. {napkinstack-0.6.1 → napkinstack-0.6.2}/PKG-INFO +1 -1
  2. {napkinstack-0.6.1 → napkinstack-0.6.2}/pyproject.toml +1 -1
  3. {napkinstack-0.6.1 → napkinstack-0.6.2}/pyproject.toml.orig +1 -1
  4. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/project.py +30 -0
  5. {napkinstack-0.6.1 → napkinstack-0.6.2}/LICENSE +0 -0
  6. {napkinstack-0.6.1 → napkinstack-0.6.2}/README.md +0 -0
  7. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/__init__.py +0 -0
  8. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/cli.py +0 -0
  9. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/compat.py +0 -0
  10. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/discovery.py +0 -0
  11. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/doctor.py +0 -0
  12. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/fitness/__init__.py +0 -0
  13. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/fitness/boundaries.py +0 -0
  14. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/fitness/hygiene.py +0 -0
  15. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/fitness/manifests.py +0 -0
  16. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/fitness/plan.py +0 -0
  17. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/fitness/pr_scope.py +0 -0
  18. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/landed.py +0 -0
  19. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/modules.py +0 -0
  20. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/provenance.py +0 -0
  21. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/pull_request.py +0 -0
  22. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/skills.py +0 -0
  23. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/templates/module/AGENTS.md +0 -0
  24. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/templates/module/MANIFEST.yaml +0 -0
  25. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/templates/module/README.md +0 -0
  26. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/templates/module/docs/adr/.gitkeep +0 -0
  27. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/templates/module/src/.gitkeep +0 -0
  28. {napkinstack-0.6.1 → napkinstack-0.6.2}/src/napkinstack/templates/module/tests/.gitkeep +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: napkinstack
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.6.1"
3
+ version = "0.6.2"
4
4
  description = "Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI."
5
5
  requires-python = ">=3.12"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.6.1"
3
+ version = "0.6.2"
4
4
  description = "Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI."
5
5
  requires-python = ">=3.12"
6
6
  license = "MIT"
@@ -23,6 +23,8 @@ from napkinstack import __version__
23
23
  SOURCE = "https://github.com/NapkinStack/engineering-os.git"
24
24
  ANSWERS = ".copier-answers.yml"
25
25
  DOWNGRADE = re.compile(r"You are downgrading from (\S+) to (\S+)\.")
26
+ # The forms of a GitHub source Copier records, from which a release page can be named.
27
+ GITHUB = re.compile(r"^(?:https://github\.com/|git@github\.com:|gh:)(?P<repo>[^/]+/[^/]+?)(?:\.git)?/?$")
26
28
 
27
29
 
28
30
  def default_ref() -> str:
@@ -30,6 +32,23 @@ def default_ref() -> str:
30
32
  return f"v{__version__}"
31
33
 
32
34
 
35
+ def release_notes(source: str, previous: str, current: str) -> str:
36
+ """Where to read what a version changes, before taking it. The published package carries
37
+ the engine and not the skeleton, so comparing two releases of it cannot show what an update
38
+ merges (D63): the notes are the only channel, and the command that brings the change names
39
+ them (D62).
40
+
41
+ It names the whole distance travelled, never the target alone (D66). One `nstack update`
42
+ can cross several versions, and the change that conflicts with the project's own files may
43
+ come from any of them: a release page holds one version and cannot answer for the others.
44
+ The CHANGELOG at the tag holds them all."""
45
+ found = GITHUB.match(source.strip())
46
+ if found:
47
+ return (f"https://github.com/{found['repo']}/blob/{current}/CHANGELOG.md"
48
+ f" — every section above {previous}")
49
+ return f"CHANGELOG.md, every section above {previous}, in {source}"
50
+
51
+
33
52
  def _git(root: Path, *args: str) -> subprocess.CompletedProcess[str]:
34
53
  return subprocess.run(["git", *args], cwd=root, capture_output=True, text=True)
35
54
 
@@ -67,6 +86,15 @@ def _explain(exc: Exception, command: str, where: Path, source: str, ref: str) -
67
86
  if text.startswith("Cannot update: version from last update not detected"):
68
87
  return (f"FAIL [{command}] The project does not come from a published version (_commit in {ANSWERS}): "
69
88
  f"no merge base.\n{action}create the project from a vX.Y.Z tag.")
89
+ # An OSError carrying an errno is the filesystem answering, not the template being out of
90
+ # reach: a full disk, a read-only path, a directory an interrupted run left behind. Calling
91
+ # it "unreachable" sends the reader to --source, --ref and the network, none of which can
92
+ # help (D65). The OSErrors Copier raises for a template it could not read carry no errno.
93
+ if isinstance(exc, OSError) and exc.errno:
94
+ return (f"FAIL [{command}] {command} stopped on a local filesystem error: {text}\n"
95
+ f"{action}read the error above and fix what it names \u2014 space, permissions, or "
96
+ "something left behind by an interrupted run. The template and the network are "
97
+ "not in question.")
70
98
  if isinstance(exc, OSError) or text == "Local template must be a directory.":
71
99
  lines = [line.split("|", 1)[-1].strip() for line in text.splitlines() if line.strip()]
72
100
  # git writes the cause on an `error:` line and the outcome on a `fatal:` one; keeping
@@ -190,5 +218,7 @@ def update(root: Path, ref: str) -> int:
190
218
 
191
219
  print(f"Branch {branch}: NapkinStack {previous} -> {current}, merged with the project's "
192
220
  "adaptations.")
221
+ print(f"What {previous} -> {current} changes, engine and project apart: "
222
+ f"{release_notes(str(_answers(root).get('_src_path') or ''), previous, current)}")
193
223
  print(f"\nNext step: git push -u origin {branch}, then open the PR; CI validates it.")
194
224
  return 0
File without changes
File without changes