forgetrail 0.4.13 → 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.
- package/INITIAL_PROMPT.md +1 -1
- package/README.md +1 -1
- package/TRY_FORGETRAIL.md +44 -5
- package/WORKFLOW.md +15 -2
- package/content/AGENT_INTEGRATION_claude.md +3 -2
- package/content/AGENT_INTEGRATION_cursor.md +2 -1
- package/content/COMPANION_TOOLS.md +1 -0
- package/content/FORGETRAIL_LITE.md +41 -75
- package/content/GREENFIELD_INTAKE.md +9 -2
- package/content/LITE_WORKFLOW_TRACKING.json +2 -1
- package/content/NEW_PROJECT_BOOTSTRAP.md +2 -3
- package/content/companion-tools.json +18 -0
- package/content/cursor-rules/npm-name-hold.mdc +1 -1
- package/content/cursor-rules/spec-completion.mdc +4 -1
- package/content/cursor-rules/specs-and-todo.mdc +5 -1
- package/content/cursor-rules/url-host-matching.mdc +12 -13
- package/content/cursor-rules/user-facing-content.mdc +2 -2
- package/content/cursor-rules/writing-voice.mdc +6 -0
- package/content/forgetrail-workspace-README.md +2 -4
- package/content/hooks/README.md +36 -0
- package/content/hooks/claude-settings-hooks.json +20 -0
- package/content/hooks/cursor-hooks.json +30 -0
- package/content/hooks/guard-edit.mjs +85 -0
- package/content/hooks/guard-shell.mjs +168 -0
- package/content/hooks/session-start.mjs +93 -0
- package/content/hooks/session-stop.mjs +56 -0
- package/content/hooks/validate-tracking-core.mjs +229 -0
- package/content/hooks/validate-tracking.mjs +65 -0
- package/docs/SPEC_FEATURE_TEMPLATE.md +12 -4
- package/docs/TECHNICAL_REFERENCE.md +1 -1
- package/package.json +2 -1
- package/prompts/microcopy-centralization.md +1 -1
- package/prompts/pre-launch-audit.md +8 -0
- package/prompts/propagate-to-forgetrail.md +1 -1
- package/prompts/rules-audit.md +74 -0
- package/prompts/second-family-critique.md +43 -0
- package/prompts/spec-review.md +65 -0
- package/scripts/forgetrail-cli.mjs +6 -0
- package/scripts/install.mjs +43 -1
- package/scripts/workspace-index.mjs +128 -0
- package/workflow_tracking.json +1 -0
- package/content/cursor-rules/forgetrail-no-trailer.mdc +0 -49
- 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
|
|
108
|
+
## 8. Verifier and Acceptance Criteria
|
|
109
109
|
|
|
110
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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/
|
|
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.
|
|
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/
|
|
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`**,
|
|
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");
|
package/scripts/install.mjs
CHANGED
|
@@ -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");
|