deepmodel-dmx 0.3.2__tar.gz → 0.3.4__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 (89) hide show
  1. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/CHANGELOG.md +19 -0
  2. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/PKG-INFO +107 -4
  3. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/README.md +106 -3
  4. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/pyproject.toml +1 -1
  5. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loop_tools.py +135 -2
  6. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loops/release.yaml +0 -1
  7. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/utility/dmx-update-memory.md +24 -1
  8. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/1-triage/dmx-create-ticket.md +34 -12
  9. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/5-ship/dmx-close-ticket.md +6 -2
  10. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/6-release/dmx-draft-release-note.md +32 -1
  11. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/validators/check_pr_ready.py +35 -0
  12. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/validators/check_spec_complete.py +1 -1
  13. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_loop_integration.py +6 -8
  14. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_loop_tools.py +208 -1
  15. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_validators.py +70 -1
  16. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/uv.lock +1 -1
  17. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/.editorconfig +0 -0
  18. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  19. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/.github/dependabot.yml +0 -0
  20. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/.github/workflows/publish.yml +0 -0
  21. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/.github/workflows/test.yml +0 -0
  22. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/.gitignore +0 -0
  23. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/.pre-commit-config.yaml +0 -0
  24. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/CLA.md +0 -0
  25. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/CONTRIBUTING.md +0 -0
  26. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/LICENSE +0 -0
  27. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/SECURITY.md +0 -0
  28. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/__init__.py +0 -0
  29. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/_workflow_version.py +0 -0
  30. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/catalog.py +0 -0
  31. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/cli.py +0 -0
  32. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/exceptions.py +0 -0
  33. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/http_auth.py +0 -0
  34. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/ide/__init__.py +0 -0
  35. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/ide/detect.py +0 -0
  36. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/ide/emitters.py +0 -0
  37. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loop_memory.py +0 -0
  38. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loop_schema.py +0 -0
  39. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loop_state.py +0 -0
  40. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loops/__init__.py +0 -0
  41. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loops/dev.yaml +0 -0
  42. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loops/plan.yaml +0 -0
  43. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loops/spec.yaml +0 -0
  44. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/loops/validate.yaml +0 -0
  45. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/repeat_until.py +0 -0
  46. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/rules/system-prompt.md +0 -0
  47. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/server.py +0 -0
  48. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/loop/dmx-loop-continue.md +0 -0
  49. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/loop/dmx-run-loop.md +0 -0
  50. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/specialist/dmx-docs.md +0 -0
  51. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/specialist/dmx-review.md +0 -0
  52. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/specialist/dmx-secure.md +0 -0
  53. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/specialist/dmx-test.md +0 -0
  54. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/utility/dmx-commit.md +0 -0
  55. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/utility/dmx-create-branch.md +0 -0
  56. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/utility/dmx-draft-pr-description.md +0 -0
  57. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/utility/dmx-status.md +0 -0
  58. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/utility/dmx-sync-branch.md +0 -0
  59. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/0-init/dmx-init.md +0 -0
  60. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/1-triage/dmx-derive-ticket.md +0 -0
  61. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/1-triage/dmx-hotfix.md +0 -0
  62. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/2-plan/dmx-plan.md +0 -0
  63. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/3-build/dmx-implement-next-phase.md +0 -0
  64. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/3-build/dmx-implement-next-task.md +0 -0
  65. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/4-validate/dmx-validate.md +0 -0
  66. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/5-ship/dmx-create-pr.md +0 -0
  67. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/6-release/dmx-create-release.md +0 -0
  68. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/skills/workflow/6-release/dmx-release-merge.md +0 -0
  69. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/tools.py +0 -0
  70. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/validator_runner.py +0 -0
  71. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/validators/__init__.py +0 -0
  72. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/validators/check_plan_complete.py +0 -0
  73. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/validators/run_tests.py +0 -0
  74. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/validators/spec_adherence.py +0 -0
  75. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/src/dmx/workspace.py +0 -0
  76. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/__init__.py +0 -0
  77. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/__snapshots__/test_ide.ambr +0 -0
  78. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/conftest.py +0 -0
  79. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_catalog.py +0 -0
  80. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_cli.py +0 -0
  81. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_ide.py +0 -0
  82. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_loop_memory.py +0 -0
  83. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_loop_schema.py +0 -0
  84. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_loop_state.py +0 -0
  85. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_repeat_until.py +0 -0
  86. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_server.py +0 -0
  87. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_tools.py +0 -0
  88. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_validator_runner.py +0 -0
  89. {deepmodel_dmx-0.3.2 → deepmodel_dmx-0.3.4}/tests/test_workspace.py +0 -0
