napkinstack 0.1.0__tar.gz → 0.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 (37) hide show
  1. napkinstack-0.3.0/PKG-INFO +147 -0
  2. napkinstack-0.3.0/README.md +132 -0
  3. {napkinstack-0.1.0 → napkinstack-0.3.0}/pyproject.toml +2 -2
  4. {napkinstack-0.1.0 → napkinstack-0.3.0}/pyproject.toml.orig +2 -2
  5. napkinstack-0.3.0/src/napkinstack/__init__.py +5 -0
  6. napkinstack-0.3.0/src/napkinstack/cli.py +116 -0
  7. napkinstack-0.3.0/src/napkinstack/discovery.py +53 -0
  8. napkinstack-0.3.0/src/napkinstack/doctor.py +314 -0
  9. {napkinstack-0.1.0 → napkinstack-0.3.0}/src/napkinstack/fitness/boundaries.py +46 -46
  10. napkinstack-0.3.0/src/napkinstack/fitness/manifests.py +204 -0
  11. napkinstack-0.3.0/src/napkinstack/fitness/plan.py +221 -0
  12. napkinstack-0.3.0/src/napkinstack/fitness/pr_scope.sh +79 -0
  13. napkinstack-0.3.0/src/napkinstack/modules.py +140 -0
  14. napkinstack-0.3.0/src/napkinstack/project.py +170 -0
  15. napkinstack-0.3.0/src/napkinstack/pull_request.py +242 -0
  16. napkinstack-0.3.0/src/napkinstack/skills.py +159 -0
  17. napkinstack-0.3.0/src/napkinstack/templates/module/AGENTS.md +36 -0
  18. napkinstack-0.3.0/src/napkinstack/templates/module/MANIFEST.yaml +82 -0
  19. napkinstack-0.3.0/src/napkinstack/templates/module/README.md +27 -0
  20. napkinstack-0.1.0/PKG-INFO +0 -139
  21. napkinstack-0.1.0/README.md +0 -124
  22. napkinstack-0.1.0/src/napkinstack/__init__.py +0 -5
  23. napkinstack-0.1.0/src/napkinstack/cli.py +0 -103
  24. napkinstack-0.1.0/src/napkinstack/doctor.py +0 -278
  25. napkinstack-0.1.0/src/napkinstack/fitness/manifests.py +0 -198
  26. napkinstack-0.1.0/src/napkinstack/fitness/pr_scope.sh +0 -79
  27. napkinstack-0.1.0/src/napkinstack/modules.py +0 -132
  28. napkinstack-0.1.0/src/napkinstack/project.py +0 -170
  29. napkinstack-0.1.0/src/napkinstack/skills.py +0 -159
  30. napkinstack-0.1.0/src/napkinstack/templates/module/AGENTS.md +0 -36
  31. napkinstack-0.1.0/src/napkinstack/templates/module/MANIFEST.yaml +0 -77
  32. napkinstack-0.1.0/src/napkinstack/templates/module/README.md +0 -27
  33. {napkinstack-0.1.0 → napkinstack-0.3.0}/LICENSE +0 -0
  34. {napkinstack-0.1.0 → napkinstack-0.3.0}/src/napkinstack/fitness/__init__.py +0 -0
  35. {napkinstack-0.1.0 → napkinstack-0.3.0}/src/napkinstack/templates/module/docs/adr/.gitkeep +0 -0
  36. {napkinstack-0.1.0 → napkinstack-0.3.0}/src/napkinstack/templates/module/src/.gitkeep +0 -0
  37. {napkinstack-0.1.0 → napkinstack-0.3.0}/src/napkinstack/templates/module/tests/.gitkeep +0 -0
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: napkinstack
3
+ Version: 0.3.0
4
+ Summary: Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Dist: pyyaml>=6.0
8
+ Requires-Dist: copier==9.18.2
9
+ Requires-Dist: pre-commit==4.6.2
10
+ Requires-Python: >=3.12
11
+ Project-URL: Homepage, https://github.com/NapkinStack/engineering-os
12
+ Project-URL: Source, https://github.com/NapkinStack/engineering-os
13
+ Project-URL: Issues, https://github.com/NapkinStack/engineering-os/issues
14
+ Description-Content-Type: text/markdown
15
+
16
+ # NapkinStack
17
+
18
+ An engineering framework for several teams and their agents working in one repository:
19
+ modules, contracts, guardrails in CI. On the Django or Rails model, one command creates
20
+ the project, which then receives new versions on demand; no application stack is imposed.
21
+ Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
22
+
23
+ > **Status: v0.2.0, published** ([PyPI](https://pypi.org/project/napkinstack/)), English
24
+ > throughout. A first pilot project, private, starts from it and puts it to the test before
25
+ > the rest. Tracking: [engine roadmap](docs/governance/plans/2026-09-15-engine-v0.1.0.md),
26
+ > [move to English](docs/governance/plans/2026-09-16-english-migration.md),
27
+ > [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
28
+
29
+ ## A project's journey
30
+
31
+ ```mermaid
32
+ flowchart LR
33
+ I["Install<br/>uv tool install"]:::cmd --> N["nstack init"]:::cmd
34
+ N --> G["Publish on GitHub<br/>apply the checklist"]:::human
35
+ G --> D["nstack doctor<br/>read-only"]:::cmd
36
+ D --> M["nstack new-module"]:::cmd
37
+ M --> W["Work in pull requests<br/>the team and its agent"]:::human
38
+ W --> U["nstack update<br/>merged branch"]:::cmd
39
+ U --> P["PR reviewed<br/>validated by CI"]:::human
40
+ P -->|"next version"| U
41
+
42
+ classDef cmd fill:#1f2937,color:#fff
43
+ classDef human fill:#065f46,color:#fff
44
+ ```
45
+
46
+ **Legend** — grey: a NapkinStack command · green: the team's action. Decision:
47
+ [PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md).
48
+
49
+ The project owns its skeleton and adapts it freely. Every new version reaches it on
50
+ demand, merged with its adaptations; the conflicts are left to the team.
51
+
52
+ ```mermaid
53
+ flowchart LR
54
+ V1["Skeleton v0.1<br/>common base"]:::ref --> F{"Three-way<br/>merge"}
55
+ V2["Skeleton v0.2<br/>NapkinStack fixes"]:::ns --> F
56
+ PR["Project<br/>the team's adaptations"]:::team --> F
57
+ F -->|"different lines"| B["Update branch<br/>fixes + adaptations"]:::ok
58
+ F -->|"same line changed"| X["Conflict marked<br/>commit refused"]:::ko
59
+
60
+ classDef ref fill:#374151,color:#fff
61
+ classDef ns fill:#1e3a8a,color:#fff
62
+ classDef team fill:#065f46,color:#fff
63
+ classDef ok fill:#065f46,color:#fff
64
+ classDef ko fill:#7c2d12,color:#fff
65
+ ```
66
+
67
+ **Legend** — grey: the version the project came from · blue: the new version · green: the
68
+ team's work and the accepted result · red: a conflict left to the team.
69
+
70
+ ## Install
71
+
72
+ ```bash
73
+ uv tool install napkinstack --with-executables-from pre-commit # prerequisites: uv and git
74
+ nstack init my-project
75
+ ```
76
+
77
+ Each project then pins its version and changes it through `nstack update`. Every published
78
+ version carries a provenance attestation, visible on PyPI, tying it to the workflow and
79
+ the commit of this repository
80
+ ([ADR-0002](docs/adr/0002-distribute-napkinstack-on-pypi.md)).
81
+
82
+ ## The commands
83
+
84
+ | Command | Role |
85
+ |---|---|
86
+ | `nstack init <folder>` | Creates the project: skeleton, git repository, initial commit, GitHub checklist |
87
+ | `nstack doctor` | Checks the workstation and the GitHub settings, read-only |
88
+ | `nstack new-module <name> <owner> <criticality>` | Creates a module, with no imposed stack |
89
+ | `nstack check`, `test`, `bootstrap` `[module]`; `nstack run <module>` | Run the commands declared in the module's manifest |
90
+ | `nstack discover <idea-file>` | Starts a discovery for your agent: the idea kept, its document created |
91
+ | `nstack plan` | The discovery, the charter and the cycles: formats, one cycle at a time, closures |
92
+ | `nstack fitness` | Manifests, boundaries between modules, skills, plan |
93
+ | `nstack pr-scope` | One PR = one module, review budget |
94
+ | `nstack e2e [module]` | Runs the module's end-to-end scenarios, when declared |
95
+ | `nstack pr-check` | The test sheet and the cycle, read from the pull request description |
96
+ | `nstack skills` | Exposes the playbooks as skills for the agent |
97
+ | `nstack update` | Lays the new version on a branch to review |
98
+
99
+ **Prerequisites**: uv and git. The guardrails really block on a public GitHub repository,
100
+ or on a private one under the Team or Pro plan; on a private repository on the Free plan
101
+ CI informs without blocking
102
+ ([a clarification of PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md)).
103
+
104
+ **AI**: NapkinStack embeds none. The team's agent (Claude Code, Codex, Copilot…) reads the
105
+ kernel and the playbooks, runs the commands, and CI accepts or refuses its proposals
106
+ exactly as it would any other contributor's.
107
+
108
+ ## This repository
109
+
110
+ ```mermaid
111
+ flowchart LR
112
+ S["skeleton/<br/>the project skeleton"]:::shipped -->|"copier.yml"| P["A team's project"]:::project
113
+ E["src/napkinstack/<br/>the nstack engine"]:::shipped -.->|"pinned version"| P
114
+ A["PRODUCT.md · docs/governance/<br/>platform/ · this repository's CI"]:::internal
115
+
116
+ classDef shipped fill:#1e3a8a,color:#fff
117
+ classDef project fill:#065f46,color:#fff
118
+ classDef internal fill:#374151,color:#fff
119
+ ```
120
+
121
+ **Legend** — blue: shipped to projects · green: a generated project, which owns its
122
+ skeleton · grey: developing NapkinStack itself, never copied (PDR-0001 R6). Solid line:
123
+ generation; dotted: a versioned dependency.
124
+
125
+ | Path | Role |
126
+ |---|---|
127
+ | `skeleton/` | What every project receives: kernel, playbooks, handbook, CI, hooks |
128
+ | `copier.yml` | The questions asked at creation (a Copier template, ADR-0001) |
129
+ | `src/napkinstack/` | The engine, the `nstack` command |
130
+ | `platform/` | The engine module's envelope: manifest, runbook, tests |
131
+ | `PRODUCT.md`, `docs/governance/` | The working context for NapkinStack itself |
132
+ | `docs/adr/`, `docs/pdr/` | NapkinStack's decisions |
133
+
134
+ ## Developing NapkinStack
135
+
136
+ ```bash
137
+ uv sync # prerequisite: uv
138
+ uv run pre-commit install
139
+ uv run nstack fitness # this repository's guardrails
140
+ uv run bash platform/tests/run.sh # the oracle: every guardrail proves it can fail
141
+ uv run nstack init /tmp/trial --source . --ref HEAD # a trial project from the working tree
142
+ uv run nstack doctor --root /tmp/trial # workstation and GitHub settings, read-only
143
+ ```
144
+
145
+ Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md), after [`PRODUCT.md`](PRODUCT.md).
146
+
147
+ Licence: [MIT](LICENSE).
@@ -0,0 +1,132 @@
1
+ # NapkinStack
2
+
3
+ An engineering framework for several teams and their agents working in one repository:
4
+ modules, contracts, guardrails in CI. On the Django or Rails model, one command creates
5
+ the project, which then receives new versions on demand; no application stack is imposed.
6
+ Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
7
+
8
+ > **Status: v0.2.0, published** ([PyPI](https://pypi.org/project/napkinstack/)), English
9
+ > throughout. A first pilot project, private, starts from it and puts it to the test before
10
+ > the rest. Tracking: [engine roadmap](docs/governance/plans/2026-09-15-engine-v0.1.0.md),
11
+ > [move to English](docs/governance/plans/2026-09-16-english-migration.md),
12
+ > [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
13
+
14
+ ## A project's journey
15
+
16
+ ```mermaid
17
+ flowchart LR
18
+ I["Install<br/>uv tool install"]:::cmd --> N["nstack init"]:::cmd
19
+ N --> G["Publish on GitHub<br/>apply the checklist"]:::human
20
+ G --> D["nstack doctor<br/>read-only"]:::cmd
21
+ D --> M["nstack new-module"]:::cmd
22
+ M --> W["Work in pull requests<br/>the team and its agent"]:::human
23
+ W --> U["nstack update<br/>merged branch"]:::cmd
24
+ U --> P["PR reviewed<br/>validated by CI"]:::human
25
+ P -->|"next version"| U
26
+
27
+ classDef cmd fill:#1f2937,color:#fff
28
+ classDef human fill:#065f46,color:#fff
29
+ ```
30
+
31
+ **Legend** — grey: a NapkinStack command · green: the team's action. Decision:
32
+ [PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md).
33
+
34
+ The project owns its skeleton and adapts it freely. Every new version reaches it on
35
+ demand, merged with its adaptations; the conflicts are left to the team.
36
+
37
+ ```mermaid
38
+ flowchart LR
39
+ V1["Skeleton v0.1<br/>common base"]:::ref --> F{"Three-way<br/>merge"}
40
+ V2["Skeleton v0.2<br/>NapkinStack fixes"]:::ns --> F
41
+ PR["Project<br/>the team's adaptations"]:::team --> F
42
+ F -->|"different lines"| B["Update branch<br/>fixes + adaptations"]:::ok
43
+ F -->|"same line changed"| X["Conflict marked<br/>commit refused"]:::ko
44
+
45
+ classDef ref fill:#374151,color:#fff
46
+ classDef ns fill:#1e3a8a,color:#fff
47
+ classDef team fill:#065f46,color:#fff
48
+ classDef ok fill:#065f46,color:#fff
49
+ classDef ko fill:#7c2d12,color:#fff
50
+ ```
51
+
52
+ **Legend** — grey: the version the project came from · blue: the new version · green: the
53
+ team's work and the accepted result · red: a conflict left to the team.
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ uv tool install napkinstack --with-executables-from pre-commit # prerequisites: uv and git
59
+ nstack init my-project
60
+ ```
61
+
62
+ Each project then pins its version and changes it through `nstack update`. Every published
63
+ version carries a provenance attestation, visible on PyPI, tying it to the workflow and
64
+ the commit of this repository
65
+ ([ADR-0002](docs/adr/0002-distribute-napkinstack-on-pypi.md)).
66
+
67
+ ## The commands
68
+
69
+ | Command | Role |
70
+ |---|---|
71
+ | `nstack init <folder>` | Creates the project: skeleton, git repository, initial commit, GitHub checklist |
72
+ | `nstack doctor` | Checks the workstation and the GitHub settings, read-only |
73
+ | `nstack new-module <name> <owner> <criticality>` | Creates a module, with no imposed stack |
74
+ | `nstack check`, `test`, `bootstrap` `[module]`; `nstack run <module>` | Run the commands declared in the module's manifest |
75
+ | `nstack discover <idea-file>` | Starts a discovery for your agent: the idea kept, its document created |
76
+ | `nstack plan` | The discovery, the charter and the cycles: formats, one cycle at a time, closures |
77
+ | `nstack fitness` | Manifests, boundaries between modules, skills, plan |
78
+ | `nstack pr-scope` | One PR = one module, review budget |
79
+ | `nstack e2e [module]` | Runs the module's end-to-end scenarios, when declared |
80
+ | `nstack pr-check` | The test sheet and the cycle, read from the pull request description |
81
+ | `nstack skills` | Exposes the playbooks as skills for the agent |
82
+ | `nstack update` | Lays the new version on a branch to review |
83
+
84
+ **Prerequisites**: uv and git. The guardrails really block on a public GitHub repository,
85
+ or on a private one under the Team or Pro plan; on a private repository on the Free plan
86
+ CI informs without blocking
87
+ ([a clarification of PDR-0001](docs/pdr/0001-create-a-project-and-receive-updates.md)).
88
+
89
+ **AI**: NapkinStack embeds none. The team's agent (Claude Code, Codex, Copilot…) reads the
90
+ kernel and the playbooks, runs the commands, and CI accepts or refuses its proposals
91
+ exactly as it would any other contributor's.
92
+
93
+ ## This repository
94
+
95
+ ```mermaid
96
+ flowchart LR
97
+ S["skeleton/<br/>the project skeleton"]:::shipped -->|"copier.yml"| P["A team's project"]:::project
98
+ E["src/napkinstack/<br/>the nstack engine"]:::shipped -.->|"pinned version"| P
99
+ A["PRODUCT.md · docs/governance/<br/>platform/ · this repository's CI"]:::internal
100
+
101
+ classDef shipped fill:#1e3a8a,color:#fff
102
+ classDef project fill:#065f46,color:#fff
103
+ classDef internal fill:#374151,color:#fff
104
+ ```
105
+
106
+ **Legend** — blue: shipped to projects · green: a generated project, which owns its
107
+ skeleton · grey: developing NapkinStack itself, never copied (PDR-0001 R6). Solid line:
108
+ generation; dotted: a versioned dependency.
109
+
110
+ | Path | Role |
111
+ |---|---|
112
+ | `skeleton/` | What every project receives: kernel, playbooks, handbook, CI, hooks |
113
+ | `copier.yml` | The questions asked at creation (a Copier template, ADR-0001) |
114
+ | `src/napkinstack/` | The engine, the `nstack` command |
115
+ | `platform/` | The engine module's envelope: manifest, runbook, tests |
116
+ | `PRODUCT.md`, `docs/governance/` | The working context for NapkinStack itself |
117
+ | `docs/adr/`, `docs/pdr/` | NapkinStack's decisions |
118
+
119
+ ## Developing NapkinStack
120
+
121
+ ```bash
122
+ uv sync # prerequisite: uv
123
+ uv run pre-commit install
124
+ uv run nstack fitness # this repository's guardrails
125
+ uv run bash platform/tests/run.sh # the oracle: every guardrail proves it can fail
126
+ uv run nstack init /tmp/trial --source . --ref HEAD # a trial project from the working tree
127
+ uv run nstack doctor --root /tmp/trial # workstation and GitHub settings, read-only
128
+ ```
129
+
130
+ Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md), after [`PRODUCT.md`](PRODUCT.md).
131
+
132
+ Licence: [MIT](LICENSE).
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.1.0"
4
- description = "Framework de travail pour faire travailler plusieurs équipes et leurs agents sur un même dépôt : modules, contrats, garde-fous en CI."
3
+ version = "0.3.0"
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"
7
7
  license-files = ["LICENSE"]
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "napkinstack"
3
- version = "0.1.0"
4
- description = "Framework de travail pour faire travailler plusieurs équipes et leurs agents sur un même dépôt : modules, contrats, garde-fous en CI."
3
+ version = "0.3.0"
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"
7
7
  license-files = ["LICENSE"]
@@ -0,0 +1,5 @@
1
+ """NapkinStack - engine: fitness functions, skills and module scaffolding."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("napkinstack")
@@ -0,0 +1,116 @@
1
+ """Single entry point `nstack` (workstream C1, PDR-0001)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import subprocess
8
+ from pathlib import Path
9
+
10
+ from napkinstack import __version__, discovery, doctor, modules, pull_request, skills
11
+ from napkinstack.fitness import boundaries, manifests, plan
12
+
13
+ PACKAGE = Path(__file__).resolve().parent
14
+
15
+
16
+ def _root(value: str) -> Path:
17
+ root = Path(value).resolve()
18
+ if not root.is_dir():
19
+ raise argparse.ArgumentTypeError(f"root not found: {value}")
20
+ return root
21
+
22
+
23
+ def _script(relative: str, *args: str, root: Path) -> int:
24
+ env = {**os.environ, "NSTACK_ROOT": str(root)}
25
+ return subprocess.run(["bash", str(PACKAGE / relative), *args], cwd=root, env=env).returncode
26
+
27
+
28
+ def _fitness(root: Path) -> int:
29
+ results = [manifests.run(root), boundaries.run(root), skills.run(root, check_only=True), plan.run(root)]
30
+ return 1 if any(results) else 0
31
+
32
+
33
+ def _add(sub, name: str, help_: str, func) -> argparse.ArgumentParser:
34
+ parser = sub.add_parser(name, help=help_)
35
+ parser.add_argument("--root", type=_root, default=Path.cwd(),
36
+ help="project root (default: current folder)")
37
+ parser.set_defaults(func=func)
38
+ return parser
39
+
40
+
41
+ def _init(args: argparse.Namespace) -> int:
42
+ from napkinstack import project # Copier is only loaded for init and update
43
+
44
+ answers = {"project_name": args.project_name, "github_repo": args.github_repo,
45
+ "owner_team": args.owner_team}
46
+ return project.init(args.destination, answers, args.source or project.SOURCE,
47
+ args.ref or project.default_ref())
48
+
49
+
50
+ def _update(args: argparse.Namespace) -> int:
51
+ from napkinstack import project
52
+
53
+ return project.update(args.root, args.ref or project.default_ref())
54
+
55
+
56
+ def build_parser() -> argparse.ArgumentParser:
57
+ parser = argparse.ArgumentParser(prog="nstack", description="NapkinStack engine.")
58
+ parser.add_argument("--version", action="version", version=f"nstack {__version__}")
59
+ sub = parser.add_subparsers(dest="command", required=True, metavar="command")
60
+ _add(sub, "manifests", "manifests, lifecycles, deprecations (M1-M9)",
61
+ lambda a: manifests.run(a.root))
62
+ _add(sub, "boundaries", "declared graph against real graph (B1-B5)",
63
+ lambda a: boundaries.run(a.root))
64
+ _add(sub, "plan", "the discovery, the charter and the cycles (C1-C7)", lambda a: plan.run(a.root))
65
+ sk = _add(sub, "skills", "generates or checks the skills (S1-S4)",
66
+ lambda a: skills.run(a.root, check_only=a.check))
67
+ sk.add_argument("--check", action="store_true", help="check without writing")
68
+ _add(sub, "fitness", "manifests + boundaries + skills + plan",
69
+ lambda a: _fitness(a.root))
70
+ _add(sub, "doctor", "diagnoses the workstation and the GitHub settings, read-only (PDR-0001)",
71
+ lambda a: doctor.run(a.root))
72
+ nm = _add(sub, "new-module", "creates a module and its guardrails, with no imposed stack",
73
+ lambda a: modules.create(a.root, a.name, a.owner, a.criticality, a.user_facing))
74
+ nm.add_argument("name", help="module name, kebab-case")
75
+ nm.add_argument("owner", help="GitHub team, organisation/team, or a user when the project has "
76
+ "no organisation")
77
+ nm.add_argument("criticality", choices=["prototype", "standard", "high", "critical"])
78
+ nm.add_argument("--user-facing", action="store_true",
79
+ help="a user sees this module: its pull requests carry a test sheet")
80
+ for verb, help_text in (("bootstrap", "prepares one module, or all of them (commands.bootstrap)"),
81
+ ("check", "format, lint, types of one module, or all (commands.check)"),
82
+ ("test", "tests of one module, or of all of them (commands.test)"),
83
+ ("e2e", "end-to-end scenarios of one module, or of all (commands.e2e)")):
84
+ vb = _add(sub, verb, help_text, lambda a, v=verb: modules.run_verb(a.root, v, a.module))
85
+ vb.add_argument("module", nargs="?", help="module name (default: all)")
86
+ rn = _add(sub, "run", "starts a module locally (commands.run)",
87
+ lambda a: modules.run_verb(a.root, "run", a.module))
88
+ rn.add_argument("module")
89
+ ps = _add(sub, "pr-scope", "one PR = one module, review budget (P1-P2)",
90
+ lambda a: _script("fitness/pr_scope.sh", a.base, root=a.root))
91
+ ps.add_argument("--base", default="origin/main")
92
+ pc = _add(sub, "pr-check", "test sheet and cycle, read from the pull request description (T1-T5, K1-K4)",
93
+ lambda a: pull_request.run(a.root, a.base, a.body_file))
94
+ pc.add_argument("--base", default="origin/main")
95
+ pc.add_argument("--body-file", type=Path, help="the description, when PR_BODY is not set")
96
+ ds = _add(sub, "discover", "starts a discovery from an idea file, for the team's agent (PDR-0002)",
97
+ lambda a: discovery.run(a.root, a.idea))
98
+ ds.add_argument("idea", type=Path, help="the idea, a .md or .txt file")
99
+ ini = sub.add_parser("init", help="creates a project from the skeleton (PDR-0001)")
100
+ ini.add_argument("destination", type=Path, help="project folder, missing or empty")
101
+ ini.add_argument("--project-name", help="project name (asked when absent)")
102
+ ini.add_argument("--github-repo", help="GitHub repository, organisation/name (asked when absent)")
103
+ ini.add_argument("--owner-team", help="owner of the foundation: organisation/team, or a user "
104
+ "when the project has no organisation (asked when absent)")
105
+ ini.add_argument("--source", help="template: URL or path (default: the NapkinStack repository)")
106
+ ini.add_argument("--ref", help="skeleton version, tag vX.Y.Z (default: the one of nstack)")
107
+ ini.set_defaults(func=_init)
108
+ up = _add(sub, "update", "merges a NapkinStack version onto a branch to review (PDR-0001)",
109
+ _update)
110
+ up.add_argument("--ref", help="target version, tag vX.Y.Z (default: the one of nstack)")
111
+ return parser
112
+
113
+
114
+ def main(argv: list[str] | None = None) -> int:
115
+ args = build_parser().parse_args(argv)
116
+ return args.func(args)
@@ -0,0 +1,53 @@
1
+ """
2
+ nstack discover — starts a project's discovery from an idea file (PDR-0002, extension).
3
+
4
+ Deterministic, no model called: the idea is kept in docs/project/inputs/, the discovery
5
+ document is created from its template, and the instruction for the team's agent is
6
+ printed. The conversation itself happens in the agent (playbooks/discovery.md).
7
+
8
+ Usage : nstack discover <idea-file> [--root ROOT]
9
+ Output: 0 when the discovery is started, 1 otherwise.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import shutil
15
+ from pathlib import Path
16
+
17
+ from napkinstack.fitness.plan import DISCOVERY, PROJECT
18
+
19
+ INPUTS = PROJECT / "inputs"
20
+ TEMPLATE = PROJECT / "_DISCOVERY_TEMPLATE.md"
21
+ SUFFIXES = {".md", ".txt"}
22
+
23
+
24
+ def run(root: Path, idea: Path) -> int:
25
+ if not (root / TEMPLATE).is_file():
26
+ print(f"FAIL [discover] {TEMPLATE} not found in {root}: not a project created by nstack init, "
27
+ "or one older than the discovery.\n Action: run it at the project root, or update "
28
+ "the project (nstack update).")
29
+ return 1
30
+ if not idea.is_file() or idea.suffix.lower() not in SUFFIXES:
31
+ print(f"FAIL [discover] {idea}: a Markdown or text file expected.\n Action: write the idea "
32
+ "in a .md or .txt file — a few lines are enough.")
33
+ return 1
34
+ if (root / DISCOVERY).exists():
35
+ print(f"FAIL [discover] {DISCOVERY} already exists: one discovery per project.\n Action: "
36
+ "continue it in your agent (playbooks/discovery.md), as a new round of the same document.")
37
+ return 1
38
+ kept = root / INPUTS / idea.name
39
+ if kept.exists() and kept.resolve() != idea.resolve():
40
+ print(f"FAIL [discover] {INPUTS / idea.name} already exists.\n Action: rename the idea file.")
41
+ return 1
42
+ kept.parent.mkdir(parents=True, exist_ok=True)
43
+ if kept.resolve() != idea.resolve():
44
+ shutil.copyfile(idea, kept)
45
+ source = (INPUTS / idea.name).as_posix()
46
+ template = (root / TEMPLATE).read_text(encoding="utf-8")
47
+ (root / DISCOVERY).write_text(template.replace("<idea file>", source), encoding="utf-8")
48
+ print(f"Discovery started: {source} kept, {DISCOVERY} created.")
49
+ print("\nNext, in your agent:")
50
+ print(f" Follow playbooks/discovery.md on {source}: interview me, one question at a time.")
51
+ print("Then, in another session, have the document challenged (stage 5); the decider decides: "
52
+ "go, clarify or kill.")
53
+ return 0