@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.
- package/README.md +156 -0
- package/assets/init/.agents/skills/earos-artifact-gen/SKILL.md +106 -0
- package/assets/init/.agents/skills/earos-artifact-gen/references/interview-guide.md +313 -0
- package/assets/init/.agents/skills/earos-artifact-gen/references/output-guide.md +367 -0
- package/assets/init/.agents/skills/earos-assess/SKILL.md +212 -0
- package/assets/init/.agents/skills/earos-assess/references/calibration-benchmarks.md +160 -0
- package/assets/init/.agents/skills/earos-assess/references/output-templates.md +311 -0
- package/assets/init/.agents/skills/earos-assess/references/scoring-protocol.md +281 -0
- package/assets/init/.agents/skills/earos-calibrate/SKILL.md +153 -0
- package/assets/init/.agents/skills/earos-calibrate/references/agreement-metrics.md +188 -0
- package/assets/init/.agents/skills/earos-calibrate/references/calibration-protocol.md +263 -0
- package/assets/init/.agents/skills/earos-create/SKILL.md +257 -0
- package/assets/init/.agents/skills/earos-create/references/criterion-writing-guide.md +268 -0
- package/assets/init/.agents/skills/earos-create/references/dependency-rules.md +193 -0
- package/assets/init/.agents/skills/earos-create/references/rubric-interview-guide.md +123 -0
- package/assets/init/.agents/skills/earos-create/references/validation-checklist.md +238 -0
- package/assets/init/.agents/skills/earos-profile-author/SKILL.md +251 -0
- package/assets/init/.agents/skills/earos-profile-author/references/criterion-writing-guide.md +280 -0
- package/assets/init/.agents/skills/earos-profile-author/references/design-methods.md +158 -0
- package/assets/init/.agents/skills/earos-profile-author/references/profile-checklist.md +173 -0
- package/assets/init/.agents/skills/earos-remediate/SKILL.md +118 -0
- package/assets/init/.agents/skills/earos-remediate/references/output-template.md +199 -0
- package/assets/init/.agents/skills/earos-remediate/references/remediation-patterns.md +330 -0
- package/assets/init/.agents/skills/earos-report/SKILL.md +85 -0
- package/assets/init/.agents/skills/earos-report/references/portfolio-template.md +181 -0
- package/assets/init/.agents/skills/earos-report/references/single-artifact-template.md +168 -0
- package/assets/init/.agents/skills/earos-review/SKILL.md +130 -0
- package/assets/init/.agents/skills/earos-review/references/challenge-patterns.md +163 -0
- package/assets/init/.agents/skills/earos-review/references/output-template.md +180 -0
- package/assets/init/.agents/skills/earos-template-fill/SKILL.md +177 -0
- package/assets/init/.agents/skills/earos-template-fill/references/evidence-writing-guide.md +186 -0
- package/assets/init/.agents/skills/earos-template-fill/references/section-rubric-mapping.md +200 -0
- package/assets/init/.agents/skills/earos-validate/SKILL.md +113 -0
- package/assets/init/.agents/skills/earos-validate/references/fix-patterns.md +281 -0
- package/assets/init/.agents/skills/earos-validate/references/validation-checks.md +287 -0
- package/assets/init/.claude/CLAUDE.md +4 -0
- package/assets/init/AGENTS.md +293 -0
- package/assets/init/CLAUDE.md +635 -0
- package/assets/init/README.md +507 -0
- package/assets/init/calibration/gold-set/.gitkeep +0 -0
- package/assets/init/calibration/results/.gitkeep +0 -0
- package/assets/init/core/core-meta-rubric.yaml +643 -0
- package/assets/init/docs/consistency-report.md +325 -0
- package/assets/init/docs/getting-started.md +194 -0
- package/assets/init/docs/profile-authoring-guide.md +51 -0
- package/assets/init/docs/terminology.md +126 -0
- package/assets/init/earos.manifest.yaml +104 -0
- package/assets/init/evaluations/.gitkeep +0 -0
- package/assets/init/examples/aws-event-driven-order-processing/artifact.yaml +2056 -0
- package/assets/init/examples/aws-event-driven-order-processing/evaluation.yaml +973 -0
- package/assets/init/examples/aws-event-driven-order-processing/report.md +244 -0
- package/assets/init/examples/example-solution-architecture.evaluation.yaml +136 -0
- package/assets/init/examples/multi-cloud-data-analytics/artifact.yaml +715 -0
- package/assets/init/overlays/data-governance.yaml +94 -0
- package/assets/init/overlays/regulatory.yaml +154 -0
- package/assets/init/overlays/security.yaml +92 -0
- package/assets/init/profiles/adr.yaml +225 -0
- package/assets/init/profiles/capability-map.yaml +223 -0
- package/assets/init/profiles/reference-architecture.yaml +426 -0
- package/assets/init/profiles/roadmap.yaml +205 -0
- package/assets/init/profiles/solution-architecture.yaml +227 -0
- package/assets/init/research/architecture-assessment-rubrics-research.docx +0 -0
- package/assets/init/research/architecture-assessment-rubrics-research.md +566 -0
- package/assets/init/research/reference-architecture-research.md +751 -0
- package/assets/init/standard/EAROS.md +1426 -0
- package/assets/init/standard/schemas/artifact.schema.json +1295 -0
- package/assets/init/standard/schemas/artifact.uischema.json +65 -0
- package/assets/init/standard/schemas/evaluation.schema.json +284 -0
- package/assets/init/standard/schemas/rubric.schema.json +383 -0
- package/assets/init/templates/evaluation-record.template.yaml +58 -0
- package/assets/init/templates/new-profile.template.yaml +65 -0
- package/bin.js +188 -0
- package/dist/assets/_basePickBy-BVu6YmSW.js +1 -0
- package/dist/assets/_baseUniq-CWRzQDz_.js +1 -0
- package/dist/assets/arc-CyDBhtDM.js +1 -0
- package/dist/assets/architectureDiagram-2XIMDMQ5-BH6O4dvN.js +36 -0
- package/dist/assets/blockDiagram-WCTKOSBZ-2xmwdjpg.js +132 -0
- package/dist/assets/c4Diagram-IC4MRINW-BNmPRFJF.js +10 -0
- package/dist/assets/channel-CiySTNoJ.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-DGQTvirp.js +1 -0
- package/dist/assets/chunk-55IACEB6-DNMAQAC_.js +1 -0
- package/dist/assets/chunk-FMBD7UC4-BJbVTQ5o.js +15 -0
- package/dist/assets/chunk-JSJVCQXG-BCxUL74A.js +1 -0
- package/dist/assets/chunk-KX2RTZJC-H7wWZOfz.js +1 -0
- package/dist/assets/chunk-NQ4KR5QH-BK4RlTQF.js +220 -0
- package/dist/assets/chunk-QZHKN3VN-0chxDV5g.js +1 -0
- package/dist/assets/chunk-WL4C6EOR-DexfQ-AV.js +189 -0
- package/dist/assets/classDiagram-VBA2DB6C-D7luWJQn.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-D7luWJQn.js +1 -0
- package/dist/assets/clone-ylgRbd3D.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-DS2IOCfZ.js +1 -0
- package/dist/assets/cytoscape.esm-CyJtwmzi.js +331 -0
- package/dist/assets/dagre-KLK3FWXG-BbSoTTa3.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-E7M64L7V-C9TvYgv0.js +24 -0
- package/dist/assets/diagram-IFDJBPK2-DowUMWrg.js +43 -0
- package/dist/assets/diagram-P4PSJMXO-BL6nrnQF.js +24 -0
- package/dist/assets/erDiagram-INFDFZHY-rXPRl8VM.js +70 -0
- package/dist/assets/flowDiagram-PKNHOUZH-DBRM99-W.js +162 -0
- package/dist/assets/ganttDiagram-A5KZAMGK-INcWFsBT.js +292 -0
- package/dist/assets/gitGraphDiagram-K3NZZRJ6-DMwpfE91.js +65 -0
- package/dist/assets/graph-DLQn37b-.js +1 -0
- package/dist/assets/index-BFFITMT8.js +650 -0
- package/dist/assets/index-H7f6VTz1.css +1 -0
- package/dist/assets/infoDiagram-LFFYTUFH-B0f4TWRM.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-PHBUUO56-CsU6XimZ.js +70 -0
- package/dist/assets/journeyDiagram-4ABVD52K-CQ7ibNib.js +139 -0
- package/dist/assets/kanban-definition-K7BYSVSG-DzEN7THt.js +89 -0
- package/dist/assets/katex-B1X10hvy.js +261 -0
- package/dist/assets/layout-C0dvb42R.js +1 -0
- package/dist/assets/linear-j4a8mGj7.js +1 -0
- package/dist/assets/mindmap-definition-YRQLILUH-DP8iEuCf.js +68 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pieDiagram-SKSYHLDU-BpIAXgAm.js +30 -0
- package/dist/assets/quadrantDiagram-337W2JSQ-DrpXn5Eg.js +7 -0
- package/dist/assets/requirementDiagram-Z7DCOOCP-Bg7EwHlG.js +73 -0
- package/dist/assets/sankeyDiagram-WA2Y5GQK-BWagRs1F.js +10 -0
- package/dist/assets/sequenceDiagram-2WXFIKYE-q5jwhivG.js +145 -0
- package/dist/assets/stateDiagram-RAJIS63D-B_J9pE-2.js +1 -0
- package/dist/assets/stateDiagram-v2-FVOUBMTO-Q_1GcybB.js +1 -0
- package/dist/assets/timeline-definition-YZTLITO2-dv0jgQ0z.js +61 -0
- package/dist/assets/treemap-KZPCXAKY-Dt1dkIE7.js +162 -0
- package/dist/assets/vennDiagram-LZ73GAT5-BdO5RgRZ.js +34 -0
- package/dist/assets/xychartDiagram-JWTSCODW-CpDVe-8v.js +7 -0
- package/dist/index.html +23 -0
- package/export-docx.js +1583 -0
- package/init.js +353 -0
- package/manifest-cli.mjs +207 -0
- package/package.json +83 -0
- package/schemas/artifact.schema.json +1295 -0
- package/schemas/artifact.uischema.json +65 -0
- package/schemas/evaluation.schema.json +284 -0
- package/schemas/rubric.schema.json +383 -0
- 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
|
+
}
|
package/manifest-cli.mjs
ADDED
|
@@ -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
|
+
}
|