jeo-code 0.8.27 → 0.8.28

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/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
8
8
 
9
+ ## [0.8.28] - 2026-07-16
10
+ _Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features._
11
+
12
+ ### Added
13
+ - **Dynamic welcome UI right-side table** (`src/tui/components/welcome.ts`, `src/commands/launch.ts`) — replaced the static metadata table with dynamic sections: "What's New" (latest changelog items), "Flow keys" (keyboard shortcuts), "Project pulse" (TypeScript language service status), and "Session trail" (recent sessions with relative time ago formatting).
14
+
15
+ ### Verified
16
+ - Full `bun test` — all tests pass.
17
+ - `bun run typecheck` — no errors.
18
+
9
19
  ## [0.8.27] - 2026-07-16
10
20
  _Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops._
11
21
 
package/README.ja.md CHANGED
@@ -283,11 +283,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
283
283
  ## 変更履歴 (Changelog)
284
284
 
285
285
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
286
+ - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
286
287
  - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
287
288
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
288
289
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
289
290
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
290
- - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
291
291
 
292
292
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
293
293
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -284,11 +284,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
284
284
  ## 변경 이력 (Changelog)
285
285
 
286
286
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
287
+ - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
287
288
  - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
288
289
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
289
290
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
290
291
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
291
- - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
292
292
 
293
293
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
294
294
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -291,11 +291,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
291
291
  ## Changelog
292
292
 
293
293
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
294
+ - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
294
295
  - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
295
296
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
296
297
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
297
298
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
298
- - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
299
299
 
300
300
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
301
301
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -283,11 +283,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
283
283
  ## 更新日志 (Changelog)
284
284
 
285
285
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
286
+ - **[0.8.28]** (2026-07-16) — Improved the welcome UI's right-side table to display dynamic sections (What's New, Flow keys, Project pulse, and Session trail) matching gajae-code (gjc) >= 0.8.0 features.
286
287
  - **[0.8.27]** (2026-07-16) — Repeat-read recovery now survives both context-overflow and refusal-result elision without reopening mutating-call loops.
287
288
  - **[0.8.26]** (2026-07-14) — Model catalog cleanup (drop sub-4.6 Anthropic `claude-haiku-4-5` from the exposed catalog/aliases per user direction) plus a focused gap analysis against `gajae-code`'s (gjc) 0.8.0→0.10.1 release history (~200 PRs) — most of gjc's surface (RPC/ACP/coordinator-mcp/IRC-sidebar/psmux/Windows-team) doesn't exist in jeo's smaller architecture and was ruled out with direct evidence; a few genuinely applicable gaps were found and closed.
288
289
  - **[0.8.25]** (2026-07-13) — Post-audit follow-up: 0.8.24's fixture-repair subagent flagged (but, correctly, did not itself fix — out of its assigned scope) a case-sensitivity bug in the new PlanSchema maker→verifier ordering rule. Fixed.
