@askthew/mcp-plugin 0.2.7 → 0.4.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.
@@ -0,0 +1,237 @@
1
+ const severityRank = {
2
+ high: 0,
3
+ medium: 1,
4
+ low: 2,
5
+ };
6
+ export function analyzeLocalPatterns(input) {
7
+ const tips = TIP_PATTERNS.map((pattern) => pattern(input)).filter((tip) => Boolean(tip));
8
+ return tips
9
+ .sort((left, right) => severityRank[left.severity] - severityRank[right.severity] || left.id.localeCompare(right.id))
10
+ .slice(0, 3);
11
+ }
12
+ export const TIP_PATTERNS = [
13
+ highProposedStagnation,
14
+ verificationFailClusters,
15
+ highChurnNoDecision,
16
+ directionThrash,
17
+ unboundedDecision,
18
+ lateNightDecisions,
19
+ singleFileObsession,
20
+ verificationMissing,
21
+ implementationWithoutSummary,
22
+ repeatedDirectionChanges,
23
+ abandonedSpike,
24
+ noRecentDecisionWhy,
25
+ longSessionNoCheckpoint,
26
+ setupOnlySession,
27
+ commandsHeavySession,
28
+ filesWithoutSignals,
29
+ ];
30
+ function highProposedStagnation({ decisions, now = new Date() }) {
31
+ const cutoff = now.getTime() - 3 * 24 * 60 * 60 * 1000;
32
+ const stuck = decisions.filter((decision) => decision.status === "proposed" && new Date(decision.createdAt).getTime() < cutoff);
33
+ if (stuck.length < 3) {
34
+ return null;
35
+ }
36
+ return {
37
+ id: "high_proposed_stagnation",
38
+ severity: "high",
39
+ tip: `${stuck.length} decisions are still proposed after more than 3 days. Block 30 minutes to commit or abandon the oldest ones.`,
40
+ alternative: "Timebox each new decision with an explicit deadline in `why`.",
41
+ evidence: stuck.slice(0, 5).map((decision) => decision.id),
42
+ };
43
+ }
44
+ function verificationFailClusters({ signals }) {
45
+ const verification = signals.filter((signal) => signal.kind === "verification_result").slice(-8);
46
+ const failures = verification.filter((signal) => /fail|failed|error|red|broken|timeout/i.test(signal.summary));
47
+ if (verification.length < 4 || failures.length < 3) {
48
+ return null;
49
+ }
50
+ return {
51
+ id: "verification_fail_clusters",
52
+ severity: "medium",
53
+ tip: `${failures.length} recent verifications failed. The fix is rarely the next blind attempt.`,
54
+ alternative: "Pause, write what specifically failed in a `direction_change` signal, then run a smaller test before the full suite.",
55
+ evidence: failures.slice(0, 5).map((signal) => `s_${signal.id}`),
56
+ };
57
+ }
58
+ function highChurnNoDecision({ signals, decisions }) {
59
+ const files = new Set(signals.flatMap((signal) => signal.filesTouched));
60
+ if (files.size < 12 || decisions.length > 0) {
61
+ return null;
62
+ }
63
+ return {
64
+ id: "high_churn_no_decision",
65
+ severity: "medium",
66
+ tip: `You touched ${files.size} files but logged 0 decisions in this window.`,
67
+ alternative: "Capture a single decision for the largest change - even one line of `why` beats none.",
68
+ evidence: [],
69
+ };
70
+ }
71
+ function directionThrash({ signals }) {
72
+ const changes = signals.filter((signal) => signal.kind === "direction_change");
73
+ if (changes.length < 4) {
74
+ return null;
75
+ }
76
+ return {
77
+ id: "direction_thrash",
78
+ severity: "high",
79
+ tip: `${changes.length} direction changes landed in one window. You may be switching approaches faster than evidence can settle.`,
80
+ alternative: "Pick one reversible path, write the stop condition, and only change course when that condition is met.",
81
+ evidence: changes.slice(0, 5).map((signal) => `s_${signal.id}`),
82
+ };
83
+ }
84
+ function unboundedDecision({ decisions }) {
85
+ const weak = decisions.filter((decision) => !decision.why || decision.why.trim().length < 12);
86
+ if (weak.length < 2) {
87
+ return null;
88
+ }
89
+ return {
90
+ id: "unbounded_decision",
91
+ severity: "medium",
92
+ tip: `${weak.length} decisions are missing a useful why, so future you will have to reconstruct the reason.`,
93
+ alternative: "Add one sentence: what tradeoff made this worth doing now?",
94
+ evidence: weak.slice(0, 5).map((decision) => decision.id),
95
+ };
96
+ }
97
+ function lateNightDecisions({ decisions }) {
98
+ const late = decisions.filter((decision) => {
99
+ const hour = new Date(decision.createdAt).getHours();
100
+ return hour <= 5 || hour >= 23;
101
+ });
102
+ if (late.length < 2) {
103
+ return null;
104
+ }
105
+ return {
106
+ id: "late_night_decisions",
107
+ severity: "low",
108
+ tip: `${late.length} decisions were logged late at night. That can be fine, but reread them when you're fresh.`,
109
+ alternative: "Mark late decisions as proposed first, then commit them after a daylight pass.",
110
+ evidence: late.slice(0, 5).map((decision) => decision.id),
111
+ };
112
+ }
113
+ function singleFileObsession({ signals }) {
114
+ const counts = new Map();
115
+ for (const signal of signals) {
116
+ for (const file of signal.filesTouched) {
117
+ counts.set(file, (counts.get(file) ?? 0) + 1);
118
+ }
119
+ }
120
+ const [file, count] = [...counts.entries()].sort((a, b) => b[1] - a[1])[0] ?? [];
121
+ if (!file || count < 6) {
122
+ return null;
123
+ }
124
+ return {
125
+ id: "single_file_obsession",
126
+ severity: "low",
127
+ tip: `One file came up in ${count} signals. It may be carrying too much responsibility.`,
128
+ alternative: "Before editing it again, name the responsibility you expect this file to own.",
129
+ evidence: [],
130
+ };
131
+ }
132
+ function verificationMissing({ signals }) {
133
+ const implementation = signals.filter((signal) => signal.kind === "implementation_update");
134
+ const verification = signals.filter((signal) => signal.kind === "verification_result");
135
+ if (implementation.length < 3 || verification.length > 0) {
136
+ return null;
137
+ }
138
+ return {
139
+ id: "verification_missing",
140
+ severity: "medium",
141
+ tip: `${implementation.length} implementation updates landed without a verification result.`,
142
+ alternative: "Run the smallest relevant check now and capture the result, even if it fails.",
143
+ evidence: implementation.slice(0, 5).map((signal) => `s_${signal.id}`),
144
+ };
145
+ }
146
+ function implementationWithoutSummary({ signals }) {
147
+ const terse = signals.filter((signal) => signal.kind === "implementation_update" && signal.summary.length < 30);
148
+ if (terse.length < 3) {
149
+ return null;
150
+ }
151
+ return {
152
+ id: "terse_implementation_updates",
153
+ severity: "low",
154
+ tip: `${terse.length} implementation updates are too terse to be useful later.`,
155
+ alternative: "Capture the change plus the reason in one sentence.",
156
+ evidence: terse.slice(0, 5).map((signal) => `s_${signal.id}`),
157
+ };
158
+ }
159
+ function repeatedDirectionChanges(input) {
160
+ return directionThrash(input);
161
+ }
162
+ function abandonedSpike({ decisions }) {
163
+ const abandoned = decisions.filter((decision) => decision.status === "abandoned");
164
+ if (abandoned.length < 3) {
165
+ return null;
166
+ }
167
+ return {
168
+ id: "abandoned_spike",
169
+ severity: "medium",
170
+ tip: `${abandoned.length} decisions were abandoned. That is useful learning, but only if the reason is captured.`,
171
+ alternative: "Add the rejection reason to each abandoned decision before moving on.",
172
+ evidence: abandoned.slice(0, 5).map((decision) => decision.id),
173
+ };
174
+ }
175
+ function noRecentDecisionWhy({ decisions }) {
176
+ const recent = decisions.slice(0, 5);
177
+ if (recent.length < 3 || recent.some((decision) => (decision.why ?? "").trim().length > 20)) {
178
+ return null;
179
+ }
180
+ return {
181
+ id: "no_recent_decision_why",
182
+ severity: "medium",
183
+ tip: "Your recent decisions have headlines, but not enough reasoning to explain the tradeoff.",
184
+ alternative: "For the next decision, write why the rejected option lost.",
185
+ evidence: recent.map((decision) => decision.id),
186
+ };
187
+ }
188
+ function longSessionNoCheckpoint({ signals }) {
189
+ if (signals.length < 10 || signals.some((signal) => signal.kind === "session_checkpoint")) {
190
+ return null;
191
+ }
192
+ return {
193
+ id: "long_session_no_checkpoint",
194
+ severity: "low",
195
+ tip: `${signals.length} signals landed without a checkpoint. The session may be hard to review later.`,
196
+ alternative: "Capture one checkpoint after each meaningful block of work.",
197
+ evidence: [],
198
+ };
199
+ }
200
+ function setupOnlySession({ signals }) {
201
+ if (signals.length !== 1 || signals[0]?.kind !== "setup_complete") {
202
+ return null;
203
+ }
204
+ return {
205
+ id: "setup_only_session",
206
+ severity: "low",
207
+ tip: "This session only has startup captured so far.",
208
+ alternative: "Capture a checkpoint after the first meaningful decision or verification.",
209
+ evidence: signals[0] ? [`s_${signals[0].id}`] : [],
210
+ };
211
+ }
212
+ function commandsHeavySession({ signals }) {
213
+ const commands = signals.reduce((total, signal) => total + signal.commandsRun.length, 0);
214
+ if (commands < 15 || signals.some((signal) => signal.kind === "direction_change")) {
215
+ return null;
216
+ }
217
+ return {
218
+ id: "commands_heavy_session",
219
+ severity: "low",
220
+ tip: `${commands} commands ran without a captured direction change. Make sure the command loop still has a clear target.`,
221
+ alternative: "Write the next expected result before running another broad command.",
222
+ evidence: [],
223
+ };
224
+ }
225
+ function filesWithoutSignals({ signals }) {
226
+ const files = new Set(signals.flatMap((signal) => signal.filesTouched));
227
+ if (files.size < 8 || signals.length >= 5) {
228
+ return null;
229
+ }
230
+ return {
231
+ id: "files_without_signals",
232
+ severity: "low",
233
+ tip: `${files.size} files appeared across only ${signals.length} signals. The capture trail is thin for the amount of movement.`,
234
+ alternative: "Add a checkpoint before the next edit so the review has a clear midpoint.",
235
+ evidence: [],
236
+ };
237
+ }
@@ -0,0 +1,19 @@
1
+ export declare const PRICING_URL = "https://askthew.com/pricing";
2
+ export declare const SUPPORT_EMAIL = "support@askthew.com";
3
+ export declare function paidDescription(description: string, mode: "paid" | "free" | "unauthenticated"): string;
4
+ export declare function paidFeatureNudge(tool: string): {
5
+ ok: boolean;
6
+ code: string;
7
+ tool: string;
8
+ message: string;
9
+ pricingUrl: string;
10
+ upgradeUrl: string;
11
+ supportEmail: string;
12
+ cta: string;
13
+ };
14
+ export declare function toolJson(value: unknown): {
15
+ content: {
16
+ type: "text";
17
+ text: string;
18
+ }[];
19
+ };
@@ -0,0 +1,30 @@
1
+ export const PRICING_URL = "https://askthew.com/pricing";
2
+ export const SUPPORT_EMAIL = "support@askthew.com";
3
+ export function paidDescription(description, mode) {
4
+ if (mode !== "free") {
5
+ return description;
6
+ }
7
+ return `${description} (paid - ${PRICING_URL})`;
8
+ }
9
+ export function paidFeatureNudge(tool) {
10
+ return {
11
+ ok: false,
12
+ code: "free_tier_paid_feature",
13
+ tool,
14
+ message: "This lives in your Ask The W workspace. It becomes available after you upgrade - your local decisions and signals will sync up automatically.",
15
+ pricingUrl: PRICING_URL,
16
+ upgradeUrl: `https://askthew.com/mcp?utm_source=mcp-plugin&utm_medium=tool-nudge&utm_campaign=mcp-free&tool=${encodeURIComponent(tool)}`,
17
+ supportEmail: SUPPORT_EMAIL,
18
+ cta: "Run: npx @askthew/mcp-plugin upgrade",
19
+ };
20
+ }
21
+ export function toolJson(value) {
22
+ return {
23
+ content: [
24
+ {
25
+ type: "text",
26
+ text: JSON.stringify(value, null, 2),
27
+ },
28
+ ],
29
+ };
30
+ }
@@ -0,0 +1,38 @@
1
+ import type { CliCredentials } from "./free-tier-policy.js";
2
+ import type { LocalStore } from "./local-store.js";
3
+ export declare function syncDryRun(store: LocalStore): {
4
+ pending: {
5
+ signals: number;
6
+ decisions: number;
7
+ };
8
+ alreadyUploaded: {
9
+ signals: number;
10
+ decisions: number;
11
+ };
12
+ };
13
+ export declare function uploadLocalStore(input: {
14
+ store: LocalStore;
15
+ credentials: CliCredentials;
16
+ syncToken?: string;
17
+ apiUrl?: string;
18
+ fetchImpl?: typeof fetch;
19
+ dryRun?: boolean;
20
+ }): Promise<{
21
+ pending: {
22
+ signals: number;
23
+ decisions: number;
24
+ };
25
+ alreadyUploaded: {
26
+ signals: number;
27
+ decisions: number;
28
+ };
29
+ ok: boolean;
30
+ dryRun: boolean;
31
+ uploaded?: undefined;
32
+ } | {
33
+ ok: boolean;
34
+ uploaded: {
35
+ signals: number;
36
+ decisions: number;
37
+ };
38
+ }>;
@@ -0,0 +1,60 @@
1
+ export function syncDryRun(store) {
2
+ const pendingSignals = store.listSignals({ limit: 100000 }).filter((signal) => !store.getMeta(`signal_uploaded:${signal.id}`));
3
+ const pendingDecisions = store.listDecisions({ limit: 100000, pendingUploadOnly: true });
4
+ const alreadySignals = store.listSignals({ limit: 100000 }).length - pendingSignals.length;
5
+ const alreadyDecisions = store.listDecisions({ limit: 100000 }).filter((decision) => decision.uploadedAt).length;
6
+ return {
7
+ pending: { signals: pendingSignals.length, decisions: pendingDecisions.length },
8
+ alreadyUploaded: { signals: alreadySignals, decisions: alreadyDecisions },
9
+ };
10
+ }
11
+ export async function uploadLocalStore(input) {
12
+ if (input.dryRun) {
13
+ return { ok: true, dryRun: true, ...syncDryRun(input.store) };
14
+ }
15
+ const fetcher = input.fetchImpl ?? fetch;
16
+ const apiUrl = (input.apiUrl ?? input.credentials.apiUrl ?? process.env.ASKTHEW_API_URL ?? "https://app.askthew.com").replace(/\/$/, "");
17
+ const token = input.syncToken ?? input.credentials.cliToken;
18
+ const signalRows = input.store
19
+ .listSignals({ limit: 100000 })
20
+ .filter((signal) => !input.store.getMeta(`signal_uploaded:${signal.id}`));
21
+ const decisionRows = input.store.listDecisions({ limit: 100000, pendingUploadOnly: true }).reverse();
22
+ let uploadedSignals = 0;
23
+ let uploadedDecisions = 0;
24
+ for (const chunk of chunks(signalRows, 100)) {
25
+ await postChunk(apiUrl, token, fetcher, "signals", chunk);
26
+ for (const signal of chunk) {
27
+ input.store.setMeta(`signal_uploaded:${signal.id}`, new Date().toISOString());
28
+ }
29
+ uploadedSignals += chunk.length;
30
+ }
31
+ for (const chunk of chunks(decisionRows, 100)) {
32
+ await postChunk(apiUrl, token, fetcher, "decisions", chunk);
33
+ for (const decision of chunk) {
34
+ input.store.updateDecision(decision.id, { uploadedAt: new Date().toISOString() });
35
+ }
36
+ uploadedDecisions += chunk.length;
37
+ }
38
+ return { ok: true, uploaded: { signals: uploadedSignals, decisions: uploadedDecisions } };
39
+ }
40
+ async function postChunk(apiUrl, token, fetcher, kind, items) {
41
+ const response = await fetcher(`${apiUrl}/api/cli/v1/upgrade/sync`, {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ Authorization: `Bearer ${token}`,
46
+ },
47
+ body: JSON.stringify({ uploadVersion: 1, kind, items }),
48
+ });
49
+ if (!response.ok) {
50
+ const body = await response.json().catch(() => null);
51
+ throw new Error(body?.error ? String(body.error) : `Upload failed for ${kind}.`);
52
+ }
53
+ }
54
+ function chunks(items, size) {
55
+ const result = [];
56
+ for (let index = 0; index < items.length; index += size) {
57
+ result.push(items.slice(index, index + size));
58
+ }
59
+ return result;
60
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { LocalStore } from "./lib/local-store.js";
7
+ test("local store migrates, captures signals, decisions, and FIFO telemetry", () => {
8
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-store-"));
9
+ const store = LocalStore.open({ path: path.join(dir, "store.sqlite") });
10
+ const signal = store.insertSignal({
11
+ sessionId: "s1",
12
+ sequence: 1,
13
+ kind: "session_checkpoint",
14
+ summary: "Checkpoint",
15
+ filesTouched: ["src/app.ts"],
16
+ commandsRun: ["npm test"],
17
+ });
18
+ const duplicate = store.insertSignal({
19
+ sessionId: "s1",
20
+ sequence: 1,
21
+ kind: "session_checkpoint",
22
+ summary: "Checkpoint duplicate",
23
+ });
24
+ const decision = store.createDecision({
25
+ rawContent: "Use local SQLite for free-tier capture.",
26
+ sessionId: "s1",
27
+ sourceSignalIds: [signal.id],
28
+ });
29
+ const firstOutbox = store.enqueueTelemetry({ n: 1 });
30
+ const secondOutbox = store.enqueueTelemetry({ n: 2 });
31
+ assert.equal(signal.id, duplicate.id);
32
+ assert.equal(store.listSignals({ sessionId: "s1" }).length, 1);
33
+ assert.equal(store.getDecision(decision.id)?.headline, "Use local SQLite for free-tier capture.");
34
+ assert.deepEqual(store.listTelemetryOutbox().map((row) => row.id), [firstOutbox, secondOutbox]);
35
+ store.close();
36
+ fs.rmSync(dir, { recursive: true, force: true });
37
+ });
package/dist/scope.d.ts CHANGED
@@ -5,4 +5,3 @@ export interface PluginScope {
5
5
  serviceName?: string;
6
6
  }
7
7
  export declare function resolvePluginScope(startDirectory?: string): PluginScope;
8
- export declare function resolveFunctionalAreaFromToml(startDirectory?: string): string;
package/dist/scope.js CHANGED
@@ -15,7 +15,6 @@ function parseAskTheWToml(filePath) {
15
15
  repoRoot: readValue("repo_root"),
16
16
  appPath: readValue("app_path"),
17
17
  serviceName: readValue("service_name"),
18
- functionalArea: readValue("functional_area"),
19
18
  };
20
19
  }
