@skyelight/mcp 0.1.2 → 0.1.4

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.js +38 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyelight/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "MCP server for Skyelight \u2014 read feedback items from your project as an agent",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tools.js CHANGED
@@ -232,6 +232,25 @@ export function renderWorkspaces(result) {
232
232
  return lines.join("\n");
233
233
  }
234
234
 
235
+ /**
236
+ * Coarse relative time.
237
+ *
238
+ * Deliberately vague past a day: an agent deciding whether to look at a
239
+ * project needs "today" or "last month", and a precise timestamp invites it
240
+ * to quote a date it has no reason to trust to the minute.
241
+ */
242
+ function ago(ts) {
243
+ const mins = Math.max(0, Math.round((Date.now() - ts) / 60000));
244
+ if (mins < 1) return "just now";
245
+ if (mins < 60) return `${mins}m ago`;
246
+ const hours = Math.round(mins / 60);
247
+ if (hours < 24) return `${hours}h ago`;
248
+ const days = Math.round(hours / 24);
249
+ if (days < 30) return `${days}d ago`;
250
+ const months = Math.round(days / 30);
251
+ return months < 12 ? `${months}mo ago` : `${Math.round(months / 12)}y ago`;
252
+ }
253
+
235
254
  export function renderProjects(result) {
236
255
  const { projects, total } = result;
237
256
  if (total === 0) {
@@ -244,7 +263,12 @@ export function renderProjects(result) {
244
263
  for (const p of projects) {
245
264
  // The workspace is only worth naming when there is more than one in
246
265
  // play; repeating it down a single-workspace list is noise.
247
- lines.push(` ${p.name}${spans ? ` (${p.workspace.name})` : ""}`);
266
+ // The activity date is why this list is ordered the way it is, and it
267
+ // was previously only in the structured payload — so an agent reading the
268
+ // prose could not tell a busy project from a dormant one, and one reading
269
+ // the raw field got `updatedAt`, which meant "renamed recently".
270
+ const when = p.lastActivityAt ? ` · ${ago(p.lastActivityAt)}` : "";
271
+ lines.push(` ${p.name}${spans ? ` (${p.workspace.name})` : ""}${when}`);
248
272
  lines.push(` ${p.id}${p.repo ? ` — ${p.repo}` : ""}`);
249
273
  }
250
274
  lines.push("");
@@ -386,6 +410,9 @@ export function renderItem(item) {
386
410
  const where = item.source.line
387
411
  ? `${item.source.file}, line ${item.source.line}`
388
412
  : item.source.file;
413
+ // Named before located: "the DoseRow component" is what someone would say,
414
+ // and it stays true after the line has moved.
415
+ const rendered = item.sourcePath?.target?.component;
389
416
  // The build too, when the page carried one. Which commit was on the
390
417
  // screen is the difference between a live bug and a stale pin — and the
391
418
  // branch is what makes that commit findable when the page was a preview
@@ -393,10 +420,11 @@ export function renderItem(item) {
393
420
  const from = [];
394
421
  if (item.source.build) from.push(`build ${item.source.build}`);
395
422
  if (item.source.branch) from.push(`branch ${item.source.branch}`);
423
+ const by = rendered ? `the ${rendered} component, ${where}` : where;
396
424
  lines.push(
397
425
  from.length
398
- ? `Written by ${where}, in ${from.join(" on ")}.`
399
- : `Written by ${where}.`,
426
+ ? `Written by ${by}, in ${from.join(" on ")}.`
427
+ : `Written by ${by}.`,
400
428
  );
401
429
  // The permalink, when the project names a repository. Pinned to the
402
430
  // commit the page carried rather than to a branch, so it opens the code
@@ -419,7 +447,11 @@ export function renderItem(item) {
419
447
  // Outermost first, so a repeated file reads as one component and a
420
448
  // change of file reads as a boundary between two.
421
449
  for (const step of path.above) {
422
- lines.push(` ${step.file}:${step.line ?? "?"} <${step.tag}>`);
450
+ // The component first when there is one: it is what a person would
451
+ // call this part of the page, and the one identifier that survives
452
+ // the lines below it moving.
453
+ const who = step.component ? `${step.component} ` : "";
454
+ lines.push(` ${who}${step.file}:${step.line ?? "?"} <${step.tag}>`);
423
455
  }
424
456
  }
425
457
  if (path.below.length) {
@@ -428,8 +460,9 @@ export function renderItem(item) {
428
460
  for (const step of path.below) {
429
461
  const indent = " ".repeat((step.depth ?? 0) + 1);
430
462
  const text = step.text ? ` "${step.text}"` : "";
463
+ const who = step.component ? ` ${step.component}` : "";
431
464
  lines.push(
432
- `${indent}<${step.tag}> ${step.file}:${step.line ?? "?"}${text}`,
465
+ `${indent}<${step.tag}>${who} ${step.file}:${step.line ?? "?"}${text}`,
433
466
  );
434
467
  }
435
468
  }