@aexol/spectral 0.9.44 → 0.9.46

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.
@@ -1 +1 @@
1
- {"version":3,"file":"crawler.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/analyzers/crawler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAe,WAAW,EAAE,WAAW,EAA8B,MAAM,aAAa,CAAC;AA4FrG,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,EACjB,WAAW,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GACjC,OAAO,CAAC,WAAW,CAAC,CAkKtB"}
1
+ {"version":3,"file":"crawler.d.ts","sourceRoot":"","sources":["../../../../src/extensions/seo/analyzers/crawler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAEV,WAAW,EACX,WAAW,EAGZ,MAAM,aAAa,CAAC;AAgHrB,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,EACjB,WAAW,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GACjC,OAAO,CAAC,WAAW,CAAC,CA+LtB"}
@@ -9,6 +9,26 @@ const DEFAULT_CRAWL_CONFIG = {
9
9
  crawlDelayMs: 500,
10
10
  includeExternal: false,
11
11
  };
12
+ /**
13
+ * Strip query parameters and hash for URL identity comparison.
14
+ * Two URLs that differ only by query params or hash should be considered
15
+ * the same page for crawling purposes — prevents re-crawling the same page
16
+ * hundreds of times when query params encode ephemeral UI state.
17
+ */
18
+ function urlIdentity(url) {
19
+ try {
20
+ const u = new URL(url);
21
+ u.search = "";
22
+ u.hash = "";
23
+ if (u.pathname.length > 1 && u.pathname.endsWith("/")) {
24
+ u.pathname = u.pathname.replace(/\/+$/, "");
25
+ }
26
+ return u.href;
27
+ }
28
+ catch {
29
+ return url;
30
+ }
31
+ }
12
32
  function extractInternalLinks(html, baseUrl) {
13
33
  const links = [];
14
34
  const hrefRegex = /<a\s[^>]*href\s*=\s*["']([^"']+)["'][^>]*>/gi;
@@ -18,22 +38,20 @@ function extractInternalLinks(html, baseUrl) {
18
38
  const resolved = new URL(match[1], baseUrl).href;
19
39
  const base = new URL(baseUrl);
20
40
  const resolvedUrl = new URL(resolved);
21
- // Only include same-host, non-fragment, non-anchor links
22
41
  if (resolvedUrl.hostname === base.hostname &&
23
42
  resolvedUrl.protocol.startsWith("http") &&
24
43
  !resolved.includes("#") &&
25
44
  !resolved.includes("javascript:") &&
26
- !resolved.includes("mailto:")) {
27
- // Normalize: remove trailing slash, lowercase path
45
+ !resolved.includes("mailto:") &&
46
+ !resolved.includes("/cdn-cgi/")) {
28
47
  const normalized = normalizeUrl(resolved);
29
48
  links.push(normalized);
30
49
  }
31
50
  }
32
51
  catch {
33
- // skip malformed URLs
52
+ // skip malformed hrefs
34
53
  }
35
54
  }
36
- // Deduplicate
37
55
  return [...new Set(links)];
38
56
  }
