whygraph 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -126,7 +126,8 @@ export function registerInitCommand(program) {
126
126
  ` App node: ${result.appId}\n` +
127
127
  ` Config: ${result.configPath}\n` +
128
128
  mcpLine +
129
- `\nRun 'whygraph up' to start the server.\n`);
129
+ `\nRun 'npx whygraph up' to start the server.\n` +
130
+ `Run 'npx whygraph onboard' to get a prompt for building your graph.\n`);
130
131
  }
131
132
  }
132
133
  catch (err) {
@@ -0,0 +1,9 @@
1
+ import type { Command } from "commander";
2
+ export interface OnboardResult {
3
+ appId: string;
4
+ appName: string;
5
+ prefix: string;
6
+ prompt: string;
7
+ }
8
+ export declare function runOnboard(targetDir: string): OnboardResult;
9
+ export declare function registerOnboardCommand(program: Command): void;
@@ -0,0 +1,109 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import yaml from "js-yaml";
4
+ import { parseEntity } from "../../entity/parser.js";
5
+ import { findWhygraphDir } from "./serve.js";
6
+ // ============================================================
7
+ // Core Logic
8
+ // ============================================================
9
+ export function runOnboard(targetDir) {
10
+ const projectDir = findWhygraphDir(targetDir);
11
+ if (!projectDir) {
12
+ throw new Error(`.whygraph/ not found. Run "npx whygraph init" first.`);
13
+ }
14
+ const configPath = join(projectDir, ".whygraph", "config.yaml");
15
+ if (!existsSync(configPath)) {
16
+ throw new Error(`.whygraph/config.yaml not found. Run "npx whygraph init" first.`);
17
+ }
18
+ const raw = readFileSync(configPath, "utf-8");
19
+ const config = yaml.load(raw);
20
+ // Find the App node
21
+ const graphDir = join(projectDir, ".whygraph", "graph");
22
+ let appId = "";
23
+ let appName = config.appName;
24
+ if (existsSync(graphDir)) {
25
+ const files = readdirSync(graphDir).filter((f) => f.endsWith(".md"));
26
+ for (const file of files) {
27
+ const content = readFileSync(join(graphDir, file), "utf-8");
28
+ const entity = parseEntity(content);
29
+ if (entity !== null && entity.label === "App") {
30
+ appId = entity.id;
31
+ /* v8 ignore next 1 */
32
+ if ("name" in entity)
33
+ appName = entity.name;
34
+ break;
35
+ }
36
+ }
37
+ }
38
+ if (!appId) {
39
+ throw new Error(`No App node found in .whygraph/graph/. Run "npx whygraph init" first.`);
40
+ }
41
+ const prompt = buildOnboardingPrompt(appName, appId, config.prefix);
42
+ return { appId, appName, prefix: config.prefix, prompt };
43
+ }
44
+ function buildOnboardingPrompt(appName, appId, prefix) {
45
+ return [
46
+ `I'm using whygraph to maintain a living decision graph for ${appName}.`,
47
+ `The root App node is \`${appId}\`.`,
48
+ ``,
49
+ `Please explore this codebase and build out the structural graph by:`,
50
+ ``,
51
+ `1. Identifying the top-level features or modules (e.g. authentication, data pipeline, API layer, UI pages).`,
52
+ ` For each one, create a Feature node using the whygraph MCP tool \`whygraph_create_node\`:`,
53
+ ` - label: Feature`,
54
+ ` - parent: ${appId}`,
55
+ ` - name: <feature name>`,
56
+ ` - status: active`,
57
+ ``,
58
+ `2. For each Feature, identify its key components (e.g. services, hooks, controllers, presenters).`,
59
+ ` For each one, create a Component node:`,
60
+ ` - label: Component`,
61
+ ` - parent: <feature id>`,
62
+ ` - name: <component name>`,
63
+ ` - status: active`,
64
+ ``,
65
+ `3. Use the \`refs\` field to link nodes to their source files where relevant.`,
66
+ ``,
67
+ `4. After building the structural graph, review the existing decisions in .whygraph/graph/ and`,
68
+ ` update their \`affects\` fields to link them to the relevant Feature or Component nodes you created.`,
69
+ ``,
70
+ `Node IDs should use the prefix \`${prefix}\` followed by a 4-character nanoid (e.g. \`${prefix}abcd\`).`,
71
+ ``,
72
+ `Start by listing the top-level directories and key entry points, then propose the Feature list`,
73
+ `before creating any nodes. I'll confirm before you proceed.`,
74
+ ].join("\n");
75
+ }
76
+ // ============================================================
77
+ // CLI Wiring
78
+ // ============================================================
79
+ export function registerOnboardCommand(program) {
80
+ program
81
+ .command("onboard")
82
+ .description("Print a codebase analysis prompt to bootstrap your whygraph graph")
83
+ .option("--json", "Output results as JSON")
84
+ .action((opts) => {
85
+ try {
86
+ const result = runOnboard(process.cwd());
87
+ if (opts.json) {
88
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
89
+ }
90
+ else {
91
+ process.stdout.write(`Paste the following prompt into your AI agent:\n\n` +
92
+ `${"─".repeat(60)}\n` +
93
+ `${result.prompt}\n` +
94
+ `${"─".repeat(60)}\n`);
95
+ }
96
+ }
97
+ catch (err) {
98
+ /* v8 ignore next 1 */
99
+ const message = err instanceof Error ? err.message : String(err);
100
+ if (opts.json) {
101
+ process.stdout.write(JSON.stringify({ error: message }, null, 2) + "\n");
102
+ }
103
+ else {
104
+ process.stderr.write(`Error: ${message}\n`);
105
+ }
106
+ process.exitCode = 1;
107
+ }
108
+ });
109
+ }
package/dist/cli/index.js CHANGED
@@ -8,6 +8,7 @@ import { registerDownCommand } from "./commands/down.js";
8
8
  import { registerInitCommand } from "./commands/init.js";
9
9
  import { registerIssuesCommand } from "./commands/issues.js";
10
10
  import { registerMcpCommand } from "./commands/mcp.js";
11
+ import { registerOnboardCommand } from "./commands/onboard.js";
11
12
  import { registerRestartCommand } from "./commands/restart.js";
12
13
  import { registerServeCommand } from "./commands/serve.js";
13
14
  import { registerStatusCommand } from "./commands/status.js";
@@ -24,6 +25,7 @@ registerDownCommand(program);
24
25
  registerInitCommand(program);
25
26
  registerIssuesCommand(program);
26
27
  registerMcpCommand(program);
28
+ registerOnboardCommand(program);
27
29
  registerRestartCommand(program);
28
30
  registerServeCommand(program);
29
31
  registerStatusCommand(program);
@@ -59,15 +59,20 @@ function generateInstructions(config) {
59
59
  "",
60
60
  `Allowed tags: ${tagList}`,
61
61
  "",
62
- "### MCP Server",
62
+ "### MCP Server (preferred)",
63
63
  "",
64
- `Use the \`whygraph\` MCP server for decision capture tools.`,
65
- "If the server is unreachable, write decision files directly to `.whygraph/graph/`.",
64
+ `Use the \`whygraph\` MCP server for decision capture tools when available.`,
65
+ "",
66
+ "### Direct File Write (fallback)",
67
+ "",
68
+ "If the MCP server is unreachable or MCP is blocked by your environment,",
69
+ "write the decision file directly to `.whygraph/graph/` using the format above.",
70
+ "The file will be picked up automatically on next server start.",
66
71
  "",
67
72
  "### Server Status",
68
73
  "",
69
- "Run `whygraph status` to check if the server is running.",
70
- "Run `whygraph up` to start the server.",
74
+ "Run `npx whygraph status` to check if the server is running.",
75
+ "Run `npx whygraph up` to start the server.",
71
76
  "",
72
77
  ].join("\n");
73
78
  }
