@infinitedusky/indusk-mcp 1.10.0 → 1.10.2

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.
@@ -22,6 +22,23 @@ export interface InduskConfig {
22
22
  */
23
23
  groupId?: string;
24
24
  };
25
+ otel?: {
26
+ /**
27
+ * Project's relationship to OpenTelemetry. Controls whether the OTel gate
28
+ * fires when the planner writes impl phases and when the validate-impl-structure
29
+ * / check-gates hooks evaluate them.
30
+ *
31
+ * - `service`: produces telemetry I want to collect (default behavior; gate fires)
32
+ * - `library`: ships to other people, never produces telemetry (gate silent)
33
+ * - `tool`: short-lived script, telemetry overhead exceeds value (gate silent)
34
+ * - `none`: explicit opt-out for legacy/prototype/internal experiments (gate silent)
35
+ *
36
+ * **If unset, behaves as `service`** (gate fires). This preserves backwards
37
+ * compatibility — existing projects without the field continue to get the OTel
38
+ * gate enforced. Opt-out is explicit; opt-in is implicit.
39
+ */
40
+ role?: "service" | "library" | "tool" | "none";
41
+ };
25
42
  }
26
43
  export declare function getConfigPath(projectRoot: string): string;
27
44
  export declare function readConfig(projectRoot: string): InduskConfig | null;
@@ -38,3 +55,18 @@ export declare function getPlanningDir(projectRoot: string): string;
38
55
  * searching Graphiti — this gives both project-scoped and cross-project knowledge.
39
56
  */
40
57
  export declare function getProjectGroupId(projectRoot: string): string;
58
+ /**
59
+ * Whether the OTel gate should fire for this project.
60
+ *
61
+ * Returns `true` if `.indusk/config.json` is missing, missing `otel.role`, or
62
+ * has `otel.role: "service"`. Returns `false` only when the project explicitly
63
+ * opts out via `otel.role: "library" | "tool" | "none"`.
64
+ *
65
+ * Used by:
66
+ * - planner skill (whether to write `#### Phase N OTel` sections into impl.md)
67
+ * - validate-impl-structure hook (whether to require an OTel section at write time)
68
+ * - check-gates hook (whether to block phase advancement on missing OTel)
69
+ *
70
+ * Backwards compatible: projects without the new field behave exactly as before.
71
+ */
72
+ export declare function shouldEmitOtelGate(projectRoot: string): boolean;
@@ -42,3 +42,22 @@ export function getProjectGroupId(projectRoot) {
42
42
  return config.graphiti.groupId;
43
43
  return basename(projectRoot);
44
44
  }
45
+ /**
46
+ * Whether the OTel gate should fire for this project.
47
+ *
48
+ * Returns `true` if `.indusk/config.json` is missing, missing `otel.role`, or
49
+ * has `otel.role: "service"`. Returns `false` only when the project explicitly
50
+ * opts out via `otel.role: "library" | "tool" | "none"`.
51
+ *
52
+ * Used by:
53
+ * - planner skill (whether to write `#### Phase N OTel` sections into impl.md)
54
+ * - validate-impl-structure hook (whether to require an OTel section at write time)
55
+ * - check-gates hook (whether to block phase advancement on missing OTel)
56
+ *
57
+ * Backwards compatible: projects without the new field behave exactly as before.
58
+ */
59
+ export function shouldEmitOtelGate(projectRoot) {
60
+ const config = readConfig(projectRoot);
61
+ const role = config?.otel?.role;
62
+ return role === undefined || role === "service";
63
+ }
@@ -65,14 +65,22 @@ The MCP server provides 23 tools. The key ones for debugging:
65
65
  - **`getFailedChecks`** / **`getFailedCheckDetails`** — View check failures
66
66
  - **`searchKnowledgeBase`** — Search Dash0 documentation
67
67
 
