erdos-problems 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,208 @@
1
+ import path from 'node:path';
2
+ import { loadLocalProblems, getProblem } from '../atlas/catalog.js';
3
+ import { loadConfig } from './config.js';
4
+ import { ensureDir, writeJson, writeText } from './files.js';
5
+ import {
6
+ getWorkspaceCheckpointIndexPath,
7
+ getWorkspaceCheckpointJsonPath,
8
+ getWorkspaceCheckpointsDir,
9
+ getWorkspaceProblemCheckpointsDir,
10
+ getWorkspaceRoot,
11
+ getWorkspaceRouteCheckpointsDir,
12
+ } from './paths.js';
13
+ import { getProblemArtifactInventory } from './problem-artifacts.js';
14
+ import { buildSunflowerStatusSnapshot } from './sunflower.js';
15
+ import { loadState, saveState, syncState } from './state.js';
16
+
17
+ function safeName(value) {
18
+ return String(value).replace(/[^a-zA-Z0-9_-]+/g, '-');
19
+ }
20
+
21
+ function renderCanonicalArtifacts(inventory) {
22
+ return inventory.canonicalArtifacts
23
+ .map((artifact) => `- ${artifact.label}: ${artifact.exists ? 'present' : 'missing'} (${artifact.path})`)
24
+ .join('\n');
25
+ }
26
+
27
+ function renderProblemCheckpoint(problem, state) {
28
+ const inventory = getProblemArtifactInventory(problem);
29
+ const sunflower = problem.cluster === 'sunflower' ? buildSunflowerStatusSnapshot(problem) : null;
30
+ const research = problem.researchState ?? {};
31
+ const activeRoute = sunflower?.activeRoute ?? research.active_route ?? '(none)';
32
+ const routeBreakthrough = sunflower?.routeBreakthrough ?? Boolean(research.route_breakthrough);
33
+ const problemSolved = sunflower?.problemSolved ?? Boolean(research.problem_solved);
34
+ const nextHonestMove = sunflower?.nextHonestMove
35
+ ?? (activeRoute !== '(none)' ? `Advance ${activeRoute} against the dossier and evidence bundle.` : 'Seed or choose an active route.');
36
+ const frontier = sunflower?.frontierDetail ?? problem.shortStatement;
37
+ const checkpointFocus = sunflower?.checkpointFocus ?? 'Keep local route claims and upstream public status cleanly separated.';
38
+
39
+ return `# Problem ${problem.problemId} Checkpoint
40
+
41
+ ## Status Ladder
42
+
43
+ - Open Problem: ${problem.problemId}
44
+ - Active Route: ${activeRoute}
45
+ - Route Breakthrough: ${routeBreakthrough ? 'yes' : 'no'}
46
+ - Problem Solved: ${problemSolved ? 'yes' : 'no'}
47
+
48
+ ## Problem Record
49
+
50
+ - Title: ${problem.title}
51
+ - Cluster: ${problem.cluster}
52
+ - Source: ${problem.sourceUrl}
53
+ - Site status: ${problem.siteStatus}
54
+ - Repo status: ${problem.repoStatus}
55
+ - Harness depth: ${problem.harnessDepth}
56
+
57
+ ## Current Frontier
58
+
59
+ - ${frontier}
60
+
61
+ ## Checkpoint Focus
62
+
63
+ - ${checkpointFocus}
64
+
65
+ ## Next Honest Move
66
+
67
+ - ${nextHonestMove}
68
+
69
+ ## Canonical Artifacts
70
+
71
+ ${renderCanonicalArtifacts(inventory)}
72
+
73
+ ## Continuation Frame
74
+
75
+ - Active agent: ${state.activeAgent || '(none)'}
76
+ - Continuation mode: ${state.continuation.mode}
77
+ - Stop rule: ${state.continuation.stopRule}
78
+ `;
79
+ }
80
+
81
+ function renderRouteCheckpoint(problem, state) {
82
+ const sunflower = problem.cluster === 'sunflower' ? buildSunflowerStatusSnapshot(problem) : null;
83
+ const frontier = sunflower?.frontierDetail ?? state.currentFrontier.detail;
84
+ const routeStory = sunflower?.routeStory ?? state.routeStory ?? '(none yet)';
85
+ const checkpointFocus = sunflower?.checkpointFocus ?? state.checkpointFocus ?? '(none yet)';
86
+
87
+ return `# Problem ${problem.problemId} Active Route Checkpoint
88
+
89
+ ## Status Ladder
90
+
91
+ - Open Problem: ${problem.problemId}
92
+ - Active Route: ${state.activeRoute || '(none)'}
93
+ - Route Breakthrough: ${state.routeBreakthrough ? 'yes' : 'no'}
94
+ - Problem Solved: ${state.problemSolved ? 'yes' : 'no'}
95
+
96
+ ## Current Frontier
97
+
98
+ - ${frontier}
99
+
100
+ ## Route Story
101
+
102
+ - ${routeStory}
103
+
104
+ ## Checkpoint Focus
105
+
106
+ - ${checkpointFocus}
107
+
108
+ ## Continuation Frame
109
+
110
+ - Continuation mode: ${state.continuation.mode}
111
+ - Stop rule: ${state.continuation.stopRule}
112
+ - Next honest move: ${state.nextHonestMove}
113
+ `;
114
+ }
115
+
116
+ function renderIndex(state, checkpoints) {
117
+ const problemRows = checkpoints.filter((entry) => entry.kind === 'problem');
118
+ const routeRows = checkpoints.filter((entry) => entry.kind === 'route');
119
+
120
+ return [
121
+ '# Erdos Checkpoints',
122
+ '',
123
+ 'This is the human-facing checkpoint shelf for the local research-loop runtime.',
124
+ 'Canonical truth still lives in dossiers, pack artifacts, upstream snapshots, and generated workspace bundles.',
125
+ '',
126
+ '## Current Status Ladder',
127
+ '',
128
+ `- Open Problem: ${state.activeProblem || '(none)'}`,
129
+ `- Active Route: ${state.activeRoute || '(none)'}`,
130
+ `- Route Breakthrough: ${state.routeBreakthrough ? 'yes' : 'no'}`,
131
+ `- Problem Solved: ${state.problemSolved ? 'yes' : 'no'}`,
132
+ '',
133
+ '## Working Context',
134
+ '',
135
+ `- Continuation Mode: ${state.continuation.mode}`,
136
+ `- Next Honest Move: ${state.nextHonestMove}`,
137
+ '',
138
+ '## Problem Checkpoints',
139
+ '',
140
+ ...(problemRows.length > 0 ? problemRows.map((row) => `- [${row.label}](${row.relativePath})`) : ['- *(none)*']),
141
+ '',
142
+ '## Route Checkpoints',
143
+ '',
144
+ ...(routeRows.length > 0 ? routeRows.map((row) => `- [${row.label}](${row.relativePath})`) : ['- *(none)*']),
145
+ '',
146
+ ].join('\n');
147
+ }
148
+
149
+ export function syncCheckpoints(workspaceRoot = getWorkspaceRoot()) {
150
+ const config = loadConfig(workspaceRoot);
151
+ const state = syncState(workspaceRoot);
152
+ const problems = loadLocalProblems();
153
+
154
+ ensureDir(getWorkspaceCheckpointsDir(workspaceRoot));
155
+ ensureDir(getWorkspaceProblemCheckpointsDir(workspaceRoot));
156
+ ensureDir(getWorkspaceRouteCheckpointsDir(workspaceRoot));
157
+
158
+ const checkpoints = [];
159
+
160
+ for (const problem of problems) {
161
+ const filename = `problem-${problem.problemId}.md`;
162
+ const fullPath = path.join(getWorkspaceProblemCheckpointsDir(workspaceRoot), filename);
163
+ writeText(fullPath, renderProblemCheckpoint(problem, state));
164
+ checkpoints.push({
165
+ kind: 'problem',
166
+ label: `Problem ${problem.problemId}`,
167
+ path: fullPath,
168
+ relativePath: path.relative(getWorkspaceCheckpointsDir(workspaceRoot), fullPath),
169
+ });
170
+ }
171
+
172
+ if (state.activeProblem && state.activeRoute) {
173
+ const problem = getProblem(state.activeProblem);
174
+ if (problem) {
175
+ const filename = `problem-${state.activeProblem}--${safeName(state.activeRoute)}.md`;
176
+ const fullPath = path.join(getWorkspaceRouteCheckpointsDir(workspaceRoot), filename);
177
+ writeText(fullPath, renderRouteCheckpoint(problem, state));
178
+ checkpoints.push({
179
+ kind: 'route',
180
+ label: `Problem ${state.activeProblem} / ${state.activeRoute}`,
181
+ path: fullPath,
182
+ relativePath: path.relative(getWorkspaceCheckpointsDir(workspaceRoot), fullPath),
183
+ });
184
+ }
185
+ }
186
+
187
+ writeText(getWorkspaceCheckpointIndexPath(workspaceRoot), renderIndex(state, checkpoints));
188
+ writeJson(getWorkspaceCheckpointJsonPath(workspaceRoot), {
189
+ syncedAt: new Date().toISOString(),
190
+ activeProblem: state.activeProblem,
191
+ activeRoute: state.activeRoute,
192
+ continuationMode: state.continuation.mode,
193
+ activeAgent: config.preferredAgent,
194
+ checkpoints,
195
+ });
196
+
197
+ const nextState = saveState({
198
+ ...loadState(workspaceRoot),
199
+ lastCheckpointSyncAt: new Date().toISOString(),
200
+ }, workspaceRoot);
201
+
202
+ return {
203
+ indexPath: getWorkspaceCheckpointIndexPath(workspaceRoot),
204
+ checkpointJsonPath: getWorkspaceCheckpointJsonPath(workspaceRoot),
205
+ checkpoints,
206
+ state: nextState,
207
+ };
208
+ }
@@ -0,0 +1,37 @@
1
+ import { fileExists, readJson, writeJson } from './files.js';
2
+ import { getWorkspaceConfigPath, getWorkspaceRoot } from './paths.js';
3
+
4
+ const DEFAULT_CONFIG = {
5
+ preferredAgent: 'erdos',
6
+ continuation: 'route',
7
+ };
8
+
9
+ export function defaultConfig() {
10
+ return { ...DEFAULT_CONFIG };
11
+ }
12
+
13
+ export function loadConfig(workspaceRoot = getWorkspaceRoot()) {
14
+ const configPath = getWorkspaceConfigPath(workspaceRoot);
15
+ if (!fileExists(configPath)) {
16
+ return defaultConfig();
17
+ }
18
+ return {
19
+ ...DEFAULT_CONFIG,
20
+ ...readJson(configPath),
21
+ };
22
+ }
23
+
24
+ export function saveConfig(config, workspaceRoot = getWorkspaceRoot()) {
25
+ const payload = {
26
+ ...DEFAULT_CONFIG,
27
+ ...config,
28
+ };
29
+ writeJson(getWorkspaceConfigPath(workspaceRoot), payload);
30
+ return payload;
31
+ }
32
+
33
+ export function ensureConfig(workspaceRoot = getWorkspaceRoot()) {
34
+ const payload = loadConfig(workspaceRoot);
35
+ saveConfig(payload, workspaceRoot);
36
+ return payload;
37
+ }
@@ -0,0 +1,65 @@
1
+ const PRESETS = {
2
+ atom: {
3
+ mode: 'atom',
4
+ reviewCadence: 'tight',
5
+ maxUnattendedMinutes: 25,
6
+ checkpointAfterLoadBearingResult: true,
7
+ stopRule: 'Stop after one load-bearing atomic step or blocker.',
8
+ },
9
+ route: {
10
+ mode: 'route',
11
+ reviewCadence: 'balanced',
12
+ maxUnattendedMinutes: 90,
13
+ checkpointAfterLoadBearingResult: true,
14
+ stopRule: 'Continue until the active route reaches a real boundary or blocker.',
15
+ },
16
+ milestone: {
17
+ mode: 'milestone',
18
+ reviewCadence: 'sparse',
19
+ maxUnattendedMinutes: 180,
20
+ checkpointAfterLoadBearingResult: false,
21
+ stopRule: 'Continue until the problem milestone boundary or blocker.',
22
+ },
23
+ };
24
+
25
+ const ALIASES = {
26
+ phase: 'route',
27
+ };
28
+
29
+ export function continuationModes() {
30
+ return ['atom', 'route', 'phase', 'milestone'];
31
+ }
32
+
33
+ export function normalizeContinuationMode(mode = 'route') {
34
+ const raw = String(mode || 'route');
35
+ if (PRESETS[raw]) {
36
+ return raw;
37
+ }
38
+ if (ALIASES[raw]) {
39
+ return ALIASES[raw];
40
+ }
41
+ return 'route';
42
+ }
43
+
44
+ export function continuationDisplay(continuation = {}) {
45
+ const requested = continuation.requestedMode || continuation.mode || 'route';
46
+ const normalized = normalizeContinuationMode(requested);
47
+ if (requested === 'phase' && normalized === 'route') {
48
+ return 'route (phase-style)';
49
+ }
50
+ return normalized;
51
+ }
52
+
53
+ export function resolveContinuation(raw = {}) {
54
+ const requested = raw.requestedMode || raw.mode || 'route';
55
+ const mode = normalizeContinuationMode(requested);
56
+ const rest = { ...raw };
57
+ delete rest.mode;
58
+ delete rest.requestedMode;
59
+ return {
60
+ ...PRESETS[mode],
61
+ ...rest,
62
+ mode,
63
+ requestedMode: requested,
64
+ };
65
+ }
@@ -0,0 +1,52 @@
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ function runGit(workspaceRoot, args) {
4
+ try {
5
+ return execFileSync('git', ['-C', workspaceRoot, ...args], {
6
+ encoding: 'utf8',
7
+ stdio: ['ignore', 'pipe', 'ignore'],
8
+ }).trim();
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+
14
+ export function gitSummary(workspaceRoot) {
15
+ if (!workspaceRoot) {
16
+ return {
17
+ exists: false,
18
+ isGitRepo: false,
19
+ clean: null,
20
+ branch: null,
21
+ detail: 'missing',
22
+ };
23
+ }
24
+
25
+ const inside = runGit(workspaceRoot, ['rev-parse', '--is-inside-work-tree']);
26
+ if (inside !== 'true') {
27
+ return {
28
+ exists: true,
29
+ isGitRepo: false,
30
+ clean: null,
31
+ branch: null,
32
+ detail: 'not a git repository',
33
+ };
34
+ }
35
+
36
+ const branch = runGit(workspaceRoot, ['rev-parse', '--abbrev-ref', 'HEAD']);
37
+ const porcelain = runGit(workspaceRoot, ['status', '--porcelain']) ?? '';
38
+ const clean = porcelain.trim().length === 0;
39
+ const changedPaths = porcelain
40
+ .split('\n')
41
+ .map((line) => line.trim())
42
+ .filter(Boolean);
43
+
44
+ return {
45
+ exists: true,
46
+ isGitRepo: true,
47
+ clean,
48
+ branch,
49
+ detail: clean ? 'clean' : `${changedPaths.length} changed path(s)`,
50
+ changedPaths,
51
+ };
52
+ }
@@ -8,72 +8,108 @@ export function getWorkspaceRoot() {
8
8
  return process.cwd();
9
9
  }
10
10
 
11
- export function getWorkspaceDir() {
12
- return path.join(getWorkspaceRoot(), '.erdos');
11
+ export function getWorkspaceDir(workspaceRoot = getWorkspaceRoot()) {
12
+ return path.join(workspaceRoot, '.erdos');
13
+ }
14
+
15
+ export function getWorkspaceConfigPath(workspaceRoot = getWorkspaceRoot()) {
16
+ return path.join(getWorkspaceDir(workspaceRoot), 'config.json');
13
17
  }
14
18
 
15
19
  export function getWorkspaceRegistryDir(workspaceRoot = getWorkspaceRoot()) {
16
- return path.join(workspaceRoot, '.erdos', 'registry');
20
+ return path.join(getWorkspaceDir(workspaceRoot), 'registry');
21
+ }
22
+
23
+ export function getWorkspaceRegistryBucketDir(bucket, workspaceRoot = getWorkspaceRoot()) {
24
+ return path.join(getWorkspaceRegistryDir(workspaceRoot), String(bucket));
17
25
  }
18
26
 
19
27
  export function getWorkspaceComputeRegistryDir(workspaceRoot = getWorkspaceRoot()) {
20
28
  return path.join(getWorkspaceRegistryDir(workspaceRoot), 'compute');
21
29
  }
22
30
 
23
- export function getWorkspaceStatePath() {
24
- return path.join(getWorkspaceDir(), 'state.json');
31
+ export function getWorkspaceStatePath(workspaceRoot = getWorkspaceRoot()) {
32
+ return path.join(getWorkspaceDir(workspaceRoot), 'state.json');
33
+ }
34
+
35
+ export function getWorkspaceStateMarkdownPath(workspaceRoot = getWorkspaceRoot()) {
36
+ return path.join(getWorkspaceDir(workspaceRoot), 'STATE.md');
37
+ }
38
+
39
+ export function getWorkspaceQuestionLedgerPath(workspaceRoot = getWorkspaceRoot()) {
40
+ return path.join(getWorkspaceDir(workspaceRoot), 'QUESTION-LEDGER.md');
41
+ }
42
+
43
+ export function getCurrentProblemPath(workspaceRoot = getWorkspaceRoot()) {
44
+ return path.join(getWorkspaceDir(workspaceRoot), 'current-problem.json');
45
+ }
46
+
47
+ export function getWorkspaceReportsDir(workspaceRoot = getWorkspaceRoot()) {
48
+ return path.join(getWorkspaceDir(workspaceRoot), 'reports');
49
+ }
50
+
51
+ export function getWorkspaceDiffPath(workspaceRoot = getWorkspaceRoot()) {
52
+ return path.join(getWorkspaceReportsDir(workspaceRoot), 'UPSTREAM_DIFF.md');
53
+ }
54
+
55
+ export function getWorkspaceUpstreamDir(workspaceRoot = getWorkspaceRoot()) {
56
+ return path.join(getWorkspaceDir(workspaceRoot), 'upstream', 'erdosproblems');
57
+ }
58
+
59
+ export function getWorkspaceUpstreamYamlPath(workspaceRoot = getWorkspaceRoot()) {
60
+ return path.join(getWorkspaceUpstreamDir(workspaceRoot), 'problems.yaml');
25
61
  }
26
62
 
27
- export function getCurrentProblemPath() {
28
- return path.join(getWorkspaceDir(), 'current-problem.json');
63
+ export function getWorkspaceUpstreamIndexPath(workspaceRoot = getWorkspaceRoot()) {
64
+ return path.join(getWorkspaceUpstreamDir(workspaceRoot), 'PROBLEMS_INDEX.json');
29
65
  }
30
66
 
31
- export function getWorkspaceReportsDir() {
32
- return path.join(getWorkspaceDir(), 'reports');
67
+ export function getWorkspaceUpstreamManifestPath(workspaceRoot = getWorkspaceRoot()) {
68
+ return path.join(getWorkspaceUpstreamDir(workspaceRoot), 'SYNC_MANIFEST.json');
33
69
  }
34
70
 
35
- export function getWorkspaceDiffPath() {
36
- return path.join(getWorkspaceReportsDir(), 'UPSTREAM_DIFF.md');
71
+ export function getWorkspaceScaffoldsDir(workspaceRoot = getWorkspaceRoot()) {
72
+ return path.join(getWorkspaceDir(workspaceRoot), 'scaffolds');
37
73
  }
38
74
 
39
- export function getWorkspaceUpstreamDir() {
40
- return path.join(getWorkspaceDir(), 'upstream', 'erdosproblems');
75
+ export function getWorkspaceProblemScaffoldDir(problemId, workspaceRoot = getWorkspaceRoot()) {
76
+ return path.join(getWorkspaceScaffoldsDir(workspaceRoot), String(problemId));
41
77
  }
42
78
 
43
- export function getWorkspaceUpstreamYamlPath() {
44
- return path.join(getWorkspaceUpstreamDir(), 'problems.yaml');
79
+ export function getWorkspacePullsDir(workspaceRoot = getWorkspaceRoot()) {
80
+ return path.join(getWorkspaceDir(workspaceRoot), 'pulls');
45
81
  }
46
82
 
47
- export function getWorkspaceUpstreamIndexPath() {
48
- return path.join(getWorkspaceUpstreamDir(), 'PROBLEMS_INDEX.json');
83
+ export function getWorkspaceProblemPullDir(problemId, workspaceRoot = getWorkspaceRoot()) {
84
+ return path.join(getWorkspacePullsDir(workspaceRoot), String(problemId));
49
85
  }
50
86
 
51
- export function getWorkspaceUpstreamManifestPath() {
52
- return path.join(getWorkspaceUpstreamDir(), 'SYNC_MANIFEST.json');
87
+ export function getWorkspaceProblemArtifactDir(problemId, workspaceRoot = getWorkspaceRoot()) {
88
+ return path.join(getWorkspaceProblemPullDir(problemId, workspaceRoot), 'artifacts');
53
89
  }
54
90
 
55
- export function getWorkspaceScaffoldsDir() {
56
- return path.join(getWorkspaceDir(), 'scaffolds');
91
+ export function getWorkspaceProblemLiteratureDir(problemId, workspaceRoot = getWorkspaceRoot()) {
92
+ return path.join(getWorkspaceProblemPullDir(problemId, workspaceRoot), 'literature');
57
93
  }
58
94
 
59
- export function getWorkspaceProblemScaffoldDir(problemId) {
60
- return path.join(getWorkspaceScaffoldsDir(), String(problemId));
95
+ export function getWorkspaceCheckpointsDir(workspaceRoot = getWorkspaceRoot()) {
96
+ return path.join(getWorkspaceDir(workspaceRoot), 'checkpoints');
61
97
  }
62
98
 
63
- export function getWorkspacePullsDir() {
64
- return path.join(getWorkspaceDir(), 'pulls');
99
+ export function getWorkspaceProblemCheckpointsDir(workspaceRoot = getWorkspaceRoot()) {
100
+ return path.join(getWorkspaceCheckpointsDir(workspaceRoot), 'problem-checkpoints');
65
101
  }
66
102
 
67
- export function getWorkspaceProblemPullDir(problemId) {
68
- return path.join(getWorkspacePullsDir(), String(problemId));
103
+ export function getWorkspaceRouteCheckpointsDir(workspaceRoot = getWorkspaceRoot()) {
104
+ return path.join(getWorkspaceCheckpointsDir(workspaceRoot), 'route-checkpoints');
69
105
  }
70
106
 
71
- export function getWorkspaceProblemArtifactDir(problemId) {
72
- return path.join(getWorkspaceProblemPullDir(problemId), 'artifacts');
107
+ export function getWorkspaceCheckpointIndexPath(workspaceRoot = getWorkspaceRoot()) {
108
+ return path.join(getWorkspaceCheckpointsDir(workspaceRoot), 'CHECKPOINTS.md');
73
109
  }
74
110
 
75
- export function getWorkspaceProblemLiteratureDir(problemId) {
76
- return path.join(getWorkspaceProblemPullDir(problemId), 'literature');
111
+ export function getWorkspaceCheckpointJsonPath(workspaceRoot = getWorkspaceRoot()) {
112
+ return path.join(getWorkspaceCheckpointsDir(workspaceRoot), 'CHECKPOINTS.json');
77
113
  }
78
114
 
79
115
  export function getProblemDir(problemId) {
@@ -0,0 +1,106 @@
1
+ import {
2
+ getProblem,
3
+ } from '../atlas/catalog.js';
4
+ import { ensureConfig, loadConfig } from './config.js';
5
+ import { fileExists, writeJson } from './files.js';
6
+ import { gitSummary } from './git.js';
7
+ import {
8
+ getWorkspaceCheckpointIndexPath,
9
+ getWorkspaceConfigPath,
10
+ getWorkspaceDir,
11
+ getWorkspaceQuestionLedgerPath,
12
+ getWorkspaceRegistryBucketDir,
13
+ getWorkspaceRoot,
14
+ getWorkspaceStateMarkdownPath,
15
+ getWorkspaceStatePath,
16
+ } from './paths.js';
17
+ import { getProblemArtifactInventory } from './problem-artifacts.js';
18
+ import { syncState } from './state.js';
19
+
20
+ function checkpointPath(workspaceRoot) {
21
+ return getWorkspaceCheckpointIndexPath(workspaceRoot);
22
+ }
23
+
24
+ export function buildPreflightReport(options = {}, workspaceRoot = getWorkspaceRoot()) {
25
+ ensureConfig(workspaceRoot);
26
+ const config = loadConfig(workspaceRoot);
27
+ const state = syncState(workspaceRoot);
28
+ const problem = state.activeProblem ? getProblem(state.activeProblem) : null;
29
+ const inventory = problem ? getProblemArtifactInventory(problem) : null;
30
+ const git = gitSummary(workspaceRoot);
31
+
32
+ const checks = {
33
+ erdosRuntime: {
34
+ ok: fileExists(getWorkspaceDir(workspaceRoot)),
35
+ detail: getWorkspaceDir(workspaceRoot),
36
+ },
37
+ configFile: {
38
+ ok: fileExists(getWorkspaceConfigPath(workspaceRoot)),
39
+ detail: getWorkspaceConfigPath(workspaceRoot),
40
+ },
41
+ stateFile: {
42
+ ok: fileExists(getWorkspaceStatePath(workspaceRoot)),
43
+ detail: getWorkspaceStatePath(workspaceRoot),
44
+ },
45
+ stateMarkdown: {
46
+ ok: fileExists(getWorkspaceStateMarkdownPath(workspaceRoot)),
47
+ detail: getWorkspaceStateMarkdownPath(workspaceRoot),
48
+ },
49
+ questionLedger: {
50
+ ok: fileExists(getWorkspaceQuestionLedgerPath(workspaceRoot)),
51
+ detail: getWorkspaceQuestionLedgerPath(workspaceRoot),
52
+ },
53
+ checkpointShelf: {
54
+ ok: fileExists(checkpointPath(workspaceRoot)),
55
+ detail: checkpointPath(workspaceRoot),
56
+ },
57
+ activeProblem: {
58
+ ok: Boolean(problem),
59
+ detail: problem ? `${problem.problemId} (${problem.title})` : '(none)',
60
+ },
61
+ activeRoute: {
62
+ ok: !problem || problem.harnessDepth !== 'deep' ? true : Boolean(state.activeRoute),
63
+ detail: state.activeRoute || '(none)',
64
+ },
65
+ canonicalDossier: {
66
+ ok: !inventory ? false : inventory.canonicalArtifacts.every((artifact) => artifact.exists),
67
+ detail: inventory ? `${inventory.canonicalArtifacts.filter((artifact) => artifact.exists).length}/${inventory.canonicalArtifacts.length} canonical file(s)` : '(no active problem)',
68
+ },
69
+ upstreamSnapshot: {
70
+ ok: Boolean(inventory?.upstreamSnapshot),
71
+ detail: inventory?.upstreamSnapshot?.kind ?? '(missing)',
72
+ },
73
+ workspaceGit: {
74
+ ok: options.allowDirty ? true : git.clean !== false,
75
+ detail: git.isGitRepo ? `${git.branch || '(unknown branch)'} / ${git.detail}` : git.detail,
76
+ },
77
+ };
78
+
79
+ let verdict = 'ok';
80
+ if (!checks.erdosRuntime.ok || !checks.configFile.ok || !checks.stateFile.ok || !checks.activeProblem.ok || !checks.canonicalDossier.ok || !checks.upstreamSnapshot.ok) {
81
+ verdict = 'blocked';
82
+ } else if (!checks.questionLedger.ok || !checks.checkpointShelf.ok || !checks.activeRoute.ok || !checks.workspaceGit.ok) {
83
+ verdict = 'needs_attention';
84
+ }
85
+
86
+ const record = {
87
+ checkedAt: new Date().toISOString(),
88
+ workspaceRoot,
89
+ verdict,
90
+ activeProblem: state.activeProblem,
91
+ problemTitle: state.problemTitle,
92
+ activeRoute: state.activeRoute,
93
+ routeBreakthrough: state.routeBreakthrough,
94
+ problemSolved: state.problemSolved,
95
+ continuationMode: state.continuation.mode,
96
+ continuationDisplay: state.continuation.requestedMode === 'phase' ? 'route (phase-style)' : state.continuation.mode,
97
+ activeAgent: config.preferredAgent,
98
+ currentFrontier: state.currentFrontier,
99
+ nextHonestMove: state.nextHonestMove,
100
+ checks,
101
+ };
102
+
103
+ const registryPath = `${Date.now()}.json`;
104
+ writeJson(`${getWorkspaceRegistryBucketDir('preflight', workspaceRoot)}/${registryPath}`, record);
105
+ return record;
106
+ }