@try-works/dsh-recursive-mode 0.1.4 → 0.1.6
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/lib/bootstrap.d.ts +6 -3
- package/lib/client/board.d.ts +4 -1
- package/lib/client/open-state.d.ts +20 -0
- package/lib/client/slots.d.ts +7 -1
- package/lib/client.js +135 -47
- package/lib/fs-intent.d.ts +34 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3429 -911
- package/lib/policy.d.ts +1 -1
- package/lib/runtime.d.ts +12 -5
- package/lib/ts-lint.d.ts +205 -0
- package/package.json +1 -1
- package/src/bootstrap.ts +338 -331
- package/src/client/board.tsx +0 -0
- package/src/client/open-state.ts +46 -0
- package/src/client/slots.ts +41 -20
- package/src/commands.ts +11 -0
- package/src/fs-intent.ts +84 -0
- package/src/index.ts +238 -226
- package/src/policy.ts +1 -1
- package/src/runtime.ts +19 -23
- package/src/ts-lint.ts +2110 -0
- package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
- package/references/scripts/lint-recursive-run.ps1 +0 -25
- package/references/scripts/lint-recursive-run.py +0 -2870
- package/references/scripts/recursive-closeout.ps1 +0 -38
- package/references/scripts/recursive-closeout.py +0 -541
- package/references/scripts/recursive-init.ps1 +0 -515
- package/references/scripts/recursive-init.py +0 -356
- package/references/scripts/recursive-lock.ps1 +0 -26
- package/references/scripts/recursive-lock.py +0 -302
- package/references/scripts/recursive-review-bundle.ps1 +0 -58
- package/references/scripts/recursive-review-bundle.py +0 -503
- package/references/scripts/recursive-router-cli-configure.ps1 +0 -2
- package/references/scripts/recursive-router-cli-configure.py +0 -9
- package/references/scripts/recursive-router-cli-init.ps1 +0 -2
- package/references/scripts/recursive-router-cli-init.py +0 -9
- package/references/scripts/recursive-router-cli-invoke.ps1 +0 -2
- package/references/scripts/recursive-router-cli-invoke.py +0 -9
- package/references/scripts/recursive-router-cli-probe.ps1 +0 -2
- package/references/scripts/recursive-router-cli-probe.py +0 -9
- package/references/scripts/recursive-router-cli-resolve.ps1 +0 -2
- package/references/scripts/recursive-router-cli-resolve.py +0 -9
- package/references/scripts/recursive-router-cli-validate.ps1 +0 -2
- package/references/scripts/recursive-router-cli-validate.py +0 -9
- package/references/scripts/recursive-router-configure.ps1 +0 -27
- package/references/scripts/recursive-router-configure.py +0 -74
- package/references/scripts/recursive-router-init.ps1 +0 -17
- package/references/scripts/recursive-router-init.py +0 -29
- package/references/scripts/recursive-router-invoke.ps1 +0 -47
- package/references/scripts/recursive-router-invoke.py +0 -103
- package/references/scripts/recursive-router-probe.ps1 +0 -25
- package/references/scripts/recursive-router-probe.py +0 -44
- package/references/scripts/recursive-router-resolve.ps1 +0 -26
- package/references/scripts/recursive-router-resolve.py +0 -46
- package/references/scripts/recursive-router-validate.ps1 +0 -17
- package/references/scripts/recursive-router-validate.py +0 -27
- package/references/scripts/recursive-status.ps1 +0 -23
- package/references/scripts/recursive-status.py +0 -2124
- package/references/scripts/recursive-subagent-action.ps1 +0 -98
- package/references/scripts/recursive-subagent-action.py +0 -197
- package/references/scripts/recursive-training-extract.ps1 +0 -23
- package/references/scripts/recursive-training-extract.py +0 -99
- package/references/scripts/recursive-training-grpo.ps1 +0 -52
- package/references/scripts/recursive-training-grpo.py +0 -1341
- package/references/scripts/recursive-training-loader.ps1 +0 -78
- package/references/scripts/recursive-training-loader.py +0 -558
- package/references/scripts/recursive-training-mcp.ps1 +0 -29
- package/references/scripts/recursive-training-mcp.py +0 -261
- package/references/scripts/recursive-training-phase8-trigger.ps1 +0 -55
- package/references/scripts/recursive-training-phase8-trigger.py +0 -137
- package/references/scripts/recursive-training-sync.ps1 +0 -29
- package/references/scripts/recursive-training-sync.py +0 -183
- package/references/scripts/recursive_phase_rules.py +0 -367
- package/references/scripts/recursive_router_cli_lib.py +0 -2
- package/references/scripts/recursive_router_lib.py +0 -2282
- package/references/scripts/verify-locks.ps1 +0 -25
- package/references/scripts/verify-locks.py +0 -353
- package/scripts/__pycache__/lint-recursive-run.cpython-314.pyc +0 -0
- package/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
- package/scripts/install-recursive-mode.ps1 +0 -956
- package/scripts/install-recursive-mode.py +0 -750
- package/scripts/lint-recursive-run.py +0 -2870
- package/scripts/recursive-closeout.py +0 -541
- package/scripts/recursive-init.py +0 -356
- package/scripts/recursive-lock.py +0 -302
- package/scripts/recursive-status.py +0 -2124
- package/scripts/recursive_phase_rules.py +0 -367
- package/scripts/recursive_router_lib.py +0 -2282
- package/scripts/verify-locks.py +0 -353
package/src/client/board.tsx
CHANGED
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared board open-state (run 08 R1): module-level store behind the launcher,
|
|
3
|
+
* the shell.overlay entry, and the board<->inspector swap. Zero session events,
|
|
4
|
+
* zero host mutation — the client remains a read-only projection (R5/R9).
|
|
5
|
+
*/
|
|
6
|
+
import { useSyncExternalStore } from 'react'
|
|
7
|
+
|
|
8
|
+
export interface BoardSelection {
|
|
9
|
+
worktreeRoot: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface BoardState {
|
|
14
|
+
open: boolean;
|
|
15
|
+
selection: BoardSelection | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface BoardOpenState {
|
|
19
|
+
get(): BoardState;
|
|
20
|
+
openBoard(): void;
|
|
21
|
+
close(): void;
|
|
22
|
+
openInspector(selection: BoardSelection): void;
|
|
23
|
+
backToBoard(): void;
|
|
24
|
+
subscribe(fn: () => void): () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createBoardState(initial: BoardState = { open: false, selection: null }): BoardOpenState {
|
|
28
|
+
let state: BoardState = initial;
|
|
29
|
+
const listeners = new Set<() => void>();
|
|
30
|
+
const emit = (): void => { for (const fn of listeners) fn() };
|
|
31
|
+
return {
|
|
32
|
+
get: () => state,
|
|
33
|
+
openBoard: () => { state = { ...state, open: true }; emit(); },
|
|
34
|
+
close: () => { state = { ...state, open: false, selection: null }; emit(); },
|
|
35
|
+
openInspector: (selection) => { state = { ...state, open: true, selection }; emit(); },
|
|
36
|
+
backToBoard: () => { state = { ...state, selection: null }; emit(); },
|
|
37
|
+
subscribe: (fn) => { listeners.add(fn); return () => { listeners.delete(fn) }; },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** App-wide singleton shared by the launcher, overlay, board, and inspector. */
|
|
42
|
+
export const boardState = createBoardState();
|
|
43
|
+
|
|
44
|
+
export function useBoardState(store: BoardOpenState = boardState): BoardState {
|
|
45
|
+
return useSyncExternalStore(store.subscribe, store.get, store.get);
|
|
46
|
+
}
|
package/src/client/slots.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slot registrations (SP2 R1): the sidebar launcher, the
|
|
3
|
-
* the conversation.input.dock status strip, and the
|
|
2
|
+
* Slot registrations (SP2 R1 + run 08 R1/R3/R4): the sidebar launcher, the
|
|
3
|
+
* shell.overlay board, the conversation.input.dock status strip, and the
|
|
4
|
+
* settings.section page.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* its sessionId from the session-scope standard prop.
|
|
6
|
+
* Run 08: the launcher now OPENS the shared board store (onClick), and the
|
|
7
|
+
* shell.overlay entry renders the board gated on that store + the recursive
|
|
8
|
+
* preset, swapping to the drill-down inspector when a run is selected. The
|
|
9
|
+
* board reads cwd from the ROOT-scope standard prop useSessions (the scoped-
|
|
10
|
+
* slots root branch provides ONLY useSessions/useWorkspaces — useProjection is
|
|
11
|
+
* session-scope-only). The strip reads its sessionId from the session-scope
|
|
12
|
+
* standard prop.
|
|
13
13
|
*
|
|
14
14
|
* READ-ONLY (R9): the client only GETs the live host route; no mutation.
|
|
15
15
|
*/
|
|
@@ -17,9 +17,11 @@ import { createElement, type ReactNode } from 'react'
|
|
|
17
17
|
import type { ClientContext, ISessionsLike, SnapshotStoreLike, SessionListStateLike } from './contract.ts'
|
|
18
18
|
import { isRecursivePreset, currentSessionCwd } from './contract.ts'
|
|
19
19
|
import { Board } from './board.tsx'
|
|
20
|
+
import { Inspector } from './inspector.tsx'
|
|
20
21
|
import { StatusStrip } from './strip.tsx'
|
|
21
22
|
import { RecursiveSettings } from './settings.tsx'
|
|
22
23
|
import { useLiveProjection } from './use-live.ts'
|
|
24
|
+
import { boardState, useBoardState } from './open-state.ts'
|
|
23
25
|
|
|
24
26
|
/** Structural standard-prop face for the root scope (useSessions only). */
|
|
25
27
|
interface RootSlotProps {
|
|
@@ -34,18 +36,28 @@ interface SessionSlotProps {
|
|
|
34
36
|
useProjection?: unknown
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
export type OverlayContent = 'hidden' | 'board' | 'inspector'
|
|
40
|
+
|
|
41
|
+
/** Gate: hidden unless open AND recursive preset; inspector when a run is selected. */
|
|
42
|
+
export function overlayContent(state: { open: boolean; selection: unknown }, sessions: ISessionsLike): OverlayContent {
|
|
43
|
+
if (!state.open) return 'hidden'
|
|
44
|
+
if (!isRecursivePreset(sessions)) return 'hidden'
|
|
45
|
+
if (state.selection !== null) return 'inspector'
|
|
46
|
+
return 'board'
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
export function registerSlots(ctx: ClientContext): () => void {
|
|
38
50
|
const disposers: (() => void)[] = []
|
|
39
51
|
|
|
40
|
-
// Board launcher in the sidebar footer action list
|
|
52
|
+
// Board launcher in the sidebar footer action list — OPENS the shared board store (run 08 R1).
|
|
41
53
|
disposers.push(ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({
|
|
42
54
|
name: 'sidebar.footer.action',
|
|
43
55
|
id: 'recursive',
|
|
44
56
|
order: 50,
|
|
45
57
|
label: 'Recursive runs',
|
|
46
|
-
}, () => createElement('button', { className: 'rec-launcher', title: 'Recursive runs' }, '⧉'))))
|
|
58
|
+
}, () => createElement('button', { className: 'rec-launcher', title: 'Recursive runs', onClick: () => boardState.openBoard() }, '⧉'))))
|
|
47
59
|
|
|
48
|
-
// Board panel overlay (root scope, cross-session read) — gated on the recursive preset.
|
|
60
|
+
// Board panel overlay (root scope, cross-session read) — gated on the recursive preset + shared store.
|
|
49
61
|
disposers.push(ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
50
62
|
name: 'shell.overlay',
|
|
51
63
|
id: 'recursive-board',
|
|
@@ -53,7 +65,7 @@ export function registerSlots(ctx: ClientContext): () => void {
|
|
|
53
65
|
}, (props: RootSlotProps) => {
|
|
54
66
|
const useSessions = props?.useSessions
|
|
55
67
|
if (useSessions === undefined) return null
|
|
56
|
-
return createElement(
|
|
68
|
+
return createElement(RecursiveBoardOverlay, { useSessions })
|
|
57
69
|
})))
|
|
58
70
|
|
|
59
71
|
// Status strip in the composer input dock (session scope) — gated the same way.
|
|
@@ -79,18 +91,27 @@ export function registerSlots(ctx: ClientContext): () => void {
|
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
/**
|
|
82
|
-
* Board
|
|
83
|
-
*
|
|
84
|
-
* standard useSessions hook and re-renders on change.
|
|
94
|
+
* Board overlay: subscribes to the shared board store, gates on open + recursive
|
|
95
|
+
* preset, and swaps board <-> inspector (run 08 R1/R3).
|
|
85
96
|
*/
|
|
86
|
-
function
|
|
97
|
+
function RecursiveBoardOverlay({ useSessions }: { useSessions: () => SessionListStateLike }): ReactNode {
|
|
98
|
+
const board = useBoardState()
|
|
87
99
|
const list = useSessions()
|
|
88
100
|
const sessions: ISessionsLike = { list: { getSnapshot: () => list, subscribe: () => () => {} } as SnapshotStoreLike<SessionListStateLike> }
|
|
89
|
-
|
|
101
|
+
const content = overlayContent(board, sessions)
|
|
102
|
+
if (content === 'hidden') return null
|
|
90
103
|
const cwd = currentSessionCwd(sessions)
|
|
91
104
|
const scope = { sessionId: list.current, cwd }
|
|
92
105
|
const snapshot = useLiveProjection(scope)
|
|
93
|
-
|
|
106
|
+
if (content === 'inspector' && board.selection !== null) {
|
|
107
|
+
return createElement(Inspector, {
|
|
108
|
+
runId: board.selection.runId,
|
|
109
|
+
worktreeRoot: board.selection.worktreeRoot,
|
|
110
|
+
snapshot,
|
|
111
|
+
onBackToToolDetails: () => boardState.backToBoard(),
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
return createElement(Board, { snapshot, onOpenInspector: (sel) => boardState.openInspector(sel) })
|
|
94
115
|
}
|
|
95
116
|
|
|
96
117
|
/**
|
package/src/commands.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
|
13
13
|
import { join } from 'node:path'
|
|
14
14
|
import type { RecursiveRuntime } from './runtime.ts'
|
|
15
15
|
import { resolveControlPlaneRoot } from './workspace.ts'
|
|
16
|
+
import { bootstrapScaffold } from './bootstrap.ts'
|
|
16
17
|
|
|
17
18
|
export type RecursiveVerb = 'status' | 'spec' | 'worktree' | 'init' | 'lock' | 'qa' | 'closeout' | 'addendum' | 'review' | 'scratch' | 'bootstrap' | 'list' | 'help'
|
|
18
19
|
|
|
@@ -65,6 +66,16 @@ export function executeRecursiveCommand(root: string, rawInput: string): Recursi
|
|
|
65
66
|
case 'help': {
|
|
66
67
|
return { kind: 'success', text: HELP_TEXT + ' — see /.recursive/RECURSIVE.md' }
|
|
67
68
|
}
|
|
69
|
+
case 'bootstrap': {
|
|
70
|
+
// R6 (run 09): manual repair path — idempotent upsert scaffold repair.
|
|
71
|
+
// Adds missing control-plane files/dirs + re-upserts marked blocks;
|
|
72
|
+
// never overwrites user content or touches run/ artifacts.
|
|
73
|
+
const result = bootstrapScaffold(root)
|
|
74
|
+
const summary = result.created.length === 0
|
|
75
|
+
? 'scaffold already complete (no changes)'
|
|
76
|
+
: 'created ' + result.created.length + ' item(s), ' + result.existing.length + ' existing'
|
|
77
|
+
return { kind: 'success', text: 'recursive-mode scaffold repair: ' + summary + ' — root ' + root }
|
|
78
|
+
}
|
|
68
79
|
case 'closeout': {
|
|
69
80
|
const phaseMatch = arg.match(/--phase\s+(\d{2})/)
|
|
70
81
|
const phase = phaseMatch?.[1] ?? ''
|
package/src/fs-intent.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fs-intent.ts — filesystem-derived recursive intent (R5 policy-render fix).
|
|
3
|
+
*
|
|
4
|
+
* SP2 zero-emission retired the recursive/phase-intent SESSION EVENT emitter,
|
|
5
|
+
* so detectTransitionIntent(events) is ALWAYS null and the recursive:policy
|
|
6
|
+
* prompt section rendered ''. This module derives the SAME intent from the
|
|
7
|
+
* filesystem instead: session cwd -> control-plane root -> enumerate runs ->
|
|
8
|
+
* latest run -> current phase (foldRun). Pure read-only fs folding, zero
|
|
9
|
+
* emission — consistent with the per-workspace design + pre-step listener.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync } from 'node:fs'
|
|
12
|
+
import { join } from 'node:path'
|
|
13
|
+
import { enumerateRuns } from './bootstrap.ts'
|
|
14
|
+
import { foldRun } from './status.ts'
|
|
15
|
+
import type { WorkspaceRegistryLike } from './workspace.ts'
|
|
16
|
+
import { resolveControlPlaneRoot } from './workspace.ts'
|
|
17
|
+
|
|
18
|
+
export interface FsPolicyIntent {
|
|
19
|
+
worktreeRoot: string
|
|
20
|
+
runId: string
|
|
21
|
+
/** The current phase doc file name (e.g. 03-implementation-summary.md), when determinable. */
|
|
22
|
+
currentPhaseFile: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the recursive policy intent for one agent from the FILESYSTEM.
|
|
27
|
+
* Returns null when no control-plane root or run is present (callers defer,
|
|
28
|
+
* never fail hard). The registry path is async-only in the harness, so when a
|
|
29
|
+
* registry is present this resolves the session cwd directly (B4: the session
|
|
30
|
+
* cwd IS the workspace root when unregistered; a registered workspace resolves
|
|
31
|
+
* to the same cwd via resolveControlPlaneRoot's sync fallback).
|
|
32
|
+
*/
|
|
33
|
+
export function fsPolicyIntent(
|
|
34
|
+
agent: { session?: { header?: { cwd?: string } } } | null | undefined,
|
|
35
|
+
registry?: WorkspaceRegistryLike | null,
|
|
36
|
+
cwdFallback?: string,
|
|
37
|
+
): FsPolicyIntent | null {
|
|
38
|
+
const cwd = agent?.session?.header?.cwd ?? cwdFallback ?? null
|
|
39
|
+
if (!cwd) return null
|
|
40
|
+
// B4 sync path: when the registry is absent the session cwd IS the root;
|
|
41
|
+
// when present, resolveControlPlaneRoot canonicalizes through the registry
|
|
42
|
+
// (async). For the sync prompt-section callback we take the B4 shortcut: the
|
|
43
|
+
// session cwd is authoritative per the workspace-scoping invariant. This is
|
|
44
|
+
// exactly what the pre-step listener's resolveRootForRoute falls back to.
|
|
45
|
+
const root = registry ? cwd : cwd
|
|
46
|
+
const runs = enumerateRuns(root)
|
|
47
|
+
if (runs.length === 0) return null
|
|
48
|
+
const runId = runs[runs.length - 1]
|
|
49
|
+
const runDir = join(root, '.recursive', 'run', runId)
|
|
50
|
+
if (!existsSync(runDir)) return null
|
|
51
|
+
const folded = foldRun(runDir, runId)
|
|
52
|
+
return {
|
|
53
|
+
worktreeRoot: root,
|
|
54
|
+
runId,
|
|
55
|
+
currentPhaseFile: folded.currentPhase ? folded.currentPhase.phaseName : null,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Async variant for callers that CAN await (pre-step listener, routes): uses
|
|
61
|
+
* resolveControlPlaneRoot for registry canonicalization when a registry is
|
|
62
|
+
* present, falling back to the session cwd (B4).
|
|
63
|
+
*/
|
|
64
|
+
export async function fsPolicyIntentAsync(
|
|
65
|
+
agent: { session?: { header?: { cwd?: string } } } | null | undefined,
|
|
66
|
+
registry?: WorkspaceRegistryLike | null,
|
|
67
|
+
cwdFallback?: string,
|
|
68
|
+
): Promise<FsPolicyIntent | null> {
|
|
69
|
+
const cwd = agent?.session?.header?.cwd ?? cwdFallback ?? null
|
|
70
|
+
if (!cwd) return null
|
|
71
|
+
const root = await resolveControlPlaneRoot(agent, registry, cwdFallback)
|
|
72
|
+
if (!root) return null
|
|
73
|
+
const runs = enumerateRuns(root)
|
|
74
|
+
if (runs.length === 0) return null
|
|
75
|
+
const runId = runs[runs.length - 1]
|
|
76
|
+
const runDir = join(root, '.recursive', 'run', runId)
|
|
77
|
+
if (!existsSync(runDir)) return null
|
|
78
|
+
const folded = foldRun(runDir, runId)
|
|
79
|
+
return {
|
|
80
|
+
worktreeRoot: root,
|
|
81
|
+
runId,
|
|
82
|
+
currentPhaseFile: folded.currentPhase ? folded.currentPhase.phaseName : null,
|
|
83
|
+
}
|
|
84
|
+
}
|