@infinitedusky/indusk-mcp 0.7.2 → 0.8.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.
@@ -80,6 +80,22 @@ if (event.tool_name === "Edit" && oldContent) {
80
80
  process.exit(0);
81
81
  }
82
82
 
83
+ // Detect workflow type from content frontmatter
84
+ function detectWorkflow(content) {
85
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
86
+ const fm = fmMatch ? fmMatch[1] : "";
87
+ const m = fm.match(/workflow:\s*(bugfix|refactor|feature|spike)/);
88
+ return m ? m[1] : "feature";
89
+ }
90
+
91
+ // Which gate types are required per workflow
92
+ const WORKFLOW_GATES = {
93
+ feature: ["verification", "context", "document"],
94
+ refactor: ["verification", "context", "document"],
95
+ bugfix: ["verification", "document"],
96
+ spike: [],
97
+ };
98
+
83
99
  // Parse phases from the NEW content (after edit) and OLD content (before edit)
84
100
  function parsePhases(content) {
85
101
  // Strip frontmatter
@@ -131,6 +147,8 @@ function parsePhases(content) {
131
147
  return phases;
132
148
  }
133
149
 
150
+ const workflow = detectWorkflow(fullContent);
151
+ const requiredGates = WORKFLOW_GATES[workflow] || WORKFLOW_GATES.feature;
134
152
  const oldPhases = parsePhases(fullContent);
135
153
  const newPhases = parsePhases(newFullContent);
136
154
 
@@ -171,15 +189,19 @@ for (const item of newlyChecked) {
171
189
  for (const phase of oldPhases) {
172
190
  if (phase.number >= item.phase) break;
173
191
 
192
+ const isOverridden = (text) =>
193
+ text.includes("(none needed)") ||
194
+ text.includes("(not applicable)") ||
195
+ text.includes("skip-reason:");
196
+
174
197
  const uncheckedGates = phase.items.filter(
175
- (i) =>
176
- !i.checked && (i.gate === "verification" || i.gate === "context" || i.gate === "document"),
198
+ (i) => !i.checked && !isOverridden(i.text) && requiredGates.includes(i.gate),
177
199
  );
178
200
 
179
201
  if (uncheckedGates.length > 0) {
180
202
  const missing = uncheckedGates.map((i) => ` [${i.gate}] ${i.text}`).join("\n");
181
203
  process.stderr.write(
182
- `Phase ${item.phase} blocked: complete Phase ${phase.number} gates first:\n${missing}\n`,
204
+ `Phase ${item.phase} blocked: complete Phase ${phase.number} gates first:\n${missing}\nTo skip a gate item, mark it with (none needed) or skip-reason: {why}\n`,
183
205
  );
184
206
  process.exit(2);
185
207
  }
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PreToolUse hook: validates that impl phases have all four gate sections.
4
+ *
5
+ * Every phase must have: implementation items, Verification, Context, Document.
6
+ * Sections can opt out with (none needed), (not applicable), or skip-reason: {why}.
7
+ *
8
+ * Exit 0 = allow the edit
9
+ * Exit 2 = block the edit (stderr sent to agent as feedback)
10
+ */
11
+
12
+ import { readFileSync } from "node:fs";
13
+
14
+ // Read hook input from stdin
15
+ let input = "";
16
+ for await (const chunk of process.stdin) {
17
+ input += chunk;
18
+ }
19
+
20
+ const event = JSON.parse(input);
21
+ const toolInput = event.tool_input ?? {};
22
+ const filePath = toolInput.file_path ?? "";
23
+
24
+ // Fast path: not an impl.md file
25
+ if (!filePath.endsWith("/impl.md") && !filePath.endsWith("\\impl.md")) {
26
+ process.exit(0);
27
+ }
28
+
29
+ // Check for skip-gates escape hatch
30
+ const newContent = toolInput.new_string ?? toolInput.content ?? "";
31
+ if (newContent.includes("<!-- skip-gates -->")) {
32
+ process.exit(0);
33
+ }
34
+
35
+ // Determine the full new content after edit
36
+ let newFullContent;
37
+ if (event.tool_name === "Edit" && toolInput.old_string) {
38
+ try {
39
+ const diskContent = readFileSync(filePath, "utf-8");
40
+ newFullContent = diskContent.replace(toolInput.old_string, newContent);
41
+ } catch {
42
+ // File doesn't exist yet — will be created by Write
43
+ newFullContent = newContent;
44
+ }
45
+ } else if (event.tool_name === "Write") {
46
+ newFullContent = toolInput.content ?? "";
47
+ } else {
48
+ process.exit(0);
49
+ }
50
+
51
+ // Only validate if this edit is adding/modifying phase structure
52
+ // Check if the edit contains phase headers
53
+ const editContent = toolInput.new_string ?? toolInput.content ?? "";
54
+ const hasPhaseHeader = /###\s+Phase\s+\d+/.test(editContent);
55
+ const hasChecklistItem = /- \[ \]/.test(editContent);
56
+
57
+ // If the edit doesn't touch phase structure, allow it
58
+ if (!hasPhaseHeader && !hasChecklistItem) {
59
+ process.exit(0);
60
+ }
61
+
62
+ // Parse frontmatter to detect workflow type
63
+ const fmMatch = newFullContent.match(/^---\n([\s\S]*?)\n---\n/);
64
+ const frontmatter = fmMatch ? fmMatch[1] : "";
65
+ const body = fmMatch ? newFullContent.slice(fmMatch[0].length) : newFullContent;
66
+
67
+ // Detect workflow type from frontmatter (workflow: bugfix|refactor|feature)
68
+ // or infer from plan structure
69
+ const workflowMatch = frontmatter.match(/workflow:\s*(bugfix|refactor|feature|spike)/);
70
+ const workflow = workflowMatch ? workflowMatch[1] : "feature";
71
+
72
+ // Different workflows have different requirements
73
+ const requirements = {
74
+ feature: { verification: true, context: true, document: true },
75
+ refactor: { verification: true, context: true, document: true },
76
+ bugfix: { verification: true, context: false, document: true },
77
+ spike: { verification: false, context: false, document: false },
78
+ }[workflow];
79
+ const lines = body.split("\n");
80
+
81
+ const phases = [];
82
+ let currentPhase = null;
83
+ let currentSection = "implementation";
84
+
85
+ for (const line of lines) {
86
+ const phaseMatch = line.match(/^###\s+Phase\s+(\d+)[:\s]+(.*)/);
87
+ if (phaseMatch) {
88
+ if (currentPhase) phases.push(currentPhase);
89
+ currentPhase = {
90
+ number: parseInt(phaseMatch[1], 10),
91
+ name: phaseMatch[2].trim(),
92
+ hasImplementation: false,
93
+ hasVerification: false,
94
+ hasContext: false,
95
+ hasDocument: false,
96
+ };
97
+ currentSection = "implementation";
98
+ continue;
99
+ }
100
+
101
+ if (!currentPhase) continue;
102
+
103
+ // Detect gate section headers
104
+ const verMatch = line.match(/^####\s+Phase\s+\d+\s+Verification\b/);
105
+ if (verMatch) {
106
+ currentPhase.hasVerification = true;
107
+ currentSection = "verification";
108
+ continue;
109
+ }
110
+
111
+ const ctxMatch = line.match(/^####\s+Phase\s+\d+\s+Context\b/);
112
+ if (ctxMatch) {
113
+ currentPhase.hasContext = true;
114
+ currentSection = "context";
115
+ continue;
116
+ }
117
+
118
+ const docMatch = line.match(/^####\s+Phase\s+\d+\s+Document\b/);
119
+ if (docMatch) {
120
+ currentPhase.hasDocument = true;
121
+ currentSection = "document";
122
+ continue;
123
+ }
124
+
125
+ // Check for implementation items
126
+ if (currentSection === "implementation" && /^-\s+\[[ x]\]/.test(line)) {
127
+ currentPhase.hasImplementation = true;
128
+ }
129
+
130
+ // Forward intelligence doesn't count as a gate
131
+ if (line.match(/^####\s+Phase\s+\d+\s+Forward Intelligence\b/)) {
132
+ currentSection = "fi";
133
+ }
134
+ }
135
+ if (currentPhase) phases.push(currentPhase);
136
+
137
+ // Check for opt-out content in gate sections
138
+ // Re-scan to check if sections that exist have (none needed) or skip-reason:
139
+ const isOptedOut = (text) =>
140
+ text.includes("(none needed)") ||
141
+ text.includes("(not applicable)") ||
142
+ text.includes("skip-reason:");
143
+
144
+ // Validate each phase
145
+ const errors = [];
146
+ for (const phase of phases) {
147
+ if (!phase.hasImplementation) continue; // Skip phases with no impl items (might be a header-only outline)
148
+
149
+ const missing = [];
150
+ if (requirements.verification && !phase.hasVerification) missing.push("Verification");
151
+ if (requirements.context && !phase.hasContext) missing.push("Context");
152
+ if (requirements.document && !phase.hasDocument) missing.push("Document");
153
+
154
+ if (missing.length > 0) {
155
+ errors.push(`Phase ${phase.number} (${phase.name}) is missing: ${missing.join(", ")}`);
156
+ }
157
+ }
158
+
159
+ if (errors.length > 0) {
160
+ const msg = errors.join("\n");
161
+ const reqNames = Object.entries(requirements)
162
+ .filter(([, v]) => v)
163
+ .map(([k]) => k.charAt(0).toUpperCase() + k.slice(1));
164
+ process.stderr.write(
165
+ `Impl structure incomplete (workflow: ${workflow}):\n${msg}\n\nThis workflow requires: ${reqNames.join(", ")} sections per phase.\nIf a section isn't needed, add it with (none needed) or skip-reason: {why}\nExample: #### Phase 1 Document\\n(none needed)\nTo change requirements, add 'workflow: bugfix' to the impl frontmatter.\n`,
166
+ );
167
+ process.exit(2);
168
+ }
169
+
170
+ process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infinitedusky/indusk-mcp",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "InDusk development system — skills, MCP tools, and CLI for structured AI-assisted development",
5
5
  "type": "module",
6
6
  "files": [
@@ -97,6 +97,35 @@ flowchart TD
97
97
 
98
98
  **Never** use bare ` ```mermaid ` blocks without the wrapper. The diagrams are often too small to read inline, and the FullscreenDiagram gives users zoom and pan controls.
99
99
 
100
+ ## Documentation by Workflow Type
101
+
102
+ The document gate's expectations vary by workflow type. The hook enforces that the section exists, but what goes in it depends on what you're building.
103
+
104
+ ### Feature (full documentation)
105
+
106
+ A feature is new functionality. It needs complete documentation:
107
+ - Write new reference pages for tools, APIs, or configuration added
108
+ - Write new guide pages for workflows introduced
109
+ - Create Mermaid diagrams for architecture, flows, and relationships
110
+ - Update existing pages that reference the area you changed
111
+ - Create FullscreenDiagram-wrapped diagrams for anything structural
112
+
113
+ ### Refactor (update existing docs)
114
+
115
+ A refactor restructures existing code. Documentation should reflect the new structure:
116
+ - Update existing pages that reference moved/renamed code
117
+ - Update diagrams that show the old structure
118
+ - Don't create new pages unless the refactor introduces new concepts
119
+ - If nothing user-facing changed, mark with `(none needed)`
120
+
121
+ ### Bugfix (document the fix)
122
+
123
+ A bugfix solves a specific problem. Documentation is about the fix, not new content:
124
+ - Update any docs that described the broken behavior
125
+ - If the bug revealed a gotcha, add it to the relevant reference page
126
+ - If the fix changes a public API or configuration, update that reference page
127
+ - For internal-only fixes, mark with `(none needed)`
128
+
100
129
  ## Shaping Impl Documents
101
130
 
102
131
  When writing an impl (via the plan skill), every phase should consider documentation:
@@ -106,7 +135,7 @@ When writing an impl (via the plan skill), every phase should consider documenta
106
135
  - [ ] {Specific docs page to write or update}
107
136
  ```
108
137
 
109
- The agent writing the impl must answer: **"What does a user or developer need to know about what this phase built?"** If the answer is "nothing user-facing" — no document items needed. Not every phase produces docs. But the question must be asked.
138
+ The agent writing the impl must answer: **"What does a user or developer need to know about what this phase built?"** If the answer is "nothing user-facing" — `(none needed)` or `skip-reason:` to opt out. Not every phase produces docs. But the question must be asked, and the section must exist (enforced by hook).
110
139
 
111
140
  ### Document Items Are Blocking
112
141
 
package/skills/plan.md CHANGED
@@ -49,7 +49,14 @@ Workflow templates are in `templates/workflows/` in the package. They describe w
49
49
 
50
50
  2. **Figure out where things stand.** If a plan folder already exists, read what's there. Check frontmatter statuses. The next document to write is the first one that's missing or incomplete.
51
51
 
52
- 3. **If starting fresh**, create the plan folder and start with the first document for the workflow type:
52
+ 3. **If starting fresh**, do a quick scan of the project (read CLAUDE.md, check the code graph) to understand the context. Then **ask the user discovery questions before doing any research or writing any documents.** The goal is to understand what they're trying to achieve, not just what they named the plan. Good discovery questions:
53
+ - "What problem are you trying to solve?" or "What should this feature do for your users?"
54
+ - "Is there anything specific you've already thought through or have strong opinions about?"
55
+ - "Are there any constraints I should know about — timeline, technology preferences, things to avoid?"
56
+
57
+ For non-developers especially, this conversation is critical. They may not know the right technical terms, but they know what they want. Draw that out before proceeding.
58
+
59
+ Once you understand the intent, create the plan folder and start with the first document for the workflow type:
53
60
  - **feature**: start with research
54
61
  - **bugfix**: start with brief (streamlined template)
55
62
  - **refactor**: start with brief (includes boundary map)
@@ -62,7 +69,7 @@ Workflow templates are in `templates/workflows/` in the package. They describe w
62
69
  - Include the graph findings in research.md — concrete numbers like "X has 12 dependents across 3 apps"
63
70
  Document what you find. The research doc records findings and analysis, but saves the recommendation for the brief.
64
71
 
65
- 4. **If research is done**, write the brief. This is where a direction emerges from the research. The brief proposes what we're building and why, informed by what the research uncovered. Present for review.
72
+ 4. **If research is done**, write the brief. This is where a direction emerges from the research. The brief proposes what we're building and why, informed by what the research uncovered. **Present the brief and have a conversation about it.** Don't just ask "does this look good?" — walk the user through it: "Here's what I'm proposing we build. Does this match what you had in mind? Is there anything missing, or anything here you don't want?" Iterate until the user is genuinely happy with the direction, then mark it as `accepted`.
66
73
 
67
74
  5. **If brief is accepted** and the workflow includes an ADR (feature only), write the ADR. The ADR formalizes the decisions that were discussed during research and led to the brief. It records what was chosen, what was rejected, and why. **After the ADR is accepted**, add a one-liner to CLAUDE.md's Key Decisions section per the context skill: `- {decision summary} — see planning/{plan}/adr.md`
68
75