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.
Files changed (70) hide show
  1. {dna_cli-0.1.0 → dna_cli-0.2.0}/PKG-INFO +2 -2
  2. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/__init__.py +2 -0
  3. dna_cli-0.2.0/dna_cli/data/onboarding-scope/AGENTS.md +68 -0
  4. dna_cli-0.2.0/dna_cli/data/onboarding-scope/skills/dna-sdlc-cli/SKILL.md +190 -0
  5. dna_cli-0.2.0/dna_cli/init_cmd.py +355 -0
  6. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/memory_cmd.py +22 -26
  7. {dna_cli-0.1.0 → dna_cli-0.2.0}/pyproject.toml +3 -3
  8. dna_cli-0.2.0/tests/test_init_cmd.py +276 -0
  9. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_memory_cmd.py +36 -0
  10. {dna_cli-0.1.0 → dna_cli-0.2.0}/.gitignore +0 -0
  11. {dna_cli-0.1.0 → dna_cli-0.2.0}/LICENSE +0 -0
  12. {dna_cli-0.1.0 → dna_cli-0.2.0}/README.md +0 -0
  13. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_active_story.py +0 -0
  14. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_banner.py +0 -0
  15. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_ctx.py +0 -0
  16. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_git_symbiosis.py +0 -0
  17. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_github_bridge.py +0 -0
  18. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/_methodology_gates.py +0 -0
  19. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/data/git-hooks/prepare-commit-msg +0 -0
  20. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/doc_cmd.py +0 -0
  21. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/docs_cmd.py +0 -0
  22. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/eval_cmd.py +0 -0
  23. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/hooks_cmd.py +0 -0
  24. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/install_cmd.py +0 -0
  25. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/issue_bridge_cmd.py +0 -0
  26. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/kind_cmd.py +0 -0
  27. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/pr_cmd.py +0 -0
  28. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/recall_cmd.py +0 -0
  29. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/research_cmd.py +0 -0
  30. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/scope_cmd.py +0 -0
  31. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/sdlc_cmd.py +0 -0
  32. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/source_cmd.py +0 -0
  33. {dna_cli-0.1.0 → dna_cli-0.2.0}/dna_cli/testkit_cmd.py +0 -0
  34. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/conftest.py +0 -0
  35. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_changelog_cmd.py +0 -0
  36. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_doc_apply_bundle_entries.py +0 -0
  37. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_doc_apply_prompt_budget.py +0 -0
  38. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_docs_cmd.py +0 -0
  39. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_eval_cmd.py +0 -0
  40. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_focus_gates_cli.py +0 -0
  41. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_focus_gates_guards.py +0 -0
  42. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_git_symbiosis_hook.py +0 -0
  43. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_hooks_cli.py +0 -0
  44. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_install_cmd.py +0 -0
  45. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_install_github_real.py +0 -0
  46. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_issue_bridge_cli.py +0 -0
  47. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_issue_comment_cli.py +0 -0
  48. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_kaizen_event.py +0 -0
  49. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_kaizen_transitions.py +0 -0
  50. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_plan_from_file.py +0 -0
  51. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_produces_cmd.py +0 -0
  52. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_recall_cmd.py +0 -0
  53. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_research_cmd.py +0 -0
  54. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_scope_detect.py +0 -0
  55. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_brief.py +0 -0
  56. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_correctness.py +0 -0
  57. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_ergonomics.py +0 -0
  58. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_scope_default.py +0 -0
  59. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_sdlc_workitem_cli.py +0 -0
  60. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_source_url_resolution.py +0 -0
  61. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_spike_cli.py +0 -0
  62. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_check.py +0 -0
  63. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_commits_backref.py +0 -0
  64. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_done_guard.py +0 -0
  65. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_pr_cli.py +0 -0
  66. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_review_pr_guard.py +0 -0
  67. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_show.py +0 -0
  68. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_story_start_gate.py +0 -0
  69. {dna_cli-0.1.0 → dna_cli-0.2.0}/tests/test_tenant_write_note.py +0 -0
  70. {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.1.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.2,>=0.1
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
+ )