forgetrail 0.5.4 → 0.5.6

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 (42) hide show
  1. package/CONTINUATION_PROMPT.md +3 -3
  2. package/INITIAL_PROMPT.md +7 -7
  3. package/README.md +3 -3
  4. package/TRY_FORGETRAIL.md +2 -2
  5. package/WORKFLOW.md +15 -15
  6. package/content/AGENT_INTEGRATION_claude.md +1 -1
  7. package/content/AGENT_INTEGRATION_cursor.md +2 -3
  8. package/content/AGENT_INTEGRATION_generic.md +4 -4
  9. package/content/AGENT_INTEGRATION_grok.md +6 -6
  10. package/content/COMPANION_TOOLS.md +3 -3
  11. package/content/FORGETRAIL_LITE.md +16 -17
  12. package/content/FORGETRAIL_PROGRESS.md +1 -1
  13. package/content/GENESIS_SPEC_PROMPT.md +1 -1
  14. package/content/GENESIS_STUB.md +1 -1
  15. package/content/GREENFIELD_INTAKE.md +2 -2
  16. package/content/KICKOFF_WITHOUT_MCP.md +1 -1
  17. package/content/NEW_PROJECT_BOOTSTRAP.md +13 -13
  18. package/content/ONE_CLICK_DEV_SETUP.md +1 -1
  19. package/content/PLAN_MODE_PATTERNS.md +2 -2
  20. package/content/POCKETBASE_SCHEMA_SCRIPT.md +1 -1
  21. package/content/POST_BOOTSTRAP_USER_MESSAGE.md +2 -2
  22. package/content/SESSION_RESUME_MCP.md +1 -2
  23. package/content/SYSTEM_HEALTH_CHECKS.md +1 -1
  24. package/content/companion-tools.json +6 -1
  25. package/content/cursor-rules/forgetrail-phase-status.mdc +3 -3
  26. package/content/examples/two-session-continuity.md +3 -5
  27. package/content/hooks/README.md +4 -3
  28. package/content/hooks/appledger-bin.mjs +33 -0
  29. package/content/hooks/session-start.mjs +6 -1
  30. package/content/hooks/session-stop.mjs +76 -4
  31. package/content/hooks/validate-tracking.mjs +26 -0
  32. package/content/scripts/forgetrail-dev-launcher.mjs +1 -1
  33. package/content/skills/forgetrail/SKILL.md +6 -8
  34. package/docs/NPM.md +3 -3
  35. package/docs/PHASE_1_BRIEF.md +2 -2
  36. package/docs/SPEC_FEATURE_TEMPLATE.md +1 -1
  37. package/package.json +1 -1
  38. package/prompts/propagate-to-forgetrail.md +7 -7
  39. package/prompts/second-family-critique.md +1 -1
  40. package/scripts/forgetrail-cli.mjs +1 -1
  41. package/scripts/install.mjs +5 -4
  42. package/scripts/tracking-cutover.test.mjs +132 -5
package/docs/NPM.md CHANGED
@@ -10,12 +10,12 @@ Two public unscoped packages:
10
10
 
11
11
  | Package | This tree | On npm until you publish | From | What it is |
12
12
  | --- | --- | --- | --- | --- |
