@trohde/earos 1.0.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 (135) hide show
  1. package/README.md +156 -0
  2. package/assets/init/.agents/skills/earos-artifact-gen/SKILL.md +106 -0
  3. package/assets/init/.agents/skills/earos-artifact-gen/references/interview-guide.md +313 -0
  4. package/assets/init/.agents/skills/earos-artifact-gen/references/output-guide.md +367 -0
  5. package/assets/init/.agents/skills/earos-assess/SKILL.md +212 -0
  6. package/assets/init/.agents/skills/earos-assess/references/calibration-benchmarks.md +160 -0
  7. package/assets/init/.agents/skills/earos-assess/references/output-templates.md +311 -0
  8. package/assets/init/.agents/skills/earos-assess/references/scoring-protocol.md +281 -0
  9. package/assets/init/.agents/skills/earos-calibrate/SKILL.md +153 -0
  10. package/assets/init/.agents/skills/earos-calibrate/references/agreement-metrics.md +188 -0
  11. package/assets/init/.agents/skills/earos-calibrate/references/calibration-protocol.md +263 -0
  12. package/assets/init/.agents/skills/earos-create/SKILL.md +257 -0
  13. package/assets/init/.agents/skills/earos-create/references/criterion-writing-guide.md +268 -0
  14. package/assets/init/.agents/skills/earos-create/references/dependency-rules.md +193 -0
  15. package/assets/init/.agents/skills/earos-create/references/rubric-interview-guide.md +123 -0
  16. package/assets/init/.agents/skills/earos-create/references/validation-checklist.md +238 -0
  17. package/assets/init/.agents/skills/earos-profile-author/SKILL.md +251 -0
  18. package/assets/init/.agents/skills/earos-profile-author/references/criterion-writing-guide.md +280 -0
  19. package/assets/init/.agents/skills/earos-profile-author/references/design-methods.md +158 -0
  20. package/assets/init/.agents/skills/earos-profile-author/references/profile-checklist.md +173 -0
  21. package/assets/init/.agents/skills/earos-remediate/SKILL.md +118 -0
  22. package/assets/init/.agents/skills/earos-remediate/references/output-template.md +199 -0
  23. package/assets/init/.agents/skills/earos-remediate/references/remediation-patterns.md +330 -0
  24. package/assets/init/.agents/skills/earos-report/SKILL.md +85 -0
  25. package/assets/init/.agents/skills/earos-report/references/portfolio-template.md +181 -0
  26. package/assets/init/.agents/skills/earos-report/references/single-artifact-template.md +168 -0
  27. package/assets/init/.agents/skills/earos-review/SKILL.md +130 -0
  28. package/assets/init/.agents/skills/earos-review/references/challenge-patterns.md +163 -0
  29. package/assets/init/.agents/skills/earos-review/references/output-template.md +180 -0
  30. package/assets/init/.agents/skills/earos-template-fill/SKILL.md +177 -0
  31. package/assets/init/.agents/skills/earos-template-fill/references/evidence-writing-guide.md +186 -0
  32. package/assets/init/.agents/skills/earos-template-fill/references/section-rubric-mapping.md +200 -0
  33. package/assets/init/.agents/skills/earos-validate/SKILL.md +113 -0
  34. package/assets/init/.agents/skills/earos-validate/references/fix-patterns.md +281 -0
  35. package/assets/init/.agents/skills/earos-validate/references/validation-checks.md +287 -0
  36. package/assets/init/.claude/CLAUDE.md +4 -0
  37. package/assets/init/AGENTS.md +293 -0
  38. package/assets/init/CLAUDE.md +635 -0
  39. package/assets/init/README.md +507 -0
  40. package/assets/init/calibration/gold-set/.gitkeep +0 -0
  41. package/assets/init/calibration/results/.gitkeep +0 -0
  42. package/assets/init/core/core-meta-rubric.yaml +643 -0
  43. package/assets/init/docs/consistency-report.md +325 -0
  44. package/assets/init/docs/getting-started.md +194 -0
  45. package/assets/init/docs/profile-authoring-guide.md +51 -0
  46. package/assets/init/docs/terminology.md +126 -0
  47. package/assets/init/earos.manifest.yaml +104 -0
  48. package/assets/init/evaluations/.gitkeep +0 -0
  49. package/assets/init/examples/aws-event-driven-order-processing/artifact.yaml +2056 -0
  50. package/assets/init/examples/aws-event-driven-order-processing/evaluation.yaml +973 -0
  51. package/assets/init/examples/aws-event-driven-order-processing/report.md +244 -0
  52. package/assets/init/examples/example-solution-architecture.evaluation.yaml +136 -0
  53. package/assets/init/examples/multi-cloud-data-analytics/artifact.yaml +715 -0
  54. package/assets/init/overlays/data-governance.yaml +94 -0
  55. package/assets/init/overlays/regulatory.yaml +154 -0
  56. package/assets/init/overlays/security.yaml +92 -0
  57. package/assets/init/profiles/adr.yaml +225 -0
  58. package/assets/init/profiles/capability-map.yaml +223 -0
  59. package/assets/init/profiles/reference-architecture.yaml +426 -0
  60. package/assets/init/profiles/roadmap.yaml +205 -0
  61. package/assets/init/profiles/solution-architecture.yaml +227 -0
  62. package/assets/init/research/architecture-assessment-rubrics-research.docx +0 -0
  63. package/assets/init/research/architecture-assessment-rubrics-research.md +566 -0
  64. package/assets/init/research/reference-architecture-research.md +751 -0
  65. package/assets/init/standard/EAROS.md +1426 -0
  66. package/assets/init/standard/schemas/artifact.schema.json +1295 -0
  67. package/assets/init/standard/schemas/artifact.uischema.json +65 -0
  68. package/assets/init/standard/schemas/evaluation.schema.json +284 -0
  69. package/assets/init/standard/schemas/rubric.schema.json +383 -0
  70. package/assets/init/templates/evaluation-record.template.yaml +58 -0
  71. package/assets/init/templates/new-profile.template.yaml +65 -0
  72. package/bin.js +188 -0
  73. package/dist/assets/_basePickBy-BVu6YmSW.js +1 -0
  74. package/dist/assets/_baseUniq-CWRzQDz_.js +1 -0
  75. package/dist/assets/arc-CyDBhtDM.js +1 -0
  76. package/dist/assets/architectureDiagram-2XIMDMQ5-BH6O4dvN.js +36 -0
  77. package/dist/assets/blockDiagram-WCTKOSBZ-2xmwdjpg.js +132 -0
  78. package/dist/assets/c4Diagram-IC4MRINW-BNmPRFJF.js +10 -0
  79. package/dist/assets/channel-CiySTNoJ.js +1 -0
  80. package/dist/assets/chunk-4BX2VUAB-DGQTvirp.js +1 -0
  81. package/dist/assets/chunk-55IACEB6-DNMAQAC_.js +1 -0
  82. package/dist/assets/chunk-FMBD7UC4-BJbVTQ5o.js +15 -0
  83. package/dist/assets/chunk-JSJVCQXG-BCxUL74A.js +1 -0
  84. package/dist/assets/chunk-KX2RTZJC-H7wWZOfz.js +1 -0
  85. package/dist/assets/chunk-NQ4KR5QH-BK4RlTQF.js +220 -0
  86. package/dist/assets/chunk-QZHKN3VN-0chxDV5g.js +1 -0
  87. package/dist/assets/chunk-WL4C6EOR-DexfQ-AV.js +189 -0
  88. package/dist/assets/classDiagram-VBA2DB6C-D7luWJQn.js +1 -0
  89. package/dist/assets/classDiagram-v2-RAHNMMFH-D7luWJQn.js +1 -0
  90. package/dist/assets/clone-ylgRbd3D.js +1 -0
  91. package/dist/assets/cose-bilkent-S5V4N54A-DS2IOCfZ.js +1 -0
  92. package/dist/assets/cytoscape.esm-CyJtwmzi.js +331 -0
  93. package/dist/assets/dagre-KLK3FWXG-BbSoTTa3.js +4 -0
  94. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  95. package/dist/assets/diagram-E7M64L7V-C9TvYgv0.js +24 -0
  96. package/dist/assets/diagram-IFDJBPK2-DowUMWrg.js +43 -0
  97. package/dist/assets/diagram-P4PSJMXO-BL6nrnQF.js +24 -0
  98. package/dist/assets/erDiagram-INFDFZHY-rXPRl8VM.js +70 -0
  99. package/dist/assets/flowDiagram-PKNHOUZH-DBRM99-W.js +162 -0
  100. package/dist/assets/ganttDiagram-A5KZAMGK-INcWFsBT.js +292 -0
  101. package/dist/assets/gitGraphDiagram-K3NZZRJ6-DMwpfE91.js +65 -0
  102. package/dist/assets/graph-DLQn37b-.js +1 -0
  103. package/dist/assets/index-BFFITMT8.js +650 -0
  104. package/dist/assets/index-H7f6VTz1.css +1 -0
  105. package/dist/assets/infoDiagram-LFFYTUFH-B0f4TWRM.js +2 -0
  106. package/dist/assets/init-Gi6I4Gst.js +1 -0
  107. package/dist/assets/ishikawaDiagram-PHBUUO56-CsU6XimZ.js +70 -0
  108. package/dist/assets/journeyDiagram-4ABVD52K-CQ7ibNib.js +139 -0
  109. package/dist/assets/kanban-definition-K7BYSVSG-DzEN7THt.js +89 -0
  110. package/dist/assets/katex-B1X10hvy.js +261 -0
  111. package/dist/assets/layout-C0dvb42R.js +1 -0
  112. package/dist/assets/linear-j4a8mGj7.js +1 -0
  113. package/dist/assets/mindmap-definition-YRQLILUH-DP8iEuCf.js +68 -0
  114. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  115. package/dist/assets/pieDiagram-SKSYHLDU-BpIAXgAm.js +30 -0
  116. package/dist/assets/quadrantDiagram-337W2JSQ-DrpXn5Eg.js +7 -0
  117. package/dist/assets/requirementDiagram-Z7DCOOCP-Bg7EwHlG.js +73 -0
  118. package/dist/assets/sankeyDiagram-WA2Y5GQK-BWagRs1F.js +10 -0
  119. package/dist/assets/sequenceDiagram-2WXFIKYE-q5jwhivG.js +145 -0
  120. package/dist/assets/stateDiagram-RAJIS63D-B_J9pE-2.js +1 -0
  121. package/dist/assets/stateDiagram-v2-FVOUBMTO-Q_1GcybB.js +1 -0
  122. package/dist/assets/timeline-definition-YZTLITO2-dv0jgQ0z.js +61 -0
  123. package/dist/assets/treemap-KZPCXAKY-Dt1dkIE7.js +162 -0
  124. package/dist/assets/vennDiagram-LZ73GAT5-BdO5RgRZ.js +34 -0
  125. package/dist/assets/xychartDiagram-JWTSCODW-CpDVe-8v.js +7 -0
  126. package/dist/index.html +23 -0
  127. package/export-docx.js +1583 -0
  128. package/init.js +353 -0
  129. package/manifest-cli.mjs +207 -0
  130. package/package.json +83 -0
  131. package/schemas/artifact.schema.json +1295 -0
  132. package/schemas/artifact.uischema.json +65 -0
  133. package/schemas/evaluation.schema.json +284 -0
  134. package/schemas/rubric.schema.json +383 -0
  135. package/serve.js +238 -0
