@try-works/dsh-recursive-mode 0.1.4 → 0.1.5
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/fs-intent.d.ts +34 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +39 -6
- package/lib/policy.d.ts +1 -1
- package/package.json +1 -1
- package/src/fs-intent.ts +84 -0
- package/src/index.ts +12 -10
- package/src/policy.ts +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { WorkspaceRegistryLike } from './workspace.ts';
|
|
2
|
+
export interface FsPolicyIntent {
|
|
3
|
+
worktreeRoot: string;
|
|
4
|
+
runId: string;
|
|
5
|
+
/** The current phase doc file name (e.g. 03-implementation-summary.md), when determinable. */
|
|
6
|
+
currentPhaseFile: string | null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the recursive policy intent for one agent from the FILESYSTEM.
|
|
10
|
+
* Returns null when no control-plane root or run is present (callers defer,
|
|
11
|
+
* never fail hard). The registry path is async-only in the harness, so when a
|
|
12
|
+
* registry is present this resolves the session cwd directly (B4: the session
|
|
13
|
+
* cwd IS the workspace root when unregistered; a registered workspace resolves
|
|
14
|
+
* to the same cwd via resolveControlPlaneRoot's sync fallback).
|
|
15
|
+
*/
|
|
16
|
+
export declare function fsPolicyIntent(agent: {
|
|
17
|
+
session?: {
|
|
18
|
+
header?: {
|
|
19
|
+
cwd?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
} | null | undefined, registry?: WorkspaceRegistryLike | null, cwdFallback?: string): FsPolicyIntent | null;
|
|
23
|
+
/**
|
|
24
|
+
* Async variant for callers that CAN await (pre-step listener, routes): uses
|
|
25
|
+
* resolveControlPlaneRoot for registry canonicalization when a registry is
|
|
26
|
+
* present, falling back to the session cwd (B4).
|
|
27
|
+
*/
|
|
28
|
+
export declare function fsPolicyIntentAsync(agent: {
|
|
29
|
+
session?: {
|
|
30
|
+
header?: {
|
|
31
|
+
cwd?: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
} | null | undefined, registry?: WorkspaceRegistryLike | null, cwdFallback?: string): Promise<FsPolicyIntent | null>;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -3444,6 +3444,42 @@ function registerRecursiveCommand(ctx, recursive) {
|
|
|
3444
3444
|
});
|
|
3445
3445
|
}
|
|
3446
3446
|
//#endregion
|
|
3447
|
+
//#region src/fs-intent.ts
|
|
3448
|
+
/**
|
|
3449
|
+
* fs-intent.ts — filesystem-derived recursive intent (R5 policy-render fix).
|
|
3450
|
+
*
|
|
3451
|
+
* SP2 zero-emission retired the recursive/phase-intent SESSION EVENT emitter,
|
|
3452
|
+
* so detectTransitionIntent(events) is ALWAYS null and the recursive:policy
|
|
3453
|
+
* prompt section rendered ''. This module derives the SAME intent from the
|
|
3454
|
+
* filesystem instead: session cwd -> control-plane root -> enumerate runs ->
|
|
3455
|
+
* latest run -> current phase (foldRun). Pure read-only fs folding, zero
|
|
3456
|
+
* emission — consistent with the per-workspace design + pre-step listener.
|
|
3457
|
+
*/
|
|
3458
|
+
/**
|
|
3459
|
+
* Resolve the recursive policy intent for one agent from the FILESYSTEM.
|
|
3460
|
+
* Returns null when no control-plane root or run is present (callers defer,
|
|
3461
|
+
* never fail hard). The registry path is async-only in the harness, so when a
|
|
3462
|
+
* registry is present this resolves the session cwd directly (B4: the session
|
|
3463
|
+
* cwd IS the workspace root when unregistered; a registered workspace resolves
|
|
3464
|
+
* to the same cwd via resolveControlPlaneRoot's sync fallback).
|
|
3465
|
+
*/
|
|
3466
|
+
function fsPolicyIntent(agent, registry, cwdFallback) {
|
|
3467
|
+
const cwd = agent?.session?.header?.cwd ?? cwdFallback ?? null;
|
|
3468
|
+
if (!cwd) return null;
|
|
3469
|
+
const root = registry ? cwd : cwd;
|
|
3470
|
+
const runs = enumerateRuns(root);
|
|
3471
|
+
if (runs.length === 0) return null;
|
|
3472
|
+
const runId = runs[runs.length - 1];
|
|
3473
|
+
const runDir = join(root, ".recursive", "run", runId);
|
|
3474
|
+
if (!existsSync(runDir)) return null;
|
|
3475
|
+
const folded = foldRun(runDir, runId);
|
|
3476
|
+
return {
|
|
3477
|
+
worktreeRoot: root,
|
|
3478
|
+
runId,
|
|
3479
|
+
currentPhaseFile: folded.currentPhase ? folded.currentPhase.phaseName : null
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
//#endregion
|
|
3447
3483
|
//#region src/snapshot.ts
|
|
3448
3484
|
/** Resolve a run's lock facts for one artifact file (receipt first, status fallback). */
|
|
3449
3485
|
function lockFactsOf(runDir, artifact) {
|
|
@@ -3682,7 +3718,7 @@ function apply(ctx, config) {
|
|
|
3682
3718
|
ctx.effect(function* () {
|
|
3683
3719
|
const workspaceRegistry = ctx.get("workspaceRegistry");
|
|
3684
3720
|
const recursive = new RecursiveRuntime(ctx, {
|
|
3685
|
-
repoRoot: process.cwd(),
|
|
3721
|
+
repoRoot: config?.repoRoot ?? process.cwd(),
|
|
3686
3722
|
workspaceRegistry
|
|
3687
3723
|
});
|
|
3688
3724
|
const disposers = [
|
|
@@ -3702,14 +3738,11 @@ function apply(ctx, config) {
|
|
|
3702
3738
|
text: (context) => {
|
|
3703
3739
|
const agent = context?.agent;
|
|
3704
3740
|
if (!agent) return "";
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
const intent = detectTransitionIntent(events);
|
|
3708
|
-
if (!intent || intent.worktreeRoot === "") return "";
|
|
3741
|
+
const intent = fsPolicyIntent(agent, workspaceRegistry);
|
|
3742
|
+
if (!intent) return "";
|
|
3709
3743
|
return renderRecursivePolicy({
|
|
3710
3744
|
worktreeRoot: intent.worktreeRoot,
|
|
3711
3745
|
runId: intent.runId,
|
|
3712
|
-
folded: recursive.foldPhase(events),
|
|
3713
3746
|
config: recursive.enforcementConfig
|
|
3714
3747
|
});
|
|
3715
3748
|
}
|
package/lib/policy.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-recursive-mode",
|
|
3
3
|
"description": "recursive-mode workflow as a DeepSeek Harness bundle: RecursiveRuntime service + recursive_status tool",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,8 +11,8 @@ import { createRecursiveCloseoutTool } from './recursive_closeout.tool.ts'
|
|
|
11
11
|
import { createRecursiveScratchTool } from './recursive_scratch.tool.ts'
|
|
12
12
|
import { registerRecursiveCommand } from './commands.ts'
|
|
13
13
|
import { evaluateToolGuard } from './enforcement.ts'
|
|
14
|
-
import { detectTransitionIntent } from './lifecycle.ts'
|
|
15
14
|
import { renderRecursivePolicy } from './policy.ts'
|
|
15
|
+
import { fsPolicyIntent } from './fs-intent.ts'
|
|
16
16
|
import { snapshotWorkspace } from './snapshot.ts'
|
|
17
17
|
import { mountRecursiveRoutesOnce, makeRecursiveRoutes, type RecursiveRouteHost } from './live-route.ts'
|
|
18
18
|
import { enumerateRuns } from './bootstrap.ts'
|
|
@@ -83,7 +83,7 @@ export const inject = ['tools']
|
|
|
83
83
|
* repo/run work and NO session-event emission here: zero recursive/* events
|
|
84
84
|
* are ever appended (resume-crash fix), and the board reads the live fs route.
|
|
85
85
|
*/
|
|
86
|
-
export function apply(ctx: Context, config?: { shellOnly?: boolean }) {
|
|
86
|
+
export function apply(ctx: Context, config?: { shellOnly?: boolean; repoRoot?: string }) {
|
|
87
87
|
// R4 shell split (02-to-be-plan.addendum-r4-r2-mount-resolution.md): the
|
|
88
88
|
// global bare-name row in cordis.patch.yml mounts with config.shellOnly=true
|
|
89
89
|
// to expose ONLY the client bundle for client discovery. It must register
|
|
@@ -97,7 +97,7 @@ export function apply(ctx: Context, config?: { shellOnly?: boolean }) {
|
|
|
97
97
|
// property access requires inject and would fail boot when undeclared.
|
|
98
98
|
// Resolve the control-plane root strictly from the session agent's cwd.
|
|
99
99
|
const workspaceRegistry = ctx.get('workspaceRegistry') as never
|
|
100
|
-
const recursive = new RecursiveRuntime(ctx, { repoRoot: process.cwd(), workspaceRegistry })
|
|
100
|
+
const recursive = new RecursiveRuntime(ctx, { repoRoot: config?.repoRoot ?? process.cwd(), workspaceRegistry })
|
|
101
101
|
|
|
102
102
|
const disposers = [
|
|
103
103
|
ctx.tools.register(createRecursiveStatusTool(recursive)),
|
|
@@ -122,14 +122,16 @@ export function apply(ctx: Context, config?: { shellOnly?: boolean }) {
|
|
|
122
122
|
name: 'recursive:policy',
|
|
123
123
|
order: 55,
|
|
124
124
|
text: (context: unknown) => {
|
|
125
|
-
const agent = (context as { agent?: { session?: { header?: { cwd?: string }
|
|
125
|
+
const agent = (context as { agent?: { session?: { header?: { cwd?: string } } } } | undefined)?.agent
|
|
126
126
|
if (!agent) return ''
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
// SP3 R5 policy-render fix: derive intent from the FILESYSTEM, not the
|
|
128
|
+
// retired recursive/phase-intent session event (zero-emission removed
|
|
129
|
+
// the emitter, so detectTransitionIntent was ALWAYS null and this
|
|
130
|
+
// section rendered ''). Pure read-only fs folding; no recursive/*
|
|
131
|
+
// events are appended.
|
|
132
|
+
const intent = fsPolicyIntent(agent, workspaceRegistry as never)
|
|
133
|
+
if (!intent) return ''
|
|
134
|
+
return renderRecursivePolicy({ worktreeRoot: intent.worktreeRoot, runId: intent.runId, config: recursive.enforcementConfig })
|
|
133
135
|
},
|
|
134
136
|
}))
|
|
135
137
|
}
|
package/src/policy.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { getArtifactRequiredSections, AUDITED_PHASE_FILES, CURRENT_WORKFLOW_PROF
|
|
|
18
18
|
export interface PolicyContext {
|
|
19
19
|
worktreeRoot: string
|
|
20
20
|
runId: string
|
|
21
|
-
folded
|
|
21
|
+
folded?: RecursivePhaseState | null
|
|
22
22
|
config?: EnforcementConfig
|
|
23
23
|
}
|
|
24
24
|
|