@laitszkin/apollo-toolkit 3.9.7 → 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.
Files changed (44) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +6 -0
  4. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  5. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  6. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  7. package/cjk-pdf/agents/openai.yaml +5 -0
  8. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  9. package/generate-spec/SKILL.md +24 -4
  10. package/generate-spec/agents/openai.yaml +1 -0
  11. package/generate-spec/references/TEMPLATE_SPEC.md +98 -0
  12. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  13. package/init-project-html/SKILL.md +181 -0
  14. package/init-project-html/agents/openai.yaml +13 -0
  15. package/init-project-html/references/TEMPLATE_SPEC.md +98 -0
  16. package/init-project-html/references/architecture-page.template.html +35 -0
  17. package/init-project-html/references/architecture.css +1059 -0
  18. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +1059 -0
  19. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +54 -0
  20. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +165 -0
  21. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +198 -0
  22. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +165 -0
  23. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +152 -0
  24. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +140 -0
  25. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +53 -0
  26. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +161 -0
  27. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +145 -0
  28. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +190 -0
  29. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +140 -0
  30. package/init-project-html/sample-demo/resources/project-architecture/index.html +337 -0
  31. package/init-project-html/scripts/architecture.js +496 -0
  32. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  33. package/lib/cli.js +2 -0
  34. package/lib/tool-runner.js +7 -0
  35. package/merge-conflict-resolver/agents/openai.yaml +5 -0
  36. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  37. package/package.json +1 -1
  38. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  39. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  40. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  41. package/spec-to-project-html/SKILL.md +116 -0
  42. package/spec-to-project-html/agents/openai.yaml +12 -0
  43. package/spec-to-project-html/references/TEMPLATE_SPEC.md +98 -0
  44. 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, '&amp;')
