@try-works/dsh-recursive-mode 0.1.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.
Files changed (88) hide show
  1. package/cordis.patch.yml +12 -0
  2. package/lib/bootstrap.d.ts +35 -0
  3. package/lib/client/board.d.ts +10 -0
  4. package/lib/client/contract.d.ts +51 -0
  5. package/lib/client/derive.d.ts +92 -0
  6. package/lib/client/index.d.ts +21 -0
  7. package/lib/client/inspector.d.ts +10 -0
  8. package/lib/client/node.d.ts +71 -0
  9. package/lib/client/settings.d.ts +6 -0
  10. package/lib/client/slots.d.ts +7 -0
  11. package/lib/client/strip.d.ts +7 -0
  12. package/lib/client.d.ts +10 -0
  13. package/lib/client.js +490 -0
  14. package/lib/closeout.d.ts +23 -0
  15. package/lib/commands.d.ts +51 -0
  16. package/lib/delegation.d.ts +92 -0
  17. package/lib/enforcement.d.ts +53 -0
  18. package/lib/events.d.ts +173 -0
  19. package/lib/handoff.d.ts +51 -0
  20. package/lib/index.d.ts +40 -0
  21. package/lib/lifecycle.d.ts +107 -0
  22. package/lib/lock.d.ts +92 -0
  23. package/lib/policy.d.ts +12 -0
  24. package/lib/projection.d.ts +29 -0
  25. package/lib/recursive_closeout.tool.d.ts +8 -0
  26. package/lib/recursive_init.tool.d.ts +2 -0
  27. package/lib/recursive_lint.tool.d.ts +2 -0
  28. package/lib/recursive_lock.tool.d.ts +2 -0
  29. package/lib/recursive_scratch.tool.d.ts +7 -0
  30. package/lib/recursive_status.tool.d.ts +2 -0
  31. package/lib/review.d.ts +39 -0
  32. package/lib/router.d.ts +77 -0
  33. package/lib/run.d.ts +29 -0
  34. package/lib/runtime.d.ts +241 -0
  35. package/lib/scratch.d.ts +18 -0
  36. package/lib/status.d.ts +19 -0
  37. package/lib/types.d.ts +104 -0
  38. package/lib/workspace.d.ts +50 -0
  39. package/package.json +119 -0
  40. package/preset/recursive/agent.cordis.yml +282 -0
  41. package/preset/recursive/preset.yml +3 -0
  42. package/scripts/install-recursive-mode.ps1 +956 -0
  43. package/scripts/install-recursive-mode.py +750 -0
  44. package/scripts/lint-recursive-run.py +2868 -0
  45. package/scripts/recursive-closeout.py +541 -0
  46. package/scripts/recursive-init.py +356 -0
  47. package/scripts/recursive-lock.py +302 -0
  48. package/scripts/recursive-status.py +2124 -0
  49. package/scripts/recursive_phase_rules.py +367 -0
  50. package/scripts/recursive_router_lib.py +2282 -0
  51. package/scripts/test-recursive-mode-smoke.ts +204 -0
  52. package/scripts/verify-locks.py +353 -0
  53. package/src/bootstrap.ts +118 -0
  54. package/src/client/board.tsx +61 -0
  55. package/src/client/contract.ts +58 -0
  56. package/src/client/derive.ts +241 -0
  57. package/src/client/index.ts +28 -0
  58. package/src/client/inspector.tsx +49 -0
  59. package/src/client/node.ts +156 -0
  60. package/src/client/settings.tsx +18 -0
  61. package/src/client/slots.ts +67 -0
  62. package/src/client/strip.tsx +28 -0
  63. package/src/client.ts +11 -0
  64. package/src/closeout.ts +183 -0
  65. package/src/commands.ts +142 -0
  66. package/src/delegation.ts +306 -0
  67. package/src/enforcement.ts +180 -0
  68. package/src/events.ts +173 -0
  69. package/src/handoff.ts +165 -0
  70. package/src/index.ts +283 -0
  71. package/src/lifecycle.ts +235 -0
  72. package/src/lock.ts +369 -0
  73. package/src/policy.ts +56 -0
  74. package/src/projection.ts +237 -0
  75. package/src/recursive_closeout.tool.ts +35 -0
  76. package/src/recursive_init.tool.ts +28 -0
  77. package/src/recursive_lint.tool.ts +29 -0
  78. package/src/recursive_lock.tool.ts +33 -0
  79. package/src/recursive_scratch.tool.ts +42 -0
  80. package/src/recursive_status.tool.ts +24 -0
  81. package/src/review.ts +178 -0
  82. package/src/router.ts +197 -0
  83. package/src/run.ts +85 -0
  84. package/src/runtime.ts +564 -0
  85. package/src/scratch.ts +85 -0
  86. package/src/status.ts +194 -0
  87. package/src/types.ts +112 -0
  88. package/src/workspace.ts +67 -0