13
- | [`forgetrail`](https://www.npmjs.com/package/forgetrail) | 0.5.3 | 0.5.3 | repo root | CLI + Lite + templates. `pnpm dlx forgetrail install --lite` |
14
- | [`forgetrail-mcp`](https://www.npmjs.com/package/forgetrail-mcp) | 0.4.3 | 0.4.2 | `mcp-server/` | MCP bin. Depends on `forgetrail` for content. `pnpm dlx forgetrail-mcp` |
13
+ | [`forgetrail`](https://www.npmjs.com/package/forgetrail) | 0.5.5 | 0.5.5 | repo root | CLI + Lite + templates. `pnpm dlx forgetrail install --lite` |
14
+ | [`forgetrail-mcp`](https://www.npmjs.com/package/forgetrail-mcp) | 0.4.3 | 0.4.3 | `mcp-server/` | MCP bin. Depends on `forgetrail` for content. `pnpm dlx forgetrail-mcp` |
15
15
 
16
16
  These are **installer and MCP channels**, not a library. App repos should not add `forgetrail` to `dependencies`.
17
17
 
18
- `forgetrail@0.5.3` and `forgetrail-mcp@0.4.2` are on npm. This tree adds `forgetrail --version`, forgetrail.dev `llms.txt`, and companion text that names `appledger/`, so `forgetrail` needs a version bump before its next publish. `forgetrail-mcp@0.4.3` changes the startup banner and the `ingestPlanArtifact` description, and is not published yet. `getCompanionSuggestions` reads its text from the installed `forgetrail`, so the new companion text reaches MCP users only after `forgetrail` is published. `pnpm run pack:check` is the gate. It does not publish.
18
+ `forgetrail@0.5.5` and `forgetrail-mcp@0.4.3` are on npm and match these `package.json` versions as of 2026-09-29. A later content change needs a version bump before it publishes. `getCompanionSuggestions` reads its text from the installed `forgetrail`. `pnpm run pack:check` is the gate. It does not publish.
19
19
 
20
20
  ---
21
21
 
@@ -4,7 +4,7 @@ _Structured capture of planning and architecture **before** code scaffolding. Go
4
4
 
5
5
  **Status:** `[draft | locked]`
6
6
  **Last updated:** `[ISO date]`
7
- **Phase 1 exit:** Do not mark the `plan` phase complete in `appledger/profiles/forgetrail.yaml` until this brief is **locked** and major commitments are decision records. Do not write `workflow_tracking.json`.
7
+ **Phase 1 exit:** Do not mark the `plan` phase complete in `appledger/profiles/forgetrail.yaml` until this brief is **locked** and major commitments are decision records.
8
8
 
9
9
  ---
10
10
 
@@ -158,7 +158,7 @@ _Ordered list of what to build after the spine runs — aligns with initial `TOD
158
158
 
159
159
  - [ ] User has confirmed stack, folder shape, data sketch, hero flow, and v1 boundaries
160
160
  - [ ] This brief is **locked** (no `[draft]` ambiguity) or remaining items are only in §9 Open questions
161
- - [ ] `appledger/` updated: a decision record for each major D#, and the session records the sign-off. Do not write `workflow_tracking.json`.
161
+ - [ ] `appledger/` updated: a decision record for each major D#, and the session records the sign-off.
162
162
  - [ ] Phase 2 opener will read **this file**, `appledger/profiles/forgetrail.yaml`, and the latest session record first
163
163
 
164
164
  > 💡 **Lesson learned:** Treat the brief and the decision records as a pair. The brief is what the next human or agent reads in one pass. Duplicated rationale is OK — drift between them is not. After Phase 2 starts, **merge** this content into `CONTEXT_PROMPT.md` per the mapping in that template’s “Handoff from Phase 1” section.
@@ -132,7 +132,7 @@ _Testable binary pass/fail checks. Avoid vague qualifiers ("performant", "clean"
132
132
 
133
133
  ## 10. Decisions (optional)
134
134
 
135
- _Lock choices here (and mirror major ones into `.forgetrail/workflow_tracking.json` → `decisions[]` when they are architectural)._
135
+ _Lock choices here (and record major ones as decision records in `appledger/` when they are architectural)._
136
136
 
137
137
  **D1.**
138
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgetrail",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "private": false,
5
5
  "description": "ForgeTrail: a persistent development system for building software with AI agents. CLI, Lite protocol, and methodology templates.",
6
6
  "license": "Apache-2.0",
@@ -2,15 +2,15 @@
2
2
 
3
3
  Run this prompt after implementing a new feature, pattern, plan, or significant enhancement in your app. It generalizes **both** durable **feature shapes** (what to document in **`TECHNICAL_REFERENCE.md`**, **`TEST_PLAN.md`**, brand/design templates) **and** reusable **lessons** (callouts in templates), then updates ForgeTrail so future projects start with that guidance.
4
4
 
5
- **Any ForgeTrail project is a valid source** — not just the flagship app that seeded these templates. A weekend one-shot with two gotchas in its tracking file propagates the same way a mature SaaS does; small projects often exercise the newest tool versions and surface the freshest scaffolding lessons. If your project has only a handful of docs (Lite bootstrap, `one-shot` archetype), skip the doc-inventory ceremony and use **Harvest mode** below.
5
+ **Any ForgeTrail project is a valid source** — not just the flagship app that seeded these templates. A weekend one-shot with two lessons in its ledger propagates the same way a mature SaaS does; small projects often exercise the newest tool versions and surface the freshest scaffolding lessons. If your project has only a handful of docs (Lite bootstrap, `one-shot` archetype), skip the doc-inventory ceremony and use **Harvest mode** below.
6
6
 
7
7
  ### Harvest mode (small projects and project wrap)
8
8
 
9
- Use this when the source project is **small** (no product journal, minimal doc set) or you are running the **wrap protocol** (WORKFLOW.md §1e) at project end. The primary discovery source is the **tracking file**, not the doc tree:
9
+ Use this when the source project is **small** (no product journal, minimal doc set) or you are running the **wrap protocol** (WORKFLOW.md §1e) at project end. The primary discovery source is the **ledger**, not the doc tree:
10
10
 
11
- 1. **Read `.forgetrail/workflow_tracking.json`** — every `gotchas[]` entry and every `decisions[]` entry — plus `CONTEXT_PROMPT.md`'s gotcha/pattern sections and any `FORGETRAIL_LITE_UPDATES.md`.
11
+ 1. **Read appledger decision records and lesson records**, plus `CONTEXT_PROMPT.md`'s gotcha/pattern sections and any `FORGETRAIL_LITE_UPDATES.md`.
12
12
  2. **Classify each entry:** *generalizable* (framework traps, CLI behavior changes, scaffolding surprises, integration lessons — anything a different project on the same stack could hit) vs *app-specific* (content decisions, domain rules). Only the first group propagates.
13
- 3. **For each generalizable entry**, run the normal Step 2 dedupe (grep ForgeTrail for existing coverage) and Step 3 mapping (which template gets it). Tracking-file gotchas usually land as **pattern memory** (`CONTEXT_PROMPT.md` callouts, `FORGETRAIL_LITE.md` scaffolding notes); occasionally a decision reveals a **feature shape** worth a stub.
13
+ 3. **For each generalizable entry**, run the normal Step 2 dedupe (grep ForgeTrail for existing coverage) and Step 3 mapping (which template gets it). Ledger lessons usually land as **pattern memory** (`CONTEXT_PROMPT.md` callouts, `FORGETRAIL_LITE.md` scaffolding notes); occasionally a decision reveals a **feature shape** worth a stub.
14
14
  4. **Zero-yield is fine.** If nothing generalizes, say so and skip the update-log entry — do not pad.
15
15
 
16
16
  Harvest mode replaces the Discovery scan and Journal-driven sections for these projects; everything from Step 2 onward applies unchanged.
@@ -44,7 +44,7 @@ When you run a **periodic** propagation (catch-up after a sprint, or anytime **A
44
44
 
45
45
  ## Context
46
46
 
47
- **Your app** is the live project. Its docs are the source of truth — **whichever of these exist**. A full Phase-7 product will have most of them; a Lite or `one-shot` project may have only `CONTEXT_PROMPT.md`, `README.md`, `TODO.md`, and the tracking file (use **Harvest mode** above in that case). Treat this list as a menu, not a requirement:
47
+ **Your app** is the live project. Its docs are the source of truth — **whichever of these exist**. A full Phase-7 product will have most of them; a Lite or `one-shot` project may have only `CONTEXT_PROMPT.md`, `README.md`, `TODO.md`, and `appledger/` (use **Harvest mode** above in that case). Treat this list as a menu, not a requirement:
48
48
 
49
49
  - `CONTEXT_PROMPT.md`
50
50
  - `docs/PHASE_1_BRIEF.md` (if you improved the Phase 1 handoff template)
@@ -91,7 +91,7 @@ Run after meaningful implementation work **and** after substantive **documentati
91
91
  | **Isolated service health checks / local Ollama** (test PB without app; install Ollama + VRAM model + completion test; avoid thinking models by default) | **`SYSTEM_HEALTH_CHECKS.md`**, **`FORGETRAIL_LITE.md`** §4.7–§4.8; **`scripts/test-pocketbase.mjs`**, **`scripts/setup-ollama.mjs`**, **`scripts/test-ollama.mjs`**, **`SCAFFOLD_INSTALL.json`** `systemHealthChecks` |
92
92
  | **URL / listing import pipeline** (fetch + HTML parse, refresh-from-source, optional LLM-assisted recover when selectors drift) | `docs/TECHNICAL_REFERENCE.md` (*URL import* — paywalls, **markup drift / optional recover**); `docs/CONTEXT_PROMPT.md`; `docs/TEST_PLAN.md` §2a; **`content/FORGETRAIL_LITE.md` §7.2**; partial spec stub under **`specs/partial/`** when shipped behavior needs follow-up. |
93
93
  | **A week+ of shipping without running this prompt** | Use **Journal-driven propagation**: sweep **`docs/PRODUCT_JOURNAL.md`** (e.g. last 10–14 days) so **Added** / **Improved** items get **`TECHNICAL_REFERENCE` / `TEST_PLAN`** stubs, not only **CONTEXT_PROMPT** lessons. |
94
- | **Project wrap — the source project is finished, delivered, or shelved** | Run **Harvest mode** (top of this prompt) as part of the wrap protocol (**WORKFLOW.md §1e**): sweep the tracking file's `gotchas[]` + `decisions[]` before the project goes quiet. Small projects count. |
94
+ | **Project wrap — the source project is finished, delivered, or shelved** | Run **Harvest mode** (top of this prompt) as part of the wrap protocol (**WORKFLOW.md §1e**): sweep appledger lesson records and decision records before the project goes quiet. Small projects count. |
95
95
  | **CI/CD, Node/`engines`, or package-manager pins changed** | Keep workflow, container, and `package.json` in sync — lessons for **DEPLOYMENT.md**, **TECHNICAL_REFERENCE.md**, **CONTEXT_PROMPT.md**. |
96
96
  | **Deploy scripts, systemd units, or process stop/restart behavior** | `TimeoutStopSec`, blue-green `systemctl stop`, and graceful-shutdown tradeoffs affect **wall-clock deploy time** as well as request safety — **DEPLOYMENT.md** (and **CONTEXT_PROMPT** if it changes operator expectations). |
97
97
  | **New scripts, hooks, or dev-tooling** | Journal/lint/build wrappers → **CONTEXT_PROMPT** (tooling) or **TECHNICAL_REFERENCE** (pipelines). |
@@ -109,7 +109,7 @@ Do **not** rely only on the immediate trigger. Skim **multiple signal sources**
109
109
 
110
110
  | Source | What to extract |
111
111
  |--------|-----------------|
112
- | **`.forgetrail/workflow_tracking.json`** (`gotchas[]`, `decisions[]`) | Framework traps, CLI/scaffolding surprises, and integration lessons logged during the build — the primary source for small projects (see **Harvest mode**), and an easy-to-miss one for large projects where docs lag the tracking file. |
112
+ | **`appledger/` decision and lesson records** | Framework traps, CLI/scaffolding surprises, and integration lessons logged during the build — the primary source for small projects (see **Harvest mode**), and an easy-to-miss one for large projects where docs lag the ledger. |
113
113
  | **`git log` / recent commits** | Subjects and paths; cluster related work; compare to the last `update-log.md` propagation date. |
114
114
  | **`docs/PRODUCT_JOURNAL.md`** (or git-derived changelog) | Day-level themes across UX, infra, and docs. Map **`Added`** / major **`Improved`** items to Step 3: new **feature-area** stubs in **`TECHNICAL_REFERENCE.md`**, **`TEST_PLAN.md`** checks, **`BRAND_AND_PRODUCT.md`** / **`DESIGN_SYSTEM.md`** when IA or positioning changes—not only **`CONTEXT_PROMPT.md`** bullets. |
115
115
  | **Recently touched `specs/`** | Durable architecture and copy decisions, including WIP specs. |
@@ -40,4 +40,4 @@ When the second model returns its review:
40
40
 
41
41
  1. **Verify observations against codebase reality:** Not all critiques apply if the opposing model lacked local file context.
42
42
  2. **Update the spec:** Add newly discovered edge cases to the Edge Cases section; tighten acceptance criteria.
43
- 3. **Log architectural pivots:** If a critique exposes an unviable path, log the alternative in `decisions[]` in `workflow_tracking.json`.
43
+ 3. **Log architectural pivots:** If a critique exposes an unviable path, record the alternative as an appledger decision record.
@@ -37,7 +37,7 @@ Install options:
37
37
  --with-genesis-stub With --lite: also create docs/GENESIS.md stub
38
38
  --force, -f Overwrite existing files
39
39
  --dry-run Preview without writing
40
- --skip-tracking Accepted and ignored. A tracking JSON file is not installed.
40
+ --skip-tracking Accepted and ignored.
41
41
  --path, -p <dir> Install elsewhere (default: current directory)
42
42
  --help, -h Show help
43
43
  --version, -v Print the forgetrail version
@@ -38,6 +38,7 @@ const HOOK_FILES = [
38
38
  "guard-shell.mjs",
39
39
  "guard-edit.mjs",
40
40
  "session-start.mjs",
41
+ "appledger-bin.mjs",
41
42
  "validate-tracking.mjs",
42
43
  "validate-tracking-core.mjs",
43
44
  "session-stop.mjs",
@@ -86,7 +87,7 @@ export function runInstallForgetrail(rawArgv, { defaultToCwd = false } = {}) {
86
87
  console.log("\nDone.");
87
88
  console.log(" Methodology: _forgetrail/WORKFLOW.md");
88
89
  console.log(" First chat: _forgetrail/INITIAL_PROMPT.md");
89
- console.log(" Project record: appledger/ (not workflow_tracking.json)");
90
+ console.log(" Project record: appledger/");
90
91
  console.log(" Hooks: .forgetrail/hooks/ (enforced via .cursor/hooks.json)");
91
92
  printLedgerNext();
92
93
  return { target, mode: "full" };
@@ -142,7 +143,7 @@ export function runInstallLite(rawArgv, { defaultToCwd = false } = {}) {
142
143
 
143
144
  console.log("\nDone.");
144
145
  console.log(" Protocol: .forgetrail/FORGETRAIL_LITE.md");
145
- console.log(" Project record: appledger/ (a tracking JSON file is not installed)");
146
+ console.log(" Project record: appledger/");
146
147
  console.log(" Hooks: .forgetrail/hooks/ (enforced via .cursor/hooks.json)");
147
148
  if (args.withGenesisStub) {
148
149
  console.log(" Genesis: docs/GENESIS.md (stub — replace with your spec)");
@@ -168,7 +169,7 @@ Usage:
168
169
  Options:
169
170
  --path, -p <dir> Target project root (default: current directory for \`forgetrail\` CLI)
170
171
  --force, -f Overwrite existing files
171
- --skip-tracking Accepted and ignored. A tracking JSON file is not installed.
172
+ --skip-tracking Accepted and ignored.
172
173
  --dry-run Print actions only
173
174
 
174
175
  Installs:
@@ -186,7 +187,7 @@ Usage:
186
187
  Options:
187
188
  --path, -p <dir> Target project root (default: current directory for \`forgetrail\` CLI)
188
189
  --force, -f Overwrite existing files
189
- --skip-tracking Accepted and ignored. A tracking JSON file is not installed.
190
+ --skip-tracking Accepted and ignored.
190
191
  --with-genesis-stub Also create docs/GENESIS.md stub (for the Try path)
191
192
  --dry-run Print actions only
192
193
 
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { mkdtempSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2
+ import { mkdtempSync, readFileSync, writeFileSync, mkdirSync, utimesSync, chmodSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -94,6 +94,70 @@ test("session start warns on a legacy file and does not quote it as the live pha
94
94
  assert.doesNotMatch(payload.additional_context, /Current Phase: 4-feature-iteration/);
95
95
  });
96
96
 
97
+ function stubAppledger(dir, line) {
98
+ const stub = join(dir, "bin");
99
+ mkdirSync(stub, { recursive: true });
100
+ if (process.platform === "win32") {
101
+ writeFileSync(join(stub, "appledger.cmd"), `@echo off\r\necho ${line}\r\n`);
102
+ } else {
103
+ const file = join(stub, "appledger");
104
+ writeFileSync(file, `#!/bin/sh\necho ${line}\n`);
105
+ chmodSync(file, 0o755);
106
+ }
107
+ const system = process.env.SystemRoot ? `${process.env.SystemRoot}\\System32` : "/usr/bin";
108
+ const sep = process.platform === "win32" ? ";" : ":";
109
+ return { PATH: [stub, system, process.env.PATH].filter(Boolean).join(sep) };
110
+ }
111
+
112
+ function runHook(script, dir, { input, env } = {}) {
113
+ return spawnSync(process.execPath, [join(root, "content", "hooks", script)], {
114
+ cwd: dir,
115
+ input,
116
+ encoding: "utf8",
117
+ env: env ? { ...process.env, ...env } : process.env,
118
+ });
119
+ }
120
+
121
+ test("session start includes orient when appledger is on PATH", () => {
122
+ const dir = mkdtempSync(join(tmpdir(), "ft-orient-"));
123
+ mkdirSync(join(dir, "appledger"), { recursive: true });
124
+ writeFileSync(join(dir, "appledger", "manifest.yaml"), "format: appledger\n");
125
+ const result = runHook("session-start.mjs", dir, { env: stubAppledger(dir, "STUB-ORIENT") });
126
+ assert.equal(result.status, 0, result.stderr);
127
+ assert.match(JSON.parse(result.stdout).additional_context, /STUB-ORIENT/);
128
+ });
129
+
130
+ test("session start keeps the ledger text when appledger is absent", () => {
131
+ const dir = mkdtempSync(join(tmpdir(), "ft-noap-"));
132
+ mkdirSync(join(dir, "appledger"), { recursive: true });
133
+ writeFileSync(join(dir, "appledger", "manifest.yaml"), "format: appledger\n");
134
+ const system = process.env.SystemRoot ? `${process.env.SystemRoot}\\System32` : "/usr/bin";
135
+ const result = runHook("session-start.mjs", dir, { env: { PATH: system } });
136
+ assert.equal(result.status, 0, result.stderr);
137
+ const text = JSON.parse(result.stdout).additional_context;
138
+ assert.match(text, /Project record: appledger/);
139
+ assert.doesNotMatch(text, /STUB-ORIENT/);
140
+ });
141
+
142
+ test("an edit under appledger/ runs check and reports a warning", () => {
143
+ const dir = mkdtempSync(join(tmpdir(), "ft-check-"));
144
+ mkdirSync(join(dir, "appledger"), { recursive: true });
145
+ const manifest = join(dir, "appledger", "manifest.yaml");
146
+ writeFileSync(manifest, "format: appledger\n");
147
+ const warned = runHook("validate-tracking.mjs", dir, {
148
+ input: JSON.stringify({ path: manifest }),
149
+ env: stubAppledger(dir, "warning missing_source example"),
150
+ });
151
+ assert.equal(warned.status, 0, warned.stderr);
152
+ assert.match(JSON.parse(warned.stdout).additional_context, /warning missing_source example/);
153
+ const clean = runHook("validate-tracking.mjs", dir, {
154
+ input: JSON.stringify({ path: manifest }),
155
+ env: stubAppledger(join(dir, "clean"), "ok ledger"),
156
+ });
157
+ assert.equal(clean.status, 0, clean.stderr);
158
+ assert.deepEqual(JSON.parse(clean.stdout), {});
159
+ });
160
+
97
161
  test("Lite and workflow instructions name the ledger", () => {
98
162
  const lite = readFileSync(join(root, "content", "FORGETRAIL_LITE.md"), "utf8");
99
163
  const legacyAt = lite.indexOf("## 11. Legacy");
@@ -101,10 +165,10 @@ test("Lite and workflow instructions name the ledger", () => {
101
165
  assert.ok(legacyAt > 0 && resumeAt > legacyAt);
102
166
  const instructional = `${lite.slice(0, legacyAt)}\n${lite.slice(resumeAt)}`;
103
167
  assert.match(lite.slice(legacyAt, resumeAt), /"schemaVersion": "lite-1"/);
104
- assert.match(instructional, /ForgeTrail Lite v2\.2\.1/);
168
+ assert.match(instructional, /ForgeTrail Lite v2\.2\.2/);
105
169
  for (const line of instructional.split("\n")) {
106
170
  if (!line.includes("workflow_tracking.json")) continue;
107
- assert.match(line, /[Dd]o not|does not|not the live phase|not recreate/, line);
171
+ assert.match(line, /legacy/, line);
108
172
  }
109
173
  const workflow = readFileSync(join(root, "WORKFLOW.md"), "utf8");
110
174
  assert.match(workflow, /not_applicable/);
@@ -112,18 +176,81 @@ test("Lite and workflow instructions name the ledger", () => {
112
176
  assert.doesNotMatch(workflow, /Where this file names that JSON/);
113
177
  for (const line of workflow.split("\n")) {
114
178
  if (!line.includes("workflow_tracking.json")) continue;
115
- assert.match(line, /[Dd]o not|does not|not the system of record|not the live phase/, line);
179
+ assert.match(line, /legacy/, line);
116
180
  }
117
181
  });
118
182
 
183
+ test("fresh-project instructions do not name the tracking file", () => {
184
+ const files = [
185
+ "INITIAL_PROMPT.md",
186
+ "CONTINUATION_PROMPT.md",
187
+ "content/AGENT_INTEGRATION_claude.md",
188
+ "content/AGENT_INTEGRATION_cursor.md",
189
+ "content/AGENT_INTEGRATION_generic.md",
190
+ "content/AGENT_INTEGRATION_grok.md",
191
+ "content/COMPANION_TOOLS.md",
192
+ "content/FORGETRAIL_PROGRESS.md",
193
+ "content/GENESIS_SPEC_PROMPT.md",
194
+ "content/GENESIS_STUB.md",
195
+ "content/GREENFIELD_INTAKE.md",
196
+ "content/KICKOFF_WITHOUT_MCP.md",
197
+ "content/NEW_PROJECT_BOOTSTRAP.md",
198
+ "content/PLAN_MODE_PATTERNS.md",
199
+ "content/POCKETBASE_SCHEMA_SCRIPT.md",
200
+ "content/POST_BOOTSTRAP_USER_MESSAGE.md",
201
+ "content/SESSION_RESUME_MCP.md",
202
+ "content/companion-tools.json",
203
+ "content/cursor-rules/forgetrail-phase-status.mdc",
204
+ "content/skills/forgetrail/SKILL.md",
205
+ "docs/PHASE_1_BRIEF.md",
206
+ "docs/SPEC_FEATURE_TEMPLATE.md",
207
+ ];
208
+ for (const file of files) {
209
+ for (const line of readFileSync(join(root, file), "utf8").split("\n")) {
210
+ if (!line.includes("workflow_tracking.json")) continue;
211
+ assert.match(line, /legacy|exists|validateTracking/, `${file}: ${line}`);
212
+ }
213
+ }
214
+ });
215
+
216
+ function stopHook(dir, input) {
217
+ const result = spawnSync(process.execPath, [join(root, "content", "hooks", "session-stop.mjs")], {
218
+ cwd: dir,
219
+ input: JSON.stringify(input),
220
+ encoding: "utf8",
221
+ });
222
+ assert.equal(result.status, 0, result.stderr);
223
+ return JSON.parse(result.stdout);
224
+ }
225
+
226
+ test("session stop reminds only after work newer than the session record", () => {
227
+ const dir = mkdtempSync(join(tmpdir(), "ft-stop-"));
228
+ mkdirSync(join(dir, "appledger", "records", "session"), { recursive: true });
229
+ writeFileSync(join(dir, "appledger", "manifest.yaml"), "format: appledger\n");
230
+ const session = join(dir, "appledger", "records", "session", "session-1.md");
231
+ writeFileSync(session, "session\n");
232
+ spawnSync("git", ["init", "-q"], { cwd: dir });
233
+ const old = new Date(Date.now() - 60_000);
234
+ utimesSync(session, old, old);
235
+ assert.deepEqual(stopHook(dir, { status: "completed", loop_count: 0 }), {});
236
+ writeFileSync(join(dir, "app.ts"), "export {};\n");
237
+ const reminder = stopHook(dir, { status: "completed", loop_count: 0 });
238
+ assert.match(reminder.followup_message, /session record/);
239
+ assert.doesNotMatch(reminder.followup_message, /workflow_tracking/);
240
+ assert.deepEqual(stopHook(dir, { status: "completed", loop_count: 1 }), {});
241
+ writeFileSync(session, "session updated\n");
242
+ assert.deepEqual(stopHook(dir, { status: "completed", loop_count: 0 }), {});
243
+ });
244
+
119
245
  test("kickoff names appledger init and does not require the tracking file", () => {
120
246
  const kickoff = readFileSync(join(root, "mcp-server", "src", "index.ts"), "utf8");
247
+ assert.match(kickoff, /z\.union\(\[z\.string\(\), z\.number\(\)\]\)/);
121
248
  assert.match(kickoff, /appledger init/);
122
249
  assert.doesNotMatch(kickoff, /no separate init command/);
123
250
  const plan = readFileSync(join(root, "content", "PLAN_MODE_PATTERNS.md"), "utf8");
124
251
  assert.match(plan, /decision record/);
125
252
  assert.doesNotMatch(plan, /decisions\[\]/);
126
- assert.match(plan, /Do not create `\.forgetrail\/workflow_tracking\.json`/);
253
+ assert.doesNotMatch(plan, /workflow_tracking/);
127
254
  const genesis = readFileSync(join(root, "content", "GENESIS_STUB.md"), "utf8");
128
255
  assert.match(genesis, /appledger init/);
129
256
  assert.doesNotMatch(genesis, /Create `\.forgetrail\/workflow_tracking\.json`/);