forgetrail 0.4.20 → 0.5.1
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 +5 -5
- package/INITIAL_PROMPT.md +15 -15
- package/README.md +5 -5
- package/TRACKING_SCHEMA.md +16 -7
- package/TRY_FORGETRAIL.md +3 -3
- package/WORKFLOW.md +50 -45
- package/content/AGENT_INTEGRATION_claude.md +1 -1
- package/content/AGENT_INTEGRATION_cursor.md +3 -3
- package/content/AGENT_INTEGRATION_generic.md +5 -5
- package/content/AGENT_INTEGRATION_grok.md +8 -8
- package/content/COMPANION_TOOLS.md +2 -2
- package/content/FORGETRAIL_LITE.md +72 -68
- package/content/FORGETRAIL_PROGRESS.md +3 -3
- package/content/GENESIS_SPEC_PROMPT.md +1 -1
- package/content/GENESIS_STUB.md +1 -1
- package/content/GREENFIELD_INTAKE.md +6 -6
- package/content/KICKOFF_WITHOUT_MCP.md +1 -1
- package/content/NEW_PROJECT_BOOTSTRAP.md +27 -27
- package/content/PLAN_MODE_PATTERNS.md +4 -4
- package/content/POST_BOOTSTRAP_USER_MESSAGE.md +1 -1
- package/content/SESSION_RESUME_MCP.md +7 -7
- package/content/companion-tools.json +5 -5
- package/content/cursor-rules/forgetrail-phase-status.mdc +8 -9
- package/content/examples/two-session-continuity.md +31 -67
- package/content/forgetrail-workspace-README.md +2 -2
- package/content/hooks/README.md +4 -4
- package/content/hooks/guard-shell.mjs +20 -12
- package/content/hooks/session-start.mjs +58 -63
- package/content/hooks/session-stop.mjs +49 -30
- package/content/hooks/validate-tracking-core.mjs +34 -1
- package/content/scripts/forgetrail-dev-launcher.mjs +12 -2
- package/content/skills/forgetrail/SKILL.md +21 -21
- package/content/skills/forgetrail/SKILL_FACTS.md +2 -2
- package/docs/CONTEXT_PROMPT.md +1 -1
- package/docs/NPM.md +8 -8
- package/docs/PHASE_1_BRIEF.md +7 -7
- package/package.json +1 -1
- package/scripts/forgetrail-cli.mjs +1 -1
- package/scripts/install-lib.mjs +1 -0
- package/scripts/install.mjs +6 -23
- package/scripts/publish-gate.mjs +6 -4
- package/scripts/tracking-cutover.test.mjs +109 -0
- package/scripts/workspace-index.mjs +9 -1
|
@@ -74,6 +74,24 @@ function main() {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// Local vault updates go through the ingotvault command. Force still asks.
|
|
78
|
+
if (/\bingotvault\b/.test(command)) {
|
|
79
|
+
if (/\s--force-with-lease\b/.test(command)) {
|
|
80
|
+
console.log(
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
permission: "ask",
|
|
83
|
+
user_message:
|
|
84
|
+
"ForgeTrail Safety: ingotvault --force-with-lease rewrites mirror history. Confirm to proceed.",
|
|
85
|
+
agent_message:
|
|
86
|
+
"ingotvault --force-with-lease requires explicit user confirmation.",
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
console.log(JSON.stringify({ permission: "allow" }));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
// 2. Check for git push (Do not push to upstream remote unless explicitly requested by user)
|
|
78
96
|
if (/\bgit\s+push\b/.test(command)) {
|
|
79
97
|
if (/\s+(-f|--force|--force-with-lease)\b/.test(command)) {
|
|
@@ -89,23 +107,13 @@ function main() {
|
|
|
89
107
|
return;
|
|
90
108
|
}
|
|
91
109
|
|
|
92
|
-
// Pushing to IngotVault or local backup mirrors is automatic to preserve work
|
|
93
|
-
const isIngotVaultOrBackup =
|
|
94
|
-
/\bgit\s+push\b.*?\b(backup|ingotvault)\b/i.test(command) ||
|
|
95
|
-
/\bgit\s+push\b.*?(?:[a-zA-Z]:[\\/]|file:\/\/|\.\/|\.\.\/)/i.test(command);
|
|
96
|
-
|
|
97
|
-
if (isIngotVaultOrBackup) {
|
|
98
|
-
console.log(JSON.stringify({ permission: "allow" }));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
110
|
console.log(
|
|
103
111
|
JSON.stringify({
|
|
104
112
|
permission: "ask",
|
|
105
113
|
user_message:
|
|
106
|
-
"ForgeTrail Safety: git push
|
|
114
|
+
"ForgeTrail Safety: git push requires explicit user approval. Local vault updates use ingotvault --repo .",
|
|
107
115
|
agent_message:
|
|
108
|
-
"git push
|
|
116
|
+
"git push requires explicit user approval. Mirror this repo with ingotvault --repo . instead of git push backup.",
|
|
109
117
|
})
|
|
110
118
|
);
|
|
111
119
|
return;
|
|
@@ -3,20 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* ForgeTrail Session Start Context Injector
|
|
5
5
|
* Hook event: sessionStart (Cursor)
|
|
6
|
-
*
|
|
7
|
-
* Retires static phase-status rules on hosts with hook support.
|
|
6
|
+
* Points the session at appledger/. A legacy workflow_tracking.json is a conflict, not the live phase.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
12
11
|
|
|
13
|
-
function
|
|
12
|
+
function walkUp(startDir, found) {
|
|
14
13
|
let curr = resolve(startDir || process.cwd());
|
|
15
14
|
for (let i = 0; i < 6; i++) {
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
const candidate2 = join(curr, "workflow_tracking.json");
|
|
19
|
-
if (existsSync(candidate2)) return candidate2;
|
|
15
|
+
const hit = found(curr);
|
|
16
|
+
if (hit) return hit;
|
|
20
17
|
const parent = resolve(curr, "..");
|
|
21
18
|
if (parent === curr) break;
|
|
22
19
|
curr = parent;
|
|
@@ -24,70 +21,68 @@ function findTrackingFile(startDir) {
|
|
|
24
21
|
return null;
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const tracking = JSON.parse(readFileSync(trackingPath, "utf-8"));
|
|
37
|
-
const projectName = tracking.project?.name || "App";
|
|
38
|
-
const phase = tracking.currentPhase || "1";
|
|
39
|
-
const isLite = tracking.schemaVersion === "lite-1";
|
|
24
|
+
function findLedger(startDir) {
|
|
25
|
+
return walkUp(startDir, (dir) => {
|
|
26
|
+
const manifest = join(dir, "appledger", "manifest.yaml");
|
|
27
|
+
if (existsSync(manifest)) return dir;
|
|
28
|
+
return null;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (Array.isArray(phaseObj?.exitCriteriaRemaining)) {
|
|
52
|
-
remaining = phaseObj.exitCriteriaRemaining;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
32
|
+
function findProjectTracking(startDir) {
|
|
33
|
+
return walkUp(startDir, (dir) => {
|
|
34
|
+
const candidate = join(dir, ".forgetrail", "workflow_tracking.json");
|
|
35
|
+
if (existsSync(candidate)) return candidate;
|
|
36
|
+
const rootStarter = join(dir, "workflow_tracking.json");
|
|
37
|
+
const methodology = existsSync(join(dir, "WORKFLOW.md")) && existsSync(join(dir, "content"));
|
|
38
|
+
if (existsSync(rootStarter) && !methodology) return rootStarter;
|
|
39
|
+
return null;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
lastLeftOff = lastSession.leftOff || lastSession.notes || "";
|
|
60
|
-
}
|
|
43
|
+
function isPointer(data) {
|
|
44
|
+
return data?.status === "pointer" && typeof data.record === "string" && data.record.replaceAll("\\", "/").includes("appledger");
|
|
45
|
+
}
|
|
61
46
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
function main() {
|
|
48
|
+
const ledgerRoot = findLedger(process.cwd());
|
|
49
|
+
const trackingPath = findProjectTracking(process.cwd());
|
|
50
|
+
const lines = [];
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
52
|
+
if (ledgerRoot) {
|
|
53
|
+
lines.push("=== ForgeTrail Context ===");
|
|
54
|
+
lines.push("Project record: appledger/");
|
|
55
|
+
lines.push("Read appledger/profiles/forgetrail.yaml for the current phase.");
|
|
56
|
+
lines.push("Read the latest session record for left_off and next_steps.");
|
|
57
|
+
lines.push("Do not update workflow_tracking.json.");
|
|
58
|
+
}
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
60
|
+
if (trackingPath) {
|
|
61
|
+
try {
|
|
62
|
+
const tracking = JSON.parse(readFileSync(trackingPath, "utf-8"));
|
|
63
|
+
if (isPointer(tracking)) {
|
|
64
|
+
if (!ledgerRoot) {
|
|
65
|
+
lines.push("=== ForgeTrail Context ===");
|
|
66
|
+
lines.push("workflow_tracking.json is a pointer to appledger/. Do not write decisions, sessions, or phase status there.");
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
lines.push("=== Legacy tracking conflict ===");
|
|
70
|
+
lines.push("workflow_tracking.json is not the system of record.");
|
|
71
|
+
lines.push("Do not add decisions, sessions, or phase status to it.");
|
|
72
|
+
lines.push("Run `appledger migrate preview`, then apply once, and write only in appledger/.");
|
|
78
73
|
}
|
|
74
|
+
} catch {
|
|
75
|
+
lines.push("workflow_tracking.json could not be read. Do not recreate it. Use appledger/.");
|
|
79
76
|
}
|
|
77
|
+
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
console.log(
|
|
84
|
-
JSON.stringify({
|
|
85
|
-
additional_context: lines.join("\n"),
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
} catch {
|
|
79
|
+
if (lines.length === 0) {
|
|
89
80
|
console.log(JSON.stringify({}));
|
|
81
|
+
return;
|
|
90
82
|
}
|
|
83
|
+
|
|
84
|
+
lines.push("========================================");
|
|
85
|
+
console.log(JSON.stringify({ additional_context: lines.join("\n") }));
|
|
91
86
|
}
|
|
92
87
|
|
|
93
88
|
main();
|
|
@@ -3,19 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* ForgeTrail Session Stop Check
|
|
5
5
|
* Hook event: stop (Cursor)
|
|
6
|
-
*
|
|
6
|
+
* Reminds the agent to update the AppLedger session, not a tracking JSON file.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
11
|
|
|
12
|
-
function
|
|
12
|
+
function walkUp(startDir, found) {
|
|
13
13
|
let curr = resolve(startDir || process.cwd());
|
|
14
14
|
for (let i = 0; i < 6; i++) {
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
-
const candidate2 = join(curr, "workflow_tracking.json");
|
|
18
|
-
if (existsSync(candidate2)) return candidate2;
|
|
15
|
+
const hit = found(curr);
|
|
16
|
+
if (hit) return hit;
|
|
19
17
|
const parent = resolve(curr, "..");
|
|
20
18
|
if (parent === curr) break;
|
|
21
19
|
curr = parent;
|
|
@@ -23,32 +21,53 @@ function findTrackingFile(startDir) {
|
|
|
23
21
|
return null;
|
|
24
22
|
}
|
|
25
23
|
|
|
24
|
+
function findLedger(startDir) {
|
|
25
|
+
return walkUp(startDir, (dir) => (existsSync(join(dir, "appledger", "manifest.yaml")) ? dir : null));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findProjectTracking(startDir) {
|
|
29
|
+
return walkUp(startDir, (dir) => {
|
|
30
|
+
const candidate = join(dir, ".forgetrail", "workflow_tracking.json");
|
|
31
|
+
if (existsSync(candidate)) return candidate;
|
|
32
|
+
return null;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isPointer(data) {
|
|
37
|
+
return data?.status === "pointer" && typeof data.record === "string" && data.record.replaceAll("\\", "/").includes("appledger");
|
|
38
|
+
}
|
|
39
|
+
|
|
26
40
|
function main() {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
const ledgerRoot = findLedger(process.cwd());
|
|
42
|
+
const trackingPath = findProjectTracking(process.cwd());
|
|
43
|
+
let legacy = false;
|
|
44
|
+
if (trackingPath) {
|
|
45
|
+
try {
|
|
46
|
+
legacy = !isPointer(JSON.parse(readFileSync(trackingPath, "utf-8")));
|
|
47
|
+
} catch {
|
|
48
|
+
legacy = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (legacy) {
|
|
53
|
+
console.log(
|
|
54
|
+
JSON.stringify({
|
|
55
|
+
followup_message:
|
|
56
|
+
"ForgeTrail reminder: workflow_tracking.json is a legacy file. Do not append sessions to it. Run `appledger migrate preview`, then apply, and record this session in appledger/.",
|
|
57
|
+
})
|
|
58
|
+
);
|
|
30
59
|
return;
|
|
31
60
|
}
|
|
32
61
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!hasTodaySession) {
|
|
43
|
-
console.log(
|
|
44
|
-
JSON.stringify({
|
|
45
|
-
followup_message:
|
|
46
|
-
"ForgeTrail Reminder: Update .forgetrail/workflow_tracking.json (sessions[], decisions[], gotchas[]) with what was accomplished before ending.",
|
|
47
|
-
})
|
|
48
|
-
);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
} catch {}
|
|
62
|
+
if (ledgerRoot) {
|
|
63
|
+
console.log(
|
|
64
|
+
JSON.stringify({
|
|
65
|
+
followup_message:
|
|
66
|
+
"ForgeTrail reminder: update the appledger session record (what was accomplished, left_off, next_steps) before ending. Do not write workflow_tracking.json.",
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
52
71
|
|
|
53
72
|
console.log(JSON.stringify({}));
|
|
54
73
|
}
|
|
@@ -41,6 +41,26 @@ function isLiteSchema(data) {
|
|
|
41
41
|
return data && data.schemaVersion === "lite-1";
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
const LEGACY_TRACKING_ISSUE =
|
|
45
|
+
"Legacy writable tracking is not the system of record. Do not add decisions, sessions, or phase status to this file. Run `appledger migrate preview`, then apply once, and write only in appledger/.";
|
|
46
|
+
|
|
47
|
+
const POINTER_WARNING =
|
|
48
|
+
"This file is a pointer to appledger/. Do not add decisions, sessions, or phase status here.";
|
|
49
|
+
|
|
50
|
+
function isPointer(data) {
|
|
51
|
+
return data.status === "pointer" && typeof data.record === "string" && data.record.replaceAll("\\", "/").includes("appledger");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isLegacyTracking(data) {
|
|
55
|
+
return (
|
|
56
|
+
"currentPhase" in data ||
|
|
57
|
+
"phases" in data ||
|
|
58
|
+
"decisions" in data ||
|
|
59
|
+
"sessions" in data ||
|
|
60
|
+
"schemaVersion" in data
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
export function validateTrackingData(data) {
|
|
45
65
|
const issues = [];
|
|
46
66
|
const warnings = [];
|
|
@@ -50,6 +70,15 @@ export function validateTrackingData(data) {
|
|
|
50
70
|
return { issues, warnings };
|
|
51
71
|
}
|
|
52
72
|
|
|
73
|
+
if (isPointer(data)) {
|
|
74
|
+
warnings.push(POINTER_WARNING);
|
|
75
|
+
return { issues, warnings };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isLegacyTracking(data)) {
|
|
79
|
+
issues.push(LEGACY_TRACKING_ISSUE);
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
const project = data.project;
|
|
54
83
|
if (!project || typeof project !== "object") {
|
|
55
84
|
issues.push("Missing top-level project object.");
|
|
@@ -214,7 +243,11 @@ export function formatValidationResult(result) {
|
|
|
214
243
|
const { issues, warnings } = result;
|
|
215
244
|
|
|
216
245
|
if (issues.length === 0 && warnings.length === 0) {
|
|
217
|
-
return "
|
|
246
|
+
return "No workflow_tracking.json is required. Project state lives in appledger/.";
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (issues.length === 0 && warnings.every((item) => item.includes("pointer"))) {
|
|
250
|
+
return "Pointer accepted. Project state lives in appledger/.\n\n" + warnings.map((item) => `- ${item}`).join("\n");
|
|
218
251
|
}
|
|
219
252
|
|
|
220
253
|
const parts = [];
|
|
@@ -143,9 +143,19 @@ async function cmdRun() {
|
|
|
143
143
|
|
|
144
144
|
function renderProgress() {
|
|
145
145
|
if (!existsSync(trackingPath)) {
|
|
146
|
-
return {
|
|
146
|
+
return {
|
|
147
|
+
text: "No workflow_tracking.json. Phase state belongs in appledger/profiles/forgetrail.yaml.",
|
|
148
|
+
md: null,
|
|
149
|
+
};
|
|
147
150
|
}
|
|
148
151
|
const t = JSON.parse(readFileSync(trackingPath, "utf8"));
|
|
152
|
+
const record = typeof t.record === "string" ? t.record.replaceAll("\\", "/") : "";
|
|
153
|
+
if (t.status === "pointer" && record.includes("appledger")) {
|
|
154
|
+
return {
|
|
155
|
+
text: "workflow_tracking.json is a pointer to appledger/. Read appledger/profiles/forgetrail.yaml.",
|
|
156
|
+
md: null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
149
159
|
const phase = t.currentPhase ?? "?";
|
|
150
160
|
const phaseId = String(phase);
|
|
151
161
|
const phaseBlock = t.phases?.[phaseId];
|
|
@@ -168,7 +178,7 @@ function renderProgress() {
|
|
|
168
178
|
const md = [
|
|
169
179
|
"# ForgeTrail progress",
|
|
170
180
|
"",
|
|
171
|
-
"_Human-readable snapshot.
|
|
181
|
+
"_Human-readable snapshot read from a legacy workflow_tracking.json. After migrate, the source of truth is `appledger/profiles/forgetrail.yaml`._",
|
|
172
182
|
"",
|
|
173
183
|
`**Current phase:** ${label} (\`${phaseId}\`) — **${status}**`,
|
|
174
184
|
"",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: forgetrail
|
|
3
|
-
description: "Enforce the ForgeTrail 7-phase lifecycle (Plan → Build → Stabilize → Iterate → Refine → Align → Harden), maintain
|
|
3
|
+
description: "Enforce the ForgeTrail 7-phase lifecycle (Plan → Build → Stabilize → Iterate → Refine → Align → Harden), maintain appledger/ as the system of record, pause at explicit phase transitions for user approval, prefer native plan modes when available, and use subagents for parallel audits/research where the host agent supports it. Do not write workflow_tracking.json. Activate for any non-trivial full-stack app development. Follow progressive documentation rules and propagate lessons back."
|
|
4
4
|
user-invocable: true
|
|
5
5
|
argument-hint: "kickoff new project | resume session | Phase 4 feature work | run black-hat audit | stabilize current issues"
|
|
6
6
|
allowed-tools: "read_file, search_replace, run_terminal_command, todo_write, spawn_subagent, getPhaseGuidance, getCompanionSuggestions, runAudit, searchLessons, validateTracking, suggestSubagentDecomposition, getTemplate, getNewProjectKickoff, getResumeSessionInstructions"
|
|
@@ -21,27 +21,28 @@ You are operating under the ForgeTrail methodology. Your primary job is to give
|
|
|
21
21
|
- 6. Align (map to brand/strategy)
|
|
22
22
|
- 7. Harden (security, perf, docs, production readiness)
|
|
23
23
|
|
|
24
|
-
Phases 4 and 5 commonly alternate. Update `
|
|
24
|
+
Phases 4 and 5 commonly alternate. Update `current_phase_instance` and criteria in `appledger/profiles/forgetrail.yaml`. Do not write `workflow_tracking.json`.
|
|
25
25
|
|
|
26
26
|
2. **At the start of every session or major turn**:
|
|
27
|
-
- Read
|
|
27
|
+
- Read `appledger/profiles/forgetrail.yaml` and the latest session record.
|
|
28
28
|
- Read `CONTEXT_PROMPT.md` and `TODO.md` if they exist.
|
|
29
29
|
- Call `getPhaseGuidance` for the current phase if needed.
|
|
30
|
-
- Summarize current phase,
|
|
30
|
+
- Summarize current phase, pending criteria, and recent decisions to the user briefly.
|
|
31
|
+
- If `workflow_tracking.json` exists and is not a pointer, do not edit it. Run `appledger migrate preview`, then apply.
|
|
31
32
|
|
|
32
33
|
3. **Pause at phase transitions**:
|
|
33
34
|
- When you believe exit criteria for the current phase are met, explicitly tell the user: "I think we have completed Phase X because [reasons]. The remaining exit criteria are [list]. Ready to move to Phase Y?"
|
|
34
|
-
- Wait for explicit user approval before
|
|
35
|
+
- Wait for explicit user approval before changing `current_phase_instance`.
|
|
35
36
|
|
|
36
|
-
4. **Maintain
|
|
37
|
-
- Log every major decision with rationale
|
|
38
|
-
-
|
|
39
|
-
- Add
|
|
40
|
-
-
|
|
41
|
-
-
|
|
37
|
+
4. **Maintain `appledger/`**:
|
|
38
|
+
- Log every major decision as a decision record with rationale.
|
|
39
|
+
- Mark a criterion `met` only when an evidence ref exists.
|
|
40
|
+
- Add a lesson or question record when something surprising or painful happens.
|
|
41
|
+
- Update the session record at natural breaks.
|
|
42
|
+
- Do not create or update `workflow_tracking.json`.
|
|
42
43
|
|
|
43
44
|
5. **Use native agent capabilities**:
|
|
44
|
-
- If the agent supports a native plan mode (e.g. Grok `/plan`), use it for Phase 1. On approval, export the plan into `docs/PHASE_1_BRIEF.md` (via `getTemplate`)
|
|
45
|
+
- If the agent supports a native plan mode (e.g. Grok `/plan`), use it for Phase 1. On approval, export the plan into `docs/PHASE_1_BRIEF.md` (via `getTemplate`) and decision records in `appledger/`. Do not write `workflow_tracking.json`.
|
|
45
46
|
- When the host supports `spawn_subagent`, use `suggestSubagentDecomposition` (or reason directly) to run audits, research, and reviews in parallel with appropriate `capability_mode`, `isolation`, and personas. Synthesize results in the parent thread and update tracking/docs.
|
|
46
47
|
- Prefer `read-only` or `execute` modes for subagents doing analysis.
|
|
47
48
|
|
|
@@ -68,18 +69,17 @@ You are operating under the ForgeTrail methodology. Your primary job is to give
|
|
|
68
69
|
## Phase Transition Protocol
|
|
69
70
|
|
|
70
71
|
When ready to advance:
|
|
71
|
-
- Confirm
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
- Only after confirmation, update `currentPhase` and start the next phase's playbook.
|
|
72
|
+
- Confirm the phase criteria that have evidence.
|
|
73
|
+
- Ask the user: "Ready to move to Phase X?"
|
|
74
|
+
- Only after confirmation, update the phase status in `appledger/profiles/forgetrail.yaml` and the session record. Do not write `workflow_tracking.json`.
|
|
75
75
|
|
|
76
76
|
## For New Projects (Greenfield)
|
|
77
77
|
|
|
78
|
-
Prefer calling `getNewProjectKickoff` (or `kickoffGreenfield`) at the very beginning.
|
|
78
|
+
Prefer calling `getNewProjectKickoff` (or `kickoffGreenfield`) at the very beginning. When `appledger` is installed, run `appledger init` as that bundle describes. Do not write `workflow_tracking.json`. Write optional Cursor rules, and follow the post-bootstrap instructions exactly. Then start with Phase 1.
|
|
79
79
|
|
|
80
80
|
## For Resuming
|
|
81
81
|
|
|
82
|
-
Call `getResumeSessionInstructions` (or read
|
|
82
|
+
Call `getResumeSessionInstructions` (or read `appledger/profiles/forgetrail.yaml`, the latest session, and CONTEXT_PROMPT) and continue from the current phase.
|
|
83
83
|
|
|
84
84
|
## Key Tools to Use Proactively
|
|
85
85
|
|
|
@@ -88,10 +88,10 @@ Call `getResumeSessionInstructions` (or read tracking + CONTEXT_PROMPT) and cont
|
|
|
88
88
|
- `getTemplate({name: "..."})` — for PHASE_1_BRIEF, CONTEXT_PROMPT, audits, etc. Use mode "shell" for clean structure.
|
|
89
89
|
- `runAudit`, `searchLessons`, `getAntiPatterns`
|
|
90
90
|
- `suggestSubagentDecomposition` (when subagents available)
|
|
91
|
-
- `validateTracking` (
|
|
92
|
-
- `ingestPlanArtifact` (after native plan mode approval — map plan → PHASE_1_BRIEF +
|
|
91
|
+
- `validateTracking` (classify a legacy `workflow_tracking.json`; a missing file is expected)
|
|
92
|
+
- `ingestPlanArtifact` (after native plan mode approval — map plan → PHASE_1_BRIEF + decision records)
|
|
93
93
|
- `todo_write` — mirror open exit criteria and next actions.
|
|
94
94
|
|
|
95
|
-
Always keep the user in the loop on phase progress and major decisions.
|
|
95
|
+
Always keep the user in the loop on phase progress and major decisions. `appledger/` plus CONTEXT_PROMPT are the source of truth across sessions, not chat history.
|
|
96
96
|
|
|
97
97
|
This skill makes you a much more reliable long-term build partner. Use it.
|
|
@@ -6,7 +6,7 @@ version: "0.3.0"
|
|
|
6
6
|
status: active
|
|
7
7
|
license: Apache-2.0
|
|
8
8
|
kind: cursor-skill
|
|
9
|
-
purpose: "Enforce the ForgeTrail 7-phase lifecycle and maintain
|
|
9
|
+
purpose: "Enforce the ForgeTrail 7-phase lifecycle and maintain appledger/ as the system of record"
|
|
10
10
|
homepage: https://forgetrail.dev
|
|
11
11
|
repository: https://github.com/Catalyst-Forge-LLC/forgetrail
|
|
12
12
|
provenance:
|
|
@@ -54,7 +54,7 @@ credits:
|
|
|
54
54
|
|
|
55
55
|
## Purpose
|
|
56
56
|
|
|
57
|
-
Enforce the ForgeTrail 7-phase lifecycle and maintain
|
|
57
|
+
Enforce the ForgeTrail 7-phase lifecycle and maintain appledger/ as the system of record
|
|
58
58
|
|
|
59
59
|
## Provenance
|
|
60
60
|
|
package/docs/CONTEXT_PROMPT.md
CHANGED
|
@@ -24,7 +24,7 @@ _If the project used ForgeTrail Phase 1 properly, **`docs/PHASE_1_BRIEF.md`** ex
|
|
|
24
24
|
| §10 Out of scope v1 | **Key Architectural Decisions** or short note under **Recent Changes** |
|
|
25
25
|
| §11 First feature batch | Seed **`TODO.md`** |
|
|
26
26
|
|
|
27
|
-
**After merge:** Keep **`PHASE_1_BRIEF.md`** in `docs/` as a dated audit trail **or** replace its body with one line: `Merged into CONTEXT_PROMPT.md on [date]. Source preserved in git history.` Do **not** delete corresponding
|
|
27
|
+
**After merge:** Keep **`PHASE_1_BRIEF.md`** in `docs/` as a dated audit trail **or** replace its body with one line: `Merged into CONTEXT_PROMPT.md on [date]. Source preserved in git history.` Do **not** delete the corresponding decision records in **`appledger/`**.
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
package/docs/NPM.md
CHANGED
|
@@ -8,14 +8,14 @@ You run `pnpm publish`. The gate does not log in or publish.
|
|
|
8
8
|
|
|
9
9
|
Two public unscoped packages:
|
|
10
10
|
|
|
11
|
-
| Package |
|
|
12
|
-
| --- | --- | --- | --- |
|
|
13
|
-
| [`forgetrail`](https://www.npmjs.com/package/forgetrail) | 0.
|
|
14
|
-
| [`forgetrail-mcp`](https://www.npmjs.com/package/forgetrail-mcp) | 0.
|
|
11
|
+
| Package | This tree | On npm until you publish | From | What it is |
|
|
12
|
+
| --- | --- | --- | --- | --- |
|
|
13
|
+
| [`forgetrail`](https://www.npmjs.com/package/forgetrail) | 0.5.0 | 0.4.20 | repo root | CLI + Lite + templates. `pnpm dlx forgetrail install --lite` |
|
|
14
|
+
| [`forgetrail-mcp`](https://www.npmjs.com/package/forgetrail-mcp) | 0.4.0 | 0.3.10 | `mcp-server/` | MCP bin. `pnpm dlx forgetrail-mcp` |
|
|
15
15
|
|
|
16
16
|
These are **installer and MCP channels**, not a library. App repos should not add `forgetrail` to `dependencies`.
|
|
17
17
|
|
|
18
|
-
`
|
|
18
|
+
`0.5.0` stops installing `workflow_tracking.json`. `0.4.0` tells kickoff to run `appledger init`. The registry copies above do not include that cutover. `pnpm run pack:check` is the gate. It does not publish.
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -59,11 +59,11 @@ The `Debugger listening` line on MCP `prepack` is Cursor attaching to a one-line
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
# Prefer pnpm dlx. npm 12 `npx forgetrail --help` on Windows failed to spawn the bin.
|
|
62
|
-
pnpm dlx forgetrail@0.
|
|
63
|
-
pnpm dlx forgetrail@0.
|
|
62
|
+
pnpm dlx forgetrail@0.5.0 --help
|
|
63
|
+
pnpm dlx forgetrail@0.5.0 install --lite --dry-run
|
|
64
64
|
|
|
65
65
|
# MCP starts; needs content via FORGETRAIL_ROOT or a sibling forgetrail install
|
|
66
|
-
pnpm dlx forgetrail-mcp@0.
|
|
66
|
+
pnpm dlx forgetrail-mcp@0.4.0
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
Optional Cursor MCP (content from a `forgetrail` install, or set the path):
|
package/docs/PHASE_1_BRIEF.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# [App Name] — Phase 1 architecture brief
|
|
2
2
|
|
|
3
|
-
_Structured capture of planning and architecture **before** code scaffolding. Goal: Phase 2 (or a new agent/session) can start from this file
|
|
3
|
+
_Structured capture of planning and architecture **before** code scaffolding. Goal: Phase 2 (or a new agent/session) can start from this file plus `appledger/` without re-reading the whole Phase 1 chat._
|
|
4
4
|
|
|
5
5
|
**Status:** `[draft | locked]`
|
|
6
6
|
**Last updated:** `[ISO date]`
|
|
7
|
-
**Phase 1 exit:** Do not mark
|
|
7
|
+
**Phase 1 exit:** Do not mark the `plan` phase complete in `appledger/profiles/forgetrail.yaml` until this brief is **locked** and major commitments are decision records. Do not write `workflow_tracking.json`.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ _Structured capture of planning and architecture **before** code scaffolding. Go
|
|
|
13
13
|
**What we are building (2–4 sentences):**
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
**Project archetype:** `[product | internal-tool | one-shot]` _(WORKFLOW.md §1d — non-`product` archetypes prune later-phase
|
|
16
|
+
**Project archetype:** `[product | internal-tool | one-shot]` _(WORKFLOW.md §1d — non-`product` archetypes prune later-phase criteria in `appledger/profiles/forgetrail.yaml`; record the pruning as a decision.)_
|
|
17
17
|
|
|
18
18
|
**What “done” looks like for v1 (measurable where possible):**
|
|
19
19
|
|
|
@@ -115,7 +115,7 @@ _What could blow schedule or architecture — honest list._
|
|
|
115
115
|
|
|
116
116
|
## 8. Architectural decisions (numbered)
|
|
117
117
|
|
|
118
|
-
_Each decision should include **WHY** and what was rejected. **Also** add each major commitment
|
|
118
|
+
_Each decision should include **WHY** and what was rejected. **Also** add each major commitment as a decision record in `appledger/` (choice, rationale, alternatives)._
|
|
119
119
|
|
|
120
120
|
**D1.**
|
|
121
121
|
|
|
@@ -158,7 +158,7 @@ _Ordered list of what to build after the spine runs — aligns with initial `TOD
|
|
|
158
158
|
|
|
159
159
|
- [ ] User has confirmed stack, folder shape, data sketch, hero flow, and v1 boundaries
|
|
160
160
|
- [ ] This brief is **locked** (no `[draft]` ambiguity) or remaining items are only in §9 Open questions
|
|
161
|
-
- [ ]
|
|
162
|
-
- [ ] Phase 2 opener will read **this file
|
|
161
|
+
- [ ] `appledger/` updated: a decision record for each major D#, and the session records the sign-off. Do not write `workflow_tracking.json`.
|
|
162
|
+
- [ ] Phase 2 opener will read **this file**, `appledger/profiles/forgetrail.yaml`, and the latest session record first
|
|
163
163
|
|
|
164
|
-
> 💡 **Lesson learned:** Treat the brief and
|
|
164
|
+
> 💡 **Lesson learned:** Treat the brief and the decision records as a pair. The brief is what the next human or agent reads in one pass. Duplicated rationale is OK — drift between them is not. After Phase 2 starts, **merge** this content into `CONTEXT_PROMPT.md` per the mapping in that template’s “Handoff from Phase 1” section.
|
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@ Install options:
|
|
|
36
36
|
--with-genesis-stub With --lite: also create docs/GENESIS.md stub
|
|
37
37
|
--force, -f Overwrite existing files
|
|
38
38
|
--dry-run Preview without writing
|
|
39
|
-
--skip-tracking
|
|
39
|
+
--skip-tracking Accepted and ignored. A tracking JSON file is not installed.
|
|
40
40
|
--path, -p <dir> Install elsewhere (default: current directory)
|
|
41
41
|
--help, -h Show help
|
|
42
42
|
|
package/scripts/install-lib.mjs
CHANGED
|
@@ -109,6 +109,7 @@ export function copyContentDir(fromDir, toDir, opts) {
|
|
|
109
109
|
ensureDir(toDir, opts.dryRun);
|
|
110
110
|
for (const name of readdirSync(fromDir)) {
|
|
111
111
|
if (name === "skills") continue;
|
|
112
|
+
if (name === "LITE_WORKFLOW_TRACKING.json") continue;
|
|
112
113
|
if (MCP_ONLY_CONTENT.has(name)) continue;
|
|
113
114
|
copyPath(join(fromDir, name), join(toDir, name), opts);
|
|
114
115
|
}
|