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.
- package/CONTINUATION_PROMPT.md +3 -3
- package/INITIAL_PROMPT.md +7 -7
- package/README.md +3 -3
- package/TRY_FORGETRAIL.md +2 -2
- package/WORKFLOW.md +15 -15
- package/content/AGENT_INTEGRATION_claude.md +1 -1
- package/content/AGENT_INTEGRATION_cursor.md +2 -3
- package/content/AGENT_INTEGRATION_generic.md +4 -4
- package/content/AGENT_INTEGRATION_grok.md +6 -6
- package/content/COMPANION_TOOLS.md +3 -3
- package/content/FORGETRAIL_LITE.md +16 -17
- package/content/FORGETRAIL_PROGRESS.md +1 -1
- package/content/GENESIS_SPEC_PROMPT.md +1 -1
- package/content/GENESIS_STUB.md +1 -1
- package/content/GREENFIELD_INTAKE.md +2 -2
- package/content/KICKOFF_WITHOUT_MCP.md +1 -1
- package/content/NEW_PROJECT_BOOTSTRAP.md +13 -13
- package/content/ONE_CLICK_DEV_SETUP.md +1 -1
- package/content/PLAN_MODE_PATTERNS.md +2 -2
- package/content/POCKETBASE_SCHEMA_SCRIPT.md +1 -1
- package/content/POST_BOOTSTRAP_USER_MESSAGE.md +2 -2
- package/content/SESSION_RESUME_MCP.md +1 -2
- package/content/SYSTEM_HEALTH_CHECKS.md +1 -1
- package/content/companion-tools.json +6 -1
- package/content/cursor-rules/forgetrail-phase-status.mdc +3 -3
- package/content/examples/two-session-continuity.md +3 -5
- package/content/hooks/README.md +4 -3
- package/content/hooks/appledger-bin.mjs +33 -0
- package/content/hooks/session-start.mjs +6 -1
- package/content/hooks/session-stop.mjs +76 -4
- package/content/hooks/validate-tracking.mjs +26 -0
- package/content/scripts/forgetrail-dev-launcher.mjs +1 -1
- package/content/skills/forgetrail/SKILL.md +6 -8
- package/docs/NPM.md +3 -3
- package/docs/PHASE_1_BRIEF.md +2 -2
- package/docs/SPEC_FEATURE_TEMPLATE.md +1 -1
- package/package.json +1 -1
- package/prompts/propagate-to-forgetrail.md +7 -7
- package/prompts/second-family-critique.md +1 -1
- package/scripts/forgetrail-cli.mjs +1 -1
- package/scripts/install.mjs +5 -4
- 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.
|
|
14
|
-
| [`forgetrail-mcp`](https://www.npmjs.com/package/forgetrail-mcp) | 0.4.3 | 0.4.
|
|
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.
|
|
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
|
|
package/docs/PHASE_1_BRIEF.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
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
|
@@ -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
|
|
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 **
|
|
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
|
|
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).
|
|
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
|
|
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
|
|
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
|
-
|
|
|
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,
|
|
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.
|
|
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
|
package/scripts/install.mjs
CHANGED
|
@@ -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/
|
|
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/
|
|
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.
|
|
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.
|
|
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\.
|
|
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, /
|
|
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, /
|
|
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.
|
|
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`/);
|