@try-works/dsh-recursive-mode 0.3.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/README.md +959 -0
- package/lib/client.js +9 -2
- package/lib/closeout-report.d.ts +113 -0
- package/lib/closeout-standards.d.ts +35 -0
- package/lib/closeout.d.ts +12 -0
- package/lib/commands.d.ts +1 -1
- package/lib/config.d.ts +202 -0
- package/lib/delegation.d.ts +123 -3
- package/lib/enforcement.d.ts +90 -1
- package/lib/errors.d.ts +168 -0
- package/lib/git-context.d.ts +17 -0
- package/lib/guard-log.d.ts +39 -0
- package/lib/handoff.d.ts +29 -0
- package/lib/hooks.d.ts +103 -0
- package/lib/identity.d.ts +61 -0
- package/lib/index.d.ts +33 -12
- package/lib/index.js +10017 -3969
- package/lib/job-log.d.ts +34 -0
- package/lib/jobs-runner.d.ts +105 -0
- package/lib/json-safe.d.ts +33 -0
- package/lib/lock.d.ts +42 -0
- package/lib/memory-feedback.d.ts +52 -0
- package/lib/memory-select.d.ts +78 -0
- package/lib/memory.d.ts +137 -0
- package/lib/model-inventory.d.ts +106 -0
- package/lib/phase-graph.d.ts +111 -0
- package/lib/phase-rules.d.ts +67 -8
- package/lib/plan-gate.d.ts +68 -0
- package/lib/policy-globs.d.ts +222 -0
- package/lib/policy-write.d.ts +42 -0
- package/lib/policy.d.ts +39 -0
- package/lib/recursive_ask.tool.d.ts +88 -0
- package/lib/recursive_closeout.tool.d.ts +1 -1
- package/lib/recursive_delegate.tool.d.ts +22 -0
- package/lib/recursive_preview.tool.d.ts +48 -0
- package/lib/recursive_review.tool.d.ts +28 -0
- package/lib/result-cap.d.ts +70 -0
- package/lib/review-round.d.ts +82 -0
- package/lib/review.d.ts +9 -0
- package/lib/role-route.d.ts +122 -0
- package/lib/router.d.ts +90 -5
- package/lib/runtime.d.ts +252 -12
- package/lib/settlement.d.ts +132 -0
- package/lib/skills-phase.d.ts +71 -0
- package/lib/skills.d.ts +70 -0
- package/lib/status.d.ts +53 -1
- package/lib/teams-loop.d.ts +91 -2
- package/lib/training.d.ts +211 -0
- package/lib/ts-lint.d.ts +15 -0
- package/lib/types.d.ts +48 -0
- package/lib/workflow-audit.d.ts +207 -0
- package/package.json +31 -31
- package/preset/recursive.patch.yml +312 -0
- package/scripts/e2e-run.mjs +51 -0
- package/scripts/link-dsh.mjs +233 -0
- package/scripts/live/fake-llm.mjs +150 -0
- package/scripts/live-session-plugin.mjs +179 -0
- package/scripts/live-session-stock.mjs +106 -0
- package/scripts/live-session.mjs +139 -0
- package/skills/recursive-mode/SKILL.md +66 -0
- package/src/client/derive.ts +18 -2
- package/src/closeout-report.ts +274 -0
- package/src/closeout-standards.ts +102 -0
- package/src/closeout.ts +39 -2
- package/src/commands.ts +116 -4
- package/src/config.ts +113 -0
- package/src/delegation.ts +336 -18
- package/src/enforcement.ts +262 -72
- package/src/errors.ts +197 -0
- package/src/git-context.ts +33 -2
- package/src/guard-log.ts +134 -0
- package/src/handoff.ts +62 -0
- package/src/hooks.ts +316 -0
- package/src/identity.ts +230 -0
- package/src/index.ts +394 -20
- package/src/job-log.ts +112 -0
- package/src/jobs-runner.ts +222 -0
- package/src/json-safe.ts +75 -0
- package/src/lock.ts +153 -16
- package/src/memory-feedback.ts +185 -0
- package/src/memory-select.ts +187 -0
- package/src/memory.ts +309 -0
- package/src/model-inventory.ts +196 -0
- package/src/phase-graph.ts +191 -0
- package/src/phase-rules.ts +236 -0
- package/src/plan-gate.ts +111 -0
- package/src/policy-globs.ts +636 -0
- package/src/policy-write.ts +210 -0
- package/src/policy.ts +70 -5
- package/src/recursive_ask.tool.ts +276 -0
- package/src/recursive_audit_team.tool.ts +7 -3
- package/src/recursive_closeout.tool.ts +36 -35
- package/src/recursive_delegate.tool.ts +194 -0
- package/src/recursive_init.tool.ts +4 -3
- package/src/recursive_lint.tool.ts +81 -6
- package/src/recursive_lock.tool.ts +21 -4
- package/src/recursive_phase.tool.ts +3 -2
- package/src/recursive_preview.tool.ts +142 -0
- package/src/recursive_review.tool.ts +190 -0
- package/src/recursive_scratch.tool.ts +5 -4
- package/src/recursive_status.tool.ts +3 -2
- package/src/recursive_worktree.tool.ts +6 -5
- package/src/result-cap.ts +130 -0
- package/src/review-round.ts +335 -0
- package/src/review.ts +17 -3
- package/src/role-route.ts +230 -0
- package/src/router.ts +128 -2
- package/src/runtime.ts +968 -39
- package/src/settlement.ts +355 -0
- package/src/skills-phase.ts +143 -0
- package/src/skills.ts +151 -0
- package/src/snapshot.ts +39 -8
- package/src/status.ts +209 -4
- package/src/teams-loop.ts +223 -9
- package/src/training.ts +565 -0
- package/src/ts-lint.ts +38 -4
- package/src/types.ts +51 -0
- package/src/workflow-audit.ts +288 -0
- package/scripts/install-preset.cmd +0 -7
- package/scripts/install-preset.js +0 -101
package/src/router.ts
CHANGED
|
@@ -11,6 +11,42 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { readFileSync, existsSync } from 'node:fs'
|
|
13
13
|
import { join } from 'node:path'
|
|
14
|
+
// T9: the per-role model lookup, so there is ONE definition of "which model does this role use".
|
|
15
|
+
// `role-route.ts` imports only the `RouterPolicy` TYPE from here, which erases at runtime — so
|
|
16
|
+
// this is a one-way runtime dependency, not the cycle that bit `policy-globs` earlier.
|
|
17
|
+
import { modelForRole } from './role-route.ts'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ⚠ FU-19 — WHAT A CHILD RUNS ON, WHEN THE USER SAYS SO AND NOTHING MORE SPECIFIC DOES.
|
|
21
|
+
*
|
|
22
|
+
* Optional on purpose, and the reason is the same one the override layer gives below: an absent field must mean
|
|
23
|
+
* "defer to the next level in the precedence", never "reset to nothing". A default here would make every field
|
|
24
|
+
* present and silently shadow the role routes and phase routes forever.
|
|
25
|
+
*/
|
|
26
|
+
export interface SubagentDefault {
|
|
27
|
+
/** The SUBAGENT provider — who creates the child (`spawn`, `fork`). NOT the LLM provider; see below. */
|
|
28
|
+
provider?: string | null
|
|
29
|
+
/** The model id to ask for. */
|
|
30
|
+
model?: string | null
|
|
31
|
+
/**
|
|
32
|
+
* ⚠ THE LLM PROVIDER — WHO SERVES THE MODEL (`deepseek-official`), which is a DIFFERENT thing from the
|
|
33
|
+
* `provider` field above. Conflating the two was a real defect in this feature's first schema: a plain
|
|
34
|
+
* `provider` could mean either, and a user setting it had no way to know which.
|
|
35
|
+
*
|
|
36
|
+
* Optional: when only a model is named, the plugin looks its provider up from the inventory DSH exposes.
|
|
37
|
+
*/
|
|
38
|
+
modelProvider?: string | null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Per-phase overrides — the narrowest level, and the one that answers "this phase needs a stronger model". */
|
|
42
|
+
export interface PhaseRoute {
|
|
43
|
+
role?: string
|
|
44
|
+
/** The subagent provider, as above. */
|
|
45
|
+
provider?: string | null
|
|
46
|
+
model?: string | null
|
|
47
|
+
/** The LLM provider serving `model`, as above. */
|
|
48
|
+
modelProvider?: string | null
|
|
49
|
+
}
|
|
14
50
|
|
|
15
51
|
export interface RouterDefaults {
|
|
16
52
|
when_role_unconfigured: string
|
|
@@ -19,6 +55,8 @@ export interface RouterDefaults {
|
|
|
19
55
|
allow_auto_assign_if_single_cli: boolean
|
|
20
56
|
probe_timeout_ms: number
|
|
21
57
|
invoke_timeout_ms: number
|
|
58
|
+
/** FU-19: the general provider/model for delegated subagents, absent when the user has not chosen one. */
|
|
59
|
+
subagent?: SubagentDefault
|
|
22
60
|
}
|
|
23
61
|
|
|
24
62
|
export interface RoleRoute {
|
|
@@ -26,6 +64,16 @@ export interface RoleRoute {
|
|
|
26
64
|
mode: string
|
|
27
65
|
cli: string | null
|
|
28
66
|
model: string | null
|
|
67
|
+
/**
|
|
68
|
+
* FU-19: the provider this role should be served by, when the user has chosen one. Absent means "let the tier
|
|
69
|
+
* ladder decide", which is what every policy scaffolded before this field did.
|
|
70
|
+
*
|
|
71
|
+
* This is the SUBAGENT provider — who creates the child. The LLM provider that serves the model is its own
|
|
72
|
+
* field below, because one name for two ideas is how a configuration becomes a guess.
|
|
73
|
+
*/
|
|
74
|
+
provider?: string | null
|
|
75
|
+
/** FU-19: the LLM provider serving this role's `model`. Optional; looked up from the inventory when omitted. */
|
|
76
|
+
modelProvider?: string | null
|
|
29
77
|
fallback: string
|
|
30
78
|
}
|
|
31
79
|
|
|
@@ -33,6 +81,8 @@ export interface RouterPolicy {
|
|
|
33
81
|
version: number
|
|
34
82
|
defaults: RouterDefaults
|
|
35
83
|
role_routes: Record<string, RoleRoute>
|
|
84
|
+
/** FU-19: phase-keyed overrides, e.g. `{ '08': { role: 'memory-auditor' } }`. Absent means none. */
|
|
85
|
+
phase_routes?: Record<string, PhaseRoute>
|
|
36
86
|
cli_overrides: Record<string, unknown>
|
|
37
87
|
custom_clis: unknown[]
|
|
38
88
|
}
|
|
@@ -43,6 +93,22 @@ export interface RouteDecision {
|
|
|
43
93
|
tier: RouteTier
|
|
44
94
|
provider?: string
|
|
45
95
|
reason: string
|
|
96
|
+
/**
|
|
97
|
+
* T9: the model the policy names for this role, or null when it names none.
|
|
98
|
+
*
|
|
99
|
+
* Present on EVERY decision because it is attached by the wrapper, never per-return-site.
|
|
100
|
+
*
|
|
101
|
+
* ⚠ THIS COMMENT USED TO SAY THE PLUGIN DOES NOT APPLY IT. That was true when it was written and is false now:
|
|
102
|
+
* `delegateReview` sets `request.agentOptions = { model }` when the provider declares the `agentOptions`
|
|
103
|
+
* capability, and reports a routing note when it does not. So this is still a value a caller can HONOUR, but
|
|
104
|
+
* the plugin now does the honouring — and says so when it cannot.
|
|
105
|
+
*/
|
|
106
|
+
model?: string | null
|
|
107
|
+
/**
|
|
108
|
+
* FU-19: where the provider and model came from, one short label per value, so "why did this child run on
|
|
109
|
+
* that model" is answerable without reading the resolution code.
|
|
110
|
+
*/
|
|
111
|
+
chosen?: { provider?: string; model?: string }
|
|
46
112
|
}
|
|
47
113
|
|
|
48
114
|
export interface SubagentProviderLike {
|
|
@@ -52,6 +118,12 @@ export interface SubagentProviderLike {
|
|
|
52
118
|
depthLimit?: boolean
|
|
53
119
|
toolFilter?: boolean
|
|
54
120
|
persona?: boolean
|
|
121
|
+
/**
|
|
122
|
+
* T9: whether this provider accepts `agentOptions` (provider/model/reasoning-effort
|
|
123
|
+
* overrides). The harness REJECTS a start that sends them to a provider without this
|
|
124
|
+
* capability, so the caller must ASK rather than assume — see `delegateReview`.
|
|
125
|
+
*/
|
|
126
|
+
agentOptions?: boolean
|
|
55
127
|
}
|
|
56
128
|
}
|
|
57
129
|
|
|
@@ -82,15 +154,51 @@ const DEFAULT_POLICY: RouterPolicy = {
|
|
|
82
154
|
custom_clis: [],
|
|
83
155
|
}
|
|
84
156
|
|
|
157
|
+
/**
|
|
158
|
+
* T7 — the settings OVERRIDE layer over the declarative file.
|
|
159
|
+
*
|
|
160
|
+
* ONE PATH, NOT TWO (the item's own interaction note): `recursive-router.json` remains the
|
|
161
|
+
* declarative source, and the settings namespace can OVERRIDE individual fields without
|
|
162
|
+
* restating the file. That is why every field here is optional and why the schema declares
|
|
163
|
+
* NO defaults for them: an absent field means "defer to the file", and a default would make
|
|
164
|
+
* every field present and silently shadow the file forever.
|
|
165
|
+
*/
|
|
166
|
+
export interface RouterPolicyOverrides {
|
|
167
|
+
defaults?: Partial<RouterPolicy['defaults']>
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Drop keys explicitly set to `undefined`, so "unset" cannot erase a file's value. */
|
|
171
|
+
function compact<T extends object>(value: T | undefined): Partial<T> {
|
|
172
|
+
if (value === undefined) return {}
|
|
173
|
+
const out: Record<string, unknown> = {}
|
|
174
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
175
|
+
if (entry !== undefined) out[key] = entry
|
|
176
|
+
}
|
|
177
|
+
return out as Partial<T>
|
|
178
|
+
}
|
|
179
|
+
|
|
85
180
|
/** Parse recursive-router.json. A missing/invalid file yields a default self-audit policy (never throws). */
|
|
86
|
-
export function loadRouterPolicy(path?: string): RouterPolicy {
|
|
181
|
+
export function loadRouterPolicy(path?: string, overrides?: RouterPolicyOverrides): RouterPolicy {
|
|
182
|
+
const base = readRouterPolicy(path)
|
|
183
|
+
const applied = compact(overrides?.defaults)
|
|
184
|
+
if (Object.keys(applied).length === 0) return base
|
|
185
|
+
return { ...base, defaults: { ...base.defaults, ...applied } }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The file (or the built-in default) — the declarative source the overrides sit on top of. */
|
|
189
|
+
function readRouterPolicy(path?: string): RouterPolicy {
|
|
87
190
|
if (!path || !existsSync(path)) return DEFAULT_POLICY
|
|
88
191
|
try {
|
|
89
192
|
const raw = JSON.parse(readFileSync(path, 'utf8')) as Partial<RouterPolicy>
|
|
90
193
|
return {
|
|
91
194
|
version: typeof raw.version === 'number' ? raw.version : 1,
|
|
195
|
+
// The spread carries `subagent` through when the file declares it and leaves it absent when it does not —
|
|
196
|
+
// which is exactly the tolerance FU-19 needs, with no extra code.
|
|
92
197
|
defaults: { ...DEFAULT_POLICY.defaults, ...(raw.defaults ?? {}) },
|
|
93
198
|
role_routes: raw.role_routes ?? {},
|
|
199
|
+
// Phase overrides are carried only when the file has them: an empty object would claim the user configured
|
|
200
|
+
// something, and absence is a fact worth keeping distinct from emptiness.
|
|
201
|
+
...(raw.phase_routes === undefined ? {} : { phase_routes: raw.phase_routes }),
|
|
94
202
|
cli_overrides: raw.cli_overrides ?? {},
|
|
95
203
|
custom_clis: raw.custom_clis ?? [],
|
|
96
204
|
}
|
|
@@ -104,13 +212,31 @@ export function routerPolicyPath(root: string): string {
|
|
|
104
212
|
return join(root, '.recursive', 'config', 'recursive-router.json')
|
|
105
213
|
}
|
|
106
214
|
|
|
215
|
+
/**
|
|
216
|
+
* T9 — attach the role's model to the decision.
|
|
217
|
+
*
|
|
218
|
+
* ⚠ WHY A WRAPPER AND NOT SIX EDITS. `resolveRoleInner` has six return sites, and adding the
|
|
219
|
+
* model to each would leave a decision shape that carries it on some paths and not others — a
|
|
220
|
+
* trap for the next reader, and exactly the kind of quiet inconsistency this plan keeps
|
|
221
|
+
* refusing to ship. Attaching it ONCE, at the seam where the decision leaves, makes the field
|
|
222
|
+
* present on EVERY path by construction. The policy lookup itself lives in `role-route.ts`
|
|
223
|
+
* (`routeForRole`), so there is one definition of "which model does this role use", not two.
|
|
224
|
+
*/
|
|
225
|
+
export function resolveRole(
|
|
226
|
+
role: string,
|
|
227
|
+
policy: RouterPolicy,
|
|
228
|
+
providers: Record<string, SubagentProviderLike>,
|
|
229
|
+
): RouteDecision {
|
|
230
|
+
return { ...resolveRoleInner(role, policy, providers), model: modelForRole(role, policy) }
|
|
231
|
+
}
|
|
232
|
+
|
|
107
233
|
/**
|
|
108
234
|
* Resolve a role to a tier, preferring a native provider whose name maps to
|
|
109
235
|
* the role (e.g. role 'code-reviewer' -> provider 'code-reviewer' or the
|
|
110
236
|
* generic spawn/fork provider). External CLIs ride their provider rows; else
|
|
111
237
|
* the policy fallback.
|
|
112
238
|
*/
|
|
113
|
-
|
|
239
|
+
function resolveRoleInner(
|
|
114
240
|
role: string,
|
|
115
241
|
policy: RouterPolicy,
|
|
116
242
|
providers: Record<string, SubagentProviderLike>,
|