@monoes/monodesign 1.2.20 → 1.2.22

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 (85) hide show
  1. package/README.md +2 -2
  2. package/cli/engine/design-system-checks.mjs +381 -0
  3. package/cli/engine/design-system-parse.mjs +433 -0
  4. package/cli/engine/design-system.mjs +20 -736
  5. package/cli/engine/engines/regex/detect-text-matchers.mjs +371 -0
  6. package/cli/engine/engines/regex/detect-text-run.mjs +186 -0
  7. package/cli/engine/engines/regex/detect-text.mjs +11 -523
  8. package/cli/engine/engines/static-html/css-cascade-color.mjs +219 -0
  9. package/cli/engine/engines/static-html/css-cascade-static-data.mjs +138 -0
  10. package/cli/engine/engines/static-html/css-cascade-static-dom.mjs +132 -0
  11. package/cli/engine/engines/static-html/css-cascade-static-parse.mjs +393 -0
  12. package/cli/engine/engines/static-html/css-cascade.mjs +24 -837
  13. package/cli/lib/monodesign-config-consent.mjs +93 -0
  14. package/cli/lib/monodesign-config-core.mjs +37 -0
  15. package/cli/lib/monodesign-config-detection.mjs +133 -0
  16. package/cli/lib/monodesign-config-filter.mjs +198 -0
  17. package/cli/lib/monodesign-config-ignore-value.mjs +212 -0
  18. package/cli/lib/monodesign-config.mjs +21 -623
  19. package/package.json +2 -2
  20. package/skill/scripts/context-fields.mjs +65 -0
  21. package/skill/scripts/context-resolve.mjs +425 -0
  22. package/skill/scripts/context-update.mjs +151 -0
  23. package/skill/scripts/context-utils.mjs +68 -0
  24. package/skill/scripts/context-workspace.mjs +245 -0
  25. package/skill/scripts/context.mjs +17 -891
  26. package/skill/scripts/hook-admin-config.mjs +152 -0
  27. package/skill/scripts/hook-admin-ignore.mjs +131 -0
  28. package/skill/scripts/hook-admin-manifest.mjs +292 -0
  29. package/skill/scripts/hook-admin.mjs +8 -541
  30. package/skill/scripts/hook-lib-cache.mjs +145 -0
  31. package/skill/scripts/hook-lib-config.mjs +292 -0
  32. package/skill/scripts/hook-lib-detector.mjs +72 -0
  33. package/skill/scripts/hook-lib-events.mjs +182 -0
  34. package/skill/scripts/hook-lib-findings.mjs +201 -0
  35. package/skill/scripts/hook-lib-ignore.mjs +205 -0
  36. package/skill/scripts/hook-lib-render.mjs +273 -0
  37. package/skill/scripts/hook-lib-run.mjs +281 -0
  38. package/skill/scripts/hook-lib-scan-targets.mjs +128 -0
  39. package/skill/scripts/hook-lib.mjs +71 -1747
  40. package/skill/scripts/lib/design-parser-colors.mjs +182 -0
  41. package/skill/scripts/lib/design-parser-extract.mjs +333 -0
  42. package/skill/scripts/lib/design-parser-sections.mjs +214 -0
  43. package/skill/scripts/lib/design-parser-yaml.mjs +135 -0
  44. package/skill/scripts/lib/design-parser.mjs +10 -783
  45. package/skill/scripts/live/manual-apply-chunk.mjs +151 -0
  46. package/skill/scripts/live/manual-apply-compact.mjs +122 -0
  47. package/skill/scripts/live/manual-apply-evidence.mjs +115 -0
  48. package/skill/scripts/live/manual-apply-log.mjs +179 -0
  49. package/skill/scripts/live/manual-apply-transaction.mjs +122 -0
  50. package/skill/scripts/live/manual-apply.mjs +65 -640
  51. package/skill/scripts/live/svelte-component-accept.mjs +248 -0
  52. package/skill/scripts/live/svelte-component-css.mjs +230 -0
  53. package/skill/scripts/live/svelte-component-markup.mjs +104 -0
  54. package/skill/scripts/live/svelte-component-scaffold.mjs +118 -0
  55. package/skill/scripts/live/svelte-component-session.mjs +158 -0
  56. package/skill/scripts/live/svelte-component.mjs +32 -792
  57. package/skill/scripts/live-accept-mutate.mjs +152 -0
  58. package/skill/scripts/live-accept-parse.mjs +370 -0
  59. package/skill/scripts/live-accept-scrub.mjs +59 -0
  60. package/skill/scripts/live-accept-search.mjs +58 -0
  61. package/skill/scripts/live-accept.mjs +15 -631
  62. package/skill/scripts/live-commit-manual-edits-paths.mjs +27 -0
  63. package/skill/scripts/live-commit-manual-edits-repair.mjs +166 -0
  64. package/skill/scripts/live-commit-manual-edits-rollback.mjs +231 -0
  65. package/skill/scripts/live-commit-manual-edits-utils.mjs +167 -0
  66. package/skill/scripts/live-commit-manual-edits-verify.mjs +356 -0
  67. package/skill/scripts/live-commit-manual-edits.mjs +24 -881
  68. package/skill/scripts/live-copy-edit-agent.mjs +16 -551
  69. package/skill/scripts/live-copy-edit-compact.mjs +137 -0
  70. package/skill/scripts/live-copy-edit-post-apply.mjs +175 -0
  71. package/skill/scripts/live-copy-edit-runners.mjs +297 -0
  72. package/skill/scripts/live-inject-csp.mjs +132 -0
  73. package/skill/scripts/live-inject-files.mjs +229 -0
  74. package/skill/scripts/live-inject-tag.mjs +114 -0
  75. package/skill/scripts/live-inject.mjs +13 -440
  76. package/skill/scripts/live-server-poll.mjs +232 -0
  77. package/skill/scripts/live-server-routes.mjs +449 -0
  78. package/skill/scripts/live-server-state.mjs +361 -0
  79. package/skill/scripts/live-server.mjs +10 -946
  80. package/skill/scripts/live-wrap-css.mjs +132 -0
  81. package/skill/scripts/live-wrap-manual-edit.mjs +153 -0
  82. package/skill/scripts/live-wrap-search.mjs +247 -0
  83. package/skill/scripts/live-wrap.mjs +21 -484
  84. package/skill/scripts/palette-seeds.mjs +401 -0
  85. package/skill/scripts/palette.mjs +1 -395
