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,165 @@
1
+ # UX Cohesion Audit
2
+
3
+ Whole-app, fresh-eyes read of where a product **confuses, blocks, distracts, or fails to delight** — everything that stops a user from *falling in love* with the app. Unlike a single-surface usability pass, this audit looks at the product as **one experience** and surfaces the cross-cutting "feels like N apps stitched together" problems that are invisible when you review surfaces in isolation. Produces a prioritized findings spec that **points at existing specs instead of re-speccing them**.
4
+
5
+ Use this when an app has matured enough that individual features are solid but the *whole* doesn't yet feel like one calm, trustworthy instrument — typically before a beta/launch, after a burst of feature work, or as a periodic "is this lovable yet?" check.
6
+
7
+ > **Companion prompts:** `panel-usability-audit.md` goes deep on *one* surface; this goes wide across *all* of them. `pre-launch-audit.md` focuses on operational/launch readiness (legal, billing, backups, errors); this focuses on the *experiential* layer. Run them together for full coverage.
8
+
9
+ ---
10
+
11
+ ## How to Use
12
+
13
+ 1. Give this prompt to an AI assistant with **full codebase access**.
14
+ 2. Optionally name a focus or exclusions (`[SCOPE]` below). Default scope is the entire authenticated product experience.
15
+ 3. The assistant does a read-only pass over the app shell, navigation, onboarding, every primary surface, and the existing spec/doc corpus, then writes one findings spec.
16
+ 4. Save the spec at `specs/ux-cohesion-audit.md` (or `<area>-cohesion-audit.md` if scoped) and add a backlog entry.
17
+
18
+ ---
19
+
20
+ ## Instructions
21
+
22
+ You are a product-minded design engineer doing a **whole-app UX cohesion audit**. The goal is to find everything that could confuse, block, distract, or otherwise prevent a user from falling in love with this app — and to capture it in a single, prioritized, honestly-scoped spec.
23
+
24
+ Your north star: **"Does the whole thing feel like one calm, trustworthy instrument, or like N strong screens stitched together?"** You are looking for the gap between *competent* and *beloved*.
25
+
26
+ ### Step 0 — Avoid double-work (read the existing corpus first)
27
+
28
+ Before auditing anything, inventory what's **already captured** so you do not re-spec owned work:
29
+
30
+ - Read the spec folder(s) (`specs/`, `specs/partial/`, `specs/completed/`) and any docs index (`docs/`, design system, brand/product docs, test plan, code-health, backlog).
31
+ - Build a map of which UX concerns **already have an owning spec** (e.g. visual consistency, information architecture, error handling, onboarding latency, keyboard shortcuts, save model).
32
+ - For anything already owned, you will **point at it**, not rewrite it — note only the *experiential residue* or a reprioritization, not a fresh design.
33
+
34
+ If a prior whole-app UX/cohesion audit already exists, treat this run as an **update**: reconcile against it, mark what shipped, and add only new findings.
35
+
36
+ ### Step 1 — Inventory the whole experience (read-only)
37
+
38
+ Read enough to build a real mental model of the product as a user moves through it. Cover at minimum:
39
+
40
+ - **App shell** — the root layout / main page: navigation, global chrome, panel/modal launching, URL state, loading and empty states.
41
+ - **Entry & onboarding** — auth/landing, first-run wizard, the path from "signed up" to "first real artifact / first win." Note where the brand *promise* and the first-run *reality* diverge.
42
+ - **Primary surfaces** — every major panel/page/drawer and its tabs/sub-tabs. Note nesting depth (panel → panel → modal) and wayfinding cost.
43
+ - **The core object's detail view** — wherever the user spends the most time (the record/document/item workspace). Map its tabs, edit buffers, and save behavior.
44
+ - **Cross-surface flows** — links that jump between surfaces; do they stack, replace, or orphan the user?
45
+ - **System feedback** — how success, in-progress, failure, empty, and stale states are expressed across surfaces. Look specifically for **silent failures** (swallowed errors, no toast) and **inconsistent acknowledgement**.
46
+ - **Async / AI / long-running work** — anywhere the app does background work; is "where did my thing go?" answered consistently?
47
+ - **Mobile / responsive** — the top journeys at a narrow width.
48
+ - **Accessibility (cohesion-relevant)** — focus management at panel/modal depth, keyboard model, color-only signaling.
49
+
50
+ Ground everything in code: name the file, component, state variable, or template branch. Verify ambiguous claims against source.
51
+
52
+ ### Step 2 — Two lenses
53
+
54
+ Analyze through **both** lenses. The first is what makes this audit different from a per-surface review.
55
+
56
+ #### Lens A — Cross-cutting themes ("feels like N apps")
57
+
58
+ These are meta-findings: individually small, collectively the difference between competent and loved. Hunt explicitly for:
59
+
60
+ 1. **Inconsistent interaction models for the same job** — e.g. some edits autosave, some need a Save button, some persist instantly, with nothing signaling which. Same for confirmation patterns, error display, progress UX.
61
+ 2. **Uneven or silent feedback** — operations that succeed loudly in one place and silently in another; swallowed `catch` blocks with no user signal; native `alert()`/`confirm()` next to polished dialogs.
62
+ 3. **Discoverability that depends on the user opting in** — high-value capabilities only findable via tour/tooltip/help, never taught by the UI itself.
63
+ 4. **Missing global affordances** — no keyboard model (`/`, new, `?`, `Esc` discipline), no command surface, for an audience that expects them.
64
+ 5. **Fragmented system legibility** — background work, AI runs, or multi-step processes whose status is expressed differently everywhere, so the user never builds one mental model.
65
+ 6. **Vocabulary / state drift** — the same concept (empty / loading / generating / stale / saved) rendered with different copy and chrome per surface.
66
+
67
+ #### Lens B — Surface-by-surface findings
68
+
69
+ Walk the product and capture concrete issues, organized by area (onboarding & first run; navigation & wayfinding; the core detail view; feedback/errors/trust; loading/empty/in-between states; visual & interaction craft; mobile; accessibility; **delight**). For each, ask:
70
+
71
+ - **Confuse:** would a capable new user be unsure what to do or where they are?
72
+ - **Block:** is there a dead end, a lost-work risk, or a step that fails without recovery?
73
+ - **Distract:** does something pull attention from the task (noise, redundant info, competing weight)?
74
+ - **Delight gap:** is there a moment the app *sets up* but doesn't cash in (first artifact, a milestone, "your single next best move")?
75
+
76
+ ### Step 3 — Severity & effort
77
+
78
+ Rate every finding:
79
+
80
+ - **Critical** — actively blocks, breaks trust, or makes the app feel broken/unsafe. Fix before broad beta.
81
+ - **High** — recurring friction/confusion that erodes the product's core promise.
82
+ - **Medium** — polish that compounds; the "good → loved" gap.
83
+ - **Low** — nice-to-have / future delight.
84
+ - **Effort:** XS / S / M / L.
85
+
86
+ Treat **delight** as first-class, not filler: a few "turn good into loved" opportunities belong in every cohesion audit.
87
+
88
+ ### Step 4 — Write the spec
89
+
90
+ Produce one spec with this shape (adapt headings to the app; keep it honest and skimmable):
91
+
92
+ ```
93
+ # UX Cohesion Audit — friction, confusion, and delight gaps
94
+
95
+ **Status:** Draft — not started
96
+ **Audience:** Product + engineering — a whole-app read of where the experience
97
+ confuses, blocks, distracts, or fails to delight.
98
+ **Related:** [link every owning spec/doc you found in Step 0]
99
+
100
+ ## 1. Why this spec exists
101
+ [What the app is, the cohesion risk, and that this catalogs gaps NOT already owned
102
+ by other specs, plus cross-cutting themes. State the method.]
103
+
104
+ ## 2. How to read the severity
105
+ [The Critical/High/Medium/Low + effort key.]
106
+
107
+ ## 3. Cross-cutting themes (the "feels like N apps" problem)
108
+ [T1..Tn — the Lens A meta-findings. For each: what, why it matters, current state,
109
+ recommendation. Where a theme is partly owned by another spec, say so and add only
110
+ the residue.]
111
+
112
+ ## 4..N Surface areas
113
+ [One section per area from Lens B. Each finding: ID, severity, what, why it matters,
114
+ current state (with file/component references), recommendation, effort.]
115
+
116
+ ## Delight
117
+ [D1..Dn — opportunities to convert "good" into "loved."]
118
+
119
+ ## Prioritized plan
120
+ [Three tiers as tables: pre-beta (trust / "not broken") → early beta (cohesion) →
121
+ beta iteration (loved). Each row: finding, effort.]
122
+
123
+ ## Relationship to existing specs (no double-spec)
124
+ [Table: theme here | owned by | what this audit ADDS. Critical for not re-speccing.]
125
+
126
+ ## Open questions
127
+ [Decisions needed before implementation: scope of first pass, ambition level,
128
+ ownership of "addition" items, etc.]
129
+ ```
130
+
131
+ ### Rules
132
+
133
+ - **Point, don't duplicate.** If a concern already has an owning spec, link it and add only the experiential residue or a reprioritization. The "Relationship to existing specs" table is mandatory.
134
+ - **Ground every finding in code.** Name the file/component/state/branch. No vague "the UX could be better."
135
+ - **Concrete recommendations.** Say what to change and roughly how, not "consider improving."
136
+ - **Severity AND effort on every finding.** Readers prioritize with both.
137
+ - **Lens A is the point.** A per-surface bug list alone is the *other* prompt; this one must surface the cross-cutting themes.
138
+ - **Delight is required.** Include a few honest "good → loved" opportunities.
139
+ - **Stay honest.** Don't manufacture findings; if a surface is strong, say so. If the app is in good shape overall, keep the spec short.
140
+ - **Don't conflate audit with redesign.** Identify and recommend; do not propose a full UI overhaul unless findings demand it.
141
+ - **Keep internal references out of UI copy.** Recommendations for user-facing text must not leak internal spec names or file paths into the product.
142
+
143
+ ---
144
+
145
+ ## Input
146
+
147
+ ```
148
+ [SCOPE] (optional): default = entire authenticated product experience. Narrow if
149
+ desired, e.g. "everything except admin panels" or "the primary pipeline flows."
150
+
151
+ Run the UX cohesion audit using the methodology above. Read the existing spec/doc
152
+ corpus FIRST (Step 0) so you point at owned work instead of re-speccing it. Save the
153
+ spec at specs/ux-cohesion-audit.md (or specs/<area>-cohesion-audit.md if scoped) and
154
+ add a backlog entry linking it.
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Run Logging
160
+
161
+ After completing the audit:
162
+
163
+ - Add a backlog entry (e.g. `TODO.md`, open `[ ]`): title **`UX cohesion audit`**, a one-line summary of the cross-cutting themes + top pre-beta items, and a link to the spec.
164
+ - A brand-new audit spec lands in `specs/` (drafts). Once any finding is implemented, move it to `specs/partial/` per your spec-lifecycle convention.
165
+ - If this run produced a generally-reusable lesson (a new cross-cutting pattern worth enforcing), consider running `propagate-to-forgetrail.md`.
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Idempotent LocalBerth claim. Missing CLI → warn, exit 0.
4
+ * Usage: node scripts/ensure-lease.mjs <name> <port>
5
+ */
6
+ import { spawnSync } from 'node:child_process';
7
+
8
+ const name = process.argv[2];
9
+ const port = process.argv[3];
10
+ if (!name || !port) {
11
+ console.error('usage: node scripts/ensure-lease.mjs <name> <port>');
12
+ process.exit(1);
13
+ }
14
+
15
+ const opt = { encoding: 'utf8', timeout: 8000, windowsHide: true, shell: process.platform === 'win32' };
16
+ const got = spawnSync('localberth', ['get', name], { ...opt, stdio: ['ignore', 'pipe', 'ignore'] });
17
+ if (got.status === 0 && String(got.stdout || '').trim()) {
18
+ process.exit(0);
19
+ }
20
+ const claim = spawnSync('localberth', ['claim', name, '--port', port], { ...opt, stdio: 'inherit' });
21
+ if (claim.error || claim.status !== 0) {
22
+ console.warn(`localberth: skip claim ${name} (install the CLI to pin this port to ${port})`);
23
+ }
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cwd } from "node:process";
4
+ import {
5
+ INSTALL_FULL_HELP,
6
+ INSTALL_LITE_HELP,
7
+ runInstallForgetrail,
8
+ runInstallLite,
9
+ } from "./install.mjs";
10
+ import { runMcpCommand } from "./mcp-status.mjs";
11
+
12
+ const CLI_HELP = `
13
+ ForgeTrail — bootstrap a project folder and run the MCP server
14
+
15
+ Usage:
16
+ forgetrail install [--lite] [options]
17
+ forgetrail mcp <subcommand> Build, status, ping, Cursor config
18
+
19
+ Global link (one-time, from your forgetrail clone):
20
+ pnpm run link:global # pnpm add -g . + PATH shim fix (Windows/Git Bash)
21
+
22
+ Then from any project folder:
23
+ cd /path/to/your-app
24
+ forgetrail install --lite
25
+ forgetrail install
26
+
27
+ From your forgetrail clone (MCP server):
28
+ forgetrail mcp build Install deps + compile dist/
29
+ forgetrail mcp status [--ping] Static checks + optional live ping
30
+ forgetrail mcp ping Live ping (JSON)
31
+ forgetrail mcp cursor-config Print recommended .cursor/mcp.json
32
+
33
+ Install options:
34
+ --lite Lite protocol only (.forgetrail/FORGETRAIL_LITE.md)
35
+ --with-genesis-stub With --lite: also create docs/GENESIS.md stub
36
+ --force, -f Overwrite existing files
37
+ --dry-run Preview without writing
38
+ --skip-tracking Skip workflow_tracking.json starter
39
+ --path, -p <dir> Install elsewhere (default: current directory)
40
+ --help, -h Show help
41
+
42
+ First-time prove-it (no MCP): see TRY_FORGETRAIL.md in the ForgeTrail repo.
43
+ Run \`forgetrail mcp --help\` for all MCP subcommands.
44
+ `.trim();
45
+
46
+ function main() {
47
+ const argv = process.argv.slice(2);
48
+
49
+ if (argv.length === 0 || ((argv.includes("--help") || argv.includes("-h")) && argv[0] !== "install" && argv[0] !== "mcp")) {
50
+ console.log(CLI_HELP);
51
+ process.exit(0);
52
+ }
53
+
54
+ const cmd = argv[0];
55
+
56
+ if (cmd === "mcp") {
57
+ runMcpCommand(argv.slice(1));
58
+ return;
59
+ }
60
+
61
+ if (cmd !== "install") {
62
+ console.error(`Unknown command: ${cmd ?? "(none)"}`);
63
+ console.error("Run forgetrail --help");
64
+ process.exit(1);
65
+ }
66
+
67
+ const rest = argv.slice(1);
68
+ const lite = rest.includes("--lite");
69
+ const passArgv = rest.filter((a) => a !== "--lite");
70
+
71
+ if (passArgv.includes("--help") || passArgv.includes("-h")) {
72
+ console.log(lite ? INSTALL_LITE_HELP : INSTALL_FULL_HELP);
73
+ process.exit(0);
74
+ }
75
+
76
+ console.log(`Target: ${cwd()}\n`);
77
+
78
+ if (lite) {
79
+ runInstallLite(passArgv, { defaultToCwd: true });
80
+ } else {
81
+ runInstallForgetrail(passArgv, { defaultToCwd: true });
82
+ }
83
+ }
84
+
85
+ main();
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runInstallLite, INSTALL_LITE_HELP } from "./install.mjs";
4
+
5
+ const argv = process.argv.slice(2);
6
+ if (argv.includes("--help") || argv.includes("-h")) {
7
+ console.log(INSTALL_LITE_HELP);
8
+ process.exit(0);
9
+ }
10
+
11
+ runInstallLite(argv, { defaultToCwd: false });
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runInstallForgetrail, INSTALL_FULL_HELP } from "./install.mjs";
4
+
5
+ const argv = process.argv.slice(2);
6
+ if (argv.includes("--help") || argv.includes("-h")) {
7
+ console.log(INSTALL_FULL_HELP);
8
+ process.exit(0);
9
+ }
10
+
11
+ runInstallForgetrail(argv, { defaultToCwd: false });
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cwd } from "node:process";
4
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ export const FORGETRAIL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
+
10
+ export function parseInstallArgs(argv) {
11
+ const args = {
12
+ target: process.env.FORGETRAIL_TARGET?.trim() || "",
13
+ force: false,
14
+ dryRun: false,
15
+ skipTracking: false,
16
+ withGenesisStub: false,
17
+ };
18
+
19
+ for (let i = 0; i < argv.length; i++) {
20
+ const arg = argv[i];
21
+ if (arg === "--path" || arg === "-p") {
22
+ args.target = argv[++i]?.trim() ?? "";
23
+ } else if (arg === "--force" || arg === "-f") {
24
+ args.force = true;
25
+ } else if (arg === "--dry-run") {
26
+ args.dryRun = true;
27
+ } else if (arg === "--skip-tracking") {
28
+ args.skipTracking = true;
29
+ } else if (arg === "--with-genesis-stub") {
30
+ args.withGenesisStub = true;
31
+ } else if (arg === "--help" || arg === "-h") {
32
+ args.help = true;
33
+ } else if (!arg.startsWith("-") && !args.target) {
34
+ args.target = arg.trim();
35
+ }
36
+ }
37
+
38
+ return args;
39
+ }
40
+
41
+ export function resolveTarget(target, { defaultToCwd = false } = {}) {
42
+ const raw = target?.trim() || (defaultToCwd ? cwd() : "");
43
+ if (!raw) {
44
+ console.error("Missing target directory.");
45
+ console.error("Usage: forgetrail install (from project folder after pnpm run link:global)");
46
+ console.error(" or: pnpm run install:forgetrail -- --path <project-root>");
47
+ console.error(" or: FORGETRAIL_TARGET=<project-root> pnpm run install:forgetrail");
48
+ process.exit(1);
49
+ }
50
+
51
+ const resolved = resolve(raw);
52
+ if (!existsSync(resolved)) {
53
+ console.error(`Target does not exist: ${resolved}`);
54
+ process.exit(1);
55
+ }
56
+ if (!statSync(resolved).isDirectory()) {
57
+ console.error(`Target is not a directory: ${resolved}`);
58
+ process.exit(1);
59
+ }
60
+ return resolved;
61
+ }
62
+
63
+ /** @deprecated use resolveTarget */
64
+ export function requireTarget(target, opts) {
65
+ return resolveTarget(target, opts);
66
+ }
67
+
68
+ export function ensureDir(path, dryRun) {
69
+ if (existsSync(path)) return;
70
+ if (dryRun) {
71
+ console.log(`[dry-run] mkdir ${path}`);
72
+ return;
73
+ }
74
+ mkdirSync(path, { recursive: true });
75
+ }
76
+
77
+ export function copyPath(from, to, { force = false, dryRun = false } = {}) {
78
+ if (!existsSync(from)) {
79
+ throw new Error(`Missing source: ${from}`);
80
+ }
81
+ if (existsSync(to) && !force) {
82
+ console.log(` skip (exists): ${to}`);
83
+ return false;
84
+ }
85
+ if (dryRun) {
86
+ console.log(`[dry-run] copy ${from} -> ${to}`);
87
+ return true;
88
+ }
89
+ ensureDir(dirname(to), dryRun);
90
+ cpSync(from, to, { recursive: true, force: true });
91
+ console.log(` wrote ${to}`);
92
+ return true;
93
+ }
94
+
95
+ /** MCP-only content — not needed for offline template-in-repo. */
96
+ export const MCP_ONLY_CONTENT = new Set([
97
+ "AGENT_INTEGRATION_claude.md",
98
+ "AGENT_INTEGRATION_cursor.md",
99
+ "AGENT_INTEGRATION_generic.md",
100
+ "AGENT_INTEGRATION_grok.md",
101
+ "PLAN_MODE_PATTERNS.md",
102
+ "NEW_PROJECT_BOOTSTRAP.md",
103
+ "SESSION_RESUME_MCP.md",
104
+ "KICKOFF_WITHOUT_MCP.md",
105
+ "POST_BOOTSTRAP_USER_MESSAGE.md",
106
+ ]);
107
+
108
+ export function copyContentDir(fromDir, toDir, opts) {
109
+ ensureDir(toDir, opts.dryRun);
110
+ for (const name of readdirSync(fromDir)) {
111
+ if (name === "skills") continue;
112
+ if (MCP_ONLY_CONTENT.has(name)) continue;
113
+ copyPath(join(fromDir, name), join(toDir, name), opts);
114
+ }
115
+ }
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Shared install logic — full template-in-repo and Lite bootstrap.
5
+ */
6
+
7
+ import { join } from "node:path";
8
+ import {
9
+ FORGETRAIL_ROOT,
10
+ copyContentDir,
11
+ copyPath,
12
+ ensureDir,
13
+ parseInstallArgs,
14
+ resolveTarget,
15
+ } from "./install-lib.mjs";
16
+
17
+ const ROOT_FILES = [
18
+ "WORKFLOW.md",
19
+ "TRACKING_SCHEMA.md",
20
+ "workflow_tracking.json",
21
+ "INITIAL_PROMPT.md",
22
+ "CONTINUATION_PROMPT.md",
23
+ "LICENSE",
24
+ ];
25
+
26
+ const LITE_FILES = [
27
+ ["FORGETRAIL_LITE.md", "FORGETRAIL_LITE.md"],
28
+ ["forgetrail-workspace-README.md", "README.md"],
29
+ ["FORGETRAIL_LITE_UPDATES.md", "FORGETRAIL_LITE_UPDATES.md"],
30
+ ];
31
+
32
+ const LITE_CURSOR_RULES = [
33
+ "forgetrail-no-trailer.mdc",
34
+ "forgetrail-updates-log.mdc",
35
+ "specs-and-todo.mdc",
36
+ "spec-completion.mdc",
37
+ ];
38
+
39
+ export function runInstallForgetrail(rawArgv, { defaultToCwd = false } = {}) {
40
+ const args = parseInstallArgs(rawArgv);
41
+ if (args.help) return { help: INSTALL_FULL_HELP };
42
+
43
+ const target = resolveTarget(args.target, { defaultToCwd });
44
+ const opts = { force: args.force, dryRun: args.dryRun };
45
+ const forgetrailDir = join(target, "_forgetrail");
46
+ const contentSrc = join(FORGETRAIL_ROOT, "content");
47
+ const contentDest = join(forgetrailDir, "content");
48
+
49
+ console.log(`ForgeTrail full install → ${forgetrailDir}`);
50
+ if (args.dryRun) console.log("(dry run)\n");
51
+
52
+ ensureDir(forgetrailDir, args.dryRun);
53
+
54
+ for (const file of ROOT_FILES) {
55
+ copyPath(join(FORGETRAIL_ROOT, file), join(forgetrailDir, file), opts);
56
+ }
57
+
58
+ copyPath(join(FORGETRAIL_ROOT, "docs"), join(forgetrailDir, "docs"), opts);
59
+ copyPath(join(FORGETRAIL_ROOT, "prompts"), join(forgetrailDir, "prompts"), opts);
60
+ copyContentDir(contentSrc, contentDest, opts);
61
+
62
+ if (!args.skipTracking) {
63
+ copyPath(
64
+ join(FORGETRAIL_ROOT, "workflow_tracking.json"),
65
+ join(target, ".forgetrail", "workflow_tracking.json"),
66
+ { ...opts, force: args.force }
67
+ );
68
+ }
69
+
70
+ console.log("\nDone.");
71
+ console.log(" Methodology: _forgetrail/WORKFLOW.md");
72
+ console.log(" First chat: _forgetrail/INITIAL_PROMPT.md");
73
+ console.log(" Tracking: .forgetrail/workflow_tracking.json");
74
+ return { target, mode: "full" };
75
+ }
76
+
77
+ export function runInstallLite(rawArgv, { defaultToCwd = false } = {}) {
78
+ const args = parseInstallArgs(rawArgv);
79
+ if (args.help) return { help: INSTALL_LITE_HELP };
80
+
81
+ const target = resolveTarget(args.target, { defaultToCwd });
82
+ const opts = { force: args.force, dryRun: args.dryRun };
83
+ const contentDir = join(FORGETRAIL_ROOT, "content");
84
+ const forgetrailDir = join(target, ".forgetrail");
85
+
86
+ console.log(`ForgeTrail Lite install → ${forgetrailDir}`);
87
+ if (args.dryRun) console.log("(dry run)\n");
88
+
89
+ ensureDir(forgetrailDir, args.dryRun);
90
+ ensureDir(join(forgetrailDir, "cursor", "rules"), args.dryRun);
91
+
92
+ for (const [src, dest] of LITE_FILES) {
93
+ copyPath(join(contentDir, src), join(forgetrailDir, dest), opts);
94
+ }
95
+
96
+ for (const rule of LITE_CURSOR_RULES) {
97
+ copyPath(
98
+ join(contentDir, "cursor-rules", rule),
99
+ join(forgetrailDir, "cursor", "rules", rule),
100
+ opts
101
+ );
102
+ }
103
+
104
+ if (!args.skipTracking) {
105
+ copyPath(
106
+ join(contentDir, "LITE_WORKFLOW_TRACKING.json"),
107
+ join(forgetrailDir, "workflow_tracking.json"),
108
+ opts
109
+ );
110
+ }
111
+
112
+ if (args.withGenesisStub) {
113
+ const genesisDest = join(target, "docs", "GENESIS.md");
114
+ ensureDir(join(target, "docs"), args.dryRun);
115
+ copyPath(join(contentDir, "GENESIS_STUB.md"), genesisDest, opts);
116
+ }
117
+
118
+ console.log("\nDone.");
119
+ console.log(" Protocol: .forgetrail/FORGETRAIL_LITE.md");
120
+ console.log(" Tracking: .forgetrail/workflow_tracking.json (lite-1 schema)");
121
+ if (args.withGenesisStub) {
122
+ console.log(" Genesis: docs/GENESIS.md (stub — replace with your spec)");
123
+ console.log(" Next: see TRY_FORGETRAIL.md in the ForgeTrail repo");
124
+ }
125
+ return { target, mode: "lite", withGenesisStub: !!args.withGenesisStub };
126
+ }
127
+
128
+ export const INSTALL_FULL_HELP = `
129
+ ForgeTrail template-in-repo install (offline / no MCP server required)
130
+
131
+ Usage:
132
+ forgetrail install [options] (from your project folder, after global link)
133
+ pnpm run install:forgetrail -- [options]
134
+
135
+ Options:
136
+ --path, -p <dir> Target project root (default: current directory for \`forgetrail\` CLI)
137
+ --force, -f Overwrite existing files
138
+ --skip-tracking Do not create .forgetrail/workflow_tracking.json
139
+ --dry-run Print actions only
140
+
141
+ Installs:
142
+ <target>/_forgetrail/ WORKFLOW, templates, prompts, content/ (curated)
143
+ <target>/.forgetrail/ workflow_tracking.json starter (unless skipped)
144
+ `.trim();
145
+
146
+ export const INSTALL_LITE_HELP = `
147
+ ForgeTrail Lite install (portable protocol, no MCP)
148
+
149
+ Usage:
150
+ forgetrail install --lite [options]
151
+ pnpm run install:lite -- [options]
152
+
153
+ Options:
154
+ --path, -p <dir> Target project root (default: current directory for \`forgetrail\` CLI)
155
+ --force, -f Overwrite existing files
156
+ --skip-tracking Do not create workflow_tracking.json starter
157
+ --with-genesis-stub Also create docs/GENESIS.md stub (for the Try path)
158
+ --dry-run Print actions only
159
+
160
+ Installs into <target>/.forgetrail/ (FORGETRAIL_LITE.md, tracking starter, cursor rules).
161
+ With --with-genesis-stub, also writes docs/GENESIS.md from content/GENESIS_STUB.md.
162
+ See TRY_FORGETRAIL.md in the ForgeTrail repo for the no-MCP prove-it recipe.
163
+ `.trim();
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Register the forgetrail CLI on PATH for pnpm global installs.
5
+ *
6
+ * pnpm 10: prefer `pnpm add -g .` over deprecated `pnpm link --global`.
7
+ * Linked packages get shims under global/.../node_modules/.bin with paths
8
+ * relative to that folder — copying them to $PNPM_HOME breaks resolution.
9
+ * This script writes shims with absolute paths to this repo's CLI entry.
10
+ */
11
+
12
+ import { writeFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { spawnSync } from "node:child_process";
15
+ import { FORGETRAIL_ROOT } from "./install-lib.mjs";
16
+
17
+ const CLI_ENTRY = join(FORGETRAIL_ROOT, "scripts", "forgetrail-cli.mjs");
18
+ const BIN_NAME = "forgetrail";
19
+
20
+ function run(cmd, args, { cwd = FORGETRAIL_ROOT } = {}) {
21
+ const r = spawnSync(cmd, args, { cwd, stdio: "inherit", shell: true, env: process.env });
22
+ if (r.status !== 0) process.exit(r.status ?? 1);
23
+ }
24
+
25
+ function pnpmPath(args) {
26
+ const r = spawnSync("pnpm", args, {
27
+ cwd: FORGETRAIL_ROOT,
28
+ encoding: "utf-8",
29
+ shell: true,
30
+ env: process.env,
31
+ });
32
+ if (r.status !== 0) {
33
+ console.error(`pnpm ${args.join(" ")} failed`);
34
+ process.exit(r.status ?? 1);
35
+ }
36
+ return (r.stdout ?? "").trim();
37
+ }
38
+
39
+ function toPosix(p) {
40
+ return p.replace(/\\/g, "/");
41
+ }
42
+
43
+ function writeGlobalBinShims() {
44
+ const globalBinDir = pnpmPath(["bin", "-g"]);
45
+ const cliPosix = toPosix(CLI_ENTRY);
46
+ const cliWin = CLI_ENTRY.replace(/\//g, "\\");
47
+
48
+ const shShim = `#!/bin/sh
49
+ exec node "${cliPosix}" "$@"
50
+ `;
51
+ const cmdShim = `@ECHO off
52
+ node "${cliWin}" %*
53
+ `;
54
+ const ps1Shim = `#!/usr/bin/env pwsh
55
+ & node "${cliWin}" @args
56
+ exit $LASTEXITCODE
57
+ `;
58
+
59
+ const targets = [
60
+ [join(globalBinDir, BIN_NAME), shShim],
61
+ [join(globalBinDir, `${BIN_NAME}.CMD`), cmdShim],
62
+ [join(globalBinDir, `${BIN_NAME}.ps1`), ps1Shim],
63
+ ];
64
+
65
+ for (const [path, content] of targets) {
66
+ writeFileSync(path, content, { mode: 0o755 });
67
+ console.log(` wrote ${path}`);
68
+ }
69
+
70
+ return globalBinDir;
71
+ }
72
+
73
+ function verify(globalBinDir) {
74
+ const r = spawnSync(process.execPath, [CLI_ENTRY, "--help"], {
75
+ stdio: "pipe",
76
+ encoding: "utf-8",
77
+ env: process.env,
78
+ });
79
+
80
+ if (r.status !== 0) {
81
+ console.error(`\nShim did not run. Try in a new terminal:\n ${BIN_NAME} --help`);
82
+ console.error(`Or:\n node ${CLI_ENTRY} --help`);
83
+ process.exit(1);
84
+ }
85
+
86
+ console.log(`\n✓ ${BIN_NAME} is available (${globalBinDir})`);
87
+ }
88
+
89
+ console.log("Registering forgetrail globally (pnpm add -g .)…\n");
90
+ run("pnpm", ["add", "-g", "."]);
91
+
92
+ console.log("\nWriting PATH shims (absolute paths)…");
93
+ const globalBinDir = writeGlobalBinShims();
94
+
95
+ console.log("\nVerifying…");
96
+ verify(globalBinDir);
97
+
98
+ console.log("\nFrom any project folder:");
99
+ console.log(" forgetrail install --lite");
100
+ console.log(" forgetrail install");