whygraph 0.1.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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/cli/commands/config.d.ts +14 -0
  4. package/dist/cli/commands/config.js +123 -0
  5. package/dist/cli/commands/down.d.ts +9 -0
  6. package/dist/cli/commands/down.js +46 -0
  7. package/dist/cli/commands/init.d.ts +17 -0
  8. package/dist/cli/commands/init.js +144 -0
  9. package/dist/cli/commands/issues.d.ts +10 -0
  10. package/dist/cli/commands/issues.js +376 -0
  11. package/dist/cli/commands/mcp.d.ts +2 -0
  12. package/dist/cli/commands/mcp.js +9 -0
  13. package/dist/cli/commands/restart.d.ts +11 -0
  14. package/dist/cli/commands/restart.js +43 -0
  15. package/dist/cli/commands/serve.d.ts +14 -0
  16. package/dist/cli/commands/serve.js +132 -0
  17. package/dist/cli/commands/server-utils.d.ts +6 -0
  18. package/dist/cli/commands/server-utils.js +94 -0
  19. package/dist/cli/commands/status.d.ts +11 -0
  20. package/dist/cli/commands/status.js +97 -0
  21. package/dist/cli/commands/up.d.ts +13 -0
  22. package/dist/cli/commands/up.js +62 -0
  23. package/dist/cli/commands/validate.d.ts +14 -0
  24. package/dist/cli/commands/validate.js +88 -0
  25. package/dist/cli/commands/viz.d.ts +7 -0
  26. package/dist/cli/commands/viz.js +97 -0
  27. package/dist/cli/index.d.ts +2 -0
  28. package/dist/cli/index.js +33 -0
  29. package/dist/entity/id.d.ts +8 -0
  30. package/dist/entity/id.js +48 -0
  31. package/dist/entity/issues.d.ts +12 -0
  32. package/dist/entity/issues.js +68 -0
  33. package/dist/entity/parser.d.ts +6 -0
  34. package/dist/entity/parser.js +166 -0
  35. package/dist/entity/types.d.ts +54 -0
  36. package/dist/entity/types.js +21 -0
  37. package/dist/entity/validate.d.ts +12 -0
  38. package/dist/entity/validate.js +136 -0
  39. package/dist/entity/writer.d.ts +16 -0
  40. package/dist/entity/writer.js +142 -0
  41. package/dist/frontend/assets/index-ByZzPwVe.css +1 -0
  42. package/dist/frontend/assets/index-F9dxfzD_.js +170 -0
  43. package/dist/frontend/index.html +14 -0
  44. package/dist/graph/cascade.d.ts +10 -0
  45. package/dist/graph/cascade.js +49 -0
  46. package/dist/graph/decisions.d.ts +11 -0
  47. package/dist/graph/decisions.js +27 -0
  48. package/dist/graph/gaps.d.ts +10 -0
  49. package/dist/graph/gaps.js +58 -0
  50. package/dist/graph/nodes.d.ts +20 -0
  51. package/dist/graph/nodes.js +33 -0
  52. package/dist/graph/projection.d.ts +6 -0
  53. package/dist/graph/projection.js +44 -0
  54. package/dist/graph/query.d.ts +15 -0
  55. package/dist/graph/query.js +82 -0
  56. package/dist/graph/search.d.ts +2 -0
  57. package/dist/graph/search.js +23 -0
  58. package/dist/graph/supersede.d.ts +7 -0
  59. package/dist/graph/supersede.js +48 -0
  60. package/dist/graph/temporal.d.ts +13 -0
  61. package/dist/graph/temporal.js +28 -0
  62. package/dist/mcp/index.d.ts +2 -0
  63. package/dist/mcp/index.js +10 -0
  64. package/dist/mcp/server.d.ts +3 -0
  65. package/dist/mcp/server.js +340 -0
  66. package/dist/onboarding/interview.d.ts +22 -0
  67. package/dist/onboarding/interview.js +92 -0
  68. package/dist/onboarding/scan.d.ts +17 -0
  69. package/dist/onboarding/scan.js +106 -0
  70. package/dist/platform/rules.d.ts +8 -0
  71. package/dist/platform/rules.js +229 -0
  72. package/dist/server/core.d.ts +26 -0
  73. package/dist/server/core.js +111 -0
  74. package/dist/server/derived.d.ts +8 -0
  75. package/dist/server/derived.js +13 -0
  76. package/dist/server/etag.d.ts +9 -0
  77. package/dist/server/etag.js +25 -0
  78. package/dist/server/http.d.ts +13 -0
  79. package/dist/server/http.js +131 -0
  80. package/dist/server/pubsub.d.ts +12 -0
  81. package/dist/server/pubsub.js +19 -0
  82. package/dist/server/schema.d.ts +2 -0
  83. package/dist/server/schema.js +362 -0
  84. package/dist/server/stale-refs.d.ts +7 -0
  85. package/dist/server/stale-refs.js +23 -0
  86. package/dist/server/watcher.d.ts +21 -0
  87. package/dist/server/watcher.js +98 -0
  88. package/dist/server/worktree-watcher.d.ts +20 -0
  89. package/dist/server/worktree-watcher.js +79 -0
  90. package/dist/server/worktree.d.ts +22 -0
  91. package/dist/server/worktree.js +84 -0
  92. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Geovanie Ruiz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # Whygraph
