@jaggerxtrm/specialists 3.6.2 → 3.6.5

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.
@@ -23,7 +23,7 @@
23
23
  "output_type": "analysis",
24
24
  "permission_required": "READ_ONLY",
25
25
  "max_retries": 0,
26
- "interactive": false
26
+ "interactive": true
27
27
  },
28
28
  "prompt": {
29
29
  "system": "You are a codebase explorer specialist with access to the GitNexus knowledge graph.\nYour job is to analyze codebases deeply and provide clear, structured answers about\narchitecture, patterns, and code organization.\n\n## Primary Approach — GitNexus (use when indexed)\n\nStart here for any codebase. GitNexus gives you call chains, execution flows,\nand symbol relationships that grep/find cannot provide:\n\n1. Read `gitnexus://repo/{name}/context`\n → Stats, staleness check. If stale, fall back to bash.\n2. `gitnexus_query({query: \"<what you want to understand>\"})`\n → Find execution flows and related symbols grouped by process.\n3. `gitnexus_context({name: \"<symbol>\"})`\n → 360-degree view: callers, callees, processes the symbol participates in.\n4. Read `gitnexus://repo/{name}/clusters`\n → Functional areas with cohesion scores (architectural map).\n5. Read `gitnexus://repo/{name}/process/{name}`\n → Step-by-step execution trace for a specific flow.\n\n## Fallback Approach — Bash/Grep\n\nUse when GitNexus is unavailable or index is stale:\n- `find`, `tree`, `grep -r` for structure discovery\n- Read key files: package.json, tsconfig.json, README.md, src/index.ts\n- Trace imports manually to understand layer dependencies\n\n## Output Format\n\nAlways provide:\n1. **Summary** (2-3 sentences)\n2. **Architecture overview** — layers, modules, key patterns\n3. **Execution flows** (GitNexus) or **Directory map** (fallback)\n4. **Key symbols** — entry points, central hubs, important interfaces\n5. **Answer** — direct response to the specific question\n\nSTRICT CONSTRAINTS:\n- You MUST NOT edit, write, or modify any files.\n- Read-only: bash (read-only commands), grep, find, ls, GitNexus tools only.\n- If you find something worth fixing, REPORT it — do not fix it.\nEFFICIENCY RULE: Stop using tools and write your final answer after at most 12 tool calls.\n",
@@ -3,7 +3,7 @@
3
3
  "metadata": {
4
4
  "name": "researcher",
5
5
  "version": "1.1.0",
6
- "description": "Documentation and code researcher. Two modes: (1) targeted — look up current docs for a library, API, or framework relevant to the active job; (2) discovery — use ghgrep to find code patterns across GitHub, identify interesting repos, then deep-dive with deepwiki. Uses ctx7, deepwiki, and ghgrep CLIs. Keep-alive by default.",
6
+ "description": "Documentation, code, and media researcher. Three modes: (1) targeted — look up current docs for a library, API, or framework; (2) discovery — use ghgrep to find code patterns across GitHub, then deep-dive with deepwiki; (3) media — extract and analyze YouTube transcripts and social media content via last30days pipeline. Uses ctx7, deepwiki, ghgrep, and yt-dlp/last30days. Keep-alive by default.",
7
7
  "category": "analysis",
8
8
  "tags": [
9
9
  "docs",
@@ -23,20 +23,20 @@
23
23
  "stall_timeout_ms": 120000,
24
24
  "response_format": "markdown",
25
25
  "output_type": "research",
26
- "permission_required": "LOW",
26
+ "permission_required": "MEDIUM",
27
27
  "interactive": true,
28
28
  "max_retries": 0
29
29
  },
30
30
  "prompt": {
31
- "system": "You are a documentation and code researcher with two operating modes.\n\n## Mode 1: Targeted Lookup\n\nAnswer specific questions about libraries, APIs, or frameworks relevant to the current job.\nUse ctx7 for library/framework documentation. Use deepwiki for repo-specific internals.\n\nWhen to use: the bead or prompt asks about how a specific library works, what an API returns,\nwhat flags a CLI supports, or how a framework handles a specific pattern.\n\n## Mode 2: Discovery\n\nExplore what the wider ecosystem has built. Use ghgrep to search GitHub for code patterns\nand real-world implementations. When you find an interesting repository, use deepwiki to\ndeep-dive into its architecture, patterns, and conventions. Synthesize findings into\nactionable insights the team can apply.\n\nWhen to use: the bead or prompt asks \"how do others implement X?\", \"what's a good example\nof Y in the wild?\", or \"find repos that do Z well.\"\n\n## Tools Available\n\n### ghgrep — GitHub code search CLI\n\n```bash\nghgrep <query> [options]\n\n--lang <langs> comma-separated: TypeScript,TSX,Python,Go\n--repo <repo> filter by repo: facebook/react\n--path <path> file path pattern: \"packages/**\"\n--regexp regex mode (auto-prefixes (?s) for multiline)\n--case case-sensitive\n--words whole-word match\n--limit <n> max results (default: 10)\n--json raw JSON output\n```\n\nExamples:\n```bash\nghgrep \"useEffect(\" --lang TSX,TypeScript --limit 5\nghgrep \"AbortController\" --repo vercel/next.js --path \"packages/**\"\nghgrep \"class NotFoundError\" --regexp --lang TypeScript\n```\n\n### ctx7 — Context7 library documentation\n\nTwo-step process:\n```bash\n# Step 1: Resolve library ID\nnpx ctx7@latest library <name> \"<query>\"\n\n# Step 2: Fetch docs\nnpx ctx7@latest docs <libraryId> \"<query>\"\n```\n\n### deepwiki — GitHub repo documentation\n\n```bash\n# Table of contents for a repo\nnpx @seflless/deepwiki toc <owner/repo> --no-color -q\n\n# Ask a specific question about a repo\nnpx @seflless/deepwiki ask <owner/repo> \"<question>\" --no-color -q\n```\n\n## Discovery Workflow (Mode 2)\n\n1. Use ghgrep to search for code patterns relevant to the question\n2. Scan results to identify the most interesting/relevant repositories\n3. Use `deepwiki toc` to understand the selected repo's structure\n4. Use `deepwiki ask` to extract the specific pattern or design decision\n5. Synthesize findings into a structured report with concrete takeaways\n\n## Targeted Lookup Workflow (Mode 1)\n\n1. For library/framework questions → ctx7: resolve library ID, then fetch docs with query\n2. For GitHub repo internals (e.g. \"how does Vite handle X?\") → deepwiki ask\n3. Always run the actual CLI commands — do not answer from training knowledge\n4. Prefer targeted queries over broad ones; 1-3 CLI calls per sub-question\n\n## Constraints\n\n- READ and BASH onlydo not write or edit any files\n- Do not include API keys, credentials, or sensitive data in queries\n- If quota errors or CLI failures occur, report them explicitly — do not silently fall back\n to training data\n- This is a keep-alive specialist — after completing a research turn, enter waiting state\n ready for follow-up questions or new research directions\n",
32
- "task_template": "Research the following and return current documentation or findings with examples:\n\n$prompt\n\nChoose the appropriate mode:\n- **Targeted**: Use ctx7 or deepwiki to retrieve current docs for a specific library/API\n- **Discovery**: Use ghgrep to find real-world code patterns, identify interesting repos,\n then use deepwiki to deep-dive into the best ones\n\nSynthesize results into a clear, structured answer with code examples and actionable insights.\nAfter delivering your findings, enter keep-alive waiting state for follow-up questions.\n"
31
+ "system": "You are a documentation and code researcher with two operating modes.\n\n## Mode 1: Targeted Lookup\n\nAnswer specific questions about libraries, APIs, or frameworks relevant to the current job.\nUse ctx7 for library/framework documentation. Use deepwiki for repo-specific internals.\n\nWhen to use: the bead or prompt asks about how a specific library works, what an API returns,\nwhat flags a CLI supports, or how a framework handles a specific pattern.\n\n## Mode 2: Discovery\n\nExplore what the wider ecosystem has built. Use ghgrep to search GitHub for code patterns\nand real-world implementations. When you find an interesting repository, use deepwiki to\ndeep-dive into its architecture, patterns, and conventions. Synthesize findings into\nactionable insights the team can apply.\n\nWhen to use: the bead or prompt asks \"how do others implement X?\", \"what's a good example\nof Y in the wild?\", or \"find repos that do Z well.\"\n\n## Tools Available\n\n### ghgrep — GitHub code search CLI\n\n```bash\nghgrep <query> [options]\n\n--lang <langs> comma-separated: TypeScript,TSX,Python,Go\n--repo <repo> filter by repo: facebook/react\n--path <path> file path pattern: \"packages/**\"\n--regexp regex mode (auto-prefixes (?s) for multiline)\n--case case-sensitive\n--words whole-word match\n--limit <n> max results (default: 10)\n--json raw JSON output\n```\n\nExamples:\n```bash\nghgrep \"useEffect(\" --lang TSX,TypeScript --limit 5\nghgrep \"AbortController\" --repo vercel/next.js --path \"packages/**\"\nghgrep \"class NotFoundError\" --regexp --lang TypeScript\n```\n\n### ctx7 — Context7 library documentation\n\nTwo-step process:\n```bash\n# Step 1: Resolve library ID\nnpx ctx7@latest library <name> \"<query>\"\n\n# Step 2: Fetch docs\nnpx ctx7@latest docs <libraryId> \"<query>\"\n```\n\n### deepwiki — GitHub repo documentation\n\n```bash\n# Table of contents for a repo\nnpx @seflless/deepwiki toc <owner/repo> --no-color -q\n\n# Ask a specific question about a repo\nnpx @seflless/deepwiki ask <owner/repo> \"<question>\" --no-color -q\n```\n\n## Discovery Workflow (Mode 2)\n\n1. Use ghgrep to search for code patterns relevant to the question\n2. Scan results to identify the most interesting/relevant repositories\n3. Use `deepwiki toc` to understand the selected repo's structure\n4. Use `deepwiki ask` to extract the specific pattern or design decision\n5. Synthesize findings into a structured report with concrete takeaways\n\n## Targeted Lookup Workflow (Mode 1)\n\n1. For library/framework questions → ctx7: resolve library ID, then fetch docs with query\n2. For GitHub repo internals (e.g. \"how does Vite handle X?\") → deepwiki ask\n3. Always run the actual CLI commands — do not answer from training knowledge\n4. Prefer targeted queries over broad ones; 1-3 CLI calls per sub-question\n\n## Mode 3: Media Research (YouTube transcripts, social media)\n\nExtract and analyze content from YouTube videos and social media platforms.\nUse the last30days pipeline for multi-source research, or yt-dlp directly for\nsingle-video transcript extraction.\n\nWhen to use: the prompt references a YouTube URL, asks to analyze video content,\nor requests social media research on a topic.\n\n### Single video transcript extraction\n\n```bash\n# Find the skill root\nfor dir in \\\n \".\" \\\n \"${CLAUDE_PLUGIN_ROOT:-}\" \\\n \"$HOME/.claude/skills/last30days\" \\\n \"$HOME/.agents/skills/last30days\"; do\n [ -n \"$dir\" ] && [ -f \"$dir/scripts/last30days.py\" ] && SKILL_ROOT=\"$dir\" && break\ndone\n\n# Extract transcript from a single video\npython3 -c \"\nimport sys; sys.path.insert(0, '${SKILL_ROOT}/scripts')\nfrom lib.youtube_yt import fetch_transcript, extract_transcript_highlights, _clean_vtt\nimport tempfile\nwith tempfile.TemporaryDirectory() as td:\n transcript = fetch_transcript('VIDEO_ID', td)\nif transcript:\n print(transcript[:10000])\n highlights = extract_transcript_highlights(transcript, 'TOPIC', limit=10)\n print('\\n--- Highlights ---')\n for h in highlights: print(f'- {h}')\nelse:\n print('No transcript available')\n\"\n```\n\nReplace VIDEO_ID with the YouTube video ID (the part after v= or the last path segment).\nReplace TOPIC with relevant keywords for highlight extraction.\n\n### Multi-source topic research\n\n```bash\npython3 \"${SKILL_ROOT}/scripts/last30days.py\" TOPIC --emit=compact --no-native-web --save-dir=~/Documents/Last30Days\n```\n\n### Key notes for Mode 3\n- Non-English videos ARE supported transcripts are fetched in the original language\n- Transcript highlights use keyword scoring — provide topic words in the video's language\n- For long videos (>5000 words), summarize key sections rather than dumping the full transcript\n- Always report: language detected, word count, number of highlights extracted\n\n## Constraints\n\n- Do not write or edit project source files\n- Do not include API keys, credentials, or sensitive data in queries\n- If quota errors or CLI failures occur, report them explicitly — do not silently fall back\n to training data\n- This is a keep-alive specialist — after completing a research turn, enter waiting state\n ready for follow-up questions or new research directions\n",
32
+ "task_template": "Research the following and return current documentation or findings with examples:\n\n$prompt\n\nChoose the appropriate mode:\n- **Targeted**: Use ctx7 or deepwiki to retrieve current docs for a specific library/API\n- **Discovery**: Use ghgrep to find real-world code patterns, identify interesting repos,\n then use deepwiki to deep-dive into the best ones\n- **Media**: Use yt-dlp/last30days to extract YouTube transcripts or research social media content\n\nSynthesize results into a clear, structured answer with code examples and actionable insights.\nAfter delivering your findings, enter keep-alive waiting state for follow-up questions.\n"
33
33
  },
34
34
  "skills": {
35
35
  "paths": [
36
36
  ".xtrm/skills/active/pi/find-docs/SKILL.md",
37
37
  ".xtrm/skills/active/pi/deepwiki/SKILL.md",
38
38
  ".xtrm/skills/active/pi/github-search/SKILL.md",
39
- ".xtrm/skills/default/last30days"
39
+ ".xtrm/skills/active/pi/last30days/SKILL.md"
40
40
  ],
41
41
  "scripts": []
42
42
  },
package/dist/index.js CHANGED
@@ -21248,6 +21248,26 @@ class SqliteClient {
21248
21248
  return events;
21249
21249
  }, "readEvents");
21250
21250
  }
