@mrtdown/core 2.0.0-alpha.3 → 2.0.0-alpha.5

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.
Files changed (140) hide show
  1. package/dist/cli/commands/create.d.ts +30 -0
  2. package/dist/cli/commands/create.js +189 -0
  3. package/dist/cli/commands/create.js.map +1 -0
  4. package/dist/cli/commands/list.d.ts +6 -0
  5. package/dist/cli/commands/list.js +106 -0
  6. package/dist/cli/commands/list.js.map +1 -0
  7. package/dist/cli/commands/show.d.ts +6 -0
  8. package/dist/cli/commands/show.js +156 -0
  9. package/dist/cli/commands/show.js.map +1 -0
  10. package/dist/cli/commands/validate.d.ts +6 -0
  11. package/dist/cli/commands/validate.js +19 -0
  12. package/dist/cli/commands/validate.js.map +1 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +162 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/helpers/keyForAffectedEntity.d.ts +1 -1
  17. package/dist/helpers/keyForAffectedEntity.js.map +1 -1
  18. package/dist/helpers/normalizeRecurringPeriod.d.ts +1 -1
  19. package/dist/helpers/normalizeRecurringPeriod.js +1 -1
  20. package/dist/helpers/normalizeRecurringPeriod.js.map +1 -1
  21. package/dist/helpers/normalizeRecurringPeriod.test.js.map +1 -1
  22. package/dist/helpers/resolvePeriods.d.ts +1 -1
  23. package/dist/helpers/resolvePeriods.js +1 -1
  24. package/dist/helpers/resolvePeriods.js.map +1 -1
  25. package/dist/helpers/resolvePeriods.test.js.map +1 -1
  26. package/dist/index.d.ts +25 -25
  27. package/dist/index.js +25 -25
  28. package/dist/index.js.map +1 -1
  29. package/dist/llm/client.d.ts +2 -0
  30. package/dist/llm/client.js +5 -0
  31. package/dist/llm/client.js.map +1 -0
  32. package/dist/llm/common/MemoryStore.d.ts +21 -0
  33. package/dist/llm/common/MemoryStore.js +100 -0
  34. package/dist/llm/common/MemoryStore.js.map +1 -0
  35. package/dist/llm/common/MemoryStore.test.d.ts +1 -0
  36. package/dist/llm/common/MemoryStore.test.js +225 -0
  37. package/dist/llm/common/MemoryStore.test.js.map +1 -0
  38. package/dist/llm/common/formatCurrentState.d.ts +10 -0
  39. package/dist/llm/common/formatCurrentState.js +342 -0
  40. package/dist/llm/common/formatCurrentState.js.map +1 -0
  41. package/dist/llm/common/tool.d.ts +32 -0
  42. package/dist/llm/common/tool.js +6 -0
  43. package/dist/llm/common/tool.js.map +1 -0
  44. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.d.ts +1 -0
  45. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js +433 -0
  46. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js.map +1 -0
  47. package/dist/llm/functions/extractClaimsFromNewEvidence/index.d.ts +18 -0
  48. package/dist/llm/functions/extractClaimsFromNewEvidence/index.js +153 -0
  49. package/dist/llm/functions/extractClaimsFromNewEvidence/index.js.map +1 -0
  50. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.d.ts +1 -0
  51. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js +168 -0
  52. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js.map +1 -0
  53. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.d.ts +19 -0
  54. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js +65 -0
  55. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js.map +1 -0
  56. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.d.ts +21 -0
  57. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js +115 -0
  58. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js.map +1 -0
  59. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.d.ts +24 -0
  60. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js +110 -0
  61. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js.map +1 -0
  62. package/dist/llm/functions/generateIssueTitleAndSlug/index.d.ts +14 -0
  63. package/dist/llm/functions/generateIssueTitleAndSlug/index.js +38 -0
  64. package/dist/llm/functions/generateIssueTitleAndSlug/index.js.map +1 -0
  65. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.d.ts +1 -0
  66. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js +23 -0
  67. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js.map +1 -0
  68. package/dist/llm/functions/translate/index.d.ts +1 -0
  69. package/dist/llm/functions/translate/index.js +59 -0
  70. package/dist/llm/functions/translate/index.js.map +1 -0
  71. package/dist/llm/functions/triageNewEvidence/eval.test.d.ts +1 -0
  72. package/dist/llm/functions/triageNewEvidence/eval.test.js +139 -0
  73. package/dist/llm/functions/triageNewEvidence/eval.test.js.map +1 -0
  74. package/dist/llm/functions/triageNewEvidence/index.d.ts +37 -0
  75. package/dist/llm/functions/triageNewEvidence/index.js +121 -0
  76. package/dist/llm/functions/triageNewEvidence/index.js.map +1 -0
  77. package/dist/llm/functions/triageNewEvidence/prompt.d.ts +1 -0
  78. package/dist/llm/functions/triageNewEvidence/prompt.js +60 -0
  79. package/dist/llm/functions/triageNewEvidence/prompt.js.map +1 -0
  80. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.d.ts +19 -0
  81. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js +65 -0
  82. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js.map +1 -0
  83. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.d.ts +19 -0
  84. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js +37 -0
  85. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js.map +1 -0
  86. package/dist/repo/issue/IssueRepository.d.ts +1 -1
  87. package/dist/repo/issue/IssueRepository.js +3 -3
  88. package/dist/repo/issue/IssueRepository.js.map +1 -1
  89. package/dist/repo/issue/helpers/deriveCurrentState.d.ts +6 -6
  90. package/dist/repo/issue/helpers/deriveCurrentState.js +1 -1
  91. package/dist/repo/issue/helpers/deriveCurrentState.js.map +1 -1
  92. package/dist/repo/issue/helpers/deriveCurrentState.test.js.map +1 -1
  93. package/dist/schema/issue/evidence.js +1 -1
  94. package/dist/schema/issue/evidence.js.map +1 -1
  95. package/dist/schema/issue/issue.js +1 -1
  96. package/dist/schema/issue/issue.js.map +1 -1
  97. package/dist/scripts/ingestViaWebhook.d.ts +1 -0
  98. package/dist/scripts/ingestViaWebhook.js +9 -0
  99. package/dist/scripts/ingestViaWebhook.js.map +1 -0
  100. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.d.ts +1 -1
  101. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js +1 -1
  102. package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js.map +1 -1
  103. package/dist/util/ingestContent/index.js +9 -9
  104. package/dist/util/ingestContent/index.js.map +1 -1
  105. package/dist/validators/buildContext.d.ts +7 -0
  106. package/dist/validators/buildContext.js +164 -0
  107. package/dist/validators/buildContext.js.map +1 -0
  108. package/dist/validators/index.d.ts +17 -0
  109. package/dist/validators/index.js +58 -0
  110. package/dist/validators/index.js.map +1 -0
  111. package/dist/validators/issue.d.ts +13 -0
  112. package/dist/validators/issue.js +220 -0
  113. package/dist/validators/issue.js.map +1 -0
  114. package/dist/validators/landmark.d.ts +7 -0
  115. package/dist/validators/landmark.js +43 -0
  116. package/dist/validators/landmark.js.map +1 -0
  117. package/dist/validators/line.d.ts +8 -0
  118. package/dist/validators/line.js +87 -0
  119. package/dist/validators/line.js.map +1 -0
  120. package/dist/validators/operator.d.ts +7 -0
  121. package/dist/validators/operator.js +43 -0
  122. package/dist/validators/operator.js.map +1 -0
  123. package/dist/validators/service.d.ts +8 -0
  124. package/dist/validators/service.js +87 -0
  125. package/dist/validators/service.js.map +1 -0
  126. package/dist/validators/station.d.ts +8 -0
  127. package/dist/validators/station.js +93 -0
  128. package/dist/validators/station.js.map +1 -0
  129. package/dist/validators/town.d.ts +7 -0
  130. package/dist/validators/town.js +43 -0
  131. package/dist/validators/town.js.map +1 -0
  132. package/dist/validators/types.d.ts +19 -0
  133. package/dist/validators/types.js +2 -0
  134. package/dist/validators/types.js.map +1 -0
  135. package/dist/validators/utils.d.ts +2 -0
  136. package/dist/validators/utils.js +9 -0
  137. package/dist/validators/utils.js.map +1 -0
  138. package/dist/write/issue/IssueWriter.d.ts +3 -3
  139. package/dist/write/issue/IssueWriter.js.map +1 -1
  140. package/package.json +2 -7
