@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.
- package/dist/lib/config.d.ts +32 -0
- package/dist/lib/config.js +19 -0
- package/extensions/dash0/skill.md +22 -12
- package/hooks/check-gates.js +51 -3
- package/hooks/validate-impl-structure.js +51 -5
- package/package.json +1 -1
- package/skills/planner.md +4 -0
package/dist/lib/config.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/config.js
CHANGED
|
@@ -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.
|
|
71
|
-
2.
|
|
72
|
-
3.
|
|
73
|
-
4.
|
|
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. getFullLogRecord → drill 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 `
|
|
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
|
|
182
|
-
-
|
|
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.
|
|
187
|
-
2.
|
|
188
|
-
3.
|
|
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
|
-
- **
|
|
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.
|
package/hooks/check-gates.js
CHANGED
|
@@ -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
|
-
|
|
119
|
-
|
|
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
|
|
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:
|
|
106
|
-
refactor: { verification: true, otel:
|
|
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
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
|
|