@graph-tl/graph 0.1.15 → 0.1.17

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/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Graph
2
2
 
3
- A task tracker built for AI agents, not humans.
3
+ [![npm version](https://img.shields.io/npm/v/@graph-tl/graph)](https://www.npmjs.com/package/@graph-tl/graph)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@graph-tl/graph)](https://www.npmjs.com/package/@graph-tl/graph)
6
+
7
+ Graph gives agents session-to-session memory with actionable next steps.
4
8
 
5
9
  Graph is an MCP server that gives agents persistent memory across sessions. They decompose work into dependency trees, claim tasks, record evidence of what they did, and hand off to the next agent automatically.
6
10
 
@@ -35,7 +39,7 @@ Graph gives agents what they actually need:
35
39
  - **Dependencies with cycle detection** — the engine knows what's blocked and what's ready
36
40
  - **Server-side ranking** — one call to get the highest-priority actionable task
37
41
  - **Evidence trail** — agents record decisions, commits, and test results so the next agent inherits that knowledge
38
- - **~450 tokens** for a full claim-work-resolve cycle (vs ~5000+ with Linear MCP)
42
+ - **Minimal overhead** batched operations and structured responses keep token usage low
39
43
 
40
44
  ## How it works
41
45
 
@@ -160,22 +164,14 @@ Environment variables (all optional):
160
164
 
161
165
  ## Token efficiency
162
166
 
163
- | Operation | Tokens | Round trips |
164
- |---|---|---|
165
- | Onboard to a 30-task project | ~500 | 1 |
166
- | Plan 4 tasks with dependencies | ~220 | 1 |
167
- | Get next actionable task | ~300 | 1 |
168
- | Resolve + see what unblocked | ~120 | 1 |
169
- | **Full claim-work-resolve cycle** | **~450** | **3** |
170
-
171
- ~90% fewer tokens and ~50% fewer round trips vs traditional tracker MCP integrations.
167
+ Graph is designed to minimize agent overhead. Every operation is a single MCP call with structured, compact responses — no pagination, no field filtering, no extra round trips. Batched operations like `graph_plan` and `graph_update` let agents do more per call, and `graph_onboard` delivers full project context in one shot instead of requiring a sequence of queries.
172
168
 
173
169
  ## Data & security
174
170
 
175
- Graph is fully local. Your data never leaves your machine.
171
+ Your data stays on your machine.
176
172
 
177
173
  - **Single SQLite file** in `~/.graph/db/` — outside your repo, nothing to gitignore
178
- - **No network calls** — stdio MCP server, no telemetry, no cloud sync
174
+ - **Local-first** — stdio MCP server, no telemetry, no cloud sync. The only network activity is `npx` fetching the package
179
175
  - **No secrets stored** — task summaries, evidence notes, and file path references only
180
176
  - **You own your data** — back it up, delete it, move it between machines
181
177
 
@@ -23,6 +23,20 @@ Read the \`hint\` field first \u2014 it tells you exactly what to do next. Then
23
23
 
24
24
  **First-run:** If the tree is empty and discovery is \`"pending"\`, this is a brand new project. Jump directly to DISCOVER below. Do not call graph_next on an empty project.
25
25
 
26
+ **Drift check:** After onboarding, check for work done outside the graph:
27
+ 1. Run \`git log --oneline -10\` to see recent commits
28
+ 2. Compare against git evidence in the graph (commit hashes from resolved tasks)
29
+ 3. If there are commits not tracked in the graph, surface them to the user:
30
+ - "Found N commits not linked to any graph task: <list>"
31
+ - Ask: add retroactively (create node + evidence), or acknowledge and move on?
32
+
33
+ This catches work done ad-hoc or through plan files that bypassed the graph. It's cheap to run and prevents silent context loss.
34
+
35
+ **Continuity confidence:** \`graph_onboard\` returns a \`continuity_confidence\` signal (\`high\`/\`medium\`/\`low\`) with a score and reasons. This tells you how reliable the inherited context is.
36
+ - **high** (70-100): proceed normally
37
+ - **medium** (40-69): surface reasons to the user, proceed with caution
38
+ - **low** (0-39): STOP. Show the reasons. Ask the user to confirm before working. Low confidence means critical context may be missing.
39
+
26
40
  ## 2. DISCOVER (when discovery is pending)
27
41
  If the project root or a task node has \`discovery: "pending"\`, you must complete discovery before decomposing it. Discovery is an interview with the user to understand what needs to happen.
28
42
 
@@ -65,29 +79,36 @@ Execute the claimed task. While working:
65
79
  - Build and run tests before considering a task done
66
80
 
67
81
  ## 6. RESOLVE
68
- When done, resolve the task with evidence:
82
+ When done, resolve the task with a structured handoff. Every resolution should answer these questions for the next agent:
83
+
84
+ - **What changed** \u2014 what was modified or created
85
+ - **Why** \u2014 reasoning behind the approach taken
86
+ - **Evidence** \u2014 commits, test results, implementation notes
87
+ - **Next action** \u2014 what should happen next (if applicable)
88
+
69
89
  \`\`\`
70
90
  graph_update({ updates: [{
71
91
  node_id: "<task-id>",
72
92
  resolved: true,
73
93
  add_evidence: [
74
- { type: "note", ref: "What you did and why" },
94
+ { type: "note", ref: "Implemented X using Y because Z. Next: wire up the API endpoint." },
75
95
  { type: "git", ref: "<commit-hash> \u2014 <summary>" },
76
- { type: "test", ref: "Test results" }
96
+ { type: "test", ref: "All 155 tests passing" }
77
97
  ],
78
98
  add_context_links: ["path/to/files/you/touched"]
79
99
  }] })
80
100
  \`\`\`
81
- Evidence is mandatory. At minimum, include one note explaining what you did.
101
+
102
+ Evidence is mandatory. Write notes as if briefing an agent who has never seen the codebase \u2014 they should understand what was done and why without reading the code.
82
103
 
83
104
  ## 7. PAUSE
84
- After resolving a task, STOP. Tell the user:
85
- - What you just completed
86
- - What the next actionable task is
87
- - Wait for the user to say "continue" before claiming the next task
105
+ After resolving a task, STOP. Show the user the project status using \`graph_status\`, then wait for them to say "continue" before claiming the next task.
88
106
 
89
107
  The user controls the pace. Do not auto-claim the next task.
90
108
 
109
+ ## Presenting status
110
+ When showing project state to the user, always use \`graph_status({ project: "..." })\` and output the \`formatted\` field directly. This gives a consistent, readable view. Never format graph data manually \u2014 use the tool.
111
+
91
112
  # Rules
92
113
 
93
114
  - NEVER start work without a claimed task
@@ -98,6 +119,7 @@ The user controls the pace. Do not auto-claim the next task.
98
119
  - ALWAYS include context_links for files you modified when resolving
99
120
  - Parent nodes auto-resolve when all their children are resolved \u2014 you don't need to manually resolve them
100
121
  - NEVER skip discovery on nodes with discovery:pending \u2014 the system will block you from decomposing
122
+ - NEVER delete resolved projects \u2014 they are the historical record. Completed projects are lightweight and preserve traceability across sessions
101
123
  - If you're approaching context limits, ensure your current task's state is captured (update with evidence even if not fully resolved) so the next agent can pick up where you left off
102
124
 
103
125
  # Record observations proactively
@@ -154,4 +176,4 @@ function handleAgentConfig(version) {
154
176
  export {
155
177
  handleAgentConfig
156
178
  };
157
- //# sourceMappingURL=chunk-DUEP3T5A.js.map
179
+ //# sourceMappingURL=chunk-M5QOJN7D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools/agent-config.ts"],"sourcesContent":["// [sl:fV9I7Vel3xT5d_Ws2YHul] Subagent delivery — free for all (retention hook)\n\nfunction agentPrompt(version: string): string {\n return `---\nname: graph\nversion: ${version}\ndescription: Use this agent for tasks tracked in Graph. Enforces the claim-work-resolve workflow — always checks graph_next before working, adds new work to the graph before executing, and resolves with evidence.\ntools: Read, Edit, Write, Bash, Glob, Grep, Task(Explore), AskUserQuestion\nmodel: sonnet\n---\n\nYou are a graph-optimized agent. You execute tasks tracked in a Graph project. Follow this workflow strictly. The human directs, you execute through the graph.\n\n# Workflow\n\n## 1. ORIENT\nOn your first call, orient yourself:\n\\`\\`\\`\ngraph_onboard({ project: \"<project-name>\" })\n\\`\\`\\`\nRead the \\`hint\\` field first — it tells you exactly what to do next. Then read the summary, evidence, knowledge, and actionable tasks.\n\n**First-run:** If the tree is empty and discovery is \\`\"pending\"\\`, this is a brand new project. Jump directly to DISCOVER below. Do not call graph_next on an empty project.\n\n**Drift check:** After onboarding, check for work done outside the graph:\n1. Run \\`git log --oneline -10\\` to see recent commits\n2. Compare against git evidence in the graph (commit hashes from resolved tasks)\n3. If there are commits not tracked in the graph, surface them to the user:\n - \"Found N commits not linked to any graph task: <list>\"\n - Ask: add retroactively (create node + evidence), or acknowledge and move on?\n\nThis catches work done ad-hoc or through plan files that bypassed the graph. It's cheap to run and prevents silent context loss.\n\n**Continuity confidence:** \\`graph_onboard\\` returns a \\`continuity_confidence\\` signal (\\`high\\`/\\`medium\\`/\\`low\\`) with a score and reasons. This tells you how reliable the inherited context is.\n- **high** (70-100): proceed normally\n- **medium** (40-69): surface reasons to the user, proceed with caution\n- **low** (0-39): STOP. Show the reasons. Ask the user to confirm before working. Low confidence means critical context may be missing.\n\n## 2. DISCOVER (when discovery is pending)\nIf the project root or a task node has \\`discovery: \"pending\"\\`, you must complete discovery before decomposing it. Discovery is an interview with the user to understand what needs to happen.\n\nUse AskUserQuestion to cover these areas (adapt to what's relevant — skip what's obvious):\n- **Scope** — What exactly needs to happen? What's explicitly out of scope?\n- **Existing patterns** — How does the codebase currently handle similar things? (explore first, then confirm)\n- **Technical approach** — What libraries, APIs, or patterns should we use?\n- **Acceptance criteria** — How will we know it's done? What does success look like?\n\nAfter the interview:\n1. Write findings as knowledge: \\`graph_knowledge_write({ project, key: \"discovery-<topic>\", content: \"...\" })\\`\n2. Flip discovery to done: \\`graph_update({ updates: [{ node_id: \"<id>\", discovery: \"done\" }] })\\`\n3. NOW decompose with graph_plan\n\nDo NOT skip discovery. If you try to add children to a node with \\`discovery: \"pending\"\\`, graph_plan will reject it.\n\n## 3. CLAIM\nGet your next task:\n\\`\\`\\`\ngraph_next({ project: \"<project-name>\", claim: true })\n\\`\\`\\`\nRead the task summary, ancestor chain (for scope), resolved dependencies (for context on what was done before you), and context links (for files to look at).\n\n## 4. PLAN\nIf you discover work that isn't in the graph, add it BEFORE executing:\n\\`\\`\\`\ngraph_plan({ nodes: [{ ref: \"new-work\", parent_ref: \"<parent-id>\", summary: \"...\" }] })\n\\`\\`\\`\nNever execute ad-hoc work. The graph is the source of truth.\n\nWhen decomposing work:\n- Set dependencies on LEAF nodes, not parent nodes. If \"Page A\" depends on \"Layout\", the dependency is from \"Page A\" to \"Layout\", not from the \"Pages\" parent to \"Layout\".\n- Keep tasks small and specific. A task should be completable in one session.\n- Parent nodes are organizational — they resolve when all children resolve. Don't put work in parent nodes.\n\n## 5. WORK\nExecute the claimed task. While working:\n- Annotate key code changes with \\`// [sl:nodeId]\\` where nodeId is the task you're working on\n- This creates a traceable link from code back to the task, its evidence, and its history\n- Build and run tests before considering a task done\n\n## 6. RESOLVE\nWhen done, resolve the task with a structured handoff. Every resolution should answer these questions for the next agent:\n\n- **What changed** — what was modified or created\n- **Why** — reasoning behind the approach taken\n- **Evidence** — commits, test results, implementation notes\n- **Next action** — what should happen next (if applicable)\n\n\\`\\`\\`\ngraph_update({ updates: [{\n node_id: \"<task-id>\",\n resolved: true,\n add_evidence: [\n { type: \"note\", ref: \"Implemented X using Y because Z. Next: wire up the API endpoint.\" },\n { type: \"git\", ref: \"<commit-hash> — <summary>\" },\n { type: \"test\", ref: \"All 155 tests passing\" }\n ],\n add_context_links: [\"path/to/files/you/touched\"]\n}] })\n\\`\\`\\`\n\nEvidence is mandatory. Write notes as if briefing an agent who has never seen the codebase — they should understand what was done and why without reading the code.\n\n## 7. PAUSE\nAfter resolving a task, STOP. Show the user the project status using \\`graph_status\\`, then wait for them to say \"continue\" before claiming the next task.\n\nThe user controls the pace. Do not auto-claim the next task.\n\n## Presenting status\nWhen showing project state to the user, always use \\`graph_status({ project: \"...\" })\\` and output the \\`formatted\\` field directly. This gives a consistent, readable view. Never format graph data manually — use the tool.\n\n# Rules\n\n- NEVER start work without a claimed task\n- NEVER resolve without evidence\n- NEVER execute ad-hoc work — add it to the graph first via graph_plan\n- NEVER auto-continue to the next task — pause and let the user decide\n- ALWAYS build and test before resolving\n- ALWAYS include context_links for files you modified when resolving\n- Parent nodes auto-resolve when all their children are resolved — you don't need to manually resolve them\n- NEVER skip discovery on nodes with discovery:pending — the system will block you from decomposing\n- NEVER delete resolved projects — they are the historical record. Completed projects are lightweight and preserve traceability across sessions\n- If you're approaching context limits, ensure your current task's state is captured (update with evidence even if not fully resolved) so the next agent can pick up where you left off\n\n# Record observations proactively\n\nGraph is the project memory across sessions. If something isn't in Graph, it's effectively forgotten. While working, record things you notice — even if they're not part of your current task:\n\n- **Warnings & errors**: CI failures, deprecation warnings, security vulnerabilities, linter issues\n- **Tech debt**: Code smells, outdated dependencies, missing tests, hardcoded values\n- **Broken things**: Flaky tests, dead links, misconfigured environments\n- **Ideas & improvements**: Performance opportunities, UX issues, missing features\n\nUse \\`graph_plan\\` to add observation nodes under the project root. Keep them lightweight — a clear summary is enough. They can always be dropped later if irrelevant.\n\nDefault to \"if in doubt, add a node.\" It's cheap to create and the next session will thank you.\n\n# Blocked status\n\nNodes can be manually blocked (separate from dependency-blocked). Use this for external blockers:\n\n\\`\\`\\`\ngraph_update({ updates: [{\n node_id: \"<id>\",\n blocked: true,\n blocked_reason: \"Waiting on API key from client\"\n}] })\n\\`\\`\\`\n\nTo unblock: \\`graph_update({ updates: [{ node_id: \"<id>\", blocked: false }] })\\`\n\n- \\`blocked_reason\\` is required when setting \\`blocked: true\\`\n- Blocked nodes won't appear in \\`graph_next\\` results\n- Unblocking auto-clears the reason\n- Use this for things like: waiting on external input, upstream API down, needs design review\n\n# Common mistakes to avoid\n\n- Setting dependencies on parent nodes instead of leaf nodes\n- Running project scaffolding tools (create-next-app, etc.) before planning in the graph\n- Resolving tasks without running tests\n- Doing work that isn't tracked in the graph\n- Continuing to the next task without pausing for user review\n- Trying to decompose a node without completing discovery first\n- Not writing knowledge entries during discovery — future agents need this context\n`;\n}\n\nexport interface AgentConfigResult {\n agent_file: string;\n install_path: string;\n instructions: string;\n}\n\nexport function handleAgentConfig(version: string): AgentConfigResult {\n return {\n agent_file: agentPrompt(version),\n install_path: \".claude/agents/graph.md\",\n instructions:\n \"Save the agent_file content to .claude/agents/graph.md in your project root. \" +\n \"Claude Code will automatically discover it and use it when tasks match the agent description.\",\n };\n}\n"],"mappings":";;;AAEA,SAAS,YAAY,SAAyB;AAC5C,SAAO;AAAA;AAAA,WAEE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgKlB;AAQO,SAAS,kBAAkB,SAAoC;AACpE,SAAO;AAAA,IACL,YAAY,YAAY,OAAO;AAAA,IAC/B,cAAc;AAAA,IACd,cACE;AAAA,EAEJ;AACF;","names":[]}
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ if (args[0] === "activate") {
6
6
  const { activate } = await import("./activate-DSDTR2EJ.js");
7
7
  activate(args[1]);
8
8
  } else if (args[0] === "init") {
9
- const { init } = await import("./init-YFC5JVM5.js");
9
+ const { init } = await import("./init-74TF4PX3.js");
10
10
  init();
11
11
  } else {
12
- const { startServer } = await import("./server-SNA2JYGG.js");
12
+ const { startServer } = await import("./server-XSKUM3G5.js");
13
13
  startServer().catch((error) => {
14
14
  console.error("Failed to start graph:", error);
15
15
  process.exit(1);
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleAgentConfig
4
- } from "./chunk-DUEP3T5A.js";
4
+ } from "./chunk-M5QOJN7D.js";
5
5
 
6
6
  // src/init.ts
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from "fs";
8
8
  import { join, dirname } from "path";
9
9
  import { fileURLToPath } from "url";
10
10
  var PKG_VERSION = "0.0.0";
@@ -63,6 +63,29 @@ function init() {
63
63
  console.log("\u2713 .claude/agents/graph.md \u2014 created graph workflow agent");
64
64
  wrote = true;
65
65
  }
66
+ const claudeMdPath = join(cwd, "CLAUDE.md");
67
+ const graphSection = `
68
+ ## Graph workflow
69
+
70
+ This project uses Graph for task tracking across sessions. Start every session with \`graph_onboard\` to see project state, actionable tasks, and continuity confidence. Follow the claim-work-resolve loop: \`graph_next\` (claim) \u2192 do work \u2192 \`graph_update\` (resolve with evidence). Don't execute ad-hoc work \u2014 add it to the graph first via \`graph_plan\`.
71
+ `;
72
+ if (existsSync(claudeMdPath)) {
73
+ const current = readFileSync(claudeMdPath, "utf8");
74
+ if (current.includes("graph_onboard")) {
75
+ console.log("\u2713 CLAUDE.md \u2014 graph workflow instructions already present");
76
+ } else {
77
+ appendFileSync(claudeMdPath, graphSection, "utf8");
78
+ console.log("\u2713 CLAUDE.md \u2014 appended graph workflow instructions");
79
+ wrote = true;
80
+ }
81
+ } else {
82
+ writeFileSync(claudeMdPath, `# CLAUDE.md
83
+
84
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
85
+ ${graphSection}`, "utf8");
86
+ console.log("\u2713 CLAUDE.md \u2014 created with graph workflow instructions");
87
+ wrote = true;
88
+ }
66
89
  console.log("");
67
90
  if (wrote) {
68
91
  console.log("Graph is ready. Restart Claude Code to load the MCP server.");
@@ -76,4 +99,4 @@ function init() {
76
99
  export {
77
100
  init
78
101
  };
79
- //# sourceMappingURL=init-YFC5JVM5.js.map
102
+ //# sourceMappingURL=init-74TF4PX3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/init.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from \"fs\";\nimport { join, dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { handleAgentConfig } from \"./tools/agent-config.js\";\n\n// [sl:hy8oXisWnrZN1BfkonUqd] npx @graph-tl/graph init — zero friction onboarding\n\nlet PKG_VERSION = \"0.0.0\";\ntry {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"));\n PKG_VERSION = pkg.version;\n} catch {}\n\nconst MCP_CONFIG = {\n command: \"npx\",\n args: [\"-y\", \"@graph-tl/graph\"],\n env: {\n GRAPH_AGENT: \"claude-code\",\n },\n};\n\nexport function init(): void {\n const cwd = process.cwd();\n let wrote = false;\n\n // 1. Write .mcp.json\n const configPath = join(cwd, \".mcp.json\");\n if (existsSync(configPath)) {\n try {\n const config = JSON.parse(readFileSync(configPath, \"utf8\"));\n if (config.mcpServers?.graph) {\n console.log(\"✓ .mcp.json — graph already configured\");\n } else {\n config.mcpServers = config.mcpServers ?? {};\n config.mcpServers.graph = MCP_CONFIG;\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf8\");\n console.log(\"✓ .mcp.json — added graph server\");\n wrote = true;\n }\n } catch {\n console.error(`✗ .mcp.json exists but is not valid JSON — skipping`);\n }\n } else {\n const config = { mcpServers: { graph: MCP_CONFIG } };\n writeFileSync(configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf8\");\n console.log(\"✓ .mcp.json — created with graph server\");\n wrote = true;\n }\n\n // 2. Write .claude/agents/graph.md\n const agentPath = join(cwd, \".claude\", \"agents\", \"graph.md\");\n const { agent_file } = handleAgentConfig(PKG_VERSION);\n if (existsSync(agentPath)) {\n const current = readFileSync(agentPath, \"utf8\");\n if (current === agent_file) {\n console.log(\"✓ .claude/agents/graph.md — already up to date\");\n } else {\n writeFileSync(agentPath, agent_file, \"utf8\");\n console.log(\"✓ .claude/agents/graph.md — updated\");\n wrote = true;\n }\n } else {\n mkdirSync(dirname(agentPath), { recursive: true });\n writeFileSync(agentPath, agent_file, \"utf8\");\n console.log(\"✓ .claude/agents/graph.md — created graph workflow agent\");\n wrote = true;\n }\n\n // 3. Append graph workflow instructions to CLAUDE.md\n // [sl:qPxNQTKru6q3nPzsNWlfe] Ensure default agent follows graph workflow\n const claudeMdPath = join(cwd, \"CLAUDE.md\");\n const graphSection = `\n## Graph workflow\n\nThis project uses Graph for task tracking across sessions. Start every session with \\`graph_onboard\\` to see project state, actionable tasks, and continuity confidence. Follow the claim-work-resolve loop: \\`graph_next\\` (claim) → do work → \\`graph_update\\` (resolve with evidence). Don't execute ad-hoc work — add it to the graph first via \\`graph_plan\\`.\n`;\n\n if (existsSync(claudeMdPath)) {\n const current = readFileSync(claudeMdPath, \"utf8\");\n if (current.includes(\"graph_onboard\")) {\n console.log(\"✓ CLAUDE.md — graph workflow instructions already present\");\n } else {\n appendFileSync(claudeMdPath, graphSection, \"utf8\");\n console.log(\"✓ CLAUDE.md — appended graph workflow instructions\");\n wrote = true;\n }\n } else {\n writeFileSync(claudeMdPath, `# CLAUDE.md\\n\\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\\n${graphSection}`, \"utf8\");\n console.log(\"✓ CLAUDE.md — created with graph workflow instructions\");\n wrote = true;\n }\n\n // 4. Summary\n console.log(\"\");\n if (wrote) {\n console.log(\"Graph is ready. Restart Claude Code to load the MCP server.\");\n console.log(\"\");\n console.log(\"Then try:\");\n console.log(' \"Use graph to plan building a REST API with auth and tests.\"');\n } else {\n console.log(\"Graph is already set up — nothing to do.\");\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,cAAc,eAAe,YAAY,WAAW,sBAAsB;AACnF,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAK9B,IAAI,cAAc;AAClB,IAAI;AACF,QAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,QAAM,MAAM,KAAK,MAAM,aAAa,KAAK,WAAW,MAAM,cAAc,GAAG,OAAO,CAAC;AACnF,gBAAc,IAAI;AACpB,QAAQ;AAAC;AAET,IAAM,aAAa;AAAA,EACjB,SAAS;AAAA,EACT,MAAM,CAAC,MAAM,iBAAiB;AAAA,EAC9B,KAAK;AAAA,IACH,aAAa;AAAA,EACf;AACF;AAEO,SAAS,OAAa;AAC3B,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,QAAQ;AAGZ,QAAM,aAAa,KAAK,KAAK,WAAW;AACxC,MAAI,WAAW,UAAU,GAAG;AAC1B,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,aAAa,YAAY,MAAM,CAAC;AAC1D,UAAI,OAAO,YAAY,OAAO;AAC5B,gBAAQ,IAAI,kDAAwC;AAAA,MACtD,OAAO;AACL,eAAO,aAAa,OAAO,cAAc,CAAC;AAC1C,eAAO,WAAW,QAAQ;AAC1B,sBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,MAAM;AACxE,gBAAQ,IAAI,4CAAkC;AAC9C,gBAAQ;AAAA,MACV;AAAA,IACF,QAAQ;AACN,cAAQ,MAAM,+DAAqD;AAAA,IACrE;AAAA,EACF,OAAO;AACL,UAAM,SAAS,EAAE,YAAY,EAAE,OAAO,WAAW,EAAE;AACnD,kBAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,MAAM;AACxE,YAAQ,IAAI,mDAAyC;AACrD,YAAQ;AAAA,EACV;AAGA,QAAM,YAAY,KAAK,KAAK,WAAW,UAAU,UAAU;AAC3D,QAAM,EAAE,WAAW,IAAI,kBAAkB,WAAW;AACpD,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,UAAU,aAAa,WAAW,MAAM;AAC9C,QAAI,YAAY,YAAY;AAC1B,cAAQ,IAAI,0DAAgD;AAAA,IAC9D,OAAO;AACL,oBAAc,WAAW,YAAY,MAAM;AAC3C,cAAQ,IAAI,+CAAqC;AACjD,cAAQ;AAAA,IACV;AAAA,EACF,OAAO;AACL,cAAU,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,kBAAc,WAAW,YAAY,MAAM;AAC3C,YAAQ,IAAI,oEAA0D;AACtE,YAAQ;AAAA,EACV;AAIA,QAAM,eAAe,KAAK,KAAK,WAAW;AAC1C,QAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAMrB,MAAI,WAAW,YAAY,GAAG;AAC5B,UAAM,UAAU,aAAa,cAAc,MAAM;AACjD,QAAI,QAAQ,SAAS,eAAe,GAAG;AACrC,cAAQ,IAAI,qEAA2D;AAAA,IACzE,OAAO;AACL,qBAAe,cAAc,cAAc,MAAM;AACjD,cAAQ,IAAI,8DAAoD;AAChE,cAAQ;AAAA,IACV;AAAA,EACF,OAAO;AACL,kBAAc,cAAc;AAAA;AAAA;AAAA,EAA0H,YAAY,IAAI,MAAM;AAC5K,YAAQ,IAAI,kEAAwD;AACpE,YAAQ;AAAA,EACV;AAGA,UAAQ,IAAI,EAAE;AACd,MAAI,OAAO;AACT,YAAQ,IAAI,6DAA6D;AACzE,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,WAAW;AACvB,YAAQ,IAAI,gEAAgE;AAAA,EAC9E,OAAO;AACL,YAAQ,IAAI,+CAA0C;AAAA,EACxD;AACF;","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-WKOEKYTF.js";
5
5
  import {
6
6
  handleAgentConfig
7
- } from "./chunk-DUEP3T5A.js";
7
+ } from "./chunk-M5QOJN7D.js";
8
8
  import {
9
9
  EngineError,
10
10
  ValidationError,
@@ -899,7 +899,19 @@ function handleDrop(op, agent) {
899
899
  }
900
900
  function handleDelete(op, agent) {
901
901
  const db = getDb();
902
- getNodeOrThrow(op.node_id);
902
+ const node = getNodeOrThrow(op.node_id);
903
+ if (node.parent === null) {
904
+ const evidenceCount = db.prepare(
905
+ `SELECT COUNT(*) as cnt FROM nodes
906
+ WHERE project = ? AND evidence != '[]'`
907
+ ).get(node.project);
908
+ if (evidenceCount.cnt > 0) {
909
+ throw new EngineError(
910
+ "delete_protected",
911
+ `Cannot delete project "${node.project}" \u2014 it contains ${evidenceCount.cnt} node(s) with evidence. Resolved projects preserve traceability across sessions. Use "drop" to mark as resolved instead.`
912
+ );
913
+ }
914
+ }
903
915
  const descendants = getAllDescendants(op.node_id);
904
916
  const allIds = [op.node_id, ...descendants];
905
917
  const placeholders = allIds.map(() => "?").join(",");
@@ -994,6 +1006,93 @@ function handleHistory(input) {
994
1006
  return result;
995
1007
  }
996
1008
 
1009
+ // src/continuity.ts
1010
+ function getProjectStats(project) {
1011
+ const db = getDb();
1012
+ const resolved = db.prepare(
1013
+ "SELECT COUNT(*) as cnt FROM nodes WHERE project = ? AND parent IS NOT NULL AND resolved = 1"
1014
+ ).get(project);
1015
+ const withEvidence = db.prepare(
1016
+ "SELECT COUNT(*) as cnt FROM nodes WHERE project = ? AND parent IS NOT NULL AND resolved = 1 AND evidence != '[]'"
1017
+ ).get(project);
1018
+ const lastAct = db.prepare(
1019
+ "SELECT MAX(updated_at) as last FROM nodes WHERE project = ?"
1020
+ ).get(project);
1021
+ const knowledge = db.prepare(
1022
+ "SELECT COUNT(*) as cnt FROM knowledge WHERE project = ?"
1023
+ ).get(project);
1024
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
1025
+ const staleBlocked = db.prepare(
1026
+ "SELECT COUNT(*) as cnt FROM nodes WHERE project = ? AND blocked = 1 AND updated_at < ?"
1027
+ ).get(project, sevenDaysAgo);
1028
+ const total = db.prepare(
1029
+ "SELECT COUNT(*) as cnt FROM nodes WHERE project = ? AND parent IS NOT NULL"
1030
+ ).get(project);
1031
+ return {
1032
+ resolvedCount: resolved.cnt,
1033
+ resolvedWithEvidence: withEvidence.cnt,
1034
+ lastActivity: lastAct.last,
1035
+ knowledgeCount: knowledge.cnt,
1036
+ staleBlockedCount: staleBlocked.cnt,
1037
+ totalNonRoot: total.cnt
1038
+ };
1039
+ }
1040
+ function computeContinuityConfidence(project) {
1041
+ const stats = getProjectStats(project);
1042
+ const reasons = [];
1043
+ let score = 100;
1044
+ if (stats.resolvedCount > 0) {
1045
+ const coverage = stats.resolvedWithEvidence / stats.resolvedCount;
1046
+ const missing = stats.resolvedCount - stats.resolvedWithEvidence;
1047
+ if (coverage < 0.5) {
1048
+ score -= 40;
1049
+ reasons.push(`${missing} of ${stats.resolvedCount} resolved tasks have no evidence`);
1050
+ } else if (coverage < 0.8) {
1051
+ score -= 20;
1052
+ reasons.push(`${missing} of ${stats.resolvedCount} resolved tasks have no evidence`);
1053
+ } else if (coverage < 1) {
1054
+ score -= 10;
1055
+ reasons.push(`${missing} resolved task(s) missing evidence`);
1056
+ }
1057
+ }
1058
+ if (stats.lastActivity) {
1059
+ const ageMs = Date.now() - new Date(stats.lastActivity).getTime();
1060
+ const ageDays = ageMs / (24 * 60 * 60 * 1e3);
1061
+ if (ageDays > 14) {
1062
+ score -= 25;
1063
+ reasons.push(`No activity for ${Math.floor(ageDays)} days`);
1064
+ } else if (ageDays > 7) {
1065
+ score -= 15;
1066
+ reasons.push(`No activity for ${Math.floor(ageDays)} days`);
1067
+ } else if (ageDays > 3) {
1068
+ score -= 5;
1069
+ reasons.push(`Last activity ${Math.floor(ageDays)} days ago`);
1070
+ }
1071
+ }
1072
+ if (stats.resolvedCount >= 5 && stats.knowledgeCount === 0) {
1073
+ score -= 15;
1074
+ reasons.push("No knowledge entries on a project with 5+ resolved tasks");
1075
+ }
1076
+ if (stats.staleBlockedCount > 0) {
1077
+ score -= 10;
1078
+ reasons.push(`${stats.staleBlockedCount} blocked item(s) not updated in 7+ days`);
1079
+ }
1080
+ if (stats.totalNonRoot === 0) {
1081
+ score -= 10;
1082
+ reasons.push("Project has no tasks yet");
1083
+ }
1084
+ score = Math.max(0, Math.min(100, score));
1085
+ let confidence;
1086
+ if (score >= 70) {
1087
+ confidence = "high";
1088
+ } else if (score >= 40) {
1089
+ confidence = "medium";
1090
+ } else {
1091
+ confidence = "low";
1092
+ }
1093
+ return { confidence, score, reasons };
1094
+ }
1095
+
997
1096
  // src/tools/onboard.ts
998
1097
  function handleOnboard(input) {
999
1098
  const evidenceLimit = optionalNumber(input?.evidence_limit, "evidence_limit", 1, 50) ?? 20;
@@ -1122,6 +1221,7 @@ function handleOnboard(input) {
1122
1221
  } else if (summary.total <= 1 && root.discovery !== "pending") {
1123
1222
  hint = `Project is empty \u2014 use graph_plan to decompose the goal into tasks.`;
1124
1223
  }
1224
+ const continuity_confidence = computeContinuityConfidence(project);
1125
1225
  return {
1126
1226
  project,
1127
1227
  goal: root.summary,
@@ -1134,6 +1234,7 @@ function handleOnboard(input) {
1134
1234
  knowledge: knowledgeRows,
1135
1235
  recently_resolved,
1136
1236
  last_activity,
1237
+ continuity_confidence,
1137
1238
  actionable
1138
1239
  };
1139
1240
  }
@@ -1189,6 +1290,173 @@ function handleTree(input) {
1189
1290
  };
1190
1291
  }
1191
1292
 
1293
+ // src/tools/status.ts
1294
+ function statusIcon(entry) {
1295
+ if (entry.resolved) return "x";
1296
+ if (entry.blocked) return "!";
1297
+ if (entry.dep_blocked) return "~";
1298
+ return " ";
1299
+ }
1300
+ function progressBar(resolved, total, width = 20) {
1301
+ if (total === 0) return "";
1302
+ const clamped = Math.min(resolved, total);
1303
+ const filled = Math.round(clamped / total * width);
1304
+ const empty = width - filled;
1305
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
1306
+ const pct = Math.round(resolved / total * 100);
1307
+ return `${bar} ${resolved}/${total} (${pct}%)`;
1308
+ }
1309
+ function timeAgo(isoDate) {
1310
+ const diff = Date.now() - new Date(isoDate).getTime();
1311
+ const mins = Math.floor(diff / 6e4);
1312
+ if (mins < 1) return "just now";
1313
+ if (mins < 60) return `${mins}m ago`;
1314
+ const hours = Math.floor(mins / 60);
1315
+ if (hours < 24) return `${hours}h ago`;
1316
+ const days = Math.floor(hours / 24);
1317
+ if (days === 1) return "yesterday";
1318
+ return `${days}d ago`;
1319
+ }
1320
+ function handleStatus(input) {
1321
+ const db = getDb();
1322
+ let project = optionalString(input?.project, "project");
1323
+ if (!project) {
1324
+ const projects = listProjects();
1325
+ if (projects.length === 0) {
1326
+ return {
1327
+ projects: [],
1328
+ hint: 'No projects yet. Create one with graph_open({ project: "my-project", goal: "..." }).'
1329
+ };
1330
+ }
1331
+ if (projects.length === 1) {
1332
+ project = projects[0].project;
1333
+ } else {
1334
+ const lines2 = ["# All Projects", ""];
1335
+ for (const p of projects) {
1336
+ const taskCount2 = p.total > 0 ? p.total - 1 : 0;
1337
+ const resolvedTasks2 = Math.min(p.resolved, taskCount2);
1338
+ const bar = taskCount2 > 0 ? progressBar(resolvedTasks2, taskCount2) : "empty";
1339
+ lines2.push(`**${p.project}** ${bar}`);
1340
+ lines2.push(` ${p.summary}`);
1341
+ lines2.push("");
1342
+ }
1343
+ lines2.push("_Specify a project name for details._");
1344
+ return { projects, hint: lines2.join("\n") };
1345
+ }
1346
+ }
1347
+ const root = getProjectRoot(project);
1348
+ if (!root) {
1349
+ throw new EngineError("project_not_found", `Project not found: ${project}`);
1350
+ }
1351
+ const summary = getProjectSummary(project);
1352
+ const rows = db.prepare(
1353
+ `SELECT n.id, n.parent, n.summary, n.resolved, n.blocked, n.blocked_reason, n.depth,
1354
+ (SELECT COUNT(*) FROM nodes c WHERE c.parent = n.id) as child_count,
1355
+ (SELECT COUNT(*) FROM nodes c WHERE c.parent = n.id AND c.resolved = 1) as resolved_children,
1356
+ (SELECT COUNT(*) FROM edges e
1357
+ JOIN nodes dep ON dep.id = e.to_node AND dep.resolved = 0
1358
+ WHERE e.from_node = n.id AND e.type = 'depends_on') as unresolved_deps
1359
+ FROM nodes n
1360
+ WHERE n.project = ? AND n.parent IS NOT NULL
1361
+ ORDER BY n.depth ASC, n.created_at ASC`
1362
+ ).all(project);
1363
+ const entries = rows.map((r) => ({
1364
+ id: r.id,
1365
+ parent: r.parent,
1366
+ summary: r.summary,
1367
+ resolved: r.resolved === 1,
1368
+ blocked: r.blocked === 1,
1369
+ blocked_reason: r.blocked_reason,
1370
+ depth: r.depth - 1,
1371
+ // relative to root
1372
+ child_count: r.child_count,
1373
+ dep_blocked: r.unresolved_deps > 0,
1374
+ resolved_children: r.resolved_children,
1375
+ total_children: r.child_count
1376
+ }));
1377
+ const lines = [];
1378
+ const taskCount = summary.total - 1;
1379
+ lines.push(`# ${project}`);
1380
+ lines.push("");
1381
+ lines.push(root.summary);
1382
+ lines.push("");
1383
+ const resolvedTasks = Math.min(summary.resolved, taskCount);
1384
+ if (taskCount > 0) {
1385
+ lines.push(progressBar(resolvedTasks, taskCount));
1386
+ const cc = computeContinuityConfidence(project);
1387
+ const ccBars = Math.ceil(cc.score / 20);
1388
+ const ccDisplay = "\u25A0".repeat(ccBars) + "\u25A1".repeat(5 - ccBars);
1389
+ lines.push(`${summary.actionable} actionable | ${summary.blocked} blocked | ${summary.unresolved - summary.blocked - summary.actionable} waiting | continuity confidence: ${cc.confidence} ${ccDisplay}`);
1390
+ if (cc.reasons.length > 0 && cc.confidence !== "high") {
1391
+ for (const reason of cc.reasons) {
1392
+ lines.push(` - ${reason}`);
1393
+ }
1394
+ }
1395
+ } else {
1396
+ lines.push("No tasks yet");
1397
+ }
1398
+ lines.push("");
1399
+ if (entries.length > 0) {
1400
+ lines.push("## Tasks");
1401
+ lines.push("");
1402
+ for (const entry of entries) {
1403
+ const icon = statusIcon(entry);
1404
+ const prefix = " ".repeat(entry.depth);
1405
+ let line = `${prefix}[${icon}] ${entry.summary}`;
1406
+ if (entry.child_count > 0) {
1407
+ const pct = Math.round(entry.resolved_children / entry.total_children * 100);
1408
+ line += ` (${entry.resolved_children}/${entry.total_children} \u2014 ${pct}%)`;
1409
+ }
1410
+ if (entry.blocked && entry.blocked_reason) {
1411
+ line += `
1412
+ ${prefix} ^ ${entry.blocked_reason}`;
1413
+ }
1414
+ lines.push(line);
1415
+ }
1416
+ lines.push("");
1417
+ }
1418
+ const recentEvents = db.prepare(
1419
+ `SELECT e.node_id, e.agent, e.action, e.timestamp, n.summary as node_summary
1420
+ FROM events e
1421
+ JOIN nodes n ON n.id = e.node_id
1422
+ WHERE n.project = ?
1423
+ ORDER BY e.timestamp DESC
1424
+ LIMIT 5`
1425
+ ).all(project);
1426
+ if (recentEvents.length > 0) {
1427
+ lines.push("## Recent Activity");
1428
+ lines.push("");
1429
+ for (const ev of recentEvents) {
1430
+ lines.push(`- ${ev.action} **${ev.node_summary}** (${ev.agent}, ${timeAgo(ev.timestamp)})`);
1431
+ }
1432
+ lines.push("");
1433
+ }
1434
+ const blocked = entries.filter((e) => e.blocked && e.blocked_reason);
1435
+ if (blocked.length > 0) {
1436
+ lines.push("## Blocked");
1437
+ lines.push("");
1438
+ for (const b of blocked) {
1439
+ lines.push(`- **${b.summary}** \u2014 ${b.blocked_reason}`);
1440
+ }
1441
+ lines.push("");
1442
+ }
1443
+ const knowledge = db.prepare(
1444
+ "SELECT key, updated_at FROM knowledge WHERE project = ? ORDER BY updated_at DESC"
1445
+ ).all(project);
1446
+ if (knowledge.length > 0) {
1447
+ lines.push("## Knowledge");
1448
+ lines.push("");
1449
+ for (const k of knowledge) {
1450
+ lines.push(`- ${k.key} (${timeAgo(k.updated_at)})`);
1451
+ }
1452
+ lines.push("");
1453
+ }
1454
+ return {
1455
+ formatted: lines.join("\n").trimEnd(),
1456
+ project
1457
+ };
1458
+ }
1459
+
1192
1460
  // src/tools/knowledge.ts
1193
1461
  import { nanoid as nanoid2 } from "nanoid";
1194
1462
  function handleKnowledgeWrite(input, agent) {
@@ -1663,6 +1931,19 @@ var TOOLS = [
1663
1931
  required: ["project"]
1664
1932
  }
1665
1933
  },
1934
+ {
1935
+ name: "graph_status",
1936
+ description: "Returns a pre-formatted markdown summary of a project's current state. Use this to present project status to the user \u2014 output the `formatted` field directly. Shows task tree with status tags, actionable items, blocked items, and knowledge entries. Omit project to auto-select or get multi-project overview.",
1937
+ inputSchema: {
1938
+ type: "object",
1939
+ properties: {
1940
+ project: {
1941
+ type: "string",
1942
+ description: "Project name. Omit to auto-select (works when there's exactly one project)."
1943
+ }
1944
+ }
1945
+ }
1946
+ },
1666
1947
  {
1667
1948
  name: "graph_agent_config",
1668
1949
  description: "Returns the graph-optimized agent configuration file for Claude Code. Save the returned content to .claude/agents/graph.md to enable the graph workflow agent.",
@@ -1800,6 +2081,9 @@ async function startServer() {
1800
2081
  case "graph_tree":
1801
2082
  result = handleTree(args);
1802
2083
  break;
2084
+ case "graph_status":
2085
+ result = handleStatus(args);
2086
+ break;
1803
2087
  case "graph_agent_config":
1804
2088
  result = handleAgentConfig(PKG_VERSION);
1805
2089
  break;
@@ -1949,4 +2233,4 @@ async function startServer() {
1949
2233
  export {
1950
2234
  startServer
1951
2235
  };
1952
- //# sourceMappingURL=server-SNA2JYGG.js.map
2236
+ //# sourceMappingURL=server-XSKUM3G5.js.map