whywhy-mcp 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.
- package/LICENSE +21 -0
- package/README.md +213 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +110 -0
- package/dist/cli.js.map +1 -0
- package/dist/decisions.d.ts +63 -0
- package/dist/decisions.js +174 -0
- package/dist/decisions.js.map +1 -0
- package/dist/git.d.ts +14 -0
- package/dist/git.js +121 -0
- package/dist/git.js.map +1 -0
- package/dist/guidance.d.ts +8 -0
- package/dist/guidance.js +43 -0
- package/dist/guidance.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +460 -0
- package/dist/schema.js +139 -0
- package/dist/schema.js.map +1 -0
- package/dist/store.d.ts +45 -0
- package/dist/store.js +181 -0
- package/dist/store.js.map +1 -0
- package/package.json +58 -0
package/dist/git.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { simpleGit } from "simple-git";
|
|
4
|
+
/**
|
|
5
|
+
* Captures git evidence (branch, commits, working diff) for a decision.
|
|
6
|
+
*
|
|
7
|
+
* Everything here is best-effort: a repo with no git, no commits, or no
|
|
8
|
+
* changes still produces a valid (mostly-null) DiffEvidence rather than
|
|
9
|
+
* throwing, so recording a decision never fails because of git state.
|
|
10
|
+
*/
|
|
11
|
+
export async function captureGitEvidence(repoRoot) {
|
|
12
|
+
const git = simpleGit(repoRoot);
|
|
13
|
+
let isRepo = false;
|
|
14
|
+
try {
|
|
15
|
+
isRepo = await git.checkIsRepo();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
isRepo = false;
|
|
19
|
+
}
|
|
20
|
+
if (!isRepo)
|
|
21
|
+
return null;
|
|
22
|
+
const evidence = {
|
|
23
|
+
branch: null,
|
|
24
|
+
base_commit: null,
|
|
25
|
+
head_commit: null,
|
|
26
|
+
files_changed: [],
|
|
27
|
+
summary: "",
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
evidence.branch = (await git.revparse(["--abbrev-ref", "HEAD"])).trim() || null;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* detached or no branch */
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
evidence.head_commit = (await git.revparse(["HEAD"])).trim().slice(0, 40) || null;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* no commits yet */
|
|
40
|
+
}
|
|
41
|
+
// base_commit = parent of HEAD when available; otherwise HEAD itself.
|
|
42
|
+
try {
|
|
43
|
+
evidence.base_commit = (await git.revparse(["HEAD~1"])).trim().slice(0, 40) || null;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
evidence.base_commit = evidence.head_commit;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const status = await git.status();
|
|
50
|
+
const changed = new Set([
|
|
51
|
+
...status.modified,
|
|
52
|
+
...status.created,
|
|
53
|
+
...status.deleted,
|
|
54
|
+
...status.renamed.map((r) => r.to),
|
|
55
|
+
...status.not_added,
|
|
56
|
+
...status.staged,
|
|
57
|
+
]);
|
|
58
|
+
evidence.files_changed = [...changed].sort();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* ignore */
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const stat = await git.raw(["diff", "--stat"]);
|
|
65
|
+
const stagedStat = await git.raw(["diff", "--stat", "--cached"]);
|
|
66
|
+
const combined = [stat.trim(), stagedStat.trim()].filter(Boolean).join("\n");
|
|
67
|
+
evidence.summary = combined
|
|
68
|
+
? truncate(combined, 4000)
|
|
69
|
+
: "No uncommitted changes in the working tree at capture time.";
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
evidence.summary = "Diff summary unavailable.";
|
|
73
|
+
}
|
|
74
|
+
return evidence;
|
|
75
|
+
}
|
|
76
|
+
function truncate(s, max) {
|
|
77
|
+
return s.length > max ? s.slice(0, max) + "\n...(truncated)" : s;
|
|
78
|
+
}
|
|
79
|
+
/** Matches common single-line comment markers across languages. */
|
|
80
|
+
const COMMENT_PATTERNS = [
|
|
81
|
+
/\/\/\s?(.*)$/, // // ...
|
|
82
|
+
/#\s?(.*)$/, // # ...
|
|
83
|
+
/--\s?(.*)$/, // -- ... (SQL)
|
|
84
|
+
/\/\*\s?(.*?)\s?\*\//, // /* ... */ on one line
|
|
85
|
+
/<!--\s?(.*?)\s?-->/, // <!-- ... -->
|
|
86
|
+
];
|
|
87
|
+
/**
|
|
88
|
+
* Scans the given files for code comments, returning a small sample so a
|
|
89
|
+
* decision record can point at the in-code rationale without bloating.
|
|
90
|
+
*/
|
|
91
|
+
export async function scanCodeComments(repoRoot, files, maxPerFile = 5) {
|
|
92
|
+
const results = [];
|
|
93
|
+
for (const rel of files) {
|
|
94
|
+
const abs = path.resolve(repoRoot, rel);
|
|
95
|
+
let content;
|
|
96
|
+
try {
|
|
97
|
+
const stat = await fs.stat(abs);
|
|
98
|
+
if (!stat.isFile() || stat.size > 512 * 1024)
|
|
99
|
+
continue; // skip huge/binary-ish
|
|
100
|
+
content = await fs.readFile(abs, "utf8");
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const lines = content.split(/\r?\n/);
|
|
106
|
+
let found = 0;
|
|
107
|
+
for (let i = 0; i < lines.length && found < maxPerFile; i++) {
|
|
108
|
+
const line = lines[i];
|
|
109
|
+
for (const pattern of COMMENT_PATTERNS) {
|
|
110
|
+
const m = line.match(pattern);
|
|
111
|
+
if (m && m[1] && m[1].trim().length > 0) {
|
|
112
|
+
results.push({ file: rel, line: i + 1, text: m[1].trim().slice(0, 300) });
|
|
113
|
+
found++;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=git.js.map
|
package/dist/git.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent-guidance snippet. This is the agent-agnostic glue: it tells any
|
|
3
|
+
* MCP-capable coding agent WHEN to call the whywhy tools. `init` appends it to
|
|
4
|
+
* CLAUDE.md (and prints it for pasting into Cursor rules, etc.).
|
|
5
|
+
*/
|
|
6
|
+
export declare const GUIDANCE_SNIPPET = "<!-- whywhy:begin -->\n## Decision logging (whywhy)\n\nThis repo uses **whywhy** to record *why* decisions are made. Whenever you and\nthe user reach a meaningful decision, call the whywhy MCP tools so the reasoning\nis preserved alongside the code.\n\n**Log a decision when you settle any of:**\n- Architecture or system design (boundaries, patterns, data flow)\n- Infrastructure or deployment (databases, hosting, queues, CI)\n- A dependency / library choice (and what you rejected)\n- An API shape or contract\n- A data-model / schema decision\n- A UI / UX direction with real trade-offs\n- A product or scope decision\n- Security, performance, or process choices\n\n**Do NOT log:** trivial edits, formatting, renames, obvious one-way-door\nfixes, or anything with no meaningful alternative.\n\n**How:**\n- When a decision is reached, call `record_decision` *before moving on*.\n Include the alternatives you weighed and why you rejected them, and pass the\n relevant `files` so whywhy can capture the diff and code comments.\n- If you are reversing a past decision, call `supersede_decision` with the old\n decision's id instead of recording a fresh, disconnected one.\n- To fix or extend an existing record, use `update_decision`.\n- To retire a decision with no replacement, use `deprecate_decision`.\n- When the user asks \"why did we \u2026?\", call `query_decisions` and answer from\n the returned records.\n\nIf you prefer not to interrupt flow, batch decisions and record them at the end\nof the session -- but never lose them.\n<!-- whywhy:end -->\n";
|
|
7
|
+
export declare const GUIDANCE_BEGIN = "<!-- whywhy:begin -->";
|
|
8
|
+
export declare const GUIDANCE_END = "<!-- whywhy:end -->";
|
package/dist/guidance.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent-guidance snippet. This is the agent-agnostic glue: it tells any
|
|
3
|
+
* MCP-capable coding agent WHEN to call the whywhy tools. `init` appends it to
|
|
4
|
+
* CLAUDE.md (and prints it for pasting into Cursor rules, etc.).
|
|
5
|
+
*/
|
|
6
|
+
export const GUIDANCE_SNIPPET = `<!-- whywhy:begin -->
|
|
7
|
+
## Decision logging (whywhy)
|
|
8
|
+
|
|
9
|
+
This repo uses **whywhy** to record *why* decisions are made. Whenever you and
|
|
10
|
+
the user reach a meaningful decision, call the whywhy MCP tools so the reasoning
|
|
11
|
+
is preserved alongside the code.
|
|
12
|
+
|
|
13
|
+
**Log a decision when you settle any of:**
|
|
14
|
+
- Architecture or system design (boundaries, patterns, data flow)
|
|
15
|
+
- Infrastructure or deployment (databases, hosting, queues, CI)
|
|
16
|
+
- A dependency / library choice (and what you rejected)
|
|
17
|
+
- An API shape or contract
|
|
18
|
+
- A data-model / schema decision
|
|
19
|
+
- A UI / UX direction with real trade-offs
|
|
20
|
+
- A product or scope decision
|
|
21
|
+
- Security, performance, or process choices
|
|
22
|
+
|
|
23
|
+
**Do NOT log:** trivial edits, formatting, renames, obvious one-way-door
|
|
24
|
+
fixes, or anything with no meaningful alternative.
|
|
25
|
+
|
|
26
|
+
**How:**
|
|
27
|
+
- When a decision is reached, call \`record_decision\` *before moving on*.
|
|
28
|
+
Include the alternatives you weighed and why you rejected them, and pass the
|
|
29
|
+
relevant \`files\` so whywhy can capture the diff and code comments.
|
|
30
|
+
- If you are reversing a past decision, call \`supersede_decision\` with the old
|
|
31
|
+
decision's id instead of recording a fresh, disconnected one.
|
|
32
|
+
- To fix or extend an existing record, use \`update_decision\`.
|
|
33
|
+
- To retire a decision with no replacement, use \`deprecate_decision\`.
|
|
34
|
+
- When the user asks "why did we …?", call \`query_decisions\` and answer from
|
|
35
|
+
the returned records.
|
|
36
|
+
|
|
37
|
+
If you prefer not to interrupt flow, batch decisions and record them at the end
|
|
38
|
+
of the session -- but never lose them.
|
|
39
|
+
<!-- whywhy:end -->
|
|
40
|
+
`;
|
|
41
|
+
export const GUIDANCE_BEGIN = "<!-- whywhy:begin -->";
|
|
42
|
+
export const GUIDANCE_END = "<!-- whywhy:end -->";
|
|
43
|
+
//# sourceMappingURL=guidance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guidance.js","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkC/B,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,uBAAuB,CAAC;AACtD,MAAM,CAAC,MAAM,YAAY,GAAG,qBAAqB,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { CategorySchema, StatusSchema, AlternativeSchema, } from "./schema.js";
|
|
6
|
+
import { DecisionStore } from "./store.js";
|
|
7
|
+
import { createDecision, deprecateDecision, listDecisions, queryDecisions, supersedeDecision, updateDecision, } from "./decisions.js";
|
|
8
|
+
/**
|
|
9
|
+
* whywhy MCP server (stdio).
|
|
10
|
+
*
|
|
11
|
+
* Exposes the decision-capture/query tools described in the PRD. The repo root
|
|
12
|
+
* defaults to the process working directory (where the agent launches the
|
|
13
|
+
* server) and can be overridden with WHYWHY_ROOT.
|
|
14
|
+
*/
|
|
15
|
+
const REPO_ROOT = process.env.WHYWHY_ROOT || process.cwd();
|
|
16
|
+
/** Lazily ensure the store exists and return it with the resolved config. */
|
|
17
|
+
async function getContext() {
|
|
18
|
+
// Resolve store path from an existing config if present.
|
|
19
|
+
let store = new DecisionStore(REPO_ROOT);
|
|
20
|
+
let config;
|
|
21
|
+
try {
|
|
22
|
+
config = await store.readConfig();
|
|
23
|
+
store = new DecisionStore(REPO_ROOT, config.store_path);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
config = await store.init();
|
|
27
|
+
}
|
|
28
|
+
await store.init(config);
|
|
29
|
+
return { store, config };
|
|
30
|
+
}
|
|
31
|
+
function ok(text, data) {
|
|
32
|
+
const body = data !== undefined ? `${text}\n\n${JSON.stringify(data, null, 2)}` : text;
|
|
33
|
+
return { content: [{ type: "text", text: body }] };
|
|
34
|
+
}
|
|
35
|
+
function fail(text) {
|
|
36
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
37
|
+
}
|
|
38
|
+
const server = new McpServer({
|
|
39
|
+
name: "whywhy",
|
|
40
|
+
version: "0.1.0",
|
|
41
|
+
});
|
|
42
|
+
/* ---- record_decision ------------------------------------------------ */
|
|
43
|
+
server.tool("record_decision", "Log a new architectural/product/development decision with its reasoning. " +
|
|
44
|
+
"Automatically captures the current git diff and any related code comments as evidence. " +
|
|
45
|
+
"Call this whenever you and the user settle a meaningful decision.", {
|
|
46
|
+
title: z.string().min(1),
|
|
47
|
+
category: CategorySchema,
|
|
48
|
+
decision: z.string().min(1),
|
|
49
|
+
rationale: z.string().min(1),
|
|
50
|
+
alternatives_considered: z.array(AlternativeSchema).optional(),
|
|
51
|
+
consequences: z.string().optional(),
|
|
52
|
+
tags: z.array(z.string()).optional(),
|
|
53
|
+
conversation_summary: z.string().optional(),
|
|
54
|
+
conversation_excerpt: z.string().optional(),
|
|
55
|
+
files: z.array(z.string()).optional(),
|
|
56
|
+
session_id: z.string().optional(),
|
|
57
|
+
}, async (input) => {
|
|
58
|
+
const { store, config } = await getContext();
|
|
59
|
+
const record = await createDecision(store, config, input);
|
|
60
|
+
return ok(`Recorded decision ${record.id}: "${record.title}" (status: active).`, record);
|
|
61
|
+
});
|
|
62
|
+
/* ---- supersede_decision --------------------------------------------- */
|
|
63
|
+
server.tool("supersede_decision", "Replace an existing decision with a new one. Creates a fresh record, links " +
|
|
64
|
+
"the two, and flips the old decision's status to 'superseded'. Use this when " +
|
|
65
|
+
"reversing or changing a previously recorded decision instead of recording a new disconnected one.", {
|
|
66
|
+
old_id: z.string().describe("The id of the decision being replaced, e.g. DR-0003."),
|
|
67
|
+
title: z.string().min(1),
|
|
68
|
+
category: CategorySchema,
|
|
69
|
+
decision: z.string().min(1),
|
|
70
|
+
rationale: z.string().min(1),
|
|
71
|
+
alternatives_considered: z.array(AlternativeSchema).optional(),
|
|
72
|
+
consequences: z.string().optional(),
|
|
73
|
+
tags: z.array(z.string()).optional(),
|
|
74
|
+
conversation_summary: z.string().optional(),
|
|
75
|
+
conversation_excerpt: z.string().optional(),
|
|
76
|
+
files: z.array(z.string()).optional(),
|
|
77
|
+
session_id: z.string().optional(),
|
|
78
|
+
}, async (input) => {
|
|
79
|
+
const { store, config } = await getContext();
|
|
80
|
+
const { old_id, ...rest } = input;
|
|
81
|
+
const existing = await store.tryReadRecord(old_id);
|
|
82
|
+
if (!existing)
|
|
83
|
+
return fail(`No decision found with id ${old_id}.`);
|
|
84
|
+
const { newRecord, oldRecord } = await supersedeDecision(store, config, old_id, rest);
|
|
85
|
+
return ok(`Created ${newRecord.id} superseding ${oldRecord.id}. ${oldRecord.id} is now 'superseded'.`, { new: newRecord, superseded: oldRecord });
|
|
86
|
+
});
|
|
87
|
+
/* ---- update_decision ------------------------------------------------ */
|
|
88
|
+
server.tool("update_decision", "Edit an existing decision record in place (fix a typo, add a consequence, " +
|
|
89
|
+
"refine the rationale). Bumps the record's version. Does NOT change identity " +
|
|
90
|
+
"or status; use supersede_decision to replace a decision.", {
|
|
91
|
+
id: z.string(),
|
|
92
|
+
title: z.string().optional(),
|
|
93
|
+
category: CategorySchema.optional(),
|
|
94
|
+
decision: z.string().optional(),
|
|
95
|
+
rationale: z.string().optional(),
|
|
96
|
+
alternatives_considered: z.array(AlternativeSchema).optional(),
|
|
97
|
+
consequences: z.string().optional(),
|
|
98
|
+
tags: z.array(z.string()).optional(),
|
|
99
|
+
conversation_summary: z.string().optional(),
|
|
100
|
+
}, async (input) => {
|
|
101
|
+
const { store } = await getContext();
|
|
102
|
+
const { id, ...edits } = input;
|
|
103
|
+
const existing = await store.tryReadRecord(id);
|
|
104
|
+
if (!existing)
|
|
105
|
+
return fail(`No decision found with id ${id}.`);
|
|
106
|
+
const record = await updateDecision(store, id, edits);
|
|
107
|
+
return ok(`Updated ${record.id} (now version ${record.version}).`, record);
|
|
108
|
+
});
|
|
109
|
+
/* ---- deprecate_decision --------------------------------------------- */
|
|
110
|
+
server.tool("deprecate_decision", "Retire a decision that no longer applies and has no direct replacement " +
|
|
111
|
+
"(e.g. a removed feature). Sets status to 'deprecated'. The record is kept.", {
|
|
112
|
+
id: z.string(),
|
|
113
|
+
reason: z.string().optional(),
|
|
114
|
+
}, async ({ id, reason }) => {
|
|
115
|
+
const { store } = await getContext();
|
|
116
|
+
const existing = await store.tryReadRecord(id);
|
|
117
|
+
if (!existing)
|
|
118
|
+
return fail(`No decision found with id ${id}.`);
|
|
119
|
+
const record = await deprecateDecision(store, id, reason);
|
|
120
|
+
return ok(`Deprecated ${record.id}.`, record);
|
|
121
|
+
});
|
|
122
|
+
/* ---- query_decisions ------------------------------------------------ */
|
|
123
|
+
server.tool("query_decisions", "Search recorded decisions to answer 'why did we …?' questions. Combine a " +
|
|
124
|
+
"free-text query with optional category/status/tag filters. Returns matching " +
|
|
125
|
+
"full records (ranked by keyword relevance) for you to reason over.", {
|
|
126
|
+
query: z.string().optional(),
|
|
127
|
+
category: CategorySchema.optional(),
|
|
128
|
+
status: StatusSchema.optional(),
|
|
129
|
+
tags: z.array(z.string()).optional(),
|
|
130
|
+
}, async (filter) => {
|
|
131
|
+
const { store } = await getContext();
|
|
132
|
+
const results = await queryDecisions(store, filter);
|
|
133
|
+
if (results.length === 0)
|
|
134
|
+
return ok("No matching decisions found.", []);
|
|
135
|
+
return ok(`Found ${results.length} matching decision(s).`, results);
|
|
136
|
+
});
|
|
137
|
+
/* ---- get_decision --------------------------------------------------- */
|
|
138
|
+
server.tool("get_decision", "Fetch one decision record in full by its id.", { id: z.string() }, async ({ id }) => {
|
|
139
|
+
const { store } = await getContext();
|
|
140
|
+
const record = await store.tryReadRecord(id);
|
|
141
|
+
if (!record)
|
|
142
|
+
return fail(`No decision found with id ${id}.`);
|
|
143
|
+
return ok(`Decision ${record.id}:`, record);
|
|
144
|
+
});
|
|
145
|
+
/* ---- list_decisions ------------------------------------------------- */
|
|
146
|
+
server.tool("list_decisions", "List/browse decisions from the index (lightweight entries, not full records). " +
|
|
147
|
+
"Optionally filter by status or category.", {
|
|
148
|
+
status: StatusSchema.optional(),
|
|
149
|
+
category: CategorySchema.optional(),
|
|
150
|
+
}, async (filter) => {
|
|
151
|
+
const { store } = await getContext();
|
|
152
|
+
const entries = await listDecisions(store, filter);
|
|
153
|
+
return ok(`${entries.length} decision(s).`, entries);
|
|
154
|
+
});
|
|
155
|
+
/* ---- resources ------------------------------------------------------ */
|
|
156
|
+
server.resource("whywhy-index", "whywhy://index", async () => {
|
|
157
|
+
const { store } = await getContext();
|
|
158
|
+
const index = await store.readIndex();
|
|
159
|
+
return {
|
|
160
|
+
contents: [
|
|
161
|
+
{
|
|
162
|
+
uri: "whywhy://index",
|
|
163
|
+
mimeType: "application/json",
|
|
164
|
+
text: JSON.stringify(index, null, 2),
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
async function main() {
|
|
170
|
+
const transport = new StdioServerTransport();
|
|
171
|
+
await server.connect(transport);
|
|
172
|
+
// Stderr is safe for logs; stdout is the MCP channel.
|
|
173
|
+
console.error(`whywhy MCP server running (root: ${REPO_ROOT})`);
|
|
174
|
+
}
|
|
175
|
+
main().catch((err) => {
|
|
176
|
+
console.error("whywhy fatal error:", err);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|