@try-works/dsh-recursive-mode 0.1.17 → 0.2.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/lib/client.js +0 -1
- package/lib/git-context.d.ts +53 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +666 -20
- package/lib/init-templates.d.ts +4 -0
- package/lib/recursive_worktree.tool.d.ts +7 -0
- package/lib/runtime.d.ts +28 -1
- package/lib/ts-lint.d.ts +8 -0
- package/lib/worktree.d.ts +59 -0
- package/package.json +1 -1
- package/src/client/use-live.ts +5 -4
- package/src/commands.ts +46 -0
- package/src/git-context.ts +132 -0
- package/src/index.ts +3 -0
- package/src/init-templates.ts +15 -2
- package/src/recursive_init.tool.ts +9 -3
- package/src/recursive_worktree.tool.ts +54 -0
- package/src/runtime.ts +57 -6
- package/src/ts-lint.ts +31 -0
- package/src/worktree.ts +229 -0
package/lib/index.js
CHANGED
|
@@ -6,6 +6,154 @@ import { createHash } from "node:crypto";
|
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
|
7
7
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
//#region src/git-context.ts
|
|
10
|
+
/**
|
|
11
|
+
* git-context.ts — live git facts used for worktree + branch awareness (R?).
|
|
12
|
+
*
|
|
13
|
+
* Worktree/branch awareness goal: the plugin must know WHICH checkout and
|
|
14
|
+
* WHICH branch a run is executing in, so Phase 0 records an honest base-vs-
|
|
15
|
+
* worktree branch split and lint fails when the recorded context no longer
|
|
16
|
+
* matches live git state.
|
|
17
|
+
*
|
|
18
|
+
* Detection primitives:
|
|
19
|
+
* - `isLinkedWorktree` — a directory is a linked git worktree when its private
|
|
20
|
+
* git dir (`git rev-parse --git-dir`) differs from the common git dir
|
|
21
|
+
* (`git rev-parse --git-common-dir`). The main checkout has both equal.
|
|
22
|
+
* - `upstreamBranch` — the current branch's upstream target with the remote
|
|
23
|
+
* prefix stripped (origin/main -> main). Used to infer the promotion source
|
|
24
|
+
* branch for dev/stage/main workflows.
|
|
25
|
+
*
|
|
26
|
+
* Every accessor is total: on missing git or non-git dirs it returns null /
|
|
27
|
+
* false, never throws. Callers defer rather than fail hard.
|
|
28
|
+
*/
|
|
29
|
+
/** Run git in a repo dir; return trimmed stdout or null on any failure. */
|
|
30
|
+
function gitRun(repoRoot, ...args) {
|
|
31
|
+
try {
|
|
32
|
+
return execFileSync("git", args, {
|
|
33
|
+
cwd: repoRoot,
|
|
34
|
+
encoding: "utf8",
|
|
35
|
+
stdio: [
|
|
36
|
+
"ignore",
|
|
37
|
+
"pipe",
|
|
38
|
+
"ignore"
|
|
39
|
+
]
|
|
40
|
+
}).trim();
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** True when a non-equal git-dir vs git-common-dir marks a linked worktree. */
|
|
46
|
+
function isLinkedWorktree(gitDir, commonDir) {
|
|
47
|
+
if (!gitDir || !commonDir) return false;
|
|
48
|
+
return gitDir !== commonDir;
|
|
49
|
+
}
|
|
50
|
+
/** Strip a remote ref prefix (origin/main -> main, remotes/origin/main -> main). */
|
|
51
|
+
function stripRemotePrefix(ref) {
|
|
52
|
+
if (!ref) return null;
|
|
53
|
+
const short = ref.trim().replace(/^refs\/remotes\//, "").replace(/^[^/]+\//, "");
|
|
54
|
+
return short === "" ? null : short;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gather normalized git facts about `repoRoot`. Total: any git failure
|
|
58
|
+
* degrades to neutral values (false / null), never throws.
|
|
59
|
+
*/
|
|
60
|
+
function gitFacts(repoRoot) {
|
|
61
|
+
const gitDir = gitRun(repoRoot, "rev-parse", "--git-dir");
|
|
62
|
+
const commonDir = gitRun(repoRoot, "rev-parse", "--git-common-dir");
|
|
63
|
+
const headSha = gitRun(repoRoot, "rev-parse", "--verify", "HEAD^{commit}");
|
|
64
|
+
const branch = gitRun(repoRoot, "symbolic-ref", "--quiet", "--short", "HEAD");
|
|
65
|
+
const toplevel = gitRun(repoRoot, "rev-parse", "--show-toplevel");
|
|
66
|
+
const upstreamRef = gitRun(repoRoot, "rev-parse", "--abbrev-ref", "@{upstream}");
|
|
67
|
+
return {
|
|
68
|
+
headSha,
|
|
69
|
+
branch: branch || null,
|
|
70
|
+
detached: !branch,
|
|
71
|
+
isWorktree: isLinkedWorktree(gitDir, commonDir),
|
|
72
|
+
gitDir,
|
|
73
|
+
commonDir,
|
|
74
|
+
toplevel,
|
|
75
|
+
upstreamBranch: branch ? stripRemotePrefix(upstreamRef) : null
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the effective base branch for a checkout. In a linked worktree this
|
|
80
|
+
* is the branch the work is based on — best inferred from the upstream target
|
|
81
|
+
* (dev/stage/main promotion source) when one is configured; otherwise fall
|
|
82
|
+
* back to the current branch. Returns null only when no branch exists.
|
|
83
|
+
*/
|
|
84
|
+
function resolveBaseBranch(facts) {
|
|
85
|
+
if (facts.branch && facts.upstreamBranch && facts.upstreamBranch !== facts.branch) return facts.upstreamBranch;
|
|
86
|
+
return facts.branch;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Verify a worktree branch is (still) based on the recorded base branch.
|
|
90
|
+
* Returns { ok, reason }. Fails when the base branch no longer exists, or the
|
|
91
|
+
* worktree branch no longer contains it (merge-base --is-ancestor fails).
|
|
92
|
+
* A missing/unresolvable branch degrades to ok:false with a reason; a
|
|
93
|
+
* non-git dir degrades to ok:true (defer — nothing to verify).
|
|
94
|
+
*/
|
|
95
|
+
function verifyBranchBase(repoRoot, baseBranch, worktreeBranch) {
|
|
96
|
+
if (!baseBranch || !worktreeBranch) return {
|
|
97
|
+
ok: true,
|
|
98
|
+
reason: null
|
|
99
|
+
};
|
|
100
|
+
if (!gitRun(repoRoot, "rev-parse", "--git-dir")) return {
|
|
101
|
+
ok: true,
|
|
102
|
+
reason: null
|
|
103
|
+
};
|
|
104
|
+
if (!gitRun(repoRoot, "rev-parse", "--verify", `${baseBranch}^{commit}`)) return {
|
|
105
|
+
ok: false,
|
|
106
|
+
reason: `recorded base branch '${baseBranch}' does not resolve in this checkout`
|
|
107
|
+
};
|
|
108
|
+
if (!gitRun(repoRoot, "rev-parse", "--verify", `${worktreeBranch}^{commit}`)) return {
|
|
109
|
+
ok: false,
|
|
110
|
+
reason: `recorded worktree branch '${worktreeBranch}' does not resolve in this checkout`
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
execFileSync("git", [
|
|
114
|
+
"-C",
|
|
115
|
+
repoRoot,
|
|
116
|
+
"merge-base",
|
|
117
|
+
"--is-ancestor",
|
|
118
|
+
baseBranch,
|
|
119
|
+
worktreeBranch
|
|
120
|
+
], { stdio: [
|
|
121
|
+
"ignore",
|
|
122
|
+
"pipe",
|
|
123
|
+
"pipe"
|
|
124
|
+
] });
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
reason: null
|
|
128
|
+
};
|
|
129
|
+
} catch {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
reason: `worktree branch '${worktreeBranch}' is not based on recorded base branch '${baseBranch}'`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Verify the recorded worktree branch matches the live HEAD branch. */
|
|
137
|
+
function verifyWorktreeBranch(repoRoot, recordedBranch) {
|
|
138
|
+
if (!recordedBranch) return {
|
|
139
|
+
ok: true,
|
|
140
|
+
reason: null
|
|
141
|
+
};
|
|
142
|
+
const live = gitRun(repoRoot, "symbolic-ref", "--quiet", "--short", "HEAD");
|
|
143
|
+
if (!live) return {
|
|
144
|
+
ok: false,
|
|
145
|
+
reason: "HEAD is detached; expected branch " + recordedBranch
|
|
146
|
+
};
|
|
147
|
+
if (live !== recordedBranch) return {
|
|
148
|
+
ok: false,
|
|
149
|
+
reason: `checkout is on branch '${live}' but 00-worktree.md records '${recordedBranch}'`
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
reason: null
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
9
157
|
//#region src/phase-rules.ts
|
|
10
158
|
/**
|
|
11
159
|
* Phase rules (R5): canonical parity port of lint-recursive-run.py's
|
|
@@ -1048,6 +1196,24 @@ function getRunDiffBasis(runDir) {
|
|
|
1048
1196
|
notes: getMdFieldValue$1(source, "Diff basis notes") ?? getMdFieldValue$1(source, "Notes")
|
|
1049
1197
|
};
|
|
1050
1198
|
}
|
|
1199
|
+
/**
|
|
1200
|
+
* verify_recorded_branches: worktree + branch awareness at lint time. Compares
|
|
1201
|
+
* the branches recorded in 00-worktree.md against live git state. Returns a
|
|
1202
|
+
* list of FAIL messages (empty when consistent). Both the base branch and the
|
|
1203
|
+
* worktree branch must resolve; the worktree branch must match the live HEAD
|
|
1204
|
+
* branch, and it must actually be based on the recorded base branch.
|
|
1205
|
+
*/
|
|
1206
|
+
function verifyRecordedBranches(repoRoot, diffBasis, runDir) {
|
|
1207
|
+
const fails = [];
|
|
1208
|
+
const baseBranch = trimMdValue$1(diffBasis.base_branch ?? "");
|
|
1209
|
+
const worktreeBranch = trimMdValue$1(diffBasis.worktree_branch ?? "");
|
|
1210
|
+
if (!baseBranch && !worktreeBranch) return fails;
|
|
1211
|
+
const wtCheck = verifyWorktreeBranch(repoRoot, worktreeBranch || null);
|
|
1212
|
+
if (!wtCheck.ok && wtCheck.reason) fails.push(wtCheck.reason);
|
|
1213
|
+
const baseCheck = verifyBranchBase(repoRoot, baseBranch || null, worktreeBranch || null);
|
|
1214
|
+
if (!baseCheck.ok && baseCheck.reason) fails.push(baseCheck.reason);
|
|
1215
|
+
return fails;
|
|
1216
|
+
}
|
|
1051
1217
|
/** normalize_diff_basis: validate + compute the executable diff basis. */
|
|
1052
1218
|
function normalizeDiffBasis(repoRoot, diffBasis) {
|
|
1053
1219
|
const baselineType = normalizeBaselineType(diffBasis.baseline_type);
|
|
@@ -2612,6 +2778,11 @@ function lintRun(repoRoot, runId) {
|
|
|
2612
2778
|
let diffBasisError = null;
|
|
2613
2779
|
if (STRICT_WORKFLOW_PROFILES$1.has(workflowProfile)) {
|
|
2614
2780
|
const diffBasis = getRunDiffBasis(runDir);
|
|
2781
|
+
const branchFails = verifyRecordedBranches(root, diffBasis, runDir);
|
|
2782
|
+
for (const message of branchFails) {
|
|
2783
|
+
totalFail += 1;
|
|
2784
|
+
writeIssue("FAIL", runDir, message);
|
|
2785
|
+
}
|
|
2615
2786
|
if (diffBasis.baseline_reference || diffBasis.normalized_baseline) {
|
|
2616
2787
|
const [rawChanged, gitError] = getGitChangedFiles(root, diffBasis);
|
|
2617
2788
|
if (gitError) {
|
|
@@ -2751,12 +2922,9 @@ function detectGitContext(repoRoot) {
|
|
|
2751
2922
|
context: {},
|
|
2752
2923
|
error: "Unable to resolve HEAD commit for Phase 0 diff basis prefill: git rev-parse returned no output"
|
|
2753
2924
|
};
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
"--short",
|
|
2758
|
-
"HEAD"
|
|
2759
|
-
]) || "(detached HEAD)";
|
|
2925
|
+
const facts = gitFacts(repoRoot);
|
|
2926
|
+
const branch = facts.branch ?? "(detached HEAD)";
|
|
2927
|
+
const baseBranch = resolveBaseBranch(facts) ?? branch;
|
|
2760
2928
|
return {
|
|
2761
2929
|
context: {
|
|
2762
2930
|
baselineType: "local commit",
|
|
@@ -2765,9 +2933,11 @@ function detectGitContext(repoRoot) {
|
|
|
2765
2933
|
normalizedBaseline: headSha,
|
|
2766
2934
|
normalizedComparison: "working-tree",
|
|
2767
2935
|
normalizedDiffCommand: "git diff --name-only " + headSha,
|
|
2768
|
-
baseBranch
|
|
2936
|
+
baseBranch,
|
|
2769
2937
|
worktreeBranch: branch,
|
|
2770
2938
|
baseCommit: headSha,
|
|
2939
|
+
isWorktree: facts.isWorktree,
|
|
2940
|
+
upstreamBranch: facts.upstreamBranch,
|
|
2771
2941
|
notes: "recursive-init prefilled this executable diff basis from the current HEAD commit. If Phase 0 later changes the chosen baseline, update every diff-basis field and rerun lint before locking."
|
|
2772
2942
|
},
|
|
2773
2943
|
error: null
|
|
@@ -4687,6 +4857,272 @@ function renderRecursivePolicy(context) {
|
|
|
4687
4857
|
return lines.join("\n");
|
|
4688
4858
|
}
|
|
4689
4859
|
//#endregion
|
|
4860
|
+
//#region src/worktree.ts
|
|
4861
|
+
/**
|
|
4862
|
+
* worktree.ts — drive linked-worktree creation and branch promotion.
|
|
4863
|
+
*
|
|
4864
|
+
* Worktree + branch awareness, operational half (R?): after detection
|
|
4865
|
+
* (git-context.ts) and lint validation (ts-lint.ts), this module performs the
|
|
4866
|
+
* git operations that actually realize the dev/stage/main worktree workflow:
|
|
4867
|
+
*
|
|
4868
|
+
* - `createLinkedWorktree`: create a linked worktree at
|
|
4869
|
+
* `<repoRoot>/.worktrees/<runId>/` on a worktree branch based on a base
|
|
4870
|
+
* branch (default: the upstream/promotion source branch).
|
|
4871
|
+
* - `promoteBranch`: fast-forward one branch into a target promotion stage
|
|
4872
|
+
* (feature -> dev -> stage -> main). The promotion is verified to be a
|
|
4873
|
+
* fast-forward first (the target must be an ancestor of the source) and
|
|
4874
|
+
* then applied where it is safe:
|
|
4875
|
+
* - if the target branch is checked out in a worktree, `git merge
|
|
4876
|
+
* --ff-only` runs THERE (safe: moves the branch and its working tree
|
|
4877
|
+
* forward together, never a merge commit);
|
|
4878
|
+
* - otherwise the branch ref is updated directly.
|
|
4879
|
+
* It never rewrites history and never creates a merge commit.
|
|
4880
|
+
*
|
|
4881
|
+
* Both operations are explicit, workspace-scoped, and total: they return
|
|
4882
|
+
* { ok, ... } rather than corrupting git state. No operation ever touches a
|
|
4883
|
+
* branch other than the explicitly requested one.
|
|
4884
|
+
*/
|
|
4885
|
+
/** Run git; throw on failure (callers catch to turn into a result). */
|
|
4886
|
+
function gitThrow(repoRoot, ...args) {
|
|
4887
|
+
return execFileSync("git", args, {
|
|
4888
|
+
cwd: repoRoot,
|
|
4889
|
+
encoding: "utf8",
|
|
4890
|
+
stdio: [
|
|
4891
|
+
"ignore",
|
|
4892
|
+
"pipe",
|
|
4893
|
+
"pipe"
|
|
4894
|
+
]
|
|
4895
|
+
}).trim();
|
|
4896
|
+
}
|
|
4897
|
+
/** Resolve the current branch of a repo dir (short name or null when detached). */
|
|
4898
|
+
function currentBranch(repoRoot) {
|
|
4899
|
+
try {
|
|
4900
|
+
return gitThrow(repoRoot, "symbolic-ref", "--quiet", "--short", "HEAD");
|
|
4901
|
+
} catch {
|
|
4902
|
+
return null;
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
/** Default worktree branch name for a run. */
|
|
4906
|
+
function defaultWorktreeBranch(runId) {
|
|
4907
|
+
return "recursive/" + runId;
|
|
4908
|
+
}
|
|
4909
|
+
/** List linked worktrees (path + branch) for a repo root. */
|
|
4910
|
+
function listWorktrees(repoRoot) {
|
|
4911
|
+
let out;
|
|
4912
|
+
try {
|
|
4913
|
+
out = gitThrow(repoRoot, "worktree", "list", "--porcelain");
|
|
4914
|
+
} catch {
|
|
4915
|
+
return [];
|
|
4916
|
+
}
|
|
4917
|
+
const result = [];
|
|
4918
|
+
let current = null;
|
|
4919
|
+
for (const line of out.split(/\r?\n/)) {
|
|
4920
|
+
const trimmed = line.trim();
|
|
4921
|
+
if (!trimmed) {
|
|
4922
|
+
if (current) {
|
|
4923
|
+
result.push(current);
|
|
4924
|
+
current = null;
|
|
4925
|
+
}
|
|
4926
|
+
continue;
|
|
4927
|
+
}
|
|
4928
|
+
if (trimmed.startsWith("worktree ")) current = {
|
|
4929
|
+
path: trimmed.slice(9),
|
|
4930
|
+
branch: null,
|
|
4931
|
+
detached: false
|
|
4932
|
+
};
|
|
4933
|
+
else if (trimmed.startsWith("branch refs/heads/") && current) current.branch = trimmed.slice(18);
|
|
4934
|
+
else if (trimmed === "detached" && current) current.detached = true;
|
|
4935
|
+
}
|
|
4936
|
+
if (current) result.push(current);
|
|
4937
|
+
return result;
|
|
4938
|
+
}
|
|
4939
|
+
/**
|
|
4940
|
+
* Find the worktree path where `branch` is currently checked out, or null.
|
|
4941
|
+
* Parses `git worktree list --porcelain` (block per worktree with a
|
|
4942
|
+
* `branch refs/heads/<name>` line).
|
|
4943
|
+
*/
|
|
4944
|
+
function findWorktreeForBranch(repoRoot, branch) {
|
|
4945
|
+
let out;
|
|
4946
|
+
try {
|
|
4947
|
+
out = gitThrow(repoRoot, "worktree", "list", "--porcelain");
|
|
4948
|
+
} catch {
|
|
4949
|
+
return null;
|
|
4950
|
+
}
|
|
4951
|
+
let currentPath = null;
|
|
4952
|
+
for (const line of out.split(/\r?\n/)) {
|
|
4953
|
+
const trimmed = line.trim();
|
|
4954
|
+
if (!trimmed) {
|
|
4955
|
+
currentPath = null;
|
|
4956
|
+
continue;
|
|
4957
|
+
}
|
|
4958
|
+
if (trimmed.startsWith("worktree ")) currentPath = trimmed.slice(9);
|
|
4959
|
+
else if (trimmed.startsWith("branch refs/heads/")) {
|
|
4960
|
+
if (trimmed.slice(18) === branch && currentPath) return currentPath;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
return null;
|
|
4964
|
+
}
|
|
4965
|
+
/**
|
|
4966
|
+
* Create a linked worktree at `.worktrees/<runId>/` for a run. The worktree
|
|
4967
|
+
* branch is cut from `baseBranch` (default: the current HEAD branch). Returns
|
|
4968
|
+
* the created worktree dir + branch. No-op-safe: if the worktree dir already
|
|
4969
|
+
* exists, returns ok with the existing dir. Refuses to create a worktree when
|
|
4970
|
+
* the run directory already exists (prevents clobbering an in-progress run).
|
|
4971
|
+
*/
|
|
4972
|
+
function createLinkedWorktree(opts) {
|
|
4973
|
+
const { repoRoot, runId } = opts;
|
|
4974
|
+
const baseBranch = opts.baseBranch ?? currentBranch(repoRoot) ?? "main";
|
|
4975
|
+
const worktreeBranch = opts.worktreeBranch ?? defaultWorktreeBranch(runId);
|
|
4976
|
+
const worktreeDir = join(repoRoot, ".worktrees", runId);
|
|
4977
|
+
if (existsSync(worktreeDir)) return {
|
|
4978
|
+
ok: true,
|
|
4979
|
+
worktreeDir,
|
|
4980
|
+
worktreeBranch,
|
|
4981
|
+
baseBranch,
|
|
4982
|
+
error: void 0
|
|
4983
|
+
};
|
|
4984
|
+
const runDir = join(repoRoot, ".recursive", "run", runId);
|
|
4985
|
+
if (existsSync(runDir)) return {
|
|
4986
|
+
ok: false,
|
|
4987
|
+
worktreeDir,
|
|
4988
|
+
worktreeBranch,
|
|
4989
|
+
baseBranch,
|
|
4990
|
+
error: "run directory already exists: " + runDir + " (refusing to create a worktree for an existing run)"
|
|
4991
|
+
};
|
|
4992
|
+
try {
|
|
4993
|
+
gitThrow(repoRoot, "rev-parse", "--verify", baseBranch + "^{commit}");
|
|
4994
|
+
} catch {
|
|
4995
|
+
return {
|
|
4996
|
+
ok: false,
|
|
4997
|
+
worktreeDir,
|
|
4998
|
+
worktreeBranch,
|
|
4999
|
+
baseBranch,
|
|
5000
|
+
error: "base branch does not resolve: " + baseBranch
|
|
5001
|
+
};
|
|
5002
|
+
}
|
|
5003
|
+
try {
|
|
5004
|
+
gitThrow(repoRoot, "worktree", "add", "-b", worktreeBranch, worktreeDir, baseBranch);
|
|
5005
|
+
} catch (err) {
|
|
5006
|
+
return {
|
|
5007
|
+
ok: false,
|
|
5008
|
+
worktreeDir,
|
|
5009
|
+
worktreeBranch,
|
|
5010
|
+
baseBranch,
|
|
5011
|
+
error: "git worktree add failed: " + err.message
|
|
5012
|
+
};
|
|
5013
|
+
}
|
|
5014
|
+
return {
|
|
5015
|
+
ok: true,
|
|
5016
|
+
worktreeDir,
|
|
5017
|
+
worktreeBranch,
|
|
5018
|
+
baseBranch
|
|
5019
|
+
};
|
|
5020
|
+
}
|
|
5021
|
+
/**
|
|
5022
|
+
* Fast-forward `toBranch` to `fromBranch` (promotion up the dev/stage/main
|
|
5023
|
+
* chain). Returns ok:false when the promotion is not a pure fast-forward
|
|
5024
|
+
* (would require a merge commit) or when the from/to branches are missing.
|
|
5025
|
+
*/
|
|
5026
|
+
function promoteBranch(opts) {
|
|
5027
|
+
const { repoRoot, fromBranch, toBranch } = opts;
|
|
5028
|
+
if (fromBranch === toBranch) return {
|
|
5029
|
+
ok: false,
|
|
5030
|
+
fromBranch,
|
|
5031
|
+
toBranch,
|
|
5032
|
+
action: "fast-forward",
|
|
5033
|
+
error: "from and to branches are identical: " + fromBranch
|
|
5034
|
+
};
|
|
5035
|
+
let fromSha = null;
|
|
5036
|
+
try {
|
|
5037
|
+
fromSha = gitThrow(repoRoot, "rev-parse", "--verify", fromBranch + "^{commit}");
|
|
5038
|
+
} catch {}
|
|
5039
|
+
if (!fromSha) return {
|
|
5040
|
+
ok: false,
|
|
5041
|
+
fromBranch,
|
|
5042
|
+
toBranch,
|
|
5043
|
+
action: "fast-forward",
|
|
5044
|
+
error: "from branch does not resolve: " + fromBranch
|
|
5045
|
+
};
|
|
5046
|
+
let toSha = null;
|
|
5047
|
+
try {
|
|
5048
|
+
toSha = gitThrow(repoRoot, "rev-parse", "--verify", toBranch + "^{commit}");
|
|
5049
|
+
} catch {}
|
|
5050
|
+
if (!toSha) try {
|
|
5051
|
+
gitThrow(repoRoot, "branch", toBranch, fromSha);
|
|
5052
|
+
return {
|
|
5053
|
+
ok: true,
|
|
5054
|
+
fromBranch,
|
|
5055
|
+
toBranch,
|
|
5056
|
+
action: "created"
|
|
5057
|
+
};
|
|
5058
|
+
} catch (err) {
|
|
5059
|
+
return {
|
|
5060
|
+
ok: false,
|
|
5061
|
+
fromBranch,
|
|
5062
|
+
toBranch,
|
|
5063
|
+
action: "created",
|
|
5064
|
+
error: "git branch failed: " + err.message
|
|
5065
|
+
};
|
|
5066
|
+
}
|
|
5067
|
+
try {
|
|
5068
|
+
execFileSync("git", [
|
|
5069
|
+
"-C",
|
|
5070
|
+
repoRoot,
|
|
5071
|
+
"merge-base",
|
|
5072
|
+
"--is-ancestor",
|
|
5073
|
+
toBranch,
|
|
5074
|
+
fromBranch
|
|
5075
|
+
], { stdio: [
|
|
5076
|
+
"ignore",
|
|
5077
|
+
"pipe",
|
|
5078
|
+
"pipe"
|
|
5079
|
+
] });
|
|
5080
|
+
} catch {
|
|
5081
|
+
return {
|
|
5082
|
+
ok: false,
|
|
5083
|
+
fromBranch,
|
|
5084
|
+
toBranch,
|
|
5085
|
+
action: "fast-forward",
|
|
5086
|
+
error: "promotion is not a fast-forward: " + toBranch + " is not an ancestor of " + fromBranch
|
|
5087
|
+
};
|
|
5088
|
+
}
|
|
5089
|
+
const checkedOutIn = findWorktreeForBranch(repoRoot, toBranch);
|
|
5090
|
+
if (checkedOutIn) try {
|
|
5091
|
+
gitThrow(checkedOutIn, "merge", "--ff-only", fromBranch);
|
|
5092
|
+
return {
|
|
5093
|
+
ok: true,
|
|
5094
|
+
fromBranch,
|
|
5095
|
+
toBranch,
|
|
5096
|
+
action: "fast-forward"
|
|
5097
|
+
};
|
|
5098
|
+
} catch (err) {
|
|
5099
|
+
return {
|
|
5100
|
+
ok: false,
|
|
5101
|
+
fromBranch,
|
|
5102
|
+
toBranch,
|
|
5103
|
+
action: "fast-forward",
|
|
5104
|
+
error: "fast-forward merge in " + checkedOutIn + " failed: " + err.message
|
|
5105
|
+
};
|
|
5106
|
+
}
|
|
5107
|
+
try {
|
|
5108
|
+
gitThrow(repoRoot, "update-ref", "refs/heads/" + toBranch, fromSha);
|
|
5109
|
+
return {
|
|
5110
|
+
ok: true,
|
|
5111
|
+
fromBranch,
|
|
5112
|
+
toBranch,
|
|
5113
|
+
action: "fast-forward"
|
|
5114
|
+
};
|
|
5115
|
+
} catch (err) {
|
|
5116
|
+
return {
|
|
5117
|
+
ok: false,
|
|
5118
|
+
fromBranch,
|
|
5119
|
+
toBranch,
|
|
5120
|
+
action: "fast-forward",
|
|
5121
|
+
error: "git update-ref failed: " + err.message
|
|
5122
|
+
};
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
//#endregion
|
|
4690
5126
|
//#region src/runtime.ts
|
|
4691
5127
|
var RecursiveRuntime = class extends Service {
|
|
4692
5128
|
/** Recursive-mode runtime service. Owns run-state reads + lock/init/lint operations. */
|
|
@@ -4953,11 +5389,28 @@ var RecursiveRuntime = class extends Service {
|
|
|
4953
5389
|
* required section (get_artifact_required_sections) + TODO + FAIL gates.
|
|
4954
5390
|
* Also scaffolds addenda/subagents/router-prompts/evidence dirs. Returns the
|
|
4955
5391
|
* run dir + created artifacts.
|
|
5392
|
+
*
|
|
5393
|
+
* When `opts.createWorktree` is true, a linked worktree is first created at
|
|
5394
|
+
* `.worktrees/<runId>/` and the run is scaffolded INSIDE it (per the
|
|
5395
|
+
* "all subsequent phases execute in worktree context" rule). The worktree
|
|
5396
|
+
* branch defaults to `recursive/<runId>` and is cut from `opts.baseBranch`
|
|
5397
|
+
* (default: current HEAD branch of the root checkout).
|
|
4956
5398
|
*/
|
|
4957
|
-
async initRun(runId, agent) {
|
|
5399
|
+
async initRun(runId, agent, opts) {
|
|
4958
5400
|
const root = await this.resolveRootFor(agent);
|
|
4959
5401
|
if (!root) throw new Error("cannot resolve workspace control-plane root for this session");
|
|
4960
|
-
|
|
5402
|
+
let scaffoldRoot = root;
|
|
5403
|
+
let worktree;
|
|
5404
|
+
if (opts?.createWorktree) {
|
|
5405
|
+
worktree = createLinkedWorktree({
|
|
5406
|
+
repoRoot: root,
|
|
5407
|
+
runId,
|
|
5408
|
+
baseBranch: opts.baseBranch
|
|
5409
|
+
});
|
|
5410
|
+
if (!worktree.ok) throw new Error(worktree.error ?? "worktree create failed");
|
|
5411
|
+
scaffoldRoot = worktree.worktreeDir;
|
|
5412
|
+
}
|
|
5413
|
+
const runDir = join(scaffoldRoot, ".recursive", "run", runId);
|
|
4961
5414
|
mkdirSync(runDir, { recursive: true });
|
|
4962
5415
|
const created = [];
|
|
4963
5416
|
const existing = [];
|
|
@@ -4970,8 +5423,8 @@ var RecursiveRuntime = class extends Service {
|
|
|
4970
5423
|
mkdirSync(p, { recursive: true });
|
|
4971
5424
|
created.push(dir + "/");
|
|
4972
5425
|
}
|
|
4973
|
-
const { context: gitContext, error: prefillError } = detectGitContext(
|
|
4974
|
-
const phase0 = [["00-requirements.md", requirementsContent(runId, "feature", "")], ["00-worktree.md", worktreeContent(runId,
|
|
5426
|
+
const { context: gitContext, error: prefillError } = detectGitContext(scaffoldRoot);
|
|
5427
|
+
const phase0 = [["00-requirements.md", requirementsContent(runId, "feature", "")], ["00-worktree.md", worktreeContent(runId, scaffoldRoot, gitContext, prefillError)]];
|
|
4975
5428
|
for (const [file, content] of phase0) {
|
|
4976
5429
|
const path = join(runDir, file);
|
|
4977
5430
|
if (existsSync(path)) {
|
|
@@ -5001,12 +5454,52 @@ var RecursiveRuntime = class extends Service {
|
|
|
5001
5454
|
writeFileSync(path, laterPhaseContent(runId, file), "utf8");
|
|
5002
5455
|
created.push(file);
|
|
5003
5456
|
}
|
|
5004
|
-
|
|
5457
|
+
const result = {
|
|
5005
5458
|
runDir,
|
|
5006
5459
|
runId,
|
|
5007
5460
|
created,
|
|
5008
5461
|
existing
|
|
5009
5462
|
};
|
|
5463
|
+
if (worktree) result.worktree = worktree;
|
|
5464
|
+
return result;
|
|
5465
|
+
}
|
|
5466
|
+
/**
|
|
5467
|
+
* Create a linked worktree for a run under the given workspace root. The
|
|
5468
|
+
* worktree branch defaults to `recursive/<runId>` and is cut from the given
|
|
5469
|
+
* base branch (default: the current HEAD branch of the root checkout).
|
|
5470
|
+
* Refuses to create over an existing run directory. Workspace-scoped.
|
|
5471
|
+
*/
|
|
5472
|
+
createRunWorktree(root, runId, baseBranch) {
|
|
5473
|
+
return createLinkedWorktree({
|
|
5474
|
+
repoRoot: root,
|
|
5475
|
+
runId,
|
|
5476
|
+
baseBranch
|
|
5477
|
+
});
|
|
5478
|
+
}
|
|
5479
|
+
/**
|
|
5480
|
+
* Promote a branch up the dev/stage/main chain (fast-forward). Workspace-scoped.
|
|
5481
|
+
*/
|
|
5482
|
+
promoteRunBranch(root, fromBranch, toBranch) {
|
|
5483
|
+
return promoteBranch({
|
|
5484
|
+
repoRoot: root,
|
|
5485
|
+
fromBranch,
|
|
5486
|
+
toBranch
|
|
5487
|
+
});
|
|
5488
|
+
}
|
|
5489
|
+
/**
|
|
5490
|
+
* Worktree + branch status for a workspace root: the linked worktrees,
|
|
5491
|
+
* which branch each is on, and the current checkout's base/upstream context.
|
|
5492
|
+
*/
|
|
5493
|
+
worktreeStatus(root) {
|
|
5494
|
+
const facts = gitFacts(root);
|
|
5495
|
+
const worktrees = listWorktrees(root);
|
|
5496
|
+
return {
|
|
5497
|
+
root,
|
|
5498
|
+
isWorktree: facts.isWorktree,
|
|
5499
|
+
branch: facts.branch,
|
|
5500
|
+
upstreamBranch: facts.upstreamBranch,
|
|
5501
|
+
worktrees
|
|
5502
|
+
};
|
|
5010
5503
|
}
|
|
5011
5504
|
/**
|
|
5012
5505
|
* Lock a DRAFT artifact (or reopen a LOCKED one). Validates prerequisites;
|
|
@@ -5232,11 +5725,21 @@ function createRecursiveStatusTool(recursive) {
|
|
|
5232
5725
|
function createRecursiveInitTool(recursive) {
|
|
5233
5726
|
return defineTool({
|
|
5234
5727
|
name: "recursive_init",
|
|
5235
|
-
description: "Scaffold a new recursive-mode run directory (or ensure an existing one) with stub artifact headers. Delegates to the RecursiveRuntime service (no duplicated scaffolding logic).",
|
|
5236
|
-
parameters: {
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5728
|
+
description: "Scaffold a new recursive-mode run directory (or ensure an existing one) with stub artifact headers. Delegates to the RecursiveRuntime service (no duplicated scaffolding logic). When createWorktree is true, a linked worktree is created first and the run is scaffolded inside it.",
|
|
5729
|
+
parameters: {
|
|
5730
|
+
runId: {
|
|
5731
|
+
type: "string",
|
|
5732
|
+
description: "Run id (e.g. 03-something). Required."
|
|
5733
|
+
},
|
|
5734
|
+
createWorktree: {
|
|
5735
|
+
type: "boolean",
|
|
5736
|
+
description: "If true, create a linked worktree at .worktrees/<runId>/ and scaffold the run inside it (default: false)."
|
|
5737
|
+
},
|
|
5738
|
+
baseBranch: {
|
|
5739
|
+
type: "string",
|
|
5740
|
+
description: "Base branch the worktree branch is cut from (default: current HEAD branch). Only used when createWorktree is true."
|
|
5741
|
+
}
|
|
5742
|
+
},
|
|
5240
5743
|
output: {
|
|
5241
5744
|
schema: { type: "json" },
|
|
5242
5745
|
render: (_args, value) => [{
|
|
@@ -5247,7 +5750,10 @@ function createRecursiveInitTool(recursive) {
|
|
|
5247
5750
|
async execute(args, exec) {
|
|
5248
5751
|
if (!args.runId || args.runId.trim() === "") return { error: "runId is required" };
|
|
5249
5752
|
try {
|
|
5250
|
-
return await recursive.initRun(args.runId.trim(), exec.agent
|
|
5753
|
+
return await recursive.initRun(args.runId.trim(), exec.agent, {
|
|
5754
|
+
createWorktree: args.createWorktree === true,
|
|
5755
|
+
baseBranch: args.baseBranch?.trim() || void 0
|
|
5756
|
+
});
|
|
5251
5757
|
} catch (err) {
|
|
5252
5758
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
5253
5759
|
}
|
|
@@ -5411,6 +5917,63 @@ function createRecursiveScratchTool(recursive) {
|
|
|
5411
5917
|
});
|
|
5412
5918
|
}
|
|
5413
5919
|
//#endregion
|
|
5920
|
+
//#region src/recursive_worktree.tool.ts
|
|
5921
|
+
/**
|
|
5922
|
+
* `recursive_worktree` — create a linked git worktree for a run and/or
|
|
5923
|
+
* promote a branch up the dev/stage/main chain. Workspace-scoped: the
|
|
5924
|
+
* operations run under the SESSION's control-plane root only.
|
|
5925
|
+
*/
|
|
5926
|
+
function createRecursiveWorktreeTool(recursive) {
|
|
5927
|
+
return defineTool({
|
|
5928
|
+
name: "recursive_worktree",
|
|
5929
|
+
description: "Create a linked git worktree for a recursive-mode run and/or promote a branch up the dev/stage/main chain. Workspace-scoped under the current session workspace.",
|
|
5930
|
+
parameters: {
|
|
5931
|
+
runId: {
|
|
5932
|
+
type: "string",
|
|
5933
|
+
description: "Run id the worktree is created for (e.g. 03-something). Required for create."
|
|
5934
|
+
},
|
|
5935
|
+
action: {
|
|
5936
|
+
type: "string",
|
|
5937
|
+
description: "create | promote | status. Default: create."
|
|
5938
|
+
},
|
|
5939
|
+
fromBranch: {
|
|
5940
|
+
type: "string",
|
|
5941
|
+
description: "Source branch for a promote action (the branch holding the new commits)."
|
|
5942
|
+
},
|
|
5943
|
+
toBranch: {
|
|
5944
|
+
type: "string",
|
|
5945
|
+
description: "Promotion target branch for a promote action (feature -> dev -> stage -> main)."
|
|
5946
|
+
},
|
|
5947
|
+
baseBranch: {
|
|
5948
|
+
type: "string",
|
|
5949
|
+
description: "Base branch the worktree branch is cut from (default: current HEAD branch)."
|
|
5950
|
+
}
|
|
5951
|
+
},
|
|
5952
|
+
output: {
|
|
5953
|
+
schema: { type: "json" },
|
|
5954
|
+
render: (_args, value) => [{
|
|
5955
|
+
type: "text",
|
|
5956
|
+
text: JSON.stringify(value, null, 2)
|
|
5957
|
+
}]
|
|
5958
|
+
},
|
|
5959
|
+
async execute(args, exec) {
|
|
5960
|
+
const action = args.action ?? "create";
|
|
5961
|
+
const root = await recursive.resolveRootFor(exec.agent);
|
|
5962
|
+
if (!root) return { error: "session is not attached to a registered workspace (cannot resolve control-plane root)" };
|
|
5963
|
+
if (action === "create") {
|
|
5964
|
+
if (!args.runId || args.runId.trim() === "") return { error: "runId is required for create" };
|
|
5965
|
+
return recursive.createRunWorktree(root, args.runId.trim(), args.baseBranch?.trim() || void 0);
|
|
5966
|
+
}
|
|
5967
|
+
if (action === "promote") {
|
|
5968
|
+
if (!args.fromBranch || !args.toBranch) return { error: "fromBranch and toBranch are required for promote" };
|
|
5969
|
+
return recursive.promoteRunBranch(root, args.fromBranch.trim(), args.toBranch.trim());
|
|
5970
|
+
}
|
|
5971
|
+
if (action === "status") return recursive.worktreeStatus(root);
|
|
5972
|
+
return { error: "action must be create | promote | status" };
|
|
5973
|
+
}
|
|
5974
|
+
});
|
|
5975
|
+
}
|
|
5976
|
+
//#endregion
|
|
5414
5977
|
//#region src/bootstrap.ts
|
|
5415
5978
|
/**
|
|
5416
5979
|
* Idempotent scaffold installer (R3). TS port of install-recursive-mode.py's
|
|
@@ -5851,6 +6414,46 @@ function executeRecursiveCommand(root, rawInput) {
|
|
|
5851
6414
|
text: "closeout scaffolded for " + runId + " phase " + phase
|
|
5852
6415
|
};
|
|
5853
6416
|
}
|
|
6417
|
+
case "worktree": {
|
|
6418
|
+
const parts = arg.trim().split(/\s+/);
|
|
6419
|
+
const op = parts[0] ?? "";
|
|
6420
|
+
if (op === "status") return {
|
|
6421
|
+
kind: "success",
|
|
6422
|
+
text: "worktree status for workspace " + root
|
|
6423
|
+
};
|
|
6424
|
+
if (op === "create") {
|
|
6425
|
+
const runId = parts[1] ?? "";
|
|
6426
|
+
if (!runId) return {
|
|
6427
|
+
kind: "error",
|
|
6428
|
+
text: "worktree create requires a run id"
|
|
6429
|
+
};
|
|
6430
|
+
const runDir = join(runRoot, runId);
|
|
6431
|
+
if (existsSync(runDir)) return {
|
|
6432
|
+
kind: "error",
|
|
6433
|
+
text: "Run already exists in this workspace: " + runId
|
|
6434
|
+
};
|
|
6435
|
+
return {
|
|
6436
|
+
kind: "success",
|
|
6437
|
+
text: "worktree created for run " + runId + " at .worktrees/" + runId
|
|
6438
|
+
};
|
|
6439
|
+
}
|
|
6440
|
+
if (op === "promote") {
|
|
6441
|
+
const fromBranch = parts[1] ?? "";
|
|
6442
|
+
const toBranch = parts[2] ?? "";
|
|
6443
|
+
if (!fromBranch || !toBranch) return {
|
|
6444
|
+
kind: "error",
|
|
6445
|
+
text: "worktree promote requires <from> <to>"
|
|
6446
|
+
};
|
|
6447
|
+
return {
|
|
6448
|
+
kind: "success",
|
|
6449
|
+
text: "promoted " + fromBranch + " -> " + toBranch
|
|
6450
|
+
};
|
|
6451
|
+
}
|
|
6452
|
+
return {
|
|
6453
|
+
kind: "error",
|
|
6454
|
+
text: "worktree requires create|promote|status"
|
|
6455
|
+
};
|
|
6456
|
+
}
|
|
5854
6457
|
case "scratch": {
|
|
5855
6458
|
const runId = arg.trim();
|
|
5856
6459
|
if (!runId) return {
|
|
@@ -5916,6 +6519,48 @@ function registerRecursiveCommand(ctx, recursive) {
|
|
|
5916
6519
|
};
|
|
5917
6520
|
}
|
|
5918
6521
|
}
|
|
6522
|
+
if (verb === "worktree" && arg) {
|
|
6523
|
+
const parts = arg.trim().split(/\s+/);
|
|
6524
|
+
const op = parts[0] ?? "";
|
|
6525
|
+
if (op === "create") {
|
|
6526
|
+
const runId = parts[1] ?? "";
|
|
6527
|
+
const baseBranch = arg.match(/--base\s+(\S+)/)?.[1];
|
|
6528
|
+
if (!runId) return {
|
|
6529
|
+
kind: "error",
|
|
6530
|
+
text: "worktree create requires a run id"
|
|
6531
|
+
};
|
|
6532
|
+
const result = recursive.createRunWorktree(root, runId, baseBranch);
|
|
6533
|
+
if (!result.ok) return {
|
|
6534
|
+
kind: "error",
|
|
6535
|
+
text: result.error ?? "worktree create failed"
|
|
6536
|
+
};
|
|
6537
|
+
return {
|
|
6538
|
+
kind: "success",
|
|
6539
|
+
text: "worktree created: " + JSON.stringify(result)
|
|
6540
|
+
};
|
|
6541
|
+
}
|
|
6542
|
+
if (op === "promote") {
|
|
6543
|
+
const fromBranch = parts[1] ?? "";
|
|
6544
|
+
const toBranch = parts[2] ?? "";
|
|
6545
|
+
if (!fromBranch || !toBranch) return {
|
|
6546
|
+
kind: "error",
|
|
6547
|
+
text: "worktree promote requires <from> <to>"
|
|
6548
|
+
};
|
|
6549
|
+
const result = recursive.promoteRunBranch(root, fromBranch, toBranch);
|
|
6550
|
+
if (!result.ok) return {
|
|
6551
|
+
kind: "error",
|
|
6552
|
+
text: result.error ?? "promote failed"
|
|
6553
|
+
};
|
|
6554
|
+
return {
|
|
6555
|
+
kind: "success",
|
|
6556
|
+
text: "promoted: " + JSON.stringify(result)
|
|
6557
|
+
};
|
|
6558
|
+
}
|
|
6559
|
+
if (op === "status") return {
|
|
6560
|
+
kind: "success",
|
|
6561
|
+
text: "worktree status: " + JSON.stringify(recursive.worktreeStatus(root))
|
|
6562
|
+
};
|
|
6563
|
+
}
|
|
5919
6564
|
return executeRecursiveCommand(root, rawInput);
|
|
5920
6565
|
}
|
|
5921
6566
|
});
|
|
@@ -6206,7 +6851,8 @@ function apply(ctx, config) {
|
|
|
6206
6851
|
ctx.tools.register(createRecursiveLockTool(recursive)),
|
|
6207
6852
|
ctx.tools.register(createRecursiveLintTool(recursive)),
|
|
6208
6853
|
ctx.tools.register(createRecursiveCloseoutTool(recursive)),
|
|
6209
|
-
ctx.tools.register(createRecursiveScratchTool(recursive))
|
|
6854
|
+
ctx.tools.register(createRecursiveScratchTool(recursive)),
|
|
6855
|
+
ctx.tools.register(createRecursiveWorktreeTool(recursive))
|
|
6210
6856
|
];
|
|
6211
6857
|
const commands = ctx.get("commands");
|
|
6212
6858
|
if (commands) disposers.push(registerRecursiveCommand({ commands }, recursive));
|
|
@@ -6308,4 +6954,4 @@ function apply(ctx, config) {
|
|
|
6308
6954
|
});
|
|
6309
6955
|
}
|
|
6310
6956
|
//#endregion
|
|
6311
|
-
export { DEFAULT_ENFORCEMENT, LifecycleDriver, OPTIONAL_PHASES, PHASES, PHASE_SEQUENCE, RECURSIVE_API_PREFIX, RUN_ARTIFACT_SEQUENCE, RUN_STATES, RecursiveRuntime, apply, buildDelegationPrompt, buildReviewBundle, capabilityProbe, childScratchPath, contentSha256, coupleGateBlockToGoal, createChildBrief, createHandoff, createRecursiveCloseoutTool, createRecursiveInitTool, createRecursiveLintTool, createRecursiveLockTool, createRecursiveScratchTool, createRecursiveStatusTool, defaultReviewToolFilter, delegate, delegationDecisionBasis, delegationError, detectTamper, detectTransitionIntent, discoverRuns, escapeRegExp, evaluateDelegationResult, evaluatePreStepGate, evaluateToolGuard, foldRecursivePhase, foldRun, foldRunCard, getAllStaleReceipts, getArtifactState, getGateStatus, getLatestRunDirectory, getLockStatus, getMdFieldValue, getNextLegalPhase, getPrerequisiteBlockers, getPrerequisites, getStaleDownstreamPhases, getTodoStats, getWorkflowProfile, hasOpenTurn, inject, invalidateReceipt, isCoreArtifact, loadRouterPolicy, lockHashFromContent, makeRecursiveRoutes, mountRecursiveRoutesOnce, name, normalizeForLockHash, phaseIndex, probeCapabilities, readReceipt, receiptPath, renderRecursivePolicy, replyPath, resolveEnforcementConfig, resolveRole, resolveRunDir, reviewBundleDir, reviewOutputSchema, routerPolicyPath, snapshotWorkspace, trimMdValue, validateChain, validateReferences, validateTransition, writeActionRecord, writeReceipt };
|
|
6957
|
+
export { DEFAULT_ENFORCEMENT, LifecycleDriver, OPTIONAL_PHASES, PHASES, PHASE_SEQUENCE, RECURSIVE_API_PREFIX, RUN_ARTIFACT_SEQUENCE, RUN_STATES, RecursiveRuntime, apply, buildDelegationPrompt, buildReviewBundle, capabilityProbe, childScratchPath, contentSha256, coupleGateBlockToGoal, createChildBrief, createHandoff, createRecursiveCloseoutTool, createRecursiveInitTool, createRecursiveLintTool, createRecursiveLockTool, createRecursiveScratchTool, createRecursiveStatusTool, createRecursiveWorktreeTool, defaultReviewToolFilter, delegate, delegationDecisionBasis, delegationError, detectTamper, detectTransitionIntent, discoverRuns, escapeRegExp, evaluateDelegationResult, evaluatePreStepGate, evaluateToolGuard, foldRecursivePhase, foldRun, foldRunCard, getAllStaleReceipts, getArtifactState, getGateStatus, getLatestRunDirectory, getLockStatus, getMdFieldValue, getNextLegalPhase, getPrerequisiteBlockers, getPrerequisites, getStaleDownstreamPhases, getTodoStats, getWorkflowProfile, hasOpenTurn, inject, invalidateReceipt, isCoreArtifact, loadRouterPolicy, lockHashFromContent, makeRecursiveRoutes, mountRecursiveRoutesOnce, name, normalizeForLockHash, phaseIndex, probeCapabilities, readReceipt, receiptPath, renderRecursivePolicy, replyPath, resolveEnforcementConfig, resolveRole, resolveRunDir, reviewBundleDir, reviewOutputSchema, routerPolicyPath, snapshotWorkspace, trimMdValue, validateChain, validateReferences, validateTransition, writeActionRecord, writeReceipt };
|