internaltool-mcp 1.6.0 → 1.6.1
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 +206 -74
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1851,25 +1851,42 @@ Set confirmed=false first to preview the full PR content, then confirmed=true to
|
|
|
1851
1851
|
// ── commit_helper ─────────────────────────────────────────────────────────────
|
|
1852
1852
|
server.tool(
|
|
1853
1853
|
'commit_helper',
|
|
1854
|
-
`Analyse local git changes and
|
|
1854
|
+
`Analyse local git changes and produce a ready-to-run conventional commit command.
|
|
1855
1855
|
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1856
|
+
Auto-detects local branch, changed files, and — when taskId is given — the task's linked branch.
|
|
1857
|
+
If the current branch does not match the task's branch, presents three options before doing anything:
|
|
1858
|
+
A) Switch to the task branch first, bring changes with you, then commit
|
|
1859
|
+
B) Stash changes now and commit on the task branch later
|
|
1860
|
+
C) Commit on the current branch (warns if that is main/master/dev)
|
|
1860
1861
|
|
|
1861
|
-
|
|
1862
|
+
Commit message format: <type>(<scope>): <description>
|
|
1863
|
+
type = feat | fix | refactor | chore | docs | test | style (auto-inferred)
|
|
1864
|
+
scope = task key (e.g. task-003) when taskId is provided
|
|
1862
1865
|
|
|
1863
|
-
|
|
1866
|
+
Also flags unsafe patterns before generating any command:
|
|
1867
|
+
- Untracked IDE/config dirs (.cursor/, .idea/, .vscode/, node_modules/)
|
|
1868
|
+
- Committing directly to main / master / dev without a task branch
|
|
1869
|
+
|
|
1870
|
+
TWO-STEP FLOW:
|
|
1871
|
+
confirmed=false → detect state, show branch mismatch options or commit preview
|
|
1872
|
+
confirmed=true + branchAction → execute the chosen path
|
|
1873
|
+
|
|
1874
|
+
branchAction values (only needed when current branch ≠ task branch):
|
|
1875
|
+
"switch_then_commit" — stash → checkout task branch → pop stash → commit + push
|
|
1876
|
+
"stash_for_later" — stash only; commit later when on the task branch
|
|
1877
|
+
"commit_here" — commit on current branch (use only when changes truly belong here)`,
|
|
1864
1878
|
{
|
|
1865
|
-
taskId:
|
|
1866
|
-
repoPath:
|
|
1867
|
-
confirmed:
|
|
1879
|
+
taskId: z.string().optional().describe("Task's MongoDB ObjectId"),
|
|
1880
|
+
repoPath: z.string().optional().describe('Absolute path to the local git repo'),
|
|
1881
|
+
confirmed: z.boolean().optional().default(false),
|
|
1882
|
+
branchAction: z.enum(['switch_then_commit', 'stash_for_later', 'commit_here'])
|
|
1883
|
+
.optional()
|
|
1884
|
+
.describe('Required when confirmed=true and current branch ≠ task branch'),
|
|
1868
1885
|
},
|
|
1869
|
-
async ({ taskId, repoPath, confirmed = false }) => {
|
|
1886
|
+
async ({ taskId, repoPath, confirmed = false, branchAction }) => {
|
|
1870
1887
|
const cwd = repoPath || process.cwd()
|
|
1871
1888
|
|
|
1872
|
-
// Read local git state
|
|
1889
|
+
// ── Read local git state ──────────────────────────────────────────────────
|
|
1873
1890
|
let porcelain = '', diffStat = '', currentBranch = ''
|
|
1874
1891
|
try {
|
|
1875
1892
|
porcelain = runGit('status --porcelain=v1', cwd)
|
|
@@ -1885,7 +1902,7 @@ Use this when the developer asks "help me commit", "what should my commit messag
|
|
|
1885
1902
|
return text({ message: 'Nothing to commit — working tree is clean.', currentBranch })
|
|
1886
1903
|
}
|
|
1887
1904
|
|
|
1888
|
-
// Fetch task
|
|
1905
|
+
// ── Fetch task ────────────────────────────────────────────────────────────
|
|
1889
1906
|
let task = null
|
|
1890
1907
|
if (taskId) {
|
|
1891
1908
|
try {
|
|
@@ -1894,87 +1911,202 @@ Use this when the developer asks "help me commit", "what should my commit messag
|
|
|
1894
1911
|
} catch { /* non-fatal */ }
|
|
1895
1912
|
}
|
|
1896
1913
|
|
|
1897
|
-
|
|
1898
|
-
const
|
|
1899
|
-
const
|
|
1914
|
+
const taskBranch = task?.github?.headBranch || null
|
|
1915
|
+
const PROTECTED = ['main', 'master', 'dev', 'develop', 'staging', 'production']
|
|
1916
|
+
const onProtected = PROTECTED.includes(currentBranch)
|
|
1917
|
+
const branchMismatch = taskBranch && currentBranch !== taskBranch
|
|
1918
|
+
|
|
1919
|
+
// ── Detect unsafe untracked paths ─────────────────────────────────────────
|
|
1920
|
+
const NOCOMMIT_PATTERNS = ['.cursor', '.idea', '.vscode', 'node_modules', '.env', 'dist', 'build', '.DS_Store']
|
|
1921
|
+
const unsafeUntracked = untracked.filter(f =>
|
|
1922
|
+
NOCOMMIT_PATTERNS.some(p => f === p || f.startsWith(p + '/'))
|
|
1923
|
+
)
|
|
1924
|
+
const safeUntracked = untracked.filter(f =>
|
|
1925
|
+
!NOCOMMIT_PATTERNS.some(p => f === p || f.startsWith(p + '/'))
|
|
1926
|
+
)
|
|
1927
|
+
|
|
1928
|
+
// ── Build commit message ──────────────────────────────────────────────────
|
|
1929
|
+
// Use task branch name for type inference if available (more reliable than current branch)
|
|
1930
|
+
const branchForType = taskBranch || currentBranch
|
|
1931
|
+
const branchLower = branchForType.toLowerCase()
|
|
1932
|
+
const diffLower = diffStat.toLowerCase()
|
|
1900
1933
|
let commitType = 'chore'
|
|
1901
|
-
if (branchLower.startsWith('feature/') || branchLower.startsWith('feat/'))
|
|
1902
|
-
else if (branchLower.startsWith('fix/') || branchLower.startsWith('hotfix/'))
|
|
1903
|
-
else if (branchLower.startsWith('refactor/'))
|
|
1904
|
-
else if (branchLower.startsWith('docs/'))
|
|
1905
|
-
else if (branchLower.startsWith('test/'))
|
|
1906
|
-
else if (diffLower.includes('test') || diffLower.includes('spec'))
|
|
1907
|
-
else if (diffLower.includes('readme') || diffLower.includes('.md')) commitType = 'docs'
|
|
1934
|
+
if (branchLower.startsWith('feature/') || branchLower.startsWith('feat/')) commitType = 'feat'
|
|
1935
|
+
else if (branchLower.startsWith('fix/') || branchLower.startsWith('hotfix/')) commitType = 'fix'
|
|
1936
|
+
else if (branchLower.startsWith('refactor/')) commitType = 'refactor'
|
|
1937
|
+
else if (branchLower.startsWith('docs/')) commitType = 'docs'
|
|
1938
|
+
else if (branchLower.startsWith('test/')) commitType = 'test'
|
|
1939
|
+
else if (diffLower.includes('test') || diffLower.includes('spec')) commitType = 'test'
|
|
1908
1940
|
else if (task) {
|
|
1909
|
-
const
|
|
1910
|
-
if (/\b(fix|bug|hotfix|patch)\b/.test(
|
|
1911
|
-
else if (/\b(refactor|cleanup
|
|
1912
|
-
else
|
|
1941
|
+
const t = (task.title || '').toLowerCase()
|
|
1942
|
+
if (/\b(fix|bug|hotfix|patch)\b/.test(t)) commitType = 'fix'
|
|
1943
|
+
else if (/\b(refactor|cleanup)\b/.test(t)) commitType = 'refactor'
|
|
1944
|
+
else commitType = 'feat'
|
|
1913
1945
|
}
|
|
1914
1946
|
|
|
1915
|
-
// Build scope
|
|
1916
1947
|
const scope = task?.key?.toLowerCase() || ''
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
//
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
? `${commitType}(${scope}): ${description}`
|
|
1939
|
-
: `${commitType}: ${description}`
|
|
1940
|
-
|
|
1941
|
-
// Staged vs not staged — build the right add command
|
|
1942
|
-
const hasUnstaged = unstaged.length > 0 || untracked.length > 0
|
|
1943
|
-
const hasStaged = staged.length > 0
|
|
1944
|
-
const addCmd = hasUnstaged ? 'git add .' : null
|
|
1945
|
-
const commitCmd = `git commit -m "${commitMsg}"`
|
|
1946
|
-
const pushCmd = `git push origin ${currentBranch}`
|
|
1947
|
-
|
|
1948
|
-
const changedFiles = [
|
|
1949
|
-
...staged.map(f => ` staged: ${f.file}`),
|
|
1950
|
-
...unstaged.map(f => ` unstaged: ${f.file}`),
|
|
1951
|
-
...untracked.map(f => ` untracked: ${f}`),
|
|
1948
|
+
let description = task
|
|
1949
|
+
? task.title.toLowerCase().replace(/[^a-z0-9 ]+/g, '').replace(/\s+/g, ' ').trim().slice(0, 60)
|
|
1950
|
+
: modified.length === 1
|
|
1951
|
+
? `update ${modified[0].split('/').pop()}`
|
|
1952
|
+
: `update ${modified.length} file(s)`
|
|
1953
|
+
|
|
1954
|
+
const commitMsg = scope ? `${commitType}(${scope}): ${description}` : `${commitType}: ${description}`
|
|
1955
|
+
|
|
1956
|
+
// ── Build add command — exclude unsafe untracked ──────────────────────────
|
|
1957
|
+
const trackedFiles = [...modified, ...safeUntracked]
|
|
1958
|
+
const addCmd = unsafeUntracked.length > 0 && (unstaged.length > 0 || safeUntracked.length > 0)
|
|
1959
|
+
? trackedFiles.map(f => `git add "${f}"`).join('\n') // explicit adds — skip unsafe
|
|
1960
|
+
: unstaged.length > 0 || safeUntracked.length > 0
|
|
1961
|
+
? 'git add .'
|
|
1962
|
+
: null // everything already staged
|
|
1963
|
+
|
|
1964
|
+
const changedFilesList = [
|
|
1965
|
+
...staged.map(f => `staged: ${f.file}`),
|
|
1966
|
+
...unstaged.map(f => `unstaged: ${f.file}`),
|
|
1967
|
+
...safeUntracked.map(f => `untracked: ${f}`),
|
|
1968
|
+
...unsafeUntracked.map(f => `⚠️ SKIP: ${f} ← do not commit this`),
|
|
1952
1969
|
]
|
|
1953
1970
|
|
|
1971
|
+
// ── PREVIEW (confirmed=false) ─────────────────────────────────────────────
|
|
1954
1972
|
if (!confirmed) {
|
|
1973
|
+
// Case 1: branch mismatch — must resolve before anything else
|
|
1974
|
+
if (branchMismatch) {
|
|
1975
|
+
const stashMsg = `wip: ${scope || currentBranch} — switching to ${taskBranch}`
|
|
1976
|
+
return text({
|
|
1977
|
+
situation: {
|
|
1978
|
+
currentBranch,
|
|
1979
|
+
taskBranch,
|
|
1980
|
+
message: `You are on "${currentBranch}" but TASK-${task?.key} is linked to "${taskBranch}". Choose how to handle your local changes before committing.`,
|
|
1981
|
+
},
|
|
1982
|
+
changedFiles: changedFilesList,
|
|
1983
|
+
unsafeUntrackedWarning: unsafeUntracked.length
|
|
1984
|
+
? `These paths should NOT be committed — add them to .gitignore: ${unsafeUntracked.join(', ')}`
|
|
1985
|
+
: null,
|
|
1986
|
+
options: {
|
|
1987
|
+
A: {
|
|
1988
|
+
branchAction: 'switch_then_commit',
|
|
1989
|
+
description: 'Switch to the task branch now, bring your changes, then commit (recommended)',
|
|
1990
|
+
commands: [
|
|
1991
|
+
`git stash push -m "${stashMsg}"`,
|
|
1992
|
+
`git fetch origin`,
|
|
1993
|
+
`git checkout ${taskBranch}`,
|
|
1994
|
+
`git stash pop`,
|
|
1995
|
+
addCmd,
|
|
1996
|
+
`git commit -m "${commitMsg}"`,
|
|
1997
|
+
`git push origin ${taskBranch}`,
|
|
1998
|
+
].filter(Boolean),
|
|
1999
|
+
},
|
|
2000
|
+
B: {
|
|
2001
|
+
branchAction: 'stash_for_later',
|
|
2002
|
+
description: 'Stash changes now and commit on the task branch later',
|
|
2003
|
+
commands: [
|
|
2004
|
+
`git stash push -m "${stashMsg}"`,
|
|
2005
|
+
`# Later: git checkout ${taskBranch} && git stash pop`,
|
|
2006
|
+
],
|
|
2007
|
+
},
|
|
2008
|
+
C: {
|
|
2009
|
+
branchAction: 'commit_here',
|
|
2010
|
+
description: `Commit on "${currentBranch}" as-is${onProtected ? ' ⚠️ THIS IS A PROTECTED BRANCH' : ''}`,
|
|
2011
|
+
warning: onProtected
|
|
2012
|
+
? `"${currentBranch}" is a protected branch. Committing here directly bypasses the PR review process. Only do this for base repo changes (e.g. .gitignore, root README) that don't belong to any feature branch.`
|
|
2013
|
+
: null,
|
|
2014
|
+
commands: [addCmd, `git commit -m "${commitMsg}"`, `git push origin ${currentBranch}`].filter(Boolean),
|
|
2015
|
+
},
|
|
2016
|
+
},
|
|
2017
|
+
requiresConfirmation: true,
|
|
2018
|
+
message: `Call commit_helper again with confirmed=true and branchAction set to "switch_then_commit", "stash_for_later", or "commit_here".`,
|
|
2019
|
+
})
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// Case 2: on correct branch — standard preview
|
|
2023
|
+
const pushCmd = `git push origin ${currentBranch}`
|
|
1955
2024
|
return text({
|
|
1956
2025
|
preview: {
|
|
1957
2026
|
suggestedMessage: commitMsg,
|
|
1958
|
-
type:
|
|
2027
|
+
type: commitType,
|
|
1959
2028
|
scope,
|
|
1960
|
-
description,
|
|
1961
2029
|
currentBranch,
|
|
1962
|
-
|
|
1963
|
-
|
|
2030
|
+
taskBranch: taskBranch || '(none linked)',
|
|
2031
|
+
onCorrectBranch: !branchMismatch,
|
|
2032
|
+
changedFiles: changedFilesList,
|
|
1964
2033
|
},
|
|
1965
|
-
|
|
2034
|
+
unsafeUntrackedWarning: unsafeUntracked.length
|
|
2035
|
+
? `These paths should NOT be committed — add them to .gitignore first: ${unsafeUntracked.join(', ')}`
|
|
2036
|
+
: null,
|
|
2037
|
+
protectedBranchWarning: onProtected
|
|
2038
|
+
? `⚠️ You are on "${currentBranch}" — a protected branch. Consider committing on a feature branch instead.`
|
|
2039
|
+
: null,
|
|
2040
|
+
commands: [addCmd, `git commit -m "${commitMsg}"`, pushCmd].filter(Boolean),
|
|
1966
2041
|
requiresConfirmation: true,
|
|
1967
|
-
message: `Suggested
|
|
1968
|
-
|
|
2042
|
+
message: `Suggested: "${commitMsg}". Call commit_helper again with confirmed=true to get the final commands.`,
|
|
2043
|
+
})
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// ── CONFIRMED (confirmed=true) ────────────────────────────────────────────
|
|
2047
|
+
if (branchMismatch && !branchAction) {
|
|
2048
|
+
return text({
|
|
2049
|
+
blocked: true,
|
|
2050
|
+
message: `Branch mismatch detected. You must set branchAction to one of: "switch_then_commit", "stash_for_later", "commit_here". Call commit_helper with confirmed=false first to see the options.`,
|
|
2051
|
+
})
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
const stashMsg = `wip: ${scope || currentBranch} — switching to ${taskBranch}`
|
|
2055
|
+
|
|
2056
|
+
if (branchAction === 'switch_then_commit') {
|
|
2057
|
+
return text({
|
|
2058
|
+
plan: 'switch_then_commit',
|
|
2059
|
+
commands: [
|
|
2060
|
+
`git stash push -m "${stashMsg}"`,
|
|
2061
|
+
`git fetch origin`,
|
|
2062
|
+
`git checkout ${taskBranch}`,
|
|
2063
|
+
`git stash pop`,
|
|
2064
|
+
addCmd,
|
|
2065
|
+
`git commit -m "${commitMsg}"`,
|
|
2066
|
+
`git push origin ${taskBranch}`,
|
|
2067
|
+
].filter(Boolean),
|
|
2068
|
+
commitMessage: commitMsg,
|
|
2069
|
+
message: `Run these commands in order. Your changes will land on "${taskBranch}" where they belong.`,
|
|
2070
|
+
nextStep: `After pushing, call raise_pr to open the pull request for ${task?.key}.`,
|
|
2071
|
+
unsafeUntrackedWarning: unsafeUntracked.length
|
|
2072
|
+
? `These will be in your working tree after stash pop — do NOT git add them: ${unsafeUntracked.join(', ')}`
|
|
2073
|
+
: null,
|
|
2074
|
+
})
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
if (branchAction === 'stash_for_later') {
|
|
2078
|
+
return text({
|
|
2079
|
+
plan: 'stash_for_later',
|
|
2080
|
+
commands: [
|
|
2081
|
+
`git stash push -m "${stashMsg}"`,
|
|
2082
|
+
],
|
|
2083
|
+
message: `Your changes are stashed. When you're ready to commit:`,
|
|
2084
|
+
resumeCommands: [
|
|
2085
|
+
`git checkout ${taskBranch}`,
|
|
2086
|
+
`git stash pop`,
|
|
2087
|
+
addCmd,
|
|
2088
|
+
`git commit -m "${commitMsg}"`,
|
|
2089
|
+
`git push origin ${taskBranch}`,
|
|
2090
|
+
].filter(Boolean),
|
|
2091
|
+
commitMessage: commitMsg,
|
|
1969
2092
|
})
|
|
1970
2093
|
}
|
|
1971
2094
|
|
|
2095
|
+
// commit_here (or no mismatch)
|
|
2096
|
+
const targetBranch = currentBranch
|
|
2097
|
+
const pushCmd = `git push origin ${targetBranch}`
|
|
1972
2098
|
return text({
|
|
1973
2099
|
commitMessage: commitMsg,
|
|
1974
|
-
commands: [addCmd,
|
|
1975
|
-
changedFiles,
|
|
1976
|
-
|
|
1977
|
-
|
|
2100
|
+
commands: [addCmd, `git commit -m "${commitMsg}"`, pushCmd].filter(Boolean),
|
|
2101
|
+
changedFiles: changedFilesList,
|
|
2102
|
+
protectedBranchWarning: onProtected
|
|
2103
|
+
? `⚠️ Committing directly to "${targetBranch}". Only appropriate for base repo changes that don't belong to a feature branch.`
|
|
2104
|
+
: null,
|
|
2105
|
+
unsafeUntrackedWarning: unsafeUntracked.length
|
|
2106
|
+
? `These were excluded from git add — add them to .gitignore: ${unsafeUntracked.join(', ')}`
|
|
2107
|
+
: null,
|
|
2108
|
+
message: `Copy-paste these commands in order.`,
|
|
2109
|
+
nextStep: taskBranch && !branchMismatch ? `After pushing, call raise_pr to open the pull request.` : null,
|
|
1978
2110
|
})
|
|
1979
2111
|
}
|
|
1980
2112
|
)
|
package/package.json
CHANGED