@graph-tl/graph 0.1.6 → 0.1.8

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.
@@ -2,6 +2,9 @@
2
2
  import {
3
3
  getLicenseTier
4
4
  } from "./chunk-WKOEKYTF.js";
5
+ import {
6
+ handleAgentConfig
7
+ } from "./chunk-ILTJI4ZN.js";
5
8
  import {
6
9
  EngineError,
7
10
  ValidationError,
@@ -25,7 +28,7 @@ import {
25
28
  requireString,
26
29
  setDbPath,
27
30
  updateNode
28
- } from "./chunk-NWCUIW6D.js";
31
+ } from "./chunk-TWT5GUXW.js";
29
32
 
30
33
  // src/server.ts
31
34
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -43,15 +46,28 @@ function handleOpen(input, agent) {
43
46
  return { projects: listProjects() };
44
47
  }
45
48
  let root = getProjectRoot(project);
49
+ let isNew = false;
46
50
  if (!root) {
47
51
  root = createNode({
48
52
  project,
49
53
  summary: goal ?? project,
54
+ discovery: "pending",
50
55
  agent
51
56
  });
57
+ isNew = true;
52
58
  }
53
59
  const summary = getProjectSummary(project);
54
- return { project, root, summary };
60
+ const result = { project, root, summary };
61
+ if (isNew) {
62
+ result.hint = `New project created. Discovery is pending \u2014 interview the user to understand scope and goals, then set discovery to "done" via graph_update before decomposing with graph_plan.`;
63
+ } else if (root.discovery === "pending") {
64
+ result.hint = `Discovery is still pending on this project. Complete the discovery interview, then set discovery to "done" via graph_update.`;
65
+ } else if (summary.actionable > 0) {
66
+ result.hint = `${summary.actionable} actionable task(s). Use graph_next to claim one.`;
67
+ } else if (summary.unresolved > 0 && summary.actionable === 0) {
68
+ result.hint = `All remaining tasks are blocked. Check dependencies with graph_query.`;
69
+ }
70
+ return result;
55
71
  }
56
72
 
57
73
  // src/edges.ts
