@phuc1403/musketeer 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/INSTALLATION.md +52 -52
  2. package/README.md +49 -49
  3. package/bin/musketeer.js +168 -168
  4. package/manifest.json +333 -301
  5. package/package.json +48 -48
  6. package/src/dotnet-scaffold-copier.js +79 -79
  7. package/src/provisioner/detect.js +93 -93
  8. package/src/self-update.js +77 -77
  9. package/template/.claude/agents/code-reviewer.md +182 -166
  10. package/template/.claude/agents/git-manager.md +18 -18
  11. package/template/.claude/agents/hallmark-auditor.md +78 -78
  12. package/template/.claude/agents/researcher.md +33 -33
  13. package/template/.claude/hooks/block-unsafe-adr-title.cjs +85 -85
  14. package/template/.claude/hooks/git-skill-reminder.cjs +53 -0
  15. package/template/.claude/hooks/init-adr-dir.cjs +173 -173
  16. package/template/.claude/hooks/inject-adr-flags.cjs +94 -94
  17. package/template/.claude/hooks/lib/adr/command-scan.cjs +115 -115
  18. package/template/.claude/hooks/lib/characteristics/checker.cjs +357 -357
  19. package/template/.claude/hooks/lib/colors.cjs +180 -122
  20. package/template/.claude/hooks/lib/git-info-cache.cjs +191 -191
  21. package/template/.claude/hooks/lib/transcript-parser.cjs +300 -277
  22. package/template/.claude/hooks/sync-adr-toc.cjs +146 -146
  23. package/template/.claude/hooks/{usage-context-awareness.cjs → usage-quota-cache-refresh.cjs} +166 -166
  24. package/template/.claude/hooks/validate-characteristics-hook.cjs +66 -66
  25. package/template/.claude/hooks/validate-cml-hook.js +145 -145
  26. package/template/.claude/skills/adr-writer/SKILL.md +48 -48
  27. package/template/.claude/skills/adr-writer/references/adr-example.md +35 -35
  28. package/template/.claude/skills/architecture-characteristic-writer/SKILL.md +215 -215
  29. package/template/.claude/skills/architecture-characteristic-writer/assets/worksheet-template.md +29 -29
  30. package/template/.claude/skills/architecture-characteristic-writer/references/characteristics-catalog.md +40 -40
  31. package/template/.claude/skills/architecture-characteristic-writer/scripts/ranking-table.cjs +171 -171
  32. package/template/.claude/skills/code-review/SKILL.md +201 -54
  33. package/template/.claude/skills/code-review/references/checklist-workflow.md +96 -0
  34. package/template/.claude/skills/code-review/references/checklists/api.md +52 -52
  35. package/template/.claude/skills/code-review/references/checklists/base.md +100 -100
  36. package/template/.claude/skills/code-review/references/checklists/web-app.md +54 -54
  37. package/template/.claude/skills/code-review/references/code-review-reception.md +113 -0
  38. package/template/.claude/skills/code-review/references/codebase-scan-workflow.md +30 -0
  39. package/template/.claude/skills/code-review/references/edge-case-scouting.md +119 -0
  40. package/template/.claude/skills/code-review/references/input-mode-resolution.md +135 -0
  41. package/template/.claude/skills/code-review/references/parallel-review-workflow.md +76 -0
  42. package/template/.claude/skills/code-review/references/requesting-code-review.md +116 -0
  43. package/template/.claude/skills/code-review/references/spec-compliance-review.md +43 -0
  44. package/template/.claude/skills/code-review/references/task-management-reviews.md +140 -0
  45. package/template/.claude/skills/code-review/references/verification-before-completion.md +139 -0
  46. package/template/.claude/skills/context-map/SKILL.md +80 -80
  47. package/template/.claude/skills/context-map/example.cml +106 -106
  48. package/template/.claude/skills/context-map/reference/Bounded Context/Bounded Context.md +40 -40
  49. package/template/.claude/skills/context-map/reference/Bounded Context/businessModel.md +5 -5
  50. package/template/.claude/skills/context-map/reference/Bounded Context/domainVisionStatement.md +2 -2
  51. package/template/.claude/skills/context-map/reference/Bounded Context/evolution.md +5 -5
  52. package/template/.claude/skills/context-map/reference/Bounded Context/implementationTechnology.md +1 -1
  53. package/template/.claude/skills/context-map/reference/Bounded Context/implements.md +1 -1
  54. package/template/.claude/skills/context-map/reference/Bounded Context/knowledgeLevel.md +4 -4
  55. package/template/.claude/skills/context-map/reference/Bounded Context/realizes.md +9 -9
  56. package/template/.claude/skills/context-map/reference/Bounded Context/refines.md +10 -10
  57. package/template/.claude/skills/context-map/reference/Bounded Context/responsibilities.md +26 -26
  58. package/template/.claude/skills/context-map/reference/Bounded Context/type.md +23 -23
  59. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Anticorruption Layer.md +5 -5
  60. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Bounded Context Relationship.md +12 -12
  61. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Conformist.md +5 -5
  62. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Customer-Supplier (C-S).md +22 -22
  63. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Open Host Service.md +4 -4
  64. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Partnership (P).md +13 -13
  65. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Published Language.md +4 -4
  66. package/template/.claude/skills/context-map/reference/Bounded Context Relationship/Shared Kernel (SK).md +12 -12
  67. package/template/.claude/skills/context-map/reference/Context Map.md +62 -62
  68. package/template/.claude/skills/context-map/reference/Domain/Domain.md +30 -30
  69. package/template/.claude/skills/context-map/reference/Domain/supports.md +33 -33
  70. package/template/.claude/skills/context-map/reference/Domain/type.md +3 -3
  71. package/template/.claude/skills/context-map/reference/Semantic Rules.md +32 -32
  72. package/template/.claude/skills/git/SKILL.md +131 -115
  73. package/template/.claude/skills/git/references/branch-management.md +88 -88
  74. package/template/.claude/skills/git/references/commit-standards.md +46 -46
  75. package/template/.claude/skills/git/references/context-efficiency.md +54 -0
  76. package/template/.claude/skills/git/references/gh-cli-guide.md +109 -109
  77. package/template/.claude/skills/git/references/safety-protocols.md +69 -69
  78. package/template/.claude/skills/git/references/workflow-commit.md +58 -58
  79. package/template/.claude/skills/git/references/workflow-merge-pr.md +136 -0
  80. package/template/.claude/skills/git/references/workflow-merge.md +48 -48
  81. package/template/.claude/skills/git/references/workflow-pr.md +58 -58
  82. package/template/.claude/skills/git/references/workflow-push.md +52 -52
  83. package/template/.claude/skills/hallmark/SKILL.md +552 -552
  84. package/template/.claude/skills/hallmark/references/anti-patterns.md +412 -412
  85. package/template/.claude/skills/hallmark/references/assets.md +406 -406
  86. package/template/.claude/skills/hallmark/references/color.md +95 -95
  87. package/template/.claude/skills/hallmark/references/component-cookbook.md +256 -256
  88. package/template/.claude/skills/hallmark/references/components/c1-outlined-chip.md +12 -12
  89. package/template/.claude/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -16
  90. package/template/.claude/skills/hallmark/references/components/c3-typographic-link.md +8 -8
  91. package/template/.claude/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -16
  92. package/template/.claude/skills/hallmark/references/components/f1-bento-grid.md +20 -20
  93. package/template/.claude/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -20
  94. package/template/.claude/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -11
  95. package/template/.claude/skills/hallmark/references/components/f4-step-sequence.md +11 -11
  96. package/template/.claude/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -11
  97. package/template/.claude/skills/hallmark/references/components/f6-product-card-grid.md +41 -41
  98. package/template/.claude/skills/hallmark/references/components/ft1-mast-headed.md +13 -13
  99. package/template/.claude/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -10
  100. package/template/.claude/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -12
  101. package/template/.claude/skills/hallmark/references/components/ft4-dense-typographic.md +10 -10
  102. package/template/.claude/skills/hallmark/references/components/ft5-statement.md +21 -21
  103. package/template/.claude/skills/hallmark/references/components/ft6-letter-close.md +19 -19
  104. package/template/.claude/skills/hallmark/references/components/ft7-newsletter-first.md +27 -27
  105. package/template/.claude/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -25
  106. package/template/.claude/skills/hallmark/references/components/h1-marquee.md +15 -15
  107. package/template/.claude/skills/hallmark/references/components/h2-split-diptych.md +15 -15
  108. package/template/.claude/skills/hallmark/references/components/h3-quote-led.md +11 -11
  109. package/template/.claude/skills/hallmark/references/components/h4-stat-led.md +14 -14
  110. package/template/.claude/skills/hallmark/references/components/h5-letter-hero.md +11 -11
  111. package/template/.claude/skills/hallmark/references/components/h6-photographic-fold.md +16 -16
  112. package/template/.claude/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -27
  113. package/template/.claude/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -23
  114. package/template/.claude/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -27
  115. package/template/.claude/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -12
  116. package/template/.claude/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -19
  117. package/template/.claude/skills/hallmark/references/components/n2-floating-chip.md +14 -14
  118. package/template/.claude/skills/hallmark/references/components/n3-side-rail.md +14 -14
  119. package/template/.claude/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -9
  120. package/template/.claude/skills/hallmark/references/components/n5-floating-pill.md +28 -28
  121. package/template/.claude/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -24
  122. package/template/.claude/skills/hallmark/references/components/n7-brutal-slab.md +22 -22
  123. package/template/.claude/skills/hallmark/references/components/n8-terminal-command.md +21 -21
  124. package/template/.claude/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -17
  125. package/template/.claude/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -15
  126. package/template/.claude/skills/hallmark/references/components/s2-hanging.md +13 -13
  127. package/template/.claude/skills/hallmark/references/components/s3-sticky-pinned.md +19 -19
  128. package/template/.claude/skills/hallmark/references/components/s4-inline-no-break.md +11 -11
  129. package/template/.claude/skills/hallmark/references/components/s5-bottom-anchored.md +13 -13
  130. package/template/.claude/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -12
  131. package/template/.claude/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -19
  132. package/template/.claude/skills/hallmark/references/components/t3-single-huge-quote.md +11 -11
  133. package/template/.claude/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -14
  134. package/template/.claude/skills/hallmark/references/contract.md +24 -24
  135. package/template/.claude/skills/hallmark/references/copy.md +182 -182
  136. package/template/.claude/skills/hallmark/references/custom-craft.md +626 -626
  137. package/template/.claude/skills/hallmark/references/custom-theme.md +329 -329
  138. package/template/.claude/skills/hallmark/references/design-md.md +116 -116
  139. package/template/.claude/skills/hallmark/references/export-formats.md +328 -328
  140. package/template/.claude/skills/hallmark/references/floating-nav.md +89 -89
  141. package/template/.claude/skills/hallmark/references/genres/atmospheric.md +65 -65
  142. package/template/.claude/skills/hallmark/references/genres/editorial.md +70 -70
  143. package/template/.claude/skills/hallmark/references/genres/modern-minimal.md +67 -67
  144. package/template/.claude/skills/hallmark/references/genres/playful.md +65 -65
  145. package/template/.claude/skills/hallmark/references/hero-enrichment.md +474 -474
  146. package/template/.claude/skills/hallmark/references/imagery-kit.md +170 -170
  147. package/template/.claude/skills/hallmark/references/interaction-and-states.md +207 -207
  148. package/template/.claude/skills/hallmark/references/layout-and-space.md +111 -111
  149. package/template/.claude/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -35
  150. package/template/.claude/skills/hallmark/references/macrostructures/02-long-document.md +34 -34
  151. package/template/.claude/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -31
  152. package/template/.claude/skills/hallmark/references/macrostructures/04-stat-led.md +32 -32
  153. package/template/.claude/skills/hallmark/references/macrostructures/05-workbench.md +32 -32
  154. package/template/.claude/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -33
  155. package/template/.claude/skills/hallmark/references/macrostructures/07-manifesto.md +32 -32
  156. package/template/.claude/skills/hallmark/references/macrostructures/08-photographic.md +34 -34
  157. package/template/.claude/skills/hallmark/references/macrostructures/09-quote-led.md +32 -32
  158. package/template/.claude/skills/hallmark/references/macrostructures/10-specimen.md +32 -32
  159. package/template/.claude/skills/hallmark/references/macrostructures/11-catalogue.md +23 -23
  160. package/template/.claude/skills/hallmark/references/macrostructures/12-letter.md +23 -23
  161. package/template/.claude/skills/hallmark/references/macrostructures/13-index-first.md +23 -23
  162. package/template/.claude/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -23
  163. package/template/.claude/skills/hallmark/references/macrostructures/15-split-studio.md +23 -23
  164. package/template/.claude/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -23
  165. package/template/.claude/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -23
  166. package/template/.claude/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -23
  167. package/template/.claude/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -23
  168. package/template/.claude/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -23
  169. package/template/.claude/skills/hallmark/references/macrostructures/21-component-playground.md +23 -23
  170. package/template/.claude/skills/hallmark/references/macrostructures.md +89 -89
  171. package/template/.claude/skills/hallmark/references/microinteractions.md +260 -260
  172. package/template/.claude/skills/hallmark/references/motion.md +109 -109
  173. package/template/.claude/skills/hallmark/references/preview-examples.md +49 -49
  174. package/template/.claude/skills/hallmark/references/responsive.md +138 -138
  175. package/template/.claude/skills/hallmark/references/slop-test.md +205 -205
  176. package/template/.claude/skills/hallmark/references/structure.md +164 -164
  177. package/template/.claude/skills/hallmark/references/study.md +511 -511
  178. package/template/.claude/skills/hallmark/references/typography.md +243 -243
  179. package/template/.claude/skills/hallmark/references/verbs/audit.md +25 -25
  180. package/template/.claude/skills/hallmark/references/verbs/redesign.md +269 -269
  181. package/template/.claude/skills/hallmark-loop/SKILL.md +105 -105
  182. package/template/.claude/skills/hallmark-loop/references/auditor-call.md +60 -60
  183. package/template/.claude/skills/hallmark-loop/references/capture.md +78 -78
  184. package/template/.claude/skills/hallmark-loop/references/loop-control.md +79 -79
  185. package/template/.claude/skills/handoff/SKILL.md +15 -15
  186. package/template/.claude/skills/knowledge-crunching/SKILL.md +94 -94
  187. package/template/.claude/skills/research/SKILL.md +69 -69
  188. package/template/.claude/skills/skill-creator/LICENSE.txt +201 -201
  189. package/template/.claude/skills/skill-creator/SKILL.md +154 -149
  190. package/template/.claude/skills/skill-creator/agents/analyzer.md +274 -274
  191. package/template/.claude/skills/skill-creator/agents/comparator.md +202 -202
  192. package/template/.claude/skills/skill-creator/agents/grader.md +223 -223
  193. package/template/.claude/skills/skill-creator/assets/eval_review.html +146 -146
  194. package/template/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -471
  195. package/template/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -1325
  196. package/template/.claude/skills/skill-creator/references/benchmark-optimization-guide.md +86 -86
  197. package/template/.claude/skills/skill-creator/references/distribution-guide.md +79 -79
  198. package/template/.claude/skills/skill-creator/references/eval-infrastructure-guide.md +129 -129
  199. package/template/.claude/skills/skill-creator/references/eval-schemas.md +121 -121
  200. package/template/.claude/skills/skill-creator/references/mcp-skills-integration.md +71 -71
  201. package/template/.claude/skills/skill-creator/references/metadata-quality-criteria.md +94 -94
  202. package/template/.claude/skills/skill-creator/references/plugin-marketplace-hosting.md +104 -104
  203. package/template/.claude/skills/skill-creator/references/plugin-marketplace-overview.md +89 -89
  204. package/template/.claude/skills/skill-creator/references/plugin-marketplace-schema.md +93 -93
  205. package/template/.claude/skills/skill-creator/references/plugin-marketplace-sources.md +103 -103
  206. package/template/.claude/skills/skill-creator/references/plugin-marketplace-troubleshooting.md +76 -76
  207. package/template/.claude/skills/skill-creator/references/script-quality-criteria.md +106 -106
  208. package/template/.claude/skills/skill-creator/references/skill-anatomy-and-requirements.md +77 -77
  209. package/template/.claude/skills/skill-creator/references/skill-creation-workflow.md +152 -151
  210. package/template/.claude/skills/skill-creator/references/skill-design-patterns.md +75 -75
  211. package/template/.claude/skills/skill-creator/references/skillmark-benchmark-criteria.md +102 -102
  212. package/template/.claude/skills/skill-creator/references/structure-organization-criteria.md +114 -114
  213. package/template/.claude/skills/skill-creator/references/testing-and-iteration.md +78 -78
  214. package/template/.claude/skills/skill-creator/references/token-efficiency-criteria.md +74 -74
  215. package/template/.claude/skills/skill-creator/references/troubleshooting-guide.md +81 -81
  216. package/template/.claude/skills/skill-creator/references/validation-checklist.md +83 -83
  217. package/template/.claude/skills/skill-creator/references/writing-effective-instructions.md +88 -88
  218. package/template/.claude/skills/skill-creator/references/yaml-frontmatter-reference.md +92 -92
  219. package/template/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -401
  220. package/template/.claude/skills/skill-creator/scripts/encoding_utils.py +36 -36
  221. package/template/.claude/skills/skill-creator/scripts/generate_report.py +326 -326
  222. package/template/.claude/skills/skill-creator/scripts/improve_description.py +248 -248
  223. package/template/.claude/skills/skill-creator/scripts/init_skill.py +360 -360
  224. package/template/.claude/skills/skill-creator/scripts/package_skill.py +143 -143
  225. package/template/.claude/skills/skill-creator/scripts/quick_validate.py +110 -110
  226. package/template/.claude/skills/skill-creator/scripts/run_eval.py +310 -310
  227. package/template/.claude/skills/skill-creator/scripts/run_loop.py +332 -332
  228. package/template/.claude/skills/skill-creator/scripts/utils.py +47 -47
  229. package/template/.claude/skills/tdd/SKILL.md +142 -142
  230. package/template/.claude/skills/tdd/deep-modules.md +15 -15
  231. package/template/.claude/skills/tdd/interface-design.md +31 -31
  232. package/template/.claude/skills/tdd/mocking.md +59 -59
  233. package/template/.claude/skills/tdd/refactoring.md +10 -10
  234. package/template/.claude/skills/tdd/tests.md +61 -61
  235. package/template/.claude/statusline.cjs +0 -0
  236. package/template/.claude/skills/code-review/references/adversarial-review.md +0 -223
