napkinstack 0.1.0__py3-none-any.whl

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.
napkinstack/skills.py ADDED
@@ -0,0 +1,159 @@
1
+ """
2
+ Génère les skills Claude Code à partir des playbooks.
3
+
4
+ Les playbooks sont la SOURCE DE VÉRITÉ (docs/os/06-decisions.md §9). Les skills en
5
+ sont dérivées : fichiers générés, jamais édités à la main, jamais commités.
6
+
7
+ Pourquoi générer plutôt qu'écrire directement des skills :
8
+ - portabilité — l'OS doit rester utilisable par un agent qui ne connaît pas les
9
+ skills. Un outil est un adaptateur, jamais une fondation
10
+ (docs/tooling-profile.md).
11
+ - source unique — deux copies d'une même règle divergent toujours.
12
+
13
+ Contrôles (mode --check, exécuté en CI) :
14
+ S1 chaque playbook a une entrée dans .nstack/skills.yaml
15
+ S2 chaque entrée pointe vers un playbook existant
16
+ S3 les skills générées correspondent aux playbooks actuels
17
+ S4 nom et description conformes à la spécification Agent Skills
18
+
19
+ S3 ne s'applique que si .claude/skills/ existe. Les skills sont gitignorées : un clone
20
+ vierge, donc la CI, n'en a aucune, et aucune ne peut y être désynchronisée.
21
+
22
+ Une racine sans playbooks/ ni .nstack/skills.yaml, comme le dépôt NapkinStack lui-même,
23
+ n'est pas concernée.
24
+
25
+ Usage :
26
+ nstack skills [--root RACINE] # génère .claude/skills/
27
+ nstack skills --check [--root RACINE] # vérifie sans écrire
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import hashlib
33
+ import re
34
+ from pathlib import Path
35
+
36
+ import yaml
37
+
38
+ SKILL_NAME = re.compile(r"[a-z0-9]+(-[a-z0-9]+)*")
39
+ BANNER = (
40
+ "<!-- GÉNÉRÉ depuis {source} par nstack skills — NE PAS ÉDITER.\n"
41
+ " Modifier le playbook, puis relancer `nstack skills`. -->"
42
+ )
43
+ MAPPING = Path(".nstack") / "skills.yaml"
44
+
45
+
46
+ def normalise(text: str) -> str:
47
+ """Description sur une seule ligne, pour un frontmatter YAML propre."""
48
+ return " ".join(text.split())
49
+
50
+
51
+ def build(root: Path, name: str, entry: dict) -> tuple[Path, str]:
52
+ body = (root / entry["source"]).read_text(encoding="utf-8")
53
+ # Sérialisé, jamais concaténé : « : » ou « # » dans une description casserait le YAML.
54
+ frontmatter = yaml.safe_dump(
55
+ {"name": name, "description": normalise(entry["description"])},
56
+ allow_unicode=True, sort_keys=False, width=float("inf"),
57
+ )
58
+ content = "---\n" + frontmatter + "---\n\n" + BANNER.format(source=entry["source"]) + "\n\n" + body
59
+ return root / ".claude" / "skills" / name / "SKILL.md", content
60
+
61
+
62
+ def digest(text: str) -> str:
63
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()[:12]
64
+
65
+
66
+ def run(root: Path, check_only: bool = False) -> int:
67
+ map_file = root / MAPPING
68
+ playbook_dir = root / "playbooks"
69
+ skill_dir = root / ".claude" / "skills"
70
+
71
+ if not map_file.is_file():
72
+ if not playbook_dir.is_dir():
73
+ print(f"Skills : non applicable, ni playbooks/ ni {MAPPING} dans {root}.")
74
+ return 0
75
+ print(f" ÉCHEC [S1] {MAPPING} introuvable dans {root} : aucun playbook n'a d'entrée.\n"
76
+ f" Créer {MAPPING}, une entrée par playbook (source, description).")
77
+ return 1
78
+
79
+ try:
80
+ brut = yaml.safe_load(map_file.read_text(encoding="utf-8")) or {}
81
+ except yaml.YAMLError as exc:
82
+ print(f" ÉCHEC [S2] {MAPPING} illisible : {exc}")
83
+ return 1
84
+ mapping = brut.get("skills") if isinstance(brut, dict) else None
85
+ if not isinstance(mapping, dict):
86
+ print(f" ÉCHEC [S2] {MAPPING} : section skills attendue, un dictionnaire nom → source et "
87
+ "description.")
88
+ return 1
89
+ failures: list[str] = [f"[S2] skill '{name}' : entrée invalide, source et description attendues"
90
+ for name, entry in mapping.items() if not isinstance(entry, dict)]
91
+ mapping = {name: entry for name, entry in mapping.items() if isinstance(entry, dict)}
92
+
93
+ # S1 — tout playbook doit avoir une entrée
94
+ declared = {Path(e["source"]).name for e in mapping.values() if e.get("source")}
95
+ for playbook in sorted(playbook_dir.glob("*.md")):
96
+ if playbook.name not in declared:
97
+ failures.append(
98
+ f"[S1] {playbook.relative_to(root)} n'a pas d'entrée dans "
99
+ f"{MAPPING}.\n Ajouter une description, ou retirer le "
100
+ f"playbook s'il ne sert plus (docs/os/10-mesure.md §6)."
101
+ )
102
+
103
+ # S2 — toute entrée doit pointer vers un playbook existant
104
+ for name, entry in mapping.items():
105
+ if not (root / entry.get("source", "")).is_file():
106
+ failures.append(f"[S2] skill '{name}' : source introuvable ({entry.get('source')})")
107
+ if not normalise(entry.get("description", "")):
108
+ failures.append(f"[S2] skill '{name}' : description vide")
109
+
110
+ # S4 — spécification Agent Skills (https://agentskills.io/specification)
111
+ for name, entry in mapping.items():
112
+ if len(name) > 64 or not SKILL_NAME.fullmatch(name):
113
+ failures.append(
114
+ f"[S4] skill '{name}' : nom invalide. 1 à 64 caractères, a-z, 0-9 et "
115
+ "tirets simples, sans tiret au début ni à la fin."
116
+ )
117
+ if len(normalise(entry.get("description", ""))) > 1024:
118
+ failures.append(f"[S4] skill '{name}' : description de plus de 1024 caractères")
119
+
120
+ if failures:
121
+ for failure in failures:
122
+ print(f" ÉCHEC {failure}")
123
+ return 1
124
+
125
+ # S3 — génération ou comparaison
126
+ if check_only and not skill_dir.is_dir():
127
+ print(f"Skills : S1, S2 et S4 conformes. S3 non applicable : "
128
+ f"{skill_dir.relative_to(root)}/ absent, aucune skill générée ici.")
129
+ return 0
130
+
131
+ stale: list[str] = []
132
+ written = 0
133
+ for name, entry in sorted(mapping.items()):
134
+ path, content = build(root, name, entry)
135
+ if check_only:
136
+ if not path.is_file():
137
+ stale.append(f"[S3] skill '{name}' absente de .claude/skills/")
138
+ elif digest(path.read_text(encoding="utf-8")) != digest(content):
139
+ stale.append(f"[S3] skill '{name}' désynchronisée de {entry['source']}")
140
+ else:
141
+ path.parent.mkdir(parents=True, exist_ok=True)
142
+ path.write_text(content, encoding="utf-8")
143
+ written += 1
144
+
145
+ if check_only:
146
+ if stale:
147
+ for item in stale:
148
+ print(f" ÉCHEC {item}")
149
+ print("\nLancer `nstack skills` pour régénérer.")
150
+ return 1
151
+ print(f"Skills : {len(mapping)} synchronisées avec les playbooks.")
152
+ return 0
153
+
154
+ print(f"Skills générées dans .claude/skills/ : {written}")
155
+ for name in sorted(mapping):
156
+ print(f" - {name}")
157
+ print("\nElles se déclenchent seules selon leur description ; l'agent peut aussi")
158
+ print("les invoquer par leur nom. Le kernel reste dans AGENTS.md.")
159
+ return 0
@@ -0,0 +1,36 @@
1
+ # {{MODULE_NAME}} — instructions locales
2
+
3
+ > Uniquement ce qui est **spécifique à ce module**.
4
+ > Ne jamais dupliquer une règle du kernel (`/AGENTS.md`) : c'est du contexte gaspillé
5
+ > et une source de divergence.
6
+ >
7
+ > Budget indicatif : 100 lignes.
8
+
9
+ ## Responsabilité
10
+
11
+ <Une phrase. Identique à celle du MANIFEST.>
12
+
13
+ ## Ce que ce module ne fait pas
14
+
15
+ <Les confusions probables, et vers quel module renvoyer.>
16
+
17
+ ## Conventions internes non devinables
18
+
19
+ <Nommage, organisation, choix qui surprendraient quelqu'un d'extérieur.>
20
+
21
+ ## Invariants métier
22
+
23
+ <Ce qui doit rester vrai en toutes circonstances. Idéalement, chacun a un test.>
24
+
25
+ ## Pièges connus
26
+
27
+ <Ce qui a déjà cassé ici. Chaque entrée devrait devenir un test — voir
28
+ docs/os/10-mesure.md §4.>
29
+
30
+ ## Zones à ne pas modifier sans validation
31
+
32
+ <Quoi, et pourquoi.>
33
+
34
+ ## Commandes non standards
35
+
36
+ <Uniquement ce qui s'écarte des verbes du MANIFEST.>
@@ -0,0 +1,77 @@
1
+ # Source de vérité machine-lisible du module.
2
+ # Lu par : les humains (onboarding), les agents (scoping), la CI (fitness functions).
3
+ # Toute divergence entre ce fichier et le code fait échouer la CI.
4
+
5
+ module:
6
+ name: "{{MODULE_NAME}}"
7
+
8
+ # UNE phrase. Si elle nécessite un "et", le module fait probablement deux choses.
9
+ responsibility: >
10
+ TODO — décrire la capacité métier de ce module en une phrase.
11
+
12
+ owner: "{{OWNER}}" # une ÉQUIPE GitHub organisation/équipe, jamais un individu
13
+ contact: "@{{OWNER}}"
14
+
15
+ # Proposé | Actif | Maintenance | Déprécié | Retiré (docs/os/02-modules.md §6)
16
+ lifecycle: Proposé
17
+
18
+ # prototype | standard | eleve | critique (docs/os/07-gouvernance.md §6)
19
+ criticality: "{{CRITICALITY}}"
20
+
21
+ # Nom du module dans le code, s'il diffère du dossier (package, namespace).
22
+ # Utilisé par la détection de frontières.
23
+ # code_name: {{MODULE_NAME}}
24
+
25
+ # Uniquement si lifecycle = Déprécié. Check rouge si la date est dépassée.
26
+ # deprecation:
27
+ # replaced_by:
28
+ # removal_date: AAAA-MM-JJ
29
+
30
+ # Contrats PRODUITS par ce module.
31
+ provides: []
32
+ # - contract: {{MODULE_NAME}}-api
33
+ # version: v1
34
+ # path: contracts/{{MODULE_NAME}}-api/v1
35
+ # stability: experimental # experimental | stable | deprecated
36
+ # # removal_date: AAAA-MM-JJ # obligatoire si stability = deprecated
37
+
38
+ # Contrats CONSOMMÉS — c'est le graphe DÉCLARÉ.
39
+ # La CI le compare au graphe RÉEL extrait du code : tout écart = violation.
40
+ consumes: []
41
+ # - contract: identity-api
42
+ # version: v1
43
+ # module: identity
44
+
45
+ # Données possédées. Personne d'autre n'y accède directement.
46
+ data:
47
+ owns: []
48
+ shared: [] # toute exception exige un ADR nommé
49
+
50
+ # Dépendances externes significatives.
51
+ # Chacune déclare sa stratégie de sortie (docs/os/06-decisions.md §3).
52
+ dependencies: []
53
+ # - name:
54
+ # purpose:
55
+ # adr:
56
+ # exit_strategy:
57
+
58
+ # Verbes standards : mêmes noms partout, commandes de la stack du module
59
+ # (docs/os/09-plateforme.md §2). `nstack <verbe> <module>` les exécute depuis ce dossier.
60
+ # Aucune stack n'est imposée : remplacer chaque commande « à déclarer ».
61
+ commands:
62
+ check: "echo 'commands.check à déclarer dans MANIFEST.yaml' >&2; exit 1" # < 2 min : format, lint, types
63
+ test: "echo 'commands.test à déclarer dans MANIFEST.yaml' >&2; exit 1" # ne démarre JAMAIS un autre module
64
+ # bootstrap: # facultatif : rendre le module exploitable depuis un clone vierge
65
+ # run: # facultatif : démarrage local, avec doublures pour les dépendances
66
+
67
+ # Budget de revue. Hérite du projet si absent (docs/os/05-workflow.md §4).
68
+ review_budget:
69
+ max_lines: 400
70
+ max_files: 15
71
+ max_modules: 1 # non ajustable
72
+
73
+ docs:
74
+ readme: README.md
75
+ agents: AGENTS.md
76
+ adr: docs/adr/
77
+ # runbook: docs/runbook.md # obligatoire si criticality >= eleve
@@ -0,0 +1,27 @@
1
+ # {{MODULE_NAME}}
2
+
3
+ <Une phrase : à quoi sert ce module.>
4
+
5
+ - **Owner** : {{OWNER}}
6
+ - **Criticité** : {{CRITICALITY}}
7
+ - **Manifest** : [MANIFEST.yaml](./MANIFEST.yaml)
8
+
9
+ ## Démarrer
10
+
11
+ ```bash
12
+ nstack bootstrap {{MODULE_NAME}} # depuis un clone vierge, si déclaré
13
+ nstack check {{MODULE_NAME}} # format, lint, types — < 2 min
14
+ nstack test {{MODULE_NAME}} # ne démarre aucun autre module
15
+ nstack run {{MODULE_NAME}} # local, avec doublures pour les dépendances
16
+ ```
17
+
18
+ Commandes : section `commands` du MANIFEST, à déclarer pour la stack du module.
19
+
20
+ ## Contrats
21
+
22
+ - Produits : voir `provides` dans le MANIFEST
23
+ - Consommés : voir `consumes` dans le MANIFEST
24
+
25
+ ## Décisions
26
+
27
+ Voir `docs/adr/`.
File without changes
File without changes
File without changes
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: napkinstack
3
+ Version: 0.1.0
4
+ Summary: Framework de travail pour faire travailler plusieurs équipes et leurs agents sur un même dépôt : modules, contrats, garde-fous en 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
+ Framework de travail pour faire travailler plusieurs équipes et leurs agents sur un même
19
+ dépôt : modules, contrats, garde-fous en CI. Sur le modèle de Django ou Rails, une commande
20
+ crée le projet, qui reçoit ensuite les nouvelles versions à sa demande ; aucune stack
21
+ applicative n'est imposée. Positionnement et vocabulaire : [`PRODUCT.md`](PRODUCT.md) §1.
22
+
23
+ > **État : en construction (v0.1.0).** Toutes les commandes fonctionnent depuis ce dépôt ;
24
+ > la première version publiée sur PyPI arrive au chantier M5, avant un premier projet
25
+ > pilote. Suivi : [feuille de route](docs/governance/plans/2026-09-15-moteur-v0.1.0.md),
26
+ > [`docs/governance/chantiers.md`](docs/governance/chantiers.md).
27
+
28
+ ## Le parcours d'un projet
29
+
30
+ ```mermaid
31
+ flowchart LR
32
+ I["Installer<br/>uv tool install"]:::cmd --> N["nstack init"]:::cmd
33
+ N --> G["Publier sur GitHub<br/>appliquer la checklist"]:::humain
34
+ G --> D["nstack doctor<br/>lecture seule"]:::cmd
35
+ D --> M["nstack new-module"]:::cmd
36
+ M --> W["Travail en PR<br/>l'équipe et son agent"]:::humain
37
+ W --> U["nstack update<br/>branche fusionnée"]:::cmd
38
+ U --> P["PR relue<br/>validée par la CI"]:::humain
39
+ P -->|"version suivante"| U
40
+
41
+ classDef cmd fill:#1f2937,color:#fff
42
+ classDef humain fill:#065f46,color:#fff
43
+ ```
44
+
45
+ **Légende** — gris : commande NapkinStack · vert : action de l'équipe. Décision :
46
+ [PDR-0001](docs/pdr/0001-creer-un-projet-et-recevoir-les-evolutions.md).
47
+
48
+ Le projet possède son squelette et l'adapte librement. Chaque nouvelle version lui arrive
49
+ à sa demande, fusionnée avec ses adaptations ; les conflits restent à l'équipe.
50
+
51
+ ```mermaid
52
+ flowchart LR
53
+ V1["Squelette v0.1<br/>base commune"]:::ref --> F{"Fusion<br/>à 3 voies"}
54
+ V2["Squelette v0.2<br/>correctifs NapkinStack"]:::ns --> F
55
+ PR["Projet<br/>adaptations de l'équipe"]:::equipe --> F
56
+ F -->|"lignes différentes"| B["Branche de mise à jour<br/>correctifs + adaptations"]:::ok
57
+ F -->|"même ligne modifiée"| X["Conflit marqué<br/>commit refusé"]:::ko
58
+
59
+ classDef ref fill:#374151,color:#fff
60
+ classDef ns fill:#1e3a8a,color:#fff
61
+ classDef equipe fill:#065f46,color:#fff
62
+ classDef ok fill:#065f46,color:#fff
63
+ classDef ko fill:#7c2d12,color:#fff
64
+ ```
65
+
66
+ **Légende** — gris : version dont le projet est issu · bleu : nouvelle version · vert :
67
+ travail de l'équipe et résultat accepté · rouge : conflit laissé à l'équipe.
68
+
69
+ ## Installer
70
+
71
+ ```bash
72
+ uv tool install napkinstack --with-executables-from pre-commit # prérequis : uv et git
73
+ nstack init mon-projet
74
+ ```
75
+
76
+ Disponible dès la publication de la v0.1.0 sur PyPI ; chaque projet épingle ensuite sa
77
+ version et la change par `nstack update`.
78
+
79
+ ## Les commandes
80
+
81
+ | Commande | Rôle |
82
+ |---|---|
83
+ | `nstack init <dossier>` | Crée le projet : squelette, dépôt git, commit initial, checklist GitHub |
84
+ | `nstack doctor` | Vérifie le poste et les réglages GitHub, en lecture seule |
85
+ | `nstack new-module <nom> <organisation>/<équipe> <criticité>` | Crée un module, sans stack imposée |
86
+ | `nstack check`, `test`, `bootstrap` `[module]` ; `nstack run <module>` | Exécutent les commandes déclarées dans le manifest du module |
87
+ | `nstack fitness` | Manifests, frontières entre modules, skills |
88
+ | `nstack pr-scope` | Une PR = un module, budget de revue |
89
+ | `nstack skills` | Expose les playbooks en skills pour l'agent |
90
+ | `nstack update` | Pose la nouvelle version sur une branche à relire |
91
+
92
+ **Prérequis** : uv et git. Les garde-fous bloquent vraiment sur un dépôt GitHub public, ou
93
+ privé sous l'offre Team ou Pro ; sur un dépôt privé de l'offre Free, la CI informe sans
94
+ bloquer ([précision de PDR-0001](docs/pdr/0001-creer-un-projet-et-recevoir-les-evolutions.md)).
95
+
96
+ **IA** : NapkinStack n'en embarque aucune. L'agent de l'équipe (Claude Code, Codex,
97
+ Copilot…) lit le kernel et les playbooks, lance les commandes, et la CI accepte ou refuse
98
+ ses propositions comme celles de n'importe quel contributeur.
99
+
100
+ ## Ce dépôt
101
+
102
+ ```mermaid
103
+ flowchart LR
104
+ S["skeleton/<br/>squelette de projet"]:::livre -->|"copier.yml"| P["Projet d'une équipe"]:::projet
105
+ E["src/napkinstack/<br/>moteur nstack"]:::livre -.->|"version épinglée"| P
106
+ A["PRODUCT.md · docs/governance/<br/>platform/ · CI du dépôt"]:::interne
107
+
108
+ classDef livre fill:#1e3a8a,color:#fff
109
+ classDef projet fill:#065f46,color:#fff
110
+ classDef interne fill:#374151,color:#fff
111
+ ```
112
+
113
+ **Légende** — bleu : livré aux projets · vert : projet généré, qui possède son squelette ·
114
+ gris : développement de NapkinStack, jamais copié (PDR-0001 R6). Trait plein : génération ;
115
+ pointillé : dépendance versionnée.
116
+
117
+ | Chemin | Rôle |
118
+ |---|---|
119
+ | `skeleton/` | Ce que reçoit chaque projet : kernel, playbooks, manuel, CI, hooks |
120
+ | `copier.yml` | Questions posées à la création (gabarit Copier, ADR-0001) |
121
+ | `src/napkinstack/` | Le moteur, commande `nstack` |
122
+ | `platform/` | Enveloppe du module moteur : manifest, runbook, tests |
123
+ | `PRODUCT.md`, `docs/governance/` | Contexte de travail sur NapkinStack |
124
+ | `docs/adr/`, `docs/pdr/` | Décisions de NapkinStack |
125
+
126
+ ## Développer NapkinStack
127
+
128
+ ```bash
129
+ uv sync # prérequis : uv
130
+ uv run pre-commit install
131
+ uv run nstack fitness # garde-fous du dépôt
132
+ uv run bash platform/tests/run.sh # oracle : chaque garde-fou prouve qu'il sait échouer
133
+ uv run nstack init /tmp/essai --source . --ref HEAD # projet d'essai depuis l'arbre de travail
134
+ uv run nstack doctor --root /tmp/essai # poste et réglages GitHub, en lecture seule
135
+ ```
136
+
137
+ Contribuer : [`CONTRIBUTING.md`](CONTRIBUTING.md), après [`PRODUCT.md`](PRODUCT.md).
138
+
139
+ Licence : [MIT](LICENSE).
@@ -0,0 +1,21 @@
1
+ napkinstack/__init__.py,sha256=NzkIDEWzZjyddt3O4Xo0SfjmzJ5sFv109T84dk4VnDY,158
2
+ napkinstack/cli.py,sha256=wd77mKByEwaV38NG-HR0414Y2_3f_q9xanNzJpBUzMc,5072
3
+ napkinstack/doctor.py,sha256=Aj-X97_1KBotW90farXRE_eyIvsWxa92iNIDC1k_I_4,13573
4
+ napkinstack/fitness/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ napkinstack/fitness/boundaries.py,sha256=VybKCHpnFAdfFpHVSawJGsUF4NVXsVahK2qtO2Y2we4,8654
6
+ napkinstack/fitness/manifests.py,sha256=UkW9r97EzF6BKW8BE6ji7Pi8jNlHErqDa2graVV3QCE,8024
7
+ napkinstack/fitness/pr_scope.sh,sha256=qozdju-j9v0I1fL6qHDqEXprO0tGSklSRys1T40z-N0,3026
8
+ napkinstack/modules.py,sha256=HnBzyhp9vjeYz4xX7p6Rh46NoFKapVC0NypLC6fy8mw,5813
9
+ napkinstack/project.py,sha256=0mFruUQu6Ny1hfP4UJWI7zOk85fp1hxufiliDCu9sso,8686
10
+ napkinstack/skills.py,sha256=eyrBug-g6dzHXS6pt1Gy8GiBV_m1kLBsnMa4eaUo240,6743
11
+ napkinstack/templates/module/AGENTS.md,sha256=MriXXCiu6wrpCmMm20SQXZY-TRaBtgoOGnMNbvnjwuk,906
12
+ napkinstack/templates/module/MANIFEST.yaml,sha256=SbpHi49LEWFQ9slQCrbhxmqW640E5Sq49Z8qn0O3RQw,2910
13
+ napkinstack/templates/module/README.md,sha256=NTVy4yGEk6pTYZtZkYZEKANRz-KzwftU2MAG_9sj6YY,716
14
+ napkinstack/templates/module/docs/adr/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ napkinstack/templates/module/src/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ napkinstack/templates/module/tests/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ napkinstack-0.1.0.dist-info/licenses/LICENSE,sha256=EJcqdCHOa6dE67xGECfIbP-_0AmafpbAWjlQyqO--T0,1068
18
+ napkinstack-0.1.0.dist-info/WHEEL,sha256=_d8F1e7SqtoW6CDj4Gi8lFC26a_7I17R7zPLCKTp4Fg,81
19
+ napkinstack-0.1.0.dist-info/entry_points.txt,sha256=zUNcg3_nsshrzjGhv-DRdegu5Eb-rha_4DckESq3NkU,49
20
+ napkinstack-0.1.0.dist-info/METADATA,sha256=k3L9a8WwKa0ZBti9saxzbtcmipH19g7qXUAycFAJvPQ,6321
21
+ napkinstack-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.15
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ nstack = napkinstack.cli:main
3
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NapkinStack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.