2
+
3
+ **The graph of why. So your agent knows before it touches anything.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/whygraph)](https://www.npmjs.com/package/whygraph)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
8
+ [![Coverage: 100%](https://img.shields.io/badge/coverage-100%25-brightgreen)]()
9
+
10
+ > Whygraph captures the architectural decisions behind your codebase — the tradeoffs accepted, the alternatives rejected, the patterns you deliberately chose — and makes them queryable by AI agents before they write a single line.
11
+
12
+ ![Whygraph visualization](https://raw.githubusercontent.com/geovanie-ruiz/whygraph/main/docs/whygraph.png)
13
+
14
+ ---
15
+
16
+ ## The Problem
17
+
18
+ You're vibe-coding. The agent is fast. Features ship in minutes. Then three sessions later, it confidently rebuilds something you already tried and abandoned. It re-introduces the pattern you explicitly rejected. It optimizes for the local signal — passing tests, satisfying the prompt — while losing the thread of why the architecture is shaped the way it is.
19
+
20
+ This isn't a hallucination problem. **It's a memory problem.** The agent isn't wrong. It just doesn't know the why.
21
+
22
+ ---
23
+
24
+ ## Quickstart
25
+
26
+ ```bash
27
+ npm install --save-dev whygraph
28
+ npx whygraph init
29
+ npx whygraph up
30
+ ```
31
+
32
+ `init` walks you through two prompts — app name and environment — then registers the MCP server and writes agent instructions to `CLAUDE.md` (Claude Code) or `AGENTS.md` (Cursor, Copilot, other).
33
+
34
+ ```text
35
+ Initialized whygraph in .whygraph/
36
+ App node: wg-ab12
37
+ Config: /your/project/.whygraph/config.yaml
38
+ MCP: registered
39
+
40
+ Run 'whygraph up' to start the server.
41
+ ```
42
+
43
+ `up` starts the server in the background:
44
+
45
+ ```text
46
+ whygraph server running at http://localhost:4777 (pid 12345)
47
+ ```
48
+
49
+ From here, agents capture decisions automatically as they work. Run `whygraph viz` to open the graph visualization.
50
+
51
+ ---
52
+
53
+ ## What Agents See
54
+
55
+ When an agent is about to modify `src/auth/session.ts`, it calls `whygraph_context` and gets back the decisions that shaped that code:
56
+
57
+ ```json
58
+ {
59
+ "nodes": [
60
+ { "id": "wg-sess", "label": "Component", "name": "Session Management" }
61
+ ],
62
+ "decisions": [
63
+ {
64
+ "id": "wg-d001",
65
+ "title": "JWT over server-side sessions",
66
+ "status": "active",
67
+ "context": "Needed stateless auth to support horizontal scaling...",
68
+ "decision": "Use short-lived JWTs with silent refresh via interceptor.",
69
+ "tradeoffs": "Gained: stateless, horizontally scalable. Lost: cannot invalidate tokens server-side.",
70
+ "alternatives": "Server-side sessions — rejected: requires sticky sessions or shared store."
71
+ }
72
+ ]
73
+ }
74
+ ```
75
+
76
+ The agent knows what was decided, why, what was traded away, and what was rejected — before it touches anything.
77
+
78
+ ---
79
+
80
+ ## MCP Tools
81
+
82
+ Whygraph exposes six MCP tools for agent integration. Read tools are always available. Write tools require strict mode (`whygraph config --mcp-mode strict`).
83
+
84
+ | Tool | Mode | Description |
85
+ | --------------------------------- | ----- | ------------------------------------------------------- |
86
+ | `whygraph_context(file, symbol?)` | read | Get decisions affecting the code you're about to modify |
87
+ | `whygraph_get_decisions(filters)` | read | Query decisions by status, tags, or date range |
88
+ | `whygraph_get_gaps(limit?)` | read | Find structural nodes with no recorded decisions |
89
+ | `whygraph_list_nodes(filters)` | read | List app, feature, and component nodes |
90
+ | `whygraph_create_decision(...)` | write | Create a decision with full validation |
91
+ | `whygraph_create_node(...)` | write | Create a structural node |
92
+
93
+ In default mode, write tools are not registered. Agents write decision files directly to `.whygraph/graph/` as markdown. The file watcher picks them up, validates, and flags issues as JSON sidecars that the `whygraph issues` command can resolve.
94
+
95
+ ---
96
+
97
+ ## Platform Integration
98
+
99
+ Whygraph works with any AI development environment.
100
+
101
+ | Platform | Agent Instructions | MCP Registration |
102
+ | ----------- | ------------------ | --------------------------------------- |
103
+ | Claude Code | `CLAUDE.md` | Auto-registered via `claude mcp add` |
104
+ | Cursor | `AGENTS.md` | `.cursor/mcp.json` (written by `init`) |
105
+ | Copilot | `AGENTS.md` | `.vscode/mcp.json` (written by `init`) |
106
+ | Other | `AGENTS.md` | `MCP_SETUP.md` with manual instructions |
107
+
108
+ **Claude Code** gets the deepest integration: MCP auto-registration, strict mode write tools, and instructions baked directly into `CLAUDE.md`.
109
+
110
+ ---
111
+
112
+ ## The Graph
113
+
114
+ <!-- GIF placeholder — coming soon -->
115
+
116
+ Whygraph models your application as a hierarchy of nodes with decisions attached to the nodes they affect.
117
+
118
+ ```text
119
+ App: MyProject
120
+ ├── Feature: Auth
121
+ │ ├── Component: Session Management
122
+ │ │ └── ◆ JWT over sessions (active)
123
+ │ └── Component: Token Refresh
124
+ │ └── ◆ Silent refresh via interceptor (active)
125
+ └── Feature: API Layer
126
+ ├── Component: Rate Limiting
127
+ │ └── ◆ Redis-backed sliding window (active)
128
+ └── ◆ REST over tRPC (active)
129
+ ```
130
+
131
+ **Node types:** `App` → `Feature` → `Component`, with `Decision` nodes attached via `AFFECTS` edges.
132
+
133
+ **Decision fields:** `title`, `date`, `context`, `decision`, `tradeoffs`, `alternatives`, `status`, `affects`, `tags`
134
+
135
+ **Tags** (fixed taxonomy): `arch`, `data`, `security`, `performance`, `integration`, `infra`, `ux`
136
+
137
+ Decisions are never deleted. A superseded decision stays in the graph with a `SUPERSEDES` edge explaining how the architecture evolved.
138
+
139
+ ---
140
+
141
+ ## CLI
142
+
143
+ | Command | Description |
144
+ | ------------------------------ | --------------------------------------------------------------------------- |
145
+ | `whygraph init` | Set up whygraph: choose environment, register MCP, write agent instructions |
146
+ | `whygraph up` | Start the server in the background |
147
+ | `whygraph down` | Stop the server |
148
+ | `whygraph restart` | Stop and restart the server |
149
+ | `whygraph serve` | Start the server in the foreground |
150
+ | `whygraph status` | Check server status and entity counts |
151
+ | `whygraph viz` | Open the graph visualization in a browser |
152
+ | `whygraph issues` | List and interactively resolve validation issues |
153
+ | `whygraph validate` | Validate all entities and cross-references |
154
+ | `whygraph config [--flag val]` | View or modify configuration |
155
+ | `whygraph mcp` | Start the MCP stdio server |
156
+
157
+ All commands accept `--json` for programmatic output.
158
+
159
+ ---
160
+
161
+ ## Philosophy
162
+
163
+ **The why is not in the code.** You can read a codebase and understand what it does. You cannot read it and understand what was tried before, what was rejected, and what trade-offs were accepted.
164
+
165
+ **Decisions, not documentation.** Structured records — context, choice, tradeoffs, alternatives — not prose. Structure is what makes decisions queryable by agents.
166
+
167
+ **Append-only.** Superseded decisions stay in the graph. They explain the path, not just the destination.
168
+
169
+ **Repo-native.** The graph lives in `.whygraph/`, versioned with your code. No external service. No account required.
170
+
171
+ **Agent-first.** The MCP tools and agent instructions are the primary interface. The CLI and visualization exist so humans can inspect what agents will read.
172
+
173
+ **Never lose data.** Entity files are always written, even if validation fails. Issues are tracked in sidecars, not by rejecting writes.
174
+
175
+ ---
176
+
177
+ ## Limitations
178
+
179
+ - The server must be running for MCP read tools and live visualization to work. In default mode, agents can write decision files directly to `.whygraph/graph/` without the server — they'll be picked up when it starts.
180
+ - Multi-agent worktree support detects divergence but does not auto-merge. Conflict resolution follows standard git workflow.
181
+ - The fixed tag taxonomy (`arch`, `data`, `security`, `performance`, `integration`, `infra`, `ux`) is intentional — it keeps decisions queryable. Custom tags are not supported in v1.
182
+ - No cloud sync or hosted option. The graph is local to your repo.
183
+
184
+ ---
185
+
186
+ ## Multi-Agent / Worktree Support
187
+
188
+ Whygraph runs one server per repo and watches all git worktrees. When agents work in parallel:
189
+
190
+ - Each worktree's `.whygraph/graph/` is watched independently
191
+ - ETag-based dirty tracking detects divergence from the main graph
192
+ - Entity IDs use NanoIDs to prevent collisions across concurrent agents
193
+ - Conflict resolution happens at git merge time
194
+
195
+ ---
196
+
197
+ ## Acknowledgments
198
+
199
+ - **[Matt Pocock](https://www.youtube.com/@mattpocockuk)** — the development workflow used to build this project (TDD, PRD-first, simplify) is based on his [5 Skills for Claude Code](https://www.youtube.com/watch?v=EJyuu6zlQCg)
200
+
201
+ ---
202
+
203
+ ## License
204
+
205
+ MIT © [Geovanie Ruiz](https://github.com/geovanie-ruiz)
@@ -0,0 +1,14 @@
1
+ import type { Command } from "commander";
2
+ import type { WhygraphConfig } from "../../entity/types.js";
3
+ export interface ConfigUpdates {
4
+ environment?: string;
5
+ mcpMode?: string;
6
+ serverPort?: number;
7
+ tags?: string[];
8
+ }
9
+ export interface ConfigResult {
10
+ config: WhygraphConfig;
11
+ updated: boolean;
12
+ }
13
+ export declare function runConfig(whygraphDir: string, updates?: ConfigUpdates): ConfigResult;
14
+ export declare function registerConfigCommand(program: Command): void;
@@ -0,0 +1,123 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import yaml from "js-yaml";
4
+ import { DECISION_TAGS } from "../../entity/types.js";
5
+ // ============================================================
6
+ // Validation
7
+ // ============================================================
8
+ const VALID_ENVIRONMENTS = [
9
+ "claude-code",
10
+ "cursor",
11
+ "copilot",
12
+ "other",
13
+ ];
14
+ const VALID_MCP_MODES = ["default", "strict"];
15
+ function validateUpdates(updates) {
16
+ if (updates.environment !== undefined &&
17
+ !VALID_ENVIRONMENTS.includes(updates.environment)) {
18
+ throw new Error(`Invalid environment "${updates.environment}". Must be one of: ${VALID_ENVIRONMENTS.join(", ")}`);
19
+ }
20
+ if (updates.mcpMode !== undefined &&
21
+ !VALID_MCP_MODES.includes(updates.mcpMode)) {
22
+ throw new Error(`Invalid mcpMode "${updates.mcpMode}". Must be one of: ${VALID_MCP_MODES.join(", ")}`);
23
+ }
24
+ if (updates.serverPort !== undefined) {
25
+ if (typeof updates.serverPort !== "number" || isNaN(updates.serverPort)) {
26
+ throw new Error(`Invalid serverPort "${updates.serverPort}". Must be a number.`);
27
+ }
28
+ }
29
+ if (updates.tags !== undefined) {
30
+ for (const tag of updates.tags) {
31
+ if (!DECISION_TAGS.includes(tag)) {
32
+ throw new Error(`Invalid tag "${tag}". Must be one of: ${DECISION_TAGS.join(", ")}`);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ // ============================================================
38
+ // Core Logic
39
+ // ============================================================
40
+ export function runConfig(whygraphDir, updates) {
41
+ const configPath = join(whygraphDir, ".whygraph", "config.yaml");
42
+ if (!existsSync(configPath)) {
43
+ throw new Error(`.whygraph/ not found in ${whygraphDir}. Run "whygraph init" first.`);
44
+ }
45
+ const raw = readFileSync(configPath, "utf-8");
46
+ const config = yaml.load(raw);
47
+ if (!updates || Object.keys(updates).length === 0) {
48
+ return { config, updated: false };
49
+ }
50
+ validateUpdates(updates);
51
+ if (updates.environment !== undefined) {
52
+ config.environment = updates.environment;
53
+ }
54
+ if (updates.mcpMode !== undefined) {
55
+ config.mcpMode = updates.mcpMode;
56
+ }
57
+ if (updates.serverPort !== undefined) {
58
+ config.serverPort = updates.serverPort;
59
+ }
60
+ if (updates.tags !== undefined) {
61
+ config.tags = updates.tags;
62
+ }
63
+ const configYaml = yaml.dump(config, { lineWidth: -1 });
64
+ writeFileSync(configPath, configYaml, "utf-8");
65
+ return { config, updated: true };
66
+ }
67
+ // ============================================================
68
+ // CLI Wiring
69
+ // ============================================================
70
+ function formatConfig(config) {
71
+ const lines = [
72
+ `appName: ${config.appName}`,
73
+ `environment: ${config.environment}`,
74
+ `prefix: ${config.prefix}`,
75
+ `idLength: ${config.idLength}`,
76
+ `tags: ${config.tags.join(", ")}`,
77
+ `mcpMode: ${config.mcpMode}`,
78
+ `serverPort: ${config.serverPort}`,
79
+ ];
80
+ return lines.join("\n");
81
+ }
82
+ export function registerConfigCommand(program) {
83
+ program
84
+ .command("config")
85
+ .description("View or update whygraph configuration")
86
+ .option("--environment <env>", "Update environment")
87
+ .option("--mcp-mode <mode>", "Update MCP mode (default | strict)")
88
+ .option("--server-port <port>", "Update server port")
89
+ .option("--json", "Output results as JSON")
90
+ .action((opts) => {
91
+ try {
92
+ const updates = {};
93
+ if (opts.environment !== undefined)
94
+ updates.environment = opts.environment;
95
+ if (opts.mcpMode !== undefined)
96
+ updates.mcpMode = opts.mcpMode;
97
+ if (opts.serverPort !== undefined)
98
+ updates.serverPort = Number(opts.serverPort);
99
+ const hasUpdates = Object.keys(updates).length > 0;
100
+ const result = runConfig(process.cwd(), hasUpdates ? updates : undefined);
101
+ if (opts.json) {
102
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
103
+ }
104
+ else {
105
+ if (result.updated) {
106
+ process.stdout.write("Configuration updated.\n\n");
107
+ }
108
+ process.stdout.write(formatConfig(result.config) + "\n");
109
+ }
110
+ }
111
+ catch (err) {
112
+ /* v8 ignore next 1 */
113
+ const message = err instanceof Error ? err.message : String(err);
114
+ if (opts.json) {
115
+ process.stdout.write(JSON.stringify({ error: message }, null, 2) + "\n");
116
+ }
117
+ else {
118
+ process.stderr.write(`Error: ${message}\n`);
119
+ }
120
+ process.exitCode = 1;
121
+ }
122
+ });
123
+ }
@@ -0,0 +1,9 @@
1
+ import type { Command } from "commander";
2
+ export declare function runDown(targetDir: string, options?: {
3
+ port?: number;
4
+ json?: boolean;
5
+ }): Promise<{
6
+ stopped: boolean;
7
+ port: number;
8
+ }>;
9
+ export declare function registerDownCommand(program: Command): void;
@@ -0,0 +1,46 @@
1
+ import { findWhygraphDir, getConfiguredPort, killProcessOnPort, waitForPortFree, } from "./server-utils.js";
2
+ export async function runDown(targetDir, options = {}) {
3
+ const projectDir = findWhygraphDir(targetDir);
4
+ if (!projectDir) {
5
+ throw new Error(`.whygraph/ not found. Run "whygraph init" first.`);
6
+ }
7
+ const port = options.port ?? getConfiguredPort(projectDir);
8
+ const killed = killProcessOnPort(port);
9
+ if (killed) {
10
+ await waitForPortFree(port);
11
+ }
12
+ return { stopped: killed, port };
13
+ }
14
+ export function registerDownCommand(program) {
15
+ program
16
+ .command("down")
17
+ .description("Stop the whygraph server")
18
+ .option("--port <number>", "Port number")
19
+ .option("--json", "Output results as JSON")
20
+ .action(async (opts) => {
21
+ try {
22
+ const port = opts.port ? parseInt(opts.port, 10) : undefined;
23
+ const result = await runDown(process.cwd(), { port, json: opts.json });
24
+ if (opts.json) {
25
+ process.stdout.write(JSON.stringify(result) + "\n");
26
+ }
27
+ else if (result.stopped) {
28
+ process.stdout.write(`whygraph server stopped (port ${result.port})\n`);
29
+ }
30
+ else {
31
+ process.stdout.write(`No server running on port ${result.port}\n`);
32
+ }
33
+ }
34
+ catch (err) {
35
+ /* v8 ignore next 1 */
36
+ const message = err instanceof Error ? err.message : String(err);
37
+ if (opts.json) {
38
+ process.stdout.write(JSON.stringify({ error: message }) + "\n");
39
+ }
40
+ else {
41
+ process.stderr.write(`Error: ${message}\n`);
42
+ }
43
+ process.exitCode = 1;
44
+ }
45
+ });
46
+ }
@@ -0,0 +1,17 @@
1
+ import type { Command } from "commander";
2
+ import type { Environment } from "../../entity/types.js";
3
+ export interface InitOptions {
4
+ appName?: string;
5
+ environment?: Environment;
6
+ json?: boolean;
7
+ }
8
+ export interface InitResult {
9
+ configPath: string;
10
+ appNodePath: string;
11
+ appId: string;
12
+ platformRulesPath: string;
13
+ mcpRegistered: boolean;
14
+ mcpSetupPath?: string;
15
+ }
16
+ export declare function runInit(targetDir: string, options: InitOptions): Promise<InitResult>;
17
+ export declare function registerInitCommand(program: Command): void;
@@ -0,0 +1,144 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import yaml from "js-yaml";
4
+ import prompts from "prompts";
5
+ import { generateId } from "../../entity/id.js";
6
+ import { writeEntity } from "../../entity/writer.js";
7
+ import { DECISION_TAGS } from "../../entity/types.js";
8
+ import { writePlatformRules } from "../../platform/rules.js";
9
+ // ============================================================
10
+ // Core Logic
11
+ // ============================================================
12
+ export async function runInit(targetDir, options) {
13
+ const whygraphDir = join(targetDir, ".whygraph");
14
+ if (existsSync(whygraphDir)) {
15
+ throw new Error(`.whygraph/ already exists in ${targetDir}. Aborting init.`);
16
+ }
17
+ // Resolve app name and environment from options or prompts
18
+ let appName = options.appName;
19
+ let environment = options.environment;
20
+ if (!appName || !environment) {
21
+ const answers = await prompts([
22
+ ...(appName
23
+ ? []
24
+ : [
25
+ {
26
+ type: "text",
27
+ name: "appName",
28
+ message: "App name:",
29
+ },
30
+ ]),
31
+ /* v8 ignore start */
32
+ ...(environment
33
+ ? []
34
+ : [
35
+ {
36
+ type: "select",
37
+ name: "environment",
38
+ message: "Environment:",
39
+ choices: [
40
+ { title: "Claude Code", value: "claude-code" },
41
+ { title: "Cursor", value: "cursor" },
42
+ { title: "Copilot", value: "copilot" },
43
+ { title: "Other", value: "other" },
44
+ ],
45
+ },
46
+ ]),
47
+ /* v8 ignore stop */
48
+ ], { onCancel: () => { throw new Error("Init cancelled by user."); } });
49
+ if (!appName)
50
+ appName = answers.appName;
51
+ /* v8 ignore next 1 */
52
+ if (!environment)
53
+ environment = answers.environment;
54
+ }
55
+ if (!appName) {
56
+ throw new Error("App name is required.");
57
+ }
58
+ if (!environment) {
59
+ throw new Error("Environment is required.");
60
+ }
61
+ // Create directory structure
62
+ const graphDir = join(whygraphDir, "graph");
63
+ mkdirSync(graphDir, { recursive: true });
64
+ // Build config
65
+ const config = {
66
+ appName,
67
+ environment,
68
+ prefix: "wg-",
69
+ idLength: 4,
70
+ tags: [...DECISION_TAGS],
71
+ mcpMode: "default",
72
+ serverPort: 4777,
73
+ };
74
+ // Write config.yaml
75
+ const configPath = join(whygraphDir, "config.yaml");
76
+ const configYaml = yaml.dump(config, { lineWidth: -1 });
77
+ writeFileSync(configPath, configYaml, "utf-8");
78
+ // Create App node
79
+ const now = new Date().toISOString();
80
+ const appId = generateId({ prefix: config.prefix, length: config.idLength });
81
+ const appNode = {
82
+ id: appId,
83
+ label: "App",
84
+ name: appName,
85
+ status: "active",
86
+ created_at: now,
87
+ updated_at: now,
88
+ };
89
+ const { filePath: appNodePath } = writeEntity(graphDir, appNode);
90
+ // Write platform-specific rules and register MCP server
91
+ const platformResult = writePlatformRules(targetDir, environment, "", config);
92
+ return {
93
+ configPath,
94
+ appNodePath,
95
+ appId,
96
+ platformRulesPath: platformResult.filePath,
97
+ mcpRegistered: platformResult.mcpRegistered,
98
+ mcpSetupPath: platformResult.mcpSetupPath,
99
+ };
100
+ }
101
+ // ============================================================
102
+ // CLI Wiring
103
+ // ============================================================
104
+ export function registerInitCommand(program) {
105
+ program
106
+ .command("init")
107
+ .description("Initialize a new whygraph project in the current directory")
108
+ .option("--app-name <name>", "Application name (skips prompt)")
109
+ .option("--environment <env>", "Environment: claude-code, cursor, copilot, other (skips prompt)")
110
+ .option("--json", "Output results as JSON")
111
+ .action(async (opts) => {
112
+ try {
113
+ const result = await runInit(process.cwd(), {
114
+ appName: opts.appName,
115
+ environment: opts.environment,
116
+ json: opts.json,
117
+ });
118
+ if (opts.json) {
119
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
120
+ }
121
+ else {
122
+ const mcpLine = result.mcpRegistered
123
+ ? ` MCP: registered\n`
124
+ : ` MCP: setup failed — see ${result.mcpSetupPath} for manual instructions\n`;
125
+ process.stdout.write(`Initialized whygraph in .whygraph/\n` +
126
+ ` App node: ${result.appId}\n` +
127
+ ` Config: ${result.configPath}\n` +
128
+ mcpLine +
129
+ `\nRun 'whygraph up' to start the server.\n`);
130
+ }
131
+ }
132
+ catch (err) {
133
+ /* v8 ignore next 1 */
134
+ const message = err instanceof Error ? err.message : String(err);
135
+ if (opts.json) {
136
+ process.stdout.write(JSON.stringify({ error: message }, null, 2) + "\n");
137
+ }
138
+ else {
139
+ process.stderr.write(`Error: ${message}\n`);
140
+ }
141
+ process.exitCode = 1;
142
+ }
143
+ });
144
+ }
@@ -0,0 +1,10 @@
1
+ import type { Command } from "commander";
2
+ import type { EntityIssue } from "../../entity/issues.js";
3
+ export interface IssuesResult {
4
+ agentNeeded: number;
5
+ cliResolvable: number;
6
+ total: number;
7
+ issues: EntityIssue[];
8
+ }
9
+ export declare function runIssues(targetDir: string): IssuesResult;
10
+ export declare function registerIssuesCommand(program: Command): void;