@riddledc/riddle-proof 0.5.0 → 0.5.2
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/README.md +6 -6
- package/dist/chunk-3MHFLQKG.js +853 -0
- package/dist/{chunk-LVP22WE4.js → chunk-5GZZZ6JA.js} +5 -1
- package/dist/engine-harness.cjs +2505 -22
- package/dist/engine-harness.js +1 -1
- package/dist/index.cjs +2512 -29
- package/dist/index.js +2 -2
- package/dist/openclaw.cjs +1 -1
- package/dist/openclaw.js +1 -1
- package/dist/proof-run-core.cjs +909 -0
- package/dist/proof-run-core.d.cts +280 -0
- package/dist/proof-run-core.d.ts +280 -0
- package/dist/proof-run-core.js +48 -0
- package/dist/proof-run-engine.cjs +2499 -0
- package/dist/proof-run-engine.d.cts +677 -0
- package/dist/proof-run-engine.d.ts +677 -0
- package/dist/proof-run-engine.js +1649 -0
- package/lib/workspace-core.mjs +391 -0
- package/package.json +15 -3
- package/runtime/lib/author.py +343 -0
- package/runtime/lib/implement.py +63 -0
- package/runtime/lib/preflight.py +246 -0
- package/runtime/lib/recon.py +1048 -0
- package/runtime/lib/riddle_core_call.mjs +151 -0
- package/runtime/lib/setup.py +387 -0
- package/runtime/lib/ship.py +834 -0
- package/runtime/lib/util.py +673 -0
- package/runtime/lib/verify.py +1223 -0
- package/runtime/pipelines/riddle-proof-author.lobster +28 -0
- package/runtime/pipelines/riddle-proof-implement.lobster +26 -0
- package/runtime/pipelines/riddle-proof-recon.lobster +79 -0
- package/runtime/pipelines/riddle-proof-setup.lobster +141 -0
- package/runtime/pipelines/riddle-proof-ship.lobster +36 -0
- package/runtime/pipelines/riddle-proof-verify.lobster +74 -0
- package/runtime/tests/recon_verify_smoke.py +1198 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
const TOOL_DEFAULT_INCLUDE = ["screenshot", "console", "result", "data", "urls", "dataset", "sitemap", "visual_diff"];
|
|
8
|
+
|
|
9
|
+
function readJson(path) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readOpenClawPluginConfig() {
|
|
18
|
+
const paths = [
|
|
19
|
+
process.env.OPENCLAW_CONFIG,
|
|
20
|
+
process.env.OPENCLAW_HOME ? join(process.env.OPENCLAW_HOME, "openclaw.json") : "",
|
|
21
|
+
join(homedir(), ".openclaw", "openclaw.json"),
|
|
22
|
+
"/root/.openclaw/openclaw.json",
|
|
23
|
+
].filter(Boolean);
|
|
24
|
+
|
|
25
|
+
for (const path of paths) {
|
|
26
|
+
const cfg = readJson(path);
|
|
27
|
+
const pluginCfg = cfg?.plugins?.entries?.["openclaw-riddledc"]?.config;
|
|
28
|
+
if (pluginCfg) return pluginCfg;
|
|
29
|
+
}
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildConfig() {
|
|
34
|
+
const pluginCfg = readOpenClawPluginConfig();
|
|
35
|
+
return {
|
|
36
|
+
apiKey: process.env.RIDDLE_API_KEY || pluginCfg.apiKey,
|
|
37
|
+
baseUrl: pluginCfg.baseUrl || "https://api.riddledc.com",
|
|
38
|
+
workspace: process.env.OPENCLAW_WORKSPACE || process.cwd(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function importFileIfExists(path) {
|
|
43
|
+
if (!path || !existsSync(path)) return null;
|
|
44
|
+
return import(pathToFileURL(path).href);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function loadCore() {
|
|
48
|
+
try {
|
|
49
|
+
return await import("@riddledc/openclaw-riddledc/core");
|
|
50
|
+
} catch (primaryErr) {
|
|
51
|
+
const candidates = [
|
|
52
|
+
process.env.RIDDLE_OPENCLAW_CORE_PATH,
|
|
53
|
+
"/root/.openclaw/extensions/openclaw-riddledc/dist/core.js",
|
|
54
|
+
"/root/.openclaw/extensions/@riddledc/openclaw-riddledc/dist/core.js",
|
|
55
|
+
"/root/.openclaw/extensions/node_modules/@riddledc/openclaw-riddledc/dist/core.js",
|
|
56
|
+
"/usr/lib/node_modules/@riddledc/openclaw-riddledc/dist/core.js",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const candidate of candidates) {
|
|
60
|
+
const mod = await importFileIfExists(candidate);
|
|
61
|
+
if (mod) return mod;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const err = primaryErr instanceof Error ? primaryErr.message : String(primaryErr);
|
|
65
|
+
throw new Error(
|
|
66
|
+
"Riddle core package not found. Install/upgrade @riddledc/openclaw-riddledc with the ./core export before running riddle-proof direct mode. Import error: " + err
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function numberValue(value, fallback) {
|
|
72
|
+
const n = Number(value);
|
|
73
|
+
return Number.isFinite(n) ? n : fallback;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeServerArgs(args) {
|
|
77
|
+
return {
|
|
78
|
+
...args,
|
|
79
|
+
directory: args.directory ?? args.dir,
|
|
80
|
+
image: args.image ?? args.server_image,
|
|
81
|
+
command: args.command ?? args.server_command,
|
|
82
|
+
port: numberValue(args.port ?? args.server_port, 3000),
|
|
83
|
+
path: args.path ?? args.server_path,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeBuildArgs(args) {
|
|
88
|
+
return {
|
|
89
|
+
...args,
|
|
90
|
+
directory: args.directory ?? args.dir,
|
|
91
|
+
command: args.command ?? args.server_command,
|
|
92
|
+
port: numberValue(args.port ?? args.server_port, 3000),
|
|
93
|
+
path: args.path ?? args.server_path,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function run(tool, args) {
|
|
98
|
+
const core = await loadCore();
|
|
99
|
+
const config = buildConfig();
|
|
100
|
+
|
|
101
|
+
if (tool === "riddle_preview") {
|
|
102
|
+
return core.createStaticPreview(config, {
|
|
103
|
+
directory: args.directory ?? args.dir,
|
|
104
|
+
framework: args.framework,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (tool === "riddle_preview_delete") {
|
|
109
|
+
return core.deleteStaticPreview(config, args.id);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (tool === "riddle_server_preview") {
|
|
113
|
+
return core.createServerPreview(config, normalizeServerArgs(args));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (tool === "riddle_build_preview") {
|
|
117
|
+
return core.createBuildPreview(config, normalizeBuildArgs(args));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (tool === "riddle_script") {
|
|
121
|
+
return core.runWithDefaults(config, args, {
|
|
122
|
+
include: TOOL_DEFAULT_INCLUDE,
|
|
123
|
+
returnAsync: !!args.async,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (tool === "riddle_run") {
|
|
128
|
+
return core.runWithDefaults(config, args.payload ?? args, {
|
|
129
|
+
include: ["screenshot", "console", "result"],
|
|
130
|
+
returnAsync: !!args.async,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new Error("Unsupported direct Riddle tool: " + tool);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function main() {
|
|
138
|
+
const tool = process.argv[2];
|
|
139
|
+
const rawArgs = process.argv[3] || "{}";
|
|
140
|
+
if (!tool) throw new Error("Usage: riddle_core_call.mjs <tool> <json-args>");
|
|
141
|
+
const args = JSON.parse(rawArgs);
|
|
142
|
+
const result = await run(tool, args);
|
|
143
|
+
console.log(JSON.stringify(result));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main().catch((err) => {
|
|
147
|
+
console.log(JSON.stringify({
|
|
148
|
+
ok: false,
|
|
149
|
+
error: err instanceof Error ? err.message : String(err),
|
|
150
|
+
}));
|
|
151
|
+
});
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""Setup: create worktrees, install deps, validate args, write state.
|
|
2
|
+
|
|
3
|
+
Idempotent — safe to re-run. Creates per-run worktrees under the active
|
|
4
|
+
workspace root by default:
|
|
5
|
+
<workspace>/.riddle-proof-worktrees/riddle-proof-<run_id>-before
|
|
6
|
+
<workspace>/.riddle-proof-worktrees/riddle-proof-<run_id>-after
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json, subprocess as sp, os, sys, shutil
|
|
10
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
11
|
+
from util import load_state, save_state, git, shell_quote
|
|
12
|
+
|
|
13
|
+
s = load_state()
|
|
14
|
+
repo = s['repo']
|
|
15
|
+
branch = (s.get('target_branch') or s['branch']).strip()
|
|
16
|
+
repo_dir = s['repo_dir']
|
|
17
|
+
base_branch = s.get('base_branch', 'main')
|
|
18
|
+
before_ref_arg = (s.get('before_ref') or s.get('base_ref') or '').strip()
|
|
19
|
+
mode = s.get('mode', 'server')
|
|
20
|
+
reference = s.get('reference', 'both') # prod, before, both
|
|
21
|
+
run_id = (s.get('run_id') or '').strip()
|
|
22
|
+
SAFE_RUN_ID = ''.join(ch if ch.isalnum() or ch in ('-', '_') else '-' for ch in run_id) or 'run'
|
|
23
|
+
|
|
24
|
+
AFTER_WORKTREE_BRANCH = 'riddle-proof/' + SAFE_RUN_ID + '-after'
|
|
25
|
+
LEGACY_WORKTREE_DIRS = ('/tmp/riddle-proof-before', '/tmp/riddle-proof-after')
|
|
26
|
+
|
|
27
|
+
if branch.startswith('riddle-proof/'):
|
|
28
|
+
raise SystemExit(
|
|
29
|
+
'Setup invariant failed: target_branch uses reserved riddle-proof/* namespace. '
|
|
30
|
+
'Run preflight again so it can choose a real agent/openclaw/* PR branch.'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# In the packaged runtime, the shared workspace helper lives at package-root/lib
|
|
35
|
+
# while the stage scripts live under package-root/runtime.
|
|
36
|
+
SKILLS_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..'))
|
|
37
|
+
WORKSPACE_CORE = os.path.join(SKILLS_ROOT, 'lib', 'workspace-core.mjs')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def workspace_core(command, payload, timeout=180):
|
|
41
|
+
if not os.path.exists(WORKSPACE_CORE):
|
|
42
|
+
raise SystemExit('workspace core helper missing: ' + WORKSPACE_CORE)
|
|
43
|
+
try:
|
|
44
|
+
result = sp.run(
|
|
45
|
+
['node', WORKSPACE_CORE, command, json.dumps(payload)],
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
timeout=timeout,
|
|
49
|
+
)
|
|
50
|
+
except sp.TimeoutExpired:
|
|
51
|
+
raise SystemExit('workspace core timed out for ' + command)
|
|
52
|
+
|
|
53
|
+
if result.returncode != 0:
|
|
54
|
+
detail = (result.stderr or result.stdout or '').strip()
|
|
55
|
+
raise SystemExit('workspace core failed for ' + command + ': ' + detail[:300])
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
return json.loads(result.stdout)
|
|
59
|
+
except Exception:
|
|
60
|
+
raise SystemExit('workspace core returned invalid JSON for ' + command + ': ' + result.stdout[:300])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def ensure_deps(project_dir, reuse_from=''):
|
|
64
|
+
payload = {'projectDir': project_dir}
|
|
65
|
+
if reuse_from:
|
|
66
|
+
payload['reuseFrom'] = reuse_from
|
|
67
|
+
result = workspace_core('ensure-deps', payload, timeout=300)
|
|
68
|
+
return result.get('status', '')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve_worktree_root(repo_dir):
|
|
72
|
+
configured = (s.get('worktree_root') or os.environ.get('RIDDLE_PROOF_WORKTREE_ROOT') or '').strip()
|
|
73
|
+
if configured:
|
|
74
|
+
return os.path.abspath(os.path.expanduser(configured))
|
|
75
|
+
repo_parent = os.path.dirname(os.path.abspath(repo_dir))
|
|
76
|
+
return os.path.join(repo_parent, '.riddle-proof-worktrees')
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cleanup_legacy_branch_worktrees(repo_dir, branch_name):
|
|
80
|
+
if not repo_dir or not os.path.exists(os.path.join(repo_dir, '.git')):
|
|
81
|
+
return
|
|
82
|
+
result = git('git worktree list --porcelain', repo_dir)
|
|
83
|
+
if result.returncode != 0:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
worktrees = []
|
|
87
|
+
current = {}
|
|
88
|
+
for line in result.stdout.splitlines() + ['']:
|
|
89
|
+
if not line.strip():
|
|
90
|
+
if current:
|
|
91
|
+
worktrees.append(current)
|
|
92
|
+
current = {}
|
|
93
|
+
continue
|
|
94
|
+
key, _, value = line.partition(' ')
|
|
95
|
+
if key == 'worktree':
|
|
96
|
+
current['path'] = value.strip()
|
|
97
|
+
elif key == 'branch':
|
|
98
|
+
current['branch'] = value.strip()
|
|
99
|
+
|
|
100
|
+
locked_ref = 'refs/heads/' + branch_name
|
|
101
|
+
for wt in worktrees:
|
|
102
|
+
path = wt.get('path', '')
|
|
103
|
+
if not (path.startswith('/tmp/riddle-proof-') and path.endswith('-before')):
|
|
104
|
+
continue
|
|
105
|
+
if wt.get('branch') != locked_ref:
|
|
106
|
+
continue
|
|
107
|
+
print('Removing stale legacy riddle-proof worktree locked to ' + branch_name + ': ' + path)
|
|
108
|
+
sp.run(
|
|
109
|
+
'git worktree remove --force ' + shell_quote(path),
|
|
110
|
+
shell=True,
|
|
111
|
+
cwd=repo_dir,
|
|
112
|
+
capture_output=True,
|
|
113
|
+
)
|
|
114
|
+
if os.path.exists(path):
|
|
115
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
116
|
+
git('git worktree prune', repo_dir)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def ref_exists(repo_dir, ref):
|
|
120
|
+
if not ref:
|
|
121
|
+
return False
|
|
122
|
+
r = git('git rev-parse --verify --quiet ' + shell_quote(ref + '^{commit}'), repo_dir)
|
|
123
|
+
return r.returncode == 0
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def resolve_before_ref(repo_dir, base_branch, requested_ref):
|
|
127
|
+
candidates = []
|
|
128
|
+
if requested_ref:
|
|
129
|
+
candidates.append((requested_ref, 'requested'))
|
|
130
|
+
if base_branch:
|
|
131
|
+
candidates.append(('origin/' + base_branch, 'remote_base_branch'))
|
|
132
|
+
candidates.append((base_branch, 'local_base_branch_fallback'))
|
|
133
|
+
|
|
134
|
+
for ref, source in candidates:
|
|
135
|
+
if ref_exists(repo_dir, ref):
|
|
136
|
+
return ref, source
|
|
137
|
+
raise SystemExit(
|
|
138
|
+
'Failed to resolve before ref. Tried: ' +
|
|
139
|
+
', '.join(ref for ref, _ in candidates if ref)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def load_repo_profile(project_dir):
|
|
144
|
+
profile_path = os.path.join(project_dir, '.riddle-proof', 'profile.json')
|
|
145
|
+
if not os.path.exists(profile_path):
|
|
146
|
+
return {}, ''
|
|
147
|
+
try:
|
|
148
|
+
with open(profile_path) as f:
|
|
149
|
+
profile = json.load(f)
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
print('Ignoring invalid Riddle Proof profile: ' + profile_path + ' (' + str(exc)[:180] + ')')
|
|
152
|
+
return {}, profile_path
|
|
153
|
+
if not isinstance(profile, dict):
|
|
154
|
+
print('Ignoring non-object Riddle Proof profile: ' + profile_path)
|
|
155
|
+
return {}, profile_path
|
|
156
|
+
return profile, profile_path
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def profile_matches_target(target, haystack):
|
|
160
|
+
keywords = target.get('keywords') if isinstance(target, dict) else []
|
|
161
|
+
if isinstance(keywords, str):
|
|
162
|
+
keywords = [keywords]
|
|
163
|
+
matched = []
|
|
164
|
+
for keyword in keywords if isinstance(keywords, list) else []:
|
|
165
|
+
needle = str(keyword).strip().lower()
|
|
166
|
+
if needle and needle in haystack:
|
|
167
|
+
matched.append(str(keyword).strip())
|
|
168
|
+
name = str(target.get('name') or '').strip()
|
|
169
|
+
if name and name.lower() in haystack and name not in matched:
|
|
170
|
+
matched.append(name)
|
|
171
|
+
return matched
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def apply_repo_profile(project_dir):
|
|
175
|
+
profile, profile_path = load_repo_profile(project_dir)
|
|
176
|
+
if not profile:
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
haystack = ' '.join([
|
|
180
|
+
str(s.get('change_request') or ''),
|
|
181
|
+
str(s.get('context') or ''),
|
|
182
|
+
str(s.get('server_path') or ''),
|
|
183
|
+
str(s.get('capture_script') or ''),
|
|
184
|
+
]).lower()
|
|
185
|
+
|
|
186
|
+
selected = {}
|
|
187
|
+
matched_keywords = []
|
|
188
|
+
targets = profile.get('targets')
|
|
189
|
+
if isinstance(targets, list):
|
|
190
|
+
for target in targets:
|
|
191
|
+
if not isinstance(target, dict):
|
|
192
|
+
continue
|
|
193
|
+
matched = profile_matches_target(target, haystack)
|
|
194
|
+
if matched:
|
|
195
|
+
selected = target
|
|
196
|
+
matched_keywords = matched
|
|
197
|
+
break
|
|
198
|
+
|
|
199
|
+
defaults = profile.get('defaults') if isinstance(profile.get('defaults'), dict) else {}
|
|
200
|
+
merged = dict(defaults)
|
|
201
|
+
if selected:
|
|
202
|
+
merged.update(selected)
|
|
203
|
+
if not merged:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
applied = []
|
|
207
|
+
simple_fields = [
|
|
208
|
+
'mode',
|
|
209
|
+
'build_command',
|
|
210
|
+
'build_output',
|
|
211
|
+
'server_image',
|
|
212
|
+
'server_command',
|
|
213
|
+
'server_port',
|
|
214
|
+
'server_path',
|
|
215
|
+
'wait_for_selector',
|
|
216
|
+
'color_scheme',
|
|
217
|
+
'allow_static_preview_fallback',
|
|
218
|
+
'use_auth',
|
|
219
|
+
'success_criteria',
|
|
220
|
+
'capture_script',
|
|
221
|
+
]
|
|
222
|
+
for field in simple_fields:
|
|
223
|
+
value = merged.get(field)
|
|
224
|
+
if value is None or str(value).strip() == '':
|
|
225
|
+
continue
|
|
226
|
+
if str(s.get(field) or '').strip():
|
|
227
|
+
continue
|
|
228
|
+
s[field] = str(value).strip()
|
|
229
|
+
applied.append(field)
|
|
230
|
+
if field == 'server_path':
|
|
231
|
+
s['server_path_source'] = 'repo_profile'
|
|
232
|
+
|
|
233
|
+
profile_context = str(merged.get('context') or merged.get('proof_context') or '').strip()
|
|
234
|
+
if profile_context:
|
|
235
|
+
existing = str(s.get('context') or '').strip()
|
|
236
|
+
note = 'Riddle Proof repo profile'
|
|
237
|
+
target_name = str(merged.get('name') or '').strip()
|
|
238
|
+
if target_name:
|
|
239
|
+
note += ' (' + target_name + ')'
|
|
240
|
+
note += ': ' + profile_context
|
|
241
|
+
s['context'] = (existing + '\n\n' + note).strip() if existing else note
|
|
242
|
+
applied.append('context')
|
|
243
|
+
|
|
244
|
+
if applied:
|
|
245
|
+
s['proof_profile'] = {
|
|
246
|
+
'path': profile_path,
|
|
247
|
+
'name': str(merged.get('name') or '').strip(),
|
|
248
|
+
'matched_keywords': matched_keywords,
|
|
249
|
+
'applied_fields': sorted(set(applied)),
|
|
250
|
+
}
|
|
251
|
+
print('Applied Riddle Proof repo profile: ' + ', '.join(s['proof_profile']['applied_fields']))
|
|
252
|
+
|
|
253
|
+
# Ensure the repo is cloned and up to date via the shared workspace core.
|
|
254
|
+
setup = workspace_core('prepare-repo', {
|
|
255
|
+
'repo': repo,
|
|
256
|
+
'branch': branch,
|
|
257
|
+
'repoDir': repo_dir,
|
|
258
|
+
'baseBranch': base_branch,
|
|
259
|
+
'workspaceRoot': os.environ.get('OPENCLAW_WORKSPACE', ''),
|
|
260
|
+
}, timeout=300)
|
|
261
|
+
repo_dir = setup.get('repoDir') or repo_dir
|
|
262
|
+
branch = setup.get('branch') or branch
|
|
263
|
+
target_branch = branch
|
|
264
|
+
if target_branch.startswith('riddle-proof/'):
|
|
265
|
+
raise SystemExit(
|
|
266
|
+
'Setup invariant failed: workspace prepared a reserved riddle-proof/* branch instead of a real PR branch.'
|
|
267
|
+
)
|
|
268
|
+
WORKTREE_ROOT = resolve_worktree_root(repo_dir)
|
|
269
|
+
BEFORE_DIR = os.path.join(WORKTREE_ROOT, 'riddle-proof-' + SAFE_RUN_ID + '-before')
|
|
270
|
+
AFTER_DIR = os.path.join(WORKTREE_ROOT, 'riddle-proof-' + SAFE_RUN_ID + '-after')
|
|
271
|
+
s['repo_dir'] = repo_dir
|
|
272
|
+
s['branch'] = branch
|
|
273
|
+
s['target_branch'] = target_branch
|
|
274
|
+
s['ship_target_branch'] = target_branch
|
|
275
|
+
s['worktree_root'] = WORKTREE_ROOT
|
|
276
|
+
save_state(s)
|
|
277
|
+
print('Prepared workspace via ' + setup.get('source', 'workspace_core') + ': ' + repo_dir)
|
|
278
|
+
os.makedirs(WORKTREE_ROOT, exist_ok=True)
|
|
279
|
+
cleanup_legacy_branch_worktrees(repo_dir, base_branch)
|
|
280
|
+
|
|
281
|
+
# Clean any stale worktrees for this run and the legacy fixed paths
|
|
282
|
+
worktree_cleanup_dirs = []
|
|
283
|
+
for candidate in (
|
|
284
|
+
BEFORE_DIR,
|
|
285
|
+
AFTER_DIR,
|
|
286
|
+
s.get('before_worktree'),
|
|
287
|
+
s.get('after_worktree'),
|
|
288
|
+
*LEGACY_WORKTREE_DIRS,
|
|
289
|
+
):
|
|
290
|
+
if candidate and candidate not in worktree_cleanup_dirs:
|
|
291
|
+
worktree_cleanup_dirs.append(candidate)
|
|
292
|
+
|
|
293
|
+
cleanup_branches = []
|
|
294
|
+
for candidate in (AFTER_WORKTREE_BRANCH, s.get('after_worktree_branch', '').strip()):
|
|
295
|
+
if candidate and candidate not in cleanup_branches:
|
|
296
|
+
cleanup_branches.append(candidate)
|
|
297
|
+
|
|
298
|
+
# Create before worktree (only if reference includes 'before')
|
|
299
|
+
before_ref = ''
|
|
300
|
+
before_ref_source = ''
|
|
301
|
+
if reference in ('before', 'both'):
|
|
302
|
+
before_ref, before_ref_source = resolve_before_ref(repo_dir, base_branch, before_ref_arg)
|
|
303
|
+
workspace_core('ensure-worktree', {
|
|
304
|
+
'repoDir': repo_dir,
|
|
305
|
+
'worktreeDir': BEFORE_DIR,
|
|
306
|
+
'ref': before_ref,
|
|
307
|
+
'detach': True,
|
|
308
|
+
'cleanupPaths': worktree_cleanup_dirs,
|
|
309
|
+
'verifyPackageJson': True,
|
|
310
|
+
}, timeout=300)
|
|
311
|
+
print('Before worktree: ' + BEFORE_DIR + ' (' + before_ref + ', source=' + before_ref_source + ')')
|
|
312
|
+
|
|
313
|
+
# Patch Next.js config if needed (export -> standalone for server mode)
|
|
314
|
+
if mode == 'server':
|
|
315
|
+
for cf in (BEFORE_DIR + '/next.config.ts', BEFORE_DIR + '/next.config.js', BEFORE_DIR + '/next.config.mjs'):
|
|
316
|
+
if os.path.exists(cf):
|
|
317
|
+
with open(cf) as f:
|
|
318
|
+
content = f.read()
|
|
319
|
+
if "output: 'export'" in content:
|
|
320
|
+
with open(cf, 'w') as f:
|
|
321
|
+
f.write(content.replace("output: 'export'", "output: 'standalone'"))
|
|
322
|
+
print('Patched before config: export -> standalone')
|
|
323
|
+
break
|
|
324
|
+
|
|
325
|
+
# Create after worktree
|
|
326
|
+
after_cleanup_dirs = [candidate for candidate in worktree_cleanup_dirs if candidate != BEFORE_DIR]
|
|
327
|
+
workspace_core('ensure-worktree', {
|
|
328
|
+
'repoDir': repo_dir,
|
|
329
|
+
'worktreeDir': AFTER_DIR,
|
|
330
|
+
'ref': branch,
|
|
331
|
+
'branchName': AFTER_WORKTREE_BRANCH,
|
|
332
|
+
'resetBranch': True,
|
|
333
|
+
'cleanupPaths': after_cleanup_dirs,
|
|
334
|
+
'cleanupBranches': cleanup_branches,
|
|
335
|
+
'verifyPackageJson': True,
|
|
336
|
+
}, timeout=300)
|
|
337
|
+
print('After worktree: ' + AFTER_DIR + ' (' + AFTER_WORKTREE_BRANCH + ' -> ' + branch + ')')
|
|
338
|
+
apply_repo_profile(AFTER_DIR)
|
|
339
|
+
save_state(s)
|
|
340
|
+
|
|
341
|
+
reuse_source = repo_dir if os.path.exists(os.path.join(repo_dir, 'package.json')) else ''
|
|
342
|
+
shared_status = ensure_deps(reuse_source) if reuse_source else ''
|
|
343
|
+
if shared_status:
|
|
344
|
+
print('Shared deps status: ' + shared_status)
|
|
345
|
+
|
|
346
|
+
before_dep_status = ''
|
|
347
|
+
if reference in ('before', 'both'):
|
|
348
|
+
before_dep_status = ensure_deps(BEFORE_DIR, reuse_from=reuse_source)
|
|
349
|
+
print('Before deps status: ' + before_dep_status)
|
|
350
|
+
|
|
351
|
+
after_dep_status = ensure_deps(AFTER_DIR, reuse_from=reuse_source)
|
|
352
|
+
print('After deps status: ' + after_dep_status)
|
|
353
|
+
|
|
354
|
+
# Patch Next.js config in after worktree if needed
|
|
355
|
+
if mode == 'server':
|
|
356
|
+
for cf in (AFTER_DIR + '/next.config.ts', AFTER_DIR + '/next.config.js', AFTER_DIR + '/next.config.mjs'):
|
|
357
|
+
if os.path.exists(cf):
|
|
358
|
+
with open(cf) as f:
|
|
359
|
+
content = f.read()
|
|
360
|
+
if "output: 'export'" in content:
|
|
361
|
+
with open(cf, 'w') as f:
|
|
362
|
+
f.write(content.replace("output: 'export'", "output: 'standalone'"))
|
|
363
|
+
print('Patched after config: export -> standalone')
|
|
364
|
+
break
|
|
365
|
+
|
|
366
|
+
s['before_worktree'] = BEFORE_DIR if reference in ('before', 'both') else ''
|
|
367
|
+
s['before_ref'] = before_ref or before_ref_arg
|
|
368
|
+
s['before_ref_source'] = before_ref_source
|
|
369
|
+
s['after_worktree'] = AFTER_DIR
|
|
370
|
+
s['after_worktree_branch'] = AFTER_WORKTREE_BRANCH
|
|
371
|
+
s['workspace_ready'] = True
|
|
372
|
+
s['stage'] = 'setup'
|
|
373
|
+
s['implementation_status'] = 'pending_recon'
|
|
374
|
+
s['dependency_install'] = {
|
|
375
|
+
'shared': bool(shared_status),
|
|
376
|
+
'before': before_dep_status,
|
|
377
|
+
'after': after_dep_status,
|
|
378
|
+
}
|
|
379
|
+
if not (s.get('capture_script') or '').strip():
|
|
380
|
+
s['proof_plan_status'] = 'pending_recon'
|
|
381
|
+
save_state(s)
|
|
382
|
+
|
|
383
|
+
print('Setup complete.')
|
|
384
|
+
if reference in ('before', 'both'):
|
|
385
|
+
print(' Before: ' + BEFORE_DIR + ' (detached ' + (before_ref or before_ref_arg) + ')')
|
|
386
|
+
print(' After: ' + AFTER_DIR + ' (' + AFTER_WORKTREE_BRANCH + ' -> ' + branch + ')')
|
|
387
|
+
print(json.dumps({'ok': True}))
|