internaltool-mcp 1.6.30 → 1.6.31

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/index.js +163 -17
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -760,8 +760,20 @@ Call confirmed=false to preview the decomposition, confirmed=true to save it.`,
760
760
  }
761
761
 
762
762
  if (!confirmed) {
763
+ // Include any project-level decomposition templates as hints
764
+ const projectRes = task.project?._id || task.project
765
+ let decompositionTemplates = []
766
+ try {
767
+ const projRes = await apiWithRetry(() => api.get(`/api/projects/${projectRes}`))
768
+ decompositionTemplates = projRes?.data?.project?.agentConfig?.decompositionTemplates || []
769
+ } catch { /* non-fatal */ }
770
+
763
771
  return text({
764
772
  decomposition: executionPlan,
773
+ decompositionTemplates: decompositionTemplates.length > 0 ? {
774
+ note: 'These project templates may match your task. Use them as a starting point for subtaskPlan.',
775
+ templates: decompositionTemplates,
776
+ } : null,
765
777
  warnings: [
766
778
  !executionPlan.scoutReportPresent ? '⚠️ No scout report on this task. Consider running a scout agent first to map the codebase before builders start.' : null,
767
779
  !executionPlan.readmePresent ? '⚠️ No implementation plan (README). Builders need a spec to work from.' : null,
@@ -927,17 +939,32 @@ Returns systemPrompt ready to use as a Claude system prompt.`,
927
939
  } catch { /* non-fatal */ }
928
940
  }
929
941
 
942
+ // Suggest relevant skills based on role and available project skills
943
+ const availableSkills = ctx.project?.agentConfig?.skills || []
944
+ const suggestedSkills = availableSkills.length > 0
945
+ ? availableSkills.map(s => ({
946
+ name: s.name,
947
+ description: s.description,
948
+ path: `.cursor/skills/${s.name}.md`,
949
+ }))
950
+ : effectiveRole === 'builder'
951
+ ? [{ name: 'run-tests', description: 'How to run and validate tests', path: '.cursor/skills/run-tests.md' }]
952
+ : effectiveRole === 'scout'
953
+ ? [{ name: 'scout-codebase', description: 'How to systematically explore and map the codebase', path: '.cursor/skills/scout-codebase.md' }]
954
+ : []
955
+
930
956
  return text({
931
- role: effectiveRole,
932
- taskKey: ctx.taskKey,
933
- taskTitle: ctx.taskTitle,
934
- systemPrompt: parts.join('\n\n'),
935
- allowedTools: ctx.customAllowedTools || null,
936
- claimedFiles: ctx.task?.claimedFiles || [],
937
- warnings: ctx.warnings || [],
938
- task: ctx.task,
939
- project: ctx.project,
940
- usage: 'Use systemPrompt as your Claude system prompt. If allowedTools is set, restrict your MCP tool calls to that list.',
957
+ role: effectiveRole,
958
+ taskKey: ctx.taskKey,
959
+ taskTitle: ctx.taskTitle,
960
+ systemPrompt: parts.join('\n\n'),
961
+ allowedTools: ctx.customAllowedTools || null,
962
+ claimedFiles: ctx.task?.claimedFiles || [],
963
+ warnings: ctx.warnings || [],
964
+ suggestedSkills,
965
+ task: ctx.task,
966
+ project: ctx.project,
967
+ usage: 'Use systemPrompt as your Claude system prompt. If allowedTools is set, restrict your MCP tool calls to that list. Read and follow each skill in suggestedSkills before starting work.',
941
968
  })
942
969
  }
943
970
  )
@@ -1634,15 +1661,19 @@ The agent files are fully self-contained: each builder knows exactly what MCP to
1634
1661
  ``,
1635
1662
  `Do these steps immediately in order — do not ask for confirmation, do not skip any step:`,
1636
1663
  ``,
1637
- `1. Call kickoff_task with taskId "${taskId}", agentRole "builder", confirmed true`,
1638
- `2. Call claim_files with taskId "${taskId}" and files [${filesArg}]`,
1639
- `3. Call get_agent_context with taskId "${taskId}" to read the full task plan`,
1640
- `4. Implement the subtask: ${st.description || st.title}`,
1664
+ `1. Call log_session_event with taskId "${taskId}", type "subagent", name "builder-${i + 1}", summary "Builder ${i + 1} started: ${st.title}"`,
1665
+ `2. Call kickoff_task with taskId "${taskId}", agentRole "builder", confirmed true`,
1666
+ `3. Call claim_files with taskId "${taskId}" and files [${filesArg}]`,
1667
+ `4. Call get_agent_context with taskId "${taskId}" to read the full task plan and suggested skills`,
1668
+ `5. Implement the subtask: ${st.description || st.title}`,
1641
1669
  ` - You may ONLY modify these files: ${fileListPlain}`,
1642
1670
  ` - Do NOT touch any other file`,
