napkinstack 0.9.3__tar.gz → 0.9.5__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 (34) hide show
  1. {napkinstack-0.9.3 → napkinstack-0.9.5}/PKG-INFO +4 -4
  2. {napkinstack-0.9.3 → napkinstack-0.9.5}/README.md +3 -3
  3. {napkinstack-0.9.3 → napkinstack-0.9.5}/pyproject.toml +2 -2
  4. {napkinstack-0.9.3 → napkinstack-0.9.5}/pyproject.toml.orig +2 -2
  5. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/__init__.py +2 -2
  6. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/cli.py +9 -0
  7. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/provenance.py +19 -1
  8. {napkinstack-0.9.3 → napkinstack-0.9.5}/LICENSE +0 -0
  9. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/assurance.py +0 -0
  10. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/compat.py +0 -0
  11. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/discovery.py +0 -0
  12. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/doctor.py +0 -0
  13. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/fitness/__init__.py +0 -0
  14. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/fitness/boundaries.py +0 -0
  15. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/fitness/hygiene.py +0 -0
  16. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/fitness/manifests.py +0 -0
  17. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/fitness/plan.py +0 -0
  18. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/fitness/pr_scope.py +0 -0
  19. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/landed.py +0 -0
  20. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/modules.py +0 -0
  21. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/project.py +0 -0
  22. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/pull_request.py +0 -0
  23. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/settings.py +0 -0
  24. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/skills.py +0 -0
  25. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/contracts/AGENTS.md +0 -0
  26. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/contracts/MANIFEST.yaml +0 -0
  27. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/contracts/README.md +0 -0
  28. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/contracts/docs/runbook.md +0 -0
  29. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/contracts/tests/.gitkeep +0 -0
  30. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/module/AGENTS.md +0 -0
  31. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/module/MANIFEST.yaml +0 -0
  32. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/module/README.md +0 -0
  33. {napkinstack-0.9.3 → napkinstack-0.9.5}/src/napkinstack/templates/module/src/.gitkeep +0 -0
  34. {napkinstack-0.9.3 → napkinstack-0.9.5}/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.9.3
3
+ Version: 0.9.5
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
@@ -20,7 +20,7 @@ modules, contracts, guardrails in CI. On the Django or Rails model, one command
20
20
  the project, which then receives new versions on demand; no application stack is imposed.
21
21
  Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
22
22
 