@@ -0,0 +1,53 @@
1
+ import { type SessionEventLike } from './lifecycle.ts';
2
+ export type EnforcementMode = 'strict' | 'advisory';
3
+ export interface EnforcementConfig {
4
+ preStep: EnforcementMode;
5
+ toolGuards: EnforcementMode;
6
+ tamper: EnforcementMode;
7
+ }
8
+ /** Validate the enforcement config shape (unknown keys fail at plugin load). */
9
+ export declare function resolveEnforcementConfig(config: unknown): EnforcementConfig;
10
+ export declare const DEFAULT_ENFORCEMENT: EnforcementConfig;
11
+ /**
12
+ * Layer 1 - agent/pre-step phase-transition gate decision.
13
+ * Reads on TRANSITION INTENT ONLY (13.5): no transition intent means the step
14
+ * passes through untouched. On a transition intent whose gates fail:
15
+ * - strict -> reject (turn ends blocked, no model call spent)
16
+ * - advisory -> enter (warn only; a recursive/gate-blocked event is emitted)
17
+ */
18
+ export interface PreStepGateDecision {
19
+ kind: 'reject' | 'enter';
20
+ gateBlocked: boolean;
21
+ failures: string[];
22
+ }
23
+ export declare function evaluatePreStepGate(events: readonly SessionEventLike[], mode?: EnforcementMode): PreStepGateDecision;
24
+ /**
25
+ * Layer 2 - tools/pre-execute guard decision.
26
+ * Pure predicate: inspects the pending tool execution (name + args) against
27
+ * the run tree under the given worktree root.
28
+ */
29
+ export type ToolGuardDecision = {
30
+ kind: 'allow';
31
+ } | {
32
+ kind: 'deny';
33
+ reason: string;
34
+ } | {
35
+ kind: 'ask';
36
+ reason?: string;
37
+ };
38
+ export interface ToolExecLike {
39
+ name: string;
40
+ arguments?: unknown;
41
+ agent?: unknown;
42
+ }
43
+ export declare function evaluateToolGuard(exec: ToolExecLike, worktreeRoot: string, activeRunId: string, mode?: EnforcementMode): ToolGuardDecision;
44
+ /**
45
+ * Layer 8 - fs/observed lock-tamper detection.
46
+ * A locked *.md whose observed version differs from the stored LockHash is
47
+ * a tamper. Returns a tamper reason (or null when clean/not-applicable).
48
+ */
49
+ export declare function detectTamper(targetPath: string, worktreeRoot: string, activeRunId: string): {
50
+ runId: string;
51
+ path: string;
52
+ reason: string;
53
+ } | null;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * recursive/* session events (Phase D R1, PROPOSAL 11.2/11.6): the complete
3
+ * durable event vocabulary the projection folds, the board renders, and the
4
+ * conversation node correlates. Every payload carries a non-empty
5
+ * { runId, worktreeRoot } so the whole plane is worktree-aware and the
6
+ * projection can group runs by their control-plane root (binding
7
+ * workspace-scoping invariant, run 03 R1).
8
+ *
9
+ * Events are LOG-ONLY (no live mirror): the projection unit (projection.ts)
10
+ * is the only derived view. Payloads are plain JSON and declared on the
11
+ * SessionEventMap merge via @deepseek-ai/dsh-session/types.
12
+ *
13
+ * The pure constructor helpers below enforce the runId + non-empty
14
+ * worktreeRoot invariant at emit time, so a malformed event fails loud at
15
+ * the emit site instead of silently landing cross-workspace.
16
+ */
17
+ import type { RecursiveRunState } from './types.ts';
18
+ /** Declare the recursive/* event family on the session event map (merge-extensible). */
19
+ declare module '@deepseek-ai/dsh-session/types' {
20
+ interface SessionEventMap {
21
+ /**
22
+ * A transition the lifecycle driver is ABOUT to validate/commit (logged
23
+ * before any file write). The pre-step gate reads this as the caller of the
24
+ * transition set — never a projection event (log-only internal signal).
25
+ */
26
+ 'recursive/phase-intent': {
27
+ runId: string;
28
+ worktreeRoot: string;
29
+ targetArtifact: string;
30
+ kind: 'lock' | 'reopen' | 'advance';
31
+ evidence?: Record<string, unknown>;
32
+ };
33
+ /** A run was created/bootstrapped in this workspace. */
34
+ 'recursive/run-created': {
35
+ runId: string;
36
+ worktreeRoot: string;
37
+ template?: string;
38
+ repo?: string;
39
+ };
40
+ /** A phase transition committed (validate -> commit -> flush -> emit). */
41
+ 'recursive/phase': {
42
+ runId: string;
43
+ worktreeRoot: string;
44
+ phase: string;
45
+ status: string;
46
+ };
47
+ /** A phase artifact was locked (lock hash + timestamp recorded). */
48
+ 'recursive/phase-locked': {
49
+ runId: string;
50
+ worktreeRoot: string;
51
+ phase: string;
52
+ lockedAt: string;
53
+ lockHash: string;
54
+ };
55
+ /** A transition's gate predicate failed (rejected in strict, warned in advisory). */
56
+ 'recursive/gate-blocked': {
57
+ runId: string;
58
+ worktreeRoot: string;
59
+ phase: string;
60
+ failures: string[];
61
+ kind: string;
62
+ };
63
+ /** fs/observed detected a change to a locked artifact outside a tool call. */
64
+ 'recursive/tamper': {
65
+ runId: string;
66
+ worktreeRoot: string;
67
+ path: string;
68
+ reason: string;
69
+ };
70
+ /** A run was merged back to the repo root (conversation history re-keys). */
71
+ 'recursive/run-merged': {
72
+ runId: string;
73
+ worktreeRoot: string;
74
+ repoRoot: string;
75
+ };
76
+ /** Run-level lifecycle flag (active/paused/blocked/complete + reason). */
77
+ 'recursive/run-state': {
78
+ runId: string;
79
+ worktreeRoot: string;
80
+ state: RecursiveRunState;
81
+ reason?: string;
82
+ };
83
+ /** A subagent started for this run. */
84
+ 'recursive/subagent-start': {
85
+ runId: string;
86
+ worktreeRoot: string;
87
+ childId: string;
88
+ role: string;
89
+ provider: string;
90
+ };
91
+ /** A subagent finished for this run. */
92
+ 'recursive/subagent-end': {
93
+ runId: string;
94
+ worktreeRoot: string;
95
+ childId: string;
96
+ role: string;
97
+ provider: string;
98
+ status: 'running' | 'done' | 'failed';
99
+ };
100
+ }
101
+ }
102
+ /** Enforce the runId + non-empty worktreeRoot invariant at emit time. */
103
+ export declare function requireRunKey(runId: string, worktreeRoot: string): void;
104
+ export interface PhaseIntentInput {
105
+ runId: string;
106
+ worktreeRoot: string;
107
+ targetArtifact: string;
108
+ kind: 'lock' | 'reopen' | 'advance';
109
+ evidence?: Record<string, unknown>;
110
+ }
111
+ export interface RunCreatedInput {
112
+ runId: string;
113
+ worktreeRoot: string;
114
+ template?: string;
115
+ repo?: string;
116
+ }
117
+ export interface PhaseInput {
118
+ runId: string;
119
+ worktreeRoot: string;
120
+ phase: string;
121
+ status: string;
122
+ }
123
+ export interface PhaseLockedInput {
124
+ runId: string;
125
+ worktreeRoot: string;
126
+ phase: string;
127
+ lockedAt: string;
128
+ lockHash: string;
129
+ }
130
+ export interface GateBlockedInput {
131
+ runId: string;
132
+ worktreeRoot: string;
133
+ phase: string;
134
+ failures: string[];
135
+ kind: string;
136
+ }
137
+ export interface TamperInput {
138
+ runId: string;
139
+ worktreeRoot: string;
140
+ path: string;
141
+ reason: string;
142
+ }
143
+ export interface RunMergedInput {
144
+ runId: string;
145
+ worktreeRoot: string;
146
+ repoRoot: string;
147
+ }
148
+ export interface RunStateInput {
149
+ runId: string;
150
+ worktreeRoot: string;
151
+ state: RecursiveRunState;
152
+ reason?: string;
153
+ }
154
+ export interface SubagentInput {
155
+ runId: string;
156
+ worktreeRoot: string;
157
+ childId: string;
158
+ role: string;
159
+ provider: string;
160
+ }
161
+ export interface SubagentEndInput extends SubagentInput {
162
+ status: 'running' | 'done' | 'failed';
163
+ }
164
+ export declare function phaseIntent(data: PhaseIntentInput): PhaseIntentInput;
165
+ export declare function runCreated(data: RunCreatedInput): RunCreatedInput;
166
+ export declare function phase(data: PhaseInput): PhaseInput;
167
+ export declare function phaseLocked(data: PhaseLockedInput): PhaseLockedInput;
168
+ export declare function gateBlocked(data: GateBlockedInput): GateBlockedInput;
169
+ export declare function tamper(data: TamperInput): TamperInput;
170
+ export declare function runMerged(data: RunMergedInput): RunMergedInput;
171
+ export declare function runState(data: RunStateInput): RunStateInput;
172
+ export declare function subagentStart(data: SubagentInput): SubagentInput;
173
+ export declare function subagentEnd(data: SubagentEndInput): SubagentEndInput;
@@ -0,0 +1,51 @@
1
+ export interface HandoffInput {
2
+ root: string;
3
+ runId: string;
4
+ delegationId: string;
5
+ role: string;
6
+ objective: string;
7
+ runDocRefs: string[];
8
+ codeRefs: string[];
9
+ auditQuestions: string[];
10
+ requiredOutput: string;
11
+ decisionBasis: string;
12
+ constraints?: string[];
13
+ }
14
+ export interface ChildBriefInput {
15
+ root: string;
16
+ runId: string;
17
+ delegationId: string;
18
+ childId: string;
19
+ slice: string;
20
+ replyContract?: string;
21
+ }
22
+ /** Main-agent handoff doc: the full delegation info for one delegation. */
23
+ export declare function createHandoff(input: HandoffInput): string;
24
+ /** Per-child brief: the receiving slice for one child. */
25
+ export declare function createChildBrief(input: ChildBriefInput): string;
26
+ /** Reply path the child must write its submission to. */
27
+ export declare function replyPath(input: {
28
+ root: string;
29
+ runId: string;
30
+ delegationId: string;
31
+ childId: string;
32
+ }): string;
33
+ /** Child-scoped disposable scratch (Phase B R5, PROPOSAL 10.7). */
34
+ export declare function childScratchPath(input: {
35
+ root: string;
36
+ runId: string;
37
+ childId: string;
38
+ }): string;
39
+ /**
40
+ * Build the reference-based delegation prompt (PROPOSAL 10.6): a pointer to
41
+ * handoff.md + brief.md, instructions to write reply.md and call report citing
42
+ * it. Short, not a monolithic paste.
43
+ */
44
+ export declare function buildDelegationPrompt(input: {
45
+ root: string;
46
+ runId: string;
47
+ delegationId: string;
48
+ childId: string;
49
+ handoffPath: string;
50
+ briefPath: string;
51
+ }): string;
package/lib/index.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { type Context } from '@deepseek-ai/cordis';
2
+ export declare const name = "@try-works/dsh-recursive-mode";
3
+ export { RecursiveRuntime } from './runtime.ts';
4
+ export { createRecursiveStatusTool } from './recursive_status.tool.ts';
5
+ export { createRecursiveInitTool } from './recursive_init.tool.ts';
6
+ export { createRecursiveLockTool } from './recursive_lock.tool.ts';
7
+ export { createRecursiveLintTool } from './recursive_lint.tool.ts';
8
+ export { createRecursiveCloseoutTool } from './recursive_closeout.tool.ts';
9
+ export { createRecursiveScratchTool } from './recursive_scratch.tool.ts';
10
+ export * from './status.ts';
11
+ export { PHASE_SEQUENCE, OPTIONAL_PHASES, normalizeForLockHash, lockHashFromContent, phaseIndex, isCoreArtifact, getPrerequisites, getLockStatus, getPrerequisiteBlockers, receiptPath, readReceipt, writeReceipt, invalidateReceipt, getStaleDownstreamPhases, getNextLegalPhase, getAllStaleReceipts, validateChain, } from './lock.ts';
12
+ export type { LockReceipt, LockStatus, PrerequisiteBlocker, StaleDownstream, ChainPhaseResult, LockChainResult, } from './lock.ts';
13
+ export * from './run.ts';
14
+ export * from './review.ts';
15
+ export * from './handoff.ts';
16
+ export * from './router.ts';
17
+ export * from './delegation.ts';
18
+ export * from './lifecycle.ts';
19
+ export * from './enforcement.ts';
20
+ export * from './policy.ts';
21
+ export * from './events.ts';
22
+ export * from './projection.ts';
23
+ /**
24
+ * Bundle plugin entry. The Loader activates this row once `tools` is available
25
+ * (`inject` below); the RecursiveRuntime service is constructed directly so it
26
+ * is provided on `ctx.recursive` for the lifetime of this fiber, and the
27
+ * read-path tools (status/init/lock/lint) are registered through it (R2/R4).
28
+ */
29
+ export declare const inject: string[];
30
+ /**
31
+ * Two-stage init (PROPOSAL §5.10):
32
+ * - Stage A (mount-time, this apply): register the isolated ctx.recursive
33
+ * service + the recursive_* tools + the /recursive command + the
34
+ * recursive:policy prompt section. No repo/run work here.
35
+ * - Stage B (agent/session-start): resolve the session's workspace root (R1),
36
+ * bootstrap the scaffold if missing, enumerate runs as dir names only, and
37
+ * inject a workspace-scoped notice. See stageBWorkflowInit + the
38
+ * agent/session-start listener below.
39
+ */
40
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,107 @@
1
+ /** Run-level durable states (PROPOSAL 8.8). */
2
+ export declare const RUN_STATES: readonly ["new", "active", "paused", "blocked", "complete"];
3
+ export type RunState = (typeof RUN_STATES)[number];
4
+ /** A proposed phase transition the gates validate before any file write. */
5
+ export interface PhaseTransitionIntent {
6
+ runId: string;
7
+ worktreeRoot: string;
8
+ targetArtifact: string;
9
+ kind: 'lock' | 'reopen' | 'advance';
10
+ evidence?: {
11
+ tddMode?: string;
12
+ redEvidencePath?: string;
13
+ greenEvidencePath?: string;
14
+ qaMode?: string;
15
+ qaSignOff?: boolean;
16
+ };
17
+ }
18
+ /** Folded phase state (last-wins over recursive/phase + recursive/run-state). */
19
+ export interface RecursivePhaseState {
20
+ runId: string;
21
+ phase: string;
22
+ status: string;
23
+ runState: RunState;
24
+ }
25
+ /** A session-event-like carrier (the pure fold reads events by shape). */
26
+ export interface SessionEventLike {
27
+ type: string;
28
+ data?: Record<string, unknown>;
29
+ }
30
+ /** Gate check result - the transition set's single output. */
31
+ export interface GateCheckResult {
32
+ passed: boolean;
33
+ failures: string[];
34
+ }
35
+ /**
36
+ * Payload shapes for the recursive/* events. These are LEGACY structural
37
+ * views kept for internal consumers; the single source of truth for the
38
+ * emitted payloads is events.ts (every event carries { runId, worktreeRoot }).
39
+ * Aligned here so no local interface drifts out of the worktree-keyed
40
+ * invariant (B7).
41
+ */
42
+ export interface RecursivePhaseEvent {
43
+ runId: string;
44
+ worktreeRoot: string;
45
+ phase: string;
46
+ status: string;
47
+ }
48
+ export interface RecursiveRunStateEvent {
49
+ runId: string;
50
+ worktreeRoot: string;
51
+ state: RunState;
52
+ reason?: string;
53
+ }
54
+ export interface RecursiveGateBlockedEvent {
55
+ runId: string;
56
+ worktreeRoot: string;
57
+ phase: string;
58
+ failures: string[];
59
+ kind: string;
60
+ }
61
+ export interface RecursiveTamperEvent {
62
+ runId: string;
63
+ worktreeRoot: string;
64
+ path: string;
65
+ reason: string;
66
+ }
67
+ export interface RecursiveTransitionFailedEvent {
68
+ runId: string;
69
+ worktreeRoot: string;
70
+ phase: string;
71
+ error: string;
72
+ }
73
+ /**
74
+ * Pure last-wins fold over the recursive/* events (the foldPlanMode pattern).
75
+ * A log with no recursive/phase folds to null; the last recursive/run-state
76
+ * wins for the run-level flag.
77
+ */
78
+ export declare function foldRecursivePhase(events: readonly SessionEventLike[], end?: number): RecursivePhaseState | null;
79
+ /** Whether the session log holds an opened turn without its closing turn/end. */
80
+ export declare function hasOpenTurn(events: readonly SessionEventLike[]): boolean;
81
+ /** Detect a pending transition intent from the session log (the lock tool logs it). */
82
+ export declare function detectTransitionIntent(events: readonly SessionEventLike[]): PhaseTransitionIntent | null;
83
+ /**
84
+ * Validate a proposed transition against the target phase's gates (PROPOSAL 8.4).
85
+ * Pure: reads the current file tree + lock.ts chain; writes nothing.
86
+ */
87
+ export declare function validateTransition(intent: PhaseTransitionIntent): GateCheckResult;
88
+ /**
89
+ * A serialized per-run transition driver (coalesced - the single-reservation
90
+ * pattern). Two concurrent 'lock Phase 3' intents queue; the second observes
91
+ * the first's committed state instead of racing the write.
92
+ */
93
+ export declare class LifecycleDriver {
94
+ private readonly drivers;
95
+ /** Run one transition serially per runId. */
96
+ serialize(runId: string, run: () => Promise<void>): Promise<void>;
97
+ }
98
+ /**
99
+ * Couple a gate-block to the goal service (PROPOSAL 8.4 goal integration).
100
+ * Graceful no-op when the goal service or agent is unavailable.
101
+ */
102
+ export declare function coupleGateBlockToGoal(goalService: {
103
+ block?: (agent: unknown, ref: unknown, reason: unknown) => unknown;
104
+ } | null | undefined, agent: unknown, ref: unknown, reason: {
105
+ code: string;
106
+ message: string;
107
+ }): boolean;
package/lib/lock.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Lock-hash + lock-chain validation for recursive-mode runs.
3
+ *
4
+ * Ports recursive-lock.py / verify-locks.py / recursive_phase_rules.py
5
+ * semantics (byte-for-byte where observable):
6
+ * - lockHashFromContent: LF-normalize, strip LockHash lines, SHA-256 hex
7
+ * - getLockStatus: MISSING / DRAFT / STALE_LOCK / LOCKED
8
+ * - getPrerequisites / getPrerequisiteBlockers: sequence ordering
9
+ * - validateChain: per-phase validity + break + next legal phase
10
+ * - receipts: read/write/invalidate with Python-identical JSON
11
+ *
12
+ * R3 (run 02).
13
+ */
14
+ export declare const PHASE_SEQUENCE: readonly ["00-requirements.md", "00-worktree.md", "01-as-is.md", "01.5-root-cause.md", "02-to-be-plan.md", "03-implementation-summary.md", "03.5-code-review.md", "04-test-summary.md", "05-manual-qa.md", "06-decisions-update.md", "07-state-update.md", "08-memory-impact.md"];
15
+ export declare const OPTIONAL_PHASES: Set<string>;
16
+ export interface LockReceipt {
17
+ artifact: string;
18
+ artifact_path: string;
19
+ artifact_hash: string;
20
+ locked_at: string;
21
+ prerequisite_hashes: Record<string, string>;
22
+ previous_receipt_hash: string | null;
23
+ receipt_hash: string;
24
+ }
25
+ export type LockStatus = 'MISSING' | 'DRAFT' | 'STALE_LOCK' | 'LOCKED';
26
+ export interface PrerequisiteBlocker {
27
+ artifact: string;
28
+ status: string;
29
+ path: string;
30
+ }
31
+ export interface StaleDownstream {
32
+ artifact: string;
33
+ reason: string;
34
+ }
35
+ export interface ChainPhaseResult {
36
+ file: string;
37
+ status: LockStatus;
38
+ lockValid: boolean;
39
+ lockProblems: string[];
40
+ }
41
+ export interface LockChainResult {
42
+ runId: string;
43
+ phases: ChainPhaseResult[];
44
+ breakPhase: string | null;
45
+ nextLegalPhase: string | null;
46
+ complete: boolean;
47
+ staleReceipts: StaleDownstream[];
48
+ }
49
+ /** LF-normalize, strip every LockHash: line, return the normalized content. */
50
+ export declare function normalizeForLockHash(content: string): string;
51
+ /** SHA-256 hex over the UTF-8 bytes of the normalized content. */
52
+ export declare function lockHashFromContent(content: string): string;
53
+ export declare function phaseIndex(artifactFile: string): number;
54
+ export declare function isCoreArtifact(artifactFile: string): boolean;
55
+ /**
56
+ * Every earlier phase in PHASE_SEQUENCE that exists on disk.
57
+ * Mirrors: [phase for phase in PHASE_SEQUENCE[:idx] if (run_dir / phase).exists()]
58
+ */
59
+ export declare function getPrerequisites(runDir: string, artifactFile: string): string[];
60
+ /**
61
+ * Canonical lock-validity classifier:
62
+ * MISSING (no file) / DRAFT (not LOCKED) / STALE_LOCK (missing or mismatched hash fields) / LOCKED.
63
+ */
64
+ export declare function getLockStatus(artifactPath: string): LockStatus;
65
+ /** Every prerequisite whose status is not LOCKED, in PHASE_SEQUENCE order. */
66
+ export declare function getPrerequisiteBlockers(runDir: string, artifactFile: string): PrerequisiteBlocker[];
67
+ export declare function receiptPath(runDir: string, artifactFile: string): string;
68
+ export declare function readReceipt(runDir: string, artifactFile: string): LockReceipt | null;
69
+ /**
70
+ * Write a lock receipt with Python-identical JSON semantics.
71
+ */
72
+ export declare function writeReceipt(runDir: string, artifactFile: string, artifactPath: string): LockReceipt;
73
+ export declare function invalidateReceipt(runDir: string, artifactFile: string): boolean;
74
+ /**
75
+ * Downstream phases (strictly after artifactFile in PHASE_SEQUENCE) whose
76
+ * receipt's prerequisite_hashes[artifactFile] differs from the current hash
77
+ * (or the artifact no longer exists).
78
+ */
79
+ export declare function getStaleDownstreamPhases(runDir: string, artifactFile: string): StaleDownstream[];
80
+ export declare function getNextLegalPhase(runDir: string): string | null;
81
+ /** All receipts whose prerequisite_hashes reference a missing or changed artifact. */
82
+ export declare function getAllStaleReceipts(runDir: string): StaleDownstream[];
83
+ /**
84
+ * Validate the full lock chain of a run: per-phase lock status, break phase,
85
+ * next legal phase, completion, and stale receipts.
86
+ */
87
+ export declare function validateChain(runDir: string, runId: string): LockChainResult;
88
+ /**
89
+ * Python json.dumps(obj, sort_keys=True, separators=(', ', ': '), ensure_ascii=True)
90
+ * compact serialization. Exported for parity testing against the Python oracle.
91
+ */
92
+ export declare function serializeForReceiptHash(obj: object): string;
@@ -0,0 +1,12 @@
1
+ import type { RecursivePhaseState } from './lifecycle.ts';
2
+ import type { EnforcementConfig } from './enforcement.ts';
3
+ export interface PolicyContext {
4
+ worktreeRoot: string;
5
+ runId: string;
6
+ folded: RecursivePhaseState | null;
7
+ config?: EnforcementConfig;
8
+ }
9
+ /**
10
+ * Render the current-phase contract. Empty string when no run is active.
11
+ */
12
+ export declare function renderRecursivePolicy(context: PolicyContext | null): string;
@@ -0,0 +1,29 @@
1
+ import type { ZodType } from 'zod';
2
+ import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection';
3
+ import type { RecursiveProjection } from './types.ts';
4
+ /** Declare the 'recursive' projection key on the shared type table. */
5
+ declare module '@deepseek-ai/dsh-session-projection/types' {
6
+ interface SessionProjectionMap {
7
+ /** Runs grouped by worktree root, then runId. */
8
+ recursive: RecursiveProjection;
9
+ }
10
+ }
11
+ /** Structural event shape the pure fold reads (unit-testable without a session). */
12
+ export interface RecursiveEventLike {
13
+ type: string;
14
+ data?: Record<string, unknown>;
15
+ }
16
+ /** Mutable fold accumulator (private to the pure fold; never leaks). */
17
+ export interface RecursiveFoldState {
18
+ /** The workspace control-plane root this unit is scoped to ('' = accept all). */
19
+ workspaceRoot: string;
20
+ /** Runs grouped by worktree root then runId. */
21
+ byWorktree: RecursiveProjection;
22
+ }
23
+ export declare function emptyRecursiveFoldState(workspaceRoot?: string): RecursiveFoldState;
24
+ /** Whether a worktreeRoot is inside (or equal to) the workspace root. */
25
+ export declare function isInsideWorkspace(worktreeRoot: string, workspaceRoot: string): boolean;
26
+ export declare function foldRecursiveProjection(state: RecursiveFoldState, event: RecursiveEventLike): RecursiveFoldState;
27
+ export declare const recursiveProjectionSchema: ZodType<RecursiveProjection>;
28
+ /** The registered projection unit (key 'recursive'). */
29
+ export declare const recursiveProjectionUnit: ProjectionDefinition<'recursive', RecursiveFoldState>;
@@ -0,0 +1,8 @@
1
+ import type { RecursiveRuntime } from './runtime.ts';
2
+ /**
3
+ * `recursive_closeout` — scaffold a Phase 4/5/6/7/8 closeout receipt under the
4
+ * SESSION's workspace only (R1 workspace-scoping invariant). The run is resolved
5
+ * via the session agent's cwd -> workspace registry; a runId outside the current
6
+ * workspace is rejected.
7
+ */
8
+ export declare function createRecursiveCloseoutTool(recursive: RecursiveRuntime): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,2 @@
1
+ import type { RecursiveRuntime } from './runtime.ts';
2
+ export declare function createRecursiveInitTool(recursive: RecursiveRuntime): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,2 @@
1
+ import type { RecursiveRuntime } from './runtime.ts';
2
+ export declare function createRecursiveLintTool(recursive: RecursiveRuntime): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,2 @@
1
+ import type { RecursiveRuntime } from './runtime.ts';
2
+ export declare function createRecursiveLockTool(recursive: RecursiveRuntime): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,7 @@
1
+ import type { RecursiveRuntime } from './runtime.ts';
2
+ /**
3
+ * `recursive_scratch` — read/write/append the run-scoped disposable scratchpad
4
+ * (R5) under the CURRENT session workspace only (R1). Scratch is git-ignored
5
+ * and never citable as an Input.
6
+ */
7
+ export declare function createRecursiveScratchTool(recursive: RecursiveRuntime): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,2 @@
1
+ import type { RecursiveRuntime } from './runtime.ts';
2
+ export declare function createRecursiveStatusTool(recursive: RecursiveRuntime): import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,39 @@
1
+ export interface ReviewBundleInput {
2
+ root: string;
3
+ runId: string;
4
+ phase: string;
5
+ role: string;
6
+ artifactPath: string;
7
+ upstreamArtifacts: string[];
8
+ auditQuestions: string[];
9
+ requiredOutput: string;
10
+ diffBasis?: {
11
+ baselineType: string;
12
+ baselineReference: string;
13
+ comparisonReference: string;
14
+ normalizedBaseline: string;
15
+ normalizedComparison: string;
16
+ normalizedDiffCommand: string;
17
+ };
18
+ codeRefs?: string[];
19
+ addenda?: string[];
20
+ priorEvidence?: string[];
21
+ memoryRefs?: string[];
22
+ changedFiles?: string[];
23
+ }
24
+ export interface ReviewBundleResult {
25
+ bundlePath: string;
26
+ repoRelativePath: string;
27
+ artifactContentHash: string;
28
+ markdown: string;
29
+ }
30
+ /** LF-normalized sha256 (matches recursive-lock.py content_sha256). */
31
+ export declare function contentSha256(content: string): string;
32
+ /**
33
+ * Build a canonical review bundle. Throws when the artifact path is missing or
34
+ * when a path would escape the workspace root (fail loud, never a silent
35
+ * bundle).
36
+ */
37
+ export declare function buildReviewBundle(input: ReviewBundleInput): ReviewBundleResult;
38
+ /** Canonical bundle dir for a run: .recursive/run/<id>/evidence/review-bundles/. */
39
+ export declare function reviewBundleDir(root: string, runId: string): string;