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/__init__.py +87 -0
- dna_cli/_active_story.py +134 -0
- dna_cli/_banner.py +61 -0
- dna_cli/_ctx.py +433 -0
- dna_cli/_git_symbiosis.py +167 -0
- dna_cli/_github_bridge.py +294 -0
- dna_cli/_methodology_gates.py +164 -0
- dna_cli/data/git-hooks/prepare-commit-msg +132 -0
- dna_cli/doc_cmd.py +842 -0
- dna_cli/docs_cmd.py +79 -0
- dna_cli/eval_cmd.py +274 -0
- dna_cli/hooks_cmd.py +146 -0
- dna_cli/install_cmd.py +496 -0
- dna_cli/issue_bridge_cmd.py +305 -0
- dna_cli/kind_cmd.py +84 -0
- dna_cli/memory_cmd.py +298 -0
- dna_cli/pr_cmd.py +251 -0
- dna_cli/recall_cmd.py +161 -0
- dna_cli/research_cmd.py +284 -0
- dna_cli/scope_cmd.py +129 -0
- dna_cli/sdlc_cmd.py +6549 -0
- dna_cli/source_cmd.py +437 -0
- dna_cli/testkit_cmd.py +352 -0
- dna_cli-0.1.0.dist-info/METADATA +57 -0
- dna_cli-0.1.0.dist-info/RECORD +28 -0
- dna_cli-0.1.0.dist-info/WHEEL +4 -0
- dna_cli-0.1.0.dist-info/entry_points.txt +2 -0
- dna_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
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,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.
|