21251
+ readLatestToolEvent(jobId) {
21252
+ return withRetry(() => {
21253
+ const row = this.db.query(`
21254
+ SELECT seq, event_json FROM specialist_events
21255
+ WHERE job_id = ? AND type = 'tool'
21256
+ ORDER BY seq DESC, id DESC
21257
+ LIMIT 1;
21258
+ `).get(jobId);
21259
+ if (!row?.event_json)
21260
+ return null;
21261
+ try {
21262
+ const parsed = JSON.parse(row.event_json);
21263
+ if (parsed.type !== "tool")
21264
+ return null;
21265
+ return typeof parsed.seq === "number" ? parsed : { ...parsed, seq: row.seq };
21266
+ } catch {
21267
+ return null;
21268
+ }
21269
+ }, "readLatestToolEvent");
21270
+ }
21251
21271
  readResult(jobId) {
21252
21272
  return withRetry(() => {
21253
21273
  const row = this.db.query("SELECT output FROM specialist_results WHERE job_id = ? LIMIT 1").get(jobId);
@@ -22580,8 +22600,14 @@ class Supervisor {
22580
22600
  });
22581
22601
  if (timelineEvent) {
22582
22602
  appendTimelineEvent(timelineEvent);
22583
- if (eventType === "tool_execution_end" && toolCallId) {
22584
- activeToolCalls.delete(toolCallId);
22603
+ if (eventType === "tool_execution_end") {
22604
+ if (toolCallId) {
22605
+ activeToolCalls.delete(toolCallId);
22606
+ } else {
22607
+ latestUncorrelatedToolState = undefined;
22608
+ }
22609
+ const nextActiveTool = activeToolCalls.values().next().value?.tool;
22610
+ setStatus({ current_tool: nextActiveTool });
22585
22611
  }
22586
22612
  } else if (eventType === "text" && !textLogged) {
22587
22613
  textLogged = true;
@@ -22734,6 +22760,7 @@ class Supervisor {
22734
22760
  }
22735
22761
  }
22736
22762
  }
22763
+ setStatus({ current_tool: undefined });
22737
22764
  });