@@ -0,0 +1,361 @@
1
+ // Live server session state: config, the shared `state` object, the pending
2
+ // event/poll queue, manual-edit coordination, and small request-auth helpers.
3
+ // Split out of live-server.mjs (file-size sweep — pure move, no behaviour
4
+ // change). Imported by live-server-routes.mjs, live-server-poll.mjs and
5
+ // live-server.mjs itself.
6
+
7
+ import path from 'node:path';
8
+ import fs from 'node:fs';
9
+ import net from 'node:net';
10
+ import { loadContext } from './context.mjs';
11
+ import { getLiveDir } from './lib/monodesign-paths.mjs';
12
+ import { countByPage as countPendingByPage } from './live/manual-edits-buffer.mjs';
13
+ import { createManualApplyController } from './live/manual-apply.mjs';
14
+ import { createManualEditRoutes } from './live/manual-edit-routes.mjs';
15
+
16
+ // PRODUCT.md / DESIGN.md live wherever context.mjs resolves. The generated
17
+ // DESIGN sidecar is project-local at .monodesign/design.json, with legacy
18
+ // DESIGN.json fallback for existing projects.
19
+ const PROJECT_CONTEXT = loadContext(process.cwd());
20
+ const CONTEXT_DIR = PROJECT_CONTEXT.contextDir;
21
+ const DESIGN_MD_PATH = PROJECT_CONTEXT.designPath
22
+ ? path.resolve(process.cwd(), PROJECT_CONTEXT.designPath)
23
+ : null;
24
+ const DEFAULT_POLL_TIMEOUT = 600_000; // 10 min — agent re-polls on timeout anyway
25
+ const SSE_HEARTBEAT_INTERVAL = 30_000; // keepalive ping every 30s
26
+ // Query param name every protected route checks against the per-process credential.
27
+ const AUTH_QUERY_KEY = 'token';
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Port detection
31
+ // ---------------------------------------------------------------------------
32
+
33
+ async function findOpenPort(start = 8400) {
34
+ return new Promise((resolve) => {
35
+ const srv = net.createServer();
36
+ srv.listen(start, '127.0.0.1', () => {
37
+ const port = srv.address().port;
38
+ srv.close(() => resolve(port));
39
+ });
40
+ srv.on('error', () => resolve(findOpenPort(start + 1)));
41
+ });
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Session state
46
+ // ---------------------------------------------------------------------------
47
+
48
+ const state = {
49
+ token: null,
50
+ port: null,
51
+ sseClients: new Set(), // SSE response objects (server→browser push)
52
+ pendingEvents: [], // browser events waiting for agent ack ({ event, leaseUntil })
53
+ pendingPolls: [], // agent poll callbacks waiting for browser events
54
+ nextEventSeq: 1,
55
+ lastAgentPollingBroadcast: null,
56
+ exitTimer: null,
57
+ sessionDir: null, // per-session tmp dir for annotation screenshots
58
+ sessionStore: null,
59
+ leaseTimer: null,
60
+ manualEditActivity: null,
61
+ nextManualEditSeq: 1,
62
+ // Deferreds for in-flight chat-routed Apply events. Keyed by event id; each
63
+ // entry is resolved when the chat agent POSTs an ack carrying the batch
64
+ // result, or rejected when the hard timeout fires.
65
+ pendingApplyDeferreds: new Map(),
66
+ // Updated whenever a /poll long-poll request arrives or is resolved with an
67
+ // event. Used to detect "a chat agent is likely attached" without requiring
68
+ // a poll to be parked at the exact moment we dispatch.
69
+ lastPollAt: 0,
70
+ timedOutApplyIds: new Map(),
71
+ };
72
+
73
+ const CHAT_POLL_FRESHNESS_MS = 60_000;
74
+ const POLL_LEASE_EXPIRY_TIMER_GRACE_MS = 2;
75
+ const DEBUG_MANUAL_EDIT_EVENTS = /^(1|true|yes)$/i.test(process.env.MONODESIGN_LIVE_DEBUG_EVENTS || '');
76
+
77
+ const manualApply = createManualApplyController({
78
+ pendingEvents: state.pendingEvents,
79
+ pendingApplyDeferreds: state.pendingApplyDeferreds,
80
+ timedOutApplyIds: state.timedOutApplyIds,
81
+ enqueueEvent,
82
+ acknowledgePendingEvent,
83
+ flushPendingPolls,
84
+ recordManualEditActivity,
85
+ cwd: () => process.cwd(),
86
+ });
87
+
88
+ const manualEditRoutes = createManualEditRoutes({
89
+ getToken: () => state.token,
90
+ manualApply,
91
+ recordManualEditActivity,
92
+ getManualEditStatus,
93
+ chatAgentLikelyActive,
94
+ cwd: () => process.cwd(),
95
+ env: () => process.env,
96
+ });
97
+
98
+ function chatAgentLikelyActive() {
99
+ if (state.pendingPolls.length > 0) return true;
100
+ if (!state.lastPollAt) return false;
101
+ return Date.now() - state.lastPollAt < CHAT_POLL_FRESHNESS_MS;
102
+ }
103
+
104
+ // Cap per-annotation upload size. A full 1920×1080 PNG is typically <1 MB;
105
+ // cap at 10 MB to guard against runaway writes from a misbehaving client.
106
+ const MAX_ANNOTATION_BYTES = 10 * 1024 * 1024;
107
+
108
+ function enqueueEvent(event) {
109
+ if (!event || (event.id && state.pendingEvents.some((entry) => entry.event?.id === event.id && entry.event?.type === event.type))) return;
110
+ state.pendingEvents.push({ event, leaseUntil: 0, seq: state.nextEventSeq++ });
111
+ flushPendingPolls();
112
+ }
113
+
114
+ function restorePendingEventsFromStore() {
115
+ if (!state.sessionStore) return;
116
+ for (const snapshot of state.sessionStore.listActiveSessions()) {
117
+ if (snapshot.pendingEvent) enqueueEvent(snapshot.pendingEvent);
118
+ }
119
+ }
120
+
121
+ function findAvailablePendingEvent(now = Date.now()) {
122
+ for (const entry of state.pendingEvents) {
123
+ if (entry.leaseUntil && entry.leaseUntil > now) continue;
124
+ return entry;
125
+ }
126
+ return null;
127
+ }
128
+
129
+ function leaseEvent(entry, leaseMs) {
130
+ if (!entry.event?.id) {
131
+ const idx = state.pendingEvents.indexOf(entry);
132
+ if (idx !== -1) state.pendingEvents.splice(idx, 1);
133
+ return entry.event;
134
+ }
135
+ entry.leaseUntil = Date.now() + leaseMs;
136
+ scheduleLeaseFlush();
137
+ broadcastAgentPollingIfChanged();
138
+ return entry.event;
139
+ }
140
+
141
+ function acknowledgePendingEvent(id) {
142
+ if (!id) return false;
143
+ const idx = state.pendingEvents.findIndex((entry) => entry.event?.id === id);
144
+ if (idx === -1) return false;
145
+ const acknowledged = state.pendingEvents[idx].event;
146
+ state.pendingEvents.splice(idx, 1);
147
+ scheduleLeaseFlush();
148
+ broadcastAgentPollingIfChanged();
149
+ return acknowledged;
150
+ }
151
+
152
+ function findPendingEventById(id) {
153
+ if (!id) return null;
154
+ const entry = state.pendingEvents.find((item) => item.event?.id === id);
155
+ return entry?.event || null;
156
+ }
157
+
158
+ function summarizePendingEventForStatus(entry) {
159
+ const event = entry.event || {};
160
+ const summary = {
161
+ id: event.id,
162
+ type: event.type,
163
+ leased: !!(entry.leaseUntil && entry.leaseUntil > Date.now()),
164
+ leaseUntil: entry.leaseUntil || null,
165
+ };
166
+ if (event.type === 'manual_edit_apply') {
167
+ summary.pageUrl = event.pageUrl || null;
168
+ summary.chunk = event.chunk || null;
169
+ summary.repair = event.repair || null;
170
+ summary.evidencePath = event.evidencePath || null;
171
+ summary.agentAction = event.agentAction || manualApply.buildAgentAction(event);
172
+ summary.manualApplySummary = manualApply.summarizeEvent(event, manualApply.getDeferred(event.id)?.batch || event.batch);
173
+ }
174
+ return summary;
175
+ }
176
+
177
+ function summarizeActiveSessionForClient(snapshot = {}) {
178
+ return {
179
+ id: snapshot.id,
180
+ phase: snapshot.phase,
181
+ pageUrl: snapshot.pageUrl ?? null,
182
+ sourceFile: snapshot.sourceFile ?? null,
183
+ previewFile: snapshot.previewFile ?? null,
184
+ previewMode: snapshot.previewMode ?? null,
185
+ expectedVariants: snapshot.expectedVariants ?? 0,
186
+ arrivedVariants: snapshot.arrivedVariants ?? 0,
187
+ visibleVariant: snapshot.visibleVariant ?? null,
188
+ checkpointRevision: snapshot.checkpointRevision ?? 0,
189
+ paramValues: snapshot.paramValues || {},
190
+ };
191
+ }
192
+
193
+ function activeSessionSummaries() {
194
+ if (!state.sessionStore) return [];
195
+ return state.sessionStore.listActiveSessions().map((snapshot) => summarizeActiveSessionForClient(snapshot));
196
+ }
197
+
198
+ function cancelQueuedAnonymousExitEvents() {
199
+ let removed = 0;
200
+ for (let i = state.pendingEvents.length - 1; i >= 0; i -= 1) {
201
+ const event = state.pendingEvents[i]?.event;
202
+ if (event?.type !== 'exit' || event.id) continue;
203
+ state.pendingEvents.splice(i, 1);
204
+ removed += 1;
205
+ }
206
+ if (removed > 0) {
207
+ scheduleLeaseFlush();
208
+ broadcastAgentPollingIfChanged();
209
+ }
210
+ return removed;
211
+ }
212
+
213
+ function scheduleLeaseFlush() {
214
+ if (state.leaseTimer) {
215
+ clearTimeout(state.leaseTimer);
216
+ state.leaseTimer = null;
217
+ }
218
+ const now = Date.now();
219
+ const nextLeaseUntil = state.pendingEvents
220
+ .map((entry) => entry.leaseUntil || 0)
221
+ .filter((leaseUntil) => leaseUntil > now)
222
+ .sort((a, b) => a - b)[0];
223
+ if (!nextLeaseUntil) return;
224
+ state.leaseTimer = setTimeout(() => {
225
+ state.leaseTimer = null;
226
+ flushPendingPolls();
227
+ broadcastAgentPollingIfChanged();
228
+ }, Math.max(0, nextLeaseUntil - now + POLL_LEASE_EXPIRY_TIMER_GRACE_MS));
229
+ }
230
+
231
+ function flushPendingPolls() {
232
+ let changed = false;
233
+ while (state.pendingPolls.length > 0) {
234
+ const entry = findAvailablePendingEvent();
235
+ if (!entry) {
236
+ scheduleLeaseFlush();
237
+ broadcastAgentPollingIfChanged();
238
+ return;
239
+ }
240
+ const poll = state.pendingPolls.shift();
241
+ poll.resolve(leaseEvent(entry, poll.leaseMs));
242
+ changed = true;
243
+ }
244
+ scheduleLeaseFlush();
245
+ if (changed) broadcastAgentPollingIfChanged();
246
+ }
247
+
248
+ function agentPollingConnected() {
249
+ const now = Date.now();
250
+ return state.pendingPolls.length > 0
251
+ || state.pendingEvents.some((entry) => entry.leaseUntil && entry.leaseUntil > now);
252
+ }
253
+
254
+ function broadcastAgentPollingIfChanged() {
255
+ const connected = agentPollingConnected();
256
+ if (state.lastAgentPollingBroadcast === connected) return;
257
+ state.lastAgentPollingBroadcast = connected;
258
+ broadcast({ type: 'agent_polling', connected });
259
+ }
260
+
261
+ /** Push a message to all connected SSE clients. */
262
+ function broadcast(msg) {
263
+ const data = `data: ${JSON.stringify(msg)}\n\n`;
264
+ for (const res of state.sseClients) {
265
+ try { res.write(data); } catch { /* client gone */ }
266
+ }
267
+ }
268
+
269
+ function recordManualEditActivity(type, details = {}) {
270
+ const entry = {
271
+ seq: state.nextManualEditSeq++,
272
+ type,
273
+ ts: new Date().toISOString(),
274
+ ...details,
275
+ };
276
+ state.manualEditActivity = entry;
277
+ if (DEBUG_MANUAL_EDIT_EVENTS) {
278
+ try {
279
+ const filePath = path.join(getLiveDir(process.cwd()), 'manual-edit-events.jsonl');
280
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
281
+ fs.appendFileSync(filePath, `${JSON.stringify(entry)}\n`);
282
+ } catch {
283
+ /* diagnostics are best-effort; never block live mode on observability */
284
+ }
285
+ }
286
+ broadcast(entry);
287
+ return entry;
288
+ }
289
+
290
+ function getManualEditStatus() {
291
+ try {
292
+ const { totalCount, perPage } = countPendingByPage(process.cwd());
293
+ return { totalCount, perPage, lastActivity: state.manualEditActivity };
294
+ } catch (err) {
295
+ return {
296
+ totalCount: null,
297
+ perPage: {},
298
+ lastActivity: state.manualEditActivity,
299
+ error: err.message,
300
+ };
301
+ }
302
+ }
303
+ function hasProjectContext() {
304
+ // PRODUCT.md carries brand voice / anti-references — that's what determines
305
+ // whether variants are brand-aware. DESIGN.md (visual tokens) is a separate
306
+ // concern, surfaced by the design panel's own empty state.
307
+ return !!PROJECT_CONTEXT.hasProduct;
308
+ }
309
+
310
+ function statOrNull(filePath) {
311
+ try { return fs.statSync(filePath); } catch { return null; }
312
+ }
313
+
314
+ // Shared gate for every protected route, including /live.js (see its call
315
+ // site for why that one needs it too). Writes the 401 itself and returns
316
+ // false so callers can `if (!requireAuthQueryCred(url, res)) return;`.
317
+ function requireAuthQueryCred(url, res) {
318
+ const provided = url.searchParams.get(AUTH_QUERY_KEY);
319
+ const expected = state[AUTH_QUERY_KEY];
320
+ if (provided !== expected) {
321
+ res.writeHead(401);
322
+ res.end('Unauthorized');
323
+ return false;
324
+ }
325
+ return true;
326
+ }
327
+
328
+ export {
329
+ PROJECT_CONTEXT,
330
+ CONTEXT_DIR,
331
+ DESIGN_MD_PATH,
332
+ DEFAULT_POLL_TIMEOUT,
333
+ SSE_HEARTBEAT_INTERVAL,
334
+ AUTH_QUERY_KEY,
335
+ findOpenPort,
336
+ state,
337
+ MAX_ANNOTATION_BYTES,
338
+ manualApply,
339
+ manualEditRoutes,
340
+ chatAgentLikelyActive,
341
+ enqueueEvent,
342
+ restorePendingEventsFromStore,
343
+ findAvailablePendingEvent,
344
+ leaseEvent,
345
+ acknowledgePendingEvent,
346
+ findPendingEventById,
347
+ summarizePendingEventForStatus,
348
+ summarizeActiveSessionForClient,
349
+ activeSessionSummaries,
350
+ cancelQueuedAnonymousExitEvents,
351
+ scheduleLeaseFlush,
352
+ flushPendingPolls,
353
+ agentPollingConnected,
354
+ broadcastAgentPollingIfChanged,
355
+ broadcast,
356
+ recordManualEditActivity,
357
+ getManualEditStatus,
358
+ hasProjectContext,
359
+ statOrNull,
360
+ requireAuthQueryCred,
361
+ };