@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
package/init.js ADDED
@@ -0,0 +1,353 @@
1
+ import { cpSync, copyFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { join, resolve, dirname, basename } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import JSZip from 'jszip';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ // ─── Alias specs per cloud ────────────────────────────────────────────────────
8
+ const AWS_ALIAS_SPECS = [
9
+ { alias: 'api-gateway', tokenVariants: [['api', 'gateway']], preferredPathTokens: ['service'] },
10
+ { alias: 'aws-cloud', tokenVariants: [['aws', 'cloud']], preferredPathTokens: ['group'] },
11
+ { alias: 'cloudfront', tokenVariants: [['cloudfront'], ['cloud', 'front']], preferredPathTokens: ['service'] },
12
+ { alias: 'cloudtrail', tokenVariants: [['cloudtrail'], ['cloud', 'trail']], preferredPathTokens: ['service'] },
13
+ { alias: 'cloudwatch', tokenVariants: [['cloudwatch'], ['cloud', 'watch']], preferredPathTokens: ['service'] },
14
+ { alias: 'cognito', tokenVariants: [['cognito']], preferredPathTokens: ['service'] },
15
+ { alias: 'data-firehose', tokenVariants: [['firehose'], ['kinesis', 'data', 'firehose']], preferredPathTokens: ['service'] },
16
+ { alias: 'dynamodb', tokenVariants: [['dynamodb'], ['dynamo', 'db']], preferredPathTokens: ['service'] },
17
+ { alias: 'ecs', tokenVariants: [['ecs'], ['elastic', 'container', 'service']], preferredPathTokens: ['service'] },
18
+ { alias: 'eks', tokenVariants: [['eks'], ['elastic', 'kubernetes']], preferredPathTokens: ['service'] },
19
+ { alias: 'elasticache', tokenVariants: [['elasticache']], preferredPathTokens: ['service'] },
20
+ { alias: 'eventbridge', tokenVariants: [['eventbridge'], ['event', 'bridge']], preferredPathTokens: ['service'] },
21
+ { alias: 'kinesis', tokenVariants: [['kinesis']], preferredPathTokens: ['service'] },
22
+ { alias: 'lambda', tokenVariants: [['lambda']], preferredPathTokens: ['service'] },
23
+ { alias: 'nat-gateway', tokenVariants: [['nat', 'gateway']], preferredPathTokens: ['resource'] },
24
+ { alias: 'private-subnet', tokenVariants: [['private', 'subnet']], preferredPathTokens: ['resource'] },
25
+ { alias: 'rds', tokenVariants: [['rds'], ['relational', 'database']], preferredPathTokens: ['service'] },
26
+ { alias: 'redshift', tokenVariants: [['redshift']], preferredPathTokens: ['service'] },
27
+ { alias: 'route53', tokenVariants: [['route', '53'], ['route53']], preferredPathTokens: ['service'] },
28
+ { alias: 's3', tokenVariants: [['s3'], ['simple', 'storage', 'service']], preferredPathTokens: ['service'] },
29
+ { alias: 'ses', tokenVariants: [['ses'], ['simple', 'email', 'service']], preferredPathTokens: ['service'] },
30
+ { alias: 'sns', tokenVariants: [['sns'], ['simple', 'notification', 'service']], preferredPathTokens: ['service'] },
31
+ { alias: 'sqs', tokenVariants: [['sqs'], ['simple', 'queue', 'service']], preferredPathTokens: ['service'] },
32
+ { alias: 'step-functions', tokenVariants: [['step', 'functions']], preferredPathTokens: ['service'] },
33
+ { alias: 'vpc', tokenVariants: [['vpc'], ['virtual', 'private', 'cloud']], preferredPathTokens: ['service'] },
34
+ { alias: 'waf', tokenVariants: [['waf'], ['web', 'application', 'firewall']], preferredPathTokens: ['service'] },
35
+ { alias: 'xray', tokenVariants: [['xray'], ['x', 'ray']], preferredPathTokens: ['service'] },
36
+ ];
37
+ const AZURE_ALIAS_SPECS = [
38
+ { alias: 'api-management', tokenVariants: [['api', 'management']], preferredPathTokens: ['icon', 'service'] },
39
+ { alias: 'app-service', tokenVariants: [['app', 'service'], ['app', 'services']], preferredPathTokens: ['icon', 'service'] },
40
+ { alias: 'application-gateway', tokenVariants: [['application', 'gateway']], preferredPathTokens: ['icon', 'service'] },
41
+ { alias: 'blob-storage', tokenVariants: [['blob', 'block'], ['blob', 'storage']], preferredPathTokens: ['icon', 'service'] },
42
+ { alias: 'cognitive-services', tokenVariants: [['cognitive', 'services']], preferredPathTokens: ['icon', 'service'] },
43
+ { alias: 'container-instances', tokenVariants: [['container', 'instances']], preferredPathTokens: ['icon', 'service'] },
44
+ { alias: 'cosmos-db', tokenVariants: [['cosmos', 'db'], ['cosmosdb']], preferredPathTokens: ['icon', 'service'] },
45
+ { alias: 'entra-id', tokenVariants: [['entra', 'id'], ['active', 'directory']], preferredPathTokens: ['icon', 'service'] },
46
+ { alias: 'event-grid', tokenVariants: [['event', 'grid']], preferredPathTokens: ['icon', 'service'] },
47
+ { alias: 'event-hubs', tokenVariants: [['event', 'hubs']], preferredPathTokens: ['icon', 'service'] },
48
+ { alias: 'front-door', tokenVariants: [['front', 'door']], preferredPathTokens: ['icon', 'service'] },
49
+ { alias: 'functions', tokenVariants: [['functions']], preferredPathTokens: ['icon', 'service'] },
50
+ { alias: 'key-vault', tokenVariants: [['key', 'vaults'], ['key', 'vault']], preferredPathTokens: ['icon', 'service'] },
51
+ { alias: 'kubernetes-service', tokenVariants: [['kubernetes', 'service'], ['aks']], preferredPathTokens: ['icon', 'service'] },
52
+ { alias: 'load-balancer', tokenVariants: [['load', 'balancer']], preferredPathTokens: ['icon', 'service'] },
53
+ { alias: 'logic-apps', tokenVariants: [['logic', 'apps']], preferredPathTokens: ['icon', 'service'] },
54
+ { alias: 'monitor', tokenVariants: [['monitor']], preferredPathTokens: ['icon', 'service'] },
55
+ { alias: 'redis-cache', tokenVariants: [['redis', 'cache'], ['cache', 'redis']], preferredPathTokens: ['icon', 'service'] },
56
+ { alias: 'service-bus', tokenVariants: [['service', 'bus']], preferredPathTokens: ['icon', 'service'] },
57
+ { alias: 'sql-database', tokenVariants: [['sql', 'database']], preferredPathTokens: ['icon', 'service'] },
58
+ { alias: 'storage-accounts', tokenVariants: [['storage', 'accounts']], preferredPathTokens: ['icon', 'service'] },
59
+ { alias: 'synapse-analytics', tokenVariants: [['synapse', 'analytics'], ['synapse']], preferredPathTokens: ['icon', 'service'] },
60
+ { alias: 'virtual-machine', tokenVariants: [['virtual', 'machine']], preferredPathTokens: ['icon', 'service'] },
61
+ { alias: 'virtual-network', tokenVariants: [['virtual', 'network']], preferredPathTokens: ['icon', 'service'] },
62
+ ];
63
+ const GCP_ALIAS_SPECS = [
64
+ { alias: 'api-gateway', tokenVariants: [['api', 'gateway']], preferredPathTokens: [] },
65
+ { alias: 'app-engine', tokenVariants: [['app', 'engine']], preferredPathTokens: [] },
66
+ { alias: 'bigquery', tokenVariants: [['bigquery'], ['big', 'query']], preferredPathTokens: [] },
67
+ { alias: 'bigtable', tokenVariants: [['bigtable'], ['big', 'table']], preferredPathTokens: [] },
68
+ { alias: 'cloud-armor', tokenVariants: [['cloud', 'armor']], preferredPathTokens: [] },
69
+ { alias: 'cloud-cdn', tokenVariants: [['cloud', 'cdn']], preferredPathTokens: [] },
70
+ { alias: 'cloud-dns', tokenVariants: [['cloud', 'dns']], preferredPathTokens: [] },
71
+ { alias: 'cloud-functions', tokenVariants: [['cloud', 'functions']], preferredPathTokens: [] },
72
+ { alias: 'cloud-load-balancing', tokenVariants: [['cloud', 'load', 'balancing'], ['load', 'balancing']], preferredPathTokens: [] },
73
+ { alias: 'cloud-logging', tokenVariants: [['cloud', 'logging']], preferredPathTokens: [] },
74
+ { alias: 'cloud-monitoring', tokenVariants: [['cloud', 'monitoring']], preferredPathTokens: [] },
75
+ { alias: 'cloud-run', tokenVariants: [['cloud', 'run']], preferredPathTokens: [] },
76
+ { alias: 'cloud-sql', tokenVariants: [['cloud', 'sql']], preferredPathTokens: [] },
77
+ { alias: 'cloud-storage', tokenVariants: [['cloud', 'storage']], preferredPathTokens: [] },
78
+ { alias: 'compute-engine', tokenVariants: [['compute', 'engine']], preferredPathTokens: [] },
79
+ { alias: 'dataflow', tokenVariants: [['dataflow'], ['data', 'flow']], preferredPathTokens: [] },
80
+ { alias: 'dataproc', tokenVariants: [['dataproc'], ['data', 'proc']], preferredPathTokens: [] },
81
+ { alias: 'firestore', tokenVariants: [['firestore'], ['fire', 'store']], preferredPathTokens: [] },
82
+ { alias: 'gke', tokenVariants: [['kubernetes', 'engine'], ['gke']], preferredPathTokens: [] },
83
+ { alias: 'iam', tokenVariants: [['iam'], ['identity', 'access']], preferredPathTokens: [] },
84
+ { alias: 'memorystore', tokenVariants: [['memorystore'], ['memory', 'store']], preferredPathTokens: [] },
85
+ { alias: 'pub-sub', tokenVariants: [['pub', 'sub'], ['pubsub']], preferredPathTokens: [] },
86
+ { alias: 'spanner', tokenVariants: [['spanner']], preferredPathTokens: [] },
87
+ { alias: 'vpc', tokenVariants: [['virtual', 'private', 'cloud'], ['vpc']], preferredPathTokens: [] },
88
+ ];
89
+ // ─── URL resolution per cloud ─────────────────────────────────────────────────
90
+ async function resolveAwsIconPackageUrl() {
91
+ const envUrl = process.env.EAROS_AWS_ICON_PACKAGE_URL;
92
+ if (envUrl)
93
+ return envUrl;
94
+ const pageUrl = process.env.EAROS_AWS_ICON_PAGE_URL ?? 'https://aws.amazon.com/architecture/icons/';
95
+ const response = await fetch(pageUrl, { redirect: 'follow' });
96
+ if (!response.ok) {
97
+ throw new Error(`Unable to load AWS icon page: HTTP ${response.status}`);
98
+ }
99
+ const html = await response.text();
100
+ const button2UrlMatch = html.match(/"button2URL":"([^"]+\.zip)"/i);
101
+ if (button2UrlMatch?.[1]) {
102
+ return new URL(button2UrlMatch[1], response.url).toString();
103
+ }
104
+ const anchorMatch = html.match(/<a[^>]+href="([^"]+)"[^>]*>\s*Icon package\s*<\/a>/i);
105
+ if (anchorMatch?.[1]) {
106
+ return new URL(anchorMatch[1], response.url).toString();
107
+ }
108
+ const zipMatches = [...html.matchAll(/https:\/\/d1\.awsstatic\.com\/[^"'\s>]+\.zip/gi)].map((match) => match[0]);
109
+ const candidateUrl = zipMatches.find((url) => /asset|icon/i.test(url)) ?? zipMatches[0];
110
+ if (candidateUrl)
111
+ return candidateUrl;
112
+ throw new Error(`Could not find the AWS icon package link on ${response.url}`);
113
+ }
114
+ async function resolveAzureIconPackageUrl() {
115
+ const envUrl = process.env.EAROS_AZURE_ICON_PACKAGE_URL;
116
+ if (envUrl)
117
+ return envUrl;
118
+ const pageUrl = process.env.EAROS_AZURE_ICON_PAGE_URL ?? 'https://learn.microsoft.com/en-us/azure/architecture/icons/';
119
+ const response = await fetch(pageUrl, { redirect: 'follow' });
120
+ if (!response.ok) {
121
+ throw new Error(`Unable to load Azure icon page: HTTP ${response.status}`);
122
+ }
123
+ const html = await response.text();
124
+ // Look for the download link — typically an azureedge.net or azure.com hosted ZIP
125
+ const downloadMatch = html.match(/href="([^"]+\.zip)"/i);
126
+ if (downloadMatch?.[1]) {
127
+ return new URL(downloadMatch[1], response.url).toString();
128
+ }
129
+ // Fallback: look for any ZIP URL in the page content
130
+ const zipMatches = [...html.matchAll(/https:\/\/[^"'\s>]+\.zip/gi)].map((match) => match[0]);
131
+ const candidateUrl = zipMatches.find((url) => /icon/i.test(url)) ?? zipMatches[0];
132
+ if (candidateUrl)
133
+ return candidateUrl;
134
+ throw new Error(`Could not find the Azure icon package link on ${response.url}`);
135
+ }
136
+ async function resolveGcpIconPackageUrl() {
137
+ const envUrl = process.env.EAROS_GCP_ICON_PACKAGE_URL;
138
+ if (envUrl)
139
+ return envUrl;
140
+ // GCP's icon page is JS-rendered, so we try the known stable URL first
141
+ const knownUrls = [
142
+ 'https://cloud.google.com/static/icons/files/google-cloud-icons.zip',
143
+ ];
144
+ for (const url of knownUrls) {
145
+ try {
146
+ const response = await fetch(url, { method: 'HEAD', redirect: 'follow' });
147
+ if (response.ok)
148
+ return url;
149
+ }
150
+ catch {
151
+ // try next
152
+ }
153
+ }
154
+ // Fallback: try to scrape the page
155
+ const pageUrl = process.env.EAROS_GCP_ICON_PAGE_URL ?? 'https://cloud.google.com/icons';
156
+ const response = await fetch(pageUrl, { redirect: 'follow' });
157
+ if (!response.ok) {
158
+ throw new Error(`Unable to load GCP icon page: HTTP ${response.status}`);
159
+ }
160
+ const html = await response.text();
161
+ const zipMatches = [...html.matchAll(/https:\/\/[^"'\s>]+\.zip/gi)].map((match) => match[0]);
162
+ const candidateUrl = zipMatches.find((url) => /icon/i.test(url)) ?? zipMatches[0];
163
+ if (candidateUrl)
164
+ return candidateUrl;
165
+ throw new Error(`Could not find the GCP icon package link on ${pageUrl}`);
166
+ }
167
+ // ─── Cloud package configs ────────────────────────────────────────────────────
168
+ const ICON_PACKAGES = [
169
+ {
170
+ name: 'AWS',
171
+ aliasDir: 'aws',
172
+ pageUrl: 'https://aws.amazon.com/architecture/icons/',
173
+ packageUrlEnvVar: 'EAROS_AWS_ICON_PACKAGE_URL',
174
+ aliasSpecs: AWS_ALIAS_SPECS,
175
+ resolveUrl: resolveAwsIconPackageUrl,
176
+ extraScore: (entry) => entry.fileTokens.has('arch') ? 5 : 0,
177
+ },
178
+ {
179
+ name: 'Azure',
180
+ aliasDir: 'azure',
181
+ pageUrl: 'https://learn.microsoft.com/en-us/azure/architecture/icons/',
182
+ packageUrlEnvVar: 'EAROS_AZURE_ICON_PACKAGE_URL',
183
+ aliasSpecs: AZURE_ALIAS_SPECS,
184
+ resolveUrl: resolveAzureIconPackageUrl,
185
+ },
186
+ {
187
+ name: 'GCP',
188
+ aliasDir: 'gcp',
189
+ pageUrl: 'https://cloud.google.com/icons',
190
+ packageUrlEnvVar: 'EAROS_GCP_ICON_PACKAGE_URL',
191
+ aliasSpecs: GCP_ALIAS_SPECS,
192
+ resolveUrl: resolveGcpIconPackageUrl,
193
+ filterEntry: (normalizedPath) => {
194
+ // GCP ZIPs contain SVG/, PNG/, and other dirs — only extract SVGs
195
+ const lower = normalizedPath.toLowerCase();
196
+ return lower.endsWith('.svg') || lower.endsWith('.pdf') || lower.endsWith('.txt') || lower.endsWith('.md');
197
+ },
198
+ },
199
+ ];
200
+ // ─── Generic icon download infrastructure ─────────────────────────────────────
201
+ function tokenizeForMatch(value) {
202
+ return new Set(value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).filter(Boolean));
203
+ }
204
+ function normalizeZipEntryPath(entryName) {
205
+ const normalized = entryName.replace(/\\/g, '/').replace(/^\/+/, '');
206
+ const segments = normalized.split('/').filter(Boolean);
207
+ if (!segments.length || segments.some((segment) => segment === '..' || segment.includes(':'))) {
208
+ return null;
209
+ }
210
+ return segments.join('/');
211
+ }
212
+ function buildExtractedIconEntry(normalizedPath, outputPath) {
213
+ return {
214
+ normalizedPath,
215
+ outputPath,
216
+ pathTokens: tokenizeForMatch(normalizedPath),
217
+ fileTokens: tokenizeForMatch(basename(normalizedPath)),
218
+ };
219
+ }
220
+ function matchesVariant(entry, tokenVariant) {
221
+ return tokenVariant.every((token) => entry.pathTokens.has(token) || entry.fileTokens.has(token));
222
+ }
223
+ function scoreAliasCandidate(entry, spec, config) {
224
+ const matchedVariant = spec.tokenVariants.find((tokenVariant) => matchesVariant(entry, tokenVariant));
225
+ if (!matchedVariant)
226
+ return Number.NEGATIVE_INFINITY;
227
+ let score = matchedVariant.length * 10;
228
+ if (spec.preferredPathTokens.length && spec.preferredPathTokens.every((token) => entry.pathTokens.has(token))) {
229
+ score += 40;
230
+ }
231
+ if (config.extraScore)
232
+ score += config.extraScore(entry);
233
+ // Prefer shorter paths (more specific matches)
234
+ score -= entry.normalizedPath.length / 1000;
235
+ return score;
236
+ }
237
+ function createIconAliases(iconsDir, extractedEntries, config) {
238
+ const aliasDir = join(iconsDir, config.aliasDir);
239
+ mkdirSync(aliasDir, { recursive: true });
240
+ let aliasCount = 0;
241
+ const missingAliases = [];
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) {
248
+ missingAliases.push(spec.alias);
249
+ continue;
250
+ }
251
+ copyFileSync(bestCandidate.entry.outputPath, join(aliasDir, `${spec.alias}.svg`));
252
+ aliasCount += 1;
253
+ }
254
+ return { aliasCount, missingAliases };
255
+ }
256
+ async function downloadIconPackage(targetDir, config) {
257
+ console.log(`Resolving ${config.name} icon package URL...`);
258
+ const packageUrl = await config.resolveUrl();
259
+ console.log(`Downloading ${config.name} icons from ${packageUrl}`);
260
+ const response = await fetch(packageUrl, { redirect: 'follow' });
261
+ if (!response.ok) {
262
+ throw new Error(`Unable to download ${config.name} icon package: HTTP ${response.status}`);
263
+ }
264
+ const zip = await JSZip.loadAsync(await response.arrayBuffer());
265
+ const iconsDir = join(targetDir, 'icons');
266
+ mkdirSync(iconsDir, { recursive: true });
267
+ let fileCount = 0;
268
+ const extractedEntries = [];
269
+ for (const [entryName, zipEntry] of Object.entries(zip.files)) {
270
+ if (zipEntry.dir)
271
+ continue;
272
+ const normalizedEntryPath = normalizeZipEntryPath(entryName);
273
+ if (!normalizedEntryPath)
274
+ continue;
275
+ if (config.filterEntry && !config.filterEntry(normalizedEntryPath))
276
+ continue;
277
+ const outputPath = join(iconsDir, normalizedEntryPath);
278
+ mkdirSync(dirname(outputPath), { recursive: true });
279
+ writeFileSync(outputPath, await zipEntry.async('nodebuffer'));
280
+ fileCount += 1;
281
+ if (normalizedEntryPath.toLowerCase().endsWith('.svg')) {
282
+ extractedEntries.push(buildExtractedIconEntry(normalizedEntryPath, outputPath));
283
+ }
284
+ }
285
+ const { aliasCount, missingAliases } = createIconAliases(iconsDir, extractedEntries, config);
286
+ return { name: config.name, packageUrl, fileCount, aliasCount, missingAliases };
287
+ }
288
+ // ─── Main ─────────────────────────────────────────────────────────────────────
289
+ export async function initWorkspace(targetDir, options = {}) {
290
+ const target = resolve(process.cwd(), targetDir);
291
+ // When compiled, init.js sits in tools/editor/ alongside assets/
292
+ const assetsDir = join(__dirname, 'assets', 'init');
293
+ const workspaceExists = existsSync(join(target, 'earos.manifest.yaml'));
294
+ if (!existsSync(assetsDir)) {
295
+ console.error('Asset directory not found. Run npm run build first.');
296
+ process.exit(1);
297
+ }
298
+ if (workspaceExists && !options.downloadIcons) {
299
+ console.error(`${target} already contains an EaROS workspace (earos.manifest.yaml exists).`);
300
+ process.exit(1);
301
+ }
302
+ if (!workspaceExists) {
303
+ mkdirSync(target, { recursive: true });
304
+ cpSync(assetsDir, target, { recursive: true });
305
+ }
306
+ else {
307
+ console.log(`EaROS workspace already exists at ${target}; downloading icons only.`);
308
+ }
309
+ let iconDownloadSummary = '';
310
+ if (options.downloadIcons) {
311
+ 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(', ')}`);
318
+ }
319
+ }
320
+ catch (error) {
321
+ console.error(` Failed to download ${config.name} icons: ${error.message}`);
322
+ }
323
+ }
324
+ if (results.length) {
325
+ const totalFiles = results.reduce((sum, r) => sum + r.fileCount, 0);
326
+ const totalAliases = results.reduce((sum, r) => sum + r.aliasCount, 0);
327
+ const aliasLines = results.map((r) => ` icons/${r.name.toLowerCase()}/ ${r.name} Mermaid icon aliases (${r.aliasCount} files)`).join('\n');
328
+ iconDownloadSummary = ` icons/ Architecture icon packages (${totalFiles} files)\n${aliasLines}\n`;
329
+ }
330
+ }
331
+ const isCurrentDir = targetDir === '.' || targetDir === './';
332
+ const cdStep = isCurrentDir ? '' : ` cd ${targetDir}\n`;
333
+ console.log(`
334
+ ✓ EaROS workspace initialized at: ${target}
335
+
336
+ Contents:
337
+ core/ Core meta-rubric (universal foundation)
338
+ profiles/ Artifact-specific profiles (5 included)
339
+ overlays/ Cross-cutting concern overlays (3 included)
340
+ standard/schemas/ JSON schemas for validation
341
+ templates/ Blank templates for new profiles and evaluations
342
+ evaluations/ Your evaluation records go here
343
+ calibration/ Calibration artifacts and results
344
+ .agents/skills/ All 10 EaROS skills for any AI coding agent
345
+ ${iconDownloadSummary} earos.manifest.yaml File inventory (single source of truth)
346
+ AGENTS.md Project guide for AI agents (agent-agnostic)
347
+
348
+ Next steps:
349
+ ${cdStep} earos Open the editor
350
+ earos validate core/core-meta-rubric.yaml Validate a file
351
+ earos manifest check Verify manifest integrity
352
+ `);
353
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * EAROS Manifest CLI (compiled JS — do not edit; source is manifest-cli.ts)
3
+ *
4
+ * Usage (via bin.js):
5
+ * earos manifest # regenerate
6
+ * earos manifest add <file> # add entry
7
+ * earos manifest check # verify consistency
8
+ *
9
+ * EAROS_REPO_ROOT env var is set by bin.js to the detected repo root.
10
+ */
11
+
12
+ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'fs'
13
+ import { resolve, dirname } from 'path'
14
+ import { fileURLToPath } from 'url'
15
+ import yaml from 'js-yaml'
16
+
17
+ const __dir = dirname(fileURLToPath(import.meta.url))
18
+ const REPO_ROOT = process.env.EAROS_REPO_ROOT ?? resolve(__dir, '../..')
19
+ const MANIFEST_PATH = resolve(REPO_ROOT, 'earos.manifest.yaml')
20
+
21
+ const SCAN_DIRS = {
22
+ core: 'core',
23
+ profiles: 'profiles',
24
+ overlays: 'overlays',
25
+ }
26
+
27
+ function readYaml(absPath) {
28
+ try {
29
+ return yaml.load(readFileSync(absPath, 'utf8'))
30
+ } catch {
31
+ return null
32
+ }
33
+ }
34
+
35
+ function scanDir(dir) {
36
+ const absDir = resolve(REPO_ROOT, dir)
37
+ if (!existsSync(absDir)) return []
38
+ return readdirSync(absDir)
39
+ .filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'))
40
+ .map((f) => {
41
+ const data = readYaml(resolve(absDir, f))
42
+ if (!data) return null
43
+ return {
44
+ path: `${dir}/${f}`,
45
+ rubric_id: data.rubric_id ?? undefined,
46
+ title: data.title ?? undefined,
47
+ artifact_type: data.artifact_type ?? undefined,
48
+ status: data.status ?? undefined,
49
+ }
50
+ })
51
+ .filter(Boolean)
52
+ }
53
+
54
+ function buildManifest() {
55
+ return {
56
+ kind: 'manifest',
57
+ version: '2.0.0',
58
+ generated: new Date().toISOString().slice(0, 10),
59
+ core: scanDir('core'),
60
+ profiles: scanDir('profiles'),
61
+ overlays: scanDir('overlays'),
62
+ schemas: [
63
+ { path: 'standard/schemas/rubric.schema.json', validates: ['core_rubric', 'profile', 'overlay'] },
64
+ { path: 'standard/schemas/evaluation.schema.json', validates: ['evaluation'] },
65
+ ],
66
+ templates: [
67
+ { path: 'templates/new-profile.template.yaml', purpose: 'scaffold for new profiles' },
68
+ { path: 'templates/evaluation-record.template.yaml', purpose: 'blank evaluation record' },
69
+ { path: 'templates/reference-architecture/Reference_Architecture_Template_v2.docx', purpose: 'author template for reference architectures' },
70
+ ],
71
+ scoring_tools: [
72
+ { path: 'tools/scoring-sheets/EAROS_Scoring_Sheet_v2.xlsx', purpose: 'general-purpose manual scoring (current)' },
73
+ { path: 'tools/scoring-sheets/EAROS_Scoring_Sheet.xlsx', purpose: 'general-purpose manual scoring (legacy)' },
74
+ { path: 'tools/scoring-sheets/EAROS_RefArch_Scoring_Sheet.xlsx', purpose: 'reference architecture scoring' },
75
+ ],
76
+ }
77
+ }
78
+
79
+ function writeManifest(manifest) {
80
+ writeFileSync(MANIFEST_PATH, yaml.dump(manifest, { lineWidth: 120 }), 'utf8')
81
+ }
82
+
83
+ function loadManifest() {
84
+ if (!existsSync(MANIFEST_PATH)) return null
85
+ return readYaml(MANIFEST_PATH)
86
+ }
87
+
88
+ // ─── CLI dispatch ──────────────────────────────────────────────────────────────
89
+
90
+ const subArgs = process.argv.slice(2)
91
+ const subCmd = subArgs[0]
92
+
93
+ if (!subCmd || subCmd === 'generate') {
94
+ const manifest = buildManifest()
95
+ writeManifest(manifest)
96
+ const counts = `core:${manifest.core?.length ?? 0} profiles:${manifest.profiles?.length ?? 0} overlays:${manifest.overlays?.length ?? 0}`
97
+ console.log(`✓ earos.manifest.yaml generated (${counts})`)
98
+ process.exit(0)
99
+ }
100
+
101
+ if (subCmd === 'add') {
102
+ const filePath = subArgs[1]
103
+ if (!filePath) {
104
+ console.error('Usage: earos manifest add <path/to/file.yaml>')
105
+ process.exit(1)
106
+ }
107
+ const manifest = loadManifest()
108
+ if (!manifest) {
109
+ console.error('No earos.manifest.yaml found. Run `earos manifest` first.')
110
+ process.exit(1)
111
+ }
112
+ const absPath = resolve(REPO_ROOT, filePath)
113
+ const data = readYaml(absPath)
114
+ if (!data) {
115
+ console.error(`Cannot read or parse: ${filePath}`)
116
+ process.exit(1)
117
+ }
118
+ const entry = {
119
+ path: filePath,
120
+ rubric_id: data.rubric_id,
121
+ title: data.title,
122
+ artifact_type: data.artifact_type,
123
+ status: data.status,
124
+ }
125
+ const kind = data.kind
126
+ if (kind === 'core_rubric') {
127
+ manifest.core = manifest.core ?? []
128
+ const idx = manifest.core.findIndex((e) => e.path === filePath)
129
+ if (idx >= 0) manifest.core[idx] = entry
130
+ else manifest.core.push(entry)
131
+ } else if (kind === 'profile') {
132
+ manifest.profiles = manifest.profiles ?? []
133
+ const idx = manifest.profiles.findIndex((e) => e.path === filePath)
134
+ if (idx >= 0) manifest.profiles[idx] = entry
135
+ else manifest.profiles.push(entry)
136
+ } else if (kind === 'overlay') {
137
+ manifest.overlays = manifest.overlays ?? []
138
+ const idx = manifest.overlays.findIndex((e) => e.path === filePath)
139
+ if (idx >= 0) manifest.overlays[idx] = entry
140
+ else manifest.overlays.push(entry)
141
+ } else {
142
+ console.error(`Unsupported kind: ${kind ?? '(none)'}. Expected core_rubric, profile, or overlay.`)
143
+ process.exit(1)
144
+ }
145
+ manifest.generated = new Date().toISOString().slice(0, 10)
146
+ writeManifest(manifest)
147
+ console.log(`✓ Added ${filePath} (${kind}, ${entry.rubric_id ?? 'no rubric_id'}) to manifest`)
148
+ process.exit(0)
149
+ }
150
+
151
+ if (subCmd === 'check') {
152
+ const manifest = loadManifest()
153
+ if (!manifest) {
154
+ console.error('No earos.manifest.yaml found. Run `earos manifest` first.')
155
+ process.exit(1)
156
+ }
157
+ const errors = []
158
+ const warnings = []
159
+
160
+ const allEntries = [
161
+ ...(manifest.core ?? []),
162
+ ...(manifest.profiles ?? []),
163
+ ...(manifest.overlays ?? []),
164
+ ]
165
+ for (const entry of allEntries) {
166
+ const absPath = resolve(REPO_ROOT, entry.path)
167
+ if (!existsSync(absPath)) {
168
+ errors.push(`MISSING on disk: ${entry.path}`)
169
+ } else {
170
+ const data = readYaml(absPath)
171
+ if (data?.rubric_id !== entry.rubric_id) {
172
+ warnings.push(`${entry.path}: rubric_id mismatch — manifest: ${entry.rubric_id}, file: ${data?.rubric_id}`)
173
+ }
174
+ }
175
+ }
176
+
177
+ for (const [section, dir] of Object.entries(SCAN_DIRS)) {
178
+ const absDir = resolve(REPO_ROOT, dir)
179
+ if (!existsSync(absDir)) continue
180
+ const files = readdirSync(absDir).filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'))
181
+ for (const f of files) {
182
+ const relPath = `${dir}/${f}`
183
+ const sectionData = manifest[section]
184
+ if (!sectionData?.some((e) => e.path === relPath)) {
185
+ errors.push(`NOT IN MANIFEST: ${relPath}`)
186
+ }
187
+ }
188
+ }
189
+
190
+ if (errors.length === 0 && warnings.length === 0) {
191
+ console.log('✓ Manifest is consistent with filesystem')
192
+ process.exit(0)
193
+ }
194
+ if (errors.length > 0) {
195
+ console.error(`✗ ${errors.length} error(s):`)
196
+ for (const e of errors) console.error(` ERROR: ${e}`)
197
+ }
198
+ if (warnings.length > 0) {
199
+ console.warn(`⚠ ${warnings.length} warning(s):`)
200
+ for (const w of warnings) console.warn(` WARN: ${w}`)
201
+ }
202
+ process.exit(errors.length > 0 ? 1 : 0)
203
+ }
204
+
205
+ console.error(`Unknown manifest subcommand: ${subCmd}`)
206
+ console.error('Usage: earos manifest [generate|add <file>|check]')
207
+ process.exit(1)
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@trohde/earos",
3
+ "version": "1.0.0",
4
+ "description": "Schema-driven editor and CLI for EaROS architecture assessment rubrics and evaluations",
5
+ "type": "module",
6
+ "bin": {
7
+ "earos": "./bin.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "vite",
11
+ "build": "tsc -p tsconfig.server.json && tsc && vite build && npm run build:assets",
12
+ "build:assets": "node scripts/build-assets.js",
13
+ "postbuild": "node scripts/postbuild.mjs",
14
+ "preview": "vite preview",
15
+ "prepublishOnly": "npm run build",
16
+ "prepare": "cd ../.. && git config core.hooksPath tools || true",
17
+ "release:patch": "npm version patch --no-git-tag-version && npm publish --access public",
18
+ "release:minor": "npm version minor --no-git-tag-version && npm publish --access public",
19
+ "release:major": "npm version major --no-git-tag-version && npm publish --access public",
20
+ "version:patch": "npm version patch --no-git-tag-version",
21
+ "version:minor": "npm version minor --no-git-tag-version",
22
+ "version:major": "npm version major --no-git-tag-version"
23
+ },
24
+ "files": [
25
+ "dist/",
26
+ "bin.js",
27
+ "serve.js",
28
+ "init.js",
29
+ "export-docx.js",
30
+ "manifest-cli.mjs",
31
+ "schemas/",
32
+ "assets/",
33
+ "README.md"
34
+ ],
35
+ "keywords": [
36
+ "earos",
37
+ "architecture",
38
+ "rubric",
39
+ "assessment",
40
+ "evaluation",
41
+ "yaml",
42
+ "editor"
43
+ ],
44
+ "author": "Thomas Rohde <rohde.thomas@gmail.com>",
45
+ "license": "CC-BY-4.0",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/ThomasRohde/EAROS"
49
+ },
50
+ "homepage": "https://github.com/ThomasRohde/EAROS/tree/master/tools/editor",
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "dependencies": {
55
+ "ajv": "^8.12.0",
56
+ "ajv-formats": "^3.0.1",
57
+ "docx": "^9.6.1",
58
+ "express": "^4.22.1",
59
+ "js-yaml": "^4.1.0",
60
+ "jszip": "^3.10.1",
61
+ "mermaid": "^11.13.0",
62
+ "open": "^10.1.0"
63
+ },
64
+ "devDependencies": {
65
+ "@emotion/react": "^11.11.4",
66
+ "@emotion/styled": "^11.11.0",
67
+ "@jsonforms/core": "^3.3.0",
68
+ "@jsonforms/material-renderers": "^3.3.0",
69
+ "@jsonforms/react": "^3.3.0",
70
+ "@mui/icons-material": "^7.0.0",
71
+ "@mui/material": "^7.0.0",
72
+ "@types/express": "^4.17.25",
73
+ "@types/js-yaml": "^4.0.9",
74
+ "@types/node": "^20.19.37",
75
+ "@types/react": "^18.3.1",
76
+ "@types/react-dom": "^18.3.1",
77
+ "@vitejs/plugin-react": "^4.2.1",
78
+ "esbuild": "^0.27.4",
79
+ "tsx": "^4.21.0",
80
+ "typescript": "^5.4.5",
81
+ "vite": "^5.2.8"
82
+ }
83
+ }