@@ -7,6 +7,25 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [Unreleased]
11
+
12
+ ## [0.3.4] — 2026-09-08
13
+
14
+ ### Fixed
15
+
16
+ - Loop run state (`.dmx/jobs/{job_id}/*.json`) and the session-note breadcrumb `_finish_loop` writes to `activeContext.md` are now committed once a loop run genuinely finishes (not `paused`/`iterating`), instead of being left as an uncommitted, local-only change — matching the README's documented "committed with the PR" contract for `jobs/`. This mattered most for the bundled `release` loop, which has no `on_complete` chain target: nothing downstream ran to commit its final state, and `close-ticket` explicitly makes no `.dmx/` changes before force-deleting the branch, so the loop's own "complete" outcome was silently and permanently lost every time. Silent no-op outside a git repo or when there's nothing to commit; if a commit is attempted but fails (e.g. rejected by a pre-commit hook), the loop's own response now surfaces a warning instead of only logging it server-side, and every git call is timeout-bounded so a hung `git commit` (e.g. waiting on a GPG passphrase) can't hang the whole MCP tool call. (GH-23)
17
+ - `close-ticket` no longer transitions the ticket to Done/Complete (or adds the "PR merged" comment) unless Step 4 actually found a merged PR for the branch — previously the transition step ran unconditionally, so running `/dmx/close-ticket` while a PR was still open could mark the ticket Done before the code had actually merged. Also hardened the transition itself to treat an already-terminal ticket/issue as a no-op success rather than an error (idempotent for `github-issues`' auto-close-via-`Closes #N`, and for Jira setups where a GitHub↔Jira integration already auto-transitioned the ticket). (GH-21)
18
+ - `draft-release-note` now commits and pushes `.dmx/releases/{version}.md` after writing it, instead of leaving it as an uncommitted local file. `release-merge` opens its PR straight from `{branch_base}`'s pushed state, so the release notes file was previously silently absent from that PR's diff whenever it hadn't been committed by hand first. Re-running it against unchanged content (nothing new merged) is a no-op rather than failing on "nothing to commit". (GH-22)
19
+ - Starting the `spec` loop, or running `/dmx/create-ticket` manually, on a repository with zero commits now surfaces a clear, actionable error instead of a confusing one. Previously, `current_branch()` (which shells out to `git rev-parse --abbrev-ref HEAD`) silently returned `None` on a freshly `git init`'d repo's unborn `HEAD`, causing the `spec` loop's branch guard to fail with a generic "could not determine the current git branch" message — even though the branch name itself was perfectly resolvable. `_branch_guard_error` now distinguishes this specific case and tells the user to commit and push before retrying. `dmx-create-ticket.md` gained the same check as its new Step 2, so manual/foreground usage fails fast before creating a ticket or attempting to branch, rather than failing later when GitHub's `create_branch` API rejects branching from a ref-less remote. (GH-19)
20
+
21
+ ## [0.3.3] — 2026-09-02
22
+
23
+ ### Fixed
24
+
25
+ - The bundled `release` loop no longer runs `update-memory` after `create-pr` has already opened the PR — `create-pr` already performs its own memory-bank sync and commit (Steps 4-5), so chaining `update-memory` immediately after left its edits (further inbox promotions, `activeContext.md` rewrites) as dangling uncommitted changes never included in the PR, and silently discarded later by `close-ticket`'s branch deletion. `release.yaml` now runs `create-pr` only.
26
+ - `update-memory` now commits its own changes at the end of its instructions, so it can never leave dangling uncommitted `.dmx/` state regardless of when or how it's invoked in the future.
27
+ - `check_pr_ready`'s `memory_updated` check now fails if `.dmx/` has any uncommitted changes (staged or not), instead of only checking the latest commit or the mere existence of `activeContext.md` — a loop-config ordering mistake now surfaces loudly instead of silently passing. (GH-15)
28
+
10
29
  ## [0.3.2] — 2026-08-31
11
30
 
12
31
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: deepmodel-dmx
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: AI SDLC MCP server — skills and rules for any AI IDE.
5
5
  License: AGPL-3.0
6
6
  License-File: LICENSE
@@ -90,7 +90,7 @@ Bundled loops for the SDLC pipeline:
90
90
  | `plan` | plan | `dev` |
91
91
  | `dev` | implement-next-phase, commit | `validate` |
92
92
  | `validate` | validate | `release` |
93
- | `release` | create-pr, update-memory | — |
93
+ | `release` | create-pr | — |
94
94
 
95
95
  Each loop config defines a goal state, optional `repeat_until` condition, validators, human gate policy, and `on_complete` chaining. Run state is written to `.dmx/jobs/{job_id}/{loop_name}-{task_id}.json` — there's no separate active-run pointer; the active run is derived by scanning a job's state files for the one non-terminal (`pending`/`running`/`paused`/`iterating`) entry, keyed off the current branch/ticket. This keeps loop state isolated per branch: pausing work on one branch and running a loop on another can't corrupt or lose either one's state.
96
96
 
@@ -121,12 +121,111 @@ Loop-level memory hooks: before the first skill runs, the runtime surfaces `acti
121
121
  }
122
122
  ```
123
123
 
124
- Add to your IDE config ([Claude Code, Copilot, Antigravity ↓](#step-1--add-the-mcp-server)). Run `/dmx/init` once per project. Then:
124
+ Add to your IDE config ([Claude Code, Copilot, Antigravity ↓](#step-1--add-the-mcp-server)). Then follow [Your first project](#your-first-project) to initialize and run the full loop on a new repo.
125
+
126
+ ## Your first project
127
+
128
+ A brand-new repo, from `/dmx/init` through the first merged PR. You review at every gate; `/dmx/loop-continue` is how you move forward.
129
+
130
+ ### Before you start
131
+
132
+ - A GitHub repo cloned locally with an `origin` remote. The spec loop creates the feature branch on GitHub from `origin/{branch_base}` (usually `main` or `master`), even if you track work in Jira or use no ticketing system.
133
+ - The dmx MCP server added to your IDE ([install guide](#step-1--add-the-mcp-server)).
134
+ - The GitHub MCP server (`user-github`) authenticated. `/dmx/init` probes it before writing any files.
135
+ - The Atlassian MCP server only if you will choose Jira at init.
136
+
137
+ ### 1. Initialize
138
+
139
+ On the integration branch (`main` or `master`):
125
140
 
126
141
  ```
127
- /dmx/create-ticket
142
+ /dmx/init
128
143
  ```
129
144
 
145
+ Choose a workflow — **sdlc** (this walkthrough) or **freestyle** (no process enforced) — and a ticketing system: `none`, `github-issues`, or `jira`. Re-run `/dmx/init` at any time to switch workflow or ticketing; existing memory bank files with content are left intact.
146
+
147
+ Init writes `.dmx/config.md` and the memory bank (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`). Open a **new chat** so the IDE rules take effect.
148
+
149
+ If you already have a `docs/` folder, init reads it when populating the memory bank. Writing the requirement first is slightly better; either order works.
150
+
151
+ ### 2. Write the product requirement
152
+
153
+ Create `docs/requirements.md` at the repo root and put the product or feature description there. You can paste the requirement into chat later instead, but a file is easier to review and reuse.
154
+
155
+ `docs/requirements.md` is a convention, not a dmx artifact. The spec loop uses whatever you point it at.
156
+
157
+ ### 3. Commit and push
158
+
159
+ The spec loop must start from the configured integration branch, and it creates the remote feature branch from whatever is already on origin. Commit `.dmx/`, `docs/`, and any files init wrote, then push:
160
+
161
+ ```
162
+ git add .
163
+ git commit -m "chore: initialize dmx and capture product requirements"
164
+ git push -u origin HEAD
165
+ ```
166
+
167
+ Stay on `main` or `master`.
168
+
169
+ ### 4. Start the spec loop
170
+
171
+ In the same message as the command, point at the requirement file or paste it:
172
+
173
+ ```
174
+ /dmx/run-loop spec
175
+
176
+ See docs/requirements.md
177
+ ```
178
+
179
+ This creates a GitHub issue or Jira ticket (if you configured one), cuts a feature branch from `origin/{branch_base}`, checks it out, and writes `.dmx/spec.md`. Then it pauses.
180
+
181
+ ### 5. Answer the spec
182
+
183
+ Open `.dmx/spec.md`. Fill in Technical Approach if it is incomplete, and answer every question. Empty answers or placeholders (`TBD`, `TODO`) fail the spec validator.
184
+
185
+ ```
186
+ /dmx/loop-continue
187
+ ```
188
+
189
+ On success the runtime auto-chains to the `plan` loop.
190
+
191
+ ### 6. Review the plan
192
+
193
+ `plan` writes `.dmx/tasks.md` with phases and tasks, then pauses. Edit freely — this is the implementation contract.
194
+
195
+ ```
196
+ /dmx/loop-continue
197
+ ```
198
+
199
+ That starts the `dev` loop.
200
+
201
+ ### 7. Build phase by phase
202
+
203
+ Each `dev` cycle:
204
+
205
+ 1. Implements the next unchecked phase in `tasks.md`, then pauses.
206
+ 2. Review the diff.
207
+ 3. `/dmx/loop-continue` runs **commit only**. It does not push.
208
+ 4. Review the commit.
209
+ 5. `/dmx/loop-continue` again — validators run, then either the next phase starts or the loop chains to `validate`.
210
+
211
+ Repeat until every phase is checked off.
212
+
213
+ ### 8. Validate and open the PR
214
+
215
+ After the last phase, `validate` runs the quality gate and pauses. Review the report, then `/dmx/loop-continue`. On success the `release` loop runs `create-pr`: it pushes the feature branch and opens the pull request.
216
+
217
+ ### 9. Merge and close
218
+
219
+ Review the PR and merge it. Then:
220
+
221
+ ```
222
+ /dmx/close-ticket
223
+ ```
224
+
225
+ That closes the ticket, comments the PR link, and deletes the feature branch locally and on origin. `/dmx/loop-continue` after merge is not a close path — the release loop ends when the PR exists.
226
+
227
+ You can run the same steps by hand (`/dmx/create-ticket`, `/dmx/plan`, `/dmx/implement-next-phase`, …) instead of loops. See the skill catalog below.
228
+
130
229
  ## Learn more
131
230
 
132
231
  - [When Is a Loop Ready to Run Without You?](https://himakara.hashnode.dev/when-is-a-loop-ready-to-run-without-you) — the thinking behind dmx
@@ -208,6 +307,10 @@ Safe to re-run. Updates config without overwriting memory bank files that alread
208
307
 
209
308
  ### Step 3 — Start your first ticket
210
309
 
310
+ On a new repo, follow [Your first project](#your-first-project) (`/dmx/run-loop spec` through `/dmx/close-ticket`).
311
+
312
+ To start a ticket by hand instead:
313
+
211
314
  ```
212
315
  /dmx/create-ticket
213
316
  ```
@@ -55,7 +55,7 @@ Bundled loops for the SDLC pipeline:
55
55
  | `plan` | plan | `dev` |
56
56
  | `dev` | implement-next-phase, commit | `validate` |
57
57
  | `validate` | validate | `release` |
58
- | `release` | create-pr, update-memory | — |
58
+ | `release` | create-pr | — |
59
59
 
60
60
  Each loop config defines a goal state, optional `repeat_until` condition, validators, human gate policy, and `on_complete` chaining. Run state is written to `.dmx/jobs/{job_id}/{loop_name}-{task_id}.json` — there's no separate active-run pointer; the active run is derived by scanning a job's state files for the one non-terminal (`pending`/`running`/`paused`/`iterating`) entry, keyed off the current branch/ticket. This keeps loop state isolated per branch: pausing work on one branch and running a loop on another can't corrupt or lose either one's state.
61
61
 
@@ -86,12 +86,111 @@ Loop-level memory hooks: before the first skill runs, the runtime surfaces `acti
86
86
  }
87
87
  ```
88
88
 
89
- Add to your IDE config ([Claude Code, Copilot, Antigravity ↓](#step-1--add-the-mcp-server)). Run `/dmx/init` once per project. Then:
89
+ Add to your IDE config ([Claude Code, Copilot, Antigravity ↓](#step-1--add-the-mcp-server)). Then follow [Your first project](#your-first-project) to initialize and run the full loop on a new repo.
90
+
91
+ ## Your first project
92
+
93
+ A brand-new repo, from `/dmx/init` through the first merged PR. You review at every gate; `/dmx/loop-continue` is how you move forward.
94
+
95
+ ### Before you start
96
+
97
+ - A GitHub repo cloned locally with an `origin` remote. The spec loop creates the feature branch on GitHub from `origin/{branch_base}` (usually `main` or `master`), even if you track work in Jira or use no ticketing system.
98
+ - The dmx MCP server added to your IDE ([install guide](#step-1--add-the-mcp-server)).
99
+ - The GitHub MCP server (`user-github`) authenticated. `/dmx/init` probes it before writing any files.
100
+ - The Atlassian MCP server only if you will choose Jira at init.
101
+
102
+ ### 1. Initialize
103
+
104
+ On the integration branch (`main` or `master`):
90
105
 
91
106
  ```
92
- /dmx/create-ticket
107
+ /dmx/init
93
108
  ```
94
109
 
110
+ Choose a workflow — **sdlc** (this walkthrough) or **freestyle** (no process enforced) — and a ticketing system: `none`, `github-issues`, or `jira`. Re-run `/dmx/init` at any time to switch workflow or ticketing; existing memory bank files with content are left intact.
111
+
112
+ Init writes `.dmx/config.md` and the memory bank (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`). Open a **new chat** so the IDE rules take effect.
113
+
114
+ If you already have a `docs/` folder, init reads it when populating the memory bank. Writing the requirement first is slightly better; either order works.
115
+
116
+ ### 2. Write the product requirement
117
+
118
+ Create `docs/requirements.md` at the repo root and put the product or feature description there. You can paste the requirement into chat later instead, but a file is easier to review and reuse.
119
+
120
+ `docs/requirements.md` is a convention, not a dmx artifact. The spec loop uses whatever you point it at.
121
+
122
+ ### 3. Commit and push
123
+
124
+ The spec loop must start from the configured integration branch, and it creates the remote feature branch from whatever is already on origin. Commit `.dmx/`, `docs/`, and any files init wrote, then push:
125
+
126
+ ```
127
+ git add .
128
+ git commit -m "chore: initialize dmx and capture product requirements"
129
+ git push -u origin HEAD
130
+ ```
131
+
132
+ Stay on `main` or `master`.
133
+
134
+ ### 4. Start the spec loop
135
+
136
+ In the same message as the command, point at the requirement file or paste it:
137
+
138
+ ```
139
+ /dmx/run-loop spec
140
+
141
+ See docs/requirements.md
142
+ ```
143
+
144
+ This creates a GitHub issue or Jira ticket (if you configured one), cuts a feature branch from `origin/{branch_base}`, checks it out, and writes `.dmx/spec.md`. Then it pauses.
145
+
146
+ ### 5. Answer the spec
147
+
148
+ Open `.dmx/spec.md`. Fill in Technical Approach if it is incomplete, and answer every question. Empty answers or placeholders (`TBD`, `TODO`) fail the spec validator.
149
+
150
+ ```
151
+ /dmx/loop-continue
152
+ ```
153
+
154
+ On success the runtime auto-chains to the `plan` loop.
155
+
156
+ ### 6. Review the plan
157
+
158
+ `plan` writes `.dmx/tasks.md` with phases and tasks, then pauses. Edit freely — this is the implementation contract.
159
+
160
+ ```
161
+ /dmx/loop-continue
162
+ ```
163
+
164
+ That starts the `dev` loop.
165
+
166
+ ### 7. Build phase by phase
167
+
168
+ Each `dev` cycle:
169
+
170
+ 1. Implements the next unchecked phase in `tasks.md`, then pauses.
171
+ 2. Review the diff.
172
+ 3. `/dmx/loop-continue` runs **commit only**. It does not push.
173
+ 4. Review the commit.
174
+ 5. `/dmx/loop-continue` again — validators run, then either the next phase starts or the loop chains to `validate`.
175
+
176
+ Repeat until every phase is checked off.
177
+
178
+ ### 8. Validate and open the PR
179
+
180
+ After the last phase, `validate` runs the quality gate and pauses. Review the report, then `/dmx/loop-continue`. On success the `release` loop runs `create-pr`: it pushes the feature branch and opens the pull request.
181
+
182
+ ### 9. Merge and close
183
+
184
+ Review the PR and merge it. Then:
185
+
186
+ ```
187
+ /dmx/close-ticket
188
+ ```
189
+
190
+ That closes the ticket, comments the PR link, and deletes the feature branch locally and on origin. `/dmx/loop-continue` after merge is not a close path — the release loop ends when the PR exists.
191
+
192
+ You can run the same steps by hand (`/dmx/create-ticket`, `/dmx/plan`, `/dmx/implement-next-phase`, …) instead of loops. See the skill catalog below.
193
+
95
194
  ## Learn more
96
195
 
97
196
  - [When Is a Loop Ready to Run Without You?](https://himakara.hashnode.dev/when-is-a-loop-ready-to-run-without-you) — the thinking behind dmx
@@ -173,6 +272,10 @@ Safe to re-run. Updates config without overwriting memory bank files that alread
173
272
 
174
273
  ### Step 3 — Start your first ticket
175
274
 
275
+ On a new repo, follow [Your first project](#your-first-project) (`/dmx/run-loop spec` through `/dmx/close-ticket`).
276
+
277
+ To start a ticket by hand instead:
278
+
176
279
  ```
177
280
  /dmx/create-ticket
178
281
  ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "deepmodel-dmx"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "AI SDLC MCP server — skills and rules for any AI IDE."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -40,6 +40,7 @@ from __future__ import annotations
40
40
  import importlib.resources as pkg
41
41
  import logging
42
42
  import re
43
+ import subprocess
43
44
  from pathlib import Path
44
45
 
45
46
  from fastmcp import (
@@ -150,6 +151,41 @@ def _read_branch_base(workspace_root: Path) -> str | None:
150
151
  return value if value and value != "{REQUIRED}" else None
151
152
 
152
153
 
154
+ def _repo_has_no_commits(root: Path) -> bool:
155
+ """True if *root* is a git repo with an unborn HEAD (zero commits).
156
+
157
+ ``git rev-parse --abbrev-ref HEAD`` — what :func:`current_branch` uses —
158
+ fails on a freshly ``git init``'d repo before its first commit, even
159
+ though the branch name (e.g. ``main``/``master``) is perfectly
160
+ resolvable via ``git symbolic-ref``. This distinguishes that specific,
161
+ fixable case (make a commit) from other reasons ``current_branch``
162
+ might return ``None`` (not a git repo at all, detached HEAD, etc.),
163
+ which don't have an actionable one-line fix.
164
+ """
165
+ try:
166
+ result = subprocess.run(
167
+ ["git", "rev-parse", "--is-inside-work-tree"],
168
+ capture_output=True,
169
+ text=True,
170
+ cwd=root,
171
+ )
172
+ except Exception: # noqa: BLE001
173
+ return False
174
+ if result.returncode != 0 or result.stdout.strip() != "true":
175
+ return False # not a git repo at all — a different problem
176
+
177
+ try:
178
+ verify = subprocess.run(
179
+ ["git", "rev-parse", "--verify", "--quiet", "HEAD"],
180
+ capture_output=True,
181
+ text=True,
182
+ cwd=root,
183
+ )
184
+ except Exception: # noqa: BLE001
185
+ return False
186
+ return verify.returncode != 0
187
+
188
+
153
189
  def _branch_guard_error(root: Path, config: LoopConfig) -> str | None:
154
190
  """Return an error message if *config* declares ``require_branch`` and
155
191
  the current branch doesn't satisfy it, else None."""
@@ -166,6 +202,13 @@ def _branch_guard_error(root: Path, config: LoopConfig) -> str | None:
166
202
 
167
203
  branch = current_branch(root)
168
204
  if branch is None:
205
+ if _repo_has_no_commits(root):
206
+ return (
207
+ f"Cannot start the `{config.name}` loop: this repository has no commits yet. "
208
+ f"`{config.name}` creates a new branch from `{branch_base}` on GitHub, which "
209
+ "requires at least one commit to exist first. Commit something (e.g. the "
210
+ "`.dmx/` files `/dmx/init` just wrote) and push to origin, then try again."
211
+ )
169
212
  return (
170
213
  f"Cannot start the `{config.name}` loop: could not determine the current git "
171
214
  f"branch. Make sure you're in a git repository checked out to `{branch_base}`."
@@ -451,6 +494,79 @@ def _start_loop(root: Path, name: str, description: str | None = None) -> str:
451
494
  # ---------------------------------------------------------------------------
452
495
 
453
496
 
497
+ _COMMIT_DMX_STATE_TIMEOUT_SECONDS = 15
498
+
499
+
500
+ def _commit_dmx_state(root: Path, message: str) -> str | None:
501
+ """Best-effort commit of any uncommitted ``.dmx/`` changes.
502
+
503
+ Called once a loop run has genuinely finished (a terminal outcome —
504
+ not ``paused``/``iterating``, which will write state again on the next
505
+ call) so its final state — the job's state JSON and the session-note
506
+ breadcrumb ``_finish_loop`` just wrote to ``activeContext.md`` — isn't
507
+ left as an uncommitted, local-only change. This matters most for a
508
+ loop with no ``on_complete`` chain target (e.g. the bundled ``release``
509
+ loop): nothing downstream runs to commit on its behalf, and
510
+ ``close-ticket`` explicitly makes no ``.dmx/`` changes before
511
+ force-deleting the branch, so an uncommitted final state here would be
512
+ silently and permanently lost — see GH-23.
513
+
514
+ Silently does nothing if *root* isn't a git repo or there's nothing to
515
+ commit — this is a best-effort durability improvement (matching the
516
+ README's "committed with the PR" claim for ``.dmx/jobs/``), not
517
+ something that should ever break a loop response.
518
+
519
+ Every subprocess call is timeout-bounded so a hung ``git`` invocation
520
+ (e.g. a pre-commit hook prompting for input, or GPG signing waiting on
521
+ a passphrase) can't hang the whole MCP tool call indefinitely.
522
+
523
+ Returns:
524
+ ``None`` if there was nothing to commit or the commit succeeded.
525
+ A short, user-facing warning string if a commit was *attempted*
526
+ but failed (e.g. a pre-commit hook rejected it) — silently
527
+ swallowing that would defeat the entire point of this call, so
528
+ callers should surface it in the loop's response rather than only
529
+ logging it server-side.
530
+ """
531
+ try:
532
+ status = subprocess.run(
533
+ ["git", "status", "--short", "--", ".dmx/"],
534
+ capture_output=True,
535
+ text=True,
536
+ cwd=root,
537
+ timeout=_COMMIT_DMX_STATE_TIMEOUT_SECONDS,
538
+ )
539
+ except Exception: # noqa: BLE001
540
+ return None
541
+ if status.returncode != 0 or not status.stdout.strip():
542
+ return None # not a git repo, or nothing under .dmx/ to commit
543
+
544
+ try:
545
+ subprocess.run(
546
+ ["git", "add", ".dmx/"],
547
+ capture_output=True,
548
+ text=True,
549
+ cwd=root,
550
+ check=True,
551
+ timeout=_COMMIT_DMX_STATE_TIMEOUT_SECONDS,
552
+ )
553
+ subprocess.run(
554
+ ["git", "commit", "-m", message],
555
+ capture_output=True,
556
+ text=True,
557
+ cwd=root,
558
+ check=True,
559
+ timeout=_COMMIT_DMX_STATE_TIMEOUT_SECONDS,
560
+ )
561
+ except Exception as exc: # noqa: BLE001
562
+ logger.warning("could not auto-commit .dmx/ state (%s): %s", message, exc)
563
+ return (
564
+ "⚠️ Could not auto-commit the loop's final `.dmx/` state (see server logs for "
565
+ "details) — run `git status .dmx/` and commit manually if needed."
566
+ )
567
+ return None
568
+
569
+
454
570
  def _finish_loop(
455
571
  root: Path,
456
572
  job_id: str,
@@ -571,25 +687,42 @@ def _finish_loop(
571
687
  f"{loop_name} loop completed (outcome: {outcome}) — chaining to "
572
688
  f"{next_loop} was configured but blocked: {chain_guard_error}",
573
689
  )
574
- return (
690
+ commit_warning = _commit_dmx_state(
691
+ root, f"chore: sync loop state for {loop_name} (job {job_id})"
692
+ )
693
+ message = (
575
694
  f"{_complete_message(loop_name, job_id, outcome)}\n\n"
576
695
  f"Configured to chain to **{next_loop}**, but it couldn't start: "
577
696
  f"{chain_guard_error}"
578
697
  )
698
+ if commit_warning:
699
+ message += f"\n\n{commit_warning}"
700
+ return message
579
701
 
580
702
  append_session_note(
581
703
  root,
582
704
  f"{loop_name} loop completed (outcome: {outcome}) — "
583
705
  f"chained to {next_loop} (job `{job_id}`).",
584
706
  )
707
+ commit_warning = _commit_dmx_state(
708
+ root, f"chore: sync loop state for {loop_name} (job {job_id})"
709
+ )
585
710
  chain_header = (
586
711
  f"**{loop_name} loop — complete** (outcome: `{outcome}`)\n\n"
587
712
  f"Chaining automatically to **{next_loop}** loop.\n\n"
588
713
  )
714
+ if commit_warning:
715
+ chain_header += f"{commit_warning}\n\n"
589
716
  return chain_header + _start_loop(root, next_loop)
590
717
 
591
718
  append_session_note(root, f"{loop_name} loop completed (outcome: {outcome}) (job `{job_id}`).")
592
- return _complete_message(loop_name, job_id, outcome)
719
+ commit_warning = _commit_dmx_state(
720
+ root, f"chore: sync loop state for {loop_name} (job {job_id})"
721
+ )
722
+ message = _complete_message(loop_name, job_id, outcome)
723
+ if commit_warning:
724
+ message += f"\n\n{commit_warning}"
725
+ return message
593
726
 
594
727
 
595
728
  # ---------------------------------------------------------------------------
@@ -1,7 +1,6 @@
1
1
  name: release
2
2
  skills:
3
3
  - create-pr
4
- - update-memory
5
4
  trigger:
6
5
  type: on_complete
7
6
  goal_state: "PR created, ticket transitioned to review, memory bank updated"
@@ -85,7 +85,27 @@ After promoting inbox items, rewrite `activeContext.md` to the learning-inbox st
85
85
 
86
86
  Do not add an `## Active Ticket` section. Branch identity lives in `spec.md`, not here.
87
87
 
88
- ## Step 6 — Return the result
88
+ ## Step 6 — Commit memory changes
89
+
90
+ Run:
91
+ ```
92
+ git status --short .dmx/
93
+ ```
94
+
95
+ If any files under `.dmx/` were modified in Steps 4–5:
96
+ ```
97
+ git add .dmx/
98
+ git commit -m "chore: sync memory bank"
99
+ ```
100
+
101
+ If nothing changed, continue without committing.
102
+
103
+ This skill must never leave uncommitted `.dmx/` changes in the working tree —
104
+ regardless of when or how it's invoked (standalone, chained after another
105
+ skill, or as part of a loop), it is responsible for committing its own edits.
106
+ Do not skip this step even if you expect the caller to commit afterward.
107
+
108
+ ## Step 7 — Return the result
89
109
 
90
110
  Output:
91
111
  ```
@@ -101,6 +121,8 @@ activeContext inbox:
101
121
  Promoted: {N} items
102
122
  Remaining: {M} items
103
123
 
124
+ {if committed in Step 6} Committed: chore: sync memory bank
125
+
104
126
  Next:
105
127
  - Run /dmx/create-ticket to start the next piece of work.
106
128
  - Run /dmx/derive-ticket if you have uncommitted changes to formalise.
@@ -112,3 +134,4 @@ Next:
112
134
  - Never store ticket-specific implementation details (function names, line numbers, single-branch decisions) in core files. Those belong in `spec.md` or `tasks.md`.
113
135
  - If `tasks.md` shows unchecked tasks remaining, note in output: "Note: tasks.md has unchecked tasks — memory updated with progress so far, not a completed state."
114
136
  - After Step 5, `activeContext.md` must not contain an `## Active Ticket` section.
137
+ - Never leave `.dmx/` changes uncommitted — Step 6 is mandatory whenever files were modified.
@@ -28,7 +28,29 @@ The project configuration is injected into your context as a rule. Extract:
28
28
 
29
29
  If configuration is not available in context, fall back to reading `.dmx/config.md`. If neither is found, stop: "Project configuration not found. Run /dmx/init to set up this project."
30
30
 
31
- ## Step 2 — Read project context
31
+ ## Step 2 — Verify the repo has at least one commit
32
+
33
+ Run:
34
+ ```
35
+ git rev-parse --verify --quiet HEAD
36
+ ```
37
+
38
+ If this fails (non-zero exit, no output — an unborn `HEAD`, meaning zero commits exist yet), stop before creating anything:
39
+ ```
40
+ This repository has no commits yet. Creating a ticket branches from `{config.branch_base}`
41
+ on GitHub, which requires at least one commit to exist first.
42
+
43
+ Commit something first, e.g. the files /dmx/init just wrote:
44
+ git add .
45
+ git commit -m "chore: initialize project"
46
+ git push -u origin HEAD
47
+
48
+ Then run /dmx/create-ticket again.
49
+ ```
50
+
51
+ Do not proceed to Step 3 in this case, even if `{{task}}` was provided.
52
+
53
+ ## Step 3 — Read project context
32
54
 
33
55
  Read the following memory bank files:
34
56
  - `.dmx/projectbrief.md` — project goals and scope
@@ -39,7 +61,7 @@ If `.dmx/` does not exist, stop: "Memory bank not found. Run /dmx/init to set up
39
61
 
40
62
  Store this context. It will inform the ticket content, technical approach, and spec questions.
41
63
 
42
- ## Step 3 — Infer type
64
+ ## Step 4 — Infer type
43
65
 
44
66
  If `{{type}}` was provided, use it. Accepted values: `feature`, `bug`, `chore`.
45
67
 
@@ -53,9 +75,9 @@ Store as `branch_type` (`feature` | `bug` | `chore`).
53
75
  For Jira `issueTypeName` mapping: `feature` → `Story`, `bug` → `Bug`, `chore` → `Task`.
54
76
  For GitHub Issues label: use `branch_type` as-is.
55
77
 
56
- ## Step 4 — Draft ticket content
78
+ ## Step 5 — Draft ticket content
57
79
 
58
- Using `{{task}}` and the project context from Step 2, generate a `summary` and `description`.
80
+ Using `{{task}}` and the project context from Step 3, generate a `summary` and `description`.
59
81
 
60
82
  ### Summary
61
83
  Single sentence, max 80 characters, imperative present tense, capital first letter, no trailing period. Specific enough that scope is clear without reading the description.
@@ -86,7 +108,7 @@ Examples:
86
108
 
87
109
  Rules: write for the implementer. For bugs, lead with observed vs expected behaviour. Acceptance criteria must be independently verifiable.
88
110
 
89
- ## Step 5 — Create the ticket
111
+ ## Step 6 — Create the ticket
90
112
 
91
113
  **If `ticketing` is `jira`:**
92
114
 
@@ -96,7 +118,7 @@ Call `createJiraIssue` on `user-atlassian`:
96
118
  ```
97
119
  cloudId: {config.cloud_id}
98
120
  projectKey: {config.project_key}
99
- issueTypeName: {Jira type from Step 3}
121
+ issueTypeName: {Jira type from Step 4}
100
122
  summary: {summary}
101
123
  description: {description}
102
124
  assignee_account_id: {account_id}
@@ -129,7 +151,7 @@ Store `ticket_url` = `{html_url}`.
129
151
 
130
152
  No ticket is created. `ticket_ref` = `none`. `ticket_url` = none.
131
153
 
132
- ## Step 6 — Construct the branch name
154
+ ## Step 7 — Construct the branch name
133
155
 
134
156
  Slugify the summary:
135
157
  - Lowercase, hyphens for spaces, remove special characters, collapse consecutive hyphens, truncate to 60 chars
@@ -140,7 +162,7 @@ Slugify the summary:
140
162
  | `github-issues` | `{branch_type}-gh-{number}-{slug}` | `feature-gh-123-add-rate-limiting` |
141
163
  | `none` | `{branch_type}-{slug}` | `feature-add-rate-limiting` |
142
164
 
143
- ## Step 7 — Create the remote branch and check out locally
165
+ ## Step 8 — Create the remote branch and check out locally
144
166
 
145
167
  Call `create_branch` on `user-github`:
146
168
  ```
@@ -158,7 +180,7 @@ git fetch origin
158
180
  git checkout {branch_name}
159
181
  ```
160
182
 
161
- ## Step 8 — Scaffold spec.md
183
+ ## Step 9 — Scaffold spec.md
162
184
 
163
185
  Write `.dmx/spec.md`:
164
186
 
@@ -179,7 +201,7 @@ ticketing: {ticketing}
179
201
  ---
180
202
 
181
203
  ## Context
182
- {From ticket description Step 4 — why this work is needed in this project}
204
+ {From ticket description Step 5 — why this work is needed in this project}
183
205
 
184
206
  ## Scope
185
207
  {Bullet list of what is included. Use project patterns to name specific layers, services, or files.}
@@ -221,7 +243,7 @@ ticketing: {ticketing}
221
243
 
222
244
  When `ticketing` is `none`, omit `ticket` from frontmatter.
223
245
 
224
- ## Step 9 — Transition to In Progress
246
+ ## Step 10 — Transition to In Progress
225
247
 
226
248
  **If `ticketing` is `jira`:**
227
249
  Call `getTransitionsForJiraIssue` on `user-atlassian`. Find `In Progress`. Call `transitionJiraIssue`.
@@ -237,7 +259,7 @@ labels: ["in-progress"]
237
259
 
238
260
  **If `ticketing` is `none`:** Skip.
239
261
 
240
- ## Step 10 — Return the result
262
+ ## Step 11 — Return the result
241
263
 
242
264
  ```
243
265
  {if ticketing ≠ none} Ticket: {ticket_ref} — {summary}