erdos-problems 0.1.1 → 0.1.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.
@@ -1,16 +1,37 @@
1
1
  problem_id: "89"
2
2
  display_name: "Erdos Problem #89"
3
- title: "Distinct Distances Lower Bound"
3
+ title: Distinct Distances Lower Bound
4
4
  source:
5
- site: "erdosproblems.com"
6
- url: "https://www.erdosproblems.com/89"
5
+ site: erdosproblems.com
6
+ url: https://www.erdosproblems.com/89
7
7
  external_id: "89"
8
+ upstream:
9
+ repo: https://github.com/teorth/erdosproblems
10
+ data_file: data/problems.yaml
11
+ number: "89"
8
12
  status:
9
- site_status: "open"
10
- site_badge: "OPEN"
11
- repo_status: "cataloged"
12
- cluster: "geometry"
13
+ site_status: open
14
+ site_badge: OPEN
15
+ repo_status: cataloged
16
+ upstream_status: open
17
+ upstream_last_update: 2025-08-31
18
+ cluster: geometry
19
+ prize:
20
+ display: $500
13
21
  related_problems: []
14
22
  family_tags:
15
- - "geometry"
16
- - "distances"
23
+ - geometry
24
+ - distances
25
+ harness:
26
+ depth: dossier
27
+ statement:
28
+ short: Ask whether every n-point set in the plane determines at least a constant
29
+ multiple of n/sqrt(log n) distinct distances.
30
+ normalized_md_path: STATEMENT.md
31
+ references_path: REFERENCES.md
32
+ evidence_path: EVIDENCE.md
33
+ formalization_path: FORMALIZATION.md
34
+ formalization:
35
+ status: statement-formalized
36
+ upstream_state: yes
37
+ upstream_last_update: 2025-10-24
@@ -1,154 +1,88 @@
1
- export const catalog = [
2
- {
3
- problemId: '18',
4
- displayName: 'Erdos Problem #18',
5
- title: 'Practical Numbers Divisor-Sum Efficiency',
6
- siteStatus: 'open',
7
- siteBadge: 'OPEN',
8
- repoStatus: 'cataloged',
9
- cluster: 'number-theory',
10
- familyTags: ['number-theory', 'divisors', 'factorials'],
11
- relatedProblems: [],
12
- sourceUrl: 'https://www.erdosproblems.com/18',
13
- shortStatement:
14
- 'Study whether infinitely many practical numbers admit polylogarithmic divisor-sum efficiency, and whether h(n!) is polylogarithmic.',
15
- harnessDepth: 'dossier',
16
- formalizationStatus: 'statement-formalized',
17
- },
18
- {
19
- problemId: '20',
20
- displayName: 'Erdos Problem #20',
21
- title: 'Strong Sunflower Problem',
22
- siteStatus: 'open',
23
- siteBadge: 'OPEN',
24
- repoStatus: 'active',
25
- cluster: 'sunflower',
26
- familyTags: ['sunflower', 'uniform-families'],
27
- relatedProblems: ['857'],
28
- sourceUrl: 'https://www.erdosproblems.com/20',
29
- shortStatement:
30
- 'Determine the strong sunflower threshold for k-uniform set systems, with the k=3 lane as the immediate active frontier.',
31
- harnessDepth: 'deep',
32
- formalizationStatus: 'active',
33
- },
34
- {
35
- problemId: '89',
36
- displayName: 'Erdos Problem #89',
37
- title: 'Distinct Distances Lower Bound',
38
- siteStatus: 'open',
39
- siteBadge: 'OPEN',
40
- repoStatus: 'cataloged',
41
- cluster: 'geometry',
42
- familyTags: ['geometry', 'distances'],
43
- relatedProblems: [],
44
- sourceUrl: 'https://www.erdosproblems.com/89',
45
- shortStatement:
46
- 'Ask whether every n-point set in the plane determines at least a constant multiple of n/sqrt(log n) distinct distances.',
47
- harnessDepth: 'dossier',
48
- formalizationStatus: 'statement-formalized',
49
- },
50
- {
51
- problemId: '536',
52
- displayName: 'Erdos Problem #536',
53
- title: 'LCM Sunflower Analogue',
54
- siteStatus: 'open',
55
- siteBadge: 'OPEN',
56
- repoStatus: 'cataloged',
57
- cluster: 'sunflower',
58
- familyTags: ['sunflower-analogue', 'number-theory'],
59
- relatedProblems: ['857'],
60
- sourceUrl: 'https://www.erdosproblems.com/536',
61
- shortStatement:
62
- 'Number-theoretic analogue of the sunflower problem framed through least common multiples.',
63
- harnessDepth: 'dossier',
64
- formalizationStatus: 'planned',
65
- },
66
- {
67
- problemId: '542',
68
- displayName: 'Erdos Problem #542',
69
- title: 'LCM-Free Sets Reciprocal Sum Bound',
70
- siteStatus: 'solved',
71
- siteBadge: 'SOLVED',
72
- repoStatus: 'historical',
73
- cluster: 'number-theory',
74
- familyTags: ['number-theory', 'least-common-multiple'],
75
- relatedProblems: ['784'],
76
- sourceUrl: 'https://www.erdosproblems.com/542',
77
- shortStatement:
78
- 'Control reciprocal sums of sets with all pairwise least common multiples above n, a problem resolved by Schinzel and Szekeres.',
79
- harnessDepth: 'dossier',
80
- formalizationStatus: 'unstarted',
81
- },
82
- {
83
- problemId: '856',
84
- displayName: 'Erdos Problem #856',
85
- title: 'Harmonic LCM Sunflower Analogue',
86
- siteStatus: 'open',
87
- siteBadge: 'OPEN',
88
- repoStatus: 'cataloged',
89
- cluster: 'sunflower',
90
- familyTags: ['sunflower-analogue', 'number-theory'],
91
- relatedProblems: ['857'],
92
- sourceUrl: 'https://www.erdosproblems.com/856',
93
- shortStatement:
94
- 'A harmonic or density-shaped LCM analogue whose exponents are explicitly linked to progress on the weak sunflower problem.',
95
- harnessDepth: 'dossier',
96
- formalizationStatus: 'planned',
97
- },
98
- {
99
- problemId: '857',
100
- displayName: 'Erdos Problem #857',
101
- title: 'Sunflower Conjecture',
102
- siteStatus: 'open',
103
- siteBadge: 'OPEN',
104
- repoStatus: 'active',
105
- cluster: 'sunflower',
106
- familyTags: ['sunflower', 'extremal-set-theory'],
107
- relatedProblems: ['20', '536', '856'],
108
- sourceUrl: 'https://www.erdosproblems.com/857',
109
- shortStatement:
110
- 'Bound the weak sunflower number m(n,k) by C(k)^n and sharpen the current active route toward asymptotic closure.',
111
- harnessDepth: 'deep',
112
- formalizationStatus: 'active',
113
- researchState: {
114
- openProblem: true,
115
- activeRoute: 'anchored_selector_linearization',
116
- routeBreakthrough: true,
117
- problemSolved: false,
118
- },
119
- },
120
- {
121
- problemId: '1008',
122
- displayName: 'Erdos Problem #1008',
123
- title: 'C4-Free Subgraph Density Problem',
124
- siteStatus: 'solved',
125
- siteBadge: 'PROVED (LEAN)',
126
- repoStatus: 'historical',
127
- cluster: 'graph-theory',
128
- familyTags: ['graph-theory', 'cycles'],
129
- relatedProblems: [],
130
- sourceUrl: 'https://www.erdosproblems.com/1008',
131
- shortStatement:
132
- 'Determine whether every graph with m edges contains a C4-free subgraph with a constant multiple of m^(2/3) edges.',
133
- harnessDepth: 'dossier',
134
- formalizationStatus: 'site-proved-lean',
135
- },
136
- ];
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parse } from 'yaml';
4
+ import { getProblemDir, repoRoot } from '../runtime/paths.js';
5
+
6
+ let cachedProblems = null;
7
+
8
+ function readProblemRecord(problemId) {
9
+ const problemDir = getProblemDir(problemId);
10
+ const yamlPath = path.join(problemDir, 'problem.yaml');
11
+ const record = parse(fs.readFileSync(yamlPath, 'utf8'));
12
+ return { problemDir, record };
13
+ }
14
+
15
+ function toCatalogProblem(problemDir, record) {
16
+ const statementRelative = record.statement?.normalized_md_path ?? 'STATEMENT.md';
17
+ const referencesRelative = record.references_path ?? 'REFERENCES.md';
18
+ const evidenceRelative = record.evidence_path ?? 'EVIDENCE.md';
19
+ const formalizationRelative = record.formalization_path ?? 'FORMALIZATION.md';
20
+
21
+ return {
22
+ problemId: String(record.problem_id),
23
+ displayName: record.display_name,
24
+ title: record.title,
25
+ sourceUrl: record.source?.url,
26
+ sourceSite: record.source?.site,
27
+ siteStatus: record.status?.site_status ?? 'unknown',
28
+ siteBadge: record.status?.site_badge ?? String(record.status?.site_status ?? 'unknown').toUpperCase(),
29
+ repoStatus: record.status?.repo_status ?? 'cataloged',
30
+ upstreamStatus: record.status?.upstream_status ?? null,
31
+ upstreamLastUpdate: record.status?.upstream_last_update ?? null,
32
+ cluster: record.cluster ?? 'uncategorized',
33
+ prize: record.prize?.display ?? null,
34
+ familyTags: record.family_tags ?? [],
35
+ relatedProblems: record.related_problems ?? [],
36
+ harnessDepth: record.harness?.depth ?? 'dossier',
37
+ shortStatement: record.statement?.short ?? '',
38
+ formalizationStatus: record.formalization?.status ?? 'unstarted',
39
+ upstreamFormalizedState: record.formalization?.upstream_state ?? null,
40
+ upstreamFormalizedLastUpdate: record.formalization?.upstream_last_update ?? null,
41
+ researchState: record.research_state ?? null,
42
+ upstream: record.upstream ?? null,
43
+ statementPath: path.join(problemDir, statementRelative),
44
+ referencesPath: path.join(problemDir, referencesRelative),
45
+ evidencePath: path.join(problemDir, evidenceRelative),
46
+ formalizationPath: path.join(problemDir, formalizationRelative),
47
+ problemDir,
48
+ problemYamlPath: path.join(problemDir, 'problem.yaml'),
49
+ record,
50
+ };
51
+ }
52
+
53
+ export function loadLocalProblems() {
54
+ if (cachedProblems) {
55
+ return cachedProblems;
56
+ }
57
+ const problemsRoot = path.join(repoRoot, 'problems');
58
+ const directories = fs
59
+ .readdirSync(problemsRoot, { withFileTypes: true })
60
+ .filter((entry) => entry.isDirectory())
61
+ .map((entry) => entry.name)
62
+ .sort((a, b) => Number(a) - Number(b));
63
+
64
+ cachedProblems = directories.map((problemId) => {
65
+ const { problemDir, record } = readProblemRecord(problemId);
66
+ return toCatalogProblem(problemDir, record);
67
+ });
68
+ return cachedProblems;
69
+ }
137
70
 
