whygraph 0.1.2 → 0.1.3

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.
@@ -0,0 +1,8 @@
1
+ import type { Command } from "commander";
2
+ export interface CrossrefResult {
3
+ outputPath: string;
4
+ nodeCount: number;
5
+ decisionCount: number;
6
+ }
7
+ export declare function runCrossref(targetDir: string): CrossrefResult;
8
+ export declare function registerCrossrefCommand(program: Command): void;
@@ -0,0 +1,132 @@
1
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { isStructuralNode, isDecisionNode } from "../../entity/types.js";
4
+ import { parseEntity } from "../../entity/parser.js";
5
+ import { findWhygraphDir } from "./serve.js";
6
+ // ============================================================
7
+ // Core Logic
8
+ // ============================================================
9
+ export function runCrossref(targetDir) {
10
+ const projectDir = findWhygraphDir(targetDir);
11
+ if (!projectDir) {
12
+ throw new Error(`.whygraph/ not found. Run "npx whygraph init" first.`);
13
+ }
14
+ const graphDir = join(projectDir, ".whygraph", "graph");
15
+ const entities = loadEntities(graphDir);
16
+ const nodes = entities.filter(isStructuralNode);
17
+ const decisions = entities.filter(isDecisionNode);
18
+ const idToNode = new Map(nodes.map((n) => [n.id, n]));
19
+ const markdown = buildMarkdown(nodes, decisions, idToNode);
20
+ const outputPath = join(projectDir, ".whygraph", "CONTEXT.md");
21
+ writeFileSync(outputPath, markdown, "utf-8");
22
+ return { outputPath, nodeCount: nodes.length, decisionCount: decisions.length };
23
+ }
24
+ function loadEntities(graphDir) {
25
+ if (!existsSync(graphDir))
26
+ return [];
27
+ return readdirSync(graphDir)
28
+ .filter((f) => f.endsWith(".md"))
29
+ .flatMap((file) => {
30
+ const content = readFileSync(join(graphDir, file), "utf-8");
31
+ const entity = parseEntity(content);
32
+ return entity !== null ? [entity] : [];
33
+ });
34
+ }
35
+ function buildMarkdown(nodes, decisions, idToNode) {
36
+ const lines = [
37
+ "# Whygraph Context Snapshot",
38
+ "",
39
+ `_Generated: ${new Date().toISOString()}_`,
40
+ "_This file is auto-generated by `npx whygraph crossref`. Do not edit manually._",
41
+ "",
42
+ ];
43
+ // Structural graph section
44
+ lines.push("## Graph Structure", "");
45
+ const apps = nodes.filter((n) => n.label === "App");
46
+ const features = nodes.filter((n) => n.label === "Feature");
47
+ const components = nodes.filter((n) => n.label === "Component");
48
+ for (const app of apps) {
49
+ lines.push(`- **App** \`${app.id}\` — ${app.name}`);
50
+ for (const feature of features.filter((f) => f.parent === app.id)) {
51
+ lines.push(` - **Feature** \`${feature.id}\` — ${feature.name}`);
52
+ for (const comp of components.filter((c) => c.parent === feature.id)) {
53
+ lines.push(` - **Component** \`${comp.id}\` — ${comp.name}`);
54
+ }
55
+ }
56
+ }
57
+ // Orphaned features (no matching app)
58
+ for (const feature of features.filter((f) => !f.parent || !idToNode.has(f.parent))) {
59
+ lines.push(`- **Feature** \`${feature.id}\` — ${feature.name} _(unlinked)_`);
60
+ for (const comp of components.filter((c) => c.parent === feature.id)) {
61
+ lines.push(` - **Component** \`${comp.id}\` — ${comp.name}`);
62
+ }
63
+ }
64
+ lines.push("");
65
+ // Decisions section
66
+ lines.push("## Decisions", "");
67
+ if (decisions.length === 0) {
68
+ lines.push("_No decisions recorded yet._", "");
69
+ }
70
+ else {
71
+ for (const d of decisions) {
72
+ lines.push(`### [${d.id}] ${d.title}`);
73
+ lines.push("");
74
+ const affectsLabels = d.affects
75
+ .map((id) => {
76
+ const node = idToNode.get(id);
77
+ return node ? `\`${id}\` (${node.name})` : `\`${id}\``;
78
+ })
79
+ .join(", ");
80
+ lines.push(`- **Status:** ${d.status} | **Date:** ${d.date} | **Tags:** ${d.tags.join(", ")}`);
81
+ if (d.affects.length > 0) {
82
+ lines.push(`- **Affects:** ${affectsLabels}`);
83
+ }
84
+ /* v8 ignore next 3 */
85
+ if (d.supersedes) {
86
+ lines.push(`- **Supersedes:** \`${d.supersedes}\``);
87
+ }
88
+ lines.push("");
89
+ lines.push(`> **Context:** ${d.context}`);
90
+ lines.push(`> **Decision:** ${d.decision}`);
91
+ lines.push(`> **Tradeoffs:** ${d.tradeoffs}`);
92
+ lines.push(`> **Alternatives:** ${d.alternatives}`);
93
+ lines.push("");
94
+ lines.push("---");
95
+ lines.push("");
96
+ }
97
+ }
98
+ return lines.join("\n");
99
+ }
100
+ // ============================================================
101
+ // CLI Wiring
102
+ // ============================================================
103
+ export function registerCrossrefCommand(program) {
104
+ program
105
+ .command("crossref")
106
+ .description("Generate .whygraph/CONTEXT.md — a graph snapshot for use when MCP is unavailable")
107
+ .option("--json", "Output results as JSON")
108
+ .action((opts) => {
109
+ try {
110
+ const result = runCrossref(process.cwd());
111
+ if (opts.json) {
112
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
113
+ }
114
+ else {
115
+ process.stdout.write(`Context snapshot written to ${result.outputPath}\n` +
116
+ ` Nodes: ${result.nodeCount}\n` +
117
+ ` Decisions: ${result.decisionCount}\n`);
118
+ }
119
+ }
120
+ catch (err) {
121
+ /* v8 ignore next 1 */
122
+ const message = err instanceof Error ? err.message : String(err);
123
+ if (opts.json) {
124
+ process.stdout.write(JSON.stringify({ error: message }, null, 2) + "\n");
125
+ }
126
+ else {
127
+ process.stderr.write(`Error: ${message}\n`);
128
+ }
129
+ process.exitCode = 1;
130
+ }
131
+ });
132
+ }
package/dist/cli/index.js CHANGED
@@ -4,6 +4,7 @@ import { Command } from "commander";
4
4
  const require = createRequire(import.meta.url);
