make-runner-mcp 2.1.0 → 2.2.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
@@ -113,24 +113,41 @@ you expect actually show up.
113
113
 
114
114
  ### Fixing it automatically, not just diagnosing it
115
115
 
116
- The procedure above (find every Makefile, work out which ones
117
- make-runner-mcp can't currently see, add the `also-read` marker for them)
118
- is written once, at [`skills/fix-makefile-links/SKILL.md`](./skills/fix-makefile-links/SKILL.md),
119
- and reachable two ways:
116
+ There's a broader compatibility procedure than just linking a Makefile
117
+ written for a human at a real terminal often has two other habits that
118
+ break under make-runner-mcp too:
119
+
120
+ - **Hardcoded interactive docker flags** — `docker exec -it`/`-itu ...`
121
+ (or `docker compose exec` without `-T`) requires a real TTY. It works
122
+ for a human, and fails or hangs when make-runner-mcp runs it (it always
123
+ spawns `make` with piped, non-tty stdio).
124
+ - **`$(MAKECMDGOALS)`-style argument passing** — a `composer`-style target
125
+ that forwards `make composer install foo` via
126
+ `$(filter-out $@,$(MAKECMDGOALS))` can't take arguments through
127
+ make-runner-mcp at all: bare positional arguments are always rejected
128
+ (see [Passing arguments through to a target](#passing-arguments-through-to-a-target)
129
+ above) — only `ARGS=value` style works.
130
+
131
+ All three (linking, TTY flags, `$(MAKECMDGOALS)`) are covered by one
132
+ procedure, written once at
133
+ [`skills/fix-makefile-compat/SKILL.md`](./skills/fix-makefile-compat/SKILL.md)
134
+ — it finds every instance of each, shows you the full proposed changeset,
135
+ and waits for your approval before writing anything. Reachable two ways:
120
136
 
121
137
  - **Already connected to this server** (the normal case — no extra setup):
122
- it's exposed as an MCP **prompt** named `fix-makefile-links`. Any
138
+ it's exposed as an MCP **prompt** named `fix-makefile-compat`. Any
123
139
  MCP client that supports prompts (Claude Code's `/mcp` prompt picker,
124
- etc.) can pull it straight from the running server — the prompt text
125
- comes back with this project's own `PROJECT_DIR`, Makefile path, and a
126
- ready-to-run `--diagnose` command already filled in, since the server
127
- serving it already knows all three. Nothing to install.
140
+ Gemini CLI's automatic MCP-prompt-as-slash-command support, etc.) can
141
+ pull it straight from the running server the prompt text comes back
142
+ with this project's own `PROJECT_DIR`, Makefile path, and a ready-to-run
143
+ `--diagnose` command already filled in, since the server serving it
144
+ already knows all three. Nothing to install.
128
145
  - **Not using make-runner-mcp as an MCP server here** (e.g. evaluating it,
129
146
  or just want the procedure without wiring up a client): install the same
130
147
  file as a standalone Claude Code skill —
131
- `cp -r skills/fix-makefile-links ~/.claude/skills/` (every project) or
148
+ `cp -r skills/fix-makefile-compat ~/.claude/skills/` (every project) or
132
149
  into a single project's own `.claude/skills/` — then run
133
- `/fix-makefile-links`. Slightly more manual (it has to search for a
150
+ `/fix-makefile-compat`. Slightly more manual (it has to search for a
134
151
  make-runner-mcp checkout itself to run `--diagnose`, rather than already
135
152
  knowing where one is), but requires nothing beyond Claude Code.
136
153
 
@@ -142,7 +159,7 @@ The server is the same everywhere — only the config file and its shape
142
159
  differ per client. Things that change between projects/teams:
143
160
 
144
161
  - `PROJECT_DIR` — the target project's root.
145
- - `#v2.1.0` — bump this to whatever tag you've actually released. Always
162
+ - `#v2.2.0` — bump this to whatever tag you've actually released. Always
146
163
  pin to a release tag, never `#main`: for a tool that executes commands,
147
164
  an unpinned branch reference means a bad push could silently change what
148
165
  runs on everyone's machine.
@@ -157,7 +174,7 @@ differ per client. Things that change between projects/teams:
157
174
  "mcpServers": {
158
175
  "makeRunner": {
159
176
  "command": "npx",
160
- "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
177
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.2.0"],
161
178
  "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
162
179
  }
163
180
  }
@@ -171,13 +188,13 @@ Project-level `.mcp.json` at the repo root (checked in, shared with the team):
171
188
  "mcpServers": {
172
189
  "makeRunner": {
173
190
  "command": "npx",
174
- "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
191
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.2.0"],
175
192
  "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
176
193
  }
177
194
  }
178
195
  }
179
196
  ```
180
- Or via the CLI: `claude mcp add makeRunner -e PROJECT_DIR=/path/to/project -e MCP_TRANSPORT=stdio -- npx -y github:davindermahal/make-runner-mcp#v2.1.0`
197
+ Or via the CLI: `claude mcp add makeRunner -e PROJECT_DIR=/path/to/project -e MCP_TRANSPORT=stdio -- npx -y github:davindermahal/make-runner-mcp#v2.2.0`
181
198
 
182
199
  ### Claude Desktop
183
200
  `claude_desktop_config.json` (Settings → Developer → Edit Config):
@@ -186,7 +203,7 @@ Or via the CLI: `claude mcp add makeRunner -e PROJECT_DIR=/path/to/project -e MC
186
203
  "mcpServers": {
187
204
  "makeRunner": {
188
205
  "command": "npx",
189
- "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
206
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.2.0"],
190
207
  "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
191
208
  }
192
209
  }
@@ -200,7 +217,7 @@ Or via the CLI: `claude mcp add makeRunner -e PROJECT_DIR=/path/to/project -e MC
200
217
  "mcpServers": {
201
218
  "makeRunner": {
202
219
  "command": "npx",
203
- "args": ["-y", "github:davindermahal/make-runner-mcp#v2.1.0"],
220
+ "args": ["-y", "github:davindermahal/make-runner-mcp#v2.2.0"],
204
221
  "env": { "PROJECT_DIR": "/path/to/project", "MCP_TRANSPORT": "stdio" }
205
222
  }
206
223
  }
@@ -232,7 +249,7 @@ on this machine):
232
249
  PROJECT_DIR=/path/to/project \
233
250
  MCP_HTTP_TOKEN=$(openssl rand -hex 24) \
234
251
  MCP_HTTP_PORT=8791 \
235
- npx -y github:davindermahal/make-runner-mcp#v2.1.0
252
+ npx -y github:davindermahal/make-runner-mcp#v2.2.0
236
253
  ```
237
254
 
238
255
  Generate a real random token (`openssl rand -hex 24` or equivalent) and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-runner-mcp",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "description": "Scoped MCP server that discovers a project's Makefile targets and exposes each one as an individual MCP tool.",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -88,15 +88,15 @@ const CONFIG_PATH =
88
88
  process.env.MCP_MAKE_CONFIG || path.join(PROJECT_DIR, ".mcp-make-config.json");
89
89
 
90
90
  // This server's own location, not the target project's — used to serve the
91
- // "fix-makefile-links" MCP prompt (below) straight from the same checkout
91
+ // "fix-makefile-compat" MCP prompt (below) straight from the same checkout
92
92
  // this server is running from, and to point that prompt's own verification
93
93
  // step back at this exact server.js rather than asking whoever receives
94
94
  // the prompt to go find one.
95
95
  const SCRIPT_PATH = fileURLToPath(import.meta.url);
96
- const FIX_MAKEFILE_LINKS_SKILL_PATH = path.join(
96
+ const FIX_MAKEFILE_COMPAT_SKILL_PATH = path.join(
97
97
  path.dirname(SCRIPT_PATH),
98
98
  "skills",
99
- "fix-makefile-links",
99
+ "fix-makefile-compat",
100
100
  "SKILL.md"
101
101
  );
102
102
 
@@ -621,7 +621,7 @@ function runDiagnostics() {
621
621
  console.log(lines.join("\n"));
622
622
  }
623
623
 
624
- // Serves skills/fix-makefile-links/SKILL.md (the same file distributed for
624
+ // Serves skills/fix-makefile-compat/SKILL.md (the same file distributed for
625
625
  // manual/Claude-Code-skill installs — see README) as an MCP prompt, so
626
626
  // simply connecting to this server is enough to get it: no separate file
627
627
  // to copy into ~/.claude/skills first. One file stays the source of truth
@@ -630,18 +630,19 @@ function runDiagnostics() {
630
630
  // PROJECT_DIR/Makefile/script path) so whoever receives the prompt doesn't
631
631
  // need to search for a make-runner-mcp checkout the way a cold read of the
632
632
  // skill file on its own would require.
633
- function buildFixMakefileLinksPrompt() {
633
+ function buildFixMakefileCompatPrompt() {
634
634
  let raw;
635
635
  try {
636
- raw = readFileSync(FIX_MAKEFILE_LINKS_SKILL_PATH, "utf8");
636
+ raw = readFileSync(FIX_MAKEFILE_COMPAT_SKILL_PATH, "utf8");
637
637
  } catch (err) {
638
- console.error(`Failed to read ${FIX_MAKEFILE_LINKS_SKILL_PATH}: ${err.message}`);
638
+ console.error(`Failed to read ${FIX_MAKEFILE_COMPAT_SKILL_PATH}: ${err.message}`);
639
639
  return (
640
- `Could not load the fix-makefile-links procedure from this server's own ` +
641
- `checkout (expected at ${FIX_MAKEFILE_LINKS_SKILL_PATH}). Run ` +
640
+ `Could not load the fix-makefile-compat procedure from this server's own ` +
641
+ `checkout (expected at ${FIX_MAKEFILE_COMPAT_SKILL_PATH}). Run ` +
642
642
  `\`PROJECT_DIR=${PROJECT_DIR} node ${SCRIPT_PATH} --diagnose\` and fix any ` +
643
643
  `gaps it reports using MAKEFILE-GUIDE.md's guidance on include/forwarding/` +
644
- `also-read links.`
644
+ `also-read links, hardcoded interactive docker flags, and $(MAKECMDGOALS)` +
645
+ `-style argument passing.`
645
646
  );