@@ -220,6 +236,12 @@ function handlePlan(input, agent) {
220
236
  let project;
221
237
  if (parentId) {
222
238
  const parentNode = getNode(parentId);
239
+ if (parentNode.discovery === "pending") {
240
+ throw new EngineError(
241
+ "discovery_pending",
242
+ `Cannot add children to "${parentNode.summary}" \u2014 discovery is pending. Complete the discovery interview first (set discovery to 'done' via graph_update), then decompose.`
243
+ );
244
+ }
223
245
  project = parentNode.project;
224
246
  } else {
225
247
  throw new Error(
@@ -290,6 +312,7 @@ function handleUpdate(input, agent) {
290
312
  node_id: entry.node_id,
291
313
  agent,
292
314
  resolved: entry.resolved,
315
+ discovery: entry.discovery,
293
316
  state: entry.state,
294
317
  summary: entry.summary,
295
318
  properties: entry.properties,
@@ -303,10 +326,42 @@ function handleUpdate(input, agent) {
303
326
  project = node.project;
304
327
  }
305
328
  }
329
+ const autoResolved = [];
330
+ if (resolvedIds.length > 0) {
331
+ const seen = new Set(resolvedIds);
332
+ const queue = [...resolvedIds];
333
+ while (queue.length > 0) {
334
+ const nodeId = queue.shift();
335
+ const node = getNode(nodeId);
336
+ if (!node?.parent) continue;
337
+ const parentId = node.parent;
338
+ if (seen.has(parentId)) continue;
339
+ seen.add(parentId);
340
+ const parent = getNode(parentId);
341
+ if (!parent || parent.resolved) continue;
342
+ const children = getChildren(parentId);
343
+ if (children.length === 0) continue;
344
+ if (children.every((c) => c.resolved)) {
345
+ const resolved = updateNode({
346
+ node_id: parentId,
347
+ agent,
348
+ resolved: true,
349
+ add_evidence: [{ type: "note", ref: "Auto-resolved: all children completed" }]
350
+ });
351
+ updated.push({ node_id: resolved.id, rev: resolved.rev });
352
+ resolvedIds.push(parentId);
353
+ autoResolved.push({ node_id: parentId, summary: parent.summary });
354
+ queue.push(parentId);
355
+ }
356
+ }
357
+ }
306
358
  const result = { updated };
307
359
  if (resolvedIds.length > 0 && project) {
308
360
  result.newly_actionable = findNewlyActionable(project, resolvedIds);
309
361
  }
362
+ if (autoResolved.length > 0) {
363
+ result.auto_resolved = autoResolved;
364
+ }
310
365
  return result;
311
366
  }
312
367
 
@@ -373,6 +428,7 @@ function buildNodeTree(nodeId, currentDepth, maxDepth) {
373
428
  id: node.id,
374
429
  summary: node.summary,
375
430
  resolved: node.resolved,
431
+ discovery: node.discovery,
376
432
  state: node.state
377
433
  };
378
434
  if (children.length === 0) {
@@ -866,9 +922,26 @@ function handleHistory(input) {
866
922
 
867
923
  // src/tools/onboard.ts
868
924
  function handleOnboard(input) {
869
- const project = requireString(input?.project, "project");
870
925
  const evidenceLimit = optionalNumber(input?.evidence_limit, "evidence_limit", 1, 50) ?? 20;
871
926
  const db = getDb();
927
+ let project = optionalString(input?.project, "project");
928
+ if (!project) {
929
+ const projects = listProjects();
930
+ if (projects.length === 0) {
931
+ return {
932
+ projects: [],
933
+ hint: 'No projects yet. Create one with graph_open({ project: "my-project", goal: "..." }).'
934
+ };
935
+ }
936
+ if (projects.length === 1) {
937
+ project = projects[0].project;
938
+ } else {
939
+ return {
940
+ projects,
941
+ hint: `${projects.length} projects found. Call graph_onboard with a specific project name.`
942
+ };
943
+ }
944
+ }
872
945
  const root = getProjectRoot(project);
873
946
  if (!root) {
874
947
  throw new EngineError("project_not_found", `Project not found: ${project}`);
@@ -885,6 +958,7 @@ function handleOnboard(input) {
885
958
  id: child.id,
886
959
  summary: child.summary,
887
960
  resolved: child.resolved === 1,
961
+ discovery: child.discovery,
888
962
  children: grandchildren.map((gc) => ({
889
963
  id: gc.id,
890
964
  summary: gc.summary,
@@ -919,6 +993,7 @@ function handleOnboard(input) {
919
993
  }
920
994
  }
921
995
  const context_links = [...linkSet].sort();
996
+ const knowledgeRows = db.prepare("SELECT key, content, updated_at FROM knowledge WHERE project = ? ORDER BY updated_at DESC").all(project);
922
997
  const actionableRows = db.prepare(
923
998
  `SELECT n.id, n.summary, n.properties FROM nodes n
924
999
  WHERE n.project = ? AND n.resolved = 0
@@ -941,115 +1016,88 @@ function handleOnboard(input) {
941
1016
  summary: row.summary,
942
1017
  properties: JSON.parse(row.properties)
943
1018
  }));
1019
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
1020
+ const recentlyResolvedRows = db.prepare(
1021
+ `SELECT id, summary, updated_at,
1022
+ (SELECT json_extract(value, '$.agent') FROM json_each(evidence) ORDER BY json_extract(value, '$.timestamp') DESC LIMIT 1) as last_agent
1023
+ FROM nodes
1024
+ WHERE project = ? AND resolved = 1 AND updated_at > ?
1025
+ ORDER BY updated_at DESC
1026
+ LIMIT 10`
1027
+ ).all(project, oneDayAgo);
1028
+ const recently_resolved = recentlyResolvedRows.map((row) => ({
1029
+ id: row.id,
1030
+ summary: row.summary,
1031
+ resolved_at: row.updated_at,
1032
+ agent: row.last_agent ?? "unknown"
1033
+ }));
1034
+ const lastActivityRow = db.prepare("SELECT MAX(updated_at) as last FROM nodes WHERE project = ?").get(project);
1035
+ const last_activity = lastActivityRow.last;
1036
+ let hint;
1037
+ if (root.discovery === "pending") {
1038
+ hint = `Discovery is pending. Interview the user to understand scope and goals, write knowledge entries with findings, then set discovery to "done" via graph_update before decomposing with graph_plan.`;
1039
+ } else if (actionable.length > 0) {
1040
+ const recentNote = recently_resolved.length > 0 ? ` ${recently_resolved.length} task(s) resolved recently.` : "";
1041
+ hint = `${actionable.length} actionable task(s) ready.${recentNote} Use graph_next({ project: "${project}", claim: true }) to claim one.`;
1042
+ } else if (summary.unresolved > 0 && summary.actionable === 0) {
1043
+ hint = `All remaining tasks are blocked. Check dependencies with graph_query.`;
1044
+ } else if (summary.total <= 1 && root.discovery !== "pending") {
1045
+ hint = `Project is empty \u2014 use graph_plan to decompose the goal into tasks.`;
1046
+ }
944
1047
  return {
945
1048
  project,
1049
+ goal: root.summary,
1050
+ discovery: root.discovery,
1051
+ hint,
946
1052
  summary,
947
1053
  tree,
948
1054
  recent_evidence,
949
1055
  context_links,
1056
+ knowledge: knowledgeRows,
1057
+ recently_resolved,
1058
+ last_activity,
950
1059
  actionable
951
1060
  };
952
1061
  }
953
1062
 
954
- // src/tools/agent-config.ts
955
- var AGENT_PROMPT = `---
956
- name: graph
957
- description: Use this agent for tasks tracked in Graph. Enforces the claim-work-resolve workflow \u2014 always checks graph_next before working, adds new work to the graph before executing, and resolves with evidence.
958
- tools: Read, Edit, Write, Bash, Glob, Grep, Task(Explore)
959
- model: sonnet
960
- ---
961
-
962
- You 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.
963
-
964
- # Workflow
965
-
966
- ## 1. ORIENT
967
- On your first call, orient yourself:
968
- \`\`\`
969
- graph_onboard({ project: "<project-name>" })
970
- \`\`\`
971
- Read the summary, recent evidence, context links, and actionable tasks. Understand what was done and what's left.
972
-
973
- ## 2. CLAIM
974
- Get your next task:
975
- \`\`\`
976
- graph_next({ project: "<project-name>", claim: true })
977
- \`\`\`
978
- Read 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).
979
-
980
- ## 3. PLAN
981
- If you discover work that isn't in the graph, add it BEFORE executing:
982
- \`\`\`
983
- graph_plan({ nodes: [{ ref: "new-work", parent_ref: "<parent-id>", summary: "..." }] })
984
- \`\`\`
985
- Never execute ad-hoc work. The graph is the source of truth.
986
-
987
- When decomposing work:
988
- - 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".
989
- - Keep tasks small and specific. A task should be completable in one session.
990
- - Parent nodes are organizational \u2014 they resolve when all children resolve. Don't put work in parent nodes.
991
-
992
- ## 4. WORK
993
- Execute the claimed task. While working:
994
- - Annotate key code changes with \`// [sl:nodeId]\` where nodeId is the task you're working on
995
- - This creates a traceable link from code back to the task, its evidence, and its history
996
- - Build and run tests before considering a task done
997
-
998
- ## 5. RESOLVE
999
- When done, resolve the task with evidence:
1000
- \`\`\`
1001
- graph_update({ updates: [{
1002
- node_id: "<task-id>",
1003
- resolved: true,
1004
- add_evidence: [
1005
- { type: "note", ref: "What you did and why" },
1006
- { type: "git", ref: "<commit-hash> \u2014 <summary>" },
1007
- { type: "test", ref: "Test results" }
1008
- ],
1009
- add_context_links: ["path/to/files/you/touched"]
1010
- }] })
1011
- \`\`\`
1012
- Evidence is mandatory. At minimum, include one note explaining what you did.
1013
-
1014
- ## 6. PAUSE
1015
- After resolving a task, STOP. Tell the user:
1016
- - What you just completed
1017
- - What the next actionable task is
1018
- - Wait for the user to say "continue" before claiming the next task
1019
-
1020
- The user controls the pace. Do not auto-claim the next task.
1021
-
1022
- # Rules
1023
-
1024
- - NEVER start work without a claimed task
1025
- - NEVER resolve without evidence
1026
- - NEVER execute ad-hoc work \u2014 add it to the graph first via graph_plan
1027
- - NEVER auto-continue to the next task \u2014 pause and let the user decide
1028
- - ALWAYS build and test before resolving
1029
- - ALWAYS include context_links for files you modified when resolving
1030
- - If a parent task becomes actionable (all children resolved), resolve it with a summary of what its children accomplished
1031
- - 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
1032
-
1033
- # Common mistakes to avoid
1034
-
1035
- - Setting dependencies on parent nodes instead of leaf nodes
1036
- - Running project scaffolding tools (create-next-app, etc.) before planning in the graph
1037
- - Resolving tasks without running tests
1038
- - Doing work that isn't tracked in the graph
1039
- - Continuing to the next task without pausing for user review
1040
- `;
1041
- function handleAgentConfig(dbPath) {
1042
- const tier = getLicenseTier(dbPath);
1043
- if (tier !== "pro") {
1044
- throw new EngineError(
1045
- "free_tier_limit",
1046
- "The graph-optimized agent configuration is a pro feature. Activate a license key to unlock it."
1063
+ // src/tools/tree.ts
1064
+ function buildTree(node, currentDepth, maxDepth, stats) {
1065
+ stats.total++;
1066
+ if (node.resolved) stats.resolved++;
1067
+ const children = getChildren(node.id);
1068
+ const treeNode = {
1069
+ id: node.id,
1070
+ summary: node.summary,
1071
+ resolved: node.resolved,
1072
+ properties: node.properties
1073
+ };
1074
+ if (children.length === 0) return treeNode;
1075
+ if (currentDepth < maxDepth) {
1076
+ treeNode.children = children.map(
1077
+ (child) => buildTree(child, currentDepth + 1, maxDepth, stats)
1047
1078
  );
1079
+ } else {
1080
+ treeNode.child_count = children.length;
1081
+ }
1082
+ return treeNode;
1083
+ }
1084
+ function handleTree(input) {
1085
+ const project = requireString(input?.project, "project");
1086
+ const depth = optionalNumber(input?.depth, "depth", 1, 20) ?? 10;
1087
+ const root = getProjectRoot(project);
1088
+ if (!root) {
1089
+ throw new EngineError("project_not_found", `Project not found: ${project}`);
1048
1090
  }
1091
+ const stats = { total: 0, resolved: 0 };
1092
+ const tree = buildTree(root, 0, depth, stats);
1049
1093
  return {
1050
- agent_file: AGENT_PROMPT,
1051
- install_path: ".claude/agents/graph.md",
1052
- instructions: "Save the agent_file content to .claude/agents/graph.md in your project root. Claude Code will automatically discover it and use it when tasks match the agent description."
1094
+ project,
1095
+ tree,
1096
+ stats: {
1097
+ total: stats.total,
1098
+ resolved: stats.resolved,
1099
+ unresolved: stats.total - stats.resolved
1100
+ }
1053
1101
  };
1054
1102
  }
1055
1103
 
@@ -1132,10 +1180,10 @@ function handleKnowledgeSearch(input) {
1132
1180
 
1133
1181
  // src/gates.ts
1134
1182
  var FREE_LIMITS = {
1135
- maxProjects: 1,
1136
- maxNodesPerProject: 50,
1137
- onboardEvidenceLimit: 5,
1138
- scopeEnabled: false
1183
+ maxProjects: Infinity,
1184
+ maxNodesPerProject: Infinity,
1185
+ onboardEvidenceLimit: 50,
1186
+ scopeEnabled: true
1139
1187
  };
1140
1188
  function checkNodeLimit(tier, project, adding) {
1141
1189
  if (tier === "pro") return;
@@ -1163,9 +1211,11 @@ function capEvidenceLimit(tier, requested) {
1163
1211
  const max = tier === "pro" ? requested ?? 20 : FREE_LIMITS.onboardEvidenceLimit;
1164
1212
  return Math.min(requested ?? max, tier === "pro" ? 50 : FREE_LIMITS.onboardEvidenceLimit);
1165
1213
  }
1166
- function checkScope(tier, scope) {
1167
- if (tier === "pro") return scope;
1168
- return void 0;
1214
+ function checkKnowledgeTier(_tier) {
1215
+ return;
1216
+ }
1217
+ function checkScope(_tier, scope) {
1218
+ return scope;
1169
1219
  }
1170
1220
 
1171
1221
  // src/server.ts
@@ -1321,6 +1371,7 @@ var TOOLS = [
1321
1371
  properties: {
1322
1372
  node_id: { type: "string" },
1323
1373
  resolved: { type: "boolean" },
1374
+ discovery: { type: "string", description: "Discovery phase status: 'pending' or 'done'. Set to 'done' after completing discovery interview." },
1324
1375
  state: { description: "Agent-defined state, any type" },
1325
1376
  summary: { type: "string" },
1326
1377
  properties: {
@@ -1472,18 +1523,32 @@ var TOOLS = [
1472
1523
  inputSchema: {
1473
1524
  type: "object",
1474
1525
  properties: {
1475
- project: { type: "string", description: "Project name (e.g. 'my-project')" },
1526
+ project: { type: "string", description: "Project name (e.g. 'my-project'). Omit to auto-select (works when there's exactly one project)." },
1476
1527
  evidence_limit: {
1477
1528
  type: "number",
1478
1529
  description: "Max evidence entries to return (default 20, max 50)"
1479
1530
  }
1531
+ }
1532
+ }
1533
+ },
1534
+ {
1535
+ name: "graph_tree",
1536
+ description: "Full tree visualization for a project. Returns the complete task hierarchy with resolve status. Use when you need to see the whole project structure beyond graph_context's single-node neighborhood.",
1537
+ inputSchema: {
1538
+ type: "object",
1539
+ properties: {
1540
+ project: { type: "string", description: "Project name (e.g. 'my-project')" },
1541
+ depth: {
1542
+ type: "number",
1543
+ description: "Max tree depth to return (default 10, max 20)"
1544
+ }
1480
1545
  },
1481
1546
  required: ["project"]
1482
1547
  }
1483
1548
  },
1484
1549
  {
1485
1550
  name: "graph_agent_config",
1486
- description: "Returns the graph-optimized agent configuration file for Claude Code. Pro tier only. Save the returned content to .claude/agents/graph.md to enable the graph workflow agent.",
1551
+ 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.",
1487
1552
  inputSchema: {
1488
1553
  type: "object",
1489
1554
  properties: {}
@@ -1558,7 +1623,7 @@ async function startServer() {
1558
1623
  case "graph_open": {
1559
1624
  const openArgs = args;
1560
1625
  if (openArgs?.project) {
1561
- const { getProjectRoot: getProjectRoot2 } = await import("./nodes-7UZATPPU.js");
1626
+ const { getProjectRoot: getProjectRoot2 } = await import("./nodes-4OJBNDHG.js");
1562
1627
  if (!getProjectRoot2(openArgs.project)) {
1563
1628
  checkProjectLimit(tier);
1564
1629
  }
@@ -1569,7 +1634,7 @@ async function startServer() {
1569
1634
  case "graph_plan": {
1570
1635
  const planArgs = args;
1571
1636
  if (planArgs?.nodes?.length > 0) {
1572
- const { getNode: getNode2 } = await import("./nodes-7UZATPPU.js");
1637
+ const { getNode: getNode2 } = await import("./nodes-4OJBNDHG.js");
1573
1638
  const firstParent = planArgs.nodes[0]?.parent_ref;
1574
1639
  if (firstParent && typeof firstParent === "string" && !planArgs.nodes.some((n) => n.ref === firstParent)) {
1575
1640
  const parentNode = getNode2(firstParent);
@@ -1613,19 +1678,26 @@ async function startServer() {
1613
1678
  result = handleOnboard(onboardArgs);
1614
1679
  break;
1615
1680
  }
1681
+ case "graph_tree":
1682
+ result = handleTree(args);
1683
+ break;
1616
1684
  case "graph_agent_config":
1617
- result = handleAgentConfig(DB_PATH);
1685
+ result = handleAgentConfig();
1618
1686
  break;
1619
1687
  case "graph_knowledge_write":
1688
+ checkKnowledgeTier(tier);
1620
1689
  result = handleKnowledgeWrite(args, AGENT_IDENTITY);
1621
1690
  break;
1622
1691
  case "graph_knowledge_read":
1692
+ checkKnowledgeTier(tier);
1623
1693
  result = handleKnowledgeRead(args);
1624
1694
  break;
1625
1695
  case "graph_knowledge_delete":
1696
+ checkKnowledgeTier(tier);
1626
1697
  result = handleKnowledgeDelete(args);
1627
1698
  break;
1628
1699
  case "graph_knowledge_search":
1700
+ checkKnowledgeTier(tier);
1629
1701
  result = handleKnowledgeSearch(args);
1630
1702
  break;
1631
1703
  default:
@@ -1681,4 +1753,4 @@ async function startServer() {
1681
1753
  export {
1682
1754
  startServer
1683
1755
  };
1684
- //# sourceMappingURL=server-6MALRQNH.js.map
1756
+ //# sourceMappingURL=server-6TNHZCUW.js.map