5
5
  const { version } = require("../../package.json");
6
6
  import { registerConfigCommand } from "./commands/config.js";
7
+ import { registerCrossrefCommand } from "./commands/crossref.js";
7
8
  import { registerDownCommand } from "./commands/down.js";
8
9
  import { registerInitCommand } from "./commands/init.js";
9
10
  import { registerIssuesCommand } from "./commands/issues.js";
@@ -21,6 +22,7 @@ program
21
22
  .description("The graph of why — so your agent knows before it touches anything.")
22
23
  .version(version);
23
24
  registerConfigCommand(program);
25
+ registerCrossrefCommand(program);
24
26
  registerDownCommand(program);
25
27
  registerInitCommand(program);
26
28
  registerIssuesCommand(program);
@@ -62,12 +62,20 @@ function generateInstructions(config) {
62
62
  "### MCP Server (preferred)",
63
63
  "",
64
64
  `Use the \`whygraph\` MCP server for decision capture tools when available.`,
65
+ `- \`whygraph_context\` — get existing decisions and structural nodes for a file`,
66
+ `- \`whygraph_create_decision\` — capture a new decision`,
65
67
  "",
66
- "### Direct File Write (fallback)",
68
+ "### Direct File Access (fallback)",
67
69
  "",
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.",
70
+ "If the MCP server is unreachable or MCP is blocked by your environment:",
71
+ "",
72
+ "**For context:** Read `.whygraph/CONTEXT.md` if it exists this is a pre-built",
73
+ "graph snapshot generated by `npx whygraph crossref`. It lists all structural nodes",
74
+ "and decisions with their `affects` cross-references. If the file is absent, ask",
75
+ "the user to run `npx whygraph crossref` to generate it.",
76
+ "",
77
+ "**For capture:** Write the decision file directly to `.whygraph/graph/`",
78
+ "using the format above. It will be picked up automatically on next server start.",
71
79
  "",
72
80
  "### Server Status",
73
81
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whygraph",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "The graph of why. So your agent knows before it touches anything.",
5
5
  "author": "Geovanie Ruiz",
6
6
  "license": "MIT",