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);
|
package/dist/platform/rules.js
CHANGED
|
@@ -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
|
-
"
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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 { ...
|
|
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
|
+
}
|