erdos-problems 0.1.0 → 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.
- package/README.md +73 -3
- package/data/upstream/erdosproblems/PROBLEMS_INDEX.json +44420 -0
- package/data/upstream/erdosproblems/SYNC_MANIFEST.json +9 -0
- package/data/upstream/erdosproblems/problems.yaml +13077 -0
- package/docs/ERDOS_PROBLEMS_PROBLEM_SCHEMA.md +104 -119
- package/package.json +5 -1
- package/problems/1008/EVIDENCE.md +4 -0
- package/problems/1008/FORMALIZATION.md +5 -0
- package/problems/1008/REFERENCES.md +5 -0
- package/problems/1008/STATEMENT.md +9 -0
- package/problems/1008/problem.yaml +37 -0
- package/problems/18/EVIDENCE.md +4 -0
- package/problems/18/FORMALIZATION.md +5 -0
- package/problems/18/REFERENCES.md +6 -0
- package/problems/18/STATEMENT.md +12 -0
- package/problems/18/problem.yaml +38 -0
- package/problems/20/problem.yaml +30 -8
- package/problems/536/problem.yaml +30 -8
- package/problems/542/EVIDENCE.md +6 -0
- package/problems/542/FORMALIZATION.md +5 -0
- package/problems/542/REFERENCES.md +5 -0
- package/problems/542/STATEMENT.md +11 -0
- package/problems/542/problem.yaml +38 -0
- package/problems/856/problem.yaml +30 -8
- package/problems/857/problem.yaml +31 -9
- package/problems/89/EVIDENCE.md +4 -0
- package/problems/89/FORMALIZATION.md +5 -0
- package/problems/89/REFERENCES.md +6 -0
- package/problems/89/STATEMENT.md +10 -0
- package/problems/89/problem.yaml +37 -0
- package/src/atlas/catalog.js +83 -72
- package/src/cli/index.js +16 -1
- package/src/commands/bootstrap.js +81 -0
- package/src/commands/dossier.js +19 -11
- package/src/commands/problem.js +117 -8
- package/src/commands/scaffold.js +60 -0
- package/src/commands/upstream.js +60 -0
- package/src/commands/workspace.js +2 -0
- package/src/runtime/files.js +37 -0
- package/src/runtime/paths.js +64 -0
- package/src/runtime/problem-artifacts.js +150 -0
- package/src/runtime/workspace.js +7 -7
- package/src/upstream/sync.js +272 -0
|
@@ -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,8 @@ 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}`);
|
|
22
24
|
console.log(`Updated at: ${summary.updatedAt ?? '(none)'}`);
|
|
23
25
|
return 0;
|
|
24
26
|
}
|
|
@@ -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
|
+
}
|
package/src/runtime/paths.js
CHANGED
|
@@ -20,6 +20,70 @@ 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
|
+
|
|
23
55
|
export function getProblemDir(problemId) {
|
|
24
56
|
return path.join(repoRoot, 'problems', String(problemId));
|
|
25
57
|
}
|
|
58
|
+
|
|
59
|
+
export function getBundledDataDir() {
|
|
60
|
+
return path.join(repoRoot, 'data');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getBundledUpstreamDir() {
|
|
64
|
+
return path.join(getBundledDataDir(), 'upstream', 'erdosproblems');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getBundledUpstreamYamlPath() {
|
|
68
|
+
return path.join(getBundledUpstreamDir(), 'problems.yaml');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getBundledUpstreamIndexPath() {
|
|
72
|
+
return path.join(getBundledUpstreamDir(), 'PROBLEMS_INDEX.json');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getBundledUpstreamManifestPath() {
|
|
76
|
+
return path.join(getBundledUpstreamDir(), 'SYNC_MANIFEST.json');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getRepoAnalysisDir() {
|
|
80
|
+
return path.join(repoRoot, 'analysis');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getRepoUpstreamDiffPath() {
|
|
84
|
+
return path.join(getRepoAnalysisDir(), 'UPSTREAM_DIFF.md');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getPackDir(packName) {
|
|
88
|
+
return path.join(repoRoot, 'packs', String(packName));
|
|
89
|
+
}
|
|
@@ -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
|
+
}
|
package/src/runtime/workspace.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
import {
|
|
4
3
|
getCurrentProblemPath,
|
|
5
4
|
getWorkspaceDir,
|
|
5
|
+
getWorkspaceProblemScaffoldDir,
|
|
6
6
|
getWorkspaceRoot,
|
|
7
7
|
getWorkspaceStatePath,
|
|
8
|
+
getWorkspaceUpstreamDir,
|
|
8
9
|
} 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
|
-
}
|
|
10
|
+
import { writeJson } from './files.js';
|
|
14
11
|
|
|
15
12
|
export function readWorkspaceState() {
|
|
16
13
|
const filePath = getWorkspaceStatePath();
|
|
@@ -61,11 +58,14 @@ export function readCurrentProblem() {
|
|
|
61
58
|
|
|
62
59
|
export function getWorkspaceSummary() {
|
|
63
60
|
const state = readWorkspaceState();
|
|
61
|
+
const activeProblem = readCurrentProblem();
|
|
64
62
|
return {
|
|
65
63
|
workspaceRoot: getWorkspaceRoot(),
|
|
66
64
|
stateDir: getWorkspaceDir(),
|
|
67
65
|
hasState: Boolean(state),
|
|
68
|
-
activeProblem
|
|
66
|
+
activeProblem,
|
|
67
|
+
upstreamDir: getWorkspaceUpstreamDir(),
|
|
68
|
+
scaffoldDir: activeProblem ? getWorkspaceProblemScaffoldDir(activeProblem) : getWorkspaceProblemScaffoldDir('<problem-id>'),
|
|
69
69
|
updatedAt: state?.updatedAt ?? null,
|
|
70
70
|
};
|
|
71
71
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { parse } from 'yaml';
|
|
5
|
+
import { loadLocalProblems } from '../atlas/catalog.js';
|
|
6
|
+
import { ensureDir, readJson, writeJson, writeText } from '../runtime/files.js';
|
|
7
|
+
import {
|
|
8
|
+
getBundledUpstreamDir,
|
|
9
|
+
getBundledUpstreamIndexPath,
|
|
10
|
+
getBundledUpstreamManifestPath,
|
|
11
|
+
getBundledUpstreamYamlPath,
|
|
12
|
+
getRepoUpstreamDiffPath,
|
|
13
|
+
getWorkspaceDiffPath,
|
|
14
|
+
getWorkspaceUpstreamDir,
|
|
15
|
+
getWorkspaceUpstreamIndexPath,
|
|
16
|
+
getWorkspaceUpstreamManifestPath,
|
|
17
|
+
getWorkspaceUpstreamYamlPath,
|
|
18
|
+
} from '../runtime/paths.js';
|
|
19
|
+
|
|
20
|
+
const UPSTREAM_REPO_URL = 'https://github.com/teorth/erdosproblems';
|
|
21
|
+
const RAW_PROBLEMS_URL = 'https://raw.githubusercontent.com/teorth/erdosproblems/master/data/problems.yaml';
|
|
22
|
+
const COMMIT_API_URL = 'https://api.github.com/repos/teorth/erdosproblems/commits?path=data/problems.yaml&per_page=1';
|
|
23
|
+
|
|
24
|
+
function sha256(text) {
|
|
25
|
+
return crypto.createHash('sha256').update(text).digest('hex');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeTag(tag) {
|
|
29
|
+
return String(tag).trim().toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeStatus(state) {
|
|
33
|
+
return String(state ?? '').trim().toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeUpstreamRecord(entry) {
|
|
37
|
+
return {
|
|
38
|
+
number: String(entry.number),
|
|
39
|
+
prize: entry.prize ?? null,
|
|
40
|
+
status: {
|
|
41
|
+
state: entry.status?.state ?? 'unknown',
|
|
42
|
+
last_update: entry.status?.last_update ?? null,
|
|
43
|
+
},
|
|
44
|
+
formalized: {
|
|
45
|
+
state: entry.formalized?.state ?? 'unknown',
|
|
46
|
+
last_update: entry.formalized?.last_update ?? null,
|
|
47
|
+
},
|
|
48
|
+
tags: (entry.tags ?? []).map(normalizeTag),
|
|
49
|
+
oeis: entry.oeis ?? [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function fetchUpstreamCommit() {
|
|
54
|
+
const response = await fetch(COMMIT_API_URL, {
|
|
55
|
+
headers: {
|
|
56
|
+
'User-Agent': 'erdos-problems-cli',
|
|
57
|
+
Accept: 'application/vnd.github+json',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const payload = await response.json();
|
|
64
|
+
return payload[0]?.sha ?? null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function fetchUpstreamSnapshot() {
|
|
68
|
+
const response = await fetch(RAW_PROBLEMS_URL, {
|
|
69
|
+
headers: {
|
|
70
|
+
'User-Agent': 'erdos-problems-cli',
|
|
71
|
+
Accept: 'text/plain',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error(`Unable to fetch upstream problems.yaml: ${response.status}`);
|
|
76
|
+
}
|
|
77
|
+
const rawYaml = await response.text();
|
|
78
|
+
const parsed = parse(rawYaml);
|
|
79
|
+
const records = parsed.map(normalizeUpstreamRecord);
|
|
80
|
+
const commit = await fetchUpstreamCommit();
|
|
81
|
+
const manifest = {
|
|
82
|
+
upstream_repo: UPSTREAM_REPO_URL,
|
|
83
|
+
source_url: RAW_PROBLEMS_URL,
|
|
84
|
+
source_file: 'data/problems.yaml',
|
|
85
|
+
fetched_at: new Date().toISOString(),
|
|
86
|
+
upstream_commit: commit,
|
|
87
|
+
raw_sha256: sha256(rawYaml),
|
|
88
|
+
entry_count: records.length,
|
|
89
|
+
};
|
|
90
|
+
const byNumber = Object.fromEntries(records.map((record) => [record.number, record]));
|
|
91
|
+
return {
|
|
92
|
+
rawYaml,
|
|
93
|
+
manifest,
|
|
94
|
+
index: {
|
|
95
|
+
generated_at: manifest.fetched_at,
|
|
96
|
+
entry_count: records.length,
|
|
97
|
+
records,
|
|
98
|
+
by_number: byNumber,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function loadSnapshotPaths(preferWorkspace) {
|
|
104
|
+
if (preferWorkspace && fs.existsSync(getWorkspaceUpstreamIndexPath())) {
|
|
105
|
+
return {
|
|
106
|
+
manifestPath: getWorkspaceUpstreamManifestPath(),
|
|
107
|
+
indexPath: getWorkspaceUpstreamIndexPath(),
|
|
108
|
+
yamlPath: getWorkspaceUpstreamYamlPath(),
|
|
109
|
+
kind: 'workspace',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
manifestPath: getBundledUpstreamManifestPath(),
|
|
114
|
+
indexPath: getBundledUpstreamIndexPath(),
|
|
115
|
+
yamlPath: getBundledUpstreamYamlPath(),
|
|
116
|
+
kind: 'bundled',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function loadActiveUpstreamSnapshot() {
|
|
121
|
+
const paths = loadSnapshotPaths(true);
|
|
122
|
+
if (!fs.existsSync(paths.indexPath) || !fs.existsSync(paths.manifestPath)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
kind: paths.kind,
|
|
127
|
+
manifest: readJson(paths.manifestPath),
|
|
128
|
+
manifestPath: paths.manifestPath,
|
|
129
|
+
index: readJson(paths.indexPath),
|
|
130
|
+
indexPath: paths.indexPath,
|
|
131
|
+
yamlPath: paths.yamlPath,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writeSnapshotToDirectory(directory, snapshot) {
|
|
136
|
+
ensureDir(directory);
|
|
137
|
+
const yamlPath = path.join(directory, 'problems.yaml');
|
|
138
|
+
const indexPath = path.join(directory, 'PROBLEMS_INDEX.json');
|
|
139
|
+
const manifestPath = path.join(directory, 'SYNC_MANIFEST.json');
|
|
140
|
+
writeText(yamlPath, snapshot.rawYaml);
|
|
141
|
+
writeJson(indexPath, snapshot.index);
|
|
142
|
+
writeJson(manifestPath, snapshot.manifest);
|
|
143
|
+
return { yamlPath, indexPath, manifestPath };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function compareTagSets(localProblem, upstreamRecord) {
|
|
147
|
+
const localTags = new Set((localProblem.familyTags ?? []).map(normalizeTag));
|
|
148
|
+
const upstreamTags = new Set((upstreamRecord.tags ?? []).map(normalizeTag));
|
|
149
|
+
const localOnly = [...localTags].filter((tag) => !upstreamTags.has(tag)).sort();
|
|
150
|
+
const upstreamOnly = [...upstreamTags].filter((tag) => !localTags.has(tag)).sort();
|
|
151
|
+
return { localOnly, upstreamOnly };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function buildUpstreamDiff() {
|
|
155
|
+
const snapshot = loadActiveUpstreamSnapshot();
|
|
156
|
+
if (!snapshot) {
|
|
157
|
+
throw new Error('No upstream snapshot available. Run `erdos upstream sync` first.');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const localProblems = loadLocalProblems();
|
|
161
|
+
const recordsByNumber = snapshot.index.by_number ?? {};
|
|
162
|
+
const overlaps = [];
|
|
163
|
+
const localOnly = [];
|
|
164
|
+
|
|
165
|
+
for (const problem of localProblems) {
|
|
166
|
+
const upstream = recordsByNumber[problem.problemId];
|
|
167
|
+
if (!upstream) {
|
|
168
|
+
localOnly.push(problem.problemId);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const statusMatches = normalizeStatus(problem.siteStatus) === normalizeStatus(upstream.status.state);
|
|
173
|
+
const formalizedMatches = normalizeStatus(problem.upstreamFormalizedState) === normalizeStatus(upstream.formalized.state);
|
|
174
|
+
const tagDiff = compareTagSets(problem, upstream);
|
|
175
|
+
|
|
176
|
+
overlaps.push({
|
|
177
|
+
problemId: problem.problemId,
|
|
178
|
+
title: problem.title,
|
|
179
|
+
cluster: problem.cluster,
|
|
180
|
+
localSiteStatus: problem.siteStatus,
|
|
181
|
+
upstreamSiteStatus: upstream.status.state,
|
|
182
|
+
statusMatches,
|
|
183
|
+
localFormalized: problem.upstreamFormalizedState,
|
|
184
|
+
upstreamFormalized: upstream.formalized.state,
|
|
185
|
+
formalizedMatches,
|
|
186
|
+
localPrize: problem.prize,
|
|
187
|
+
upstreamPrize: upstream.prize,
|
|
188
|
+
localOnlyTags: tagDiff.localOnly,
|
|
189
|
+
upstreamOnlyTags: tagDiff.upstreamOnly,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const upstreamOnly = Object.keys(recordsByNumber)
|
|
194
|
+
.filter((problemId) => !localProblems.some((problem) => problem.problemId === problemId))
|
|
195
|
+
.sort((a, b) => Number(a) - Number(b));
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
snapshot,
|
|
199
|
+
localProblemCount: localProblems.length,
|
|
200
|
+
upstreamProblemCount: snapshot.index.entry_count,
|
|
201
|
+
overlaps,
|
|
202
|
+
localOnly,
|
|
203
|
+
upstreamOnlyCount: upstreamOnly.length,
|
|
204
|
+
upstreamOnlyPreview: upstreamOnly.slice(0, 20),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function renderUpstreamDiffMarkdown(diff) {
|
|
209
|
+
const lines = [];
|
|
210
|
+
lines.push('# Upstream Diff');
|
|
211
|
+
lines.push('');
|
|
212
|
+
lines.push(`Snapshot kind: \
|
|
213
|
+
${diff.snapshot.kind}`.replace('\
|
|
214
|
+
', ''));
|
|
215
|
+
lines.push(`Upstream commit: ${diff.snapshot.manifest.upstream_commit ?? '(unknown)'}`);
|
|
216
|
+
lines.push(`Fetched at: ${diff.snapshot.manifest.fetched_at}`);
|
|
217
|
+
lines.push(`Local seeded problems: ${diff.localProblemCount}`);
|
|
218
|
+
lines.push(`Upstream total problems: ${diff.upstreamProblemCount}`);
|
|
219
|
+
lines.push(`Upstream-only problems not yet locally seeded: ${diff.upstreamOnlyCount}`);
|
|
220
|
+
lines.push('');
|
|
221
|
+
lines.push('## Overlap');
|
|
222
|
+
lines.push('');
|
|
223
|
+
lines.push('| ID | Cluster | Local site | Upstream site | Status | Local formalized | Upstream formalized | Tags |');
|
|
224
|
+
lines.push('| --- | --- | --- | --- | --- | --- | --- | --- |');
|
|
225
|
+
for (const row of diff.overlaps) {
|
|
226
|
+
const status = row.statusMatches ? 'match' : 'DIFF';
|
|
227
|
+
const formalized = row.formalizedMatches ? 'match' : 'DIFF';
|
|
228
|
+
const tagNotes = [];
|
|
229
|
+
if (row.localOnlyTags.length > 0) {
|
|
230
|
+
tagNotes.push(`local-only: ${row.localOnlyTags.join(', ')}`);
|
|
231
|
+
}
|
|
232
|
+
if (row.upstreamOnlyTags.length > 0) {
|
|
233
|
+
tagNotes.push(`upstream-only: ${row.upstreamOnlyTags.join(', ')}`);
|
|
234
|
+
}
|
|
235
|
+
lines.push(`| ${row.problemId} | ${row.cluster} | ${row.localSiteStatus} | ${row.upstreamSiteStatus} | ${status} | ${row.localFormalized ?? '(unset)'} | ${row.upstreamFormalized ?? '(unset)'} | ${tagNotes.join('; ') || 'match'} |`);
|
|
236
|
+
}
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push('## Upstream-Only Preview');
|
|
239
|
+
lines.push('');
|
|
240
|
+
lines.push(diff.upstreamOnlyPreview.join(', ') || '(none)');
|
|
241
|
+
lines.push('');
|
|
242
|
+
return lines.join('\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function syncUpstream({ writePackageSnapshot = false } = {}) {
|
|
246
|
+
const snapshot = await fetchUpstreamSnapshot();
|
|
247
|
+
const workspacePaths = writeSnapshotToDirectory(getWorkspaceUpstreamDir(), snapshot);
|
|
248
|
+
let bundledPaths = null;
|
|
249
|
+
if (writePackageSnapshot) {
|
|
250
|
+
bundledPaths = writeSnapshotToDirectory(getBundledUpstreamDir(), snapshot);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
snapshot,
|
|
254
|
+
workspacePaths,
|
|
255
|
+
bundledPaths,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function writeDiffArtifacts({ writePackageReport = false } = {}) {
|
|
260
|
+
const diff = buildUpstreamDiff();
|
|
261
|
+
const markdown = renderUpstreamDiffMarkdown(diff);
|
|
262
|
+
writeText(getWorkspaceDiffPath(), markdown);
|
|
263
|
+
if (writePackageReport) {
|
|
264
|
+
writeText(getRepoUpstreamDiffPath(), markdown);
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
diff,
|
|
268
|
+
markdown,
|
|
269
|
+
workspaceDiffPath: getWorkspaceDiffPath(),
|
|
270
|
+
repoDiffPath: writePackageReport ? getRepoUpstreamDiffPath() : null,
|
|
271
|
+
};
|
|
272
|
+
}
|