@trohde/earos 1.1.2 → 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.
- package/assets/init/docs/getting-started.md +1 -1
- package/assets/init/docs/onboarding/agent-assisted.md +19 -19
- package/assets/init/docs/onboarding/first-assessment.md +18 -18
- package/assets/init/docs/onboarding/governed-review.md +10 -10
- package/assets/init/docs/onboarding/overview.md +15 -15
- package/assets/init/docs/onboarding/scaling-optimization.md +13 -13
- package/assets/init/docs/plans/2026-03-23-001-refactor-site-review-findings-plan.md +195 -0
- package/assets/init/docs/plans/2026-03-23-002-refactor-cli-review-findings-plan.md +736 -0
- package/assets/init/docs/profile-authoring-guide.md +5 -9
- package/assets/init/docs/terminology.md +1 -1
- package/bin.js +156 -36
- package/dist/assets/{_basePickBy-PmSUrUsK.js → _basePickBy-BlC_TeV6.js} +1 -1
- package/dist/assets/{_baseUniq-HuZouVIz.js → _baseUniq-CVy7rcC1.js} +1 -1
- package/dist/assets/{arc-CJFxtF3d.js → arc-Cd8wvd7z.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-XA-oU2UG.js → architectureDiagram-2XIMDMQ5-D_f4_aMp.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-Oxp-wAST.js → blockDiagram-WCTKOSBZ-B-y6N5--.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-D8m5hQH9.js → c4Diagram-IC4MRINW-C3-v3oNT.js} +1 -1
- package/dist/assets/channel-BSC0F15G.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-D2kBTn2O.js → chunk-4BX2VUAB-CMPwQN83.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Dxqrf5oZ.js → chunk-55IACEB6-Bdkfhvrr.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DoOEFFQC.js → chunk-FMBD7UC4-ptKQX5uF.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-BerphV2K.js → chunk-JSJVCQXG-DO0UU_OX.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-CxUAqT05.js → chunk-KX2RTZJC-DRj2OZnD.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-fCqZgFkU.js → chunk-NQ4KR5QH-C4Nsf7ww.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-HlpHnJEy.js → chunk-QZHKN3VN-B1GO0Nwy.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-D9yxAHyd.js → chunk-WL4C6EOR-lFR6fjR8.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-BHDWMOEz.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-BHDWMOEz.js +1 -0
- package/dist/assets/clone-BdN-3iAD.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-F5xOBvqW.js → cose-bilkent-S5V4N54A-IpR9mVIO.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-CD3BTpHv.js → dagre-KLK3FWXG-B4YA6T7N.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-C3D9MCay.js → diagram-E7M64L7V-Do5l6es_.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-zJBVM-GK.js → diagram-IFDJBPK2-D5MxfKVv.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-BrmFZOLB.js → diagram-P4PSJMXO-Djr28EgW.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-aSMhKiV2.js → erDiagram-INFDFZHY-BuM-rbCL.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-DwgX7l8F.js → flowDiagram-PKNHOUZH-By3WGI7Q.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-C57Hz6QW.js → ganttDiagram-A5KZAMGK-GLmBfK72.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-CuchqqGh.js → gitGraphDiagram-K3NZZRJ6-BN0iXeIv.js} +1 -1
- package/dist/assets/{graph-CPFGBV5J.js → graph-CDzuMtjV.js} +1 -1
- package/dist/assets/{index-DMt1cpG6.js → index-DoeSN_Oe.js} +130 -130
- package/dist/assets/{infoDiagram-LFFYTUFH-Dd_5tfX7.js → infoDiagram-LFFYTUFH-C888gaFw.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-DwosSEvT.js → ishikawaDiagram-PHBUUO56-ChIO9DG-.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-BuCxcsX0.js → journeyDiagram-4ABVD52K-CufMUDcs.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-DF_1UCkW.js → kanban-definition-K7BYSVSG-BpsSVpX8.js} +1 -1
- package/dist/assets/{layout-DIcS6m1g.js → layout-B8RWVBSF.js} +1 -1
- package/dist/assets/{linear-BXkwBhoJ.js → linear-BJwxtq9r.js} +1 -1
- package/dist/assets/{mindmap-definition-YRQLILUH-DcDvYagd.js → mindmap-definition-YRQLILUH-C6WPimbf.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-BmeDeWDM.js → pieDiagram-SKSYHLDU-DeCGMWf8.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-3zfjULUM.js → quadrantDiagram-337W2JSQ-D9TWaS83.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-B2wQMJpq.js → requirementDiagram-Z7DCOOCP-DTnuXlAq.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-__kKlCTq.js → sankeyDiagram-WA2Y5GQK-B2dplCgD.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-B7O81Vih.js → sequenceDiagram-2WXFIKYE-cBvgSSju.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-CcJaDrAK.js → stateDiagram-RAJIS63D-Cwr7VtSX.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-B59h7VTZ.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-DSaQQqIU.js → timeline-definition-YZTLITO2-Dkp163fK.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-9Hcrd8XD.js → treemap-KZPCXAKY-BUWHa5xU.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-BqHNyca2.js → vennDiagram-LZ73GAT5-BihD66ma.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-BqeYf6Fk.js → xychartDiagram-JWTSCODW-Cw4lPbuZ.js} +1 -1
- package/dist/index.html +1 -1
- package/export-docx.js +12 -4
- package/init.js +19 -14
- package/manifest-cli.mjs +32 -3
- package/package.json +3 -2
- package/serve.js +44 -19
- package/utils/export-markdown.js +486 -0
- package/dist/assets/channel-SoktpVBQ.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-BT2AdZTe.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-BT2AdZTe.js +0 -1
- package/dist/assets/clone-DOjIfi5r.js +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-B2goOPt-.js +0 -1
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "refactor: Address 28 EaROS CLI code review findings"
|
|
3
|
+
type: refactor
|
|
4
|
+
status: completed
|
|
5
|
+
date: 2026-03-23
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# refactor: Address 28 EaROS CLI Code Review Findings
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
A comprehensive 6-agent code review of the EaROS CLI (`@trohde/earos` v1.2.0 at `tools/editor/`) identified 28 findings across security, performance, architecture, type safety, agent parity, and simplification. This plan organizes them into 5 phases ordered by risk and dependency, with each phase independently shippable as a version bump.
|
|
13
|
+
|
|
14
|
+
**Review agents used:** Security Sentinel, Performance Oracle, Architecture Strategist, Kieran TypeScript Reviewer, Agent-Native Reviewer, Code Simplicity Reviewer.
|
|
15
|
+
|
|
16
|
+
## Problem Statement
|
|
17
|
+
|
|
18
|
+
The CLI works correctly for its current user base but has:
|
|
19
|
+
- **Security gaps** (path traversal bypass, network-exposed API, no security headers) that could allow file read/write from adjacent machines or directories
|
|
20
|
+
- **Dead code** (`cli.ts`, `manifest-cli.ts`) creating contributor confusion
|
|
21
|
+
- **Agent parity gaps** — the CLI cannot export rubrics/evaluations to DOCX, has no `--json` output, and lacks evaluation listing without the server
|
|
22
|
+
- **Performance inefficiencies** — sequential icon downloads, uncached filesystem scans
|
|
23
|
+
- **Type safety debt** — `strict: false`, 49+ `any` usages, silent validation bypass
|
|
24
|
+
|
|
25
|
+
## Proposed Solution
|
|
26
|
+
|
|
27
|
+
Five phases, each independently committable and publishable:
|
|
28
|
+
|
|
29
|
+
1. **Security Hardening** — fix path traversal, bind to localhost, add headers (patch bump)
|
|
30
|
+
2. **Dead Code & Simplification** — delete dead files, extract shared utils (patch bump)
|
|
31
|
+
3. **CLI Agent Parity** — extend export, add `--json` flags, new list commands (minor bump)
|
|
32
|
+
4. **Performance** — parallelize downloads, lazy-load schema (patch bump)
|
|
33
|
+
5. **Code Quality** — enable strict mode, de-duplicate, move mermaid dep (patch bump)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Technical Approach
|
|
38
|
+
|
|
39
|
+
### Phase 1: Security Hardening
|
|
40
|
+
|
|
41
|
+
**Estimated effort:** Small (1-2 hours). All changes are in `bin.js` and `serve.ts`/`serve.js`.
|
|
42
|
+
|
|
43
|
+
#### 1.1 Fix `safeRepoPath()` path traversal bypass
|
|
44
|
+
|
|
45
|
+
**File:** `tools/editor/src/serve.ts:28-33`, compiled to `tools/editor/serve.js:24-30`
|
|
46
|
+
|
|
47
|
+
The current `abs.startsWith(repoRoot)` check allows access to sibling directories whose names share the same prefix (e.g., `EAROS-private/` when repo root is `EAROS`).
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// serve.ts — BEFORE
|
|
51
|
+
function safeRepoPath(repoRoot: string, rawPath: string): string | null {
|
|
52
|
+
const decoded = decodeURIComponent(rawPath)
|
|
53
|
+
const abs = resolve(repoRoot, decoded)
|
|
54
|
+
if (!abs.startsWith(repoRoot)) return null
|
|
55
|
+
return abs
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// serve.ts — AFTER
|
|
59
|
+
import { sep } from 'path'
|
|
60
|
+
|
|
61
|
+
function safeRepoPath(repoRoot: string, rawPath: string): string | null {
|
|
62
|
+
const decoded = decodeURIComponent(rawPath)
|
|
63
|
+
const abs = resolve(repoRoot, decoded)
|
|
64
|
+
// Append separator to prevent prefix collisions (e.g., EAROS vs EAROS-private)
|
|
65
|
+
if (abs !== repoRoot && !abs.startsWith(repoRoot + sep)) return null
|
|
66
|
+
return abs
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Additionally, restrict `POST /api/file/*` writes to YAML files only:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// serve.ts — POST handler, add after safeRepoPath check
|
|
74
|
+
if (!absPath.endsWith('.yaml') && !absPath.endsWith('.yml')) {
|
|
75
|
+
res.status(400).json({ error: 'Only YAML files can be written' })
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- [ ] Fix `safeRepoPath()` with trailing separator — `serve.ts:28-33`
|
|
81
|
+
- [ ] Restrict POST writes to `.yaml`/`.yml` extensions — `serve.ts:140`
|
|
82
|
+
- [ ] Apply the same fixes to compiled `serve.js`
|
|
83
|
+
|
|
84
|
+
#### 1.2 Bind Express to localhost
|
|
85
|
+
|
|
86
|
+
**File:** `tools/editor/src/serve.ts:239`, compiled to `tools/editor/serve.js:231`
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// BEFORE
|
|
90
|
+
app.listen(port, () => { ... })
|
|
91
|
+
|
|
92
|
+
// AFTER — default to localhost, allow override for Docker/WSL2
|
|
93
|
+
const host = process.env.EAROS_HOST ?? '127.0.0.1'
|
|
94
|
+
app.listen(port, host, () => { ... })
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- [ ] Bind to `127.0.0.1` with `EAROS_HOST` env var override — `serve.ts:239`
|
|
98
|
+
- [ ] Update help text in `bin.js` to document `EAROS_HOST` — `bin.js:81-92`
|
|
99
|
+
|
|
100
|
+
#### 1.3 Fix Mermaid image path traversal
|
|
101
|
+
|
|
102
|
+
**File:** `tools/editor/src/export-docx.ts` (search for `resolveLocalMermaidImage`), compiled to `export-docx.js:48-56`
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// AFTER — add containment check
|
|
106
|
+
function resolveLocalMermaidImage(assetPath: string): string | null {
|
|
107
|
+
const relativePath = assetPath.replace(/^\/+/, '')
|
|
108
|
+
for (const baseDir of LOCAL_MERMAID_IMAGE_DIRS) {
|
|
109
|
+
const candidate = resolve(baseDir, relativePath)
|
|
110
|
+
if (!candidate.startsWith(baseDir + sep)) continue // containment check
|
|
111
|
+
if (existsSync(candidate)) return candidate
|
|
112
|
+
}
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- [ ] Add containment check to `resolveLocalMermaidImage` — `export-docx.ts`
|
|
118
|
+
|
|
119
|
+
#### 1.4 Add security headers
|
|
120
|
+
|
|
121
|
+
**File:** `tools/editor/src/serve.ts`, after `app.use(express.json(...))`
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Security headers (no dependency needed — manual is fine for a local tool)
|
|
125
|
+
app.use((_req, res, next) => {
|
|
126
|
+
res.setHeader('X-Content-Type-Options', 'nosniff')
|
|
127
|
+
res.setHeader('X-Frame-Options', 'DENY')
|
|
128
|
+
next()
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- [ ] Add security headers middleware — `serve.ts`, after line 63
|
|
133
|
+
|
|
134
|
+
#### 1.5 Sanitize error messages
|
|
135
|
+
|
|
136
|
+
Replace `String(e)` in API error responses with generic messages:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// BEFORE
|
|
140
|
+
res.status(500).json({ error: String(e) })
|
|
141
|
+
|
|
142
|
+
// AFTER
|
|
143
|
+
console.error('[API error]', e)
|
|
144
|
+
res.status(500).json({ error: 'Internal server error' })
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Apply to all 5 error handlers in `serve.ts` (lines 134, 149, 179, 197, 215). Keep the 400-level errors as-is (they return controlled messages, not exception details).
|
|
148
|
+
|
|
149
|
+
- [ ] Sanitize 500-level error responses in all API handlers — `serve.ts`
|
|
150
|
+
|
|
151
|
+
#### 1.6 Scope JSON body limit
|
|
152
|
+
|
|
153
|
+
The 25MB limit is only needed by the DOCX export routes. `POST /api/file/*` could receive large artifacts, so use 5MB there.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Global default
|
|
157
|
+
app.use(express.json({ limit: '1mb' }))
|
|
158
|
+
|
|
159
|
+
// Per-route overrides
|
|
160
|
+
const largeBody = express.json({ limit: '25mb' })
|
|
161
|
+
app.post('/api/export/docx', largeBody, async (req, res) => { ... })
|
|
162
|
+
app.post('/api/export/docx/rubric', largeBody, async (req, res) => { ... })
|
|
163
|
+
app.post('/api/export/docx/evaluation', largeBody, async (req, res) => { ... })
|
|
164
|
+
|
|
165
|
+
const mediumBody = express.json({ limit: '5mb' })
|
|
166
|
+
app.post('/api/file/*', mediumBody, (req, res) => { ... })
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- [ ] Set global body limit to 1MB — `serve.ts:63`
|
|
170
|
+
- [ ] Set 25MB limit on export routes — `serve.ts:154,183,201`
|
|
171
|
+
- [ ] Set 5MB limit on file write route — `serve.ts:140`
|
|
172
|
+
|
|
173
|
+
#### 1.7 Rebuild server files
|
|
174
|
+
|
|
175
|
+
After all Phase 1 changes to `.ts` source files:
|
|
176
|
+
|
|
177
|
+
- [ ] Run `tsc -p tsconfig.server.json` to recompile `serve.js` and `export-docx.js`
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
### Phase 2: Dead Code & Simplification
|
|
182
|
+
|
|
183
|
+
**Estimated effort:** Small (30 min). Low-risk deletions and extractions.
|
|
184
|
+
|
|
185
|
+
#### 2.1 Delete `src/cli.ts`
|
|
186
|
+
|
|
187
|
+
This 78-line file is dead code. It is not in `tsconfig.server.json`'s `files` array, not imported by anything, and has diverged from `bin.js` (missing `init`, `export`, `manifest`, `dev` commands). The header comment says "The compiled output is NOT used."
|
|
188
|
+
|
|
189
|
+
- [ ] Delete `tools/editor/src/cli.ts`
|
|
190
|
+
- [ ] Verify it is not referenced in any tsconfig `include` or `files` array
|
|
191
|
+
|
|
192
|
+
#### 2.2 Delete `manifest-cli.ts`
|
|
193
|
+
|
|
194
|
+
This file is not compiled (not in `tsconfig.server.json`). The actual runtime file is `manifest-cli.mjs` (hand-maintained). The `.ts` file has diverged — it hardcodes `REPO_ROOT` as `resolve(__dir, '../..')` while the `.mjs` reads from `process.env.EAROS_REPO_ROOT`.
|
|
195
|
+
|
|
196
|
+
- [ ] Delete `tools/editor/manifest-cli.ts`
|
|
197
|
+
- [ ] Remove the stale header comment in `manifest-cli.mjs` that says "source is manifest-cli.ts"
|
|
198
|
+
|
|
199
|
+
#### 2.3 Import manifest-cli directly (optional, defer if risky)
|
|
200
|
+
|
|
201
|
+
Currently `bin.js` spawns `manifest-cli.mjs` as a child process via `spawnSync`. This works but adds process overhead and requires env-var IPC. The simplest improvement is to export a function from `manifest-cli.mjs` and import it:
|
|
202
|
+
|
|
203
|
+
**Decision:** Defer to a later iteration. The current `spawnSync` approach works, and refactoring `manifest-cli.mjs` to remove its 7 `process.exit()` calls is a medium-effort change with marginal benefit. Keep as a future cleanup.
|
|
204
|
+
|
|
205
|
+
- [ ] Update `manifest-cli.mjs` header comment to remove false "compiled" claim
|
|
206
|
+
|
|
207
|
+
#### 2.4 De-duplicate `findRepoRoot()`
|
|
208
|
+
|
|
209
|
+
This function is duplicated in `bin.js:14-20` and `serve.ts:19-26`. Since `bin.js` is hand-written JS and `serve.ts` is compiled, the cleanest approach is to keep both but ensure they stay identical. Document this in a comment.
|
|
210
|
+
|
|
211
|
+
**Decision:** Low priority given Phase 2.3 is deferred. Leave as-is with a comment.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### Phase 3: CLI Agent Parity
|
|
216
|
+
|
|
217
|
+
**Estimated effort:** Medium (2-3 hours). Extends `bin.js` with new commands and flags.
|
|
218
|
+
|
|
219
|
+
#### 3.1 Extend `earos export` to all YAML kinds
|
|
220
|
+
|
|
221
|
+
**File:** `tools/editor/bin.js:94-130`
|
|
222
|
+
|
|
223
|
+
Currently rejects non-artifact files. Detect `kind` field and route to the appropriate export function:
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
// bin.js — replace the artifact-only check
|
|
227
|
+
const kind = artifactData?.kind
|
|
228
|
+
if (kind === 'artifact' || !kind) {
|
|
229
|
+
const { exportToDocx } = await import('./export-docx.js')
|
|
230
|
+
const buf = await exportToDocx(artifactData)
|
|
231
|
+
// ... write file
|
|
232
|
+
} else if (kind === 'evaluation') {
|
|
233
|
+
const { exportEvaluationToDocx } = await import('./export-docx.js')
|
|
234
|
+
const buf = await exportEvaluationToDocx(artifactData)
|
|
235
|
+
const outputPath = inputPath.replace(/\.(yaml|yml)$/i, '') + '-assessment.docx'
|
|
236
|
+
// ... write file
|
|
237
|
+
} else if (kind === 'core_rubric' || kind === 'profile' || kind === 'overlay') {
|
|
238
|
+
const { exportRubricToDocx } = await import('./export-docx.js')
|
|
239
|
+
const buf = await exportRubricToDocx(artifactData)
|
|
240
|
+
// ... write file
|
|
241
|
+
} else {
|
|
242
|
+
console.error(`Unsupported kind for export: ${kind}`)
|
|
243
|
+
process.exit(1)
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- [ ] Extend `earos export` to detect `kind` and route to appropriate exporter — `bin.js:94-130`
|
|
248
|
+
- [ ] Update help text to reflect all supported kinds — `bin.js:87`
|
|
249
|
+
|
|
250
|
+
#### 3.2 Add `--json` flag to `earos validate`
|
|
251
|
+
|
|
252
|
+
**Convention:** `--json` mode outputs only JSON to stdout; all status/error messages go to stderr.
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
// bin.js — in validateFile()
|
|
256
|
+
const jsonMode = args.includes('--json')
|
|
257
|
+
|
|
258
|
+
if (valid) {
|
|
259
|
+
if (jsonMode) {
|
|
260
|
+
process.stdout.write(JSON.stringify({ valid: true, kind: kind ?? 'unknown', file: filePath }) + '\n')
|
|
261
|
+
} else {
|
|
262
|
+
console.log(`✓ ${filePath} is valid (kind: ${kind ?? 'unknown'})`)
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
const result = {
|
|
266
|
+
valid: false,
|
|
267
|
+
file: filePath,
|
|
268
|
+
kind: kind ?? 'unknown',
|
|
269
|
+
errors: validate.errors.map(err => ({
|
|
270
|
+
path: err.instancePath || '(root)',
|
|
271
|
+
message: err.message
|
|
272
|
+
}))
|
|
273
|
+
}
|
|
274
|
+
if (jsonMode) {
|
|
275
|
+
process.stdout.write(JSON.stringify(result) + '\n')
|
|
276
|
+
} else {
|
|
277
|
+
console.error(`✗ ${filePath} — ${validate.errors.length} error(s):`)
|
|
278
|
+
for (const err of validate.errors) {
|
|
279
|
+
console.error(` ${err.instancePath || '(root)'} ${err.message}`)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
process.exit(1)
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- [ ] Add `--json` flag to `earos validate` — `bin.js:31-76`
|
|
287
|
+
- [ ] Update help text — `bin.js:87`
|
|
288
|
+
|
|
289
|
+
#### 3.3 Add `--json` flag to `earos manifest check`
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
// manifest-cli.mjs — in the check block
|
|
293
|
+
const jsonMode = subArgs.includes('--json')
|
|
294
|
+
|
|
295
|
+
// At the end of check:
|
|
296
|
+
if (jsonMode) {
|
|
297
|
+
process.stdout.write(JSON.stringify({ consistent: errors.length === 0, errors, warnings }) + '\n')
|
|
298
|
+
process.exit(errors.length > 0 ? 1 : 0)
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- [ ] Add `--json` flag to `earos manifest check` — `manifest-cli.mjs:151-202`
|
|
303
|
+
|
|
304
|
+
#### 3.4 Add `earos manifest list [--json]`
|
|
305
|
+
|
|
306
|
+
New subcommand that outputs the manifest contents.
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
// manifest-cli.mjs — add before the "Unknown subcommand" block
|
|
310
|
+
if (subCmd === 'list') {
|
|
311
|
+
const manifest = loadManifest()
|
|
312
|
+
if (!manifest) {
|
|
313
|
+
console.error('No earos.manifest.yaml found. Run `earos manifest` first.')
|
|
314
|
+
process.exit(1)
|
|
315
|
+
}
|
|
316
|
+
if (subArgs.includes('--json')) {
|
|
317
|
+
process.stdout.write(JSON.stringify(manifest, null, 2) + '\n')
|
|
318
|
+
} else {
|
|
319
|
+
const sections = ['core', 'profiles', 'overlays']
|
|
320
|
+
for (const section of sections) {
|
|
321
|
+
const entries = manifest[section] ?? []
|
|
322
|
+
if (entries.length === 0) continue
|
|
323
|
+
console.log(`\n${section}:`)
|
|
324
|
+
for (const e of entries) {
|
|
325
|
+
console.log(` ${e.path} ${e.rubric_id ?? ''} ${e.title ?? ''}`)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
process.exit(0)
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
- [ ] Add `earos manifest list [--json]` — `manifest-cli.mjs`
|
|
334
|
+
- [ ] Update help text in `bin.js:91`
|
|
335
|
+
|
|
336
|
+
#### 3.5 Add `earos list evaluations [--json]`
|
|
337
|
+
|
|
338
|
+
Extract `findEvaluationFiles` from `serve.ts`'s `startServer()` closure into a standalone function, then reuse in `bin.js`.
|
|
339
|
+
|
|
340
|
+
The simplest approach: duplicate the 15-line scan function in `bin.js` (it's pure `fs` operations). This avoids coupling `bin.js` to `serve.js`.
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
// bin.js — new command block
|
|
344
|
+
} else if (args[0] === 'list' && args[1] === 'evaluations') {
|
|
345
|
+
const REPO_ROOT = findRepoRoot()
|
|
346
|
+
const jsonMode = args.includes('--json')
|
|
347
|
+
// ... inline findEvaluationFiles + summary logic
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
- [ ] Add `earos list evaluations [--json]` command — `bin.js`
|
|
352
|
+
- [ ] Include summary metadata (status, score, date, title) in output
|
|
353
|
+
- [ ] Update help text — `bin.js:81-92`
|
|
354
|
+
|
|
355
|
+
#### 3.6 Add Markdown export (`earos export <file> --format md`)
|
|
356
|
+
|
|
357
|
+
The `exportArtifactToMarkdown`, `exportRubricToMarkdown` functions in `src/utils/export-markdown.ts` are pure functions (no DOM dependency) except for `downloadAsFile()` which uses `document.createElement`. To make them available in Node.js:
|
|
358
|
+
|
|
359
|
+
1. Add `src/utils/export-markdown.ts` to `tsconfig.server.json`'s `files` array
|
|
360
|
+
2. Guard `downloadAsFile` with `typeof document !== 'undefined'`
|
|
361
|
+
3. Import in `bin.js` when `--format md` is passed
|
|
362
|
+
|
|
363
|
+
Note: `exportEvaluationToMarkdown` has a different signature (takes 4 args instead of 1). The CLI would need to handle that or skip it initially.
|
|
364
|
+
|
|
365
|
+
**Decision:** Implement for artifact and rubric only in this phase. Evaluation Markdown export requires assembling dimension/result data that the CLI does not currently load.
|
|
366
|
+
|
|
367
|
+
- [ ] Guard `downloadAsFile` with environment check — `export-markdown.ts:39`
|
|
368
|
+
- [ ] Add `export-markdown.ts` to `tsconfig.server.json` `files` array
|
|
369
|
+
- [ ] Add `--format` flag to `earos export` (`docx` default, `md` alternative) — `bin.js`
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### Phase 4: Performance
|
|
374
|
+
|
|
375
|
+
**Estimated effort:** Small (30-60 min). Isolated changes with clear impact.
|
|
376
|
+
|
|
377
|
+
#### 4.1 Parallelize icon downloads
|
|
378
|
+
|
|
379
|
+
**File:** `tools/editor/src/init.ts:396-406`
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
// BEFORE
|
|
383
|
+
for (const config of ICON_PACKAGES) {
|
|
384
|
+
const result = await downloadIconPackage(target, config)
|
|
385
|
+
results.push(result)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// AFTER
|
|
389
|
+
const settled = await Promise.allSettled(
|
|
390
|
+
ICON_PACKAGES.map(config => downloadIconPackage(target, config))
|
|
391
|
+
)
|
|
392
|
+
for (const outcome of settled) {
|
|
393
|
+
if (outcome.status === 'fulfilled') {
|
|
394
|
+
results.push(outcome.value)
|
|
395
|
+
if (outcome.value.missingAliases.length) {
|
|
396
|
+
console.warn(` Missing ${outcome.value.name} icon aliases: ${outcome.value.missingAliases.join(', ')}`)
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
console.error(` Failed to download icons: ${outcome.reason?.message ?? outcome.reason}`)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Note:** Parallel downloads may interleave console output. This is acceptable.
|
|
405
|
+
|
|
406
|
+
- [ ] Replace sequential icon download loop with `Promise.allSettled` — `init.ts:396-406`
|
|
407
|
+
|
|
408
|
+
#### 4.2 Replace sort-for-max with linear scan
|
|
409
|
+
|
|
410
|
+
**File:** `tools/editor/src/init.ts:312-316`
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// BEFORE
|
|
414
|
+
const bestCandidate = extractedEntries
|
|
415
|
+
.map(entry => ({ entry, score: scoreAliasCandidate(entry, spec, config) }))
|
|
416
|
+
.filter(c => Number.isFinite(c.score))
|
|
417
|
+
.sort((a, b) => b.score - a.score)[0]
|
|
418
|
+
|
|
419
|
+
// AFTER
|
|
420
|
+
let bestScore = Number.NEGATIVE_INFINITY
|
|
421
|
+
let bestEntry: ExtractedIconEntry | null = null
|
|
422
|
+
for (const entry of extractedEntries) {
|
|
423
|
+
const score = scoreAliasCandidate(entry, spec, config)
|
|
424
|
+
if (score > bestScore) {
|
|
425
|
+
bestScore = score
|
|
426
|
+
bestEntry = entry
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
- [ ] Replace `sort()[0]` with linear max-scan in `createIconAliases` — `init.ts:312-316`
|
|
432
|
+
|
|
433
|
+
#### 4.3 Lazy-load artifact schema
|
|
434
|
+
|
|
435
|
+
**File:** `tools/editor/src/export-docx.ts` (search for `ARTIFACT_SCHEMA`)
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// BEFORE
|
|
439
|
+
const ARTIFACT_SCHEMA = loadArtifactSchema()
|
|
440
|
+
|
|
441
|
+
// AFTER
|
|
442
|
+
let _artifactSchema: Record<string, any> | null | undefined
|
|
443
|
+
function getArtifactSchema() {
|
|
444
|
+
if (_artifactSchema === undefined) {
|
|
445
|
+
_artifactSchema = loadArtifactSchema()
|
|
446
|
+
}
|
|
447
|
+
return _artifactSchema
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Replace all references to `ARTIFACT_SCHEMA` with `getArtifactSchema()`.
|
|
452
|
+
|
|
453
|
+
- [ ] Lazy-load `ARTIFACT_SCHEMA` on first use — `export-docx.ts`
|
|
454
|
+
|
|
455
|
+
#### 4.4 Rebuild server files
|
|
456
|
+
|
|
457
|
+
- [ ] Run `tsc -p tsconfig.server.json` to recompile `init.js` and `export-docx.js`
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### Phase 5: Code Quality & Architecture
|
|
462
|
+
|
|
463
|
+
**Estimated effort:** Medium-Large (3-5 hours). The `strict: true` migration is the largest item.
|
|
464
|
+
|
|
465
|
+
#### 5.1 Enable strict mode incrementally
|
|
466
|
+
|
|
467
|
+
**File:** `tools/editor/tsconfig.server.json`
|
|
468
|
+
|
|
469
|
+
**Step 1:** Run `tsc -p tsconfig.server.json --strict --noEmit 2>&1 | wc -l` to assess error count.
|
|
470
|
+
|
|
471
|
+
**Step 2:** Enable `strictNullChecks` and `noImplicitAny` first (the highest-value flags):
|
|
472
|
+
|
|
473
|
+
```json
|
|
474
|
+
{
|
|
475
|
+
"compilerOptions": {
|
|
476
|
+
"strict": false,
|
|
477
|
+
"strictNullChecks": true,
|
|
478
|
+
"noImplicitAny": true
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**Step 3:** Fix resulting errors in `serve.ts`, `init.ts`, and `export-docx.ts`. For `export-docx.ts`, the 42+ `any` usages can be addressed by:
|
|
484
|
+
- Typing section renderer parameters with interfaces derived from `artifact.schema.json`
|
|
485
|
+
- Replacing `as any` casts with narrowing checks
|
|
486
|
+
- Adding `unknown` instead of `any` for truly dynamic data
|
|
487
|
+
|
|
488
|
+
**Step 4:** Once those two flags pass, enable full `strict: true`.
|
|
489
|
+
|
|
490
|
+
- [ ] Assess strict mode error count — `tsconfig.server.json`
|
|
491
|
+
- [ ] Enable `strictNullChecks` + `noImplicitAny` and fix errors
|
|
492
|
+
- [ ] Enable full `strict: true` and fix remaining errors
|
|
493
|
+
|
|
494
|
+
#### 5.2 De-duplicate `getGateSeverity`
|
|
495
|
+
|
|
496
|
+
**File:** `tools/editor/src/components/AssessmentSummary.tsx:40`
|
|
497
|
+
|
|
498
|
+
The function is duplicated in `AssessmentSummary.tsx` and `score-helpers.tsx`. Gate logic must have a single source of truth.
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// AssessmentSummary.tsx — BEFORE
|
|
502
|
+
function getGateSeverity(gate: any): string { ... }
|
|
503
|
+
|
|
504
|
+
// AssessmentSummary.tsx — AFTER
|
|
505
|
+
import { getGateSeverity } from '../utils/score-helpers'
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
- [ ] Remove duplicate `getGateSeverity` from `AssessmentSummary.tsx`, import from `score-helpers.tsx`
|
|
509
|
+
|
|
510
|
+
#### 5.3 Move `mermaid` to devDependencies
|
|
511
|
+
|
|
512
|
+
**File:** `tools/editor/package.json:61`
|
|
513
|
+
|
|
514
|
+
**Prerequisite:** Verify the Vite build fully bundles mermaid into `dist/`. Steps:
|
|
515
|
+
1. Move `mermaid` from `dependencies` to `devDependencies`
|
|
516
|
+
2. Run `npm run build`
|
|
517
|
+
3. Run `npm pack` to create tarball
|
|
518
|
+
4. Install tarball in a temp directory: `npm install /path/to/trohde-earos-*.tgz`
|
|
519
|
+
5. Run `npx earos --help` — if it works, mermaid is fully bundled
|
|
520
|
+
6. Start the server and load the editor — verify Mermaid diagrams render
|
|
521
|
+
|
|
522
|
+
- [ ] Move `mermaid` to `devDependencies` — `package.json`
|
|
523
|
+
- [ ] Verify Vite bundle is self-contained after the move
|
|
524
|
+
|
|
525
|
+
#### 5.4 Extract shared helpers
|
|
526
|
+
|
|
527
|
+
Create `tools/editor/src/utils/format-helpers.ts`:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
export function str(v: unknown): string {
|
|
531
|
+
if (v == null) return ''
|
|
532
|
+
if (typeof v === 'string') return v.trim()
|
|
533
|
+
if (typeof v === 'number' || typeof v === 'boolean') return String(v)
|
|
534
|
+
return ''
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function humanize(key: string): string {
|
|
538
|
+
return key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
542
|
+
return !!v && typeof v === 'object' && !Array.isArray(v)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export const SECTION_ORDER = [ /* ... shared constant ... */ ]
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Then update imports in `export-docx.ts` (rename `prettyLabel` to `humanize`) and `export-markdown.ts`.
|
|
549
|
+
|
|
550
|
+
Also extract `extractDiagrams` and `DIAGRAM_FIELDS` into `utils/diagrams.ts` for sharing between `export-docx.ts` and `mermaid.ts`.
|
|
551
|
+
|
|
552
|
+
- [ ] Create `src/utils/format-helpers.ts` with shared `str`, `humanize`, `isPlainObject`, `SECTION_ORDER`
|
|
553
|
+
- [ ] Create `src/utils/diagrams.ts` with shared `extractDiagrams` and `DIAGRAM_FIELDS`
|
|
554
|
+
- [ ] Update `export-docx.ts` to import from shared modules (rename `prettyLabel` to `humanize`)
|
|
555
|
+
- [ ] Update `export-markdown.ts` to import from shared modules
|
|
556
|
+
- [ ] Update `mermaid.ts` to import `DIAGRAM_FIELDS` from shared module
|
|
557
|
+
|
|
558
|
+
#### 5.5 Extract document shell boilerplate
|
|
559
|
+
|
|
560
|
+
The `Document` construction with Header, Footer, margins, packing is copy-pasted across 3 export functions. Extract a `buildDocShell` factory.
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
// export-docx.ts — new shared function
|
|
564
|
+
function buildDocShell(opts: {
|
|
565
|
+
title: string
|
|
566
|
+
creator: string
|
|
567
|
+
headerLabel: string
|
|
568
|
+
children: Paragraph[]
|
|
569
|
+
}): Buffer {
|
|
570
|
+
const doc = new Document({
|
|
571
|
+
creator: opts.creator,
|
|
572
|
+
title: opts.title,
|
|
573
|
+
// ... shared margins, header, footer, ToC pattern
|
|
574
|
+
sections: [{ children: opts.children }]
|
|
575
|
+
})
|
|
576
|
+
return Packer.toBuffer(doc)
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
- [ ] Extract `buildDocShell` from the 3 export functions — `export-docx.ts`
|
|
581
|
+
|
|
582
|
+
#### 5.6 Fix silent validation bypass
|
|
583
|
+
|
|
584
|
+
**File:** `tools/editor/src/utils/validate.ts:27-28`
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
// BEFORE
|
|
588
|
+
try {
|
|
589
|
+
cache.set(key, ajv.compile(rest))
|
|
590
|
+
} catch {
|
|
591
|
+
return null // Caller treats null as "valid"!
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// AFTER
|
|
595
|
+
try {
|
|
596
|
+
cache.set(key, ajv.compile(rest))
|
|
597
|
+
} catch (e) {
|
|
598
|
+
console.warn('[validate] Schema compilation failed:', e)
|
|
599
|
+
// Return a validator that always fails with a clear message
|
|
600
|
+
const failValidator = (() => false) as any
|
|
601
|
+
failValidator.errors = [{ instancePath: '', message: 'Schema compilation failed' }]
|
|
602
|
+
cache.set(key, failValidator)
|
|
603
|
+
return failValidator
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
- [ ] Fix schema compilation failure to report invalid instead of valid — `validate.ts:27`
|
|
608
|
+
|
|
609
|
+
#### 5.7 Add Express server caching for evaluation endpoints
|
|
610
|
+
|
|
611
|
+
**File:** `tools/editor/src/serve.ts:87-123`
|
|
612
|
+
|
|
613
|
+
Add a simple TTL cache for evaluation file scans:
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
let evalCache: { files: Array<{ path: string; name: string }>; ts: number } | null = null
|
|
617
|
+
const EVAL_CACHE_TTL = 5000 // 5 seconds
|
|
618
|
+
|
|
619
|
+
function getCachedEvaluationFiles(repoRoot: string): Array<{ path: string; name: string }> {
|
|
620
|
+
if (evalCache && Date.now() - evalCache.ts < EVAL_CACHE_TTL) return evalCache.files
|
|
621
|
+
const files: Array<{ path: string; name: string }> = []
|
|
622
|
+
for (const dir of ['examples', 'evaluations']) {
|
|
623
|
+
files.push(...findEvaluationFiles(resolve(repoRoot, dir), `${dir}/`))
|
|
624
|
+
}
|
|
625
|
+
evalCache = { files, ts: Date.now() }
|
|
626
|
+
return files
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
- [ ] Add TTL cache for evaluation file scans — `serve.ts`
|
|
631
|
+
- [ ] Invalidate cache on POST /api/file/* writes to evaluation paths
|
|
632
|
+
|
|
633
|
+
#### 5.8 Rebuild all server files
|
|
634
|
+
|
|
635
|
+
- [ ] Run `tsc -p tsconfig.server.json` to recompile all server files
|
|
636
|
+
- [ ] Run `npm run build` for full rebuild
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## System-Wide Impact
|
|
641
|
+
|
|
642
|
+
- **Build pipeline:** Phases 3 and 5 modify `tsconfig.server.json` (adding `export-markdown.ts` to files, enabling strict flags). The 4-stage build (`tsc server → tsc check → vite build → build:assets`) is unchanged.
|
|
643
|
+
- **Published package:** The `files` array in `package.json` does not change. Compiled `.js` files are updated in place. No new files ship to npm (shared utils are compiled into their consumers by `tsc`).
|
|
644
|
+
- **Breaking changes:** Phase 1 changes the default bind address from `0.0.0.0` to `127.0.0.1`. Users running the server in Docker/WSL2 must set `EAROS_HOST=0.0.0.0`. Document in the changelog.
|
|
645
|
+
- **Backward compatibility:** All existing CLI commands continue to work unchanged. New flags (`--json`, `--format`) are additive. The `earos export` extension is backward-compatible (existing artifact exports work the same).
|
|
646
|
+
|
|
647
|
+
## Acceptance Criteria
|
|
648
|
+
|
|
649
|
+
### Phase 1 — Security
|
|
650
|
+
- [ ] `safeRepoPath` rejects paths to sibling directories (e.g., `../EAROS-evil/`)
|
|
651
|
+
- [ ] Server only listens on `127.0.0.1` by default
|
|
652
|
+
- [ ] `resolveLocalMermaidImage` rejects `..` traversal beyond icon directories
|
|
653
|
+
- [ ] API 500 responses do not leak filesystem paths
|
|
654
|
+
- [ ] POST `/api/file/*` rejects non-YAML file extensions
|
|
655
|
+
- [ ] JSON body >1MB rejected on non-export routes
|
|
656
|
+
|
|
657
|
+
### Phase 2 — Dead Code
|
|
658
|
+
- [ ] `src/cli.ts` deleted
|
|
659
|
+
- [ ] `manifest-cli.ts` deleted
|
|
660
|
+
- [ ] `manifest-cli.mjs` header comment updated
|
|
661
|
+
|
|
662
|
+
### Phase 3 — Agent Parity
|
|
663
|
+
- [ ] `earos export evaluation.yaml` produces a DOCX file
|
|
664
|
+
- [ ] `earos export rubric.yaml` produces a DOCX file
|
|
665
|
+
- [ ] `earos validate --json` outputs valid JSON to stdout
|
|
666
|
+
- [ ] `earos manifest check --json` outputs valid JSON to stdout
|
|
667
|
+
- [ ] `earos manifest list` outputs manifest contents
|
|
668
|
+
- [ ] `earos list evaluations --json` discovers and lists evaluation files
|
|
669
|
+
- [ ] `earos export artifact.yaml --format md` produces a Markdown file
|
|
670
|
+
- [ ] Help text (`earos --help`) documents all new commands and flags
|
|
671
|
+
|
|
672
|
+
### Phase 4 — Performance
|
|
673
|
+
- [ ] `earos init --icons` downloads all 3 icon packages in parallel
|
|
674
|
+
- [ ] Alias matching uses linear scan instead of sort
|
|
675
|
+
- [ ] `ARTIFACT_SCHEMA` is not loaded until first export
|
|
676
|
+
|
|
677
|
+
### Phase 5 — Code Quality
|
|
678
|
+
- [ ] `tsconfig.server.json` has `strict: true`
|
|
679
|
+
- [ ] `getGateSeverity` exists in one place only (`score-helpers.tsx`)
|
|
680
|
+
- [ ] `mermaid` is in `devDependencies`, editor still renders diagrams
|
|
681
|
+
- [ ] Shared helpers extracted to `format-helpers.ts` and `diagrams.ts`
|
|
682
|
+
- [ ] Document shell boilerplate exists once as `buildDocShell`
|
|
683
|
+
- [ ] Schema compilation failure reports validation error (not silent pass)
|
|
684
|
+
- [ ] Evaluation endpoints use cached file scans
|
|
685
|
+
|
|
686
|
+
## Dependencies & Risks
|
|
687
|
+
|
|
688
|
+
| Risk | Mitigation |
|
|
689
|
+
|------|------------|
|
|
690
|
+
| `strict: true` surfaces hundreds of errors in `export-docx.ts` | Incremental approach: `strictNullChecks` + `noImplicitAny` first |
|
|
691
|
+
| Moving `mermaid` to devDeps breaks runtime | Verify with `npm pack` + clean install before publishing |
|
|
692
|
+
| Localhost binding breaks Docker users | `EAROS_HOST` env var override documented in help text |
|
|
693
|
+
| Body limit reduction breaks large artifact saves | 5MB limit for `/api/file/*` (well above typical artifact size) |
|
|
694
|
+
| `export-markdown.ts` has browser-only `downloadAsFile` | Guard with `typeof document` check before adding to server build |
|
|
695
|
+
|
|
696
|
+
## Implementation Order
|
|
697
|
+
|
|
698
|
+
```
|
|
699
|
+
Phase 1 (Security) ──→ Phase 2 (Dead Code) ──→ Phase 3 (Agent Parity)
|
|
700
|
+
│
|
|
701
|
+
Phase 4 (Performance) ─────────────────────────────────┤
|
|
702
|
+
│
|
|
703
|
+
Phase 5 (Code Quality)
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Phases 1 and 4 are independent and can run in parallel. Phase 3 depends on Phase 2 (dead code removal first). Phase 5 depends on Phase 3 (shared helper extraction benefits from the new `export-markdown.ts` server build target).
|
|
707
|
+
|
|
708
|
+
## Version Bump Strategy
|
|
709
|
+
|
|
710
|
+
| Phase | Bump | Version |
|
|
711
|
+
|-------|------|---------|
|
|
712
|
+
| Phase 1: Security | patch | 1.2.1 |
|
|
713
|
+
| Phase 2: Dead code | patch | 1.2.2 |
|
|
714
|
+
| Phase 3: Agent parity | minor | 1.3.0 |
|
|
715
|
+
| Phase 4: Performance | patch | 1.3.1 |
|
|
716
|
+
| Phase 5: Code quality | patch | 1.3.2 |
|
|
717
|
+
|
|
718
|
+
Or combine Phases 1+2 into one patch and 4+5 into another, yielding: `1.2.1 → 1.3.0 → 1.3.1`.
|
|
719
|
+
|
|
720
|
+
## Sources & References
|
|
721
|
+
|
|
722
|
+
### Internal References
|
|
723
|
+
- Prior art: `docs/plans/2026-03-23-001-refactor-site-review-findings-plan.md` — same review-then-fix pattern applied to the `site/` frontend
|
|
724
|
+
- Path traversal guard: `tools/editor/src/serve.ts:28-33`
|
|
725
|
+
- CLI dispatch: `tools/editor/bin.js:80-188`
|
|
726
|
+
- Export pipeline: `tools/editor/src/export-docx.ts` (~1770 lines)
|
|
727
|
+
- Server build config: `tools/editor/tsconfig.server.json`
|
|
728
|
+
- Package config: `tools/editor/package.json`
|
|
729
|
+
|
|
730
|
+
### Review Agents
|
|
731
|
+
- Security Sentinel: 2 HIGH, 4 MEDIUM, 3 LOW findings
|
|
732
|
+
- Performance Oracle: 3 CRITICAL, 8 optimization opportunities
|
|
733
|
+
- Architecture Strategist: 8 findings on patterns, drift, dead code
|
|
734
|
+
- Kieran TypeScript Reviewer: strict mode, 42+ `any` usages, duplication
|
|
735
|
+
- Agent-Native Reviewer: 16/20 capabilities agent-accessible, 4 gaps
|
|
736
|
+
- Code Simplicity Reviewer: ~175 LOC removable (5.3%), 5 simplification opportunities
|