23
- > **Status: v0.9.3, published** on [PyPI](https://pypi.org/project/napkinstack/).
23
+ > **Status: v0.9.5, published** on [PyPI](https://pypi.org/project/napkinstack/).
24
24
  > What each version changes, engine and project apart: [`CHANGELOG.md`](CHANGELOG.md).
25
25
  > What is open: [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
26
26
 
@@ -84,8 +84,8 @@ To try it without installing anything, or to install it from this repository ins
84
84
  registry — always pinned to a release tag:
85
85
 
86
86
  ```bash
87
- uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.9.3" nstack init my-project
88
- uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.9.3"
87
+ uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.9.5" nstack init my-project
88
+ uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.9.5"
89
89
  ```
90
90
 
91
91
  **Two channels, one published artefact.** The registry publishes: the PyPI artefact of a
@@ -5,7 +5,7 @@ modules, contracts, guardrails in CI. On the Django or Rails model, one command
5
5
  the project, which then receives new versions on demand; no application stack is imposed.
6
6
  Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
7
7
 
8
- > **Status: v0.9.3, published** on [PyPI](https://pypi.org/project/napkinstack/).
8
+ > **Status: v0.9.5, published** on [PyPI](https://pypi.org/project/napkinstack/).
9
9
  > What each version changes, engine and project apart: [`CHANGELOG.md`](CHANGELOG.md).
10
10
  > What is open: [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
11
11
 
@@ -69,8 +69,8 @@ To try it without installing anything, or to install it from this repository ins
69
69
  registry — always pinned to a release tag:
70
70
 
71
71
  ```bash
72
- uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.9.3" nstack init my-project
73
- uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.9.3"
72
+ uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.9.5" nstack init my-project
73
+ uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.9.5"
74
74
  ```
75
75
 
76
76
  **Two channels, one published artefact.** The registry publishes: the PyPI artefact of a
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.9.3"
3
+ version = "0.9.5"
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"
@@ -27,5 +27,5 @@ dev = ["pytest==9.1.1"]
27
27
  required-version = ">=0.12.14,<0.13"
28
28
 
29
29
  [build-system]
30
- requires = ["uv_build>=0.12.14,<0.13"]
30
+ requires = ["uv_build>=0.12.14,<0.14"]
31
31
  build-backend = "uv_build"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.9.3"
3
+ version = "0.9.5"
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,5 +23,5 @@ dev = ["pytest==9.1.1"]
23
23
  required-version = ">=0.12.14,<0.13"
24
24
 
25
25
  [build-system]
26
- requires = ["uv_build>=0.12.14,<0.13"]
26
+ requires = ["uv_build>=0.12.14,<0.14"]
27
27
  build-backend = "uv_build"
@@ -1,5 +1,5 @@
1
1
  """NapkinStack - engine: fitness functions, skills and module scaffolding."""
2
2
 
3
- from importlib.metadata import version
3
+ from napkinstack.provenance import version
4
4
 
5
- __version__ = version("napkinstack")
5
+ __version__ = version()
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  import json
7
+ import os
7
8
  import sys
8
9
  from pathlib import Path
9
10
 
@@ -183,8 +184,16 @@ def _run(a) -> int:
183
184
  return modules.run_verb(root, "run", a.module, arguments)
184
185
 
185
186
 
187
+ # What git hands a hook to name its repository (D126): a hook of a linked worktree gets GIT_DIR,
188
+ # and every git the engine runs, in a module or a checkout of its own, would read that one.
189
+ # The engine names its project with --root; git finds the rest from where it runs.
190
+ GIT_LOCATION = ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR")
191
+
192
+
186
193
  def main(argv: list[str] | None = None) -> int:
187
194
  argv = sys.argv[1:] if argv is None else argv
195
+ for name in GIT_LOCATION:
196
+ os.environ.pop(name, None)
188
197
  if late := provenance.behind(_named_root(argv)):
189
198
  print(late, file=sys.stderr, flush=True)
190
199
  args = build_parser().parse_args(argv)
@@ -14,6 +14,7 @@ import importlib.metadata
14
14
  import json
15
15
  import re
16
16
  import subprocess
17
+ import tomllib
17
18
  from pathlib import Path
18
19
  from urllib.parse import urlparse
19
20
  from urllib.request import url2pathname
@@ -24,6 +25,23 @@ ANSWERS = ".copier-answers.yml"
24
25
  PUBLISHED = re.compile(r"v\d+\.\d+\.\d+")
25
26
 
26
27
 
28
+ def version() -> str:
29
+ """The engine's version. An editable install's metadata is written once, at install, and the
30
+ checkout moves on: its own pyproject.toml says the version its source is at (D122)."""
31
+ distribution = importlib.metadata.distribution("napkinstack")
32
+ try:
33
+ data = json.loads(distribution.read_text("direct_url.json") or "{}")
34
+ except json.JSONDecodeError: # read at import: a damaged record never stops the engine
35
+ return distribution.version
36
+ if data.get("dir_info", {}).get("editable"):
37
+ source = Path(url2pathname(urlparse(data.get("url", "")).path)) / "pyproject.toml"
38
+ try:
39
+ return str(tomllib.loads(source.read_text(encoding="utf-8"))["project"]["version"])
40
+ except (OSError, KeyError, tomllib.TOMLDecodeError):
41
+ pass
42
+ return distribution.version
43
+
44
+
27
45
  def engine() -> tuple[str, str | None]:
28
46
  """(version, origin): origin is None for a version installed from the registry."""
29
47
  distribution = importlib.metadata.distribution("napkinstack")
@@ -44,7 +62,7 @@ def engine() -> tuple[str, str | None]:
44
62
  path = Path(url2pathname(urlparse(data.get("url", "")).path))
45
63
  commit = subprocess.run(["git", "describe", "--always", "--dirty"], cwd=path,
46
64
  capture_output=True, text=True).stdout.strip() if path.is_dir() else ""
47
- return distribution.version, f"a local checkout at {commit or 'an unknown commit'}"
65
+ return version(), f"a local checkout at {commit or 'an unknown commit'}"
48
66
 
49
67
 
50
68
  def recorded(root: Path) -> str | None:
File without changes