68
- ### MCP Usage Pattern
68
+ ### MCP Usage Pattern — Traces First
69
+
70
+ **Always start with spans.** Logs are correlated with traces, so finding the right trace first helps you find the right logs — not the other way around.
71
+
69
72
  ```
70
- 1. getLogRecordsfind the log summary, get log record IDs
71
- 2. getFullLogRecorddrill into a specific log for all attributes
72
- 3. If log has traceId getTraceDetails to see the full request chain
73
- 4. getServiceCatalogsee all services, error rates, dependencies
73
+ 1. getSpans (last 30s, no filters) scan recent spans for anything matching your investigation
74
+ 2. Found a relevant span? getTraceDetails to see the full request chain
75
+ 3. Use trace/span IDs to find correlated logs getLogRecords with traceId filter
76
+ 4. getFullLogRecorddrill into a specific log for all attributes
74
77
  ```
75
78
 
79
+ **Why traces first:**
80
+ - Spans show the full request lifecycle — what happened, how long, what called what
81
+ - Logs are attached to spans via trace context — the trace tells you which logs matter
82
+ - Starting with logs means guessing at filters; starting with spans gives you the map
83
+
76
84
  ## When to Use MCP vs CLI
77
85
 
78
86
  | Task | Use |
@@ -83,7 +91,7 @@ The MCP server provides 23 tools. The key ones for debugging:
83
91
  | Get full trace hierarchy | MCP `getTraceDetails` (preferred) |
84
92
  | Run PromQL metrics query | Either — MCP for simple, CLI for complex |
85
93
  | Deploy dashboard from YAML | CLI (`dash0 apply -f`) |
86
- | Diagnose a failing service | MCP `getServiceCatalog` → `getLogRecords` → `getFullLogRecord` |
94
+ | Diagnose a failing service | MCP `getSpans` (last 30s) → `getTraceDetails` → `getLogRecords` (by traceId) |
87
95
  | CI/CD observability checks | CLI (scriptable, agent mode) |
88
96
  | Discover available attributes | MCP `getAttributeKeys` / `getAttributeValues` |
89
97
 
