@laitszkin/apollo-toolkit 3.9.6 → 3.10.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/AGENTS.md +2 -0
- package/CHANGELOG.md +23 -0
- package/README.md +6 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/cjk-pdf/agents/openai.yaml +5 -0
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/generate-spec/SKILL.md +24 -4
- package/generate-spec/agents/openai.yaml +1 -0
- package/generate-spec/references/TEMPLATE_SPEC.md +98 -0
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/init-project-html/SKILL.md +181 -0
- package/init-project-html/agents/openai.yaml +13 -0
- package/init-project-html/references/TEMPLATE_SPEC.md +98 -0
- package/init-project-html/references/architecture-page.template.html +35 -0
- package/init-project-html/references/architecture.css +1059 -0
- package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +1059 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +54 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +165 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +198 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +165 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +152 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +140 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +53 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +161 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +145 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +190 -0
- package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +140 -0
- package/init-project-html/sample-demo/resources/project-architecture/index.html +337 -0
- package/init-project-html/scripts/architecture.js +496 -0
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/lib/cli.js +2 -0
- package/lib/tool-runner.js +7 -0
- package/merge-changes-from-local-branches/SKILL.md +75 -128
- package/merge-changes-from-local-branches/agents/openai.yaml +1 -1
- package/merge-conflict-resolver/agents/openai.yaml +5 -0
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/package.json +1 -1
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/spec-to-project-html/SKILL.md +116 -0
- package/spec-to-project-html/agents/openai.yaml +12 -0
- package/spec-to-project-html/references/TEMPLATE_SPEC.md +98 -0
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// architecture.js — open the project HTML architecture atlas, or render
|
|
3
|
+
// a paginated before/after diff viewer from every active spec's
|
|
4
|
+
// architecture_diff/ directory under docs/plans/.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// architecture.js # same as `open`
|
|
8
|
+
// architecture.js open [--project <root>] [--no-open]
|
|
9
|
+
// architecture.js diff [--project <root>] [--out <dir>] [--no-open]
|
|
10
|
+
// architecture.js --help
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const { spawn } = require('node:child_process');
|
|
17
|
+
|
|
18
|
+
const ATLAS_REL = path.join('resources', 'project-architecture', 'index.html');
|
|
19
|
+
const RESOURCES_REL = path.join('resources', 'project-architecture');
|
|
20
|
+
const PLANS_REL = path.join('docs', 'plans');
|
|
21
|
+
const DIFF_DIRNAME = 'architecture_diff';
|
|
22
|
+
const REMOVED_FILE = '_removed.txt';
|
|
23
|
+
const DEFAULT_OUT_REL = path.join('.apollo-toolkit', 'architecture-diff');
|
|
24
|
+
|
|
25
|
+
const USAGE = `apltk architecture — open the project architecture atlas or its diff viewer.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
apltk architecture Open resources/project-architecture/index.html
|
|
29
|
+
apltk architecture open Same as above
|
|
30
|
+
apltk architecture diff Render every architecture_diff/ as one paginated viewer
|
|
31
|
+
apltk architecture --help Show this help
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--project <root> Override project root (default: nearest ancestor with resources/project-architecture/index.html)
|
|
35
|
+
--out <dir> Override viewer output dir (default: <project>/${DEFAULT_OUT_REL})
|
|
36
|
+
--no-open Skip launching the browser (CI/test friendly)
|
|
37
|
+
-h, --help Show this help`;
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv) {
|
|
40
|
+
const args = [...argv];
|
|
41
|
+
const result = {
|
|
42
|
+
subcommand: 'open',
|
|
43
|
+
projectRoot: null,
|
|
44
|
+
out: null,
|
|
45
|
+
open: true,
|
|
46
|
+
help: false,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
50
|
+
const candidate = args[0];
|
|
51
|
+
if (candidate === 'open' || candidate === 'diff') {
|
|
52
|
+
result.subcommand = candidate;
|
|
53
|
+
args.shift();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
while (args.length > 0) {
|
|
58
|
+
const arg = args.shift();
|
|
59
|
+
if (arg === '--help' || arg === '-h') {
|
|
60
|
+
result.help = true;
|
|
61
|
+
} else if (arg === '--project') {
|
|
62
|
+
const value = args.shift();
|
|
63
|
+
if (!value) throw new Error('Missing value for --project');
|
|
64
|
+
result.projectRoot = path.resolve(value);
|
|
65
|
+
} else if (arg === '--out') {
|
|
66
|
+
const value = args.shift();
|
|
67
|
+
if (!value) throw new Error('Missing value for --out');
|
|
68
|
+
result.out = path.resolve(value);
|
|
69
|
+
} else if (arg === '--no-open') {
|
|
70
|
+
result.open = false;
|
|
71
|
+
} else {
|
|
72
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findProjectRoot(startDir) {
|
|
80
|
+
let dir = path.resolve(startDir);
|
|
81
|
+
while (true) {
|
|
82
|
+
if (fs.existsSync(path.join(dir, ATLAS_REL))) return dir;
|
|
83
|
+
const parent = path.dirname(dir);
|
|
84
|
+
if (parent === dir) return null;
|
|
85
|
+
dir = parent;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function openInBrowser(filePath) {
|
|
90
|
+
const platform = process.platform;
|
|
91
|
+
let command;
|
|
92
|
+
let args;
|
|
93
|
+
if (platform === 'darwin') {
|
|
94
|
+
command = 'open';
|
|
95
|
+
args = [filePath];
|
|
96
|
+
} else if (platform === 'win32') {
|
|
97
|
+
command = 'cmd';
|
|
98
|
+
args = ['/c', 'start', '""', filePath];
|
|
99
|
+
} else {
|
|
100
|
+
command = 'xdg-open';
|
|
101
|
+
args = [filePath];
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
105
|
+
child.on('error', () => { /* best effort */ });
|
|
106
|
+
child.unref();
|
|
107
|
+
} catch (_err) {
|
|
108
|
+
/* best effort */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function walkArchitectureDiffDirs(plansRoot) {
|
|
113
|
+
const result = [];
|
|
114
|
+
if (!fs.existsSync(plansRoot)) return result;
|
|
115
|
+
|
|
116
|
+
function recurse(dir) {
|
|
117
|
+
let entries;
|
|
118
|
+
try {
|
|
119
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
120
|
+
} catch (_err) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
if (!entry.isDirectory()) continue;
|
|
125
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
126
|
+
const full = path.join(dir, entry.name);
|
|
127
|
+
if (entry.name === DIFF_DIRNAME) {
|
|
128
|
+
result.push(full);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
recurse(full);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
recurse(plansRoot);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function walkAfterStateHtml(diffDir) {
|
|
139
|
+
const out = [];
|
|
140
|
+
function recurse(dir, relParts) {
|
|
141
|
+
let entries;
|
|
142
|
+
try {
|
|
143
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
144
|
+
} catch (_err) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
if (entry.name === 'assets') continue;
|
|
149
|
+
if (entry.name === REMOVED_FILE) continue;
|
|
150
|
+
if (entry.name.startsWith('.')) continue;
|
|
151
|
+
const full = path.join(dir, entry.name);
|
|
152
|
+
const nextRel = [...relParts, entry.name];
|
|
153
|
+
if (entry.isDirectory()) {
|
|
154
|
+
recurse(full, nextRel);
|
|
155
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
|
|
156
|
+
out.push({ abs: full, rel: nextRel.join('/') });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
recurse(diffDir, []);
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readRemovedManifest(diffDir) {
|
|
165
|
+
const manifestPath = path.join(diffDir, REMOVED_FILE);
|
|
166
|
+
if (!fs.existsSync(manifestPath)) return [];
|
|
167
|
+
return fs.readFileSync(manifestPath, 'utf8')
|
|
168
|
+
.split(/\r?\n/)
|
|
169
|
+
.map((line) => line.trim())
|
|
170
|
+
.filter((line) => line && !line.startsWith('#'));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function collectChanges(projectRoot) {
|
|
174
|
+
const resourcesRoot = path.join(projectRoot, RESOURCES_REL);
|
|
175
|
+
const plansRoot = path.join(projectRoot, PLANS_REL);
|
|
176
|
+
const diffDirs = walkArchitectureDiffDirs(plansRoot);
|
|
177
|
+
const changes = [];
|
|
178
|
+
|
|
179
|
+
for (const diffDir of diffDirs) {
|
|
180
|
+
const specDir = path.dirname(diffDir);
|
|
181
|
+
const specLabel = path.relative(projectRoot, specDir);
|
|
182
|
+
|
|
183
|
+
for (const after of walkAfterStateHtml(diffDir)) {
|
|
184
|
+
const beforeAbs = path.join(resourcesRoot, after.rel);
|
|
185
|
+
const beforeExists = fs.existsSync(beforeAbs);
|
|
186
|
+
changes.push({
|
|
187
|
+
kind: beforeExists ? 'modified' : 'added',
|
|
188
|
+
rel: after.rel,
|
|
189
|
+
spec: specLabel,
|
|
190
|
+
beforePath: beforeExists ? path.relative(projectRoot, beforeAbs) : null,
|
|
191
|
+
afterPath: path.relative(projectRoot, after.abs),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const removedRel of readRemovedManifest(diffDir)) {
|
|
196
|
+
const beforeAbs = path.join(resourcesRoot, removedRel);
|
|
197
|
+
if (!fs.existsSync(beforeAbs)) continue;
|
|
198
|
+
changes.push({
|
|
199
|
+
kind: 'removed',
|
|
200
|
+
rel: removedRel,
|
|
201
|
+
spec: specLabel,
|
|
202
|
+
beforePath: path.relative(projectRoot, beforeAbs),
|
|
203
|
+
afterPath: null,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
changes.sort((a, b) => {
|
|
209
|
+
if (a.spec !== b.spec) return a.spec.localeCompare(b.spec);
|
|
210
|
+
if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
|
|
211
|
+
return a.rel.localeCompare(b.rel);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return changes;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function htmlEscape(value) {
|
|
218
|
+
return String(value)
|
|
219
|
+
.replace(/&/g, '&')
|
|
220
|
+
.replace(/</g, '<')
|
|
221
|
+
.replace(/>/g, '>')
|
|
222
|
+
.replace(/"/g, '"')
|
|
223
|
+
.replace(/'/g, ''');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function toViewerRel(outDir, projectRoot, projectRelPath) {
|
|
227
|
+
if (!projectRelPath) return null;
|
|
228
|
+
const absolute = path.resolve(projectRoot, projectRelPath);
|
|
229
|
+
const rel = path.relative(outDir, absolute);
|
|
230
|
+
return rel.split(path.sep).join('/');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function renderViewer({ changes, projectRoot, outDir }) {
|
|
234
|
+
const pages = changes.map((change) => ({
|
|
235
|
+
kind: change.kind,
|
|
236
|
+
rel: change.rel,
|
|
237
|
+
spec: change.spec,
|
|
238
|
+
beforeSrc: toViewerRel(outDir, projectRoot, change.beforePath),
|
|
239
|
+
afterSrc: toViewerRel(outDir, projectRoot, change.afterPath),
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
const summary = {
|
|
243
|
+
total: pages.length,
|
|
244
|
+
modified: pages.filter((p) => p.kind === 'modified').length,
|
|
245
|
+
added: pages.filter((p) => p.kind === 'added').length,
|
|
246
|
+
removed: pages.filter((p) => p.kind === 'removed').length,
|
|
247
|
+
projectRoot,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const payload = JSON.stringify({ pages, summary });
|
|
251
|
+
|
|
252
|
+
return `<!DOCTYPE html>
|
|
253
|
+
<html lang="en" data-atlas="diff-viewer">
|
|
254
|
+
<head>
|
|
255
|
+
<meta charset="utf-8">
|
|
256
|
+
<title>Architecture diff — ${htmlEscape(path.basename(projectRoot))}</title>
|
|
257
|
+
<style>
|
|
258
|
+
:root {
|
|
259
|
+
color-scheme: light dark;
|
|
260
|
+
--bg: #0f172a;
|
|
261
|
+
--panel: #1e293b;
|
|
262
|
+
--text: #e2e8f0;
|
|
263
|
+
--muted: #94a3b8;
|
|
264
|
+
--accent: #38bdf8;
|
|
265
|
+
--added: #4ade80;
|
|
266
|
+
--removed: #f87171;
|
|
267
|
+
--modified: #facc15;
|
|
268
|
+
}
|
|
269
|
+
* { box-sizing: border-box; }
|
|
270
|
+
html, body { height: 100%; margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: var(--bg); color: var(--text); }
|
|
271
|
+
body { display: flex; flex-direction: column; min-height: 100vh; }
|
|
272
|
+
header { padding: 12px 20px; background: var(--panel); border-bottom: 1px solid #334155; display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; }
|
|
273
|
+
header .title { font-size: 14px; color: var(--muted); }
|
|
274
|
+
header .title strong { color: var(--text); }
|
|
275
|
+
header .summary { display: flex; gap: 12px; font-size: 12px; color: var(--muted); }
|
|
276
|
+
header .summary span.count { font-weight: 600; }
|
|
277
|
+
header .summary .modified { color: var(--modified); }
|
|
278
|
+
header .summary .added { color: var(--added); }
|
|
279
|
+
header .summary .removed { color: var(--removed); }
|
|
280
|
+
main { flex: 1; display: flex; flex-direction: column; }
|
|
281
|
+
.meta { padding: 10px 20px; background: var(--bg); border-bottom: 1px solid #334155; display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; font-size: 13px; }
|
|
282
|
+
.meta .left { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
283
|
+
.badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; border: 1px solid currentColor; }
|
|
284
|
+
.badge.modified { color: var(--modified); }
|
|
285
|
+
.badge.added { color: var(--added); }
|
|
286
|
+
.badge.removed { color: var(--removed); }
|
|
287
|
+
.path { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text); }
|
|
288
|
+
.spec { color: var(--muted); font-size: 12px; }
|
|
289
|
+
.nav { display: flex; align-items: center; gap: 8px; }
|
|
290
|
+
.nav button { background: transparent; color: var(--text); border: 1px solid #475569; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
|
291
|
+
.nav button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
|
292
|
+
.nav button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
293
|
+
.nav .counter { font-variant-numeric: tabular-nums; color: var(--muted); min-width: 72px; text-align: center; }
|
|
294
|
+
.frames { flex: 1; display: grid; gap: 1px; background: #334155; padding: 1px; min-height: 0; }
|
|
295
|
+
.frames.split { grid-template-columns: 1fr 1fr; }
|
|
296
|
+
.frames.single { grid-template-columns: 1fr; }
|
|
297
|
+
.pane { background: #ffffff; display: flex; flex-direction: column; min-height: 0; }
|
|
298
|
+
.pane h2 { margin: 0; padding: 8px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; background: #f1f5f9; color: #1e293b; border-bottom: 1px solid #cbd5f5; display: flex; align-items: center; gap: 8px; }
|
|
299
|
+
.pane h2 .side-badge { font-size: 10px; padding: 1px 6px; border-radius: 4px; background: #cbd5f5; color: #1e293b; }
|
|
300
|
+
.pane h2.before .side-badge { background: #fee2e2; color: #991b1b; }
|
|
301
|
+
.pane h2.after .side-badge { background: #dcfce7; color: #166534; }
|
|
302
|
+
.pane iframe { flex: 1; width: 100%; border: 0; background: #ffffff; }
|
|
303
|
+
.empty { display: flex; align-items: center; justify-content: center; padding: 32px; font-size: 14px; color: var(--muted); }
|
|
304
|
+
footer { padding: 8px 20px; background: var(--panel); border-top: 1px solid #334155; font-size: 12px; color: var(--muted); }
|
|
305
|
+
footer kbd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; background: #0f172a; padding: 1px 6px; border-radius: 4px; border: 1px solid #475569; }
|
|
306
|
+
</style>
|
|
307
|
+
</head>
|
|
308
|
+
<body>
|
|
309
|
+
<header>
|
|
310
|
+
<div class="title">Apollo Toolkit · <strong>architecture diff</strong> · ${htmlEscape(path.basename(projectRoot))}</div>
|
|
311
|
+
<div class="summary">
|
|
312
|
+
<span><span class="count">${summary.total}</span> change<span>${summary.total === 1 ? '' : 's'}</span></span>
|
|
313
|
+
<span class="modified"><span class="count">${summary.modified}</span> modified</span>
|
|
314
|
+
<span class="added"><span class="count">${summary.added}</span> added</span>
|
|
315
|
+
<span class="removed"><span class="count">${summary.removed}</span> removed</span>
|
|
316
|
+
</div>
|
|
317
|
+
</header>
|
|
318
|
+
<main>
|
|
319
|
+
<div class="meta">
|
|
320
|
+
<div class="left">
|
|
321
|
+
<span id="badge" class="badge modified">modified</span>
|
|
322
|
+
<span class="path" id="path">—</span>
|
|
323
|
+
<span class="spec" id="spec">—</span>
|
|
324
|
+
</div>
|
|
325
|
+
<div class="nav">
|
|
326
|
+
<button id="prev" type="button" aria-label="Previous change">← Prev</button>
|
|
327
|
+
<span class="counter" id="counter">0 / 0</span>
|
|
328
|
+
<button id="next" type="button" aria-label="Next change">Next →</button>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="frames" id="frames">
|
|
332
|
+
<div class="empty" id="empty">No architecture diffs found under docs/plans/**/architecture_diff/.</div>
|
|
333
|
+
</div>
|
|
334
|
+
</main>
|
|
335
|
+
<footer>
|
|
336
|
+
Navigate with <kbd>←</kbd> / <kbd>→</kbd> or the buttons above. Each page pairs the current atlas (left) with the proposed-after HTML (right) for one affected page.
|
|
337
|
+
</footer>
|
|
338
|
+
<script id="__diff_payload" type="application/json">${payload.replace(/</g, '\\u003c')}</script>
|
|
339
|
+
<script>
|
|
340
|
+
(function () {
|
|
341
|
+
const data = JSON.parse(document.getElementById('__diff_payload').textContent);
|
|
342
|
+
const pages = data.pages || [];
|
|
343
|
+
const framesEl = document.getElementById('frames');
|
|
344
|
+
const emptyEl = document.getElementById('empty');
|
|
345
|
+
const badgeEl = document.getElementById('badge');
|
|
346
|
+
const pathEl = document.getElementById('path');
|
|
347
|
+
const specEl = document.getElementById('spec');
|
|
348
|
+
const counterEl = document.getElementById('counter');
|
|
349
|
+
const prevBtn = document.getElementById('prev');
|
|
350
|
+
const nextBtn = document.getElementById('next');
|
|
351
|
+
|
|
352
|
+
if (pages.length === 0) {
|
|
353
|
+
counterEl.textContent = '0 / 0';
|
|
354
|
+
prevBtn.disabled = true;
|
|
355
|
+
nextBtn.disabled = true;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let index = 0;
|
|
360
|
+
|
|
361
|
+
function render() {
|
|
362
|
+
const page = pages[index];
|
|
363
|
+
badgeEl.className = 'badge ' + page.kind;
|
|
364
|
+
badgeEl.textContent = page.kind;
|
|
365
|
+
pathEl.textContent = page.rel;
|
|
366
|
+
specEl.textContent = page.spec;
|
|
367
|
+
counterEl.textContent = (index + 1) + ' / ' + pages.length;
|
|
368
|
+
prevBtn.disabled = index === 0;
|
|
369
|
+
nextBtn.disabled = index === pages.length - 1;
|
|
370
|
+
|
|
371
|
+
framesEl.innerHTML = '';
|
|
372
|
+
if (page.kind === 'modified') {
|
|
373
|
+
framesEl.className = 'frames split';
|
|
374
|
+
framesEl.appendChild(buildPane('Before', page.beforeSrc, 'before'));
|
|
375
|
+
framesEl.appendChild(buildPane('After', page.afterSrc, 'after'));
|
|
376
|
+
} else if (page.kind === 'added') {
|
|
377
|
+
framesEl.className = 'frames single';
|
|
378
|
+
framesEl.appendChild(buildPane('After (new)', page.afterSrc, 'after'));
|
|
379
|
+
} else if (page.kind === 'removed') {
|
|
380
|
+
framesEl.className = 'frames single';
|
|
381
|
+
framesEl.appendChild(buildPane('Before (removed)', page.beforeSrc, 'before'));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function buildPane(label, src, side) {
|
|
386
|
+
const pane = document.createElement('div');
|
|
387
|
+
pane.className = 'pane';
|
|
388
|
+
const heading = document.createElement('h2');
|
|
389
|
+
heading.className = side;
|
|
390
|
+
const sideBadge = document.createElement('span');
|
|
391
|
+
sideBadge.className = 'side-badge';
|
|
392
|
+
sideBadge.textContent = side;
|
|
393
|
+
heading.appendChild(sideBadge);
|
|
394
|
+
heading.appendChild(document.createTextNode(' ' + label));
|
|
395
|
+
pane.appendChild(heading);
|
|
396
|
+
const frame = document.createElement('iframe');
|
|
397
|
+
frame.src = src;
|
|
398
|
+
frame.loading = 'lazy';
|
|
399
|
+
frame.title = label;
|
|
400
|
+
pane.appendChild(frame);
|
|
401
|
+
return pane;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
prevBtn.addEventListener('click', () => { if (index > 0) { index--; render(); } });
|
|
405
|
+
nextBtn.addEventListener('click', () => { if (index < pages.length - 1) { index++; render(); } });
|
|
406
|
+
document.addEventListener('keydown', (event) => {
|
|
407
|
+
if (event.key === 'ArrowLeft') { prevBtn.click(); }
|
|
408
|
+
else if (event.key === 'ArrowRight') { nextBtn.click(); }
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
emptyEl.remove();
|
|
412
|
+
render();
|
|
413
|
+
})();
|
|
414
|
+
</script>
|
|
415
|
+
</body>
|
|
416
|
+
</html>
|
|
417
|
+
`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function runOpen(opts, io) {
|
|
421
|
+
const projectRoot = opts.projectRoot || findProjectRoot(process.cwd());
|
|
422
|
+
if (!projectRoot) {
|
|
423
|
+
io.stderr.write(
|
|
424
|
+
`Could not find resources/project-architecture/index.html. Pass --project <root> or generate the atlas via the init-project-html skill.\n`,
|
|
425
|
+
);
|
|
426
|
+
return 1;
|
|
427
|
+
}
|
|
428
|
+
const atlas = path.join(projectRoot, ATLAS_REL);
|
|
429
|
+
if (!fs.existsSync(atlas)) {
|
|
430
|
+
io.stderr.write(`Atlas not found: ${atlas}\n`);
|
|
431
|
+
return 1;
|
|
432
|
+
}
|
|
433
|
+
io.stdout.write(`${atlas}\n`);
|
|
434
|
+
if (opts.open) openInBrowser(atlas);
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function runDiff(opts, io) {
|
|
439
|
+
const projectRoot = opts.projectRoot || findProjectRoot(process.cwd());
|
|
440
|
+
if (!projectRoot) {
|
|
441
|
+
io.stderr.write(
|
|
442
|
+
`Could not find resources/project-architecture/index.html. Pass --project <root> or generate the atlas via the init-project-html skill.\n`,
|
|
443
|
+
);
|
|
444
|
+
return 1;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const outDir = opts.out || path.join(projectRoot, DEFAULT_OUT_REL);
|
|
448
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
449
|
+
|
|
450
|
+
const changes = collectChanges(projectRoot);
|
|
451
|
+
const html = renderViewer({ changes, projectRoot, outDir });
|
|
452
|
+
const indexPath = path.join(outDir, 'index.html');
|
|
453
|
+
fs.writeFileSync(indexPath, html, 'utf8');
|
|
454
|
+
|
|
455
|
+
io.stdout.write(`${indexPath}\n`);
|
|
456
|
+
io.stdout.write(
|
|
457
|
+
`Diff pages: ${changes.length} (modified=${changes.filter((c) => c.kind === 'modified').length}, added=${changes.filter((c) => c.kind === 'added').length}, removed=${changes.filter((c) => c.kind === 'removed').length})\n`,
|
|
458
|
+
);
|
|
459
|
+
if (opts.open) openInBrowser(indexPath);
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function main(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
464
|
+
let opts;
|
|
465
|
+
try {
|
|
466
|
+
opts = parseArgs(argv);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
io.stderr.write(`${error.message}\n\n${USAGE}\n`);
|
|
469
|
+
return 1;
|
|
470
|
+
}
|
|
471
|
+
if (opts.help) {
|
|
472
|
+
io.stdout.write(`${USAGE}\n`);
|
|
473
|
+
return 0;
|
|
474
|
+
}
|
|
475
|
+
if (opts.subcommand === 'open') return runOpen(opts, io);
|
|
476
|
+
if (opts.subcommand === 'diff') return runDiff(opts, io);
|
|
477
|
+
io.stderr.write(`Unknown subcommand: ${opts.subcommand}\n\n${USAGE}\n`);
|
|
478
|
+
return 1;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (require.main === module) {
|
|
482
|
+
const code = main(process.argv.slice(2));
|
|
483
|
+
process.exit(code);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
module.exports = {
|
|
487
|
+
parseArgs,
|
|
488
|
+
findProjectRoot,
|
|
489
|
+
collectChanges,
|
|
490
|
+
renderViewer,
|
|
491
|
+
toViewerRel,
|
|
492
|
+
walkArchitectureDiffDirs,
|
|
493
|
+
walkAfterStateHtml,
|
|
494
|
+
readRemovedManifest,
|
|
495
|
+
main,
|
|
496
|
+
};
|
|
Binary file
|
package/lib/cli.js
CHANGED
|
@@ -158,6 +158,8 @@ function buildHelpText({ version, colorEnabled }) {
|
|
|
158
158
|
' apltk all',
|
|
159
159
|
' apltk filter-logs app.log --start 2026-03-24T10:00:00Z',
|
|
160
160
|
' apltk create-specs "Membership upgrade flow" --change-name membership-upgrade-flow',
|
|
161
|
+
' apltk architecture # open resources/project-architecture/index.html',
|
|
162
|
+
' apltk architecture diff # paginated before/after view of all docs/plans/.../architecture_diff/',
|
|
161
163
|
' apltk tools',
|
|
162
164
|
' apollo-toolkit all',
|
|
163
165
|
'',
|
package/lib/tool-runner.js
CHANGED
|
@@ -3,6 +3,13 @@ const path = require('node:path');
|
|
|
3
3
|
const { spawn } = require('node:child_process');
|
|
4
4
|
|
|
5
5
|
const TOOL_COMMANDS = [
|
|
6
|
+
{
|
|
7
|
+
name: 'architecture',
|
|
8
|
+
skill: 'init-project-html',
|
|
9
|
+
script: 'init-project-html/scripts/architecture.js',
|
|
10
|
+
runner: 'node',
|
|
11
|
+
description: 'Open the project HTML architecture atlas, or render a paginated diff (`architecture diff`).',
|
|
12
|
+
},
|
|
6
13
|
{
|
|
7
14
|
name: 'filter-logs',
|
|
8
15
|
skill: 'analyse-app-logs',
|