mcp-phase-gate 2.0.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 ADDED
@@ -0,0 +1,497 @@
1
+ # 🧠 MCP Orquestador
2
+
3
+ *Read this in [Spanish](README.es.md) · This is the English README.*
4
+
5
+ An MCP (Model Context Protocol) server that splits every incoming requirement into **five gated phases**, and forces each one to leave a written document behind before the next can start. Your client's panel will show it as `mcp-phase-gate`.
6
+
7
+ > The server's interface is in English (tool names, parameters). The **phase prompts that drive the AI's behaviour are written in Spanish**, so the documents it produces come out in Spanish. If you want them in another language, translate `prompts/*.md` — no code changes needed.
8
+
9
+ ## The problem
10
+
11
+ You ask an AI assistant to implement a requirement and it does what they all do: it starts writing code. It breaks something it never read. It quietly makes decisions it never asked you about. And when you switch chats, or sit down at your other machine, the entire reasoning is gone — because it lived in the conversation, and **conversations don't travel**.
12
+
13
+ ## How it solves it
14
+
15
+ The assistant doesn't get to touch code until phase 4. By then it has diagnosed the problem by reading the repository, asked you what it couldn't decide alone, and written a plan checked against the real code. Every phase leaves a document in a central folder, so the thinking outlives both the chat and the device.
16
+
17
+ ```mermaid
18
+ flowchart TD
19
+ R([An email, a ticket, a requirement]) --> F1
20
+ F1["<b>1 · Discovery</b><br/>reads the code and diagnoses"]
21
+ F2["<b>2 · Decisions</b><br/>asks, validates, and stops"]
22
+ F3["<b>3 · Technical Plan</b><br/>turns decisions into exact code"]
23
+ F4["<b>4 · Execution</b><br/>implements and verifies by running"]
24
+ F5["<b>5 · Audit</b><br/>reviews as an outside reviewer"]
25
+
26
+ F1 -- "01 · Technical Analysis" --> F2
27
+ F2 -- "02 · Decisions" --> F3
28
+ F3 -- "03 · Technical Plan" --> F4
29
+ F4 -- "04 · Execution" --> F5
30
+ F5 --> PR([Audit and PR description, in the chat])
31
+ ```
32
+
33
+ **The mandatory stop is phase 2**: it halts there and will not move on until you answer. This isn't a polite suggestion to ask questions — the phase does not close without your answers, and whatever you decide gets written down with its rationale.
34
+
35
+ And between one phase and the next there is a **gate**, which is what gives the server its name: ask for a phase while skipping the previous one and the server notices and warns you ([see below](#-the-gate-skipping-a-phase-stops-being-invisible)).
36
+
37
+ In phase 5 the AI only reads. **You always make the commits and open the PR.**
38
+
39
+ ## Who it's for
40
+
41
+ For people working on **code that already exists and must not break**, who need a record of why each thing was done. It fits especially well if you work from more than one machine, or if you have to justify decisions weeks later.
42
+
43
+ It is not for throwaway prototypes or greenfield projects: five phases to change a colour is absurd ceremony.
44
+
45
+ It speaks standard MCP, so it works with any compatible client. It is **tested on Cursor, VS Code and Claude Code** — see [Installation](#-installation).
46
+
47
+ ---
48
+
49
+ ## 📁 How the documentation is organised
50
+
51
+ Every task lives in a folder with this fixed structure:
52
+
53
+ ```
54
+ DOCUMENTATION/
55
+ ├── PROJECT-A/
56
+ │ └── Proyectos/
57
+ │ └── <task_name>/
58
+ │ ├── 00 - Contexto Inicial.md ← written by start_task
59
+ │ ├── 01 - Análisis Técnico.md ← Phase 1
60
+ │ ├── 02 - Decisiones.md ← Phase 2
61
+ │ ├── 03 - Plan Técnico.md ← Phase 3
62
+ │ ├── 04 - Ejecución.md ← Phase 4
63
+ │ └── 05 - Auditoría.md ← Phase 5
64
+ ├── PROJECT-B/
65
+ │ └── Proyectos/
66
+ │ └── <task_name>/
67
+ └── PROJECT-C/
68
+ └── Proyectos/
69
+ └── <task_name>/
70
+ ```
71
+
72
+ The `Proyectos` level is the tasks subfolder, and its name is configurable — see `ORQUESTADOR_TASKS_SUBDIR` below. The document names come from the phase prompts, which is why they are in Spanish by default.
73
+
74
+ > The server builds this path **automatically**. You (or the LLM) only supply `project`, `task_name` and `file_name`; nobody has to type a full path.
75
+
76
+ ---
77
+
78
+ ## 💻 Installation
79
+
80
+ Two routes, same destination:
81
+
82
+ - **With `npx`** — nothing to clone or build, and it updates itself. Recommended if you just want to use it.
83
+ - **From source** — if you're going to edit the prompts in place, or contribute.
84
+
85
+ The code is identical everywhere; the only thing that changes between machines is **the paths to your folders**, because the system user and where you keep your documentation differ. That's why they are environment variables and not constants in the code.
86
+
87
+ ### Prerequisites
88
+ - **Node.js 22 or newer.** Check with `node -v`. That's what `package.json` declares and the only thing CI tests (22 and 24); earlier versions are out of support.
89
+ - A folder for your central documentation, available locally. If it lives in a synced folder (OneDrive, Google Drive, Dropbox, iCloud) it travels between your machines on its own — that's the recommended setup, but not a requirement: the server only ever receives a local path and doesn't know what's behind it.
90
+ - **An MCP client**: Cursor, VS Code or Claude Code.
91
+ - **Git**, only for the from-source route.
92
+
93
+ ---
94
+
95
+ ### 🚀 Route A — with `npx` (recommended)
96
+
97
+ Nothing to install: `npx` fetches the package the first time and caches it. Everything is configured in your MCP client's file.
98
+
99
+ **The key you give it is what identifies the server and prefixes its tools**, so you can name it whatever you like. The examples use `mcp-phase-gate`, the same as the npm package and the name the server reports for itself — one name in all three places, so there's nothing to remember.
100
+
101
+ Remember that in JSON every `\` must be doubled as `\\`.
102
+
103
+ <details open>
104
+ <summary><b>Cursor</b> — <code>~/.cursor/mcp.json</code> (create it if missing)</summary>
105
+
106
+ ```json
107
+ {
108
+ "mcpServers": {
109
+ "mcp-phase-gate": {
110
+ "command": "npx",
111
+ "args": ["-y", "mcp-phase-gate"],
112
+ "env": {
113
+ "ORQUESTADOR_DOCS_PATH": "C:\\Users\\YOUR_USER\\OneDrive - YOUR ORGANISATION\\Documents\\DOCUMENTATION",
114
+ "ORQUESTADOR_REPOS_PATH": "C:\\projects"
115
+ }
116
+ }
117
+ }
118
+ }
119
+ ```
120
+ </details>
121
+
122
+ <details>
123
+ <summary><b>VS Code</b> — <code>%APPDATA%\Code\User\mcp.json</code></summary>
124
+
125
+ Two differences to watch: the top-level key is `servers`, not `mcpServers`, and `type` must be declared.
126
+
127
+ ```json
128
+ {
129
+ "servers": {
130
+ "mcp-phase-gate": {
131
+ "type": "stdio",
132
+ "command": "npx",
133
+ "args": ["-y", "mcp-phase-gate"],
134
+ "env": {
135
+ "ORQUESTADOR_DOCS_PATH": "C:\\Users\\YOUR_USER\\OneDrive - YOUR ORGANISATION\\Documents\\DOCUMENTATION",
136
+ "ORQUESTADOR_REPOS_PATH": "C:\\projects"
137
+ }
138
+ }
139
+ }
140
+ }
141
+ ```
142
+ </details>
143
+
144
+ <details>
145
+ <summary><b>Claude Code</b> — one line</summary>
146
+
147
+ ```bash
148
+ claude mcp add mcp-phase-gate --env ORQUESTADOR_DOCS_PATH="C:\Users\YOUR_USER\Documents\DOCUMENTATION" --env ORQUESTADOR_REPOS_PATH="C:\projects" -- npx -y mcp-phase-gate
149
+ ```
150
+
151
+ Or by hand, in a `.mcp.json` at the project root, shaped like the Cursor one.
152
+ </details>
153
+
154
+ That `ORQUESTADOR_DOCS_PATH` is only an **example** using a OneDrive folder. `G:\My Drive\Documentation`, `D:\Dropbox\Docs` or plain `C:\Docs` with no cloud at all work just as well.
155
+
156
+ > **There is no `.env` on this route.** The package lives inside `node_modules`, which is no place to keep your configuration, so the paths go in the `"env"` block — that's the only option here. The `.env` file only exists on the route below.
157
+
158
+ **Both variables are required and have no default value.** If one is missing, or points at a folder that doesn't exist, the tools that need it say so by name instead of failing later for some unrelated-looking reason.
159
+
160
+ > Typing `npx mcp-phase-gate` straight into a terminal **looks like it hangs**, and nothing is broken: it's an MCP server waiting for messages on stdin. If you just want to check it's there, run `npx mcp-phase-gate --help`.
161
+
162
+ ---
163
+
164
+ ### 🔧 Route B — from source
165
+
166
+ Only needed if you're going to edit the repo's prompts directly, or contribute. You do *not* need it to add prompts of your own: that's done by layering folders, [see below](#-the-phase-prompts-live-in-prompts).
167
+
168
+ **1. Clone and build.** `build/` is **not** committed, so it has to be generated on every machine:
169
+
170
+ ```bash
171
+ git clone https://github.com/Montse2308/mcp-phase-gate.git
172
+ cd mcp-phase-gate
173
+ npm install
174
+ npm run build
175
+ ```
176
+
177
+ **2. Create your `.env`** with THIS machine's paths (never committed):
178
+
179
+ ```bash
180
+ cp .env.example .env
181
+ ```
182
+
183
+ Single backslashes on Windows, no quotes:
184
+
185
+ ```ini
186
+ ORQUESTADOR_DOCS_PATH=C:\Users\YOUR_USER\OneDrive - YOUR ORGANISATION\Documents\DOCUMENTATION
187
+ ORQUESTADOR_REPOS_PATH=C:\projects
188
+ ```
189
+
190
+ > On Windows, save `.env` as **UTF-8 without BOM**. Creating it from PowerShell with `>` produces UTF-16, accented characters arrive mangled, and the path "doesn't exist" even though it looks right.
191
+
192
+ **3. Register the server** as in route A, but swapping `command` and `args` for your clone's `build/index.js`, and dropping the `"env"` block (the paths come from `.env` now):
193
+
194
+ ```json
195
+ "command": "node",
196
+ "args": ["C:\\<WHERE_YOU_CLONED>\\mcp-phase-gate\\build\\index.js"]
197
+ ```
198
+
199
+ In **Claude Code**, a `.mcp.json` at this repo's root can use a **relative** path (`"args": ["build/index.js"]`), since it launches the server from that folder. It's the only one of the three configs that works unchanged on any machine.
200
+
201
+ > If a variable is defined both in `.env` and in the client's `"env"` block, the client's wins.
202
+
203
+ ---
204
+
205
+ ### ✅ Verify it works
206
+ 1. Restart the client, or toggle the server off and on in its MCP panel.
207
+ 2. It should show up active with its 8 tools. In **Cursor** that's Settings → MCP; in **VS Code**, the extensions/MCP view; in **Claude Code**, `/mcp`.
208
+ 3. Try it from the chat: *"Use the `get_active_task` tool from mcp-phase-gate"* — it should answer, even if only to say there's no active task yet.
209
+
210
+ ---
211
+
212
+ ## 🔄 Working across several machines
213
+
214
+ - If your documentation lives in a synced folder, it travels on its own. The only per-machine file is `.env` (Step 3), which is local and never committed.
215
+ - The server remembers the **active task of each project** (see `get_active_task`). It's one pointer per project, not a single global one: you can have one window on one project and another on a different one without them clobbering each other. That record is **local to each machine** and lives in your user data folder, outside the repo, so it survives deleting `build/` or re-cloning:
216
+ - **Windows:** `%APPDATA%\mcp-phase-gate\active-tasks.json`
217
+ - **macOS:** `~/Library/Application Support/mcp-phase-gate/active-tasks.json`
218
+ - **Linux:** `~/.local/state/mcp-phase-gate/active-tasks.json`
219
+
220
+ If you switch machines or chats and the LLM "forgot" where to write, tell it to call `get_active_task`, or just give it `project` + `task_name`.
221
+ - **The phase isn't stored — it's derived.** The server looks at which documents the task folder already has and works out the phase from there, because each phase declares its own document's name. That's why the answer can never drift: delete `02 - Decisiones.md` because it came out wrong and the task drops back to Phase 2 on its own.
222
+ - And if the pointer file is lost — new machine, or you deleted it — `get_active_task` doesn't go silent: it proposes the most recently touched task and **tells you it's a guess**, so you can confirm before writing.
223
+ - **Golden rule:** if you use a synced folder, let the sync finish before starting work on the other machine, to avoid conflicted copies.
224
+
225
+ ---
226
+
227
+ ## 🗂️ More than one documentation folder? (optional)
228
+
229
+ **This is not part of the flow. If one folder is enough for you, skip this section.**
230
+
231
+ You may want separate documentation sets that never mix — one for work and one for personal projects, say, each somewhere different, or one in the cloud and one not. Nothing new is needed: **register the server more than once** in your client, with a different `env` block each time.
232
+
233
+ ```jsonc
234
+ {
235
+ "mcpServers": {
236
+ "orquestador-work": {
237
+ "command": "npx",
238
+ "args": ["-y", "mcp-phase-gate"],
239
+ "env": {
240
+ "ORQUESTADOR_DOCS_PATH": "C:\\Users\\YOUR_USER\\OneDrive - YOUR ORGANISATION\\Documents\\DOCUMENTATION",
241
+ "ORQUESTADOR_REPOS_PATH": "C:\\projects",
242
+ "ORQUESTADOR_STATE_PATH": "C:\\Users\\YOUR_USER\\.orquestador\\work"
243
+ }
244
+ },
245
+ "orquestador-personal": {
246
+ "command": "npx",
247
+ "args": ["-y", "mcp-phase-gate"],
248
+ "env": {
249
+ "ORQUESTADOR_DOCS_PATH": "G:\\My Drive\\Documentation",
250
+ "ORQUESTADOR_REPOS_PATH": "D:\\dev",
251
+ "ORQUESTADOR_TASKS_SUBDIR": "",
252
+ "ORQUESTADOR_STATE_PATH": "C:\\Users\\YOUR_USER\\.orquestador\\personal"
253
+ }
254
+ }
255
+ }
256
+ }
257
+ ```
258
+
259
+ It's the same package in both — only the paths differ. Each registration can even have its own tasks subfolder; above, the personal one leaves it empty so tasks hang directly off the project.
260
+
261
+ > ⚠️ **`ORQUESTADOR_STATE_PATH` must differ between registrations.** Share it and the active-task pointers clobber each other **silently**: the state is keyed by project name, so two documentation roots holding a project with the same name end up sharing one entry, and one answers for the other. No error is raised — you just get the wrong task back.
262
+
263
+ Two more things that bite and aren't obvious:
264
+
265
+ - **Mirror mode, not streaming.** Google Drive in *streaming* mode and OneDrive with *Files On-Demand* leave files as placeholders until something opens them. This server lives on listing folders and checking whether files exist, so placeholders make it slow, or make it fail with no network. Keep the documentation folder **downloaded locally** if you can.
266
+ - **Keep code repos out of the synced folder.** Documentation in the cloud is fine. Code isn't: sync competing with git over `.git/` corrupts repositories. Point `ORQUESTADOR_REPOS_PATH` at a local disk.
267
+
268
+ ---
269
+
270
+ ## 🧩 What lives OUTSIDE the repo (one copy per machine)
271
+
272
+ `git pull` does **not** update these four things. If something stopped working right after pulling, the cause is almost always here.
273
+
274
+ | What | Where | Why it isn't in the repo |
275
+ |------|-------|--------------------------|
276
+ | The compiled server | `build/` in your clone | It's gitignored. **After every `git pull` that touches `src/`, run `npm run build`** or the client keeps executing the previous version. |
277
+ | This machine's paths | `.env` at the root (Step 3) | The system user and the documentation folder differ per machine. |
278
+ | The MCP registration | Your client's config (Step 4) | In Cursor and VS Code it holds the absolute path to `build/index.js`, which depends on where you cloned and points into a gitignored folder. No single path can be right on every machine. See [`docs/decisiones.md`](docs/decisiones.md). The exception is Claude Code's `.mcp.json`, which uses a relative path. |
279
+ | The `/f1`–`/f5` commands in **Cursor** | `~/.cursor/commands/f<N>.md` | Cursor only reads them from the user folder, so they're copied by hand on each machine. Not an issue in **Claude Code**: they're versioned in `.claude/commands/` and arrive with the pull. |
280
+
281
+ ### The phase commands
282
+
283
+ You invoke them by typing `/f1`, `/f2`, etc. in the chat. **They are orchestration only**: which tools to call and in what order. The behaviour, the document's structure and its filename all come from the phase prompt in `prompts/`, so tuning a phase doesn't mean editing its command.
284
+
285
+ | Command | Calls | Document it leaves |
286
+ |---------|-------|--------------------|
287
+ | `/f1` | `start_task` + `get_phase_prompt(1)` | Phase 1's |
288
+ | `/f2` | `get_phase_prompt(2)`, reads phase 1's | Phase 2's, after you answer the questions |
289
+ | `/f3` | `get_phase_prompt(3)`, reads the previous ones | Phase 3's |
290
+ | `/f4` | `get_phase_prompt(4)`, reads the plan | Phase 4's, short and at the end |
291
+ | `/f5` | `get_phase_prompt(5)` | Phase 5's: the audit and the PR description, which also go in the chat |
292
+
293
+ No command names a file: the name is declared in each phase prompt's header and `get_phase_prompt` hands it to the model.
294
+
295
+ `/f1` and `/f5` expect data after the command: `/f1` needs project, task_name and the full requirement; `/f5`, which commits to audit. The other four start from `get_active_task`.
296
+
297
+ **Where to put them, per client:**
298
+
299
+ - **Claude Code:** already in [`.claude/commands/`](.claude/commands), versioned. They work when you open this repo, no setup. To use them from your other projects, copy them to `~/.claude/commands/`.
300
+ - **Cursor:** copy them to `~/.cursor/commands/`. From there they work across all your projects.
301
+ - **VS Code:** no slash commands of its own; paste the file contents into the chat.
302
+
303
+ The exact contents of all five are also in [`docs/comandos-de-fase.md`](docs/comandos-de-fase.md) (Spanish).
304
+
305
+ ---
306
+
307
+ ## 🚦 The gate: skipping a phase stops being invisible
308
+
309
+ Ask for a phase ahead of the one you're on and the prompt arrives with a warning on top:
310
+
311
+ ```
312
+ ## ATENCIÓN — COMPUERTA DE FASE
313
+
314
+ Pediste la Fase 4, pero la tarea "login-sso" no tiene los documentos de fases anteriores:
315
+
316
+ - Fase 3: falta `03 - Plan Técnico.md`
317
+
318
+ Esta tarea va en la Fase 3 (Plan Técnico). NO hagas el trabajo de la Fase 4 sobre
319
+ documentos que no existen ni te los inventes a partir del contexto del chat.
320
+ ```
321
+
322
+ **It warns, it doesn't block.** The prompt is still delivered, so the day you want to skip a phase deliberately on a trivial change, you can. What changed is that it no longer goes unnoticed.
323
+
324
+ Three things it deliberately does **not** do:
325
+
326
+ - **It doesn't get in the way of going back.** On Phase 5 and asking for Phase 2 to fix decisions isn't skipping anything, and it stays quiet. It only looks for gaps behind you.
327
+ - **It doesn't make you type anything extra.** You still type `/f4` and nothing else. Working out which task you mean is the server's job, using the pointer it already keeps — not the model asking you.
328
+ - **It doesn't go quiet when it can't check.** If several projects have an active task and none was named, it says so instead of pretending everything is in order.
329
+
330
+ ---
331
+
332
+ ## 🛠️ Available tools
333
+
334
+ | Tool | What it does |
335
+ |------|--------------|
336
+ | `start_task` | Creates the folder for a **new** task (`<PROJECT>/<tasks subfolder>/<task>`), saves the initial context and makes it the **active task of its project**. If the task already exists it refuses and points you at `switch_task`, so the initial context is never overwritten. |
337
+ | `get_active_task` | Returns the active task and **which phase it's on**, derived from the documents it already has. Without `project` it answers for every project. Use it when opening a new chat or after losing track. |
338
+ | `list_tasks` | Lists the tasks that exist and each one's phase, most recently touched first. For picking up something old or seeing what was left half-done. |
339
+ | `switch_task` | Changes which task is active for a project. **Touches no files**: it's the correct way to resume an existing task. |
340
+ | `get_phase_prompt` | Returns the behaviour rules for a phase (1–5), with the global rules prepended and the exact document filenames appended. The text lives in `prompts/`. It also checks the **gate**: pass `project` and, if you skipped a phase, the prompt arrives with the warning on top. |
341
+ | `read_central_doc` | Reads a file from the central documentation. **Recommended:** pass `project` + `task_name` + `file_name` and let the server build the path. It also accepts a relative `file_path`. |
342
+ | `write_central_doc` | Writes/overwrites a file. Same parameters as `read_central_doc` plus `content`. |
343
+ | `read_cross_repo` | Reads files from your other local repos, so you can check how another codebase solved something without switching windows. |
344
+
345
+ ### How the server builds paths
346
+ - **Documentation:** `ORQUESTADOR_DOCS_PATH` / `<PROJECT>` / `ORQUESTADOR_TASKS_SUBDIR` / `<task>` / `<file>`
347
+ - **Code repos:** `ORQUESTADOR_REPOS_PATH` / `<repo>` / `<file>`
348
+
349
+ ### What counts as a project
350
+
351
+ There is no list of projects to maintain. **A project is any folder that contains the tasks subfolder**, so registering a new one just means creating that subfolder. Folders without it — credentials, shared material, dead archives — stay out on their own.
352
+
353
+ A project that doesn't exist is rejected, showing the list of the ones that do, instead of silently creating a stray folder. To **start** a brand new project you have to ask on purpose: `start_task` accepts `crear_proyecto: true`, and without that flag a typo can't pollute your documentation.
354
+
355
+ > If you leave `ORQUESTADOR_TASKS_SUBDIR` empty, the rule can't be applied and **any** folder counts as a project.
356
+
357
+ ---
358
+
359
+ ## 📝 The phase prompts live in `prompts/`
360
+
361
+ Each phase's text is a plain markdown file, not code:
362
+
363
+ ```
364
+ prompts/
365
+ ├── global-rules.md ← "Zero Breakage" rules, prepended to EVERY phase
366
+ ├── fase-1-descubrimiento.md
367
+ ├── fase-2-decisiones.md
368
+ ├── fase-3-plan-tecnico.md
369
+ ├── fase-4-ejecucion.md
370
+ └── fase-5-auditoria-pr.md ← includes the PR description contract
371
+ ```
372
+
373
+ **They are read on every call.** Edit a `.md` and the next `get_phase_prompt` already returns the new version — no `npm run build`, no client restart. That's what makes it practical to tune a phase and test it immediately.
374
+
375
+ Each phase declares, in a header at the top of the file, what its document is called:
376
+
377
+ ```markdown
378
+ ---
379
+ documento: 02 - Decisiones.md
380
+ ---
381
+
382
+ # Decisiones
383
+ ```
384
+
385
+ `get_phase_prompt` reads that header and appends, at the end of the prompt, the exact filename for this phase and for the previous ones. **That's why the `/f1`–`/f5` commands no longer name any file:** the name lives in one place, so changing a prompt doesn't force you to edit a command on every machine too. A phase without `documento` — like phase 5, which delivers in the chat — announces itself as such.
386
+
387
+ Phase 1 also declares `documento_inicial`, the file `start_task` writes with the initial context.
388
+
389
+ Format rules:
390
+ - The file must be named `fase-<N>-<anything>.md` (`phase-<N>-...` also works).
391
+ - The header is optional and never leaks into the prompt: it's stripped before delivery.
392
+ - **The number of phases is not hardcoded:** add `fase-6-deployment.md` and phase 6 exists immediately.
393
+ - The first `# Heading` in the file becomes the name shown in the tool description.
394
+ - `global-rules.md` is automatically prepended to the phase content.
395
+
396
+ ### Using your own prompts without losing the packaged ones
397
+
398
+ `ORQUESTADOR_PROMPTS_PATH` points at a folder of yours that is **layered on top of** the packaged one. It does not replace it.
399
+
400
+ To change only phase 2, your folder holds **one file**:
401
+
402
+ ```
403
+ my-prompts/
404
+ └── fase-2-my-way.md
405
+ ```
406
+
407
+ Phase 2 is now yours and phases 1, 3, 4 and 5 stay as the packaged ones — including whatever improvements new versions bring. This used to be a full replacement: changing one phase meant copying all six, ending up with frozen copies, and any phase you forgot to copy **disappeared with no warning**.
408
+
409
+ Three details that matter:
410
+
411
+ - **Layering is by phase number, not by filename.** Your `fase-2-my-way.md` overrides the package's `fase-2-decisiones.md`. You don't have to guess the original's name.
412
+ - **It also adds.** A `fase-6-deployment.md` in your folder creates phase 6, nothing else needed.
413
+ - **Your `global-rules.md` replaces the packaged one, it doesn't stack.** Concatenating two rule sets makes it impossible to tell which file an unwanted instruction came from, exactly when you need to know. If you want the packaged rules plus extras, copy them and add.
414
+
415
+ On the `npx` route this is the **only** way to customise: the packaged prompts live inside `node_modules`, where editing them achieves nothing because the next install wipes them.
416
+
417
+ ### The PR description contract (phase 5)
418
+
419
+ Phase 5 doesn't only audit: it drafts the PR description under a strict contract, so the result is consistent regardless of which AI model is used.
420
+
421
+ - **Title:** `type(area): sentence`, and nothing else — no ticket numbers.
422
+ - **Fixed structure:** the why (`Problem` if something was broken, `Context` if it's new) → grouped `Changes` → closing sections.
423
+ - **Closing sections are conditional**, each with its own trigger: `Scope` only if you touched existing code, `Verification` only if the testing was substantial, `Deployment notes` only if there are migrations.
424
+ - **How bullets are grouped depends on the shape of the change** (by component, by sub-feature, by data dimension…), with a decision table in the prompt. The description adapts to the size of the ticket without becoming unpredictable.
425
+ - **Length limits in words**, not adjectives: trivial (60–100), small (120–160), normal (200–260), large (350–450, hard cap 500).
426
+ - **Delivered inside a four-backtick code block**, so copy-pasting into GitHub gives you raw markdown instead of already-rendered text.
427
+
428
+ To adapt it to your own style, edit `prompts/fase-5-auditoria-pr.md`. The highest-leverage change is replacing the examples under "EJEMPLOS DE REFERENCIA" with PR descriptions of your own: models imitate that voice far more than they follow instructions.
429
+
430
+ > The reasoning behind each rule is in [`docs/decisiones.md`](docs/decisiones.md) (Spanish). Read it before changing things: several rules that look arbitrary are solving a concrete problem.
431
+
432
+ ---
433
+
434
+ ## ⚙️ Environment variables
435
+
436
+ Loaded from the `.env` at the repo root, or from the `"env"` block in your client config.
437
+
438
+ The first two are **required and have no default**: they point at folders that only exist on your machine, so any default would be somebody else's path. If one is missing, or points at something that doesn't exist or isn't a folder, the tools that use it return the variable name and where to configure it. Tools that don't depend on it — like `get_phase_prompt` — keep working.
439
+
440
+ | Variable | Default | Description |
441
+ |----------|---------|-------------|
442
+ | `ORQUESTADOR_DOCS_PATH` | **none, required** | Path to the root of your central documentation. **Different on each machine.** |
443
+ | `ORQUESTADOR_REPOS_PATH` | **none, required** | Path to the folder holding your code repositories. |
444
+ | `ORQUESTADOR_TASKS_SUBDIR` | `Proyectos` | Subfolder where tasks live inside each project. Empty means tasks hang directly off the project. It's also the rule that decides what counts as a project (see above). |
445
+ | `ORQUESTADOR_PROMPTS_PATH` | none | Folder holding your own prompts. They are **layered on top of** the packaged ones rather than replacing them, so you only drop in the phases you want to change ([see above](#using-your-own-prompts-without-losing-the-packaged-ones)). |
446
+ | `ORQUESTADOR_STATE_PATH` | Your user data folder (see above) | Where the active-task pointers are stored. Rarely needs touching; useful to isolate state in tests, and **required if you register the server more than once** (see [More than one documentation folder?](#-more-than-one-documentation-folder-optional)). |
447
+
448
+ ---
449
+
450
+ ## 🧑‍💻 Development (if you change the code)
451
+
452
+ > **Note:** this applies to **code** changes only. If all you edited was a prompt under `prompts/*.md`, there is **nothing to build and nothing to restart** — they're read on every call.
453
+
454
+ The source lives in `src/`, split by responsibility:
455
+
456
+ | File | What it handles |
457
+ |------|-----------------|
458
+ | `config.ts` | Environment paths, where state lives, server identity |
459
+ | `paths.ts` | How every path is built, and the check that it stays inside its base |
460
+ | `phases.ts` | Discovering the phases and assembling each one's prompt |
461
+ | `tasks.ts` | Which tasks exist, which phase each is on, and which one is active |
462
+ | `index.ts` | The server's tools. It computes nothing: it translates between the protocol and the above |
463
+
464
+ The client runs the compiled `build/index.js`, so after any change:
465
+
466
+ ```bash
467
+ npm run build
468
+ ```
469
+
470
+ Then restart the MCP server in your client so it picks up the new version.
471
+
472
+ Available scripts:
473
+ - `npm run build` → compiles TypeScript into `build/`.
474
+ - `npm test` → compiles the tests and runs them with Node's test runner (needs **Node 22+**).
475
+ - `npm start` → runs the compiled server.
476
+ - `npm run dev` → runs straight from TypeScript with `ts-node`.
477
+
478
+ Tests live in `test/` and need no extra dependency: they use `node:test`, which ships with
479
+ Node. They build a throwaway central-documentation tree in a temp folder, so they never touch
480
+ yours. CI runs them on every PR.
481
+
482
+ ### Where everything is
483
+
484
+ | What you're looking for | Where |
485
+ |-------------------------|-------|
486
+ | What the project does today | This README (and its Spanish twin, [`README.es.md`](README.es.md) — **change one, change the other**) |
487
+ | Why it's designed this way | [`docs/decisiones.md`](docs/decisiones.md) (Spanish) |
488
+ | The `/f1`–`/f5` commands, to recreate them elsewhere | [`docs/comandos-de-fase.md`](docs/comandos-de-fase.md) (Spanish) |
489
+ | That a change broke nothing | [`test/`](test), via `npm test`. CI runs them on every PR |
490
+ | What's still missing | [Issues](https://github.com/Montse2308/mcp-phase-gate/issues) — grouped under the `v1-uso-propio` and `v2-publico` labels |
491
+ | What's already been done | The commit history and merged PRs |
492
+
493
+ ---
494
+
495
+ ## 📄 License
496
+
497
+ [MIT](LICENSE).
@@ -0,0 +1,17 @@
1
+ export declare const MODULE_DIR: string;
2
+ export type RutaRequerida = {
3
+ variable: string;
4
+ apuntaA: string;
5
+ };
6
+ export declare const DOCS: RutaRequerida;
7
+ export declare const REPOS: RutaRequerida;
8
+ export declare function requireBasePath({ variable, apuntaA }: RutaRequerida): string;
9
+ export declare function tasksSubdir(): string;
10
+ export declare function defaultPromptsPath(): string;
11
+ export declare function promptsLayers(): string[];
12
+ export declare const APP_DIR_NAME = "mcp-phase-gate";
13
+ export declare function stateDir(): string;
14
+ export declare function stateFile(): string;
15
+ export declare const SERVER_NAME = "mcp-phase-gate";
16
+ export declare const SERVER_VERSION: string;
17
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,136 @@
1
+ // Configuración del servidor: rutas del entorno, ubicación del estado e identidad.
2
+ //
3
+ // Todo lo que sale del entorno se lee en cada llamada, no una vez al importar el módulo.
4
+ // Es lo que ya hacía `requireBasePath` y aquí se extiende al resto: un valor capturado al
5
+ // arrancar obliga a reiniciar el cliente para cambiarlo, y vuelve imposible probar una
6
+ // función con otra configuración sin recargar el módulo entero.
7
+ import * as fs from "fs";
8
+ import * as os from "os";
9
+ import * as path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import dotenv from "dotenv";
12
+ // Carpeta de este módulo. Sirve para localizar el .env y el package.json de la raíz del repo.
13
+ export const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
14
+ // Carga el archivo .env de la raíz del repo (un nivel arriba de build/ o src/).
15
+ // Así cada dispositivo define sus propias rutas sin tocar el mcp.json.
16
+ // quiet: true evita que dotenv v17 imprima su banner en stdout, que es el canal del JSON-RPC.
17
+ dotenv.config({ path: path.join(MODULE_DIR, "..", ".env"), quiet: true });
18
+ export const DOCS = {
19
+ variable: "ORQUESTADOR_DOCS_PATH",
20
+ apuntaA: "la carpeta raíz de la Documentación Central",
21
+ };
22
+ export const REPOS = {
23
+ variable: "ORQUESTADOR_REPOS_PATH",
24
+ apuntaA: "la carpeta que contiene tus repositorios de código",
25
+ };
26
+ // Se resuelve y valida en cada llamada, no una vez al arrancar. Así el servidor se queda en
27
+ // pie aunque falte configuración, las tools que no dependen de estas rutas siguen sirviendo,
28
+ // y el error llega al chat —donde está el usuario— en vez de quedarse enterrado en un log.
29
+ export function requireBasePath({ variable, apuntaA }) {
30
+ const valor = process.env[variable]?.trim();
31
+ if (!valor) {
32
+ throw new Error(`Falta la variable ${variable}, que debe apuntar a ${apuntaA}. Defínela en el archivo ` +
33
+ `.env de la raíz del repo, o en el bloque "env" del mcp.json de tu cliente.`);
34
+ }
35
+ const ruta = path.resolve(valor);
36
+ // Una variable definida pero incorrecta falla igual de tarde que una ausente, así que la
37
+ // existencia se comprueba aquí. El caso del encoding ya mordió una vez y no es adivinable.
38
+ if (!fs.existsSync(ruta)) {
39
+ throw new Error(`${variable} apunta a "${ruta}", que no existe en este equipo. Corrige la ruta en el .env. ` +
40
+ `Si lleva acentos, revisa que el .env esté guardado en UTF-8 sin BOM: en UTF-16 los ` +
41
+ `acentos llegan rotos y la carpeta parece no existir.`);
42
+ }
43
+ if (!fs.statSync(ruta).isDirectory()) {
44
+ throw new Error(`${variable} apunta a "${ruta}", que es un archivo y no una carpeta.`);
45
+ }
46
+ return ruta;
47
+ }
48
+ // Subcarpeta donde viven las tareas dentro de cada proyecto (ej. PROYECTO-A/Proyectos/<tarea>).
49
+ // Es una convención de organización, no una ruta de dispositivo, pero vive en el entorno
50
+ // para no tener que introducir un archivo de configuración por una sola cadena.
51
+ // Vacía a propósito significa que las tareas cuelgan directo del proyecto.
52
+ export function tasksSubdir() {
53
+ return process.env.ORQUESTADOR_TASKS_SUBDIR?.trim() ?? "Proyectos";
54
+ }
55
+ // ─── Prompts, en capas ──────────────────────────────────────────────────────
56
+ // Los prompts de las fases viven en archivos .md sueltos. Vivir fuera del código permite
57
+ // afinar el texto de una fase sin recompilar: se edita el .md y la siguiente llamada a
58
+ // get_phase_prompt ya trae la versión nueva.
59
+ //
60
+ // Se cargan en CAPAS, no por reemplazo: primero los del paquete y encima, si existe, la
61
+ // carpeta que declare el usuario. La última capa que declare una fase es la que queda.
62
+ //
63
+ // Antes `ORQUESTADOR_PROMPTS_PATH` sustituía la carpeta entera, y eso solo funcionaba con un
64
+ // clon del repo alrededor, donde editar `prompts/` es lo natural. Instalado desde npm los de
65
+ // fábrica caen dentro de node_modules, donde editarlos no sirve porque la siguiente
66
+ // instalación se los lleva. Reemplazar obligaba entonces a copiar las cinco fases para
67
+ // cambiar una: te quedas con copias congeladas que ya no reciben mejoras y, si se te olvida
68
+ // copiar una, esa fase simplemente deja de existir sin que nada te avise.
69
+ export function defaultPromptsPath() {
70
+ return path.join(MODULE_DIR, "..", "prompts");
71
+ }
72
+ // De menor a mayor prioridad. La del usuario va al final: gana.
73
+ export function promptsLayers() {
74
+ const capas = [defaultPromptsPath()];
75
+ const propia = process.env.ORQUESTADOR_PROMPTS_PATH?.trim();
76
+ if (propia)
77
+ capas.push(path.resolve(propia));
78
+ return capas;
79
+ }
80
+ // ─── Estado local ───────────────────────────────────────────────────────────
81
+ // El estado vive en la carpeta de datos del usuario, NO junto al build. `build/` es una
82
+ // carpeta generada: borrarla para recompilar desde cero se llevaba el estado por delante.
83
+ // Además, esta ubicación es la única que funciona cuando el servidor se ejecuta desde un
84
+ // paquete instalado, sin repo alrededor.
85
+ // Sigue al nombre del paquete: esta ruta aparece escrita en el README, así que dejarla con el
86
+ // nombre viejo obligaba a explicar por qué la carpeta se llama distinto que todo lo demás.
87
+ // El precio de renombrarla es que los punteros guardados con el nombre anterior dejan de verse.
88
+ // No se migran: el puntero se recalcula solo con un `switch_task`, y arrastrar código de
89
+ // migración para un dato que cuesta un comando en recuperarse es más caro que el problema.
90
+ export const APP_DIR_NAME = "mcp-phase-gate";
91
+ export function stateDir() {
92
+ if (process.env.ORQUESTADOR_STATE_PATH) {
93
+ return process.env.ORQUESTADOR_STATE_PATH;
94
+ }
95
+ const home = os.homedir();
96
+ if (process.platform === "win32") {
97
+ const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
98
+ return path.join(appData, APP_DIR_NAME);
99
+ }
100
+ if (process.platform === "darwin") {
101
+ return path.join(home, "Library", "Application Support", APP_DIR_NAME);
102
+ }
103
+ const stateHome = process.env.XDG_STATE_HOME || path.join(home, ".local", "state");
104
+ return path.join(stateHome, APP_DIR_NAME);
105
+ }
106
+ // El nombre es plural porque el archivo guarda un puntero por proyecto, no una sola tarea.
107
+ // El singular `active_task.json` fue el formato anterior y ya no se lee: ver `limpiarEstadoViejo`.
108
+ export function stateFile() {
109
+ return path.join(stateDir(), "active-tasks.json");
110
+ }
111
+ // ─── Identidad del servidor ─────────────────────────────────────────────────
112
+ // Es lo que el cliente muestra en su panel de MCP. Se llamaba "cursor-mcp-orchestrator",
113
+ // que era doblemente equivocado: el servidor habla MCP estándar y funciona igual en VS Code
114
+ // o Claude Code, donde leer "cursor-" desorienta; y "orchestrator" no dice qué orquesta.
115
+ // "phase-gate" es el término de ingeniería de procesos para avanzar por fases sin poder
116
+ // saltarse la anterior, que es exactamente lo que hace este flujo.
117
+ //
118
+ // Lleva el prefijo "mcp-" para ser exactamente el nombre del paquete en npm y el del repo:
119
+ // tres nombres iguales en vez de tres parecidos, que es lo que obligaba a recordar cuál era
120
+ // cuál. Redundante dentro de un servidor MCP, sí, pero el paquete lo necesita para que se
121
+ // encuentre buscando "mcp" y ganó la igualdad.
122
+ //
123
+ // Ojo: esto es metadato. Quien identifica al servidor y da el prefijo a las tools es la clave
124
+ // del archivo de configuración del cliente, así que renombrar aquí no rompe ningún registro.
125
+ export const SERVER_NAME = "mcp-phase-gate";
126
+ // La versión sale del package.json para no tener el mismo número en dos sitios.
127
+ export const SERVER_VERSION = (() => {
128
+ try {
129
+ const pkg = JSON.parse(fs.readFileSync(path.join(MODULE_DIR, "..", "package.json"), "utf-8"));
130
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
131
+ }
132
+ catch {
133
+ return "0.0.0";
134
+ }
135
+ })();
136
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map