deepclause-pi 0.2.0 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepclause-pi",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Pi-hosted runtime for DeepClause DML programs",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "prepublishOnly": "npm run check"
44
44
  },
45
45
  "dependencies": {
46
- "deepclause-sdk": "npm:deepclause-sdk@0.0.87",
46
+ "deepclause-sdk": "npm:deepclause-sdk@0.0.89",
47
47
  "typebox": "1.3.7"
48
48
  },
49
49
  "peerDependencies": {
@@ -52,6 +52,10 @@ Generated steps use `executor=dml` for contained model reasoning and `executor=p
52
52
 
53
53
  Contextual plans require explicit user execution with `/dc-run plans/<name>.dml` and confirmation. They cannot run through model-callable `dc_run`, because that would nest a pi agent turn inside the calling agent turn.
54
54
 
55
+ ### Change plans
56
+
57
+ `/dc-plan <request> --change=<slug>` targets a change instead of `plans/`. The same planning turn first creates `changes/<slug>/` with normal file tools (proposal.md, one delta spec per capability under `specs/`, optional design.md), then commits `changes/<slug>/tasks.dml`: `plan_task/2` facts with `satisfies` (scenario ids) and declarative `checks`, plus the managed `plan_task_status/2` block. Every step must declare at least one check. Re-running the same change without `--update` fails once `tasks.dml` exists; `--update` (or a leading `update` keyword) regenerates it and resets every status to pending. Review it with `/dc-check <slug>` and execute it with `/dc-apply <slug>`.
58
+
55
59
  ## Program structure and arguments
56
60
 
57
61
  Pi passes zero to three positional **strings** to `agent_main`:
@@ -391,6 +395,68 @@ The tool extracts a deterministic Mermaid seed, has pi rewrite it in the chosen
391
395
 
392
396
  Do not hand-write Mermaid for the user, and do not copy diagram tooling into the workspace. Regenerating a grade replaces only that grade's sidecar (`<name>.presentation.mmd` / `<name>.specification.mmd`).
393
397
 
398
+ ## Specs and deltas
399
+
400
+ DeepClause keeps behaviour specs separate from executable plans:
401
+
402
+ - `.pi/deepclause/specs/**/*.spec.md` — capability specs, the source of truth for behaviour.
403
+ - `.pi/deepclause/changes/<slug>/specs/**/*.md` — change deltas.
404
+ - `.pi/deepclause/lib/specs.dml` — the deterministic parser/validator used by the spec skills.
405
+ - `.pi/deepclause/lib/apply.dml` — the task driver (verify, retry, status write-back).
406
+ - `.pi/deepclause/skills/spec_validate.dml`, `spec_status.dml`, `spec_query.dml`, `spec_graph.dml`.
407
+
408
+ `tasks.dml` uses `plan_task/2` and `plan_task_status/2` — **not** `task/2`, which collides with
409
+ DML's built-in `task/N` predicate and will not unify after being read.
410
+
411
+ Specs are plain Markdown and must describe **behaviour only** — no commands, file paths,
412
+ library choices, or implementation plans; those belong in `design.md` or `tasks.dml`.
413
+ Structure:
414
+
415
+ - `## Purpose`
416
+ - `### Requirement: <name>` followed by prose using SHALL / MUST / SHOULD
417
+ - `#### Scenario: <name>` with `- **WHEN**` and `- **THEN**` (exactly four hashes)
418
+
419
+ Delta files wrap requirements in `## ADDED Requirements`, `## MODIFIED Requirements`,
420
+ `## REMOVED Requirements`, or `## RENAMED Requirements`. `MODIFIED` carries the full
421
+ replacement requirement.
422
+
423
+ Validate and inspect without spending model tokens:
424
+
425
+ - `/dc-check` — parse and validate every spec and delta; reports 3-hash scenarios, missing
426
+ scenarios and duplicates with line numbers.
427
+ - `/dc-run spec_status` — capability and delta inventory.
428
+ - `/dc-run spec_query <capability>` — one capability's requirements and scenarios.
429
+ - `/dc-run spec_graph capabilities|changes` — deterministic Mermaid graph.
430
+ - `/dc-run spec_merge <change>` — preview the delta merge into `specs/` (read-only).
431
+ - `/dc-run spec_coverage <change>` — which delta scenarios are covered by `tasks.dml`,
432
+ which tasks lack checks, and which `satisfies` ids are unknown. `/dc-check` reports the
433
+ same coverage and treats uncovered scenarios as errors once a `tasks.dml` exists.
434
+ - `/dc-run spec_scaffold <change>` — print a draft `tasks.dml` with one task per delta
435
+ scenario (read-only; fill in executor, tools, expected and checks).
436
+ - `/dc-run spec_apply <change> plan` — list the tasks and the approved verification commands
437
+ (read-only).
438
+ - `/dc-apply <change>` — execute the remaining tasks, verify each task's declarative checks
439
+ (`exists`, `cmd`, `model`), retry with the failure feedback (up to 3 attempts), and rewrite the
440
+ `plan_task_status/2` block in `tasks.dml`. The `cmd(...)` set is approved once before the run;
441
+ each command runs through the allowlisted `dc_verify_run` tool. A git snapshot is recorded first
442
+ (in `change.json`, with `applyState`). If the run does not finish, the working tree and the
443
+ `done`/`failed` statuses are **preserved**: re-run `/dc-apply <change>` to resume from the
444
+ remaining tasks, or `/dc-apply <change> --abort` to discard the apply and restore the snapshot.
445
+ A dirty tree refuses a fresh snapshot, so the apply then proceeds without rollback.
446
+
447
+ After `/dc-plan`, `/dc-apply` and `/dc-archive` leave uncommitted changes, the extension offers
448
+ to commit them (`git add -A` with a suggested message) or reminds you to. A clean tree is what
449
+ lets the next `/dc-apply` take a rollback snapshot; consider gitignoring
450
+ `.pi/deepclause/diagrams/` and `.pi/deepclause/changes/*/change.json`.
451
+ - `/dc-archive <change>` — show the preview, confirm, write the merged spec, then move the
452
+ change to `changes/archive/`. The DML step (`spec_archive.dml`) is marked `% Mutating: true`,
453
+ so `/dc-run` refuses it directly; always archive through `/dc-archive` so the merge is reviewed
454
+ first and the change folder is moved.
455
+ - `dc_spec_graph` — pi tool that renders the capability/change graph in the diagram viewer.
456
+
457
+ These skills are pure DML utilities: do not add model calls or runtime tools to them, and do
458
+ not rewrite the parser by hand.
459
+
394
460
  ## Conservative editing rules
395
461
 
396
462
  When modifying an existing skill:
@@ -0,0 +1,188 @@
1
+ %%% apply.dml — deterministic task driver, consulted by spec_apply.dml.
2
+ %%%
3
+ %%% Reads changes/<change>/tasks.dml (plan_task/2 + plan_task_status/2),
4
+ %%% executes the remaining tasks, verifies each task's checks, retries with
5
+ %%% feedback, and rewrites the managed status block.
6
+ %%%
7
+ %%% Execution is delegated per task: executor=pi calls the pi_agent_step bridge;
8
+ %%% executor=dml uses task/N with the current model. Checks are declarative:
9
+ %%% exists(Path), cmd(Command), model(Question).
10
+
11
+ :- consult('.pi/deepclause/lib/specs.dml').
12
+
13
+ sp_change_dir(Change, Dir) :- atomic_list_concat([".pi/deepclause/changes/", Change], Dir).
14
+
15
+ file_exists(Path) :-
16
+ ( atom_string(PathAtom, Path)
17
+ -> true
18
+ ; PathAtom = Path
19
+ ),
20
+ catch((open(PathAtom, read, Stream), close(Stream)), _, fail).
21
+
22
+ remaining_tasks(Tasks, Statuses, Remaining) :-
23
+ findall(plan_task(Id, Props), (
24
+ member(plan_task(Id, Props), Tasks),
25
+ \+ sp_status_done(Statuses, Id)
26
+ ), Remaining).
27
+
28
+ %% Snapshot/accept are optional: without the tools (or outside git) the apply just
29
+ %% proceeds without rollback, and the report says so.
30
+ take_snapshot(Snap) :-
31
+ ( catch(exec(dc_apply_snapshot, S), Err, S = error(Err)),
32
+ nonvar(S)
33
+ -> Snap = S
34
+ ; Snap = none
35
+ ).
36
+
37
+ accept_apply(none) :- !.
38
+ accept_apply(error(_)) :- !.
39
+ accept_apply(_) :- catch(exec(dc_apply_accept, _), _, true).
40
+
41
+ rollback_line(none, "rollback: unavailable (snapshot tool inactive or not a git repo)").
42
+ rollback_line(error(Err), Line) :- !, format(string(Line), "rollback: unavailable (~w)", [Err]).
43
+ rollback_line(Ref, Line) :-
44
+ format(string(Line), "rollback: snapshot ~w recorded (restored by /dc-apply on a non-OK run)", [Ref]).
45
+
46
+ run_plan(Change, Max, Report) :-
47
+ sp_change_dir(Change, Dir),
48
+ atomic_list_concat([Dir, "/tasks.dml"], TasksPath),
49
+ sp_tasks(Dir, Tasks),
50
+ take_snapshot(Snap),
51
+ sp_task_statuses(Dir, Statuses0),
52
+ remaining_tasks(Tasks, Statuses0, Remaining),
53
+ length(Tasks, Total),
54
+ length(Remaining, RemainingCount),
55
+ format(string(Start), "apply ~w: ~w tasks, ~w remaining, max ~w attempts", [Change, Total, RemainingCount, Max]),
56
+ output(Start),
57
+ run_all(TasksPath, Change, Remaining, Max, Statuses0, Statuses),
58
+ sp_task_statuses(Dir, FinalStatuses),
59
+ findall(Id, (member(plan_task(Id, _), Tasks), \+ sp_status_done(FinalStatuses, Id)), Outstanding),
60
+ length(Outstanding, OutstandingCount),
61
+ Done is Total - OutstandingCount,
62
+ sp_count_repairs(FinalStatuses, Repairs),
63
+ format(string(Header), "apply ~w: ~w/~w tasks verified (~w repairs)", [Change, Done, Total, Repairs]),
64
+ ( OutstandingCount =:= 0
65
+ -> accept_apply(Snap),
66
+ Result = ["status: OK"]
67
+ ; findall(Line, (member(Id, Outstanding), format(string(Line), " outstanding: ~w", [Id])), Lines),
68
+ Result = ["status: INCOMPLETE"|Lines]
69
+ ),
70
+ rollback_line(Snap, RollbackLine),
71
+ append([Header], Result, Body0),
72
+ append(Body0, [RollbackLine], Body),
73
+ atomics_to_string(Body, "\n", Report).
74
+
75
+ run_all(_, _, [], _, Statuses, Statuses).
76
+ run_all(TasksPath, Change, [plan_task(Id, Props)|Rest], Max, Statuses0, Statuses) :-
77
+ run_task(TasksPath, Statuses0, Change, Id, Props, Max, Statuses1),
78
+ run_all(TasksPath, Change, Rest, Max, Statuses1, Statuses).
79
+
80
+ run_task(TasksPath, Statuses0, Change, Id, Props, Max, Statuses) :-
81
+ attempt(TasksPath, Statuses0, Change, Id, Props, 1, Max, none, Statuses).
82
+
83
+ attempt(TasksPath, Statuses0, Change, Id, Props, N, Max, Feedback, Statuses) :-
84
+ N =< Max,
85
+ format(string(Progress), "task ~w attempt ~w/~w", [Id, N, Max]),
86
+ output(Progress),
87
+ execute(Change, Id, Props, Feedback, Summary),
88
+ verify_task(Props, Summary, Verdict),
89
+ ( Verdict = ok
90
+ -> sp_set_status(Statuses0, Id, done(N), Statuses),
91
+ sp_write_statuses(TasksPath, Statuses),
92
+ format(string(Done), "task ~w verified on attempt ~w", [Id, N]),
93
+ output(Done)
94
+ ; Verdict = fail(Evidence),
95
+ N1 is N + 1,
96
+ sp_set_status(Statuses0, Id, failed(N1, Evidence), Statuses1),
97
+ sp_write_statuses(TasksPath, Statuses1),
98
+ format(string(Msg), "task ~w failed verification: ~w", [Id, Evidence]),
99
+ output(Msg),
100
+ attempt(TasksPath, Statuses1, Change, Id, Props, N1, Max, Evidence, Statuses)
101
+ ).
102
+ attempt(_, Statuses, _, Id, _, N, Max, _, Statuses) :-
103
+ N > Max,
104
+ format(string(Msg), "task ~w exhausted ~w attempts", [Id, Max]),
105
+ output(Msg).
106
+
107
+ execute(Change, Id, Props, Feedback, Summary) :-
108
+ catch(get_dict(executor, Props, Executor), _, Executor = dml),
109
+ catch(get_dict(do, Props, Do), _, Do = ""),
110
+ catch(get_dict(expected, Props, Expected), _, Expected = ""),
111
+ build_instruction(Id, Do, Expected, Feedback, Instruction),
112
+ ( Executor == pi
113
+ -> catch(get_dict(tools, Props, Tools), _, Tools = []),
114
+ exec(pi_agent_step(instruction: Instruction, tools: Tools, expected: Expected, skills: []), Summary)
115
+ ; task(Instruction, string(Summary))
116
+ ).
117
+
118
+ build_instruction(Id, Do, Expected, none, Instruction) :-
119
+ format(string(Instruction), "Task ~w. ~w~nExpected result: ~w", [Id, Do, Expected]).
120
+ build_instruction(Id, Do, Expected, Feedback, Instruction) :-
121
+ Feedback \= none,
122
+ format(string(Instruction),
123
+ "Task ~w. ~w~nExpected result: ~w~n~nThe previous attempt failed verification: ~w~nFix only what is needed; do not redo the whole task.",
124
+ [Id, Do, Expected, Feedback]).
125
+
126
+ verify_task(Props, Summary, Verdict) :-
127
+ catch(get_dict(checks, Props, Checks), _, Checks = []),
128
+ findall(Message, (member(Check, Checks), run_check(Check, Message), Message \= ok), Failures),
129
+ ( Summary == ""
130
+ -> Verdict = fail("delegated step returned no summary")
131
+ ; Failures == []
132
+ -> Verdict = ok
133
+ ; atomic_list_concat(Failures, "; ", Evidence),
134
+ Verdict = fail(Evidence)
135
+ ).
136
+
137
+ run_check(exists(Path), Result) :-
138
+ ( file_exists(Path)
139
+ -> Result = ok
140
+ ; format(string(Result), "missing artifact ~w", [Path])
141
+ ).
142
+ run_check(cmd(Command), Result) :- run_check(cmd(Command, 1), Result).
143
+ run_check(cmd(Command, _), Result) :-
144
+ catch(exec(dc_verify_run(command: Command), Dict), Err, Dict = error(Err)),
145
+ ( Dict = error(Err)
146
+ -> format(string(Result), "command error: ~w", [Err])
147
+ ; get_dict(exitCode, Dict, Code),
148
+ ( Code =:= 0
149
+ -> Result = ok
150
+ ; catch(get_dict(stderr, Dict, ErrText), _, ErrText = ""),
151
+ normalize_space(string(Short), ErrText),
152
+ format(string(Result), "command failed: ~w (~w)", [Command, Short])
153
+ )
154
+ ).
155
+ run_check(model(Question), Result) :-
156
+ format(string(Instruction), "Verify by inspection only; do not modify. ~w Reply PASS or FAIL with a short reason.", [Question]),
157
+ catch(exec(pi_agent_step(instruction: Instruction, tools: ["read"], expected: "PASS or FAIL", skills: []), Verdict), _, Verdict = ""),
158
+ ( sub_string(Verdict, _, _, _, "PASS")
159
+ -> Result = ok
160
+ ; format(string(Result), "model check failed: ~w", [Verdict])
161
+ ).
162
+
163
+ check_command(cmd(Command), Command) :- !.
164
+ check_command(cmd(Command, _), Command) :- !.
165
+
166
+ plan_report(Change, Report) :-
167
+ sp_change_dir(Change, Dir),
168
+ sp_tasks(Dir, Tasks),
169
+ length(Tasks, Total),
170
+ findall(Line, (
171
+ member(plan_task(Id, Props), Tasks),
172
+ catch(get_dict(checks, Props, Checks), _, Checks = []),
173
+ catch(get_dict(do, Props, Do), _, Do = ""),
174
+ format(string(Line), " task ~w: ~w checks: ~w", [Id, Do, Checks])
175
+ ), Lines),
176
+ findall(Command, (
177
+ member(plan_task(_, Props), Tasks),
178
+ catch(get_dict(checks, Props, Checks), _, Checks = []),
179
+ member(Check, Checks),
180
+ check_command(Check, Command)
181
+ ), Commands0),
182
+ sort(Commands0, Commands),
183
+ findall(CommandLine, (member(Command, Commands), format(string(CommandLine), "command: ~w", [Command])), CommandLines),
184
+ format(string(Header), "apply plan ~w: ~w tasks", [Change, Total]),
185
+ append([Header], Lines, A),
186
+ append(A, [""], B),
187
+ append(B, CommandLines, Body),
188
+ atomics_to_string(Body, "\n", Report).
@@ -0,0 +1,20 @@
1
+ % spec_apply — execute a change's tasks.dml with verification and bounded retries.
2
+ % Mutating: true
3
+ % Usage: /dc-run spec_apply <change> (or /dc-apply <change>)
4
+ % /dc-run spec_apply <change> plan (preview, read-only)
5
+ :- consult('.pi/deepclause/lib/apply.dml').
6
+
7
+ agent_main(Change, "plan") :-
8
+ plan_report(Change, Report),
9
+ answer(Report).
10
+
11
+ agent_main(Change) :-
12
+ run_plan(Change, 3, Report),
13
+ answer(Report).
14
+
15
+ agent_main(Change, _Mode) :-
16
+ run_plan(Change, 3, Report),
17
+ answer(Report).
18
+
19
+ agent_main :-
20
+ answer("usage: /dc-run spec_apply <change> [plan]").
@@ -0,0 +1,11 @@
1
+ % spec_archive — merge a change delta into specs/ and move the change to archive/.
2
+ % Mutating: true
3
+ % Usage: /dc-run spec_archive <change>
4
+ :- consult('.pi/deepclause/lib/specs.dml').
5
+
6
+ agent_main(Change) :-
7
+ sp_archive(Change, apply, Report),
8
+ answer(Report).
9
+
10
+ agent_main :-
11
+ answer("usage: /dc-run spec_archive <change>").
@@ -0,0 +1,26 @@
1
+ % spec_coverage — scenario coverage of a change by its tasks.dml.
2
+ % Usage: /dc-run spec_coverage <change>
3
+ :- consult('.pi/deepclause/lib/specs.dml').
4
+
5
+ agent_main(Change) :-
6
+ atomic_list_concat([".pi/deepclause/changes/", Change], ChangeDir),
7
+ sp_coverage(ChangeDir, Scenarios, Tasks, Uncovered, NoCheck, Orphan),
8
+ length(Scenarios, ScenarioCount),
9
+ length(Tasks, TaskCount),
10
+ length(Uncovered, UncoveredCount),
11
+ Covered is ScenarioCount - UncoveredCount,
12
+ format(string(Header), "scenario coverage ~w: ~w/~w covered, ~w tasks", [Change, Covered, ScenarioCount, TaskCount]),
13
+ ( Uncovered == []
14
+ -> UncoveredLines = [" all scenarios covered"]
15
+ ; findall(Line, (member(Id, Uncovered), format(string(Line), " uncovered: ~w", [Id])), UncoveredLines)
16
+ ),
17
+ findall(Line2, (member(Id, NoCheck), format(string(Line2), " task without checks: ~w", [Id])), NoCheckLines),
18
+ findall(Line3, (member(Id, Orphan), format(string(Line3), " satisfies unknown scenario: ~w", [Id])), OrphanLines),
19
+ append([Header], UncoveredLines, A),
20
+ append(A, NoCheckLines, B),
21
+ append(B, OrphanLines, Body),
22
+ atomics_to_string(Body, "\n", Report),
23
+ answer(Report).
24
+
25
+ agent_main :-
26
+ answer("usage: /dc-run spec_coverage <change>").
@@ -0,0 +1,12 @@
1
+ % spec_graph — deterministic Mermaid graphs of specs and changes.
2
+ % Usage: /dc-run spec_graph [capabilities|changes]
3
+ % Views: capabilities (default), changes.
4
+ :- consult('.pi/deepclause/lib/specs.dml').
5
+
6
+ agent_main :-
7
+ sp_graph(capabilities, Mermaid),
8
+ answer(Mermaid).
9
+
10
+ agent_main(View) :-
11
+ sp_graph(View, Mermaid),
12
+ answer(Mermaid).
@@ -0,0 +1,10 @@
1
+ % spec_merge — preview merging a change delta into specs/ (read-only).
2
+ % Usage: /dc-run spec_merge <change>
3
+ :- consult('.pi/deepclause/lib/specs.dml').
4
+
5
+ agent_main(Change) :-
6
+ sp_archive(Change, plan, Report),
7
+ answer(Report).
8
+
9
+ agent_main :-
10
+ answer("usage: /dc-run spec_merge <change>").
@@ -0,0 +1,10 @@
1
+ % spec_query — deterministic capability inspector.
2
+ % Usage: /dc-run spec_query <capability> e.g. /dc-run spec_query ui/theme
3
+ :- consult('.pi/deepclause/lib/specs.dml').
4
+
5
+ agent_main(Capability) :-
6
+ sp_query(Capability, Report),
7
+ answer(Report).
8
+
9
+ agent_main :-
10
+ answer("usage: /dc-run spec_query <capability> e.g. ui/theme").
@@ -0,0 +1,10 @@
1
+ % spec_scaffold — draft a tasks.dml for a change from its delta scenarios (read-only).
2
+ % Usage: /dc-run spec_scaffold <change>
3
+ :- consult('.pi/deepclause/lib/specs.dml').
4
+
5
+ agent_main(Change) :-
6
+ sp_scaffold(Change, Draft),
7
+ answer(Draft).
8
+
9
+ agent_main :-
10
+ answer("usage: /dc-run spec_scaffold <change>").
@@ -0,0 +1,7 @@
1
+ % spec_status — deterministic spec/delta inventory.
2
+ % Usage: /dc-run spec_status
3
+ :- consult('.pi/deepclause/lib/specs.dml').
4
+
5
+ agent_main :-
6
+ sp_status(Report),
7
+ answer(Report).
@@ -0,0 +1,9 @@
1
+ % spec_validate — deterministic spec/delta validation.
2
+ % Usage: /dc-run spec_validate
3
+ % Pure DML: no model calls, no runtime tools. Reports structural errors with
4
+ % line numbers (3-hash scenarios, missing scenarios, duplicates).
5
+ :- consult('.pi/deepclause/lib/specs.dml').
6
+
7
+ agent_main :-
8
+ sp_check_all(Report),
9
+ answer(Report).