21
20
  function findRepoRoot(startDirectory) {
@@ -53,8 +52,3 @@ export function resolvePluginScope(startDirectory = process.cwd()) {
53
52
  ...(parsedConfig.serviceName ? { serviceName: parsedConfig.serviceName } : {}),
54
53
  };
55
54
  }
56
- export function resolveFunctionalAreaFromToml(startDirectory = process.cwd()) {
57
- const cwd = path.resolve(startDirectory);
58
- const parsedConfig = parseAskTheWToml(path.join(cwd, ".asktheworld.toml"));
59
- return parsedConfig.functionalArea || "";
60
- }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { resolvePluginScope } from "./scope.js";
7
+ test("resolvePluginScope derives repo and app path from cwd", () => {
8
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-scope-"));
9
+ const repoRoot = path.join(tempRoot, "repo");
10
+ const appRoot = path.join(repoRoot, "apps", "landing");
11
+ fs.mkdirSync(path.join(repoRoot, ".git"), { recursive: true });
12
+ fs.mkdirSync(appRoot, { recursive: true });
13
+ const scope = resolvePluginScope(appRoot);
14
+ assert.equal(scope.repoName, "repo");
15
+ assert.equal(scope.repoRoot, repoRoot);
16
+ assert.equal(scope.appPath, "apps/landing");
17
+ fs.rmSync(tempRoot, { recursive: true, force: true });
18
+ });
19
+ test("resolvePluginScope respects explicit .asktheworld.toml values", () => {
20
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-scope-config-"));
21
+ fs.mkdirSync(tempRoot, { recursive: true });
22
+ fs.writeFileSync(path.join(tempRoot, ".asktheworld.toml"), [
23
+ 'repo_name = "main-platform"',
24
+ 'app_path = "apps/decisions"',
25
+ 'service_name = "decisions"',
26
+ ].join("\n"), "utf8");
27
+ const scope = resolvePluginScope(tempRoot);
28
+ assert.equal(scope.repoName, "main-platform");
29
+ assert.equal(scope.appPath, "apps/decisions");
30
+ assert.equal(scope.serviceName, "decisions");
31
+ fs.rmSync(tempRoot, { recursive: true, force: true });
32
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { analyzeLocalPatterns } from "./lib/tip-engine.js";
4
+ function signal(id, kind, summary = "ok") {
5
+ return {
6
+ id,
7
+ sessionId: "s1",
8
+ sequence: id,
9
+ kind,
10
+ summary,
11
+ evidence: [],
12
+ filesTouched: [`file${id}.ts`],
13
+ commandsRun: [],
14
+ metadata: {},
15
+ capturedAt: "2026-05-05T10:00:00.000Z",
16
+ };
17
+ }
18
+ test("tip engine is deterministic and sorts top severity first", () => {
19
+ const signals = [
20
+ signal(1, "direction_change"),
21
+ signal(2, "direction_change"),
22
+ signal(3, "direction_change"),
23
+ signal(4, "direction_change"),
24
+ signal(5, "verification_result", "failed"),
25
+ signal(6, "verification_result", "failed"),
26
+ signal(7, "verification_result", "failed"),
27
+ signal(8, "verification_result", "passed"),
28
+ ];
29
+ const decisions = [1, 2, 3].map((index) => ({
30
+ id: `d_${index}`,
31
+ sessionId: "s1",
32
+ headline: `Decision ${index}`,
33
+ why: "",
34
+ status: "proposed",
35
+ alignment: null,
36
+ files: [],
37
+ sourceSignalIds: [],
38
+ rawContent: `Decision ${index}`,
39
+ createdAt: "2026-05-01T10:00:00.000Z",
40
+ updatedAt: "2026-05-01T10:00:00.000Z",
41
+ uploadedAt: null,
42
+ }));
43
+ const first = analyzeLocalPatterns({ signals, decisions, now: new Date("2026-05-05T10:00:00.000Z") });
44
+ const second = analyzeLocalPatterns({ signals, decisions, now: new Date("2026-05-05T10:00:00.000Z") });
45
+ assert.deepEqual(first, second);
46
+ assert.equal(first.length, 3);
47
+ assert.equal(first[0]?.severity, "high");
48
+ });
49
+ test("tip engine stays quiet on empty input", () => {
50
+ assert.deepEqual(analyzeLocalPatterns({ signals: [], decisions: [] }), []);
51
+ });
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@askthew/mcp-plugin",
3
- "version": "0.2.7",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
- "description": "Ask The W MCP connector for capturing compact coding-agent session signals.",
5
+ "description": "Ask The W MCP connector for local-first coding-agent decisions, signals, and review.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -11,14 +11,8 @@
11
11
  },
12
12
  "files": [
13
13
  "README.md",
14
- "dist/cli.d.ts",
15
- "dist/cli.js",
16
- "dist/index.d.ts",
17
- "dist/index.js",
18
- "dist/install.d.ts",
19
- "dist/install.js",
20
- "dist/scope.d.ts",
21
- "dist/scope.js"
14
+ "dist/**/*.d.ts",
15
+ "dist/**/*.js"
22
16
  ],
23
17
  "keywords": [
24
18
  "askthew",
@@ -57,6 +51,9 @@
57
51
  },
58
52
  "dependencies": {
59
53
  "@modelcontextprotocol/sdk": "^1.3.0",
54
+ "better-sqlite3": "^11.10.0",
55
+ "nanoid": "^5.1.5",
56
+ "prompts": "^2.4.2",
60
57
  "zod": "^3.24.2"
61
58
  },
62
59
  "devDependencies": {