@askthew/mcp-plugin 0.4.6 → 0.4.8

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.
@@ -1 +0,0 @@
1
- export {};
@@ -1,314 +0,0 @@
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 { formatInstallCommand, installBehaviorInstructions, installHostConfig, mergeHostSettings, resolveSettingsPath, sendInstallHeartbeat, uninstallBehaviorInstructions, uninstallHostConfig, verificationNextStep, } from "./install.js";
7
- test("mergeHostSettings preserves unrelated MCP servers and replaces askthew", () => {
8
- const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-merge-project-"));
9
- fs.writeFileSync(path.join(tempProject, "package.json"), "{}", "utf8");
10
- const merged = mergeHostSettings({
11
- existingSettings: {
12
- theme: "dark",
13
- mcpServers: {
14
- github: {
15
- command: "github-mcp",
16
- },
17
- askthew: {
18
- command: "old-command",
19
- },
20
- },
21
- },
22
- hostType: "codex",
23
- token: "token-123",
24
- apiUrl: "https://askthew.example.com",
25
- serverName: "askthew_workspace_a",
26
- cwd: tempProject,
27
- });
28
- assert.deepEqual(merged.theme, "dark");
29
- assert.deepEqual(Object.keys(merged.mcpServers ?? {}).sort(), ["askthew_workspace_a", "github"]);
30
- assert.deepEqual(merged.mcpServers.askthew_workspace_a, {
31
- command: "npx",
32
- args: ["-y", "--prefer-online", "--package", "@askthew/mcp-plugin@latest", "askthew-mcp"],
33
- env: {
34
- ASKTHEW_INSTALL_TOKEN: "token-123",
35
- ASKTHEW_API_URL: "https://askthew.example.com",
36
- ASKTHEW_HOST_TYPE: "codex",
37
- ASKTHEW_SERVER_NAME: "askthew_workspace_a",
38
- ASKTHEW_REPO_NAME: path.basename(tempProject),
39
- ASKTHEW_REPO_ROOT: tempProject,
40
- },
41
- });
42
- fs.rmSync(tempProject, { recursive: true, force: true });
43
- });
44
- test("installHostConfig writes Claude Code local MCP settings and stays idempotent", () => {
45
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-mcp-install-"));
46
- const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-claude-project-"));
47
- const settingsPath = resolveSettingsPath({
48
- hostType: "claude_code",
49
- homeDirectory: tempHome,
50
- });
51
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
52
- fs.writeFileSync(settingsPath, JSON.stringify({
53
- projects: {
54
- [tempProject]: {
55
- mcpServers: {
56
- github: {
57
- command: "github-mcp",
58
- },
59
- },
60
- },
61
- },
62
- }, null, 2), "utf8");
63
- const first = installHostConfig({
64
- hostType: "claude_code",
65
- token: "token-456",
66
- apiUrl: "https://askthew.example.com",
67
- serverName: "askthew_workspace_a",
68
- clientId: "claude_code",
69
- clientLabel: "Claude Code",
70
- homeDirectory: tempHome,
71
- cwd: tempProject,
72
- });
73
- const afterFirst = fs.readFileSync(settingsPath, "utf8");
74
- const second = installHostConfig({
75
- hostType: "claude_code",
76
- token: "token-456",
77
- apiUrl: "https://askthew.example.com",
78
- serverName: "askthew_workspace_a",
79
- clientId: "claude_code",
80
- clientLabel: "Claude Code",
81
- homeDirectory: tempHome,
82
- cwd: tempProject,
83
- });
84
- const afterSecond = fs.readFileSync(settingsPath, "utf8");
85
- const parsed = JSON.parse(afterSecond);
86
- const projectServers = parsed.projects[tempProject].mcpServers;
87
- assert.equal(first.settingsPath, settingsPath);
88
- assert.equal(second.settingsPath, settingsPath);
89
- assert.equal(afterFirst, afterSecond);
90
- assert.deepEqual(Object.keys(projectServers ?? {}).sort(), ["askthew_workspace_a", "github"]);
91
- assert.equal(projectServers.askthew_workspace_a.env.ASKTHEW_INSTALL_TOKEN, "token-456");
92
- assert.equal(projectServers.askthew_workspace_a.env.ASKTHEW_CLIENT_ID, "claude_code");
93
- assert.equal(projectServers.askthew_workspace_a.env.ASKTHEW_CLIENT_LABEL, "Claude Code");
94
- fs.rmSync(tempHome, { recursive: true, force: true });
95
- fs.rmSync(tempProject, { recursive: true, force: true });
96
- });
97
- test("installHostConfig writes Codex MCP servers to config.toml", () => {
98
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-mcp-multi-workspace-"));
99
- const settingsPath = resolveSettingsPath({
100
- hostType: "codex",
101
- homeDirectory: tempHome,
102
- });
103
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
104
- fs.writeFileSync(settingsPath, [
105
- 'model = "gpt-5"',
106
- "",
107
- "[mcp_servers.github]",
108
- 'command = "github-mcp"',
109
- "",
110
- ].join("\n"), "utf8");
111
- installHostConfig({
112
- hostType: "codex",
113
- token: "token-a",
114
- apiUrl: "https://askthew.example.com",
115
- serverName: "askthew_workspace_a",
116
- homeDirectory: tempHome,
117
- });
118
- installHostConfig({
119
- hostType: "codex",
120
- token: "token-b",
121
- apiUrl: "https://askthew.example.com",
122
- serverName: "askthew_workspace_b",
123
- homeDirectory: tempHome,
124
- });
125
- const toml = fs.readFileSync(settingsPath, "utf8");
126
- assert.match(toml, /\[mcp_servers\.github\]/);
127
- assert.match(toml, /\[mcp_servers\.askthew_workspace_a\]/);
128
- assert.match(toml, /\[mcp_servers\.askthew_workspace_b\]/);
129
- assert.match(toml, /ASKTHEW_INSTALL_TOKEN = "token-a"/);
130
- assert.match(toml, /ASKTHEW_INSTALL_TOKEN = "token-b"/);
131
- fs.rmSync(tempHome, { recursive: true, force: true });
132
- });
133
- test("installHostConfig dry run returns merged JSON without writing a file", () => {
134
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-mcp-dry-run-"));
135
- const settingsPath = resolveSettingsPath({
136
- hostType: "codex",
137
- homeDirectory: tempHome,
138
- });
139
- const result = installHostConfig({
140
- hostType: "codex",
141
- token: "token-789",
142
- apiUrl: "https://askthew.example.com",
143
- serverName: "askthew_workspace_a",
144
- homeDirectory: tempHome,
145
- dryRun: true,
146
- });
147
- assert.equal(result.wroteFile, false);
148
- assert.equal(fs.existsSync(settingsPath), false);
149
- assert.match(result.json, /ASKTHEW_INSTALL_TOKEN/);
150
- assert.match(result.json, /\[mcp_servers\.askthew_workspace_a\]/);
151
- fs.rmSync(tempHome, { recursive: true, force: true });
152
- });
153
- test("installHostConfig writes Cursor global mcp.json", () => {
154
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-cursor-mcp-"));
155
- const settingsPath = resolveSettingsPath({
156
- hostType: "cursor",
157
- homeDirectory: tempHome,
158
- });
159
- installHostConfig({
160
- hostType: "cursor",
161
- token: "token-cursor",
162
- apiUrl: "https://askthew.example.com",
163
- serverName: "askthew",
164
- homeDirectory: tempHome,
165
- });
166
- const parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
167
- assert.deepEqual(Object.keys(parsed.mcpServers ?? {}), ["askthew"]);
168
- assert.equal(parsed.mcpServers.askthew.command, "npx");
169
- assert.equal(parsed.mcpServers.askthew.env.ASKTHEW_HOST_TYPE, "cursor");
170
- fs.rmSync(tempHome, { recursive: true, force: true });
171
- });
172
- test("formatInstallCommand emits the one-command guided install form", () => {
173
- const command = formatInstallCommand({
174
- hostType: "codex",
175
- token: "token-abc",
176
- apiUrl: "https://askthew.example.com",
177
- serverName: "askthew_workspace_a",
178
- });
179
- assert.equal(command, 'npx -y --prefer-online --package @askthew/mcp-plugin@latest askthew-mcp install --host codex --token "token-abc" --api-url "https://askthew.example.com" --server-name "askthew_workspace_a"');
180
- });
181
- test("verificationNextStep points users to every-session startup capture", () => {
182
- const nextStep = verificationNextStep("codex");
183
- assert.match(nextStep, /Refresh Ask The W/);
184
- assert.match(nextStep, /every new Codex session in this repo/);
185
- assert.match(nextStep, /before plan mode or exploration/);
186
- assert.match(nextStep, /list_mcp_resources\/list_mcp_resource_templates may be empty/);
187
- });
188
- test("sendInstallHeartbeat pings Ask The W after config install", async () => {
189
- const calls = [];
190
- const ok = await sendInstallHeartbeat({
191
- hostType: "codex",
192
- token: "token-abc",
193
- apiUrl: "https://askthew.example.com/",
194
- serverName: "askthew",
195
- cwd: process.cwd(),
196
- fetchImpl: async (url, init) => {
197
- calls.push({
198
- url: String(url),
199
- body: JSON.parse(String(init?.body ?? "{}")),
200
- });
201
- return new Response("{}", { status: 200 });
202
- },
203
- });
204
- assert.equal(ok, true);
205
- assert.equal(calls[0]?.url, "https://askthew.example.com/api/connectors/mcp/heartbeat");
206
- assert.equal(calls[0]?.body.installToken, "token-abc");
207
- assert.equal(calls[0]?.body.hostType, "codex");
208
- assert.equal(calls[0]?.body.serverName, "askthew");
209
- assert.equal(calls[0]?.body.clientId, "codex");
210
- });
211
- test("installBehaviorInstructions adds persistent Codex tracking rules without clobbering existing instructions", () => {
212
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-agent-instructions-"));
213
- const claudePath = path.join(tempRoot, "CLAUDE.md");
214
- const agentsPath = path.join(tempRoot, "AGENTS.md");
215
- fs.writeFileSync(agentsPath, "# Existing instructions\n\nKeep this section.\n", "utf8");
216
- const result = installBehaviorInstructions({
217
- hostType: "codex",
218
- cwd: tempRoot,
219
- });
220
- const claudeContents = fs.readFileSync(claudePath, "utf8");
221
- const contents = fs.readFileSync(agentsPath, "utf8");
222
- assert.equal(result.path, agentsPath);
223
- assert.deepEqual(result.paths, [claudePath, agentsPath]);
224
- assert.match(claudeContents, /Ask The W Plugin/);
225
- assert.match(claudeContents, /At the start of every new Codex session in this repo/);
226
- assert.match(contents, /# Existing instructions/);
227
- assert.match(contents, /Ask The W Plugin/);
228
- assert.match(contents, /capture_session_signal/);
229
- assert.match(contents, /At the start of every new Codex session in this repo/);
230
- assert.match(contents, /before plan mode, exploration, or any normal reply/);
231
- assert.match(contents, /metadata\.recovered_missed_startup=true/);
232
- assert.match(contents, /before using tools that write files/);
233
- fs.rmSync(tempRoot, { recursive: true, force: true });
234
- });
235
- test("installBehaviorInstructions creates Cursor rule file", () => {
236
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-cursor-rules-"));
237
- const result = installBehaviorInstructions({
238
- hostType: "cursor",
239
- cwd: tempRoot,
240
- });
241
- assert.equal(result.path, path.join(tempRoot, ".cursor", "rules", "askthew.mdc"));
242
- assert.match(fs.readFileSync(result.path, "utf8"), /alwaysApply: true/);
243
- assert.match(fs.readFileSync(result.path, "utf8"), /Ask The W Plugin/);
244
- assert.match(fs.readFileSync(result.path, "utf8"), /At the start of every new Cursor session/);
245
- assert.match(fs.readFileSync(result.path, "utf8"), /before plan mode, exploration, or any normal reply/);
246
- assert.match(fs.readFileSync(result.path, "utf8"), /metadata\.recovered_missed_startup=true/);
247
- fs.rmSync(tempRoot, { recursive: true, force: true });
248
- });
249
- test("installBehaviorInstructions adds stack-specific verification nudges", () => {
250
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-stack-rules-"));
251
- fs.writeFileSync(path.join(tempRoot, "package.json"), JSON.stringify({
252
- dependencies: {
253
- next: "^15.0.0",
254
- openai: "^5.0.0",
255
- vite: "^6.0.0",
256
- },
257
- }), "utf8");
258
- const result = installBehaviorInstructions({
259
- hostType: "codex",
260
- cwd: tempRoot,
261
- dryRun: true,
262
- });
263
- assert.match(result.content, /Next\.js detected/);
264
- assert.match(result.content, /OpenAI SDK detected/);
265
- assert.match(result.content, /Vite detected/);
266
- fs.rmSync(tempRoot, { recursive: true, force: true });
267
- });
268
- test("uninstall removes host config and Ask The W agent instruction blocks", () => {
269
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-uninstall-home-"));
270
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-uninstall-project-"));
271
- installHostConfig({
272
- hostType: "codex",
273
- token: "token-uninstall",
274
- apiUrl: "https://askthew.example.com",
275
- serverName: "askthew",
276
- homeDirectory: tempHome,
277
- });
278
- installBehaviorInstructions({
279
- hostType: "codex",
280
- cwd: tempRoot,
281
- });
282
- const removed = uninstallHostConfig({
283
- hostType: "codex",
284
- serverName: "askthew",
285
- homeDirectory: tempHome,
286
- });
287
- const instructions = uninstallBehaviorInstructions({
288
- hostType: "codex",
289
- cwd: tempRoot,
290
- });
291
- assert.doesNotMatch(fs.readFileSync(removed.settingsPath, "utf8"), /askthew/);
292
- assert.equal(removed.foundConfigFile, true);
293
- assert.equal(removed.removedServer, true);
294
- assert.equal(removed.wroteFile, true);
295
- assert.equal(instructions.paths.length, 2);
296
- assert.doesNotMatch(fs.readFileSync(path.join(tempRoot, "AGENTS.md"), "utf8"), /ASKTHEW_PLUGIN_INSTRUCTIONS_START/);
297
- assert.doesNotMatch(fs.readFileSync(path.join(tempRoot, "CLAUDE.md"), "utf8"), /ASKTHEW_PLUGIN_INSTRUCTIONS_START/);
298
- fs.rmSync(tempHome, { recursive: true, force: true });
299
- fs.rmSync(tempRoot, { recursive: true, force: true });
300
- });
301
- test("uninstall reports when no broken-npx install artifacts exist", () => {
302
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-uninstall-empty-home-"));
303
- const removed = uninstallHostConfig({
304
- hostType: "codex",
305
- serverName: "askthew",
306
- homeDirectory: tempHome,
307
- dryRun: true,
308
- });
309
- assert.equal(removed.foundConfigFile, false);
310
- assert.equal(removed.removedServer, false);
311
- assert.equal(removed.wroteFile, false);
312
- assert.equal(removed.json, "");
313
- fs.rmSync(tempHome, { recursive: true, force: true });
314
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,29 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import crypto from "node:crypto";
4
- import fs from "node:fs";
5
- import os from "node:os";
6
- import path from "node:path";
7
- import { ensureLocalIdentity, signLocalIdentityPayload } from "./lib/local-identity.js";
8
- import { identityPath } from "./lib/paths.js";
9
- test("local identity uses install id as primary identity and signs summary payloads", () => {
10
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-local-identity-"));
11
- try {
12
- const env = { ASKTHEW_DATA_DIR: dataDir };
13
- const identity = ensureLocalIdentity({
14
- emailClaim: "Founder@Example.com",
15
- apiUrl: "https://app.askthew.com",
16
- env,
17
- });
18
- const body = JSON.stringify({ schemaVersion: 1, activity: { signalCount: 1 } });
19
- const signed = signLocalIdentityPayload({ identity, body, timestamp: "2026-05-07T12:00:00.000Z" });
20
- assert.match(identity.installId, /^[0-9a-f-]{36}$/);
21
- assert.equal(identity.emailClaim, "founder@example.com");
22
- assert.equal(fs.existsSync(identityPath(env)), true);
23
- assert.equal(crypto.verify(null, Buffer.from(`${signed.timestamp}.${body}`), identity.publicKey, Buffer.from(signed.signature, "base64url")), true);
24
- assert.equal(crypto.verify(null, Buffer.from(`${signed.timestamp}.${JSON.stringify({ tampered: true })}`), identity.publicKey, Buffer.from(signed.signature, "base64url")), false);
25
- }
26
- finally {
27
- fs.rmSync(dataDir, { recursive: true, force: true });
28
- }
29
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,71 +0,0 @@
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
- });
38
- test("local store scopes trail rows, links decisions, and tracks lifecycle timestamps", () => {
39
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-store-scope-"));
40
- const store = LocalStore.open({ path: path.join(dir, "store.sqlite") });
41
- const scoped = store.insertSignal({
42
- sessionId: "s1",
43
- sequence: 1,
44
- kind: "direction_change",
45
- summary: "Let's go with local search because it is fast.",
46
- scopeKey: "repo-a",
47
- });
48
- store.insertSignal({
49
- sessionId: "s2",
50
- sequence: 1,
51
- kind: "direction_change",
52
- summary: "Use a different decision in another repo.",
53
- scopeKey: "repo-b",
54
- });
55
- const decision = store.createDecision({
56
- rawContent: "Adopt local search.",
57
- sessionId: "s1",
58
- sourceSignalIds: [scoped.id],
59
- scopeKey: "repo-a",
60
- });
61
- const committed = store.updateDecision(decision.id, { status: "committed" });
62
- assert.equal(store.listSignals({ scopeKey: "repo-a" }).length, 1);
63
- assert.equal(store.listSignals({ scopeKey: "repo-b" }).length, 1);
64
- assert.equal(store.listDecisions({ scopeKey: "repo-a" }).length, 1);
65
- assert.equal(store.getDecisionForSignal(scoped.id)?.id, decision.id);
66
- assert.equal(Boolean(decision.proposedAt), true);
67
- assert.equal(Boolean(committed?.committedAt), true);
68
- assert.equal(store.mostRecentSessionId({ scopeKey: "repo-a" }), "s1");
69
- store.close();
70
- fs.rmSync(dir, { recursive: true, force: true });
71
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,49 +0,0 @@
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
- });
33
- test("resolvePluginScope prefers install-time repo env over npx sandbox cwd", () => {
34
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-scope-env-"));
35
- const repoRoot = path.join(tempRoot, "ThesisEngine-main");
36
- const sandboxRoot = path.join(tempRoot, "task");
37
- fs.mkdirSync(path.join(repoRoot, ".git"), { recursive: true });
38
- fs.mkdirSync(sandboxRoot, { recursive: true });
39
- fs.writeFileSync(path.join(sandboxRoot, "package.json"), "{}", "utf8");
40
- const scope = resolvePluginScope(sandboxRoot, {
41
- ASKTHEW_REPO_NAME: "ThesisEngine-main",
42
- ASKTHEW_REPO_ROOT: repoRoot,
43
- ASKTHEW_APP_PATH: "apps/app",
44
- });
45
- assert.equal(scope.repoName, "ThesisEngine-main");
46
- assert.equal(scope.repoRoot, repoRoot);
47
- assert.equal(scope.appPath, "apps/app");
48
- fs.rmSync(tempRoot, { recursive: true, force: true });
49
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,85 +0,0 @@
1
- import test from "node:test";
2
- import assert from "node:assert/strict";
3
- import { buildTimelineInsights, buildLocalTimeline } from "./lib/timeline-insights.js";
4
- function signal(id, day) {
5
- return {
6
- id,
7
- sessionId: "session-a",
8
- sequence: id,
9
- kind: "session_checkpoint",
10
- summary: "checkpoint",
11
- evidence: [],
12
- filesTouched: [],
13
- commandsRun: [],
14
- metadata: {},
15
- capturedAt: `${day}T10:00:00.000Z`,
16
- };
17
- }
18
- test("local timeline buckets signals and decisions by day", () => {
19
- const decisions = [{
20
- id: "d1",
21
- sessionId: "session-a",
22
- headline: "Decision",
23
- why: null,
24
- status: "proposed",
25
- alignment: null,
26
- files: [],
27
- sourceSignalIds: [],
28
- rawContent: "Decision",
29
- createdAt: "2026-05-01T11:00:00.000Z",
30
- updatedAt: "2026-05-01T11:00:00.000Z",
31
- uploadedAt: null,
32
- }];
33
- const points = buildLocalTimeline({
34
- scope: "day",
35
- signals: [signal(1, "2026-05-01"), signal(2, "2026-05-02")],
36
- decisions,
37
- });
38
- assert.deepEqual(points.map((point) => [point.x, point.signalCount, point.decisionCount]), [
39
- ["2026-05-01", 1, 1],
40
- ["2026-05-02", 1, 0],
41
- ]);
42
- });
43
- test("local timeline insight ids stay in timeline namespace", () => {
44
- const insights = buildTimelineInsights([{ x: "2026-05-01", signalCount: 20, decisionCount: 1 }]);
45
- assert.ok(insights.every((insight) => insight.id.startsWith("timeline.")));
46
- });
47
- test("session timeline buckets by sessionId without inventing Other", () => {
48
- const decisions = [
49
- {
50
- id: "d1",
51
- sessionId: null,
52
- headline: "Decision without session",
53
- why: null,
54
- status: "proposed",
55
- alignment: null,
56
- files: [],
57
- sourceSignalIds: [],
58
- rawContent: "Decision without session",
59
- createdAt: "2026-05-01T11:00:00.000Z",
60
- updatedAt: "2026-05-01T11:00:00.000Z",
61
- uploadedAt: null,
62
- },
63
- {
64
- id: "d2",
65
- sessionId: "session-a",
66
- headline: "Decision with session",
67
- why: null,
68
- status: "proposed",
69
- alignment: null,
70
- files: [],
71
- sourceSignalIds: [],
72
- rawContent: "Decision with session",
73
- createdAt: "2026-05-01T11:00:00.000Z",
74
- updatedAt: "2026-05-01T11:00:00.000Z",
75
- uploadedAt: null,
76
- },
77
- ];
78
- const points = buildLocalTimeline({
79
- scope: "session",
80
- signals: [signal(1, "2026-05-01")],
81
- decisions,
82
- });
83
- assert.deepEqual(points.map((point) => point.x), ["session-a"]);
84
- assert.equal(points[0]?.decisionCount, 1);
85
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,51 +0,0 @@
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
- });