@@ -1,357 +1,357 @@
1
- 'use strict';
2
-
3
- // Structural checker for the two architecture-characteristic documents.
4
- //
5
- // Checks only what is mechanically decidable. The ranking ORDER is a judgement
6
- // call and is never questioned here; what is checked is that the table is
7
- // complete, that every comparative reason names the row it actually sits above,
8
- // and that the worksheet is a faithful derivation of the ranking's top 7.
9
- //
10
- // The catalog markdown is the single source of truth for which characteristics
11
- // exist, which of them are implicit, and how the composites decompose. Nothing
12
- // here restates it — edit the markdown and this checker follows.
13
-
14
- const fs = require('fs');
15
- const path = require('path');
16
-
17
- const DEFAULT_CATALOG_PATH = path.join(
18
- __dirname,
19
- '..',
20
- '..',
21
- '..',
22
- 'skills',
23
- 'architecture-characteristic-writer',
24
- 'references',
25
- 'characteristics-catalog.md'
26
- );
27
-
28
- const DRIVING_LIMIT = 7;
29
-
30
- // The ranking table is found by its header row rather than by the heading above
31
- // it. A heading is prose the author may reword or duplicate; this signature is
32
- // the table itself. Both the checker and scripts/ranking-table.cjs locate the
33
- // table this way, so neither can end up working on a different table.
34
- const RANKING_HEADER = /^\|\s*order\s*\|\s*characteristic\s*\|\s*reason\s*\|/i;
35
-
36
- // "feasibility (cost/time)" and "**feasibility**" both normalise to "feasibility"
37
- // so the checker never argues with formatting.
38
- function normalise(cell) {
39
- return String(cell)
40
- .replace(/`([^`]*)`/g, '$1')
41
- .replace(/\*\*/g, '')
42
- .replace(/\([^)]*\)/g, '')
43
- .replace(/\s+/g, ' ')
44
- .trim()
45
- .toLowerCase();
46
- }
47
-
48
- const isPlaceholder = (row) => row.some((c) => /\{[a-z0-9_]+\}/i.test(c)) || row.some((c) => c === '...');
49
-
50
- const splitCells = (line) =>
51
- line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
52
-
53
- const isSeparator = (cells) => cells.every((c) => /^:?-+:?$/.test(c) || c === '');
54
-
55
- // Returns the data rows of the first pipe table under a heading matching `re`,
56
- // or of the whole document when `re` is null. Header and separator are dropped.
57
- function parseTable(markdown, re) {
58
- const lines = markdown.split(/\r?\n/);
59
- let start = 0;
60
-
61
- if (re) {
62
- const i = lines.findIndex((l) => /^#{1,6}\s/.test(l) && re.test(l));
63
- if (i === -1) return null;
64
- start = i + 1;
65
- const next = lines.slice(start).findIndex((l) => /^#{1,6}\s/.test(l));
66
- if (next !== -1) lines.length = start + next;
67
- }
68
-
69
- const rows = [];
70
- let seenHeader = false;
71
-
72
- for (const line of lines.slice(start)) {
73
- const trimmed = line.trim();
74
- if (!trimmed.startsWith('|')) {
75
- if (seenHeader && rows.length) break;
76
- continue;
77
- }
78
- const cells = splitCells(trimmed);
79
- if (!seenHeader) {
80
- seenHeader = true;
81
- continue;
82
- }
83
- if (isSeparator(cells)) continue;
84
- rows.push(cells);
85
- }
86
-
87
- return seenHeader ? rows : null;
88
- }
89
-
90
- // Line bounds of the table whose header matches `headerRe`, covering the header
91
- // through the last consecutive pipe line. Returns null when there is no match.
92
- function locateTable(lines, headerRe) {
93
- for (let i = 0; i < lines.length; i += 1) {
94
- if (!lines[i].trim().startsWith('|') || !headerRe.test(lines[i].trim())) continue;
95
- let end = i + 1;
96
- while (end < lines.length && lines[end].trim().startsWith('|')) end += 1;
97
- return { start: i, end };
98
- }
99
- return null;
100
- }
101
-
102
- // Data rows of the table located by `headerRe`, header and separator dropped.
103
- function tableRows(markdown, headerRe) {
104
- const lines = markdown.split(/\r?\n/);
105
- const at = locateTable(lines, headerRe);
106
- if (!at) return null;
107
- return lines
108
- .slice(at.start + 1, at.end)
109
- .map(splitCells)
110
- .filter((c) => !isSeparator(c));
111
- }
112
-
113
- // Reads the catalog markdown and derives every set the checker needs from it.
114
- // Throws when the file is absent or a section is unreadable; the caller decides
115
- // whether that is fatal.
116
- function loadCatalog(catalogPath = DEFAULT_CATALOG_PATH) {
117
- const markdown = fs.readFileSync(catalogPath, 'utf8');
118
-
119
- const names = (re) => {
120
- const rows = parseTable(markdown, re);
121
- if (!rows) throw new Error(`no table found under a heading matching ${re}`);
122
- return rows.filter((r) => r.length && !isPlaceholder(r)).map((r) => normalise(r[0]));
123
- };
124
-
125
- const common = names(/common/i);
126
- const implicit = names(/implicit/i);
127
-
128
- const compositeRows = parseTable(markdown, /composite/i);
129
- if (!compositeRows) throw new Error('no Composite Architecture Characteristics table found');
130
-
131
- const composites = {};
132
- for (const row of compositeRows.filter((r) => r.length >= 2 && !isPlaceholder(r))) {
133
- composites[normalise(row[0])] = row[1].split('+').map(normalise).filter(Boolean);
134
- }
135
-
136
- if (!common.length || !implicit.length) throw new Error('the Common or Implicit section is empty');
137
-
138
- return { catalog: [...common, ...implicit], implicit, composites };
139
- }
140
-
141
- function checkRanking(markdown, { catalog }) {
142
- const errors = [];
143
- const found = tableRows(markdown, RANKING_HEADER);
144
- if (!found) {
145
- return {
146
- errors: ['ranking: no table with a "| Order | Characteristic | Reason |" header row'],
147
- order: [],
148
- };
149
- }
150
- const raw = found.filter((r) => r.length >= 3);
151
- const unfilled = raw.filter(isPlaceholder);
152
- const rows = raw.filter((r) => !isPlaceholder(r));
153
-
154
- // A scaffold is a blank form, not a broken document: every reason is still a
155
- // placeholder, bar the last row whose reason is only the em dash.
156
- const hasContent = (r) => !isPlaceholder(r) && !/^[—-]$/.test(String(r[2]).trim());
157
- if (raw.length && !raw.some(hasContent)) return { errors: [], order: [] };
158
- if (!rows.length) return { errors: ['ranking: no table rows found'], order: [] };
159
-
160
- // Half-filled, so adjacency cannot be judged: a filled row's neighbour may be
161
- // one of the rows still holding a placeholder. Ask for the rest first.
162
- if (unfilled.length) {
163
- return {
164
- errors: [`ranking: ${unfilled.length} row(s) still hold the {why} placeholder, fill them in`],
165
- order: [],
166
- };
167
- }
168
-
169
- const order = rows.map((r) => normalise(r[1]));
170
-
171
- // Rule 1 — the Order column counts 1..N with no gaps and no repeats.
172
- rows.forEach((r, i) => {
173
- const n = Number(String(r[0]).trim());
174
- if (n !== i + 1) errors.push(`ranking row ${i + 1}: Order column reads "${r[0]}", expected ${i + 1}`);
175
- });
176
-
177
- // Rule 2 — the ranked set is exactly the catalog. Nothing missing, nothing
178
- // invented, nothing ranked twice.
179
- const seen = new Set();
180
- order.forEach((name, i) => {
181
- if (!catalog.includes(name)) errors.push(`ranking row ${i + 1}: "${name}" is not in the catalog`);
182
- if (seen.has(name)) errors.push(`ranking row ${i + 1}: "${name}" is ranked more than once`);
183
- seen.add(name);
184
- });
185
- for (const name of catalog) {
186
- if (!seen.has(name)) errors.push(`ranking: "${name}" is missing`);
187
- }
188
-
189
- // Rules 3 and 4 — every reason names the row it actually sits above. This is
190
- // what goes stale after a reorder, so it is checked by string, not by eye.
191
- rows.forEach((r, i) => {
192
- const reason = String(r[2]).trim();
193
- const last = i === rows.length - 1;
194
-
195
- if (last) {
196
- if (!/^[—-]$/.test(reason)) {
197
- errors.push(`ranking row ${i + 1}: last row has nothing below it, its Reason must be "—"`);
198
- }
199
- return;
200
- }
201
-
202
- const below = order[i + 1];
203
- const m = reason.match(/^above\s+([^:]+):\s*(.*)$/i);
204
-
205
- if (!m) {
206
- errors.push(`ranking row ${i + 1} (${order[i]}): Reason must start with "Above ${below}:"`);
207
- return;
208
- }
209
- if (normalise(m[1]) !== below) {
210
- errors.push(
211
- `ranking row ${i + 1} (${order[i]}): Reason compares against "${m[1].trim()}" but the row below is "${below}"`
212
- );
213
- }
214
- if (m[2].trim().length < 15) {
215
- errors.push(`ranking row ${i + 1} (${order[i]}): Reason has a prefix but no justification after it`);
216
- }
217
- });
218
-
219
- return { errors, order };
220
- }
221
-
222
- function checkWorksheet(markdown, order, { implicit, composites }) {
223
- const errors = [];
224
- const driving = (parseTable(markdown, /driving/i) || []).filter((r) => r.length >= 2 && !isPlaceholder(r));
225
- const implicitRows = (parseTable(markdown, /implicit/i) || []).filter(
226
- (r) => r.length >= 1 && !isPlaceholder(r)
227
- );
228
-
229
- if (!driving.length) return ['worksheet: no Driving Characteristics rows found'];
230
-
231
- const names = driving.map((r) => normalise(r[1] !== undefined && r.length >= 3 ? r[1] : r[0]));
232
-
233
- // Rule 5 — no backfill. Composing shrinks the section; it never refills.
234
- if (names.length > DRIVING_LIMIT) {
235
- errors.push(`worksheet: ${names.length} driving characteristics, maximum is ${DRIVING_LIMIT}`);
236
- }
237
-
238
- // Listed twice, so the count is a lie and the top 7 cannot all be present.
239
- // Said plainly here, or it surfaces later as a confusing pile of "missing".
240
- const twice = new Set();
241
- names.forEach((name, i) => {
242
- if (names.indexOf(name) !== i) twice.add(name);
243
- });
244
- for (const name of twice) errors.push(`worksheet: "${name}" is listed more than once`);
245
-
246
- // Without a readable ranking the remaining rules have nothing to derive from.
247
- if (!order.length) return errors;
248
-
249
- const top = order.slice(0, DRIVING_LIMIT);
250
- const absorbed = new Set();
251
-
252
- for (const name of names) {
253
- const parts = composites[name];
254
-
255
- if (parts) {
256
- // Rule 7 — a composite stands only when the whole of it ranked top 7.
257
- const missing = parts.filter((p) => !top.includes(p));
258
- if (missing.length) {
259
- errors.push(
260
- `worksheet: "${name}" needs all of [${parts.join(', ')}] in the top 7, missing [${missing.join(', ')}]`
261
- );
262
- }
263
- parts.forEach((p) => absorbed.add(p));
264
-
265
- // Rule 8 — a composite and its own parts must never be listed side by side.
266
- const both = parts.filter((p) => names.includes(p));
267
- if (both.length) {
268
- errors.push(`worksheet: "${name}" is listed alongside its own component [${both.join(', ')}]`);
269
- }
270
- continue;
271
- }
272
-
273
- // Rule 6 — everything driving traces back to the top 7.
274
- if (!top.includes(name)) {
275
- const rank = order.indexOf(name);
276
- errors.push(
277
- rank === -1
278
- ? `worksheet: "${name}" is not in the ranking at all`
279
- : `worksheet: "${name}" is ranked ${rank + 1}, outside the top 7`
280
- );
281
- }
282
- }
283
-
284
- const present = new Set([...names, ...absorbed]);
285
-
286
- // Rule 6b — the top 7 is taken whole. Every one of them is either listed or
287
- // swallowed by a composite; none may be quietly dropped.
288
- for (const name of top) {
289
- if (!present.has(name)) {
290
- errors.push(`worksheet: "${name}" is ranked ${order.indexOf(name) + 1} but is missing from the worksheet`);
291
- }
292
- }
293
-
294
- // Rule 9 — the implicit set is a pure derivation: the catalog's implicit
295
- // characteristics, less any that reached the top 7, counting those a
296
- // composite swallowed.
297
- const expected = implicit.filter((c) => !present.has(c));
298
- const actual = implicitRows.map((r) => normalise(r[0])).filter((c) => implicit.includes(c));
299
-
300
- for (const c of expected) {
301
- if (!actual.includes(c)) errors.push(`worksheet: implicit characteristic "${c}" is missing`);
302
- }
303
- for (const c of actual) {
304
- if (!expected.includes(c)) {
305
- errors.push(`worksheet: "${c}" is in the top 7, so it must not be listed as implicit`);
306
- }
307
- }
308
-
309
- return errors;
310
- }
311
-
312
- // ranking and worksheet are file contents; either may be null when absent.
313
- // The catalog sets default to the shipped markdown; pass `catalog` to inject.
314
- function check({ ranking = null, worksheet = null, catalog = null, catalogPath } = {}) {
315
- let sets = catalog;
316
-
317
- if (!sets) {
318
- try {
319
- sets = loadCatalog(catalogPath);
320
- } catch (err) {
321
- return [`cannot read the characteristics catalog, so nothing can be checked: ${err.message}`];
322
- }
323
- }
324
-
325
- let errors = [];
326
- let order = [];
327
-
328
- if (ranking !== null) {
329
- const res = checkRanking(ranking, sets);
330
- errors = errors.concat(res.errors);
331
- order = res.order;
332
- }
333
-
334
- if (worksheet !== null) {
335
- if (ranking === null) {
336
- errors.push('worksheet: no ranking file to derive from, write the ranking first');
337
- } else {
338
- errors = errors.concat(checkWorksheet(worksheet, order, sets));
339
- }
340
- }
341
-
342
- return errors;
343
- }
344
-
345
- module.exports = {
346
- check,
347
- checkRanking,
348
- checkWorksheet,
349
- parseTable,
350
- locateTable,
351
- splitCells,
352
- isSeparator,
353
- normalise,
354
- loadCatalog,
355
- RANKING_HEADER,
356
- DEFAULT_CATALOG_PATH,
357
- };
1
+ 'use strict';
2
+
3
+ // Structural checker for the two architecture-characteristic documents.
4
+ //
5
+ // Checks only what is mechanically decidable. The ranking ORDER is a judgement
6
+ // call and is never questioned here; what is checked is that the table is
7
+ // complete, that every comparative reason names the row it actually sits above,
8
+ // and that the worksheet is a faithful derivation of the ranking's top 7.
9
+ //
10
+ // The catalog markdown is the single source of truth for which characteristics
11
+ // exist, which of them are implicit, and how the composites decompose. Nothing
12
+ // here restates it — edit the markdown and this checker follows.
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const DEFAULT_CATALOG_PATH = path.join(
18
+ __dirname,
19
+ '..',
20
+ '..',
21
+ '..',
22
+ 'skills',
23
+ 'architecture-characteristic-writer',
24
+ 'references',
25
+ 'characteristics-catalog.md'
26
+ );
27
+
28
+ const DRIVING_LIMIT = 7;
29
+
30
+ // The ranking table is found by its header row rather than by the heading above
31
+ // it. A heading is prose the author may reword or duplicate; this signature is
32
+ // the table itself. Both the checker and scripts/ranking-table.cjs locate the
33
+ // table this way, so neither can end up working on a different table.
34
+ const RANKING_HEADER = /^\|\s*order\s*\|\s*characteristic\s*\|\s*reason\s*\|/i;
35
+
36
+ // "feasibility (cost/time)" and "**feasibility**" both normalise to "feasibility"
37
+ // so the checker never argues with formatting.
38
+ function normalise(cell) {
39
+ return String(cell)
40
+ .replace(/`([^`]*)`/g, '$1')
41
+ .replace(/\*\*/g, '')
42
+ .replace(/\([^)]*\)/g, '')
43
+ .replace(/\s+/g, ' ')
44
+ .trim()
45
+ .toLowerCase();
46
+ }
47
+
48
+ const isPlaceholder = (row) => row.some((c) => /\{[a-z0-9_]+\}/i.test(c)) || row.some((c) => c === '...');
49
+
50
+ const splitCells = (line) =>
51
+ line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
52
+
53
+ const isSeparator = (cells) => cells.every((c) => /^:?-+:?$/.test(c) || c === '');
54
+
55
+ // Returns the data rows of the first pipe table under a heading matching `re`,
56
+ // or of the whole document when `re` is null. Header and separator are dropped.
57
+ function parseTable(markdown, re) {
58
+ const lines = markdown.split(/\r?\n/);
59
+ let start = 0;
60
+
61
+ if (re) {
62
+ const i = lines.findIndex((l) => /^#{1,6}\s/.test(l) && re.test(l));
63
+ if (i === -1) return null;
64
+ start = i + 1;
65
+ const next = lines.slice(start).findIndex((l) => /^#{1,6}\s/.test(l));
66
+ if (next !== -1) lines.length = start + next;
67
+ }
68
+
69
+ const rows = [];
70
+ let seenHeader = false;
71
+
72
+ for (const line of lines.slice(start)) {
73
+ const trimmed = line.trim();
74
+ if (!trimmed.startsWith('|')) {
75
+ if (seenHeader && rows.length) break;
76
+ continue;
77
+ }
78
+ const cells = splitCells(trimmed);
79
+ if (!seenHeader) {
80
+ seenHeader = true;
81
+ continue;
82
+ }
83
+ if (isSeparator(cells)) continue;
84
+ rows.push(cells);
85
+ }
86
+
87
+ return seenHeader ? rows : null;
88
+ }
89
+
90
+ // Line bounds of the table whose header matches `headerRe`, covering the header
91
+ // through the last consecutive pipe line. Returns null when there is no match.
92
+ function locateTable(lines, headerRe) {
93
+ for (let i = 0; i < lines.length; i += 1) {
94
+ if (!lines[i].trim().startsWith('|') || !headerRe.test(lines[i].trim())) continue;
95
+ let end = i + 1;
96
+ while (end < lines.length && lines[end].trim().startsWith('|')) end += 1;
97
+ return { start: i, end };
98
+ }
99
+ return null;
100
+ }
101
+
102
+ // Data rows of the table located by `headerRe`, header and separator dropped.
103
+ function tableRows(markdown, headerRe) {
104
+ const lines = markdown.split(/\r?\n/);
105
+ const at = locateTable(lines, headerRe);
106
+ if (!at) return null;
107
+ return lines
108
+ .slice(at.start + 1, at.end)
109
+ .map(splitCells)
110
+ .filter((c) => !isSeparator(c));
111
+ }
112
+
113
+ // Reads the catalog markdown and derives every set the checker needs from it.
114
+ // Throws when the file is absent or a section is unreadable; the caller decides
115
+ // whether that is fatal.
116
+ function loadCatalog(catalogPath = DEFAULT_CATALOG_PATH) {
117
+ const markdown = fs.readFileSync(catalogPath, 'utf8');
118
+
119
+ const names = (re) => {
120
+ const rows = parseTable(markdown, re);
121
+ if (!rows) throw new Error(`no table found under a heading matching ${re}`);
122
+ return rows.filter((r) => r.length && !isPlaceholder(r)).map((r) => normalise(r[0]));
123
+ };
124
+
125
+ const common = names(/common/i);
126
+ const implicit = names(/implicit/i);
127
+
128
+ const compositeRows = parseTable(markdown, /composite/i);
129
+ if (!compositeRows) throw new Error('no Composite Architecture Characteristics table found');
130
+
131
+ const composites = {};
132
+ for (const row of compositeRows.filter((r) => r.length >= 2 && !isPlaceholder(r))) {
133
+ composites[normalise(row[0])] = row[1].split('+').map(normalise).filter(Boolean);
134
+ }
135
+
136
+ if (!common.length || !implicit.length) throw new Error('the Common or Implicit section is empty');
137
+
138
+ return { catalog: [...common, ...implicit], implicit, composites };
139
+ }
140
+
141
+ function checkRanking(markdown, { catalog }) {
142
+ const errors = [];
143
+ const found = tableRows(markdown, RANKING_HEADER);
144
+ if (!found) {
145
+ return {
146
+ errors: ['ranking: no table with a "| Order | Characteristic | Reason |" header row'],
147
+ order: [],
148
+ };
149
+ }
150
+ const raw = found.filter((r) => r.length >= 3);
151
+ const unfilled = raw.filter(isPlaceholder);
152
+ const rows = raw.filter((r) => !isPlaceholder(r));
153
+
154
+ // A scaffold is a blank form, not a broken document: every reason is still a
155
+ // placeholder, bar the last row whose reason is only the em dash.
156
+ const hasContent = (r) => !isPlaceholder(r) && !/^[—-]$/.test(String(r[2]).trim());
157
+ if (raw.length && !raw.some(hasContent)) return { errors: [], order: [] };
158
+ if (!rows.length) return { errors: ['ranking: no table rows found'], order: [] };
159
+
160
+ // Half-filled, so adjacency cannot be judged: a filled row's neighbour may be
161
+ // one of the rows still holding a placeholder. Ask for the rest first.
162
+ if (unfilled.length) {
163
+ return {
164
+ errors: [`ranking: ${unfilled.length} row(s) still hold the {why} placeholder, fill them in`],
165
+ order: [],
166
+ };
167
+ }
168
+
169
+ const order = rows.map((r) => normalise(r[1]));
170
+
171
+ // Rule 1 — the Order column counts 1..N with no gaps and no repeats.
172
+ rows.forEach((r, i) => {
173
+ const n = Number(String(r[0]).trim());
174
+ if (n !== i + 1) errors.push(`ranking row ${i + 1}: Order column reads "${r[0]}", expected ${i + 1}`);
175
+ });
176
+
177
+ // Rule 2 — the ranked set is exactly the catalog. Nothing missing, nothing
178
+ // invented, nothing ranked twice.
179
+ const seen = new Set();
180
+ order.forEach((name, i) => {
181
+ if (!catalog.includes(name)) errors.push(`ranking row ${i + 1}: "${name}" is not in the catalog`);
182
+ if (seen.has(name)) errors.push(`ranking row ${i + 1}: "${name}" is ranked more than once`);
183
+ seen.add(name);
184
+ });
185
+ for (const name of catalog) {
186
+ if (!seen.has(name)) errors.push(`ranking: "${name}" is missing`);
187
+ }
188
+
189
+ // Rules 3 and 4 — every reason names the row it actually sits above. This is
190
+ // what goes stale after a reorder, so it is checked by string, not by eye.
191
+ rows.forEach((r, i) => {
192
+ const reason = String(r[2]).trim();
193
+ const last = i === rows.length - 1;
194
+
195
+ if (last) {
196
+ if (!/^[—-]$/.test(reason)) {
197
+ errors.push(`ranking row ${i + 1}: last row has nothing below it, its Reason must be "—"`);
198
+ }
199
+ return;
200
+ }
201
+
202
+ const below = order[i + 1];
203
+ const m = reason.match(/^above\s+([^:]+):\s*(.*)$/i);
204
+
205
+ if (!m) {
206
+ errors.push(`ranking row ${i + 1} (${order[i]}): Reason must start with "Above ${below}:"`);
207
+ return;
208
+ }
209
+ if (normalise(m[1]) !== below) {
210
+ errors.push(
211
+ `ranking row ${i + 1} (${order[i]}): Reason compares against "${m[1].trim()}" but the row below is "${below}"`
212
+ );
213
+ }
214
+ if (m[2].trim().length < 15) {
215
+ errors.push(`ranking row ${i + 1} (${order[i]}): Reason has a prefix but no justification after it`);
216
+ }
217
+ });
218
+
219
+ return { errors, order };
220
+ }
221
+
222
+ function checkWorksheet(markdown, order, { implicit, composites }) {
223
+ const errors = [];
224
+ const driving = (parseTable(markdown, /driving/i) || []).filter((r) => r.length >= 2 && !isPlaceholder(r));
225
+ const implicitRows = (parseTable(markdown, /implicit/i) || []).filter(
226
+ (r) => r.length >= 1 && !isPlaceholder(r)
227
+ );
228
+
229
+ if (!driving.length) return ['worksheet: no Driving Characteristics rows found'];
230
+
231
+ const names = driving.map((r) => normalise(r[1] !== undefined && r.length >= 3 ? r[1] : r[0]));
232
+
233
+ // Rule 5 — no backfill. Composing shrinks the section; it never refills.
234
+ if (names.length > DRIVING_LIMIT) {
235
+ errors.push(`worksheet: ${names.length} driving characteristics, maximum is ${DRIVING_LIMIT}`);
236
+ }
237
+
238
+ // Listed twice, so the count is a lie and the top 7 cannot all be present.
239
+ // Said plainly here, or it surfaces later as a confusing pile of "missing".
240
+ const twice = new Set();
241
+ names.forEach((name, i) => {
242
+ if (names.indexOf(name) !== i) twice.add(name);
243
+ });
244
+ for (const name of twice) errors.push(`worksheet: "${name}" is listed more than once`);
245
+
246
+ // Without a readable ranking the remaining rules have nothing to derive from.
247
+ if (!order.length) return errors;
248
+
249
+ const top = order.slice(0, DRIVING_LIMIT);
250
+ const absorbed = new Set();
251
+
252
+ for (const name of names) {
253
+ const parts = composites[name];
254
+
255
+ if (parts) {
256
+ // Rule 7 — a composite stands only when the whole of it ranked top 7.
257
+ const missing = parts.filter((p) => !top.includes(p));
258
+ if (missing.length) {
259
+ errors.push(
260
+ `worksheet: "${name}" needs all of [${parts.join(', ')}] in the top 7, missing [${missing.join(', ')}]`
261
+ );
262
+ }
263
+ parts.forEach((p) => absorbed.add(p));
264
+
265
+ // Rule 8 — a composite and its own parts must never be listed side by side.
266
+ const both = parts.filter((p) => names.includes(p));
267
+ if (both.length) {
268
+ errors.push(`worksheet: "${name}" is listed alongside its own component [${both.join(', ')}]`);
269
+ }
270
+ continue;
271
+ }
272
+
273
+ // Rule 6 — everything driving traces back to the top 7.
274
+ if (!top.includes(name)) {
275
+ const rank = order.indexOf(name);
276
+ errors.push(
277
+ rank === -1
278
+ ? `worksheet: "${name}" is not in the ranking at all`
279
+ : `worksheet: "${name}" is ranked ${rank + 1}, outside the top 7`
280
+ );
281
+ }
282
+ }
283
+
284
+ const present = new Set([...names, ...absorbed]);
285
+
286
+ // Rule 6b — the top 7 is taken whole. Every one of them is either listed or
287
+ // swallowed by a composite; none may be quietly dropped.
288
+ for (const name of top) {
289
+ if (!present.has(name)) {
290
+ errors.push(`worksheet: "${name}" is ranked ${order.indexOf(name) + 1} but is missing from the worksheet`);
291
+ }
292
+ }
293
+
294
+ // Rule 9 — the implicit set is a pure derivation: the catalog's implicit
295
+ // characteristics, less any that reached the top 7, counting those a
296
+ // composite swallowed.
297
+ const expected = implicit.filter((c) => !present.has(c));
298
+ const actual = implicitRows.map((r) => normalise(r[0])).filter((c) => implicit.includes(c));
299
+
300
+ for (const c of expected) {
301
+ if (!actual.includes(c)) errors.push(`worksheet: implicit characteristic "${c}" is missing`);
302
+ }
303
+ for (const c of actual) {
304
+ if (!expected.includes(c)) {
305
+ errors.push(`worksheet: "${c}" is in the top 7, so it must not be listed as implicit`);
306
+ }
307
+ }
308
+
309
+ return errors;
310
+ }
311
+
312
+ // ranking and worksheet are file contents; either may be null when absent.
313
+ // The catalog sets default to the shipped markdown; pass `catalog` to inject.
314
+ function check({ ranking = null, worksheet = null, catalog = null, catalogPath } = {}) {
315
+ let sets = catalog;
316
+
317
+ if (!sets) {
318
+ try {
319
+ sets = loadCatalog(catalogPath);
320
+ } catch (err) {
321
+ return [`cannot read the characteristics catalog, so nothing can be checked: ${err.message}`];
322
+ }
323
+ }
324
+
325
+ let errors = [];
326
+ let order = [];
327
+
328
+ if (ranking !== null) {
329
+ const res = checkRanking(ranking, sets);
330
+ errors = errors.concat(res.errors);
331
+ order = res.order;
332
+ }
333
+
334
+ if (worksheet !== null) {
335
+ if (ranking === null) {
336
+ errors.push('worksheet: no ranking file to derive from, write the ranking first');
337
+ } else {
338
+ errors = errors.concat(checkWorksheet(worksheet, order, sets));
339
+ }
340
+ }
341
+
342
+ return errors;
343
+ }
344
+
345
+ module.exports = {
346
+ check,
347
+ checkRanking,
348
+ checkWorksheet,
349
+ parseTable,
350
+ locateTable,
351
+ splitCells,
352
+ isSeparator,
353
+ normalise,
354
+ loadCatalog,
355
+ RANKING_HEADER,
356
+ DEFAULT_CATALOG_PATH,
357
+ };