@@ -178,14 +186,16 @@ The CLI auto-detects Claude Code sessions and switches to agent mode (JSON outpu
178
186
 
179
187
  ### During verification
180
188
  When a verification step involves checking that a service is working correctly, don't just check the HTTP status — query Dash0 for:
181
- - Recent error logs for the service: `dash0 --experimental logs query --from now-15m --filter "service.name is game-server" --filter "otel.log.severity.range is_one_of ERROR WARN"`
182
- - Log entries matching the feature you modified
189
+ - **Recent spans first**: use MCP `getSpans` with a short time range (last 30s) to see what the service just did look for errors, slow spans, or missing expected operations
190
+ - **Then correlated logs**: once you have a trace ID from a relevant span, use `getLogRecords` filtered by that trace ID to see exactly what happened during that request
191
+ - **Only use broad log queries as a fallback** if no spans are present (e.g., the service isn't instrumented yet)
183
192
 
184
193
  ### During debugging
185
194
  When something fails:
186
- 1. Check logs first — `dash0 --experimental logs query --from now-1h --filter "otel.log.severity.range is_one_of ERROR WARN" -o csv`
187
- 2. Find the trace — look for trace IDs in log entries, then `dash0 --experimental traces get <trace-id>`
188
- 3. Check metricsis this a new problem or an existing one? Compare error rates over time.
195
+ 1. **Get recent spans first**use MCP `getSpans` with a short time range (last 30s–1m). Don't filter yet scan the results for spans matching what you're investigating.
196
+ 2. **Drill into the trace**found a relevant span? Use `getTraceDetails` to see the full request chain, timing, errors, and hierarchy.
197
+ 3. **Find correlated logs** use the trace ID from step 2 to query `getLogRecords` with a `traceId` filter. This gives you exactly the logs tied to that request, no guessing.
198
+ 4. **Check metrics** — is this a new problem or an existing one? Compare error rates over time.
189
199
 
190
200
  ### During retrospective
191
201
  Query Dash0 for metrics that show the impact of the plan:
@@ -201,5 +211,5 @@ Dash0 ingests standard OpenTelemetry data. If your services already export OTLP
201
211
 
202
212
  - **CLI `--experimental` required**: All query commands (logs, traces, metrics) require the `--experimental` flag. This will change when these commands become stable.
203
213
  - **Default time range is narrow**: Always specify `--from` when querying logs. Without it, you may get empty results.
204
- - **No trace search**: The CLI can only fetch traces by ID (`traces get <id>`), not search for them. Find trace IDs in log entries first.
214
+ - **CLI has no trace search**: The CLI can only fetch traces by ID (`traces get <id>`), not search for them. Use the MCP `getSpans` tool to search spans — it supports filters and time ranges. The CLI requires you to already have a trace ID.
205
215
  - **Profile auth may not load in Claude Code shell**: Run `source ~/.zshrc` before `dash0` commands if auth fails.
@@ -2,11 +2,16 @@
2
2
  /**
3
3
  * PreToolUse hook: blocks phase transitions in impl.md when gates are incomplete.
4
4
  *
5
+ * The OTel gate is conditional on the project's `otel.role` in .indusk/config.json:
6
+ * - unset or "service": OTel gate is enforced (default)
7
+ * - "library" / "tool" / "none": OTel gate is silenced (mirrors validate-impl-structure)
8
+ *
5
9
  * Exit 0 = allow the edit
6
10
  * Exit 2 = block the edit (stderr sent to agent as feedback)
7
11
  */
8
12
 
9
- import { readFileSync } from "node:fs";
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { resolve } from "node:path";
10
15
 
11
16
  // Read hook input from stdin
12
17
  let input = "";
@@ -115,14 +120,57 @@ function detectWorkflow(content) {
115
120
  return m ? m[1] : "feature";
116
121
  }
117
122
 
118
- // Which gate types are required per workflow
119
- const WORKFLOW_GATES = {
123
+ /**
124
+ * Find the project root by walking up from a starting directory looking for
125
+ * a .indusk/ or .claude/ directory. Falls back to startDir if none found.
126
+ */
127
+ function findProjectRoot(startDir) {
128
+ let dir = startDir;
129
+ for (let i = 0; i < 10; i++) {
130
+ if (existsSync(`${dir}/.indusk`) || existsSync(`${dir}/.claude`)) return dir;
131
+ const parent = resolve(dir, "..");
132
+ if (parent === dir) break;
133
+ dir = parent;
134
+ }
135
+ return startDir;
136
+ }
137
+
138
+ /**
139
+ * Whether the OTel gate should fire for this project. Reads .indusk/config.json
140
+ * and checks otel.role. Returns true if missing/unset/"service", false for
141
+ * library/tool/none. Mirrors shouldEmitOtelGate() in apps/indusk-mcp/src/lib/config.ts.
142
+ */
143
+ function shouldEmitOtelGate(projectRoot) {
144
+ const configPath = `${projectRoot}/.indusk/config.json`;
145
+ if (!existsSync(configPath)) return true;
146
+ try {
147
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
148
+ const role = config?.otel?.role;
149
+ return role === undefined || role === "service";
150
+ } catch {
151
+ return true;
152
+ }
153
+ }
154
+
155
+ const projectRoot = findProjectRoot(event.cwd ?? process.cwd());
156
+ const otelGateEnabled = shouldEmitOtelGate(projectRoot);
157
+
158
+ // Which gate types are required per workflow.
159
+ // OTel is filtered out below when the project opts out via otel.role.
160
+ const WORKFLOW_GATES_BASE = {
120
161
  feature: ["verification", "otel", "context", "document"],
121
162
  refactor: ["verification", "otel", "context", "document"],
122
163
  bugfix: ["verification", "document"],
123
164
  spike: [],
124
165
  };
125
166
 
167
+ const WORKFLOW_GATES = Object.fromEntries(
168
+ Object.entries(WORKFLOW_GATES_BASE).map(([wf, gates]) => [
169
+ wf,
170
+ otelGateEnabled ? gates : gates.filter((g) => g !== "otel"),
171
+ ]),
172
+ );
173
+
126
174
  // Parse phases from the NEW content (after edit) and OLD content (before edit)
127
175
  function parsePhases(content) {
128
176
  // Strip frontmatter
@@ -2,14 +2,20 @@
2
2
  /**
3
3
  * PreToolUse hook: validates that impl phases have all four gate sections.
4
4
  *
5
- * Every phase must have: implementation items, Verification, OTel, Context, Document.
5
+ * Every phase must have: implementation items, Verification, OTel*, Context, Document.
6
6
  * Sections can opt out with (none needed), (not applicable), or skip-reason: {why}.
7
7
  *
8
+ * *OTel section is conditional on the project's `otel.role` in .indusk/config.json:
9
+ * - unset or "service": OTel section is required (default behavior)
10
+ * - "library" / "tool" / "none": OTel section is NOT required (gate silenced)
11
+ * This mirrors `shouldEmitOtelGate()` in apps/indusk-mcp/src/lib/config.ts.
12
+ *
8
13
  * Exit 0 = allow the edit
9
14
  * Exit 2 = block the edit (stderr sent to agent as feedback)
10
15
  */
11
16
 
12
- import { readFileSync } from "node:fs";
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { resolve } from "node:path";
13
19
 
14
20
  // Read hook input from stdin
15
21
  let input = "";
@@ -26,6 +32,43 @@ if (!filePath.endsWith("/impl.md") && !filePath.endsWith("\\impl.md")) {
26
32
  process.exit(0);
27
33
  }
28
34
 
35
+ /**
36
+ * Find the project root by walking up from a starting directory looking for
37
+ * a .indusk/ or .claude/ directory. Falls back to event.cwd if none found.
38
+ * Mirrors the pattern used in check-catchup.js.
39
+ */
40
+ function findProjectRoot(startDir) {
41
+ let dir = startDir;
42
+ for (let i = 0; i < 10; i++) {
43
+ if (existsSync(`${dir}/.indusk`) || existsSync(`${dir}/.claude`)) return dir;
44
+ const parent = resolve(dir, "..");
45
+ if (parent === dir) break;
46
+ dir = parent;
47
+ }
48
+ return startDir;
49
+ }
50
+
51
+ /**
52
+ * Whether the OTel gate should fire for this project. Reads .indusk/config.json
53
+ * and checks otel.role. Returns true if the config is missing, if otel.role is
54
+ * unset, or if otel.role is "service" — matches shouldEmitOtelGate() in
55
+ * apps/indusk-mcp/src/lib/config.ts exactly.
56
+ */
57
+ function shouldEmitOtelGate(projectRoot) {
58
+ const configPath = `${projectRoot}/.indusk/config.json`;
59
+ if (!existsSync(configPath)) return true;
60
+ try {
61
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
62
+ const role = config?.otel?.role;
63
+ return role === undefined || role === "service";
64
+ } catch {
65
+ return true; // on parse error, preserve default behavior
66
+ }
67
+ }
68
+
69
+ const projectRoot = findProjectRoot(event.cwd ?? process.cwd());
70
+ const otelGateEnabled = shouldEmitOtelGate(projectRoot);
71
+
29
72
  // Check for skip-gates escape hatch
30
73
  const newContent = toolInput.new_string ?? toolInput.content ?? "";
31
74
 
@@ -100,10 +143,13 @@ const body = fmMatch ? newFullContent.slice(fmMatch[0].length) : newFullContent;
100
143
  const workflowMatch = frontmatter.match(/workflow:\s*(bugfix|refactor|feature|spike)/);
101
144
  const workflow = workflowMatch ? workflowMatch[1] : "feature";
102
145
 
103
- // Different workflows have different requirements
146
+ // Different workflows have different requirements.
147
+ // OTel is further gated on the project's `otel.role` in .indusk/config.json —
148
+ // libraries/tools/none opt out of the OTel gate entirely. Workflows that normally
149
+ // require OTel (feature, refactor) will only require it when otelGateEnabled is true.
104
150
  const requirements = {
105
- feature: { verification: true, otel: true, context: true, document: true },
106
- refactor: { verification: true, otel: true, context: true, document: true },
151
+ feature: { verification: true, otel: otelGateEnabled, context: true, document: true },
152
+ refactor: { verification: true, otel: otelGateEnabled, context: true, document: true },
107
153
  bugfix: { verification: true, otel: false, context: false, document: true },
108
154
  spike: { verification: false, otel: false, context: false, document: false },
109
155
  }[workflow];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infinitedusky/indusk-mcp",
3
- "version": "1.10.0",
3
+ "version": "1.10.2",
4
4
  "description": "InDusk development system — skills, MCP tools, and CLI for structured AI-assisted development",
5
5
  "type": "module",
6
6
  "files": [
package/skills/planner.md CHANGED
@@ -108,6 +108,8 @@ Workflow templates are in `templates/workflows/` in the package. They describe w
108
108
 
109
109
  Default is `ask`. See the work skill "Gate Override Policy" for full details on what each mode enforces at execution time.
110
110
 
111
+ **OTel gate is conditional on `otel.role`.** Read `.indusk/config.json` for the project's `otel.role` field (or use the `shouldEmitOtelGate(projectRoot)` helper from `apps/indusk-mcp/src/lib/config.ts`). The OTel gate fires for projects whose `otel.role` is unset or `"service"` — these are user-facing apps that produce telemetry you want to collect. **Do NOT write `#### Phase N OTel` sections** for projects whose `otel.role` is `"library"`, `"tool"`, or `"none"` — these are libraries, CLIs, or scripts that should never emit telemetry and writing OTel gates for them is friction without value. The `validate-impl-structure` and `check-gates` hooks apply the same rule. The other gates (verify, context, document) always apply regardless of `otel.role`.
112
+
111
113
  7. **If impl is completed** (all items checked off by `/work`), invoke the retrospective skill (`/retrospective {plan-name}`). This handles the structured audit (docs, tests, quality, context), knowledge handoff to the docs site, and archival. Do not write a freeform retrospective — use the skill. (Bugfix and refactor workflows may skip retrospective for small changes — user's call.)
112
114
 
113
115
  8. **Always present each document for review** before moving to the next stage. The user signs off on each step.
@@ -280,6 +282,8 @@ For multi-phase impls, include a boundary map showing what each phase produces a
280
282
  function withdrawFor(wallet: address, player: address, amount: uint256, historyHash: bytes32)
281
283
  ```
282
284
 
285
+ {OPTIONAL: #### Phase 1 OTel — include ONLY if the project's `otel.role` in `.indusk/config.json` is unset or `"service"`. Skip the entire OTel block for projects with `otel.role: "library" | "tool" | "none"`. Use `shouldEmitOtelGate(projectRoot)` from `apps/indusk-mcp/src/lib/config.ts` to decide.}
286
+
283
287
  #### Phase 1 OTel
284
288
  - [ ] {Instrumentation check — are new code paths observable? See the OTel skill for patterns. Example items: "New endpoints have manual spans with `otel.category` and domain attributes", "Errors recorded with `recordException` + `setStatus(ERROR)` + trace-correlated log". Ask: "did this phase add endpoints, business logic, state transitions, or error paths?" If not, this section can be opted out per gate policy.}
285
289