646
647
  }
647
648
  // Strip the leading YAML frontmatter block — it's Claude Code skill
@@ -651,10 +652,10 @@ function buildFixMakefileLinksPrompt() {
651
652
  const context =
652
653
  `You are receiving this as an MCP prompt served directly by the ` +
653
654
  `make-runner-mcp server already configured for this project — you ` +
654
- `already have everything Step 5 asks you to go find:\n\n` +
655
+ `already have everything Step 8's live verification asks you to go find:\n\n` +
655
656
  `Project root (PROJECT_DIR): ${PROJECT_DIR}\n` +
656
657
  `Root Makefile: ${MAKEFILE}\n` +
657
- `This server's own script, for Step 5's live verification: ${SCRIPT_PATH}\n` +
658
+ `This server's own script, for Step 8's live verification: ${SCRIPT_PATH}\n` +
658
659
  ` e.g. PROJECT_DIR="${PROJECT_DIR}" node "${SCRIPT_PATH}" --diagnose\n\n` +
659
660
  `---\n\n`;
660
661
  return context + body;
@@ -880,33 +881,36 @@ function createServer() {
880
881
  }
881
882
  });
882
883
 
883
- // A single prompt, "fix-makefile-links": see buildFixMakefileLinksPrompt()
884
+ // A single prompt, "fix-makefile-compat": see buildFixMakefileCompatPrompt()
884
885
  // above for what it actually serves and why. This is what makes it
