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.
- package/index.js +163 -17
- 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:
|
|
932
|
-
taskKey:
|
|
933
|
-
taskTitle:
|
|
934
|
-
systemPrompt:
|
|
935
|
-
allowedTools:
|
|
936
|
-
claimedFiles:
|
|
937
|
-
warnings:
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
|
1638
|
-
`2. Call
|
|
1639
|
-
`3. Call
|
|
1640
|
-
`4.
|
|
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
|
-
`
|
|
1644
|
-
`6.
|
|
1645
|
-
`7. Call
|
|
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