138
71
  export function listProblems(filters = {}) {
139
72
  const cluster = filters.cluster ? String(filters.cluster).toLowerCase() : null;
140
73
  const repoStatus = filters.repoStatus ? String(filters.repoStatus).toLowerCase() : null;
141
74
  const harnessDepth = filters.harnessDepth ? String(filters.harnessDepth).toLowerCase() : null;
75
+ const siteStatus = filters.siteStatus ? String(filters.siteStatus).toLowerCase() : null;
142
76
 
143
- return [...catalog]
77
+ return loadLocalProblems()
144
78
  .filter((entry) => (cluster ? entry.cluster === cluster : true))
145
79
  .filter((entry) => (repoStatus ? entry.repoStatus === repoStatus : true))
146
80
  .filter((entry) => (harnessDepth ? entry.harnessDepth === harnessDepth : true))
147
- .sort((a, b) => Number(a.problemId) - Number(b.problemId));
81
+ .filter((entry) => (siteStatus ? entry.siteStatus === siteStatus : true));
148
82
  }
149
83
 
150
84
  export function getProblem(problemId) {
151
- return catalog.find((entry) => entry.problemId === String(problemId));
85
+ return loadLocalProblems().find((entry) => entry.problemId === String(problemId));
152
86
  }
153
87
 
