forgetrail 0.4.14 → 0.4.15

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 (43) hide show
  1. package/INITIAL_PROMPT.md +1 -1
  2. package/README.md +1 -1
  3. package/TRY_FORGETRAIL.md +44 -5
  4. package/WORKFLOW.md +15 -2
  5. package/content/AGENT_INTEGRATION_claude.md +3 -2
  6. package/content/AGENT_INTEGRATION_cursor.md +2 -1
  7. package/content/COMPANION_TOOLS.md +1 -0
  8. package/content/FORGETRAIL_LITE.md +41 -75
  9. package/content/GREENFIELD_INTAKE.md +9 -2
  10. package/content/LITE_WORKFLOW_TRACKING.json +2 -1
  11. package/content/NEW_PROJECT_BOOTSTRAP.md +2 -3
  12. package/content/companion-tools.json +18 -0
  13. package/content/cursor-rules/npm-name-hold.mdc +1 -1
  14. package/content/cursor-rules/spec-completion.mdc +4 -1
  15. package/content/cursor-rules/specs-and-todo.mdc +5 -1
  16. package/content/cursor-rules/url-host-matching.mdc +12 -13
  17. package/content/cursor-rules/user-facing-content.mdc +2 -2
  18. package/content/cursor-rules/writing-voice.mdc +6 -0
  19. package/content/forgetrail-workspace-README.md +2 -4
  20. package/content/hooks/README.md +36 -0
  21. package/content/hooks/claude-settings-hooks.json +20 -0
  22. package/content/hooks/cursor-hooks.json +30 -0
  23. package/content/hooks/guard-edit.mjs +85 -0
  24. package/content/hooks/guard-shell.mjs +168 -0
  25. package/content/hooks/session-start.mjs +93 -0
  26. package/content/hooks/session-stop.mjs +56 -0
  27. package/content/hooks/validate-tracking-core.mjs +229 -0
  28. package/content/hooks/validate-tracking.mjs +65 -0
  29. package/docs/SPEC_FEATURE_TEMPLATE.md +12 -4
  30. package/docs/TECHNICAL_REFERENCE.md +1 -1
  31. package/package.json +2 -1
  32. package/prompts/microcopy-centralization.md +1 -1
  33. package/prompts/pre-launch-audit.md +8 -0
  34. package/prompts/propagate-to-forgetrail.md +1 -1
  35. package/prompts/rules-audit.md +74 -0
  36. package/prompts/second-family-critique.md +43 -0
  37. package/prompts/spec-review.md +65 -0
  38. package/scripts/forgetrail-cli.mjs +6 -0
  39. package/scripts/install.mjs +43 -1
  40. package/scripts/workspace-index.mjs +128 -0
  41. package/workflow_tracking.json +1 -0
  42. package/content/cursor-rules/forgetrail-no-trailer.mdc +0 -49
  43. package/content/cursor-rules/us-english.mdc +0 -9
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Structural validation for .forgetrail/workflow_tracking.json (MCP full + Lite shapes).
3
+ * Standalone zero-dependency module for ForgeTrail hooks and MCP.
4
+ */
5
+
6
+ const VALID_MCP_PHASES = [
7
+ "1-architecture",
8
+ "2-scaffolding",
9
+ "3-stabilization",
10
+ "4-feature-iteration",
11
+ "5-refactoring",
12
+ "6-strategic-review",
13
+ "7-hardening",
14
+ ];
15
+
16
+ const VALID_LITE_PHASES = ["1", "2", "3", "4", "5", "6", "7"];
17
+
18
+ const VALID_GOTCHA_CATEGORIES = new Set([
19
+ "environment",
20
+ "integration",
21
+ "data-model",
22
+ "ui",
23
+ "performance",
24
+ "ai-output",
25
+ "security",
26
+ "tooling",
27
+ "other",
28
+ ]);
29
+
30
+ const VALID_DECISION_STATUS = new Set(["active", "superseded", "revisit"]);
31
+
32
+ const VALID_PHASE_STATUS = new Set([
33
+ "not_started",
34
+ "pending",
35
+ "in_progress",
36
+ "completed",
37
+ "revisiting",
38
+ ]);
39
+
40
+ function isLiteSchema(data) {
41
+ return data && data.schemaVersion === "lite-1";
42
+ }
43
+
44
+ export function validateTrackingData(data) {
45
+ const issues = [];
46
+ const warnings = [];
47
+
48
+ if (!data || typeof data !== "object") {
49
+ issues.push("Tracking data is empty or not an object.");
50
+ return { issues, warnings };
51
+ }
52
+
53
+ const project = data.project;
54
+ if (!project || typeof project !== "object") {
55
+ issues.push("Missing top-level project object.");
56
+ } else if (!project.name || typeof project.name !== "string" || !project.name.trim()) {
57
+ issues.push("project.name is missing or empty.");
58
+ }
59
+
60
+ if (!data.currentPhase) {
61
+ issues.push("Missing currentPhase.");
62
+ }
63
+
64
+ const lite = isLiteSchema(data);
65
+ const currentPhase = data.currentPhase;
66
+
67
+ if (currentPhase) {
68
+ if (lite) {
69
+ const phaseStr = String(currentPhase);
70
+ if (!VALID_LITE_PHASES.includes(phaseStr)) {
71
+ issues.push(
72
+ `Lite schema currentPhase "${phaseStr}" is not a valid phase number (1–7).`
73
+ );
74
+ }
75
+ } else if (
76
+ typeof currentPhase === "string" &&
77
+ !VALID_MCP_PHASES.includes(currentPhase)
78
+ ) {
79
+ issues.push(
80
+ `currentPhase "${currentPhase}" is not a recognized MCP phase id (e.g. "1-architecture").`
81
+ );
82
+ }
83
+ }
84
+
85
+ const phases = data.phases;
86
+ if (!phases || typeof phases !== "object") {
87
+ issues.push("Missing phases object.");
88
+ return { issues, warnings };
89
+ }
90
+
91
+ const phaseKeys = Object.keys(phases);
92
+ if (phaseKeys.length === 0) {
93
+ issues.push("No phases defined in tracking.");
94
+ }
95
+
96
+ if (!lite && currentPhase && typeof currentPhase === "string" && !phaseKeys.includes(currentPhase)) {
97
+ issues.push(`currentPhase "${currentPhase}" is not a key in phases.`);
98
+ }
99
+
100
+ for (const [key, rawPhase] of Object.entries(phases)) {
101
+ if (!rawPhase || typeof rawPhase !== "object") {
102
+ issues.push(`phases["${key}"] is not an object.`);
103
+ continue;
104
+ }
105
+ const phase = rawPhase;
106
+
107
+ if (phase.status && !VALID_PHASE_STATUS.has(String(phase.status))) {
108
+ warnings.push(
109
+ `phases["${key}"].status "${phase.status}" is not a standard value (not_started | in_progress | completed | revisiting).`
110
+ );
111
+ }
112
+
113
+ if (lite) {
114
+ if (phase.exitCriteria !== undefined && (typeof phase.exitCriteria !== "object" || phase.exitCriteria === null)) {
115
+ issues.push(`phases["${key}"].exitCriteria should be an object of boolean flags (Lite schema).`);
116
+ }
117
+ } else {
118
+ if (phase.exitCriteriaMet !== undefined && !Array.isArray(phase.exitCriteriaMet)) {
119
+ issues.push(`phases["${key}"].exitCriteriaMet should be an array.`);
120
+ }
121
+ if (phase.exitCriteriaRemaining !== undefined && !Array.isArray(phase.exitCriteriaRemaining)) {
122
+ issues.push(`phases["${key}"].exitCriteriaRemaining should be an array.`);
123
+ }
124
+ const met = phase.exitCriteriaMet ?? [];
125
+ const remaining = phase.exitCriteriaRemaining ?? [];
126
+ if (Array.isArray(met) && Array.isArray(remaining)) {
127
+ const overlap = met.filter((item) => remaining.includes(item));
128
+ if (overlap.length > 0) {
129
+ warnings.push(
130
+ `phases["${key}"] has exit criteria in both Met and Remaining: ${overlap.slice(0, 3).join("; ")}${overlap.length > 3 ? "…" : ""}`
131
+ );
132
+ }
133
+ }
134
+ }
135
+
136
+ const subagentRuns = phase.subagentRuns;
137
+ if (subagentRuns !== undefined && !Array.isArray(subagentRuns)) {
138
+ issues.push(`phases["${key}"].subagentRuns should be an array when present.`);
139
+ }
140
+ }
141
+
142
+ const decisions = data.decisions;
143
+ if (decisions !== undefined) {
144
+ if (!Array.isArray(decisions)) {
145
+ issues.push("decisions should be an array.");
146
+ } else {
147
+ const seenIds = new Set();
148
+ for (let i = 0; i < decisions.length; i++) {
149
+ const d = decisions[i];
150
+ if (!d || typeof d !== "object") {
151
+ issues.push(`decisions[${i}] is not an object.`);
152
+ continue;
153
+ }
154
+ if (!d.decision || typeof d.decision !== "string") {
155
+ issues.push(`decisions[${i}] missing decision text.`);
156
+ }
157
+ if (!d.rationale || typeof d.rationale !== "string") {
158
+ warnings.push(`decisions[${i}] missing rationale (recommended for Phase 1 handoff).`);
159
+ }
160
+ if (d.id && typeof d.id === "string") {
161
+ if (seenIds.has(d.id)) {
162
+ issues.push(`Duplicate decisions id "${d.id}".`);
163
+ }
164
+ seenIds.add(d.id);
165
+ }
166
+ if (d.status && !VALID_DECISION_STATUS.has(String(d.status))) {
167
+ warnings.push(`decisions[${i}].status "${d.status}" is not standard (active | superseded | revisit).`);
168
+ }
169
+ if (
170
+ d.alternatives_considered !== undefined &&
171
+ !Array.isArray(d.alternatives_considered)
172
+ ) {
173
+ warnings.push(`decisions[${i}].alternatives_considered should be an array when present.`);
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ const gotchas = data.gotchas;
180
+ if (gotchas !== undefined) {
181
+ if (!Array.isArray(gotchas)) {
182
+ issues.push("gotchas should be an array.");
183
+ } else {
184
+ for (let i = 0; i < gotchas.length; i++) {
185
+ const g = gotchas[i];
186
+ if (!g || typeof g !== "object") {
187
+ issues.push(`gotchas[${i}] is not an object.`);
188
+ continue;
189
+ }
190
+ if (!g.issue || typeof g.issue !== "string") {
191
+ warnings.push(`gotchas[${i}] missing issue description.`);
192
+ }
193
+ if (!g.resolution || typeof g.resolution !== "string") {
194
+ warnings.push(`gotchas[${i}] missing resolution.`);
195
+ }
196
+ if (g.category && !VALID_GOTCHA_CATEGORIES.has(String(g.category))) {
197
+ warnings.push(
198
+ `gotchas[${i}].category "${g.category}" is not a standard category (environment, integration, …).`
199
+ );
200
+ }
201
+ }
202
+ }
203
+ }
204
+
205
+ const sessions = data.sessions;
206
+ if (sessions !== undefined && !Array.isArray(sessions)) {
207
+ issues.push("sessions should be an array when present.");
208
+ }
209
+
210
+ return { issues, warnings };
211
+ }
212
+
213
+ export function formatValidationResult(result) {
214
+ const { issues, warnings } = result;
215
+
216
+ if (issues.length === 0 && warnings.length === 0) {
217
+ return "Tracking file looks structurally healthy.";
218
+ }
219
+
220
+ const parts = [];
221
+ if (issues.length > 0) {
222
+ parts.push("Issues (fix these):\n" + issues.map((i) => `- ${i}`).join("\n"));
223
+ }
224
+ if (warnings.length > 0) {
225
+ parts.push("Warnings:\n" + warnings.map((w) => `- ${w}`).join("\n"));
226
+ }
227
+
228
+ return parts.join("\n\n");
229
+ }
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ForgeTrail Tracking Validator Hook
5
+ * Hook event: afterFileEdit (Cursor) / PostToolUse (Claude Code)
6
+ * Automatically validates .forgetrail/workflow_tracking.json upon edit.
7
+ */
8
+
9
+ import { readFileSync, existsSync } from "node:fs";
10
+ import { validateTrackingData, formatValidationResult } from "./validate-tracking-core.mjs";
11
+
12
+ function readStdin() {
13
+ try {
14
+ const raw = readFileSync(0, "utf-8");
15
+ if (raw.trim()) return JSON.parse(raw);
16
+ } catch {}
17
+ return {};
18
+ }
19
+
20
+ function main() {
21
+ const payload = readStdin();
22
+ const filePath =
23
+ payload.path ||
24
+ payload.file_path ||
25
+ payload.target_file ||
26
+ payload.input?.path ||
27
+ payload.tool_input?.path ||
28
+ "";
29
+
30
+ if (!filePath.endsWith("workflow_tracking.json")) {
31
+ console.log(JSON.stringify({}));
32
+ return;
33
+ }
34
+
35
+ if (!existsSync(filePath)) {
36
+ console.log(JSON.stringify({}));
37
+ return;
38
+ }
39
+
40
+ try {
41
+ const content = JSON.parse(readFileSync(filePath, "utf-8"));
42
+ const result = validateTrackingData(content);
43
+
44
+ if (result.issues.length > 0 || result.warnings.length > 0) {
45
+ const formatted = formatValidationResult(result);
46
+ console.log(
47
+ JSON.stringify({
48
+ additional_context: `=== ForgeTrail Tracking Validation Notice ===\n${formatted}\n=============================================`,
49
+ })
50
+ );
51
+ return;
52
+ }
53
+ } catch (err) {
54
+ console.log(
55
+ JSON.stringify({
56
+ additional_context: `ForgeTrail Tracking Syntax Error: Failed to parse ${filePath} as valid JSON: ${err.message}`,
57
+ })
58
+ );
59
+ return;
60
+ }
61
+
62
+ console.log(JSON.stringify({}));
63
+ }
64
+
65
+ main();
@@ -105,12 +105,20 @@ _Surfaces, empty/loading/error states, copy notes, a11y._
105
105
 
106
106
  ---
107
107
 
108
- ## 8. Acceptance criteria
108
+ ## 8. Verifier and Acceptance Criteria
109
109
 
110
- _Testable. Prefer “Given / when / then” or checkbox style. The feature is not done until these pass._
110
+ ### 8.1 Verifier command
111
+ _Exact automated command(s) that verify this feature (e.g. `pnpm run test:feature`, `pnpm run verify`, or dedicated test script). A delivery spec without a runnable verifier command fails spec review._
111
112
 
112
- 1. Given …, when …, then …
113
- 2. Given …, when …, then …
113
+ ```bash
114
+ pnpm test
115
+ ```
116
+
117
+ ### 8.2 Acceptance criteria (boolean-checkable)
118
+ _Testable binary pass/fail checks. Avoid vague qualifiers ("performant", "clean"). The feature is not done until each criterion evaluates to true._
119
+
120
+ - [ ] Given ..., when ..., then ...
121
+ - [ ] Given ..., when ..., then ...
114
122
 
115
123
  ---
116
124
 
@@ -97,7 +97,7 @@ Document **what actually happens** when users remove data — not only the happy
97
97
 
98
98
  **Pre-release target:** inline audit at **0 UI prose not in export**; sync audit passes for shipped features.
99
99
 
100
- **Cursor rules:** `.cursor/rules/user-facing-content.mdc` (no spec paths in UI), `.cursor/rules/us-english.mdc` (US English default).
100
+ **Cursor rules:** `.cursor/rules/writing-voice.mdc` (voice, theme, and locale defaults), `.cursor/rules/user-facing-content.mdc` (no spec paths in UI).
101
101
 
102
102
  ## Feature Documentation
103
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgetrail",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
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",
@@ -23,6 +23,7 @@
23
23
  "LICENSE"
24
24
  ],
25
25
  "scripts": {
26
+ "workspace:index": "node scripts/workspace-index.mjs",
26
27
  "install:forgetrail": "node scripts/install-forgetrail.mjs",
27
28
  "install:lite": "node scripts/install-forgetrail-lite.mjs",
28
29
  "link:global": "node scripts/link-global.mjs",
@@ -107,7 +107,7 @@ Split **prose** (paragraphs, intros, help bodies) from **terms** (labels, toolti
107
107
 
108
108
  **User-facing content rule:** Help, onboarding, tours, toasts, and empty states must **not** mention internal spec paths or engineering jargon (see `.cursor/rules/user-facing-content.mdc`).
109
109
 
110
- **Locale:** Default **US English** for product copy and LLM prompts that generate user-visible text (see `.cursor/rules/us-english.mdc`).
110
+ **Locale:** Default **US English** for product copy and LLM prompts that generate user-visible text, adapting to target audience or locale (see `.cursor/rules/writing-voice.mdc`).
111
111
 
112
112
  ---
113
113
 
@@ -136,6 +136,14 @@ and one-line fix suggestions. For each finding, include the checklist item numbe
136
136
  - [ ] Verify any template markers or placeholders are fully resolved in generated content.
137
137
  - [ ] Check that generated content sections don't repeat each other unnecessarily.
138
138
 
139
+ ### 12. Post-Deploy Smoke Verification
140
+
141
+ - [ ] Fetch the live production URL (`curl -sI https://...`) and assert it returns HTTP 200 (or expected canonical redirect).
142
+ - [ ] Confirm response HTML contains expected head tags and root elements (not an empty white screen or unhandled server crash).
143
+ - [ ] Verify that static assets (CSS, JS bundles, favicon, logos) return HTTP 200 without CORS or MIME type errors.
144
+ - [ ] Verify that public health check routes (e.g. `/api/health`) return HTTP 200 OK.
145
+ - [ ] Verify live SSL certificate validity and TLS configuration.
146
+
139
147
  ---
140
148
 
141
149
  ## Severity Guide
@@ -96,7 +96,7 @@ Run after meaningful implementation work **and** after substantive **documentati
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). |
98
98
  | **`.cursor/rules/` git / commit policy** | e.g. `git-user-commits.mdc`, `commit-messages.mdc` — keep **this repo’s** `.cursor/rules/` aligned with your app when you propagate workflow changes: **end of each request**, stage only paths from that request and **commit** with message from **`git diff --staged`** (multi-line body by default); push remains explicit-only. |
99
- | **Microcopy centralization completed or copy-module pattern adopted** | **`prompts/microcopy-centralization.md`**, **`docs/TECHNICAL_REFERENCE.md`** § User-facing copy, **`docs/CODE_QUALITY.md`** audit checklist, **`docs/BRAND_AND_PRODUCT.md`** duplication policy, **`docs/CONTEXT_PROMPT.md`**, **`.cursor/rules/user-facing-content.mdc`**, **`.cursor/rules/us-english.mdc`**, **`content/cursor-rules/`** mirrors — generalize patterns only, not app-specific strings. |
99
+ | **Microcopy centralization completed or copy-module pattern adopted** | **`prompts/microcopy-centralization.md`**, **`docs/TECHNICAL_REFERENCE.md`** § User-facing copy, **`docs/CODE_QUALITY.md`** audit checklist, **`docs/BRAND_AND_PRODUCT.md`** duplication policy, **`docs/CONTEXT_PROMPT.md`**, **`content/cursor-rules/writing-voice.mdc`**, **`content/cursor-rules/user-facing-content.mdc`** — generalize patterns only, not app-specific strings. |
100
100
  | **Assertive product voice / hedging *can* removed from capability copy** | **`.cursor/rules/user-facing-content.mdc`** (Assertive capability copy), **`docs/BRAND_AND_PRODUCT.md`** (We say / We don't say table), **`docs/CONTEXT_PROMPT.md`**, **`docs/CODE_QUALITY.md`** pre-launch grep, **`docs/TEST_PLAN.md`** §4.5; MCP **`cursor-rules/`** mirror. |
101
101
  | **Brand theme / positioning spine crystallized** (voice rule file, banned framings, signature-line system, origin-narrative constraints) | **`docs/BRAND_AND_PRODUCT.md`** → *Positioning spine (theme lock)* + *Long-form origin narrative (constraints file)*; **`docs/CONTEXT_PROMPT.md`** pattern + anti-pattern; **`content/cursor-rules/writing-voice.mdc`** template. |
102
102
  | **Structured eligibility or requirement fit** (education, clearance, license, work auth — taxonomy + extraction + badges + filters + LLM context) | **`docs/TECHNICAL_REFERENCE.md`** → *[Structured eligibility / requirement fit]* stub; **`docs/TEST_PLAN.md`** §4.6; **`docs/BRAND_AND_PRODUCT.md`** one-line declare-once lesson; do **not** copy domain-specific enums/regex. |
@@ -0,0 +1,74 @@
1
+ # Rules and Environment Audit
2
+
3
+ Analyze the persistent rules, instructions, and hooks loaded into the agent's environment (`.cursor/rules/`, `CLAUDE.md`, `AGENTS.md`, or system prompts).
4
+
5
+ The goal is to calculate the per-turn token tax, identify overlaps and obsolete instructions, and convert always-on text into deterministic hooks, scoped file globs, or on-demand skills.
6
+
7
+ ---
8
+
9
+ ## How to Use
10
+
11
+ Provide this prompt to an AI agent with access to `.cursor/rules/`, `CLAUDE.md`, or `AGENTS.md`:
12
+
13
+ ```
14
+ Perform a Rules and Environment Audit on this repository.
15
+ Inspect all files in .cursor/rules/ (or CLAUDE.md / AGENTS.md).
16
+ Analyze per-turn token consumption, alwaysApply status, overlaps, and hook migration opportunities.
17
+ Deliver a ranked trimming and migration proposal.
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Audit Checklist
23
+
24
+ ### 1. Inventory and Token Weight
25
+
26
+ - [ ] Measure estimated token count per rule file (bytes ÷ 4 or tokenizer).
27
+ - [ ] Classify each rule as:
28
+ - **Always-on** (`alwaysApply: true` or unscoped).
29
+ - **Scoped** (`globs: [...]`).
30
+ - **Manual / on-demand** (`alwaysApply: false` without globs).
31
+ - [ ] Calculate the total per-turn baseline tax (sum of all always-on rules).
32
+
33
+ ### 2. Overlap and Redundancy
34
+
35
+ - [ ] Do multiple rules restate the same constraint (e.g. git commit conventions, package manager rules, voice guidelines)?
36
+ - [ ] Do rules duplicate instructions already provided by the host environment or CLI tooling?
37
+ - [ ] Can companion rules (e.g. Smell Check and writing-voice) be consolidated into a single coherent guide?
38
+
39
+ ### 3. Hook Migration Candidates
40
+
41
+ Can any prose rule be replaced by a deterministic hook that costs zero tokens on normal turns?
42
+
43
+ - [ ] **Package manager locking:** Rules telling agents to use pnpm can be enforced by a shell guard hook when `pnpm-lock.yaml` is present.
44
+ - [ ] **Git push restrictions:** Rules telling agents not to push can be enforced by `permission: "ask"` in a shell hook.
45
+ - [ ] **Pre-commit verification:** Rules requiring typecheck/build before commit can be run automatically before `git commit` executes.
46
+ - [ ] **Tracking maintenance:** Rules telling agents to read/update tracking can be replaced by `sessionStart` context injection and `afterFileEdit` tracking validation.
47
+
48
+ ### 4. Skill Conversion Candidates
49
+
50
+ - [ ] Is the rule only needed during specific tasks (e.g. documentation propagation, release tagging, database migration)?
51
+ - [ ] If so, should it be converted to an on-demand Skill (`SKILL.md`) loaded only when triggered, rather than consuming context on every code edit?
52
+
53
+ ---
54
+
55
+ ## Output Format
56
+
57
+ Return the audit with:
58
+
59
+ 1. **Rule Inventory Table:**
60
+ | Rule | Lines | Est. Tokens | Mode (Always / Glob / Manual) | Purpose |
61
+ |------|-------|-------------|-------------------------------|---------|
62
+ | ... | ... | ... | ... | ... |
63
+
64
+ 2. **Per-Turn Token Tax:**
65
+ - Current always-on tokens per turn: `N` tokens.
66
+ - 40-turn chat impact: `40 × N` tokens.
67
+
68
+ 3. **Trimming & Migration Recommendations:**
69
+ - **Retire (replaced by hooks):** Rules that can be deleted because hooks now enforce them.
70
+ - **Scope (convert to globs):** Always-on rules that only apply to specific file extensions or directories.
71
+ - **Skillify (convert to on-demand):** Deep workflow rules that should load only when specifically invoked.
72
+ - **Consolidate:** Rules that overlap.
73
+
74
+ 4. **Projected Savings:** New always-on token count and percentage reduction.
@@ -0,0 +1,43 @@
1
+ # Second-Family Critique
2
+
3
+ Feed a specification or architectural proposal to an opposing model family before executing substantial work.
4
+
5
+ ## Rationale
6
+
7
+ Single-model workflows replicate the blind spots of their training. A Claude model excels at nuance and long-context synthesis but can drift into over-complicated abstractions. An OpenAI or Gemini model excels at concise logic and edge-case boundaries but can skip narrative continuity or over-simplify complex domain rules.
8
+
9
+ Running a plan past a different model family surfaces assumptions that look reasonable inside a single conversation history.
10
+
11
+ ---
12
+
13
+ ## How to Use
14
+
15
+ Copy the prompt block below, attach your spec or Phase 1 brief, and run it in a model from a different family (e.g. run a Claude-authored spec in GPT/Gemini, or vice versa):
16
+
17
+ ```markdown
18
+ You are an adversarial technical reviewer evaluating an architecture and implementation specification.
19
+ Your goal is to surface hidden assumptions, fragile dependencies, concurrency risks, and unearned optimism.
20
+
21
+ Review the attached specification:
22
+ [Paste or attach spec / brief content here]
23
+
24
+ Answer these 5 questions directly without preamble:
25
+
26
+ 1. What is the single most fragile assumption in this design?
27
+ 2. What edge case will break first under real user behavior or network lag?
28
+ 3. Where does the specification use vague verbs or hand-waving instead of concrete interfaces?
29
+ 4. What verifier or test is missing that could let broken behavior slip into production?
30
+ 5. If you had to simplify this implementation by 30% without dropping core utility, what would you cut?
31
+
32
+ Rank your critical observations from highest risk to lowest risk.
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Processing the Critique
38
+
39
+ When the second model returns its review:
40
+
41
+ 1. **Verify observations against codebase reality:** Not all critiques apply if the opposing model lacked local file context.
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`.
@@ -0,0 +1,65 @@
1
+ # Spec Review
2
+
3
+ A structured pre-implementation audit for feature specifications. Run this against any draft spec in `specs/` before writing multi-file code. Modeled after Bugbot and Security Review: read the spec, check it against the live codebase, and catch ambiguities, missing verifiers, or architectural drift before code starts.
4
+
5
+ ---
6
+
7
+ ## How to Use
8
+
9
+ Provide this prompt to an AI agent with read access to the repository:
10
+
11
+ ```
12
+ Perform a Spec Review on [path/to/spec.md].
13
+ Inspect the existing codebase to verify assumptions, referenced files, and API contracts.
14
+ Report findings grouped by VERIFIER, FEASIBILITY, ACCEPTANCE CRITERIA, and AMBIGUITIES.
15
+ Conclude with a clear verdict: APPROVED, REVISE, or BLOCKED.
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Review Dimensions
21
+
22
+ ### 1. Verifier Presence (Gate)
23
+
24
+ - [ ] Does the spec include a dedicated **How to test / Verifier command** section?
25
+ - [ ] Is the command runnable by an agent or developer (e.g. `pnpm test`, `pnpm run verify`, or targeted test script)?
26
+ - [ ] If no automated test exists, does it name the exact manual verification steps with expected outputs?
27
+ - **Rule:** A delivery spec without a runnable verifier fails this gate automatically.
28
+
29
+ ### 2. Codebase Grounding & Feasibility
30
+
31
+ - [ ] Do referenced files, routes, components, and functions actually exist in the codebase?
32
+ - [ ] Does the proposed approach align with existing conventions (imports, error handling, state management)?
33
+ - [ ] Does the proposed data model conflict with existing database schemas or migrations?
34
+ - [ ] Will this change break existing tests or public API contracts?
35
+
36
+ ### 3. Acceptance Criteria Quality
37
+
38
+ - [ ] Are criteria written as boolean-checkable statements?
39
+ - [ ] Do criteria specify clear inputs and expected outcomes (Given / When / Then)?
40
+ - [ ] Are vague qualifiers ("fast", "intuitive", "clean", "responsive") eliminated or replaced with concrete metrics?
41
+ - [ ] Are sad paths, error conditions, and empty states covered?
42
+
43
+ ### 4. Edge Cases and Traps
44
+
45
+ - [ ] What happens on network failure, timeout, or invalid inputs?
46
+ - [ ] Are concurrency, race conditions, or multi-tab usage considered?
47
+ - [ ] Are secrets, environment variables, or permissions handled properly?
48
+
49
+ ---
50
+
51
+ ## Output Format
52
+
53
+ Return your review in the following structure:
54
+
55
+ 1. **Summary:** 1–2 sentences on what the spec intends to build.
56
+ 2. **Verifier Gate:** Pass or Fail (name the verifier command or flag missing).
57
+ 3. **Findings Table:**
58
+ | Severity | Category | Item | Issue / Risk | Suggested Fix |
59
+ |----------|----------|------|--------------|---------------|
60
+ | Blocker | Feasibility | ... | ... | ... |
61
+ | Warning | Acceptance Criteria | ... | ... | ... |
62
+ 4. **Verdict:**
63
+ - **APPROVED:** Ready for implementation.
64
+ - **REVISE:** Minor fixes needed in spec before implementation.
65
+ - **BLOCKED:** Architectural or verifier gaps prevent starting code.
@@ -14,6 +14,7 @@ ForgeTrail — bootstrap a project folder and run the MCP server
14
14
 
15
15
  Usage:
16
16
  forgetrail install [--lite] [options]
17
+ forgetrail workspace [dir] Scan workspace and emit cross-repo status table
17
18
  forgetrail mcp <subcommand> Build, status, ping, Cursor config
18
19
 
19
20
  Global link (one-time, from your forgetrail clone):
@@ -58,6 +59,11 @@ function main() {
58
59
  return;
59
60
  }
60
61
 
62
+ if (cmd === "workspace" || cmd === "index") {
63
+ import("./workspace-index.mjs");
64
+ return;
65
+ }
66
+
61
67
  if (cmd !== "install") {
62
68
  console.error(`Unknown command: ${cmd ?? "(none)"}`);
63
69
  console.error("Run forgetrail --help");
@@ -30,12 +30,23 @@ const LITE_FILES = [
30
30
  ];
31
31
 
32
32
  const LITE_CURSOR_RULES = [
33
- "forgetrail-no-trailer.mdc",
34
33
  "forgetrail-updates-log.mdc",
35
34
  "specs-and-todo.mdc",
36
35
  "spec-completion.mdc",
37
36
  ];
38
37
 
38
+ const HOOK_FILES = [
39
+ "guard-shell.mjs",
40
+ "guard-edit.mjs",
41
+ "session-start.mjs",
42
+ "validate-tracking.mjs",
43
+ "validate-tracking-core.mjs",
44
+ "session-stop.mjs",
45
+ "cursor-hooks.json",
46
+ "claude-settings-hooks.json",
47
+ "README.md",
48
+ ];
49
+
39
50
  export function runInstallForgetrail(rawArgv, { defaultToCwd = false } = {}) {
40
51
  const args = parseInstallArgs(rawArgv);
41
52
  if (args.help) return { help: INSTALL_FULL_HELP };
@@ -67,10 +78,25 @@ export function runInstallForgetrail(rawArgv, { defaultToCwd = false } = {}) {
67
78
  );
68
79
  }
69
80
 
81
+ const hooksSrc = join(FORGETRAIL_ROOT, "content", "hooks");
82
+ const hooksDest = join(target, ".forgetrail", "hooks");
83
+ ensureDir(hooksDest, args.dryRun);
84
+ for (const hookFile of HOOK_FILES) {
85
+ copyPath(join(hooksSrc, hookFile), join(hooksDest, hookFile), opts);
86
+ }
87
+ const cursorDir = join(target, ".cursor");
88
+ ensureDir(cursorDir, args.dryRun);
89
+ copyPath(
90
+ join(hooksSrc, "cursor-hooks.json"),
91
+ join(cursorDir, "hooks.json"),
92
+ opts
93
+ );
94
+
70
95
  console.log("\nDone.");
71
96
  console.log(" Methodology: _forgetrail/WORKFLOW.md");
72
97
  console.log(" First chat: _forgetrail/INITIAL_PROMPT.md");
73
98
  console.log(" Tracking: .forgetrail/workflow_tracking.json");
99
+ console.log(" Hooks: .forgetrail/hooks/ (enforced via .cursor/hooks.json)");
74
100
  return { target, mode: "full" };
75
101
  }
76
102
 
@@ -109,6 +135,21 @@ export function runInstallLite(rawArgv, { defaultToCwd = false } = {}) {
109
135
  );
110
136
  }
111
137
 
138
+ const hooksSrc = join(contentDir, "hooks");
139
+ const hooksDest = join(forgetrailDir, "hooks");
140
+ ensureDir(hooksDest, args.dryRun);
141
+ for (const hookFile of HOOK_FILES) {
142
+ copyPath(join(hooksSrc, hookFile), join(hooksDest, hookFile), opts);
143
+ }
144
+
145
+ const cursorDir = join(target, ".cursor");
146
+ ensureDir(cursorDir, args.dryRun);
147
+ copyPath(
148
+ join(hooksSrc, "cursor-hooks.json"),
149
+ join(cursorDir, "hooks.json"),
150
+ opts
151
+ );
152
+
112
153
  if (args.withGenesisStub) {
113
154
  const genesisDest = join(target, "docs", "GENESIS.md");
114
155
  ensureDir(join(target, "docs"), args.dryRun);
@@ -118,6 +159,7 @@ export function runInstallLite(rawArgv, { defaultToCwd = false } = {}) {
118
159
  console.log("\nDone.");
119
160
  console.log(" Protocol: .forgetrail/FORGETRAIL_LITE.md");
120
161
  console.log(" Tracking: .forgetrail/workflow_tracking.json (lite-1 schema)");
162
+ console.log(" Hooks: .forgetrail/hooks/ (enforced via .cursor/hooks.json)");
121
163
  if (args.withGenesisStub) {
122
164
  console.log(" Genesis: docs/GENESIS.md (stub — replace with your spec)");
123
165
  console.log(" Next: see TRY_FORGETRAIL.md in the ForgeTrail repo");