@infinitedusky/indusk-mcp 1.9.2 → 1.10.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/dist/bin/commands/extensions.js +17 -6
- package/dist/bin/commands/init.js +60 -2
- package/dist/bin/commands/update.js +32 -1
- package/dist/lib/config.d.ts +19 -0
- package/dist/lib/config.js +17 -1
- package/dist/lib/extension-loader.d.ts +1 -0
- package/dist/lib/extension-loader.js +1 -4
- package/extensions/excalidraw/manifest.json +1 -3
- package/extensions/excalidraw/skill.md +1 -1
- package/extensions/graphiti/manifest.json +10 -0
- package/extensions/graphiti/skill.md +118 -26
- package/package.json +1 -1
- package/skills/catchup.md +25 -1
- package/skills/{plan.md → planner.md} +31 -7
- package/skills/retrospective.md +31 -1
- package/skills/toolbelt.md +2 -2
- package/skills/work.md +24 -3
|
@@ -3,7 +3,7 @@ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, write
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { globSync } from "glob";
|
|
6
|
-
import {
|
|
6
|
+
import { disabledDir, disableExtension, enableExtension, ensureExtensionsDirs, extensionConfigDir, extensionsDir, getEnabledExtensions, isEnabled, loadExtension, loadExtensions, resolveManifestPath, } from "../../lib/extension-loader.js";
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const packageRoot = join(__dirname, "../../..");
|
|
9
9
|
const builtinDir = join(packageRoot, "extensions");
|
|
@@ -635,20 +635,31 @@ function printMcpInstructions(name, manifest) {
|
|
|
635
635
|
}
|
|
636
636
|
if (allResolved) {
|
|
637
637
|
const args = [
|
|
638
|
-
"mcp",
|
|
639
|
-
|
|
638
|
+
"mcp",
|
|
639
|
+
"add",
|
|
640
|
+
"-t",
|
|
641
|
+
"http",
|
|
642
|
+
...headerArgs.flatMap((h) => {
|
|
640
643
|
const match = h.match(/--header "(.+): (.+)"/);
|
|
641
644
|
if (match)
|
|
642
645
|
return ["--header", `${match[1]}: ${match[2]}`];
|
|
643
646
|
return [];
|
|
644
647
|
}),
|
|
645
|
-
"-s",
|
|
648
|
+
"-s",
|
|
649
|
+
"project",
|
|
650
|
+
"--",
|
|
651
|
+
name,
|
|
652
|
+
url,
|
|
646
653
|
];
|
|
647
|
-
const cmd = `claude ${args.map(a => a.includes(" ") ? `"${a}"` : a).join(" ")}`;
|
|
654
|
+
const cmd = `claude ${args.map((a) => (a.includes(" ") ? `"${a}"` : a)).join(" ")}`;
|
|
648
655
|
console.info(`\n ${name}: adding MCP server with credentials from .env...`);
|
|
649
656
|
// Remove existing entry first so we always write fresh credentials
|
|
650
657
|
try {
|
|
651
|
-
execSync(`claude mcp remove -s project ${name}`, {
|
|
658
|
+
execSync(`claude mcp remove -s project ${name}`, {
|
|
659
|
+
cwd: process.cwd(),
|
|
660
|
+
timeout: 10000,
|
|
661
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
662
|
+
});
|
|
652
663
|
}
|
|
653
664
|
catch {
|
|
654
665
|
// Ignore — may not exist
|
|
@@ -46,6 +46,9 @@ function createCgcIgnore(projectRoot) {
|
|
|
46
46
|
"dist/",
|
|
47
47
|
"build/",
|
|
48
48
|
".git/",
|
|
49
|
+
".jj/",
|
|
50
|
+
".indusk/",
|
|
51
|
+
".claude/",
|
|
49
52
|
"*.png",
|
|
50
53
|
"*.jpg",
|
|
51
54
|
"*.svg",
|
|
@@ -296,6 +299,21 @@ export async function init(projectRoot, options = {}) {
|
|
|
296
299
|
else {
|
|
297
300
|
console.info(" skip: codegraphcontext MCP server (already exists)");
|
|
298
301
|
}
|
|
302
|
+
// Add graphiti MCP server (Phase 5.5 — Streamable HTTP, runs in indusk-infra container)
|
|
303
|
+
const graphitiAddCommand = "claude mcp add -t http -s project graphiti http://localhost:8100/mcp";
|
|
304
|
+
if (!existingServers.has("graphiti") || force) {
|
|
305
|
+
try {
|
|
306
|
+
execSync(graphitiAddCommand, { cwd: projectRoot, stdio: "pipe", timeout: 10000 });
|
|
307
|
+
console.info(" added: graphiti MCP server (http://localhost:8100/mcp)");
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
console.info(" failed: could not add graphiti MCP server — run manually:");
|
|
311
|
+
console.info(` ${graphitiAddCommand}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
console.info(" skip: graphiti MCP server (already exists)");
|
|
316
|
+
}
|
|
299
317
|
// 4b. Check infrastructure container
|
|
300
318
|
console.info("\n[Infrastructure]");
|
|
301
319
|
try {
|
|
@@ -755,12 +773,52 @@ export async function init(projectRoot, options = {}) {
|
|
|
755
773
|
writeConfig(projectRoot, config);
|
|
756
774
|
console.info(`\n[Config]`);
|
|
757
775
|
console.info(` create: .indusk/config.json (mode: ${config.mode})`);
|
|
776
|
+
// Create initial handoff so /catchup runs full orientation on first session
|
|
777
|
+
const handoffPath = join(projectRoot, ".claude/handoff.md");
|
|
778
|
+
if (!existsSync(handoffPath) || force) {
|
|
779
|
+
const today = new Date().toISOString().split("T")[0];
|
|
780
|
+
const handoffContent = [
|
|
781
|
+
"# Handoff",
|
|
782
|
+
"",
|
|
783
|
+
`**Date:** ${today}`,
|
|
784
|
+
"**Session:** Fresh init — first session orientation needed",
|
|
785
|
+
"",
|
|
786
|
+
"## Catchup Status",
|
|
787
|
+
"- [ ] mcp-ready",
|
|
788
|
+
"- [ ] handoff",
|
|
789
|
+
"- [ ] lessons",
|
|
790
|
+
"- [ ] health",
|
|
791
|
+
"- [ ] context",
|
|
792
|
+
"- [ ] plans",
|
|
793
|
+
"- [ ] skills",
|
|
794
|
+
"- [ ] extensions",
|
|
795
|
+
"- [ ] graph",
|
|
796
|
+
"",
|
|
797
|
+
"## What Was Being Worked On",
|
|
798
|
+
"`indusk init` just set up this project. No prior session.",
|
|
799
|
+
"",
|
|
800
|
+
"## Where It Stopped",
|
|
801
|
+
"Init complete. Agent needs full orientation (lessons, context, skills, extensions, graph).",
|
|
802
|
+
"",
|
|
803
|
+
"## What's Next",
|
|
804
|
+
"1. Run `/catchup` to orient the agent (reads lessons, context, skills, extensions, graph)",
|
|
805
|
+
"2. Edit CLAUDE.md with project details",
|
|
806
|
+
"3. Start planning: `/planner your-first-feature`",
|
|
807
|
+
"",
|
|
808
|
+
"## Open Issues",
|
|
809
|
+
"None — fresh project.",
|
|
810
|
+
"",
|
|
811
|
+
].join("\n");
|
|
812
|
+
writeFileSync(handoffPath, handoffContent);
|
|
813
|
+
console.info("\n[Handoff]");
|
|
814
|
+
console.info(" create: .claude/handoff.md (first-session orientation)");
|
|
815
|
+
}
|
|
758
816
|
// Summary
|
|
759
817
|
console.info("\nDone!");
|
|
760
818
|
console.info("\n⚠ Restart Claude Code to load the updated MCP server and skills.");
|
|
761
819
|
console.info("\nNext steps:");
|
|
762
820
|
console.info(" 1. Set up infrastructure (if not done): indusk infra start");
|
|
763
|
-
console.info(" 2. Restart Claude Code");
|
|
821
|
+
console.info(" 2. Restart Claude Code — /catchup will auto-orient the agent");
|
|
764
822
|
console.info(" 3. Edit CLAUDE.md with your project details");
|
|
765
|
-
console.info(" 4. Start planning: /
|
|
823
|
+
console.info(" 4. Start planning: /planner your-first-feature");
|
|
766
824
|
}
|
|
@@ -274,7 +274,38 @@ export async function update(projectRoot) {
|
|
|
274
274
|
console.info(` ${name}: update hook failed`);
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
|
-
|
|
277
|
+
// Phase 5.5: ensure declared MCP server is registered in .mcp.json.
|
|
278
|
+
// If the manifest's top-level mcp_server.add_command is set and the server
|
|
279
|
+
// is missing from .mcp.json, run the command. Idempotent — skips if present.
|
|
280
|
+
// The MCP server's name in .mcp.json is the extension name (matches init's
|
|
281
|
+
// `claude mcp add ... <extName> ...` convention).
|
|
282
|
+
const mcpServer = manifest?.mcp_server;
|
|
283
|
+
if (mcpServer?.add_command) {
|
|
284
|
+
const mcpJsonPath = join(projectRoot, ".mcp.json");
|
|
285
|
+
let alreadyRegistered = false;
|
|
286
|
+
if (existsSync(mcpJsonPath)) {
|
|
287
|
+
try {
|
|
288
|
+
const mcpJson = JSON.parse(readFileSync(mcpJsonPath, "utf-8"));
|
|
289
|
+
alreadyRegistered = !!mcpJson.mcpServers?.[name];
|
|
290
|
+
}
|
|
291
|
+
catch { }
|
|
292
|
+
}
|
|
293
|
+
if (!alreadyRegistered) {
|
|
294
|
+
try {
|
|
295
|
+
execSync(mcpServer.add_command, {
|
|
296
|
+
cwd: projectRoot,
|
|
297
|
+
stdio: "pipe",
|
|
298
|
+
timeout: 10000,
|
|
299
|
+
});
|
|
300
|
+
console.info(` ${name}: registered MCP server`);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
console.info(` ${name}: failed to register MCP server — run manually:`);
|
|
304
|
+
console.info(` ${mcpServer.add_command}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else if (mcpServer?.setup_instructions) {
|
|
278
309
|
console.info(` ${name}: MCP server setup — see .claude/skills/${name}/SKILL.md`);
|
|
279
310
|
}
|
|
280
311
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -14,8 +14,27 @@ export interface InduskConfig {
|
|
|
14
14
|
testRunner?: string;
|
|
15
15
|
linter?: string;
|
|
16
16
|
};
|
|
17
|
+
graphiti?: {
|
|
18
|
+
/**
|
|
19
|
+
* Group id used for project-specific Graphiti episodes. Defaults to the
|
|
20
|
+
* project directory basename. Override here if the directory name differs
|
|
21
|
+
* from the desired group id (e.g. shared monorepo, renamed project).
|
|
22
|
+
*/
|
|
23
|
+
groupId?: string;
|
|
24
|
+
};
|
|
17
25
|
}
|
|
18
26
|
export declare function getConfigPath(projectRoot: string): string;
|
|
19
27
|
export declare function readConfig(projectRoot: string): InduskConfig | null;
|
|
20
28
|
export declare function writeConfig(projectRoot: string, config: InduskConfig): void;
|
|
21
29
|
export declare function getPlanningDir(projectRoot: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Get the Graphiti group id for project-specific episodes.
|
|
32
|
+
*
|
|
33
|
+
* Resolution order:
|
|
34
|
+
* 1. .indusk/config.json `graphiti.groupId` if set
|
|
35
|
+
* 2. Project directory basename
|
|
36
|
+
*
|
|
37
|
+
* Use `[getProjectGroupId(root), "shared"]` as the default group_ids list when
|
|
38
|
+
* searching Graphiti — this gives both project-scoped and cross-project knowledge.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getProjectGroupId(projectRoot: string): string;
|
package/dist/lib/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { basename, dirname, join } from "node:path";
|
|
3
3
|
const CONFIG_PATH = ".indusk/config.json";
|
|
4
4
|
export function getConfigPath(projectRoot) {
|
|
5
5
|
return join(projectRoot, CONFIG_PATH);
|
|
@@ -26,3 +26,19 @@ export function getPlanningDir(projectRoot) {
|
|
|
26
26
|
// Default to new path (will be created by init)
|
|
27
27
|
return newPath;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the Graphiti group id for project-specific episodes.
|
|
31
|
+
*
|
|
32
|
+
* Resolution order:
|
|
33
|
+
* 1. .indusk/config.json `graphiti.groupId` if set
|
|
34
|
+
* 2. Project directory basename
|
|
35
|
+
*
|
|
36
|
+
* Use `[getProjectGroupId(root), "shared"]` as the default group_ids list when
|
|
37
|
+
* searching Graphiti — this gives both project-scoped and cross-project knowledge.
|
|
38
|
+
*/
|
|
39
|
+
export function getProjectGroupId(projectRoot) {
|
|
40
|
+
const config = readConfig(projectRoot);
|
|
41
|
+
if (config?.graphiti?.groupId)
|
|
42
|
+
return config.graphiti.groupId;
|
|
43
|
+
return basename(projectRoot);
|
|
44
|
+
}
|
|
@@ -100,10 +100,7 @@ function loadFromDir(baseDir, enabled) {
|
|
|
100
100
|
export function loadExtensions(projectRoot) {
|
|
101
101
|
const dir = extensionsDir(projectRoot);
|
|
102
102
|
const disDir = disabledDir(projectRoot);
|
|
103
|
-
return [
|
|
104
|
-
...loadFromDir(dir, true),
|
|
105
|
-
...loadFromDir(disDir, false),
|
|
106
|
-
];
|
|
103
|
+
return [...loadFromDir(dir, true), ...loadFromDir(disDir, false)];
|
|
107
104
|
}
|
|
108
105
|
export function getEnabledExtensions(projectRoot) {
|
|
109
106
|
return loadExtensions(projectRoot).filter((e) => e.enabled);
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
"mcp_server": {
|
|
8
8
|
"type": "http",
|
|
9
9
|
"url": "https://mcp.excalidraw.com",
|
|
10
|
-
"setup_instructions": [
|
|
11
|
-
"claude mcp add -t http -- excalidraw https://mcp.excalidraw.com"
|
|
12
|
-
]
|
|
10
|
+
"setup_instructions": ["claude mcp add -t http -- excalidraw https://mcp.excalidraw.com"]
|
|
13
11
|
}
|
|
14
12
|
}
|
|
@@ -41,7 +41,7 @@ That's it. No tokens, no config files, no environment variables.
|
|
|
41
41
|
|
|
42
42
|
## When to Create Diagrams
|
|
43
43
|
|
|
44
|
-
- **During `/
|
|
44
|
+
- **During `/planner` research or brief**: sketch the proposed architecture to make it concrete
|
|
45
45
|
- **During `/work` teach mode**: visualize what a phase is building before and after
|
|
46
46
|
- **During debugging**: draw the request flow to identify where things break
|
|
47
47
|
- **During retrospective**: before/after architecture comparison
|
|
@@ -18,6 +18,16 @@
|
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
|
+
"mcp_server": {
|
|
22
|
+
"type": "http",
|
|
23
|
+
"url": "http://localhost:8100/mcp",
|
|
24
|
+
"add_command": "claude mcp add -t http -s project graphiti http://localhost:8100/mcp",
|
|
25
|
+
"setup_instructions": [
|
|
26
|
+
"Registered automatically by `indusk init` and re-added by `indusk update` if missing.",
|
|
27
|
+
"The Graphiti MCP server runs inside the `indusk-infra` container on port 8100.",
|
|
28
|
+
"Exposes 9 tools: add_memory, search_nodes, search_memory_facts, get_episodes, get_entity_edge, delete_episode, delete_entity_edge, clear_graph, get_status."
|
|
29
|
+
]
|
|
30
|
+
},
|
|
21
31
|
"detect": {
|
|
22
32
|
"command": "docker inspect --format='{{.State.Running}}' indusk-infra 2>/dev/null | grep true"
|
|
23
33
|
}
|
|
@@ -2,16 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
Graphiti is an episodic memory system backed by FalkorDB. It extracts entities and facts from text, detects contradictions, and supports semantic search across project-specific and shared knowledge.
|
|
4
4
|
|
|
5
|
+
The Graphiti MCP server runs inside the `indusk-infra` container on `http://localhost:8100/mcp` and is registered automatically by `indusk init`. The agent has direct access via `mcp__graphiti__*` tools — there is no wrapping layer.
|
|
6
|
+
|
|
5
7
|
## When to Use
|
|
6
8
|
|
|
7
|
-
- **
|
|
9
|
+
- **Capture**: After a decision, retro finding, or correction — anything worth remembering across sessions
|
|
8
10
|
- **Search**: Before making assumptions — check what's already known about a topic
|
|
9
|
-
- **
|
|
11
|
+
- **Recall**: At session start (`/catchup` does this automatically) or when working in an unfamiliar area
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
The agent has nine `mcp__graphiti__*` tools. The five you'll use most:
|
|
16
|
+
|
|
17
|
+
| Tool | Purpose |
|
|
18
|
+
|------|---------|
|
|
19
|
+
| `add_memory` | Capture an episode (text/JSON/message) — entities and facts are extracted asynchronously |
|
|
20
|
+
| `search_nodes` | Find entities by natural-language query |
|
|
21
|
+
| `search_memory_facts` | Find facts (relationships between entities) by query |
|
|
22
|
+
| `get_episodes` | List recent episodes by group |
|
|
23
|
+
| `get_status` | Check that Graphiti is reachable and the database is healthy |
|
|
24
|
+
|
|
25
|
+
The other four (`delete_episode`, `delete_entity_edge`, `get_entity_edge`, `clear_graph`) are for cleanup — use sparingly, never in normal flow.
|
|
10
26
|
|
|
11
27
|
## Core Concepts
|
|
12
28
|
|
|
13
29
|
### Episodes
|
|
14
|
-
An episode is a chunk of text that Graphiti processes into entities and facts. Think of it as "something that happened" — a decision was made, a bug was found, a convention was established.
|
|
30
|
+
An episode is a chunk of text that Graphiti processes into entities and facts. Think of it as "something that happened" — a decision was made, a bug was found, a convention was established. Episodes are processed in the background; entities appear in search results once extraction completes (a few seconds).
|
|
15
31
|
|
|
16
32
|
### Group IDs
|
|
17
33
|
Every episode belongs to a group. Groups isolate knowledge:
|
|
@@ -21,54 +37,123 @@ Every episode belongs to a group. Groups isolate knowledge:
|
|
|
21
37
|
| `{project-name}` | Project-specific knowledge | `infinitedusky`, `numero` |
|
|
22
38
|
| `shared` | Cross-project conventions | Developer preferences, universal patterns |
|
|
23
39
|
|
|
24
|
-
When searching, always include both the project group and `shared` to get the full picture.
|
|
40
|
+
When searching, always include both the project group and `shared` to get the full picture. Use `getProjectGroupId(projectRoot)` (from `apps/indusk-mcp/src/lib/config.ts`) to get the project group consistently — it reads `.indusk/config.json` `graphiti.groupId` if set, otherwise falls back to the project directory basename.
|
|
25
41
|
|
|
26
42
|
### Entities and Facts
|
|
27
43
|
Graphiti extracts:
|
|
28
44
|
- **Entities**: Named things (tools, patterns, files, concepts)
|
|
29
45
|
- **Facts**: Relationships between entities with temporal validity
|
|
30
46
|
|
|
31
|
-
Facts can be contradicted — if you add "the parser handles three gate types" and later "the parser handles four gate types", Graphiti invalidates the old fact.
|
|
47
|
+
Facts can be contradicted — if you add "the parser handles three gate types" and later "the parser handles four gate types", Graphiti invalidates the old fact. The contradicted fact gets `invalid_at` set; the new one gets `valid_at` set. Search results respect this — invalid facts are excluded by default.
|
|
32
48
|
|
|
33
49
|
## Patterns
|
|
34
50
|
|
|
35
51
|
### Capturing a Decision
|
|
36
|
-
|
|
52
|
+
|
|
53
|
+
After an ADR is accepted or a significant choice is made, the planner skill calls this automatically. To do it manually:
|
|
54
|
+
|
|
37
55
|
```
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
mcp__graphiti__add_memory({
|
|
57
|
+
name: "auth-approach-decision",
|
|
58
|
+
episode_body: "We chose JWT with refresh tokens over session cookies because the API serves both web and mobile clients. Session cookies don't work well with React Native.",
|
|
59
|
+
group_id: "myproject",
|
|
60
|
+
source: "text",
|
|
61
|
+
source_description: "ADR"
|
|
62
|
+
})
|
|
41
63
|
```
|
|
42
64
|
|
|
65
|
+
The `name` should be short and topical — the agent uses it as a handle when re-discussing the decision. The `episode_body` should be detailed enough that someone reading it cold understands the decision and the reasoning.
|
|
66
|
+
|
|
43
67
|
### Capturing a Correction
|
|
44
|
-
|
|
68
|
+
|
|
69
|
+
When the user corrects the agent, the work skill prompts `context learn` AND captures the correction as an episode:
|
|
70
|
+
|
|
45
71
|
```
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
mcp__graphiti__add_memory({
|
|
73
|
+
name: "correction-pnpm-ce",
|
|
74
|
+
episode_body: "Always use `pnpm ce`, never `npx ce`. The composable.env skill specifies pnpm and the project's package.json maps `ce` to the binary. npx invokes a different code path.",
|
|
75
|
+
group_id: "shared",
|
|
76
|
+
source: "text",
|
|
77
|
+
source_description: "user correction"
|
|
78
|
+
})
|
|
49
79
|
```
|
|
50
80
|
|
|
81
|
+
Choosing `shared` vs project group:
|
|
82
|
+
- **`shared`**: tools, conventions, patterns that apply across projects ("always use pnpm ce", "never mock the database in integration tests")
|
|
83
|
+
- **`{project-name}`**: facts specific to one project's code, data, or domain ("the impl-parser handles four gate types per phase", "the bet matching engine is order-book based")
|
|
84
|
+
|
|
85
|
+
When in doubt, ask: "Would this correction make sense to a different project?" Yes → `shared`. No → project group.
|
|
86
|
+
|
|
51
87
|
### Searching Before Acting
|
|
88
|
+
|
|
52
89
|
Before making assumptions about how something works:
|
|
90
|
+
|
|
53
91
|
```
|
|
54
|
-
|
|
55
|
-
|
|
92
|
+
mcp__graphiti__search_nodes({
|
|
93
|
+
query: "authentication middleware",
|
|
94
|
+
group_ids: ["myproject", "shared"],
|
|
95
|
+
max_nodes: 10
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
mcp__graphiti__search_memory_facts({
|
|
99
|
+
query: "how does auth work in this project",
|
|
100
|
+
group_ids: ["myproject", "shared"],
|
|
101
|
+
max_facts: 10
|
|
102
|
+
})
|
|
56
103
|
```
|
|
57
104
|
|
|
105
|
+
Always search both the project group and `shared` — knowledge is split across them. The wrapper class `GraphitiClient` in `apps/indusk-mcp/src/lib/graphiti-client.ts` does this automatically when called internally; agents calling `mcp__graphiti__*` tools directly need to pass both group ids in the request.
|
|
106
|
+
|
|
58
107
|
### Capturing a Retrospective Finding
|
|
59
|
-
|
|
108
|
+
|
|
109
|
+
After a plan retrospective surfaces a useful insight, the retrospective skill captures it as an episode:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
mcp__graphiti__add_memory({
|
|
113
|
+
name: "retro-gate-enforcement-1",
|
|
114
|
+
episode_body: "Plan gates need hook-based enforcement, not just instructions. The agent skipped gates when they were advisory only. PreToolUse hooks that block phase transitions are the fix.",
|
|
115
|
+
group_id: "infinitedusky",
|
|
116
|
+
source: "text",
|
|
117
|
+
source_description: "retrospective insight"
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
One episode per insight, named `retro-{plan}-{n}`. If the retro surfaces a contradiction (we thought X, found Y), capture both framings as separate episodes — Graphiti's contradiction detection invalidates the older one.
|
|
122
|
+
|
|
123
|
+
### Recall at Session Start
|
|
124
|
+
|
|
125
|
+
The `/catchup` skill calls this automatically after reading project context, but you can also trigger it manually:
|
|
126
|
+
|
|
60
127
|
```
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
128
|
+
mcp__graphiti__search_nodes({
|
|
129
|
+
query: "recent decisions",
|
|
130
|
+
group_ids: ["myproject", "shared"],
|
|
131
|
+
max_nodes: 5
|
|
132
|
+
})
|
|
64
133
|
```
|
|
65
134
|
|
|
135
|
+
Surface anything notable to the user. Pay extra attention to facts where `invalid_at` is recent — those are areas where prior decisions changed and active plans may be working from stale assumptions.
|
|
136
|
+
|
|
137
|
+
## Capture Triggers (Where Episodes Come From)
|
|
138
|
+
|
|
139
|
+
In normal workflow, episodes are written automatically by other skills. The agent rarely calls `add_memory` directly:
|
|
140
|
+
|
|
141
|
+
| Trigger | Skill | Episode |
|
|
142
|
+
|---------|-------|---------|
|
|
143
|
+
| Brief accepted | `planner` | `brief-accepted-{plan}` — Problem + Proposed Direction in project group |
|
|
144
|
+
| ADR accepted | `planner` | `adr-{plan}` — Y-statement in project group |
|
|
145
|
+
| User correction (`context learn`) | `work` | `correction-{slug}` — lesson body in `shared` or project group |
|
|
146
|
+
| Retrospective lesson | `retrospective` | `retro-{plan}-{n}` — one per insight in project group |
|
|
147
|
+
| Retrospective "would do differently" | `retrospective` | `retro-{plan}-wdid-{n}` — one per item in project group |
|
|
148
|
+
|
|
149
|
+
The agent should call `add_memory` directly **only** when something worth remembering happens outside these trigger points. Most of the time, just trust the skills to capture and let `/catchup` recall.
|
|
150
|
+
|
|
66
151
|
## What NOT to Capture
|
|
67
152
|
|
|
68
|
-
- Code structure
|
|
69
|
-
- Git history
|
|
70
|
-
- Ephemeral state
|
|
71
|
-
- Things already in CLAUDE.md or lessons
|
|
153
|
+
- **Code structure** — CGC handles this. `function X calls function Y` is a graph relationship, not an episode.
|
|
154
|
+
- **Git history** — `git log` is authoritative.
|
|
155
|
+
- **Ephemeral state** — current task, in-progress work, todo lists. Use the TodoWrite tool, not Graphiti.
|
|
156
|
+
- **Things already in CLAUDE.md or lessons** — those layers exist for stable, project-wide truths. Graphiti is for things that have temporal context and might be contradicted.
|
|
72
157
|
|
|
73
158
|
Graphiti is for knowledge that has temporal context — decisions that might change, facts that might be contradicted, insights that accumulate over time.
|
|
74
159
|
|
|
@@ -86,6 +171,13 @@ Global config (API keys, OTel): `~/.indusk/config.env`
|
|
|
86
171
|
|
|
87
172
|
### Graceful Degradation
|
|
88
173
|
If the `indusk-infra` container is down:
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
- The
|
|
174
|
+
- `mcp__graphiti__*` tools will report a clean error (the MCP transport fails to connect)
|
|
175
|
+
- The agent should fall back to flat-file context (CLAUDE.md, lessons, skills) and continue working
|
|
176
|
+
- The `GraphitiClient` wrapper class (used internally by indusk-mcp) returns null/empty instead of throwing
|
|
177
|
+
- Don't pretend the call succeeded — if Graphiti is down, capture is lost. Tell the user to `indusk infra start`.
|
|
178
|
+
|
|
179
|
+
### Health Check
|
|
180
|
+
```
|
|
181
|
+
mcp__graphiti__get_status({})
|
|
182
|
+
```
|
|
183
|
+
Returns `{ status: "healthy", message: "..." }` when Graphiti is reachable and the database is connected.
|
package/package.json
CHANGED
package/skills/catchup.md
CHANGED
|
@@ -64,6 +64,29 @@ Read it fully. Don't skim.
|
|
|
64
64
|
|
|
65
65
|
**After reading, edit the handoff to check off:** `- [x] context`
|
|
66
66
|
|
|
67
|
+
### 4.5. Recall from Graphiti
|
|
68
|
+
|
|
69
|
+
CLAUDE.md is the stable, slow-changing layer of project memory. Graphiti is the fast, temporal layer — it captures decisions, corrections, and retrospective insights as they happen. Catchup pulls both layers so the agent starts the session with full context.
|
|
70
|
+
|
|
71
|
+
**Recall recent decisions and lessons:**
|
|
72
|
+
```
|
|
73
|
+
mcp__graphiti__search_nodes({
|
|
74
|
+
query: "recent decisions and lessons",
|
|
75
|
+
group_ids: ["{project-group}", "shared"],
|
|
76
|
+
max_nodes: 8
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The project group comes from the `getProjectGroupId(projectRoot)` helper (in `apps/indusk-mcp/src/lib/config.ts`). Always include both the project group and `shared` so cross-project conventions surface alongside project-specific knowledge.
|
|
81
|
+
|
|
82
|
+
**Surface contradictions:** look at the returned nodes for any whose `attributes` reference recently invalidated facts (Graphiti marks superseded facts with `invalid_at`). If a recently invalidated fact relates to an active plan or current code area, flag it to the user — those are places where assumptions changed.
|
|
83
|
+
|
|
84
|
+
**Output format:** include a "Graphiti recall" section in the catchup summary with the most relevant 3-5 nodes by name + summary. Don't dump everything — surface what's actionable.
|
|
85
|
+
|
|
86
|
+
**Graceful degradation:** If `mcp__graphiti__search_nodes` is unavailable (Graphiti container down, transport error), skip this step silently and add a note to the catchup summary: `Graphiti: unavailable (run \`indusk infra start\` to recall episodic memory)`. Catchup should not fail if Graphiti is down — the rest of the layers are still valid.
|
|
87
|
+
|
|
88
|
+
**After completing recall, edit the handoff to check off:** `- [x] graphiti` (added to the catchup status box section)
|
|
89
|
+
|
|
67
90
|
### 5. Check Active Plans
|
|
68
91
|
Call `list_plans`. This shows every plan and its status. Pay attention to:
|
|
69
92
|
- Plans with status `in-progress` — these are actively being worked on
|
|
@@ -78,7 +101,7 @@ Call `extensions_status` to see what extensions are enabled and their capabiliti
|
|
|
78
101
|
Call `get_skill_summaries` to load the name, description, and type of every installed skill. This returns a compact summary — you do NOT need to read each skill file individually. The full skill content loads automatically when the user invokes a slash command.
|
|
79
102
|
|
|
80
103
|
Skill types:
|
|
81
|
-
- **process** — workflow skills with slash commands (
|
|
104
|
+
- **process** — workflow skills with slash commands (planner, work, verify, context, document, retrospective)
|
|
82
105
|
- **extension** — tool integrations (cgc, composable-env, excalidraw, etc.)
|
|
83
106
|
- **domain** — technology-specific best practices (typescript, testing, etc.)
|
|
84
107
|
|
|
@@ -104,6 +127,7 @@ After completing all steps, present a brief summary to the user:
|
|
|
104
127
|
- Extensions: N enabled [list names]
|
|
105
128
|
- Active plans: [list with current phase]
|
|
106
129
|
- Codebase: [N files indexed]
|
|
130
|
+
- Graphiti recall: [3-5 most relevant nodes by name + summary, or "unavailable" if Graphiti is down]
|
|
107
131
|
|
|
108
132
|
Ready to pick up. What would you like to do?
|
|
109
133
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: planner
|
|
3
3
|
description: Create and advance plans. Every plan follows the same document lifecycle — research, brief, ADR, impl, retrospective. Knows how to write each one, what order they go in, and how to pick up where things left off.
|
|
4
4
|
argument-hint: "[workflow] [plan name] — workflow: feature (default), bugfix, refactor, spike"
|
|
5
5
|
---
|
|
@@ -29,15 +29,15 @@ General-purpose research (insights useful across plans) also lives in `research/
|
|
|
29
29
|
|
|
30
30
|
## Workflow Types
|
|
31
31
|
|
|
32
|
-
The first argument to `/
|
|
32
|
+
The first argument to `/planner` can optionally be a workflow type that controls which documents are created:
|
|
33
33
|
|
|
34
34
|
| Command | Workflow | Documents |
|
|
35
35
|
|---------|----------|-----------|
|
|
36
|
-
| `/
|
|
37
|
-
| `/
|
|
38
|
-
| `/
|
|
39
|
-
| `/
|
|
40
|
-
| `/
|
|
36
|
+
| `/planner bugfix auth-expiry` | bugfix | brief + impl only |
|
|
37
|
+
| `/planner refactor extract-auth` | refactor | brief + impl (with boundary map) |
|
|
38
|
+
| `/planner spike redis-options` | spike | research only |
|
|
39
|
+
| `/planner feature payment-flow` | feature | full lifecycle (default) |
|
|
40
|
+
| `/planner payment-flow` | feature | same — no type defaults to feature |
|
|
41
41
|
|
|
42
42
|
Parse the input: if the first word is `bugfix`, `refactor`, `spike`, or `feature`, use that workflow. Otherwise, default to `feature`. The remaining words become the plan name (kebab-cased).
|
|
43
43
|
|
|
@@ -74,8 +74,32 @@ Workflow templates are in `templates/workflows/` in the package. They describe w
|
|
|
74
74
|
|
|
75
75
|
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. **Consider creating a visual sketch** of the proposed architecture with Excalidraw (if the extension is enabled) — a hand-drawn diagram makes the proposal concrete and easier to discuss. **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`.
|
|
76
76
|
|
|
77
|
+
**When the brief moves from `draft` to `accepted`**, capture the decision in Graphiti:
|
|
78
|
+
```
|
|
79
|
+
mcp__graphiti__add_memory({
|
|
80
|
+
name: "brief-accepted-{plan-name}",
|
|
81
|
+
episode_body: "{Problem}\n\n{Proposed Direction}\n\n{Scope summary if helpful}",
|
|
82
|
+
group_id: "{project-group}",
|
|
83
|
+
source: "text",
|
|
84
|
+
source_description: "brief acceptance"
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
The `group_id` is the project group (use `getProjectGroupId(projectRoot)` from `apps/indusk-mcp/src/lib/config.ts` — defaults to project directory basename, override via `.indusk/config.json` `graphiti.groupId`). Skip silently if `mcp__graphiti__add_memory` is unavailable (Graphiti may be down — degrade gracefully, do not fail the brief acceptance).
|
|
88
|
+
|
|
77
89
|
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 .indusk/planning/{plan}/adr.md`
|
|
78
90
|
|
|
91
|
+
**When the ADR moves from `proposed` to `accepted`**, capture the Y-statement in Graphiti:
|
|
92
|
+
```
|
|
93
|
+
mcp__graphiti__add_memory({
|
|
94
|
+
name: "adr-{plan-name}",
|
|
95
|
+
episode_body: "In the context of {use case}, facing {constraint}, we decided for {chosen option} and against {rejected alternatives}, to achieve {desired outcome}, accepting {tradeoff}, because {rationale}.",
|
|
96
|
+
group_id: "{project-group}",
|
|
97
|
+
source: "text",
|
|
98
|
+
source_description: "ADR acceptance"
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
The Y-statement is rich enough that Graphiti will extract entities for the chosen option, rejected alternatives, constraint, and rationale — and will detect contradictions if a later ADR overrides this one. Skip silently on Graphiti unavailability.
|
|
102
|
+
|
|
79
103
|
6. **If ADR is accepted** (or brief is accepted for bugfix/refactor), write the impl. Break into phased checklists with concrete tasks. For refactor workflows, include a `## Boundary Map` section. For multi-phase impls of any type, consider adding a boundary map.
|
|
80
104
|
|
|
81
105
|
**Gate policy applies when writing impls.** Set `gate_policy` in the impl frontmatter (`strict`, `ask`, or `auto`). The `validate-impl-structure` hook enforces this at write time:
|
package/skills/retrospective.md
CHANGED
|
@@ -21,7 +21,7 @@ The retrospective skill replaces the freeform "write a retrospective" step with
|
|
|
21
21
|
## When to Use
|
|
22
22
|
|
|
23
23
|
- After `/work` completes all impl phases and the status is `completed`
|
|
24
|
-
- When `/
|
|
24
|
+
- When `/planner {name}` detects the impl is completed and the next step is retrospective
|
|
25
25
|
- Directly via `/retrospective {plan-name}`
|
|
26
26
|
|
|
27
27
|
## The Audit Checklist
|
|
@@ -91,6 +91,36 @@ If yes, call `add_lesson` for each one. These become personal lessons in `.claud
|
|
|
91
91
|
|
|
92
92
|
If no lessons emerged, that's fine — not every plan produces new knowledge. Move on.
|
|
93
93
|
|
|
94
|
+
**Also capture each retrospective insight in Graphiti** so it becomes part of the temporal knowledge graph and can surface in future searches and contradictions.
|
|
95
|
+
|
|
96
|
+
For each item in the retrospective's **What We Learned** section:
|
|
97
|
+
```
|
|
98
|
+
mcp__graphiti__add_memory({
|
|
99
|
+
name: "retro-{plan-name}-{n}",
|
|
100
|
+
episode_body: "{the full insight, including context — not just a one-line summary}",
|
|
101
|
+
group_id: "{project-group}",
|
|
102
|
+
source: "text",
|
|
103
|
+
source_description: "retrospective lesson"
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
For each item in the retrospective's **What We'd Do Differently** section:
|
|
108
|
+
```
|
|
109
|
+
mcp__graphiti__add_memory({
|
|
110
|
+
name: "retro-{plan-name}-wdid-{n}",
|
|
111
|
+
episode_body: "{the hindsight item, with reasoning}",
|
|
112
|
+
group_id: "{project-group}",
|
|
113
|
+
source: "text",
|
|
114
|
+
source_description: "retrospective hindsight"
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use the project group (from `getProjectGroupId(projectRoot)`) — most retrospective insights are project-specific. Promote to `shared` only if the insight is clearly cross-project (those should also become a personal lesson via `add_lesson`).
|
|
119
|
+
|
|
120
|
+
**Contradictions:** If the retrospective surfaces a moment where "we thought X but found Y", capture both as separate episodes. Graphiti's contradiction detection will invalidate the older fact when it sees the conflicting one. This is one of Graphiti's most useful features — it remembers that a previous assumption was overturned, so the agent doesn't accidentally re-introduce it later.
|
|
121
|
+
|
|
122
|
+
Skip silently if `mcp__graphiti__add_memory` is unavailable — Graphiti capture is best-effort, and lesson recording via `add_lesson` is the canonical path. Graphiti capture is supplementary.
|
|
123
|
+
|
|
94
124
|
### Step 7: Context Audit
|
|
95
125
|
|
|
96
126
|
Re-read CLAUDE.md in full. After the entire impl is done, verify:
|
package/skills/toolbelt.md
CHANGED
|
@@ -17,7 +17,7 @@ To see which MCP servers and extensions are active, call `extensions_status`.
|
|
|
17
17
|
## How the Skills Work Together
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
/
|
|
20
|
+
/planner → creates planning docs (research, brief, ADR, impl)
|
|
21
21
|
↓
|
|
22
22
|
/work → executes impl checklist, phase by phase
|
|
23
23
|
each phase has four gates:
|
|
@@ -139,7 +139,7 @@ While executing impl items:
|
|
|
139
139
|
|
|
140
140
|
Gates prevent skipping important work. Three enforcement levels, set via `gate_policy` in impl frontmatter or `.claude/settings.json`:
|
|
141
141
|
|
|
142
|
-
| Mode | Writing the impl (`/
|
|
142
|
+
| Mode | Writing the impl (`/planner`) | Executing the impl (`/work`) |
|
|
143
143
|
|------|---------------------------|------------------------------|
|
|
144
144
|
| **`strict`** | Every gate must have a real item. No `(none needed)`. | Every item must be completed. No skipping. |
|
|
145
145
|
| **`ask`** (default) | Every gate must have a real item. No `(none needed)`. | Skip only with conversation proof: `(none needed — asked: "..." — user: "...")` |
|
package/skills/work.md
CHANGED
|
@@ -70,7 +70,7 @@ Three modes, configured via `gate_policy` in the impl frontmatter or `.claude/se
|
|
|
70
70
|
|
|
71
71
|
| Mode | Behavior |
|
|
72
72
|
|------|----------|
|
|
73
|
-
| **`strict`** | No overrides at any stage. Every gate must have a real item when the impl is written (`/
|
|
73
|
+
| **`strict`** | No overrides at any stage. Every gate must have a real item when the impl is written (`/planner`), and every item must be completed during `/work`. No `(none needed)`, no `skip-reason:`, no conversation proof. |
|
|
74
74
|
| **`ask`** (default) | Every gate must have a real item when the impl is written. During `/work`, the agent must ask the user before skipping, and include proof of the conversation in the skip format. Hooks enforce both stages. |
|
|
75
75
|
| **`auto`** | Gates can be pre-filled with `(none needed)` or `skip-reason:` at write time. During `/work`, the agent can skip without asking. Use when running autonomously. |
|
|
76
76
|
|
|
@@ -118,7 +118,7 @@ In `ask` mode, skipped gates MUST include proof that the conversation happened:
|
|
|
118
118
|
|
|
119
119
|
The hook validates that both `asked:` and `user:` are present with non-empty quoted content. Bare `(none needed)` or `skip-reason:` without conversation proof will be **blocked by the hook**.
|
|
120
120
|
|
|
121
|
-
| Mode | At write time (`/
|
|
121
|
+
| Mode | At write time (`/planner`) | At execution time (`/work`) |
|
|
122
122
|
|------|------------------------|---------------------------|
|
|
123
123
|
| `strict` | No opt-outs — real items required | No skipping — everything completed |
|
|
124
124
|
| `ask` | No opt-outs — real items required | Skip only with conversation proof |
|
|
@@ -148,7 +148,7 @@ The hook validates that both `asked:` and `user:` are present with non-empty quo
|
|
|
148
148
|
- Update impl status to `completed`
|
|
149
149
|
- Summarize what was done
|
|
150
150
|
- If this plan included an ADR, confirm CLAUDE.md's Key Decisions was updated
|
|
151
|
-
- Let the user know the plan is ready for a retrospective if they want one (`/
|
|
151
|
+
- Let the user know the plan is ready for a retrospective if they want one (`/planner {name}` will pick up at the retrospective stage)
|
|
152
152
|
|
|
153
153
|
## Teach Mode
|
|
154
154
|
|
|
@@ -202,6 +202,27 @@ When you are corrected mid-work — the user says "no, not that way" or "don't d
|
|
|
202
202
|
|
|
203
203
|
Don't wait to be told. Corrections are the most valuable source of project knowledge.
|
|
204
204
|
|
|
205
|
+
**When the user confirms `context learn`, ALSO capture the correction in Graphiti:**
|
|
206
|
+
```
|
|
207
|
+
mcp__graphiti__add_memory({
|
|
208
|
+
name: "correction-{slug}",
|
|
209
|
+
episode_body: "{lesson text}",
|
|
210
|
+
group_id: "{shared OR project-group}",
|
|
211
|
+
source: "text",
|
|
212
|
+
source_description: "user correction"
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Where `{slug}` is a short kebab-case label for the topic (e.g. `correction-pnpm-ce`, `correction-graphiti-error-handling`).
|
|
217
|
+
|
|
218
|
+
**Choosing `shared` vs project group:**
|
|
219
|
+
- **`shared`**: tools, conventions, patterns that apply across projects. Examples: "always use pnpm ce", "never mock the database in integration tests", "use jj describe-then-do for commits". The lesson is generally true and a different project would benefit from it.
|
|
220
|
+
- **`{project-group}`**: facts specific to this project's code, data, or domain. Examples: "the impl-parser handles four gate types per phase", "graph_ensure auto-repairs the indusk-infra container". The lesson only makes sense in the context of this project.
|
|
221
|
+
|
|
222
|
+
When in doubt, ask: "Would this correction make sense to a different project?" Yes → `shared`. No → project group.
|
|
223
|
+
|
|
224
|
+
Use `getProjectGroupId(projectRoot)` from `apps/indusk-mcp/src/lib/config.ts` to get the project group consistently. Skip silently if `mcp__graphiti__add_memory` is unavailable — Graphiti capture is best-effort, do not fail the work item.
|
|
225
|
+
|
|
205
226
|
## Commits (jj)
|
|
206
227
|
|
|
207
228
|
Use the **describe-then-do** workflow from the jj skill:
|