@@ -0,0 +1,30 @@
1
+ type CreateOptions = {
2
+ dataDir: string;
3
+ dryRun?: boolean;
4
+ stdin?: boolean;
5
+ };
6
+ export declare function runCreateIssue(opts: CreateOptions, args: {
7
+ date?: string;
8
+ slug?: string;
9
+ title?: string;
10
+ type?: string;
11
+ source?: string;
12
+ }): Promise<number>;
13
+ export declare function runCreateTown(opts: CreateOptions, args: {
14
+ id?: string;
15
+ name?: string;
16
+ }): Promise<number>;
17
+ export declare function runCreateLandmark(opts: CreateOptions, args: {
18
+ id?: string;
19
+ name?: string;
20
+ }): Promise<number>;
21
+ export declare function runCreateOperator(opts: CreateOptions, args: {
22
+ id?: string;
23
+ name?: string;
24
+ foundedAt?: string;
25
+ url?: string;
26
+ }): Promise<number>;
27
+ export declare function runCreateStation(opts: CreateOptions, args: Record<string, string | undefined>): Promise<number>;
28
+ export declare function runCreateLine(opts: CreateOptions, args: Record<string, string | undefined>): Promise<number>;
29
+ export declare function runCreateService(opts: CreateOptions, args: Record<string, string | undefined>): Promise<number>;
30
+ export {};
@@ -0,0 +1,189 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { createInterface } from 'node:readline';
4
+ import { IssueTypeSchema } from '../../schema/issue/issueType.js';
5
+ import { FileWriteStore } from '../../write/common/FileWriteStore.js';
6
+ import { MRTDownWriter } from '../../write/MRTDownWriter.js';
7
+ function translationsFromEn(en) {
8
+ return {
9
+ 'en-SG': en,
10
+ 'zh-Hans': null,
11
+ ms: null,
12
+ ta: null,
13
+ };
14
+ }
15
+ export async function runCreateIssue(opts, args) {
16
+ const { date, slug, title, type, source } = args;
17
+ if (!date || !slug || !title) {
18
+ console.error('Usage: create issue --date YYYY-MM-DD --slug <slug> --title <title> [--type disruption|maintenance|infra] [--source <source>]');
19
+ return 1;
20
+ }
21
+ const dateMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
22
+ if (!dateMatch) {
23
+ console.error('Invalid date format. Use YYYY-MM-DD.');
24
+ return 1;
25
+ }
26
+ const issueId = `${date}-${slug}`;
27
+ const issueType = (type && IssueTypeSchema.safeParse(type).success ? type : 'disruption');
28
+ const issue = {
29
+ id: issueId,
30
+ type: issueType,
31
+ title: translationsFromEn(title),
32
+ titleMeta: { source: source ?? 'cli' },
33
+ };
34
+ if (opts.dryRun) {
35
+ console.log('Would create:', JSON.stringify(issue, null, 2));
36
+ return 0;
37
+ }
38
+ const store = new FileWriteStore(opts.dataDir);
39
+ const writer = new MRTDownWriter({ store });
40
+ writer.issues.create(issue);
41
+ console.log(`Created issue: ${join(opts.dataDir, 'issue', dateMatch[1], dateMatch[2], issueId)}`);
42
+ return 0;
43
+ }
44
+ export async function runCreateTown(opts, args) {
45
+ if (!args.id || !args.name) {
46
+ console.error('Usage: create town --id <id> --name <name>');
47
+ return 1;
48
+ }
49
+ const town = {
50
+ id: args.id,
51
+ name: translationsFromEn(args.name),
52
+ };
53
+ if (opts.dryRun) {
54
+ console.log('Would create:', JSON.stringify(town, null, 2));
55
+ return 0;
56
+ }
57
+ const store = new FileWriteStore(opts.dataDir);
58
+ const writer = new MRTDownWriter({ store });
59
+ writer.towns.create(town);
60
+ console.log(`Created town: ${join(opts.dataDir, 'town', `${town.id}.json`)}`);
61
+ return 0;
62
+ }
63
+ export async function runCreateLandmark(opts, args) {
64
+ if (!args.id || !args.name) {
65
+ console.error('Usage: create landmark --id <id> --name <name>');
66
+ return 1;
67
+ }
68
+ const landmark = {
69
+ id: args.id,
70
+ name: translationsFromEn(args.name),
71
+ };
72
+ if (opts.dryRun) {
73
+ console.log('Would create:', JSON.stringify(landmark, null, 2));
74
+ return 0;
75
+ }
76
+ const store = new FileWriteStore(opts.dataDir);
77
+ const writer = new MRTDownWriter({ store });
78
+ writer.landmarks.create(landmark);
79
+ console.log(`Created landmark: ${join(opts.dataDir, 'landmark', `${landmark.id}.json`)}`);
80
+ return 0;
81
+ }
82
+ export async function runCreateOperator(opts, args) {
83
+ if (!args.id || !args.name || !args.foundedAt) {
84
+ console.error('Usage: create operator --id <id> --name <name> --founded-at YYYY-MM-DD [--url <url>]');
85
+ return 1;
86
+ }
87
+ const operator = {
88
+ id: args.id,
89
+ name: translationsFromEn(args.name),
90
+ foundedAt: args.foundedAt,
91
+ url: args.url ?? null,
92
+ };
93
+ if (opts.dryRun) {
94
+ console.log('Would create:', JSON.stringify(operator, null, 2));
95
+ return 0;
96
+ }
97
+ const store = new FileWriteStore(opts.dataDir);
98
+ const writer = new MRTDownWriter({ store });
99
+ writer.operators.create(operator);
100
+ console.log(`Created operator: ${join(opts.dataDir, 'operator', `${operator.id}.json`)}`);
101
+ return 0;
102
+ }
103
+ async function readStdin() {
104
+ const rl = createInterface({ input: process.stdin });
105
+ const chunks = [];
106
+ for await (const line of rl) {
107
+ chunks.push(line);
108
+ }
109
+ return chunks.join('\n');
110
+ }
111
+ export async function runCreateStation(opts, args) {
112
+ let json;
113
+ if (opts.stdin) {
114
+ const raw = await readStdin();
115
+ json = JSON.parse(raw);
116
+ }
117
+ else {
118
+ const file = args.stdinFile ?? args.file;
119
+ if (file) {
120
+ const raw = await readFile(file, 'utf-8');
121
+ json = JSON.parse(raw);
122
+ }
123
+ else {
124
+ console.error('Usage: create station --stdin < JSON | create station --file <path>');
125
+ return 1;
126
+ }
127
+ }
128
+ if (opts.dryRun) {
129
+ console.log('Would create station:', JSON.stringify(json, null, 2));
130
+ return 0;
131
+ }
132
+ const store = new FileWriteStore(opts.dataDir);
133
+ const writer = new MRTDownWriter({ store });
134
+ writer.stations.create(json);
135
+ return 0;
136
+ }
137
+ export async function runCreateLine(opts, args) {
138
+ let json;
139
+ if (opts.stdin) {
140
+ const raw = await readStdin();
141
+ json = JSON.parse(raw);
142
+ }
143
+ else {
144
+ const file = args.stdinFile ?? args.file;
145
+ if (file) {
146
+ const raw = await readFile(file, 'utf-8');
147
+ json = JSON.parse(raw);
148
+ }
149
+ else {
150
+ console.error('Usage: create line --stdin < JSON | create line --file <path>');
151
+ return 1;
152
+ }
153
+ }
154
+ if (opts.dryRun) {
155
+ console.log('Would create line:', JSON.stringify(json, null, 2));
156
+ return 0;
157
+ }
158
+ const store = new FileWriteStore(opts.dataDir);
159
+ const writer = new MRTDownWriter({ store });
160
+ writer.lines.create(json);
161
+ return 0;
162
+ }
163
+ export async function runCreateService(opts, args) {
164
+ let json;
165
+ if (opts.stdin) {
166
+ const raw = await readStdin();
167
+ json = JSON.parse(raw);
168
+ }
169
+ else {
170
+ const file = args.stdinFile ?? args.file;
171
+ if (file) {
172
+ const raw = await readFile(file, 'utf-8');
173
+ json = JSON.parse(raw);
174
+ }
175
+ else {
176
+ console.error('Usage: create service --stdin < JSON | create service --file <path>');
177
+ return 1;
178
+ }
179
+ }
180
+ if (opts.dryRun) {
181
+ console.log('Would create service:', JSON.stringify(json, null, 2));
182
+ return 0;
183
+ }
184
+ const store = new FileWriteStore(opts.dataDir);
185
+ const writer = new MRTDownWriter({ store });
186
+ writer.services.create(json);
187
+ return 0;
188
+ }
189
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"/","sources":["cli/commands/create.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport type { IssueType } from '../../schema/issue/issueType.js';\nimport { IssueTypeSchema } from '../../schema/issue/issueType.js';\nimport { FileWriteStore } from '../../write/common/FileWriteStore.js';\nimport { MRTDownWriter } from '../../write/MRTDownWriter.js';\n\ntype CreateOptions = {\n dataDir: string;\n dryRun?: boolean;\n stdin?: boolean;\n};\n\nfunction translationsFromEn(en: string) {\n return {\n 'en-SG': en,\n 'zh-Hans': null,\n ms: null,\n ta: null,\n };\n}\n\nexport async function runCreateIssue(\n opts: CreateOptions,\n args: {\n date?: string;\n slug?: string;\n title?: string;\n type?: string;\n source?: string;\n },\n): Promise<number> {\n const { date, slug, title, type, source } = args;\n if (!date || !slug || !title) {\n console.error(\n 'Usage: create issue --date YYYY-MM-DD --slug <slug> --title <title> [--type disruption|maintenance|infra] [--source <source>]',\n );\n return 1;\n }\n\n const dateMatch = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(date);\n if (!dateMatch) {\n console.error('Invalid date format. Use YYYY-MM-DD.');\n return 1;\n }\n\n const issueId = `${date}-${slug}`;\n const issueType = (\n type && IssueTypeSchema.safeParse(type).success ? type : 'disruption'\n ) as IssueType;\n\n const issue = {\n id: issueId,\n type: issueType,\n title: translationsFromEn(title),\n titleMeta: { source: source ?? 'cli' },\n };\n\n if (opts.dryRun) {\n console.log('Would create:', JSON.stringify(issue, null, 2));\n return 0;\n }\n\n const store = new FileWriteStore(opts.dataDir);\n const writer = new MRTDownWriter({ store });\n writer.issues.create(issue);\n console.log(\n `Created issue: ${join(opts.dataDir, 'issue', dateMatch[1], dateMatch[2], issueId)}`,\n );\n return 0;\n}\n\nexport async function runCreateTown(\n opts: CreateOptions,\n args: { id?: string; name?: string },\n): Promise<number> {\n if (!args.id || !args.name) {\n console.error('Usage: create town --id <id> --name <name>');\n return 1;\n }\n\n const town = {\n id: args.id,\n name: translationsFromEn(args.name),\n };\n\n if (opts.dryRun) {\n console.log('Would create:', JSON.stringify(town, null, 2));\n return 0;\n }\n\n const store = new FileWriteStore(opts.dataDir);\n const writer = new MRTDownWriter({ store });\n writer.towns.create(town);\n console.log(`Created town: ${join(opts.dataDir, 'town', `${town.id}.json`)}`);\n return 0;\n}\n\nexport async function runCreateLandmark(\n opts: CreateOptions,\n args: { id?: string; name?: string },\n): Promise<number> {\n if (!args.id || !args.name) {\n console.error('Usage: create landmark --id <id> --name <name>');\n return 1;\n }\n\n const landmark = {\n id: args.id,\n name: translationsFromEn(args.name),\n };\n\n if (opts.dryRun) {\n console.log('Would create:', JSON.stringify(landmark, null, 2));\n return 0;\n }\n\n const store = new FileWriteStore(opts.dataDir);\n const writer = new MRTDownWriter({ store });\n writer.landmarks.create(landmark);\n console.log(\n `Created landmark: ${join(opts.dataDir, 'landmark', `${landmark.id}.json`)}`,\n );\n return 0;\n}\n\nexport async function runCreateOperator(\n opts: CreateOptions,\n args: { id?: string; name?: string; foundedAt?: string; url?: string },\n): Promise<number> {\n if (!args.id || !args.name || !args.foundedAt) {\n console.error(\n 'Usage: create operator --id <id> --name <name> --founded-at YYYY-MM-DD [--url <url>]',\n );\n return 1;\n }\n\n const operator = {\n id: args.id,\n name: translationsFromEn(args.name),\n foundedAt: args.foundedAt,\n url: args.url ?? null,\n };\n\n if (opts.dryRun) {\n console.log('Would create:', JSON.stringify(operator, null, 2));\n return 0;\n }\n\n const store = new FileWriteStore(opts.dataDir);\n const writer = new MRTDownWriter({ store });\n writer.operators.create(operator);\n console.log(\n `Created operator: ${join(opts.dataDir, 'operator', `${operator.id}.json`)}`,\n );\n return 0;\n}\n\nasync function readStdin(): Promise<string> {\n const rl = createInterface({ input: process.stdin });\n const chunks: string[] = [];\n for await (const line of rl) {\n chunks.push(line);\n }\n return chunks.join('\\n');\n}\n\nexport async function runCreateStation(\n opts: CreateOptions,\n args: Record<string, string | undefined>,\n): Promise<number> {\n let json: unknown;\n if (opts.stdin) {\n const raw = await readStdin();\n json = JSON.parse(raw);\n } else {\n const file = args.stdinFile ?? args.file;\n if (file) {\n const raw = await readFile(file, 'utf-8');\n json = JSON.parse(raw);\n } else {\n console.error(\n 'Usage: create station --stdin < JSON | create station --file <path>',\n );\n return 1;\n }\n }\n\n if (opts.dryRun) {\n console.log('Would create station:', JSON.stringify(json, null, 2));\n return 0;\n }\n\n const store = new FileWriteStore(opts.dataDir);\n const writer = new MRTDownWriter({ store });\n writer.stations.create(json as Parameters<typeof writer.stations.create>[0]);\n return 0;\n}\n\nexport async function runCreateLine(\n opts: CreateOptions,\n args: Record<string, string | undefined>,\n): Promise<number> {\n let json: unknown;\n if (opts.stdin) {\n const raw = await readStdin();\n json = JSON.parse(raw);\n } else {\n const file = args.stdinFile ?? args.file;\n if (file) {\n const raw = await readFile(file, 'utf-8');\n json = JSON.parse(raw);\n } else {\n console.error(\n 'Usage: create line --stdin < JSON | create line --file <path>',\n );\n return 1;\n }\n }\n\n if (opts.dryRun) {\n console.log('Would create line:', JSON.stringify(json, null, 2));\n return 0;\n }\n\n const store = new FileWriteStore(opts.dataDir);\n const writer = new MRTDownWriter({ store });\n writer.lines.create(json as Parameters<typeof writer.lines.create>[0]);\n return 0;\n}\n\nexport async function runCreateService(\n opts: CreateOptions,\n args: Record<string, string | undefined>,\n): Promise<number> {\n let json: unknown;\n if (opts.stdin) {\n const raw = await readStdin();\n json = JSON.parse(raw);\n } else {\n const file = args.stdinFile ?? args.file;\n if (file) {\n const raw = await readFile(file, 'utf-8');\n json = JSON.parse(raw);\n } else {\n console.error(\n 'Usage: create service --stdin < JSON | create service --file <path>',\n );\n return 1;\n }\n }\n\n if (opts.dryRun) {\n console.log('Would create service:', JSON.stringify(json, null, 2));\n return 0;\n }\n\n const store = new FileWriteStore(opts.dataDir);\n const writer = new MRTDownWriter({ store });\n writer.services.create(json as Parameters<typeof writer.services.create>[0]);\n return 0;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export type ListOptions = {
2
+ dataDir: string;
3
+ entity: 'issue' | 'town' | 'landmark' | 'operator' | 'station' | 'line' | 'service';
4
+ json?: boolean;
5
+ };
6
+ export declare function runList(opts: ListOptions): number;
@@ -0,0 +1,106 @@
1
+ import { FileStore } from '../../repo/common/FileStore.js';
2
+ import { MRTDownRepository } from '../../repo/MRTDownRepository.js';
3
+ function nameEn(item) {
4
+ return item.name?.['en-SG'] ?? '—';
5
+ }
6
+ export function runList(opts) {
7
+ const store = new FileStore(opts.dataDir);
8
+ const repo = new MRTDownRepository({ store });
9
+ switch (opts.entity) {
10
+ case 'issue': {
11
+ const bundles = repo.issues.list();
12
+ if (opts.json) {
13
+ console.log(JSON.stringify(bundles.map((b) => ({
14
+ id: b.issue.id,
15
+ type: b.issue.type,
16
+ title: b.issue.title['en-SG'],
17
+ })), null, 2));
18
+ }
19
+ else {
20
+ for (const b of bundles) {
21
+ console.log(`${b.issue.id} ${b.issue.title['en-SG'] ?? b.issue.id}`);
22
+ }
23
+ }
24
+ break;
25
+ }
26
+ case 'town': {
27
+ const items = repo.towns.list();
28
+ if (opts.json) {
29
+ console.log(JSON.stringify(items, null, 2));
30
+ }
31
+ else {
32
+ for (const item of items) {
33
+ console.log(`${item.id} ${nameEn(item)}`);
34
+ }
35
+ }
36
+ break;
37
+ }
38
+ case 'landmark': {
39
+ const items = repo.landmarks.list();
40
+ if (opts.json) {
41
+ console.log(JSON.stringify(items, null, 2));
42
+ }
43
+ else {
44
+ for (const item of items) {
45
+ console.log(`${item.id} ${nameEn(item)}`);
46
+ }
47
+ }
48
+ break;
49
+ }
50
+ case 'operator': {
51
+ const items = repo.operators.list();
52
+ if (opts.json) {
53
+ console.log(JSON.stringify(items, null, 2));
54
+ }
55
+ else {
56
+ for (const item of items) {
57
+ console.log(`${item.id} ${nameEn(item)}`);
58
+ }
59
+ }
60
+ break;
61
+ }
62
+ case 'station': {
63
+ const items = repo.stations.list();
64
+ if (opts.json) {
65
+ console.log(JSON.stringify(items, null, 2));
66
+ }
67
+ else {
68
+ for (const item of items) {
69
+ console.log(`${item.id} ${nameEn(item)}`);
70
+ }
71
+ }
72
+ break;
73
+ }
74
+ case 'line': {
75
+ const items = repo.lines.list();
76
+ if (opts.json) {
77
+ console.log(JSON.stringify(items, null, 2));
78
+ }
79
+ else {
80
+ for (const item of items) {
81
+ console.log(`${item.id} ${nameEn(item)} ${item.type}`);
82
+ }
83
+ }
84
+ break;
85
+ }
86
+ case 'service': {
87
+ const items = repo.services.list();
88
+ if (opts.json) {
89
+ console.log(JSON.stringify(items, null, 2));
90
+ }
91
+ else {
92
+ for (const item of items) {
93
+ console.log(`${item.id} ${nameEn(item)} ${item.lineId}`);
94
+ }
95
+ }
96
+ break;
97
+ }
98
+ default: {
99
+ const _ = opts.entity;
100
+ console.error(`Unknown entity: ${opts.entity}`);
101
+ return 1;
102
+ }
103
+ }
104
+ return 0;
105
+ }
106
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"/","sources":["cli/commands/list.ts"],"names":[],"mappings":"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","sourcesContent":["import { FileStore } from '../../repo/common/FileStore.js';\nimport { MRTDownRepository } from '../../repo/MRTDownRepository.js';\n\nexport type ListOptions = {\n dataDir: string;\n entity:\n | 'issue'\n | 'town'\n | 'landmark'\n | 'operator'\n | 'station'\n | 'line'\n | 'service';\n json?: boolean;\n};\n\nfunction nameEn(item: { name?: { 'en-SG'?: string } }): string {\n return item.name?.['en-SG'] ?? '—';\n}\n\nexport function runList(opts: ListOptions): number {\n const store = new FileStore(opts.dataDir);\n const repo = new MRTDownRepository({ store });\n\n switch (opts.entity) {\n case 'issue': {\n const bundles = repo.issues.list();\n if (opts.json) {\n console.log(\n JSON.stringify(\n bundles.map((b) => ({\n id: b.issue.id,\n type: b.issue.type,\n title: b.issue.title['en-SG'],\n })),\n null,\n 2,\n ),\n );\n } else {\n for (const b of bundles) {\n console.log(`${b.issue.id} ${b.issue.title['en-SG'] ?? b.issue.id}`);\n }\n }\n break;\n }\n case 'town': {\n const items = repo.towns.list();\n if (opts.json) {\n console.log(JSON.stringify(items, null, 2));\n } else {\n for (const item of items) {\n console.log(`${item.id} ${nameEn(item)}`);\n }\n }\n break;\n }\n case 'landmark': {\n const items = repo.landmarks.list();\n if (opts.json) {\n console.log(JSON.stringify(items, null, 2));\n } else {\n for (const item of items) {\n console.log(`${item.id} ${nameEn(item)}`);\n }\n }\n break;\n }\n case 'operator': {\n const items = repo.operators.list();\n if (opts.json) {\n console.log(JSON.stringify(items, null, 2));\n } else {\n for (const item of items) {\n console.log(`${item.id} ${nameEn(item)}`);\n }\n }\n break;\n }\n case 'station': {\n const items = repo.stations.list();\n if (opts.json) {\n console.log(JSON.stringify(items, null, 2));\n } else {\n for (const item of items) {\n console.log(`${item.id} ${nameEn(item)}`);\n }\n }\n break;\n }\n case 'line': {\n const items = repo.lines.list();\n if (opts.json) {\n console.log(JSON.stringify(items, null, 2));\n } else {\n for (const item of items) {\n console.log(`${item.id} ${nameEn(item)} ${item.type}`);\n }\n }\n break;\n }\n case 'service': {\n const items = repo.services.list();\n if (opts.json) {\n console.log(JSON.stringify(items, null, 2));\n } else {\n for (const item of items) {\n console.log(`${item.id} ${nameEn(item)} ${item.lineId}`);\n }\n }\n break;\n }\n default: {\n const _: never = opts.entity;\n console.error(`Unknown entity: ${opts.entity}`);\n return 1;\n }\n }\n\n return 0;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export type ShowIssueOptions = {
2
+ dataDir: string;
3
+ issueId: string;
4
+ json?: boolean;
5
+ };
6
+ export declare function runShowIssue(opts: ShowIssueOptions): number;
@@ -0,0 +1,156 @@
1
+ import { DateTime } from 'luxon';
2
+ import { FileStore } from '../../repo/common/FileStore.js';
3
+ import { deriveCurrentState } from '../../repo/issue/helpers/deriveCurrentState.js';
4
+ import { IssueRepository } from '../../repo/issue/IssueRepository.js';
5
+ import { resolvePeriods, } from '../../helpers/resolvePeriods.js';
6
+ function formatScope(scope) {
7
+ switch (scope.type) {
8
+ case 'service.whole':
9
+ return 'whole line';
10
+ case 'service.segment':
11
+ return `${scope.fromStationId} → ${scope.toStationId}`;
12
+ case 'service.point':
13
+ return scope.stationId ?? '?';
14
+ default:
15
+ return JSON.stringify(scope);
16
+ }
17
+ }
18
+ function formatEffect(effect) {
19
+ if (!effect)
20
+ return '—';
21
+ if (effect.kind === 'delay') {
22
+ return effect.duration ? `delay (${effect.duration})` : 'delay';
23
+ }
24
+ return effect.kind;
25
+ }
26
+ function formatPeriod(period) {
27
+ const start = period.startAt;
28
+ const end = period.endAtResolved ?? period.endAt ?? 'ongoing';
29
+ let suffix = '';
30
+ if (period.endAtSource === 'inferred' && period.endAtReason) {
31
+ suffix = ` (inferred: ${period.endAtReason})`;
32
+ }
33
+ return `${start} → ${end}${suffix}`;
34
+ }
35
+ const MODES = ['canonical', 'operational'];
36
+ function buildResolveMode(mode, evidence) {
37
+ switch (mode) {
38
+ case 'canonical':
39
+ return { kind: 'canonical' };
40
+ case 'operational': {
41
+ const lastEvidenceAt = evidence.length > 0
42
+ ? evidence.reduce((latest, e) => (e.ts > latest ? e.ts : latest), evidence[0].ts)
43
+ : null;
44
+ return { kind: 'operational', lastEvidenceAt };
45
+ }
46
+ }
47
+ }
48
+ function resolvePeriodsByMode(periods, asOf, evidence) {
49
+ const result = {};
50
+ for (const mode of MODES) {
51
+ result[mode] =
52
+ periods.length > 0
53
+ ? resolvePeriods({
54
+ periods,
55
+ asOf,
56
+ mode: buildResolveMode(mode, evidence),
57
+ })
58
+ : [];
59
+ }
60
+ return result;
61
+ }
62
+ export function runShowIssue(opts) {
63
+ const store = new FileStore(opts.dataDir);
64
+ const repo = new IssueRepository(store);
65
+ const bundle = repo.get(opts.issueId);
66
+ if (!bundle) {
67
+ console.error(`Issue not found: ${opts.issueId}`);
68
+ return 1;
69
+ }
70
+ const state = deriveCurrentState(bundle);
71
+ const asOf = DateTime.now().toISO() ?? '';
72
+ const resolvedServices = Object.fromEntries(Object.entries(state.services).map(([key, svc]) => [
73
+ key,
74
+ {
75
+ ...svc,
76
+ periodsByMode: resolvePeriodsByMode(svc.periods, asOf, bundle.evidence),
77
+ },
78
+ ]));
79
+ const resolvedFacilities = Object.fromEntries(Object.entries(state.facilities).map(([key, fac]) => [
80
+ key,
81
+ {
82
+ ...fac,
83
+ periodsByMode: resolvePeriodsByMode(fac.periods, asOf, bundle.evidence),
84
+ },
85
+ ]));
86
+ if (opts.json) {
87
+ const output = {
88
+ issue: bundle.issue,
89
+ evidenceCount: bundle.evidence.length,
90
+ impactEventCount: bundle.impactEvents.length,
91
+ currentState: {
92
+ ...state,
93
+ services: resolvedServices,
94
+ facilities: resolvedFacilities,
95
+ },
96
+ };
97
+ console.log(JSON.stringify(output, null, 2));
98
+ return 0;
99
+ }
100
+ // Human-readable output
101
+ const { issue, evidence, impactEvents } = bundle;
102
+ console.log(`\n${issue.title['en-SG'] ?? issue.id}`);
103
+ console.log('─'.repeat(60));
104
+ console.log(`ID: ${issue.id}`);
105
+ console.log(`Type: ${issue.type}`);
106
+ console.log(`Path: ${bundle.path}`);
107
+ console.log(`Evidence: ${evidence.length} item(s)`);
108
+ console.log(`Impact: ${impactEvents.length} event(s)`);
109
+ console.log('');
110
+ const hasServices = Object.keys(resolvedServices).length > 0;
111
+ const hasFacilities = Object.keys(resolvedFacilities).length > 0;
112
+ if (hasServices) {
113
+ console.log('Current state — Services');
114
+ console.log('─'.repeat(40));
115
+ for (const [, svc] of Object.entries(resolvedServices)) {
116
+ console.log(` ${svc.serviceId}`);
117
+ console.log(` effect: ${formatEffect(svc.effect)}`);
118
+ if (svc.scopes.length > 0) {
119
+ console.log(` scopes: ${svc.scopes.map(formatScope).join('; ')}`);
120
+ }
121
+ for (const mode of MODES) {
122
+ const periods = svc.periodsByMode[mode];
123
+ if (periods.length > 0) {
124
+ console.log(` periods (${mode}): ${periods.map(formatPeriod).join('; ')}`);
125
+ }
126
+ }
127
+ if (svc.causes.length > 0) {
128
+ console.log(` causes: ${svc.causes.join(', ')}`);
129
+ }
130
+ console.log('');
131
+ }
132
+ }
133
+ if (hasFacilities) {
134
+ console.log('Current state — Facilities');
135
+ console.log('─'.repeat(40));
136
+ for (const [, fac] of Object.entries(resolvedFacilities)) {
137
+ console.log(` ${fac.stationId} (${fac.kind})`);
138
+ console.log(` effect: ${formatEffect(fac.effect)}`);
139
+ for (const mode of MODES) {
140
+ const periods = fac.periodsByMode[mode];
141
+ if (periods.length > 0) {
142
+ console.log(` periods (${mode}): ${periods.map(formatPeriod).join('; ')}`);
143
+ }
144
+ }
145
+ if (fac.causes.length > 0) {
146
+ console.log(` causes: ${fac.causes.join(', ')}`);
147
+ }
148
+ console.log('');
149
+ }
150
+ }
151
+ if (!hasServices && !hasFacilities) {
152
+ console.log('Current state: (no services or facilities affected)');
153
+ }
154
+ return 0;
155
+ }
156
+ //# sourceMappingURL=show.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"show.js","sourceRoot":"/","sources":["cli/commands/show.ts"],"names":[],"mappings":"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","sourcesContent":["import { DateTime } from 'luxon';\nimport { FileStore } from '../../repo/common/FileStore.js';\nimport { deriveCurrentState } from '../../repo/issue/helpers/deriveCurrentState.js';\nimport { IssueRepository } from '../../repo/issue/IssueRepository.js';\nimport type { Period } from '../../schema/issue/period.js';\nimport {\n type ResolvePeriodsMode,\n resolvePeriods,\n} from '../../helpers/resolvePeriods.js';\n\nexport type ShowIssueOptions = {\n dataDir: string;\n issueId: string;\n json?: boolean;\n};\n\nfunction formatScope(scope: {\n type: string;\n fromStationId?: string;\n toStationId?: string;\n stationId?: string;\n}): string {\n switch (scope.type) {\n case 'service.whole':\n return 'whole line';\n case 'service.segment':\n return `${scope.fromStationId} → ${scope.toStationId}`;\n case 'service.point':\n return scope.stationId ?? '?';\n default:\n return JSON.stringify(scope);\n }\n}\n\nfunction formatEffect(\n effect: { kind: string; duration?: string | null } | null,\n): string {\n if (!effect) return '—';\n if (effect.kind === 'delay') {\n return effect.duration ? `delay (${effect.duration})` : 'delay';\n }\n return effect.kind;\n}\n\ntype ResolvedPeriod = {\n startAt: string;\n endAt: string | null;\n endAtResolved: string | null;\n endAtSource: 'fact' | 'inferred' | 'none';\n endAtReason?: 'crowd_decay' | 'evidence_timeout';\n};\n\nfunction formatPeriod(period: ResolvedPeriod): string {\n const start = period.startAt;\n const end = period.endAtResolved ?? period.endAt ?? 'ongoing';\n let suffix = '';\n if (period.endAtSource === 'inferred' && period.endAtReason) {\n suffix = ` (inferred: ${period.endAtReason})`;\n }\n return `${start} → ${end}${suffix}`;\n}\n\nconst MODES = ['canonical', 'operational'] as const;\n\nfunction buildResolveMode(\n mode: (typeof MODES)[number],\n evidence: { ts: string }[],\n): ResolvePeriodsMode {\n switch (mode) {\n case 'canonical':\n return { kind: 'canonical' };\n case 'operational': {\n const lastEvidenceAt =\n evidence.length > 0\n ? evidence.reduce<string>(\n (latest, e) => (e.ts > latest ? e.ts : latest),\n evidence[0].ts,\n )\n : null;\n return { kind: 'operational', lastEvidenceAt };\n }\n }\n}\n\nfunction resolvePeriodsByMode(\n periods: Period[],\n asOf: string,\n evidence: { ts: string }[],\n): Record<(typeof MODES)[number], ResolvedPeriod[]> {\n const result = {} as Record<(typeof MODES)[number], ResolvedPeriod[]>;\n for (const mode of MODES) {\n result[mode] =\n periods.length > 0\n ? resolvePeriods({\n periods,\n asOf,\n mode: buildResolveMode(mode, evidence),\n })\n : [];\n }\n return result;\n}\n\nexport function runShowIssue(opts: ShowIssueOptions): number {\n const store = new FileStore(opts.dataDir);\n const repo = new IssueRepository(store);\n\n const bundle = repo.get(opts.issueId);\n if (!bundle) {\n console.error(`Issue not found: ${opts.issueId}`);\n return 1;\n }\n\n const state = deriveCurrentState(bundle);\n const asOf = DateTime.now().toISO() ?? '';\n\n const resolvedServices = Object.fromEntries(\n Object.entries(state.services).map(([key, svc]) => [\n key,\n {\n ...svc,\n periodsByMode: resolvePeriodsByMode(svc.periods, asOf, bundle.evidence),\n },\n ]),\n );\n\n const resolvedFacilities = Object.fromEntries(\n Object.entries(state.facilities).map(([key, fac]) => [\n key,\n {\n ...fac,\n periodsByMode: resolvePeriodsByMode(fac.periods, asOf, bundle.evidence),\n },\n ]),\n );\n\n if (opts.json) {\n const output = {\n issue: bundle.issue,\n evidenceCount: bundle.evidence.length,\n impactEventCount: bundle.impactEvents.length,\n currentState: {\n ...state,\n services: resolvedServices,\n facilities: resolvedFacilities,\n },\n };\n console.log(JSON.stringify(output, null, 2));\n return 0;\n }\n\n // Human-readable output\n const { issue, evidence, impactEvents } = bundle;\n console.log(`\\n${issue.title['en-SG'] ?? issue.id}`);\n console.log('─'.repeat(60));\n console.log(`ID: ${issue.id}`);\n console.log(`Type: ${issue.type}`);\n console.log(`Path: ${bundle.path}`);\n console.log(`Evidence: ${evidence.length} item(s)`);\n console.log(`Impact: ${impactEvents.length} event(s)`);\n console.log('');\n\n const hasServices = Object.keys(resolvedServices).length > 0;\n const hasFacilities = Object.keys(resolvedFacilities).length > 0;\n\n if (hasServices) {\n console.log('Current state — Services');\n console.log('─'.repeat(40));\n for (const [, svc] of Object.entries(resolvedServices)) {\n console.log(` ${svc.serviceId}`);\n console.log(` effect: ${formatEffect(svc.effect)}`);\n if (svc.scopes.length > 0) {\n console.log(` scopes: ${svc.scopes.map(formatScope).join('; ')}`);\n }\n for (const mode of MODES) {\n const periods = svc.periodsByMode[mode];\n if (periods.length > 0) {\n console.log(\n ` periods (${mode}): ${periods.map(formatPeriod).join('; ')}`,\n );\n }\n }\n if (svc.causes.length > 0) {\n console.log(` causes: ${svc.causes.join(', ')}`);\n }\n console.log('');\n }\n }\n\n if (hasFacilities) {\n console.log('Current state — Facilities');\n console.log('─'.repeat(40));\n for (const [, fac] of Object.entries(resolvedFacilities)) {\n console.log(` ${fac.stationId} (${fac.kind})`);\n console.log(` effect: ${formatEffect(fac.effect)}`);\n for (const mode of MODES) {\n const periods = fac.periodsByMode[mode];\n if (periods.length > 0) {\n console.log(\n ` periods (${mode}): ${periods.map(formatPeriod).join('; ')}`,\n );\n }\n }\n if (fac.causes.length > 0) {\n console.log(` causes: ${fac.causes.join(', ')}`);\n }\n console.log('');\n }\n }\n\n if (!hasServices && !hasFacilities) {\n console.log('Current state: (no services or facilities affected)');\n }\n\n return 0;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import { type ValidationScope } from '../../validators/index.js';
2
+ export interface ValidateCliOptions {
3
+ dataDir: string;
4
+ scope?: ValidationScope[];
5
+ }
6
+ export declare function runValidate(opts: ValidateCliOptions): number;
@@ -0,0 +1,19 @@
1
+ import { FileStore } from '../../repo/common/FileStore.js';
2
+ import { validateAll } from '../../validators/index.js';
3
+ export function runValidate(opts) {
4
+ const store = new FileStore(opts.dataDir);
5
+ const errors = validateAll(store, { scope: opts.scope });
6
+ if (errors.length === 0) {
7
+ console.log('All data files are valid.');
8
+ return 0;
9
+ }
10
+ console.error(`\nFound ${errors.length} validation error(s):\n`);
11
+ for (const err of errors) {
12
+ const loc = err.line ? `${err.file}:${err.line}` : err.file;
13
+ console.error(` ${loc}`);
14
+ console.error(` ${err.message}`);
15
+ console.error('');
16
+ }
17
+ return 1;
18
+ }
19
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"/","sources":["cli/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAwB,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAO9E,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAEzD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,MAAM,yBAAyB,CAAC,CAAC;IACjE,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC","sourcesContent":["import { FileStore } from '../../repo/common/FileStore.js';\nimport { type ValidationScope, validateAll } from '../../validators/index.js';\n\nexport interface ValidateCliOptions {\n dataDir: string;\n scope?: ValidationScope[];\n}\n\nexport function runValidate(opts: ValidateCliOptions): number {\n const store = new FileStore(opts.dataDir);\n const errors = validateAll(store, { scope: opts.scope });\n\n if (errors.length === 0) {\n console.log('All data files are valid.');\n return 0;\n }\n\n console.error(`\\nFound ${errors.length} validation error(s):\\n`);\n for (const err of errors) {\n const loc = err.line ? `${err.file}:${err.line}` : err.file;\n console.error(` ${loc}`);\n console.error(` ${err.message}`);\n console.error('');\n }\n return 1;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};