dna-cli 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.
dna_cli/testkit_cmd.py ADDED
@@ -0,0 +1,352 @@
1
+ """`dna sdlc test-guide` + `dna sdlc test-run` — CLI for the testkit Kinds.
2
+
3
+ Phase C of the TESTS-as-first-class-SDLC work. Registers two subgroups on the
4
+ existing ``sdlc`` group (kept in its own module so the 7k-line sdlc_cmd.py
5
+ god-file doesn't grow):
6
+
7
+ - ``dna sdlc test-guide create`` — author a TestGuide (manual, or stub the
8
+ steps from a Story's acceptance_criteria via ``--from-ac``).
9
+ - ``dna sdlc test-run record`` — record a TestRun execution. This is the
10
+ FOCUS-visible moment: it stamps an ``artifact_produced`` event on each
11
+ verified Story's timeline + appends to its ``produces[]`` hub, so the run
12
+ shows in FOCUS and lights the journey's ``verify`` phase.
13
+
14
+ The ``story done`` test-gate WARNING lives inline in sdlc_cmd.py (avoids an
15
+ import cycle); ``passing_run_for_story`` here is the shared predicate.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from typing import Any
20
+
21
+ import click
22
+
23
+ from dna_cli._ctx import dna_session, fail
24
+ from dna_cli.sdlc_cmd import (
25
+ DEFAULT_SCOPE,
26
+ _append_produces,
27
+ _append_timeline,
28
+ _cli_actor,
29
+ _now_iso,
30
+ _scope_option,
31
+ sdlc,
32
+ )
33
+
34
+ TESTKIT_API_VERSION = "github.com/ruinosus/dna/testkit/v1"
35
+
36
+ _TEST_KINDS = ["manual", "smoke", "e2e", "regression", "integration"]
37
+ _OUTCOMES = ["pass", "fail", "partial", "blocked"]
38
+
39
+
40
+ def _build_testkit_raw(kind: str, name: str, spec: dict[str, Any]) -> dict[str, Any]:
41
+ return {
42
+ "apiVersion": TESTKIT_API_VERSION,
43
+ "kind": kind,
44
+ "metadata": {"name": name},
45
+ "spec": spec,
46
+ }
47
+
48
+
49
+ def _ac_text(ac: Any) -> str:
50
+ """Acceptance-criteria entries are either ``{"text": ...}`` dicts or bare
51
+ strings — normalize to the text."""
52
+ if isinstance(ac, dict):
53
+ return str(ac.get("text") or ac.get("criterion") or "").strip()
54
+ return str(ac or "").strip()
55
+
56
+
57
+ def _ref_story_name(ref: str) -> str | None:
58
+ """``Story/s-x`` → ``s-x``; a bare ``s-x`` passes through; non-Story refs
59
+ (Issue/...) return None (only Stories carry the produces/timeline hub here)."""
60
+ ref = ref.strip()
61
+ if "/" in ref:
62
+ kind, _, nm = ref.partition("/")
63
+ return nm if kind == "Story" else None
64
+ return ref or None
65
+
66
+
67
+ # ── passing-run predicate (shared with the story-done gate) ─────────────────
68
+
69
+ def passing_run_for_story(scope: str, story_name: str) -> str | None:
70
+ """Return the name of a PRODUCT-lane TestRun with outcome=pass that verifies
71
+ this Story, or None.
72
+
73
+ s-testkit-done-requires-product-smoke: the ``story done`` gate counts only
74
+ the HUMAN product lane (a TestRun whose guide is ``smoke`` or ``manual``).
75
+ The automated lane (integration/e2e/regression) is proven by CI on the PR,
76
+ not by a hand-recorded run — so an integration ``pass`` does NOT satisfy the
77
+ gate. A run whose guide can't be resolved is treated as non-product (the
78
+ gate stays honest)."""
79
+ from dna.extensions.testkit import PRODUCT_TEST_KINDS
80
+
81
+ ref = f"Story/{story_name}"
82
+ try:
83
+ with dna_session(scope) as s:
84
+ runs = s.query_list("TestRun")
85
+ guides = s.query_list("TestGuide")
86
+ except Exception: # noqa: BLE001 — gate is best-effort
87
+ return None
88
+ product_guides = {
89
+ g.name for g in guides
90
+ if str(((g.spec if isinstance(getattr(g, "spec", None), dict) else {}) or {}).get("kind_of_test"))
91
+ in PRODUCT_TEST_KINDS
92
+ }
93
+ for r in runs:
94
+ sp = r.spec if isinstance(getattr(r, "spec", None), dict) else {}
95
+ if str(sp.get("outcome")) != "pass":
96
+ continue
97
+ verifies = sp.get("verifies") or []
98
+ if ref not in verifies and story_name not in verifies:
99
+ continue
100
+ if sp.get("guide_ref") in product_guides:
101
+ return r.name
102
+ return None
103
+
104
+
105
+ # ── test-guide ──────────────────────────────────────────────────────────────
106
+
107
+ def _step_stub(ac: str, *, product: bool) -> dict[str, str]:
108
+ """Stub one step from an acceptance criterion.
109
+
110
+ s-testkit-product-guide-authoring: ``--product`` makes it UI-first and
111
+ leigo-proof — an action a non-dev can do in the Studio, a ``where`` route
112
+ placeholder, and an ``expected`` that describes the CORRECT behavior. The
113
+ tester marks ✗ ONLY if the product is actually broken — a product smoke is
114
+ never authored to force a failure."""
115
+ if product:
116
+ return {
117
+ "action": f"No Studio, valide: {ac}",
118
+ "where": "<rota/tela — ex. /scopes/:scope/...>",
119
+ "expected": "<o que você VÊ quando está certo (marque ✗ SÓ se estiver quebrado)>",
120
+ }
121
+ return {"action": f"Validar: {ac}", "expected": "<descreva o resultado esperado>"}
122
+
123
+
124
+ @sdlc.group("test-guide")
125
+ def test_guide_group() -> None:
126
+ """Test guides (roteiros) — declarative test scripts that verify work items."""
127
+
128
+
129
+ @test_guide_group.command("create")
130
+ @click.argument("name")
131
+ @click.option("--description", default=None, help="What this guide validates.")
132
+ @click.option("--kind-of-test", "kind_of_test", type=click.Choice(_TEST_KINDS),
133
+ default="manual", show_default=True)
134
+ @click.option("--product", is_flag=True,
135
+ help="Scaffold a UI-first PRODUCT smoke: forces kind_of_test=smoke and "
136
+ "(with --from-ac) generates leigo-proof steps with a 'where' route + "
137
+ "observable 'expected'. The tester marks ✗ ONLY if the product is "
138
+ "broken — never author a step that forces a failure.")
139
+ @click.option("--verifies", multiple=True,
140
+ help="Work item this guide verifies, e.g. 'Story/s-x' (repeatable).")
141
+ @click.option("--from-ac", "from_ac", default=None,
142
+ help="Story name: stub one step per acceptance_criteria (you fill 'expected').")
143
+ @click.option("--step", "steps_in", multiple=True,
144
+ help="A step as 'action :: expected' (repeatable).")
145
+ @click.option("--owner", default=None, help="Actor who owns this guide.")
146
+ @_scope_option
147
+ def cmd_test_guide_create(
148
+ name: str, description: str | None, kind_of_test: str, product: bool,
149
+ verifies: tuple[str, ...], from_ac: str | None,
150
+ steps_in: tuple[str, ...], owner: str | None, scope: str,
151
+ ) -> None:
152
+ """Create a TestGuide. Manual steps via --step, or stub them from a Story's
153
+ acceptance_criteria via --from-ac (you then fill in each 'expected'). Pass
154
+ --product to scaffold a UI-first product smoke (the lane the done-gate counts)."""
155
+ if product:
156
+ kind_of_test = "smoke" # --product is the product lane
157
+ steps: list[dict[str, str]] = []
158
+ for raw in steps_in:
159
+ action, sep, expected = raw.partition("::")
160
+ steps.append({"action": action.strip(), "expected": expected.strip() if sep else ""})
161
+
162
+ verifies_list = list(verifies)
163
+ if from_ac:
164
+ with dna_session(scope) as s:
165
+ story = s.get_doc("Story", from_ac)
166
+ if story is None:
167
+ raise fail(f"--from-ac: Story '{from_ac}' não encontrada em {scope!r}.")
168
+ sp = story.spec if isinstance(story.spec, dict) else {}
169
+ acs = [_ac_text(a) for a in (sp.get("acceptance_criteria") or [])]
170
+ acs = [a for a in acs if a]
171
+ if not acs:
172
+ click.secho(f"⚠ Story '{from_ac}' não tem acceptance_criteria — guide criado sem steps stub.", fg="yellow")
173
+ for a in acs:
174
+ steps.append(_step_stub(a, product=product))
175
+ sref = f"Story/{from_ac}"
176
+ if sref not in verifies_list:
177
+ verifies_list.append(sref)
178
+ if description is None:
179
+ description = f"Roteiro de teste de {from_ac} (derivado das acceptance_criteria)."
180
+
181
+ if not steps:
182
+ raise fail("nenhum step — passe --step 'ação :: esperado' ou --from-ac <story>.")
183
+ if description is None:
184
+ raise fail("--description é obrigatório (ou use --from-ac pra derivar).")
185
+
186
+ now = _now_iso()
187
+ spec: dict[str, Any] = {
188
+ "description": description,
189
+ "kind_of_test": kind_of_test,
190
+ "status": "active",
191
+ "steps": steps,
192
+ "verifies": verifies_list,
193
+ "created_at": now,
194
+ "updated_at": now,
195
+ }
196
+ if owner:
197
+ spec["owner"] = owner
198
+
199
+ with dna_session(scope) as s:
200
+ raw = _build_testkit_raw("TestGuide", name, spec)
201
+ s.run(s.kernel.write_document(scope, "TestGuide", name, raw))
202
+ click.secho(f"CREATED TestGuide/{name} ({kind_of_test}, {len(steps)} steps) in {scope}", fg="green")
203
+ if verifies_list:
204
+ click.secho(f" verifies: {', '.join(verifies_list)}", fg="cyan")
205
+ # i-100 — surface the guide on each verified Story's produces[] too, so
206
+ # FOCUS shows the guide alongside its run (symmetric with test-run record).
207
+ _stamp_verified_stories(
208
+ scope, verifies_list,
209
+ produced_kind="TestGuide", produced_name=name, role="test-guide",
210
+ timeline_summary=f"TestGuide {name} ({kind_of_test})",
211
+ )
212
+
213
+
214
+ def _build_screenshot_refs(upload_results: list[dict[str, Any]]) -> list[dict[str, str]]:
215
+ """Map raw ``/assets/upload`` responses → ``TestRun.spec.screenshots[]`` refs.
216
+
217
+ Each ref is ``{asset, mime, blob}`` (asset name + mime + blob_path) — the
218
+ blob is required to build the Studio image URL
219
+ (``/scopes/{scope}/docs/Asset/{asset}/files/{blob}``). Asset-backed, NOT
220
+ inline base64. Pure + side-effect-free so it's unit-testable without HTTP.
221
+ """
222
+ refs: list[dict[str, str]] = []
223
+ for r in upload_results:
224
+ refs.append({
225
+ "asset": r["name"],
226
+ "mime": r.get("mime") or "application/octet-stream",
227
+ "blob": r.get("blob_path") or "blob.bin",
228
+ })
229
+ return refs
230
+
231
+
232
+ # ── test-run ──────────────────────────────────────────────────────────────
233
+
234
+ @sdlc.group("test-run")
235
+ def test_run_group() -> None:
236
+ """Test runs — execution records of a TestGuide (the verify-phase signal)."""
237
+
238
+
239
+ @test_run_group.command("record")
240
+ @click.argument("guide")
241
+ @click.option("--outcome", type=click.Choice(_OUTCOMES), required=True)
242
+ @click.option("--by", "executed_by", default=None, help="Actor who ran it (default: CLI actor).")
243
+ @click.option("--note", "notes", default=None, help="Free-text notes on the run.")
244
+ @click.option("--name", "run_name", default=None,
245
+ help="Run doc name (default: tr-<guide>-<timestamp>).")
246
+ @click.option("--evidence", multiple=True,
247
+ help="Ref/link backing the outcome, e.g. 'HtmlArtifact/ha-x' (repeatable).")
248
+ @click.option("--screenshot", "screenshots", multiple=True,
249
+ type=click.Path(exists=True, dir_okay=False),
250
+ help="Print de evidência (imagem). Repetível. Uploadado como Asset.")
251
+ @_scope_option
252
+ def cmd_test_run_record(
253
+ guide: str, outcome: str, executed_by: str | None, notes: str | None,
254
+ run_name: str | None, evidence: tuple[str, ...],
255
+ screenshots: tuple[str, ...], scope: str,
256
+ ) -> None:
257
+ """Record a TestRun for a TestGuide. Inherits the guide's `verifies`, then
258
+ stamps each verified Story (artifact_produced timeline event + produces[]) —
259
+ so the run shows in FOCUS and lights the journey's `verify` phase."""
260
+ with dna_session(scope) as s:
261
+ gdoc = s.get_doc("TestGuide", guide)
262
+ if gdoc is None:
263
+ raise fail(f"TestGuide '{guide}' não encontrado em {scope!r}. Crie com `dna sdlc test-guide create`.")
264
+ gspec = gdoc.spec if isinstance(gdoc.spec, dict) else {}
265
+ verifies = list(gspec.get("verifies") or [])
266
+
267
+ now = _now_iso()
268
+ if not run_name:
269
+ run_name = f"tr-{guide}-{now[:19].replace(':', '').replace('-', '').replace('T', '-')}"
270
+ actor = executed_by or _cli_actor()
271
+ spec: dict[str, Any] = {
272
+ "guide_ref": guide,
273
+ "outcome": outcome,
274
+ "verifies": verifies,
275
+ "executed_by": actor,
276
+ "executed_at": now,
277
+ }
278
+ if notes:
279
+ spec["notes"] = notes
280
+ if evidence:
281
+ spec["evidence"] = list(evidence)
282
+
283
+ # s-testrun-cli-screenshot: upload each print as an Asset (NOT base64
284
+ # inline), then reference it on spec.screenshots[] so the Studio can build
285
+ # the blob URL and render it with a lightbox.
286
+ if screenshots:
287
+ click.secho(
288
+ "⚠ --screenshot requires the Asset upload service — not "
289
+ "available in this kernel-local distribution; recording the "
290
+ "run without screenshots (use --evidence to reference files).",
291
+ fg="yellow", err=True,
292
+ )
293
+
294
+ with dna_session(scope) as s:
295
+ raw = _build_testkit_raw("TestRun", run_name, spec)
296
+ s.run(s.kernel.write_document(scope, "TestRun", run_name, raw))
297
+ color = "green" if outcome == "pass" else ("red" if outcome == "fail" else "yellow")
298
+ click.secho(f"RECORDED TestRun/{run_name} → {outcome} (guide {guide})", fg=color)
299
+
300
+ # FOCUS + verify-phase: stamp each verified Story.
301
+ _stamp_verified_stories(
302
+ scope, verifies,
303
+ produced_kind="TestRun", produced_name=run_name, role="test-run",
304
+ timeline_summary=f"TestRun {run_name} → {outcome}", outcome=outcome,
305
+ )
306
+
307
+
308
+ def _stamp_verified_stories(
309
+ scope: str, verifies: list[str], *,
310
+ produced_kind: str, produced_name: str, role: str,
311
+ timeline_summary: str, **timeline_extra: Any,
312
+ ) -> None:
313
+ """Stamp each verified Story's ``produces[]`` + an ``artifact_produced``
314
+ timeline event, so the artifact shows in FOCUS and lights the verify phase.
315
+
316
+ i-100 — shared by ``test-guide create`` (role=test-guide) and ``test-run
317
+ record`` (role=test-run) so a Story surfaces BOTH its guide and its run.
318
+ Best-effort: a stamping miss never fails the underlying create/record.
319
+ """
320
+ for ref in verifies:
321
+ story_name = _ref_story_name(ref)
322
+ if not story_name:
323
+ continue
324
+ try:
325
+ with dna_session(scope) as s:
326
+ story = s.get_doc("Story", story_name)
327
+ if story is None:
328
+ continue
329
+ sp = dict(story.spec) if isinstance(story.spec, dict) else {}
330
+ _append_produces(sp, produced_kind, produced_name, role=role)
331
+ _append_timeline(
332
+ sp, "artifact_produced",
333
+ kind=produced_kind, name=produced_name,
334
+ summary=timeline_summary, **timeline_extra,
335
+ )
336
+ sp["updated_at"] = _now_iso()
337
+ s.run(s.kernel.write_document(scope, "Story", story_name, _build_story_raw(story_name, sp)))
338
+ click.secho(f" → Story/{story_name}: produces + artifact_produced (FOCUS + verify)", fg="cyan")
339
+ except Exception as e: # noqa: BLE001 — stamping is best-effort
340
+ click.secho(f" ⚠ não consegui carimbar Story/{story_name}: {e}", fg="yellow", err=True)
341
+
342
+
343
+ def _build_story_raw(name: str, spec: dict[str, Any]) -> dict[str, Any]:
344
+ # Story envelope uses the SDLC apiVersion (not testkit's).
345
+ from dna_cli.sdlc_cmd import _build_raw
346
+ return _build_raw("Story", name, spec)
347
+
348
+
349
+ def register() -> None:
350
+ """No-op: importing this module registers the groups on ``sdlc`` via the
351
+ decorators above. Kept so callers can ``from . import testkit_cmd`` with
352
+ an explicit, greppable intent."""
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: dna-cli
3
+ Version: 0.1.0
4
+ Summary: DNA command-line interface — the `dna` binary for the declarative SDLC + document CRUD (kernel-local, no service required)
5
+ Project-URL: Homepage, https://ruinosus.github.io/dna/
6
+ Project-URL: Repository, https://github.com/ruinosus/dna
7
+ Project-URL: Documentation, https://ruinosus.github.io/dna/reference/cli/
8
+ Project-URL: Changelog, https://github.com/ruinosus/dna/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/ruinosus/dna/issues
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,cli,declarative,dna,manifests,sdlc
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: <3.14,>=3.12
18
+ Requires-Dist: click>=8.1.0
19
+ Requires-Dist: dna-sdk<0.2,>=0.1
20
+ Requires-Dist: pyyaml>=6.0
21
+ Requires-Dist: rich>=13.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: dna-sdk[search-sqlite]; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # dna-cli
28
+
29
+ The `dna` command-line interface for **DNA — Domain Notation of Anything**:
30
+ document CRUD (`dna doc`, `dna kind`, `dna scope`, `dna source`), semantic
31
+ recall and memory (`dna recall`, `dna memory`), research syntheses
32
+ (`dna research`), and a declarative, story-first SDLC (`dna sdlc`) — all
33
+ kernel-local against your filesystem or SQL source, no service required.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install dna-cli # or: uv tool install dna-cli
39
+ ```
40
+
41
+ Pre-release / exact-pin alternative — from the repo:
42
+
43
+ ```bash
44
+ cd packages/sdk-py && uv venv && uv pip install -e ".[dev]" -e ../cli
45
+ ```
46
+
47
+ ## Quick taste
48
+
49
+ ```console
50
+ $ dna scope list
51
+ $ dna doc show Agent greeter --scope hello-genome
52
+ $ dna sdlc story create s-my-story --title "..." --ac "..." --dod "..."
53
+ $ dna recall "reciprocal rank fusion" --kind Story -k 1
54
+ ```
55
+
56
+ Full CLI reference: <https://ruinosus.github.io/dna/reference/cli/> ·
57
+ Repository: <https://github.com/ruinosus/dna>
@@ -0,0 +1,28 @@
1
+ dna_cli/__init__.py,sha256=qJO7dZUjGqZIfItVfDFIgJr8XXsysjn_8tIpdSvlpqA,2850
2
+ dna_cli/_active_story.py,sha256=b3GArDMDE0QOotJEB-OE-4wFO-ACxmYqGs_6OgK2rJk,4415
3
+ dna_cli/_banner.py,sha256=k7zr4SlAL9VIeiPdtQLeTzRRDFUo8sOVxxQpENRIQ94,2006
4
+ dna_cli/_ctx.py,sha256=B269YsqoKkPHmmb0hCt1L-gqCkXUeZzG4M8ILEIzsxo,15924
5
+ dna_cli/_git_symbiosis.py,sha256=yhc0Pzy9h8clAWPyS7qBRXtDCKmlvfiOBpCy_78rP0Q,6449
6
+ dna_cli/_github_bridge.py,sha256=D8ipKrMuLQ3W1UY_hSY5iamicr8K3ZAzjxavinMn-Po,11279
7
+ dna_cli/_methodology_gates.py,sha256=XLa7fi1AQM4f5LmMmEstgzO3H4e2jCa9HT_A9T7W1dI,5717
8
+ dna_cli/doc_cmd.py,sha256=QDbajSnoflvAMHMSA7dwefnxMkdhOdCQVD-1Qw4BdEE,33351
9
+ dna_cli/docs_cmd.py,sha256=mzUMzcBhisr8pF0HlY1EbuoAzAQIOEF-9dVSBIcaFPU,2572
10
+ dna_cli/eval_cmd.py,sha256=A3FFA3RGyAXOsTKPShzY78D2mjsX346hqGFrtS_xTjo,11708
11
+ dna_cli/hooks_cmd.py,sha256=dS3reKqH0RT_rNzQoeNV0zGWRmpEQzwRjrEvCRrc5Ws,5981
12
+ dna_cli/install_cmd.py,sha256=iwWAw-KEv5rayB18P9j1PDYWKW6gztpGCqVW8aSQYrU,20542
13
+ dna_cli/issue_bridge_cmd.py,sha256=pgdTHDRkws1W7cCbL5atCBJrQ0hE8KYFmUiyCJEU-Hw,12046
14
+ dna_cli/kind_cmd.py,sha256=Pk6mqNnWSCXXzvnExi63mRymF36ht3VP3GNT4Qg6QFQ,3118
15
+ dna_cli/memory_cmd.py,sha256=cuyZuKGEVK18Tyjjwzh4eVPSvix0Bn_DWmUh6zyWHxk,12734
16
+ dna_cli/pr_cmd.py,sha256=h35dQi0hKDIZfftHrdqSkMVdVf7_CB7lx5EPezFKkfw,10346
17
+ dna_cli/recall_cmd.py,sha256=PUeGBntlmwA-6aySErrYdEZWSkUMBkAuciUFcj-edzc,6555
18
+ dna_cli/research_cmd.py,sha256=QXHYna4VRBK1GJtt-eb4WibkVs3hG6Bd72-A27dbFdU,11859
19
+ dna_cli/scope_cmd.py,sha256=czxomR3LE-9kyBAi3hh7NtG4Lfv_TjqnDckFHBwexTw,4905
20
+ dna_cli/sdlc_cmd.py,sha256=tRwzm5ZAhMF8SyJZPXalEPFG4XjU3iLgbFAzImuqehQ,275129
21
+ dna_cli/source_cmd.py,sha256=l57lF2i2sqsSMLDYAfPObWzHPicYsFQoA9KVELmvGt8,16770
22
+ dna_cli/testkit_cmd.py,sha256=MA2g8J7tV_LW81HmftTrozzDBVnXiG6AXnOlbI_WQXE,15398
23
+ dna_cli/data/git-hooks/prepare-commit-msg,sha256=DbEj42YchjsKrX9TzomCsTwznf_owKoJFx1aK0abY24,4560
24
+ dna_cli-0.1.0.dist-info/METADATA,sha256=T4ns8lSGvC6ZNaP1YUVPrDvnXyQ4QAUZqJUOYjplsYk,2044
25
+ dna_cli-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
26
+ dna_cli-0.1.0.dist-info/entry_points.txt,sha256=R-OX8kcY1Qb_RYPOJm3WGqWctVgB86Ay2R0-E90CwM8,37
27
+ dna_cli-0.1.0.dist-info/licenses/LICENSE,sha256=GNjRuMWS2rRbTig0czVoSHsscH43saa9y6I99v0Xcig,1073
28
+ dna_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dna = dna_cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DNA contributors
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.