39
57
  function parseRobotsTxtForDisallowed(content, userAgent) {
@@ -53,7 +71,8 @@ function parseRobotsTxtForDisallowed(content, userAgent) {
53
71
  if (key === "user-agent") {
54
72
  currentUserAgents.push(value.toLowerCase());
55
73
  }
56
- else if (inRelevantBlock || currentUserAgents.some((ua) => ua === "*" || ua === userAgent.toLowerCase())) {
74
+ else if (inRelevantBlock ||
75
+ currentUserAgents.some((ua) => ua === "*" || ua === userAgent.toLowerCase())) {
57
76
  inRelevantBlock = true;
58
77
  if (key === "disallow") {
59
78
  if (value)
@@ -91,7 +110,6 @@ export async function crawlSite(startUrl, config, crawlConfig) {
91
110
  const cfg = { ...DEFAULT_CRAWL_CONFIG, ...crawlConfig };
92
111
  const normalizedStart = normalizeUrl(startUrl);
93
112
  const baseHost = new URL(normalizedStart).hostname;
94
- // Fetch robots.txt
95
113
  let disallowedPaths = [];
96
114
  let robotsCrawlDelay = null;
97
115
  if (cfg.respectRobotsTxt) {
@@ -104,23 +122,31 @@ export async function crawlSite(startUrl, config, crawlConfig) {
104
122
  }
105
123
  }
106
124
  catch {
107
- // robots.txt fetch failedproceed without restrictions
125
+ // robots.txt not availablecrawl freely
108
126
  }
109
127
  }
110
- const delayMs = robotsCrawlDelay ? robotsCrawlDelay * 1000 : cfg.crawlDelayMs;
111
- // BFS crawl
112
- const visited = new Set();
113
- const queue = [{ url: normalizedStart, depth: 0 }];
128
+ const delayMs = robotsCrawlDelay
129
+ ? robotsCrawlDelay * 1000
130
+ : cfg.crawlDelayMs;
131
+ const visitedIdentities = new Set();
132
+ const queuedIdentities = new Set();
133
+ const queue = [];
134
+ const startIdentity = urlIdentity(normalizedStart);
135
+ queuedIdentities.add(startIdentity);
136
+ queue.push({ url: normalizedStart, depth: 0 });
114
137
  const pages = [];
115
138
  const brokenLinks = [];
116
139
  const errors = [];
117
140
  const allDiscoveredLinks = new Set();
118
- while (queue.length > 0 && visited.size < cfg.maxPages) {
119
- // Process batch of concurrent pages
141
+ while (queue.length > 0 &&
142
+ visitedIdentities.size < cfg.maxPages) {
120
143
  const batch = [];
121
- while (batch.length < cfg.concurrency && queue.length > 0 && visited.size + batch.length < cfg.maxPages) {
144
+ while (batch.length < cfg.concurrency &&
145
+ queue.length > 0 &&
146
+ visitedIdentities.size + batch.length < cfg.maxPages) {
122
147
  const next = queue.shift();
123
- if (visited.has(next.url))
148
+ const identity = urlIdentity(next.url);
149
+ if (visitedIdentities.has(identity))
124
150
  continue;
125
151
  if (isDisallowed(next.url, disallowedPaths))
126
152
  continue;
@@ -165,21 +191,28 @@ export async function crawlSite(startUrl, config, crawlConfig) {
165
191
  continue;
166
192
  }
167
193
  const page = result.value;
168
- visited.add(page.url);
194
+ const identity = urlIdentity(page.url);
195
+ visitedIdentities.add(identity);
169
196
  pages.push(page);
170
- // Check for broken external links from page data
171
197
  if (page.pageData) {
172
198
  for (const link of page.pageData.links) {
173
199
  if (link.type === "external")
174
200
  continue;
175
201
  allDiscoveredLinks.add(link.href);
176
- // Internal links we haven't visited yet — add to queue
177
202
  const normalized = normalizeUrl(link.href);
178
- if (!visited.has(normalized) && normalized !== page.url) {
203
+ const linkIdentity = urlIdentity(normalized);
204
+ if (!visitedIdentities.has(linkIdentity) &&
205
+ !queuedIdentities.has(linkIdentity) &&
206
+ linkIdentity !== identity) {
179
207
  try {
180
208
  const linkUrl = new URL(normalized);
181
- if (linkUrl.hostname === baseHost && page.depth < cfg.maxDepth) {
182
- queue.push({ url: normalized, depth: page.depth + 1 });
209
+ if (linkUrl.hostname === baseHost &&
210
+ page.depth < cfg.maxDepth) {
211
+ queuedIdentities.add(linkIdentity);
212
+ queue.push({
213
+ url: normalized,
214
+ depth: page.depth + 1,
215
+ });
183
216
  }
184
217
  }
185
218
  catch {
@@ -188,13 +221,12 @@ export async function crawlSite(startUrl, config, crawlConfig) {
188
221
  }
189
222
  }
190
223
  }
191
- // Delay between batches
192
224
  if (delayMs > 0) {
193
225
  await sleep(delayMs);
194
226
  }
195
227
  }
196
228
  }
197
- // Detect broken links check pages with non-200 status
229
+ // Detect broken links (pages crawled with 4xx/5xx status)
198
230
  for (const page of pages) {
199
231
  if (page.statusCode >= 400) {
200
232
  brokenLinks.push({
@@ -205,9 +237,7 @@ export async function crawlSite(startUrl, config, crawlConfig) {
205
237
  });
206
238
  }
207
239
  }
208
- // Detect duplicate content
209
240
  const duplicateGroups = detectDuplicates(pages);
210
- // Orphan pages = pages only discovered via crawling that weren't linked from any other page
211
241
  const linkedPages = new Set();
212
242
  for (const page of pages) {
213
243
  for (const link of page.linksFound) {
@@ -217,7 +247,6 @@ export async function crawlSite(startUrl, config, crawlConfig) {
217
247
  const orphanPages = pages
218
248
  .filter((p) => p.depth === 0 && !linkedPages.has(p.url))
219
249
  .map((p) => p.url);
220
- // Build link graph
221
250
  const linkGraph = pages.map((p) => ({
222
251
  url: p.url,
223
252
  internalInlinks: pages.reduce((count, other) => count + (other.linksFound.includes(p.url) ? 1 : 0), 0),
@@ -241,10 +270,20 @@ export async function crawlSite(startUrl, config, crawlConfig) {
241
270
  }
242
271
  function detectDuplicates(pages) {
243
272
  const groups = [];
273
+ // Use urlIdentity for deduplication so /studio/ and /studio/?pako=...
274
+ // are counted as the same page
275
+ const identitySet = new Set();
276
+ const uniquePages = pages.filter((p) => {
277
+ const id = urlIdentity(p.url);
278
+ if (identitySet.has(id))
279
+ return false;
280
+ identitySet.add(id);
281
+ return true;
282
+ });
244
283
  const titleMap = new Map();
245
284
  const descMap = new Map();
246
285
  const h1Map = new Map();
247
- for (const page of pages) {
286
+ for (const page of uniquePages) {
248
287
  if (!page.pageData)
249
288
  continue;
250
289
  const title = page.pageData.title?.trim().toLowerCase();
@@ -269,17 +308,17 @@ function detectDuplicates(pages) {
269
308
  h1Map.get(h1).push(page.url);
270
309
  }
271
310
  }
272
- for (const [title, urls] of titleMap) {
311
+ for (const [value, urls] of titleMap) {
273
312
  if (urls.length > 1)
274
- groups.push({ type: "title", value: title, pageUrls: urls });
313
+ groups.push({ type: "title", value, pageUrls: urls });
275
314
  }
276
- for (const [desc, urls] of descMap) {
315
+ for (const [value, urls] of descMap) {
277
316
  if (urls.length > 1)
278
- groups.push({ type: "description", value: desc, pageUrls: urls });
317
+ groups.push({ type: "description", value, pageUrls: urls });
279
318
  }
280
- for (const [h1, urls] of h1Map) {
319
+ for (const [value, urls] of h1Map) {
281
320
  if (urls.length > 1)
282
- groups.push({ type: "h1", value: h1, pageUrls: urls });
321
+ groups.push({ type: "h1", value, pageUrls: urls });
283
322
  }
284
323
  return groups;
285
324
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-stream.d.ts","sourceRoot":"","sources":["../../src/server/session-stream.ts"],"names":[],"mappings":"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"}
@@ -125,80 +125,6 @@ function selectHistoryForBridge(history, hasMemorySnapshot) {
125
125
  const selected = history.slice(Math.max(0, start + 1));
126
126
  return selected.length > 0 ? selected : [history[history.length - 1]];
127
127
  }
128
- function prunePersistedHistoryAfterCompaction(store, sessionId, bridge) {
129
- if (!bridge.getSessionBranch)
130
- return;
131
- try {
132
- const entries = bridge.getSessionBranch();
133
- // Pi appends a CompactionEntry (type "compaction") at the end whose
134
- // firstKeptEntryId marks the first branch entry that survives in LLM context.
135
- // We scan backwards so we find the most recent compaction (there should be
136
- // exactly one per compaction pass).
137
- const lastCompaction = [...entries].reverse().find((entry) => entry.type === "compaction");
138
- const firstKeptEntryId = lastCompaction?.firstKeptEntryId;
139
- if (!firstKeptEntryId) {
140
- console.log(`[spectral] prune: skipped – no compaction entry (or no firstKeptEntryId) ` +
141
- `in branch for ${sessionId} (${entries.length} entries)`);
142
- return;
143
- }
144
- const keptEntryIndex = entries.findIndex((entry) => entry.id === firstKeptEntryId);
145
- if (keptEntryIndex === -1) {
146
- console.log(`[spectral] prune: skipped – firstKeptEntryId "${firstKeptEntryId}" ` +
147
- `not found in ${entries.length} branch entries for ${sessionId}`);
148
- return;
149
- }
150
- // Count user + assistant messages in the kept portion of the branch.
151
- // Tool-result entries are separate branch entries but are NOT separate
152
- // SQLite rows – they live inside the assistant message's events_jsonl.
153
- // Counting them would inflate the count and make the prune a silent no-op.
154
- let keptPersistedMessageCount = 0;
155
- for (let i = keptEntryIndex; i < entries.length; i++) {
156
- const entry = entries[i];
157
- if (entry.type !== "message")
158
- continue;
159
- const role = entry.message && typeof entry.message === "object" && "role" in entry.message
160
- ? entry.message.role
161
- : undefined;
162
- if (role === "user" || role === "assistant" || role === "system") {
163
- keptPersistedMessageCount += 1;
164
- }
165
- }
166
- const existing = store.getMessages(sessionId);
167
- if (existing.length === 0)
168
- return;
169
- console.log(`[spectral] prune: ${sessionId} – branch has ${entries.length} entries, ` +
170
- `kept portion starts at index ${keptEntryIndex} (id=${firstKeptEntryId}), ` +
171
- `${keptPersistedMessageCount} user+assistant entries kept, ` +
172
- `${existing.length} SQLite messages`);
173
- if (keptPersistedMessageCount <= 0) {
174
- console.log(`[spectral] prune: skipped – no kept messages for ${sessionId}`);
175
- return;
176
- }
177
- if (keptPersistedMessageCount >= existing.length) {
178
- console.log(`[spectral] prune: skipped – kept message count (${keptPersistedMessageCount}) ` +
179
- `>= SQLite message count (${existing.length}) for ${sessionId}`);
180
- return;
181
- }
182
- const keptMessages = existing.slice(-keptPersistedMessageCount);
183
- const pruned = existing.length - keptMessages.length;
184
- store.deleteMessagesBySession(sessionId);
185
- for (const msg of keptMessages) {
186
- store.appendMessage(sessionId, {
187
- id: msg.id,
188
- role: msg.role,
189
- content: msg.content,
190
- eventsJsonl: msg.events,
191
- createdAt: msg.createdAt,
192
- images: msg.images,
193
- });
194
- }
195
- console.log(`[spectral] prune: finished – dropped ${pruned} pre-compaction message(s) ` +
196
- `for ${sessionId}, ${keptMessages.length} kept`);
197
- }
198
- catch (err) {
199
- console.warn(`[spectral] warn: failed to prune persisted history after compaction for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
200
- }
201
- }
202
128
  function defaultMemoryStatus(cwd) {
203
129
  const config = loadConfig(cwd);
204
130
  return {
@@ -484,7 +410,6 @@ export class SessionStreamManager {
484
410
  // Prune persisted history synchronously after compaction, so even if the
485
411
  // compaction_end event handler races or misses, SQLite reflects the
486
412
  // compacted state before the next user prompt or reconnect.
487
- prunePersistedHistoryAfterCompaction(this.store, sessionId, stream.bridge);
488
413
  }
489
414
  finally {
490
415
  if (stream.compacting)
@@ -1270,9 +1195,9 @@ export class SessionStreamManager {
1270
1195
  }
1271
1196
  if (event.type === "compaction_end") {
1272
1197
  stream.compacting = false;
1273
- if (!event.aborted) {
1274
- prunePersistedHistoryAfterCompaction(this.store, stream.sessionId, stream.bridge);
1275
- }
1198
+ // Keep the full persisted history in SQLite after compaction.
1199
+ // The LLM context is already compacted via the bridge/session manager;
1200
+ // landing can still load older messages through the REST endpoint.
1276
1201
  persistObservationalMemorySnapshot(this.store, stream.sessionId, stream.bridge);
1277
1202
  // Drain the prompt queue after compaction finishes. During
1278
1203
  // compaction, auto-dequeue in agent_end is skipped to prevent the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.9.44",
3
+ "version": "0.9.46",
4
4
  "description": "AI coding agent for Aexol with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,