dna-cli 0.1.0__tar.gz → 0.2.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.
- {dna_cli-0.1.0 → dna_cli-0.2.0}/PKG-INFO +2 -2
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/__init__.py +2 -0
- dna_cli-0.2.0/dna_cli/data/onboarding-scope/AGENTS.md +68 -0
- dna_cli-0.2.0/dna_cli/data/onboarding-scope/skills/dna-sdlc-cli/SKILL.md +190 -0
- dna_cli-0.2.0/dna_cli/init_cmd.py +355 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/memory_cmd.py +22 -26
- {dna_cli-0.1.0 → dna_cli-0.2.0}/pyproject.toml +3 -3
- dna_cli-0.2.0/tests/test_init_cmd.py +276 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_memory_cmd.py +36 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/.gitignore +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/LICENSE +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/README.md +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_active_story.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_banner.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_ctx.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_git_symbiosis.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_github_bridge.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_methodology_gates.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/data/git-hooks/prepare-commit-msg +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/doc_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/docs_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/eval_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/hooks_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/install_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/issue_bridge_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/kind_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/pr_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/recall_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/research_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/scope_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/sdlc_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/source_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/testkit_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/conftest.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_changelog_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_doc_apply_bundle_entries.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_doc_apply_prompt_budget.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_docs_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_eval_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_focus_gates_cli.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_focus_gates_guards.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_git_symbiosis_hook.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_hooks_cli.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_install_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_install_github_real.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_issue_bridge_cli.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_issue_comment_cli.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_kaizen_event.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_kaizen_transitions.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_plan_from_file.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_produces_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_recall_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_research_cmd.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_scope_detect.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_brief.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_correctness.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_ergonomics.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_scope_default.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_workitem_cli.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_source_url_resolution.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_spike_cli.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_check.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_commits_backref.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_done_guard.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_pr_cli.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_review_pr_guard.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_show.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_start_gate.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_tenant_write_note.py +0 -0
- {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_testkit_cmd.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dna-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: DNA command-line interface — the `dna` binary for the declarative SDLC + document CRUD (kernel-local, no service required)
|
|
5
5
|
Project-URL: Homepage, https://ruinosus.github.io/dna/
|
|
6
6
|
Project-URL: Repository, https://github.com/ruinosus/dna
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Requires-Python: <3.14,>=3.12
|
|
18
18
|
Requires-Dist: click>=8.1.0
|
|
19
|
-
Requires-Dist: dna-sdk<0.
|
|
19
|
+
Requires-Dist: dna-sdk<0.3,>=0.2
|
|
20
20
|
Requires-Dist: pyyaml>=6.0
|
|
21
21
|
Requires-Dist: rich>=13.0
|
|
22
22
|
Provides-Extra: dev
|
|
@@ -19,6 +19,7 @@ from dna_cli import (
|
|
|
19
19
|
doc_cmd,
|
|
20
20
|
docs_cmd,
|
|
21
21
|
eval_cmd,
|
|
22
|
+
init_cmd,
|
|
22
23
|
install_cmd,
|
|
23
24
|
kind_cmd,
|
|
24
25
|
memory_cmd,
|
|
@@ -79,6 +80,7 @@ from dna_cli import pr_cmd as _pr_cmd # noqa: E402,F401
|
|
|
79
80
|
# the GitHub Issues side of the symbiosis (bridge with provenance).
|
|
80
81
|
from dna_cli import issue_bridge_cmd as _issue_bridge_cmd # noqa: E402,F401
|
|
81
82
|
main.add_command(source_cmd.source)
|
|
83
|
+
main.add_command(init_cmd.init)
|
|
82
84
|
main.add_command(install_cmd.install)
|
|
83
85
|
main.add_command(eval_cmd.eval_)
|
|
84
86
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# AGENTS.md — how work is tracked in this project
|
|
2
|
+
|
|
3
|
+
This project uses [DNA](https://github.com/ruinosus/dna) (Domain Notation of
|
|
4
|
+
Anything) to track its own lifecycle: Stories, Features, Epics and Issues are
|
|
5
|
+
declarative YAML documents in the board scope under `.dna/`, written and
|
|
6
|
+
transitioned via the `dna sdlc` CLI. This file was materialized by `dna init`
|
|
7
|
+
and is itself a live `agents.md/v1` instance the DNA SDK parses and
|
|
8
|
+
round-trips byte-faithfully.
|
|
9
|
+
|
|
10
|
+
## SDLC protocol — work is tracked in-repo via `dna sdlc`
|
|
11
|
+
|
|
12
|
+
The flow is **story-first**: file the Story before the work, narrate while
|
|
13
|
+
building, verify before closing.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
dna sdlc brief # session start — what's in flight
|
|
17
|
+
dna sdlc hooks install # one-time per clone — commit trailers
|
|
18
|
+
dna sdlc feature create f-my-area --title "..." --desc "..." # parent Feature
|
|
19
|
+
dna sdlc story create s-my-work --feature f-my-area --desc "..." \
|
|
20
|
+
--ac "Given/When/Then ..." --dod "code+tests+docs ..." # AC + DoD required
|
|
21
|
+
dna sdlc story start s-my-work --plan "plan of attack" # plan gate
|
|
22
|
+
dna sdlc story comment s-my-work --body "decided X because Y" # narrate as you go
|
|
23
|
+
dna sdlc test-guide create tg-my-work --verifies Story/s-my-work --step "run :: expect"
|
|
24
|
+
dna sdlc test-run record tg-my-work --outcome pass # test gate for done
|
|
25
|
+
dna sdlc story pr s-my-work # gh pr create, pre-filled FROM the story
|
|
26
|
+
dna sdlc story done s-my-work # only after the PR merges
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
While a story is active, every commit is stamped with `Work-Item:` +
|
|
30
|
+
`dna-sdlc[bot]` trailers by the versioned hook — that is the provenance
|
|
31
|
+
seal linking git history to the work item (`dna sdlc story commits s-x`).
|
|
32
|
+
|
|
33
|
+
## Conventions
|
|
34
|
+
|
|
35
|
+
- **Story-first.** Non-trivial work starts with `dna sdlc story create`
|
|
36
|
+
(AC + DoD are mandatory at create time) and `story start` (the plan
|
|
37
|
+
gate — substantial work gets a real plan via `--plan-file`, not a
|
|
38
|
+
one-liner).
|
|
39
|
+
- **Narrate as you go.** Status changes record *that* something happened,
|
|
40
|
+
not *what*. Post `dna sdlc story comment` for each meaningful step or
|
|
41
|
+
decision — the timeline is what stakeholders and future sessions read.
|
|
42
|
+
- **Gapless definition of done.** Never mark a story `done` with a gap:
|
|
43
|
+
finish to market standard, or keep it `in-progress` / decompose into
|
|
44
|
+
tracked child stories. `story done` requires a passing TestRun
|
|
45
|
+
(`--allow-no-tests` is for recorded exceptions only).
|
|
46
|
+
- **Review = open PR; done = merged.** `story review` expects an open PR
|
|
47
|
+
on the branch; a story in `review` with no PR is stale. Once a PR is
|
|
48
|
+
approved, stop pushing to its branch — further work goes to a new branch.
|
|
49
|
+
- **Surface IDs.** Print full slug IDs in backticks (`s-foo`, `i-012-bar`)
|
|
50
|
+
so they are paste-able into `dna sdlc story show` / `git log --grep`.
|
|
51
|
+
|
|
52
|
+
## Do not
|
|
53
|
+
|
|
54
|
+
- **Never hand-edit `.dna/**.yaml` for status changes** — the CLI is the
|
|
55
|
+
canonical write path (validation, timeline and journey events fire there).
|
|
56
|
+
- **Never do non-trivial work without an active story** — unstamped commits
|
|
57
|
+
are invisible to `story commits` / `story show`; absence is signal.
|
|
58
|
+
- **Never mark a story `done` with a gap** — finish to market standard or
|
|
59
|
+
keep it `in-progress` / decompose into tracked child stories.
|
|
60
|
+
|
|
61
|
+
## Learn more
|
|
62
|
+
|
|
63
|
+
- Skill: the `dna-sdlc-cli` skill (agentskills.io SKILL.md format) is
|
|
64
|
+
materialized by `dna init` into your agent tool's skills directory —
|
|
65
|
+
`.claude/skills/`, `.github/skills/`, `.cursor/skills/` or
|
|
66
|
+
`.opencode/skills/` — with the full workflow. Same content everywhere.
|
|
67
|
+
- Docs: <https://ruinosus.github.io/dna/> — CLI reference, Kinds guide,
|
|
68
|
+
SDLC guide ("Your git log is your SDLC").
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dna-sdlc-cli
|
|
3
|
+
description: "Use whenever work in this project needs SDLC tracking (Epic, Feature, Story, Issue, Spec, Plan, Spike, TestGuide/TestRun), or the user asks to file/start/narrate/close lifecycle docs, open a story PR, or wire the git hooks. Operate via the `dna sdlc` CLI \u2014 never edit `.dna/` YAMLs by hand for status changes; the CLI is the canonical write path (validation, timeline events and the derived journey fire correctly)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DNA SDLC CLI — the work tracker for this project
|
|
7
|
+
|
|
8
|
+
This project tracks its own lifecycle as DNA documents: Stories, Features,
|
|
9
|
+
Epics and Issues are first-class YAML documents in the board scope under
|
|
10
|
+
`.dna/` (the CLI's default source `./.dna` — run `dna` from the project
|
|
11
|
+
root, no env var needed; the sole board scope is auto-detected, or set
|
|
12
|
+
`DNA_SDLC_SCOPE`). Don't manually edit those YAMLs when changing status —
|
|
13
|
+
the CLI is the canonical write path.
|
|
14
|
+
|
|
15
|
+
## ⚖️ Maxim — zero gaps (the supreme rule)
|
|
16
|
+
|
|
17
|
+
**Never ship an activity with a gap.** Every Story/Feature/Epic/Issue is
|
|
18
|
+
either finished to market standard with no gaps, or it **stays
|
|
19
|
+
`in-progress`** until it is. There is no "ship with a TODO for later"
|
|
20
|
+
without a tracked Story covering it.
|
|
21
|
+
|
|
22
|
+
Before `story done`, the work must satisfy its own AC + DoD — close them
|
|
23
|
+
granularly with evidence:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
dna sdlc story check s-foo --ac 1 --dod "tests" --evidence "PR #42"
|
|
27
|
+
dna sdlc story check s-foo --all --evidence "commit abc123 + CI green"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
When a gap appears mid-story:
|
|
31
|
+
|
|
32
|
+
1. **Small + adjacent** (lint fix, doc tweak): include in the same Story.
|
|
33
|
+
2. **Medium + orthogonal**: file a follow-up Story; current one goes
|
|
34
|
+
`dna sdlc story block s-foo --reason "waiting on s-foo-followup"`.
|
|
35
|
+
3. **Large + systemic**: file a Feature; the Story stays blocked until its
|
|
36
|
+
first covering Story ships.
|
|
37
|
+
|
|
38
|
+
## Vocabulary
|
|
39
|
+
|
|
40
|
+
| Kind | Equivalent | Slug prefix | Verb group |
|
|
41
|
+
|------|------------|-------------|------------|
|
|
42
|
+
| Initiative / Roadmap | Initiative | free-form | `initiative` |
|
|
43
|
+
| Epic | Epic | `e-...` | `epic` |
|
|
44
|
+
| Feature | Feature | `f-...` | `feature` |
|
|
45
|
+
| Story | User Story | `s-...` | `story` |
|
|
46
|
+
| Issue | Bug/Task/Question | `i-NNN-...` (auto-numbered) | `issue` |
|
|
47
|
+
| Spec / Plan | RFC / implementation plan | free-form | `spec` / `plan` |
|
|
48
|
+
| Spike | time-boxed investigation | `sp-...` | `spike` |
|
|
49
|
+
| TestGuide / TestRun | test script / execution record | `tg-...` / `tr-...` | `test-guide` / `test-run` |
|
|
50
|
+
|
|
51
|
+
Story statuses: `needs-triage | todo | in-progress | review | done | blocked
|
|
52
|
+
| deferred | cancelled`. Issue arc: `file → triage → start → resolve`.
|
|
53
|
+
|
|
54
|
+
**Command shape:** *listing* is `dna sdlc list <Kind>` (capital-K:
|
|
55
|
+
`list Story --status in-progress`, `list Issue`); *acting on one doc* is
|
|
56
|
+
`dna sdlc <noun> <verb> <name>` (`story start`, `issue resolve`,
|
|
57
|
+
`feature`/`epic` verbs via their groups). There is no `story list`. When
|
|
58
|
+
unsure: `dna sdlc <noun> --help`.
|
|
59
|
+
|
|
60
|
+
## Session start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
dna sdlc brief # one-screen bootstrap: in-flight work, spikes, lessons, hot issues
|
|
64
|
+
dna sdlc current # every doc currently in-progress
|
|
65
|
+
dna sdlc next # snapshot of active work
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## The story-first lifecycle
|
|
69
|
+
|
|
70
|
+
### 1. File it — AC + DoD are required at create time
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dna sdlc story create s-my-work \
|
|
74
|
+
--feature f-parent \
|
|
75
|
+
--desc "what and why, one line" \
|
|
76
|
+
--priority high --labels cli,docs --reporter my-agent \
|
|
77
|
+
--ac "Given/When/Then acceptance criterion" \
|
|
78
|
+
--dod "code + tests green + docs updated"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The CLI refuses Stories without `--ac`/`--dod` (exit criteria are not
|
|
82
|
+
optional). `--ac`/`--dod` are repeatable. Bugs/tasks are Issues:
|
|
83
|
+
`dna sdlc issue file --slug my-bug --type bug --severity high --desc "..."`.
|
|
84
|
+
|
|
85
|
+
### 2. Start it — the plan gate
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
dna sdlc story start s-my-work --plan "plan of attack in 1-3 lines" # inline Plan
|
|
89
|
+
dna sdlc story start s-my-work --plan-file plan.md # rich multi-section plan
|
|
90
|
+
dna sdlc story start s-my-work --no-plan --skip-reason "1-line hotfix" # honest, recorded skip
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Substantial work gets a real plan (`--plan-file`), not a one-liner. Side
|
|
94
|
+
effect: `story start` stamps `.dna/active-story.txt`, which the git hook
|
|
95
|
+
reads (below).
|
|
96
|
+
|
|
97
|
+
### 3. Build — narrate as you go
|
|
98
|
+
|
|
99
|
+
Status changes record *that* something happened, not *what*. Post a comment
|
|
100
|
+
for each meaningful step or decision — the timeline is what stakeholders
|
|
101
|
+
(and future sessions) read:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
dna sdlc story comment s-my-work --body "now refactoring the cache to LRU"
|
|
105
|
+
dna sdlc story comment s-my-work --body "decided maxsize=64 because scans are heavy" --type decision
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`--type` auto-detects decision-shaped comments; `--commit-ref` ties a note
|
|
109
|
+
to a SHA (auto-detected from HEAD). The transition verbs (`start`, `review`,
|
|
110
|
+
`done`) warn when the timeline went mute — narrate inline with `--note "..."`.
|
|
111
|
+
|
|
112
|
+
### 4. Verify — the test gate
|
|
113
|
+
|
|
114
|
+
`story done` **requires a passing TestRun** that verifies the Story
|
|
115
|
+
(mirroring the AC/DoD guard at create):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
dna sdlc test-guide create tg-my-work --description "what it validates" \
|
|
119
|
+
--verifies Story/s-my-work --step "run X :: expect Y" --step "run Z :: expect W"
|
|
120
|
+
dna sdlc test-guide create tg-my-work --from-ac s-my-work # or stub steps from the AC
|
|
121
|
+
dna sdlc test-run record tg-my-work --outcome pass --note "all green locally + CI"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Escape hatch `story done --allow-no-tests` is for recorded exceptions only.
|
|
125
|
+
|
|
126
|
+
### 5. PR — born from the story, review = open PR
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
dna sdlc story pr s-my-work # gh pr create, pre-filled FROM the story
|
|
130
|
+
dna sdlc story pr s-my-work --dry-run # print title + body, no gh call
|
|
131
|
+
dna sdlc pr-footer s-my-work # just the attribution footer, for hand-made PRs
|
|
132
|
+
dna sdlc story review s-my-work # requires an open PR on the branch (or --no-pr --reason)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`story pr` assembles title (`feat(<label>): <title> (<s-x>)`), body
|
|
136
|
+
(description + AC as a checklist) and the attribution footer, then stamps
|
|
137
|
+
the PR URL back onto the timeline. **PR ready = stop pushing to its
|
|
138
|
+
branch** — squash-merge captures the branch at the merge click; commits
|
|
139
|
+
pushed after approval are silently dropped. Further work goes to a new
|
|
140
|
+
branch off the merged base.
|
|
141
|
+
|
|
142
|
+
### 6. Done — only after merge
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
dna sdlc story done s-my-work --summary "what shipped"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
A Story in `review` with no open PR is stale; `done` before the merge is a
|
|
149
|
+
lie. `done` auto-stamps `commit_ref`, warns on empty outputs (link
|
|
150
|
+
artifacts via `dna sdlc produces add`), and honors the test gate.
|
|
151
|
+
|
|
152
|
+
## Git symbiosis — trailers close the git↔SDLC loop
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
dna sdlc hooks install # one-time per clone → git config core.hooksPath scripts/git-hooks
|
|
156
|
+
dna sdlc hooks status # show wiring: hooksPath, active story, coauthor
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
While a Story is active, every `git commit` is stamped with
|
|
160
|
+
`Work-Item: Story/<name>` + the `dna-sdlc[bot]` co-author trailer. No
|
|
161
|
+
active story → no stamp (absence is signal). The way back needs no
|
|
162
|
+
bookkeeping:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
dna sdlc story show s-my-work # header + AC/DoD + plan + recent timeline
|
|
166
|
+
dna sdlc story commits s-my-work # every commit tied to the Story (trailers + timeline)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Surface IDs in chat (always)
|
|
170
|
+
|
|
171
|
+
When starting or shipping a doc, print the **full slug ID in backticks**
|
|
172
|
+
(`s-foo-bar`, `i-012-some-bug`) so the human can paste it into
|
|
173
|
+
`dna sdlc story show` / `git log --grep`. After a chunk of work, list each
|
|
174
|
+
ID touched with its transition (`s-foo: todo → done`).
|
|
175
|
+
|
|
176
|
+
## Other verbs (one-liners — `dna sdlc <verb> --help`)
|
|
177
|
+
|
|
178
|
+
`epic` / `feature` / `initiative` (parents; `feature` rolls up child
|
|
179
|
+
Stories) · `spike` (investigation arc: comment → answer with findings +
|
|
180
|
+
follow-up) · `adr` / `spec` / `plan` (design docs, ADR-style statuses) ·
|
|
181
|
+
`kaizen` (continuous-improvement observations) · `journey` (phase ledger —
|
|
182
|
+
derived; rarely needs manual writes) · `demand` (Story + journey-discover
|
|
183
|
+
in one shot) · `produces` / `cite` (link artifacts / sources) ·
|
|
184
|
+
`changelog` (release notes per scope) · `extract-decisions` (mine timelines).
|
|
185
|
+
|
|
186
|
+
## When NOT to use this skill
|
|
187
|
+
|
|
188
|
+
- Reading documents programmatically (use the SDK: `Kernel.quick(...)` /
|
|
189
|
+
`mi.all("Story")` — see the dna-sdk docs).
|
|
190
|
+
- Bulk renames/deletes (use `dna doc` + a git commit, not the SDLC verbs).
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""``dna init`` — agent-ready onboarding for a consumer project.
|
|
2
|
+
|
|
3
|
+
``pip install dna-sdk dna-cli`` delivers a working kernel + CLI, but the
|
|
4
|
+
project's AI agent arrives blind: the SDLC skill, the AGENTS.md conventions
|
|
5
|
+
and the git hooks live in the DNA repo, not in the packages. ``dna init``
|
|
6
|
+
closes that gap with one command, and it does so the DNA way — the
|
|
7
|
+
onboarding assets travel as **Kinds inside an embedded onboarding scope**
|
|
8
|
+
(package-data at ``dna_cli/data/onboarding-scope/``) and are materialized
|
|
9
|
+
by the SDK's own market readers/writers, not by copying text templates:
|
|
10
|
+
|
|
11
|
+
1. **Board** — creates ``.dna/<scope>/`` with a Genome written through
|
|
12
|
+
``kernel.write_document`` (the same scope-bootstrap ``dna install``
|
|
13
|
+
performs; every write guard runs).
|
|
14
|
+
2. **Skill** — reads the embedded ``skills/dna-sdlc-cli`` bundle with the
|
|
15
|
+
``agentskills-skill`` reader ONCE and projects it with the
|
|
16
|
+
byte-faithful SkillWriter into the skill directory of each selected
|
|
17
|
+
tool (``--tools``). The agentskills.io SKILL.md format is identical
|
|
18
|
+
across ~40 tools — only the directory differs (see ``TOOL_SKILL_DIRS``);
|
|
19
|
+
the Kind is the source of truth, the projections are regenerable.
|
|
20
|
+
3. **AGENTS.md** — the CANONICAL instruction surface (agents.md/v1 —
|
|
21
|
+
Linux Foundation stewardship, 28+ tools read it, Claude Code
|
|
22
|
+
included): read with the ``agentsmd-agent`` reader, written to the
|
|
23
|
+
project root with the AgentDefinitionWriter (the generated file
|
|
24
|
+
parses back with the same reader — market fidelity on the front
|
|
25
|
+
door). No per-tool instruction files are generated; a thin
|
|
26
|
+
CLAUDE.md/GEMINI.md pointer is the user's optional call.
|
|
27
|
+
4. **Git hooks** — wires ``core.hooksPath`` at the repo's versioned
|
|
28
|
+
hooks dir (same semantics as ``dna sdlc hooks install``); skipped
|
|
29
|
+
with a note when the directory is not a git repository.
|
|
30
|
+
|
|
31
|
+
Design evidence: Research/rsh-cross-tool-agent-standards (dna-development
|
|
32
|
+
board) — the cross-tool adoption facts behind the multi-tool projection.
|
|
33
|
+
|
|
34
|
+
Idempotent by default: an existing file is never overwritten without
|
|
35
|
+
``--force``, and the summary says exactly what was created vs skipped.
|
|
36
|
+
"""
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import asyncio
|
|
40
|
+
import re
|
|
41
|
+
import stat
|
|
42
|
+
from importlib.resources import files as _pkg_files
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
import click
|
|
46
|
+
|
|
47
|
+
from dna_cli._ctx import build_source_from_env, fail, print_json
|
|
48
|
+
|
|
49
|
+
SKILL_NAME = "dna-sdlc-cli"
|
|
50
|
+
|
|
51
|
+
#: Container dirs seeded (with .gitkeep) so the fresh board is recognized by
|
|
52
|
+
#: the CLI's sole-SDLC-scope autodetection (``_autodetect_sdlc_scope`` probes
|
|
53
|
+
#: for these) and survives a git clone before the first Story lands.
|
|
54
|
+
_BOARD_CONTAINERS = ("stories", "features", "issues")
|
|
55
|
+
|
|
56
|
+
#: Tool → skill-projection directory. The agentskills.io SKILL.md FORMAT is
|
|
57
|
+
#: identical across tools; only the discovery directory is tool-specific
|
|
58
|
+
#: (Research/rsh-cross-tool-agent-standards). One Skill Kind, N projections.
|
|
59
|
+
TOOL_SKILL_DIRS: dict[str, str] = {
|
|
60
|
+
"claude": ".claude/skills",
|
|
61
|
+
"copilot": ".github/skills",
|
|
62
|
+
"cursor": ".cursor/skills",
|
|
63
|
+
"opencode": ".opencode/skills",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#: Sensible default: Claude Code + GitHub Copilot cover the two biggest
|
|
67
|
+
#: agent populations; the rest are one ``--tools`` flag away.
|
|
68
|
+
DEFAULT_TOOLS = ("claude", "copilot")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _parse_tools(value: str) -> list[str]:
|
|
72
|
+
"""Parse ``--tools`` into a validated, order-preserving tool list."""
|
|
73
|
+
if value.strip().lower() == "all":
|
|
74
|
+
return list(TOOL_SKILL_DIRS)
|
|
75
|
+
tools: list[str] = []
|
|
76
|
+
for part in value.split(","):
|
|
77
|
+
tool = part.strip().lower()
|
|
78
|
+
if not tool:
|
|
79
|
+
continue
|
|
80
|
+
if tool not in TOOL_SKILL_DIRS:
|
|
81
|
+
raise fail(
|
|
82
|
+
f"--tools: unknown tool {tool!r} — pick from "
|
|
83
|
+
f"{', '.join(TOOL_SKILL_DIRS)} (or 'all')"
|
|
84
|
+
)
|
|
85
|
+
if tool not in tools:
|
|
86
|
+
tools.append(tool)
|
|
87
|
+
if not tools:
|
|
88
|
+
raise fail("--tools: no tools selected")
|
|
89
|
+
return tools
|
|
90
|
+
|
|
91
|
+
#: Step outcome labels used in the summary + --json payload.
|
|
92
|
+
_CREATED = "created"
|
|
93
|
+
_SKIPPED = "skipped"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _onboarding_root() -> Path:
|
|
97
|
+
"""Root of the embedded onboarding scope shipped as package-data."""
|
|
98
|
+
return Path(str(_pkg_files("dna_cli").joinpath("data/onboarding-scope")))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _derive_scope(target: Path) -> str:
|
|
102
|
+
"""Default board-scope name: ``<dirname>-dev``, slugified.
|
|
103
|
+
|
|
104
|
+
Follows the pilot precedent (repo ``foundry-assured`` → board scope
|
|
105
|
+
``foundry-dev``): the board is the *dev-time* SDLC scope, distinct
|
|
106
|
+
from any runtime prompt scope the project may later add.
|
|
107
|
+
"""
|
|
108
|
+
base = re.sub(r"[^a-z0-9]+", "-", target.resolve().name.lower()).strip("-")
|
|
109
|
+
base = base or "project"
|
|
110
|
+
return base if base.endswith("-dev") else f"{base}-dev"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _board_exists(target: Path, scope: str) -> bool:
|
|
114
|
+
"""Same dual-marker contract as ``dna install`` / ``dna scope detect``."""
|
|
115
|
+
scope_dir = target / ".dna" / scope
|
|
116
|
+
return (scope_dir / "Genome.yaml").exists() or (scope_dir / "manifest.yaml").exists()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def _bootstrap_board(target: Path, scope: str) -> None:
|
|
120
|
+
"""Create ``.dna/<scope>/`` with a Genome, written through the kernel.
|
|
121
|
+
|
|
122
|
+
Mirrors the scope-bootstrap in ``install_cmd`` ("a scope is born from
|
|
123
|
+
its Genome"): a full ``Kernel.auto()`` boot + ``write_document``, so
|
|
124
|
+
schema validation and every write guard run — never a hand-rolled YAML
|
|
125
|
+
dump. The source is pinned to ``<target>/.dna`` explicitly (NOT the
|
|
126
|
+
ambient ``DNA_SOURCE_URL``), because ``--dir`` names the project being
|
|
127
|
+
initialized, wherever the caller's env points.
|
|
128
|
+
"""
|
|
129
|
+
from dna.kernel import Kernel
|
|
130
|
+
|
|
131
|
+
kernel = Kernel.auto()
|
|
132
|
+
source = await build_source_from_env(
|
|
133
|
+
kernel, _source_url=f"file://{(target / '.dna').resolve()}"
|
|
134
|
+
)
|
|
135
|
+
kernel.source(source)
|
|
136
|
+
await kernel.write_document(scope, "Genome", scope, {
|
|
137
|
+
"apiVersion": "github.com/ruinosus/dna/v1",
|
|
138
|
+
"kind": "Genome",
|
|
139
|
+
"metadata": {
|
|
140
|
+
"name": scope,
|
|
141
|
+
"description": (
|
|
142
|
+
f"Dev-time SDLC board for {target.resolve().name} — "
|
|
143
|
+
f"Features/Stories/Issues tracked via `dna sdlc`. "
|
|
144
|
+
f"Created by `dna init`."
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
"spec": {},
|
|
148
|
+
})
|
|
149
|
+
# Seed the SDLC containers: `dna sdlc` autodetects the sole scope that
|
|
150
|
+
# HAS them (adopter boards have arbitrary names — i-012), and a fresh
|
|
151
|
+
# board must be detectable before its first Story. .gitkeep makes the
|
|
152
|
+
# empty dirs survive a git clone.
|
|
153
|
+
scope_dir = target / ".dna" / scope
|
|
154
|
+
for container in _BOARD_CONTAINERS:
|
|
155
|
+
d = scope_dir / container
|
|
156
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
(d / ".gitkeep").touch()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _materialize_skills(
|
|
161
|
+
target: Path, force: bool, tools: list[str],
|
|
162
|
+
) -> list[tuple[str, str, str]]:
|
|
163
|
+
"""Skill Kind → one projection per selected tool, via reader→writer.
|
|
164
|
+
|
|
165
|
+
The embedded bundle is parsed ONCE with the registered
|
|
166
|
+
``agentskills-skill`` reader and re-emitted with the byte-faithful
|
|
167
|
+
SkillWriter into each tool's skill directory — the same round-trip
|
|
168
|
+
machinery the market-conformance suites enforce. Returns one
|
|
169
|
+
``(step, outcome, relative path)`` per tool.
|
|
170
|
+
"""
|
|
171
|
+
from dna.extensions.agentskills import SkillReader, SkillWriter
|
|
172
|
+
from dna.kernel.bundle_handle import FilesystemBundleHandle
|
|
173
|
+
|
|
174
|
+
raw = SkillReader().read(
|
|
175
|
+
FilesystemBundleHandle(_onboarding_root() / "skills" / SKILL_NAME)
|
|
176
|
+
)
|
|
177
|
+
writer = SkillWriter()
|
|
178
|
+
results: list[tuple[str, str, str]] = []
|
|
179
|
+
for tool in tools:
|
|
180
|
+
rel = f"{TOOL_SKILL_DIRS[tool]}/{SKILL_NAME}"
|
|
181
|
+
dest = target / TOOL_SKILL_DIRS[tool] / SKILL_NAME
|
|
182
|
+
if (dest / "SKILL.md").exists() and not force:
|
|
183
|
+
results.append((f"skill[{tool}]", _SKIPPED, rel))
|
|
184
|
+
continue
|
|
185
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
186
|
+
writer.write(FilesystemBundleHandle(dest), raw)
|
|
187
|
+
results.append((f"skill[{tool}]", _CREATED, rel))
|
|
188
|
+
return results
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _materialize_agents_md(target: Path, force: bool) -> tuple[str, str]:
|
|
192
|
+
"""AgentDefinition Kind → ``<target>/AGENTS.md`` via reader→writer."""
|
|
193
|
+
from dna.extensions.agentsmd import AgentDefinitionReader, AgentDefinitionWriter
|
|
194
|
+
from dna.kernel.bundle_handle import FilesystemBundleHandle
|
|
195
|
+
|
|
196
|
+
if (target / "AGENTS.md").exists() and not force:
|
|
197
|
+
return _SKIPPED, "AGENTS.md"
|
|
198
|
+
raw = AgentDefinitionReader().read(FilesystemBundleHandle(_onboarding_root()))
|
|
199
|
+
AgentDefinitionWriter().write(FilesystemBundleHandle(target), raw)
|
|
200
|
+
return _CREATED, "AGENTS.md"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _install_hooks(target: Path) -> tuple[str, str]:
|
|
204
|
+
"""Wire the git↔SDLC hook — same semantics as ``dna sdlc hooks install``,
|
|
205
|
+
with an explicit ``cwd`` (``--dir`` may not be the CWD) and skip-with-note
|
|
206
|
+
instead of hard failure, so ``dna init`` degrades gracefully in a dir
|
|
207
|
+
that is not (yet) a git repository. Returns ``(outcome, detail)``.
|
|
208
|
+
"""
|
|
209
|
+
from dna_cli import _git_symbiosis as gs
|
|
210
|
+
|
|
211
|
+
root = gs.repo_root(cwd=target)
|
|
212
|
+
if root is None:
|
|
213
|
+
return _SKIPPED, "not a git repository — run `git init`, then `dna sdlc hooks install`"
|
|
214
|
+
current_raw = gs._run_git(["config", "--get", "core.hooksPath"], cwd=root)
|
|
215
|
+
current = (current_raw or "").strip() or None
|
|
216
|
+
if current not in (None, gs.HOOKS_DIR):
|
|
217
|
+
return _SKIPPED, (
|
|
218
|
+
f"core.hooksPath already set to '{current}' — merge your hooks into "
|
|
219
|
+
f"{gs.HOOKS_DIR}/ and run `dna sdlc hooks install`"
|
|
220
|
+
)
|
|
221
|
+
# Materialize the versioned hook from the packaged copy when absent
|
|
222
|
+
# (same file `dna sdlc hooks install` ensures).
|
|
223
|
+
hook = root / gs.HOOKS_DIR / gs.HOOK_NAME
|
|
224
|
+
if not hook.exists():
|
|
225
|
+
hook.parent.mkdir(parents=True, exist_ok=True)
|
|
226
|
+
hook.write_bytes(gs.hook_source_path().read_bytes())
|
|
227
|
+
mode = hook.stat().st_mode
|
|
228
|
+
if not mode & stat.S_IXUSR:
|
|
229
|
+
hook.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
230
|
+
if current is None:
|
|
231
|
+
if gs._run_git(["config", "core.hooksPath", gs.HOOKS_DIR], cwd=root) is None:
|
|
232
|
+
return _SKIPPED, "git config core.hooksPath failed"
|
|
233
|
+
return _CREATED, f"core.hooksPath = {gs.HOOKS_DIR}"
|
|
234
|
+
return _SKIPPED, f"already wired (core.hooksPath = {gs.HOOKS_DIR})"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@click.command("init")
|
|
238
|
+
@click.option(
|
|
239
|
+
"--scope", "scope_opt", default=None,
|
|
240
|
+
help="Board scope name (default: '<dirname>-dev', slugified — the "
|
|
241
|
+
"pilot convention for a dev-time SDLC board).",
|
|
242
|
+
)
|
|
243
|
+
@click.option(
|
|
244
|
+
"--dir", "dir_opt", default=".",
|
|
245
|
+
help="Project directory to initialize (default: current directory).",
|
|
246
|
+
)
|
|
247
|
+
@click.option(
|
|
248
|
+
"--tools", "tools_opt", default=",".join(DEFAULT_TOOLS), show_default=True,
|
|
249
|
+
help="Comma-separated agent tools to project the SDLC skill for "
|
|
250
|
+
f"({', '.join(TOOL_SKILL_DIRS)} — or 'all'). The SKILL.md format "
|
|
251
|
+
"is identical across tools; only the directory differs.",
|
|
252
|
+
)
|
|
253
|
+
@click.option(
|
|
254
|
+
"--force", is_flag=True,
|
|
255
|
+
help="Overwrite existing onboarding files (skill projections, "
|
|
256
|
+
"AGENTS.md). The board Genome is never rewritten — an existing "
|
|
257
|
+
"board is verified and kept.",
|
|
258
|
+
)
|
|
259
|
+
@click.option("--json", "as_json", is_flag=True, help="Machine-readable summary.")
|
|
260
|
+
def init(scope_opt: str | None, dir_opt: str, tools_opt: str,
|
|
261
|
+
force: bool, as_json: bool) -> None:
|
|
262
|
+
"""Make a project agent-ready: board + skill + AGENTS.md + git hooks.
|
|
263
|
+
|
|
264
|
+
One command bootstraps everything an AI coding agent needs to work
|
|
265
|
+
DNA-style in this project:
|
|
266
|
+
|
|
267
|
+
\b
|
|
268
|
+
.dna/<scope>/ SDLC board (Genome via the kernel)
|
|
269
|
+
<tool>/skills/dna-sdlc-cli/ the SDLC workflow skill (agentskills.io),
|
|
270
|
+
projected per --tools (.claude/skills,
|
|
271
|
+
.github/skills, .cursor/skills, ...)
|
|
272
|
+
AGENTS.md the canonical instruction surface
|
|
273
|
+
(agents.md/v1 — read by 28+ agent tools)
|
|
274
|
+
git hooks Work-Item commit trailers
|
|
275
|
+
|
|
276
|
+
The assets ship inside dna-cli as an embedded onboarding scope of real
|
|
277
|
+
Kinds and are materialized by the SDK's own byte-faithful
|
|
278
|
+
readers/writers — one Kind, N regenerable projections. AGENTS.md serves
|
|
279
|
+
every tool at once; Gemini CLI users can point GEMINI.md at it.
|
|
280
|
+
|
|
281
|
+
Idempotent: re-running never overwrites an existing file unless
|
|
282
|
+
--force is given; the summary reports what was created vs skipped.
|
|
283
|
+
|
|
284
|
+
Examples:
|
|
285
|
+
|
|
286
|
+
\b
|
|
287
|
+
dna init # here, board '<dirname>-dev'
|
|
288
|
+
dna init --scope acme-dev # explicit board scope
|
|
289
|
+
dna init --tools all # every supported tool dir
|
|
290
|
+
dna init --tools claude,cursor # explicit projection set
|
|
291
|
+
dna init --dir ../other-project # initialize another directory
|
|
292
|
+
"""
|
|
293
|
+
target = Path(dir_opt)
|
|
294
|
+
if not target.is_dir():
|
|
295
|
+
raise fail(f"--dir {dir_opt!r} is not an existing directory")
|
|
296
|
+
target = target.resolve()
|
|
297
|
+
scope = scope_opt or _derive_scope(target)
|
|
298
|
+
if not re.fullmatch(r"[a-z0-9][a-z0-9-]*", scope):
|
|
299
|
+
raise fail(
|
|
300
|
+
f"--scope {scope!r} must be a slug (lowercase letters, digits, '-')"
|
|
301
|
+
)
|
|
302
|
+
tools = _parse_tools(tools_opt)
|
|
303
|
+
|
|
304
|
+
results: list[tuple[str, str, str]] = [] # (step, outcome, detail)
|
|
305
|
+
|
|
306
|
+
# 1. Board — create when missing, verify + keep when present.
|
|
307
|
+
board_rel = f".dna/{scope}"
|
|
308
|
+
if _board_exists(target, scope):
|
|
309
|
+
results.append(("board", _SKIPPED, f"{board_rel} already exists"))
|
|
310
|
+
else:
|
|
311
|
+
asyncio.run(_bootstrap_board(target, scope))
|
|
312
|
+
results.append(("board", _CREATED, board_rel))
|
|
313
|
+
|
|
314
|
+
# 2. Skill Kind → one projection per tool (byte-faithful writer).
|
|
315
|
+
results.extend(_materialize_skills(target, force, tools))
|
|
316
|
+
|
|
317
|
+
# 3. AGENTS.md (agentsmd-agent Kind) — the canonical instruction surface.
|
|
318
|
+
outcome, detail = _materialize_agents_md(target, force)
|
|
319
|
+
results.append(("agents-md", outcome, detail))
|
|
320
|
+
|
|
321
|
+
# 4. Git hooks (Work-Item trailers).
|
|
322
|
+
outcome, detail = _install_hooks(target)
|
|
323
|
+
results.append(("hooks", outcome, detail))
|
|
324
|
+
|
|
325
|
+
if as_json:
|
|
326
|
+
print_json({
|
|
327
|
+
"dir": str(target),
|
|
328
|
+
"scope": scope,
|
|
329
|
+
"steps": [
|
|
330
|
+
{"step": s, "outcome": o, "detail": d} for s, o, d in results
|
|
331
|
+
],
|
|
332
|
+
})
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
click.secho(f"dna init — {target} (board scope: {scope})", bold=True)
|
|
336
|
+
step_width = max(len(s) for s, _, _ in results)
|
|
337
|
+
for step, outcome, detail in results:
|
|
338
|
+
color = "green" if outcome == _CREATED else "yellow"
|
|
339
|
+
click.secho(f" {outcome:<7} {step:<{step_width}} {detail}", fg=color)
|
|
340
|
+
n_created = sum(1 for _, o, _ in results if o == _CREATED)
|
|
341
|
+
n_skipped = len(results) - n_created
|
|
342
|
+
click.secho(
|
|
343
|
+
f"\n{n_created} created · {n_skipped} skipped"
|
|
344
|
+
+ ("" if force or not n_skipped else " (re-run with --force to overwrite files)"),
|
|
345
|
+
bold=True,
|
|
346
|
+
)
|
|
347
|
+
click.echo(
|
|
348
|
+
"\nNext steps:\n"
|
|
349
|
+
" dna sdlc feature create f-my-area --title \"...\" --desc \"...\"\n"
|
|
350
|
+
" dna sdlc story create s-my-first-story --feature f-my-area --desc \"...\" \\\n"
|
|
351
|
+
" --ac \"Given/When/Then ...\" --dod \"code+tests+docs\"\n"
|
|
352
|
+
" dna sdlc story start s-my-first-story --plan \"plan of attack\"\n"
|
|
353
|
+
f" (your agent: read AGENTS.md + the {SKILL_NAME} skill in its "
|
|
354
|
+
f"tool's skills dir)"
|
|
355
|
+
)
|