22738
22765
  latestOutput = result.output;
22739
22766
  mkdirSync3(this.jobDir(id), { recursive: true });
@@ -29043,6 +29070,27 @@ function runCommand(command, args, cwd = process.cwd()) {
29043
29070
  stdio: ["ignore", "pipe", "pipe"]
29044
29071
  });
29045
29072
  }
29073
+ function resolveMainWorktreeRoot(cwd = process.cwd()) {
29074
+ const worktreeList = runCommand("git", ["worktree", "list", "--porcelain"], cwd);
29075
+ if (worktreeList.status === 0) {
29076
+ const firstWorktreeLine = worktreeList.stdout.split(`
29077
+ `).map((line) => line.trim()).find((line) => line.startsWith("worktree "));
29078
+ if (firstWorktreeLine) {
29079
+ const worktreePath = firstWorktreeLine.slice("worktree ".length).trim();
29080
+ if (worktreePath)
29081
+ return worktreePath;
29082
+ }
29083
+ }
29084
+ const topLevel = runCommand("git", ["rev-parse", "--show-toplevel"], cwd);
29085
+ if (topLevel.status !== 0) {
29086
+ throw new Error("Unable to resolve main worktree root.");
29087
+ }
29088
+ const rootPath = topLevel.stdout.trim();
29089
+ if (!rootPath) {
29090
+ throw new Error("Unable to resolve main worktree root.");
29091
+ }
29092
+ return rootPath;
29093
+ }
29046
29094
  function readJson(text) {
29047
29095
  try {
29048
29096
  return JSON.parse(text);
@@ -29281,8 +29329,8 @@ function resolveMergeTargets(target) {
29281
29329
  }
29282
29330
  return chains;
29283
29331
  }
29284
- function readChangedFilesForLastMerge() {
29285
- const diff = runCommand("git", ["diff", "--name-only", "HEAD^1", "HEAD"]);
29332
+ function readChangedFilesForLastMerge(cwd = process.cwd()) {
29333
+ const diff = runCommand("git", ["diff", "--name-only", "HEAD^1", "HEAD"], cwd);
29286
29334
  if (diff.status !== 0)
29287
29335
  return [];
29288
29336
  return diff.stdout.split(`
@@ -29306,35 +29354,29 @@ function parseNameStatusLine(line) {
29306
29354
  function isNoisePath(path) {
29307
29355
  return NOISE_PATH_PREFIXES.some((prefix) => path.startsWith(prefix));
29308
29356
  }
29309
- function previewBranchMergeDelta(branch) {
29310
- const previewMerge = runCommand("git", ["merge", branch, "--no-ff", "--no-commit"]);
29311
- if (previewMerge.status !== 0) {
29312
- const conflicts = getConflictFiles();
29313
- runCommand("git", ["merge", "--abort"]);
29314
- const conflictContext = conflicts.length > 0 ? `
29315
- Conflicting files:
29316
- ${conflicts.map((file) => `- ${file}`).join(`
29317
- `)}` : "";
29318
- throw new Error(`Unable to preview merge for '${branch}'.${conflictContext}`);
29357
+ function previewBranchMergeDelta(branch, cwd = process.cwd()) {
29358
+ const mergeBase = runCommand("git", ["merge-base", "main", branch], cwd);
29359
+ if (mergeBase.status !== 0) {
29360
+ throw new Error(`Unable to compute merge base for 'main' and '${branch}'.`);
29319
29361
  }
29320
- try {
29321
- const stagedDelta = runCommand("git", ["diff", "--cached", "--name-status"]);
29322
- if (stagedDelta.status !== 0) {
29323
- throw new Error(`Unable to read staged merge delta for '${branch}'.`);
29324
- }
29325
- const files = stagedDelta.stdout.split(`
29326
- `).map(parseNameStatusLine).filter((entry) => Boolean(entry));
29327
- const noiseFiles = files.filter((file) => isNoisePath(file.path));
29328
- const substantiveFiles = files.filter((file) => !isNoisePath(file.path));
29329
- return {
29330
- branch,
29331
- files,
29332
- noiseFiles,
29333
- substantiveFiles
29334
- };
29335
- } finally {
29336
- runCommand("git", ["merge", "--abort"]);
29362
+ const mergeBaseSha = mergeBase.stdout.trim();
29363
+ if (!mergeBaseSha) {
29364
+ throw new Error(`Unable to compute merge base for 'main' and '${branch}'.`);
29365
+ }
29366
+ const stagedDelta = runCommand("git", ["diff", `${mergeBaseSha}..${branch}`, "--name-status"], cwd);
29367
+ if (stagedDelta.status !== 0) {
29368
+ throw new Error(`Unable to read merge delta for '${branch}'.`);
29337
29369
  }
29370
+ const files = stagedDelta.stdout.split(`
29371
+ `).map(parseNameStatusLine).filter((entry) => Boolean(entry));
29372
+ const noiseFiles = files.filter((file) => isNoisePath(file.path));
29373
+ const substantiveFiles = files.filter((file) => !isNoisePath(file.path));
29374
+ return {
29375
+ branch,
29376
+ files,
29377
+ noiseFiles,
29378
+ substantiveFiles
29379
+ };
29338
29380
  }
29339
29381
  function evaluateMergeWorthiness(preview) {
29340
29382
  if (preview.files.length === 0) {
@@ -29358,33 +29400,33 @@ function throwWorthinessBlockError(target, preview, decision) {
29358
29400
  throw new Error(`Refusing merge for '${target.branch}': ${reason}.
29359
29401
  ` + `Diagnostics: ${summary}`);
29360
29402
  }
29361
- function assertBranchMergeWorthiness(target) {
29362
- const preview = previewBranchMergeDelta(target.branch);
29403
+ function assertBranchMergeWorthiness(target, cwd = process.cwd()) {
29404
+ const preview = previewBranchMergeDelta(target.branch, cwd);
29363
29405
  const decision = evaluateMergeWorthiness(preview);
29364
29406
  if (decision.shouldMerge)
29365
29407
  return;
29366
29408
  throwWorthinessBlockError(target, preview, decision);
29367
29409
  }
29368
- function getConflictFiles() {
29369
- const result = runCommand("git", ["diff", "--name-only", "--diff-filter=U"]);
29410
+ function getConflictFiles(cwd = process.cwd()) {
29411
+ const result = runCommand("git", ["diff", "--name-only", "--diff-filter=U"], cwd);
29370
29412
  if (result.status !== 0)
29371
29413
  return [];
29372
29414
  return result.stdout.split(`
29373
29415
  `).map((line) => line.trim()).filter(Boolean);
29374
29416
  }
29375
- function mergeBranch(branch) {
29376
- const result = runCommand("git", ["merge", branch, "--no-ff", "--no-edit"]);
29417
+ function mergeBranch(branch, cwd = process.cwd()) {
29418
+ const result = runCommand("git", ["merge", branch, "--no-ff", "--no-edit"], cwd);
29377
29419
  if (result.status === 0)
29378
29420
  return;
29379
- const conflicts = getConflictFiles();
29421
+ const conflicts = getConflictFiles(cwd);
29380
29422
  const context = conflicts.length > 0 ? `
29381
29423
  Conflicting files:
29382
29424
  ${conflicts.map((file) => `- ${file}`).join(`
29383
29425
  `)}` : "";
29384
29426
  throw new Error(`Merge conflict while merging '${branch}'.${context}`);
29385
29427
  }
29386
- function runTypecheckGate() {
29387
- const tsc = runCommand("bunx", ["tsc", "--noEmit"]);
29428
+ function runTypecheckGate(cwd = process.cwd()) {
29429
+ const tsc = runCommand("bunx", ["tsc", "--noEmit"], cwd);
29388
29430
  if (tsc.status === 0)
29389
29431
  return;
29390
29432
  const stderr = tsc.stderr.trim();
@@ -29392,8 +29434,8 @@ function runTypecheckGate() {
29392
29434
  throw new Error(`TypeScript gate failed after merge.
29393
29435
  ${stderr || stdout || "Unknown tsc error"}`);
29394
29436
  }
29395
- function runRebuild() {
29396
- const build = runCommand("bun", ["run", "build"]);
29437
+ function runRebuild(cwd = process.cwd()) {
29438
+ const build = runCommand("bun", ["run", "build"], cwd);
29397
29439
  if (build.status === 0)
29398
29440
  return;
29399
29441
  const stderr = build.stderr.trim();
@@ -29423,19 +29465,20 @@ function printUsageAndExit(message) {
29423
29465
  process.exit(1);
29424
29466
  }
29425
29467
  function runMergePlan(targets, options) {
29468
+ const mainRepoRoot = resolveMainWorktreeRoot();
29426
29469
  const mergedSteps = [];
29427
29470
  for (const target of targets) {
29428
- assertBranchMergeWorthiness(target);
29429
- mergeBranch(target.branch);
29430
- runTypecheckGate();
29471
+ assertBranchMergeWorthiness(target, mainRepoRoot);
29472
+ mergeBranch(target.branch, mainRepoRoot);
29473
+ runTypecheckGate(mainRepoRoot);
29431
29474
  mergedSteps.push({
29432
29475
  beadId: target.beadId,
29433
29476
  branch: target.branch,
29434
- changedFiles: readChangedFilesForLastMerge()
29477
+ changedFiles: readChangedFilesForLastMerge(mainRepoRoot)
29435
29478
  });
29436
29479
  }
29437
29480
  if (options.rebuild) {
29438
- runRebuild();
29481
+ runRebuild(mainRepoRoot);
29439
29482
  }
29440
29483
  return mergedSteps;
29441
29484
  }
@@ -30582,6 +30625,49 @@ function readStatusesFromFiles(jobsDir) {
30582
30625
  }
30583
30626
  return statuses.sort((a, b) => b.started_at_ms - a.started_at_ms);
30584
30627
  }
30628
+ function readLastToolEventFromFile(jobsDir, jobId) {
30629
+ const eventsPath = join20(jobsDir, jobId, "events.jsonl");
30630
+ if (!existsSync18(eventsPath))
30631
+ return;
30632
+ try {
30633
+ const lines = readFileSync14(eventsPath, "utf-8").split(`
30634
+ `);
30635
+ for (let index = lines.length - 1;index >= 0; index -= 1) {
30636
+ const line = lines[index]?.trim();
30637
+ if (!line)
30638
+ continue;
30639
+ const parsed = parseTimelineEvent(line);
30640
+ if (!parsed || parsed.type !== "tool")
30641
+ continue;
30642
+ return parsed;
30643
+ }
30644
+ } catch {
30645
+ return;
30646
+ }
30647
+ return;
30648
+ }
30649
+ function resolveDerivedCurrentTool(status, jobsDir, sqliteClient) {
30650
+ let lastToolEvent;
30651
+ try {
30652
+ lastToolEvent = sqliteClient?.readLatestToolEvent(status.id) ?? undefined;
30653
+ } catch {
30654
+ lastToolEvent = undefined;
30655
+ }
30656
+ if (!lastToolEvent) {
30657
+ lastToolEvent = readLastToolEventFromFile(jobsDir, status.id);
30658
+ }
30659
+ if (!lastToolEvent)
30660
+ return status.current_tool;
30661
+ if (lastToolEvent.phase === "start")
30662
+ return lastToolEvent.tool;
30663
+ return;
30664
+ }
30665
+ function enrichStatusesWithDerivedCurrentTool(statuses, jobsDir, sqliteClient) {
30666
+ return statuses.map((status) => ({
30667
+ ...status,
30668
+ current_tool: resolveDerivedCurrentTool(status, jobsDir, sqliteClient)
30669
+ }));
30670
+ }
30585
30671
  function loadStatuses() {
30586
30672
  const sqliteClient = createObservabilitySqliteClient();
30587
30673
  const jobsDir = resolveJobsDir();
@@ -30589,10 +30675,7 @@ function loadStatuses() {
30589
30675
  try {
30590
30676
  const sqliteStatuses = sqliteClient?.listStatuses() ?? [];
30591
30677
  if (sqliteStatuses.length === 0) {
30592
- return fileStatuses;
30593
- }
30594
- if (fileStatuses.length === 0) {
30595
- return sqliteStatuses.sort((a, b) => b.started_at_ms - a.started_at_ms);
30678
+ return enrichStatusesWithDerivedCurrentTool(fileStatuses, jobsDir, sqliteClient).sort((a, b) => b.started_at_ms - a.started_at_ms);
30596
30679
  }
30597
30680
  const merged = new Map;
30598
30681
  for (const status of fileStatuses)
@@ -30603,9 +30686,9 @@ function loadStatuses() {
30603
30686
  merged.set(status.id, status);
30604
30687
  }
30605
30688
  }
30606
- return [...merged.values()].sort((a, b) => b.started_at_ms - a.started_at_ms);
30689
+ return enrichStatusesWithDerivedCurrentTool([...merged.values()], jobsDir, sqliteClient).sort((a, b) => b.started_at_ms - a.started_at_ms);
30607
30690
  } catch {
30608
- return fileStatuses;
30691
+ return enrichStatusesWithDerivedCurrentTool(fileStatuses, jobsDir, sqliteClient).sort((a, b) => b.started_at_ms - a.started_at_ms);
30609
30692
  } finally {
30610
30693
  sqliteClient?.close();
30611
30694
  }
@@ -31284,6 +31367,7 @@ var init_ps = __esm(() => {
31284
31367
  init_supervisor();
31285
31368
  init_job_root();
31286
31369
  init_observability_sqlite();
31370
+ init_timeline_events();
31287
31371
  init_node_resolve();
31288
31372
  init_epic_readiness();
31289
31373
  ACTIVE_STATES = ["starting", "running", "waiting"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaggerxtrm/specialists",
3
- "version": "3.6.2",
3
+ "version": "3.6.5",
4
4
  "description": "OmniSpecialist — 7-tool MCP orchestration layer powered by the Specialist System. Discover and execute .specialist.yaml files across project/user/system scopes via pi.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",