@@ -0,0 +1,383 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/ThomasRohde/EAROS/main/standard/schemas/rubric.schema.json",
4
+ "title": "EAROS Rubric/Profile/Overlay Schema",
5
+ "type": "object",
6
+ "required": [
7
+ "rubric_id",
8
+ "version",
9
+ "kind",
10
+ "title",
11
+ "artifact_type",
12
+ "dimensions",
13
+ "scoring",
14
+ "outputs"
15
+ ],
16
+ "properties": {
17
+ "rubric_id": {
18
+ "type": "string",
19
+ "description": "Unique identifier e.g. EAROS-CORE-002"
20
+ },
21
+ "version": {
22
+ "type": "string",
23
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
24
+ },
25
+ "kind": {
26
+ "type": "string",
27
+ "enum": [
28
+ "core_rubric",
29
+ "profile",
30
+ "overlay"
31
+ ]
32
+ },
33
+ "title": {
34
+ "type": "string"
35
+ },
36
+ "status": {
37
+ "type": "string",
38
+ "enum": [
39
+ "draft",
40
+ "candidate",
41
+ "approved",
42
+ "deprecated"
43
+ ]
44
+ },
45
+ "effective_date": {
46
+ "type": "string",
47
+ "format": "date"
48
+ },
49
+ "next_review_date": {
50
+ "type": "string",
51
+ "format": "date"
52
+ },
53
+ "owner": {
54
+ "type": "string"
55
+ },
56
+ "artifact_type": {
57
+ "type": "string"
58
+ },
59
+ "inherits": {
60
+ "type": "array",
61
+ "items": {
62
+ "type": "string"
63
+ },
64
+ "description": "Rubric IDs this extends"
65
+ },
66
+ "purpose": {
67
+ "type": "array",
68
+ "items": {
69
+ "type": "string"
70
+ }
71
+ },
72
+ "stakeholders": {
73
+ "type": "array",
74
+ "items": {
75
+ "type": "string"
76
+ }
77
+ },
78
+ "viewpoints": {
79
+ "type": "array",
80
+ "items": {
81
+ "type": "string"
82
+ }
83
+ },
84
+ "applicability": {
85
+ "type": "object",
86
+ "description": "Conditions under which this rubric applies"
87
+ },
88
+ "design_method": {
89
+ "type": "string",
90
+ "enum": [
91
+ "decision_centred",
92
+ "viewpoint_centred",
93
+ "lifecycle_centred",
94
+ "risk_centred",
95
+ "pattern_library"
96
+ ],
97
+ "description": "v2.0: Which of the five profile design methods was used"
98
+ },
99
+ "dimensions": {
100
+ "type": "array",
101
+ "minItems": 1,
102
+ "items": {
103
+ "type": "object",
104
+ "required": [
105
+ "id",
106
+ "name",
107
+ "criteria"
108
+ ],
109
+ "properties": {
110
+ "id": {
111
+ "type": "string"
112
+ },
113
+ "name": {
114
+ "type": "string"
115
+ },
116
+ "description": {
117
+ "type": "string"
118
+ },
119
+ "weight": {
120
+ "type": "number",
121
+ "minimum": 0
122
+ },
123
+ "criteria": {
124
+ "type": "array",
125
+ "minItems": 1,
126
+ "items": {
127
+ "type": "object",
128
+ "required": [
129
+ "id",
130
+ "question",
131
+ "metric_type",
132
+ "scale",
133
+ "required_evidence",
134
+ "scoring_guide"
135
+ ],
136
+ "properties": {
137
+ "id": {
138
+ "type": "string"
139
+ },
140
+ "question": {
141
+ "type": "string"
142
+ },
143
+ "description": {
144
+ "type": "string"
145
+ },
146
+ "metric_type": {
147
+ "type": "string",
148
+ "enum": [
149
+ "ordinal",
150
+ "binary",
151
+ "count",
152
+ "presence",
153
+ "coverage"
154
+ ]
155
+ },
156
+ "scale": {
157
+ "oneOf": [
158
+ {
159
+ "type": "array"
160
+ },
161
+ {
162
+ "type": "object"
163
+ },
164
+ {
165
+ "type": "string"
166
+ }
167
+ ]
168
+ },
169
+ "weight": {
170
+ "type": "number"
171
+ },
172
+ "gate": {
173
+ "oneOf": [
174
+ {
175
+ "type": "boolean"
176
+ },
177
+ {
178
+ "type": "object",
179
+ "required": [
180
+ "enabled",
181
+ "severity"
182
+ ],
183
+ "properties": {
184
+ "enabled": {
185
+ "type": "boolean"
186
+ },
187
+ "severity": {
188
+ "type": "string",
189
+ "enum": [
190
+ "advisory",
191
+ "major",
192
+ "critical"
193
+ ]
194
+ },
195
+ "failure_effect": {
196
+ "type": "string"
197
+ }
198
+ }
199
+ }
200
+ ]
201
+ },
202
+ "required_evidence": {
203
+ "type": "array",
204
+ "items": {
205
+ "oneOf": [
206
+ {
207
+ "type": "string"
208
+ },
209
+ {
210
+ "type": "object"
211
+ }
212
+ ]
213
+ }
214
+ },
215
+ "scoring_guide": {
216
+ "type": "object",
217
+ "additionalProperties": {
218
+ "type": "string"
219
+ }
220
+ },
221
+ "anti_patterns": {
222
+ "type": "array",
223
+ "items": {
224
+ "type": "string"
225
+ }
226
+ },
227
+ "remediation_hints": {
228
+ "type": "array",
229
+ "items": {
230
+ "type": "string"
231
+ }
232
+ },
233
+ "examples": {
234
+ "type": "object",
235
+ "properties": {
236
+ "good": {
237
+ "type": "array",
238
+ "items": {
239
+ "type": "string"
240
+ }
241
+ },
242
+ "bad": {
243
+ "type": "array",
244
+ "items": {
245
+ "type": "string"
246
+ }
247
+ }
248
+ },
249
+ "description": "v2.0: Positive and negative examples for AI disambiguation"
250
+ },
251
+ "decision_tree": {
252
+ "type": "string",
253
+ "description": "v2.0: IF/THEN disambiguation logic for AI agents"
254
+ },
255
+ "tags": {
256
+ "type": "array",
257
+ "items": {
258
+ "type": "string"
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ },
267
+ "scoring": {
268
+ "type": "object",
269
+ "required": [
270
+ "scale",
271
+ "method",
272
+ "thresholds"
273
+ ],
274
+ "properties": {
275
+ "scale": {
276
+ "type": "string"
277
+ },
278
+ "agent_scale": {
279
+ "type": "string",
280
+ "description": "v2.0: Optional collapsed scale for pure agent evaluation"
281
+ },
282
+ "method": {
283
+ "type": "string"
284
+ },
285
+ "thresholds": {
286
+ "type": "object"
287
+ },
288
+ "na_policy": {
289
+ "type": "string"
290
+ },
291
+ "confidence_policy": {
292
+ "type": "string"
293
+ },
294
+ "reliability_targets": {
295
+ "type": "object",
296
+ "description": "v2.0: Inter-rater reliability targets",
297
+ "properties": {
298
+ "binary_agreement": {
299
+ "type": "string"
300
+ },
301
+ "ordinal_kappa": {
302
+ "type": "string"
303
+ },
304
+ "overall_correlation": {
305
+ "type": "string"
306
+ }
307
+ }
308
+ }
309
+ }
310
+ },
311
+ "outputs": {
312
+ "type": "object",
313
+ "required": [
314
+ "require_evidence_refs",
315
+ "require_confidence",
316
+ "require_actions"
317
+ ],
318
+ "properties": {
319
+ "require_evidence_refs": {
320
+ "type": "boolean"
321
+ },
322
+ "require_confidence": {
323
+ "type": "boolean"
324
+ },
325
+ "require_actions": {
326
+ "type": "boolean"
327
+ },
328
+ "require_evidence_class": {
329
+ "type": "boolean",
330
+ "description": "v2.0: Require observed/inferred/external classification"
331
+ },
332
+ "require_evidence_anchors": {
333
+ "type": "boolean",
334
+ "description": "v2.0: RULERS-style citation+quotation"
335
+ },
336
+ "formats": {
337
+ "type": "array",
338
+ "items": {
339
+ "type": "string"
340
+ }
341
+ }
342
+ }
343
+ },
344
+ "agent_evaluation": {
345
+ "type": "object",
346
+ "description": "v2.0: Agent-specific evaluation configuration",
347
+ "properties": {
348
+ "dag_steps": {
349
+ "type": "array",
350
+ "items": {
351
+ "type": "string"
352
+ }
353
+ },
354
+ "rubric_locked": {
355
+ "type": "boolean"
356
+ },
357
+ "rubric_lock_version": {
358
+ "type": "string"
359
+ },
360
+ "calibration_method": {
361
+ "type": "string",
362
+ "enum": [
363
+ "rulers_wasserstein",
364
+ "llm_rubric_network",
365
+ "manual"
366
+ ]
367
+ }
368
+ }
369
+ },
370
+ "calibration": {
371
+ "type": "object"
372
+ },
373
+ "change_log": {
374
+ "type": "array",
375
+ "items": {
376
+ "type": "object"
377
+ }
378
+ },
379
+ "metadata": {
380
+ "type": "object"
381
+ }
382
+ }
383
+ }
package/serve.js ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * EaROS standalone Express server.
3
+ * Serves the pre-built React app from dist/ and provides the API endpoints.
4
+ */
5
+ import express from 'express';
6
+ import { createServer } from 'http';
7
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
8
+ import { resolve, dirname } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import yaml from 'js-yaml';
11
+ import open from 'open';
12
+ import { exportToDocx, exportRubricToDocx, exportEvaluationToDocx } from './export-docx.js';
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ function findRepoRoot() {
15
+ const cwd = process.cwd();
16
+ if (existsSync(resolve(cwd, 'earos.manifest.yaml')))
17
+ return cwd;
18
+ // Dev fallback: two levels above tools/editor/
19
+ const devRoot = resolve(__dirname, '../..');
20
+ if (existsSync(resolve(devRoot, 'earos.manifest.yaml')))
21
+ return devRoot;
22
+ return cwd;
23
+ }
24
+ function safeRepoPath(repoRoot, rawPath) {
25
+ const decoded = decodeURIComponent(rawPath);
26
+ const abs = resolve(repoRoot, decoded);
27
+ if (!abs.startsWith(repoRoot))
28
+ return null;
29
+ return abs;
30
+ }
31
+ function findAvailablePort(preferred) {
32
+ return new Promise((resolvePort) => {
33
+ const probe = createServer();
34
+ probe.listen(preferred, () => {
35
+ const port = probe.address().port;
36
+ probe.close(() => resolvePort(port));
37
+ });
38
+ probe.on('error', () => {
39
+ // Port in use — let OS assign a random one
40
+ const fallback = createServer();
41
+ fallback.listen(0, () => {
42
+ const port = fallback.address().port;
43
+ fallback.close(() => resolvePort(port));
44
+ });
45
+ });
46
+ });
47
+ }
48
+ export async function startServer(fileArg) {
49
+ const REPO_ROOT = findRepoRoot();
50
+ const distDir = resolve(__dirname, 'dist');
51
+ if (!existsSync(distDir)) {
52
+ console.error('dist/ not found. Run: npm run build');
53
+ process.exit(1);
54
+ }
55
+ const app = express();
56
+ app.use(express.json({ limit: '25mb' }));
57
+ // GET /api/manifest or GET /api/files
58
+ const manifestHandler = (_req, res) => {
59
+ const manifestPath = resolve(REPO_ROOT, 'earos.manifest.yaml');
60
+ if (!existsSync(manifestPath)) {
61
+ res.status(404).json({ error: 'earos.manifest.yaml not found — run: earos manifest' });
62
+ }
63
+ else {
64
+ res.json(yaml.load(readFileSync(manifestPath, 'utf8')));
65
+ }
66
+ };
67
+ app.get('/api/manifest', manifestHandler);
68
+ app.get('/api/files', manifestHandler);
69
+ // GET /api/evaluations — recursively scan for evaluation files
70
+ function findEvaluationFiles(baseDir, prefix) {
71
+ const found = [];
72
+ if (!existsSync(baseDir))
73
+ return found;
74
+ try {
75
+ for (const entry of readdirSync(baseDir, { withFileTypes: true })) {
76
+ if (entry.isDirectory()) {
77
+ found.push(...findEvaluationFiles(resolve(baseDir, entry.name), `${prefix}${entry.name}/`));
78
+ }
79
+ else if (entry.name.endsWith('.evaluation.yaml') || entry.name === 'evaluation.yaml') {
80
+ found.push({ path: `${prefix}${entry.name}`, name: entry.name });
81
+ }
82
+ }
83
+ }
84
+ catch { /* skip unreadable dirs */ }
85
+ return found;
86
+ }
87
+ app.get('/api/evaluations', (_req, res) => {
88
+ const files = [];
89
+ for (const dir of ['examples', 'evaluations']) {
90
+ files.push(...findEvaluationFiles(resolve(REPO_ROOT, dir), `${dir}/`));
91
+ }
92
+ res.json({ files });
93
+ });
94
+ // GET /api/evaluations/summary — lightweight metadata per evaluation file
95
+ app.get('/api/evaluations/summary', (_req, res) => {
96
+ const files = [];
97
+ for (const dir of ['examples', 'evaluations']) {
98
+ files.push(...findEvaluationFiles(resolve(REPO_ROOT, dir), `${dir}/`));
99
+ }
100
+ const summaries = files.map((f) => {
101
+ const absPath = resolve(REPO_ROOT, f.path);
102
+ try {
103
+ const data = yaml.load(readFileSync(absPath, 'utf8'));
104
+ return {
105
+ path: f.path,
106
+ name: f.name,
107
+ overall_status: data?.overall_status ?? undefined,
108
+ overall_score: data?.overall_score ?? undefined,
109
+ evaluation_date: data?.evaluation_date ?? undefined,
110
+ title: data?.artifact_ref?.title ?? data?.artifact_id ?? f.name.replace(/\.evaluation\.yaml$|\.yaml$/, ''),
111
+ };
112
+ }
113
+ catch {
114
+ return { path: f.path, name: f.name, title: f.name };
115
+ }
116
+ });
117
+ res.json({ summaries });
118
+ });
119
+ // GET /api/file/:path (path may contain slashes)
120
+ app.get('/api/file/*', (req, res) => {
121
+ const rawPath = req.params[0];
122
+ const absPath = safeRepoPath(REPO_ROOT, rawPath);
123
+ if (!absPath) {
124
+ res.status(403).json({ error: 'Path outside repo root' });
125
+ return;
126
+ }
127
+ if (!existsSync(absPath)) {
128
+ res.status(404).json({ error: `File not found: ${rawPath}` });
129
+ return;
130
+ }
131
+ try {
132
+ res.json(yaml.load(readFileSync(absPath, 'utf8')));
133
+ }
134
+ catch (e) {
135
+ res.status(500).json({ error: String(e) });
136
+ }
137
+ });
138
+ // POST /api/file/:path
139
+ app.post('/api/file/*', (req, res) => {
140
+ const rawPath = req.params[0];
141
+ const absPath = safeRepoPath(REPO_ROOT, rawPath);
142
+ if (!absPath) {
143
+ res.status(403).json({ error: 'Path outside repo root' });
144
+ return;
145
+ }
146
+ try {
147
+ const content = yaml.dump(req.body, { lineWidth: 120, noRefs: true });
148
+ writeFileSync(absPath, content, 'utf8');
149
+ res.json({ ok: true });
150
+ }
151
+ catch (e) {
152
+ res.status(500).json({ error: String(e) });
153
+ }
154
+ });
155
+ // POST /api/export/docx — generate a Word document from artifact JSON
156
+ app.post('/api/export/docx', async (req, res) => {
157
+ try {
158
+ const payload = req.body;
159
+ const artifactData = payload?.artifactData ?? payload;
160
+ const renderedDiagrams = payload?.artifactData && payload.renderedDiagrams && typeof payload.renderedDiagrams === 'object'
161
+ ? payload.renderedDiagrams
162
+ : undefined;
163
+ if (!artifactData || typeof artifactData !== 'object') {
164
+ res.status(400).json({ error: 'Request body must be artifact JSON' });
165
+ return;
166
+ }
167
+ const buf = await exportToDocx(artifactData, renderedDiagrams);
168
+ const title = artifactData?.metadata?.title?.replace(/[^a-z0-9]/gi, '-').toLowerCase() ?? 'artifact';
169
+ res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
170
+ res.setHeader('Content-Disposition', `attachment; filename="${title}.docx"`);
171
+ res.send(buf);
172
+ }
173
+ catch (e) {
174
+ const message = e instanceof Error ? e.message : String(e);
175
+ if (message.startsWith('Missing browser-rendered diagrams for export:')) {
176
+ res.status(400).json({ error: message });
177
+ return;
178
+ }
179
+ res.status(500).json({ error: message });
180
+ }
181
+ });
182
+ // POST /api/export/docx/rubric — generate a Word document from rubric JSON
183
+ app.post('/api/export/docx/rubric', async (req, res) => {
184
+ try {
185
+ const data = req.body;
186
+ if (!data || typeof data !== 'object') {
187
+ res.status(400).json({ error: 'Request body must be rubric JSON' });
188
+ return;
189
+ }
190
+ const buf = await exportRubricToDocx(data);
191
+ const title = data?.title?.replace(/[^a-z0-9]/gi, '-').toLowerCase() ?? 'rubric';
192
+ res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
193
+ res.setHeader('Content-Disposition', `attachment; filename="${title}.docx"`);
194
+ res.send(buf);
195
+ }
196
+ catch (e) {
197
+ res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
198
+ }
199
+ });
200
+ // POST /api/export/docx/evaluation — generate a Word document from evaluation JSON
201
+ app.post('/api/export/docx/evaluation', async (req, res) => {
202
+ try {
203
+ const data = req.body;
204
+ if (!data || typeof data !== 'object') {
205
+ res.status(400).json({ error: 'Request body must be evaluation JSON' });
206
+ return;
207
+ }
208
+ const buf = await exportEvaluationToDocx(data);
209
+ const title = data?.artifact_ref?.title?.replace(/[^a-z0-9]/gi, '-').toLowerCase() ?? 'evaluation';
210
+ res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
211
+ res.setHeader('Content-Disposition', `attachment; filename="${title}-assessment.docx"`);
212
+ res.send(buf);
213
+ }
214
+ catch (e) {
215
+ res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
216
+ }
217
+ });
218
+ // Unknown API routes
219
+ app.all('/api/*', (_req, res) => {
220
+ res.status(404).json({ error: 'Unknown API route' });
221
+ });
222
+ // Workspace icon assets
223
+ app.use('/icons', express.static(resolve(REPO_ROOT, 'icons')));
224
+ // Static files — served after API routes
225
+ app.use(express.static(distDir));
226
+ // SPA fallback
227
+ app.get('*', (_req, res) => {
228
+ res.sendFile(resolve(distDir, 'index.html'));
229
+ });
230
+ const port = await findAvailablePort(process.env.PORT ? parseInt(process.env.PORT, 10) : 3000);
231
+ app.listen(port, () => {
232
+ const url = fileArg
233
+ ? `http://localhost:${port}?file=${encodeURIComponent(fileArg)}`
234
+ : `http://localhost:${port}`;
235
+ console.log(`EaROS Editor → ${url}`);
236
+ open(url);
237
+ });
238
+ }