@try-works/dsh-recursive-mode 0.4.2 → 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 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
- - **12 tools** on the agent surface, one slash command, a workspace control plane, and a memory plane that
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
- - **862 tests across 91 files**, three parity specs against the reference implementation, a live-session
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["12 recursive_* tools"]
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 twelve tools
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
- | `recursive_review` | **Independent review** of the phase artifact, with a repair path |
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
- > **Two delegation paths exist, and they are different animals.** The workflow's own — `recursive_review`,
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 twelve tools, `/recursive`, the policy prompt section |
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 + 12 tools<br/>+ /recursive + recursive:policy"]
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` — **862 tests, 91 files**, including three **parity specs** (lock, status, lint) against the reference implementation |
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` — 15 tests asserting documented paths, tools, and sections exist |
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` → **862/862**, verified in the closing sweep |
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` (12), `commands.ts`, `hooks.ts`, `status.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
- 862 tests, three parity specs against the reference implementation, a live-session harness, and an unattended
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.
@@ -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;
@@ -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
- export declare function RecursiveSettings({ close }: RecursiveSettingsProps): import("react").DetailedReactHTMLElement<{
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>;