289
290
  - **[0.8.24]** (2026-07-13) — Follow-up gap analysis against the same external "self-improving agent system" framework (Fable-5-style loops), continuing 0.8.23's audit into 4 more primitives: independent-verifier ENFORCEMENT (0.8.23 had the gate logic but nothing forced a plan to actually contain one), model-tier safety-boundary fallback (a false-positive safety refusal previously backed off forever on the SAME model instead of trying a genuinely different one — the pattern already shipped for rate limits), memory confidence that was self-assigned once instead of earned via a real verification event, and Dynamic Workflows (jeo had parallel fan-out but no sequential composition or real control flow across subagent calls). Closed all 4, the last one non-trivially: an in-process `AsyncFunction` (the same pattern `browser {run}` already ships) cannot be given a real wall-clock timeout — a synchronous bug in agent-authored code blocks jeo's own event loop forever, and `Promise.race` can never preempt it (verified empirically). Fixed by running Dynamic Workflows scripts in an isolated `Worker` (a genuinely separate OS thread `worker.terminate()` can preempt unconditionally, mirroring `bashTool`'s own SIGTERM/SIGKILL escalation on a spawned process) with `task()` bridged back to the main thread over a `postMessage` RPC — config/credentials never leave the main thread, and every dispatch (however the script shapes it) stays bounded by a real concurrency semaphore, not just a per-call count cap.
290
- - **[0.8.23]** (2026-07-12) — Gap analysis against an external "self-improving agent system" framework (Fable-5-style loops: independent verifiers, memory compounding, model-tier cost discipline, and scheduled routines) found jeo-code already had strong equivalents for most primitives — but 3 real gaps and 1 deliberately-scoped-safe gap. Closed all 4: skills that were "hand-authored, read-mostly, zero learning" now compound from real session failures; screenshots that were captured but never judged now close a real vision-verify loop; the `/goal` verifier and bulk fan-out dispatch that silently rode the same full-price model as the work they graded/executed now default to a cheap tier; and "runs without your laptop" is now achievable via a generated GitHub Actions template wrapping jeo's existing headless mode — with zero new attack surface inside jeo-code itself (no code-exec sandbox, no in-process scheduler/webhook receiver, both deliberately rejected as out of scope). Also absorbs 3 fixes shipped after 0.8.22 but never changelogged: a rate-limit fast-fallback audit that closed 4 real gaps, plus the cross-file `mock.module()` test-isolation class this release's own test additions collided with and fixed at its root cause.
291
291
 
292
292
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
293
293
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.8.27",
3
+ "version": "0.8.28",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -2129,6 +2129,25 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2129
2129
  const activeStartModel = sessionModel || defaultModel;
2130
2130
  const { provider: startProvider } = await describeModel(activeStartModel);
2131
2131
  const welcomeTheme = resolveTheme(process.env);
2132
+
2133
+ const formatTimeAgo = (isoString?: string): string => {
2134
+ if (!isoString) return "unknown";
2135
+ const diffMs = Date.now() - new Date(isoString).getTime();
2136
+ const diffMins = Math.floor(diffMs / 60000);
2137
+ if (diffMins < 1) return "just now";
2138
+ if (diffMins < 60) return `${diffMins}m ago`;
2139
+ const diffHours = Math.floor(diffMins / 60);
2140
+ if (diffHours < 24) return `${diffHours}h ago`;
2141
+ const diffDays = Math.floor(diffHours / 24);
2142
+ return `${diffDays}d ago`;
2143
+ };
2144
+
2145
+ const sessions = await listSessions(cwd || process.cwd());
2146
+ const recentSessions = sessions.map(s => ({
2147
+ name: s.title || s.preview || s.id,
2148
+ timeAgo: formatTimeAgo(s.timestamp),
2149
+ }));
2150
+
2132
2151
  const welcomeData = {
2133
2152
  version: pkg.version,
2134
2153
  model: activeStartModel,
@@ -2137,6 +2156,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2137
2156
  thinking: sessionThinking ?? "medium",
2138
2157
  sessionId,
2139
2158
  contextFiles: contextFiles.map(f => f.path),
2159
+ recentSessions,
2140
2160
  cols: terminalSize().cols,
2141
2161
  unicode: supportsUnicode(),
2142
2162
  color: welcomeTheme.color,
@@ -4,7 +4,24 @@ import * as os from "node:os";
4
4
  import { renderForgeMark, FORGE_MARK_ART_GRAND } from "./ascii-art";
5
5
  import { truncate, isTTY } from "../terminal";
6
6
  import { detectColorLevel, ColorLevel, visibleWidth } from "./color";
7
+ import { parseChangelogSections, changelogText } from "../../util/whats-new";
7
8
 
9
+ function getLatestChangelogItems(): { version: string; items: string[] } {
10
+ try {
11
+ const sections = parseChangelogSections(changelogText);
12
+ if (sections.length > 0) {
13
+ const latest = sections[0];
14
+ const items: string[] = [];
15
+ for (const g of latest.groups) {
16
+ for (const item of g.items) {
17
+ items.push(item);
18
+ }
19
+ }
20
+ return { version: latest.version, items };
21
+ }
22
+ } catch {}
23
+ return { version: "", items: [] };
24
+ }
8
25
  export interface WelcomeData {
9
26
  version: string;
10
27
  model: string;
@@ -47,29 +64,99 @@ function shortenPath(p: string | undefined, maxWidth: number, unicode: boolean):
47
64
  return truncate(`${ell}${s.slice(-Math.max(1, maxWidth - visibleWidth(ell)))}`, maxWidth);
48
65
  }
49
66
 
50
- function metaRows(d: WelcomeData, width: number, unicode: boolean, useColor: boolean): string[] {
51
- const labelWidth = 8;
52
- const valueWidth = Math.max(1, width - labelWidth - 3);
53
- const key = useColor ? chalk.dim : (s: string) => s;
54
- const rows: string[] = [];
55
- const push = (label: string, value: string): void => {
56
- const lhs = key(padLine(label, labelWidth, "right"));
57
- const rhs = truncate(value || "-", valueWidth);
58
- rows.push(padLine(`${lhs} │ ${rhs}`, width));
67
+ function renderRightColumn(d: WelcomeData, width: number, unicode: boolean, useColor: boolean): string[] {
68
+ const accent = useColor ? (d.accent ?? chalk.cyan) : (s: string) => s;
69
+ const dim = useColor ? chalk.dim : (s: string) => s;
70
+ const gray = useColor ? chalk.gray : (s: string) => s;
71
+ const bold = useColor ? chalk.bold : (s: string) => s;
72
+ const bulletChar = unicode ? "•" : "-";
73
+ const hChar = unicode ? "─" : "-";
74
+ const sepLine = dim(hChar.repeat(width));
75
+
76
+ const lines: string[] = [];
77
+
78
+ // 1. What's New
79
+ lines.push(padLine(` ${bold(accent("What's New"))}`, width));
80
+ const changelog = getLatestChangelogItems();
81
+ if (changelog.items.length > 0) {
82
+ const prefix = ` ${bulletChar} `;
83
+ const textWidth = Math.max(1, width - visibleWidth(prefix));
84
+ const shown = changelog.items.slice(0, 3);
85
+ for (const item of shown) {
86
+ const text = visibleWidth(item) > textWidth ? truncate(item, textWidth) : item;
87
+ lines.push(padLine(`${dim(prefix)}${gray(text)}`, width));
88
+ }
89
+ } else {
90
+ lines.push(padLine(gray(" Ready for your next prompt"), width));
91
+ }
92
+
93
+ lines.push(padLine(sepLine, width));
94
+
95
+ // 2. Flow keys
96
+ lines.push(padLine(` ${bold(accent("Flow keys"))}`, width));
97
+ const FLOW_KEY_ITEMS = [
98
+ { key: "/", label: "commands" },
99
+ { key: "tab", label: "complete" },
100
+ { key: "ctrl+l", label: "model" },
101
+ { key: "ctrl+c", label: "cancel" },
102
+ { key: "alt+enter", label: "newline" },
103
+ ];
104
+ const flowKeyItemText = (item: { key: string; label: string }) => {
105
+ const k = useColor ? chalk.dim(item.key) : item.key;
106
+ const l = useColor ? chalk.gray(item.label) : item.label;
107
+ return `${k} ${l}`;
59
108
  };
60
- push("version", `v${d.version}`);
61
- push("folder", shortenPath(d.cwd, valueWidth, unicode));
62
- push("model", d.model);
63
- if (d.provider) push("provider", d.provider);
64
- if (d.thinking) push("thinking", d.thinking);
65
- if (d.sessionId) push("session", d.sessionId.slice(0, 8));
66
-
67
- const files = d.contextFiles ?? [];
68
- if (files.length > 0 && width >= 28) {
69
- const shown = files.slice(0, width >= 34 ? 3 : 2).map(f => path.basename(f));
70
- push("files", shown.join(", ") + (files.length > shown.length ? ` +${files.length - shown.length}` : ""));
109
+ const dot = unicode ? "·" : "*";
110
+ const flowSep = ` ${dim(dot)} `;
111
+ let currentFlowLine = "";
112
+ const flowLines: string[] = [];
113
+ for (const item of FLOW_KEY_ITEMS) {
114
+ const segment = flowKeyItemText(item);
115
+ const next = currentFlowLine ? `${currentFlowLine}${flowSep}${segment}` : segment;
116
+ if (currentFlowLine && visibleWidth(next) > width - 2) {
117
+ flowLines.push(` ${currentFlowLine}`);
118
+ currentFlowLine = segment;
119
+ } else {
120
+ currentFlowLine = next;
121
+ }
122
+ }
123
+ if (currentFlowLine) flowLines.push(` ${currentFlowLine}`);
124
+ for (const line of flowLines.slice(0, 2)) {
125
+ lines.push(padLine(line, width));
71
126
  }
72
- return rows;
127
+
128
+ lines.push(padLine(sepLine, width));
129
+
130
+ // 3. Project pulse
131
+ lines.push(padLine(` ${bold(accent("Project pulse"))}`, width));
132
+ const dotChar = unicode ? "●" : "*";
133
+ const lspStatus = useColor ? chalk.green(dotChar) : dotChar;
134
+ lines.push(padLine(` ${lspStatus} ${gray("TypeScript")} ${dim("ready")}`, width));
135
+
136
+ lines.push(padLine(sepLine, width));
137
+
138
+ // 4. Session trail
139
+ lines.push(padLine(` ${bold(accent("Session trail"))}`, width));
140
+ const recent = d.recentSessions ?? [];
141
+ if (recent.length > 0) {
142
+ const bulletPrefix = ` ${bulletChar} `;
143
+ const prefixWidth = visibleWidth(bulletPrefix);
144
+ for (const session of recent.slice(0, 3)) {
145
+ const timeSuffixRaw = ` (${session.timeAgo})`;
146
+ const timeWidth = visibleWidth(timeSuffixRaw);
147
+ const nameBudget = Math.max(1, width - prefixWidth - timeWidth);
148
+ const nameVis = visibleWidth(session.name);
149
+ const name = nameVis > nameBudget ? truncate(session.name, nameBudget) : session.name;
150
+ const bullet = useColor ? chalk.dim(bulletPrefix) : bulletPrefix;
151
+ const sName = useColor ? chalk.gray(name) : name;
152
+ const sTime = useColor ? chalk.dim(timeSuffixRaw) : timeSuffixRaw;
153
+ lines.push(padLine(`${bullet}${sName}${sTime}`, width));
154
+ }
155
+ } else {
156
+ lines.push(padLine(gray(" No saved sessions"), width));
157
+ }
158
+
159
+ return lines;
73
160
  }
74
161
 
75
162
  function centerColumn(lines: string[], width: number): string[] {
@@ -173,13 +260,9 @@ export function renderWelcome(d: WelcomeData): string[] {
173
260
 
174
261
  let content: string[];
175
262
  if (wideMeta) {
176
- const tableTitle = useColor ? chalk.bold("workspace") : "workspace";
177
263
  const rightContent = [
178
264
  "",
179
- padLine(tableTitle, rightWidth, "center"),
180
- (useColor ? chalk.dim : (s: string) => s)(padLine("forge context", rightWidth, "center")),
181
- "",
182
- ...metaRows(d, rightWidth, unicode, useColor),
265
+ ...renderRightColumn(d, rightWidth, unicode, useColor),
183
266
  "",
184
267
  ];
185
268
  content = zipColumns(centerColumn(leftContent, leftWidth), rightContent, leftWidth, gap, rightWidth);
@@ -1,5 +1,6 @@
1
1
  import pkg from "../../package.json";
2
2
  import changelogText from "../../CHANGELOG.md" with { type: "text" };
3
+ export { changelogText };
3
4
  import { compareVersions } from "../commands/update";
4
5
  import * as os from "node:os";
5
6
  import * as path from "node:path";