erdos-problems 0.1.2 → 0.1.4
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 +31 -3
- package/docs/ERDOS_PROBLEMS_PROBLEM_SCHEMA.md +20 -2
- package/docs/ERDOS_PROBLEMS_REPO_SPEC.md +11 -1
- package/package.json +1 -1
- package/packs/sunflower/compute/857/m8_exactness_cube_and_certificate_v0.yaml +26 -0
- package/src/cli/index.js +8 -0
- package/src/commands/problem.js +6 -0
- package/src/commands/pull.js +203 -0
- package/src/commands/sunflower.js +97 -0
- package/src/commands/workspace.js +15 -0
- package/src/runtime/paths.js +16 -0
- package/src/runtime/problem-artifacts.js +32 -0
- package/src/runtime/sunflower.js +208 -0
- package/src/runtime/workspace.js +2 -0
- package/src/upstream/site.js +80 -0
package/README.md
CHANGED
|
@@ -18,9 +18,11 @@ Official binary:
|
|
|
18
18
|
|
|
19
19
|
- atlas layer with canonical local `problems/<id>/problem.yaml` records
|
|
20
20
|
- bundled upstream snapshot from `teorth/erdosproblems`
|
|
21
|
-
- workspace `.erdos/` state for active-problem selection, upstream refreshes, reports, and
|
|
21
|
+
- workspace `.erdos/` state for active-problem selection, upstream refreshes, reports, scaffolds, and pull bundles
|
|
22
22
|
- sunflower cluster as the first deep harness pack
|
|
23
|
+
- packaged compute-lane metadata for deep sunflower problems, surfaced directly in the CLI
|
|
23
24
|
- seeded atlas now includes open and solved problems beyond sunflower
|
|
25
|
+
- unseeded problems can still be pulled into a workspace from the bundled upstream snapshot
|
|
24
26
|
|
|
25
27
|
Seeded problems:
|
|
26
28
|
- `18`, `20`, `89`, `536`, `542`, `856`, `857`, `1008`
|
|
@@ -30,7 +32,7 @@ Seeded problems:
|
|
|
30
32
|
```bash
|
|
31
33
|
erdos problem list --cluster sunflower
|
|
32
34
|
erdos bootstrap problem 857
|
|
33
|
-
erdos problem artifacts 857
|
|
35
|
+
erdos problem artifacts 857 --json
|
|
34
36
|
erdos dossier show 857
|
|
35
37
|
```
|
|
36
38
|
|
|
@@ -40,6 +42,22 @@ What `bootstrap` does:
|
|
|
40
42
|
- includes the upstream record when a bundled or workspace snapshot is available
|
|
41
43
|
- gives an agent a ready-to-read local artifact bundle immediately after install
|
|
42
44
|
|
|
45
|
+
## Pull bundles
|
|
46
|
+
|
|
47
|
+
For any problem number in the upstream snapshot, you can create a workspace bundle even if the problem is not yet seeded locally:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
erdos pull problem 857
|
|
51
|
+
erdos pull problem 999 --include-site
|
|
52
|
+
erdos pull problem 999 --refresh-upstream
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
What `pull` does:
|
|
56
|
+
- creates `.erdos/pulls/<id>/`
|
|
57
|
+
- includes the upstream record when available
|
|
58
|
+
- includes the local canonical dossier too when the problem is seeded locally
|
|
59
|
+
- can optionally add a live site snapshot and plain-text extract
|
|
60
|
+
|
|
43
61
|
## CLI
|
|
44
62
|
|
|
45
63
|
```bash
|
|
@@ -55,6 +73,8 @@ erdos problem artifacts 857 --json
|
|
|
55
73
|
erdos cluster list
|
|
56
74
|
erdos cluster show sunflower
|
|
57
75
|
erdos workspace show
|
|
76
|
+
erdos sunflower status 857
|
|
77
|
+
erdos sunflower status --json
|
|
58
78
|
erdos dossier show
|
|
59
79
|
erdos upstream show
|
|
60
80
|
erdos upstream sync
|
|
@@ -62,6 +82,8 @@ erdos upstream diff
|
|
|
62
82
|
erdos scaffold problem 857
|
|
63
83
|
erdos bootstrap problem 857
|
|
64
84
|
erdos bootstrap problem 857 --sync-upstream
|
|
85
|
+
erdos pull problem 857
|
|
86
|
+
erdos pull problem 857 --include-site
|
|
65
87
|
```
|
|
66
88
|
|
|
67
89
|
## Canonical Sources
|
|
@@ -84,8 +106,14 @@ For each seeded problem, the canonical local dossier lives in `problems/<id>/`:
|
|
|
84
106
|
The CLI can surface these directly:
|
|
85
107
|
- `erdos problem artifacts <id>` shows the canonical inventory
|
|
86
108
|
- `erdos problem artifacts <id> --json` emits machine-readable inventory
|
|
87
|
-
- `erdos scaffold problem <id>` copies the
|
|
109
|
+
- `erdos scaffold problem <id>` copies the seeded dossier into the active workspace
|
|
88
110
|
- `erdos bootstrap problem <id>` selects the problem and creates the scaffold in one step
|
|
111
|
+
- `erdos pull problem <id>` creates a workspace bundle for any problem in the upstream snapshot
|
|
112
|
+
|
|
113
|
+
For deep sunflower problems, the CLI also surfaces packaged compute lanes:
|
|
114
|
+
- `erdos sunflower status <id>` shows route + compute posture together
|
|
115
|
+
- `erdos workspace show` includes the active sunflower compute summary when applicable
|
|
116
|
+
- `erdos scaffold problem <id>` copies packaged compute packets into `.erdos/scaffolds/<id>/COMPUTE/`
|
|
89
117
|
|
|
90
118
|
## Notes
|
|
91
119
|
|
|
@@ -12,10 +12,11 @@ The goal is:
|
|
|
12
12
|
- open and solved problems use the same shape
|
|
13
13
|
- local dossier truth and upstream public truth stay explicitly separated
|
|
14
14
|
- packaged CLI installs can scaffold problem workspaces from canonical artifacts immediately
|
|
15
|
+
- unseeded problems can still be pulled into a workspace bundle from upstream truth
|
|
15
16
|
|
|
16
17
|
## Canonical Files
|
|
17
18
|
|
|
18
|
-
Each problem should have:
|
|
19
|
+
Each seeded problem should have:
|
|
19
20
|
|
|
20
21
|
- `problems/<id>/problem.yaml`
|
|
21
22
|
- `problems/<id>/STATEMENT.md`
|
|
@@ -29,6 +30,12 @@ Bundled upstream snapshot artifacts live in:
|
|
|
29
30
|
- `data/upstream/erdosproblems/PROBLEMS_INDEX.json`
|
|
30
31
|
- `data/upstream/erdosproblems/SYNC_MANIFEST.json`
|
|
31
32
|
|
|
33
|
+
Workspace-generated artifacts may live in:
|
|
34
|
+
|
|
35
|
+
- `.erdos/scaffolds/<id>/`
|
|
36
|
+
- `.erdos/pulls/<id>/`
|
|
37
|
+
- `.erdos/upstream/erdosproblems/`
|
|
38
|
+
|
|
32
39
|
## Canonical Truth Split
|
|
33
40
|
|
|
34
41
|
### External public truth
|
|
@@ -178,4 +185,15 @@ The sync commands should produce:
|
|
|
178
185
|
- upstream record snapshot for that problem when available
|
|
179
186
|
- generated artifact index for agent consumption
|
|
180
187
|
|
|
181
|
-
This
|
|
188
|
+
This is the seeded-problem path.
|
|
189
|
+
|
|
190
|
+
## Pull Contract
|
|
191
|
+
|
|
192
|
+
`erdos pull problem <id>` should create a broader workspace-ready bundle containing:
|
|
193
|
+
|
|
194
|
+
- upstream record snapshot for that problem when available
|
|
195
|
+
- generated artifact index for agent consumption
|
|
196
|
+
- seeded local dossier files too when the problem already exists in `problems/<id>/`
|
|
197
|
+
- optional live site snapshot and extracted text when `--include-site` is used
|
|
198
|
+
|
|
199
|
+
This makes a fresh npm-installed CLI immediately useful to an agentic workflow even for problems that are not yet fully seeded as local dossiers.
|
|
@@ -209,6 +209,7 @@ And also:
|
|
|
209
209
|
- gates
|
|
210
210
|
- atoms
|
|
211
211
|
- ready queue
|
|
212
|
+
- compute lane awareness
|
|
212
213
|
- generated checkpoints
|
|
213
214
|
- literature mapping
|
|
214
215
|
|
|
@@ -240,8 +241,18 @@ erdos sunflower setup 857
|
|
|
240
241
|
erdos sunflower warnings 857
|
|
241
242
|
erdos sunflower pass 857
|
|
242
243
|
erdos sunflower frontier 857
|
|
244
|
+
erdos sunflower status 857
|
|
243
245
|
```
|
|
244
246
|
|
|
247
|
+
`erdos sunflower status` should be the public-shell place where route state and
|
|
248
|
+
compute posture meet:
|
|
249
|
+
|
|
250
|
+
- active route
|
|
251
|
+
- breakthrough state
|
|
252
|
+
- packaged compute lane
|
|
253
|
+
- next compute action
|
|
254
|
+
- whether paid approval is required
|
|
255
|
+
|
|
245
256
|
## Content Policy
|
|
246
257
|
|
|
247
258
|
The repo should not blindly mirror `erdosproblems.com` text.
|
|
@@ -281,4 +292,3 @@ This keeps the repo publishable and reduces licensing ambiguity.
|
|
|
281
292
|
The first public promise should be:
|
|
282
293
|
|
|
283
294
|
> `erdos-problems` is a CLI atlas and staged research harness for Paul Erdős problems, with the sunflower family as the first deeply integrated pack.
|
|
284
|
-
|
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
lane_id: m8_exactness_cube_and_certificate_v0
|
|
2
|
+
problem_id: "857"
|
|
3
|
+
cluster: sunflower
|
|
4
|
+
question: "Is M(8,3) = 45 exact, equivalently is the target-46 SAT instance UNSAT?"
|
|
5
|
+
claim_level_goal: Exact
|
|
6
|
+
status: ready_for_local_scout
|
|
7
|
+
price_checked_local_date: "2026-03-25"
|
|
8
|
+
recommendation: cpu_first
|
|
9
|
+
approval_required: true
|
|
10
|
+
summary: "Packaged sunflower compute lane for the first exact M(8,3) certification packet. The next honest move is the local scout, not paid launch."
|
|
11
|
+
source_repo:
|
|
12
|
+
name: sunflower-coda
|
|
13
|
+
canonical_packet_path: analysis/compute/m8_exactness_cube_and_certificate_v0/launch_packet.yaml
|
|
14
|
+
public_feature:
|
|
15
|
+
command: erdos sunflower status 857
|
|
16
|
+
scaffold_subdir: COMPUTE
|
|
17
|
+
rungs:
|
|
18
|
+
- name: local_scout
|
|
19
|
+
role: local_unmetered
|
|
20
|
+
note: "Run the local scout packet first and require clean artifacts before any paid rung."
|
|
21
|
+
- name: cpu_transfer
|
|
22
|
+
role: paid_metered
|
|
23
|
+
note: "Preferred first paid rung once the local scout has earned promotion."
|
|
24
|
+
- name: heuristic_gpu_sidecar
|
|
25
|
+
role: paid_metered
|
|
26
|
+
note: "Optional and heuristic-only; it supports the exact lane but does not upgrade claims by itself."
|
package/src/cli/index.js
CHANGED
|
@@ -2,7 +2,9 @@ import { runBootstrapCommand } from '../commands/bootstrap.js';
|
|
|
2
2
|
import { runClusterCommand } from '../commands/cluster.js';
|
|
3
3
|
import { runDossierCommand } from '../commands/dossier.js';
|
|
4
4
|
import { runProblemCommand } from '../commands/problem.js';
|
|
5
|
+
import { runPullCommand } from '../commands/pull.js';
|
|
5
6
|
import { runScaffoldCommand } from '../commands/scaffold.js';
|
|
7
|
+
import { runSunflowerCommand } from '../commands/sunflower.js';
|
|
6
8
|
import { runUpstreamCommand } from '../commands/upstream.js';
|
|
7
9
|
import { runWorkspaceCommand } from '../commands/workspace.js';
|
|
8
10
|
|
|
@@ -18,12 +20,14 @@ function printUsage() {
|
|
|
18
20
|
console.log(' erdos cluster list');
|
|
19
21
|
console.log(' erdos cluster show <name>');
|
|
20
22
|
console.log(' erdos workspace show');
|
|
23
|
+
console.log(' erdos sunflower status [<id>] [--json]');
|
|
21
24
|
console.log(' erdos dossier show <id>');
|
|
22
25
|
console.log(' erdos upstream show');
|
|
23
26
|
console.log(' erdos upstream sync [--write-package-snapshot]');
|
|
24
27
|
console.log(' erdos upstream diff [--write-package-report]');
|
|
25
28
|
console.log(' erdos scaffold problem <id> [--dest <path>]');
|
|
26
29
|
console.log(' erdos bootstrap problem <id> [--dest <path>] [--sync-upstream]');
|
|
30
|
+
console.log(' erdos pull problem <id> [--dest <path>] [--include-site] [--refresh-upstream]');
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
const args = process.argv.slice(2);
|
|
@@ -39,6 +43,8 @@ if (!command || command === 'help' || command === '--help') {
|
|
|
39
43
|
exitCode = runClusterCommand(rest);
|
|
40
44
|
} else if (command === 'workspace') {
|
|
41
45
|
exitCode = runWorkspaceCommand(rest);
|
|
46
|
+
} else if (command === 'sunflower') {
|
|
47
|
+
exitCode = runSunflowerCommand(rest);
|
|
42
48
|
} else if (command === 'dossier') {
|
|
43
49
|
exitCode = runDossierCommand(rest);
|
|
44
50
|
} else if (command === 'upstream') {
|
|
@@ -47,6 +53,8 @@ if (!command || command === 'help' || command === '--help') {
|
|
|
47
53
|
exitCode = runScaffoldCommand(rest);
|
|
48
54
|
} else if (command === 'bootstrap') {
|
|
49
55
|
exitCode = await runBootstrapCommand(rest);
|
|
56
|
+
} else if (command === 'pull') {
|
|
57
|
+
exitCode = await runPullCommand(rest);
|
|
50
58
|
} else {
|
|
51
59
|
console.error(`Unknown command: ${command}`);
|
|
52
60
|
printUsage();
|
package/src/commands/problem.js
CHANGED
|
@@ -129,6 +129,12 @@ function printArtifactInventory(problem, inventory, asJson) {
|
|
|
129
129
|
if (inventory.packContext) {
|
|
130
130
|
console.log(`- ${inventory.packContext.label}: ${inventory.packContext.exists ? 'present' : 'missing'} (${inventory.packContext.path})`);
|
|
131
131
|
}
|
|
132
|
+
if (inventory.computePackets.length > 0) {
|
|
133
|
+
console.log('Compute packets:');
|
|
134
|
+
for (const packet of inventory.computePackets) {
|
|
135
|
+
console.log(`- ${packet.label}: ${packet.exists ? 'present' : 'missing'} (${packet.path}) [${packet.status}]`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
132
138
|
if (inventory.upstreamSnapshot) {
|
|
133
139
|
console.log('Upstream snapshot:');
|
|
134
140
|
console.log(`- kind: ${inventory.upstreamSnapshot.kind}`);
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { getProblem } from '../atlas/catalog.js';
|
|
3
|
+
import { ensureDir, writeJson, writeText } from '../runtime/files.js';
|
|
4
|
+
import { getWorkspaceProblemPullDir } from '../runtime/paths.js';
|
|
5
|
+
import { scaffoldProblem } from '../runtime/problem-artifacts.js';
|
|
6
|
+
import { loadActiveUpstreamSnapshot, syncUpstream } from '../upstream/sync.js';
|
|
7
|
+
import { fetchProblemSiteSnapshot } from '../upstream/site.js';
|
|
8
|
+
|
|
9
|
+
function parsePullArgs(args) {
|
|
10
|
+
const [kind, value, ...rest] = args;
|
|
11
|
+
if (kind !== 'problem') {
|
|
12
|
+
return { error: 'Only `erdos pull problem <id>` is supported right now.' };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let destination = null;
|
|
16
|
+
let includeSite = false;
|
|
17
|
+
let refreshUpstream = false;
|
|
18
|
+
|
|
19
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
20
|
+
const token = rest[index];
|
|
21
|
+
if (token === '--dest') {
|
|
22
|
+
destination = rest[index + 1];
|
|
23
|
+
if (!destination) {
|
|
24
|
+
return { error: 'Missing destination path after --dest.' };
|
|
25
|
+
}
|
|
26
|
+
index += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (token === '--include-site') {
|
|
30
|
+
includeSite = true;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (token === '--refresh-upstream') {
|
|
34
|
+
refreshUpstream = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
return { error: `Unknown pull option: ${token}` };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
problemId: value,
|
|
42
|
+
destination,
|
|
43
|
+
includeSite,
|
|
44
|
+
refreshUpstream,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writeUpstreamOnlyBundle(problemId, destination, upstreamRecord, snapshot) {
|
|
49
|
+
ensureDir(destination);
|
|
50
|
+
|
|
51
|
+
if (upstreamRecord) {
|
|
52
|
+
writeJson(path.join(destination, 'UPSTREAM_RECORD.json'), upstreamRecord);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const generatedAt = new Date().toISOString();
|
|
56
|
+
writeJson(path.join(destination, 'PROBLEM.json'), {
|
|
57
|
+
generatedAt,
|
|
58
|
+
problemId,
|
|
59
|
+
title: `Erdos Problem #${problemId}`,
|
|
60
|
+
cluster: null,
|
|
61
|
+
siteStatus: upstreamRecord?.status?.state ?? 'unknown',
|
|
62
|
+
repoStatus: 'upstream-only',
|
|
63
|
+
harnessDepth: 'unseeded',
|
|
64
|
+
sourceUrl: `https://www.erdosproblems.com/${problemId}`,
|
|
65
|
+
activeRoute: null,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
writeJson(path.join(destination, 'ARTIFACT_INDEX.json'), {
|
|
69
|
+
generatedAt,
|
|
70
|
+
problemId,
|
|
71
|
+
copiedArtifacts: [],
|
|
72
|
+
canonicalArtifacts: [],
|
|
73
|
+
upstreamSnapshot: snapshot
|
|
74
|
+
? {
|
|
75
|
+
kind: snapshot.kind,
|
|
76
|
+
manifestPath: snapshot.manifestPath,
|
|
77
|
+
indexPath: snapshot.indexPath,
|
|
78
|
+
yamlPath: snapshot.yamlPath,
|
|
79
|
+
upstreamCommit: snapshot.manifest.upstream_commit ?? null,
|
|
80
|
+
fetchedAt: snapshot.manifest.fetched_at,
|
|
81
|
+
}
|
|
82
|
+
: null,
|
|
83
|
+
includedUpstreamRecord: Boolean(upstreamRecord),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
writeText(
|
|
87
|
+
path.join(destination, 'README.md'),
|
|
88
|
+
[
|
|
89
|
+
`# Erdos Problem ${problemId} Pull Bundle`,
|
|
90
|
+
'',
|
|
91
|
+
'This bundle was generated from upstream public metadata.',
|
|
92
|
+
'',
|
|
93
|
+
`- Source: https://www.erdosproblems.com/${problemId}`,
|
|
94
|
+
`- Upstream record included: ${upstreamRecord ? 'yes' : 'no'}`,
|
|
95
|
+
'',
|
|
96
|
+
'This problem is not yet seeded locally as a canonical dossier in this package.',
|
|
97
|
+
'',
|
|
98
|
+
].join('\n'),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function maybeWriteSiteBundle(problemId, destination, includeSite) {
|
|
103
|
+
if (!includeSite) {
|
|
104
|
+
return { attempted: false, included: false, error: null };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const siteSnapshot = await fetchProblemSiteSnapshot(problemId);
|
|
109
|
+
writeText(path.join(destination, 'SITE_SNAPSHOT.html'), siteSnapshot.html);
|
|
110
|
+
writeText(path.join(destination, 'SITE_EXTRACT.txt'), siteSnapshot.text);
|
|
111
|
+
writeJson(path.join(destination, 'SITE_EXTRACT.json'), {
|
|
112
|
+
url: siteSnapshot.url,
|
|
113
|
+
fetchedAt: siteSnapshot.fetchedAt,
|
|
114
|
+
title: siteSnapshot.title,
|
|
115
|
+
previewLines: siteSnapshot.previewLines,
|
|
116
|
+
});
|
|
117
|
+
writeText(
|
|
118
|
+
path.join(destination, 'SITE_SUMMARY.md'),
|
|
119
|
+
[
|
|
120
|
+
`# Erdős Problem #${problemId} Site Summary`,
|
|
121
|
+
'',
|
|
122
|
+
`Source: ${siteSnapshot.url}`,
|
|
123
|
+
`Fetched at: ${siteSnapshot.fetchedAt}`,
|
|
124
|
+
`Title: ${siteSnapshot.title}`,
|
|
125
|
+
'',
|
|
126
|
+
'## Preview',
|
|
127
|
+
'',
|
|
128
|
+
...siteSnapshot.previewLines.map((line) => `- ${line}`),
|
|
129
|
+
'',
|
|
130
|
+
].join('\n'),
|
|
131
|
+
);
|
|
132
|
+
return { attempted: true, included: true, error: null };
|
|
133
|
+
} catch (error) {
|
|
134
|
+
writeText(path.join(destination, 'SITE_FETCH_ERROR.txt'), String(error.message ?? error));
|
|
135
|
+
return { attempted: true, included: false, error: String(error.message ?? error) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function runPullCommand(args) {
|
|
140
|
+
if (args.length === 0 || args[0] === 'help' || args[0] === '--help') {
|
|
141
|
+
console.log('Usage:');
|
|
142
|
+
console.log(' erdos pull problem <id> [--dest <path>] [--include-site] [--refresh-upstream]');
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const parsed = parsePullArgs(args);
|
|
147
|
+
if (parsed.error) {
|
|
148
|
+
console.error(parsed.error);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
if (!parsed.problemId) {
|
|
152
|
+
console.error('Missing problem id.');
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (parsed.refreshUpstream) {
|
|
157
|
+
await syncUpstream();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const localProblem = getProblem(parsed.problemId);
|
|
161
|
+
const snapshot = loadActiveUpstreamSnapshot();
|
|
162
|
+
const upstreamRecord = snapshot?.index?.by_number?.[String(parsed.problemId)] ?? null;
|
|
163
|
+
|
|
164
|
+
if (!localProblem && !upstreamRecord) {
|
|
165
|
+
console.error(`Problem ${parsed.problemId} is not present in the local dossier set or upstream snapshot.`);
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const destination = parsed.destination
|
|
170
|
+
? path.resolve(parsed.destination)
|
|
171
|
+
: getWorkspaceProblemPullDir(parsed.problemId);
|
|
172
|
+
|
|
173
|
+
let scaffoldResult = null;
|
|
174
|
+
if (localProblem) {
|
|
175
|
+
scaffoldResult = scaffoldProblem(localProblem, destination);
|
|
176
|
+
} else {
|
|
177
|
+
writeUpstreamOnlyBundle(String(parsed.problemId), destination, upstreamRecord, snapshot);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
const siteStatus = await maybeWriteSiteBundle(String(parsed.problemId), destination, parsed.includeSite);
|
|
182
|
+
|
|
183
|
+
writeJson(path.join(destination, 'PULL_STATUS.json'), {
|
|
184
|
+
generatedAt: new Date().toISOString(),
|
|
185
|
+
problemId: String(parsed.problemId),
|
|
186
|
+
usedLocalDossier: Boolean(localProblem),
|
|
187
|
+
includedUpstreamRecord: Boolean(upstreamRecord),
|
|
188
|
+
upstreamSnapshotKind: snapshot?.kind ?? null,
|
|
189
|
+
siteSnapshotAttempted: siteStatus.attempted,
|
|
190
|
+
siteSnapshotIncluded: siteStatus.included,
|
|
191
|
+
siteSnapshotError: siteStatus.error,
|
|
192
|
+
scaffoldArtifactsCopied: scaffoldResult?.copiedArtifacts.length ?? 0,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
console.log(`Pull bundle created: ${destination}`);
|
|
196
|
+
console.log(`Local canonical dossier included: ${localProblem ? 'yes' : 'no'}`);
|
|
197
|
+
console.log(`Upstream record included: ${upstreamRecord ? 'yes' : 'no'}`);
|
|
198
|
+
console.log(`Live site snapshot included: ${siteStatus.included ? 'yes' : 'no'}`);
|
|
199
|
+
if (siteStatus.error) {
|
|
200
|
+
console.log(`Live site snapshot note: ${siteStatus.error}`);
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getProblem } from '../atlas/catalog.js';
|
|
2
|
+
import { getWorkspaceRoot } from '../runtime/paths.js';
|
|
3
|
+
import { buildSunflowerStatusSnapshot, writeSunflowerStatusRecord } from '../runtime/sunflower.js';
|
|
4
|
+
import { readCurrentProblem } from '../runtime/workspace.js';
|
|
5
|
+
|
|
6
|
+
function parseStatusArgs(args) {
|
|
7
|
+
const parsed = {
|
|
8
|
+
problemId: null,
|
|
9
|
+
asJson: false,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
13
|
+
const token = args[index];
|
|
14
|
+
if (token === '--json') {
|
|
15
|
+
parsed.asJson = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (!parsed.problemId) {
|
|
19
|
+
parsed.problemId = token;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
return { error: `Unknown sunflower status option: ${token}` };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function printSunflowerStatus(snapshot, registryPaths) {
|
|
29
|
+
console.log(`${snapshot.displayName} sunflower harness`);
|
|
30
|
+
console.log(`Title: ${snapshot.title}`);
|
|
31
|
+
console.log(`Active route: ${snapshot.activeRoute ?? '(none)'}`);
|
|
32
|
+
console.log(`Route breakthrough: ${snapshot.routeBreakthrough ? 'yes' : 'no'}`);
|
|
33
|
+
console.log(`Open problem: ${snapshot.openProblem ? 'yes' : 'no'}`);
|
|
34
|
+
console.log(`Problem solved: ${snapshot.problemSolved ? 'yes' : 'no'}`);
|
|
35
|
+
console.log(`Compute lane present: ${snapshot.computeLanePresent ? 'yes' : 'no'}`);
|
|
36
|
+
console.log(`Compute lane count: ${snapshot.computeLaneCount}`);
|
|
37
|
+
console.log(`Compute summary: ${snapshot.computeSummary}`);
|
|
38
|
+
console.log(`Compute next: ${snapshot.computeNextAction}`);
|
|
39
|
+
if (snapshot.activePacket) {
|
|
40
|
+
console.log(`Compute lane: ${snapshot.activePacket.laneId} [${snapshot.activePacket.status}]`);
|
|
41
|
+
console.log(`Claim level goal: ${snapshot.activePacket.claimLevelGoal}`);
|
|
42
|
+
console.log(`Recommendation: ${snapshot.activePacket.recommendation || '(none)'}`);
|
|
43
|
+
console.log(`Approval required: ${snapshot.activePacket.approvalRequired ? 'yes' : 'no'}`);
|
|
44
|
+
console.log(`Price checked: ${snapshot.activePacket.priceCheckedLocalDate || '(unknown)'}`);
|
|
45
|
+
console.log(`Packet file: ${snapshot.activePacket.packetFileName}`);
|
|
46
|
+
}
|
|
47
|
+
console.log(`Registry record: ${registryPaths.latestPath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function runSunflowerCommand(args) {
|
|
51
|
+
const [subcommand, ...rest] = args;
|
|
52
|
+
|
|
53
|
+
if (!subcommand || subcommand === 'help' || subcommand === '--help') {
|
|
54
|
+
console.log('Usage:');
|
|
55
|
+
console.log(' erdos sunflower status [<id>] [--json]');
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (subcommand !== 'status') {
|
|
60
|
+
console.error(`Unknown sunflower subcommand: ${subcommand}`);
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const parsed = parseStatusArgs(rest);
|
|
65
|
+
if (parsed.error) {
|
|
66
|
+
console.error(parsed.error);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const problemId = parsed.problemId ?? readCurrentProblem();
|
|
71
|
+
if (!problemId) {
|
|
72
|
+
console.error('Missing problem id and no active problem is selected.');
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const problem = getProblem(problemId);
|
|
77
|
+
if (!problem) {
|
|
78
|
+
console.error(`Unknown problem: ${problemId}`);
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (problem.cluster !== 'sunflower') {
|
|
83
|
+
console.error(`Problem ${problem.problemId} is not in the sunflower harness.`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const snapshot = buildSunflowerStatusSnapshot(problem);
|
|
88
|
+
const registryPaths = writeSunflowerStatusRecord(problem, snapshot, getWorkspaceRoot());
|
|
89
|
+
|
|
90
|
+
if (parsed.asJson) {
|
|
91
|
+
console.log(JSON.stringify({ ...snapshot, registryPaths }, null, 2));
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
printSunflowerStatus(snapshot, registryPaths);
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getProblem } from '../atlas/catalog.js';
|
|
2
|
+
import { buildSunflowerStatusSnapshot } from '../runtime/sunflower.js';
|
|
1
3
|
import { getWorkspaceSummary } from '../runtime/workspace.js';
|
|
2
4
|
|
|
3
5
|
export function runWorkspaceCommand(args) {
|
|
@@ -21,6 +23,19 @@ export function runWorkspaceCommand(args) {
|
|
|
21
23
|
console.log(`Active problem: ${summary.activeProblem ?? '(none)'}`);
|
|
22
24
|
console.log(`Workspace upstream dir: ${summary.upstreamDir}`);
|
|
23
25
|
console.log(`Workspace scaffold dir: ${summary.scaffoldDir}`);
|
|
26
|
+
console.log(`Workspace pull dir: ${summary.pullDir}`);
|
|
24
27
|
console.log(`Updated at: ${summary.updatedAt ?? '(none)'}`);
|
|
28
|
+
if (summary.activeProblem) {
|
|
29
|
+
const problem = getProblem(summary.activeProblem);
|
|
30
|
+
if (problem?.cluster === 'sunflower') {
|
|
31
|
+
const sunflower = buildSunflowerStatusSnapshot(problem);
|
|
32
|
+
console.log(`Sunflower route: ${sunflower.activeRoute ?? '(none)'}`);
|
|
33
|
+
console.log(`Sunflower compute: ${sunflower.computeLanePresent ? 'yes' : 'no'}`);
|
|
34
|
+
if (sunflower.activePacket) {
|
|
35
|
+
console.log(`Sunflower compute lane: ${sunflower.activePacket.laneId} [${sunflower.activePacket.status}]`);
|
|
36
|
+
}
|
|
37
|
+
console.log(`Sunflower compute next: ${sunflower.computeNextAction}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
25
40
|
return 0;
|
|
26
41
|
}
|
package/src/runtime/paths.js
CHANGED
|
@@ -12,6 +12,14 @@ export function getWorkspaceDir() {
|
|
|
12
12
|
return path.join(getWorkspaceRoot(), '.erdos');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export function getWorkspaceRegistryDir(workspaceRoot = getWorkspaceRoot()) {
|
|
16
|
+
return path.join(workspaceRoot, '.erdos', 'registry');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getWorkspaceComputeRegistryDir(workspaceRoot = getWorkspaceRoot()) {
|
|
20
|
+
return path.join(getWorkspaceRegistryDir(workspaceRoot), 'compute');
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
export function getWorkspaceStatePath() {
|
|
16
24
|
return path.join(getWorkspaceDir(), 'state.json');
|
|
17
25
|
}
|
|
@@ -52,6 +60,14 @@ export function getWorkspaceProblemScaffoldDir(problemId) {
|
|
|
52
60
|
return path.join(getWorkspaceScaffoldsDir(), String(problemId));
|
|
53
61
|
}
|
|
54
62
|
|
|
63
|
+
export function getWorkspacePullsDir() {
|
|
64
|
+
return path.join(getWorkspaceDir(), 'pulls');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getWorkspaceProblemPullDir(problemId) {
|
|
68
|
+
return path.join(getWorkspacePullsDir(), String(problemId));
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
export function getProblemDir(problemId) {
|
|
56
72
|
return path.join(repoRoot, 'problems', String(problemId));
|
|
57
73
|
}
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { loadActiveUpstreamSnapshot } from '../upstream/sync.js';
|
|
4
4
|
import { copyFileIfPresent, ensureDir, writeJson, writeText } from './files.js';
|
|
5
5
|
import { getPackDir } from './paths.js';
|
|
6
|
+
import { listSunflowerComputePackets } from './sunflower.js';
|
|
6
7
|
|
|
7
8
|
const DOSSIER_FILES = [
|
|
8
9
|
['problem.yaml', 'problemYamlPath', 'problem.yaml'],
|
|
@@ -42,6 +43,15 @@ export function getProblemArtifactInventory(problem) {
|
|
|
42
43
|
}
|
|
43
44
|
: null;
|
|
44
45
|
|
|
46
|
+
const computePackets = listSunflowerComputePackets(problem.problemId).map((packet) => ({
|
|
47
|
+
label: packet.laneId || packet.packetFileName,
|
|
48
|
+
path: packet.packetPath,
|
|
49
|
+
destinationName: packet.packetFileName,
|
|
50
|
+
exists: fs.existsSync(packet.packetPath),
|
|
51
|
+
status: packet.status,
|
|
52
|
+
claimLevelGoal: packet.claimLevelGoal,
|
|
53
|
+
}));
|
|
54
|
+
|
|
45
55
|
return {
|
|
46
56
|
generatedAt: new Date().toISOString(),
|
|
47
57
|
problemId: problem.problemId,
|
|
@@ -54,6 +64,7 @@ export function getProblemArtifactInventory(problem) {
|
|
|
54
64
|
problemDir: problem.problemDir,
|
|
55
65
|
canonicalArtifacts,
|
|
56
66
|
packContext,
|
|
67
|
+
computePackets,
|
|
57
68
|
upstreamSnapshot: snapshot
|
|
58
69
|
? {
|
|
59
70
|
kind: snapshot.kind,
|
|
@@ -96,6 +107,24 @@ export function scaffoldProblem(problem, destination) {
|
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
|
|
110
|
+
const computeArtifacts = [];
|
|
111
|
+
const computeDir = path.join(destination, 'COMPUTE');
|
|
112
|
+
for (const packet of inventory.computePackets) {
|
|
113
|
+
if (!packet.exists) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const destinationPath = path.join(computeDir, packet.destinationName);
|
|
117
|
+
if (copyFileIfPresent(packet.path, destinationPath)) {
|
|
118
|
+
computeArtifacts.push({
|
|
119
|
+
label: packet.label,
|
|
120
|
+
sourcePath: packet.path,
|
|
121
|
+
destinationPath,
|
|
122
|
+
status: packet.status,
|
|
123
|
+
claimLevelGoal: packet.claimLevelGoal,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
99
128
|
if (inventory.upstreamRecord) {
|
|
100
129
|
writeJson(path.join(destination, 'UPSTREAM_RECORD.json'), inventory.upstreamRecord);
|
|
101
130
|
}
|
|
@@ -117,8 +146,10 @@ export function scaffoldProblem(problem, destination) {
|
|
|
117
146
|
problemId: problem.problemId,
|
|
118
147
|
bundledProblemDir: problem.problemDir,
|
|
119
148
|
copiedArtifacts,
|
|
149
|
+
computeArtifacts,
|
|
120
150
|
packContext: inventory.packContext,
|
|
121
151
|
canonicalArtifacts: inventory.canonicalArtifacts,
|
|
152
|
+
computePackets: inventory.computePackets,
|
|
122
153
|
upstreamSnapshot: inventory.upstreamSnapshot,
|
|
123
154
|
includedUpstreamRecord: inventory.upstreamRecordIncluded,
|
|
124
155
|
};
|
|
@@ -138,6 +169,7 @@ export function scaffoldProblem(problem, destination) {
|
|
|
138
169
|
`- Repo status: ${problem.repoStatus}`,
|
|
139
170
|
`- Harness depth: ${problem.harnessDepth}`,
|
|
140
171
|
`- Upstream record included: ${inventory.upstreamRecordIncluded ? 'yes' : 'no'}`,
|
|
172
|
+
`- Compute packets copied: ${computeArtifacts.length}`,
|
|
141
173
|
'',
|
|
142
174
|
].join('\n'),
|
|
143
175
|
);
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import { writeJson } from './files.js';
|
|
5
|
+
import { getPackDir, getWorkspaceComputeRegistryDir } from './paths.js';
|
|
6
|
+
|
|
7
|
+
const CLAIM_LEVEL_PRIORITY = {
|
|
8
|
+
Exact: 4,
|
|
9
|
+
Verified: 3,
|
|
10
|
+
Heuristic: 2,
|
|
11
|
+
Conjecture: 1,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const STATUS_PRIORITY = {
|
|
15
|
+
paid_active: 7,
|
|
16
|
+
local_scout_running: 6,
|
|
17
|
+
ready_for_paid_transfer: 5,
|
|
18
|
+
ready_for_local_scout: 4,
|
|
19
|
+
active: 3,
|
|
20
|
+
blocked: 2,
|
|
21
|
+
complete: 1,
|
|
22
|
+
unknown: 0,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function getSunflowerComputeDir(problemId) {
|
|
26
|
+
return path.join(getPackDir('sunflower'), 'compute', String(problemId));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readComputePacket(packetPath) {
|
|
30
|
+
const parsed = parse(fs.readFileSync(packetPath, 'utf8')) ?? {};
|
|
31
|
+
return {
|
|
32
|
+
laneId: String(parsed.lane_id ?? '').trim(),
|
|
33
|
+
problemId: String(parsed.problem_id ?? '').trim(),
|
|
34
|
+
cluster: String(parsed.cluster ?? 'sunflower').trim(),
|
|
35
|
+
question: String(parsed.question ?? '').trim(),
|
|
36
|
+
claimLevelGoal: String(parsed.claim_level_goal ?? '').trim(),
|
|
37
|
+
status: String(parsed.status ?? 'unknown').trim() || 'unknown',
|
|
38
|
+
priceCheckedLocalDate: String(parsed.price_checked_local_date ?? '').trim(),
|
|
39
|
+
recommendation: String(parsed.recommendation ?? '').trim(),
|
|
40
|
+
approvalRequired: Boolean(parsed.approval_required),
|
|
41
|
+
summary: String(parsed.summary ?? '').trim(),
|
|
42
|
+
packetPath,
|
|
43
|
+
packetFileName: path.basename(packetPath),
|
|
44
|
+
sourceRepo: parsed.source_repo ?? null,
|
|
45
|
+
publicFeature: parsed.public_feature ?? null,
|
|
46
|
+
rungs: Array.isArray(parsed.rungs) ? parsed.rungs : [],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function listSunflowerComputePackets(problemId) {
|
|
51
|
+
const computeDir = getSunflowerComputeDir(problemId);
|
|
52
|
+
if (!fs.existsSync(computeDir)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return fs
|
|
57
|
+
.readdirSync(computeDir)
|
|
58
|
+
.filter((entry) => entry.endsWith('.yaml') || entry.endsWith('.yml'))
|
|
59
|
+
.sort()
|
|
60
|
+
.map((entry) => readComputePacket(path.join(computeDir, entry)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function chooseActivePacket(packets) {
|
|
64
|
+
if (packets.length === 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ranked = [...packets].sort((left, right) => {
|
|
69
|
+
const statusDelta =
|
|
70
|
+
(STATUS_PRIORITY[right.status] ?? STATUS_PRIORITY.unknown)
|
|
71
|
+
- (STATUS_PRIORITY[left.status] ?? STATUS_PRIORITY.unknown);
|
|
72
|
+
if (statusDelta !== 0) {
|
|
73
|
+
return statusDelta;
|
|
74
|
+
}
|
|
75
|
+
const claimDelta =
|
|
76
|
+
(CLAIM_LEVEL_PRIORITY[right.claimLevelGoal] ?? 0)
|
|
77
|
+
- (CLAIM_LEVEL_PRIORITY[left.claimLevelGoal] ?? 0);
|
|
78
|
+
if (claimDelta !== 0) {
|
|
79
|
+
return claimDelta;
|
|
80
|
+
}
|
|
81
|
+
return right.laneId.localeCompare(left.laneId);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return ranked[0];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function deriveSummary(packet) {
|
|
88
|
+
if (!packet) {
|
|
89
|
+
return {
|
|
90
|
+
computeSummary: 'No packaged compute lane is registered for this sunflower problem yet.',
|
|
91
|
+
computeNextAction: 'Stay in the atlas/dossier lane until a frozen benchmark and artifact bundle exist.',
|
|
92
|
+
budgetState: 'not_applicable',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const budgetState = packet.approvalRequired ? 'approval_required' : 'not_required';
|
|
97
|
+
if (packet.summary) {
|
|
98
|
+
return {
|
|
99
|
+
computeSummary: packet.summary,
|
|
100
|
+
computeNextAction:
|
|
101
|
+
packet.status === 'ready_for_local_scout'
|
|
102
|
+
? 'Run the local scout first, then decide whether paid compute is honestly earned.'
|
|
103
|
+
: packet.status === 'ready_for_paid_transfer'
|
|
104
|
+
? 'If budget approval is granted, launch the preferred paid rung and mirror the artifacts back into the workspace.'
|
|
105
|
+
: packet.status === 'paid_active'
|
|
106
|
+
? 'Let the active metered run finish and pull back logs, artifacts, and impact notes before upgrading any claim.'
|
|
107
|
+
: packet.status === 'complete'
|
|
108
|
+
? 'Review the completed artifact bundle and decide whether the next move is replay, certificate assembly, or a new frozen lane.'
|
|
109
|
+
: 'Refresh the compute lane status before making it part of the next route decision.',
|
|
110
|
+
budgetState,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (packet.status === 'ready_for_local_scout') {
|
|
115
|
+
return {
|
|
116
|
+
computeSummary: `${packet.laneId} is packaged and ready for its local scout.`,
|
|
117
|
+
computeNextAction: 'Run the local scout first, then decide whether paid compute is honestly earned.',
|
|
118
|
+
budgetState,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (packet.status === 'ready_for_paid_transfer') {
|
|
123
|
+
return {
|
|
124
|
+
computeSummary: `${packet.laneId} cleared the scout and is ready for the ${packet.recommendation || 'paid'} rung.`,
|
|
125
|
+
computeNextAction: 'If budget approval is granted, launch the preferred paid rung and mirror the artifacts back into the workspace.',
|
|
126
|
+
budgetState,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (packet.status === 'paid_active') {
|
|
131
|
+
return {
|
|
132
|
+
computeSummary: `${packet.laneId} is actively using metered compute.`,
|
|
133
|
+
computeNextAction: 'Let the active metered run finish and pull back logs, artifacts, and impact notes before upgrading any claim.',
|
|
134
|
+
budgetState,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (packet.status === 'complete') {
|
|
139
|
+
return {
|
|
140
|
+
computeSummary: `${packet.laneId} has a completed compute packet.`,
|
|
141
|
+
computeNextAction: 'Review the completed artifact bundle and decide whether the next move is replay, certificate assembly, or a new frozen lane.',
|
|
142
|
+
budgetState,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
computeSummary: `${packet.laneId} is packaged with status ${packet.status}.`,
|
|
148
|
+
computeNextAction: 'Refresh the compute lane status before making it part of the next route decision.',
|
|
149
|
+
budgetState,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function compactPacket(packet) {
|
|
154
|
+
if (!packet) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
laneId: packet.laneId,
|
|
160
|
+
status: packet.status,
|
|
161
|
+
claimLevelGoal: packet.claimLevelGoal,
|
|
162
|
+
question: packet.question,
|
|
163
|
+
recommendation: packet.recommendation,
|
|
164
|
+
approvalRequired: packet.approvalRequired,
|
|
165
|
+
priceCheckedLocalDate: packet.priceCheckedLocalDate,
|
|
166
|
+
packetFileName: packet.packetFileName,
|
|
167
|
+
sourceRepo: packet.sourceRepo,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function buildSunflowerStatusSnapshot(problem) {
|
|
172
|
+
const packets = listSunflowerComputePackets(problem.problemId);
|
|
173
|
+
const activePacket = chooseActivePacket(packets);
|
|
174
|
+
const summary = deriveSummary(activePacket);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
generatedAt: new Date().toISOString(),
|
|
178
|
+
problemId: problem.problemId,
|
|
179
|
+
displayName: problem.displayName,
|
|
180
|
+
title: problem.title,
|
|
181
|
+
cluster: problem.cluster,
|
|
182
|
+
activeRoute: problem.researchState?.active_route ?? null,
|
|
183
|
+
routeBreakthrough: Boolean(problem.researchState?.route_breakthrough),
|
|
184
|
+
openProblem: Boolean(problem.researchState?.open_problem),
|
|
185
|
+
problemSolved: Boolean(problem.researchState?.problem_solved),
|
|
186
|
+
computeLanePresent: Boolean(activePacket),
|
|
187
|
+
computeLaneCount: packets.length,
|
|
188
|
+
computeSummary: summary.computeSummary,
|
|
189
|
+
computeNextAction: summary.computeNextAction,
|
|
190
|
+
budgetState: summary.budgetState,
|
|
191
|
+
activePacket: compactPacket(activePacket),
|
|
192
|
+
computePackets: packets.map((packet) => compactPacket(packet)),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function writeSunflowerStatusRecord(problem, snapshot, workspaceRoot) {
|
|
197
|
+
const registryDir = getWorkspaceComputeRegistryDir(workspaceRoot);
|
|
198
|
+
const timestamp = new Date().toISOString().replaceAll(':', '-');
|
|
199
|
+
const timestampedPath = path.join(registryDir, `${timestamp}__p${problem.problemId}.json`);
|
|
200
|
+
const latestPath = path.join(registryDir, `latest__p${problem.problemId}.json`);
|
|
201
|
+
writeJson(timestampedPath, snapshot);
|
|
202
|
+
writeJson(latestPath, snapshot);
|
|
203
|
+
return {
|
|
204
|
+
registryDir,
|
|
205
|
+
timestampedPath,
|
|
206
|
+
latestPath,
|
|
207
|
+
};
|
|
208
|
+
}
|
package/src/runtime/workspace.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import {
|
|
3
3
|
getCurrentProblemPath,
|
|
4
4
|
getWorkspaceDir,
|
|
5
|
+
getWorkspaceProblemPullDir,
|
|
5
6
|
getWorkspaceProblemScaffoldDir,
|
|
6
7
|
getWorkspaceRoot,
|
|
7
8
|
getWorkspaceStatePath,
|
|
@@ -66,6 +67,7 @@ export function getWorkspaceSummary() {
|
|
|
66
67
|
activeProblem,
|
|
67
68
|
upstreamDir: getWorkspaceUpstreamDir(),
|
|
68
69
|
scaffoldDir: activeProblem ? getWorkspaceProblemScaffoldDir(activeProblem) : getWorkspaceProblemScaffoldDir('<problem-id>'),
|
|
70
|
+
pullDir: activeProblem ? getWorkspaceProblemPullDir(activeProblem) : getWorkspaceProblemPullDir('<problem-id>'),
|
|
69
71
|
updatedAt: state?.updatedAt ?? null,
|
|
70
72
|
};
|
|
71
73
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const SITE_BASE_URL = 'https://www.erdosproblems.com';
|
|
2
|
+
|
|
3
|
+
function decodeEntities(text) {
|
|
4
|
+
return text
|
|
5
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
|
|
6
|
+
.replace(/&#(\d+);/g, (_, decimal) => String.fromCodePoint(Number.parseInt(decimal, 10)))
|
|
7
|
+
.replace(/ /g, ' ')
|
|
8
|
+
.replace(/&/g, '&')
|
|
9
|
+
.replace(/"/g, '"')
|
|
10
|
+
.replace(/'/g, "'")
|
|
11
|
+
.replace(/</g, '<')
|
|
12
|
+
.replace(/>/g, '>');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function collapseWhitespace(text) {
|
|
16
|
+
return text.replace(/[ \t]+/g, ' ').replace(/\s*\n\s*/g, '\n').trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function htmlToReadableText(html) {
|
|
20
|
+
const withoutScripts = html
|
|
21
|
+
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
22
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ');
|
|
23
|
+
const blockSeparated = withoutScripts
|
|
24
|
+
.replace(/<(br|\/p|\/div|\/li|\/h1|\/h2|\/h3|\/section|\/article|\/tr)>/gi, '\n')
|
|
25
|
+
.replace(/<li[^>]*>/gi, '- ')
|
|
26
|
+
.replace(/<p[^>]*>/gi, '\n')
|
|
27
|
+
.replace(/<div[^>]*>/gi, '\n')
|
|
28
|
+
.replace(/<h[1-6][^>]*>/gi, '\n');
|
|
29
|
+
const stripped = blockSeparated.replace(/<[^>]+>/g, ' ');
|
|
30
|
+
const decoded = decodeEntities(stripped);
|
|
31
|
+
const normalizedLines = decoded
|
|
32
|
+
.split('\n')
|
|
33
|
+
.map((line) => collapseWhitespace(line))
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
return normalizedLines.join('\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractTitle(html, problemId) {
|
|
39
|
+
const match = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
40
|
+
if (!match) {
|
|
41
|
+
return `Erdos Problem #${problemId}`;
|
|
42
|
+
}
|
|
43
|
+
return collapseWhitespace(decodeEntities(match[1]));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function selectPreviewLines(lines) {
|
|
47
|
+
const anchorIndex = lines.findIndex((line) => /^(OPEN|SOLVED|PROVED|PARTIAL)\b/i.test(line));
|
|
48
|
+
if (anchorIndex >= 0) {
|
|
49
|
+
return lines.slice(anchorIndex, anchorIndex + 24);
|
|
50
|
+
}
|
|
51
|
+
return lines.slice(0, 24);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function fetchProblemSiteSnapshot(problemId) {
|
|
55
|
+
const url = `${SITE_BASE_URL}/${problemId}`;
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
headers: {
|
|
58
|
+
'User-Agent': 'erdos-problems-cli',
|
|
59
|
+
Accept: 'text/html',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(`Unable to fetch problem page ${problemId}: ${response.status}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const html = await response.text();
|
|
68
|
+
const text = htmlToReadableText(html);
|
|
69
|
+
const title = extractTitle(html, problemId);
|
|
70
|
+
const lines = text.split('\n').filter(Boolean);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
url,
|
|
74
|
+
fetchedAt: new Date().toISOString(),
|
|
75
|
+
html,
|
|
76
|
+
title,
|
|
77
|
+
text,
|
|
78
|
+
previewLines: selectPreviewLines(lines),
|
|
79
|
+
};
|
|
80
|
+
}
|