@try-works/dsh-recursive-mode 0.4.3 → 0.4.4
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 +20 -15
- package/lib/client/index.d.ts +3 -1
- package/lib/client/settings-view.d.ts +97 -0
- package/lib/client/settings.d.ts +25 -1
- package/lib/client.js +530 -11
- package/lib/enforcement.d.ts +37 -0
- package/lib/index.js +123 -18
- package/lib/memory-feedback.d.ts +25 -2
- package/package.json +1 -1
- package/src/client/index.ts +3 -1
- package/src/client/settings-view.ts +211 -0
- package/src/client/settings.tsx +194 -8
- package/src/client/slots.ts +15 -2
- package/src/client/styles.ts +237 -0
- package/src/enforcement.ts +74 -3
- package/src/index.ts +47 -10
- package/src/memory-feedback.ts +50 -5
- package/src/phase-rules.ts +10 -1
- package/src/policy-globs.ts +25 -3
- package/src/policy-write.ts +1 -1
- package/src/runtime.ts +29 -0
package/README.md
CHANGED
|
@@ -7,10 +7,10 @@ plans, plans before code, independent review before a lock, tests before a claim
|
|
|
7
7
|
that says who locked it and on what evidence. The discipline lives in the harness, not in a prompt, so it cannot
|
|
8
8
|
be skipped by an agent that is in a hurry.
|
|
9
9
|
|
|
10
|
-
- **
|
|
10
|
+
- **13 tools** on the agent surface, one slash command, a workspace control plane, and a memory plane that
|
|
11
11
|
learns from what actually got used.
|
|
12
12
|
- **Zero runtime dependencies** beyond the harness itself — everything is a structural seam.
|
|
13
|
-
- **
|
|
13
|
+
- **A full test suite**, three parity specs against the reference implementation, a live-session
|
|
14
14
|
harness, and a fresh-clone check that runs unattended.
|
|
15
15
|
|
|
16
16
|
> **Honest status, up front.** One capability in this repository is **implemented but NOT VERIFIED live**: the
|
|
@@ -117,7 +117,7 @@ flowchart TB
|
|
|
117
117
|
LOCK["lock.ts<br/>monotonic locks + receipts"]
|
|
118
118
|
DELEG["delegation.ts + router.ts<br/>who reviews"]
|
|
119
119
|
MEM["memory*.ts<br/>what gets injected"]
|
|
120
|
-
TOOLSET["
|
|
120
|
+
TOOLSET["13 recursive_* tools"]
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
subgraph disk["Workspace control plane (.recursive/)"]
|
|
@@ -164,7 +164,7 @@ control plane on disk is the only state it trusts across restarts.
|
|
|
164
164
|
|
|
165
165
|
## 4. Capabilities
|
|
166
166
|
|
|
167
|
-
### 4.1 The
|
|
167
|
+
### 4.1 The thirteen tools
|
|
168
168
|
|
|
169
169
|
| Tool | What it does |
|
|
170
170
|
|---|---|
|
|
@@ -173,11 +173,12 @@ control plane on disk is the only state it trusts across restarts.
|
|
|
173
173
|
| `recursive_lock` | Lock a phase — refuses if the artifact does not meet the standard |
|
|
174
174
|
| `recursive_lint` | Lint a run or an artifact against the phase standard, with remediation text |
|
|
175
175
|
| `recursive_closeout` | Report what a phase artifact is missing; **writes a receipt, never the artifact** |
|
|
176
|
-
| `recursive_phase` | Read the phase graph: what is required, what is next, what is blocked |
|
|
177
|
-
| `recursive_worktree` | Create/promote a git worktree for a run, so work is isolated |
|
|
178
176
|
| `recursive_scratch` | The child's scratch space: durable, per-run working notes |
|
|
179
|
-
| `
|
|
177
|
+
| `recursive_worktree` | Create/promote a git worktree for a run, so work is isolated |
|
|
178
|
+
| `recursive_phase` | Read the phase graph: what is required, what is next, what is blocked |
|
|
180
179
|
| `recursive_audit_team` | Fan a phase out across roles (audit) |
|
|
180
|
+
| `recursive_review` | **Independent review** of the phase artifact, with a repair path |
|
|
181
|
+
| `recursive_delegate` | **Delegate the work of a phase** to a durable child; it produces, you judge |
|
|
181
182
|
| `recursive_ask` | Ask the workspace a question, with the control plane as context |
|
|
182
183
|
| `recursive_preview` | Preview what a tool would do, without doing it |
|
|
183
184
|
|
|
@@ -434,6 +435,10 @@ flowchart TB
|
|
|
434
435
|
P035 ==>|"the review phase"| REV
|
|
435
436
|
P08 -.->|"memory-auditor role"| REV
|
|
436
437
|
|
|
438
|
+
DELEG["recursive_delegate(phase)<br/>the phase's WORK, handed to a durable child<br/>the child produces · the main agent judges"]
|
|
439
|
+
P01 -.->|"work delegation, in any phase"| DELEG
|
|
440
|
+
P03 -.->|"the phase's actual work"| DELEG
|
|
441
|
+
|
|
437
442
|
TEAM["recursive_audit_team<br/>one phase per ROLE, one item per reviewer"]
|
|
438
443
|
P01 -.-> TEAM
|
|
439
444
|
P02 -.-> TEAM
|
|
@@ -500,7 +505,7 @@ progress reads as a per-phase per-role review rather than an undifferentiated pi
|
|
|
500
505
|
> phase's skill carries `audited: yes — this phase needs a delegated audit` or `audited: no`
|
|
501
506
|
> (`skills-phase.ts`), so the expectation is stated per phase rather than implied.
|
|
502
507
|
>
|
|
503
|
-
> **
|
|
508
|
+
> **Three delegation paths exist, and they are different animals.** `recursive_delegate` hands the *work itself* to a durable child and the main agent judges what comes back; `recursive_review` asks for an *independent judgement* and carries a repair leg; `recursive_audit_team` fans a phase out across roles. The first produces, the second judges, the third multiplies. The workflow's own — `recursive_review`,
|
|
504
509
|
> `recursive_audit_team` — is phase-aware and writes evidence **into the run** (brief, reply, action record,
|
|
505
510
|
> settlement), which is what makes the per-phase contract checkable. The harness's generic subagent and team
|
|
506
511
|
> tools are always available and know nothing about phases; work delegated through those leaves no run-scoped
|
|
@@ -717,7 +722,7 @@ The plugin ships **two halves that mount in different planes**, and knowing whic
|
|
|
717
722
|
| Half | Artifact | Plane | What it carries |
|
|
718
723
|
|---|---|---|---|
|
|
719
724
|
| **Bundle** | `cordis.patch.yml` (declared as `dsh.bundle.patch`) | **profile** | one enabled row so the host's ClientModuleRegistry can discover the UI half |
|
|
720
|
-
| **Agent preset** | `preset/recursive/agent.cordis.yml` + `preset/recursive/preset.yml` | **agent plane** | **the entire server surface**: the `RecursiveRuntime` service, the
|
|
725
|
+
| **Agent preset** | `preset/recursive/agent.cordis.yml` + `preset/recursive/preset.yml` | **agent plane** | **the entire server surface**: the `RecursiveRuntime` service, the thirteen tools, `/recursive`, the policy prompt section |
|
|
721
726
|
|
|
722
727
|
```mermaid
|
|
723
728
|
flowchart TB
|
|
@@ -739,7 +744,7 @@ flowchart TB
|
|
|
739
744
|
SEL["session selects the recursive preset<br/>registered by the bundle row"]
|
|
740
745
|
STD["the standard coding agent surface<br/>+ tool-presentation mode: both"]
|
|
741
746
|
REALM["group recursive-realm<br/>isolate: true"]
|
|
742
|
-
SURF["RecursiveRuntime +
|
|
747
|
+
SURF["RecursiveRuntime + 13 tools<br/>+ /recursive + recursive:policy"]
|
|
743
748
|
SEL --> STD
|
|
744
749
|
SEL --> REALM --> SURF
|
|
745
750
|
end
|
|
@@ -887,11 +892,11 @@ surface arrives when a session opts into the `recursive` preset. See
|
|
|
887
892
|
|
|
888
893
|
| Claim | How it is established |
|
|
889
894
|
|---|---|
|
|
890
|
-
| The workflow's own rules hold | `pnpm test` —
|
|
895
|
+
| The workflow's own rules hold | `pnpm test` — the whole suite, including three **parity specs** (lock, status, lint) against the reference implementation |
|
|
891
896
|
| The tree is sound | `pnpm typecheck` (0 errors), `pnpm build` (0) |
|
|
892
|
-
| The docs match the code | `docs-contract.spec.ts` —
|
|
897
|
+
| The docs match the code | `docs-contract.spec.ts` — asserts that documented paths, tools, sections and the README tool table all match the code |
|
|
893
898
|
| The plugin runs under a real harness | `pnpm e2e` — the FU-1 harness, **10/10**, including the behaviour test where removing one shard changes what the agent is told |
|
|
894
|
-
| A fresh checkout works unattended | `git clone` → `pnpm install` → **
|
|
899
|
+
| A fresh checkout works unattended | `git clone` → `pnpm install` → **the suite passes**, verified in the closing sweep |
|
|
895
900
|
| The plugin runs in a real CLI session | `scripts/live-session-plugin.mjs` — headless CLI, temp HOME, scripted LLM; the run reaches the review tool and the control plane is written |
|
|
896
901
|
| Memory actually reaches the model | the P5 harness test, end to end |
|
|
897
902
|
| The guard refuses locked writes | enforcement specs plus the live probe that found the overwrite defect |
|
|
@@ -927,7 +932,7 @@ unverified.**
|
|
|
927
932
|
| The guard | `enforcement.ts`, `policy-globs.ts`, `fs-intent.ts`, `guard-log.ts`, `errors.ts` |
|
|
928
933
|
| Delegation & review | `delegation.ts`, `router.ts`, `role-route.ts`, `review.ts`, `review-round.ts`, `settlement.ts`, `handoff.ts`, `teams-loop.ts`, `live-route.ts` |
|
|
929
934
|
| Memory | `memory.ts`, `memory-select.ts`, `memory-feedback.ts` |
|
|
930
|
-
| Surfaces | `recursive_*.tool.ts` (
|
|
935
|
+
| Surfaces | `recursive_*.tool.ts` (13), `commands.ts`, `hooks.ts`, `status.ts` |
|
|
931
936
|
| Infrastructure | `jobs-runner.ts`, `job-log.ts`, `goals-projection.ts`, `lifecycle.ts`, `bootstrap.ts`, `init-templates.ts`, `worktree.ts`, `git-context.ts`, `scratch.ts`, `skills.ts`, `skills-phase.ts`, `training.ts`, `workflow-audit.ts`, `plan-gate.ts`, `result-cap.ts`, `json-safe.ts`, `identity.ts`, `policy.ts` |
|
|
932
937
|
|
|
933
938
|
**Documents:**
|
|
@@ -954,6 +959,6 @@ pointer files a workspace gets), `scripts/` (harness, e2e, live-session, install
|
|
|
954
959
|
sections, monotonic locks with receipts, a guard that refuses writes to locked artifacts, an independent review
|
|
955
960
|
routed to whichever provider is actually available, a memory plane whose scoring is printed on demand, and a
|
|
956
961
|
closeout that lints the whole run and writes a receipt instead of touching the agent's work. It is verified by
|
|
957
|
-
|
|
962
|
+
a full test suite, three parity specs against the reference implementation, a live-session harness, and an unattended
|
|
958
963
|
fresh-clone check — and it reports the one capability it has not managed to prove live, with the host-side
|
|
959
964
|
reason, rather than describing it as working.
|
package/lib/client/index.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export { Board, listRuns } from './board.tsx';
|
|
|
16
16
|
export { Inspector } from './inspector.tsx';
|
|
17
17
|
export { DocViewer, parseDoc, parseInline } from './doc-viewer.tsx';
|
|
18
18
|
export { RecursiveView } from './slots.ts';
|
|
19
|
-
export { RecursiveSettings } from './settings.tsx';
|
|
19
|
+
export { RecursiveSettings, RecursiveSettingsLive } from './settings.tsx';
|
|
20
|
+
export { settingsView } from './settings-view.ts';
|
|
21
|
+
export type { SettingsView, SettingsRunView, SettingsRow } from './settings-view.ts';
|
|
20
22
|
export { useLiveProjection } from './use-live.ts';
|
|
21
23
|
export type { LiveProjectionSnapshot } from './use-live.ts';
|
|
22
24
|
export { fetchLiveState, subscribeLiveEvents } from './host-api.ts';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure display model for the Recursive settings panel (read-only report).
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS MODULE EXISTS. The old panel rendered one sentence and no values — it
|
|
5
|
+
* claimed to report the host's configuration while reporting nothing. The data
|
|
6
|
+
* was already arriving: `host-api.ts` serves {root, projection, revision} on
|
|
7
|
+
* every fs change plus a 15s heartbeat, and `derive.ts` already derives board
|
|
8
|
+
* facts from the same projection. This module adds no data path; it is the same
|
|
9
|
+
* projection read for a different surface, and it is PURE (no React, no fs, no
|
|
10
|
+
* session window — the read-only-client + projection-over-files invariant, §11.9).
|
|
11
|
+
*
|
|
12
|
+
* THE ABSENCE RULE (`text()` + `SettingsRow.value === null`): a value is printed
|
|
13
|
+
* only when the projection carried it. A missing/blank value renders as
|
|
14
|
+
* `not reported` — never an empty row and never a plausible default. Note that
|
|
15
|
+
* `derive.cardFacts()` deliberately defaults a subagent's absent `status` to
|
|
16
|
+
* 'done'; this model does NOT, because a report that invents "done" is a report
|
|
17
|
+
* that lies. Required collection fields (`tampers`, `subagents`) that are present
|
|
18
|
+
* but empty print `none` — that IS their value; fields the card omits print
|
|
19
|
+
* `not reported`.
|
|
20
|
+
*/
|
|
21
|
+
import type { PhasePosition, RecursiveRunCard } from '../types.ts';
|
|
22
|
+
import type { LiveProjectionValue } from './contract.ts';
|
|
23
|
+
import { type PillKind } from './derive.ts';
|
|
24
|
+
/** A label/value line. `value === null` means the projection carried NO value. */
|
|
25
|
+
export interface SettingsRow {
|
|
26
|
+
id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
value: string | null;
|
|
29
|
+
}
|
|
30
|
+
/** One folded phase row, with the T21 single derived position printed verbatim. */
|
|
31
|
+
export interface SettingsPhaseRow {
|
|
32
|
+
phase: string;
|
|
33
|
+
/** The phase doc filename, or null for a slot this run does not have (03.5). */
|
|
34
|
+
fileName: string | null;
|
|
35
|
+
/** The raw folded status ('LOCKED' / 'DRAFT' / '—'). */
|
|
36
|
+
status: string;
|
|
37
|
+
/** T21 single derived position; null when the producer carried none. */
|
|
38
|
+
position: PhasePosition | null;
|
|
39
|
+
lockedAt: string | null;
|
|
40
|
+
lockHash: string | null;
|
|
41
|
+
present: boolean;
|
|
42
|
+
pill: PillKind;
|
|
43
|
+
}
|
|
44
|
+
/** One subagent record: role/provider exactly as the host fold recorded them. */
|
|
45
|
+
export interface SettingsSubagentRow {
|
|
46
|
+
childId: string;
|
|
47
|
+
role: string | null;
|
|
48
|
+
provider: string | null;
|
|
49
|
+
status: string | null;
|
|
50
|
+
}
|
|
51
|
+
/** One unresolved in-flight work item (T18). */
|
|
52
|
+
export interface SettingsPendingRow {
|
|
53
|
+
kind: string;
|
|
54
|
+
delegationId: string;
|
|
55
|
+
detail: string;
|
|
56
|
+
}
|
|
57
|
+
/** One run's report. */
|
|
58
|
+
export interface SettingsRunView {
|
|
59
|
+
runId: string;
|
|
60
|
+
worktreeRoot: string;
|
|
61
|
+
state: string;
|
|
62
|
+
pill: PillKind;
|
|
63
|
+
/** Scalar facts; a null value means the card did not carry that field. */
|
|
64
|
+
rows: SettingsRow[];
|
|
65
|
+
phases: SettingsPhaseRow[];
|
|
66
|
+
/** null when the card carried no `subagents` key; [] when it carried an empty one. */
|
|
67
|
+
subagents: SettingsSubagentRow[] | null;
|
|
68
|
+
/** null when the card carried no `pendingWork` key; [] when it carried an empty one. */
|
|
69
|
+
pendingWork: SettingsPendingRow[] | null;
|
|
70
|
+
}
|
|
71
|
+
/** Whether the route has answered, and whether it resolved a recursive root. */
|
|
72
|
+
export type SettingsStatus = 'no-frame' | 'no-root' | 'connected';
|
|
73
|
+
/** The whole panel model. */
|
|
74
|
+
export interface SettingsView {
|
|
75
|
+
status: SettingsStatus;
|
|
76
|
+
root: string | null;
|
|
77
|
+
revision: number | null;
|
|
78
|
+
runs: SettingsRunView[];
|
|
79
|
+
/**
|
|
80
|
+
* Route-level facts the payload does not carry AT ALL — the panel names them
|
|
81
|
+
* so a reader is never left to assume the client is hiding them.
|
|
82
|
+
*/
|
|
83
|
+
notCarried: SettingsRow[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The `/state` payload is {root, projection, revision} (host-api.ts /
|
|
87
|
+
* live-route.ts, both typed by LiveProjectionValue). Nothing else rides it, so
|
|
88
|
+
* anything else is reported as not carried rather than guessed.
|
|
89
|
+
*/
|
|
90
|
+
export declare const NOT_CARRIED: readonly SettingsRow[];
|
|
91
|
+
/** One run card -> its report. */
|
|
92
|
+
export declare function settingsRunView(card: RecursiveRunCard): SettingsRunView;
|
|
93
|
+
/**
|
|
94
|
+
* The panel model for one live snapshot (null = the route has not answered yet).
|
|
95
|
+
* Every run in the projection is reported; nothing is filtered or defaulted.
|
|
96
|
+
*/
|
|
97
|
+
export declare function settingsView(snapshot: LiveProjectionValue | null): SettingsView;
|
package/lib/client/settings.d.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
|
+
import { type LiveProjectionValue, type SessionListStateLike, type SnapshotSelectorHook, type WorkspaceListStateLike } from './contract.ts';
|
|
2
|
+
/** Rendered in place of every value the projection did not carry. */
|
|
3
|
+
export declare const ABSENT_LABEL = "not reported";
|
|
1
4
|
export interface RecursiveSettingsProps {
|
|
2
5
|
close: () => void;
|
|
6
|
+
/** The live host-route frame, or null while the first fetch is in flight. */
|
|
7
|
+
snapshot?: LiveProjectionValue | null;
|
|
3
8
|
}
|
|
4
|
-
|
|
9
|
+
/** The panel: a report of one live frame. Pure — snapshot in, tree out. */
|
|
10
|
+
export declare function RecursiveSettings({ close, snapshot }: RecursiveSettingsProps): import("react").DetailedReactHTMLElement<{
|
|
5
11
|
className: string;
|
|
6
12
|
}, HTMLElement>;
|
|
13
|
+
/** The settings seat props: the root standard kit plus the shell's close affordance. */
|
|
14
|
+
export interface RecursiveSettingsSeatProps {
|
|
15
|
+
close: () => void;
|
|
16
|
+
useSessions?: SnapshotSelectorHook<SessionListStateLike>;
|
|
17
|
+
useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike>;
|
|
18
|
+
}
|
|
19
|
+
export interface RecursiveSettingsLiveProps {
|
|
20
|
+
close: () => void;
|
|
21
|
+
useSessions: SnapshotSelectorHook<SessionListStateLike>;
|
|
22
|
+
useWorkspaces: SnapshotSelectorHook<WorkspaceListStateLike>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The live seat: same scope resolution as the board seats (sessionId PRIMARY,
|
|
26
|
+
* workspace path as the cwd hint, session cwd while workspaces hydrate). Hooks
|
|
27
|
+
* are called unconditionally — the seat only renders this component when both
|
|
28
|
+
* selector hooks are present (slots.ts), so the hook order is fixed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function RecursiveSettingsLive({ close, useSessions, useWorkspaces }: RecursiveSettingsLiveProps): import("react").FunctionComponentElement<RecursiveSettingsProps>;
|