erdos-problems 0.1.1 → 0.1.3

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,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,60 @@
1
+ import path from 'node:path';
2
+ import { getProblem } from '../atlas/catalog.js';
3
+ import { scaffoldProblem } from '../runtime/problem-artifacts.js';
4
+ import { getWorkspaceProblemScaffoldDir } from '../runtime/paths.js';
5
+ import { readCurrentProblem } from '../runtime/workspace.js';
6
+
7
+ export function parseScaffoldArgs(args) {
8
+ const [kind, value, ...rest] = args;
9
+ if (kind !== 'problem') {
10
+ return { error: 'Only `erdos scaffold problem <id>` is supported right now.' };
11
+ }
12
+ let destination = null;
13
+ for (let index = 0; index < rest.length; index += 1) {
14
+ const token = rest[index];
15
+ if (token === '--dest') {
16
+ destination = rest[index + 1];
17
+ if (!destination) {
18
+ return { error: 'Missing destination path after --dest.' };
19
+ }
20
+ index += 1;
21
+ continue;
22
+ }
23
+ return { error: `Unknown scaffold option: ${token}` };
24
+ }
25
+ return { problemId: value ?? readCurrentProblem(), destination };
26
+ }
27
+
28
+ export function runScaffoldCommand(args) {
29
+ if (args.length === 0 || args[0] === 'help' || args[0] === '--help') {
30
+ console.log('Usage:');
31
+ console.log(' erdos scaffold problem <id> [--dest <path>]');
32
+ return 0;
33
+ }
34
+
35
+ const parsed = parseScaffoldArgs(args);
36
+ if (parsed.error) {
37
+ console.error(parsed.error);
38
+ return 1;
39
+ }
40
+ if (!parsed.problemId) {
41
+ console.error('Missing problem id and no active problem is selected.');
42
+ return 1;
43
+ }
44
+
45
+ const problem = getProblem(parsed.problemId);
46
+ if (!problem) {
47
+ console.error(`Unknown problem: ${parsed.problemId}`);
48
+ return 1;
49
+ }
50
+
51
+ const destination = parsed.destination
52
+ ? path.resolve(parsed.destination)
53
+ : getWorkspaceProblemScaffoldDir(problem.problemId);
54
+
55
+ const result = scaffoldProblem(problem, destination);
56
+ console.log(`Scaffold created: ${result.destination}`);
57
+ console.log(`Artifacts copied: ${result.copiedArtifacts.length}`);
58
+ console.log(`Upstream record included: ${result.inventory.upstreamRecordIncluded ? 'yes' : 'no'}`);
59
+ return 0;
60
+ }
@@ -0,0 +1,60 @@
1
+ import { buildUpstreamDiff, loadActiveUpstreamSnapshot, syncUpstream, writeDiffArtifacts } from '../upstream/sync.js';
2
+
3
+ export async function runUpstreamCommand(args) {
4
+ const [subcommand, ...rest] = args;
5
+
6
+ if (!subcommand || subcommand === 'help' || subcommand === '--help') {
7
+ console.log('Usage:');
8
+ console.log(' erdos upstream show');
9
+ console.log(' erdos upstream sync [--write-package-snapshot]');
10
+ console.log(' erdos upstream diff [--write-package-report]');
11
+ return 0;
12
+ }
13
+
14
+ if (subcommand === 'show') {
15
+ const snapshot = loadActiveUpstreamSnapshot();
16
+ if (!snapshot) {
17
+ console.log('No upstream snapshot available yet. Run `erdos upstream sync`.');
18
+ return 0;
19
+ }
20
+ console.log(`Snapshot kind: ${snapshot.kind}`);
21
+ console.log(`Upstream repo: ${snapshot.manifest.upstream_repo}`);
22
+ console.log(`Upstream commit: ${snapshot.manifest.upstream_commit ?? '(unknown)'}`);
23
+ console.log(`Fetched at: ${snapshot.manifest.fetched_at}`);
24
+ console.log(`Entries: ${snapshot.manifest.entry_count}`);
25
+ return 0;
26
+ }
27
+
28
+ if (subcommand === 'sync') {
29
+ const writePackageSnapshot = rest.includes('--write-package-snapshot');
30
+ const result = await syncUpstream({ writePackageSnapshot });
31
+ console.log(`Fetched upstream commit: ${result.snapshot.manifest.upstream_commit ?? '(unknown)'}`);
32
+ console.log(`Workspace snapshot: ${result.workspacePaths.manifestPath}`);
33
+ if (result.bundledPaths) {
34
+ console.log(`Bundled snapshot: ${result.bundledPaths.manifestPath}`);
35
+ }
36
+ const diffArtifacts = writeDiffArtifacts({ writePackageReport: writePackageSnapshot });
37
+ console.log(`Workspace diff report: ${diffArtifacts.workspaceDiffPath}`);
38
+ if (diffArtifacts.repoDiffPath) {
39
+ console.log(`Repo diff report: ${diffArtifacts.repoDiffPath}`);
40
+ }
41
+ return 0;
42
+ }
43
+
44
+ if (subcommand === 'diff') {
45
+ const writePackageReport = rest.includes('--write-package-report');
46
+ const diffArtifacts = writeDiffArtifacts({ writePackageReport });
47
+ const diff = buildUpstreamDiff();
48
+ console.log(`Local seeded problems: ${diff.localProblemCount}`);
49
+ console.log(`Upstream total problems: ${diff.upstreamProblemCount}`);
50
+ console.log(`Upstream-only count: ${diff.upstreamOnlyCount}`);
51
+ console.log(`Workspace diff report: ${diffArtifacts.workspaceDiffPath}`);
52
+ if (diffArtifacts.repoDiffPath) {
53
+ console.log(`Repo diff report: ${diffArtifacts.repoDiffPath}`);
54
+ }
55
+ return 0;
56
+ }
57
+
58
+ console.error(`Unknown upstream subcommand: ${subcommand}`);
59
+ return 1;
60
+ }
@@ -19,6 +19,9 @@ export function runWorkspaceCommand(args) {
19
19
  console.log(`State dir: ${summary.stateDir}`);
20
20
  console.log(`Initialized: ${summary.hasState ? 'yes' : 'no'}`);
21
21
  console.log(`Active problem: ${summary.activeProblem ?? '(none)'}`);
22
+ console.log(`Workspace upstream dir: ${summary.upstreamDir}`);
23
+ console.log(`Workspace scaffold dir: ${summary.scaffoldDir}`);
24
+ console.log(`Workspace pull dir: ${summary.pullDir}`);
22
25
  console.log(`Updated at: ${summary.updatedAt ?? '(none)'}`);
23
26
  return 0;
24
27
  }