1643
- `5. Run npm test all tests must pass before continuing`,
1644
- `6. Call commit_helper with taskId "${taskId}"`,
1645
- `7. Call update_task with taskId "${taskId}" and mark the subtask "${st.title}" as done`,
1671
+ ` - Follow any skills listed in get_agent_context suggestedSkills`,
1672
+ `6. Run npm test all tests must pass before continuing`,
1673
+ `7. Call commit_helper with taskId "${taskId}"`,
1674
+ `8. Call update_task with taskId "${taskId}" and mark the subtask "${st.title}" as done`,
1675
+ `9. Call resume_task with taskId "${taskId}", agentRole "reviewer", confirmed true — transition to reviewer role`,
1676
+ `10. Call log_session_event with taskId "${taskId}", type "subagent", name "builder-${i + 1}", summary "Builder ${i + 1} finished: ${st.title}"`,
1646
1677
  ``,
1647
1678
  `You are builder ${i + 1} of ${parallelCount} running in parallel. Another builder is working on different files at the same time.`,
1648
1679
  ].join('\n')
@@ -1705,6 +1736,99 @@ The agent files are fully self-contained: each builder knows exactly what MCP to
1705
1736
  })
1706
1737
  }
1707
1738
  )
1739
+
1740
+ // ── wait_for_group ─────────────────────────────────────────────────────────────
1741
+ server.tool(
1742
+ 'wait_for_group',
1743
+ `Poll until all subtasks in a decomposition group are marked done. Use after get_parallel_kickoffs.
1744
+
1745
+ COORDINATOR WORKFLOW:
1746
+ 1. Call get_parallel_kickoffs — writes builder agent files, user starts them
1747
+ 2. Call wait_for_group with the groupIndex returned — blocks until all builders finish
1748
+ 3. Call get_parallel_kickoffs again for the next group
1749
+
1750
+ Polls every 10 s, times out after 30 min by default. Returns immediately if already done.`,
1751
+ {
1752
+ taskId: z.string().describe("Task's MongoDB ObjectId"),
1753
+ groupIndex: z.number().int().min(1).describe('1-based group number to wait for (matches group from get_parallel_kickoffs)'),
1754
+ pollIntervalMs: z.number().optional().default(10000).describe('Poll interval in ms (default 10 s)'),
1755
+ timeoutMs: z.number().optional().default(1800000).describe('Max wait in ms (default 30 min)'),
1756
+ },
1757
+ async ({ taskId, groupIndex, pollIntervalMs = 10000, timeoutMs = 1800000 }) => {
1758
+ trackTaskActivity(taskId, 'wait_for_group', { summary: `Waiting for Group ${groupIndex}` })
1759
+ const deadline = Date.now() + timeoutMs
1760
+ let attempts = 0
1761
+
1762
+ while (Date.now() < deadline) {
1763
+ attempts++
1764
+ const taskRes = await apiWithRetry(() => api.get(`/api/tasks/${taskId}`))
1765
+ if (!taskRes?.success) return errorText('Task not found')
1766
+ const task = taskRes.data.task
1767
+
1768
+ if (!task.decomposition?.trim()) return errorText('No decomposition found. Call decompose_task first.')
1769
+ let dec
1770
+ try { dec = JSON.parse(task.decomposition) } catch {
1771
+ return errorText('Decomposition JSON is malformed.')
1772
+ }
1773
+
1774
+ const execOrder = dec.executionOrder || []
1775
+ const targetGroup = execOrder[groupIndex - 1]
1776
+ if (!targetGroup) return errorText(`Group ${groupIndex} does not exist. Decomposition has ${execOrder.length} group(s).`)
1777
+
1778
+ const boardSubtasks = task.subtasks || []
1779
+ const isDone = (title) => boardSubtasks.some(s => s.done && (s.title === title || s.title.endsWith(title)))
1780
+
1781
+ const doneTitles = targetGroup.filter(t => isDone(t))
1782
+ const pendingTitles = targetGroup.filter(t => !isDone(t))
1783
+
1784
+ if (pendingTitles.length === 0) {
1785
+ return text({
1786
+ done: true,
1787
+ group: groupIndex,
1788
+ subtasks: targetGroup,
1789
+ done_count: doneTitles.length,
1790
+ attempts,
1791
+ message: `✅ All ${targetGroup.length} subtask(s) in Group ${groupIndex} are done after ${attempts} poll(s).`,
1792
+ nextStep: groupIndex < execOrder.length
1793
+ ? `Call get_parallel_kickoffs with taskId="${taskId}" to get Group ${groupIndex + 1}'s builder prompts.`
1794
+ : 'All groups complete. Run final tests then call raise_pr.',
1795
+ })
1796
+ }
1797
+
1798
+ // Log progress every 6 polls (~1 min)
1799
+ if (attempts % 6 === 0) {
1800
+ trackTaskActivity(taskId, 'wait_for_group', {
1801
+ summary: `Still waiting for Group ${groupIndex}: ${pendingTitles.length} pending (${doneTitles.length}/${targetGroup.length} done)`,
1802
+ })
1803
+ }
1804
+
1805
+ if (Date.now() + pollIntervalMs < deadline) {
1806
+ await new Promise(resolve => setTimeout(resolve, pollIntervalMs))
1807
+ } else {
1808
+ break
1809
+ }
1810
+ }
1811
+
1812
+ // Timed out — fetch final state
1813
+ const finalRes = await apiWithRetry(() => api.get(`/api/tasks/${taskId}`))
1814
+ const finalTask = finalRes?.data?.task
1815
+ let finalDec
1816
+ try { finalDec = JSON.parse(finalTask?.decomposition || '{}') } catch { finalDec = {} }
1817
+ const finalGroup = (finalDec.executionOrder || [])[groupIndex - 1] || []
1818
+ const finalBoard = finalTask?.subtasks || []
1819
+ const finalIsDone = (t) => finalBoard.some(s => s.done && (s.title === t || s.title.endsWith(t)))
1820
+ const stillPending = finalGroup.filter(t => !finalIsDone(t))
1821
+
1822
+ return text({
1823
+ timedOut: true,
1824
+ group: groupIndex,
1825
+ pending: stillPending,
1826
+ attempts,
1827
+ message: `⏱ Timed out after ${attempts} poll(s). ${stillPending.length} subtask(s) still pending: ${stillPending.join(', ')}`,
1828
+ hint: 'Check the Background Agents panel — a builder may have crashed. Call wait_for_group again to resume waiting, or proceed manually.',
1829
+ })
1830
+ }
1831
+ )
1708
1832
  }
