forgetrail 0.3.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 (98) hide show
  1. package/CONTINUATION_PROMPT.md +42 -0
  2. package/INITIAL_PROMPT.md +214 -0
  3. package/LICENSE +201 -0
  4. package/README.md +198 -0
  5. package/TRACKING_SCHEMA.md +162 -0
  6. package/TRY_FORGETRAIL.md +78 -0
  7. package/WORKFLOW.md +921 -0
  8. package/content/AGENT_INTEGRATION_claude.md +38 -0
  9. package/content/AGENT_INTEGRATION_cursor.md +53 -0
  10. package/content/AGENT_INTEGRATION_generic.md +50 -0
  11. package/content/AGENT_INTEGRATION_grok.md +82 -0
  12. package/content/DEV_AUTOMATION_SCRIPTS.md +103 -0
  13. package/content/FORGETRAIL_LITE.md +1202 -0
  14. package/content/FORGETRAIL_LITE_UPDATES.md +13 -0
  15. package/content/FORGETRAIL_PROGRESS.md +26 -0
  16. package/content/GENESIS_SPEC_PROMPT.md +70 -0
  17. package/content/GENESIS_STUB.md +18 -0
  18. package/content/GREENFIELD_INTAKE.md +76 -0
  19. package/content/KICKOFF_WITHOUT_MCP.md +16 -0
  20. package/content/LITE_WORKFLOW_TRACKING.json +42 -0
  21. package/content/NEW_PROJECT_BOOTSTRAP.md +180 -0
  22. package/content/ONE_CLICK_DEV_SETUP.md +142 -0
  23. package/content/PLAN_MODE_PATTERNS.md +43 -0
  24. package/content/POCKETBASE_SCHEMA_SCRIPT.md +52 -0
  25. package/content/POST_BOOTSTRAP_USER_MESSAGE.md +51 -0
  26. package/content/SCAFFOLD_INSTALL.json +110 -0
  27. package/content/SESSION_RESUME_MCP.md +35 -0
  28. package/content/SYSTEM_HEALTH_CHECKS.md +131 -0
  29. package/content/USER_REPLY_FORMAT.md +41 -0
  30. package/content/cursor-rules/forgetrail-lessons-gate.mdc +30 -0
  31. package/content/cursor-rules/forgetrail-lessons-mcp.mdc +24 -0
  32. package/content/cursor-rules/forgetrail-no-trailer.mdc +49 -0
  33. package/content/cursor-rules/forgetrail-phase-status.mdc +61 -0
  34. package/content/cursor-rules/forgetrail-updates-log.mdc +30 -0
  35. package/content/cursor-rules/spec-completion.mdc +33 -0
  36. package/content/cursor-rules/specs-and-todo.mdc +26 -0
  37. package/content/cursor-rules/url-host-matching.mdc +31 -0
  38. package/content/cursor-rules/us-english.mdc +9 -0
  39. package/content/cursor-rules/user-facing-content.mdc +31 -0
  40. package/content/examples/GENESIS_SAMPLE_mars-habitat-roster.md +154 -0
  41. package/content/forgetrail-workspace-README.md +43 -0
  42. package/content/scripts/forgetrail-dev-launcher.mjs +208 -0
  43. package/content/scripts/forgetrail-env.mjs +51 -0
  44. package/content/scripts/setup-ollama.mjs +207 -0
  45. package/content/scripts/setup-pocketbase.mjs +133 -0
  46. package/content/scripts/test-ollama.mjs +71 -0
  47. package/content/scripts/test-pocketbase.mjs +110 -0
  48. package/content/skills/forgetrail/SKILL.md +92 -0
  49. package/docs/AUTOMATED_TESTING.md +269 -0
  50. package/docs/BLACK_HAT_REPORT.md +143 -0
  51. package/docs/BRAND_AND_PRODUCT.md +381 -0
  52. package/docs/BUGS.md +62 -0
  53. package/docs/BUSINESS_PLAN.md +256 -0
  54. package/docs/CODE_QUALITY.md +170 -0
  55. package/docs/CONTEXT_PROMPT.md +357 -0
  56. package/docs/DEPLOYMENT.md +181 -0
  57. package/docs/DESIGN_SYSTEM.md +414 -0
  58. package/docs/DEV_ESTIMATE.md +127 -0
  59. package/docs/FORGETRAIL_RENAME.md +193 -0
  60. package/docs/IDEAS.md +19 -0
  61. package/docs/MARKETING_GROWTH.md +301 -0
  62. package/docs/NAMING_EXPLORATION.md +406 -0
  63. package/docs/NPM.md +98 -0
  64. package/docs/PHASE_1_BRIEF.md +161 -0
  65. package/docs/README.md +75 -0
  66. package/docs/SPEC_FEATURE_TEMPLATE.md +155 -0
  67. package/docs/SPEC_UI_CHROME_NAV_TEMPLATE.md +48 -0
  68. package/docs/TECHNICAL_REFERENCE.md +836 -0
  69. package/docs/TEST_PLAN.md +467 -0
  70. package/docs/TODO.md +125 -0
  71. package/package.json +68 -0
  72. package/prompts/black-hat-audit.md +235 -0
  73. package/prompts/brand-copy-edit-pass.md +55 -0
  74. package/prompts/cialdini-marketing-audit.md +141 -0
  75. package/prompts/competitor-deep-dive.md +201 -0
  76. package/prompts/docs-alignment-audit.md +108 -0
  77. package/prompts/engineering-skill-library.md +75 -0
  78. package/prompts/landing-page-rewrite.md +63 -0
  79. package/prompts/microcopy-centralization.md +128 -0
  80. package/prompts/panel-usability-audit.md +197 -0
  81. package/prompts/personal-beta-outreach.md +195 -0
  82. package/prompts/pre-launch-audit.md +148 -0
  83. package/prompts/product-feedback-to-spec.md +76 -0
  84. package/prompts/propagate-to-forgetrail.md +315 -0
  85. package/prompts/user-facing-content-sync-audit.md +200 -0
  86. package/prompts/ux-cohesion-audit.md +165 -0
  87. package/scripts/ensure-lease.mjs +23 -0
  88. package/scripts/forgetrail-cli.mjs +85 -0
  89. package/scripts/install-forgetrail-lite.mjs +11 -0
  90. package/scripts/install-forgetrail.mjs +11 -0
  91. package/scripts/install-lib.mjs +115 -0
  92. package/scripts/install.mjs +163 -0
  93. package/scripts/link-global.mjs +100 -0
  94. package/scripts/mcp-lib.mjs +177 -0
  95. package/scripts/mcp-status.mjs +155 -0
  96. package/scripts/publish-gate.mjs +110 -0
  97. package/scripts/rename-to-forgetrail.mjs +273 -0
  98. package/workflow_tracking.json +124 -0
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readFileSync, statSync } from "node:fs";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawnSync } from "node:child_process";
7
+
8
+ export const FORGETRAIL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
+ export const MCP_SERVER_DIR = join(FORGETRAIL_ROOT, "mcp-server");
10
+ export const MCP_ENTRY = join(MCP_SERVER_DIR, "dist", "index.js");
11
+ export const WORKFLOW_PATH = join(FORGETRAIL_ROOT, "WORKFLOW.md");
12
+
13
+ export function readJson(path) {
14
+ try {
15
+ return JSON.parse(readFileSync(path, "utf-8"));
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ export function normalizePath(p) {
22
+ if (!p || typeof p !== "string") return "";
23
+ return resolve(p.replace(/\//g, "\\").replace(/\\/g, "/").replace(/^([A-Za-z]):/, (_, d) => `${d.toUpperCase()}:`));
24
+ }
25
+
26
+ /** Find .cursor/mcp.json starting at dir, walking up to root. */
27
+ export function findCursorMcpJson(startDir = process.cwd()) {
28
+ let dir = resolve(startDir);
29
+ for (;;) {
30
+ const candidate = join(dir, ".cursor", "mcp.json");
31
+ if (existsSync(candidate)) return candidate;
32
+ const parent = dirname(dir);
33
+ if (parent === dir) break;
34
+ dir = parent;
35
+ }
36
+ return join(FORGETRAIL_ROOT, ".cursor", "mcp.json");
37
+ }
38
+
39
+ export function analyzeCursorMcpConfig(mcpJsonPath) {
40
+ const issues = [];
41
+ const notes = [];
42
+ if (!existsSync(mcpJsonPath)) {
43
+ return {
44
+ path: mcpJsonPath,
45
+ found: false,
46
+ issues: ["No .cursor/mcp.json found (checked cwd ancestors and forgetrail root)."],
47
+ notes: [
48
+ "Create .cursor/mcp.json with a forgetrail server entry pointing at mcp-server/dist/index.js.",
49
+ "See mcp-server/README.md or run: forgetrail mcp cursor-config",
50
+ ],
51
+ };
52
+ }
53
+
54
+ const raw = readJson(mcpJsonPath);
55
+ if (!raw?.mcpServers) {
56
+ return { path: mcpJsonPath, found: true, issues: ["Invalid mcp.json: missing mcpServers."], notes: [] };
57
+ }
58
+
59
+ const forgetrail =
60
+ raw.mcpServers.forgetrail ??
61
+ raw.mcpServers["forgetrail-mcp"] ??
62
+ Object.entries(raw.mcpServers).find(([k]) => /forgetrail/i.test(k))?.[1];
63
+
64
+ if (!forgetrail) {
65
+ issues.push('No "forgetrail" entry under mcpServers.');
66
+ notes.push('Add a server named "forgetrail" (Cursor shows this name in MCP settings).');
67
+ return { path: mcpJsonPath, found: true, issues, notes, config: raw };
68
+ }
69
+
70
+ const args = forgetrail.args ?? [];
71
+ const entryArg = args.find((a) => typeof a === "string" && a.endsWith("index.js"));
72
+ if (!entryArg) {
73
+ issues.push("forgetrail MCP args should include mcp-server/dist/index.js");
74
+ } else if (!existsSync(entryArg)) {
75
+ issues.push(`Configured server path does not exist: ${entryArg}`);
76
+ notes.push("Run: forgetrail mcp build");
77
+ } else if (normalizePath(entryArg) !== normalizePath(MCP_ENTRY)) {
78
+ notes.push(`Configured entry: ${entryArg}`);
79
+ notes.push(`This repo build: ${MCP_ENTRY}`);
80
+ }
81
+
82
+ if (forgetrail.command && forgetrail.command !== "node") {
83
+ notes.push(`command is "${forgetrail.command}" (node is typical).`);
84
+ }
85
+
86
+ const envRoot = forgetrail.env?.FORGETRAIL_ROOT;
87
+ if (envRoot && normalizePath(envRoot) !== normalizePath(FORGETRAIL_ROOT)) {
88
+ notes.push(`FORGETRAIL_ROOT in mcp.json: ${envRoot}`);
89
+ } else if (!envRoot) {
90
+ notes.push(
91
+ "Optional: set env.FORGETRAIL_ROOT in mcp.json if the server lives outside the default parent-of-mcp-server layout."
92
+ );
93
+ }
94
+
95
+ return { path: mcpJsonPath, found: true, issues, notes, config: raw, entryArg };
96
+ }
97
+
98
+ export function staticMcpChecks() {
99
+ const issues = [];
100
+ const notes = [];
101
+
102
+ if (!existsSync(WORKFLOW_PATH)) {
103
+ issues.push(`WORKFLOW.md missing at ${WORKFLOW_PATH}`);
104
+ }
105
+
106
+ if (!existsSync(join(MCP_SERVER_DIR, "node_modules"))) {
107
+ issues.push("mcp-server/node_modules missing — run: forgetrail mcp build");
108
+ }
109
+
110
+ if (!existsSync(MCP_ENTRY)) {
111
+ issues.push("mcp-server/dist/index.js missing — run: forgetrail mcp build");
112
+ }
113
+
114
+ const pkg = readJson(join(MCP_SERVER_DIR, "package.json"));
115
+ if (pkg?.version) {
116
+ notes.push(`forgetrail-mcp package version: ${pkg.version}`);
117
+ }
118
+
119
+ notes.push(`FORGETRAIL_ROOT (default): ${FORGETRAIL_ROOT}`);
120
+
121
+ return { issues, notes };
122
+ }
123
+
124
+ export function posixPath(p) {
125
+ return p.replace(/\\/g, "/");
126
+ }
127
+
128
+ export function mcpClientConfigObject() {
129
+ return {
130
+ mcpServers: {
131
+ forgetrail: {
132
+ command: "node",
133
+ args: [posixPath(MCP_ENTRY)],
134
+ env: { FORGETRAIL_ROOT: posixPath(FORGETRAIL_ROOT) },
135
+ },
136
+ },
137
+ };
138
+ }
139
+
140
+ /** Copy-paste MCP client setup — shown after `forgetrail mcp build`. */
141
+ export function printMcpClientSetupBanner({ showBuildComplete = true } = {}) {
142
+ const entry = posixPath(MCP_ENTRY);
143
+
144
+ if (showBuildComplete) {
145
+ console.log("\n✓ Build complete.\n");
146
+ }
147
+
148
+ console.log("Connect ForgeTrail MCP (Cursor starts the server for you — no `mcp dev` needed):\n");
149
+ console.log(" Cursor — project .cursor/mcp.json or Settings → MCP → add server:");
150
+ console.log(JSON.stringify(mcpClientConfigObject(), null, 2));
151
+ console.log("");
152
+ console.log(" Claude Desktop — %APPDATA%\\Claude\\claude_desktop_config.json (same mcpServers block)");
153
+ console.log(` Claude Code — claude mcp add forgetrail node ${entry}`);
154
+ console.log("");
155
+ console.log(" Then reload MCP in Cursor (Settings → MCP) or restart Cursor.");
156
+ console.log(" Verify: forgetrail mcp ping | Reprint: forgetrail mcp cursor-config");
157
+ console.log("");
158
+ console.log(' First agent prompt: "Call ForgeTrail getNewProjectKickoff and set up the project."');
159
+ console.log("");
160
+ }
161
+
162
+ export function runMcpBuild() {
163
+ console.log("Building ForgeTrail MCP server...\n");
164
+ const install = spawnSync("pnpm", ["install"], { cwd: MCP_SERVER_DIR, stdio: "inherit", shell: true });
165
+ if (install.status !== 0) {
166
+ process.exit(install.status ?? 1);
167
+ }
168
+ const build = spawnSync("pnpm", ["run", "build"], { cwd: MCP_SERVER_DIR, stdio: "inherit", shell: true });
169
+ if (build.status !== 0) {
170
+ process.exit(build.status ?? 1);
171
+ }
172
+ printMcpClientSetupBanner();
173
+ }
174
+
175
+ export function printCursorConfigTemplate() {
176
+ printMcpClientSetupBanner({ showBuildComplete: false });
177
+ }
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import {
8
+ MCP_SERVER_DIR,
9
+ analyzeCursorMcpConfig,
10
+ findCursorMcpJson,
11
+ staticMcpChecks,
12
+ printCursorConfigTemplate,
13
+ runMcpBuild,
14
+ } from "./mcp-lib.mjs";
15
+
16
+ const HELP = `
17
+ ForgeTrail MCP — build, status, and Cursor setup helpers
18
+
19
+ Usage:
20
+ forgetrail mcp build Install deps + compile mcp-server/dist/
21
+ forgetrail mcp status [--ping] Static checks + optional live ping tool call
22
+ forgetrail mcp ping Live ping only (JSON)
23
+ forgetrail mcp cursor-config Print recommended .cursor/mcp.json
24
+ forgetrail mcp dev Run server on stdio (blocks; for debugging)
25
+ forgetrail mcp inspector Open MCP Inspector in browser
26
+
27
+ Note: Cursor starts the MCP server itself. You usually do not run \`mcp dev\`
28
+ for normal use — run \`mcp build\` once, then enable MCP in Cursor settings.
29
+ `.trim();
30
+
31
+ function runLivePing() {
32
+ const pingScript = join(MCP_SERVER_DIR, "scripts", "mcp-ping.mjs");
33
+ if (!existsSync(pingScript)) {
34
+ console.error("Missing mcp-server/scripts/mcp-ping.mjs");
35
+ process.exit(1);
36
+ }
37
+ const r = spawnSync(process.execPath, [pingScript], {
38
+ cwd: MCP_SERVER_DIR,
39
+ stdio: "inherit",
40
+ env: process.env,
41
+ shell: false,
42
+ });
43
+ process.exit(r.status ?? 1);
44
+ }
45
+
46
+ function runDev() {
47
+ console.log("Starting ForgeTrail MCP on stdio (Ctrl+C to stop)...\n");
48
+ const r = spawnSync("pnpm", ["run", "dev"], {
49
+ cwd: MCP_SERVER_DIR,
50
+ stdio: "inherit",
51
+ shell: true,
52
+ });
53
+ process.exit(r.status ?? 0);
54
+ }
55
+
56
+ function runInspector() {
57
+ if (!existsSync(join(MCP_SERVER_DIR, "dist", "index.js"))) {
58
+ console.error("Build first: forgetrail mcp build");
59
+ process.exit(1);
60
+ }
61
+ console.log("Opening MCP Inspector (browser)...\n");
62
+ const r = spawnSync(
63
+ "npx",
64
+ ["@modelcontextprotocol/inspector", "node", "dist/index.js"],
65
+ { cwd: MCP_SERVER_DIR, stdio: "inherit", shell: true }
66
+ );
67
+ process.exit(r.status ?? 0);
68
+ }
69
+
70
+ function runStatus({ withPing = false }) {
71
+ console.log("ForgeTrail MCP status\n");
72
+
73
+ const staticResult = staticMcpChecks();
74
+ console.log("## Server files");
75
+ if (staticResult.notes.length) {
76
+ for (const n of staticResult.notes) console.log(` ${n}`);
77
+ }
78
+ if (staticResult.issues.length) {
79
+ for (const i of staticResult.issues) console.log(` ✗ ${i}`);
80
+ } else {
81
+ console.log(" ✓ dist/build looks ready");
82
+ }
83
+
84
+ const mcpPath = findCursorMcpJson();
85
+ const cursor = analyzeCursorMcpConfig(mcpPath);
86
+ console.log("\n## Cursor MCP config");
87
+ console.log(` Path: ${cursor.path}`);
88
+ if (cursor.found && cursor.issues.length === 0) {
89
+ console.log(" ✓ forgetrail server entry looks valid");
90
+ }
91
+ for (const i of cursor.issues) console.log(` ✗ ${i}`);
92
+ for (const n of cursor.notes) console.log(` · ${n}`);
93
+
94
+ console.log("\n## Cursor checklist");
95
+ console.log(" 1. Settings → MCP → forgetrail should show connected (green).");
96
+ console.log(" 2. In Agent chat, ask: Call ForgeTrail ping");
97
+ console.log(" 3. If tools are missing, reload MCP or restart Cursor.");
98
+
99
+ const failed = staticResult.issues.length + cursor.issues.length;
100
+
101
+ if (withPing) {
102
+ console.log("\n## Live ping\n");
103
+ runLivePing();
104
+ return;
105
+ }
106
+
107
+ if (failed > 0) {
108
+ console.log("\nFix issues above, then: forgetrail mcp status --ping");
109
+ process.exit(1);
110
+ }
111
+
112
+ console.log("\nRun live JSON-RPC ping: forgetrail mcp ping");
113
+ }
114
+
115
+ export function runMcpCommand(argv) {
116
+ const cmd = argv[0];
117
+ const rest = argv.slice(1);
118
+
119
+ if (!cmd || cmd === "--help" || cmd === "-h") {
120
+ console.log(HELP);
121
+ return;
122
+ }
123
+
124
+ switch (cmd) {
125
+ case "build":
126
+ runMcpBuild();
127
+ break;
128
+ case "status":
129
+ runStatus({ withPing: rest.includes("--ping") });
130
+ break;
131
+ case "ping":
132
+ runLivePing();
133
+ break;
134
+ case "cursor-config":
135
+ printCursorConfigTemplate();
136
+ break;
137
+ case "dev":
138
+ runDev();
139
+ break;
140
+ case "inspector":
141
+ runInspector();
142
+ break;
143
+ default:
144
+ console.error(`Unknown mcp subcommand: ${cmd}`);
145
+ console.error("Run: forgetrail mcp --help");
146
+ process.exit(1);
147
+ }
148
+ }
149
+
150
+ const isMain =
151
+ process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
152
+
153
+ if (isMain) {
154
+ runMcpCommand(process.argv.slice(2));
155
+ }
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pre-publish checks. Does not log in or publish.
5
+ * Run from the repo root: pnpm run pack:check
6
+ */
7
+
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join, resolve } from "node:path";
10
+ import { spawnSync } from "node:child_process";
11
+ import { fileURLToPath } from "node:url";
12
+ import { FORGETRAIL_ROOT, MCP_ENTRY, MCP_SERVER_DIR } from "./mcp-lib.mjs";
13
+
14
+ const ROOT_PKG = join(FORGETRAIL_ROOT, "package.json");
15
+ const MCP_PKG = join(MCP_SERVER_DIR, "package.json");
16
+
17
+ function fail(msg) {
18
+ console.error(`publish-gate: ${msg}`);
19
+ process.exit(1);
20
+ }
21
+
22
+ function readPkg(path) {
23
+ try {
24
+ return JSON.parse(readFileSync(path, "utf8"));
25
+ } catch {
26
+ fail(`could not read ${path}`);
27
+ }
28
+ }
29
+
30
+ function requireFields(pkg, label, fields) {
31
+ for (const field of fields) {
32
+ if (!pkg[field]) fail(`${label} is missing "${field}"`);
33
+ }
34
+ if (pkg.private === true) fail(`${label} is private: true (unset it to publish)`);
35
+ }
36
+
37
+ function mustExist(path, label) {
38
+ if (!existsSync(path)) fail(`missing ${label}: ${path}`);
39
+ }
40
+
41
+ const root = readPkg(ROOT_PKG);
42
+ const mcp = readPkg(MCP_PKG);
43
+
44
+ requireFields(root, "forgetrail", [
45
+ "name",
46
+ "version",
47
+ "license",
48
+ "bin",
49
+ "files",
50
+ "repository",
51
+ "homepage",
52
+ ]);
53
+ requireFields(mcp, "forgetrail-mcp", [
54
+ "name",
55
+ "version",
56
+ "license",
57
+ "bin",
58
+ "files",
59
+ "repository",
60
+ ]);
61
+
62
+ if (root.name !== "forgetrail") fail(`root package name is "${root.name}", expected forgetrail`);
63
+ if (mcp.name !== "forgetrail-mcp") fail(`mcp package name is "${mcp.name}", expected forgetrail-mcp`);
64
+
65
+ mustExist(join(FORGETRAIL_ROOT, "LICENSE"), "LICENSE");
66
+ mustExist(join(FORGETRAIL_ROOT, "README.md"), "README.md");
67
+ mustExist(join(FORGETRAIL_ROOT, "WORKFLOW.md"), "WORKFLOW.md");
68
+ mustExist(join(FORGETRAIL_ROOT, "content", "FORGETRAIL_LITE.md"), "Lite");
69
+ mustExist(join(FORGETRAIL_ROOT, "scripts", "forgetrail-cli.mjs"), "CLI");
70
+
71
+ const build = spawnSync("pnpm", ["run", "build"], {
72
+ cwd: MCP_SERVER_DIR,
73
+ stdio: "inherit",
74
+ shell: true,
75
+ });
76
+ if (build.status !== 0) fail("mcp-server build failed");
77
+ mustExist(MCP_ENTRY, "mcp-server/dist/index.js");
78
+
79
+ const pack = spawnSync("pnpm", ["pack", "--dry-run", "--json"], {
80
+ cwd: FORGETRAIL_ROOT,
81
+ encoding: "utf8",
82
+ shell: true,
83
+ });
84
+ if (pack.status !== 0) {
85
+ console.error(pack.stderr);
86
+ fail("pnpm pack --dry-run failed for forgetrail");
87
+ }
88
+
89
+ const packed = pack.stdout ?? "";
90
+ const banned = ["site/", "specs/", ".cursor/", "mcp-server/src/", "mcp-server/node_modules/"];
91
+ for (const needle of banned) {
92
+ if (packed.includes(needle)) fail(`tarball must not include ${needle}`);
93
+ }
94
+ for (const needed of ["WORKFLOW.md", "README.md", "LICENSE", "content/FORGETRAIL_LITE.md", "scripts/forgetrail-cli.mjs"]) {
95
+ if (!packed.includes(needed)) fail(`tarball is missing ${needed}`);
96
+ }
97
+
98
+ console.log("\n✓ forgetrail pack check passed");
99
+ console.log(` forgetrail@${root.version}`);
100
+ console.log(` forgetrail-mcp@${mcp.version} (build ok at ${MCP_ENTRY})`);
101
+ console.log("\nPublish (you run these):");
102
+ console.log(" pnpm publish --access public");
103
+ console.log(" pnpm --dir mcp-server publish --access public");
104
+ console.log("\nDry-run first if you want:");
105
+ console.log(" pnpm publish --dry-run --access public");
106
+
107
+ const here = fileURLToPath(import.meta.url);
108
+ if (resolve(process.argv[1] ?? "") === resolve(here)) {
109
+ // ran as CLI; work already done above
110
+ }