deepclause-pi 0.1.5 → 0.3.0
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.
- package/README.md +36 -0
- package/dist/diagram/extract.d.ts +5 -0
- package/dist/diagram/extract.js +701 -0
- package/dist/diagram/grade.d.ts +41 -0
- package/dist/diagram/grade.js +70 -0
- package/dist/diagram/validate.d.ts +36 -0
- package/dist/diagram/validate.js +148 -0
- package/dist/diagram/viewer.d.ts +36 -0
- package/dist/diagram/viewer.js +99 -0
- package/dist/diagram/workspace.d.ts +24 -0
- package/dist/diagram/workspace.js +106 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +462 -17
- package/dist/model.d.ts +16 -0
- package/dist/model.js +28 -0
- package/dist/planner.d.ts +27 -2
- package/dist/planner.js +109 -4
- package/dist/runtime.d.ts +15 -1
- package/dist/runtime.js +116 -3
- package/dist/workspace.d.ts +3 -0
- package/dist/workspace.js +20 -0
- package/docs/DIAGRAM_INTEGRATION_PROPOSAL.md +154 -0
- package/docs/SPECKIT.md +222 -0
- package/docs/SPEC_LAYER_PROPOSAL.md +1893 -0
- package/package.json +1 -1
- package/src/assets/AGENTS.md +82 -0
- package/src/assets/apply.dml +188 -0
- package/src/assets/spec_apply.dml +20 -0
- package/src/assets/spec_archive.dml +11 -0
- package/src/assets/spec_coverage.dml +26 -0
- package/src/assets/spec_graph.dml +12 -0
- package/src/assets/spec_merge.dml +10 -0
- package/src/assets/spec_query.dml +10 -0
- package/src/assets/spec_scaffold.dml +10 -0
- package/src/assets/spec_status.dml +7 -0
- package/src/assets/spec_validate.dml +9 -0
- package/src/assets/specs.dml +991 -0
- package/src/assets/vendor/mermaid.min.js +3636 -0
- package/src/assets/viewer.template.html +319 -0
- package/src/diagram/extract.ts +721 -0
- package/src/diagram/grade.ts +104 -0
- package/src/diagram/validate.ts +188 -0
- package/src/diagram/viewer.ts +144 -0
- package/src/diagram/workspace.ts +109 -0
- package/src/index.ts +507 -16
- package/src/model.ts +46 -0
- package/src/planner.ts +123 -3
- package/src/runtime.ts +117 -2
- package/src/workspace.ts +24 -0
|
@@ -0,0 +1,1893 @@
|
|
|
1
|
+
# A DeepClause spec layer for `deepclause-pi`
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
**Design sketch; phase 1 implemented on branch `feat/spec-layer-phase1`.** This
|
|
6
|
+
document records the design discussion that started from
|
|
7
|
+
[OpenSpec](https://github.com/Fission-AI/openspec) and asks how its ideas map onto
|
|
8
|
+
DML and the existing `deepclause-pi` plan/executor model.
|
|
9
|
+
|
|
10
|
+
Implemented in phase 1: the deterministic engine (`src/assets/specs.dml`), the
|
|
11
|
+
`spec_validate` / `spec_status` / `spec_query` / `spec_graph` skills, `/dc-check`,
|
|
12
|
+
the `dc_spec_graph` tool, and workspace seeding of `specs/`, `changes/` and `lib/`.
|
|
13
|
+
|
|
14
|
+
Implemented in phase 2: delta merging (`sp_archive/3`, `spec_merge.dml`,
|
|
15
|
+
`spec_archive.dml`) and the `/dc-archive` command, which previews the merge,
|
|
16
|
+
confirms, writes `specs/`, and moves the change folder to `changes/archive/`.
|
|
17
|
+
Unchanged requirement blocks are preserved line-for-line. The archive *move* is
|
|
18
|
+
done host-side because directory `rename_file/2` is unreliable in the WASM
|
|
19
|
+
filesystem. `RENAMED` deltas are refused for now.
|
|
20
|
+
|
|
21
|
+
Implemented in phase 3: `tasks.dml` as a first-class artifact (`plan_task/2`
|
|
22
|
+
facts read with native term I/O), scenario coverage reporting (`spec_coverage.dml`
|
|
23
|
+
and a coverage section in `/dc-check`), and a read-only `spec_scaffold.dml` that
|
|
24
|
+
drafts one task per delta scenario. Note the `plan_task` naming: `task/2` collides
|
|
25
|
+
with DML's built-in `task/N`.
|
|
26
|
+
|
|
27
|
+
Implemented in phase 4: the task driver (`lib/apply.dml`) with per-task declarative
|
|
28
|
+
checks (`exists`, `cmd`, `model`), bounded retry that threads the failure feedback
|
|
29
|
+
into the repair instruction, and a managed `plan_task_status/2` write-back block in
|
|
30
|
+
`tasks.dml`; the allowlisted `dc_verify_run` tool; the `spec_apply.dml` skill; and
|
|
31
|
+
`/dc-apply <change>`, which previews the tasks and the exact command set, confirms,
|
|
32
|
+
then applies with `pi_agent_step` and `dc_verify_run` enabled.
|
|
33
|
+
|
|
34
|
+
Implemented in phase 8: commit prompts. After `/dc-plan`, `/dc-apply` and `/dc-archive`
|
|
35
|
+
leave a dirty tree, the extension shows the changed files and offers to commit them
|
|
36
|
+
(`git add -A` plus a suggested `<action>: <change>` message), or reminds the user when
|
|
37
|
+
they decline. A clean tree is what lets the next `/dc-apply` take a rollback snapshot.
|
|
38
|
+
|
|
39
|
+
Not yet implemented: the `deltas.dml` / `index.dml` files, and `RENAMED` support in merge.
|
|
40
|
+
|
|
41
|
+
Implemented in phase 7: resumable apply and a merge guard. `/dc-plan --change` fails
|
|
42
|
+
before spending a planning turn when `tasks.dml` already exists; `--update` (or a
|
|
43
|
+
leading `update` keyword) regenerates it and resets statuses to pending. Merging now
|
|
44
|
+
refuses `MODIFIED`/`REMOVED` entries that do not exist in the target spec, and refuses
|
|
45
|
+
`MODIFIED`/`REMOVED` for a brand-new capability, instead of silently dropping them.
|
|
46
|
+
`/dc-apply` **preserves** an interrupted apply (working tree plus `done`/`failed`
|
|
47
|
+
statuses, `applyState: in_progress` in `change.json`) so a re-run resumes from the
|
|
48
|
+
remaining tasks; `/dc-apply --abort` discards it and restores the snapshot.
|
|
49
|
+
|
|
50
|
+
Implemented in phase 6: apply-time rollback. `dc_apply_snapshot` records a git ref
|
|
51
|
+
(refusing a dirty tree) in `change.json`; `dc_apply_accept` clears it on success; the
|
|
52
|
+
`/dc-apply` harness restores on any run that does not report `status: OK`, including
|
|
53
|
+
cancellation, using `git reset --hard` plus `git clean -fd`. When no snapshot is
|
|
54
|
+
available (not a git repo, or a dirty tree) the apply proceeds and the report says
|
|
55
|
+
rollback is unavailable. Two runtime quirks surfaced: `exists_file/1` is unreliable
|
|
56
|
+
in the WASM filesystem (use `open/2`), and a DML predicate named `snapshot/1`
|
|
57
|
+
collides with a runtime accessor, so the driver uses `take_snapshot/1`.
|
|
58
|
+
|
|
59
|
+
Implemented in phase 5: change-aware planning. `/dc-plan <request> --change=<slug>`
|
|
60
|
+
writes `changes/<slug>/tasks.dml` (`plan_task/2` with `satisfies` and encoded
|
|
61
|
+
`checks`, plus the managed status block) instead of a standalone plan. Check
|
|
62
|
+
encoding is `cmd:<command>`, `exists:<path>` or `model:<question>`; change plans
|
|
63
|
+
require at least one check per step, and step ids may be OpenSpec-style (`1.1`).
|
|
64
|
+
The planning prompt also instructs pi to create the change's proposal and delta
|
|
65
|
+
specs before committing. Coverage is validated by `/dc-check`, and the flow is
|
|
66
|
+
closed by `/dc-apply` and `/dc-archive`.
|
|
67
|
+
|
|
68
|
+
It builds directly on [DC_PLAN_PROPOSAL.md](DC_PLAN_PROPOSAL.md), which describes
|
|
69
|
+
the shipped `/dc-plan` + `pi_agent_step` architecture. This document does not
|
|
70
|
+
change that architecture; it proposes a spec layer on top of it.
|
|
71
|
+
|
|
72
|
+
Revision note: an earlier draft attached executable checks to requirements and
|
|
73
|
+
kept a Markdown `tasks.md`. Both were wrong and are corrected below — checks are
|
|
74
|
+
implementation details and live in `tasks.dml`, and Markdown is canonical only for
|
|
75
|
+
behavior.
|
|
76
|
+
|
|
77
|
+
Related reading:
|
|
78
|
+
|
|
79
|
+
- `.pi/deepclause/AGENTS.md` — DML authoring rules for this integration
|
|
80
|
+
- `.pi/deepclause/DML_REFERENCE.md` — bundled DML language reference
|
|
81
|
+
- `docs/AUTHORING_GUIDE_ANALYSIS.md` — why the authoring guide looks the way it does
|
|
82
|
+
|
|
83
|
+
## Goals
|
|
84
|
+
|
|
85
|
+
- Give pi a place to agree on **what to build** before building it: specs as the
|
|
86
|
+
source of truth, changes as reviewable deltas.
|
|
87
|
+
- Make spec **validation and merging deterministic DML**, not model judgment and
|
|
88
|
+
not more TypeScript string handling.
|
|
89
|
+
- Make `apply` an **executable, verifiable plan**: per-task implementation checks,
|
|
90
|
+
bounded repair retries, and a deterministic rollback path.
|
|
91
|
+
- Stay inside the existing constraints: minimal slash commands, no Markdown-to-DML
|
|
92
|
+
compiler, user-triggered execution, opt-in model-callable `dc_run`.
|
|
93
|
+
|
|
94
|
+
## Non-goals
|
|
95
|
+
|
|
96
|
+
- **No natural-language → DML compiler.** Parsing and validating a *structured*
|
|
97
|
+
spec is deterministic logic. Turning prose into DML is not, and stays out.
|
|
98
|
+
- **No second session store.** `.pi/deepclause/` is files; pi remains the session
|
|
99
|
+
owner. The parsed term tree is never persisted.
|
|
100
|
+
- **No reimplementation of OpenSpec's CLI.** It has a mature CLI and a 30+ tool
|
|
101
|
+
integration matrix. Only two things are borrowed: the spec/change convention and
|
|
102
|
+
the artifact-graph idea.
|
|
103
|
+
- **No new top-level commands beyond the agreed set.** OpenSpec's phase commands
|
|
104
|
+
(`propose`, `apply`, `archive`, `verify`, …) become arguments to `/dc-plan` and
|
|
105
|
+
named skills run with `/dc-run`.
|
|
106
|
+
|
|
107
|
+
## The mental model
|
|
108
|
+
|
|
109
|
+
> **Specs are behavior. Changes are executable. `/dc-plan` thinks and writes;
|
|
110
|
+
> `/dc-run` proves and applies.**
|
|
111
|
+
|
|
112
|
+
Two verbs for the user, not twelve. Four artifacts per change, each with one job:
|
|
113
|
+
|
|
114
|
+
| Layer | Artifact | Answers | Canonical form |
|
|
115
|
+
|---|---|---|---|
|
|
116
|
+
| Behavior | `specs/**`, delta | *what must be true* | Markdown |
|
|
117
|
+
| Approach | `design.md` (optional) | *how, and why* | Markdown |
|
|
118
|
+
| Implementation | `tasks.dml` | *what to do, and how far we got* | DML facts |
|
|
119
|
+
| Execution | `apply.dml` | *how to drive it* | DML program |
|
|
120
|
+
|
|
121
|
+
The rule that keeps them separate:
|
|
122
|
+
|
|
123
|
+
> **Markdown is canonical only for behavior and rationale. Anything executable,
|
|
124
|
+
> enumerable, or stateful — tasks, checks, progress — is DML data.**
|
|
125
|
+
|
|
126
|
+
## The workflow in practice (user perspective)
|
|
127
|
+
|
|
128
|
+
You describe what you want in plain language — "add dark mode with
|
|
129
|
+
system-preference detection" — and run `/dc-plan`. Pi explores the repository,
|
|
130
|
+
reads the existing specs, and writes a change folder: a proposal, a behavior-only
|
|
131
|
+
delta under `changes/<slug>/specs/`, an optional `design.md`, plus `tasks.dml` (the
|
|
132
|
+
implementation plan, each task carrying its verification) and `apply.dml` (the
|
|
133
|
+
executable entry). Nothing is final until you approve it at the commit dialog, and
|
|
134
|
+
the specs themselves are plain Markdown you can read and hand-edit at any time.
|
|
135
|
+
|
|
136
|
+
`/dc-check <change>` then validates everything deterministically with zero model
|
|
137
|
+
calls — grammar, delta consistency, scenario coverage, discoverable checks, and
|
|
138
|
+
conflicts with other in-flight changes. `/dc-run <change>` executes the plan: it
|
|
139
|
+
delegates each bounded step to pi with exactly the tools that step needs, runs the
|
|
140
|
+
task's declared checks, and on a failed check retries with the failure evidence fed
|
|
141
|
+
back into the repair attempt. You approve the verification commands once as a suite
|
|
142
|
+
rather than per invocation, and if a task cannot be repaired the working tree is
|
|
143
|
+
restored from the snapshot taken at the start, so a failed apply never leaves a
|
|
144
|
+
half-applied change.
|
|
145
|
+
|
|
146
|
+
When it succeeds, `/dc-run spec_archive <change>` shows a diff and merges the delta
|
|
147
|
+
into `specs/`, moves the change to `changes/archive/`, and updates the delta index.
|
|
148
|
+
Because features, deltas, tasks, and checks are all queryable facts, you can ask
|
|
149
|
+
questions no chat history can answer: which changes touch `ui/theme`, which
|
|
150
|
+
scenarios are still uncovered, whether two in-flight changes collide on the same
|
|
151
|
+
requirement, and which check proved which scenario. The conversation proposes, the
|
|
152
|
+
spec is the contract, the plan executes, and the facts let you audit.
|
|
153
|
+
|
|
154
|
+
The rest of this section walks the same path with exact commands, showing what
|
|
155
|
+
lands on disk at each step and what the runtime does while a plan is executing.
|
|
156
|
+
|
|
157
|
+
### 0. Bootstrap — `/dc`
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
> /dc
|
|
161
|
+
|
|
162
|
+
DeepClause pi runtime
|
|
163
|
+
Model: anthropic/claude-sonnet-4
|
|
164
|
+
Status: idle
|
|
165
|
+
Root: .pi/deepclause
|
|
166
|
+
Skills: .pi/deepclause/skills
|
|
167
|
+
Plans: .pi/deepclause/plans
|
|
168
|
+
Context: turn (verbose default: false)
|
|
169
|
+
Model tool (dc_run): disabled
|
|
170
|
+
Commands: /dc-list, /dc-plan, /dc-run, /dc-tool, /dc-cancel
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Files.** On first use, `initializeWorkspace()` creates
|
|
174
|
+
`.pi/deepclause/{config.json,AGENTS.md,DML_REFERENCE.md,skills/,plans/}` and seeds
|
|
175
|
+
`example.dml` and `deep_research.dml`. Every write uses `writeIfMissing`, so
|
|
176
|
+
existing user files are never overwritten. The spec layer adds `specs/`,
|
|
177
|
+
`changes/`, and `lib/`, seeded the same way.
|
|
178
|
+
|
|
179
|
+
### 1. Explore — just talk
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
> how should we do theming without adding dependencies?
|
|
183
|
+
|
|
184
|
+
[pi reads src/ styles, package.json, and specs/ui/system.spec.md,
|
|
185
|
+
then answers in the session]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**No command, no files.** Exploration is ordinary conversation: pi's system prompt
|
|
189
|
+
already carries the DeepClause authoring instructions, so nothing has to be
|
|
190
|
+
"unlocked." There is no planning transaction, no `dc_plan_commit`, and no change
|
|
191
|
+
folder. Using `/dc-plan` here would open a transaction that never commits (and log
|
|
192
|
+
that fact in debug), so it is the wrong tool for a discussion you may never act on.
|
|
193
|
+
|
|
194
|
+
### 2. Plan — `/dc-plan`
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
> /dc-plan add dark mode with system-preference detection --name=add_dark_mode
|
|
198
|
+
|
|
199
|
+
⚠ Starting a contextual pi planning turn. Review the generated plan before it is written.
|
|
200
|
+
|
|
201
|
+
[pi explores: src/theme.ts, package.json, specs/ui/system.spec.md, changes/]
|
|
202
|
+
[pi writes the Markdown artifacts with its normal tools]
|
|
203
|
+
[pi calls dc_plan_commit]
|
|
204
|
+
|
|
205
|
+
⚠ Create executable DeepClause plan?
|
|
206
|
+
Add dark mode
|
|
207
|
+
Objective: Add a light/dark theme that defaults to the system preference.
|
|
208
|
+
Steps: 5
|
|
209
|
+
Pi tools: read, edit
|
|
210
|
+
1. [pi] 1.1 Add a ThemeProvider context
|
|
211
|
+
2. [pi] 1.2 Add light/dark CSS custom properties
|
|
212
|
+
3. [pi] 1.3 Default to prefers-color-scheme
|
|
213
|
+
4. [pi] 1.4 Reject invalid stored values
|
|
214
|
+
5. [pi] 1.5 Add the theme toggle to the header
|
|
215
|
+
[Confirm] [Cancel]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Files after the turn:**
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
changes/add_dark_mode/
|
|
222
|
+
├── change.json # schema, created, digests, snapshot ref
|
|
223
|
+
├── proposal.md # pi, normal tools
|
|
224
|
+
├── specs/ui/theme.spec.md # pi, normal tools — behavior only
|
|
225
|
+
├── design.md # pi, normal tools
|
|
226
|
+
├── deltas.dml # emitted: delta ops + delta_status(pending)
|
|
227
|
+
├── tasks.dml # emitted: plan_task/2 definitions + plan_task_status/2
|
|
228
|
+
└── apply.dml # emitted: entry + "% Required pi tools:" metadata
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**What happened during the turn.** Pi never writes DML. The Markdown artifacts are
|
|
232
|
+
written with ordinary file tools. `dc_plan_commit` then runs `validatePlanSpec`
|
|
233
|
+
against the live snapshot — every requested tool must exist *and* be currently
|
|
234
|
+
active, no step may request a control tool, every delta scenario must be covered,
|
|
235
|
+
and every `cmd(...)` check must be discoverable in the repo — then assembles the
|
|
236
|
+
DML with `assemblePlanDml`, validates it with `validateWithProlog`, and writes it
|
|
237
|
+
non-destructively. Result:
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
DeepClause
|
|
241
|
+
Created change add_dark_mode
|
|
242
|
+
proposal.md, specs/ui/theme.spec.md, design.md
|
|
243
|
+
tasks.dml 5 tasks, 5 checks, 3 scenarios covered
|
|
244
|
+
apply.dml contextual plan (2 pi tools)
|
|
245
|
+
Run it with: /dc-run add_dark_mode
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**What is in the generated files.**
|
|
249
|
+
|
|
250
|
+
`proposal.md` — why, what, capabilities, impact:
|
|
251
|
+
|
|
252
|
+
```markdown
|
|
253
|
+
# Add dark mode
|
|
254
|
+
|
|
255
|
+
## Why
|
|
256
|
+
Users on dark-preference systems get a bright UI with no way to change it.
|
|
257
|
+
|
|
258
|
+
## What Changes
|
|
259
|
+
- Introduce a `ui/theme` capability for runtime theme selection.
|
|
260
|
+
- Default to the operating system colour-scheme preference on first run.
|
|
261
|
+
- Persist an explicit user choice.
|
|
262
|
+
|
|
263
|
+
## Capabilities
|
|
264
|
+
### New Capabilities
|
|
265
|
+
- `ui/theme`: runtime light/dark theme selection and persistence.
|
|
266
|
+
|
|
267
|
+
### Modified Capabilities
|
|
268
|
+
- `ui/system`: theme switching must no longer require a reload.
|
|
269
|
+
|
|
270
|
+
## Impact
|
|
271
|
+
- `src/theme/` (new), `src/app/App.tsx`, `index.html`
|
|
272
|
+
- No new dependencies.
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`specs/ui/theme.spec.md` (inside the change) — the delta. Behavior only, no
|
|
276
|
+
implementation detail:
|
|
277
|
+
|
|
278
|
+
```markdown
|
|
279
|
+
---
|
|
280
|
+
change: add_dark_mode
|
|
281
|
+
schema: spec-driven
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
# Spec Delta
|
|
285
|
+
|
|
286
|
+
## Purpose
|
|
287
|
+
Lets users choose between light and dark themes, defaulting to the operating
|
|
288
|
+
system preference.
|
|
289
|
+
|
|
290
|
+
## ADDED Requirements
|
|
291
|
+
|
|
292
|
+
### Requirement: Theme selection
|
|
293
|
+
The app SHALL let users switch between light and dark themes at runtime.
|
|
294
|
+
|
|
295
|
+
#### Scenario: User toggles dark mode
|
|
296
|
+
- **WHEN** the user clicks the theme toggle
|
|
297
|
+
- **THEN** the app switches to dark mode and persists the choice
|
|
298
|
+
|
|
299
|
+
#### Scenario: Invalid stored value is rejected
|
|
300
|
+
- **WHEN** a stored theme value is neither "light" nor "dark"
|
|
301
|
+
- **THEN** the app falls back to the system preference and shows no error
|
|
302
|
+
|
|
303
|
+
### Requirement: System-preference default
|
|
304
|
+
The app SHALL default to the operating system colour-scheme preference when no
|
|
305
|
+
choice has been stored.
|
|
306
|
+
|
|
307
|
+
#### Scenario: First run on a dark-preference system
|
|
308
|
+
- **WHEN** the app starts with no stored theme and the OS reports dark
|
|
309
|
+
- **THEN** it renders dark without writing a stored choice
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`specs/ui/system.spec.md` (inside the change) — the modification to an existing
|
|
313
|
+
capability:
|
|
314
|
+
|
|
315
|
+
```markdown
|
|
316
|
+
---
|
|
317
|
+
change: add_dark_mode
|
|
318
|
+
schema: spec-driven
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
# Spec Delta
|
|
322
|
+
|
|
323
|
+
## MODIFIED Requirements
|
|
324
|
+
|
|
325
|
+
### Requirement: Theme switching
|
|
326
|
+
The app SHALL apply theme changes without a full page reload.
|
|
327
|
+
|
|
328
|
+
#### Scenario: No reload on toggle
|
|
329
|
+
- **WHEN** the user toggles the theme
|
|
330
|
+
- **THEN** the visible theme updates in place and the document is not reloaded
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`design.md` — approach and trade-offs, where implementation detail is allowed:
|
|
334
|
+
|
|
335
|
+
```markdown
|
|
336
|
+
# Design
|
|
337
|
+
|
|
338
|
+
## Context
|
|
339
|
+
The app reads a theme once from localStorage at boot (`src/app/App.tsx`).
|
|
340
|
+
|
|
341
|
+
## Goals / Non-Goals
|
|
342
|
+
**Goals:** no new dependencies; no reload on toggle.
|
|
343
|
+
**Non-Goals:** per-component theming; high-contrast mode.
|
|
344
|
+
|
|
345
|
+
## Decisions
|
|
346
|
+
- CSS custom properties on `:root`, not a CSS-in-JS theme object — no dependency,
|
|
347
|
+
and it works with the existing stylesheet.
|
|
348
|
+
- `prefers-color-scheme` via `matchMedia`, read once at boot and subscribed for
|
|
349
|
+
later changes.
|
|
350
|
+
|
|
351
|
+
## Risks / Trade-offs
|
|
352
|
+
- [Flash of the wrong theme on first paint] → set the class from an inline script
|
|
353
|
+
before hydration.
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`deltas.dml` — the delta as queryable facts plus lifecycle state:
|
|
357
|
+
|
|
358
|
+
```prolog
|
|
359
|
+
% deltas.dml — spec deltas for change add_dark_mode
|
|
360
|
+
% Derived from:
|
|
361
|
+
% changes/add_dark_mode/specs/ui/theme.spec.md sha256:9f2c…
|
|
362
|
+
% changes/add_dark_mode/specs/ui/system.spec.md sha256:41ab…
|
|
363
|
+
|
|
364
|
+
delta("add_dark_mode", added, "ui/theme", req("theme-selection", "Theme selection")).
|
|
365
|
+
delta("add_dark_mode", added, "ui/theme", req("system-preference", "System-preference default")).
|
|
366
|
+
delta("add_dark_mode", modified, "ui/system", req("theme-switching", "Theme switching")).
|
|
367
|
+
|
|
368
|
+
change_meta("add_dark_mode", schema(spec_driven), skip_specs(false),
|
|
369
|
+
created("2025-09-17"), snapshot(none)).
|
|
370
|
+
|
|
371
|
+
% --- lifecycle state (managed by apply / spec_archive) ---
|
|
372
|
+
delta_status("add_dark_mode", "ui/theme", "theme-selection", pending).
|
|
373
|
+
delta_status("add_dark_mode", "ui/theme", "system-preference", pending).
|
|
374
|
+
delta_status("add_dark_mode", "ui/system", "theme-switching", pending).
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`tasks.dml` — the implementation plan and its state. Note `satisfies` closes the loop
|
|
378
|
+
back to the delta's scenario ids, and every task declares at least one check:
|
|
379
|
+
|
|
380
|
+
```prolog
|
|
381
|
+
% tasks.dml — implementation plan for change add_dark_mode
|
|
382
|
+
% Plan format: 2
|
|
383
|
+
|
|
384
|
+
plan_task("1.1", task{
|
|
385
|
+
executor: pi,
|
|
386
|
+
do: "Add a ThemeProvider context exposing theme and setTheme.",
|
|
387
|
+
tools: ["read", "edit"],
|
|
388
|
+
expected: "src/theme/ThemeProvider.tsx exports ThemeProvider and typechecks.",
|
|
389
|
+
satisfies: ["ui/theme#theme-selection"],
|
|
390
|
+
checks: [ exists("src/theme/ThemeProvider.tsx"),
|
|
391
|
+
cmd("npm run typecheck") ]
|
|
392
|
+
}).
|
|
393
|
+
|
|
394
|
+
plan_task("1.2", task{
|
|
395
|
+
executor: pi,
|
|
396
|
+
do: "Add light/dark CSS custom properties and apply them to the document root.",
|
|
397
|
+
tools: ["read", "edit"],
|
|
398
|
+
expected: "Toggling updates the visible theme without a reload.",
|
|
399
|
+
satisfies: ["ui/theme#theme-selection", "ui/system#theme-switching"],
|
|
400
|
+
checks: [ cmd("npx vitest run src/theme") ]
|
|
401
|
+
}).
|
|
402
|
+
|
|
403
|
+
plan_task("1.3", task{
|
|
404
|
+
executor: pi,
|
|
405
|
+
do: "Default to prefers-color-scheme when nothing is stored, without persisting.",
|
|
406
|
+
tools: ["read", "edit"],
|
|
407
|
+
expected: "A first run on a dark system renders dark and writes no stored key.",
|
|
408
|
+
satisfies: ["ui/theme#system-preference"],
|
|
409
|
+
checks: [ cmd("npx vitest run src/theme") ]
|
|
410
|
+
}).
|
|
411
|
+
|
|
412
|
+
plan_task("1.4", task{
|
|
413
|
+
executor: pi,
|
|
414
|
+
do: "Reject a stored value that is neither light nor dark, falling back to the system preference.",
|
|
415
|
+
tools: ["read", "edit"],
|
|
416
|
+
expected: "An invalid stored value renders the system preference and shows no error.",
|
|
417
|
+
satisfies: ["ui/theme#invalid-stored-value"],
|
|
418
|
+
checks: [ cmd("npx vitest run src/theme") ]
|
|
419
|
+
}).
|
|
420
|
+
|
|
421
|
+
plan_task("1.5", task{
|
|
422
|
+
executor: pi,
|
|
423
|
+
do: "Add the theme toggle to the header.",
|
|
424
|
+
tools: ["read", "edit"],
|
|
425
|
+
expected: "The toggle switches themes and persists the choice.",
|
|
426
|
+
satisfies: ["ui/theme#theme-selection"],
|
|
427
|
+
checks: [ exists("src/components/ThemeToggle.tsx"),
|
|
428
|
+
cmd("npm run typecheck") ]
|
|
429
|
+
}).
|
|
430
|
+
|
|
431
|
+
% --- execution state (managed by apply.dml; do not edit by hand) ---
|
|
432
|
+
plan_task_status("1.1", pending).
|
|
433
|
+
plan_task_status("1.2", pending).
|
|
434
|
+
plan_task_status("1.3", pending).
|
|
435
|
+
plan_task_status("1.4", pending).
|
|
436
|
+
plan_task_status("1.5", pending).
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
`apply.dml` — only wiring and the metadata `/dc-run` preflight reads, so the plan
|
|
440
|
+
file stays clean data:
|
|
441
|
+
|
|
442
|
+
```prolog
|
|
443
|
+
% apply.dml — executable entry for change add_dark_mode
|
|
444
|
+
% Plan format: 2
|
|
445
|
+
% Change: add_dark_mode
|
|
446
|
+
% Required pi tools: read, edit
|
|
447
|
+
% Contextual: true
|
|
448
|
+
|
|
449
|
+
:- consult('.pi/deepclause/lib/apply.dml').
|
|
450
|
+
:- consult('.pi/deepclause/changes/add_dark_mode/tasks.dml').
|
|
451
|
+
|
|
452
|
+
agent_main :-
|
|
453
|
+
run_plan('.pi/deepclause/changes/add_dark_mode/tasks.dml',
|
|
454
|
+
"add_dark_mode", 3).
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
`change.json` — the machine manifest the harness reads: schema, drift digests, and
|
|
458
|
+
the snapshot ref (still `null` until apply starts):
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
{
|
|
462
|
+
"schema": "spec-driven",
|
|
463
|
+
"slug": "add_dark_mode",
|
|
464
|
+
"created": "2025-09-17",
|
|
465
|
+
"contextMode": "branch",
|
|
466
|
+
"digests": {
|
|
467
|
+
"specs/ui/theme.spec.md": "sha256:9f2c…",
|
|
468
|
+
"specs/ui/system.spec.md": "sha256:41ab…"
|
|
469
|
+
},
|
|
470
|
+
"snapshot": null,
|
|
471
|
+
"skipSpecs": false,
|
|
472
|
+
"retireCapabilities": false
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Why four artifacts instead of one: the **delta** is what a reviewer reads and what
|
|
477
|
+
archive merges; `deltas.dml` is that same delta as **facts** so `/dc-check` and
|
|
478
|
+
`spec_status` can reason without re-parsing Markdown; `tasks.dml` is the
|
|
479
|
+
**implementation plan**; and `apply.dml` carries only wiring plus preflight
|
|
480
|
+
metadata.
|
|
481
|
+
|
|
482
|
+
### 3. Validate — `/dc-check`
|
|
483
|
+
|
|
484
|
+
```text
|
|
485
|
+
> /dc-check add_dark_mode
|
|
486
|
+
|
|
487
|
+
DeepClause CHECK add_dark_mode (spec_validate.dml) 0 tokens
|
|
488
|
+
|
|
489
|
+
requirements 2 added, 1 modified, 0 removed, 0 renamed
|
|
490
|
+
scenarios 3 ok (every requirement has ≥1)
|
|
491
|
+
coverage 3/3 scenarios referenced by tasks.dml
|
|
492
|
+
checks 5/5 tasks declare verification; 5 commands discoverable
|
|
493
|
+
|
|
494
|
+
0 errors, 0 warnings, 0 model calls.
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Files.** Nothing is written. `spec_validate.dml` consults `lib/specs.dml`, parses
|
|
498
|
+
the delta and the existing specs, reads `tasks.dml` as facts, and emits a report. If
|
|
499
|
+
an error is reported, fix it with `/dc-plan update add_dark_mode fix the validation
|
|
500
|
+
errors` (a planning turn that edits the Markdown and re-commits the plan) and run
|
|
501
|
+
`/dc-check` again.
|
|
502
|
+
|
|
503
|
+
### 4. Execute — `/dc-run`
|
|
504
|
+
|
|
505
|
+
```text
|
|
506
|
+
> /dc-run add_dark_mode
|
|
507
|
+
|
|
508
|
+
⚠ Run contextual DeepClause plan?
|
|
509
|
+
Change: add_dark_mode (5 tasks, 3 scenarios)
|
|
510
|
+
Delegates bounded steps to pi with these tools: read, edit
|
|
511
|
+
Verification commands (approved once for this run):
|
|
512
|
+
npm run typecheck
|
|
513
|
+
npx vitest run src/theme
|
|
514
|
+
npm run build
|
|
515
|
+
Max attempts per task: 3.
|
|
516
|
+
The working tree is snapshotted first and restored if a task cannot be repaired.
|
|
517
|
+
[Confirm] [Cancel]
|
|
518
|
+
|
|
519
|
+
DeepClause RUNNING changes/add_dark_mode/apply.dml 12.4s
|
|
520
|
+
anthropic/claude-sonnet-4 | context=branch | verbose
|
|
521
|
+
Phase: task 1.3, attempt 1/3: default to prefers-color-scheme
|
|
522
|
+
Usage: 41,207 input / 6,940 output tokens
|
|
523
|
+
Output:
|
|
524
|
+
Task 1.1, attempt 1/3
|
|
525
|
+
Task 1.2, attempt 1/3
|
|
526
|
+
Task 1.2 failed verification: command failed: npx vitest run src/theme
|
|
527
|
+
Task 1.2, attempt 2/3
|
|
528
|
+
Task 1.3, attempt 1/3
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**What happens during execution, step by step:**
|
|
532
|
+
|
|
533
|
+
1. **Preflight.** `/dc-run` resolves the change to `changes/add_dark_mode/apply.dml`,
|
|
534
|
+
reads `% Required pi tools:` and `% Contextual:` from its metadata, verifies each
|
|
535
|
+
tool is installed and active, and collects the verification suite from the
|
|
536
|
+
`checks(...)` of every task in `tasks.dml`. You approve the suite once.
|
|
537
|
+
2. **Snapshot.** `dc_apply_snapshot` records `HEAD` and `git status`; the ref is
|
|
538
|
+
written to `change.json`. A dirty tree is refused unless `--allow-dirty`.
|
|
539
|
+
3. **Per task, in order:** delegate the step through `pi_agent_step`, which swaps in
|
|
540
|
+
exactly that step's tools and restores the previous tool set on every exit path;
|
|
541
|
+
then run the task's checks through `dc_verify_run` (restricted to the approved
|
|
542
|
+
commands). On success, write `plan_task_status(Id, done(N))` into the managed block of
|
|
543
|
+
`tasks.dml` and move on.
|
|
544
|
+
4. **On a failed check:** append the failure evidence to the *next* attempt's
|
|
545
|
+
instruction ("the previous attempt failed verification with: … fix only what is
|
|
546
|
+
needed") and retry, up to the attempt budget. Each attempt rolls memory back, so
|
|
547
|
+
a repair starts clean and only sees the threaded evidence.
|
|
548
|
+
5. **Change-level gate.** After all tasks, `verify_change/2` asserts that every
|
|
549
|
+
scenario in the delta has at least one passing check. A plan cannot answer
|
|
550
|
+
successfully without this.
|
|
551
|
+
6. **Accept.** `dc_apply_accept` marks the snapshot accepted; the result is published
|
|
552
|
+
into the pi session with usage and status.
|
|
553
|
+
|
|
554
|
+
```text
|
|
555
|
+
DeepClause
|
|
556
|
+
Change add_dark_mode applied 42.1s
|
|
557
|
+
5/5 tasks verified (1 repair), 3/3 scenarios covered
|
|
558
|
+
Snapshot abc1234 accepted
|
|
559
|
+
Usage: 118,442 input / 21,309 output tokens
|
|
560
|
+
Next: /dc-run spec_archive add_dark_mode
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
`tasks.dml` after the run:
|
|
564
|
+
|
|
565
|
+
```prolog
|
|
566
|
+
% --- execution state (managed by apply.dml; do not edit by hand) ---
|
|
567
|
+
plan_task_status("1.1", done(1)).
|
|
568
|
+
plan_task_status("1.2", done(2)).
|
|
569
|
+
plan_task_status("1.3", done(1)).
|
|
570
|
+
plan_task_status("1.4", done(1)).
|
|
571
|
+
plan_task_status("1.5", done(1)).
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### 5. Revise — `/dc-plan update`
|
|
575
|
+
|
|
576
|
+
```text
|
|
577
|
+
> /dc-plan update add_dark_mode make the toggle keyboard-accessible
|
|
578
|
+
|
|
579
|
+
⚠ Starting a contextual pi planning turn...
|
|
580
|
+
[pi edits specs/ui/theme.spec.md and tasks.dml non-destructively]
|
|
581
|
+
[dc_plan_commit re-validates coverage and writes the updated files]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Files.** The delta and `tasks.dml` are edited in place; already-verified tasks keep
|
|
585
|
+
their `done(N)` status, newly added tasks start `pending`. `change.json` digests are
|
|
586
|
+
refreshed.
|
|
587
|
+
|
|
588
|
+
### 6. Land — `/dc-run spec_archive`
|
|
589
|
+
|
|
590
|
+
```text
|
|
591
|
+
> /dc-run spec_archive add_dark_mode
|
|
592
|
+
|
|
593
|
+
⚠ Archive will modify .pi/deepclause/specs/ui/theme.spec.md
|
|
594
|
+
+ ADDED Theme selection
|
|
595
|
+
+ ADDED System-preference default
|
|
596
|
+
~ MODIFIED Theme switching (2 lines changed)
|
|
597
|
+
Digest check: specs match the delta (sha256:9f2c…)
|
|
598
|
+
[Confirm] [Cancel]
|
|
599
|
+
|
|
600
|
+
DeepClause
|
|
601
|
+
Archived changes/add_dark_mode → changes/archive/2025-09-17-add_dark_mode
|
|
602
|
+
specs/ui/theme.spec.md updated (+2, ~1)
|
|
603
|
+
index.dml regenerated; delta_status set to archived
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
**What happens.** `spec_merge.dml` re-parses the existing spec and the delta,
|
|
607
|
+
re-checks the drift digest, applies RENAMED → REMOVED → MODIFIED → ADDED, validates
|
|
608
|
+
the merged spec, and only then writes `specs/`. The change folder moves to
|
|
609
|
+
`changes/archive/<date>-<slug>`, `delta_status` becomes `archived`, and the derived
|
|
610
|
+
`index.dml` is regenerated.
|
|
611
|
+
|
|
612
|
+
**After the archive**, `specs/` holds the capability as the new source of truth:
|
|
613
|
+
|
|
614
|
+
```markdown
|
|
615
|
+
---
|
|
616
|
+
capability: ui/theme
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
# Theme Specification
|
|
620
|
+
|
|
621
|
+
## Purpose
|
|
622
|
+
Lets users choose between light and dark themes, defaulting to the operating
|
|
623
|
+
system preference.
|
|
624
|
+
|
|
625
|
+
## Requirements
|
|
626
|
+
|
|
627
|
+
### Requirement: Theme selection
|
|
628
|
+
The app SHALL let users switch between light and dark themes at runtime.
|
|
629
|
+
|
|
630
|
+
#### Scenario: User toggles dark mode
|
|
631
|
+
- **WHEN** the user clicks the theme toggle
|
|
632
|
+
- **THEN** the app switches to dark mode and persists the choice
|
|
633
|
+
|
|
634
|
+
#### Scenario: Invalid stored value is rejected
|
|
635
|
+
- **WHEN** a stored theme value is neither "light" nor "dark"
|
|
636
|
+
- **THEN** the app falls back to the system preference and shows no error
|
|
637
|
+
|
|
638
|
+
### Requirement: System-preference default
|
|
639
|
+
The app SHALL default to the operating system colour-scheme preference when no
|
|
640
|
+
choice has been stored.
|
|
641
|
+
|
|
642
|
+
#### Scenario: First run on a dark-preference system
|
|
643
|
+
- **WHEN** the app starts with no stored theme and the OS reports dark
|
|
644
|
+
- **THEN** it renders dark without writing a stored choice
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
and the regenerated `index.dml` looks like this:
|
|
648
|
+
|
|
649
|
+
```prolog
|
|
650
|
+
% index.dml — DERIVED. Do not edit. Regenerate with /dc-run spec_reindex.
|
|
651
|
+
capability("ui/theme", "Theme Specification",
|
|
652
|
+
purpose("Lets users choose between light and dark themes, defaulting to the operating system preference."),
|
|
653
|
+
source("specs/ui/theme.spec.md", "sha256:6d10…")).
|
|
654
|
+
requirement("ui/theme", "theme-selection", "Theme selection").
|
|
655
|
+
requirement("ui/theme", "system-preference", "System-preference default").
|
|
656
|
+
scenario("ui/theme", "user-toggles-dark-mode", "User toggles dark mode", "theme-selection").
|
|
657
|
+
scenario("ui/theme", "invalid-stored-value", "Invalid stored value is rejected", "theme-selection").
|
|
658
|
+
scenario("ui/theme", "first-run-dark-system", "First run on a dark-preference system", "system-preference").
|
|
659
|
+
touch("add_dark_mode", "ui/theme").
|
|
660
|
+
touch("add_dark_mode", "ui/system").
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### 7. Query — `/dc-run spec_status` and `spec_query`
|
|
664
|
+
|
|
665
|
+
```text
|
|
666
|
+
> /dc-run spec_status
|
|
667
|
+
|
|
668
|
+
DeepClause SPECS (spec_status.dml) 0 tokens
|
|
669
|
+
capabilities 2 specs/ui/theme.spec.md, specs/ui/system.spec.md
|
|
670
|
+
in flight 1 add_dark_mode 5/5 tasks, 3/3 scenarios
|
|
671
|
+
archived 3
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
```text
|
|
675
|
+
> /dc-run spec_query ui/theme
|
|
676
|
+
|
|
677
|
+
capability ui/theme — Theme Specification
|
|
678
|
+
requirements
|
|
679
|
+
theme-selection 2 scenarios verified
|
|
680
|
+
system-preference 1 scenario verified
|
|
681
|
+
touched by
|
|
682
|
+
add_dark_mode archived 2025-09-17
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
**Files.** Both are read-only and derived-on-demand: they parse the specs (or read
|
|
686
|
+
`index.dml` when it is present and its digests match) and write nothing.
|
|
687
|
+
|
|
688
|
+
### When things go wrong
|
|
689
|
+
|
|
690
|
+
```text
|
|
691
|
+
> /dc-run add_dark_mode
|
|
692
|
+
...
|
|
693
|
+
Phase: task 1.4, attempt 3/3: add the theme toggle
|
|
694
|
+
Task 1.4 failed verification: command failed: npx vitest run src/theme
|
|
695
|
+
|
|
696
|
+
⚠ Change add_dark_mode could not be verified after 3 attempts.
|
|
697
|
+
Restoring the working tree to snapshot abc1234…
|
|
698
|
+
|
|
699
|
+
DeepClause execution failed: step_exhausted(1.4, 3)
|
|
700
|
+
Working tree restored to abc1234. tasks.dml reset to pending.
|
|
701
|
+
Re-run /dc-check add_dark_mode for details, then /dc-plan update add_dark_mode.
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**What happens.** `attempt/7` exhausts the budget, writes `failed(3, Evidence)`, and
|
|
705
|
+
throws. The DML `catch` calls `dc_apply_restore`; the harness `finally` performs the
|
|
706
|
+
same restore on abort or crash, so `/dc-cancel` and a killed session are covered
|
|
707
|
+
too. Because progress lives in a tracked file, the restore reverts it along with the
|
|
708
|
+
code — the change genuinely is not done.
|
|
709
|
+
|
|
710
|
+
### Cancel
|
|
711
|
+
|
|
712
|
+
```text
|
|
713
|
+
> /dc-cancel
|
|
714
|
+
|
|
715
|
+
⚠ Cancelling DeepClause execution
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
Aborts the active controller: a running `pi_agent_step` is aborted, the tool set is
|
|
719
|
+
restored, and the harness `finally` restores the snapshot. `tasks.dml` returns to
|
|
720
|
+
its last persisted state.
|
|
721
|
+
|
|
722
|
+
### Brownfield onboarding
|
|
723
|
+
|
|
724
|
+
```text
|
|
725
|
+
> /dc-plan onboard the checkout flow
|
|
726
|
+
|
|
727
|
+
[pi reads src/checkout and drafts specs/checkout/checkout.spec.md,
|
|
728
|
+
marked status: draft and inferred: true; no change folder, no execution]
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
**Files.** Writes a draft capability spec directly under `specs/` (not a delta),
|
|
732
|
+
because there is no change to apply — the behaviour already exists. You review and
|
|
733
|
+
hand-edit it; once it looks right, remove the draft marker and it becomes source of
|
|
734
|
+
truth. Onboarding never runs `dc_plan_commit` and never modifies code.
|
|
735
|
+
|
|
736
|
+
## What was borrowed from OpenSpec
|
|
737
|
+
|
|
738
|
+
| Borrowed | Not borrowed |
|
|
739
|
+
|---|---|
|
|
740
|
+
| `specs/` (current behavior) vs `changes/` (proposed deltas) | the npm CLI / binary |
|
|
741
|
+
| `### Requirement:` + `#### Scenario:` grammar | the 30+ tool integrations |
|
|
742
|
+
| Delta ops: ADDED / MODIFIED / REMOVED / RENAMED | Markdown as the only interface |
|
|
743
|
+
| "Every task states how to verify completion" | the imperative `specs-apply.ts` implementation |
|
|
744
|
+
| Artifact graph with `requires:` + instructions | `tasks.md` (replaced by `tasks.dml`) |
|
|
745
|
+
|
|
746
|
+
OpenSpec's deterministic spec logic is ~4,400 lines of TypeScript
|
|
747
|
+
(`src/core/specs-apply.ts` alone is 1,378). It encodes ordering
|
|
748
|
+
(RENAMED → REMOVED → MODIFIED → ADDED) and a pile of conflict rules imperatively,
|
|
749
|
+
and its own instructions warn about silent failures such as *"Scenarios MUST use
|
|
750
|
+
exactly 4 hashtags; using 3 fails silently."* That is precisely the kind of logic
|
|
751
|
+
DML is good at, and the kind of failure a grammar prevents.
|
|
752
|
+
|
|
753
|
+
## User-facing surface
|
|
754
|
+
|
|
755
|
+
### Commands
|
|
756
|
+
|
|
757
|
+
| OpenSpec | DeepClause surface | Who does the work |
|
|
758
|
+
|---|---|---|
|
|
759
|
+
| `/opsx:explore` | just talk to pi (no command, no transaction) | pi turn |
|
|
760
|
+
| `/opsx:new`, `/opsx:propose` | `/dc-plan <request>` | pi turn + `dc_plan_commit` |
|
|
761
|
+
| `/opsx:continue`, `/opsx:update` | `/dc-plan update <change> <what>` | pi turn |
|
|
762
|
+
| `/opsx:apply` | `/dc-apply <change>` | `lib/apply.dml` + `pi_agent_step` + `dc_verify_run` |
|
|
763
|
+
| `/opsx:verify` | `/dc-check <change>` | **pure DML, zero model calls** |
|
|
764
|
+
| `/opsx:archive`, `/opsx:bulk-archive` | `/dc-archive <change>` (preview + confirm + merge + move) | **pure DML merge + one confirm** |
|
|
765
|
+
| `/opsx:sync` | `/dc-run spec_sync <change>` | pure DML |
|
|
766
|
+
| `/opsx:onboard` | nothing — pi reads the repo natively | pi turn |
|
|
767
|
+
|
|
768
|
+
`/dc-check` graduates from "optional later" in `AGENTS.md` to required. No other
|
|
769
|
+
commands are added; `/dc`, `/dc-list`, `/dc-tool`, `/dc-cancel` keep their current
|
|
770
|
+
meaning.
|
|
771
|
+
|
|
772
|
+
### Walkthrough
|
|
773
|
+
|
|
774
|
+
```text
|
|
775
|
+
> /dc-plan add dark mode with system-preference detection
|
|
776
|
+
|
|
777
|
+
⚠ Starting a contextual pi planning turn. Review the generated plan before it is written.
|
|
778
|
+
|
|
779
|
+
[pi explores src/theme.ts, package.json, specs/ui/system.spec.md, changes/]
|
|
780
|
+
|
|
781
|
+
DeepClause change add_dark_mode
|
|
782
|
+
Created .pi/deepclause/changes/add_dark_mode/
|
|
783
|
+
proposal.md why / what / impact
|
|
784
|
+
specs/ui/theme.spec.md +2 requirements, +3 scenarios (behavior only)
|
|
785
|
+
design.md 3 decisions, 2 risks
|
|
786
|
+
tasks.dml 5 tasks, 5 checks, 3 scenarios covered
|
|
787
|
+
apply.dml executable entry (3 attempts, 2 pi tools)
|
|
788
|
+
Capabilities: new `ui/theme`, modified `ui/system`
|
|
789
|
+
Check: /dc-check add_dark_mode Apply: /dc-run add_dark_mode
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
```text
|
|
793
|
+
> /dc-check add_dark_mode
|
|
794
|
+
|
|
795
|
+
DeepClause CHECK add_dark_mode (spec_validate.dml) 0 tokens
|
|
796
|
+
|
|
797
|
+
requirements 4 added, 1 modified, 0 removed, 1 renamed
|
|
798
|
+
scenarios 9 ok (every requirement has ≥1)
|
|
799
|
+
coverage 9/9 scenarios referenced by tasks.dml
|
|
800
|
+
checks 5/5 tasks declare verification; 5 commands discoverable
|
|
801
|
+
|
|
802
|
+
ERROR specs/ui/theme.spec.md:71 `### Scenario:` uses 3 hashes; must be `####`.
|
|
803
|
+
ERROR archive would fail: MODIFIED "Theme selection" not found
|
|
804
|
+
(closest: "Theme switching").
|
|
805
|
+
ERROR tasks.dml: task "1.4" satisfies unknown scenario
|
|
806
|
+
ui/theme#no-such-scenario
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
```text
|
|
810
|
+
> /dc-run add_dark_mode
|
|
811
|
+
|
|
812
|
+
⚠ Run contextual DeepClause plan?
|
|
813
|
+
Delegates bounded steps to pi with these tools: read, edit
|
|
814
|
+
Verification commands (approved once for this run):
|
|
815
|
+
npm run typecheck
|
|
816
|
+
npx vitest run src/theme
|
|
817
|
+
npm run build
|
|
818
|
+
Max attempts per task: 3. Restores the working tree on failure.
|
|
819
|
+
[Confirm]
|
|
820
|
+
|
|
821
|
+
DeepClause RUNNING changes/add_dark_mode/apply.dml 4.2s
|
|
822
|
+
provider/model | context=branch | verbose
|
|
823
|
+
Phase: task 1.2, attempt 2/3: add CSS custom properties
|
|
824
|
+
Usage: 31,204 input / 5,881 output tokens
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
```text
|
|
828
|
+
> /dc-run spec_archive add_dark_mode
|
|
829
|
+
|
|
830
|
+
⚠ Archive will modify .pi/deepclause/specs/ui/theme.spec.md
|
|
831
|
+
+ ADDED Theme selection
|
|
832
|
+
+ ADDED System-preference default
|
|
833
|
+
~ MODIFIED Theme switching (2 lines changed)
|
|
834
|
+
[Confirm]
|
|
835
|
+
|
|
836
|
+
Archived → changes/archive/2025-09-17-add_dark_mode/
|
|
837
|
+
specs/ui/theme.spec.md updated (+2 requirements, 1 modified)
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## Workspace layout
|
|
841
|
+
|
|
842
|
+
```text
|
|
843
|
+
.pi/deepclause/
|
|
844
|
+
├── specs/ # source of truth (current behavior, pure Markdown)
|
|
845
|
+
│ └── ui/theme.spec.md
|
|
846
|
+
├── changes/ # in-flight work
|
|
847
|
+
│ ├── add_dark_mode/
|
|
848
|
+
│ │ ├── change.json # schema, digests, snapshot ref
|
|
849
|
+
│ │ ├── proposal.md # why / what / impact
|
|
850
|
+
│ │ ├── specs/ui/theme.spec.md # delta: behavior only
|
|
851
|
+
│ │ ├── design.md # approach, decisions (optional)
|
|
852
|
+
│ │ ├── tasks.dml # implementation plan + execution state
|
|
853
|
+
│ │ ├── deltas.dml # this change's delta ops + lifecycle status
|
|
854
|
+
│ │ └── apply.dml # executable entry + preflight metadata
|
|
855
|
+
│ └── archive/2025-09-17-add_dark_mode/
|
|
856
|
+
├── index.dml # OPTIONAL derived inventory (disposable cache)
|
|
857
|
+
├── lib/
|
|
858
|
+
│ ├── specs.dml # spec/delta grammar, validators, merge
|
|
859
|
+
│ └── apply.dml # plan driver: verify, retry, rollback
|
|
860
|
+
├── skills/
|
|
861
|
+
│ ├── spec_validate.dml
|
|
862
|
+
│ ├── spec_merge.dml
|
|
863
|
+
│ ├── spec_archive.dml
|
|
864
|
+
│ ├── spec_coverage.dml
|
|
865
|
+
│ ├── spec_status.dml
|
|
866
|
+
│ ├── spec_query.dml
|
|
867
|
+
│ ├── spec_reindex.dml # planned
|
|
868
|
+
│ ├── spec_graph.dml
|
|
869
|
+
│ ├── spec_sync.dml # planned
|
|
870
|
+
│ ├── spec_scaffold.dml
|
|
871
|
+
│ └── spec_apply.dml
|
|
872
|
+
├── plans/ # standalone plans not tied to a change
|
|
873
|
+
├── diagrams/
|
|
874
|
+
├── AGENTS.md
|
|
875
|
+
├── DML_REFERENCE.md
|
|
876
|
+
├── SPEC_AUTHORING.md # spec-writing guide (new)
|
|
877
|
+
└── config.json
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
> **Scope note.** The current `AGENTS.md` contract lists only `config.json`,
|
|
881
|
+
> `AGENTS.md`, `DML_REFERENCE.md`, `skills/`, and `plans/`. Adding `specs/`,
|
|
882
|
+
> `changes/`, `lib/`, and a derived `index.dml` is a deliberate extension and
|
|
883
|
+
> needs an explicit decision.
|
|
884
|
+
> All new files follow the existing non-destructive initialization rule
|
|
885
|
+
> (`writeIfMissing`): user files are never overwritten.
|
|
886
|
+
|
|
887
|
+
## Spec format: behavior only
|
|
888
|
+
|
|
889
|
+
Specs are plain, OpenSpec-compatible Markdown. A capability spec:
|
|
890
|
+
|
|
891
|
+
````markdown
|
|
892
|
+
---
|
|
893
|
+
capability: ui/theme
|
|
894
|
+
owners: [frontend]
|
|
895
|
+
related: [ui/system]
|
|
896
|
+
---
|
|
897
|
+
|
|
898
|
+
# Theme Specification
|
|
899
|
+
|
|
900
|
+
## Purpose
|
|
901
|
+
Lets users choose between light and dark themes, defaulting to the operating
|
|
902
|
+
system preference.
|
|
903
|
+
|
|
904
|
+
## Requirements
|
|
905
|
+
|
|
906
|
+
### Requirement: Theme selection
|
|
907
|
+
The app SHALL let users switch between light and dark themes at runtime.
|
|
908
|
+
|
|
909
|
+
#### Scenario: User toggles dark mode
|
|
910
|
+
- **WHEN** the user clicks the theme toggle
|
|
911
|
+
- **THEN** the app switches to dark mode and persists the choice
|
|
912
|
+
|
|
913
|
+
#### Scenario: Invalid stored value is rejected
|
|
914
|
+
- **WHEN** a stored theme value is neither "light" nor "dark"
|
|
915
|
+
- **THEN** the app falls back to the system preference and shows no error
|
|
916
|
+
|
|
917
|
+
### Requirement: System-preference default
|
|
918
|
+
The app SHALL default to the operating system colour-scheme preference when no
|
|
919
|
+
choice has been stored.
|
|
920
|
+
|
|
921
|
+
#### Scenario: First run on a dark-preference system
|
|
922
|
+
- **WHEN** the app starts with no stored theme and the OS reports dark
|
|
923
|
+
- **THEN** it renders dark without writing a stored choice
|
|
924
|
+
````
|
|
925
|
+
|
|
926
|
+
A change delta uses `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`. `MODIFIED`
|
|
927
|
+
carries the full replacement requirement, `REMOVED` carries `**Reason**` and
|
|
928
|
+
`**Migration**`, `RENAMED` uses `FROM:`/`TO:`.
|
|
929
|
+
|
|
930
|
+
### What does not belong in a spec
|
|
931
|
+
|
|
932
|
+
No commands, no file paths, no test runners, no library choices, no task lists,
|
|
933
|
+
and no `prolog` blocks. The test is OpenSpec's own: *if the implementation can
|
|
934
|
+
change without changing externally visible behavior, it does not belong here.*
|
|
935
|
+
|
|
936
|
+
This was a correction to an earlier draft of this document, which attached
|
|
937
|
+
`check(cmd("npx vitest run src/theme"))` to requirements. That leaks
|
|
938
|
+
implementation into the behavior contract and belongs in `tasks.dml`.
|
|
939
|
+
|
|
940
|
+
### Scenario ids are the join key
|
|
941
|
+
|
|
942
|
+
Each scenario gets a stable id derived from its heading:
|
|
943
|
+
`capability#scenario-slug`, e.g. `ui/theme#user-toggles-dark-mode`. Nothing else
|
|
944
|
+
in the spec needs machine-readable content. The id is what tasks reference, and
|
|
945
|
+
what coverage and conformance are computed over.
|
|
946
|
+
|
|
947
|
+
### Delta integrity
|
|
948
|
+
|
|
949
|
+
The delta records no digests itself. Drift digests live in `change.json`, because
|
|
950
|
+
a self-digest inside a hand-editable file goes stale the moment anyone edits it.
|
|
951
|
+
|
|
952
|
+
## The engine: DCG parsing
|
|
953
|
+
|
|
954
|
+
DML reasons over terms; specs are text. A DCG is the bridge, and it parses only
|
|
955
|
+
the **syntax envelope** — never the prose.
|
|
956
|
+
|
|
957
|
+
Parses (`lib/specs.dml`):
|
|
958
|
+
|
|
959
|
+
- spec files: frontmatter, `# Title`, `## Purpose`, `### Requirement: <name>`,
|
|
960
|
+
`#### Scenario: <name>`, GIVEN/WHEN/THEN bullets, RFC 2119 keywords
|
|
961
|
+
- delta files: section headers, `FROM:`/`TO:`, `**Reason**`/`**Migration**`
|
|
962
|
+
- fenced code blocks are swallowed so heading-like text inside them never matches
|
|
963
|
+
|
|
964
|
+
`change.json` is JSON. `tasks.dml` is DML facts and is not parsed by the grammar
|
|
965
|
+
at all — it is consulted.
|
|
966
|
+
|
|
967
|
+
Produces a tree of terms:
|
|
968
|
+
|
|
969
|
+
```prolog
|
|
970
|
+
spec(
|
|
971
|
+
purpose("Theme and layout behaviour for the application."),
|
|
972
|
+
[ requirement("Theme selection",
|
|
973
|
+
"The app SHALL let users switch between light and dark themes.",
|
|
974
|
+
[ scenario("user-toggles-dark-mode", "User toggles dark mode",
|
|
975
|
+
[when("the user clicks the theme toggle"),
|
|
976
|
+
then("the app switches to dark mode and persists the choice")]) ])
|
|
977
|
+
]).
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
Rules are ordinary DCG clauses over a line list, e.g.:
|
|
981
|
+
|
|
982
|
+
```prolog
|
|
983
|
+
requirement(req(Name, Text, Scenarios)) -->
|
|
984
|
+
heading(3, Line),
|
|
985
|
+
{ parse_requirement_header(Line, Name) },
|
|
986
|
+
body(Text),
|
|
987
|
+
scenarios(Scenarios).
|
|
988
|
+
|
|
989
|
+
scenario(scenario(Id, Name, Steps)) -->
|
|
990
|
+
heading(4, Line), % 4 hashes: 3 simply does not match
|
|
991
|
+
{ parse_scenario_header(Line, Name, Id) },
|
|
992
|
+
steps(Steps).
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
Why a grammar rather than regex:
|
|
996
|
+
|
|
997
|
+
| Situation | Regex | DCG |
|
|
998
|
+
|---|---|---|
|
|
999
|
+
| Wrong hashtag count | counts, often silently accepts | rule does not match → hard error with position |
|
|
1000
|
+
| `### Requirement:` inside a fence | false positive | consumed as opaque text |
|
|
1001
|
+
| Requirement with no scenario | post-hoc cross-check | scenarios are a child non-terminal |
|
|
1002
|
+
| Heading inside `## Notes` | matches out of context | unreachable from the spec state |
|
|
1003
|
+
| MODIFIED names an absent requirement | manual lookup | unification against the parsed spec |
|
|
1004
|
+
|
|
1005
|
+
Because DML is Prolog, **parse failure is validation failure**: the caller learns
|
|
1006
|
+
which non-terminal failed and where.
|
|
1007
|
+
|
|
1008
|
+
### The parse tree is ephemeral
|
|
1009
|
+
|
|
1010
|
+
The tree exists only for one `/dc-run` execution and is then discarded. Markdown
|
|
1011
|
+
is the database. Re-parsing is cheap and lossless, so caching would only introduce
|
|
1012
|
+
drift. Only four things persist: merged `specs/**`, the moved `archive/` folder,
|
|
1013
|
+
`tasks.dml` status updates, and the pi session's final answer.
|
|
1014
|
+
|
|
1015
|
+
If a run needs to reason repeatedly over the parse, it may `assertz` facts for the
|
|
1016
|
+
duration — session-scoped, discarded at run end.
|
|
1017
|
+
|
|
1018
|
+
### Lossless merge
|
|
1019
|
+
|
|
1020
|
+
`spec_merge` parses **both** the existing spec and the delta, rewrites, and
|
|
1021
|
+
re-renders. Because the grammar retains each block's raw text, unchanged
|
|
1022
|
+
requirements round-trip byte-for-byte and only edited blocks change. Apply order is
|
|
1023
|
+
RENAMED → REMOVED → MODIFIED → ADDED. The merge emits a `Trace` that is what the
|
|
1024
|
+
confirm dialog shows before any write.
|
|
1025
|
+
|
|
1026
|
+
## Verification
|
|
1027
|
+
|
|
1028
|
+
Verification splits in two, and conflating them was the earlier draft's mistake.
|
|
1029
|
+
|
|
1030
|
+
| Kind | Question | Lives in | Nature |
|
|
1031
|
+
|---|---|---|---|
|
|
1032
|
+
| **Behavioral** | does the system do what the scenario says? | the spec, *as the scenario* | implementation-neutral acceptance |
|
|
1033
|
+
| **Technical** | does it typecheck, build, pass tests, exist? | `tasks.dml` checks | implementation detail |
|
|
1034
|
+
|
|
1035
|
+
The spec's only verification artifact is the scenario. The concrete check that
|
|
1036
|
+
proves a scenario is change-scoped and lives on the task.
|
|
1037
|
+
|
|
1038
|
+
### Checks are data terms, not clauses
|
|
1039
|
+
|
|
1040
|
+
A check is a **declarative term** dispatched by a fixed interpreter — never an
|
|
1041
|
+
asserted clause and never `call/1`:
|
|
1042
|
+
|
|
1043
|
+
```prolog
|
|
1044
|
+
checks([ exists("src/theme/ThemeProvider.tsx"),
|
|
1045
|
+
cmd("npm run typecheck"),
|
|
1046
|
+
cmd("npx vitest run src/theme", retry(2)),
|
|
1047
|
+
model("does the toggle persist across reload?") ])
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
```prolog
|
|
1051
|
+
run_check(exists(P), ok) :- exists_file(P), !.
|
|
1052
|
+
run_check(exists(P), missing(P)).
|
|
1053
|
+
|
|
1054
|
+
run_check(cmd(C), Result) :-
|
|
1055
|
+
exec(dc_verify_run(command: C), Dict),
|
|
1056
|
+
get_dict(exitCode, Dict, Code),
|
|
1057
|
+
( Code =:= 0 -> Result = ok ; Result = failed_command(C, Code) ).
|
|
1058
|
+
|
|
1059
|
+
run_check(model(Q), Result) :- ... pi_agent_step, PASS/FAIL ...
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
Why data and not code:
|
|
1063
|
+
|
|
1064
|
+
- `task/N` and `prompt/N` are LLM calls: non-deterministic, token-costing, and able
|
|
1065
|
+
to claim success without checking. **Never use them as gates.** They remain the
|
|
1066
|
+
right tool for prose and synthesis.
|
|
1067
|
+
- Asserting arbitrary clauses from a spec or task file turns a review artifact into
|
|
1068
|
+
executable code, a supply-chain risk if specs are shared. Terms-of-known-shape
|
|
1069
|
+
have no such surface: the parser can only produce `exists/1`, `cmd/1`, `cmd/2`,
|
|
1070
|
+
`model/1`.
|
|
1071
|
+
- A deterministic predicate does **not** need a runtime tool. DML supports
|
|
1072
|
+
`:- consult('lib/specs.dml').`, so validators are shared Prolog predicates.
|
|
1073
|
+
|
|
1074
|
+
### Where checks come from
|
|
1075
|
+
|
|
1076
|
+
Pi grounds each task's checks in the repository during the planning turn — real
|
|
1077
|
+
`package.json` scripts, real paths, real test targets. The plan validator enforces:
|
|
1078
|
+
|
|
1079
|
+
1. **Every task declares at least one check.**
|
|
1080
|
+
2. **`cmd(...)` entries must be discoverable** — an npm script that exists in
|
|
1081
|
+
`package.json`, a test path that exists, a CI command. An invented
|
|
1082
|
+
`cmd("npm test:theme")` is worse than no check: the repair loop chases a
|
|
1083
|
+
phantom. Non-discoverable checks are demoted to `model(...)` with a warning.
|
|
1084
|
+
3. **`model(...)` checks are labelled** in output, so a green apply never reports
|
|
1085
|
+
unqualified success when a human-judgment check was involved.
|
|
1086
|
+
|
|
1087
|
+
### Coverage, at two levels
|
|
1088
|
+
|
|
1089
|
+
Scenario ids are the join key.
|
|
1090
|
+
|
|
1091
|
+
- **Plan-time (deterministic):** every scenario in the delta appears in at least
|
|
1092
|
+
one task's `satisfies`. A change that drops a scenario cannot commit a plan.
|
|
1093
|
+
- **Apply-time:** each task's checks run; the driver records a
|
|
1094
|
+
`scenario → check → result` trace. The change-level gate `verify_change/2`
|
|
1095
|
+
asserts every scenario in the delta has at least one passing check. So
|
|
1096
|
+
end-to-end traceability exists without duplicating test invocations in the spec.
|
|
1097
|
+
|
|
1098
|
+
An optional phase-2 refinement is test annotations
|
|
1099
|
+
(`@dc ui/theme#user-toggles-dark-mode` in the test file) so the suite discovers the
|
|
1100
|
+
scenario→test mapping instead of the plan declaring it.
|
|
1101
|
+
|
|
1102
|
+
### Approving checks once, not per invocation
|
|
1103
|
+
|
|
1104
|
+
`pi_bash` prompts per command. A retry loop with 3 checks × 3 attempts × 6 tasks
|
|
1105
|
+
would be a wall of dialogs, and the unattended rollback path cannot prompt at all.
|
|
1106
|
+
So verification commands are approved **as a suite** at `/dc-run` confirm time,
|
|
1107
|
+
after which a scoped `dc_verify_run(command)` tool is available for that run only,
|
|
1108
|
+
restricted to the declared commands (exact match, workspace cwd, timeout, returns
|
|
1109
|
+
exit code and stdout).
|
|
1110
|
+
|
|
1111
|
+
This is the same justification as `dc_apply_snapshot`/`dc_apply_restore`: it needs
|
|
1112
|
+
the host shell and must run unattended. There is no pure-Prolog substitute.
|
|
1113
|
+
|
|
1114
|
+
## The change artifacts
|
|
1115
|
+
|
|
1116
|
+
### `tasks.dml` — implementation plan and execution state
|
|
1117
|
+
|
|
1118
|
+
Data only. Definitions in `plan_task/2`, state in `plan_task_status/2`, joined by task id.
|
|
1119
|
+
The fact functor is `plan_task/2`, **not** `task/2`: `task/2` collides with DML's
|
|
1120
|
+
built-in `task/N` predicate, and a term read from the file then refuses to unify
|
|
1121
|
+
with `task(Id, Props)`. This was found while implementing phase 3.
|
|
1122
|
+
|
|
1123
|
+
```prolog
|
|
1124
|
+
% tasks.dml — implementation plan for change add_dark_mode
|
|
1125
|
+
|
|
1126
|
+
plan_task("1.1", task{
|
|
1127
|
+
executor: pi,
|
|
1128
|
+
do: "Add a ThemeProvider context exposing theme and setTheme.",
|
|
1129
|
+
tools: ["read", "edit"],
|
|
1130
|
+
expected: "src/theme/ThemeProvider.tsx exports ThemeProvider and typechecks.",
|
|
1131
|
+
satisfies: ["ui/theme#user-toggles-dark-mode"],
|
|
1132
|
+
checks: [ exists("src/theme/ThemeProvider.tsx"),
|
|
1133
|
+
cmd("npm run typecheck") ]
|
|
1134
|
+
}).
|
|
1135
|
+
|
|
1136
|
+
plan_task("1.2", task{
|
|
1137
|
+
executor: pi,
|
|
1138
|
+
do: "Add light/dark CSS custom properties applied to the document root.",
|
|
1139
|
+
tools: ["read", "edit"],
|
|
1140
|
+
expected: "Toggling updates the visible theme without a reload.",
|
|
1141
|
+
satisfies: ["ui/theme#user-toggles-dark-mode"],
|
|
1142
|
+
checks: [ cmd("npx vitest run src/theme") ]
|
|
1143
|
+
}).
|
|
1144
|
+
|
|
1145
|
+
% --- execution state (managed by apply.dml; do not edit by hand) ---
|
|
1146
|
+
plan_task_status("1.1", pending).
|
|
1147
|
+
plan_task_status("1.2", pending).
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
Status is a plain compound term, chosen over a dict so it is trivial to match,
|
|
1151
|
+
write, and round-trip:
|
|
1152
|
+
|
|
1153
|
+
```prolog
|
|
1154
|
+
plan_task_status("1.5", pending).
|
|
1155
|
+
plan_task_status("1.1", done(1)). % verified on attempt 1
|
|
1156
|
+
plan_task_status("1.2", failed(3, "vitest: 1 failing (ThemeProvider.test.tsx:42)")).
|
|
1157
|
+
plan_task_status("1.3", skipped("subsumed by 1.1")).
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
Rules:
|
|
1161
|
+
|
|
1162
|
+
- `done` is written **only after `verify/3` returns `ok`** — it means verified, not
|
|
1163
|
+
attempted.
|
|
1164
|
+
- `failed` and `skipped` are recorded rather than dropped, so the change-level gate
|
|
1165
|
+
can explain itself and a re-run is auditable.
|
|
1166
|
+
- Progress is a query, not a separate file:
|
|
1167
|
+
|
|
1168
|
+
```prolog
|
|
1169
|
+
remaining(Id) :- plan_task(Id, _), \+ plan_task_status(Id, done(_)).
|
|
1170
|
+
all_done :- forall(plan_task(Id, _), plan_task_status(Id, done(_))).
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
OpenSpec's "all tasks complete" archive check becomes `all_done`.
|
|
1174
|
+
|
|
1175
|
+
### `apply.dml` — executable entry and preflight metadata
|
|
1176
|
+
|
|
1177
|
+
Small, generated, stable per change:
|
|
1178
|
+
|
|
1179
|
+
```prolog
|
|
1180
|
+
% apply.dml — executable entry for change add_dark_mode
|
|
1181
|
+
% Plan format: 2
|
|
1182
|
+
% Change: add_dark_mode
|
|
1183
|
+
% Required pi tools: read, edit
|
|
1184
|
+
% Contextual: true
|
|
1185
|
+
|
|
1186
|
+
:- consult('.pi/deepclause/lib/apply.dml').
|
|
1187
|
+
:- consult('.pi/deepclause/changes/add_dark_mode/tasks.dml').
|
|
1188
|
+
|
|
1189
|
+
agent_main :-
|
|
1190
|
+
run_plan('.pi/deepclause/changes/add_dark_mode/tasks.dml',
|
|
1191
|
+
"add_dark_mode", 3).
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
Why both files rather than one:
|
|
1195
|
+
|
|
1196
|
+
- `tasks.dml` is **content** — readable, diffable, reviewable, greppable, and only
|
|
1197
|
+
changes status as work proceeds.
|
|
1198
|
+
- `apply.dml` is **wiring and metadata** — which driver, which slug, attempt
|
|
1199
|
+
budget, and the fields `/dc-run` preflight needs.
|
|
1200
|
+
|
|
1201
|
+
### How `apply.dml` reads and writes `tasks.dml`
|
|
1202
|
+
|
|
1203
|
+
Read: `consult(tasks.dml)` at start, which loads definitions and current status.
|
|
1204
|
+
|
|
1205
|
+
Write: a **managed marker block**, so the driver never touches the definitions:
|
|
1206
|
+
|
|
1207
|
+
```prolog
|
|
1208
|
+
% in lib/apply.dml
|
|
1209
|
+
record_status(TasksPath, Id, Status) :-
|
|
1210
|
+
read_file_to_string(TasksPath, Text, []),
|
|
1211
|
+
split_managed_block(Text, Head, _OldBlock), % split at the marker comment
|
|
1212
|
+
findall(Id-S, ( plan_task(Id0, _), plan_task_status(Id0, S), Id = Id0 ), Statuses),
|
|
1213
|
+
render_status_block(Statuses, Block),
|
|
1214
|
+
atomic_write(TasksPath, Head, Block). % temp file + rename
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
Properties this buys:
|
|
1218
|
+
|
|
1219
|
+
- **Definitions and comments are never rewritten.** Only the status block is
|
|
1220
|
+
regenerated, so the diff per task is one line and hand-written comments above the
|
|
1221
|
+
marker survive.
|
|
1222
|
+
- **Crash-safe resume.** Write after each *verified* task, so an interrupted run
|
|
1223
|
+
leaves the block reflecting the last completed task, and `remaining/1` resumes.
|
|
1224
|
+
- **No fragile round-tripping.** The driver never parses and re-renders DML dicts;
|
|
1225
|
+
it only serializes `plan_task_status/2` facts, which is trivial.
|
|
1226
|
+
- **Atomicity.** Temp file plus `rename_file/2`, so a crash mid-write cannot leave
|
|
1227
|
+
a truncated plan.
|
|
1228
|
+
|
|
1229
|
+
Progress is versioned in git. Diffs are readable. A failed apply that triggers
|
|
1230
|
+
`git reset --hard <snapshot>` reverts progress along with code — which is correct,
|
|
1231
|
+
because the tasks genuinely are not done.
|
|
1232
|
+
|
|
1233
|
+
## Feature and delta index
|
|
1234
|
+
|
|
1235
|
+
Tasks are authored facts (`tasks.dml`); features and deltas are **derived** from
|
|
1236
|
+
canonical Markdown. The distinction matters: a derived fact set must be disposable,
|
|
1237
|
+
digest-stamped, and never hand-edited, or it becomes a second source of truth. The
|
|
1238
|
+
one genuinely *authored* piece is delta lifecycle status, which Markdown does not
|
|
1239
|
+
capture.
|
|
1240
|
+
|
|
1241
|
+
### Per-change delta record — `changes/<slug>/deltas.dml`
|
|
1242
|
+
|
|
1243
|
+
```prolog
|
|
1244
|
+
% deltas.dml — spec deltas for change add_dark_mode
|
|
1245
|
+
% Source digests:
|
|
1246
|
+
% specs/ui/theme.spec.md sha256:9f2c…
|
|
1247
|
+
% specs/ui/system.spec.md sha256:41ab…
|
|
1248
|
+
|
|
1249
|
+
delta("add_dark_mode", added, "ui/theme", req("theme-selection", "Theme selection")).
|
|
1250
|
+
delta("add_dark_mode", modified, "ui/system", req("theme-switching", "Theme switching")).
|
|
1251
|
+
delta("add_dark_mode", removed, "ui/system", req("legacy-theme"),
|
|
1252
|
+
reason("Replaced by theme-selection"),
|
|
1253
|
+
migration("Use ui/theme#theme-selection")).
|
|
1254
|
+
delta("add_dark_mode", renamed, "ui/system",
|
|
1255
|
+
from("theme-switch"), to("theme-switching")).
|
|
1256
|
+
|
|
1257
|
+
change_meta("add_dark_mode", schema(spec_driven), skip_specs(false),
|
|
1258
|
+
snapshot("abc1234"), created("2025-09-17")).
|
|
1259
|
+
|
|
1260
|
+
% --- lifecycle state (managed by apply / spec_archive) ---
|
|
1261
|
+
delta_status("add_dark_mode", "ui/theme", "theme-selection", verified("2025-09-17")).
|
|
1262
|
+
delta_status("add_dark_mode", "ui/system", "theme-switching", applied("2025-09-17")).
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
Requirements get stable slugs (`theme-selection`) just like scenarios, so identity
|
|
1266
|
+
survives a `RENAMED`.
|
|
1267
|
+
|
|
1268
|
+
### Workspace inventory — `index.dml` (optional, derived)
|
|
1269
|
+
|
|
1270
|
+
```prolog
|
|
1271
|
+
% index.dml — DERIVED. Do not edit. Regenerate with /dc-run spec_reindex.
|
|
1272
|
+
capability("ui/theme", "Theme Specification", purpose("Lets users choose …"),
|
|
1273
|
+
source("specs/ui/theme.spec.md", "sha256:9f2c…")).
|
|
1274
|
+
requirement("ui/theme", "theme-selection", "Theme selection").
|
|
1275
|
+
scenario("ui/theme", "user-toggles-dark-mode", "User toggles dark mode", "theme-selection").
|
|
1276
|
+
touch("add_dark_mode", "ui/theme").
|
|
1277
|
+
touch("add_dark_mode", "ui/system").
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
### What it makes queryable
|
|
1281
|
+
|
|
1282
|
+
```prolog
|
|
1283
|
+
in_flight(Ch) :- change_meta(Ch, _, _, _, _), \+ archived(Ch).
|
|
1284
|
+
touches(Ch, Cap) :- delta(Ch, _, Cap, _).
|
|
1285
|
+
|
|
1286
|
+
% conflict: two in-flight changes modify the same requirement
|
|
1287
|
+
conflict(Cap, Req, C1, C2) :-
|
|
1288
|
+
in_flight(C1), in_flight(C2), C1 \== C2,
|
|
1289
|
+
delta(C1, modified, Cap, req(Req, _)),
|
|
1290
|
+
delta(C2, modified, Cap, req(Req, _)).
|
|
1291
|
+
|
|
1292
|
+
% coverage hole in a change
|
|
1293
|
+
uncovered(Ch, Cap, S) :-
|
|
1294
|
+
delta(Ch, added, Cap, _), scenario(Cap, S, _, _),
|
|
1295
|
+
\+ task_satisfies(Ch, Cap, S).
|
|
1296
|
+
|
|
1297
|
+
% spec hygiene
|
|
1298
|
+
orphan_requirement(Cap, R) :- requirement(Cap, R, _), \+ scenario(Cap, _, _, R).
|
|
1299
|
+
barren_capability(Cap) :- capability(Cap, _, _, _), \+ requirement(Cap, _, _).
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
The payoff is a traceability matrix — change → requirement → scenario → task →
|
|
1303
|
+
check → status — which answers "which check proved which scenario, when, and with
|
|
1304
|
+
what result" without a sidecar evidence file.
|
|
1305
|
+
|
|
1306
|
+
### Rules
|
|
1307
|
+
|
|
1308
|
+
1. Markdown stays canonical for behavior. The definition half of `deltas.dml` and
|
|
1309
|
+
all of `index.dml` are derived; `delta_status/2` and `change_meta/1` are authored,
|
|
1310
|
+
like `plan_task_status/2`.
|
|
1311
|
+
2. Every derived fact carries its source digest. Mismatch means stale.
|
|
1312
|
+
3. Stale means **regenerate**, never patch. Query skills refuse or auto-reindex on
|
|
1313
|
+
mismatch.
|
|
1314
|
+
4. The index is never hand-edited, and should probably not be committed — it churns
|
|
1315
|
+
on every spec edit.
|
|
1316
|
+
5. **Default to deriving on demand.** For a handful of capabilities the query skill
|
|
1317
|
+
parses and asserts in-run and writes nothing. Persist `index.dml` only when
|
|
1318
|
+
parsing cost or cross-session/cross-repo queries justify it. This is the same
|
|
1319
|
+
principle as the ephemeral parse tree; the file is an optimization, not a source.
|
|
1320
|
+
|
|
1321
|
+
### Exposure
|
|
1322
|
+
|
|
1323
|
+
- User: `/dc-run spec_status`, `/dc-run spec_query <capability>` — deterministic,
|
|
1324
|
+
0 tokens, tabular output.
|
|
1325
|
+
- Model: an optional **read-only `dc_spec_query` tool** so pi can ask "which changes
|
|
1326
|
+
touch `ui/system`?" mid-turn. This is not a contradiction of the rejected
|
|
1327
|
+
`dc_spec_verify`: that was a **gate** (a correctness decision, which must not be
|
|
1328
|
+
LLM-invocable), whereas read-only introspection is exactly what the model should
|
|
1329
|
+
be able to call, like `pi_workspace_list`. Either way the query language is a
|
|
1330
|
+
**fixed allowlist of predicates**, never arbitrary `call/1`.
|
|
1331
|
+
|
|
1332
|
+
## Capability and change graphs
|
|
1333
|
+
|
|
1334
|
+
The index already *is* the graph — capabilities, requirements, scenarios, deltas,
|
|
1335
|
+
tasks, checks and statuses. Rendering it is a view over those facts, and
|
|
1336
|
+
`deepclause-pi` already has the renderer: `dc_diagram` turns a `.dml` file into a
|
|
1337
|
+
presentation- or specification-grade Mermaid diagram, writes an offline viewer
|
|
1338
|
+
under `.pi/deepclause/diagrams/`, and opens it (`src/diagram/*`). Spec graphs reuse
|
|
1339
|
+
that viewer; only the source changes from "one DML program" to "the spec facts".
|
|
1340
|
+
|
|
1341
|
+
### Views
|
|
1342
|
+
|
|
1343
|
+
Every view is generated from derived facts, so it is deterministic and costs
|
|
1344
|
+
0 tokens.
|
|
1345
|
+
|
|
1346
|
+
**Capabilities** — the inventory tree:
|
|
1347
|
+
|
|
1348
|
+
```mermaid
|
|
1349
|
+
flowchart LR
|
|
1350
|
+
CapTheme["ui/theme — Theme Specification"]
|
|
1351
|
+
CapTheme --> ReqSel["Requirement: Theme selection"]
|
|
1352
|
+
CapTheme --> ReqDef["Requirement: System-preference default"]
|
|
1353
|
+
ReqSel --> SceToggle["Scenario: User toggles dark mode"]
|
|
1354
|
+
ReqSel --> SceInvalid["Scenario: Invalid stored value is rejected"]
|
|
1355
|
+
ReqDef --> SceFirstRun["Scenario: First run on a dark-preference system"]
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
**Changes** — what each change touches, edges labelled by delta op:
|
|
1359
|
+
|
|
1360
|
+
```mermaid
|
|
1361
|
+
flowchart LR
|
|
1362
|
+
ChAdd["add_dark_mode<br/>verified"] -- "ADDED ×2" --> CapTheme
|
|
1363
|
+
ChAdd -- "MODIFIED" --> CapSystem["ui/system — System Specification"]
|
|
1364
|
+
```
|
|
1365
|
+
|
|
1366
|
+
**Traceability** — change → requirement → scenario → task → check, coloured by
|
|
1367
|
+
status:
|
|
1368
|
+
|
|
1369
|
+
```mermaid
|
|
1370
|
+
flowchart LR
|
|
1371
|
+
Req["Requirement: Theme selection"]
|
|
1372
|
+
Sce["Scenario: User toggles dark mode"]
|
|
1373
|
+
T11["1.1 ThemeProvider · done(1)"]
|
|
1374
|
+
T12["1.2 CSS custom properties · done(2)"]
|
|
1375
|
+
Chk["check: npm run typecheck · ok"]
|
|
1376
|
+
Req --> Sce --> T11 --> Chk
|
|
1377
|
+
Sce --> T12
|
|
1378
|
+
classDef done fill:#e8f5e9
|
|
1379
|
+
class T11,T12 done
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
**Lifecycle** — the change state machine:
|
|
1383
|
+
|
|
1384
|
+
```mermaid
|
|
1385
|
+
stateDiagram-v2
|
|
1386
|
+
[*] --> proposed
|
|
1387
|
+
proposed --> checked
|
|
1388
|
+
checked --> applied
|
|
1389
|
+
applied --> verified
|
|
1390
|
+
verified --> archived
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
**Conflicts** — emitted only when non-empty: two in-flight changes modifying the
|
|
1394
|
+
same requirement.
|
|
1395
|
+
|
|
1396
|
+
**Dependencies** — capability-to-capability links (`related`/`requires` in
|
|
1397
|
+
frontmatter), once those exist.
|
|
1398
|
+
|
|
1399
|
+
### How it is built
|
|
1400
|
+
|
|
1401
|
+
```
|
|
1402
|
+
spec facts (index.dml / deltas.dml / tasks.dml)
|
|
1403
|
+
│ spec_graph.dml — pure DML, deterministic
|
|
1404
|
+
▼
|
|
1405
|
+
Mermaid text ──► src/diagram/viewer.ts ──► diagrams/spec-<view>.html ──► opens
|
|
1406
|
+
```
|
|
1407
|
+
|
|
1408
|
+
- `spec_graph.dml` selects and emits the graph, reading the same facts as
|
|
1409
|
+
`spec_query`. Zero model calls.
|
|
1410
|
+
- The existing viewer modules (`buildViewer`, `openViewerInBrowser`,
|
|
1411
|
+
`validateMermaid`, `writeSidecar`) render and open it — no new rendering code.
|
|
1412
|
+
- Grade follows `dc_diagram`: **presentation** collapses to capability level and
|
|
1413
|
+
shows changes and status only; **specification** expands to
|
|
1414
|
+
requirement → scenario → task → check.
|
|
1415
|
+
|
|
1416
|
+
### Invocation
|
|
1417
|
+
|
|
1418
|
+
- **Natural language:** "show me the graph of capabilities and changes" → pi calls
|
|
1419
|
+
an optional read-only **`dc_spec_graph`** tool, exactly as it calls `dc_diagram`
|
|
1420
|
+
today (the `AUTHORING_INSTRUCTION` gains one sentence). No new slash command.
|
|
1421
|
+
- **Deterministic:** `/dc-run spec_graph [view] [target]
|
|
1422
|
+
[--grade=presentation|specification]`, e.g. `/dc-run spec_graph trace ui/theme`.
|
|
1423
|
+
Useful in CI, or when you want the graph without the model.
|
|
1424
|
+
- Optional later: a top-level `/dc-graph` command if usage justifies it. Not needed
|
|
1425
|
+
initially.
|
|
1426
|
+
|
|
1427
|
+
### Rules
|
|
1428
|
+
|
|
1429
|
+
- Read-only and derived. Never edits specs, tasks, or the index.
|
|
1430
|
+
- Generated from facts, not prose, so the same workspace always yields the same
|
|
1431
|
+
graph (digests make staleness visible).
|
|
1432
|
+
- Large workspaces: filter by target (`ui/theme`), group collapsed nodes
|
|
1433
|
+
(`+7 requirements`), and cap edge counts. A graph of 500 requirements is a
|
|
1434
|
+
hairball — presentation grade should aggregate.
|
|
1435
|
+
- No model polishing by default. Unlike `dc_diagram`'s optional polish step, a spec
|
|
1436
|
+
graph has a deterministic correct answer.
|
|
1437
|
+
|
|
1438
|
+
## Generation pipeline
|
|
1439
|
+
|
|
1440
|
+
The pipeline exists today; the spec layer extends it.
|
|
1441
|
+
|
|
1442
|
+
1. `/dc-plan <request>` opens a `planningTransaction` and sends
|
|
1443
|
+
`buildPlanningPrompt(...)`.
|
|
1444
|
+
2. `setPlanCommitActive(true)` registers `dc_plan_commit` and adds it to the
|
|
1445
|
+
active tool set for that turn only.
|
|
1446
|
+
3. Pi explores with its normal tools and calls `dc_plan_commit` exactly once.
|
|
1447
|
+
4. `validatePlanSpec(params, snapshot)` checks the spec against the live snapshot.
|
|
1448
|
+
5. `ctx.ui.confirm` shows a preview.
|
|
1449
|
+
6. `assemblePlanDml(plan, snapshot)` emits the DML.
|
|
1450
|
+
7. `validateGeneratedPlan(dml)` rejects `.deepclause/` and runs
|
|
1451
|
+
`validateWithProlog(dml)`.
|
|
1452
|
+
8. `writePlanNonDestructively` writes the files, never overwriting.
|
|
1453
|
+
|
|
1454
|
+
Two guarantees fall out: **the model cannot produce invalid DML**, and **nothing is
|
|
1455
|
+
written until the user confirms**.
|
|
1456
|
+
|
|
1457
|
+
### Change-aware generation
|
|
1458
|
+
|
|
1459
|
+
Three stages make task↔scenario mapping deterministic rather than model-invented:
|
|
1460
|
+
|
|
1461
|
+
- **Stage 1 — DML scaffold (0 tokens).** Before the planning turn,
|
|
1462
|
+
`spec_scaffold.dml` parses the delta and emits a draft: one task per planned
|
|
1463
|
+
implementation step, each carrying the scenario ids it must satisfy and a
|
|
1464
|
+
suggested check derived from what the repo actually offers. Coverage is computed
|
|
1465
|
+
from the parse tree, not guessed.
|
|
1466
|
+
- **Stage 2 — pi enrichment.** The draft is injected into `buildPlanningPrompt`.
|
|
1467
|
+
Pi's job is bounded: choose `executor`, pick `requiredTools` from the exact
|
|
1468
|
+
active set, ground each check in real commands and paths, and phrase `do` and
|
|
1469
|
+
`expected`.
|
|
1470
|
+
- **Stage 3 — validated assembly.** `validatePlanSpec` gains the coverage and
|
|
1471
|
+
discoverability checks, then `assemblePlanDml` emits `tasks.dml` + `apply.dml`.
|
|
1472
|
+
|
|
1473
|
+
The commit payload **is** the task list. There is no Markdown intermediate and no
|
|
1474
|
+
`tasks.md`.
|
|
1475
|
+
|
|
1476
|
+
### Why the model fills a typed payload, not DML
|
|
1477
|
+
|
|
1478
|
+
`validatePlanSpec` checks the plan against the **live pi environment** — that every
|
|
1479
|
+
requested tool actually exists *and* is currently active, and that no step requests
|
|
1480
|
+
`dc_run`/`dc_plan_commit`/`pi_agent_step`. That check can only happen in TypeScript,
|
|
1481
|
+
because the DML runtime is pure Prolog with no access to pi's tool registry. If pi
|
|
1482
|
+
wrote raw DML, that validation would be unavailable at commit time.
|
|
1483
|
+
|
|
1484
|
+
So: **the file is DML; the authoring interface is a typed commit.** Hand-editing
|
|
1485
|
+
`tasks.dml` or `apply.dml` remains allowed, and gets its environment check later at
|
|
1486
|
+
`/dc-run` preflight (`readPlanRequiredTools` plus the active-tool check).
|
|
1487
|
+
|
|
1488
|
+
The split in one line: **DML derives, pi decides, TypeScript assembles.**
|
|
1489
|
+
|
|
1490
|
+
## The driver: plan as data + consulted interpreter
|
|
1491
|
+
|
|
1492
|
+
All loop-shaped logic lives once in `lib/apply.dml` and is exercised by every
|
|
1493
|
+
change. `% Plan format:` versions the file; `consult` means old plans pick up
|
|
1494
|
+
driver fixes.
|
|
1495
|
+
|
|
1496
|
+
```prolog
|
|
1497
|
+
run_plan(TasksPath, Change, Max) :-
|
|
1498
|
+
remaining_tasks(Ids),
|
|
1499
|
+
run_all(TasksPath, Change, Ids, Max),
|
|
1500
|
+
verify_change(Change, ok),
|
|
1501
|
+
final_report(Change).
|
|
1502
|
+
|
|
1503
|
+
remaining_tasks(Ids) :-
|
|
1504
|
+
findall(Id, (plan_task(Id, _), \+ plan_task_status(Id, done(_))), Ids).
|
|
1505
|
+
|
|
1506
|
+
run_all(_, _, [], _).
|
|
1507
|
+
run_all(TasksPath, Change, [Id|Rest], Max) :-
|
|
1508
|
+
plan_task(Id, Step),
|
|
1509
|
+
run_task(TasksPath, Change, Id, Step, Max),
|
|
1510
|
+
run_all(TasksPath, Change, Rest, Max).
|
|
1511
|
+
|
|
1512
|
+
run_task(TasksPath, Change, Id, Step, Max) :-
|
|
1513
|
+
attempt(TasksPath, Change, Id, Step, 1, Max, none, Summary),
|
|
1514
|
+
synthesize_task(Id, Step, Summary).
|
|
1515
|
+
|
|
1516
|
+
attempt(TasksPath, Change, Id, Step, N, Max, Feedback, Summary) :-
|
|
1517
|
+
N =< Max,
|
|
1518
|
+
format(string(Progress), "Task ~w, attempt ~w/~w", [Id, N, Max]),
|
|
1519
|
+
output(Progress),
|
|
1520
|
+
execute(Id, Step, Feedback, Summary),
|
|
1521
|
+
verify(Step, Summary, Verdict),
|
|
1522
|
+
( Verdict = ok
|
|
1523
|
+
-> record_status(TasksPath, Id, done(N)),
|
|
1524
|
+
record_verified(Change, Id, N, Summary)
|
|
1525
|
+
; Verdict = fail(Evidence),
|
|
1526
|
+
N1 is N + 1,
|
|
1527
|
+
format(string(Msg), "Task ~w failed verification: ~w", [Id, Evidence]),
|
|
1528
|
+
output(Msg),
|
|
1529
|
+
record_status(TasksPath, Id, failed(N1, Evidence)),
|
|
1530
|
+
attempt(TasksPath, Change, Id, Step, N1, Max, Evidence, Summary)
|
|
1531
|
+
).
|
|
1532
|
+
|
|
1533
|
+
attempt(TasksPath, _, Id, _, N, Max, _, _) :-
|
|
1534
|
+
N > Max,
|
|
1535
|
+
record_status(TasksPath, Id, failed(Max, "attempts exhausted")),
|
|
1536
|
+
throw(step_exhausted(Id, Max)).
|
|
1537
|
+
```
|
|
1538
|
+
|
|
1539
|
+
The retry is a **repair**, not a rerun: the failed attempt's evidence is threaded
|
|
1540
|
+
back into the delegated instruction.
|
|
1541
|
+
|
|
1542
|
+
```prolog
|
|
1543
|
+
execute(Id, Step, none, Summary) :-
|
|
1544
|
+
instruction(Id, Step, Instruction, Tools, Expected),
|
|
1545
|
+
exec(pi_agent_step(instruction: Instruction, tools: Tools,
|
|
1546
|
+
expected: Expected, skills: []), Summary).
|
|
1547
|
+
|
|
1548
|
+
execute(Id, Step, Feedback, Summary) :-
|
|
1549
|
+
Feedback \= none,
|
|
1550
|
+
instruction(Id, Step, Instruction, Tools, Expected),
|
|
1551
|
+
format(string(Fix),
|
|
1552
|
+
"~w~n~nThe previous attempt failed verification with:~n~w~n~nFix only what is needed; do not redo the whole step.",
|
|
1553
|
+
[Instruction, Feedback]),
|
|
1554
|
+
exec(pi_agent_step(instruction: Fix, tools: Tools,
|
|
1555
|
+
expected: Expected, skills: []), Summary).
|
|
1556
|
+
```
|
|
1557
|
+
|
|
1558
|
+
`verify/3` **always succeeds** and binds a verdict, so the caller can decide:
|
|
1559
|
+
|
|
1560
|
+
```prolog
|
|
1561
|
+
verify(Step, Summary, ok) :-
|
|
1562
|
+
Summary \= "",
|
|
1563
|
+
get_dict(checks, Step, Checks),
|
|
1564
|
+
\+ ( member(C, Checks), run_check(C, R), R \= ok ),
|
|
1565
|
+
!.
|
|
1566
|
+
verify(Step, Summary, fail(Evidence)) :-
|
|
1567
|
+
( Summary == ""
|
|
1568
|
+
-> Evidence = "delegated step returned no summary"
|
|
1569
|
+
; get_dict(checks, Step, Checks),
|
|
1570
|
+
findall(M, (member(C, Checks), run_check(C, M), M \= ok), Msgs),
|
|
1571
|
+
( Msgs = [] -> Evidence = "verification failed"
|
|
1572
|
+
; atomic_list_concat(Msgs, "; ", Evidence) )
|
|
1573
|
+
).
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
Design notes:
|
|
1577
|
+
|
|
1578
|
+
- **Feedback is threaded as an argument**, not asserted. Backtracking rolls memory
|
|
1579
|
+
back in DML, so a locally-bound failure would be lost and an asserted one would
|
|
1580
|
+
accumulate. Threading keeps each repair self-contained.
|
|
1581
|
+
- **The gate uses negation-as-failure** ("all checks pass"), the natural Prolog
|
|
1582
|
+
idiom for a requirement.
|
|
1583
|
+
- **`throw` on exhaustion** is what makes the rollback path reachable.
|
|
1584
|
+
|
|
1585
|
+
The illustrative predicates need smoke tests against the WASM runtime before they
|
|
1586
|
+
are load-bearing: `consult` path resolution, `exists_file/1`,
|
|
1587
|
+
`atomic_list_concat/3`, `rename_file/2`, `get_dict/3` over consulted dicts, and
|
|
1588
|
+
`sub_string/5` are documented as available, but the DML reference warns that not
|
|
1589
|
+
all SWI builtins are guaranteed.
|
|
1590
|
+
|
|
1591
|
+
## Preflight and detection changes
|
|
1592
|
+
|
|
1593
|
+
Two existing helpers need to change:
|
|
1594
|
+
|
|
1595
|
+
- **`isContextualPlan` must not search for `pi_agent_step(`.** With the driver in
|
|
1596
|
+
`lib/apply.dml`, that string is no longer in the change file. Detection must key
|
|
1597
|
+
off metadata: `% Contextual: true`, or the presence of `% Required pi tools:`.
|
|
1598
|
+
- **`resolveDmlPath` needs a change rule.** A bare name currently resolves to
|
|
1599
|
+
`skills/<name>.dml`. It needs `changes/<slug>/apply.dml` with a documented
|
|
1600
|
+
precedence (skills → plans → changes), or users type
|
|
1601
|
+
`/dc-run changes/add_dark_mode/apply`.
|
|
1602
|
+
|
|
1603
|
+
## Rollback and atomicity
|
|
1604
|
+
|
|
1605
|
+
The goal: apply mutates the working tree and must be rollbackable, and a failed
|
|
1606
|
+
apply must never leave a half-applied change.
|
|
1607
|
+
|
|
1608
|
+
### Why not a git worktree
|
|
1609
|
+
|
|
1610
|
+
`pi_agent_step` runs inside the live pi session, whose working directory is
|
|
1611
|
+
`ctx.cwd`. The delegated turn's `read`/`edit`/`bash` tools operate there, and there
|
|
1612
|
+
is no per-turn cwd override. Pointing the model at a separate worktree would
|
|
1613
|
+
require a separate pi process or path-rewriting every tool call — isolation
|
|
1614
|
+
theater. Additionally, `specs/` and `changes/` live inside the repo, so a worktree
|
|
1615
|
+
would check out its own copy of the specs the plan is reading.
|
|
1616
|
+
|
|
1617
|
+
A worktree becomes viable only if pi grows a per-turn working-directory override.
|
|
1618
|
+
Until then, snapshot + restore in place. (A worktree *is* the right tool for
|
|
1619
|
+
parallel *development* workstreams — see the parallel strategy section — just not
|
|
1620
|
+
for runtime apply isolation.)
|
|
1621
|
+
|
|
1622
|
+
### Snapshot + restore
|
|
1623
|
+
|
|
1624
|
+
```
|
|
1625
|
+
snapshot → run tasks → verify → accept
|
|
1626
|
+
↘ failure/abort → restore
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
- At start: record `git rev-parse HEAD` and `git status --porcelain`; refuse if the
|
|
1630
|
+
tree is dirty unless `--allow-dirty` (then `git stash create` for tracked
|
|
1631
|
+
changes). Record untracked paths that did not exist before.
|
|
1632
|
+
- On failure: `git checkout -- <paths>` / `git reset --hard <snapshot>`, plus
|
|
1633
|
+
removal of newly created untracked paths. Reset only ever targets the
|
|
1634
|
+
harness-recorded snapshot.
|
|
1635
|
+
- Persist the ref in `change.json` (`applySnapshot: <sha>`) so a failed apply is
|
|
1636
|
+
recoverable and idempotent, and print the exact recovery command in the result.
|
|
1637
|
+
|
|
1638
|
+
### The fallback clause is not enough on its own
|
|
1639
|
+
|
|
1640
|
+
- The DML fallback clause only fires on **logical** failure (a goal fails or
|
|
1641
|
+
throws). It does **not** fire on `/dc-cancel` or a crash: the abort stops the
|
|
1642
|
+
generator and `executeDml`'s `finally` disposes the SDK. So the authoritative
|
|
1643
|
+
restore lives in the **harness `finally`**, which sees logical failure, abort,
|
|
1644
|
+
and exceptions uniformly.
|
|
1645
|
+
- `answer/1` commits. A plan that partially succeeds and then answers will not
|
|
1646
|
+
reset — which is why the verification gate must `throw`/`fail`, not merely
|
|
1647
|
+
report.
|
|
1648
|
+
|
|
1649
|
+
Shape:
|
|
1650
|
+
|
|
1651
|
+
```prolog
|
|
1652
|
+
agent_main :-
|
|
1653
|
+
catch(
|
|
1654
|
+
( exec(dc_apply_snapshot(change: "add_dark_mode"), Snap),
|
|
1655
|
+
run_plan(TasksPath, "add_dark_mode", 3),
|
|
1656
|
+
exec(dc_apply_accept(change: "add_dark_mode", snapshot: Snap), _),
|
|
1657
|
+
answer(Report)
|
|
1658
|
+
),
|
|
1659
|
+
Error,
|
|
1660
|
+
( exec(dc_apply_restore(change: "add_dark_mode", snapshot: Snap), _),
|
|
1661
|
+
format(string(Msg), "Apply failed and was rolled back: ~w", [Error]),
|
|
1662
|
+
throw(rolled_back(Msg))
|
|
1663
|
+
)
|
|
1664
|
+
).
|
|
1665
|
+
|
|
1666
|
+
agent_main :- % belt-and-braces for pure logical failure
|
|
1667
|
+
answer("Change add_dark_mode did not complete. The working tree was restored by the harness.").
|
|
1668
|
+
```
|
|
1669
|
+
|
|
1670
|
+
Both layers are needed because they cover different failure modes.
|
|
1671
|
+
|
|
1672
|
+
### Progress and rollback interaction
|
|
1673
|
+
|
|
1674
|
+
Three policy questions fall out of writing status into a tracked file:
|
|
1675
|
+
|
|
1676
|
+
1. **Cross-run attempt counts revert on rollback**, so a task could retry forever
|
|
1677
|
+
across separate runs. Keep attempt history outside the worktree if that matters,
|
|
1678
|
+
or write a `failed(N)` line *after* restore deliberately.
|
|
1679
|
+
2. **Partial progress is lost.** If apply fails at task 5 of 6, restore reverts
|
|
1680
|
+
tasks 1–4 as well. Preserving partial progress requires restore scoped to the
|
|
1681
|
+
files each task touched rather than a blanket reset — an explicit policy choice,
|
|
1682
|
+
not a default.
|
|
1683
|
+
3. **Failure evidence lands in a committed file** via
|
|
1684
|
+
`failed(N, Evidence)`. If that is unwanted, store `failed(N)` only and keep
|
|
1685
|
+
detail in the pi session.
|
|
1686
|
+
|
|
1687
|
+
## Runtime tools
|
|
1688
|
+
|
|
1689
|
+
| Tool | Scope | Justification |
|
|
1690
|
+
|---|---|---|
|
|
1691
|
+
| `pi_workspace_list` | read-only directory listing | exists today |
|
|
1692
|
+
| `pi_bash` | approval-gated shell | exists today |
|
|
1693
|
+
| `pi_agent_step` | bounded delegated pi turn | exists today; contextual plans only |
|
|
1694
|
+
| `dc_verify_run` | declared verification commands only | needs host shell; must run unattended |
|
|
1695
|
+
| `dc_apply_snapshot` / `dc_apply_restore` | harness-recorded git snapshot | needs host git; must run unattended |
|
|
1696
|
+
| `dc_spec_query` (optional) | read-only spec/delta index queries | introspection, not a gate; fixed predicate allowlist |
|
|
1697
|
+
| `dc_spec_graph` (optional) | read-only capability/change graph rendering | view over derived facts via the existing diagram viewer |
|
|
1698
|
+
|
|
1699
|
+
`tasks.dml` is **not** written through a tool. The driver rewrites its managed
|
|
1700
|
+
status block with native Prolog file I/O (`open/3`, `rename_file/2`) in the WASM
|
|
1701
|
+
filesystem, where the workspace is mounted at `/workspace`. Reads and writes stay
|
|
1702
|
+
inside the workspace by construction. Human-facing writes to `specs/` and
|
|
1703
|
+
`changes/` still go through the same non-destructive policy as the rest of the
|
|
1704
|
+
extension.
|
|
1705
|
+
|
|
1706
|
+
## Validation gates
|
|
1707
|
+
|
|
1708
|
+
| Layer | Gate | Blocks |
|
|
1709
|
+
|---|---|---|
|
|
1710
|
+
| spec | every requirement has ≥1 scenario; no implementation detail; RFC 2119 usage | `/dc-check` failure |
|
|
1711
|
+
| spec | delta consistency (MODIFIED exists, no ADDED/MODIFIED collision) | archive |
|
|
1712
|
+
| plan | every scenario covered by ≥1 task `satisfies` | writing `tasks.dml` |
|
|
1713
|
+
| plan | every task declares verification | writing `tasks.dml` |
|
|
1714
|
+
| plan | `cmd(...)` checks discoverable in the repo | silently bogus checks |
|
|
1715
|
+
| plan | no in-flight change modifies the same requirement (index query) | committing a conflicting change |
|
|
1716
|
+
| apply | all checks pass; change-level `verify_change/2` | `answer/1` |
|
|
1717
|
+
| archive | merged spec well-formed; drift digest matches | writing `specs/` |
|
|
1718
|
+
| archive | index regenerated; delta status set to archived | stale inventory |
|
|
1719
|
+
|
|
1720
|
+
Today only the plan-shape checks exist (`validatePlanSpec`: steps, ids, tools
|
|
1721
|
+
exist/active, no control tools; `validateGeneratedPlan`: DML parses). The spec
|
|
1722
|
+
relationship — coverage, requirement existence, behavioral purity — is absent and
|
|
1723
|
+
is what this layer adds.
|
|
1724
|
+
|
|
1725
|
+
## Security and hardening
|
|
1726
|
+
|
|
1727
|
+
- **No executable content in specs.** Moving checks out of the spec removes the
|
|
1728
|
+
supply-chain risk of asserting clauses from a shared review artifact. The
|
|
1729
|
+
remaining executable surfaces are `tasks.dml` (consulted data, dispatched by a
|
|
1730
|
+
fixed interpreter) and `dc_verify_run` (an exact command allowlist).
|
|
1731
|
+
- **Shell-metacharacter bypass.** The "discoverable command" check is string
|
|
1732
|
+
matching. A discovered-looking command that smuggles metacharacters must be
|
|
1733
|
+
rejected at approval time; the suite approval should render the exact argv.
|
|
1734
|
+
- **WASM workspace escape.** The assumption that Prolog file I/O cannot leave
|
|
1735
|
+
`/workspace` needs adversarial tests: `..` traversal, symlinks, absolute paths,
|
|
1736
|
+
and `consult` targets.
|
|
1737
|
+
- **Concurrency.** `activeController` guards one pi process. Two sessions in the
|
|
1738
|
+
same repo can still race on `specs/`, `changes/`, and the managed status block.
|
|
1739
|
+
A workspace lock (or "one writer per workspace") is needed before multi-user use.
|
|
1740
|
+
- **Cost and latency.** Retries multiply tokens; suite approval multiplies
|
|
1741
|
+
commands. There is no budget cap today, only usage reporting. A run budget
|
|
1742
|
+
(`--max-tokens`/`--max-cost`) and a circuit breaker are prerequisites for
|
|
1743
|
+
unattended use.
|
|
1744
|
+
- **Provenance.** Consider `changes/<slug>/evidence.json` or session linking so an
|
|
1745
|
+
archived change can answer "which checks proved which scenario, when, and with
|
|
1746
|
+
what result."
|
|
1747
|
+
|
|
1748
|
+
## Open questions
|
|
1749
|
+
|
|
1750
|
+
1. **Layout scope.** Add `specs/`, `changes/`, `lib/` under `.pi/deepclause/`?
|
|
1751
|
+
This extends the `AGENTS.md` contract.
|
|
1752
|
+
2. **`/dc-check`.** Accept it as a required command rather than "optional later"?
|
|
1753
|
+
3. **`consult` smoke test.** Verify relative-path resolution, `exists_file/1`,
|
|
1754
|
+
`atomic_list_concat/3`, `rename_file/2`, consulted dicts, and `sub_string/5` in
|
|
1755
|
+
the WASM runtime.
|
|
1756
|
+
4. **Driver versioning.** `lib/apply.dml` is mutable and shared. Semver it
|
|
1757
|
+
(`lib/apply-v2.dml`) or pin a digest per plan, so a driver change cannot
|
|
1758
|
+
silently alter the meaning of an approved plan.
|
|
1759
|
+
5. **Drift digest scheme.** What to hash, and where in `change.json`.
|
|
1760
|
+
6. **Failure evidence in committed files.** Keep `failed(N, Evidence)` or
|
|
1761
|
+
`failed(N)` only?
|
|
1762
|
+
7. **Partial-progress restore.** Blanket `reset --hard`, or restore scoped to
|
|
1763
|
+
files each task touched?
|
|
1764
|
+
8. **Cross-run attempt counts.** Keep history outside the worktree?
|
|
1765
|
+
9. **Model-assisted checks.** Always allowed with a label, or opt-in per change?
|
|
1766
|
+
10. **Retire capabilities.** Adopt OpenSpec's `retire_capabilities` opt-in for the
|
|
1767
|
+
one destructive archive step?
|
|
1768
|
+
11. **Escalation.** After attempt exhaustion, ask the user before restoring, or
|
|
1769
|
+
restore immediately?
|
|
1770
|
+
12. **Full suite.** Optional `full_suite/1` run after all tasks, before the
|
|
1771
|
+
change-level gate?
|
|
1772
|
+
13. **Tasks-in-one-file.** Revisit whether `tasks.dml` + `apply.dml` should ever
|
|
1773
|
+
collapse into one file; two is right for now because it separates content from
|
|
1774
|
+
metadata.
|
|
1775
|
+
14. **Index persistence.** Derive on demand, or commit a digest-stamped
|
|
1776
|
+
`index.dml`? At what scale does lazy parsing stop being fast enough?
|
|
1777
|
+
15. **`delta_status` granularity.** Per requirement, or per change/operation?
|
|
1778
|
+
Per requirement allows partial application but costs more bookkeeping.
|
|
1779
|
+
16. **Stacked changes.** Two changes touching the same requirement need a
|
|
1780
|
+
supersede/depends-on relation, or conflict detection becomes noise.
|
|
1781
|
+
17. **Cross-store queries.** The index is per workspace; OpenSpec-style stores
|
|
1782
|
+
would need merged indexes or a fan-out query.
|
|
1783
|
+
18. **Graph views and defaults.** Which views ship first, and what is the default
|
|
1784
|
+
grade for a plain "show me the spec graph"?
|
|
1785
|
+
19. **Graph scale.** Aggregation and filtering strategy for large workspaces.
|
|
1786
|
+
20. **`/dc-graph` command vs `dc_spec_graph` tool only.** Add a top-level command,
|
|
1787
|
+
or keep it as natural language plus `/dc-run spec_graph`?
|
|
1788
|
+
|
|
1789
|
+
## Implementation phases
|
|
1790
|
+
|
|
1791
|
+
Serial foundation first — see the parallel strategy below.
|
|
1792
|
+
|
|
1793
|
+
**Phase 0 (serial, prerequisite)**
|
|
1794
|
+
|
|
1795
|
+
0.1 Freeze the contracts: normative `docs/SPEC_FORMAT.md` (grammar, term schema,
|
|
1796
|
+
error codes), `change.json` schema + `schemaVersion`, the `plan_task`/`plan_task_status`
|
|
1797
|
+
shapes, and the `dc_plan_commit` payload v2.
|
|
1798
|
+
0.2 Build the test harness: grammar conformance corpus, golden merge files, a
|
|
1799
|
+
recording/replay LLM backend, an extension harness with a scriptable `pi` fake.
|
|
1800
|
+
0.3 Split `src/index.ts` into command/tool modules; `index.ts` becomes a thin
|
|
1801
|
+
composition root.
|
|
1802
|
+
0.4 Implement `lib/specs.dml` (grammar + validators) and `spec_validate.dml`.
|
|
1803
|
+
|
|
1804
|
+
**Phases (parallelizable after Phase 0)**
|
|
1805
|
+
|
|
1806
|
+
1. **Decide layout** (open question 1).
|
|
1807
|
+
2. **`/dc-check`** wiring — highest value, lowest risk, no change to `/dc-plan`.
|
|
1808
|
+
3. **`lib/apply.dml` driver** — task/status model, per-task verification, bounded
|
|
1809
|
+
repair, managed status block.
|
|
1810
|
+
4. **`dc_verify_run` + suite approval** at `/dc-run` confirm time.
|
|
1811
|
+
5. **`dc_apply_snapshot` / `dc_apply_restore` + harness `finally`** rollback.
|
|
1812
|
+
6. **`spec_scaffold` + coverage and discoverability checks** in `validatePlanSpec`.
|
|
1813
|
+
7. **`assemblePlanDml` v2** — emits `tasks.dml` + `apply.dml`.
|
|
1814
|
+
8. **`spec_merge.dml` + `spec_archive`** with diff-and-confirm and digest checks.
|
|
1815
|
+
9. **Change resolution and detection** — `resolveDmlPath` change rule,
|
|
1816
|
+
metadata-based `isContextualPlan`, `/dc-list` and `/dc` spec/change awareness.
|
|
1817
|
+
10. **Feature/delta index** — `deltas.dml` emission, `spec_query.dml`,
|
|
1818
|
+
`spec_reindex.dml`, and the index-based conflict and coverage checks.
|
|
1819
|
+
11. Optionally, a **DML-declared artifact schema** (facts like
|
|
1820
|
+
`artifact(id, requires, instruction, template)`) to generalize today's
|
|
1821
|
+
hard-coded `PlanSpec` — the OpenSpec artifact-graph idea, in DML. Gate this
|
|
1822
|
+
behind evidence that plan schemas actually vary.
|
|
1823
|
+
12. **Spec graphs** — `spec_graph.dml`, the optional read-only `dc_spec_graph`
|
|
1824
|
+
tool, and reuse of the existing diagram viewer for the capability, changes,
|
|
1825
|
+
traceability, lifecycle and conflict views.
|
|
1826
|
+
|
|
1827
|
+
## Parallel implementation strategy
|
|
1828
|
+
|
|
1829
|
+
**The governing rule: parallelism is gated by interface stability, not headcount.**
|
|
1830
|
+
Subagents are separate `pi` processes with isolated context, up to 8 tasks / 4
|
|
1831
|
+
concurrent, with a per-task `cwd` and output capped at 50 KB. They share the
|
|
1832
|
+
filesystem, so parallel writers collide unless isolated.
|
|
1833
|
+
|
|
1834
|
+
**Serial critical path (single owner):** Phase 0 above. Contracts first, harness
|
|
1835
|
+
second, `index.ts` split third, deterministic core fourth. Everything else can fan
|
|
1836
|
+
out after that.
|
|
1837
|
+
|
|
1838
|
+
**Workstreams (four concurrent, one writer per file/module):**
|
|
1839
|
+
|
|
1840
|
+
| WS | Scope | Files (single-writer) | Depends on |
|
|
1841
|
+
|---|---|---|---|
|
|
1842
|
+
| WS1 | Grammar, validators, merge | `lib/specs.dml`, `skills/spec_*.dml`, fixtures | — |
|
|
1843
|
+
| WS2 | Harness runtime tools | extracted `src/tools/verify.ts`, `src/tools/apply.ts`, `src/runtime.ts` | frozen tool schemas |
|
|
1844
|
+
| WS3 | Planner/assembly | `src/planner.ts` (task/status emission, coverage checks) | WS1 term schema |
|
|
1845
|
+
| WS4 | Change lifecycle | `src/commands/{plan,list,check}.ts`, `spec_scaffold.dml` | WS1, WS3 |
|
|
1846
|
+
| WS5 | Docs + fixtures | `docs/`, `SPEC_AUTHORING.md`, `tests/fixtures/` | starts immediately |
|
|
1847
|
+
|
|
1848
|
+
**How to run them:**
|
|
1849
|
+
|
|
1850
|
+
- **Scouts are free parallelism.** Read-only recon (existing planner behaviour,
|
|
1851
|
+
OpenSpec's `specs-apply.ts` semantics, SDK `consult`/WASM file-IO behaviour) has
|
|
1852
|
+
no conflict risk. Also use scouts to adversarially probe the security assumptions
|
|
1853
|
+
above.
|
|
1854
|
+
- **One writer per file.** Where overlap is unavoidable, give each writer a **git
|
|
1855
|
+
worktree** via the subagent `cwd` parameter. A worktree is the wrong tool for
|
|
1856
|
+
runtime apply isolation (pi cannot chdir the session) but exactly the right tool
|
|
1857
|
+
for build-time isolation.
|
|
1858
|
+
- **Chain mode for dependency:** `scout → planner → worker`.
|
|
1859
|
+
- **Reviewers must not write.** Gate each workstream against the frozen spec and
|
|
1860
|
+
conformance corpus, then merge.
|
|
1861
|
+
- **Workers commit, not paste.** Each worker commits in its worktree and returns
|
|
1862
|
+
branch + commit + files changed.
|
|
1863
|
+
- **Cap at four live workstreams** given the concurrency limit and the hot files
|
|
1864
|
+
(`planner.ts`, `runtime.ts`, `commands/*`).
|
|
1865
|
+
- **Budget tokens.** Four parallel workers burn cost quickly; tie each workstream
|
|
1866
|
+
to an attempt/cost ceiling.
|
|
1867
|
+
|
|
1868
|
+
**What parallelization will not fix:** the human remains the integrator; the
|
|
1869
|
+
conformance corpus is what lets workers self-check instead of asking; cross-cutting
|
|
1870
|
+
`PlanSpec`/`plan_task_status` changes must stay single-owner; integration testing is
|
|
1871
|
+
serial and needs a dedicated window.
|
|
1872
|
+
|
|
1873
|
+
## Decisions log
|
|
1874
|
+
|
|
1875
|
+
| Consideration | Outcome |
|
|
1876
|
+
|---|---|
|
|
1877
|
+
| `dc_spec_verify` runtime tool | **Rejected.** Pure Prolog; use `consult('lib/specs.dml')` and plain predicates. Do not make a deterministic predicate LLM-callable. |
|
|
1878
|
+
| `task/N`/`prompt/N` for verification | **Rejected as gates.** LLM calls are non-deterministic; keep them for prose and synthesis. |
|
|
1879
|
+
| Checks attached to requirements/scenarios in the spec | **Rejected.** Implementation detail; the spec is behavioral only. Checks live on tasks. |
|
|
1880
|
+
| Embedded executable `prolog` blocks in specs | **Rejected.** Both clause embedding (supply-chain risk) and annotation mini-syntaxes (second grammar, hidden from review). Checks are declarative data terms in `tasks.dml`. |
|
|
1881
|
+
| `tasks.md` (Markdown task list) | **Rejected.** A redundant view of the plan; drift surface. Replaced by `tasks.dml`. |
|
|
1882
|
+
| Single DML file holding tasks + driver + metadata | **Rejected for now.** `tasks.dml` (content) and `apply.dml` (wiring + preflight metadata) have different owners and change rates. Revisit in open question 13. |
|
|
1883
|
+
| `change.json.completed` progress record | **Rejected.** Progress belongs in `tasks.dml` as `plan_task_status/2`, queryable and diffable. |
|
|
1884
|
+
| Status as a dict | **Rejected.** Compound terms (`done(1)`, `failed(3, E)`) match, write, and round-trip trivially. |
|
|
1885
|
+
| Read/write `tasks.dml` via a runtime tool | **Rejected.** Native Prolog file I/O with a managed marker block is sufficient and keeps logic in DML. |
|
|
1886
|
+
| Git worktree for runtime apply isolation | **Rejected** while pi has no per-turn cwd override; use snapshot + restore in place. (Worktrees remain correct for parallel development.) |
|
|
1887
|
+
| Restore only in the DML fallback clause | **Rejected.** Does not fire on abort/crash; authoritative restore belongs in the harness `finally`. |
|
|
1888
|
+
| Persisted parsed-term database | **Rejected as a source.** The tree is ephemeral; Markdown is the only source of truth for behavior. A digest-stamped, disposable index is allowed only as a cache that is regenerated on mismatch, never hand-edited, and derived on demand by default. |
|
|
1889
|
+
| Per-command shell approval for checks | **Rejected.** Approve the verification suite once; scope `dc_verify_run` to declared commands. |
|
|
1890
|
+
| Straight-line generated plan | **Rejected.** Emit plan data + consulted driver so gating, retry, and repair are shared and testable. |
|
|
1891
|
+
| `pi_agent_step(` string search for contextual detection | **Rejected.** Driver lives in `lib/`; key off `% Contextual:`/`% Required pi tools:` metadata instead. |
|
|
1892
|
+
| `dc_spec_query` read-only tool | **Accepted (optional).** Unlike the rejected `dc_spec_verify` gate, read-only introspection is a legitimate model-callable tool; the query language stays a fixed predicate allowlist. |
|
|
1893
|
+
| New rendering code for spec graphs | **Rejected.** Reuse the existing `dc_diagram` / `src/diagram/*` Mermaid viewer; `spec_graph.dml` only supplies the graph as generated Mermaid text. |
|