154
88
  export function getCluster(clusterName) {
@@ -166,6 +100,10 @@ export function getCluster(clusterName) {
166
100
  }
167
101
 
168
102
  export function listClusters() {
169
- const names = [...new Set(catalog.map((entry) => entry.cluster))].sort();
103
+ const names = [...new Set(loadLocalProblems().map((entry) => entry.cluster))].sort();
170
104
  return names.map((name) => getCluster(name));
171
105
  }
106
+
107
+ export function clearCatalogCache() {
108
+ cachedProblems = null;
109
+ }
package/src/cli/index.js CHANGED
@@ -1,20 +1,29 @@
1
+ import { runBootstrapCommand } from '../commands/bootstrap.js';
1
2
  import { runClusterCommand } from '../commands/cluster.js';
2
3
  import { runDossierCommand } from '../commands/dossier.js';
3
4
  import { runProblemCommand } from '../commands/problem.js';
5
+ import { runScaffoldCommand } from '../commands/scaffold.js';
6
+ import { runUpstreamCommand } from '../commands/upstream.js';
4
7
  import { runWorkspaceCommand } from '../commands/workspace.js';
5
8
 
6
9
  function printUsage() {
7
10
  console.log('erdos-problems CLI');
8
11
  console.log('');
9
12
  console.log('Usage:');
10
- console.log(' erdos problem list [--cluster <name>]');
13
+ console.log(' erdos problem list [--cluster <name>] [--repo-status <status>] [--harness-depth <depth>] [--site-status <status>]');
11
14
  console.log(' erdos problem show <id>');
12
15
  console.log(' erdos problem use <id>');
13
16
  console.log(' erdos problem current');
17
+ console.log(' erdos problem artifacts [<id>] [--json]');
14
18
  console.log(' erdos cluster list');
15
19
  console.log(' erdos cluster show <name>');
16
20
  console.log(' erdos workspace show');
17
21
  console.log(' erdos dossier show <id>');
22
+ console.log(' erdos upstream show');
23
+ console.log(' erdos upstream sync [--write-package-snapshot]');
24
+ console.log(' erdos upstream diff [--write-package-report]');
25
+ console.log(' erdos scaffold problem <id> [--dest <path>]');
26
+ console.log(' erdos bootstrap problem <id> [--dest <path>] [--sync-upstream]');
18
27
  }
19
28
 
20
29
  const args = process.argv.slice(2);
@@ -32,6 +41,12 @@ if (!command || command === 'help' || command === '--help') {
32
41
  exitCode = runWorkspaceCommand(rest);
33
42
  } else if (command === 'dossier') {
34
43
  exitCode = runDossierCommand(rest);
44
+ } else if (command === 'upstream') {
45
+ exitCode = await runUpstreamCommand(rest);
46
+ } else if (command === 'scaffold') {
47
+ exitCode = runScaffoldCommand(rest);
48
+ } else if (command === 'bootstrap') {
49
+ exitCode = await runBootstrapCommand(rest);
35
50
  } else {
36
51
  console.error(`Unknown command: ${command}`);
37
52
  printUsage();
@@ -0,0 +1,81 @@
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 { setCurrentProblem } from '../runtime/workspace.js';
6
+ import { syncUpstream } from '../upstream/sync.js';
7
+
8
+ function parseBootstrapArgs(args) {
9
+ const [kind, value, ...rest] = args;
10
+ if (kind !== 'problem') {
11
+ return { error: 'Only `erdos bootstrap problem <id>` is supported right now.' };
12
+ }
13
+
14
+ let destination = null;
15
+ let syncUpstreamSnapshot = false;
16
+
17
+ for (let index = 0; index < rest.length; index += 1) {
18
+ const token = rest[index];
19
+ if (token === '--dest') {
20
+ destination = rest[index + 1];
21
+ if (!destination) {
22
+ return { error: 'Missing destination path after --dest.' };
23
+ }
24
+ index += 1;
25
+ continue;
26
+ }
27
+ if (token === '--sync-upstream') {
28
+ syncUpstreamSnapshot = true;
29
+ continue;
30
+ }
31
+ return { error: `Unknown bootstrap option: ${token}` };
32
+ }
33
+
34
+ return {
35
+ problemId: value,
36
+ destination,
37
+ syncUpstreamSnapshot,
38
+ };
39
+ }
40
+
41
+ export async function runBootstrapCommand(args) {
42
+ if (args.length === 0 || args[0] === 'help' || args[0] === '--help') {
43
+ console.log('Usage:');
44
+ console.log(' erdos bootstrap problem <id> [--dest <path>] [--sync-upstream]');
45
+ return 0;
46
+ }
47
+
48
+ const parsed = parseBootstrapArgs(args);
49
+ if (parsed.error) {
50
+ console.error(parsed.error);
51
+ return 1;
52
+ }
53
+ if (!parsed.problemId) {
54
+ console.error('Missing problem id.');
55
+ return 1;
56
+ }
57
+
58
+ const problem = getProblem(parsed.problemId);
59
+ if (!problem) {
60
+ console.error(`Unknown problem: ${parsed.problemId}`);
61
+ return 1;
62
+ }
63
+
64
+ if (parsed.syncUpstreamSnapshot) {
65
+ const syncResult = await syncUpstream();
66
+ console.log(`Workspace upstream snapshot refreshed: ${syncResult.workspacePaths.manifestPath}`);
67
+ }
68
+
69
+ setCurrentProblem(problem.problemId);
70
+ const destination = parsed.destination
71
+ ? path.resolve(parsed.destination)
72
+ : getWorkspaceProblemScaffoldDir(problem.problemId);
73
+ const result = scaffoldProblem(problem, destination);
74
+
75
+ console.log(`Bootstrapped problem ${problem.problemId} (${problem.title})`);
76
+ console.log(`Active problem: ${problem.problemId}`);
77
+ console.log(`Scaffold dir: ${result.destination}`);
78
+ console.log(`Artifacts copied: ${result.copiedArtifacts.length}`);
79
+ console.log(`Upstream record included: ${result.inventory.upstreamRecordIncluded ? 'yes' : 'no'}`);
80
+ return 0;
81
+ }
@@ -1,10 +1,14 @@
1
1
  import fs from 'node:fs';
2
- import path from 'node:path';
3
2
  import { getProblem } from '../atlas/catalog.js';
4
- import { getProblemDir } from '../runtime/paths.js';
5
3
  import { readCurrentProblem } from '../runtime/workspace.js';
6
4
 
7
- const sections = ['STATEMENT.md', 'REFERENCES.md', 'EVIDENCE.md', 'FORMALIZATION.md'];
5
+ const sections = [
6
+ ['problem.yaml', 'problemYamlPath'],
7
+ ['STATEMENT.md', 'statementPath'],
8
+ ['REFERENCES.md', 'referencesPath'],
9
+ ['EVIDENCE.md', 'evidencePath'],
10
+ ['FORMALIZATION.md', 'formalizationPath'],
11
+ ];
8
12
 
9
13
  export function runDossierCommand(args) {
10
14
  const [subcommand, value] = args;
@@ -32,20 +36,24 @@ export function runDossierCommand(args) {
32
36
  return 1;
33
37
  }
34
38
 
35
- const problemDir = getProblemDir(problem.problemId);
36
39
  console.log(`${problem.displayName} dossier`);
37
- console.log(`Directory: ${problemDir}`);
40
+ console.log(`Directory: ${problem.problemDir}`);
38
41
  console.log('Sections:');
39
- for (const section of sections) {
40
- const filePath = path.join(problemDir, section);
42
+ for (const [label, key] of sections) {
43
+ const filePath = problem[key];
41
44
  const exists = fs.existsSync(filePath);
42
- console.log(`- ${section}: ${exists ? 'present' : 'missing'}`);
45
+ console.log(`- ${label}: ${exists ? 'present' : 'missing'}`);
43
46
  }
44
- const statementPath = path.join(problemDir, 'STATEMENT.md');
45
- if (fs.existsSync(statementPath)) {
47
+ console.log('');
48
+ console.log('Canonical metadata:');
49
+ console.log(`- cluster: ${problem.cluster}`);
50
+ console.log(`- repo status: ${problem.repoStatus}`);
51
+ console.log(`- harness depth: ${problem.harnessDepth}`);
52
+ console.log(`- upstream number: ${problem.upstream?.number ?? '(unset)'}`);
53
+ if (fs.existsSync(problem.statementPath)) {
46
54
  console.log('');
47
55
  console.log('Statement preview:');
48
- console.log(fs.readFileSync(statementPath, 'utf8').trim());
56
+ console.log(fs.readFileSync(problem.statementPath, 'utf8').trim());
49
57
  }
50
58
  return 0;
51
59
  }
@@ -1,4 +1,5 @@
1
1
  import { getProblem, listProblems } from '../atlas/catalog.js';
2
+ import { getProblemArtifactInventory } from '../runtime/problem-artifacts.js';
2
3
  import { readCurrentProblem, setCurrentProblem } from '../runtime/workspace.js';
3
4
 
4
5
  function parseListFilters(args) {
@@ -32,11 +33,42 @@ function parseListFilters(args) {
32
33
  index += 1;
33
34
  continue;
34
35
  }
36
+ if (token === '--site-status') {
37
+ const siteStatus = args[index + 1];
38
+ if (!siteStatus) {
39
+ return { error: 'Missing site status after --site-status.' };
40
+ }
41
+ filters.siteStatus = siteStatus;
42
+ index += 1;
43
+ continue;
44
+ }
35
45
  return { error: `Unknown list option: ${token}` };
36
46
  }
37
47
  return { filters };
38
48
  }
39
49
 
50
+ function parseArtifactArgs(args) {
51
+ const parsed = {
52
+ problemId: null,
53
+ asJson: false,
54
+ };
55
+
56
+ for (let index = 0; index < args.length; index += 1) {
57
+ const token = args[index];
58
+ if (token === '--json') {
59
+ parsed.asJson = true;
60
+ continue;
61
+ }
62
+ if (!parsed.problemId) {
63
+ parsed.problemId = token;
64
+ continue;
65
+ }
66
+ return { error: `Unknown artifact option: ${token}` };
67
+ }
68
+
69
+ return parsed;
70
+ }
71
+
40
72
  function printProblemTable(rows, activeProblem) {
41
73
  console.log('ID Site Repo Cluster Depth Active Title');
42
74
  for (const row of rows) {
@@ -59,28 +91,64 @@ function printProblem(problem) {
59
91
  console.log(`Repo status: ${problem.repoStatus}`);
60
92
  console.log(`Cluster: ${problem.cluster}`);
61
93
  console.log(`Harness depth: ${problem.harnessDepth}`);
94
+ console.log(`Prize: ${problem.prize ?? '(none)'}`);
62
95
  console.log(`Formalization: ${problem.formalizationStatus}`);
96
+ console.log(`Upstream formalized: ${problem.upstreamFormalizedState ?? '(unknown)'}`);
97
+ console.log(`Upstream last update: ${problem.upstreamLastUpdate ?? '(unknown)'}`);
63
98
  console.log(`Related: ${problem.relatedProblems.join(', ') || '(none)'}`);
64
99
  console.log(`Tags: ${problem.familyTags.join(', ') || '(none)'}`);
65
100
  console.log(`Statement: ${problem.shortStatement}`);
66
101
  if (problem.researchState) {
67
102
  console.log('Research state:');
68
- console.log(` open problem: ${problem.researchState.openProblem ? 'yes' : 'no'}`);
69
- console.log(` active route: ${problem.researchState.activeRoute}`);
70
- console.log(` route breakthrough: ${problem.researchState.routeBreakthrough ? 'yes' : 'no'}`);
71
- console.log(` problem solved: ${problem.researchState.problemSolved ? 'yes' : 'no'}`);
103
+ console.log(` open problem: ${problem.researchState.open_problem ? 'yes' : 'no'}`);
104
+ console.log(` active route: ${problem.researchState.active_route}`);
105
+ console.log(` route breakthrough: ${problem.researchState.route_breakthrough ? 'yes' : 'no'}`);
106
+ console.log(` problem solved: ${problem.researchState.problem_solved ? 'yes' : 'no'}`);
107
+ }
108
+ if (problem.upstream) {
109
+ console.log('Upstream provenance:');
110
+ console.log(` repo: ${problem.upstream.repo}`);
111
+ console.log(` data file: ${problem.upstream.data_file}`);
112
+ console.log(` number: ${problem.upstream.number}`);
72
113
  }
73
114
  }
74
115
 
116
+ function printArtifactInventory(problem, inventory, asJson) {
117
+ if (asJson) {
118
+ console.log(JSON.stringify(inventory, null, 2));
119
+ return;
120
+ }
121
+
122
+ console.log(`${problem.displayName} canonical artifacts`);
123
+ console.log(`Problem directory: ${inventory.problemDir}`);
124
+ console.log(`Source: ${inventory.sourceUrl}`);
125
+ console.log('Canonical files:');
126
+ for (const artifact of inventory.canonicalArtifacts) {
127
+ console.log(`- ${artifact.label}: ${artifact.exists ? 'present' : 'missing'} (${artifact.path})`);
128
+ }
129
+ if (inventory.packContext) {
130
+ console.log(`- ${inventory.packContext.label}: ${inventory.packContext.exists ? 'present' : 'missing'} (${inventory.packContext.path})`);
131
+ }
132
+ if (inventory.upstreamSnapshot) {
133
+ console.log('Upstream snapshot:');
134
+ console.log(`- kind: ${inventory.upstreamSnapshot.kind}`);
135
+ console.log(`- manifest: ${inventory.upstreamSnapshot.manifestPath}`);
136
+ console.log(`- index: ${inventory.upstreamSnapshot.indexPath}`);
137
+ console.log(`- commit: ${inventory.upstreamSnapshot.upstreamCommit ?? '(unknown)'}`);
138
+ }
139
+ console.log(`Upstream record available: ${inventory.upstreamRecordIncluded ? 'yes' : 'no'}`);
140
+ }
141
+
75
142
  export function runProblemCommand(args) {
76
143
  const [subcommand, value, ...rest] = args;
77
144
 
78
145
  if (!subcommand || subcommand === 'help' || subcommand === '--help') {
79
146
  console.log('Usage:');
80
- console.log(' erdos problem list [--cluster <name>] [--repo-status <status>] [--harness-depth <depth>]');
147
+ console.log(' erdos problem list [--cluster <name>] [--repo-status <status>] [--harness-depth <depth>] [--site-status <status>]');
81
148
  console.log(' erdos problem show <id>');
82
149
  console.log(' erdos problem use <id>');
83
150
  console.log(' erdos problem current');
151
+ console.log(' erdos problem artifacts [<id>] [--json]');
84
152
  return 0;
85
153
  }
86
154
 
@@ -139,6 +207,27 @@ export function runProblemCommand(args) {
139
207
  return 0;
140
208
  }
141
209
 
210
+ if (subcommand === 'artifacts') {
211
+ const parsed = parseArtifactArgs([value, ...rest].filter(Boolean));
212
+ if (parsed.error) {
213
+ console.error(parsed.error);
214
+ return 1;
215
+ }
216
+ const problemId = parsed.problemId ?? readCurrentProblem();
217
+ if (!problemId) {
218
+ console.error('Missing problem id and no active problem is selected.');
219
+ return 1;
220
+ }
221
+ const problem = getProblem(problemId);
222
+ if (!problem) {
223
+ console.error(`Unknown problem: ${problemId}`);
224
+ return 1;
225
+ }
226
+ const inventory = getProblemArtifactInventory(problem);
227
+ printArtifactInventory(problem, inventory, parsed.asJson);
228
+ return 0;
229
+ }
230
+
142
231
  console.error(`Unknown problem subcommand: ${subcommand}`);
143
232
  return 1;
144
233
  }
@@ -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
+ }