@@ -89,7 +94,7 @@ function upsertMarkedSection(existing, content) {
89
94
  // ============================================================
90
95
  function registerMcpWithClaude(projectDir) {
91
96
  try {
92
- execSync("claude mcp add --scope project whygraph -- whygraph mcp", {
97
+ execSync("claude mcp add --scope project whygraph -- npx whygraph mcp", {
93
98
  cwd: projectDir,
94
99
  stdio: "ignore",
95
100
  });
@@ -106,7 +111,7 @@ function registerMcpWithClaude(projectDir) {
106
111
  }
107
112
  const servers = (mcpJson.mcpServers ?? {});
108
113
  if (!servers.whygraph) {
109
- servers.whygraph = { type: "stdio", command: "whygraph", args: ["mcp"] };
114
+ servers.whygraph = { type: "stdio", command: "npx", args: ["whygraph", "mcp"] };
110
115
  mcpJson.mcpServers = servers;
111
116
  writeFileSync(mcpJsonPath, JSON.stringify(mcpJson, null, 2) + "\n", "utf-8");
112
117
  }
@@ -129,7 +134,7 @@ function registerMcpWithCursor(projectDir) {
129
134
  }
130
135
  const servers = (mcp.mcpServers ?? {});
131
136
  if (!servers.whygraph) {
132
- servers.whygraph = { command: "whygraph", args: ["mcp"] };
137
+ servers.whygraph = { command: "npx", args: ["whygraph", "mcp"] };
133
138
  mcp.mcpServers = servers;
134
139
  writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
135
140
  }
@@ -150,7 +155,7 @@ function registerMcpWithCopilot(projectDir) {
150
155
  }
151
156
  const servers = (mcp.servers ?? {});
152
157
  if (!servers.whygraph) {
153
- servers.whygraph = { type: "stdio", command: "whygraph", args: ["mcp"] };
158
+ servers.whygraph = { type: "stdio", command: "npx", args: ["whygraph", "mcp"] };
154
159
  mcp.servers = servers;
155
160
  writeFileSync(mcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
156
161
  }
@@ -167,16 +172,16 @@ function writeMcpSetupMd(projectDir, environment) {
167
172
  const lines = ["# Whygraph MCP Setup", ""];
168
173
  switch (environment) {
169
174
  case "claude-code":
170
- lines.push("Run the following command in your project root:", "", "```bash", "claude mcp add --scope project whygraph -- whygraph mcp", "```");
175
+ lines.push("Run the following command in your project root:", "", "```bash", "claude mcp add --scope project whygraph -- npx whygraph mcp", "```");
171
176
  break;
172
177
  case "cursor":
173
- lines.push("Add the following to `.cursor/mcp.json` in your project root:", "", "```json", JSON.stringify({ mcpServers: { whygraph: { command: "whygraph", args: ["mcp"] } } }, null, 2), "```");
178
+ lines.push("Add the following to `.cursor/mcp.json` in your project root:", "", "```json", JSON.stringify({ mcpServers: { whygraph: { command: "npx", args: ["whygraph", "mcp"] } } }, null, 2), "```");
174
179
  break;
175
180
  case "copilot":
176
- lines.push("Add the following to `.vscode/mcp.json` in your project root:", "", "```json", JSON.stringify({ servers: { whygraph: { type: "stdio", command: "whygraph", args: ["mcp"] } } }, null, 2), "```");
181
+ lines.push("Add the following to `.vscode/mcp.json` in your project root:", "", "```json", JSON.stringify({ servers: { whygraph: { type: "stdio", command: "npx", args: ["whygraph", "mcp"] } } }, null, 2), "```");
177
182
  break;
178
183
  default:
179
- lines.push("Add the whygraph MCP server to your AI assistant's MCP configuration.", "", "**Command:** `whygraph mcp`", "**Transport:** stdio", "", "Refer to your AI assistant's documentation for how to register MCP servers.");
184
+ lines.push("Add the whygraph MCP server to your AI assistant's MCP configuration.", "", "**Command:** `npx whygraph mcp`", "**Transport:** stdio", "", "Refer to your AI assistant's documentation for how to register MCP servers.");
180
185
  }
181
186
  writeFileSync(filePath, lines.join("\n") + "\n", "utf-8");
182
187
  return filePath;
@@ -197,7 +202,7 @@ export function writePlatformRules(projectDir, environment, _primeOutput, config
197
202
  case "copilot": {
198
203
  const mcpRegistered = registerMcpWithCopilot(projectDir);
199
204
  const mcpSetupPath = mcpRegistered ? undefined : writeMcpSetupMd(projectDir, environment);
200
- return { ...writeAgentsMd(projectDir, instructions, environment), mcpRegistered, mcpSetupPath };
205
+ return { ...writeCopilotInstructions(projectDir, instructions), mcpRegistered, mcpSetupPath };
201
206
  }
202
207
  case "other": {
203
208
  const mcpSetupPath = writeMcpSetupMd(projectDir, environment);
@@ -227,3 +232,17 @@ function writeAgentsMd(projectDir, instructions, environment) {
227
232
  writeFileSync(filePath, newContent, "utf-8");
228
233
  return { environment, filePath };
229
234
  }
235
+ function writeCopilotInstructions(projectDir, instructions) {
236
+ const githubDir = join(projectDir, ".github");
237
+ if (!existsSync(githubDir)) {
238
+ mkdirSync(githubDir, { recursive: true });
239
+ }
240
+ const filePath = join(githubDir, "copilot-instructions.md");
241
+ let existing = "";
242
+ if (existsSync(filePath)) {
243
+ existing = readFileSync(filePath, "utf-8");
244
+ }
245
+ const newContent = upsertMarkedSection(existing, instructions);
246
+ writeFileSync(filePath, newContent, "utf-8");
247
+ return { environment: "copilot", filePath };
248
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whygraph",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The graph of why. So your agent knows before it touches anything.",
5
5
  "author": "Geovanie Ruiz",
6
6
  "license": "MIT",