@try-works/dsh-recursive-mode 0.1.5 → 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.
Files changed (85) hide show
  1. package/lib/bootstrap.d.ts +6 -3
  2. package/lib/client/board.d.ts +4 -1
  3. package/lib/client/open-state.d.ts +20 -0
  4. package/lib/client/slots.d.ts +7 -1
  5. package/lib/client.js +135 -47
  6. package/lib/index.js +3391 -906
  7. package/lib/runtime.d.ts +12 -5
  8. package/lib/ts-lint.d.ts +205 -0
  9. package/package.json +1 -1
  10. package/src/bootstrap.ts +338 -331
  11. package/src/client/board.tsx +0 -0
  12. package/src/client/open-state.ts +46 -0
  13. package/src/client/slots.ts +41 -20
  14. package/src/commands.ts +11 -0
  15. package/src/index.ts +238 -228
  16. package/src/runtime.ts +19 -23
  17. package/src/ts-lint.ts +2110 -0
  18. package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  19. package/references/scripts/lint-recursive-run.ps1 +0 -25
  20. package/references/scripts/lint-recursive-run.py +0 -2870
  21. package/references/scripts/recursive-closeout.ps1 +0 -38
  22. package/references/scripts/recursive-closeout.py +0 -541
  23. package/references/scripts/recursive-init.ps1 +0 -515
  24. package/references/scripts/recursive-init.py +0 -356
  25. package/references/scripts/recursive-lock.ps1 +0 -26
  26. package/references/scripts/recursive-lock.py +0 -302
  27. package/references/scripts/recursive-review-bundle.ps1 +0 -58
  28. package/references/scripts/recursive-review-bundle.py +0 -503
  29. package/references/scripts/recursive-router-cli-configure.ps1 +0 -2
  30. package/references/scripts/recursive-router-cli-configure.py +0 -9
  31. package/references/scripts/recursive-router-cli-init.ps1 +0 -2
  32. package/references/scripts/recursive-router-cli-init.py +0 -9
  33. package/references/scripts/recursive-router-cli-invoke.ps1 +0 -2
  34. package/references/scripts/recursive-router-cli-invoke.py +0 -9
  35. package/references/scripts/recursive-router-cli-probe.ps1 +0 -2
  36. package/references/scripts/recursive-router-cli-probe.py +0 -9
  37. package/references/scripts/recursive-router-cli-resolve.ps1 +0 -2
  38. package/references/scripts/recursive-router-cli-resolve.py +0 -9
  39. package/references/scripts/recursive-router-cli-validate.ps1 +0 -2
  40. package/references/scripts/recursive-router-cli-validate.py +0 -9
  41. package/references/scripts/recursive-router-configure.ps1 +0 -27
  42. package/references/scripts/recursive-router-configure.py +0 -74
  43. package/references/scripts/recursive-router-init.ps1 +0 -17
  44. package/references/scripts/recursive-router-init.py +0 -29
  45. package/references/scripts/recursive-router-invoke.ps1 +0 -47
  46. package/references/scripts/recursive-router-invoke.py +0 -103
  47. package/references/scripts/recursive-router-probe.ps1 +0 -25
  48. package/references/scripts/recursive-router-probe.py +0 -44
  49. package/references/scripts/recursive-router-resolve.ps1 +0 -26
  50. package/references/scripts/recursive-router-resolve.py +0 -46
  51. package/references/scripts/recursive-router-validate.ps1 +0 -17
  52. package/references/scripts/recursive-router-validate.py +0 -27
  53. package/references/scripts/recursive-status.ps1 +0 -23
  54. package/references/scripts/recursive-status.py +0 -2124
  55. package/references/scripts/recursive-subagent-action.ps1 +0 -98
  56. package/references/scripts/recursive-subagent-action.py +0 -197
  57. package/references/scripts/recursive-training-extract.ps1 +0 -23
  58. package/references/scripts/recursive-training-extract.py +0 -99
  59. package/references/scripts/recursive-training-grpo.ps1 +0 -52
  60. package/references/scripts/recursive-training-grpo.py +0 -1341
  61. package/references/scripts/recursive-training-loader.ps1 +0 -78
  62. package/references/scripts/recursive-training-loader.py +0 -558
  63. package/references/scripts/recursive-training-mcp.ps1 +0 -29
  64. package/references/scripts/recursive-training-mcp.py +0 -261
  65. package/references/scripts/recursive-training-phase8-trigger.ps1 +0 -55
  66. package/references/scripts/recursive-training-phase8-trigger.py +0 -137
  67. package/references/scripts/recursive-training-sync.ps1 +0 -29
  68. package/references/scripts/recursive-training-sync.py +0 -183
  69. package/references/scripts/recursive_phase_rules.py +0 -367
  70. package/references/scripts/recursive_router_cli_lib.py +0 -2
  71. package/references/scripts/recursive_router_lib.py +0 -2282
  72. package/references/scripts/verify-locks.ps1 +0 -25
  73. package/references/scripts/verify-locks.py +0 -353
  74. package/scripts/__pycache__/lint-recursive-run.cpython-314.pyc +0 -0
  75. package/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  76. package/scripts/install-recursive-mode.ps1 +0 -956
  77. package/scripts/install-recursive-mode.py +0 -750
  78. package/scripts/lint-recursive-run.py +0 -2870
  79. package/scripts/recursive-closeout.py +0 -541
  80. package/scripts/recursive-init.py +0 -356
  81. package/scripts/recursive-lock.py +0 -302
  82. package/scripts/recursive-status.py +0 -2124
  83. package/scripts/recursive_phase_rules.py +0 -367
  84. package/scripts/recursive_router_lib.py +0 -2282
  85. package/scripts/verify-locks.py +0 -353
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
+ }
@@ -1,15 +1,15 @@
1
1
  /**
2
- * Slot registrations (SP2 R1): the sidebar launcher, the shell.overlay board,
3
- * the conversation.input.dock status strip, and the settings.section page.
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
- * The board (root scope) and strip (session scope) render ONLY for the
6
- * recursive preset the gate reads the CURRENT session's agentPreset from the
7
- * sessions list snapshot (acceptance #3: a non-recursive session shows NOTHING).
8
- *
9
- * The board reads cwd from the ROOT-scope standard prop useSessions (the
10
- * scoped-slots root branch provides ONLY useSessions/useWorkspaces useProjection
11
- * is session-scope-only, which is why the board never reads it). The strip reads
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 (always visible; opens the board).
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(RecursiveBoardGate, { useSessions })
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 gate: renders the board ONLY when the current session is on the
83
- * recursive preset. The gate itself reads the live sessions snapshot via the
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 RecursiveBoardGate({ useSessions }: { useSessions: () => SessionListStateLike }): ReactNode {
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
- if (!isRecursivePreset(sessions)) return null
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
- return createElement(Board, { snapshot })
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] ?? ''