saga-mcp 1.11.0 → 1.12.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/README.md CHANGED
@@ -513,6 +513,7 @@ What you get:
513
513
  - **Board** — kanban across the five task statuses; drag a card to change its status
514
514
  - **Epics** — the full Epic → Task → Subtask tree, which is the fastest way to review a spec an agent just wrote
515
515
  - **Notes** and **Activity** — decisions and the complete change history
516
+ - **Markdown** — descriptions, comments and notes render headings, tables, lists, code and links. Agent-written content is escaped before any markdown rule runs, so raw HTML can never reach the page, and only http/https/mailto links are followed
516
517
  - **Archived section** — archived epics collapse below a divider, with a "show archived (N)" toggle
517
518
  - **Task drawer** — edit any field, comment, remove or restore a comment, lock the description, drag subtasks into order, and set which subtasks wait on which. Each subtask has one control carrying its whole state (todo / in progress / done, or blocked), and the drawer resizes by dragging its edge
518
519
  - **Project switcher** — every project in the database, so one central `.tracker.db` covers all your repos; every tab, including Activity, is scoped to the selected project
package/dist/web/ui.js CHANGED
@@ -266,6 +266,37 @@ body.resizing { cursor: ew-resize; user-select: none; }
266
266
  font-size: 12px; color: var(--muted);
267
267
  }
268
268
  .hiddenbar hr { flex: 1; border: none; border-top: 1px dashed var(--border); }
269
+ .md-p { margin: 0 0 8px; }
270
+ .md-p:last-child { margin-bottom: 0; }
271
+ .md-h { margin: 12px 0 6px; font-size: 14px; font-weight: 650; }
272
+ .md-h:first-child { margin-top: 0; }
273
+ h1.md-h { font-size: 17px; } h2.md-h { font-size: 15px; } h3.md-h { font-size: 14px; }
274
+ .md-list { margin: 0 0 8px; padding-left: 20px; }
275
+ .md-list li { margin: 2px 0; }
276
+ .md-task { list-style: none; margin-left: -18px; }
277
+ .md-quote {
278
+ margin: 0 0 8px; padding: 4px 10px; color: var(--muted);
279
+ border-left: 3px solid var(--border);
280
+ }
281
+ .md-hr { border: none; border-top: 1px solid var(--border); margin: 12px 0; }
282
+ .md-code {
283
+ margin: 0 0 8px; padding: 8px 10px; overflow-x: auto;
284
+ background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
285
+ font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.45;
286
+ }
287
+ code {
288
+ background: var(--panel-2); border: 1px solid var(--border); border-radius: 4px;
289
+ padding: 0 4px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px;
290
+ }
291
+ .md-code code { background: none; border: none; padding: 0; font-size: inherit; }
292
+ /* Wide tables scroll inside their own box rather than stretching the drawer. */
293
+ .md-tablewrap { overflow-x: auto; margin: 0 0 8px; }
294
+ .md-table { border-collapse: collapse; font-size: 12px; width: 100%; }
295
+ .md-table th, .md-table td {
296
+ border: 1px solid var(--border); padding: 4px 8px; text-align: left; vertical-align: top;
297
+ }
298
+ .md-table th { background: var(--panel-2); font-weight: 600; white-space: nowrap; }
299
+ .md-body a { color: var(--accent); }
269
300
  .toast {
270
301
  position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%);
271
302
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
@@ -357,6 +388,185 @@ function tagPills(json) {
357
388
  }
358
389
  function canEdit() { return !S.readOnly; }
359
390
 