1709
1833
 
1710
1834
  // ── Standup activity formatter ────────────────────────────────────────────────
@@ -4554,6 +4678,19 @@ branchAction values (only needed when current branch ≠ task branch):
4554
4678
  ...unsafeUntracked.map(f => `⚠️ SKIP: ${f} ← do not commit this`),
4555
4679
  ]
4556
4680
 
4681
+ // ── Claims validation ─────────────────────────────────────────────────────
4682
+ // If this task has claimed files, warn if staged changes include unclaimed files.
4683
+ let claimsViolations = []
4684
+ if (taskId) {
4685
+ try {
4686
+ const claimRes = await apiWithRetry(() => api.get(`/api/tasks/${taskId}`))
4687
+ const claimedFiles = claimRes?.data?.task?.claimedFiles || []
4688
+ if (claimedFiles.length > 0) {
4689
+ claimsViolations = changedFilesList.filter(f => !claimedFiles.some(cf => f.endsWith(cf) || cf.endsWith(f)))
4690
+ }
4691
+ } catch { /* non-fatal */ }
4692
+ }
4693
+
4557
4694
  // ── Block if task is parked — prevent Dev A pushing after handoff ────────
4558
4695
  if (task) {
4559
4696
  const isParked = !!(task.parkNote?.parkedAt)
@@ -4666,6 +4803,11 @@ branchAction values (only needed when current branch ≠ task branch):
4666
4803
  : null,
4667
4804
  commands: [addCmd, `git commit -m "${commitMsg}"`, pushCmd].filter(Boolean),
4668
4805
  requiresConfirmation: true,
4806
+ claimsViolations: claimsViolations.length > 0 ? {
4807
+ warning: `⚠️ ${claimsViolations.length} file(s) in your staged changes are NOT in your claimed files list. You may only edit claimed files.`,
4808
+ unclaimed: claimsViolations,
4809
+ claimedFiles: changedFilesList.filter(f => !claimsViolations.includes(f)),
4810
+ } : null,
4669
4811
  message: `Suggested: "${commitMsg}". Call commit_helper again with confirmed=true to get the final commands.`,
4670
4812
  })
4671
4813
  }
@@ -4732,6 +4874,10 @@ branchAction values (only needed when current branch ≠ task branch):
4732
4874
  unsafeUntrackedWarning: unsafeUntracked.length
4733
4875
  ? `These were excluded from git add — add them to .gitignore: ${unsafeUntracked.join(', ')}`
4734
4876
  : null,
4877
+ claimsViolations: claimsViolations.length > 0 ? {
4878
+ warning: `⚠️ ${claimsViolations.length} file(s) modified that are NOT in your claimed files. Review before committing.`,
4879
+ unclaimed: claimsViolations,
4880
+ } : null,
4735
4881
  message: `Copy-paste these commands in order.`,
4736
4882
  nextStep: taskBranch && !branchMismatch ? `After pushing, call raise_pr to open the pull request.` : null,
4737
4883
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "internaltool-mcp",
3
- "version": "1.6.30",
3
+ "version": "1.6.31",
4
4
  "description": "MCP server for InternalTool — connect AI assistants (Claude Code, Cursor) to your project and task management platform",
5
5
  "type": "module",
6
6
  "main": "index.js",