@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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"/","sources":["util/ingestContent/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolve } from 'node:path';\nimport { DateTime } from 'luxon';\nimport { ulid } from 'ulid';\nimport { computeImpactFromEvidenceClaims } from '#helpers/computeImpactFromEvidenceClaims.js';\nimport { extractClaimsFromNewEvidence } from '#llm/functions/extractClaimsFromNewEvidence/index.js';\nimport { generateIssueTitleAndSlug } from '#llm/functions/generateIssueTitleAndSlug/index.js';\nimport { translate } from '#llm/functions/translate/index.js';\nimport { triageNewEvidence } from '#llm/functions/triageNewEvidence/index.js';\nimport { FileStore } from '#repo/common/FileStore.js';\nimport { MRTDownRepository } from '#repo/MRTDownRepository.js';\nimport type { IssueBundle } from '#schema/issue/bundle.js';\nimport type { Evidence } from '#schema/issue/evidence.js';\nimport type { Issue } from '#schema/issue/issue.js';\nimport { FileWriteStore } from '#write/common/FileWriteStore.js';\nimport { MRTDownWriter } from '#write/MRTDownWriter.js';\nimport { assert } from '../assert.js';\nimport { getSlugDateTimeFromClaims } from './helpers/getSlugDateTimeFromClaims.js';\nimport type { IngestContent } from './types.js';\n\nconst DATA_DIR = resolve(import.meta.dirname, '../../../data');\n\nconst store = new FileStore(DATA_DIR);\nconst writeStore = new FileWriteStore(DATA_DIR);\nconst repo = new MRTDownRepository({ store });\nconst writer = new MRTDownWriter({ store: writeStore });\n\n/**\n * Ingests content from social media, news, or other sources into the MRTDown issue system.\n *\n * Triages the content to determine if it belongs to an existing issue or a new one, extracts\n * claims, computes impact (affected lines, stations, periods), and persists evidence and impact\n * events. Irrelevant content is ignored.\n *\n * @param content - The content to ingest (Reddit post, news article, or Twitter/Mastodon post).\n * @returns `null` when content is irrelevant or after successful ingestion.\n */\nexport async function ingestContent(content: IngestContent) {\n // --- Normalise input ---\n // HACK: Force `createdAt` to be Asia/Singapore timezone\n const createdAt = DateTime.fromISO(content.createdAt)\n .setZone('Asia/Singapore')\n .toISO();\n assert(createdAt != null, 'Expected valid createdAt');\n\n content.createdAt = createdAt;\n console.log('[ingestContent]', content);\n\n // --- Triage: existing issue, new issue, or irrelevant ---\n const triageResult = await triageNewEvidence({\n newEvidence: {\n ts: content.createdAt,\n text: getText(content),\n },\n repo,\n });\n console.log('[ingestContent.triageNewEvidence]', triageResult);\n\n if (triageResult.result.kind === 'irrelevant-content') {\n console.log('[ingestContent] Nothing to do.');\n return null;\n }\n\n // --- Extract structured claims (lines, stations, periods, effects) ---\n const { claims } = await extractClaimsFromNewEvidence({\n newEvidence: {\n ts: content.createdAt,\n text: getText(content),\n },\n repo,\n });\n console.log('[ingestContent.extractClaimsFromNewEvidence]', claims);\n\n // --- Resolve issue bundle: fetch existing or create new ---\n let issueBundle: IssueBundle;\n\n switch (triageResult.result.kind) {\n case 'part-of-existing-issue': {\n // Load full bundle (issue + evidence + impact) for impact computation\n const { issueId } = triageResult.result;\n const existingBundle = repo.issues.get(issueId);\n assert(existingBundle != null, `Expected issue for id=${issueId}`);\n issueBundle = existingBundle;\n break;\n }\n case 'part-of-new-issue': {\n // Create issue: derive date from claims, generate title/slug, translate, persist\n const slugDateTime = DateTime.fromISO(\n getSlugDateTimeFromClaims(claims) ?? content.createdAt,\n );\n assert(slugDateTime.isValid, `Invalid date: ${content.createdAt}`);\n\n const { title, slug } = await generateIssueTitleAndSlug({\n text: getText(content),\n });\n console.log('[ingestContent.generateSlug]', slug);\n\n const translatedTitles = await translate(title);\n\n const issueId = `${slugDateTime.toFormat('yyyy-MM-dd')}-${slug}`;\n\n const issue: Issue = {\n id: issueId,\n type: triageResult.result.issueType,\n title: translatedTitles,\n titleMeta: {\n source: '@openai/gpt-5-nano',\n },\n };\n writer.issues.create(issue);\n\n issueBundle = {\n issue,\n evidence: [],\n impactEvents: [],\n path: DATA_DIR,\n };\n break;\n }\n }\n\n // --- Build evidence record ---\n const contentDateTime = DateTime.fromISO(content.createdAt);\n assert(contentDateTime.isValid, `Invalid date: ${content.createdAt}`);\n\n const evidence: Evidence = {\n id: `ev_${ulid(contentDateTime.toMillis())}`,\n ts: contentDateTime.toISO({ includeOffset: true }),\n type: getEvidenceType(content),\n text: getText(content),\n sourceUrl: content.url,\n render: {\n text: await translate(getText(content)),\n source: '@openai/gpt-5-nano',\n },\n };\n\n // --- Compute impact events from claims (effects, scopes, periods) ---\n const { newImpactEvents } = computeImpactFromEvidenceClaims({\n issueBundle: {\n ...issueBundle,\n evidence: [...issueBundle.evidence, evidence],\n },\n evidenceId: evidence.id,\n evidenceTs: evidence.ts,\n claims,\n });\n\n // --- Persist to disk ---\n writer.issues.appendEvidence(issueBundle.issue.id, evidence);\n for (const impact of newImpactEvents) {\n writer.issues.appendImpact(issueBundle.issue.id, impact);\n }\n\n return null;\n}\n\n/**\n * Extracts the primary text content from an IngestContent item based on its source type.\n *\n * @param content - The content to extract text from.\n * @returns The text body (selftext for Reddit, summary for news, text for social).\n */\nfunction getText(content: IngestContent) {\n switch (content.source) {\n case 'reddit': {\n return content.selftext;\n }\n case 'news-website': {\n return content.summary;\n }\n case 'twitter':\n case 'mastodon': {\n return content.text;\n }\n }\n}\n\n/**\n * Maps IngestContent source type to the corresponding Evidence type for provenance tracking.\n *\n * @param content - The content to classify.\n * @returns The evidence type: official-statement (Reddit), media.report (news), or public.report (social).\n */\nfunction getEvidenceType(content: IngestContent) {\n switch (content.source) {\n case 'reddit': {\n return 'official-statement';\n }\n case 'news-website': {\n return 'media.report';\n }\n case 'twitter':\n case 'mastodon': {\n return 'public.report';\n }\n }\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["util/ingestContent/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolve } from 'node:path';\nimport { DateTime } from 'luxon';\nimport { ulid } from 'ulid';\nimport { computeImpactFromEvidenceClaims } from '../../helpers/computeImpactFromEvidenceClaims.js';\nimport { extractClaimsFromNewEvidence } from '../../llm/functions/extractClaimsFromNewEvidence/index.js';\nimport { generateIssueTitleAndSlug } from '../../llm/functions/generateIssueTitleAndSlug/index.js';\nimport { translate } from '../../llm/functions/translate/index.js';\nimport { triageNewEvidence } from '../../llm/functions/triageNewEvidence/index.js';\nimport { FileStore } from '../../repo/common/FileStore.js';\nimport { MRTDownRepository } from '../../repo/MRTDownRepository.js';\nimport type { IssueBundle } from '../../schema/issue/bundle.js';\nimport type { Evidence } from '../../schema/issue/evidence.js';\nimport type { Issue } from '../../schema/issue/issue.js';\nimport { FileWriteStore } from '../../write/common/FileWriteStore.js';\nimport { MRTDownWriter } from '../../write/MRTDownWriter.js';\nimport { assert } from '../assert.js';\nimport { getSlugDateTimeFromClaims } from './helpers/getSlugDateTimeFromClaims.js';\nimport type { IngestContent } from './types.js';\n\nconst DATA_DIR = resolve(import.meta.dirname, '../../../data');\n\nconst store = new FileStore(DATA_DIR);\nconst writeStore = new FileWriteStore(DATA_DIR);\nconst repo = new MRTDownRepository({ store });\nconst writer = new MRTDownWriter({ store: writeStore });\n\n/**\n * Ingests content from social media, news, or other sources into the MRTDown issue system.\n *\n * Triages the content to determine if it belongs to an existing issue or a new one, extracts\n * claims, computes impact (affected lines, stations, periods), and persists evidence and impact\n * events. Irrelevant content is ignored.\n *\n * @param content - The content to ingest (Reddit post, news article, or Twitter/Mastodon post).\n * @returns `null` when content is irrelevant or after successful ingestion.\n */\nexport async function ingestContent(content: IngestContent) {\n // --- Normalise input ---\n // HACK: Force `createdAt` to be Asia/Singapore timezone\n const createdAt = DateTime.fromISO(content.createdAt)\n .setZone('Asia/Singapore')\n .toISO();\n assert(createdAt != null, 'Expected valid createdAt');\n\n content.createdAt = createdAt;\n console.log('[ingestContent]', content);\n\n // --- Triage: existing issue, new issue, or irrelevant ---\n const triageResult = await triageNewEvidence({\n newEvidence: {\n ts: content.createdAt,\n text: getText(content),\n },\n repo,\n });\n console.log('[ingestContent.triageNewEvidence]', triageResult);\n\n if (triageResult.result.kind === 'irrelevant-content') {\n console.log('[ingestContent] Nothing to do.');\n return null;\n }\n\n // --- Extract structured claims (lines, stations, periods, effects) ---\n const { claims } = await extractClaimsFromNewEvidence({\n newEvidence: {\n ts: content.createdAt,\n text: getText(content),\n },\n repo,\n });\n console.log('[ingestContent.extractClaimsFromNewEvidence]', claims);\n\n // --- Resolve issue bundle: fetch existing or create new ---\n let issueBundle: IssueBundle;\n\n switch (triageResult.result.kind) {\n case 'part-of-existing-issue': {\n // Load full bundle (issue + evidence + impact) for impact computation\n const { issueId } = triageResult.result;\n const existingBundle = repo.issues.get(issueId);\n assert(existingBundle != null, `Expected issue for id=${issueId}`);\n issueBundle = existingBundle;\n break;\n }\n case 'part-of-new-issue': {\n // Create issue: derive date from claims, generate title/slug, translate, persist\n const slugDateTime = DateTime.fromISO(\n getSlugDateTimeFromClaims(claims) ?? content.createdAt,\n );\n assert(slugDateTime.isValid, `Invalid date: ${content.createdAt}`);\n\n const { title, slug } = await generateIssueTitleAndSlug({\n text: getText(content),\n });\n console.log('[ingestContent.generateSlug]', slug);\n\n const translatedTitles = await translate(title);\n\n const issueId = `${slugDateTime.toFormat('yyyy-MM-dd')}-${slug}`;\n\n const issue: Issue = {\n id: issueId,\n type: triageResult.result.issueType,\n title: translatedTitles,\n titleMeta: {\n source: '@openai/gpt-5-nano',\n },\n };\n writer.issues.create(issue);\n\n issueBundle = {\n issue,\n evidence: [],\n impactEvents: [],\n path: DATA_DIR,\n };\n break;\n }\n }\n\n // --- Build evidence record ---\n const contentDateTime = DateTime.fromISO(content.createdAt);\n assert(contentDateTime.isValid, `Invalid date: ${content.createdAt}`);\n\n const evidence: Evidence = {\n id: `ev_${ulid(contentDateTime.toMillis())}`,\n ts: contentDateTime.toISO({ includeOffset: true }),\n type: getEvidenceType(content),\n text: getText(content),\n sourceUrl: content.url,\n render: {\n text: await translate(getText(content)),\n source: '@openai/gpt-5-nano',\n },\n };\n\n // --- Compute impact events from claims (effects, scopes, periods) ---\n const { newImpactEvents } = computeImpactFromEvidenceClaims({\n issueBundle: {\n ...issueBundle,\n evidence: [...issueBundle.evidence, evidence],\n },\n evidenceId: evidence.id,\n evidenceTs: evidence.ts,\n claims,\n });\n\n // --- Persist to disk ---\n writer.issues.appendEvidence(issueBundle.issue.id, evidence);\n for (const impact of newImpactEvents) {\n writer.issues.appendImpact(issueBundle.issue.id, impact);\n }\n\n return null;\n}\n\n/**\n * Extracts the primary text content from an IngestContent item based on its source type.\n *\n * @param content - The content to extract text from.\n * @returns The text body (selftext for Reddit, summary for news, text for social).\n */\nfunction getText(content: IngestContent) {\n switch (content.source) {\n case 'reddit': {\n return content.selftext;\n }\n case 'news-website': {\n return content.summary;\n }\n case 'twitter':\n case 'mastodon': {\n return content.text;\n }\n }\n}\n\n/**\n * Maps IngestContent source type to the corresponding Evidence type for provenance tracking.\n *\n * @param content - The content to classify.\n * @returns The evidence type: official-statement (Reddit), media.report (news), or public.report (social).\n */\nfunction getEvidenceType(content: IngestContent) {\n switch (content.source) {\n case 'reddit': {\n return 'official-statement';\n }\n case 'news-website': {\n return 'media.report';\n }\n case 'twitter':\n case 'mastodon': {\n return 'public.report';\n }\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { IStore } from '../repo/common/store.js';
2
+ import type { ValidationContext } from './types.js';
3
+ /**
4
+ * Loads all entity IDs from the store into a ValidationContext.
5
+ * Use this to build context once, then pass it to validators for relationship checks.
6
+ */
7
+ export declare function buildContext(store: IStore): ValidationContext;
@@ -0,0 +1,164 @@
1
+ import { join } from 'node:path';
2
+ import { NdJson } from 'json-nd';
3
+ import z from 'zod';
4
+ import { LandmarkSchema } from '../schema/Landmark.js';
5
+ import { LineSchema } from '../schema/Line.js';
6
+ import { OperatorSchema } from '../schema/Operator.js';
7
+ import { ServiceSchema } from '../schema/Service.js';
8
+ import { StationSchema } from '../schema/Station.js';
9
+ import { TownSchema } from '../schema/Town.js';
10
+ import { DIR_ISSUE, DIR_LANDMARK, DIR_LINE, DIR_OPERATOR, DIR_SERVICE, DIR_STATION, DIR_TOWN, } from '../constants.js';
11
+ import { loadJson } from './utils.js';
12
+ /**
13
+ * Loads all entity IDs from the store into a ValidationContext.
14
+ * Use this to build context once, then pass it to validators for relationship checks.
15
+ */
16
+ export function buildContext(store) {
17
+ const ctx = {
18
+ townIds: new Set(),
19
+ landmarkIds: new Set(),
20
+ operatorIds: new Set(),
21
+ lineIds: new Set(),
22
+ serviceIds: new Set(),
23
+ stationIds: new Set(),
24
+ evidenceIdsByIssue: new Map(),
25
+ };
26
+ try {
27
+ for (const file of store.listDir(DIR_TOWN)) {
28
+ if (!file.endsWith('.json'))
29
+ continue;
30
+ const path = join(DIR_TOWN, file);
31
+ const raw = loadJson(store, path);
32
+ const parsed = TownSchema.safeParse(raw);
33
+ if (parsed.success)
34
+ ctx.townIds.add(parsed.data.id);
35
+ else
36
+ console.warn(`[buildContext] Skipping invalid town: ${path}`, parsed.error?.message ?? parsed.error);
37
+ }
38
+ }
39
+ catch (err) {
40
+ console.warn('[buildContext] Failed to load towns:', err instanceof Error ? err.message : err);
41
+ }
42
+ try {
43
+ for (const file of store.listDir(DIR_LANDMARK)) {
44
+ if (!file.endsWith('.json'))
45
+ continue;
46
+ const path = join(DIR_LANDMARK, file);
47
+ const raw = loadJson(store, path);
48
+ const parsed = LandmarkSchema.safeParse(raw);
49
+ if (parsed.success)
50
+ ctx.landmarkIds.add(parsed.data.id);
51
+ else
52
+ console.warn(`[buildContext] Skipping invalid landmark: ${path}`, parsed.error?.message ?? parsed.error);
53
+ }
54
+ }
55
+ catch (err) {
56
+ console.warn('[buildContext] Failed to load landmarks:', err instanceof Error ? err.message : err);
57
+ }
58
+ try {
59
+ for (const file of store.listDir(DIR_OPERATOR)) {
60
+ if (!file.endsWith('.json'))
61
+ continue;
62
+ const path = join(DIR_OPERATOR, file);
63
+ const raw = loadJson(store, path);
64
+ const parsed = OperatorSchema.safeParse(raw);
65
+ if (parsed.success)
66
+ ctx.operatorIds.add(parsed.data.id);
67
+ else
68
+ console.warn(`[buildContext] Skipping invalid operator: ${path}`, parsed.error?.message ?? parsed.error);
69
+ }
70
+ }
71
+ catch (err) {
72
+ console.warn('[buildContext] Failed to load operators:', err instanceof Error ? err.message : err);
73
+ }
74
+ try {
75
+ for (const file of store.listDir(DIR_LINE)) {
76
+ if (!file.endsWith('.json'))
77
+ continue;
78
+ const path = join(DIR_LINE, file);
79
+ const raw = loadJson(store, path);
80
+ const parsed = LineSchema.safeParse(raw);
81
+ if (parsed.success)
82
+ ctx.lineIds.add(parsed.data.id);
83
+ else
84
+ console.warn(`[buildContext] Skipping invalid line: ${path}`, parsed.error?.message ?? parsed.error);
85
+ }
86
+ }
87
+ catch (err) {
88
+ console.warn('[buildContext] Failed to load lines:', err instanceof Error ? err.message : err);
89
+ }
90
+ try {
91
+ for (const file of store.listDir(DIR_STATION)) {
92
+ if (!file.endsWith('.json'))
93
+ continue;
94
+ const path = join(DIR_STATION, file);
95
+ const raw = loadJson(store, path);
96
+ const parsed = StationSchema.safeParse(raw);
97
+ if (parsed.success)
98
+ ctx.stationIds.add(parsed.data.id);
99
+ else
100
+ console.warn(`[buildContext] Skipping invalid station: ${path}`, parsed.error?.message ?? parsed.error);
101
+ }
102
+ }
103
+ catch (err) {
104
+ console.warn('[buildContext] Failed to load stations:', err instanceof Error ? err.message : err);
105
+ }
106
+ try {
107
+ for (const file of store.listDir(DIR_SERVICE)) {
108
+ if (!file.endsWith('.json'))
109
+ continue;
110
+ const path = join(DIR_SERVICE, file);
111
+ const raw = loadJson(store, path);
112
+ const parsed = ServiceSchema.safeParse(raw);
113
+ if (parsed.success)
114
+ ctx.serviceIds.add(parsed.data.id);
115
+ else
116
+ console.warn(`[buildContext] Skipping invalid service: ${path}`, parsed.error?.message ?? parsed.error);
117
+ }
118
+ }
119
+ catch (err) {
120
+ console.warn('[buildContext] Failed to load services:', err instanceof Error ? err.message : err);
121
+ }
122
+ try {
123
+ const years = store.listDir(DIR_ISSUE);
124
+ for (const year of years) {
125
+ if (!/^\d{4}$/.test(year))
126
+ continue;
127
+ const monthsPath = join(DIR_ISSUE, year);
128
+ const months = store.listDir(monthsPath);
129
+ for (const month of months) {
130
+ if (!/^\d{2}$/.test(month))
131
+ continue;
132
+ const issuesPath = join(monthsPath, month);
133
+ const issues = store.listDir(issuesPath);
134
+ for (const issueId of issues) {
135
+ const relBase = join(DIR_ISSUE, year, month, issueId);
136
+ const evidencePath = join(relBase, 'evidence.ndjson');
137
+ const evidenceIds = new Set();
138
+ try {
139
+ const content = store.readText(evidencePath).trim();
140
+ if (content) {
141
+ const parsed = NdJson.parse(content);
142
+ for (const row of parsed) {
143
+ const idParsed = z.object({ id: z.string() }).safeParse(row);
144
+ if (idParsed.success)
145
+ evidenceIds.add(idParsed.data.id);
146
+ else
147
+ console.warn(`[buildContext] Skipping invalid evidence row in ${evidencePath}:`, idParsed.error?.message ?? idParsed.error);
148
+ }
149
+ }
150
+ }
151
+ catch (err) {
152
+ console.warn(`[buildContext] Failed to read evidence: ${evidencePath}`, err instanceof Error ? err.message : err);
153
+ }
154
+ ctx.evidenceIdsByIssue.set(relBase, evidenceIds);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ catch (err) {
160
+ console.warn('[buildContext] Failed to load issues/evidence:', err instanceof Error ? err.message : err);
161
+ }
162
+ return ctx;
163
+ }
164
+ //# sourceMappingURL=buildContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildContext.js","sourceRoot":"/","sources":["validators/buildContext.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from 'node:path';\nimport { NdJson } from 'json-nd';\nimport z from 'zod';\nimport { LandmarkSchema } from '../schema/Landmark.js';\nimport { LineSchema } from '../schema/Line.js';\nimport { OperatorSchema } from '../schema/Operator.js';\nimport { ServiceSchema } from '../schema/Service.js';\nimport { StationSchema } from '../schema/Station.js';\nimport { TownSchema } from '../schema/Town.js';\nimport {\n DIR_ISSUE,\n DIR_LANDMARK,\n DIR_LINE,\n DIR_OPERATOR,\n DIR_SERVICE,\n DIR_STATION,\n DIR_TOWN,\n} from '../constants.js';\nimport type { IStore } from '../repo/common/store.js';\nimport type { ValidationContext } from './types.js';\nimport { loadJson } from './utils.js';\n\n/**\n * Loads all entity IDs from the store into a ValidationContext.\n * Use this to build context once, then pass it to validators for relationship checks.\n */\nexport function buildContext(store: IStore): ValidationContext {\n const ctx: ValidationContext = {\n townIds: new Set(),\n landmarkIds: new Set(),\n operatorIds: new Set(),\n lineIds: new Set(),\n serviceIds: new Set(),\n stationIds: new Set(),\n evidenceIdsByIssue: new Map(),\n };\n\n try {\n for (const file of store.listDir(DIR_TOWN)) {\n if (!file.endsWith('.json')) continue;\n const path = join(DIR_TOWN, file);\n const raw = loadJson<unknown>(store, path);\n const parsed = TownSchema.safeParse(raw);\n if (parsed.success) ctx.townIds.add(parsed.data.id);\n else\n console.warn(\n `[buildContext] Skipping invalid town: ${path}`,\n parsed.error?.message ?? parsed.error,\n );\n }\n } catch (err) {\n console.warn(\n '[buildContext] Failed to load towns:',\n err instanceof Error ? err.message : err,\n );\n }\n\n try {\n for (const file of store.listDir(DIR_LANDMARK)) {\n if (!file.endsWith('.json')) continue;\n const path = join(DIR_LANDMARK, file);\n const raw = loadJson<unknown>(store, path);\n const parsed = LandmarkSchema.safeParse(raw);\n if (parsed.success) ctx.landmarkIds.add(parsed.data.id);\n else\n console.warn(\n `[buildContext] Skipping invalid landmark: ${path}`,\n parsed.error?.message ?? parsed.error,\n );\n }\n } catch (err) {\n console.warn(\n '[buildContext] Failed to load landmarks:',\n err instanceof Error ? err.message : err,\n );\n }\n\n try {\n for (const file of store.listDir(DIR_OPERATOR)) {\n if (!file.endsWith('.json')) continue;\n const path = join(DIR_OPERATOR, file);\n const raw = loadJson<unknown>(store, path);\n const parsed = OperatorSchema.safeParse(raw);\n if (parsed.success) ctx.operatorIds.add(parsed.data.id);\n else\n console.warn(\n `[buildContext] Skipping invalid operator: ${path}`,\n parsed.error?.message ?? parsed.error,\n );\n }\n } catch (err) {\n console.warn(\n '[buildContext] Failed to load operators:',\n err instanceof Error ? err.message : err,\n );\n }\n\n try {\n for (const file of store.listDir(DIR_LINE)) {\n if (!file.endsWith('.json')) continue;\n const path = join(DIR_LINE, file);\n const raw = loadJson<unknown>(store, path);\n const parsed = LineSchema.safeParse(raw);\n if (parsed.success) ctx.lineIds.add(parsed.data.id);\n else\n console.warn(\n `[buildContext] Skipping invalid line: ${path}`,\n parsed.error?.message ?? parsed.error,\n );\n }\n } catch (err) {\n console.warn(\n '[buildContext] Failed to load lines:',\n err instanceof Error ? err.message : err,\n );\n }\n\n try {\n for (const file of store.listDir(DIR_STATION)) {\n if (!file.endsWith('.json')) continue;\n const path = join(DIR_STATION, file);\n const raw = loadJson<unknown>(store, path);\n const parsed = StationSchema.safeParse(raw);\n if (parsed.success) ctx.stationIds.add(parsed.data.id);\n else\n console.warn(\n `[buildContext] Skipping invalid station: ${path}`,\n parsed.error?.message ?? parsed.error,\n );\n }\n } catch (err) {\n console.warn(\n '[buildContext] Failed to load stations:',\n err instanceof Error ? err.message : err,\n );\n }\n\n try {\n for (const file of store.listDir(DIR_SERVICE)) {\n if (!file.endsWith('.json')) continue;\n const path = join(DIR_SERVICE, file);\n const raw = loadJson<unknown>(store, path);\n const parsed = ServiceSchema.safeParse(raw);\n if (parsed.success) ctx.serviceIds.add(parsed.data.id);\n else\n console.warn(\n `[buildContext] Skipping invalid service: ${path}`,\n parsed.error?.message ?? parsed.error,\n );\n }\n } catch (err) {\n console.warn(\n '[buildContext] Failed to load services:',\n err instanceof Error ? err.message : err,\n );\n }\n\n try {\n const years = store.listDir(DIR_ISSUE);\n for (const year of years) {\n if (!/^\\d{4}$/.test(year)) continue;\n const monthsPath = join(DIR_ISSUE, year);\n const months = store.listDir(monthsPath);\n for (const month of months) {\n if (!/^\\d{2}$/.test(month)) continue;\n const issuesPath = join(monthsPath, month);\n const issues = store.listDir(issuesPath);\n for (const issueId of issues) {\n const relBase = join(DIR_ISSUE, year, month, issueId);\n const evidencePath = join(relBase, 'evidence.ndjson');\n const evidenceIds = new Set<string>();\n try {\n const content = store.readText(evidencePath).trim();\n if (content) {\n const parsed = NdJson.parse(content);\n for (const row of parsed) {\n const idParsed = z.object({ id: z.string() }).safeParse(row);\n if (idParsed.success) evidenceIds.add(idParsed.data.id);\n else\n console.warn(\n `[buildContext] Skipping invalid evidence row in ${evidencePath}:`,\n idParsed.error?.message ?? idParsed.error,\n );\n }\n }\n } catch (err) {\n console.warn(\n `[buildContext] Failed to read evidence: ${evidencePath}`,\n err instanceof Error ? err.message : err,\n );\n }\n ctx.evidenceIdsByIssue.set(relBase, evidenceIds);\n }\n }\n }\n } catch (err) {\n console.warn(\n '[buildContext] Failed to load issues/evidence:',\n err instanceof Error ? err.message : err,\n );\n }\n\n return ctx;\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { IStore } from '../repo/common/store.js';
2
+ import type { ValidationError } from './types.js';
3
+ export type ValidationScope = 'town' | 'landmark' | 'operator' | 'station' | 'line' | 'service' | 'issue';
4
+ export interface ValidateOptions {
5
+ /** When set, only run validators for these entity types. */
6
+ scope?: ValidationScope[];
7
+ }
8
+ export declare function validateAll(store: IStore, options?: ValidateOptions): ValidationError[];
9
+ export { buildContext } from './buildContext.js';
10
+ export { validateIssue, validateIssues } from './issue.js';
11
+ export { validateLandmarks } from './landmark.js';
12
+ export { validateLines, validateLinesRelationships, } from './line.js';
13
+ export { validateOperators } from './operator.js';
14
+ export { validateServices, validateServicesRelationships, } from './service.js';
15
+ export { validateStations, validateStationsRelationships, } from './station.js';
16
+ export { validateTowns } from './town.js';
17
+ export type { ValidationContext, ValidationError } from './types.js';
@@ -0,0 +1,58 @@
1
+ import { buildContext } from './buildContext.js';
2
+ import { validateIssues } from './issue.js';
3
+ import { validateLandmarks } from './landmark.js';
4
+ import { validateLines, validateLinesRelationships } from './line.js';
5
+ import { validateOperators } from './operator.js';
6
+ import { validateServices, validateServicesRelationships } from './service.js';
7
+ import { validateStations, validateStationsRelationships } from './station.js';
8
+ import { validateTowns } from './town.js';
9
+ const SCOPE_NEEDS_CONTEXT = [
10
+ 'station',
11
+ 'line',
12
+ 'service',
13
+ 'issue',
14
+ ];
15
+ function inScope(scope, type) {
16
+ return !scope || scope.has(type);
17
+ }
18
+ export function validateAll(store, options) {
19
+ const scope = options?.scope;
20
+ const scopeSet = scope && scope.length > 0 ? new Set(scope) : null;
21
+ const needsContext = !scopeSet || SCOPE_NEEDS_CONTEXT.some((t) => scopeSet.has(t));
22
+ const ctx = needsContext ? buildContext(store) : null;
23
+ const allErrors = [];
24
+ if (inScope(scopeSet, 'town'))
25
+ allErrors.push(...validateTowns(store));
26
+ if (inScope(scopeSet, 'landmark'))
27
+ allErrors.push(...validateLandmarks(store));
28
+ if (inScope(scopeSet, 'operator'))
29
+ allErrors.push(...validateOperators(store));
30
+ if (inScope(scopeSet, 'line')) {
31
+ allErrors.push(...validateLines(store));
32
+ if (ctx)
33
+ allErrors.push(...validateLinesRelationships(store, ctx));
34
+ }
35
+ if (inScope(scopeSet, 'station')) {
36
+ allErrors.push(...validateStations(store));
37
+ if (ctx)
38
+ allErrors.push(...validateStationsRelationships(store, ctx));
39
+ }
40
+ if (inScope(scopeSet, 'service')) {
41
+ allErrors.push(...validateServices(store));
42
+ if (ctx)
43
+ allErrors.push(...validateServicesRelationships(store, ctx));
44
+ }
45
+ if (inScope(scopeSet, 'issue') && ctx) {
46
+ allErrors.push(...validateIssues(store, ctx));
47
+ }
48
+ return allErrors;
49
+ }
50
+ export { buildContext } from './buildContext.js';
51
+ export { validateIssue, validateIssues } from './issue.js';
52
+ export { validateLandmarks } from './landmark.js';
53
+ export { validateLines, validateLinesRelationships, } from './line.js';
54
+ export { validateOperators } from './operator.js';
55
+ export { validateServices, validateServicesRelationships, } from './service.js';
56
+ export { validateStations, validateStationsRelationships, } from './station.js';
57
+ export { validateTowns } from './town.js';
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["validators/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IStore } from '../repo/common/store.js';\nimport { buildContext } from './buildContext.js';\nimport { validateIssues } from './issue.js';\nimport { validateLandmarks } from './landmark.js';\nimport { validateLines, validateLinesRelationships } from './line.js';\nimport { validateOperators } from './operator.js';\nimport { validateServices, validateServicesRelationships } from './service.js';\nimport { validateStations, validateStationsRelationships } from './station.js';\nimport { validateTowns } from './town.js';\nimport type { ValidationError } from './types.js';\n\nexport type ValidationScope =\n | 'town'\n | 'landmark'\n | 'operator'\n | 'station'\n | 'line'\n | 'service'\n | 'issue';\n\nexport interface ValidateOptions {\n /** When set, only run validators for these entity types. */\n scope?: ValidationScope[];\n}\n\nconst SCOPE_NEEDS_CONTEXT: ValidationScope[] = [\n 'station',\n 'line',\n 'service',\n 'issue',\n];\n\nfunction inScope(\n scope: Set<ValidationScope> | null,\n type: ValidationScope,\n): boolean {\n return !scope || scope.has(type);\n}\n\nexport function validateAll(\n store: IStore,\n options?: ValidateOptions,\n): ValidationError[] {\n const scope = options?.scope;\n const scopeSet = scope && scope.length > 0 ? new Set(scope) : null;\n const needsContext =\n !scopeSet || SCOPE_NEEDS_CONTEXT.some((t) => scopeSet.has(t));\n const ctx = needsContext ? buildContext(store) : null;\n\n const allErrors: ValidationError[] = [];\n\n if (inScope(scopeSet, 'town')) allErrors.push(...validateTowns(store));\n if (inScope(scopeSet, 'landmark'))\n allErrors.push(...validateLandmarks(store));\n if (inScope(scopeSet, 'operator'))\n allErrors.push(...validateOperators(store));\n if (inScope(scopeSet, 'line')) {\n allErrors.push(...validateLines(store));\n if (ctx) allErrors.push(...validateLinesRelationships(store, ctx));\n }\n if (inScope(scopeSet, 'station')) {\n allErrors.push(...validateStations(store));\n if (ctx) allErrors.push(...validateStationsRelationships(store, ctx));\n }\n if (inScope(scopeSet, 'service')) {\n allErrors.push(...validateServices(store));\n if (ctx) allErrors.push(...validateServicesRelationships(store, ctx));\n }\n if (inScope(scopeSet, 'issue') && ctx) {\n allErrors.push(...validateIssues(store, ctx));\n }\n\n return allErrors;\n}\n\nexport { buildContext } from './buildContext.js';\nexport { validateIssue, validateIssues } from './issue.js';\nexport { validateLandmarks } from './landmark.js';\nexport {\n validateLines,\n validateLinesRelationships,\n} from './line.js';\nexport { validateOperators } from './operator.js';\nexport {\n validateServices,\n validateServicesRelationships,\n} from './service.js';\nexport {\n validateStations,\n validateStationsRelationships,\n} from './station.js';\nexport { validateTowns } from './town.js';\nexport type { ValidationContext, ValidationError } from './types.js';\n"]}
@@ -0,0 +1,13 @@
1
+ import type { ImpactEvent } from '../schema/issue/impactEvent.js';
2
+ import type { IStore } from '../repo/common/store.js';
3
+ import type { ValidationContext, ValidationError } from './types.js';
4
+ export declare function validateIssueSchema(data: unknown): ValidationError[];
5
+ export declare function validateEvidenceSchema(data: unknown): ValidationError[];
6
+ export declare function validateImpactEventSchema(data: unknown): ValidationError[];
7
+ export declare function validateImpactEventRelationships(event: ImpactEvent, evidenceIds: Set<string>, file: string, lineNum: number, ctx?: ValidationContext): ValidationError[];
8
+ /**
9
+ * Validates a single issue at the given path (e.g. "issue/2025/03/2025-03-11-x").
10
+ * Pass ctx for relationship validation (serviceIds, stationIds, evidenceIds).
11
+ */
12
+ export declare function validateIssue(store: IStore, relBase: string, ctx?: ValidationContext): ValidationError[];
13
+ export declare function validateIssues(store: IStore, ctx?: ValidationContext): ValidationError[];
@@ -0,0 +1,220 @@
1
+ import { join } from 'node:path';
2
+ import { NdJson } from 'json-nd';
3
+ import z from 'zod';
4
+ import { EvidenceSchema } from '../schema/issue/evidence.js';
5
+ import { ImpactEventSchema } from '../schema/issue/impactEvent.js';
6
+ import { IssueSchema } from '../schema/issue/issue.js';
7
+ import { DIR_ISSUE } from '../constants.js';
8
+ import { loadJson } from './utils.js';
9
+ export function validateIssueSchema(data) {
10
+ const result = IssueSchema.safeParse(data);
11
+ if (result.success)
12
+ return [];
13
+ return result.error.issues.map((i) => ({
14
+ file: '',
15
+ message: `${i.path.length > 0 ? i.path.join('.') : 'root'}: ${i.message}`,
16
+ }));
17
+ }
18
+ export function validateEvidenceSchema(data) {
19
+ const result = EvidenceSchema.safeParse(data);
20
+ if (result.success)
21
+ return [];
22
+ return result.error.issues.map((i) => ({
23
+ file: '',
24
+ message: `${i.path.length > 0 ? i.path.join('.') : 'root'}: ${i.message}`,
25
+ }));
26
+ }
27
+ export function validateImpactEventSchema(data) {
28
+ const result = ImpactEventSchema.safeParse(data);
29
+ if (result.success)
30
+ return [];
31
+ return result.error.issues.map((i) => ({
32
+ file: '',
33
+ message: `${i.path.length > 0 ? i.path.join('.') : 'root'}: ${i.message}`,
34
+ }));
35
+ }
36
+ export function validateImpactEventRelationships(event, evidenceIds, file, lineNum, ctx) {
37
+ const errors = [];
38
+ if (!evidenceIds.has(event.basis.evidenceId)) {
39
+ errors.push({
40
+ file,
41
+ line: lineNum,
42
+ message: `basis.evidenceId "${event.basis.evidenceId}" does not exist in evidence`,
43
+ });
44
+ }
45
+ if (!ctx)
46
+ return errors;
47
+ if (event.entity.type === 'service') {
48
+ if (!ctx.serviceIds.has(event.entity.serviceId)) {
49
+ errors.push({
50
+ file,
51
+ line: lineNum,
52
+ message: `entity.serviceId "${event.entity.serviceId}" does not exist`,
53
+ });
54
+ }
55
+ }
56
+ else {
57
+ if (!ctx.stationIds.has(event.entity.stationId)) {
58
+ errors.push({
59
+ file,
60
+ line: lineNum,
61
+ message: `entity.stationId "${event.entity.stationId}" does not exist`,
62
+ });
63
+ }
64
+ }
65
+ if ('serviceScopes' in event && event.serviceScopes) {
66
+ for (const scope of event.serviceScopes) {
67
+ if (scope.type === 'service.segment') {
68
+ if (!ctx.stationIds.has(scope.fromStationId)) {
69
+ errors.push({
70
+ file,
71
+ line: lineNum,
72
+ message: `serviceScopes[].fromStationId "${scope.fromStationId}" does not exist`,
73
+ });
74
+ }
75
+ if (!ctx.stationIds.has(scope.toStationId)) {
76
+ errors.push({
77
+ file,
78
+ line: lineNum,
79
+ message: `serviceScopes[].toStationId "${scope.toStationId}" does not exist`,
80
+ });
81
+ }
82
+ }
83
+ else if (scope.type === 'service.point') {
84
+ if (!ctx.stationIds.has(scope.stationId)) {
85
+ errors.push({
86
+ file,
87
+ line: lineNum,
88
+ message: `serviceScopes[].stationId "${scope.stationId}" does not exist`,
89
+ });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ return errors;
95
+ }
96
+ function validateIssueAtPath(store, relBase, ctx) {
97
+ const errors = [];
98
+ const issueJsonPath = join(relBase, 'issue.json');
99
+ if (!store.exists(issueJsonPath)) {
100
+ errors.push({
101
+ file: issueJsonPath,
102
+ message: 'Issue not found',
103
+ });
104
+ return errors;
105
+ }
106
+ const issueRaw = loadJson(store, issueJsonPath);
107
+ if (issueRaw) {
108
+ const schemaErrs = validateIssueSchema(issueRaw);
109
+ for (const e of schemaErrs) {
110
+ errors.push({ ...e, file: e.file || issueJsonPath });
111
+ }
112
+ }
113
+ else {
114
+ errors.push({
115
+ file: issueJsonPath,
116
+ message: 'Failed to parse JSON',
117
+ });
118
+ }
119
+ const evidenceIdsFromCtx = ctx?.evidenceIdsByIssue.get(relBase);
120
+ const evidenceIds = evidenceIdsFromCtx !== undefined ? evidenceIdsFromCtx : new Set();
121
+ const needToBuildEvidenceIds = evidenceIdsFromCtx === undefined;
122
+ const evidencePath = join(relBase, 'evidence.ndjson');
123
+ try {
124
+ const content = store.readText(evidencePath).trim();
125
+ if (content) {
126
+ const parsed = NdJson.parse(content);
127
+ for (let i = 0; i < parsed.length; i++) {
128
+ const row = parsed[i];
129
+ const schemaErrs = validateEvidenceSchema(row);
130
+ for (const e of schemaErrs) {
131
+ errors.push({
132
+ ...e,
133
+ file: evidencePath,
134
+ line: i + 1,
135
+ });
136
+ }
137
+ if (needToBuildEvidenceIds) {
138
+ const idParsed = z.object({ id: z.string() }).safeParse(row);
139
+ if (idParsed.success)
140
+ evidenceIds.add(idParsed.data.id);
141
+ }
142
+ }
143
+ }
144
+ }
145
+ catch {
146
+ // ignore missing
147
+ }
148
+ const impactPath = join(relBase, 'impact.ndjson');
149
+ try {
150
+ const content = store.readText(impactPath).trim();
151
+ if (content) {
152
+ const parsed = NdJson.parse(content);
153
+ for (let i = 0; i < parsed.length; i++) {
154
+ const row = parsed[i];
155
+ const schemaErrs = validateImpactEventSchema(row);
156
+ for (const e of schemaErrs) {
157
+ errors.push({
158
+ ...e,
159
+ file: impactPath,
160
+ line: i + 1,
161
+ });
162
+ }
163
+ const eventResult = z
164
+ .object({
165
+ entity: z.any(),
166
+ basis: z.any(),
167
+ serviceScopes: z.any().optional(),
168
+ })
169
+ .safeParse(row);
170
+ if (eventResult.success) {
171
+ const relErrs = validateImpactEventRelationships(row, evidenceIds, impactPath, i + 1, ctx);
172
+ errors.push(...relErrs);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ catch {
178
+ // ignore missing
179
+ }
180
+ return errors;
181
+ }
182
+ /**
183
+ * Validates a single issue at the given path (e.g. "issue/2025/03/2025-03-11-x").
184
+ * Pass ctx for relationship validation (serviceIds, stationIds, evidenceIds).
185
+ */
186
+ export function validateIssue(store, relBase, ctx) {
187
+ return validateIssueAtPath(store, relBase, ctx);
188
+ }
189
+ export function validateIssues(store, ctx) {
190
+ const errors = [];
191
+ try {
192
+ const years = store.listDir(DIR_ISSUE);
193
+ for (const year of years) {
194
+ if (!/^\d{4}$/.test(year))
195
+ continue;
196
+ const monthsPath = join(DIR_ISSUE, year);
197
+ const months = store.listDir(monthsPath);
198
+ for (const month of months) {
199
+ if (!/^\d{2}$/.test(month))
200
+ continue;
201
+ const issuesPath = join(monthsPath, month);
202
+ const issues = store.listDir(issuesPath);
203
+ for (const issueId of issues) {
204
+ const relBase = join(DIR_ISSUE, year, month, issueId);
205
+ errors.push(...validateIssueAtPath(store, relBase, ctx));
206
+ }
207
+ }
208
+ }
209
+ }
210
+ catch (err) {
211
+ if (err.code !== 'ENOENT') {
212
+ errors.push({
213
+ file: DIR_ISSUE,
214
+ message: err instanceof Error ? err.message : String(err),
215
+ });
216
+ }
217
+ }
218
+ return errors;
219
+ }
220
+ //# sourceMappingURL=issue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue.js","sourceRoot":"/","sources":["validators/issue.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from 'node:path';\nimport { NdJson } from 'json-nd';\nimport z from 'zod';\nimport { EvidenceSchema } from '../schema/issue/evidence.js';\nimport type { ImpactEvent } from '../schema/issue/impactEvent.js';\nimport { ImpactEventSchema } from '../schema/issue/impactEvent.js';\nimport { IssueSchema } from '../schema/issue/issue.js';\nimport { DIR_ISSUE } from '../constants.js';\nimport type { IStore } from '../repo/common/store.js';\nimport type { ValidationContext, ValidationError } from './types.js';\nimport { loadJson } from './utils.js';\n\nexport function validateIssueSchema(data: unknown): ValidationError[] {\n const result = IssueSchema.safeParse(data);\n if (result.success) return [];\n return result.error.issues.map((i) => ({\n file: '',\n message: `${i.path.length > 0 ? i.path.join('.') : 'root'}: ${i.message}`,\n }));\n}\n\nexport function validateEvidenceSchema(data: unknown): ValidationError[] {\n const result = EvidenceSchema.safeParse(data);\n if (result.success) return [];\n return result.error.issues.map((i) => ({\n file: '',\n message: `${i.path.length > 0 ? i.path.join('.') : 'root'}: ${i.message}`,\n }));\n}\n\nexport function validateImpactEventSchema(data: unknown): ValidationError[] {\n const result = ImpactEventSchema.safeParse(data);\n if (result.success) return [];\n return result.error.issues.map((i) => ({\n file: '',\n message: `${i.path.length > 0 ? i.path.join('.') : 'root'}: ${i.message}`,\n }));\n}\n\nexport function validateImpactEventRelationships(\n event: ImpactEvent,\n evidenceIds: Set<string>,\n file: string,\n lineNum: number,\n ctx?: ValidationContext,\n): ValidationError[] {\n const errors: ValidationError[] = [];\n\n if (!evidenceIds.has(event.basis.evidenceId)) {\n errors.push({\n file,\n line: lineNum,\n message: `basis.evidenceId \"${event.basis.evidenceId}\" does not exist in evidence`,\n });\n }\n\n if (!ctx) return errors;\n\n if (event.entity.type === 'service') {\n if (!ctx.serviceIds.has(event.entity.serviceId)) {\n errors.push({\n file,\n line: lineNum,\n message: `entity.serviceId \"${event.entity.serviceId}\" does not exist`,\n });\n }\n } else {\n if (!ctx.stationIds.has(event.entity.stationId)) {\n errors.push({\n file,\n line: lineNum,\n message: `entity.stationId \"${event.entity.stationId}\" does not exist`,\n });\n }\n }\n\n if ('serviceScopes' in event && event.serviceScopes) {\n for (const scope of event.serviceScopes) {\n if (scope.type === 'service.segment') {\n if (!ctx.stationIds.has(scope.fromStationId)) {\n errors.push({\n file,\n line: lineNum,\n message: `serviceScopes[].fromStationId \"${scope.fromStationId}\" does not exist`,\n });\n }\n if (!ctx.stationIds.has(scope.toStationId)) {\n errors.push({\n file,\n line: lineNum,\n message: `serviceScopes[].toStationId \"${scope.toStationId}\" does not exist`,\n });\n }\n } else if (scope.type === 'service.point') {\n if (!ctx.stationIds.has(scope.stationId)) {\n errors.push({\n file,\n line: lineNum,\n message: `serviceScopes[].stationId \"${scope.stationId}\" does not exist`,\n });\n }\n }\n }\n }\n\n return errors;\n}\n\nfunction validateIssueAtPath(\n store: IStore,\n relBase: string,\n ctx?: ValidationContext,\n): ValidationError[] {\n const errors: ValidationError[] = [];\n const issueJsonPath = join(relBase, 'issue.json');\n\n if (!store.exists(issueJsonPath)) {\n errors.push({\n file: issueJsonPath,\n message: 'Issue not found',\n });\n return errors;\n }\n\n const issueRaw = loadJson<unknown>(store, issueJsonPath);\n if (issueRaw) {\n const schemaErrs = validateIssueSchema(issueRaw);\n for (const e of schemaErrs) {\n errors.push({ ...e, file: e.file || issueJsonPath });\n }\n } else {\n errors.push({\n file: issueJsonPath,\n message: 'Failed to parse JSON',\n });\n }\n\n const evidenceIdsFromCtx = ctx?.evidenceIdsByIssue.get(relBase);\n const evidenceIds =\n evidenceIdsFromCtx !== undefined ? evidenceIdsFromCtx : new Set<string>();\n const needToBuildEvidenceIds = evidenceIdsFromCtx === undefined;\n\n const evidencePath = join(relBase, 'evidence.ndjson');\n try {\n const content = store.readText(evidencePath).trim();\n if (content) {\n const parsed = NdJson.parse(content);\n for (let i = 0; i < parsed.length; i++) {\n const row = parsed[i];\n const schemaErrs = validateEvidenceSchema(row);\n for (const e of schemaErrs) {\n errors.push({\n ...e,\n file: evidencePath,\n line: i + 1,\n });\n }\n if (needToBuildEvidenceIds) {\n const idParsed = z.object({ id: z.string() }).safeParse(row);\n if (idParsed.success) evidenceIds.add(idParsed.data.id);\n }\n }\n }\n } catch {\n // ignore missing\n }\n\n const impactPath = join(relBase, 'impact.ndjson');\n try {\n const content = store.readText(impactPath).trim();\n if (content) {\n const parsed = NdJson.parse(content);\n for (let i = 0; i < parsed.length; i++) {\n const row = parsed[i];\n const schemaErrs = validateImpactEventSchema(row);\n for (const e of schemaErrs) {\n errors.push({\n ...e,\n file: impactPath,\n line: i + 1,\n });\n }\n const eventResult = z\n .object({\n entity: z.any(),\n basis: z.any(),\n serviceScopes: z.any().optional(),\n })\n .safeParse(row);\n if (eventResult.success) {\n const relErrs = validateImpactEventRelationships(\n row as ImpactEvent,\n evidenceIds,\n impactPath,\n i + 1,\n ctx,\n );\n errors.push(...relErrs);\n }\n }\n }\n } catch {\n // ignore missing\n }\n\n return errors;\n}\n\n/**\n * Validates a single issue at the given path (e.g. \"issue/2025/03/2025-03-11-x\").\n * Pass ctx for relationship validation (serviceIds, stationIds, evidenceIds).\n */\nexport function validateIssue(\n store: IStore,\n relBase: string,\n ctx?: ValidationContext,\n): ValidationError[] {\n return validateIssueAtPath(store, relBase, ctx);\n}\n\nexport function validateIssues(\n store: IStore,\n ctx?: ValidationContext,\n): ValidationError[] {\n const errors: ValidationError[] = [];\n try {\n const years = store.listDir(DIR_ISSUE);\n for (const year of years) {\n if (!/^\\d{4}$/.test(year)) continue;\n const monthsPath = join(DIR_ISSUE, year);\n const months = store.listDir(monthsPath);\n for (const month of months) {\n if (!/^\\d{2}$/.test(month)) continue;\n const issuesPath = join(monthsPath, month);\n const issues = store.listDir(issuesPath);\n for (const issueId of issues) {\n const relBase = join(DIR_ISSUE, year, month, issueId);\n errors.push(...validateIssueAtPath(store, relBase, ctx));\n }\n }\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n errors.push({\n file: DIR_ISSUE,\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n return errors;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { IStore } from '../repo/common/store.js';
2
+ import type { ValidationError } from './types.js';
3
+ export declare function validateLandmarkSchema(data: unknown): {
4
+ file: string;
5
+ message: string;
6
+ }[];
7
+ export declare function validateLandmarks(store: IStore): ValidationError[];