@@ -0,0 +1,37 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export function ensureDir(dirPath) {
5
+ fs.mkdirSync(dirPath, { recursive: true });
6
+ }
7
+
8
+ export function writeJson(filePath, payload) {
9
+ ensureDir(path.dirname(filePath));
10
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2) + '\n');
11
+ }
12
+
13
+ export function readJson(filePath) {
14
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
15
+ }
16
+
17
+ export function writeText(filePath, text) {
18
+ ensureDir(path.dirname(filePath));
19
+ fs.writeFileSync(filePath, text);
20
+ }
21
+
22
+ export function readText(filePath) {
23
+ return fs.readFileSync(filePath, 'utf8');
24
+ }
25
+
26
+ export function fileExists(filePath) {
27
+ return fs.existsSync(filePath);
28
+ }
29
+
30
+ export function copyFileIfPresent(sourcePath, destinationPath) {
31
+ if (!fileExists(sourcePath)) {
32
+ return false;
33
+ }
34
+ ensureDir(path.dirname(destinationPath));
35
+ fs.copyFileSync(sourcePath, destinationPath);
36
+ return true;
37
+ }
@@ -20,6 +20,78 @@ export function getCurrentProblemPath() {
20
20
  return path.join(getWorkspaceDir(), 'current-problem.json');
21
21
  }
22
22
 
23
+ export function getWorkspaceReportsDir() {
24
+ return path.join(getWorkspaceDir(), 'reports');
25
+ }
26
+
27
+ export function getWorkspaceDiffPath() {
28
+ return path.join(getWorkspaceReportsDir(), 'UPSTREAM_DIFF.md');
29
+ }
30
+
31
+ export function getWorkspaceUpstreamDir() {
32
+ return path.join(getWorkspaceDir(), 'upstream', 'erdosproblems');
33
+ }
34
+
35
+ export function getWorkspaceUpstreamYamlPath() {
36
+ return path.join(getWorkspaceUpstreamDir(), 'problems.yaml');
37
+ }
38
+
39
+ export function getWorkspaceUpstreamIndexPath() {
40
+ return path.join(getWorkspaceUpstreamDir(), 'PROBLEMS_INDEX.json');
41
+ }
42
+
43
+ export function getWorkspaceUpstreamManifestPath() {
44
+ return path.join(getWorkspaceUpstreamDir(), 'SYNC_MANIFEST.json');
45
+ }
46
+
47
+ export function getWorkspaceScaffoldsDir() {
48
+ return path.join(getWorkspaceDir(), 'scaffolds');
49
+ }
50
+
51
+ export function getWorkspaceProblemScaffoldDir(problemId) {
52
+ return path.join(getWorkspaceScaffoldsDir(), String(problemId));
53
+ }
54
+
55
+ export function getWorkspacePullsDir() {
56
+ return path.join(getWorkspaceDir(), 'pulls');
57
+ }
58
+
59
+ export function getWorkspaceProblemPullDir(problemId) {
60
+ return path.join(getWorkspacePullsDir(), String(problemId));
61
+ }
62
+
23
63
  export function getProblemDir(problemId) {
24
64
  return path.join(repoRoot, 'problems', String(problemId));
25
65
  }
