@trohde/earos 1.2.0 → 1.3.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 (69) hide show
  1. package/assets/init/docs/getting-started.md +1 -1
  2. package/assets/init/docs/onboarding/agent-assisted.md +19 -19
  3. package/assets/init/docs/onboarding/first-assessment.md +18 -18
  4. package/assets/init/docs/onboarding/governed-review.md +10 -10
  5. package/assets/init/docs/onboarding/overview.md +15 -15
  6. package/assets/init/docs/onboarding/scaling-optimization.md +13 -13
  7. package/assets/init/docs/plans/2026-03-23-001-refactor-site-review-findings-plan.md +195 -0
  8. package/assets/init/docs/plans/2026-03-23-002-refactor-cli-review-findings-plan.md +736 -0
  9. package/assets/init/docs/terminology.md +1 -1
  10. package/bin.js +156 -36
  11. package/dist/assets/{_basePickBy-PmSUrUsK.js → _basePickBy-BlC_TeV6.js} +1 -1
  12. package/dist/assets/{_baseUniq-HuZouVIz.js → _baseUniq-CVy7rcC1.js} +1 -1
  13. package/dist/assets/{arc-CJFxtF3d.js → arc-Cd8wvd7z.js} +1 -1
  14. package/dist/assets/{architectureDiagram-2XIMDMQ5-XA-oU2UG.js → architectureDiagram-2XIMDMQ5-D_f4_aMp.js} +1 -1
  15. package/dist/assets/{blockDiagram-WCTKOSBZ-Oxp-wAST.js → blockDiagram-WCTKOSBZ-B-y6N5--.js} +1 -1
  16. package/dist/assets/{c4Diagram-IC4MRINW-D8m5hQH9.js → c4Diagram-IC4MRINW-C3-v3oNT.js} +1 -1
  17. package/dist/assets/channel-BSC0F15G.js +1 -0
  18. package/dist/assets/{chunk-4BX2VUAB-D2kBTn2O.js → chunk-4BX2VUAB-CMPwQN83.js} +1 -1
  19. package/dist/assets/{chunk-55IACEB6-Dxqrf5oZ.js → chunk-55IACEB6-Bdkfhvrr.js} +1 -1
  20. package/dist/assets/{chunk-FMBD7UC4-DoOEFFQC.js → chunk-FMBD7UC4-ptKQX5uF.js} +1 -1
  21. package/dist/assets/{chunk-JSJVCQXG-BerphV2K.js → chunk-JSJVCQXG-DO0UU_OX.js} +1 -1
  22. package/dist/assets/{chunk-KX2RTZJC-CxUAqT05.js → chunk-KX2RTZJC-DRj2OZnD.js} +1 -1
  23. package/dist/assets/{chunk-NQ4KR5QH-fCqZgFkU.js → chunk-NQ4KR5QH-C4Nsf7ww.js} +1 -1
  24. package/dist/assets/{chunk-QZHKN3VN-HlpHnJEy.js → chunk-QZHKN3VN-B1GO0Nwy.js} +1 -1
  25. package/dist/assets/{chunk-WL4C6EOR-D9yxAHyd.js → chunk-WL4C6EOR-lFR6fjR8.js} +1 -1
  26. package/dist/assets/classDiagram-VBA2DB6C-BHDWMOEz.js +1 -0
  27. package/dist/assets/classDiagram-v2-RAHNMMFH-BHDWMOEz.js +1 -0
  28. package/dist/assets/clone-BdN-3iAD.js +1 -0
  29. package/dist/assets/{cose-bilkent-S5V4N54A-F5xOBvqW.js → cose-bilkent-S5V4N54A-IpR9mVIO.js} +1 -1
  30. package/dist/assets/{dagre-KLK3FWXG-CD3BTpHv.js → dagre-KLK3FWXG-B4YA6T7N.js} +1 -1
  31. package/dist/assets/{diagram-E7M64L7V-C3D9MCay.js → diagram-E7M64L7V-Do5l6es_.js} +1 -1
  32. package/dist/assets/{diagram-IFDJBPK2-zJBVM-GK.js → diagram-IFDJBPK2-D5MxfKVv.js} +1 -1
  33. package/dist/assets/{diagram-P4PSJMXO-BrmFZOLB.js → diagram-P4PSJMXO-Djr28EgW.js} +1 -1
  34. package/dist/assets/{erDiagram-INFDFZHY-aSMhKiV2.js → erDiagram-INFDFZHY-BuM-rbCL.js} +1 -1
  35. package/dist/assets/{flowDiagram-PKNHOUZH-DwgX7l8F.js → flowDiagram-PKNHOUZH-By3WGI7Q.js} +1 -1
  36. package/dist/assets/{ganttDiagram-A5KZAMGK-C57Hz6QW.js → ganttDiagram-A5KZAMGK-GLmBfK72.js} +1 -1
  37. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-CuchqqGh.js → gitGraphDiagram-K3NZZRJ6-BN0iXeIv.js} +1 -1
  38. package/dist/assets/{graph-CPFGBV5J.js → graph-CDzuMtjV.js} +1 -1
  39. package/dist/assets/{index-DMt1cpG6.js → index-DoeSN_Oe.js} +130 -130
  40. package/dist/assets/{infoDiagram-LFFYTUFH-Dd_5tfX7.js → infoDiagram-LFFYTUFH-C888gaFw.js} +1 -1
  41. package/dist/assets/{ishikawaDiagram-PHBUUO56-DwosSEvT.js → ishikawaDiagram-PHBUUO56-ChIO9DG-.js} +1 -1
  42. package/dist/assets/{journeyDiagram-4ABVD52K-BuCxcsX0.js → journeyDiagram-4ABVD52K-CufMUDcs.js} +1 -1
  43. package/dist/assets/{kanban-definition-K7BYSVSG-DF_1UCkW.js → kanban-definition-K7BYSVSG-BpsSVpX8.js} +1 -1
  44. package/dist/assets/{layout-DIcS6m1g.js → layout-B8RWVBSF.js} +1 -1
  45. package/dist/assets/{linear-BXkwBhoJ.js → linear-BJwxtq9r.js} +1 -1
  46. package/dist/assets/{mindmap-definition-YRQLILUH-DcDvYagd.js → mindmap-definition-YRQLILUH-C6WPimbf.js} +1 -1
  47. package/dist/assets/{pieDiagram-SKSYHLDU-BmeDeWDM.js → pieDiagram-SKSYHLDU-DeCGMWf8.js} +1 -1
  48. package/dist/assets/{quadrantDiagram-337W2JSQ-3zfjULUM.js → quadrantDiagram-337W2JSQ-D9TWaS83.js} +1 -1
  49. package/dist/assets/{requirementDiagram-Z7DCOOCP-B2wQMJpq.js → requirementDiagram-Z7DCOOCP-DTnuXlAq.js} +1 -1
  50. package/dist/assets/{sankeyDiagram-WA2Y5GQK-__kKlCTq.js → sankeyDiagram-WA2Y5GQK-B2dplCgD.js} +1 -1
  51. package/dist/assets/{sequenceDiagram-2WXFIKYE-B7O81Vih.js → sequenceDiagram-2WXFIKYE-cBvgSSju.js} +1 -1
  52. package/dist/assets/{stateDiagram-RAJIS63D-CcJaDrAK.js → stateDiagram-RAJIS63D-Cwr7VtSX.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-FVOUBMTO-B59h7VTZ.js +1 -0
  54. package/dist/assets/{timeline-definition-YZTLITO2-DSaQQqIU.js → timeline-definition-YZTLITO2-Dkp163fK.js} +1 -1
  55. package/dist/assets/{treemap-KZPCXAKY-9Hcrd8XD.js → treemap-KZPCXAKY-BUWHa5xU.js} +1 -1
  56. package/dist/assets/{vennDiagram-LZ73GAT5-BqHNyca2.js → vennDiagram-LZ73GAT5-BihD66ma.js} +1 -1
  57. package/dist/assets/{xychartDiagram-JWTSCODW-BqeYf6Fk.js → xychartDiagram-JWTSCODW-Cw4lPbuZ.js} +1 -1
  58. package/dist/index.html +1 -1
  59. package/export-docx.js +12 -4
  60. package/init.js +19 -14
  61. package/manifest-cli.mjs +32 -3
  62. package/package.json +3 -2
  63. package/serve.js +44 -19
  64. package/utils/export-markdown.js +486 -0
  65. package/dist/assets/channel-SoktpVBQ.js +0 -1
  66. package/dist/assets/classDiagram-VBA2DB6C-BT2AdZTe.js +0 -1
  67. package/dist/assets/classDiagram-v2-RAHNMMFH-BT2AdZTe.js +0 -1
  68. package/dist/assets/clone-DOjIfi5r.js +0 -1
  69. package/dist/assets/stateDiagram-v2-FVOUBMTO-B2goOPt-.js +0 -1
package/init.js CHANGED
@@ -240,15 +240,20 @@ function createIconAliases(iconsDir, extractedEntries, config) {
240
240
  let aliasCount = 0;
241
241
  const missingAliases = [];
242
242
  for (const spec of config.aliasSpecs) {
243
- const bestCandidate = extractedEntries
244
- .map((entry) => ({ entry, score: scoreAliasCandidate(entry, spec, config) }))
245
- .filter((candidate) => Number.isFinite(candidate.score))
246
- .sort((left, right) => right.score - left.score)[0];
247
- if (!bestCandidate) {
243
+ let bestScore = Number.NEGATIVE_INFINITY;
244
+ let bestEntry = null;
245
+ for (const entry of extractedEntries) {
246
+ const score = scoreAliasCandidate(entry, spec, config);
247
+ if (score > bestScore) {
248
+ bestScore = score;
249
+ bestEntry = entry;
250
+ }
251
+ }
252
+ if (!bestEntry) {
248
253
  missingAliases.push(spec.alias);
249
254
  continue;
250
255
  }
251
- copyFileSync(bestCandidate.entry.outputPath, join(aliasDir, `${spec.alias}.svg`));
256
+ copyFileSync(bestEntry.outputPath, join(aliasDir, `${spec.alias}.svg`));
252
257
  aliasCount += 1;
253
258
  }
254
259
  return { aliasCount, missingAliases };
@@ -309,16 +314,16 @@ export async function initWorkspace(targetDir, options = {}) {
309
314
  let iconDownloadSummary = '';
310
315
  if (options.downloadIcons) {
311
316
  const results = [];
312
- for (const config of ICON_PACKAGES) {
313
- try {
314
- const result = await downloadIconPackage(target, config);
315
- results.push(result);
316
- if (result.missingAliases.length) {
317
- console.warn(` Missing ${config.name} icon aliases: ${result.missingAliases.join(', ')}`);
317
+ const settled = await Promise.allSettled(ICON_PACKAGES.map(config => downloadIconPackage(target, config)));
318
+ for (const outcome of settled) {
319
+ if (outcome.status === 'fulfilled') {
320
+ results.push(outcome.value);
321
+ if (outcome.value.missingAliases.length) {
322
+ console.warn(` Missing ${outcome.value.name} icon aliases: ${outcome.value.missingAliases.join(', ')}`);
318
323
  }
319
324
  }
320
- catch (error) {
321
- console.error(` Failed to download ${config.name} icons: ${error.message}`);
325
+ else {
326
+ console.error(` Failed to download icons: ${outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)}`);
322
327
  }
323
328
  }
324
329
  if (results.length) {
package/manifest-cli.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  /**
2
- * EAROS Manifest CLI (compiled JS — do not edit; source is manifest-cli.ts)
2
+ * EAROS Manifest CLI
3
3
  *
4
4
  * Usage (via bin.js):
5
5
  * earos manifest # regenerate
6
6
  * earos manifest add <file> # add entry
7
- * earos manifest check # verify consistency
7
+ * earos manifest check [--json] # verify consistency
8
+ * earos manifest list [--json] # list manifest contents
8
9
  *
9
10
  * EAROS_REPO_ROOT env var is set by bin.js to the detected repo root.
10
11
  */
@@ -154,6 +155,7 @@ if (subCmd === 'check') {
154
155
  console.error('No earos.manifest.yaml found. Run `earos manifest` first.')
155
156
  process.exit(1)
156
157
  }
158
+ const jsonMode = subArgs.includes('--json')
157
159
  const errors = []
158
160
  const warnings = []
159
161
 
@@ -187,6 +189,11 @@ if (subCmd === 'check') {
187
189
  }
188
190
  }
189
191
 
192
+ if (jsonMode) {
193
+ process.stdout.write(JSON.stringify({ consistent: errors.length === 0, errors, warnings }, null, 2) + '\n')
194
+ process.exit(errors.length > 0 ? 1 : 0)
195
+ }
196
+
190
197
  if (errors.length === 0 && warnings.length === 0) {
191
198
  console.log('✓ Manifest is consistent with filesystem')
192
199
  process.exit(0)
@@ -202,6 +209,28 @@ if (subCmd === 'check') {
202
209
  process.exit(errors.length > 0 ? 1 : 0)
203
210
  }
204
211
 
212
+ if (subCmd === 'list') {
213
+ const manifest = loadManifest()
214
+ if (!manifest) {
215
+ console.error('No earos.manifest.yaml found. Run `earos manifest` first.')
216
+ process.exit(1)
217
+ }
218
+ if (subArgs.includes('--json')) {
219
+ process.stdout.write(JSON.stringify(manifest, null, 2) + '\n')
220
+ } else {
221
+ const sections = ['core', 'profiles', 'overlays']
222
+ for (const section of sections) {
223
+ const entries = manifest[section] ?? []
224
+ if (entries.length === 0) continue
225
+ console.log(`\n${section}:`)
226
+ for (const e of entries) {
227
+ console.log(` ${e.path} ${e.rubric_id ?? ''} ${e.title ?? ''}`)
228
+ }
229
+ }
230
+ }
231
+ process.exit(0)
232
+ }
233
+
205
234
  console.error(`Unknown manifest subcommand: ${subCmd}`)
206
- console.error('Usage: earos manifest [generate|add <file>|check]')
235
+ console.error('Usage: earos manifest [generate|add <file>|check|list]')
207
236
  process.exit(1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trohde/earos",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Schema-driven editor and CLI for EaROS architecture assessment rubrics and evaluations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "serve.js",
28
28
  "init.js",
29
29
  "export-docx.js",
30
+ "utils/",
30
31
  "manifest-cli.mjs",
31
32
  "schemas/",
32
33
  "assets/",
@@ -58,7 +59,6 @@
58
59
  "express": "^4.22.1",
59
60
  "js-yaml": "^4.1.0",
60
61
  "jszip": "^3.10.1",
61
- "mermaid": "^11.13.0",
62
62
  "open": "^10.1.0"
63
63
  },
64
64
  "devDependencies": {
@@ -76,6 +76,7 @@
76
76
  "@types/react-dom": "^18.3.1",
77
77
  "@vitejs/plugin-react": "^4.2.1",
78
78
  "esbuild": "^0.27.4",
79
+ "mermaid": "^11.13.0",
79
80
  "tsx": "^4.21.0",
80
81
  "typescript": "^5.4.5",
81
82
  "vite": "^5.2.8"
package/serve.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import express from 'express';
6
6
  import { createServer } from 'http';
7
7
  import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
8
- import { resolve, dirname } from 'path';
8
+ import { resolve, dirname, sep } from 'path';
9
9
  import { fileURLToPath } from 'url';
10
10
  import yaml from 'js-yaml';
11
11
  import open from 'open';
@@ -24,7 +24,7 @@ function findRepoRoot() {
24
24
  function safeRepoPath(repoRoot, rawPath) {
25
25
  const decoded = decodeURIComponent(rawPath);
26
26
  const abs = resolve(repoRoot, decoded);
27
- if (!abs.startsWith(repoRoot))
27
+ if (abs !== repoRoot && !abs.startsWith(repoRoot + sep))
28
28
  return null;
29
29
  return abs;
30
30
  }
@@ -45,6 +45,8 @@ function findAvailablePort(preferred) {
45
45
  });
46
46
  });
47
47
  }
48
+ let evalCache = null;
49
+ const EVAL_CACHE_TTL = 5000;
48
50
  export async function startServer(fileArg) {
49
51
  const REPO_ROOT = findRepoRoot();
50
52
  const distDir = resolve(__dirname, 'dist');
@@ -53,7 +55,12 @@ export async function startServer(fileArg) {
53
55
  process.exit(1);
54
56
  }
55
57
  const app = express();
56
- app.use(express.json({ limit: '25mb' }));
58
+ app.use(express.json({ limit: '1mb' }));
59
+ app.use((_req, res, next) => {
60
+ res.setHeader('X-Content-Type-Options', 'nosniff');
61
+ res.setHeader('X-Frame-Options', 'DENY');
62
+ next();
63
+ });
57
64
  // GET /api/manifest or GET /api/files
58
65
  const manifestHandler = (_req, res) => {
59
66
  const manifestPath = resolve(REPO_ROOT, 'earos.manifest.yaml');
@@ -84,19 +91,23 @@ export async function startServer(fileArg) {
84
91
  catch { /* skip unreadable dirs */ }
85
92
  return found;
86
93
  }
87
- app.get('/api/evaluations', (_req, res) => {
94
+ function getCachedEvaluationFiles(repoRoot) {
95
+ if (evalCache && Date.now() - evalCache.ts < EVAL_CACHE_TTL)
96
+ return evalCache.files;
88
97
  const files = [];
89
98
  for (const dir of ['examples', 'evaluations']) {
90
- files.push(...findEvaluationFiles(resolve(REPO_ROOT, dir), `${dir}/`));
99
+ files.push(...findEvaluationFiles(resolve(repoRoot, dir), `${dir}/`));
91
100
  }
101
+ evalCache = { files, ts: Date.now() };
102
+ return files;
103
+ }
104
+ app.get('/api/evaluations', (_req, res) => {
105
+ const files = getCachedEvaluationFiles(REPO_ROOT);
92
106
  res.json({ files });
93
107
  });
94
108
  // GET /api/evaluations/summary — lightweight metadata per evaluation file
95
109
  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
- }
110
+ const files = getCachedEvaluationFiles(REPO_ROOT);
100
111
  const summaries = files.map((f) => {
101
112
  const absPath = resolve(REPO_ROOT, f.path);
102
113
  try {
@@ -132,28 +143,38 @@ export async function startServer(fileArg) {
132
143
  res.json(yaml.load(readFileSync(absPath, 'utf8')));
133
144
  }
134
145
  catch (e) {
135
- res.status(500).json({ error: String(e) });
146
+ console.error('[API error]', e);
147
+ res.status(500).json({ error: 'Internal server error' });
136
148
  }
137
149
  });
138
150
  // POST /api/file/:path
139
- app.post('/api/file/*', (req, res) => {
151
+ app.post('/api/file/*', express.json({ limit: '5mb' }), (req, res) => {
140
152
  const rawPath = req.params[0];
141
153
  const absPath = safeRepoPath(REPO_ROOT, rawPath);
142
154
  if (!absPath) {
143
155
  res.status(403).json({ error: 'Path outside repo root' });
144
156
  return;
145
157
  }
158
+ if (!absPath.endsWith('.yaml') && !absPath.endsWith('.yml')) {
159
+ res.status(400).json({ error: 'Only YAML files can be written' });
160
+ return;
161
+ }
146
162
  try {
147
163
  const content = yaml.dump(req.body, { lineWidth: 120, noRefs: true });
148
164
  writeFileSync(absPath, content, 'utf8');
165
+ // Invalidate eval cache if writing an evaluation file
166
+ if (absPath.endsWith('.evaluation.yaml') || absPath.includes('/evaluations/')) {
167
+ evalCache = null;
168
+ }
149
169
  res.json({ ok: true });
150
170
  }
151
171
  catch (e) {
152
- res.status(500).json({ error: String(e) });
172
+ console.error('[API error]', e);
173
+ res.status(500).json({ error: 'Internal server error' });
153
174
  }
154
175
  });
155
176
  // POST /api/export/docx — generate a Word document from artifact JSON
156
- app.post('/api/export/docx', async (req, res) => {
177
+ app.post('/api/export/docx', express.json({ limit: '25mb' }), async (req, res) => {
157
178
  try {
158
179
  const payload = req.body;
159
180
  const artifactData = payload?.artifactData ?? payload;
@@ -176,11 +197,12 @@ export async function startServer(fileArg) {
176
197
  res.status(400).json({ error: message });
177
198
  return;
178
199
  }
179
- res.status(500).json({ error: message });
200
+ console.error('[API error]', e);
201
+ res.status(500).json({ error: 'Internal server error' });
180
202
  }
181
203
  });
182
204
  // POST /api/export/docx/rubric — generate a Word document from rubric JSON
183
- app.post('/api/export/docx/rubric', async (req, res) => {
205
+ app.post('/api/export/docx/rubric', express.json({ limit: '25mb' }), async (req, res) => {
184
206
  try {
185
207
  const data = req.body;
186
208
  if (!data || typeof data !== 'object') {
@@ -194,11 +216,12 @@ export async function startServer(fileArg) {
194
216
  res.send(buf);
195
217
  }
196
218
  catch (e) {
197
- res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
219
+ console.error('[API error]', e);
220
+ res.status(500).json({ error: 'Internal server error' });
198
221
  }
199
222
  });
200
223
  // POST /api/export/docx/evaluation — generate a Word document from evaluation JSON
201
- app.post('/api/export/docx/evaluation', async (req, res) => {
224
+ app.post('/api/export/docx/evaluation', express.json({ limit: '25mb' }), async (req, res) => {
202
225
  try {
203
226
  const data = req.body;
204
227
  if (!data || typeof data !== 'object') {
@@ -212,7 +235,8 @@ export async function startServer(fileArg) {
212
235
  res.send(buf);
213
236
  }
214
237
  catch (e) {
215
- res.status(500).json({ error: e instanceof Error ? e.message : String(e) });
238
+ console.error('[API error]', e);
239
+ res.status(500).json({ error: 'Internal server error' });
216
240
  }
217
241
  });
218
242
  // Unknown API routes
@@ -228,7 +252,8 @@ export async function startServer(fileArg) {
228
252
  res.sendFile(resolve(distDir, 'index.html'));
229
253
  });
230
254
  const port = await findAvailablePort(process.env.PORT ? parseInt(process.env.PORT, 10) : 3000);
231
- app.listen(port, () => {
255
+ const host = process.env.EAROS_HOST ?? '127.0.0.1';
256
+ app.listen(port, host, () => {
232
257
  const url = fileArg
233
258
  ? `http://localhost:${port}?file=${encodeURIComponent(fileArg)}`
234
259
  : `http://localhost:${port}`;