391
+ /* ---------- markdown ---------- */
392
+
393
+ /**
394
+ * A small markdown renderer for descriptions, comments and notes.
395
+ *
396
+ * Content here is written by agents, so the order of operations is the whole
397
+ * security design: EVERY character is HTML-escaped first, and the markdown
398
+ * transformations then run over already-escaped text. Raw HTML in the source
399
+ * can never reach innerHTML, because by the time any rule matches, \`<\` is
400
+ * already \`&lt;\`. There is no sanitiser to get wrong and no allow-list to keep
401
+ * up to date.
402
+ *
403
+ * Links are the one place a payload could still hide, so their href is
404
+ * restricted to http, https and mailto.
405
+ */
406
+ function mdEscape(text) {
407
+ return String(text).replace(/[&<>"']/g, function (c) {
408
+ return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
409
+ });
410
+ }
411
+
412
+ /** Only schemes that cannot execute. Anything else renders as plain text. */
413
+ function mdSafeHref(href) {
414
+ var trimmed = href.trim();
415
+ // The href arrives already escaped, so &amp; must be read back as &.
416
+ var plain = trimmed.replace(/&amp;/g, '&');
417
+ if (/^(https?:\\/\\/|mailto:)/i.test(plain)) return plain;
418
+ if (/^[/#]/.test(plain)) return plain;
419
+ return null;
420
+ }
421
+
422
+ /** Inline rules, applied to already-escaped text. */
423
+ function mdInline(text, codeStore) {
424
+ var out = text;
425
+
426
+ // Code spans first, stashed so no later rule edits their contents.
427
+ out = out.replace(/\`([^\`]+)\`/g, function (_m, code) {
428
+ codeStore.push('<code>' + code + '</code>');
429
+ return 'C' + (codeStore.length - 1) + '';
430
+ });
431
+
432
+ out = out.replace(/\\[([^\\]]+)\\]\\(([^)\\s]+)\\)/g, function (whole, label, href) {
433
+ var safe = mdSafeHref(href);
434
+ // A rejected link renders as the literal source rather than a bare label,
435
+ // so nothing is silently rewritten and the reader sees what was written.
436
+ if (!safe) return whole;
437
+ return '<a href="' + safe + '" target="_blank" rel="noopener noreferrer">' + label + '</a>';
438
+ });
439
+
440
+ out = out.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');
441
+ out = out.replace(/__([^_]+)__/g, '<strong>$1</strong>');
442
+ out = out.replace(/(^|[^*])\\*([^*\\n]+)\\*/g, '$1<em>$2</em>');
443
+ out = out.replace(/~~([^~]+)~~/g, '<del>$1</del>');
444
+ return out;
445
+ }
446
+
447
+ function mdRestore(text, codeStore) {
448
+ return text.replace(/C(\\d+)/g, function (_m, i) { return codeStore[Number(i)]; });
449
+ }
450
+
451
+ /** A pipe table needs a delimiter row underneath the header. */
452
+ function mdIsTableDelimiter(line) {
453
+ return /^\\s*\\|?\\s*:?-{2,}:?\\s*(\\|\\s*:?-{2,}:?\\s*)*\\|?\\s*$/.test(line);
454
+ }
455
+
456
+ function mdSplitRow(line) {
457
+ var trimmed = line.trim().replace(/^\\|/, '').replace(/\\|$/, '');
458
+ return trimmed.split('|').map(function (cell) { return cell.trim(); });
459
+ }
460
+
461
+ function md(source) {
462
+ if (!source) return '';
463
+ var codeStore = [];
464
+ var lines = mdEscape(source).split('\\n');
465
+ var html = [];
466
+ var i = 0;
467
+
468
+ while (i < lines.length) {
469
+ var line = lines[i];
470
+
471
+ // fenced code — taken verbatim, no inline rules applied
472
+ var fence = /^\\s*\`\`\`(.*)$/.exec(line);
473
+ if (fence) {
474
+ var body = [];
475
+ i++;
476
+ while (i < lines.length && !/^\\s*\`\`\`/.test(lines[i])) { body.push(lines[i]); i++; }
477
+ i++;
478
+ html.push('<pre class="md-code"><code>' + body.join('\\n') + '</code></pre>');
479
+ continue;
480
+ }
481
+
482
+ if (/^\\s*$/.test(line)) { i++; continue; }
483
+
484
+ var hr = /^\\s*([-*_])(\\s*\\1){2,}\\s*$/.test(line);
485
+ if (hr) { html.push('<hr class="md-hr">'); i++; continue; }
486
+
487
+ var heading = /^(#{1,6})\\s+(.*)$/.exec(line);
488
+ if (heading) {
489
+ var level = heading[1].length;
490
+ html.push('<h' + level + ' class="md-h">' + mdInline(heading[2], codeStore) + '</h' + level + '>');
491
+ i++;
492
+ continue;
493
+ }
494
+
495
+ // table: a header row followed by a delimiter row
496
+ if (line.indexOf('|') >= 0 && i + 1 < lines.length && mdIsTableDelimiter(lines[i + 1])) {
497
+ var head = mdSplitRow(line);
498
+ i += 2;
499
+ var rows = [];
500
+ while (i < lines.length && lines[i].indexOf('|') >= 0 && !/^\\s*$/.test(lines[i])) {
501
+ rows.push(mdSplitRow(lines[i]));
502
+ i++;
503
+ }
504
+ var t = '<div class="md-tablewrap"><table class="md-table"><thead><tr>';
505
+ head.forEach(function (cell) { t += '<th>' + mdInline(cell, codeStore) + '</th>'; });
506
+ t += '</tr></thead><tbody>';
507
+ rows.forEach(function (row) {
508
+ t += '<tr>';
509
+ row.forEach(function (cell) { t += '<td>' + mdInline(cell, codeStore) + '</td>'; });
510
+ t += '</tr>';
511
+ });
512
+ html.push(t + '</tbody></table></div>');
513
+ continue;
514
+ }
515
+
516
+ var quote = /^\\s*&gt;\\s?(.*)$/.exec(line);
517
+ if (quote) {
518
+ var qbody = [quote[1]];
519
+ i++;
520
+ while (i < lines.length && /^\\s*&gt;\\s?(.*)$/.test(lines[i])) {
521
+ qbody.push(/^\\s*&gt;\\s?(.*)$/.exec(lines[i])[1]);
522
+ i++;
523
+ }
524
+ html.push('<blockquote class="md-quote">' + mdInline(qbody.join(' '), codeStore) + '</blockquote>');
525
+ continue;
526
+ }
527
+
528
+ var bullet = /^(\\s*)([-*+])\\s+(.*)$/.exec(line);
529
+ var numbered = /^(\\s*)(\\d+)[.)]\\s+(.*)$/.exec(line);
530
+ if (bullet || numbered) {
531
+ var ordered = !!numbered;
532
+ var items = [];
533
+ while (i < lines.length) {
534
+ var b = /^(\\s*)([-*+])\\s+(.*)$/.exec(lines[i]);
535
+ var n = /^(\\s*)(\\d+)[.)]\\s+(.*)$/.exec(lines[i]);
536
+ var m = ordered ? n : b;
537
+ if (!m) break;
538
+ var text = m[3];
539
+ // task-list checkboxes, rendered as a disabled box
540
+ var box = /^\\[([ xX])\\]\\s+(.*)$/.exec(text);
541
+ if (box) {
542
+ items.push('<li class="md-task"><input type="checkbox" disabled' +
543
+ (box[1] === ' ' ? '' : ' checked') + '> ' + mdInline(box[2], codeStore) + '</li>');
544
+ } else {
545
+ items.push('<li>' + mdInline(text, codeStore) + '</li>');
546
+ }
547
+ i++;
548
+ }
549
+ html.push('<' + (ordered ? 'ol' : 'ul') + ' class="md-list">' + items.join('') + '</' + (ordered ? 'ol' : 'ul') + '>');
550
+ continue;
551
+ }
552
+
553
+ // paragraph: consecutive non-blank lines that start no other block
554
+ var para = [line];
555
+ i++;
556
+ while (i < lines.length && !/^\\s*$/.test(lines[i]) &&
557
+ !/^(#{1,6})\\s/.test(lines[i]) && !/^\\s*\`\`\`/.test(lines[i]) &&
558
+ !/^\\s*([-*+])\\s/.test(lines[i]) && !/^\\s*\\d+[.)]\\s/.test(lines[i]) &&
559
+ !/^\\s*&gt;\\s?/.test(lines[i]) &&
560
+ !(lines[i].indexOf('|') >= 0 && i + 1 < lines.length && mdIsTableDelimiter(lines[i + 1]))) {
561
+ para.push(lines[i]);
562
+ i++;
563
+ }
564
+ html.push('<p class="md-p">' + mdInline(para.join(' '), codeStore) + '</p>');
565
+ }
566
+
567
+ return mdRestore(html.join(''), codeStore);
568
+ }
569
+
360
570
  /* ---------- generic modal form ---------- */
361
571
 
362
572
  function closeModal() {
@@ -581,7 +791,7 @@ function viewOverview() {
581
791
  pill('st', o.project.status) +
582
792
  '<span class="muted">' + (s.completion_pct || 0) + '% complete</span>' +
583
793
  (canEdit() ? '<button class="btn" id="editProject">Edit</button>' : '') + '</div>' +
584
- (o.project.description ? '<pre class="body muted">' + esc(o.project.description) + '</pre>' : '') +
794
+ (o.project.description ? '<div class="body md-body muted">' + md(o.project.description) + '</div>' : '') +
585
795
  '<div style="margin-top:10px">' + progressBar(s.completion_pct) + '</div>' +
586
796
  '</div>';
587
797
 
@@ -702,7 +912,7 @@ function viewEpics() {
702
912
  '</div>';
703
913
  if (open) {
704
914
  h += '<div class="epic-body">' +
705
- (e.description ? '<pre class="body muted" style="margin-bottom:10px">' + esc(e.description) + '</pre>' : '') +
915
+ (e.description ? '<div class="body md-body muted" style="margin-bottom:10px">' + md(e.description) + '</div>' : '') +
706
916
  '<div class="tlist">' +
707
917
  (list.length ? list.map(function (t) {
708
918
  var extra = t.subtask_count ? '☑ ' + t.subtask_done + '/' + t.subtask_count : '';
@@ -738,7 +948,7 @@ function viewNotes() {
738
948
  '</div>' +
739
949
  (n.related_entity_type ? '<div class="muted" style="font-size:12px;margin-top:2px">on ' +
740
950
  esc(n.related_entity_type) + ' #' + esc(n.related_entity_id) + '</div>' : '') +
741
- '<pre class="body">' + esc(n.content) + '</pre>' +
951
+ '<div class="body md-body">' + md(n.content) + '</div>' +
742
952
  (tagPills(n.tags) ? '<div style="margin-top:8px">' + tagPills(n.tags) + '</div>' : '') +
743
953
  '</div>';
744
954
  }).join('');
@@ -903,7 +1113,7 @@ function drawTask() {
903
1113
  '</h3>' +
904
1114
  (locked ? '<div class="empty">Agents cannot rewrite this description while it is locked — ' +
905
1115
  'they are told to comment instead.</div>' : '') +
906
- (t.description ? '<pre class="body">' + esc(t.description) + '</pre>'
1116
+ (t.description ? '<div class="body md-body">' + md(t.description) + '</div>'
907
1117
  : '<div class="empty">No description.</div>');
908
1118
 
909
1119
  h += '<h3>Subtasks <span class="muted">(' + t.subtasks.length + ')</span></h3>';
@@ -977,7 +1187,7 @@ function drawTask() {
977
1187
  (ed ? (c.is_deleted ? '<button class="link" data-cmt-restore="' + c.id + '">restore</button>'
978
1188
  : '<button class="link" data-cmt-del="' + c.id + '">remove</button>') : '') +
979
1189
  '</div>' +
980
- '<pre class="body' + (c.is_deleted ? ' strike' : '') + '">' + esc(c.content) + '</pre>' +
1190
+ '<div class="body md-body' + (c.is_deleted ? ' strike' : '') + '">' + md(c.content) + '</div>' +
981
1191
  (c.is_deleted && c.delete_reason ? '<div class="hdr">reason: ' + esc(c.delete_reason) + '</div>' : '') +
982
1192
  '</div>';
983
1193
  }).join('') : '<div class="empty">No comments.</div>';
@@ -992,7 +1202,7 @@ function drawTask() {
992
1202
  h += '<h3>Notes' + (ed ? ' <button class="btn" id="addTaskNote">+ Note</button>' : '') + '</h3>';
993
1203
  h += t.notes.length ? t.notes.map(function (n) {
994
1204
  return '<div class="cmt"><div class="hdr">' + esc(label(n.note_type)) + ' · ' + fmtDate(n.created_at) +
995
- '</div><strong>' + esc(n.title) + '</strong><pre class="body">' + esc(n.content) + '</pre></div>';
1205
+ '</div><strong>' + esc(n.title) + '</strong><div class="body md-body">' + md(n.content) + '</div></div>';
996
1206
  }).join('') : '<div class="empty">None.</div>';
997
1207
 
998
1208
  if (t.activity.length) {
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwnDpB,CAAC"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/web/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA00DpB,CAAC"}
package/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "saga-mcp",
4
4
  "display_name": "Saga — Project Tracker for AI Agents",
5
- "version": "1.11.0",
5
+ "version": "1.12.0",
6
6
  "description": "A Jira-like project tracker MCP server for AI agents. SQLite-backed, per-project scoped, with full hierarchy and activity logging — so LLMs never lose track.",
7
7
  "long_description": "Saga gives your AI assistant a structured SQLite database to track projects, epics, tasks, subtasks, notes, and decisions across sessions. No more scattered markdown files — one `tracker_dashboard` call gives full project context to resume work.\n\n**Key features:**\n- Full hierarchy: Projects > Epics > Tasks > Subtasks\n- Task dependencies: Express sequencing with auto-block/unblock\n- Comments: Threaded discussions on tasks for decision trails, with reversible soft-delete\n- Templates: Reusable task sets with variable substitution\n- Dashboard: One tool call gives full overview with natural language summary\n- SQLite: Self-contained `.tracker.db` file per project — zero setup\n- Activity log: Every mutation is automatically tracked\n- Notes system: Decisions, context, meeting notes, blockers\n- Batch operations: Create multiple subtasks or update multiple tasks in one call\n- 35 focused tools with safety annotations\n- Web UI: `saga-web` serves a local dashboard for browsing and editing the same database\n- Import/export: Full project backup and migration as JSON",
8
8
  "author": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "saga-mcp",
3
3
  "mcpName": "io.github.spranab/saga-mcp",
4
- "version": "1.11.0",
4
+ "version": "1.12.0",
5
5
  "description": "A Jira-like project tracker MCP server for AI agents. SQLite-backed, per-project scoped, with full hierarchy (Projects > Epics > Tasks > Subtasks), activity logging, and a dashboard \u2014 so LLMs never lose track.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",