erdos-problems 0.1.0

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,5 @@
1
+ # Problem 20 Formalization
2
+
3
+ Current posture:
4
+ - formalization status: active
5
+ - deep-harness sibling problem in the sunflower pack
@@ -0,0 +1,4 @@
1
+ # Problem 20 References
2
+
3
+ - Erdos Problems page: <https://www.erdosproblems.com/20>
4
+ - Related sunflower problem: `857`
@@ -0,0 +1,7 @@
1
+ # Problem 20 Statement
2
+
3
+ Source: <https://www.erdosproblems.com/20>
4
+
5
+ Normalized focus:
6
+ - strong / uniform sunflower problem
7
+ - deep-harness sibling to Problem 857
@@ -0,0 +1,16 @@
1
+ problem_id: "20"
2
+ display_name: "Erdos Problem #20"
3
+ title: "Strong Sunflower Problem"
4
+ source:
5
+ site: "erdosproblems.com"
6
+ url: "https://www.erdosproblems.com/20"
7
+ external_id: "20"
8
+ status:
9
+ site_status: "open"
10
+ repo_status: "active"
11
+ cluster: "sunflower"
12
+ related_problems:
13
+ - "857"
14
+ family_tags:
15
+ - "sunflower"
16
+ - "uniform-families"
@@ -0,0 +1,3 @@
1
+ # Problem 536 Evidence
2
+
3
+ Current dossier placeholder.
@@ -0,0 +1,5 @@
1
+ # Problem 536 Formalization
2
+
3
+ Current posture:
4
+ - formalization status: planned
5
+ - dossier-first, not yet a deep harness
@@ -0,0 +1,4 @@
1
+ # Problem 536 References
2
+
3
+ - Erdos Problems page: <https://www.erdosproblems.com/536>
4
+ - Related sunflower problem: `857`
@@ -0,0 +1,7 @@
1
+ # Problem 536 Statement
2
+
3
+ Source: <https://www.erdosproblems.com/536>
4
+
5
+ Normalized focus:
6
+ - LCM analogue related to weak sunflower structure
7
+ - dossier-first seed problem in the sunflower cluster
@@ -0,0 +1,16 @@
1
+ problem_id: "536"
2
+ display_name: "Erdos Problem #536"
3
+ title: "LCM Sunflower Analogue"
4
+ source:
5
+ site: "erdosproblems.com"
6
+ url: "https://www.erdosproblems.com/536"
7
+ external_id: "536"
8
+ status:
9
+ site_status: "open"
10
+ repo_status: "cataloged"
11
+ cluster: "sunflower"
12
+ related_problems:
13
+ - "857"
14
+ family_tags:
15
+ - "sunflower-analogue"
16
+ - "number-theory"
@@ -0,0 +1,3 @@
1
+ # Problem 856 Evidence
2
+
3
+ Current dossier placeholder.
@@ -0,0 +1,5 @@
1
+ # Problem 856 Formalization
2
+
3
+ Current posture:
4
+ - formalization status: planned
5
+ - dossier-first, not yet a deep harness
@@ -0,0 +1,4 @@
1
+ # Problem 856 References
2
+
3
+ - Erdos Problems page: <https://www.erdosproblems.com/856>
4
+ - Related sunflower problem: `857`
@@ -0,0 +1,7 @@
1
+ # Problem 856 Statement
2
+
3
+ Source: <https://www.erdosproblems.com/856>
4
+
5
+ Normalized focus:
6
+ - harmonic or density-shaped LCM analogue linked to weak sunflower progress
7
+ - dossier-first seed problem in the sunflower cluster
@@ -0,0 +1,16 @@
1
+ problem_id: "856"
2
+ display_name: "Erdos Problem #856"
3
+ title: "Harmonic LCM Sunflower Analogue"
4
+ source:
5
+ site: "erdosproblems.com"
6
+ url: "https://www.erdosproblems.com/856"
7
+ external_id: "856"
8
+ status:
9
+ site_status: "open"
10
+ repo_status: "cataloged"
11
+ cluster: "sunflower"
12
+ related_problems:
13
+ - "857"
14
+ family_tags:
15
+ - "sunflower-analogue"
16
+ - "number-theory"
@@ -0,0 +1,9 @@
1
+ # Problem 857 Evidence
2
+
3
+ Current dossier placeholder.
4
+
5
+ Track here:
6
+ - route breakthroughs
7
+ - formal theorem checkpoints
8
+ - computational artifacts
9
+ - failed-path summaries
@@ -0,0 +1,6 @@
1
+ # Problem 857 Formalization
2
+
3
+ Current posture:
4
+ - formalization status: active
5
+ - active route: anchored_selector_linearization
6
+ - main upstream lab: sunflower-coda
@@ -0,0 +1,5 @@
1
+ # Problem 857 References
2
+
3
+ - Erdos Problems page: <https://www.erdosproblems.com/857>
4
+ - Related problems: `20`, `536`, `856`
5
+ - Repo note: this is the primary weak-sunflower deep-harness problem
@@ -0,0 +1,8 @@
1
+ # Problem 857 Statement
2
+
3
+ Source: <https://www.erdosproblems.com/857>
4
+
5
+ Normalized focus:
6
+ - weak sunflower problem
7
+ - bound `m(n,k)` by `C(k)^n`
8
+ - current deep-harness frontier lives in the sunflower pack
@@ -0,0 +1,23 @@
1
+ problem_id: "857"
2
+ display_name: "Erdos Problem #857"
3
+ title: "Sunflower Conjecture"
4
+ source:
5
+ site: "erdosproblems.com"
6
+ url: "https://www.erdosproblems.com/857"
7
+ external_id: "857"
8
+ status:
9
+ site_status: "open"
10
+ repo_status: "active"
11
+ cluster: "sunflower"
12
+ related_problems:
13
+ - "20"
14
+ - "536"
15
+ - "856"
16
+ family_tags:
17
+ - "sunflower"
18
+ - "extremal-set-theory"
19
+ research_state:
20
+ open_problem: true
21
+ active_route: "anchored_selector_linearization"
22
+ route_breakthrough: true
23
+ problem_solved: false
@@ -0,0 +1,98 @@
1
+ export const catalog = [
2
+ {
3
+ problemId: '20',
4
+ displayName: 'Erdos Problem #20',
5
+ title: 'Strong Sunflower Problem',
6
+ siteStatus: 'open',
7
+ repoStatus: 'active',
8
+ cluster: 'sunflower',
9
+ familyTags: ['sunflower', 'uniform-families'],
10
+ relatedProblems: ['857'],
11
+ sourceUrl: 'https://www.erdosproblems.com/20',
12
+ shortStatement:
13
+ 'Determine the strong sunflower threshold for k-uniform set systems, with the k=3 lane as the immediate active frontier.',
14
+ harnessDepth: 'deep',
15
+ formalizationStatus: 'active',
16
+ },
17
+ {
18
+ problemId: '536',
19
+ displayName: 'Erdos Problem #536',
20
+ title: 'LCM Sunflower Analogue',
21
+ siteStatus: 'open',
22
+ repoStatus: 'cataloged',
23
+ cluster: 'sunflower',
24
+ familyTags: ['sunflower-analogue', 'number-theory'],
25
+ relatedProblems: ['857'],
26
+ sourceUrl: 'https://www.erdosproblems.com/536',
27
+ shortStatement:
28
+ 'Number-theoretic analogue of the sunflower problem framed through least common multiples.',
29
+ harnessDepth: 'dossier',
30
+ formalizationStatus: 'planned',
31
+ },
32
+ {
33
+ problemId: '856',
34
+ displayName: 'Erdos Problem #856',
35
+ title: 'Harmonic LCM Sunflower Analogue',
36
+ siteStatus: 'open',
37
+ repoStatus: 'cataloged',
38
+ cluster: 'sunflower',
39
+ familyTags: ['sunflower-analogue', 'number-theory'],
40
+ relatedProblems: ['857'],
41
+ sourceUrl: 'https://www.erdosproblems.com/856',
42
+ shortStatement:
43
+ 'A harmonic or density-shaped LCM analogue whose exponents are explicitly linked to progress on the weak sunflower problem.',
44
+ harnessDepth: 'dossier',
45
+ formalizationStatus: 'planned',
46
+ },
47
+ {
48
+ problemId: '857',
49
+ displayName: 'Erdos Problem #857',
50
+ title: 'Sunflower Conjecture',
51
+ siteStatus: 'open',
52
+ repoStatus: 'active',
53
+ cluster: 'sunflower',
54
+ familyTags: ['sunflower', 'extremal-set-theory'],
55
+ relatedProblems: ['20', '536', '856'],
56
+ sourceUrl: 'https://www.erdosproblems.com/857',
57
+ shortStatement:
58
+ 'Bound the weak sunflower number m(n,k) by C(k)^n and sharpen the current active route toward asymptotic closure.',
59
+ harnessDepth: 'deep',
60
+ formalizationStatus: 'active',
61
+ researchState: {
62
+ openProblem: true,
63
+ activeRoute: 'anchored_selector_linearization',
64
+ routeBreakthrough: true,
65
+ problemSolved: false,
66
+ },
67
+ },
68
+ ];
69
+
70
+ export function listProblems(filters = {}) {
71
+ const cluster = filters.cluster ? String(filters.cluster).toLowerCase() : null;
72
+ return [...catalog]
73
+ .filter((entry) => (cluster ? entry.cluster === cluster : true))
74
+ .sort((a, b) => Number(a.problemId) - Number(b.problemId));
75
+ }
76
+
77
+ export function getProblem(problemId) {
78
+ return catalog.find((entry) => entry.problemId === String(problemId));
79
+ }
80
+
81
+ export function getCluster(clusterName) {
82
+ const name = String(clusterName).toLowerCase();
83
+ const problems = listProblems({ cluster: name });
84
+ if (problems.length === 0) {
85
+ return null;
86
+ }
87
+ return {
88
+ name,
89
+ problems,
90
+ deepHarnessProblems: problems.filter((entry) => entry.harnessDepth === 'deep'),
91
+ dossierProblems: problems.filter((entry) => entry.harnessDepth === 'dossier'),
92
+ };
93
+ }
94
+
95
+ export function listClusters() {
96
+ const names = [...new Set(catalog.map((entry) => entry.cluster))].sort();
97
+ return names.map((name) => getCluster(name));
98
+ }
@@ -0,0 +1,41 @@
1
+ import { runClusterCommand } from '../commands/cluster.js';
2
+ import { runDossierCommand } from '../commands/dossier.js';
3
+ import { runProblemCommand } from '../commands/problem.js';
4
+ import { runWorkspaceCommand } from '../commands/workspace.js';
5
+
6
+ function printUsage() {
7
+ console.log('erdos-problems CLI');
8
+ console.log('');
9
+ console.log('Usage:');
10
+ console.log(' erdos problem list [--cluster <name>]');
11
+ console.log(' erdos problem show <id>');
12
+ console.log(' erdos problem use <id>');
13
+ console.log(' erdos problem current');
14
+ console.log(' erdos cluster list');
15
+ console.log(' erdos cluster show <name>');
16
+ console.log(' erdos workspace show');
17
+ console.log(' erdos dossier show <id>');
18
+ }
19
+
20
+ const args = process.argv.slice(2);
21
+ const [command, ...rest] = args;
22
+
23
+ let exitCode = 0;
24
+
25
+ if (!command || command === 'help' || command === '--help') {
26
+ printUsage();
27
+ } else if (command === 'problem') {
28
+ exitCode = runProblemCommand(rest);
29
+ } else if (command === 'cluster') {
30
+ exitCode = runClusterCommand(rest);
31
+ } else if (command === 'workspace') {
32
+ exitCode = runWorkspaceCommand(rest);
33
+ } else if (command === 'dossier') {
34
+ exitCode = runDossierCommand(rest);
35
+ } else {
36
+ console.error(`Unknown command: ${command}`);
37
+ printUsage();
38
+ exitCode = 1;
39
+ }
40
+
41
+ process.exitCode = exitCode;
@@ -0,0 +1,52 @@
1
+ import { getCluster, listClusters } from '../atlas/catalog.js';
2
+
3
+ function printCluster(cluster) {
4
+ console.log(`Cluster: ${cluster.name}`);
5
+ console.log(`Problems: ${cluster.problems.map((problem) => problem.problemId).join(', ')}`);
6
+ console.log(`Deep harness: ${cluster.deepHarnessProblems.map((problem) => problem.problemId).join(', ') || '(none)'}`);
7
+ console.log(`Dossier-first: ${cluster.dossierProblems.map((problem) => problem.problemId).join(', ') || '(none)'}`);
8
+ console.log('Summary:');
9
+ if (cluster.name === 'sunflower') {
10
+ console.log(' Weak sunflower core: 857');
11
+ console.log(' Strong sunflower sibling: 20');
12
+ console.log(' Related analogues: 536, 856');
13
+ }
14
+ }
15
+
16
+ export function runClusterCommand(args) {
17
+ const [subcommand, value] = args;
18
+
19
+ if (!subcommand || subcommand === 'help' || subcommand === '--help') {
20
+ console.log('Usage:');
21
+ console.log(' erdos cluster list');
22
+ console.log(' erdos cluster show <name>');
23
+ return 0;
24
+ }
25
+
26
+ if (subcommand === 'list') {
27
+ console.log('Clusters:');
28
+ for (const cluster of listClusters()) {
29
+ console.log(`- ${cluster.name}: ${cluster.problems.length} problems, ${cluster.deepHarnessProblems.length} deep-harness`);
30
+ }
31
+ return 0;
32
+ }
33
+
34
+ if (subcommand !== 'show') {
35
+ console.error(`Unknown cluster subcommand: ${subcommand}`);
36
+ return 1;
37
+ }
38
+
39
+ if (!value) {
40
+ console.error('Missing cluster name.');
41
+ return 1;
42
+ }
43
+
44
+ const cluster = getCluster(value);
45
+ if (!cluster) {
46
+ console.error(`Unknown cluster: ${value}`);
47
+ return 1;
48
+ }
49
+
50
+ printCluster(cluster);
51
+ return 0;
52
+ }
@@ -0,0 +1,51 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getProblem } from '../atlas/catalog.js';
4
+ import { getProblemDir } from '../runtime/paths.js';
5
+ import { readCurrentProblem } from '../runtime/workspace.js';
6
+
7
+ const sections = ['STATEMENT.md', 'REFERENCES.md', 'EVIDENCE.md', 'FORMALIZATION.md'];
8
+
9
+ export function runDossierCommand(args) {
10
+ const [subcommand, value] = args;
11
+
12
+ if (!subcommand || subcommand === 'help' || subcommand === '--help') {
13
+ console.log('Usage:');
14
+ console.log(' erdos dossier show <id>');
15
+ return 0;
16
+ }
17
+
18
+ if (subcommand !== 'show') {
19
+ console.error(`Unknown dossier subcommand: ${subcommand}`);
20
+ return 1;
21
+ }
22
+
23
+ const problemId = value ?? readCurrentProblem();
24
+ if (!problemId) {
25
+ console.error('Missing problem id and no active problem is selected.');
26
+ return 1;
27
+ }
28
+
29
+ const problem = getProblem(problemId);
30
+ if (!problem) {
31
+ console.error(`Unknown problem: ${problemId}`);
32
+ return 1;
33
+ }
34
+
35
+ const problemDir = getProblemDir(problem.problemId);
36
+ console.log(`${problem.displayName} dossier`);
37
+ console.log(`Directory: ${problemDir}`);
38
+ console.log('Sections:');
39
+ for (const section of sections) {
40
+ const filePath = path.join(problemDir, section);
41
+ const exists = fs.existsSync(filePath);
42
+ console.log(`- ${section}: ${exists ? 'present' : 'missing'}`);
43
+ }
44
+ const statementPath = path.join(problemDir, 'STATEMENT.md');
45
+ if (fs.existsSync(statementPath)) {
46
+ console.log('');
47
+ console.log('Statement preview:');
48
+ console.log(fs.readFileSync(statementPath, 'utf8').trim());
49
+ }
50
+ return 0;
51
+ }
@@ -0,0 +1,124 @@
1
+ import { getProblem, listProblems } from '../atlas/catalog.js';
2
+ import { readCurrentProblem, setCurrentProblem } from '../runtime/workspace.js';
3
+
4
+ function parseListFilters(args) {
5
+ const filters = {};
6
+ for (let index = 0; index < args.length; index += 1) {
7
+ const token = args[index];
8
+ if (token === '--cluster') {
9
+ const cluster = args[index + 1];
10
+ if (!cluster) {
11
+ return { error: 'Missing cluster name after --cluster.' };
12
+ }
13
+ filters.cluster = cluster;
14
+ index += 1;
15
+ continue;
16
+ }
17
+ return { error: `Unknown list option: ${token}` };
18
+ }
19
+ return { filters };
20
+ }
21
+
22
+ function printProblemTable(rows, activeProblem) {
23
+ console.log('ID Site Repo Cluster Active Title');
24
+ for (const row of rows) {
25
+ const id = row.problemId.padEnd(4, ' ');
26
+ const site = row.siteStatus.padEnd(6, ' ');
27
+ const repo = row.repoStatus.padEnd(10, ' ');
28
+ const cluster = row.cluster.padEnd(11, ' ');
29
+ const active = (row.problemId === activeProblem ? '*' : '-').padEnd(7, ' ');
30
+ console.log(`${id} ${site} ${repo} ${cluster} ${active} ${row.title}`);
31
+ }
32
+ }
33
+
34
+ function printProblem(problem) {
35
+ console.log(problem.displayName);
36
+ console.log(`Title: ${problem.title}`);
37
+ console.log(`Source: ${problem.sourceUrl}`);
38
+ console.log(`Site status: ${problem.siteStatus}`);
39
+ console.log(`Repo status: ${problem.repoStatus}`);
40
+ console.log(`Cluster: ${problem.cluster}`);
41
+ console.log(`Harness depth: ${problem.harnessDepth}`);
42
+ console.log(`Formalization: ${problem.formalizationStatus}`);
43
+ console.log(`Related: ${problem.relatedProblems.join(', ') || '(none)'}`);
44
+ console.log(`Tags: ${problem.familyTags.join(', ') || '(none)'}`);
45
+ console.log(`Statement: ${problem.shortStatement}`);
46
+ if (problem.researchState) {
47
+ console.log('Research state:');
48
+ console.log(` open problem: ${problem.researchState.openProblem ? 'yes' : 'no'}`);
49
+ console.log(` active route: ${problem.researchState.activeRoute}`);
50
+ console.log(` route breakthrough: ${problem.researchState.routeBreakthrough ? 'yes' : 'no'}`);
51
+ console.log(` problem solved: ${problem.researchState.problemSolved ? 'yes' : 'no'}`);
52
+ }
53
+ }
54
+
55
+ export function runProblemCommand(args) {
56
+ const [subcommand, value, ...rest] = args;
57
+
58
+ if (!subcommand || subcommand === 'help' || subcommand === '--help') {
59
+ console.log('Usage:');
60
+ console.log(' erdos problem list [--cluster <name>]');
61
+ console.log(' erdos problem show <id>');
62
+ console.log(' erdos problem use <id>');
63
+ console.log(' erdos problem current');
64
+ return 0;
65
+ }
66
+
67
+ if (subcommand === 'list') {
68
+ const parsed = parseListFilters([value, ...rest].filter(Boolean));
69
+ if (parsed.error) {
70
+ console.error(parsed.error);
71
+ return 1;
72
+ }
73
+ printProblemTable(listProblems(parsed.filters), readCurrentProblem());
74
+ return 0;
75
+ }
76
+
77
+ if (subcommand === 'show') {
78
+ const problemId = value ?? readCurrentProblem();
79
+ if (!problemId) {
80
+ console.error('Missing problem id and no active problem is selected.');
81
+ return 1;
82
+ }
83
+ const problem = getProblem(problemId);
84
+ if (!problem) {
85
+ console.error(`Unknown problem: ${problemId}`);
86
+ return 1;
87
+ }
88
+ printProblem(problem);
89
+ return 0;
90
+ }
91
+
92
+ if (subcommand === 'use') {
93
+ if (!value) {
94
+ console.error('Missing problem id.');
95
+ return 1;
96
+ }
97
+ const problem = getProblem(value);
98
+ if (!problem) {
99
+ console.error(`Unknown problem: ${value}`);
100
+ return 1;
101
+ }
102
+ setCurrentProblem(problem.problemId);
103
+ console.log(`Active problem set to ${problem.problemId} (${problem.title})`);
104
+ return 0;
105
+ }
106
+
107
+ if (subcommand === 'current') {
108
+ const problemId = readCurrentProblem();
109
+ if (!problemId) {
110
+ console.log('No active problem selected.');
111
+ return 0;
112
+ }
113
+ const problem = getProblem(problemId);
114
+ if (!problem) {
115
+ console.error(`Active problem ${problemId} is not in the catalog.`);
116
+ return 1;
117
+ }
118
+ printProblem(problem);
119
+ return 0;
120
+ }
121
+
122
+ console.error(`Unknown problem subcommand: ${subcommand}`);
123
+ return 1;
124
+ }
@@ -0,0 +1,24 @@
1
+ import { getWorkspaceSummary } from '../runtime/workspace.js';
2
+
3
+ export function runWorkspaceCommand(args) {
4
+ const [subcommand] = args;
5
+
6
+ if (!subcommand || subcommand === 'help' || subcommand === '--help') {
7
+ console.log('Usage:');
8
+ console.log(' erdos workspace show');
9
+ return 0;
10
+ }
11
+
12
+ if (subcommand !== 'show') {
13
+ console.error(`Unknown workspace subcommand: ${subcommand}`);
14
+ return 1;
15
+ }
16
+
17
+ const summary = getWorkspaceSummary();
18
+ console.log(`Workspace root: ${summary.workspaceRoot}`);
19
+ console.log(`State dir: ${summary.stateDir}`);
20
+ console.log(`Initialized: ${summary.hasState ? 'yes' : 'no'}`);
21
+ console.log(`Active problem: ${summary.activeProblem ?? '(none)'}`);
22
+ console.log(`Updated at: ${summary.updatedAt ?? '(none)'}`);
23
+ return 0;
24
+ }
@@ -0,0 +1,25 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ const here = path.dirname(fileURLToPath(import.meta.url));
5
+ export const repoRoot = path.resolve(here, '..', '..');
6
+
7
+ export function getWorkspaceRoot() {
8
+ return process.cwd();
9
+ }
10
+
11
+ export function getWorkspaceDir() {
12
+ return path.join(getWorkspaceRoot(), '.erdos');
13
+ }
14
+
15
+ export function getWorkspaceStatePath() {
16
+ return path.join(getWorkspaceDir(), 'state.json');
17
+ }
18
+
19
+ export function getCurrentProblemPath() {
20
+ return path.join(getWorkspaceDir(), 'current-problem.json');
21
+ }
22
+
23
+ export function getProblemDir(problemId) {
24
+ return path.join(repoRoot, 'problems', String(problemId));
25
+ }
@@ -0,0 +1,71 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ getCurrentProblemPath,
5
+ getWorkspaceDir,
6
+ getWorkspaceRoot,
7
+ getWorkspaceStatePath,
8
+ } 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
+ }
14
+
15
+ export function readWorkspaceState() {
16
+ const filePath = getWorkspaceStatePath();
17
+ if (!fs.existsSync(filePath)) {
18
+ return null;
19
+ }
20
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
21
+ }
22
+
23
+ export function ensureWorkspaceState() {
24
+ const existing = readWorkspaceState();
25
+ if (existing) {
26
+ return existing;
27
+ }
28
+ const now = new Date().toISOString();
29
+ const state = {
30
+ workspaceRoot: getWorkspaceRoot(),
31
+ createdAt: now,
32
+ updatedAt: now,
33
+ };
34
+ writeJson(getWorkspaceStatePath(), state);
35
+ return state;
36
+ }
37
+
38
+ export function setCurrentProblem(problemId) {
39
+ const existing = ensureWorkspaceState();
40
+ const now = new Date().toISOString();
41
+ writeJson(getCurrentProblemPath(), {
42
+ problemId: String(problemId),
43
+ selectedAt: now,
44
+ });
45
+ writeJson(getWorkspaceStatePath(), {
46
+ workspaceRoot: getWorkspaceRoot(),
47
+ createdAt: existing.createdAt ?? now,
48
+ updatedAt: now,
49
+ activeProblem: String(problemId),
50
+ });
51
+ }
52
+
53
+ export function readCurrentProblem() {
54
+ const filePath = getCurrentProblemPath();
55
+ if (!fs.existsSync(filePath)) {
56
+ return null;
57
+ }
58
+ const payload = JSON.parse(fs.readFileSync(filePath, 'utf8'));
59
+ return payload.problemId ?? null;
60
+ }
61
+
62
+ export function getWorkspaceSummary() {
63
+ const state = readWorkspaceState();
64
+ return {
65
+ workspaceRoot: getWorkspaceRoot(),
66
+ stateDir: getWorkspaceDir(),
67
+ hasState: Boolean(state),
68
+ activeProblem: readCurrentProblem(),
69
+ updatedAt: state?.updatedAt ?? null,
70
+ };
71
+ }