66
+
67
+ export function getBundledDataDir() {
68
+ return path.join(repoRoot, 'data');
69
+ }
70
+
71
+ export function getBundledUpstreamDir() {
72
+ return path.join(getBundledDataDir(), 'upstream', 'erdosproblems');
73
+ }
74
+
75
+ export function getBundledUpstreamYamlPath() {
76
+ return path.join(getBundledUpstreamDir(), 'problems.yaml');
77
+ }
78
+
79
+ export function getBundledUpstreamIndexPath() {
80
+ return path.join(getBundledUpstreamDir(), 'PROBLEMS_INDEX.json');
81
+ }
82
+
83
+ export function getBundledUpstreamManifestPath() {
84
+ return path.join(getBundledUpstreamDir(), 'SYNC_MANIFEST.json');
85
+ }
86
+
87
+ export function getRepoAnalysisDir() {
88
+ return path.join(repoRoot, 'analysis');
89
+ }
90
+
91
+ export function getRepoUpstreamDiffPath() {
92
+ return path.join(getRepoAnalysisDir(), 'UPSTREAM_DIFF.md');
93
+ }
94
+
95
+ export function getPackDir(packName) {
96
+ return path.join(repoRoot, 'packs', String(packName));
97
+ }
@@ -0,0 +1,150 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { loadActiveUpstreamSnapshot } from '../upstream/sync.js';
4
+ import { copyFileIfPresent, ensureDir, writeJson, writeText } from './files.js';
5
+ import { getPackDir } from './paths.js';
6
+
7
+ const DOSSIER_FILES = [
8
+ ['problem.yaml', 'problemYamlPath', 'problem.yaml'],
9
+ ['STATEMENT.md', 'statementPath', 'STATEMENT.md'],
10
+ ['REFERENCES.md', 'referencesPath', 'REFERENCES.md'],
11
+ ['EVIDENCE.md', 'evidencePath', 'EVIDENCE.md'],
12
+ ['FORMALIZATION.md', 'formalizationPath', 'FORMALIZATION.md'],
13
+ ];
14
+
15
+ function getPackContextPath(problem) {
16
+ if (!problem.cluster) {
17
+ return null;
18
+ }
19
+ return path.join(getPackDir(problem.cluster), 'README.md');
20
+ }
21
+
22
+ export function getProblemArtifactInventory(problem) {
23
+ const snapshot = loadActiveUpstreamSnapshot();
24
+ const upstreamRecord = snapshot?.index?.by_number?.[problem.problemId] ?? null;
25
+ const canonicalArtifacts = DOSSIER_FILES.map(([label, key, destinationName]) => {
26
+ const filePath = problem[key];
27
+ return {
28
+ label,
29
+ path: filePath,
30
+ destinationName,
31
+ exists: fs.existsSync(filePath),
32
+ };
33
+ });
34
+
35
+ const packContextPath = getPackContextPath(problem);
36
+ const packContext = packContextPath
37
+ ? {
38
+ label: 'PACK_CONTEXT.md',
39
+ path: packContextPath,
40
+ destinationName: 'PACK_CONTEXT.md',
41
+ exists: fs.existsSync(packContextPath),
42
+ }
43
+ : null;
44
+
45
+ return {
46
+ generatedAt: new Date().toISOString(),
47
+ problemId: problem.problemId,
48
+ displayName: problem.displayName,
49
+ title: problem.title,
50
+ sourceUrl: problem.sourceUrl,
51
+ cluster: problem.cluster,
52
+ repoStatus: problem.repoStatus,
53
+ harnessDepth: problem.harnessDepth,
54
+ problemDir: problem.problemDir,
55
+ canonicalArtifacts,
56
+ packContext,
57
+ upstreamSnapshot: snapshot
58
+ ? {
59
+ kind: snapshot.kind,
60
+ manifestPath: snapshot.manifestPath,
61
+ indexPath: snapshot.indexPath,
62
+ yamlPath: snapshot.yamlPath,
63
+ upstreamCommit: snapshot.manifest.upstream_commit ?? null,
64
+ fetchedAt: snapshot.manifest.fetched_at,
65
+ }
66
+ : null,
67
+ upstreamRecordIncluded: Boolean(upstreamRecord),
68
+ upstreamRecord,
69
+ };
70
+ }
71
+
72
+ export function scaffoldProblem(problem, destination) {
73
+ ensureDir(destination);
74
+ const inventory = getProblemArtifactInventory(problem);
75
+
76
+ const copiedArtifacts = [];
77
+ for (const artifact of inventory.canonicalArtifacts) {
78
+ const destinationPath = path.join(destination, artifact.destinationName);
79
+ if (copyFileIfPresent(artifact.path, destinationPath)) {
80
+ copiedArtifacts.push({
81
+ label: artifact.label,
82
+ sourcePath: artifact.path,
83
+ destinationPath,
84
+ });
85
+ }
86
+ }
87
+
88
+ if (inventory.packContext?.exists) {
89
+ const destinationPath = path.join(destination, inventory.packContext.destinationName);
90
+ if (copyFileIfPresent(inventory.packContext.path, destinationPath)) {
91
+ copiedArtifacts.push({
92
+ label: inventory.packContext.label,
93
+ sourcePath: inventory.packContext.path,
94
+ destinationPath,
95
+ });
96
+ }
97
+ }
98
+
99
+ if (inventory.upstreamRecord) {
100
+ writeJson(path.join(destination, 'UPSTREAM_RECORD.json'), inventory.upstreamRecord);
101
+ }
102
+
103
+ const problemRecord = {
104
+ generatedAt: inventory.generatedAt,
105
+ problemId: problem.problemId,
106
+ title: problem.title,
107
+ cluster: problem.cluster,
108
+ siteStatus: problem.siteStatus,
109
+ repoStatus: problem.repoStatus,
110
+ harnessDepth: problem.harnessDepth,
111
+ sourceUrl: problem.sourceUrl,
112
+ activeRoute: problem.researchState?.active_route ?? null,
113
+ };
114
+
115
+ const artifactIndex = {
116
+ generatedAt: inventory.generatedAt,
117
+ problemId: problem.problemId,
118
+ bundledProblemDir: problem.problemDir,
119
+ copiedArtifacts,
120
+ packContext: inventory.packContext,
121
+ canonicalArtifacts: inventory.canonicalArtifacts,
122
+ upstreamSnapshot: inventory.upstreamSnapshot,
123
+ includedUpstreamRecord: inventory.upstreamRecordIncluded,
124
+ };
125
+
126
+ writeJson(path.join(destination, 'PROBLEM.json'), problemRecord);
127
+ writeJson(path.join(destination, 'ARTIFACT_INDEX.json'), artifactIndex);
128
+ writeText(
129
+ path.join(destination, 'README.md'),
130
+ [
131
+ `# Erdos Problem ${problem.problemId} Scaffold`,
132
+ '',
133
+ 'This scaffold was generated by the erdos CLI.',
134
+ '',
135
+ `- Title: ${problem.title}`,
136
+ `- Cluster: ${problem.cluster}`,
137
+ `- Source: ${problem.sourceUrl}`,
138
+ `- Repo status: ${problem.repoStatus}`,
139
+ `- Harness depth: ${problem.harnessDepth}`,
140
+ `- Upstream record included: ${inventory.upstreamRecordIncluded ? 'yes' : 'no'}`,
141
+ '',
142
+ ].join('\n'),
143
+ );
144
+
145
+ return {
146
+ destination,
147
+ copiedArtifacts,
148
+ inventory,
149
+ };
150
+ }
@@ -1,16 +1,14 @@
1
1
  import fs from 'node:fs';
