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.
- package/CONTINUATION_PROMPT.md +42 -0
- package/INITIAL_PROMPT.md +214 -0
- package/LICENSE +201 -0
- package/README.md +198 -0
- package/TRACKING_SCHEMA.md +162 -0
- package/TRY_FORGETRAIL.md +78 -0
- package/WORKFLOW.md +921 -0
- package/content/AGENT_INTEGRATION_claude.md +38 -0
- package/content/AGENT_INTEGRATION_cursor.md +53 -0
- package/content/AGENT_INTEGRATION_generic.md +50 -0
- package/content/AGENT_INTEGRATION_grok.md +82 -0
- package/content/DEV_AUTOMATION_SCRIPTS.md +103 -0
- package/content/FORGETRAIL_LITE.md +1202 -0
- package/content/FORGETRAIL_LITE_UPDATES.md +13 -0
- package/content/FORGETRAIL_PROGRESS.md +26 -0
- package/content/GENESIS_SPEC_PROMPT.md +70 -0
- package/content/GENESIS_STUB.md +18 -0
- package/content/GREENFIELD_INTAKE.md +76 -0
- package/content/KICKOFF_WITHOUT_MCP.md +16 -0
- package/content/LITE_WORKFLOW_TRACKING.json +42 -0
- package/content/NEW_PROJECT_BOOTSTRAP.md +180 -0
- package/content/ONE_CLICK_DEV_SETUP.md +142 -0
- package/content/PLAN_MODE_PATTERNS.md +43 -0
- package/content/POCKETBASE_SCHEMA_SCRIPT.md +52 -0
- package/content/POST_BOOTSTRAP_USER_MESSAGE.md +51 -0
- package/content/SCAFFOLD_INSTALL.json +110 -0
- package/content/SESSION_RESUME_MCP.md +35 -0
- package/content/SYSTEM_HEALTH_CHECKS.md +131 -0
- package/content/USER_REPLY_FORMAT.md +41 -0
- package/content/cursor-rules/forgetrail-lessons-gate.mdc +30 -0
- package/content/cursor-rules/forgetrail-lessons-mcp.mdc +24 -0
- package/content/cursor-rules/forgetrail-no-trailer.mdc +49 -0
- package/content/cursor-rules/forgetrail-phase-status.mdc +61 -0
- package/content/cursor-rules/forgetrail-updates-log.mdc +30 -0
- package/content/cursor-rules/spec-completion.mdc +33 -0
- package/content/cursor-rules/specs-and-todo.mdc +26 -0
- package/content/cursor-rules/url-host-matching.mdc +31 -0
- package/content/cursor-rules/us-english.mdc +9 -0
- package/content/cursor-rules/user-facing-content.mdc +31 -0
- package/content/examples/GENESIS_SAMPLE_mars-habitat-roster.md +154 -0
- package/content/forgetrail-workspace-README.md +43 -0
- package/content/scripts/forgetrail-dev-launcher.mjs +208 -0
- package/content/scripts/forgetrail-env.mjs +51 -0
- package/content/scripts/setup-ollama.mjs +207 -0
- package/content/scripts/setup-pocketbase.mjs +133 -0
- package/content/scripts/test-ollama.mjs +71 -0
- package/content/scripts/test-pocketbase.mjs +110 -0
- package/content/skills/forgetrail/SKILL.md +92 -0
- package/docs/AUTOMATED_TESTING.md +269 -0
- package/docs/BLACK_HAT_REPORT.md +143 -0
- package/docs/BRAND_AND_PRODUCT.md +381 -0
- package/docs/BUGS.md +62 -0
- package/docs/BUSINESS_PLAN.md +256 -0
- package/docs/CODE_QUALITY.md +170 -0
- package/docs/CONTEXT_PROMPT.md +357 -0
- package/docs/DEPLOYMENT.md +181 -0
- package/docs/DESIGN_SYSTEM.md +414 -0
- package/docs/DEV_ESTIMATE.md +127 -0
- package/docs/FORGETRAIL_RENAME.md +193 -0
- package/docs/IDEAS.md +19 -0
- package/docs/MARKETING_GROWTH.md +301 -0
- package/docs/NAMING_EXPLORATION.md +406 -0
- package/docs/NPM.md +98 -0
- package/docs/PHASE_1_BRIEF.md +161 -0
- package/docs/README.md +75 -0
- package/docs/SPEC_FEATURE_TEMPLATE.md +155 -0
- package/docs/SPEC_UI_CHROME_NAV_TEMPLATE.md +48 -0
- package/docs/TECHNICAL_REFERENCE.md +836 -0
- package/docs/TEST_PLAN.md +467 -0
- package/docs/TODO.md +125 -0
- package/package.json +68 -0
- package/prompts/black-hat-audit.md +235 -0
- package/prompts/brand-copy-edit-pass.md +55 -0
- package/prompts/cialdini-marketing-audit.md +141 -0
- package/prompts/competitor-deep-dive.md +201 -0
- package/prompts/docs-alignment-audit.md +108 -0
- package/prompts/engineering-skill-library.md +75 -0
- package/prompts/landing-page-rewrite.md +63 -0
- package/prompts/microcopy-centralization.md +128 -0
- package/prompts/panel-usability-audit.md +197 -0
- package/prompts/personal-beta-outreach.md +195 -0
- package/prompts/pre-launch-audit.md +148 -0
- package/prompts/product-feedback-to-spec.md +76 -0
- package/prompts/propagate-to-forgetrail.md +315 -0
- package/prompts/user-facing-content-sync-audit.md +200 -0
- package/prompts/ux-cohesion-audit.md +165 -0
- package/scripts/ensure-lease.mjs +23 -0
- package/scripts/forgetrail-cli.mjs +85 -0
- package/scripts/install-forgetrail-lite.mjs +11 -0
- package/scripts/install-forgetrail.mjs +11 -0
- package/scripts/install-lib.mjs +115 -0
- package/scripts/install.mjs +163 -0
- package/scripts/link-global.mjs +100 -0
- package/scripts/mcp-lib.mjs +177 -0
- package/scripts/mcp-status.mjs +155 -0
- package/scripts/publish-gate.mjs +110 -0
- package/scripts/rename-to-forgetrail.mjs +273 -0
- package/workflow_tracking.json +124 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Keep FORGETRAIL_LITE_UPDATES.md current with protocol gaps discovered in this repo
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ForgeTrail Lite feedback log (FORGETRAIL_LITE_UPDATES.md)
|
|
7
|
+
|
|
8
|
+
This repo may carry **`FORGETRAIL_LITE.md`** (upstream protocol) plus **`FORGETRAIL_LITE_UPDATES.md`** (candidate patches) under **`.forgetrail/`** (gitignored when the repo is published).
|
|
9
|
+
|
|
10
|
+
## When to update `FORGETRAIL_LITE_UPDATES.md`
|
|
11
|
+
|
|
12
|
+
After substantive work, if you find something that **should be fixed or clarified in ForgeTrail Lite itself** — not just this app — add or extend an entry:
|
|
13
|
+
|
|
14
|
+
- Missing steps (e.g. `.env` loading, pnpm native builds, Vite monorepo `loadEnv` paths).
|
|
15
|
+
- Anti-patterns Lite doesn't warn about yet.
|
|
16
|
+
- Errors in the Lite defaults for **pnpm + TypeScript + monorepos** or **Cursor/agent** workflows.
|
|
17
|
+
- Doc structure suggestions (which § to patch).
|
|
18
|
+
|
|
19
|
+
**Do not** log routine bug fixes or product-only notes; use `.forgetrail/workflow_tracking.json → gotchas[]` for session-local incidents. **Do** log items a **future agent bootstrapping any ForgeTrail Lite project** would benefit from if the upstream file were updated.
|
|
20
|
+
|
|
21
|
+
## How to write an entry
|
|
22
|
+
|
|
23
|
+
1. Prefer **adding a numbered section** (or bullet under an existing section) with: what went wrong, suggested Lite change (§ reference if possible), optional project pointer.
|
|
24
|
+
2. Update the **summary table** at the bottom if the topic is new.
|
|
25
|
+
3. Keep tone aimed at **FORGETRAIL_LITE.md maintainers**, not end users.
|
|
26
|
+
4. If the same issue is already covered, **tighten or cross-link** instead of duplicating.
|
|
27
|
+
|
|
28
|
+
## This rule
|
|
29
|
+
|
|
30
|
+
The feedback loop is documented in **`.forgetrail/FORGETRAIL_LITE.md` §1.6** when Lite is present — so humans know why the file stays locally.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Where delivery specs live after implementation — completed vs partial
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Spec Completion Protocol
|
|
7
|
+
|
|
8
|
+
Applies to **delivery specs** only. Does **not** apply to **canonical** specs in `specs/canonical/` (they stay put). See `specs-and-todo.mdc`.
|
|
9
|
+
|
|
10
|
+
After **any substantive implementation pass** tracked by a delivery spec, the file MUST live in **`specs/completed/`** (fully done) or **`specs/partial/`** (work remains). Do not leave in-progress delivery specs in the parent `specs/` folder.
|
|
11
|
+
|
|
12
|
+
## When the spec is fully implemented
|
|
13
|
+
|
|
14
|
+
1. Set **Status** to `Implemented` (add a Status line if missing).
|
|
15
|
+
2. Append or fill **Implementation summary** at the end:
|
|
16
|
+
- `Implemented: YYYY-MM-DD`
|
|
17
|
+
- What shipped (by area or file), briefly
|
|
18
|
+
- Verification (tests, checks, manual walkthrough)
|
|
19
|
+
3. Mark the matching **TODO.md** item `[x]` and point at `specs/completed/<file>.md`.
|
|
20
|
+
4. **Move** the file to `specs/completed/` and update links across the repo.
|
|
21
|
+
|
|
22
|
+
## When work is partial or phased
|
|
23
|
+
|
|
24
|
+
1. Refresh **Status** (e.g. Partial; what remains).
|
|
25
|
+
2. **Move** to `specs/partial/` if not already there.
|
|
26
|
+
3. Update links to `specs/partial/<file>.md`.
|
|
27
|
+
4. Keep TODO.md accurate and pointing at the partial path.
|
|
28
|
+
|
|
29
|
+
Optional: short **Progress** notes with dates while partial. Full Implementation summary is required only for `completed/`.
|
|
30
|
+
|
|
31
|
+
## New specs
|
|
32
|
+
|
|
33
|
+
Brand-new **delivery** specs stay in **`specs/`** until implementation **starts**, then move to **`specs/partial/`**. Brand-new **canonical** specs go directly into **`specs/canonical/`**.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: New specs must match ForgeTrail conventions and get a TODO.md entry
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Specs and TODO Backlog
|
|
7
|
+
|
|
8
|
+
When **creating a new delivery spec** under `specs/`:
|
|
9
|
+
|
|
10
|
+
1. **Start from the template:** copy `docs/SPEC_FEATURE_TEMPLATE.md` from ForgeTrail (MCP: `getTemplate({ name: "SPEC_FEATURE_TEMPLATE" })`), or the Lite skeleton in `.forgetrail/FORGETRAIL_LITE.md` §3.1. Match neighboring specs' naming (`kebab-case.md`).
|
|
11
|
+
2. **Add or update `TODO.md`:** link the new spec with an open `[ ]` checklist item. Do not add a tracked delivery spec without a backlog entry.
|
|
12
|
+
3. **Header:** set `Spec kind: Delivery` (or `Canonical reference`), `Status`, and Related links.
|
|
13
|
+
|
|
14
|
+
## Where specs live
|
|
15
|
+
|
|
16
|
+
**Delivery specs** (bounded work with acceptance criteria):
|
|
17
|
+
|
|
18
|
+
- **`specs/`** — Drafts and **not-yet-started** proposals.
|
|
19
|
+
- **`specs/partial/`** — Implementation **started** or **phased**; not all acceptance criteria met.
|
|
20
|
+
- **`specs/completed/`** — Fully implemented (see each file's Implementation summary).
|
|
21
|
+
|
|
22
|
+
**Canonical specs** (living reference / methodology, no delivery lifecycle):
|
|
23
|
+
|
|
24
|
+
- **`specs/canonical/`** — Catalogs and references that **outlive one implementation pass**. Exempt from `partial/` → `completed/` moves. Header: `**Spec kind:** Canonical reference` and a `Status:` line for catalog state, not delivery state.
|
|
25
|
+
|
|
26
|
+
Moving delivery specs and updating links: **Spec Completion Protocol** (`spec-completion.mdc`).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Prefer hostMatchesDomain over hostname.includes for board/ATS URL checks
|
|
3
|
+
globs:
|
|
4
|
+
- src/**/*.{ts,js}
|
|
5
|
+
- outpost/**/*.{js,mjs}
|
|
6
|
+
- scripts/**/*.{js,mjs,ts}
|
|
7
|
+
alwaysApply: false
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# URL host matching
|
|
11
|
+
|
|
12
|
+
## Prefer shared helpers
|
|
13
|
+
|
|
14
|
+
- For board / ATS / “is this LinkedIn / Indeed / Greenhouse?” decisions, use **`hostMatchesDomain` / `urlMatchesHostDomain`** from `$lib/urlHost` (Outpost: `outpost/src/urlHost.js`, keep in sync).
|
|
15
|
+
- **Do not** write `hostname.includes('linkedin.com')` or `url.includes('indeed.com')` for host allow/deny. Substring checks match lookalikes (`evil-linkedin.com`, `linkedin.com.attacker.example`) and trip CodeQL `js/incomplete-url-substring-sanitization`.
|
|
16
|
+
|
|
17
|
+
## Parse first
|
|
18
|
+
|
|
19
|
+
- Prefer `new URL(...)` then match on **`hostname`**. Keep path checks on **`pathname`** (e.g. `/jobs/view`), not on the full href string when tightening.
|
|
20
|
+
|
|
21
|
+
## Outgoing fetch
|
|
22
|
+
|
|
23
|
+
- App: `assertSafeUrl` at the boundary; follow redirects with re-check (`safeFetch`).
|
|
24
|
+
- Outpost: `assertPublicHttpUrl` before slot acquisition / `goto`. Align blocked hosts (localhost, private IPs, `.local` / `.internal`, credentialed URLs).
|
|
25
|
+
|
|
26
|
+
## Schemes and sanitization
|
|
27
|
+
|
|
28
|
+
- Scheme checks: **allowlist** `http:` / `https:`; do not only deny `javascript:`.
|
|
29
|
+
- Tag stripping / entity decode: use shared helpers (`stripMarkupTags`, amp-last entity decode) or a parser. If a regex is extraction-only, say so in a one-line comment.
|
|
30
|
+
- GitHub Actions: always set top-level `permissions:` (least privilege).
|
|
31
|
+
- Never return `err.stack` or raw exception text to HTTP clients; log server-side and return stable error codes.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Default locale for product copy and LLM-facing prompts is US English
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# US English Default
|
|
7
|
+
|
|
8
|
+
- Use **US English** spelling and conventions for **user-visible product copy**, in-app help, marketing strings, and **prompts** that generate user-facing text (e.g. journal summaries, suggested copy), unless the user explicitly asks for another locale.
|
|
9
|
+
- When editing or generating long-form project docs meant for the same audience as the product, default to US English unless context calls for otherwise.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: User-visible UI copy must not reference internal specs or engineering jargon
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# User-Facing Content
|
|
7
|
+
|
|
8
|
+
## Internal vs external
|
|
9
|
+
|
|
10
|
+
- **Help panel**, onboarding, tours, toasts, empty states, labels, and any other **end-user-facing** text must **not** mention internal specs, spec file names, or paths under `specs/`. Specs are for builders only.
|
|
11
|
+
- It is fine to reference **in-app feature names** and menu paths users actually see.
|
|
12
|
+
|
|
13
|
+
## Tone and clarity
|
|
14
|
+
|
|
15
|
+
- Prefer **outcome-oriented** language (what the user gets or does), not implementation details.
|
|
16
|
+
- Avoid engineering jargon in UI strings (e.g. storage format, schema field names, provider names) unless the user explicitly needs it for troubleshooting—and then keep it in expert/help contexts only.
|
|
17
|
+
|
|
18
|
+
## Assertive capability copy
|
|
19
|
+
|
|
20
|
+
When describing what **the product does**, use direct verbs — not hedging **can**:
|
|
21
|
+
|
|
22
|
+
- **Do:** “Resume Forge **strengthens** your file.” / “The Prospect Engine **suggests** matching roles.” / “Foreman **proposes** a run plan.”
|
|
23
|
+
- **Don't:** “…**can strengthen**…” / “…**can suggest**…” when stating a shipped capability.
|
|
24
|
+
|
|
25
|
+
**Keep *can* when it means:**
|
|
26
|
+
|
|
27
|
+
- **User permission or option** — “You can change it later.”
|
|
28
|
+
- **Factual limit** — “what we can read from the listing automatically.”
|
|
29
|
+
- **People, not features** — “connections who can help you.”
|
|
30
|
+
|
|
31
|
+
Grep user-facing modules for `\bcan\b` after marketing or feature copy edits; fix capability hedges in the same pass.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# GENESIS.md — Mars Habitat Duty Roster (sample)
|
|
2
|
+
|
|
3
|
+
> **Fiction.** This is a made-up product for ForgeTrail demos. It is not a real NASA/SpaceX tool. The *problem shape* is deliberately ordinary: shared household / shift scheduling, with a Mars habitat skin so it is memorable.
|
|
4
|
+
|
|
5
|
+
**Status:** sample for shape reference
|
|
6
|
+
**Canonical path in a real project:** `docs/GENESIS.md`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem statement
|
|
11
|
+
|
|
12
|
+
Six people share a pressurized habitat on Mars. Every sol (Martian day) someone must cover **galley**, **greenhouse**, **life-support walkthrough**, and **EVA prep**. Today the crew tracks this in a shared spreadsheet plus a group chat. Swaps happen in chat and never make it back into the sheet. Newcomers cannot tell who is on deck for the next three sols without pinging the commander.
|
|
13
|
+
|
|
14
|
+
**Manual workaround today:** export the sheet to CSV, paste into a second tab labeled "truth," and argue in chat when the two disagree.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Goals and explicit non-goals (v1 scope)
|
|
19
|
+
|
|
20
|
+
**Goals**
|
|
21
|
+
|
|
22
|
+
- One shared duty roster per habitat, visible to all crew.
|
|
23
|
+
- Assign people to duty slots by sol; show the next 7 sols at a glance.
|
|
24
|
+
- Request and accept a swap without leaving the app.
|
|
25
|
+
- Keep a short audit trail of who changed what (name + sol + duty).
|
|
26
|
+
|
|
27
|
+
**Non-goals (v1)**
|
|
28
|
+
|
|
29
|
+
- No payroll, time clocks, or Earth-timezone conversion.
|
|
30
|
+
- No integration with real habitat telemetry or suit systems.
|
|
31
|
+
- No multi-habitat fleet management.
|
|
32
|
+
- No mobile native apps (responsive web is enough).
|
|
33
|
+
- No AI that auto-assigns duties (humans assign; the tool remembers).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Required background on the underlying data / file format
|
|
38
|
+
|
|
39
|
+
v1 is **local-first**. The builder may choose how to persist, but the domain export format is locked for handoff and backup:
|
|
40
|
+
|
|
41
|
+
- **Export/import:** a single UTF-8 JSON file, `roster-export.json`.
|
|
42
|
+
- **Top-level keys:** `habitatId` (string), `crew` (array), `duties` (array of duty type ids), `assignments` (array), `swapRequests` (array), `schemaVersion` (semver string, start at `"1.0.0"`).
|
|
43
|
+
- **Assignment row:** `{ "sol": number, "dutyId": string, "personId": string, "updatedAt": ISO-8601, "updatedBy": string }`.
|
|
44
|
+
- **Idempotent import:** importing a file replaces the local roster only after a confirmation step; never silent overwrite.
|
|
45
|
+
- **No binary formats** in v1. Spreadsheet import is out of scope; CSV export of the current 7-sol view is nice-to-have, not required.
|
|
46
|
+
|
|
47
|
+
Builders should document any chosen on-disk layout (e.g. one JSON file in the user data dir) in the Phase 1 brief; this Genesis only constrains the **portable** export shape.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Core domain concepts / model
|
|
52
|
+
|
|
53
|
+
- **Habitat** — one roster context (name + id).
|
|
54
|
+
- **Crew member** — person with display name and optional role tag (commander, specialist, …). Role tags are labels only in v1.
|
|
55
|
+
- **Duty type** — named slot kind (galley, greenhouse, life-support, eva-prep). Fixed list in v1; editable list is v2.
|
|
56
|
+
- **Sol** — integer day index for the mission; the UI shows "Sol N" not Earth dates.
|
|
57
|
+
- **Assignment** — one person on one duty type for one sol.
|
|
58
|
+
- **Swap request** — from person A to person B for a specific assignment; states: `pending`, `accepted`, `declined`, `cancelled`.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Functional requirements
|
|
63
|
+
|
|
64
|
+
### 1. Roster board
|
|
65
|
+
|
|
66
|
+
1.1 Show the next 7 sols as columns and duty types as rows (or equivalent clear grid).
|
|
67
|
+
1.2 Each cell shows the assigned person or "Unassigned."
|
|
68
|
+
1.3 Commander (or any crew in v1: no ACL yet) can set or clear an assignment in two clicks or fewer.
|
|
69
|
+
1.4 Changing an assignment appends an audit entry.
|
|
70
|
+
|
|
71
|
+
### 2. Crew and duty setup
|
|
72
|
+
|
|
73
|
+
2.1 Add / rename / deactivate crew (deactivated people cannot receive new assignments; historical cells stay).
|
|
74
|
+
2.2 Duty types ship as the four defaults above; renaming labels is allowed; adding/removing types is out of scope for v1.
|
|
75
|
+
|
|
76
|
+
### 3. Swaps
|
|
77
|
+
|
|
78
|
+
3.1 Assignee can request a swap with another crew member for one cell.
|
|
79
|
+
3.2 Target can accept or decline.
|
|
80
|
+
3.3 On accept, assignments swap and both sides see an audit entry.
|
|
81
|
+
3.4 Pending requests appear in a simple list; no email/push required for v1 (in-app only).
|
|
82
|
+
|
|
83
|
+
### 4. Export / import
|
|
84
|
+
|
|
85
|
+
4.1 Export current roster to `roster-export.json` matching the schema above.
|
|
86
|
+
4.2 Import with explicit confirm; reject unknown `schemaVersion` with a clear message.
|
|
87
|
+
4.3 Never modify the import file on disk.
|
|
88
|
+
|
|
89
|
+
### 5. Empty and first-run states
|
|
90
|
+
|
|
91
|
+
5.1 First launch offers "Create habitat" with name + add at least two crew before the board unlocks.
|
|
92
|
+
5.2 Unassigned cells are obvious; do not invent placeholder names.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Non-functional requirements
|
|
97
|
+
|
|
98
|
+
- **Local-only:** no accounts, no cloud sync in v1.
|
|
99
|
+
- **Privacy:** roster data stays on the device; no analytics.
|
|
100
|
+
- **Safety:** import never runs without confirmation; export is read-only relative to source data.
|
|
101
|
+
- **Performance:** board for 6 crew × 4 duties × 7 sols must feel instant on a mid-range laptop.
|
|
102
|
+
- **Platform:** desktop web or desktop-capable responsive UI; keyboard usable for assignment changes.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Edge cases
|
|
107
|
+
|
|
108
|
+
- Two people edit different cells "at once" on one shared machine (last write wins; audit shows both).
|
|
109
|
+
- Swap target is deactivated after request was sent → auto-cancel with message.
|
|
110
|
+
- Import file missing `assignments` array → reject with field path in the error.
|
|
111
|
+
- Sol numbers are not contiguous (crew skipped a sol in the sheet) → allow gaps; board still shows a 7-sol window from "today's sol" setting.
|
|
112
|
+
- Same person assigned to two duties on one sol → **allowed** in v1 (habitat life is busy); show both cells normally.
|
|
113
|
+
- Duplicate `personId` in import → reject.
|
|
114
|
+
- Very long display names → truncate in grid, full name in detail.
|
|
115
|
+
- User clears the only commander tag → allowed; tags are cosmetic.
|
|
116
|
+
- Browser refresh mid-swap → pending request must still be present after reload (persist before ack UI).
|
|
117
|
+
- Export while a pending swap exists → include `swapRequests` as-is.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Suggested milestones
|
|
122
|
+
|
|
123
|
+
- **M1.** Create habitat, crew, duty labels, 7-sol board, assign/clear, persist locally, audit log.
|
|
124
|
+
- **M2.** Swap request / accept / decline.
|
|
125
|
+
- **M3.** JSON export / import with confirmation.
|
|
126
|
+
- **M4.** Polish empty states + one-page README for crew.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Acceptance criteria
|
|
131
|
+
|
|
132
|
+
- Given a habitat with 6 crew and default duties, when I assign "Alex" to galley on Sol 104, then the board shows Alex in that cell after reload.
|
|
133
|
+
- Given Alex is on galley Sol 104, when Alex requests a swap with Sam and Sam accepts, then Sam is on galley Sol 104 and Alex is not.
|
|
134
|
+
- Given a valid `roster-export.json`, when I import it into an empty app and confirm, then the board matches the file.
|
|
135
|
+
- Given a file with `schemaVersion` `"99.0.0"`, when I import, then the app refuses and explains that the version is unsupported.
|
|
136
|
+
- Given I never confirm an import, when the file is selected, then on-disk app data is unchanged.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Open questions
|
|
141
|
+
|
|
142
|
+
| # | Question |
|
|
143
|
+
|---|----------|
|
|
144
|
+
| 1 | What is "today's sol" at first launch: user-entered mission sol, or start at 0? |
|
|
145
|
+
| 2 | Should v1 allow printing a one-page "next 7 sols" view? |
|
|
146
|
+
| 3 | Is a single shared browser profile enough, or do we need a simple device PIN? |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Constraints
|
|
151
|
+
|
|
152
|
+
- Local-only; no cloud accounts.
|
|
153
|
+
- Never overwrite local data on import without an explicit confirm step.
|
|
154
|
+
- No real spacecraft systems; this is a crew coordination toy with a Mars skin.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Local ForgeTrail workspace (`.forgetrail/`)
|
|
2
|
+
|
|
3
|
+
This folder holds **ForgeTrail agent artifacts** — tracking, platform rules, and (for Lite boots) the protocol file. ForgeTrail upstream is **Apache 2.0**; you may commit this folder or **gitignore** it for a slimmer public app repo. Never commit secrets here.
|
|
4
|
+
|
|
5
|
+
Used for **both** ForgeTrail Lite file bootstrap and **MCP greenfield** kickoff.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|------|---------|
|
|
11
|
+
| `workflow_tracking.json` | Phase / decisions / session tracking (all approaches) |
|
|
12
|
+
| `FORGETRAIL_LITE.md` | Full Lite kickoff protocol (Lite file bootstrap only) |
|
|
13
|
+
| `FORGETRAIL_LITE_UPDATES.md` | Optional local feedback log (§1.6) — merge accepted items upstream |
|
|
14
|
+
| `AGENTS.md` | Agent instructions — cite or symlink for your IDE |
|
|
15
|
+
| `CLAUDE.md` | Claude Code trailer override |
|
|
16
|
+
| `IDEAS.md` | Backlog parking lot |
|
|
17
|
+
| `cursor/rules/*.mdc` | Cursor rule snippets (no-trailer, updates-log, …) |
|
|
18
|
+
|
|
19
|
+
## Wire up Cursor (one-time)
|
|
20
|
+
|
|
21
|
+
From the **repo root**:
|
|
22
|
+
|
|
23
|
+
**Git Bash / WSL:**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
mkdir -p .cursor/rules
|
|
27
|
+
ln -sf ../../.forgetrail/cursor/rules/forgetrail-no-trailer.mdc .cursor/rules/
|
|
28
|
+
ln -sf ../../.forgetrail/cursor/rules/forgetrail-updates-log.mdc .cursor/rules/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Windows cmd (junction):**
|
|
32
|
+
|
|
33
|
+
```bat
|
|
34
|
+
mkdir .cursor\rules 2>nul
|
|
35
|
+
mklink .cursor\rules\forgetrail-no-trailer.mdc ..\.forgetrail\cursor\rules\forgetrail-no-trailer.mdc
|
|
36
|
+
mklink .cursor\rules\forgetrail-updates-log.mdc ..\.forgetrail\cursor\rules\forgetrail-updates-log.mdc
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Optional: symlink `.forgetrail/AGENTS.md` → `AGENTS.md` at repo root if a tool requires root placement (also gitignore root copies if you symlink).
|
|
40
|
+
|
|
41
|
+
## Recovering after clone
|
|
42
|
+
|
|
43
|
+
If `.forgetrail/` was gitignored, it is not in git. Copy from upstream Catalyst Forge (`content/`) or restore from backup; MCP-only projects can recreate tracking via **`getInitialWorkflowTracking`**.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reference dev launcher — copy to app repo scripts/forgetrail-dev-launcher.mjs
|
|
4
|
+
* Wire repo-root setup.bat / run.bat / status.bat (Windows) and setup.sh / run.sh / status.sh (Mac/Linux).
|
|
5
|
+
*
|
|
6
|
+
* Commands: setup | run | status
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { join, dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const repoRoot = join(__dirname, "..");
|
|
15
|
+
const trackingPath = join(repoRoot, ".forgetrail", "workflow_tracking.json");
|
|
16
|
+
const progressPath = join(repoRoot, "docs", "FORGETRAIL_PROGRESS.md");
|
|
17
|
+
const isWin = process.platform === "win32";
|
|
18
|
+
|
|
19
|
+
const PHASE_LABELS = {
|
|
20
|
+
// Display names — see TRACKING_SCHEMA.md "Phase ID vocabulary (Lite vs MCP)"
|
|
21
|
+
"1": "Plan",
|
|
22
|
+
"2": "Build",
|
|
23
|
+
"3": "Stabilize",
|
|
24
|
+
"4": "Iterate",
|
|
25
|
+
"5": "Refine",
|
|
26
|
+
"6": "Align",
|
|
27
|
+
"7": "Harden",
|
|
28
|
+
"1-architecture": "Plan",
|
|
29
|
+
"2-scaffolding": "Build",
|
|
30
|
+
"3-stabilization": "Stabilize",
|
|
31
|
+
"4-feature-iteration": "Iterate",
|
|
32
|
+
"5-refactoring": "Refine",
|
|
33
|
+
"6-strategic-review": "Align",
|
|
34
|
+
"7-hardening": "Harden",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Lite schema: exitCriteria { key: boolean }. MCP schema: exitCriteriaMet / exitCriteriaRemaining arrays. */
|
|
38
|
+
function getExitCriteriaStatus(phaseBlock) {
|
|
39
|
+
if (!phaseBlock) return { met: [], remaining: [] };
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(phaseBlock.exitCriteriaMet) || Array.isArray(phaseBlock.exitCriteriaRemaining)) {
|
|
42
|
+
return {
|
|
43
|
+
met: Array.isArray(phaseBlock.exitCriteriaMet) ? phaseBlock.exitCriteriaMet : [],
|
|
44
|
+
remaining: Array.isArray(phaseBlock.exitCriteriaRemaining) ? phaseBlock.exitCriteriaRemaining : [],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const criteria = phaseBlock.exitCriteria;
|
|
49
|
+
if (criteria && typeof criteria === "object" && !Array.isArray(criteria)) {
|
|
50
|
+
const met = [];
|
|
51
|
+
const remaining = [];
|
|
52
|
+
for (const [key, value] of Object.entries(criteria)) {
|
|
53
|
+
if (value) met.push(key);
|
|
54
|
+
else remaining.push(key);
|
|
55
|
+
}
|
|
56
|
+
return { met, remaining };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { met: [], remaining: [] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function run(cmd, args, opts = {}) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const p = spawn(cmd, args, { stdio: "inherit", cwd: repoRoot, shell: isWin, ...opts });
|
|
65
|
+
p.on("error", reject);
|
|
66
|
+
p.on("exit", (c) => (c === 0 ? resolve() : reject(new Error(`${cmd} exited ${c}`))));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hasScript(name) {
|
|
71
|
+
try {
|
|
72
|
+
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
|
|
73
|
+
return Boolean(pkg.scripts?.[name]);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function cmdSetup() {
|
|
80
|
+
console.log("=== ForgeTrail setup (first time or refresh) ===\n");
|
|
81
|
+
if (!existsSync(join(repoRoot, "node_modules"))) {
|
|
82
|
+
console.log("Installing dependencies…");
|
|
83
|
+
await run("pnpm", ["install"]);
|
|
84
|
+
}
|
|
85
|
+
if (hasScript("setup:pocketbase")) {
|
|
86
|
+
await run("pnpm", ["run", "setup:pocketbase"]);
|
|
87
|
+
} else if (existsSync(join(repoRoot, "scripts", "setup-pocketbase.mjs"))) {
|
|
88
|
+
await run("node", ["scripts/setup-pocketbase.mjs"]);
|
|
89
|
+
}
|
|
90
|
+
if (hasScript("pocketbase:schema")) {
|
|
91
|
+
console.log("\nApplying PocketBase schema (needs PocketBase running + .env admin creds)…");
|
|
92
|
+
console.log("(If this fails, run setup again after starting PocketBase once.)\n");
|
|
93
|
+
try {
|
|
94
|
+
await run("pnpm", ["run", "pocketbase:schema"]);
|
|
95
|
+
} catch {
|
|
96
|
+
console.log("Schema step skipped or failed — you can run: pnpm run pocketbase:schema");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (hasScript("env:check")) await run("pnpm", ["run", "env:check"]);
|
|
100
|
+
console.log("\nSetup pass complete. Next: double-click run.bat (or pnpm run dev).");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function pbHealth() {
|
|
104
|
+
const env = {};
|
|
105
|
+
const envPath = join(repoRoot, ".env");
|
|
106
|
+
if (existsSync(envPath)) {
|
|
107
|
+
for (const line of readFileSync(envPath, "utf8").split(/\r?\n/)) {
|
|
108
|
+
const m = line.match(/^\s*PUBLIC_POCKETBASE_URL\s*=\s*(.+)/);
|
|
109
|
+
if (m) {
|
|
110
|
+
try {
|
|
111
|
+
const u = new URL(m[1].trim());
|
|
112
|
+
const res = await fetch(`${u.origin}/api/health`, { signal: AbortSignal.timeout(2000) });
|
|
113
|
+
return res.ok;
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function cmdRun() {
|
|
124
|
+
console.log("=== Starting dev environment ===\n");
|
|
125
|
+
if (!(await pbHealth()) && hasScript("pocketbase")) {
|
|
126
|
+
console.log("Starting PocketBase in the background…");
|
|
127
|
+
const child = spawn("pnpm", ["run", "pocketbase"], {
|
|
128
|
+
cwd: repoRoot,
|
|
129
|
+
detached: true,
|
|
130
|
+
stdio: "ignore",
|
|
131
|
+
shell: isWin,
|
|
132
|
+
});
|
|
133
|
+
child.unref();
|
|
134
|
+
await new Promise((r) => setTimeout(r, 2500));
|
|
135
|
+
}
|
|
136
|
+
if (!hasScript("dev")) {
|
|
137
|
+
console.error("No pnpm run dev script — check README.md");
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
console.log("Starting app (Ctrl+C to stop)…\n");
|
|
141
|
+
await run("pnpm", ["run", "dev"]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function renderProgress() {
|
|
145
|
+
if (!existsSync(trackingPath)) {
|
|
146
|
+
return { text: "No .forgetrail/workflow_tracking.json yet — agent bootstrap not finished.", md: null };
|
|
147
|
+
}
|
|
148
|
+
const t = JSON.parse(readFileSync(trackingPath, "utf8"));
|
|
149
|
+
const phase = t.currentPhase ?? "?";
|
|
150
|
+
const phaseId = String(phase);
|
|
151
|
+
const phaseBlock = t.phases?.[phaseId];
|
|
152
|
+
const label = PHASE_LABELS[phaseId] || phaseBlock?.name || `Phase ${phase}`;
|
|
153
|
+
const status = phaseBlock?.status || "unknown";
|
|
154
|
+
const { met, remaining } = getExitCriteriaStatus(phaseBlock);
|
|
155
|
+
const lines = [
|
|
156
|
+
`ForgeTrail phase: ${label} (${phaseId}) — ${status}`,
|
|
157
|
+
"",
|
|
158
|
+
met.length ? "Done (this phase):" : "",
|
|
159
|
+
...met.map((k) => ` ✓ ${k}`),
|
|
160
|
+
met.length ? "" : "",
|
|
161
|
+
remaining.length ? "Still open:" : "Exit criteria for this phase look complete (confirm before advancing):",
|
|
162
|
+
...remaining.map((k) => ` - ${k}`),
|
|
163
|
+
];
|
|
164
|
+
if (t.sessions?.length) {
|
|
165
|
+
const last = t.sessions[t.sessions.length - 1];
|
|
166
|
+
lines.push("", `Last session: ${last.date} — ${last.summary || "(no summary)"}`);
|
|
167
|
+
}
|
|
168
|
+
const md = [
|
|
169
|
+
"# ForgeTrail progress",
|
|
170
|
+
"",
|
|
171
|
+
"_Human-readable snapshot. Source of truth: `.forgetrail/workflow_tracking.json`._",
|
|
172
|
+
"",
|
|
173
|
+
`**Current phase:** ${label} (\`${phaseId}\`) — **${status}**`,
|
|
174
|
+
"",
|
|
175
|
+
"## What's done (this phase)",
|
|
176
|
+
"",
|
|
177
|
+
...(met.length ? met.map((k) => `- [x] ${k}`) : ["- _(none marked met yet)_"]),
|
|
178
|
+
"",
|
|
179
|
+
"## Still to do (this phase)",
|
|
180
|
+
"",
|
|
181
|
+
...(remaining.length ? remaining.map((k) => `- [ ] ${k}`) : ["- _(none listed — verify before advancing)_"]),
|
|
182
|
+
"",
|
|
183
|
+
"## Refresh",
|
|
184
|
+
"",
|
|
185
|
+
"Double-click **status.bat** (Windows) or run **./status.sh** / **pnpm run forgetrail:status** anytime.",
|
|
186
|
+
"",
|
|
187
|
+
].join("\n");
|
|
188
|
+
return { text: lines.join("\n"), md };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function cmdStatus() {
|
|
192
|
+
const { text, md } = renderProgress();
|
|
193
|
+
console.log(text);
|
|
194
|
+
if (md) {
|
|
195
|
+
mkdirSync(join(repoRoot, "docs"), { recursive: true });
|
|
196
|
+
writeFileSync(progressPath, md, "utf8");
|
|
197
|
+
console.log(`\nWrote ${progressPath}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cmd = process.argv[2];
|
|
202
|
+
if (cmd === "setup") cmdSetup().catch((e) => { console.error(e.message || e); process.exit(1); });
|
|
203
|
+
else if (cmd === "run") cmdRun().catch((e) => { console.error(e.message || e); process.exit(1); });
|
|
204
|
+
else if (cmd === "status") cmdStatus();
|
|
205
|
+
else {
|
|
206
|
+
console.log("Usage: node scripts/forgetrail-dev-launcher.mjs setup|run|status");
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shared env + repo root helpers for ForgeTrail reference scripts.
|
|
4
|
+
* Copy to app repo scripts/forgetrail-env.mjs alongside other forgetrail scripts.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
export function repoRootFromImportMeta(importMetaUrl) {
|
|
11
|
+
return join(dirname(fileURLToPath(importMetaUrl)), "..");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function loadEnv(repoRoot) {
|
|
15
|
+
const envPath = join(repoRoot, ".env");
|
|
16
|
+
if (!existsSync(envPath)) return {};
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const line of readFileSync(envPath, "utf8").split(/\r?\n/)) {
|
|
19
|
+
const t = line.trim();
|
|
20
|
+
if (!t || t.startsWith("#")) continue;
|
|
21
|
+
const i = t.indexOf("=");
|
|
22
|
+
if (i < 1) continue;
|
|
23
|
+
out[t.slice(0, i).trim()] = t.slice(i + 1).trim().replace(/^["']|["']$/g, "");
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function pocketBaseUrlFromEnv(env) {
|
|
29
|
+
const raw =
|
|
30
|
+
env.PUBLIC_POCKETBASE_URL ||
|
|
31
|
+
env.POCKETBASE_URL ||
|
|
32
|
+
(env.POCKETBASE_HTTP_HOST && env.POCKETBASE_HTTP_PORT
|
|
33
|
+
? `http://${env.POCKETBASE_HTTP_HOST}:${env.POCKETBASE_HTTP_PORT}`
|
|
34
|
+
: "");
|
|
35
|
+
if (!raw) return null;
|
|
36
|
+
try {
|
|
37
|
+
const u = new URL(raw.startsWith("http") ? raw : `http://${raw}`);
|
|
38
|
+
return u;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ollamaBaseUrlFromEnv(env) {
|
|
45
|
+
const raw = env.OLLAMA_BASE_URL || env.OLLAMA_HOST || "http://127.0.0.1:11434";
|
|
46
|
+
try {
|
|
47
|
+
return new URL(raw.startsWith("http") ? raw : `http://${raw}`);
|
|
48
|
+
} catch {
|
|
49
|
+
return new URL("http://127.0.0.1:11434");
|
|
50
|
+
}
|
|
51
|
+
}
|