220
+ .replace(/</g, '&lt;')
221
+ .replace(/>/g, '&gt;')
222
+ .replace(/"/g, '&quot;')
223
+ .replace(/'/g, '&#39;');
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
+ };
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
  '',
@@ -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',
@@ -0,0 +1,5 @@
1
+ interface:
2
+ display_name: "merge-conflict-resolver"
3
+ short_description: "Resolve git merge conflicts with deterministic, evidence-based rules"
4
+ default_prompt: >-
5
+ Use $merge-conflict-resolver to read both sides of conflict markers, apply scenario-specific resolution rules that preserve intended behavior, verify with the project’s tests or checks, and when the user needs persistence, hand off to $commit-and-push instead of ad-hoc git commands.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laitszkin/apollo-toolkit",
3
- "version": "3.9.7",
3
+ "version": "3.10.0",
4
4
  "description": "Apollo Toolkit npm installer for managed skill copying across Codex, OpenClaw, and Trae.",
5
5
  "license": "MIT",
6
6
  "author": "LaiTszKin",
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: spec-to-project-html
3
+ description: >-
4
+ Sync HTML architecture pages to active planning specs. **MUST** preserve the two-layer rule: macro `index.html` stays ONE SVG showing both feature-module and sub-module interactions (multi-edge, producer/consumer loops, cross-feature data-row); each sub-module page stays self-only (function I/O + variables-with-business-purpose + internal data flow + local errors). Feature `index.html` stays lightweight (story + submodule-nav). Read strategy mirrors `init-project-html`: list affected feature modules first; with subagents, dispatch one read-only subagent per affected feature to return summaries before patching; without subagents, patch one feature's pages plus its macro slice before moving to the next — never load every affected feature's code into the main context at once. Ground in repo evidence; mark `TBD` when code is missing.
5
+ ---
6
+
7
+ # Spec To Project HTML
8
+
9
+ ## Dependencies
10
+
11
+ - Required: **`init-project-html`** — its `SKILL.md` is the binding rulebook for page contracts (Rules 1–7), and its `references/architecture.css` is the shared asset to copy. Local cheat sheet (same vocabulary + class hooks + DOM snippets) lives in this skill's own `references/TEMPLATE_SPEC.md`.
12
+ - Conditional: **`recover-missing-plan`** if a spec pointer is missing.
13
+ - Conditional: **`generate-spec`** / **`implement-specs*`** terminology for requirement IDs.
14
+ - Optional: **`review-spec-related-changes`** when verifying diagram updates against specs.
15
+ - Fallback: spec demands components that are absent from code → label **planned / gap** in macro summary and on affected nodes; **MUST NOT** mark them as implemented.
16
+
17
+ ## Non-negotiables
18
+
19
+ - **MUST** read specs in order unless the user directs otherwise: `spec.md` → `design.md` → `contract.md` → coordination notes.
20
+ - **MUST** open and parse the existing macro `index.html`, every `features/<slug>/index.html`, **and every sub-module HTML** before editing — preserve `data-feature-id` / `data-submodule-id` / `data-edge-id` unless a rename is explicit and propagated everywhere (including the macro SVG nav and the macro manifest).
21
+ - **MUST** obey `init-project-html/SKILL.md` Rules 1–7 (page contracts, naming, assets, accessibility, forbidden shortcuts). Two reminders that this skill violates most often:
22
+ - **Rule 1** — cross-submodule interactions live only in the macro SVG (multi-edge, producer/consumer, cross-feature data-row); the macro **MUST** index every sub-module page.
23
+ - **Rule 2** — sub-module pages contain only `sub-io` + `sub-vars` (variables-with-business-purpose) + `sub-dataflow` + `sub-errors`; no cross-boundary narrative.
24
+ - **MUST** reconcile new requirements and design deltas:
25
+ - In the macro SVG, add/remove/relabel sub-module nodes and edges; every new node also gets a sub-module HTML and a manifest row.
26
+ - On sub-module pages, add/remove/update function I/O and internal data flow; **never** describe cross-boundary interactions here — update the macro edges instead.
27
+ - **MUST** migrate any leftover legacy structure during the same edit pass:
28
+ - feature index `flow-chart--submodules` / `flow-edge-manifest` / `flow-return-row` → move to the macro; feature index reverts to lightweight.
29
+ - sub-module page `submodule-role` cross-boundary prose or cross-boundary manifests → delete or compress into a single "see macro" link.
30
+ - legacy single-file `features/<slug>.html` → migrate to the directory layout.
31
+ - **MUST NOT** shrink the macro into prose-only or feature-only diagrams: the multi-edge + sub-module layer must remain visible in the SVG.
32
+ - **MUST NOT** drop modules that are still present in code just because the spec omits them — keep them, or annotate as "out of spec scope" with a reason.
33
+ - **MUST** scope reads to the **affected feature modules** identified from the spec/design diff (plus any feature owning a cross-feature edge into an affected one). Apply the same context-safe read strategy as `init-project-html` Rule 3:
34
+ - **With subagents** — main agent lists affected features first, then dispatches **one read-only subagent per affected feature** to deep-read and return a structured change summary (affected sub-modules, variable / I/O / boundary deltas). Main agent **only** receives summaries, and only after every subagent reports does it patch the macro and pages in one pass.
35
+ - **Without subagents** — process features one at a time: read one affected feature, **immediately** patch its `features/<slug>/` tree plus the macro cluster/nodes/edges (mark edges pointing at unread affected features with `data-edge-target-pending`). Drop function-level details from memory before reading the next feature. After all features are patched, resolve every pending edge in a final pass.
36
+ - **Forbidden**: loading every affected feature's source into the main agent's context before patching — early details get pushed out and macro/sub-module pages contradict each other.
37
+
38
+ ## Standards (summary)
39
+
40
+ - **Evidence**: cite the spec passage (design subsystem entry) alongside the code path.
41
+ - **Execution**: locate the plan set → read the existing HTML tree → list affected feature modules → branch by environment (subagent fan-out OR sequential read-patch-write; resolve macro pending edges last) → link check.
42
+ - **Quality**: the macro SVG keeps "≥2 edges on one node pair", "at least one producer/consumer loop", and "at least one cross-feature data-row" (when the spec still requires it); sub-module pages stay self-only; CSS paths correct; no pending edges left.
43
+ - **Output**: touches only `resources/project-architecture/**`. If files are missing, scaffold via `init-project-html` first, then merge spec deltas.
44
+
45
+ ## Workflow
46
+
47
+ ### 1) Resolve spec inputs
48
+
49
+ User-pointed path wins; otherwise use the batch `coordination.md` or `recover-missing-plan` to find the most recent plan; collect `R…` / `INT-…` IDs for traceability.
50
+
51
+ ### 2) List the affected feature modules (no function bodies yet)
52
+
53
+ Derive from the spec/design diff which feature modules change: new nodes, edge changes, variable changes, error changes, retired sub-modules… **Also** include any feature that owns the other end of a cross-feature edge that is being changed (even if that feature's own spec is untouched). Record only `slug + change-kind`; do not enter function bodies yet.
54
+
55
+ ### 3) Read the current HTML atlas (scoped to affected features)
56
+
57
+ Load the macro `index.html` + every affected feature directory + that directory's sub-module pages; list existing node/edge IDs; lock the IDs to preserve (so steps 4/5 do not silently rename).
58
+
59
+ ### 4) Branch the deep-read + patch by environment (mirrors `init-project-html` Rule 3)
60
+
61
+ #### 4A) With subagents (preferred)
62
+
63
+ Dispatch one **read-only subagent per affected feature**, requiring this summary:
64
+
65
+ > **Feature `<slug>` change summary**
66
+ > - Matching spec passages / requirement IDs.
67
+ > - Affected sub-modules (added / renamed / retired / I/O changed).
68
+ > - Per sub-module: function I/O, variables-with-business-purpose (additions / removals / renames), internal flow, errors raised.
69
+ > - Boundary changes: new / changed / removed call edges and data-row edges (name the other-end feature / sub-module).
70
+ > - Spec items the code does not yet scaffold: mark as `planned` / `gap`.
71
+
72
+ Main agent collects summaries and patches in one pass:
73
+
74
+ 1. Patch the macro `index.html`: add/change/remove `<a><g class="m-sub">` nodes; add/change/remove `<path class="m-edge ...">`; mirror in `flow-edge-manifest--macro`; preserve multi-edge, call/return pairs, cross-feature data-row.
75
+ 2. Patch each affected `features/<slug>/index.html` (still lightweight) and each affected sub-module page (self-only: `sub-io`, `sub-vars`, `sub-dataflow`, `sub-errors` all in sync).
76
+ 3. Update `<nav class="atlas-submodule-index">`: link new pages; remove retired pages.
77
+
78
+ - **Pause →** Do `planned` / `gap` nodes appear consistently in both the macro and `atlas-summary` as "not yet implemented"? Inconsistency would mislead readers.
79
+
80
+ #### 4B) Without subagents — feature-by-feature read-patch-write
81
+
82
+ Process the step-2 list one feature at a time. Per feature, run the full inner loop:
83
+
84
+ 1. **Deep-read** this feature's affected sub-modules (functions, variables, boundaries).
85
+ 2. **Patch immediately**:
86
+ - `features/<slug>/index.html` (refresh user story; add/remove rows in `submodule-nav`; do NOT re-introduce a cross-submodule flowchart).
87
+ - Each affected sub-module page:
88
+ - Function I/O table updated (additions / renames / signature changes).
89
+ - `sub-vars`: add, remove, or rename variables (including DB columns, config knobs, counters, time anchors) introduced by the spec/design; rewrite the business-purpose column to match the new branches.
90
+ - `sub-dataflow` steps + small SVG refreshed.
91
+ - `sub-errors` updated with any new error types.
92
+ - Any leftover "who I talk to" text → move into a macro edge, or delete.
93
+ 3. **Incremental macro patch**: sync this feature's nodes/edges/manifest rows; mark edges pointing at **unread** affected features as `data-edge-target-pending="<future-slug>"`.
94
+ 4. **Drop function-level memory** of this feature; keep only cluster id and pending-edge notes.
95
+ 5. Return to step 1 for the next feature.
96
+
97
+ Final pass after every feature is patched:
98
+
99
+ - Resolve every macro `pending` edge; none may remain.
100
+ - Verify `<nav class="atlas-submodule-index">` matches the final set of sub-module pages.
101
+
102
+ - **Pause →** Pending edges left? Step 2 missed an affected feature — re-read and patch.
103
+
104
+ ### 5) Traceability (suggested)
105
+
106
+ Use `feature-trace` to map spec IDs to implementation status: `met` / `partial` / `planned` / `gap`.
107
+
108
+ ### 6) Report
109
+
110
+ List touched files, new edges / nodes, the read strategy used (4A or 4B), unresolved spec-vs-code gaps, and any follow-ups.
111
+
112
+ ## Sample hints
113
+
114
+ - **Batch merge**: when one domain has multiple specs, decide first whether sub-modules merge; keep node IDs unique.
115
+ - **Spec shrinks scope**: soften or remove nodes, but keep a `figcaption` footnote in the macro explaining the retired requirement.
116
+ - **Design-only change**: still re-review macro edges — interaction order shifts even when no module is added.