2
- import path from 'node:path';
3
2
  import {
4
3
  getCurrentProblemPath,
5
4
  getWorkspaceDir,
5
+ getWorkspaceProblemPullDir,
6
+ getWorkspaceProblemScaffoldDir,
6
7
  getWorkspaceRoot,
7
8
  getWorkspaceStatePath,
9
+ getWorkspaceUpstreamDir,
8
10
  } from './paths.js';
9
-
10
- function writeJson(filePath, payload) {
11
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
12
- fs.writeFileSync(filePath, JSON.stringify(payload, null, 2) + '\n');
13
- }
11
+ import { writeJson } from './files.js';
14
12
 
15
13
  export function readWorkspaceState() {
16
14
  const filePath = getWorkspaceStatePath();
@@ -61,11 +59,15 @@ export function readCurrentProblem() {
61
59
 
62
60
  export function getWorkspaceSummary() {
63
61
  const state = readWorkspaceState();
62
+ const activeProblem = readCurrentProblem();
64
63
  return {
65
64
  workspaceRoot: getWorkspaceRoot(),
66
65
  stateDir: getWorkspaceDir(),
67
66
  hasState: Boolean(state),
68
- activeProblem: readCurrentProblem(),
67
+ activeProblem,
68
+ upstreamDir: getWorkspaceUpstreamDir(),
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
  }