@infinitedusky/indusk-mcp 1.10.0 → 1.10.1
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/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
|
+
}
|
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
|
|