885
886
  // reachable to any MCP client without a separate skill-file install —
886
887
  // connecting to this server is enough.
887
888
  server.setRequestHandler(ListPromptsRequestSchema, async () => ({
888
889
  prompts: [
889
890
  {
890
- name: "fix-makefile-links",
891
+ name: "fix-makefile-compat",
891
892
  description:
892
- "Find every Makefile in this project, check which ones make-runner-mcp can " +
893
- "actually discover, and add the missing `also-read` link automatically for " +
894
- "any that are orphaned instead of just reporting the gap.",
893
+ "Find everything in this project's Makefile(s) that keeps make-runner-mcp from " +
894
+ "discovering or correctly running a target an unlinked secondary Makefile, " +
895
+ "hardcoded interactive docker flags (-it/-itu) that only work for a human at a " +
896
+ "real terminal, and $(MAKECMDGOALS)-style argument passing this server's " +
897
+ "security model rejects outright — and fix each one, with your approval before " +
898
+ "writing anything.",
895
899
  },
896
900
  ],
897
901
  }));
898
902
 
899
903
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
900
904
  const { name } = request.params;
901
- if (name !== "fix-makefile-links") {
905
+ if (name !== "fix-makefile-compat") {
902
906
  throw new Error(`Unknown prompt: ${name}`);
903
907
  }
904
908
  return {
905
- description: "Diagnose and fix why make-runner-mcp isn't discovering a secondary Makefile's targets.",
909
+ description: "Diagnose and fix why a Makefile target isn't discoverable by, or compatible with, make-runner-mcp.",
906
910
  messages: [
907
911
  {
908
912
  role: "user",
909
- content: { type: "text", text: buildFixMakefileLinksPrompt() },
913
+ content: { type: "text", text: buildFixMakefileCompatPrompt() },
910
914
  },
911
915
  ],
912
916
  };
@@ -0,0 +1,346 @@
1
+ ---
2
+ description: Find everything in this project's Makefile(s) that keeps make-runner-mcp from discovering or correctly running a target — a secondary Makefile that isn't linked in, hardcoded interactive docker flags (`-it`/`-itu`) that only work for a human at a real terminal, and bare-word `$(MAKECMDGOALS)`-style argument passing that make-runner-mcp's security model rejects outright — and fix each one, showing you every change and waiting for your approval before writing anything. Only run when explicitly invoked as /fix-makefile-compat.
3
+ argument-hint: [optional: path to a local make-runner-mcp checkout, for live --diagnose verification]
4
+ disable-model-invocation: true
5
+ allowed-tools: Read Glob Grep Bash Edit AskUserQuestion
6
+ ---
7
+
8
+ # Make this project's Makefile(s) work for both a human and make-runner-mcp
9
+
10
+ Input: $ARGUMENTS (optional path to a make-runner-mcp checkout — see Step 6)
11
+
12
+ Goal: a human typing `make <target>` at their own terminal keeps getting
13
+ exactly the experience they already have — colored output, an interactive
14
+ shell, a live progress bar, whatever the Makefile already does for them —
15
+ while the *same* target also works when make-runner-mcp invokes it, which
16
+ happens through a real `make` process that has no terminal at all and
17
+ whose security model flatly refuses to run anything but the one named
18
+ target with only flag/`VAR=value` arguments. This skill finds every place
19
+ that assumption breaks and fixes it, three categories at a time, with your
20
+ approval before any file changes.
21
+
22
+ This does **not** cover everything `MAKEFILE-GUIDE.md` checks — denylist
23
+ false positives, reliance on reserved make variables, and description
24
+ backfilling are separate, higher-judgment concerns. Mention any you notice
25
+ along the way in your final report, but don't act on them here.
26
+
27
+ ## Step 0: Preconditions
28
+
29
+ - Confirm a root `Makefile` exists at the project root (where this was
30
+ invoked, or a directory named in $ARGUMENTS/the conversation). If not,
31
+ stop and say so — make-runner-mcp requires `<project root>/Makefile` to
32
+ exist; there's nothing to fix yet.
33
+ - `git status --porcelain` (if this is a git repo). If dirty, proceed
34
+ anyway, but say so in your final report — your edits will land alongside
35
+ whatever's already uncommitted.
36
+
37
+ ## Step 1: Inventory every Makefile in the project
38
+
39
+ ```bash
40
+ find . -name Makefile \
41
+ -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/vendor/*' \
42
+ -not -path '*/.venv/*' -not -path '*/venv/*'
43
+ ```
44
+
45
+ Read every file this finds. Set aside the root one; everything else is a
46
+ **candidate secondary Makefile**. Every check below (Steps 2–5) applies to
47
+ the root Makefile and to every file reached from it (Step 2), not just the
48
+ root file in isolation — a target defined in a real-`include`d file is
49
+ just as much make-runner-mcp's problem as one in the root file itself.
50
+
51
+ ## Step 2: Resolve what the root Makefile already reaches (for the linking check)
52
+
53
+ Read the root Makefile (and recurse into anything it reaches, same as
54
+ below) and classify every line into one of three link mechanisms — this
55
+ mirrors exactly what make-runner-mcp's own parser does, so a file found
56
+ here is one make-runner-mcp already discovers today:
57
+
58
+ 1. **Real include**: a line matching `^\s*(-|s)?include\s+(.+)$`. Each
59
+ whitespace-separated path on the line, resolved relative to the
60
+ *including* file's own directory. A path containing `$(` can't be
61
+ resolved by make-runner-mcp either — note it as **unresolvable
62
+ include** in your final report rather than treating it as reachable.
63
+ 2. **Catch-all forwarding**: a rule whose target is bare `%:` (not
64
+ `%.o:` or `docker-%:` — those are ordinary pattern rules, not this
65
+ idiom), whose recipe (the tab-indented lines immediately after it)
66
+ contains `$(MAKE) ... -C <dir>`. Reaches `<dir>/Makefile`. A `-C`
67
+ value containing `$(` similarly can't be resolved — note it the same
68
+ way as an unresolvable include.
69
+ 3. **The marker this skill itself manages**: a comment line matching
70
+ `^\s*#{1,2}\s*make-runner:\s*also-read\s+(.+)$`. Each
71
+ whitespace-separated path, resolved the same way as `include`.
72
+
73
+ For every file reached via (1) or (3), recurse into it too — apply the
74
+ same three checks to its contents, since an included/linked file can
75
+ itself `include` or mark further files. (Forwarding targets, (2), are a
76
+ Makefile invoked by a live `$(MAKE)` at build time in a fresh directory
77
+ context, not textually processed further here — don't recurse into them
78
+ for more forwarding/include chains; treat the forwarded file itself as
79
+ reached and stop there, matching what make-runner-mcp's own parser does.)
80
+
81
+ Build the **reached set**: the realpath of every file resolved this way,
82
+ starting from the root Makefile itself. Any candidate secondary Makefile
83
+ from Step 1 whose realpath is **not** in this set is **orphaned** —
84
+ make-runner-mcp cannot see its targets at all right now.
85
+
86
+ ## Step 3: Fix orphaned Makefiles (linking)
87
+
88
+ For each orphaned Makefile, the default and safest fix is to add this line
89
+ to the root Makefile (near any existing `include`/`also-read` lines if
90
+ present, otherwise near the top, after any leading variable assignments):
91
+
92
+ ```makefile
93
+ ## make-runner: also-read <path, relative to the root Makefile's directory>
94
+ ```
95
+
96
+ This is a plain comment — it changes nothing about what a human running
97
+ `make` by hand experiences, and it works regardless of *why* the file was
98
+ orphaned. Prefer it over adding a real `include` here: a real `include`
99
+ would need you to verify the whole orphaned file is safe to textually pull
100
+ into the root Makefile's namespace (no colliding variable/target names, no
101
+ assumption about running from a different `cwd`), which is exactly the
102
+ kind of judgment call this skill should not make silently.
103
+
104
+ **Exception — flag instead of fixing:** before proposing the marker for a
105
+ given orphaned file, grep its recipes for `$(UPPERCASE_VAR)`-style tokens
106
+ and check whether each one is assigned *only* in the root Makefile and not
107
+ in the orphaned file itself (excluding standard built-ins like `$(MAKE)`,
108
+ `$(CC)`, `$(SHELL)`). If you find one, the `also-read` marker would run
109
+ this file without that variable defined — likely breaking or silently
110
+ changing its recipe. For that file only: don't propose an edit, add it to
111
+ **NEEDS HUMAN DECISION** in your final report with the variable name and
112
+ where it's defined. This is a heuristic, not exhaustive — say so.
113
+
114
+ ## Step 4: Find hardcoded interactive docker flags
115
+
116
+ ```bash
117
+ grep -nE '\bdocker (exec|run)\b|\bdocker compose (exec|run)\b|\bdocker-compose (exec|run)\b' Makefile
118
+ ```
119
+
120
+ (Run across every file from Step 1, not just the root.) For every line
121
+ this returns, look for a **short-option token containing `t`** (a single
122
+ `-` followed by one or more letters, e.g. `-it`, `-ti`, `-itu`, `-t`,
123
+ `-dit`) or the long form `--tty` (check separately: `grep -n -- '--tty'
124
+ Makefile`). That's Docker's "allocate a pseudo-TTY" flag. A recipe using
125
+ it only works when `make` itself has a real terminal on its own stdin — a
126
+ human running `make <target>` directly. It fails outright ("the input
127
+ device is not a TTY") or, if `-i`/`--interactive` survives without `-t`,
128
+ can hang waiting on stdin that's never closed — whenever `make` is
129
+ invoked non-interactively: through make-runner-mcp (which always spawns
130
+ `make` with piped, non-tty stdio), through most CI systems, and through
131
+ several coding-agent shell tools.
132
+
133
+ **Before proposing a fix, check whether the file already has a variable
134
+ for this** — grep for an existing `$(shell [ -t 0 ]` / `test -t 0` /
135
+ `tty -s`-style expression already assigned to a variable. If one exists,
136
+ reuse its exact name in the substitutions below instead of introducing a
137
+ second one.
138
+
139
+ If none exists, propose adding this once per file that needs it (near the
140
+ top, after other variable declarations, before the first target — name it
141
+ `DOCKER_IT` unless that collides with an existing variable in the file, in
142
+ which case pick another short, clear name and use it consistently):
143
+
144
+ ```makefile
145
+ # `-t` requires a real TTY to allocate a pseudo-terminal against; without one, docker either
146
+ # errors ("the input device is not a TTY") or hangs. Empty here (no `-it`) whenever stdin isn't a
147
+ # real terminal -- e.g. make-runner-mcp, which always spawns `make` this way, or a non-interactive
148
+ # CI/agent shell. A human running `make <target>` directly still gets identical behavior to before.
149
+ DOCKER_IT := $(shell [ -t 0 ] && echo -it)
150
+ ```
151
+
152
+ Then, for every matched line, replace the interactive-flag token with
153
+ `$(DOCKER_IT)`, unbundling any *other* flags in that same token so they
154
+ stay literal and always applied:
155
+
156
+ ```makefile
157
+ # BEFORE
158
+ some-target:
159
+ docker exec -itu $(shell id -u):$(shell id -g) $(CONTAINER) some-command
160
+
161
+ # AFTER
162
+ some-target:
163
+ docker exec $(DOCKER_IT) -u $(shell id -u):$(shell id -g) $(CONTAINER) some-command
164
+ ```
165
+
166
+ A bare `-it`/`-ti`/`--interactive --tty` (no other bundled letters)
167
+ becomes exactly `$(DOCKER_IT)` with nothing else added. Drop **both** `i`
168
+ and `t` into the variable together, not just `t` — an `-i`-only mode with
169
+ no real tty can still hang on an open, never-closed stdin pipe (exactly
170
+ how make-runner-mcp spawns `make`), so there's no safe reason to keep `-i`
171
+ alone when there's no `-t` to go with it. A target whose entire purpose is
172
+ opening an interactive shell (e.g. a `bash`/`sh` target with no other
173
+ arguments) will still get the same variable — running it through
174
+ make-runner-mcp will simply do nothing useful and time out, which is
175
+ correct: an interactive shell isn't meaningful through an agent either
176
+ way, and this isn't something to solve further here.
177
+
178
+ If this project already has parallel "-ci" or "-noninteractive"-suffixed
179
+ duplicate targets working around this exact problem (check target names
180
+ and comments for it), **do not delete or rename them** — they may be
181
+ referenced elsewhere (CI config, docs, muscle memory). Leave them exactly
182
+ as they are; they become redundant but harmless once the primary targets
183
+ are fixed.
184
+
185
+ ## Step 5: Find and convert `$(MAKECMDGOALS)`-style argument passing
186
+
187
+ ```bash
188
+ grep -n "MAKECMDGOALS" Makefile
189
+ grep -n 'filter-out \$@' Makefile
190
+ grep -n "wordlist" Makefile
191
+ grep -n "^%:" Makefile
192
+ ```
193
+
194
+ (Run across every file from Step 1.) Any hit means at least one target
195
+ relies on bare-word passthrough — make-runner-mcp always rejects a bare
196
+ positional argument outright (it would be treated as an *additional build
197
+ goal*, which could smuggle a second, unvetted target onto the same
198
+ invocation), so a target using this pattern simply can't take arguments
199
+ through the MCP tool at all right now, even though it works fine for a
200
+ human typing `make composer require some/package` directly.
201
+
202
+ For each hit, find which real target's recipe consumes it (read a few
203
+ lines above/below the match) and add it to a list: **targets to
204
+ convert**.
205
+
206
+ A `^%:` hit whose recipe contains `$(MAKE)` with a `-C` flag (forwarding
207
+ any undefined goal to another directory's Makefile) is a *different*,
208
+ already-supported idiom — make-runner-mcp discovers and exposes those
209
+ forwarded targets automatically. Do not add it to **targets to convert**.
210
+ Only a `%:` rule that swallows bare-word `$(MAKECMDGOALS)` arguments
211
+ (typically paired with an `@:` no-op recipe, no `$(MAKE) -C ...` call) is
212
+ the pattern this step converts.
213
+
214
+ For each target on the list:
215
+
216
+ 1. Replace every use of `$(MAKECMDGOALS)`, `$(filter-out $@,$(MAKECMDGOALS))`,
217
+ or similar with a single variable named `ARGS`.
218
+ 2. Rewrite (or write, if missing) its `##` comment: `## <what it does>.
219
+ Pass args via ARGS, e.g. ARGS="<realistic example>"`.
220
+
221
+ ```makefile
222
+ # BEFORE
223
+ composer: ## Run composer
224
+ docker compose exec app composer $(filter-out $@,$(MAKECMDGOALS))
225
+
226
+ # AFTER
227
+ composer: ## Run composer inside the app container. Pass args via ARGS, e.g. ARGS="install symfony/console --no-dev"
228
+ docker compose exec app composer $(ARGS)
229
+ ```
230
+
231
+ Do not rename the target. Do not change what command it runs beyond this
232
+ substitution. If a target also had a hardcoded interactive flag (Step 4),
233
+ apply both fixes to the same recipe line in one edit.
234
+
235
+ After converting every target on the list, check whether the bare `%:`
236
+ catch-all rule (and its `@:` no-op recipe, and any `.PHONY` entry naming
237
+ it) is now dead code — nothing else still using `$(MAKECMDGOALS)`. If so,
238
+ propose removing it too, as part of the same changeset.
239
+
240
+ **Skip if the example value doesn't fit the safe character set:** check
241
+ each `ARGS="..."` example against `^[A-Za-z0-9 _.,+/@:^~=-]*$` (letters,
242
+ digits, spaces, and `` _ . , + / @ : ^ ~ = - ``). If your chosen example
243
+ doesn't match, pick a different, representative one — if nothing
244
+ realistic fits, leave that target unconverted and add it to **NEEDS HUMAN
245
+ DECISION** instead, per `MAKEFILE-GUIDE.md` §8's guidance not to invent a
246
+ workaround (no encoding tricks).
247
+
248
+ ## Step 6: Show the full changeset and get approval before writing anything
249
+
250
+ Assemble every proposed change from Steps 3–5 into one list — file,
251
+ category (linking / TTY / ARGS), and a short before/after snippet for
252
+ each. Show this to the user as a single summary and ask for explicit
253
+ approval before editing any file — e.g. via `AskUserQuestion`, or by
254
+ plainly asking "Apply these N changes?" and waiting for a clear yes. If
255
+ the user approves some categories but not others, apply only what was
256
+ approved. Don't treat your own tool permissions (if file edits happen to
257
+ be pre-approved in this session) as a substitute for this — ask anyway.
258
+
259
+ If there is nothing to fix in any category, say so plainly ("Already
260
+ compatible — nothing to change") and stop; don't invent work.
261
+
262
+ ## Step 7: Apply the approved changes
263
+
264
+ Write exactly what was approved, nothing more.
265
+
266
+ ## Step 8: Verify
267
+
268
+ - **Linking**: re-run Step 2's classification against the now-edited root
269
+ Makefile and confirm every previously-orphaned file (other than ones
270
+ left for human decision) now appears in the reached set.
271
+ - **TTY flags**: run `make -n <target> ...` for at least one fixed target,
272
+ from your own shell. Your own shell is itself non-interactive (this is
273
+ exactly the condition make-runner-mcp always runs under), so the printed
274
+ command should show *no* `-it`/`-t`/`--tty` — confirm that directly in
275
+ the output.
276
+ - **ARGS conversion**: for every target converted in Step 5, run
277
+ `make -n <target> ARGS="<the example value from its comment>"` and
278
+ confirm the printed command substitutes `ARGS` in the right place and
279
+ the target isn't silently doing nothing.
280
+ - **Live check, if available**: if $ARGUMENTS names a local
281
+ make-runner-mcp checkout (a directory containing `server.js`), or
282
+ `command -v make-runner-mcp` resolves to something, run:
283
+ ```bash
284
+ PROJECT_DIR="$(pwd)" node <path-to-that-checkout>/server.js --diagnose
285
+ ```
286
+ Confirm newly-linked targets appear in `EXPOSED TARGETS`, and its
287
+ `UNRESOLVED ... PATHS` / `FILES REJECTED` sections are empty for files
288
+ you just fixed. If unavailable, say so and note the checks above stand
289
+ in for it.
290
+
291
+ Do **not** invoke any real `make <target>` recipe with side effects
292
+ (anything that isn't `-n`/dry-run) to test — that executes real commands,
293
+ which is outside what this skill is for.
294
+
295
+ ## Step 9: Report
296
+
297
+ ```
298
+ MAKEFILES FOUND:
299
+ - <path>: <root | already linked via include/forwarding/also-read | orphaned>
300
+
301
+ LINKING FIXED (also-read marker added):
302
+ - <path>
303
+
304
+ TTY FLAGS FIXED:
305
+ - <target>: <e.g. "docker exec -itu ... -> docker exec $(DOCKER_IT) -u ...">
306
+
307
+ ARGS CONVERTED (MAKECMDGOALS -> ARGS):
308
+ - <target>: <one line: what changed>
309
+
310
+ DEAD CODE REMOVED:
311
+ - <catch-all rule removed, or "none">
312
+
313
+ NEEDS HUMAN DECISION:
314
+ - <target/file>: <exact reason>
315
+
316
+ VERIFIED:
317
+ - <target>: <"dry-run only" | "dry-run + live --diagnose" | "not tested -- reason">
318
+
319
+ NOT TOUCHED (and why):
320
+ - <target>: <e.g. "already uses ARGS and no interactive flags, no change needed">
321
+ ```
322
+
323
+ Also mention, once, that the `also-read` marker and any of these fixes
324
+ only take effect with a make-runner-mcp version that includes them — if
325
+ this project's MCP client config pins an older release tag, the fix won't
326
+ apply until that tag is bumped.
327
+
328
+ ## Guardrails — do not do these, under any circumstance
329
+
330
+ - **Don't rename or restructure a target to dodge the hard-coded
331
+ denylist** (`deploy`, `destroy`, `prod`, `publish`, `release`, `rm-*`).
332
+ That's a separate, higher-judgment concern outside this skill's scope —
333
+ mention it in your report if you notice it, don't act on it.
334
+ - **Don't try to route around the `VAR=value` character restriction** in
335
+ Step 5 — no encoding tricks, no alternate delimiters. If a target's real
336
+ arguments don't fit, leave it as `NEEDS HUMAN DECISION`.
337
+ - **Don't edit `.mcp-make-config.json`** as a substitute for fixing a
338
+ target — it only narrows what's exposed, it doesn't change how a target
339
+ runs or receives arguments.
340
+ - **Don't edit make-runner-mcp's own `server.js`** from inside a target
341
+ project's repo. If something here genuinely needs a change to
342
+ make-runner-mcp itself, report it, don't make it from here.
343
+ - **Don't mark something as tested if you didn't actually run a command
344
+ and see its output.** If you couldn't test (missing tooling, no
345
+ container running, etc.), say so plainly in Step 9 rather than guessing
346
+ that it probably works.
@@ -1,169 +0,0 @@
1
- ---
2
- description: Find every Makefile in the current project, check which ones make-runner-mcp can actually discover (via a real `include`/`-include` directive, the `%:` catch-all forwarding idiom, or a `## make-runner: also-read <path>` marker), and add the missing link automatically for any that are orphaned — instead of just reporting the gap. Only run when explicitly invoked as /fix-makefile-links.
3
- argument-hint: [optional: path to a local make-runner-mcp checkout, to run --diagnose for live verification]
4
- disable-model-invocation: true
5
- allowed-tools: Read Glob Grep Bash Edit AskUserQuestion
6
- ---
7
-
8
- # Fix make-runner-mcp Makefile linking
9
-
10
- Input: $ARGUMENTS (optional path to a make-runner-mcp checkout — see Step 5)
11
-
12
- Goal: make sure every Makefile in this project that defines real targets is
13
- actually reachable from `<project root>/Makefile` — the only file
14
- make-runner-mcp reads — and fix it automatically where it isn't, rather
15
- than just telling the user their Makefile isn't being picked up.
16
-
17
- This handles **linking/discovery only**. It does not touch recipe bodies,
18
- convert `$(MAKECMDGOALS)`-style argument passing, or fix denylist false
19
- positives — those are separate, higher-judgment concerns covered by
20
- make-runner-mcp's own `MAKEFILE-GUIDE.md`; mention it in your final report
21
- if you notice any of those along the way, but don't act on them here.
22
-
23
- ## Step 0: Preconditions
24
-
25
- - Confirm a root `Makefile` exists at the project root (where this was
26
- invoked, or a directory the user named in $ARGUMENTS/the conversation).
27
- If not, stop and say so — make-runner-mcp requires
28
- `<project root>/Makefile` to exist; there's nothing to link yet.
29
- - `git status --porcelain` (if this is a git repo). If dirty, proceed
30
- anyway — the only edit this skill makes is appending comment lines to
31
- the root Makefile — but mention in the final report that the edit will
32
- land alongside whatever else is already uncommitted.
33
-
34
- ## Step 1: Inventory every Makefile in the project
35
-
36
- ```bash
37
- find . -name Makefile \
38
- -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/vendor/*' \
39
- -not -path '*/.venv/*' -not -path '*/venv/*'
40
- ```
41
-
42
- Read every file this finds. Set aside the root one; everything else is a
43
- **candidate secondary Makefile**.
44
-
45
- If this returns only the root Makefile, stop here and report: "Only one
46
- Makefile in this project — nothing to link." Don't invent work.
47
-
48
- ## Step 2: Resolve what the root Makefile already reaches
49
-
50
- Read the root Makefile (and recurse into anything it reaches, same as
51
- below) and classify every line into one of three link mechanisms —
52
- this mirrors exactly what make-runner-mcp's own parser does, so a file
53
- found here is one make-runner-mcp already discovers today:
54
-
55
- 1. **Real include**: a line matching `^\s*(-|s)?include\s+(.+)$`. Each
56
- whitespace-separated path on the line, resolved relative to the
57
- *including* file's own directory. A path containing `$(` can't be
58
- resolved by make-runner-mcp either — note it as **unresolvable
59
- include** (see Step 4) rather than treating it as reachable.
60
- 2. **Catch-all forwarding**: a rule whose target is bare `%:` (not
61
- `%.o:` or `docker-%:` — those are ordinary pattern rules, not this
62
- idiom), whose recipe (the tab-indented lines immediately after it)
63
- contains `$(MAKE) ... -C <dir>`. Reaches `<dir>/Makefile`. A `-C`
64
- value containing `$(` similarly can't be resolved — note it the same
65
- way as an unresolvable include.
66
- 3. **The marker this skill itself manages**: a comment line matching
67
- `^\s*#{1,2}\s*make-runner:\s*also-read\s+(.+)$`. Each
68
- whitespace-separated path, resolved the same way as `include`.
69
-
70
- For every file reached via (1) or (3), recurse into it too — apply the
71
- same three checks to its contents, since an included/linked file can
72
- itself `include` or mark further files. (Forwarding targets, (2), are a
73
- Makefile invoked by a live `$(MAKE)` at build time in a fresh directory
74
- context, not textually processed further here — don't recurse into them
75
- for more forwarding/include chains; treat the forwarded file itself as
76
- reached and stop there, matching what make-runner-mcp's own parser does.)
77
-
78
- Build the **reached set**: the realpath of every file resolved this way,
79
- starting from the root Makefile itself.
80
-
81
- ## Step 3: Compute the gap
82
-
83
- Any candidate secondary Makefile from Step 1 whose realpath is **not** in
84
- the reached set from Step 2 is **orphaned** — make-runner-mcp cannot see
85
- its targets right now, regardless of why (never referenced at all, or
86
- referenced only via an unresolvable `$(VAR)` path).
87
-
88
- If the gap is empty: report "All Makefiles are already correctly linked —
89
- nothing to fix" and stop.
90
-
91
- ## Step 4: Fix each orphaned file
92
-
93
- For each orphaned Makefile, the default and safest fix is to append this
94
- line to the root Makefile (near any existing `include`/`also-read` lines
95
- if present, otherwise near the top, after any leading variable
96
- assignments):
97
-
98
- ```makefile
99
- ## make-runner: also-read <path, relative to the root Makefile's directory>
100
- ```
101
-
102
- This is a plain comment — it changes nothing about what a human running
103
- `make` by hand experiences, and it works regardless of *why* the file was
104
- orphaned. Prefer it over adding a real `include` here: a real `include`
105
- would need you to verify the whole orphaned file is safe to textually pull
106
- into the root Makefile's namespace (no colliding variable/target names,
107
- no assumption about running from a different `cwd`), which is exactly the
108
- kind of judgment call this skill should not make silently.
109
-
110
- **Exception — flag instead of auto-fixing:** before applying the marker to
111
- a given orphaned file, grep its recipes for `$(UPPERCASE_VAR)`-style
112
- tokens and check whether each one is assigned *only* in the root Makefile
113
- and not in the orphaned file itself (excluding standard built-ins like
114
- `$(MAKE)`, `$(CC)`, `$(SHELL)`). If you find one, the `also-read` marker
115
- would run this file without that variable defined — likely breaking or
116
- silently changing its recipe. For that file only: don't edit anything,
117
- add it to a **NEEDS HUMAN DECISION** list in your final report with the
118
- variable name and where it's defined, and suggest either (a) defining that
119
- variable directly in the orphaned file too, or (b) using a real `include`
120
- once a human has verified there's no namespace collision. This is a
121
- heuristic, not exhaustive — say so.
122
-
123
- Apply the marker fix for every other orphaned file in one edit to the root
124
- Makefile (one line per file), then move on.
125
-
126
- ## Step 5: Verify
127
-
128
- Re-run Step 2's classification by hand against the now-edited root
129
- Makefile and confirm every previously-orphaned file (other than ones left
130
- for human decision) now appears in the reached set.
131
-
132
- If $ARGUMENTS names a local make-runner-mcp checkout (a directory
133
- containing `server.js`), or `command -v make-runner-mcp` resolves to
134
- something, run the real thing for a live check instead of trusting your
135
- own by-hand classification alone:
136
-
137
- ```bash
138
- PROJECT_DIR="$(pwd)" node <path-to-that-checkout>/server.js --diagnose
139
- ```
140
-
141
- Check its `EXPOSED TARGETS` section lists targets from the newly-linked
142
- file(s), and its `UNRESOLVED ... PATHS` / `FILES REJECTED` sections are
143
- empty for the files you just fixed. If no checkout is available, say so in
144
- the report and note that the by-hand verification above stands in for it.
145
-
146
- Do **not** invoke any actual `make <target>` to test — that executes real
147
- recipes, which is outside what this skill is for.
148
-
149
- ## Step 6: Report
150
-
151
- State plainly, in this shape:
152
-
153
- ```
154
- MAKEFILES FOUND:
155
- - <path>: <root | already linked via include/forwarding/also-read | orphaned>
156
-
157
- FIXED (also-read marker added to root Makefile):
158
- - <path>
159
-
160
- NEEDS HUMAN DECISION:
161
- - <path>: <reason — e.g. "recipe uses $(FOO), only defined in root Makefile">
162
-
163
- VERIFIED: <"live --diagnose output matches" | "by-hand classification only — no make-runner-mcp checkout available to confirm">
164
- ```
165
-
166
- Also mention, once, that the `also-read` marker only works with a
167
- make-runner-mcp version that supports it — if this project's MCP client
168
- config pins an older release tag, the fix won't take effect until that
169
- tag is bumped.