diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs
index 10b8633d2..04766e671 100644
--- a/get-shit-done/bin/lib/init.cjs
+++ b/get-shit-done/bin/lib/init.cjs
@@ -44,6 +44,22 @@ function withProjectRoot(cwd, result) {
if (config.response_language) {
result.response_language = config.response_language;
}
+ // Inject project identity into all init outputs so handoff blocks
+ // can include project context for cross-session continuity.
+ if (config.project_code) {
+ result.project_code = config.project_code;
+ }
+ // Extract project title from PROJECT.md first H1 heading.
+ const projectMdPath = path.join(planningDir(cwd), 'PROJECT.md');
+ try {
+ if (fs.existsSync(projectMdPath)) {
+ const content = fs.readFileSync(projectMdPath, 'utf8');
+ const h1Match = content.match(/^#\s+(.+)$/m);
+ if (h1Match) {
+ result.project_title = h1Match[1].trim();
+ }
+ }
+ } catch { /* intentionally empty */ }
return result;
}
diff --git a/get-shit-done/bin/lib/phase.cjs b/get-shit-done/bin/lib/phase.cjs
index 7e8d58cee..805c475b2 100644
--- a/get-shit-done/bin/lib/phase.cjs
+++ b/get-shit-done/bin/lib/phase.cjs
@@ -585,10 +585,12 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
*/
function renameDecimalPhases(phasesDir, baseInt, removedDecimal) {
const renamedDirs = [], renamedFiles = [];
- const decPattern = new RegExp(`^${baseInt}\\.(\\d+)-(.+)$`);
+ // Capture the zero-padded prefix (e.g. "06" from "06.3-slug") so the renamed
+ // directory preserves the original padding format.
+ const decPattern = new RegExp(`^(0*${baseInt})\\.(\\d+)-(.+)$`);
const dirs = readSubdirectories(phasesDir, true);
const toRename = dirs
- .map(dir => { const m = dir.match(decPattern); return m ? { dir, oldDecimal: parseInt(m[1], 10), slug: m[2] } : null; })
+ .map(dir => { const m = dir.match(decPattern); return m ? { dir, prefix: m[1], oldDecimal: parseInt(m[2], 10), slug: m[3] } : null; })
.filter(item => item && item.oldDecimal > removedDecimal)
.sort((a, b) => b.oldDecimal - a.oldDecimal); // descending to avoid conflicts
@@ -596,7 +598,7 @@ function renameDecimalPhases(phasesDir, baseInt, removedDecimal) {
const newDecimal = item.oldDecimal - 1;
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
const newPhaseId = `${baseInt}.${newDecimal}`;
- const newDirName = `${baseInt}.${newDecimal}-${item.slug}`;
+ const newDirName = `${item.prefix}.${newDecimal}-${item.slug}`;
fs.renameSync(path.join(phasesDir, item.dir), path.join(phasesDir, newDirName));
renamedDirs.push({ from: item.dir, to: newDirName });
for (const f of fs.readdirSync(path.join(phasesDir, newDirName))) {
diff --git a/get-shit-done/references/continuation-format.md b/get-shit-done/references/continuation-format.md
index bed4e7146..e810859b5 100644
--- a/get-shit-done/references/continuation-format.md
+++ b/get-shit-done/references/continuation-format.md
@@ -7,7 +7,7 @@ Standard format for presenting next steps after completing a command or workflow
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**{identifier}: {name}** — {one-line description}
@@ -24,6 +24,9 @@ Standard format for presenting next steps after completing a command or workflow
---
```
+> If `project_code` is not set in the init context, omit the project identity suffix:
+> `## ▶ Next Up` (no ` — [CODE] Title`).
+
## Format Rules
1. **Always show what it is** — name + description, never just a command path
@@ -32,6 +35,7 @@ Standard format for presenting next steps after completing a command or workflow
4. **`/clear` first** — always show `/clear` before the command so users run it in the correct order
5. **"Also available" not "Other options"** — sounds more app-like
6. **Visual separators** — `---` above and below to make it stand out
+7. **Project identity in heading** — include `[PROJECT_CODE] PROJECT_TITLE` from init context so handoffs are self-identifying across sessions. If `project_code` is not set, omit the suffix entirely (just `## ▶ Next Up`)
## Variants
@@ -40,7 +44,7 @@ Standard format for presenting next steps after completing a command or workflow
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**02-03: Refresh Token Rotation** — Add /api/auth/refresh with sliding expiry
@@ -64,7 +68,7 @@ Add note that this is the last plan and what comes after:
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**02-03: Refresh Token Rotation** — Add /api/auth/refresh with sliding expiry
Final plan in Phase 2
@@ -87,7 +91,7 @@ Add note that this is the last plan and what comes after:
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase 2: Authentication** — JWT login flow with refresh tokens
@@ -116,7 +120,7 @@ Show completion status before next action:
3/3 plans executed
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase 3: Core Features** — User dashboard, settings, and data export
@@ -141,7 +145,7 @@ When there's no clear primary action:
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase 3: Core Features** — User dashboard, settings, and data export
@@ -165,7 +169,7 @@ When there's no clear primary action:
All 4 phases shipped
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Start v1.1** — questioning → research → requirements → roadmap
diff --git a/get-shit-done/workflows/add-phase.md b/get-shit-done/workflows/add-phase.md
index e438c24a5..e8fc06eba 100644
--- a/get-shit-done/workflows/add-phase.md
+++ b/get-shit-done/workflows/add-phase.md
@@ -83,7 +83,7 @@ Roadmap updated: .planning/ROADMAP.md
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase {N}: {description}**
diff --git a/get-shit-done/workflows/add-tests.md b/get-shit-done/workflows/add-tests.md
index 527450237..ce87b3c9f 100644
--- a/get-shit-done/workflows/add-tests.md
+++ b/get-shit-done/workflows/add-tests.md
@@ -315,7 +315,7 @@ Present next steps:
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
{if bugs discovered:}
**Fix discovered bugs:** `/gsd-quick fix the {N} test failures discovered in phase ${phase_number}`
diff --git a/get-shit-done/workflows/audit-milestone.md b/get-shit-done/workflows/audit-milestone.md
index c56f34307..282836fe1 100644
--- a/get-shit-done/workflows/audit-milestone.md
+++ b/get-shit-done/workflows/audit-milestone.md
@@ -225,7 +225,7 @@ All requirements covered. Cross-phase integration verified. E2E flows complete.
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Complete milestone** — archive and tag
@@ -270,7 +270,7 @@ Phases needing validation: run `/gsd-validate-phase {N}` for each flagged phase.
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Plan gap closure** — create phases to complete milestone
diff --git a/get-shit-done/workflows/complete-milestone.md b/get-shit-done/workflows/complete-milestone.md
index c287671e5..6f1ea3fc7 100644
--- a/get-shit-done/workflows/complete-milestone.md
+++ b/get-shit-done/workflows/complete-milestone.md
@@ -780,7 +780,7 @@ Tag: v[X.Y]
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Start Next Milestone** — questioning → research → requirements → roadmap
diff --git a/get-shit-done/workflows/discuss-phase-assumptions.md b/get-shit-done/workflows/discuss-phase-assumptions.md
index 985b3a7be..265eba4f9 100644
--- a/get-shit-done/workflows/discuss-phase-assumptions.md
+++ b/get-shit-done/workflows/discuss-phase-assumptions.md
@@ -593,7 +593,7 @@ Created: .planning/phases/${PADDED_PHASE}-${SLUG}/${PADDED_PHASE}-CONTEXT.md
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase ${PHASE}: {phase_name}** — {Goal from ROADMAP.md}
diff --git a/get-shit-done/workflows/discuss-phase.md b/get-shit-done/workflows/discuss-phase.md
index 7016be949..7ab323120 100644
--- a/get-shit-done/workflows/discuss-phase.md
+++ b/get-shit-done/workflows/discuss-phase.md
@@ -1038,7 +1038,7 @@ Created: .planning/phases/${PADDED_PHASE}-${SLUG}/${PADDED_PHASE}-CONTEXT.md
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase ${PHASE}: [Name]** — [Goal from ROADMAP.md]
diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md
index 9b077a5da..4308ea78e 100644
--- a/get-shit-done/workflows/execute-phase.md
+++ b/get-shit-done/workflows/execute-phase.md
@@ -1335,7 +1335,7 @@ Items saved to `{phase_num}-HUMAN-UAT.md` — they will appear in `/gsd-progress
{Gap summaries from VERIFICATION.md}
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
`/clear` then:
diff --git a/get-shit-done/workflows/map-codebase.md b/get-shit-done/workflows/map-codebase.md
index c7b1e7a2f..75b770542 100644
--- a/get-shit-done/workflows/map-codebase.md
+++ b/get-shit-done/workflows/map-codebase.md
@@ -345,7 +345,7 @@ Created .planning/codebase/:
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Initialize project** — use codebase context for planning
diff --git a/get-shit-done/workflows/new-milestone.md b/get-shit-done/workflows/new-milestone.md
index ad1e90e1e..d82260bd9 100644
--- a/get-shit-done/workflows/new-milestone.md
+++ b/get-shit-done/workflows/new-milestone.md
@@ -514,7 +514,7 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs: create milest
**[N] phases** | **[X] requirements** | Ready to build ✓
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase [N]: [Phase Name]** — [Goal]
diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md
index be37611f6..906eb3817 100644
--- a/get-shit-done/workflows/new-project.md
+++ b/get-shit-done/workflows/new-project.md
@@ -1192,7 +1192,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
```
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase 1: [Phase Name]** — [Goal from ROADMAP.md]
@@ -1214,7 +1214,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
```
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase 1: [Phase Name]** — [Goal from ROADMAP.md]
diff --git a/get-shit-done/workflows/plan-milestone-gaps.md b/get-shit-done/workflows/plan-milestone-gaps.md
index 4e127fb07..2c6eb4b03 100644
--- a/get-shit-done/workflows/plan-milestone-gaps.md
+++ b/get-shit-done/workflows/plan-milestone-gaps.md
@@ -159,7 +159,7 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs(roadmap): add
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Plan first gap closure phase**
diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md
index ff4d54cd0..9a04c71f7 100644
--- a/get-shit-done/workflows/plan-phase.md
+++ b/get-shit-done/workflows/plan-phase.md
@@ -1228,7 +1228,7 @@ Verification: {Passed | Passed with override | Skipped}
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Execute Phase {X}** — run all {N} plans
diff --git a/get-shit-done/workflows/progress.md b/get-shit-done/workflows/progress.md
index b3ebbc4c6..1d6e13c1b 100644
--- a/get-shit-done/workflows/progress.md
+++ b/get-shit-done/workflows/progress.md
@@ -211,7 +211,7 @@ Read its `` section.
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**{phase}-{plan}: [Plan Name]** — [objective summary from PLAN.md]
@@ -240,7 +240,7 @@ PHASE_HAS_UI=$(echo "$PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true" ||
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase {N}: {Name}** — {Goal from ROADMAP.md}
✓ Context gathered, ready to plan
@@ -257,7 +257,7 @@ PHASE_HAS_UI=$(echo "$PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true" ||
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase {N}: {Name}** — {Goal from ROADMAP.md}
@@ -280,7 +280,7 @@ PHASE_HAS_UI=$(echo "$PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true" ||
```
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase {N}: {Name}** — {Goal from ROADMAP.md}
@@ -388,7 +388,7 @@ NEXT_HAS_UI=$(echo "$NEXT_PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true
## ✓ Phase {Z} Complete
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase {Z+1}: {Name}** — {Goal from ROADMAP.md}
@@ -413,7 +413,7 @@ NEXT_HAS_UI=$(echo "$NEXT_PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true
## ✓ Phase {Z} Complete
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase {Z+1}: {Name}** — {Goal from ROADMAP.md}
@@ -441,7 +441,7 @@ NEXT_HAS_UI=$(echo "$NEXT_PHASE_SECTION" | grep -qi "UI hint.*yes" && echo "true
All {N} phases finished!
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Complete Milestone** — archive and prepare for next
@@ -472,7 +472,7 @@ Read MILESTONES.md to find the last completed milestone version.
Ready to plan the next milestone.
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Start Next Milestone** — questioning → research → requirements → roadmap
diff --git a/get-shit-done/workflows/resume-project.md b/get-shit-done/workflows/resume-project.md
index 1c91732ee..71088c5c0 100644
--- a/get-shit-done/workflows/resume-project.md
+++ b/get-shit-done/workflows/resume-project.md
@@ -231,7 +231,7 @@ Based on user selection, route to appropriate workflow:
```
---
- ## ▶ Next Up
+ ## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**{phase}-{plan}: [Plan Name]** — [objective from PLAN.md]
@@ -245,7 +245,7 @@ Based on user selection, route to appropriate workflow:
```
---
- ## ▶ Next Up
+ ## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase [N]: [Name]** — [Goal from ROADMAP.md]
diff --git a/get-shit-done/workflows/transition.md b/get-shit-done/workflows/transition.md
index 0d87281c8..37e8c48be 100644
--- a/get-shit-done/workflows/transition.md
+++ b/get-shit-done/workflows/transition.md
@@ -471,7 +471,7 @@ Exit skill and invoke SlashCommand("/gsd-discuss-phase [X+1] --auto ${GSD_WS}")
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase [X+1]: [Name]** — [Goal from ROADMAP.md]
@@ -495,7 +495,7 @@ Exit skill and invoke SlashCommand("/gsd-discuss-phase [X+1] --auto ${GSD_WS}")
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Phase [X+1]: [Name]** — [Goal from ROADMAP.md]
✓ Context gathered, ready to plan
@@ -606,7 +606,7 @@ Exit skill and invoke SlashCommand("/gsd-complete-milestone {version} ${GSD_WS}"
---
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Complete Milestone {version}** — archive and prepare for next
diff --git a/get-shit-done/workflows/ui-phase.md b/get-shit-done/workflows/ui-phase.md
index c736474ef..2c7ec3a46 100644
--- a/get-shit-done/workflows/ui-phase.md
+++ b/get-shit-done/workflows/ui-phase.md
@@ -261,7 +261,7 @@ Dimensions: 6/6 passed
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
{If CONTEXT.md exists for this phase:}
**Plan Phase {N}** — planner will use UI-SPEC.md as design context
diff --git a/get-shit-done/workflows/undo.md b/get-shit-done/workflows/undo.md
index 5eb294f98..375ef46a4 100644
--- a/get-shit-done/workflows/undo.md
+++ b/get-shit-done/workflows/undo.md
@@ -279,7 +279,7 @@ Show next steps:
```
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Review state** — verify project is in expected state after revert
diff --git a/get-shit-done/workflows/verify-work.md b/get-shit-done/workflows/verify-work.md
index d30020cc8..3b4c02d0e 100644
--- a/get-shit-done/workflows/verify-work.md
+++ b/get-shit-done/workflows/verify-work.md
@@ -676,7 +676,7 @@ Plans verified and ready for execution.
───────────────────────────────────────────────────────────────
-## ▶ Next Up
+## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
**Execute fixes** — run fix plans
diff --git a/tests/init.test.cjs b/tests/init.test.cjs
index b8d5036f6..96307891a 100644
--- a/tests/init.test.cjs
+++ b/tests/init.test.cjs
@@ -1426,6 +1426,87 @@ describe('#2192: init plan-phase includes auto-advance config to prevent separat
});
});
+// ─────────────────────────────────────────────────────────────────────────────
+// withProjectRoot: project identity injection (#1948)
+// ─────────────────────────────────────────────────────────────────────────────
+
+describe('withProjectRoot project identity', () => {
+ let tmpDir;
+
+ beforeEach(() => {
+ tmpDir = createTempProject();
+ });
+
+ afterEach(() => {
+ cleanup(tmpDir);
+ });
+
+ test('injects project_code when config.project_code is set', () => {
+ fs.writeFileSync(
+ path.join(tmpDir, '.planning', 'config.json'),
+ JSON.stringify({ project_code: 'CK' })
+ );
+
+ const result = runGsdTools(['init', 'quick', 'test task'], tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `Command failed: ${result.error}`);
+
+ const output = JSON.parse(result.output);
+ assert.strictEqual(output.project_code, 'CK');
+ });
+
+ test('injects project_title extracted from PROJECT.md H1', () => {
+ fs.writeFileSync(
+ path.join(tmpDir, '.planning', 'config.json'),
+ JSON.stringify({ project_code: 'CK' })
+ );
+ fs.writeFileSync(
+ path.join(tmpDir, '.planning', 'PROJECT.md'),
+ '# CareKit\n\nA care management platform.\n'
+ );
+
+ const result = runGsdTools(['init', 'quick', 'test task'], tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `Command failed: ${result.error}`);
+
+ const output = JSON.parse(result.output);
+ assert.strictEqual(output.project_title, 'CareKit');
+ });
+
+ test('omits project_code and project_title when project_code is not set', () => {
+ fs.writeFileSync(
+ path.join(tmpDir, '.planning', 'config.json'),
+ JSON.stringify({})
+ );
+
+ const result = runGsdTools(['init', 'quick', 'test task'], tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `Command failed: ${result.error}`);
+
+ const output = JSON.parse(result.output);
+ assert.strictEqual(output.project_code, undefined,
+ 'project_code should not be present when not configured');
+ // project_title may or may not be present depending on PROJECT.md existence,
+ // but without project_code the workflow omits the identity suffix entirely
+ });
+
+ test('omits project_title when PROJECT.md does not exist', () => {
+ fs.writeFileSync(
+ path.join(tmpDir, '.planning', 'config.json'),
+ JSON.stringify({ project_code: 'CK' })
+ );
+ // Ensure no PROJECT.md exists (createTempProject doesn't create one)
+ const projectMdPath = path.join(tmpDir, '.planning', 'PROJECT.md');
+ if (fs.existsSync(projectMdPath)) fs.unlinkSync(projectMdPath);
+
+ const result = runGsdTools(['init', 'quick', 'test task'], tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `Command failed: ${result.error}`);
+
+ const output = JSON.parse(result.output);
+ assert.strictEqual(output.project_code, 'CK',
+ 'project_code should still be present');
+ assert.strictEqual(output.project_title, undefined,
+ 'project_title should not be present when PROJECT.md is missing');
+ });
+});
+
// ─────────────────────────────────────────────────────────────────────────────
// roadmap analyze command
// ─────────────────────────────────────────────────────────────────────────────