diff --git a/.changeset/clever-eagles-jump.md b/.changeset/clever-eagles-jump.md
new file mode 100644
index 000000000..3ebbb7211
--- /dev/null
+++ b/.changeset/clever-eagles-jump.md
@@ -0,0 +1,5 @@
+---
+type: Changed
+pr: 3789
+---
+**Section separators now render responsively instead of wrapping** — stage banners, checkpoints, completion and error panels used fixed-width runs of box-drawing characters (a 53-column `━` rule, a 62-column `╔═╗` box). In a narrower pane those runs wrap and the border comes apart from the heading it framed. GSD now emits Markdown headings and `---` thematic breaks, which adapt to the available width in every runtime. (#3028)
diff --git a/agents/gsd-framework-selector.md b/agents/gsd-framework-selector.md
index b9c9d5d99..830cafcdf 100644
--- a/agents/gsd-framework-selector.md
+++ b/agents/gsd-framework-selector.md
@@ -134,9 +134,7 @@ FRAMEWORK_RECOMMENDATION:
Display to user:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- FRAMEWORK RECOMMENDATION
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### FRAMEWORK RECOMMENDATION
◆ Primary Pick: {framework}
{rationale}
diff --git a/commands/gsd/quick.md b/commands/gsd/quick.md
index 35b89b57d..e4597bb87 100644
--- a/commands/gsd/quick.md
+++ b/commands/gsd/quick.md
@@ -86,12 +86,14 @@ For each directory found:
Display format:
```
Quick Tasks
-────────────────────────────────────────────────────────────
+
+---
slug date status
backup-s3-policy 2026-04-10 in-progress
auth-token-refresh-fix 2026-04-09 complete ✓
update-node-deps 2026-04-08 abandoned? (>7 days, no summary)
-────────────────────────────────────────────────────────────
+
+---
3 tasks (1 complete, 2 incomplete/in-progress)
```
@@ -113,12 +115,14 @@ If no directory found, print `No quick task found with slug: {SLUG}` and stop.
Read PLAN.md and SUMMARY.md (if exists) for the given slug. Display:
```
Quick Task: {slug}
-─────────────────────────────────────
+
+---
Plan file: .planning/quick/{dir}/PLAN.md
Status: {status from SUMMARY.md frontmatter, or "no summary yet"}
Description: {first non-empty line from PLAN.md after frontmatter}
Last action: {last meaningful line of SUMMARY.md, or "none"}
-─────────────────────────────────────
+
+---
Resume with: /gsd:quick resume {slug}
```
diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md
index 5e4da0dc9..b919b26e0 100644
--- a/docs/USER-GUIDE.md
+++ b/docs/USER-GUIDE.md
@@ -11,6 +11,7 @@ A narrative companion guide to GSD Core — orient yourself here, then follow th
- [Slash-command forms](#slash-command-forms-hyphen-vs-colon)
- [Namespace routing primer](#namespace-routing-primer-gsd-ns--v140)
+- [Reading GSD's output](#reading-gsds-output)
- [Project lifecycle overview](#project-lifecycle-overview)
- [Workflow Diagrams](#workflow-diagrams)
- [UI Design Contract](#ui-design-contract)
@@ -73,6 +74,66 @@ On the seven nesting runtimes listed above, upgrading to v1.40 changes skill inv
---
+## Reading GSD's output
+
+GSD marks its sections with Markdown, not with drawn borders. There are exactly
+three forms, and every workflow, checkpoint and report uses them:
+
+| You see | It means |
+|---|---|
+| `### GSD ► {STAGE NAME}` | A major workflow transition — planning, executing a wave, verifying, completing |
+| `### CHECKPOINT: {Type}` | GSD is waiting on you. The bolded `**→ …**` line at the bottom tells you what to type |
+| `---` | A break between two sections — most often before the **▶ Next Up** block at the end of a completion |
+
+Panels that used to be drawn with box characters are now a heading followed by
+their rows as ordinary lines. A checkpoint reads:
+
+```
+### CHECKPOINT: Verification Required
+
+Progress: 5/8 tasks complete
+Task: Responsive dashboard layout
+
+How to verify:
+ 1. Visit: http://localhost:3000/dashboard
+
+---
+
+**→ YOUR ACTION: Type "approved" or describe issues**
+```
+
+### Why there are no drawn borders
+
+Earlier releases framed stage banners between two 53-character runs
+of `━`, and checkpoints were a 62-column box drawn with `╔`, `║` and `╚`. Those
+runs are ordinary text to whatever renders GSD's output. In a pane narrower than
+the run, the rule wraps and the leftover glyphs land on a second line — so the
+border comes apart from the heading it was framing, and the output looks broken
+rather than merely narrow. Reported against the Codex desktop interface in
+[#3028](https://github.com/open-gsd/gsd-core/issues/3028).
+
+A Markdown heading and a thematic break carry the same structure without
+committing to a width, so they read correctly in a narrow pane and a wide one.
+
+This is unconditional: every runtime gets the same output. The alternative — a
+capability flag that kept line-art for terminal-oriented runtimes — was weighed
+and rejected, because it leaves two output conventions to keep in sync forever
+for a gain that is aesthetic rather than structural. What a terminal loses is the
+drawn frame; what it keeps is the title, the hierarchy and the break, none of
+which depended on the frame. The reasoning is recorded in
+`gsd-core/references/ui-brand.md § Why this is unconditional, not per-runtime`.
+If you run GSD in a host where the heading form reads worse than the old boxes
+did, that is worth reporting — it is the evidence that would justify the flag.
+
+Single-cell tokens are unaffected and unchanged: the status symbols
+(`✓ ✗ ◆ ○ ⚠`), the `GSD ►` prefix, and the ten-cell progress gauge
+(`Progress: ████████░░ 80%`) are not runs and do not wrap.
+
+The convention is specified in `gsd-core/references/ui-brand.md` and enforced
+against all shipped content by `tests/responsive-separators.test.cjs`.
+
+---
+
## Project lifecycle overview
The core GSD loop is: **discuss → plan → execute → verify → ship**, repeated per phase. The full step-by-step walkthrough — including example outputs, what files get created, and all the flags in play — is in the dedicated tutorial.
diff --git a/docs/how-to/update-gsd.md b/docs/how-to/update-gsd.md
index 218b03485..188fa79be 100644
--- a/docs/how-to/update-gsd.md
+++ b/docs/how-to/update-gsd.md
@@ -75,9 +75,12 @@ Installed: 1.39.0
Latest: 1.41.0
### What's New
-────────────────────────────────────────────────────────────
+
+---
+
[changelog entries for 1.40.0 and 1.41.0]
-────────────────────────────────────────────────────────────
+
+---
Proceed with update? [Yes, update now / No, cancel]
```
diff --git a/docs/ja-JP/how-to/update-gsd.md b/docs/ja-JP/how-to/update-gsd.md
index f3bf1c5b6..7354f9821 100644
--- a/docs/ja-JP/how-to/update-gsd.md
+++ b/docs/ja-JP/how-to/update-gsd.md
@@ -54,9 +54,12 @@ Installed: 1.39.0
Latest: 1.41.0
### What's New
-────────────────────────────────────────────────────────────
+
+---
+
[changelog entries for 1.40.0 and 1.41.0]
-────────────────────────────────────────────────────────────
+
+---
Proceed with update? [Yes, update now / No, cancel]
```
diff --git a/docs/ko-KR/how-to/update-gsd.md b/docs/ko-KR/how-to/update-gsd.md
index a15924c22..a0a25ccf5 100644
--- a/docs/ko-KR/how-to/update-gsd.md
+++ b/docs/ko-KR/how-to/update-gsd.md
@@ -54,9 +54,12 @@ Installed: 1.39.0
Latest: 1.41.0
### What's New
-────────────────────────────────────────────────────────────
+
+---
+
[changelog entries for 1.40.0 and 1.41.0]
-────────────────────────────────────────────────────────────
+
+---
Proceed with update? [Yes, update now / No, cancel]
```
diff --git a/docs/pt-BR/how-to/update-gsd.md b/docs/pt-BR/how-to/update-gsd.md
index 128261fd4..3efcd324a 100644
--- a/docs/pt-BR/how-to/update-gsd.md
+++ b/docs/pt-BR/how-to/update-gsd.md
@@ -54,9 +54,12 @@ Installed: 1.39.0
Latest: 1.41.0
### What's New
-────────────────────────────────────────────────────────────
+
+---
+
[changelog entries for 1.40.0 and 1.41.0]
-────────────────────────────────────────────────────────────
+
+---
Proceed with update? [Yes, update now / No, cancel]
```
diff --git a/docs/tutorials/your-first-project.md b/docs/tutorials/your-first-project.md
index 9198c5895..c2e267a1c 100644
--- a/docs/tutorials/your-first-project.md
+++ b/docs/tutorials/your-first-project.md
@@ -441,17 +441,15 @@ deliverables into checkpoints. It presents one checkpoint at a time; the first
one looks like this (the test wording depends on what was built):
```text
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: Verification Required ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: Verification Required
**Test 1: Add a to-do**
Running `node todo.js add "buy milk"` creates a pending item without errors.
-──────────────────────────────────────────────────────────────
-Type `pass` or describe what's wrong.
-──────────────────────────────────────────────────────────────
+---
+
+**Type `pass` or describe what's wrong.**
```
Type `pass` when reality matches, or describe what differs. GSD records the
diff --git a/docs/zh-CN/how-to/update-gsd.md b/docs/zh-CN/how-to/update-gsd.md
index 3dc3c740c..7a4aaf804 100644
--- a/docs/zh-CN/how-to/update-gsd.md
+++ b/docs/zh-CN/how-to/update-gsd.md
@@ -54,9 +54,12 @@ Installed: 1.39.0
Latest: 1.41.0
### What's New
-────────────────────────────────────────────────────────────
+
+---
+
[changelog entries for 1.40.0 and 1.41.0]
-────────────────────────────────────────────────────────────
+
+---
Proceed with update? [Yes, update now / No, cancel]
```
diff --git a/docs/zh-CN/references/checkpoints.md b/docs/zh-CN/references/checkpoints.md
index 9be3db209..9e0fdda78 100644
--- a/docs/zh-CN/references/checkpoints.md
+++ b/docs/zh-CN/references/checkpoints.md
@@ -202,9 +202,7 @@
**对于 checkpoint:human-verify:**
```
-╔═══════════════════════════════════════════════════════╗
-║ CHECKPOINT: 需要验证 ║
-╚═══════════════════════════════════════════════════════╝
+### CHECKPOINT: 需要验证
进度: 5/8 任务完成
任务: 响应式仪表板布局
@@ -217,16 +215,14 @@
3. 平板端 (768px): 侧边栏折叠为图标
4. 移动端 (375px): 侧边栏隐藏,出现汉堡菜单
-────────────────────────────────────────────────────────
-→ 你的操作: 输入 "approved" 或描述问题
-────────────────────────────────────────────────────────
+---
+
+**→ 你的操作: 输入 "approved" 或描述问题**
```
**对于 checkpoint:decision:**
```
-╔═══════════════════════════════════════════════════════╗
-║ CHECKPOINT: 需要决策 ║
-╚═══════════════════════════════════════════════════════╝
+### CHECKPOINT: 需要决策
进度: 2/6 任务完成
任务: 选择认证提供商
@@ -248,9 +244,9 @@
优点: 免费,无供应商锁定,广泛采用
缺点: 更多设置工作,自行 DIY 安全更新
-────────────────────────────────────────────────────────
-→ 你的操作: 选择 supabase、clerk 或 nextauth
-────────────────────────────────────────────────────────
+---
+
+**→ 你的操作: 选择 supabase、clerk 或 nextauth**
```
## 认证门控
diff --git a/docs/zh-CN/references/ui-brand.md b/docs/zh-CN/references/ui-brand.md
index 08b7840bd..a45f44036 100644
--- a/docs/zh-CN/references/ui-brand.md
+++ b/docs/zh-CN/references/ui-brand.md
@@ -2,14 +2,43 @@
面向用户的 GSD 输出的视觉模式。编排器通过 @ 引用此文件。
+## 分隔符与横幅
+
+**绝不要输出固定宽度的制表符(box-drawing)长串。** 由 `━`、`─`、`═` 组成的长串,对渲染
+输出的宿主而言只是普通文本。当窗格比该长串更窄时,它会折行,多余的字符落到第二行,于是边框
+与它本应框住的标题就散开了。Markdown 会自适应可用宽度,53 个字符的横线不会。
+
+只有三种形式,没有其他:
+
+| 需求 | 输出 |
+|---|---|
+| 带标题的区块 —— 阶段、检查点、完成、错误 | `### {标题}`(ATX 标题) |
+| 两个区块之间的分隔 | 单独一行的 `---`,**其上方必须有一个空行** |
+| 带边框的信息面板 | `### {标题}`,随后各行以普通文本列出 |
+
+**`---` 上方的空行是关键,不是排版讲究。** 紧跟在一行文本下方的 `---` 会被解析为该行的
+setext 标题下划线,而不是分隔线 —— 那条横线会把上面那行"吞掉"。空行才使它成为分隔线。
+(`---` *下方*的空行是可选的:分隔线是叶子块,后面的内容无论如何都会另起一块。)
+
+**阶段横幅只用标题,不要在其上方加 `---`。** ATX 标题本身就是分隔,而且不会像裸的 `---`
+那样被误解析。
+
+### 为什么是统一形式,而非按运行时区分
+
+曾考虑过引入 `rendersMarkdown` 能力键:终端类运行时保留线条图形,Markdown 宿主使用
+Markdown。该方案被否决 —— 它需要在每个运行时描述符及解析器中新增一个键,并且会留下两套必须
+永远保持同步的输出约定,正是本仓库已记录的"分叉"缺陷类别。在纯终端里,标题与分隔线所承载的
+结构与原来的横线对并无二致,却不必锁定宽度,因此第二套约定并无收益。若将来确实出现需要线条
+图形的运行时,届时再依据该证据添加此键。
+
+---
+
## 阶段横幅
用于主要工作流过渡。
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► {阶段名称}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► {阶段名称}
```
**阶段名称(大写):**
@@ -25,20 +54,18 @@
---
-## 检查点框
+## 检查点面板
-需要用户操作。62 字符宽度。
+需要用户操作。
```
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: {类型} ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: {类型}
{内容}
-──────────────────────────────────────────────────────────────
-→ {操作提示}
-──────────────────────────────────────────────────────────────
+---
+
+**→ {操作提示}**
```
**类型:**
@@ -102,7 +129,7 @@
始终在主要完成后。
```
-───────────────────────────────────────────────────────────────
+---
## ▶ 下一步
@@ -112,22 +139,20 @@
`/clear` 优先 → 全新上下文窗口
-───────────────────────────────────────────────────────────────
+---
**也可选:**
- (根据工作流选填可选命令,例如 `/gsd-progress --next`)
-───────────────────────────────────────────────────────────────
+---
```
---
-## 错误框
+## 错误面板
```
-╔══════════════════════════════════════════════════════════════╗
-║ ERROR ║
-╚══════════════════════════════════════════════════════════════╝
+### ERROR
{错误描述}
@@ -150,8 +175,16 @@
## 反模式
-- 变化的框/横幅宽度
-- 混合横幅样式(`===`、`---`、`***`)
+- 用 `━`、`─`、`═` 的固定宽度长串作分隔符 —— 在窄窗格中会折行
+- 用双线制表符(U+2554、U+2557、U+255A、U+255D、U+2551、U+2560、U+2563)画的边框面板 ——
+ 边框会独立于内容折行。此处以码位命名而非直接展示,因为守卫检查会拒绝这些字符出现在任何
+ 随包分发的内容中。
+- 紧跟在一行文本下方、中间没有空行的 `---` —— 那是 setext 标题下划线而非分隔线,会把上面
+ 那行吞掉
+- 把标题夹在两条横线之间 —— 标题本身就是分隔
+- 混合横幅样式(`===`、`***`)
- 横幅中缺少 `GSD ►` 前缀
- 随机 emoji(`🚀`、`✨`、`💫`)
-- 完成后缺少下一步区块
\ No newline at end of file
+- 完成后缺少下一步区块
+
+由 `tests/responsive-separators.test.cjs` 强制执行。
\ No newline at end of file
diff --git a/gsd-core/references/checkpoints.md b/gsd-core/references/checkpoints.md
index 25d616ec8..ab52164f0 100644
--- a/gsd-core/references/checkpoints.md
+++ b/gsd-core/references/checkpoints.md
@@ -323,9 +323,7 @@ When Claude encounters `type="checkpoint:*"`:
**For checkpoint:human-verify:**
```
-╔═══════════════════════════════════════════════════════╗
-║ CHECKPOINT: Verification Required ║
-╚═══════════════════════════════════════════════════════╝
+### CHECKPOINT: Verification Required
Progress: 5/8 tasks complete
Task: Responsive dashboard layout
@@ -338,16 +336,14 @@ How to verify:
3. Tablet (768px): Sidebar collapses to icons
4. Mobile (375px): Sidebar hidden, hamburger menu appears
-────────────────────────────────────────────────────────
-→ YOUR ACTION: Type "approved" or describe issues
-────────────────────────────────────────────────────────
+---
+
+**→ YOUR ACTION: Type "approved" or describe issues**
```
**For checkpoint:decision:**
```
-╔═══════════════════════════════════════════════════════╗
-║ CHECKPOINT: Decision Required ║
-╚═══════════════════════════════════════════════════════╝
+### CHECKPOINT: Decision Required
Progress: 2/6 tasks complete
Task: Select authentication provider
@@ -369,16 +365,14 @@ Options:
Pros: Free, no vendor lock-in, widely adopted
Cons: More setup work, DIY security updates
-────────────────────────────────────────────────────────
-→ YOUR ACTION: Select supabase, clerk, or nextauth
-────────────────────────────────────────────────────────
+---
+
+**→ YOUR ACTION: Select supabase, clerk, or nextauth**
```
**For checkpoint:human-action:**
```
-╔═══════════════════════════════════════════════════════╗
-║ CHECKPOINT: Action Required ║
-╚═══════════════════════════════════════════════════════╝
+### CHECKPOINT: Action Required
Progress: 3/8 tasks complete
Task: Deploy to Vercel
@@ -393,9 +387,9 @@ What you need to do:
I'll verify: vercel whoami returns your account
-────────────────────────────────────────────────────────
-→ YOUR ACTION: Type "done" when authenticated
-────────────────────────────────────────────────────────
+---
+
+**→ YOUR ACTION: Type "done" when authenticated**
```
diff --git a/gsd-core/references/execute-mvp-tdd.md b/gsd-core/references/execute-mvp-tdd.md
index bba22c9c3..0beb9fc81 100644
--- a/gsd-core/references/execute-mvp-tdd.md
+++ b/gsd-core/references/execute-mvp-tdd.md
@@ -40,9 +40,7 @@ The executor MUST:
2. Emit a structured halt report:
```
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- MVP+TDD GATE TRIPPED — Plan {plan_id}, Task {task_id}
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### MVP+TDD GATE TRIPPED — Plan {plan_id}, Task {task_id}
Reason: {missing_red_commit | red_commit_not_failing | feat_before_test}
diff --git a/gsd-core/references/gate-prompts.md b/gsd-core/references/gate-prompts.md
index feecace8b..94b68548c 100644
--- a/gsd-core/references/gate-prompts.md
+++ b/gsd-core/references/gate-prompts.md
@@ -2,7 +2,7 @@
Reusable prompt patterns for structured gate checks in workflows and agents.
-**For checkpoint box format details, see `gsd-core/references/ui-brand.md`** -- checkpoint boxes use double-line border drawing with 62-character inner width.
+**For checkpoint format details, see `gsd-core/references/ui-brand.md`** -- a checkpoint is a `### CHECKPOINT: {Type}` heading followed by its content, then a `---` break and the bolded action prompt. Never a fixed-width drawn border: it wraps in a narrow pane.
## Rules
diff --git a/gsd-core/references/offer-next.md b/gsd-core/references/offer-next.md
index b808e4d02..003124238 100644
--- a/gsd-core/references/offer-next.md
+++ b/gsd-core/references/offer-next.md
@@ -4,7 +4,6 @@
extraction restores byte-budget headroom the frozen ceiling exists to provide.
-->
-
**Exception:** If `gaps_found`, the `verify_phase_goal` step already presents the gap-closure path (`/gsd:plan-phase {X} --gaps`). No additional routing needed — skip auto-advance.
**No-transition check (spawned by auto-advance chain):**
@@ -41,10 +40,9 @@ STOP. Do not proceed to auto-advance or transition.
**If `--auto` flag present OR `AUTO_MODE` is true (AND verification passed with no gaps):**
```
-╔══════════════════════════════════════════╗
-║ AUTO-ADVANCING → TRANSITION ║
-║ Phase {X} verified, continuing chain ║
-╚══════════════════════════════════════════╝
+### AUTO-ADVANCING → TRANSITION
+
+Phase {X} verified, continuing chain
```
Execute the transition workflow inline (do NOT use Agent — orchestrator context is ~10-15%, transition needs phase completion data already in context):
diff --git a/gsd-core/references/tdd.md b/gsd-core/references/tdd.md
index 92a367240..2e2de42f6 100644
--- a/gsd-core/references/tdd.md
+++ b/gsd-core/references/tdd.md
@@ -289,9 +289,7 @@ When `workflow.tdd_mode` is enabled, the execute-phase orchestrator inserts a co
### Review Checkpoint Format
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- TDD REVIEW — Phase {X}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### TDD REVIEW — Phase {X}
TDD Plans: {count} | Gate violations: {count}
diff --git a/gsd-core/references/ui-brand.md b/gsd-core/references/ui-brand.md
index 9a9676b78..8a70a0e56 100644
--- a/gsd-core/references/ui-brand.md
+++ b/gsd-core/references/ui-brand.md
@@ -2,14 +2,51 @@
Visual patterns for user-facing GSD output. Orchestrators @-reference this file.
+## Separators and Banners
+
+**Never emit a fixed-width run of box-drawing characters.** A run of `━`, `─` or
+`═` is ordinary text to the host that renders your output. In a narrower pane it
+wraps, leaving orphan glyphs on a second line and coming apart from the heading it
+was meant to frame. Markdown adapts to the available width; a 53-character rule
+does not.
+
+Three forms, and nothing else:
+
+| Need | Emit |
+|---|---|
+| A titled section — stage, phase, checkpoint, completion, error | `### {TITLE}` (ATX heading) |
+| A break between two sections | `---` on its own line, **with a blank line above it** |
+| A framed panel of rows | `### {TITLE}` followed by the rows as plain lines |
+
+**The blank line above `---` is load-bearing, not cosmetic.** A `---` placed
+directly under a line of text is parsed as a setext heading underline for that
+line, not as a thematic break — the rule silently swallows the line above it. A
+blank line is what makes it a thematic break. (A blank line *after* `---` is
+optional: a thematic break is a leaf block, so whatever follows starts a new
+block either way. Add one where it reads better.)
+
+**A stage banner is a heading alone — do not put a `---` above it.** An ATX
+heading already separates, and it cannot be misparsed the way a bare `---` can.
+
+### Why this is unconditional, not per-runtime
+
+The alternative considered was a `rendersMarkdown` capability key, keeping
+line-art for terminal-oriented runtimes and Markdown for Markdown hosts. It was
+rejected: it needs a new descriptor key across every runtime plus the resolver,
+and it leaves two output conventions to keep in sync forever — the divergence
+class this repo already has a defect entry for. A heading and a thematic break
+carry the same structure in a plain terminal that a rule pair did, without
+committing to a width, so the second convention buys nothing. If a runtime ever
+turns up that genuinely needs line-art, add the key then, against that evidence.
+
+---
+
## Stage Banners
Use for major workflow transitions.
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► {STAGE NAME}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► {STAGE NAME}
```
**Stage names (uppercase):**
@@ -25,20 +62,18 @@ Use for major workflow transitions.
---
-## Checkpoint Boxes
+## Checkpoint Panels
-User action required. 62-character width.
+User action required.
```
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: {Type} ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: {Type}
{Content}
-──────────────────────────────────────────────────────────────
-→ {ACTION PROMPT}
-──────────────────────────────────────────────────────────────
+---
+
+**→ {ACTION PROMPT}**
```
**Types:**
@@ -60,6 +95,9 @@ User action required. 62-character width.
🎉 Milestone complete (only in banner)
```
+Status symbols are single characters, not runs — they do not wrap and are
+unaffected by the separator rule above.
+
---
## Progress Display
@@ -79,6 +117,9 @@ Tasks: 2/4 complete
Plans: 3/5 complete
```
+The bar itself is a fixed 10-cell gauge, not a separator; it is intentionally
+fixed-width and stays as it is.
+
---
## Spawning Indicators
@@ -104,7 +145,7 @@ Plans: 3/5 complete
Always at end of major completions.
```
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up
@@ -114,23 +155,19 @@ Always at end of major completions.
`{copy-paste command}`
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- `/gsd-alternative-1` — description
- `/gsd-alternative-2` — description
-
-───────────────────────────────────────────────────────────────
```
---
-## Error Box
+## Error Panel
```
-╔══════════════════════════════════════════════════════════════╗
-║ ERROR ║
-╚══════════════════════════════════════════════════════════════╝
+### ERROR
{Error description}
@@ -149,14 +186,21 @@ Always at end of major completions.
| 3 | ○ | 0/2 | 0% |
```
+Table rules use ASCII `-`, never box-drawing characters.
+
---
## Anti-Patterns
-- Varying box/banner widths
-- Mixing banner styles (`===`, `---`, `***`)
+- Fixed-width runs of `━`, `─` or `═` as separators — they wrap in a narrow pane
+- Box panels drawn with double-line box characters (U+2554, U+2557, U+255A, U+255D, U+2551, U+2560, U+2563) — the borders wrap independently of their contents. They are named here by code point rather than shown, because the guard below rejects the characters themselves anywhere in shipped content.
+- A `---` directly under a line of text with no blank line between — that is a setext heading underline, not a break, and it swallows the line above
+- Boxing a heading between two rules — the heading is the separator
+- Mixing banner styles (`===`, `***`)
- Skipping `GSD ►` prefix in banners
- Random emoji (`🚀`, `✨`, `💫`)
- Missing Next Up block after completions
+Enforced by `tests/responsive-separators.test.cjs`.
+
diff --git a/gsd-core/workflows/add-tests.md b/gsd-core/workflows/add-tests.md
index 88803ca23..04b5797d2 100644
--- a/gsd-core/workflows/add-tests.md
+++ b/gsd-core/workflows/add-tests.md
@@ -63,9 +63,7 @@ Exit.
Present banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► ADD TESTS — Phase ${phase_number}: ${phase_name}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► ADD TESTS — Phase ${phase_number}: ${phase_name}
```
@@ -111,7 +109,6 @@ Read each file to verify classification. Don't classify based on filename alone.
Present the classification to the user for confirmation before proceeding:
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
```
@@ -280,9 +277,7 @@ For each approved E2E test:
Create a test coverage report and present to user:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► TEST GENERATION COMPLETE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► TEST GENERATION COMPLETE
## Results
diff --git a/gsd-core/workflows/ai-integration-phase.md b/gsd-core/workflows/ai-integration-phase.md
index 5e24cfbcf..e4b2b5eec 100644
--- a/gsd-core/workflows/ai-integration-phase.md
+++ b/gsd-core/workflows/ai-integration-phase.md
@@ -78,7 +78,6 @@ Continue (non-blocking).
AI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-AI-SPEC.md 2>/dev/null | head -1)
```
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
**If exists:** Use AskUserQuestion:
- header: "Existing AI-SPEC"
@@ -96,9 +95,7 @@ If "Update": continue to step 5.
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AI DESIGN CONTRACT — PHASE {N}: {name}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AI DESIGN CONTRACT — PHASE {N}: {name}
◆ Step 1/4 — Framework Selection...
```
@@ -265,9 +262,7 @@ gsd_run query commit "docs({phase_slug}): generate AI-SPEC.md — {primary_frame
## 12. Display Completion
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AI-SPEC COMPLETE — PHASE {N}: {name}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AI-SPEC COMPLETE — PHASE {N}: {name}
◆ Framework: {primary_framework}
◆ System Type: {system_type}
diff --git a/gsd-core/workflows/audit-milestone.md b/gsd-core/workflows/audit-milestone.md
index 4b919ee8c..9f5008d67 100644
--- a/gsd-core/workflows/audit-milestone.md
+++ b/gsd-core/workflows/audit-milestone.md
@@ -244,7 +244,7 @@ Output this markdown directly (not as a code block). Route based on status:
All requirements covered. Cross-phase integration verified. E2E flows complete.
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -254,7 +254,7 @@ All requirements covered. Cross-phase integration verified. E2E flows complete.
/gsd:complete-milestone {version}
-───────────────────────────────────────────────────────────────
+---
---
@@ -289,7 +289,7 @@ All requirements covered. Cross-phase integration verified. E2E flows complete.
Phases needing validation: run `/gsd:validate-phase {N}` for each flagged phase.
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -310,13 +310,13 @@ For Nyquist-coverage gaps flagged in the table above, prefer running
` if SECURITY.md was flagged) before inserting a new closure phase —
they may close the gap retroactively without a new phase.
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- cat .planning/v{version}-MILESTONE-AUDIT.md — see full report
- /gsd:complete-milestone {version} — proceed anyway (accept tech debt)
-───────────────────────────────────────────────────────────────
+---
---
@@ -338,7 +338,7 @@ All requirements met. No critical blockers. Accumulated tech debt needs review.
### Total: {N} items across {M} phases
-───────────────────────────────────────────────────────────────
+---
## ▶ Options
@@ -356,7 +356,7 @@ Insert a closure phase using the standard chain:
/gsd:plan-phase
/gsd:execute-phase
-───────────────────────────────────────────────────────────────
+---
diff --git a/gsd-core/workflows/autonomous.md b/gsd-core/workflows/autonomous.md
index bc546a3e5..6ad629558 100644
--- a/gsd-core/workflows/autonomous.md
+++ b/gsd-core/workflows/autonomous.md
@@ -106,9 +106,7 @@ Parse JSON for: `milestone_version`, `milestone_name`, `phase_count`, `completed
Display startup banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS
Milestone: {milestone_version} — {milestone_name}
Phases: {phase_count} total, {completed_phases} complete
@@ -175,9 +173,7 @@ Exit cleanly.
**If no incomplete phases remain:**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS ▸ COMPLETE 🎉
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS ▸ COMPLETE 🎉
All phases complete! Nothing left to do.
```
@@ -216,9 +212,7 @@ Extract `phase_name`, `goal`, `success_criteria` from each. Store for use in exe
For the current phase, display the progress banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS ▸ Phase {N}/{T}: {Name} [████░░░░] {P}%
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS ▸ Phase {N}/{T}: {Name} [████░░░░] {P}%
```
Where N is the ROADMAP phase number, T is the milestone `phase_count`, and P = completed milestone phases / T × 100. Use `phase_count`, not remaining phases: phase 63 in a 7-phase milestone is `Phase 63/7`, not `Phase 63/3`. If N > T, render `Phase {N} ({position}/{T})`. Use an 8-character bar with █ and ░.
@@ -594,9 +588,7 @@ Read and execute: `$HOME/.claude/gsd-core/references/autonomous-smart-discuss.md
**If `TO_PHASE` is set and current phase number >= `TO_PHASE`:** The target phase has been reached. Do not iterate further. Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS ▸ --to ${TO_PHASE} REACHED
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS ▸ --to ${TO_PHASE} REACHED
Completed through phase ${TO_PHASE} as requested.
Remaining phases were not executed.
@@ -646,9 +638,7 @@ If all phases complete, proceed to lifecycle step.
**If `ONLY_PHASE` is set:** Skip lifecycle. A single phase does not trigger audit/complete/cleanup. Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS ▸ PHASE ${ONLY_PHASE} COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS ▸ PHASE ${ONLY_PHASE} COMPLETE ✓
Phase ${ONLY_PHASE}: ${PHASE_NAME} — Done
Mode: Single phase (--only)
@@ -664,9 +654,7 @@ Exit cleanly.
Display lifecycle transition banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS ▸ LIFECYCLE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS ▸ LIFECYCLE
All phases complete → Starting lifecycle: audit → complete → cleanup
Milestone: {milestone_version} — {milestone_name}
@@ -755,9 +743,7 @@ Cleanup shows its own dry-run and asks user for approval internally — this is
Display final completion banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS ▸ COMPLETE 🎉
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS ▸ COMPLETE 🎉
Milestone: {milestone_version} — {milestone_name}
Status: Complete ✅
@@ -788,9 +774,7 @@ When any phase operation fails or a blocker is detected, present 3 options via A
**On "Stop autonomous mode":** Display progress summary:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTONOMOUS ▸ STOPPED
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTONOMOUS ▸ STOPPED
Completed: {list of completed phases}
Skipped: {list of skipped phases}
diff --git a/gsd-core/workflows/code-review.md b/gsd-core/workflows/code-review.md
index b86ae66ea..cfe439243 100644
--- a/gsd-core/workflows/code-review.md
+++ b/gsd-core/workflows/code-review.md
@@ -690,11 +690,11 @@ TOTAL=$(echo "$FRONTMATTER" | grep "total:" | head -1 | cut -d: -f2 | xargs)
Display inline summary to user:
```
-═══════════════════════════════════════════════════════════════
+---
Code Review Complete: Phase ${PHASE_NUMBER} (${PHASE_NAME})
-───────────────────────────────────────────────────────────────
+---
Depth: ${REVIEW_DEPTH} (${DEPTH_PROVENANCE})
Files Reviewed: ${FILES_REVIEWED}
@@ -703,10 +703,11 @@ Display inline summary to user:
Critical: ${CRITICAL}
Warning: ${WARNING}
Info: ${INFO}
- ──────────
+
+---
Total: ${TOTAL}
-───────────────────────────────────────────────────────────────
+---
```
If status is "clean":
@@ -735,7 +736,7 @@ grep -A 3 "^### CR-\|^### BL-\|^### WR-" "${REVIEW_PATH}" | head -n 12
**Note on tests:** Automated tests for this command and workflow are planned for Phase 4 (Pipeline Integration & Testing, requirement INFR-03). Phase 2 focuses on correct implementation; Phase 4 adds regression coverage across platforms.
-═══════════════════════════════════════════════════════════════
+---
diff --git a/gsd-core/workflows/debug.md b/gsd-core/workflows/debug.md
index 58e86ee65..ca85849f6 100644
--- a/gsd-core/workflows/debug.md
+++ b/gsd-core/workflows/debug.md
@@ -44,7 +44,8 @@ For each file found, parse frontmatter fields (`status`, `trigger`, `updated`) a
```
Active Debug Sessions
-─────────────────────────────────────────────
+
+---
# Slug Status Updated
1 auth-token-null investigating 2026-04-12
hypothesis: JWT decode fails when token contains nested claims
@@ -53,7 +54,8 @@ Active Debug Sessions
2 form-submit-500 fixing 2026-04-11
hypothesis: Missing null check on req.body.user
next: Verify fix passes regression test
-─────────────────────────────────────────────
+
+---
Run `/gsd:debug continue ` to resume a session.
No sessions? `/gsd:debug ` to start.
```
diff --git a/gsd-core/workflows/diagnose-issues.md b/gsd-core/workflows/diagnose-issues.md
index 3e1c525e0..ab6ee9afb 100644
--- a/gsd-core/workflows/diagnose-issues.md
+++ b/gsd-core/workflows/diagnose-issues.md
@@ -258,9 +258,7 @@ gsd_run query commit "docs({phase_num}): add root causes from diagnosis" --files
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► DIAGNOSIS COMPLETE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► DIAGNOSIS COMPLETE
| Gap (Truth) | Root Cause | Files |
|-------------|------------|-------|
diff --git a/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md b/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md
index 07d0596b6..07018d4aa 100644
--- a/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md
+++ b/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md
@@ -2,9 +2,7 @@
Display banner:
```text
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTO-ADVANCING TO PLAN
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTO-ADVANCING TO PLAN
Context captured (assumptions mode). Launching plan-phase...
```
diff --git a/gsd-core/workflows/discuss-phase/modes/chain.md b/gsd-core/workflows/discuss-phase/modes/chain.md
index 4463a4fa4..11afd986d 100644
--- a/gsd-core/workflows/discuss-phase/modes/chain.md
+++ b/gsd-core/workflows/discuss-phase/modes/chain.md
@@ -48,9 +48,7 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
Banner:
```
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTO-ADVANCING TO PLAN
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTO-ADVANCING TO PLAN
Context captured. Launching plan-phase...
```
@@ -69,9 +67,7 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
- **PHASE COMPLETE** → Full chain succeeded. Display:
```
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PHASE ${PHASE} COMPLETE
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PHASE ${PHASE} COMPLETE
Auto-advance pipeline finished: discuss → plan → execute
diff --git a/gsd-core/workflows/do.md b/gsd-core/workflows/do.md
index 8b49d6856..3e250f5f8 100644
--- a/gsd-core/workflows/do.md
+++ b/gsd-core/workflows/do.md
@@ -18,7 +18,6 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/d
**Check for input.**
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
If `$ARGUMENTS` is empty, ask via AskUserQuestion:
@@ -85,9 +84,7 @@ Which approach fits better?
**Show the routing decision.**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► ROUTING
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► ROUTING
**Input:** {first 80 chars of $ARGUMENTS}
**Routing to:** {chosen command}
diff --git a/gsd-core/workflows/eval-review.md b/gsd-core/workflows/eval-review.md
index 8b121c6e5..2ea0c6026 100644
--- a/gsd-core/workflows/eval-review.md
+++ b/gsd-core/workflows/eval-review.md
@@ -30,9 +30,7 @@ AGENT_SKILLS_AUDITOR=$(gsd_run query agent-skills gsd-eval-auditor)
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► EVAL AUDIT — PHASE {N}: {name}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► EVAL AUDIT — PHASE {N}: {name}
```
## 1. Detect Input State
@@ -47,7 +45,6 @@ EVAL_REVIEW_FILE=$(ls "${PHASE_DIR}"/*-EVAL-REVIEW.md 2>/dev/null | head -1)
**State B** — SUMMARY.md exists, no AI-SPEC.md: Audit against general best practices
**State C** — No SUMMARY.md: Exit — "Phase {N} not executed. Run /gsd:execute-phase {N} first."
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
**If `EVAL_REVIEW_FILE` non-empty:** Use AskUserQuestion:
- header: "Existing Eval Review"
@@ -121,9 +118,7 @@ Read the written EVAL-REVIEW.md. Extract:
## 5. Display Summary
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► EVAL AUDIT COMPLETE — PHASE {N}: {name}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► EVAL AUDIT COMPLETE — PHASE {N}: {name}
◆ Score: {overall_score}/100
◆ Verdict: {verdict}
diff --git a/gsd-core/workflows/execute-plan.md b/gsd-core/workflows/execute-plan.md
index 62eb3789b..cda873c02 100644
--- a/gsd-core/workflows/execute-plan.md
+++ b/gsd-core/workflows/execute-plan.md
@@ -333,7 +333,7 @@ Canonical per-task commit rules live in **`agents/gsd-executor.md`** (`
On `type="checkpoint:*"`: automate everything possible first. Checkpoints are for verification/decisions only.
-Display: `CHECKPOINT: [Type]` box → Progress {X}/{Y} → Task name → type-specific content → `YOUR ACTION: [signal]`
+Display: `### CHECKPOINT: [Type]` heading → Progress {X}/{Y} → Task name → type-specific content → `---` → `**YOUR ACTION: [signal]**`
| Type | Content | Resume signal |
|------|---------|---------------|
diff --git a/gsd-core/workflows/health.md b/gsd-core/workflows/health.md
index c2b63b0cb..b4362a180 100644
--- a/gsd-core/workflows/health.md
+++ b/gsd-core/workflows/health.md
@@ -14,7 +14,6 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/d
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
-
**Parse arguments:**
@@ -136,9 +135,7 @@ If the check prints, append it to the Warnings section of the report as `[W025]`
**Format and display results:**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD Health Check
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD Health Check
Status: HEALTHY | DEGRADED | BROKEN
Errors: N | Warnings: N | Info: N
diff --git a/gsd-core/workflows/import.md b/gsd-core/workflows/import.md
index f2bfa463e..df27f5c7a 100644
--- a/gsd-core/workflows/import.md
+++ b/gsd-core/workflows/import.md
@@ -19,9 +19,7 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/d
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► IMPORT
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► IMPORT
```
@@ -57,9 +55,7 @@ test -f "{FILEPATH}" || echo "FILE_NOT_FOUND"
If FILE_NOT_FOUND: display error and exit:
```
-╔══════════════════════════════════════════════════════════════╗
-║ ERROR ║
-╚══════════════════════════════════════════════════════════════╝
+### ERROR
File not found: {FILEPATH}
@@ -251,9 +247,7 @@ gsd_run query commit "docs({phase}): import plan from {basename FILEPATH}" --fil
Display completion:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► IMPORT COMPLETE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► IMPORT COMPLETE
```
Show: plan filename written, phase directory, validation result, next steps.
diff --git a/gsd-core/workflows/inbox.md b/gsd-core/workflows/inbox.md
index 79f1a78df..45ea84d7c 100644
--- a/gsd-core/workflows/inbox.md
+++ b/gsd-core/workflows/inbox.md
@@ -24,7 +24,6 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/d
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
-
Verify prerequisites:
@@ -351,7 +350,7 @@ AskUserQuestion:
```
-───────────────────────────────────────────────────────────────
+---
## Inbox Triage Complete
@@ -368,7 +367,7 @@ Next steps:
- Merge ready PRs
- Triage unclassified issues
-───────────────────────────────────────────────────────────────
+---
```
diff --git a/gsd-core/workflows/ingest-docs.md b/gsd-core/workflows/ingest-docs.md
index 1c54899a3..32f933b65 100644
--- a/gsd-core/workflows/ingest-docs.md
+++ b/gsd-core/workflows/ingest-docs.md
@@ -14,9 +14,7 @@ Scan a repo for mixed planning documents (ADR, PRD, SPEC, DOC), synthesize them
Display the stage banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► INGEST DOCS
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► INGEST DOCS
```
@@ -332,9 +330,7 @@ gsd_run commit \
Display completion:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► INGEST DOCS COMPLETE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► INGEST DOCS COMPLETE
```
Show:
diff --git a/gsd-core/workflows/list-seeds.md b/gsd-core/workflows/list-seeds.md
index f28d39971..5af83021c 100644
--- a/gsd-core/workflows/list-seeds.md
+++ b/gsd-core/workflows/list-seeds.md
@@ -37,11 +37,13 @@ Render the seeds as a table, sorted by `seed_id` (already sorted by the tool). T
```
Seeds
-─────────────────────────────────────────────────────────────────────
+
+---
ID Status Scope Trigger Title
SEED-001 dormant large when websockets land Real-time collaboration
SEED-006 triggered medium MILE-04 planning Remove legacy auth crates
-─────────────────────────────────────────────────────────────────────
+
+---
seeds ()
```
diff --git a/gsd-core/workflows/manager.md b/gsd-core/workflows/manager.md
index 22cf96820..c4c46e026 100644
--- a/gsd-core/workflows/manager.md
+++ b/gsd-core/workflows/manager.md
@@ -40,16 +40,15 @@ These are empty strings by default. Set via: `gsd-tools.cjs query config-set man
Display startup banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► MANAGER
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► MANAGER
{milestone_version} — {milestone_name}
{phase_count} phases · {completed_count} complete
✓ Discuss → inline ◆ Plan/Execute → inline (background when FLATTEN=false)
Dashboard auto-refreshes when background work is active.
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+---
```
Proceed to dashboard step.
@@ -92,9 +91,7 @@ Use `deps_display` from init JSON for the Deps column — shows which phases thi
Example output:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► DASHBOARD
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► DASHBOARD
████████████░░░░░░░░ 60% (3/5 phases)
◆ Background: Planning Phase 4
| # | Phase | Deps | D | P | E | Status |
@@ -116,9 +113,7 @@ Use `queued_milestone_version` and `queued_milestone_name` for the header. Phase
Example:
```
- ───────────────────────────────────────────────────────────────
- ◆ Queued — {queued_milestone_version} {queued_milestone_name} ({queued_phases.length} phases)
- ───────────────────────────────────────────────────────────────
+### ◆ Queued — {queued_milestone_version} {queued_milestone_name} ({queued_phases.length} phases)
| # | Phase | Deps | Status |
|---|----------------------|------|--------------|
| 31| Email Logs | — | · Queued |
@@ -134,16 +129,13 @@ Queued phases are NOT eligible for the Continue action menu — they live in a f
If `all_complete` is true:
```
-╔══════════════════════════════════════════════════════════════╗
-║ MILESTONE COMPLETE ║
-╚══════════════════════════════════════════════════════════════╝
+### MILESTONE COMPLETE
All {phase_count} phases verified complete. Ready for final steps:
→ /gsd:verify-work — run acceptance testing
→ /gsd:complete-milestone — archive and wrap up
```
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Ask user via AskUserQuestion:
- **question:** "All phases complete. What next?"
@@ -188,9 +180,7 @@ Handle responses:
Display recommendations compactly:
```
-───────────────────────────────────────────────────────────────
-▶ Next Steps
-───────────────────────────────────────────────────────────────
+### ▶ Next Steps
Continue:
→ Execute Phase 32 (background)
@@ -411,15 +401,14 @@ Classify the error:
Display final status with progress bar:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SESSION END
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SESSION END
{milestone_version} — {milestone_name}
{PROGRESS_BAR} {progress_pct}% ({completed_count}/{phase_count} phases)
Resume anytime: /gsd:manager
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+---
```
**Note:** Any background agents still running will continue to completion. Their results will be visible on next `/gsd:manager` or `/gsd:progress` invocation.
diff --git a/gsd-core/workflows/new-milestone.md b/gsd-core/workflows/new-milestone.md
index 6d5583c8b..22a661c57 100644
--- a/gsd-core/workflows/new-milestone.md
+++ b/gsd-core/workflows/new-milestone.md
@@ -131,9 +131,7 @@ AskUserQuestion(
Before writing any files, present a summary of what was gathered and ask for confirmation.
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► MILESTONE SUMMARY
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► MILESTONE SUMMARY
**Milestone v[X.Y]: [Name]**
@@ -325,9 +323,7 @@ AskUserQuestion: "Research the domain ecosystem for new features before defining
**If user chose "Research first":**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► RESEARCHING
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► RESEARCHING
◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
→ Stack, Features, Architecture, Pitfalls
@@ -422,9 +418,7 @@ This guarantees `gsd-roadmapper` (which lists SUMMARY.md as required reading) ne
Display key findings from SUMMARY.md:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► RESEARCH COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► RESEARCH COMPLETE ✓
**Stack additions:** [from SUMMARY.md]
**Feature table stakes:** [from SUMMARY.md]
@@ -436,9 +430,7 @@ Display key findings from SUMMARY.md:
## 9. Define Requirements
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► DEFINING REQUIREMENTS
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► DEFINING REQUIREMENTS
```
Read PROJECT.md: core value, current milestone goals, validated requirements (what exists).
@@ -509,9 +501,7 @@ gsd_run query commit "docs: define milestone v[X.Y] requirements" --files .plann
## 10. Create Roadmap
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► CREATING ROADMAP
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► CREATING ROADMAP
◆ Spawning roadmapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -645,9 +635,7 @@ Print a summary:
## 11. Done
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► MILESTONE INITIALIZED ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► MILESTONE INITIALIZED ✓
**Milestone v[X.Y]: [Name]**
diff --git a/gsd-core/workflows/new-project.md b/gsd-core/workflows/new-project.md
index 142c89b93..8eafd1d19 100644
--- a/gsd-core/workflows/new-project.md
+++ b/gsd-core/workflows/new-project.md
@@ -151,9 +151,7 @@ If spike/sketch findings skills exist, read their SKILL.md files to inform the q
**Display stage banner:**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUESTIONING
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUESTIONING
```
**Open the conversation:**
@@ -700,9 +698,7 @@ Use AskUserQuestion:
Display stage banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► RESEARCHING
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► RESEARCHING
Researching [domain] ecosystem...
```
@@ -942,9 +938,7 @@ This guarantees `gsd-roadmapper` (which lists SUMMARY.md as required reading) ne
Display research complete banner and key findings:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► RESEARCH COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► RESEARCH COMPLETE ✓
## Key Findings
@@ -962,9 +956,7 @@ Files: `.planning/research/`
Display stage banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► DEFINING REQUIREMENTS
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► DEFINING REQUIREMENTS
```
**Load context:**
@@ -1131,9 +1123,7 @@ When `TEXT_MODE=true` (per the workflow's existing TEXT_MODE handling for non-Cl
Display stage banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► CREATING ROADMAP
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► CREATING ROADMAP
◆ Spawning roadmapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -1300,9 +1290,7 @@ gsd_run query commit "docs: create roadmap ([N] phases)" --files .planning/ROADM
Present completion summary:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PROJECT INITIALIZED ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PROJECT INITIALIZED ✓
**[Project Name]**
@@ -1321,9 +1309,7 @@ Present completion summary:
**If auto mode:**
```
-╔══════════════════════════════════════════╗
-║ AUTO-ADVANCING → DISCUSS PHASE 1 ║
-╚══════════════════════════════════════════╝
+### AUTO-ADVANCING → DISCUSS PHASE 1
```
Exit skill and invoke SlashCommand("/gsd:discuss-phase 1 --auto")
@@ -1340,7 +1326,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
**If Phase 1 has UI (`PHASE1_HAS_UI` is `true`):**
```
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -1356,13 +1342,13 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
- /gsd:ui-phase 1 — generate UI design contract (recommended for frontend phases)
- /gsd:plan-phase 1 — skip discussion, plan directly
-───────────────────────────────────────────────────────────────
+---
```
**If Phase 1 has no UI:**
```
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -1377,7 +1363,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
**Also available:**
- /gsd:plan-phase 1 — skip discussion, plan directly
-───────────────────────────────────────────────────────────────
+---
```
diff --git a/gsd-core/workflows/plan-phase.md b/gsd-core/workflows/plan-phase.md
index 63035b208..f3416856b 100644
--- a/gsd-core/workflows/plan-phase.md
+++ b/gsd-core/workflows/plan-phase.md
@@ -337,9 +337,7 @@ If user selects "Skip research": skip to step 6.
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► RESEARCHING PHASE {X}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► RESEARCHING PHASE {X}
◆ Spawning researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -434,9 +432,7 @@ Resolve active contribution hooks from `PLAN_PRE_HOOKS_JSON` where `kind == "con
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SECURITY THREAT MODEL REQUIRED (ASVS L{SECURITY_ASVS})
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SECURITY THREAT MODEL REQUIRED (ASVS L{SECURITY_ASVS})
Each PLAN.md must include a block.
Block on: {SECURITY_BLOCK} severity threats.
@@ -522,7 +518,8 @@ Output this markdown directly (not as a code block):
## ⚠ UI-SPEC.md missing for Phase {N}
▶ Recommended next step:
`/gsd:ui-phase {N} ${GSD_WS}` — generate UI design contract before planning
-───────────────────────────────────────────────
+
+---
Also available:
- `/gsd:plan-phase {N} --skip-ui ${GSD_WS}` — plan without UI-SPEC (not recommended for frontend phases)
```
@@ -618,9 +615,7 @@ Pattern mapper activation is owned by the `pattern-mapper` capability's `plan:pr
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PATTERN MAPPING PHASE {X}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PATTERN MAPPING PHASE {X}
◆ Spawning pattern mapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -690,9 +685,7 @@ independent of the teams-status guard above, AC2).
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PLANNING PHASE {X}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PLANNING PHASE {X}
◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -993,9 +986,7 @@ Use AskUserQuestion for each gap (or batch if multiple gaps).
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► VERIFYING PLANS
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► VERIFYING PLANS
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -1222,9 +1213,7 @@ BOUNCE_SCRIPT=$(gsd_run query config-get workflow.plan_bounce_script --raw 2>/de
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► BOUNCING PLANS (External Refinement)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► BOUNCING PLANS (External Refinement)
Script: ${BOUNCE_SCRIPT}
Max passes: ${BOUNCE_PASSES}
@@ -1498,9 +1487,7 @@ fi
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► AUTO-ADVANCING TO EXECUTE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► AUTO-ADVANCING TO EXECUTE
Plans ready. Launching execute-phase...
```
@@ -1515,9 +1502,7 @@ The `--no-transition` flag tells execute-phase to return status after verificati
**Handle execute-phase return:**
- **PHASE COMPLETE** → Display final summary:
```
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PHASE ${PHASE} COMPLETE ✓
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PHASE ${PHASE} COMPLETE ✓
Auto-advance pipeline finished.
@@ -1541,9 +1526,7 @@ Output this markdown directly (not as a code block):
`${GAPS_EXEC_FLAG}` projects the just-completed planning mode onto the follow-up execute command (#3297): it expands to `--gaps-only` for a `--gaps` planning run (so the handoff points at execute-phase's gap-closure scope — only the newly created `gap_closure: true` plans — not the whole phase) and to empty for a standard or `--reviews` run (whole-phase scope, unchanged). Substitute it verbatim; when empty, collapse the extra space.
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PHASE {X} PLANNED ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PHASE {X} PLANNED ✓
**Phase {X}: {Name}** — {N} plan(s) in {M} wave(s)
@@ -1555,7 +1538,7 @@ Output this markdown directly (not as a code block):
Research: {Completed | Used existing | Skipped}
Verification: {Passed | Passed with override | Skipped}
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -1565,7 +1548,7 @@ Verification: {Passed | Passed with override | Skipped}
/gsd:execute-phase {X} ${GAPS_EXEC_FLAG} ${GSD_WS}
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- cat .planning/phases/{phase-dir}/*-PLAN.md — review plans
@@ -1573,7 +1556,7 @@ Verification: {Passed | Passed with override | Skipped}
- /gsd:review --phase {X} --all — peer review plans with external AIs
- /gsd:plan-phase {X} --reviews — replan incorporating review feedback
-───────────────────────────────────────────────────────────────
+---
diff --git a/gsd-core/workflows/plan-phase/steps/prd-express-path.md b/gsd-core/workflows/plan-phase/steps/prd-express-path.md
index e05c988fa..47e85dd6e 100644
--- a/gsd-core/workflows/plan-phase/steps/prd-express-path.md
+++ b/gsd-core/workflows/plan-phase/steps/prd-express-path.md
@@ -13,9 +13,7 @@ fi
2. Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PRD EXPRESS PATH
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PRD EXPRESS PATH
Using PRD: {PRD_FILE}
Generating CONTEXT.md from requirements...
diff --git a/gsd-core/workflows/plan-review-convergence.md b/gsd-core/workflows/plan-review-convergence.md
index e5f6690f6..59a271cc9 100644
--- a/gsd-core/workflows/plan-review-convergence.md
+++ b/gsd-core/workflows/plan-review-convergence.md
@@ -134,9 +134,7 @@ PHASE_INFO=$(gsd_run roadmap get-phase "${PHASE}")
Display startup banner:
```text
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PLAN CONVERGENCE — Phase {phase_number}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PLAN CONVERGENCE — Phase {phase_number}
Reviewers: {REVIEWER_DISPLAY}
Max cycles: {MAX_CYCLES}
@@ -400,9 +398,7 @@ gsd_run state planned-phase --phase "${PHASE}" --name "${phase_name}" --plans "$
Display:
```text
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► CONVERGENCE COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► CONVERGENCE COMPLETE ✓
Phase {phase_number} converged in {cycle} cycle(s).
No HIGH concerns remaining.
diff --git a/gsd-core/workflows/pr-branch.md b/gsd-core/workflows/pr-branch.md
index bc5163114..013f6c9e9 100644
--- a/gsd-core/workflows/pr-branch.md
+++ b/gsd-core/workflows/pr-branch.md
@@ -63,9 +63,7 @@ if [ "$PR_STRICT" = "true" ]; then PR_MODE="strict"; else PR_MODE="default"; PR_
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PR BRANCH
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PR BRANCH
Branch: {CURRENT_BRANCH}
Target: {TARGET}
diff --git a/gsd-core/workflows/profile-user.md b/gsd-core/workflows/profile-user.md
index 88addea26..8458756d8 100644
--- a/gsd-core/workflows/profile-user.md
+++ b/gsd-core/workflows/profile-user.md
@@ -21,7 +21,6 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/d
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
-
## 1. Initialize
Parse flags from $ARGUMENTS:
@@ -37,7 +36,6 @@ PROFILE_PATH="$HOME/.claude/gsd-core/USER-PROFILE.md"
**If profile exists AND --refresh NOT set AND --questionnaire NOT set:**
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- header: "Existing Profile"
@@ -72,9 +70,7 @@ Continue to step 2.
Display consent screen:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD > PROFILE YOUR CODING STYLE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD > PROFILE YOUR CODING STYLE
Claude starts every conversation generic. A profile teaches Claude
how YOU actually work -- not how you think you work.
@@ -421,9 +417,7 @@ If nothing changed: Display "No changes detected -- your profile is already up t
**Display final summary:**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD > PROFILE COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD > PROFILE COMPLETE ✓
Your profile: $HOME/.claude/gsd-core/USER-PROFILE.md
```
diff --git a/gsd-core/workflows/quick.md b/gsd-core/workflows/quick.md
index a7c8924f8..a85683b1b 100644
--- a/gsd-core/workflows/quick.md
+++ b/gsd-core/workflows/quick.md
@@ -65,63 +65,49 @@ Display banner based on active flags:
If `$FULL_MODE` (all phases enabled — `--full` or all granular flags):
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUICK TASK (FULL)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUICK TASK (FULL)
◆ Discussion + research + plan checking + verification enabled
```
If `$DISCUSS_MODE` and `$VALIDATE_MODE` (no research):
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUICK TASK (DISCUSS + VALIDATE)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUICK TASK (DISCUSS + VALIDATE)
◆ Discussion + plan checking + verification enabled
```
If `$DISCUSS_MODE` and `$RESEARCH_MODE` (no validate):
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUICK TASK (DISCUSS + RESEARCH)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUICK TASK (DISCUSS + RESEARCH)
◆ Discussion + research enabled
```
If `$RESEARCH_MODE` and `$VALIDATE_MODE` (no discuss):
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUICK TASK (RESEARCH + VALIDATE)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUICK TASK (RESEARCH + VALIDATE)
◆ Research + plan checking + verification enabled
```
If `$DISCUSS_MODE` only:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUICK TASK (DISCUSS)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUICK TASK (DISCUSS)
◆ Discussion phase enabled — surfacing gray areas before planning
```
If `$RESEARCH_MODE` only:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUICK TASK (RESEARCH)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUICK TASK (RESEARCH)
◆ Research phase enabled — investigating approaches before planning
```
If `$VALIDATE_MODE` only:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► QUICK TASK (VALIDATE)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► QUICK TASK (VALIDATE)
◆ Plan checking + verification enabled
```
diff --git a/gsd-core/workflows/quick/steps/discussion-phase.md b/gsd-core/workflows/quick/steps/discussion-phase.md
index 6bc92fc26..8b1e32d40 100644
--- a/gsd-core/workflows/quick/steps/discussion-phase.md
+++ b/gsd-core/workflows/quick/steps/discussion-phase.md
@@ -4,9 +4,7 @@ Skip this step entirely if NOT `$DISCUSS_MODE`.
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► DISCUSSING QUICK TASK
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► DISCUSSING QUICK TASK
◆ Surfacing gray areas for: ${DESCRIPTION}
```
diff --git a/gsd-core/workflows/quick/steps/plan-checker-loop.md b/gsd-core/workflows/quick/steps/plan-checker-loop.md
index 1685062a9..3df2d259c 100644
--- a/gsd-core/workflows/quick/steps/plan-checker-loop.md
+++ b/gsd-core/workflows/quick/steps/plan-checker-loop.md
@@ -4,9 +4,7 @@ Skip this step entirely if NOT `$VALIDATE_MODE`.
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► CHECKING PLAN
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► CHECKING PLAN
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
diff --git a/gsd-core/workflows/quick/steps/quick-verification.md b/gsd-core/workflows/quick/steps/quick-verification.md
index 41b1314d6..3f1488f97 100644
--- a/gsd-core/workflows/quick/steps/quick-verification.md
+++ b/gsd-core/workflows/quick/steps/quick-verification.md
@@ -4,9 +4,7 @@ Skip this step entirely if NOT `$VALIDATE_MODE`.
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► VERIFYING RESULTS
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► VERIFYING RESULTS
◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
diff --git a/gsd-core/workflows/quick/steps/research-phase.md b/gsd-core/workflows/quick/steps/research-phase.md
index ddf164487..ecbfb652d 100644
--- a/gsd-core/workflows/quick/steps/research-phase.md
+++ b/gsd-core/workflows/quick/steps/research-phase.md
@@ -4,9 +4,7 @@ Skip this step entirely if NOT `$RESEARCH_MODE`.
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► RESEARCHING QUICK TASK
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► RESEARCHING QUICK TASK
◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
diff --git a/gsd-core/workflows/resume-project.md b/gsd-core/workflows/resume-project.md
index d00719c46..38d48146a 100644
--- a/gsd-core/workflows/resume-project.md
+++ b/gsd-core/workflows/resume-project.md
@@ -133,17 +133,13 @@ fi
Present complete project status to user:
```
-╔══════════════════════════════════════════════════════════════╗
-║ PROJECT STATUS ║
-╠══════════════════════════════════════════════════════════════╣
-║ Building: [one-liner from PROJECT.md "What This Is"] ║
-║ ║
-║ Phase: [X] of [Y] - [Phase name] ║
-║ Plan: [A] of [B] - [Status] ║
-║ Progress: [██████░░░░] XX% ║
-║ ║
-║ Last activity: [date] - [what happened] ║
-╚══════════════════════════════════════════════════════════════╝
+### PROJECT STATUS
+
+Building: [one-liner from PROJECT.md "What This Is"]
+Phase: [X] of [Y] - [Phase name]
+Plan: [A] of [B] - [Status]
+Progress: [██████░░░░] XX%
+Last activity: [date] - [what happened]
[If incomplete work found:]
⚠️ Incomplete work detected:
diff --git a/gsd-core/workflows/review.md b/gsd-core/workflows/review.md
index 20d0576fb..19ed9e458 100644
--- a/gsd-core/workflows/review.md
+++ b/gsd-core/workflows/review.md
@@ -404,9 +404,7 @@ blocked, not silently redirected** (ADR-2782 D5).
Display progress:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► CROSS-AI REVIEW — Phase {N}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► CROSS-AI REVIEW — Phase {N}
◆ Reviewing with {CLI}... done ✓
◆ Reviewing with {CLI}... done ✓
@@ -507,9 +505,7 @@ gsd_run query commit "docs: cross-AI review for phase {N}" --files {phase_dir}/{
Display summary:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► REVIEW COMPLETE
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► REVIEW COMPLETE
Phase {N} reviewed by {count} AI systems.
diff --git a/gsd-core/workflows/settings-advanced.md b/gsd-core/workflows/settings-advanced.md
index 3c97f5272..c6b98de89 100644
--- a/gsd-core/workflows/settings-advanced.md
+++ b/gsd-core/workflows/settings-advanced.md
@@ -747,9 +747,7 @@ Never write a tier the user explicitly chose to keep; the existing value must su
Display:
```text
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► ADVANCED SETTINGS UPDATED
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► ADVANCED SETTINGS UPDATED
| Setting | Value |
|--------------------------------------------|-------|
diff --git a/gsd-core/workflows/settings-integrations.md b/gsd-core/workflows/settings-integrations.md
index a1315e7d1..21e29402b 100644
--- a/gsd-core/workflows/settings-integrations.md
+++ b/gsd-core/workflows/settings-integrations.md
@@ -302,9 +302,7 @@ Display the masked confirmation table. **No plaintext API keys appear in this
output under any circumstance.**
```text
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► INTEGRATIONS UPDATED
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► INTEGRATIONS UPDATED
Search Integrations
| Field | Value |
diff --git a/gsd-core/workflows/settings.md b/gsd-core/workflows/settings.md
index 6aa4d00b1..7f406d631 100644
--- a/gsd-core/workflows/settings.md
+++ b/gsd-core/workflows/settings.md
@@ -606,9 +606,7 @@ Write `~/.gsd/defaults.json` with:
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SETTINGS UPDATED
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SETTINGS UPDATED
| Setting | Value |
|----------------------|-------|
diff --git a/gsd-core/workflows/ship.md b/gsd-core/workflows/ship.md
index b0105d195..055ed18f5 100644
--- a/gsd-core/workflows/ship.md
+++ b/gsd-core/workflows/ship.md
@@ -571,7 +571,7 @@ Each dispatch is best-effort: if it errors, record a warning and continue — ne
```
-───────────────────────────────────────────────────────────────
+---
## ✓ Phase {X}: {Name} — Shipped
@@ -587,7 +587,7 @@ Next steps:
- /gsd:complete-milestone (if last phase in milestone)
- /gsd:progress (to see what's next)
-───────────────────────────────────────────────────────────────
+---
```
diff --git a/gsd-core/workflows/sketch-wrap-up.md b/gsd-core/workflows/sketch-wrap-up.md
index 3312fc3e1..0b6fa7840 100644
--- a/gsd-core/workflows/sketch-wrap-up.md
+++ b/gsd-core/workflows/sketch-wrap-up.md
@@ -13,9 +13,7 @@ Read all files referenced by the invoking prompt's execution_context before star
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SKETCH WRAP-UP
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SKETCH WRAP-UP
```
@@ -55,17 +53,15 @@ Present each unprocessed sketch in ascending order. For each sketch, show:
Then ask the user:
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: Decision Required ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: Decision Required
Sketch {NNN}: {name} — Winner: Variant {X}
{key design decisions summary}
-──────────────────────────────────────────────────────────────
-→ Include / Exclude / Partial / Let me look at it
-──────────────────────────────────────────────────────────────
+---
+
+**→ Include / Exclude / Partial / Let me look at it**
**If "Let me look at it":**
1. Provide: `open .planning/sketches/NNN-name/index.html`
@@ -239,9 +235,7 @@ gsd_run query commit "docs(sketch-wrap-up): package [N] sketch findings into pro
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SKETCH WRAP-UP COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SKETCH WRAP-UP COMPLETE ✓
**Curated:** {N} sketches ({included} included, {excluded} excluded)
**Design areas:** {list}
@@ -252,7 +246,7 @@ gsd_run query commit "docs(sketch-wrap-up): package [N] sketch findings into pro
The sketch-findings skill will auto-load when building the UI.
```
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up
@@ -260,7 +254,7 @@ The sketch-findings skill will auto-load when building the UI.
`/gsd:sketch` (run with no argument — its frontier mode analyzes the sketch landscape and proposes consistency and frontier sketches)
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- `/gsd:plan-phase` — start building the real UI
@@ -268,7 +262,7 @@ The sketch-findings skill will auto-load when building the UI.
- `/gsd:sketch [idea]` — sketch a specific new design area
- `/gsd:explore` — continue exploring
-───────────────────────────────────────────────────────────────
+---
diff --git a/gsd-core/workflows/sketch.md b/gsd-core/workflows/sketch.md
index 82623aab7..799d6bc85 100644
--- a/gsd-core/workflows/sketch.md
+++ b/gsd-core/workflows/sketch.md
@@ -21,9 +21,7 @@ Read all files referenced by the invoking prompt's execution_context before star
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SKETCHING
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SKETCHING
```
Parse `$ARGUMENTS` for:
@@ -272,9 +270,7 @@ open .planning/sketches/NNN-descriptive-name/index.html
**e.** Present to the user with a checkpoint:
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: Verification Required ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: Verification Required
**Sketch {NNN}: {name}**
@@ -282,9 +278,9 @@ Open: `open .planning/sketches/NNN-name/index.html`
Compare: {what to look for between variants}
-──────────────────────────────────────────────────────────────
-→ Which variant feels right? Or cherry-pick elements across variants.
-──────────────────────────────────────────────────────────────
+---
+
+**→ Which variant feels right? Or cherry-pick elements across variants.**
**f.** Handle feedback:
- **Pick a direction:** mark winner, move to next sketch
@@ -315,9 +311,7 @@ gsd_run query commit "docs(sketch-NNN): [winning direction] — [key visual insi
After all sketches complete:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SKETCH COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SKETCH COMPLETE ✓
## Design Direction
{what we landed on overall}
@@ -329,7 +323,7 @@ After all sketches complete:
{anything unresolved or worth revisiting}
```
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up
@@ -337,14 +331,14 @@ After all sketches complete:
`/gsd:sketch --wrap-up`
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- `/gsd:sketch` — sketch more (or run with no argument for frontier mode)
- `/gsd:plan-phase` — start building the real UI
- `/gsd:spike` — spike technical feasibility of a design pattern
-───────────────────────────────────────────────────────────────
+---
diff --git a/gsd-core/workflows/smart-entry.md b/gsd-core/workflows/smart-entry.md
index 9d00ddd94..1ce6e9cc8 100644
--- a/gsd-core/workflows/smart-entry.md
+++ b/gsd-core/workflows/smart-entry.md
@@ -93,9 +93,7 @@ Wait for the user's response before continuing. Map the chosen number to the cor
**Show the routing decision.**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SMART ENTRY
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SMART ENTRY
**Situation:** {situation}
**Routing to:** {chosen command}
diff --git a/gsd-core/workflows/spike-wrap-up.md b/gsd-core/workflows/spike-wrap-up.md
index bdccecaf7..82c59b3ec 100644
--- a/gsd-core/workflows/spike-wrap-up.md
+++ b/gsd-core/workflows/spike-wrap-up.md
@@ -13,9 +13,7 @@ Read all files referenced by the invoking prompt's execution_context before star
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SPIKE WRAP-UP
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SPIKE WRAP-UP
```
@@ -267,9 +265,7 @@ gsd_run query commit "docs(spike-wrap-up): package [N] spike findings into proje
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SPIKE WRAP-UP COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SPIKE WRAP-UP COMPLETE ✓
**Processed:** {N} spikes
**Feature areas:** {list}
@@ -287,7 +283,7 @@ The spike-findings skill will auto-load in future build conversations.
After the summary, present next-step options:
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up
@@ -295,7 +291,7 @@ After the summary, present next-step options:
`/gsd:spike` (run with no argument — its frontier mode analyzes the spike landscape and proposes integration and frontier spikes)
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- `/gsd:plan-phase` — start planning the real implementation
@@ -303,7 +299,7 @@ After the summary, present next-step options:
- `/gsd:explore` — continue exploring
- Other
-───────────────────────────────────────────────────────────────
+---
diff --git a/gsd-core/workflows/spike.md b/gsd-core/workflows/spike.md
index db1def70a..8241d648b 100644
--- a/gsd-core/workflows/spike.md
+++ b/gsd-core/workflows/spike.md
@@ -20,12 +20,9 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/d
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
-
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SPIKING
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SPIKING
```
Parse `$ARGUMENTS` for:
@@ -175,15 +172,13 @@ Order by risk — most likely to kill the idea runs first.
**If `QUICK_MODE` is true:** Skip.
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: Decision Required ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: Decision Required
{spike table from decompose step}
-──────────────────────────────────────────────────────────────
-→ Build all in this order, or adjust the list?
-──────────────────────────────────────────────────────────────
+---
+
+**→ Build all in this order, or adjust the list?**
@@ -358,17 +353,15 @@ tags: [tag1, tag2]
- Self-verifiable: run, iterate if findings warrant deeper investigation, update verdict
- Needs human judgment: present checkpoint box:
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: Verification Required ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: Verification Required
**Spike {NNN}: {name}**
**How to run:** {command}
**What to expect:** {concrete outcomes}
-──────────────────────────────────────────────────────────────
-→ Does this match what you expected? Describe what you see.
-──────────────────────────────────────────────────────────────
+---
+
+**→ Does this match what you expected? Describe what you see.**
**h.** Update `.planning/spikes/MANIFEST.md` with the spike's row, setting the Idea column to
this spike's idea key.
@@ -390,16 +383,14 @@ Do not rush to a verdict. A spike that says "VALIDATED — it works" with no nua
**k.** If core assumption invalidated:
-╔══════════════════════════════════════════════════════════════╗
-║ CHECKPOINT: Decision Required ║
-╚══════════════════════════════════════════════════════════════╝
+### CHECKPOINT: Decision Required
Core assumption invalidated by Spike {NNN}.
{what was invalidated and why}
-──────────────────────────────────────────────────────────────
-→ Continue with remaining spikes / Pivot approach / Abandon
-──────────────────────────────────────────────────────────────
+---
+
+**→ Continue with remaining spikes / Pivot approach / Abandon**
@@ -435,9 +426,7 @@ gsd_run query commit "docs(spikes): update conventions" --files .planning/spikes
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► SPIKE COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► SPIKE COMPLETE ✓
## Verdicts
@@ -456,7 +445,7 @@ gsd_run query commit "docs(spikes): update conventions" --files .planning/spikes
{what to use, avoid, watch out for}
```
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up
@@ -464,14 +453,14 @@ gsd_run query commit "docs(spikes): update conventions" --files .planning/spikes
`/gsd:spike --wrap-up`
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- `/gsd:spike` — spike more ideas (or run with no argument for frontier mode)
- `/gsd:plan-phase` — start planning the real implementation
- `/gsd:explore` — continue exploring the idea
-───────────────────────────────────────────────────────────────
+---
diff --git a/gsd-core/workflows/thread.md b/gsd-core/workflows/thread.md
index deeed8c20..6fce00545 100644
--- a/gsd-core/workflows/thread.md
+++ b/gsd-core/workflows/thread.md
@@ -42,12 +42,14 @@ Apply filter for LIST-OPEN (show only status=open or status=in_progress) or LIST
Display:
```
Context Threads
-─────────────────────────────────────────────────────────
+
+---
slug status updated title
auth-decision open 2026-04-09 OAuth vs Session tokens
db-schema-v2 in_progress 2026-04-07 Connection pool sizing
frontend-build-tools resolved 2026-04-01 Vite vs webpack
-─────────────────────────────────────────────────────────
+
+---
3 threads (2 open/in_progress, 1 resolved)
```
@@ -96,7 +98,8 @@ When SUBCMD=status and SLUG is set (already sanitized):
2. Read the file and display a summary:
```
Thread: {SLUG}
- ─────────────────────────────────────
+
+---
Title: {title from frontmatter or # heading}
Status: {status from frontmatter or ## Status heading}
Updated: {updated from frontmatter}
@@ -107,7 +110,8 @@ When SUBCMD=status and SLUG is set (already sanitized):
Next Steps:
{content of ## Next Steps section}
- ─────────────────────────────────────
+
+---
Resume with: /gsd:thread {SLUG}
Close with: /gsd:thread close {SLUG}
```
diff --git a/gsd-core/workflows/ui-phase.md b/gsd-core/workflows/ui-phase.md
index 9fd8df446..2cd4e8e17 100644
--- a/gsd-core/workflows/ui-phase.md
+++ b/gsd-core/workflows/ui-phase.md
@@ -115,9 +115,7 @@ If "Update": continue to step 5.
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► UI DESIGN CONTRACT — PHASE {N}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► UI DESIGN CONTRACT — PHASE {N}
◆ Spawning UI researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -188,9 +186,7 @@ Display blocker details and options. Exit workflow.
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► VERIFYING UI-SPEC
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► VERIFYING UI-SPEC
◆ Spawning UI checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -423,16 +419,14 @@ STATE coverage and REFERENCES those rows rather than restating the copy (de-dup)
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► UI-SPEC READY ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► UI-SPEC READY ✓
**Phase {N}: {Name}** — UI design contract approved
Dimensions: 7/7 passed
{If any FLAGs: "Recommendations: {N} (non-blocking)"}
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -448,7 +442,7 @@ Dimensions: 7/7 passed
(or `/gsd:plan-phase {N}` to skip discussion)
-───────────────────────────────────────────────────────────────
+---
```
## 11. Commit (if configured)
diff --git a/gsd-core/workflows/ui-review.md b/gsd-core/workflows/ui-review.md
index c0c376f36..b3bf810d7 100644
--- a/gsd-core/workflows/ui-review.md
+++ b/gsd-core/workflows/ui-review.md
@@ -33,9 +33,7 @@ UI_AUDITOR_MODEL=$(gsd_run query resolve-model gsd-ui-auditor --raw)
Display banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► UI AUDIT — PHASE {N}: {name}
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► UI AUDIT — PHASE {N}: {name}
```
## 1. Detect Input State
@@ -127,9 +125,7 @@ Agent(
Display score summary:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► UI AUDIT COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► UI AUDIT COMPLETE ✓
**Phase {N}: {Name}** — Overall: {score}/24
@@ -149,7 +145,7 @@ Top fixes:
Full review: {path to UI-REVIEW.md}
-───────────────────────────────────────────────────────────────
+---
## ▶ Next
@@ -157,7 +153,7 @@ Full review: {path to UI-REVIEW.md}
- `/gsd:verify-work {N}` — UAT testing before phase completion
-───────────────────────────────────────────────────────────────
+---
```
## Automated UI Verification (when Playwright-MCP is available)
diff --git a/gsd-core/workflows/ultraplan-phase.md b/gsd-core/workflows/ultraplan-phase.md
index 8960fd8e2..835e64795 100644
--- a/gsd-core/workflows/ultraplan-phase.md
+++ b/gsd-core/workflows/ultraplan-phase.md
@@ -13,9 +13,7 @@ affect the core planning pipeline.
Display the stage banner:
```text
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► ULTRAPLAN PHASE ⚠ BETA
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► ULTRAPLAN PHASE ⚠ BETA
Ultraplan is in research preview (Claude Code v2.1.91+).
Use /gsd:plan-phase for stable local planning.
```
@@ -44,9 +42,7 @@ fi
If the output is empty or unset, display the following error and exit:
```text
-╔══════════════════════════════════════════════════════════════╗
-║ RUNTIME ERROR ║
-╚══════════════════════════════════════════════════════════════╝
+### RUNTIME ERROR
/gsd:ultraplan-phase requires Claude Code.
ultraplan is not available in this runtime.
@@ -159,9 +155,7 @@ Display the return-path instructions **before** triggering ultraplan so they are
in the terminal scroll-back after ultraplan launches:
```text
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- WHEN THE PLAN IS READY — WHAT TO DO
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### WHEN THE PLAN IS READY — WHAT TO DO
When ◆ ultraplan ready appears in your terminal:
@@ -176,9 +170,7 @@ When ◆ ultraplan ready appears in your terminal:
/gsd:import will run conflict detection, convert to GSD format,
validate via plan-checker, update ROADMAP.md, and commit.
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-Launching ultraplan for Phase {N}: {phase_name}...
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### Launching ultraplan for Phase {N}: {phase_name}...
```
diff --git a/gsd-core/workflows/undo.md b/gsd-core/workflows/undo.md
index 14d5f7541..ad4717b31 100644
--- a/gsd-core/workflows/undo.md
+++ b/gsd-core/workflows/undo.md
@@ -15,14 +15,11 @@ RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/d
**If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
-
Display the stage banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► UNDO
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► UNDO
```
@@ -70,7 +67,6 @@ Recent GSD commits:
3. ghi9012 fix(02-03): correct validation logic
```
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion to ask:
- question: "Which commits to revert? Enter numbers (e.g., 1,3) or 'all'"
@@ -237,9 +233,7 @@ If any revert fails (merge conflict or error):
```
3. Display:
```
- ╔══════════════════════════════════════════════════════════════╗
- ║ ERROR ║
- ╚══════════════════════════════════════════════════════════════╝
+### ERROR
Revert failed on commit ${HASH}.
Likely cause: merge conflict with subsequent changes.
@@ -271,9 +265,7 @@ git commit -m "revert: undo ${N} selected commits — ${REVERT_REASON}"
Display the completion banner:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► UNDO COMPLETE ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► UNDO COMPLETE ✓
```
Show summary:
@@ -284,7 +276,7 @@ Show summary:
Show next steps:
```
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -294,13 +286,13 @@ Show next steps:
/gsd:progress
-───────────────────────────────────────────────────────────────
+---
**Also available:**
- `/gsd:execute-phase ${PHASE}` — re-execute if needed
- `/gsd:undo --last 1` — undo the revert itself if something went wrong
-───────────────────────────────────────────────────────────────
+---
```
diff --git a/gsd-core/workflows/update.md b/gsd-core/workflows/update.md
index 7b1c44e09..76805bede 100644
--- a/gsd-core/workflows/update.md
+++ b/gsd-core/workflows/update.md
@@ -9,7 +9,6 @@ Read all files referenced by the invoking prompt's execution_context before star
**If `response_language` is configured:** All user-facing questions, prompts, and explanations in this workflow MUST be presented in that language. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
-
Detect the installed GSD version, scope, runtime, and config dir.
@@ -253,11 +252,12 @@ rm -f "$CHANGELOG_TMP"
**Latest:** {LATEST_VERSION}
### What's New
-────────────────────────────────────────────────────────────
+
+---
{CHANGELOG_PREVIEW}
-────────────────────────────────────────────────────────────
+---
⚠️ **Note:** The installer performs a clean install of GSD folders:
- `commands/gsd/` will be wiped and replaced
@@ -277,7 +277,6 @@ Your custom files in other locations are preserved:
If you've modified any GSD files directly, they'll be automatically backed up to `gsd-local-patches/` and can be reapplied with `/gsd:update --reapply` after the update.
```
-
**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available.
Use AskUserQuestion:
- Question: "Proceed with update?"
@@ -480,9 +479,7 @@ The SessionStart hook (`gsd-check-update.js`) writes to the detected runtime's c
Format completion message (changelog was already shown in confirmation step):
```
-╔═══════════════════════════════════════════════════════════╗
-║ GSD Updated: v1.5.10 → v1.5.15 ║
-╚═══════════════════════════════════════════════════════════╝
+### GSD Updated: v1.5.10 → v1.5.15
⚠️ Restart your runtime to pick up the new commands.
@@ -490,7 +487,6 @@ Format completion message (changelog was already shown in confirmation step):
```
-
`backup_custom_files` copied user-added files into `gsd-user-files-backup/`
before the wipe. Offer to put them back — now, against the release that was
diff --git a/gsd-core/workflows/verify-work.md b/gsd-core/workflows/verify-work.md
index eeae64bed..13656de97 100644
--- a/gsd-core/workflows/verify-work.md
+++ b/gsd-core/workflows/verify-work.md
@@ -649,9 +649,12 @@ Parse the JSON output. For the CURRENT PHASE ONLY, surface:
If any are found, display:
```
Phase {N} Artifact Check
-─────────────────────────────────────────────────
+
+---
+
{list each item with status and file path}
-─────────────────────────────────────────────────
+
+---
These items are open. Proceed anyway? [Y/n]
```
@@ -687,9 +690,7 @@ Diagnosis runs automatically - no user prompt. Parallel agents investigate simul
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► PLANNING FIXES
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► PLANNING FIXES
◆ Spawning planner for gap closure... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -754,9 +755,7 @@ On return:
Display:
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► VERIFYING FIX PLANS
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► VERIFYING FIX PLANS
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
@@ -860,9 +859,7 @@ Wait for user response.
**Present completion and next steps:**
```
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- GSD ► FIXES READY ✓
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+### GSD ► FIXES READY ✓
**Phase {X}: {Name}** — {N} gap(s) diagnosed, {M} fix plan(s) created
@@ -873,7 +870,7 @@ Wait for user response.
Plans verified and ready for execution.
-───────────────────────────────────────────────────────────────
+---
## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}
@@ -881,7 +878,7 @@ Plans verified and ready for execution.
`/clear` then `/gsd:execute-phase {phase} --gaps-only`
-───────────────────────────────────────────────────────────────
+---
```
diff --git a/skills/gsd-quick/SKILL.md b/skills/gsd-quick/SKILL.md
index 157f9dc0d..e1194f703 100644
--- a/skills/gsd-quick/SKILL.md
+++ b/skills/gsd-quick/SKILL.md
@@ -86,12 +86,14 @@ For each directory found:
Display format:
```
Quick Tasks
-────────────────────────────────────────────────────────────
+
+---
slug date status
backup-s3-policy 2026-04-10 in-progress
auth-token-refresh-fix 2026-04-09 complete ✓
update-node-deps 2026-04-08 abandoned? (>7 days, no summary)
-────────────────────────────────────────────────────────────
+
+---
3 tasks (1 complete, 2 incomplete/in-progress)
```
@@ -113,12 +115,14 @@ If no directory found, print `No quick task found with slug: {SLUG}` and stop.
Read PLAN.md and SUMMARY.md (if exists) for the given slug. Display:
```
Quick Task: {slug}
-─────────────────────────────────────
+
+---
Plan file: .planning/quick/{dir}/PLAN.md
Status: {status from SUMMARY.md frontmatter, or "no summary yet"}
Description: {first non-empty line from PLAN.md after frontmatter}
Last action: {last meaningful line of SUMMARY.md, or "none"}
-─────────────────────────────────────
+
+---
Resume with: /gsd-quick resume {slug}
```
diff --git a/src/audit.cts b/src/audit.cts
index 71dcfdf56..71b08ea89 100644
--- a/src/audit.cts
+++ b/src/audit.cts
@@ -1330,11 +1330,8 @@ function auditOpenArtifacts(cwd: string): AuditResult {
function formatAuditReport(auditResult: AuditResult): string {
const { counts, items, has_open_items, acknowledged } = auditResult;
const lines: string[] = [];
- const hr = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
- lines.push(hr);
- lines.push(' Milestone Close: Open Artifact Audit');
- lines.push(hr);
+ lines.push('### Milestone Close: Open Artifact Audit');
// WARNING 3 (#3458 follow-up review): the acknowledged tally previously
// existed only in `--json` output — the human report could not tell
@@ -1343,12 +1340,12 @@ function formatAuditReport(auditResult: AuditResult): string {
if (!has_open_items) {
lines.push('');
if (acknowledged.total > 0) {
- lines.push(` All artifact types clear (${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} still suppressed).`);
+ lines.push(`All artifact types clear (${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} still suppressed).`);
} else {
- lines.push(' All artifact types clear. Safe to proceed.');
+ lines.push('All artifact types clear. Safe to proceed.');
}
lines.push('');
- lines.push(hr);
+ lines.push('---');
return lines.join('\n');
}
@@ -1459,12 +1456,12 @@ function formatAuditReport(auditResult: AuditResult): string {
}
lines.push('');
- lines.push(hr);
- lines.push(` ${counts.total} item${counts.total !== 1 ? 's' : ''} require decisions before close.`);
+ lines.push('---');
+ lines.push('');
+ lines.push(`**${counts.total} item${counts.total !== 1 ? 's' : ''} require decisions before close.**`);
if (acknowledged.total > 0) {
- lines.push(` ${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} also suppressed above the ${counts.total} open item${counts.total !== 1 ? 's' : ''}.`);
+ lines.push(`${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} also suppressed above the ${counts.total} open item${counts.total !== 1 ? 's' : ''}.`);
}
- lines.push(hr);
return lines.join('\n');
}
diff --git a/src/check-command-router.cts b/src/check-command-router.cts
index 47db5aa06..e6f5e2227 100644
--- a/src/check-command-router.cts
+++ b/src/check-command-router.cts
@@ -890,7 +890,6 @@ function cmdTddReviewCheckpoint(projectDir: string, args: string[], raw: boolean
const violations = rows.filter(r => r.status === 'FAIL').length;
// Build review table
- const sep = '━'.repeat(53);
const tableHeader = '| Plan | RED | GREEN | REFACTOR | Status |';
const tableDivider = '|------|-----|-------|----------|--------|';
const tableRows = rows.map(r =>
@@ -898,9 +897,7 @@ function cmdTddReviewCheckpoint(projectDir: string, args: string[], raw: boolean
);
let table = [
- sep,
- ` TDD REVIEW — Phase ${phase}`,
- sep,
+ `### TDD REVIEW — Phase ${phase}`,
'',
`TDD Plans: ${tddPlanFiles.length} | Gate violations: ${violations}`,
'',
diff --git a/src/uat.cts b/src/uat.cts
index 1c9f6c977..5f3d73922 100644
--- a/src/uat.cts
+++ b/src/uat.cts
@@ -421,8 +421,6 @@ interface CheckpointFrame {
direction?: 'rtl';
}
-const CHECKPOINT_BOX_WIDTH = 64; // total column width of the ╔══...╗ border, borders stay byte-identical
-
const CHECKPOINT_FRAMES: Record = {
english: {
banner: 'CHECKPOINT: Verification Required',
@@ -529,56 +527,6 @@ function resolveCheckpointFrame(responseLanguage: string | undefined): Checkpoin
return (key && CHECKPOINT_FRAMES[key]) || CHECKPOINT_FRAMES.english;
}
-// Approximate terminal-cell width. East Asian Width W/F code points occupy two
-// cells, while Unicode combining marks occupy no additional cell beyond their
-// base character. Counting only W/F ranges is insufficient for scripts such as
-// Devanagari: Hindi vowel signs and viramas are combining marks, and treating
-// each as a full cell visibly shifts the checkpoint box's right border.
-function isWideCodePoint(codePoint: number): boolean {
- return (
- (codePoint >= 0x1100 && codePoint <= 0x115f) || // Hangul Jamo
- codePoint === 0x2329 || codePoint === 0x232a ||
- (codePoint >= 0x2e80 && codePoint <= 0x303e) || // CJK Radicals .. CJK Symbols and Punctuation
- (codePoint >= 0x3041 && codePoint <= 0x33ff) || // Hiragana .. CJK Compatibility
- (codePoint >= 0x3400 && codePoint <= 0x4dbf) || // CJK Unified Ideographs Extension A
- (codePoint >= 0x4e00 && codePoint <= 0x9fff) || // CJK Unified Ideographs
- (codePoint >= 0xa000 && codePoint <= 0xa4cf) || // Yi Syllables
- (codePoint >= 0xac00 && codePoint <= 0xd7a3) || // Hangul Syllables
- (codePoint >= 0xf900 && codePoint <= 0xfaff) || // CJK Compatibility Ideographs
- (codePoint >= 0xfe30 && codePoint <= 0xfe4f) || // CJK Compatibility Forms
- (codePoint >= 0xff00 && codePoint <= 0xff60) || // Fullwidth Forms
- (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
- (codePoint >= 0x20000 && codePoint <= 0x3fffd) // CJK Unified Ideographs Extension B+ / supplementary
- );
-}
-
-// Non-spacing/enclosing marks and format controls occupy zero terminal cells.
-// Spacing combining marks (General_Category=Mc), such as Devanagari vowel
-// signs, still advance the cursor and must contribute one column.
-const ZERO_WIDTH_MARK_RE = /\p{gc=Mn}|\p{gc=Me}|\p{gc=Cf}/u;
-
-// Iterates by Unicode code point (not UTF-16 code unit) so astral characters
-// are measured once, not as two surrogate units.
-function displayWidth(text: string): number {
- let width = 0;
- for (const ch of text) {
- if (ZERO_WIDTH_MARK_RE.test(ch)) continue;
- width += isWideCodePoint(ch.codePointAt(0) as number) ? 2 : 1;
- }
- return width;
-}
-
-// Pads `text` into a `║ text… ║` line matching CHECKPOINT_BOX_WIDTH. Content
-// that overflows the box (a longer translated string) is left unpadded rather
-// than truncated — a slightly ragged border beats losing text.
-function checkpointBoxLine(text: string): string {
- const innerWidth = CHECKPOINT_BOX_WIDTH - 2;
- const content = ` ${text}`;
- const padLength = innerWidth - displayWidth(content);
- const padded = padLength > 0 ? content + ' '.repeat(padLength) : content;
- return `║${padded}║`;
-}
-
const RTL_ISOLATE = '\u2067';
const POP_DIRECTIONAL_ISOLATE = '\u2069';
@@ -593,17 +541,15 @@ function buildCheckpoint(currentTest: { number: number; name: string; expected:
const banner = isolateCheckpointFrameText(frame.banner, frame);
const instruction = isolateCheckpointFrameText(frame.instruction, frame);
return [
- '╔══════════════════════════════════════════════════════════════╗',
- checkpointBoxLine(banner),
- '╚══════════════════════════════════════════════════════════════╝',
+ `### ${banner}`,
'',
`**Test ${currentTest.number}: ${currentTest.name}**`,
'',
currentTest.expected,
'',
- '──────────────────────────────────────────────────────────────',
- instruction,
- '──────────────────────────────────────────────────────────────',
+ '---',
+ '',
+ `**${instruction}**`,
].join('\n');
}
@@ -1676,7 +1622,6 @@ export = {
CHECKPOINT_FRAMES,
CHECKPOINT_LANGUAGE_ALIASES,
resolveCheckpointFrame,
- checkpointBoxLine,
parseDeferredItems,
parseDeferredItemsWithStatus,
acknowledgeDeferredItem,
diff --git a/tests/audit-command-cutover.test.cjs b/tests/audit-command-cutover.test.cjs
index 92c6cc37c..c560e4795 100644
--- a/tests/audit-command-cutover.test.cjs
+++ b/tests/audit-command-cutover.test.cjs
@@ -238,8 +238,8 @@ describe('audit cutover: output shape equivalence', () => {
`audit-open must succeed. stderr: ${result.error}`);
const lines = result.output.split('\n').map(l => l.trim()).filter(Boolean);
assert.ok(
- lines.includes('Milestone Close: Open Artifact Audit'),
- `report title must appear as a standalone line; got: ${JSON.stringify(lines.slice(0, 5))}`,
+ lines.includes('### Milestone Close: Open Artifact Audit'),
+ `report title must appear as a standalone Markdown heading line; got: ${JSON.stringify(lines.slice(0, 5))}`,
);
});
@@ -481,7 +481,7 @@ describe('audit-open — does not crash with ReferenceError (#2659)', () => {
* 2. Even after switching to `core.output(formatted, raw)`, the human-readable
* branch JSON-stringifies the formatted string because `core.output` only
* bypasses JSON encoding when called as `core.output(null, true, rawValue)`.
- * Result: stdout contains `"━━━…\n Milestone Close: …\n…"` (a JSON string
+ * Result: stdout contains `"### Milestone Close: …\n…"` (a JSON string
* literal) instead of the rendered report.
*
* The shape assertions below catch both regressions structurally — never via
@@ -640,10 +640,10 @@ describe('audit-open — output shape (#2911)', () => {
const lines = result.output.split('\n').map(l => l.trim()).filter(Boolean);
- // The first non-empty line must be the divider character row, *not* a
- // JSON-encoded string starting with a quote. If core.output JSON-stringified
+ // The first non-empty line must be the rendered Markdown heading row, *not*
+ // a JSON-encoded string starting with a quote. If core.output JSON-stringified
// the formatted report, the entire payload sits on one line wrapped in
- // double quotes ("━━━…\n…").
+ // double quotes ("### Milestone Close: …\n…").
assert.ok(
!result.output.startsWith('"'),
'text-mode stdout must not begin with a JSON quote (would mean the report was JSON.stringified)'
@@ -655,8 +655,8 @@ describe('audit-open — output shape (#2911)', () => {
// Section headers from formatAuditReport that must appear as standalone lines.
assert.ok(
- lines.includes('Milestone Close: Open Artifact Audit'),
- `expected report title as a standalone line; got lines: ${JSON.stringify(lines.slice(0, 5))}`
+ lines.includes('### Milestone Close: Open Artifact Audit'),
+ `expected report title as a standalone Markdown heading line; got lines: ${JSON.stringify(lines.slice(0, 5))}`
);
assert.ok(
lines.includes('All artifact types clear. Safe to proceed.'),
diff --git a/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json b/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json
index 7d404c976..2a3c7122e 100644
--- a/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json
+++ b/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json
@@ -1,6 +1,6 @@
{
"version": 1,
"paths": {
- "execute-plan.md": "#3370: the Pattern A dispatch prompt spec gained the gate-semantics clause (gate=\"blocking\" (the default) is auto-approvable in auto-mode per the executor's own checkpoint protocol, gate=\"blocking-human\" always surfaces to a human; add no instruction overriding that protocol), closing the identically-shaped dispatch-time gap on the single-plan path named in the issue. Growth ~248 bytes (38913 -> 39161, still under the DEFAULT 40 KiB ceiling). Supersedes the spent #2652 fragment (merged into next), which also named execute-plan.md and would otherwise double-ack the same path. #3659 appends --mode \"$ISOLATION\" to the #2649 pre-dispatch base-check and corrects the baseRef restore-advice to the orchestrator/harness split (+154B; the harness does not read baseRef, #48). Deliberate growth. — #3299 append: the tracer feedback gate's interactive branch, which read `Interactive: STOP -> return a checkpoint:human-verify` and keyed on auto-mode alone, now branches on HUMAN_VERIFY_MODE — under the documented `end-of-phase` default an automated-only tracer `` is re-run and continues to expansion with no checkpoint. Growth is 796 bytes (39315 -> 40111, still under the DEFAULT_CAP of 40960 LF bytes, 849 bytes of headroom; the earlier 39161 -> 39957 / 1003 figures were measured before the 2026-08-22 merge of next and are superseded): that branch plus a `HUMAN_VERIFY_MODE=` read alongside the existing RUNTIME/USE_WORKTREES reads in parse_segments. The read carries an explicit `--default end-of-phase` because `workflow.human_verify_mode` is absent from SCHEMA_DEFAULTS (src/config.cts): a bare config-get exits non-zero with `Key not found` on any project whose config.json predates #3309, which is every pre-existing project and the reporter's exact config. The three carve-outs (``, `gate=\"blocking-human\"`, `mid-flight`) are stated inline here rather than delegated to gsd-core/references/dispatch-isolation-gate.md, because this file is the inline dispatch path used for step-by-step / non-Claude-Code execution, where agents/gsd-executor.md is never loaded. This entry carries #3370's own reason forward verbatim above rather than replacing it — that growth is in the base and still needs its account. The blocking-human STOP is evaluated BEFORE the auto-mode branch here, matching golden rule 6 in checkpoints.md — the earlier ordering let an autonomous run continue past a blocking-human tracer."
+ "execute-plan.md": "#3370: the Pattern A dispatch prompt spec gained the gate-semantics clause (gate=\"blocking\" (the default) is auto-approvable in auto-mode per the executor's own checkpoint protocol, gate=\"blocking-human\" always surfaces to a human; add no instruction overriding that protocol), closing the identically-shaped dispatch-time gap on the single-plan path named in the issue. Growth ~248 bytes (38913 -> 39161, still under the DEFAULT 40 KiB ceiling). Supersedes the spent #2652 fragment (merged into next), which also named execute-plan.md and would otherwise double-ack the same path. #3659 appends --mode \"$ISOLATION\" to the #2649 pre-dispatch base-check and corrects the baseRef restore-advice to the orchestrator/harness split (+154B; the harness does not read baseRef, #48). Deliberate growth. — #3299 append: the tracer feedback gate's interactive branch, which read `Interactive: STOP -> return a checkpoint:human-verify` and keyed on auto-mode alone, now branches on HUMAN_VERIFY_MODE — under the documented `end-of-phase` default an automated-only tracer `` is re-run and continues to expansion with no checkpoint. Growth is 796 bytes (39315 -> 40111, still under the DEFAULT_CAP of 40960 LF bytes, 849 bytes of headroom; the earlier 39161 -> 39957 / 1003 figures were measured before the 2026-08-22 merge of next and are superseded): that branch plus a `HUMAN_VERIFY_MODE=` read alongside the existing RUNTIME/USE_WORKTREES reads in parse_segments. The read carries an explicit `--default end-of-phase` because `workflow.human_verify_mode` is absent from SCHEMA_DEFAULTS (src/config.cts): a bare config-get exits non-zero with `Key not found` on any project whose config.json predates #3309, which is every pre-existing project and the reporter's exact config. The three carve-outs (``, `gate=\"blocking-human\"`, `mid-flight`) are stated inline here rather than delegated to gsd-core/references/dispatch-isolation-gate.md, because this file is the inline dispatch path used for step-by-step / non-Claude-Code execution, where agents/gsd-executor.md is never loaded. This entry carries #3370's own reason forward verbatim above rather than replacing it — that growth is in the base and still needs its account. The blocking-human STOP is evaluated BEFORE the auto-mode branch here, matching golden rule 6 in checkpoints.md — the earlier ordering let an autonomous run continue past a blocking-human tracer. — #3028 append: the checkpoint_protocol step's one-line display spec described the old drawn box (`CHECKPOINT: [Type]` box … `YOUR ACTION: [signal]`). Under #3028 a checkpoint is a `### CHECKPOINT: [Type]` heading, a `---` break and a bolded action prompt, so the spec now reads `### CHECKPOINT: [Type]` heading → Progress {X}/{Y} → Task name → type-specific content → `---` → `**YOUR ACTION: [signal]**`. Growth is 22 bytes (40111 -> 40133, still under the DEFAULT_CAP of 40960 LF bytes, 827 bytes of headroom) — the naming of the two new Markdown tokens and the heading marker. Appended here rather than filed as a new fragment because a growth ack keys on the BARE FILENAME and #3370 already declares `execute-plan.md`; two sources naming one path is a hard duplicate-key error, so this follows the same supersede-by-append route #3370 itself took for the spent #2652 fragment. Every other file this change touches SHRANK, which needs no acknowledgment."
}
}
diff --git a/tests/responsive-separators.test.cjs b/tests/responsive-separators.test.cjs
new file mode 100644
index 000000000..be9867c5b
--- /dev/null
+++ b/tests/responsive-separators.test.cjs
@@ -0,0 +1,297 @@
+// Guards the responsive-separator convention defined in
+// gsd-core/references/ui-brand.md § "Separators and Banners".
+//
+// Shipped content (gsd-core/, agents/, commands/) is runtime-loaded text: an
+// agent reads it and emits the banners it describes. A fixed-width run of
+// box-drawing characters is ordinary text to a Markdown-rendering host, so in a
+// narrower pane it wraps and leaves orphan glyphs on a second line, coming apart
+// from the heading it was meant to frame. Markdown headings and thematic breaks
+// adapt to the available width instead.
+//
+// Enhancement for https://github.com/open-gsd/gsd-core/issues/3028.
+//
+// SCOPE - deliberately narrow, see the negative-space cases below:
+// * A "rule line" is a line whose trimmed content is composed ENTIRELY of
+// U+2500, U+2501 or U+2550, three or more of them. Tree glyphs, boxed-table
+// rows, progress bars and inline art all carry other characters and are
+// never flagged.
+// * A "panel character" is one of U+2554 U+2557 U+255A U+255D U+2551 anywhere
+// on a line.
+// * docs/ is NOT scanned: a documentation page may legitimately quote the old
+// form while explaining the change. src/ is not scanned either - its
+// section-divider comments are read in an editor, never emitted.
+
+const { describe, test, afterEach } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('fs');
+const path = require('path');
+const fc = require('fast-check');
+const { createTempDir, cleanup } = require('./helpers.cjs');
+
+const REPO_ROOT = path.join(__dirname, '..');
+
+// Runtime-loaded text: an agent reads these and emits what they describe.
+const SHIPPED_ROOTS = ['gsd-core', 'agents', 'commands'];
+
+// A shorter run than MIN_RULE_RUN is a glyph fragment, not a rule.
+const MIN_RULE_RUN = 3;
+const RULE_LINE_RE = /^[─━═]{3,}$/;
+
+// Double-line box vocabulary only. Single-line characters (└ ├ │ ...) are
+// deliberately absent: they are legitimate tree glyphs, not panel borders.
+const PANEL_CHAR_RE = /[╔╗╚╝║╠╣╦╩╬]/;
+
+const HEAVY = '━';
+const LIGHT = '─';
+const DOUBLE = '═';
+
+// `trim()` also strips a trailing \r, so CRLF files are handled without a
+// separate newline split dialect.
+function isFixedWidthRuleLine(line) {
+ return RULE_LINE_RE.test(line.trim());
+}
+
+function hasPanelCharacter(line) {
+ return PANEL_CHAR_RE.test(line);
+}
+
+function findMarkdownFiles(dir) {
+ const out = [];
+ let entries;
+ try {
+ entries = fs.readdirSync(dir, { withFileTypes: true });
+ } catch (err) {
+ if (err.code === 'ENOENT') return out;
+ throw err;
+ }
+ for (const entry of entries) {
+ const full = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ out.push(...findMarkdownFiles(full));
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
+ out.push(full);
+ }
+ }
+ return out;
+}
+
+function shippedMarkdownFiles() {
+ const files = [];
+ for (const root of SHIPPED_ROOTS) {
+ files.push(...findMarkdownFiles(path.join(REPO_ROOT, root)));
+ }
+ return files;
+}
+
+function scan(files, predicate) {
+ const violations = [];
+ for (const filePath of files) {
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const lines = content.split(/\r?\n/);
+ for (let i = 0; i < lines.length; i++) {
+ if (predicate(lines[i])) {
+ violations.push(
+ `${path.relative(REPO_ROOT, filePath)}:${i + 1}: ${lines[i].trim().slice(0, 40)}`
+ );
+ }
+ }
+ }
+ return violations;
+}
+
+const REMEDY =
+ '\n\nPer gsd-core/references/ui-brand.md "Separators and Banners":\n' +
+ ' - a stage banner is an ATX heading with no rule lines;\n' +
+ ' - a panel becomes a heading followed by its rows as plain lines;\n' +
+ ' - a divider between sections is --- with a blank line above and below.\n' +
+ 'A fixed-width run wraps in a narrow pane and comes apart from its heading.\n' +
+ 'See https://github.com/open-gsd/gsd-core/issues/3028';
+
+describe('responsive-separators', () => {
+ const tmpDirs = [];
+ afterEach(() => {
+ while (tmpDirs.length) cleanup(tmpDirs.pop());
+ });
+
+ function tempFile(name, content) {
+ const dir = createTempDir('responsive-separators');
+ tmpDirs.push(dir);
+ const filePath = path.join(dir, name);
+ fs.writeFileSync(filePath, content, 'utf-8');
+ return filePath;
+ }
+
+ test('the shipped-content walk actually finds files', () => {
+ const files = shippedMarkdownFiles();
+ // Without this the two repo-wide scans below would pass vacuously if the
+ // roots were ever renamed out from under them.
+ assert.ok(
+ files.length > 100,
+ `expected the shipped roots (${SHIPPED_ROOTS.join(', ')}) to hold markdown, found ${files.length}`
+ );
+ for (const root of SHIPPED_ROOTS) {
+ assert.ok(
+ files.some((f) => f.startsWith(path.join(REPO_ROOT, root) + path.sep)),
+ `no markdown found under shipped root "${root}"`
+ );
+ }
+ });
+
+ test('no shipped content emits a fixed-width rule line', () => {
+ const violations = scan(shippedMarkdownFiles(), isFixedWidthRuleLine);
+ assert.deepStrictEqual(
+ violations,
+ [],
+ 'Fixed-width box-drawing rule lines found in shipped content:\n' +
+ violations.map((v) => ` - ${v}`).join('\n') +
+ REMEDY
+ );
+ });
+
+ test('no shipped content emits box-panel characters', () => {
+ const violations = scan(shippedMarkdownFiles(), hasPanelCharacter);
+ assert.deepStrictEqual(
+ violations,
+ [],
+ 'Box-panel characters found in shipped content:\n' +
+ violations.map((v) => ` - ${v}`).join('\n') +
+ REMEDY
+ );
+ });
+
+ test(`run of ${MIN_RULE_RUN - 1} is not a rule (limit-1)`, () => {
+ assert.equal(isFixedWidthRuleLine(HEAVY.repeat(MIN_RULE_RUN - 1)), false);
+ assert.equal(isFixedWidthRuleLine(LIGHT.repeat(MIN_RULE_RUN - 1)), false);
+ assert.equal(isFixedWidthRuleLine(DOUBLE.repeat(MIN_RULE_RUN - 1)), false);
+ });
+
+ test(`run of ${MIN_RULE_RUN} is a rule (limit)`, () => {
+ assert.equal(isFixedWidthRuleLine(HEAVY.repeat(MIN_RULE_RUN)), true);
+ assert.equal(isFixedWidthRuleLine(LIGHT.repeat(MIN_RULE_RUN)), true);
+ assert.equal(isFixedWidthRuleLine(DOUBLE.repeat(MIN_RULE_RUN)), true);
+ });
+
+ test(`run of ${MIN_RULE_RUN + 1} is a rule (limit+1)`, () => {
+ assert.equal(isFixedWidthRuleLine(HEAVY.repeat(MIN_RULE_RUN + 1)), true);
+ });
+
+ test('run of 1 is not a rule', () => {
+ assert.equal(isFixedWidthRuleLine(HEAVY), false);
+ });
+
+ test('lines that merely contain box-drawing glyphs are not rules', () => {
+ const notRules = [
+ '└── file.md',
+ '├── dir/',
+ '├────┼────┤',
+ 'Progress: ████░░ 80%',
+ '| --- | --- |',
+ '---',
+ '### GSD ► EXECUTING WAVE 8',
+ '',
+ ' \t ',
+ `a${HEAVY.repeat(3)}`,
+ `${HEAVY.repeat(3)}a`,
+ ];
+ for (const line of notRules) {
+ assert.equal(
+ isFixedWidthRuleLine(line),
+ false,
+ `should not be a rule: ${JSON.stringify(line)}`
+ );
+ }
+ });
+
+ test('an indented rule line is still a rule', () => {
+ assert.equal(isFixedWidthRuleLine(` ${HEAVY.repeat(4)} `), true);
+ assert.equal(isFixedWidthRuleLine(`\t${LIGHT.repeat(3)}\t`), true);
+ });
+
+ test('mixed rule characters still count as a rule', () => {
+ assert.equal(isFixedWidthRuleLine(`${LIGHT}${LIGHT}${LIGHT}${HEAVY}${HEAVY}${DOUBLE}`), true);
+ });
+
+ test('panel characters are detected anywhere on the line', () => {
+ assert.equal(hasPanelCharacter('╔══╗'), true);
+ assert.equal(hasPanelCharacter('║ CHECKPOINT: Verification Required ║'), true);
+ assert.equal(hasPanelCharacter('╚══╝'), true);
+ assert.equal(hasPanelCharacter('╠══╣'), true);
+ assert.equal(hasPanelCharacter('├──┤'), false);
+ assert.equal(hasPanelCharacter('│ tree'), false);
+ assert.equal(hasPanelCharacter('### CHECKPOINT: Verification Required'), false);
+ assert.equal(hasPanelCharacter('└── file.md'), false);
+ });
+
+ test('the scan flags a heavy fixed-width rule line, with path and line number', () => {
+ const filePath = tempFile(
+ 'banner.md',
+ ['Display banner:', '', HEAVY.repeat(53), ' GSD ► PLANNING', ''].join('\n')
+ );
+ const violations = scan([filePath], isFixedWidthRuleLine);
+ assert.equal(violations.length, 1);
+ assert.match(violations[0], /banner\.md:3: /);
+ });
+
+ test('the scan flags box-panel characters', () => {
+ const filePath = tempFile(
+ 'panel.md',
+ ['╔══╗', '║ ERROR ║', '╚══╝'].join('\n')
+ );
+ const violations = scan([filePath], hasPanelCharacter);
+ assert.equal(violations.length, 3);
+ });
+
+ test('CRLF files are scanned identically to LF files', () => {
+ const crlf = tempFile(
+ 'crlf.md',
+ ['Display:', HEAVY.repeat(53), '└── file.md', ''].join('\r\n')
+ );
+ const violations = scan([crlf], isFixedWidthRuleLine);
+ assert.equal(violations.length, 1, 'the \\r must not defeat the rule-line trim');
+ assert.match(violations[0], /crlf\.md:2: /);
+ });
+
+ test('a file with no separators produces no violations', () => {
+ const filePath = tempFile(
+ 'clean.md',
+ ['# Title', '', '### GSD ► PLANNING', '', '---', '', 'Body.'].join('\n')
+ );
+ assert.deepStrictEqual(scan([filePath], isFixedWidthRuleLine), []);
+ assert.deepStrictEqual(scan([filePath], hasPanelCharacter), []);
+ });
+
+ test('an empty shipped file produces no violations', () => {
+ const filePath = tempFile('empty.md', '');
+ assert.deepStrictEqual(scan([filePath], isFixedWidthRuleLine), []);
+ assert.deepStrictEqual(scan([filePath], hasPanelCharacter), []);
+ });
+
+ const RULE_CHAR = fc.constantFrom(LIGHT, HEAVY, DOUBLE);
+
+ test(`property: any pure run of ${MIN_RULE_RUN}+ rule characters is flagged`, () => {
+ fc.assert(
+ fc.property(
+ fc.array(RULE_CHAR, { minLength: MIN_RULE_RUN, maxLength: 120 }),
+ fc.constantFrom('', ' ', ' ', '\t', ' \t '),
+ fc.constantFrom('', ' ', ' ', '\t', '\r'),
+ (chars, lead, trail) => isFixedWidthRuleLine(`${lead}${chars.join('')}${trail}`)
+ ),
+ { seed: 3028, numRuns: 300 }
+ );
+ });
+
+ test('property: a line carrying any non-rule, non-whitespace character is never flagged', () => {
+ fc.assert(
+ fc.property(
+ fc.array(RULE_CHAR, { minLength: 0, maxLength: 60 }),
+ fc
+ .string({ minLength: 1, maxLength: 12 })
+ .filter((s) => /\S/.test(s) && !/^[─━═\s]*$/.test(s)),
+ fc.array(RULE_CHAR, { minLength: 0, maxLength: 60 }),
+ (before, intruder, after) =>
+ isFixedWidthRuleLine(`${before.join('')}${intruder}${after.join('')}`) === false
+ ),
+ { seed: 3028, numRuns: 300 }
+ );
+ });
+});
diff --git a/tests/uat.test.cjs b/tests/uat.test.cjs
index 051a8f56b..b44a6d61a 100644
--- a/tests/uat.test.cjs
+++ b/tests/uat.test.cjs
@@ -15,7 +15,6 @@ const {
CHECKPOINT_FRAMES,
CHECKPOINT_LANGUAGE_ALIASES,
resolveCheckpointFrame,
- checkpointBoxLine,
parseDeferredItems,
} = require('../gsd-core/bin/lib/uat.cjs');
@@ -932,12 +931,12 @@ describe('uat render-checkpoint', () => {
assert.ok(japanese.includes('チェックポイント'));
assert.ok(japanese.includes('`pass`'));
- // Structural lines (borders, separators, Test N heading, expected content) are untouched.
- assert.ok(japanese.includes('╔══════════════════════════════════════════════════════════════╗'));
- assert.ok(japanese.includes('╚══════════════════════════════════════════════════════════════╝'));
- assert.ok(japanese.includes('──────────────────────────────────────────────────────────────'));
+ // Structural lines (heading marker, separator, Test N heading, expected content) are untouched.
+ assert.ok(japanese.includes('### チェックポイント: 検証が必要です'));
+ assert.ok(japanese.includes('---'));
assert.ok(japanese.includes('**Test 1: Sample**'));
assert.ok(japanese.includes('Something happens.'));
+ assert.ok(!/[╔╗╚╝║]/.test(japanese), 'the box border must be gone (#3028)');
assert.notStrictEqual(japanese, english);
});
@@ -1116,91 +1115,73 @@ describe('uat render-checkpoint', () => {
);
});
- // Regression: #2402 review medium finding — checkpointBoxLine() padded using
- // JS string `.length` (UTF-16 code units), not display width. The property
- // below supplies an independent, category-labelled cell-width oracle rather
- // than copying the implementation's Unicode range logic.
- describe('checkpoint banner padding uses terminal display width (#2402, #2530)', () => {
- test('property: category-labelled strings are padded to a 62-cell interior', () => {
- const oneCell = fc.constantFrom(
- { text: 'a', width: 1 },
- { text: '7', width: 1 },
- { text: ' ', width: 1 },
- { text: '\u093e', width: 1 }, // Mc: DEVANAGARI VOWEL SIGN AA
- { text: '\u093f', width: 1 }, // Mc: DEVANAGARI VOWEL SIGN I
- { text: '\u0949', width: 1 }, // Mc: DEVANAGARI VOWEL SIGN CANDRA O
- );
- const zeroCell = fc.constantFrom(
- { text: '\u0301', width: 0 }, // Mn: COMBINING ACUTE ACCENT
- { text: '\u093c', width: 0 }, // Mn: DEVANAGARI SIGN NUKTA
- { text: '\u20dd', width: 0 }, // Me: COMBINING ENCLOSING CIRCLE
- { text: '\u200d', width: 0 }, // Cf: ZERO WIDTH JOINER
- { text: '\u2066', width: 0 }, // Cf: LEFT-TO-RIGHT ISOLATE
- { text: '\u2069', width: 0 }, // Cf: POP DIRECTIONAL ISOLATE
- );
- const twoCell = fc.constantFrom(
- { text: '界', width: 2 },
- { text: '語', width: 2 },
- { text: '한', width: 2 },
- );
+ // Regression: #3028 — the checkpoint renderer no longer draws a 64-column
+ // double-line box (checkpointBoxLine/displayWidth/isWideCodePoint/
+ // ZERO_WIDTH_MARK_RE/CHECKPOINT_BOX_WIDTH were removed from src/uat.cts).
+ // These cases now pin the heading form (`### {banner}`) directly instead of
+ // a padded box interior; the localized-language coverage that used to prove
+ // display-width-correct padding now proves the banner text is emitted
+ // intact, unpadded, and box-free.
+ describe('checkpoint banner renders as a heading, not a box (#2402, #2530, #3028)', () => {
+ test('exact rendered banner heading for Japanese/Chinese/Korean (regression pin)', () => {
+ const currentTest = { number: 1, name: 'Sample', expected: 'Something happens.' };
+ const japanese = buildCheckpoint(currentTest, 'Japanese');
+ const chinese = buildCheckpoint(currentTest, 'Chinese');
+ const korean = buildCheckpoint(currentTest, 'Korean');
- fc.assert(fc.property(
- fc.array(fc.oneof(oneCell, zeroCell, twoCell), { maxLength: 35 }),
- (cells) => {
- const text = cells.map((cell) => cell.text).join('');
- const textWidth = cells.reduce((sum, cell) => sum + cell.width, 0);
- const padding = ' '.repeat(Math.max(0, 60 - textWidth));
- assert.strictEqual(
- checkpointBoxLine(text),
- `║ ${text}${padding}║`,
- );
- },
- ));
- });
+ assert.strictEqual(japanese.split('\n')[0], '### チェックポイント: 検証が必要です');
+ assert.strictEqual(chinese.split('\n')[0], '### 检查点:需要验证');
+ assert.strictEqual(korean.split('\n')[0], '### 체크포인트: 검증 필요');
- test('padding boundary: width limit-1, limit, and limit+1', () => {
- for (const width of [59, 60, 61]) {
- const text = 'a'.repeat(width);
- assert.strictEqual(
- checkpointBoxLine(text),
- `║ ${text}${' '.repeat(Math.max(0, 60 - width))}║`,
- `unexpected rendering at text width ${width}`,
- );
+ for (const output of [japanese, chinese, korean]) {
+ assert.ok(!/[╔╗╚╝║]/.test(output), 'the box border must be gone (#3028)');
}
});
- test('exact rendered banner lines for Japanese/Chinese/Korean (regression pin)', () => {
+ test('exact rendered Hindi banner heading ignores combining-mark cell width (regression pin)', () => {
const currentTest = { number: 1, name: 'Sample', expected: 'Something happens.' };
- assert.strictEqual(
- buildCheckpoint(currentTest, 'Japanese').split('\n')[1],
- '║ チェックポイント: 検証が必要です ║',
- );
- assert.strictEqual(
- buildCheckpoint(currentTest, 'Chinese').split('\n')[1],
- '║ 检查点:需要验证 ║',
- );
- assert.strictEqual(
- buildCheckpoint(currentTest, 'Korean').split('\n')[1],
- '║ 체크포인트: 검증 필요 ║',
- );
- });
-
- test('exact rendered Hindi banner line ignores combining-mark cell width (regression pin)', () => {
- const currentTest = { number: 1, name: 'Sample', expected: 'Something happens.' };
- assert.strictEqual(
- buildCheckpoint(currentTest, 'Hindi').split('\n')[1],
- `║ चेकपॉइंट: सत्यापन आवश्यक${' '.repeat(40)}║`,
- );
+ const hindi = buildCheckpoint(currentTest, 'Hindi');
+ assert.strictEqual(hindi.split('\n')[0], '### चेकपॉइंट: सत्यापन आवश्यक');
+ assert.ok(!/[╔╗╚╝║]/.test(hindi), 'the box border must be gone (#3028)');
});
test('exact rendered Arabic frame is isolated inside the LTR checkpoint layout', () => {
const currentTest = { number: 1, name: 'Sample', expected: 'Something happens.' };
const arabic = buildCheckpoint(currentTest, 'Arabic');
+ // The one behavior the box removal must not disturb: the RTL banner and
+ // instruction text stay wrapped in directional isolates.
assert.strictEqual(
- arabic.split('\n')[1],
- `║ \u2067نقطة تحقق: المراجعة مطلوبة\u2069${' '.repeat(34)}║`,
+ arabic.split('\n')[0],
+ `### نقطة تحقق: المراجعة مطلوبة`,
);
- assert.ok(arabic.includes('\u2067اكتب `pass` أو صف المشكلة.\u2069'));
+ assert.ok(arabic.includes('اكتب `pass` أو صف المشكلة.'));
+ assert.ok(!/[╔╗╚╝║]/.test(arabic), 'the box border must be gone (#3028)');
+ });
+
+ test('emits an over-long banner intact (no box to overflow)', (t) => {
+ // Previously a banner exceeding the 64-column inner width produced a
+ // ragged, unpadded border. Now there is no border to overflow — the
+ // full heading text is emitted intact regardless of length. None of the
+ // shipped frames are long enough to exercise this, so a synthetic frame
+ // is registered on the exported (mutable) lookup tables for the
+ // duration of the test.
+ const longBanner = `${'X'.repeat(80)}: Verification required well beyond the old 64-column box width`;
+ const frameKey = '__test_overlong_frame__3028__';
+ const aliasKey = '__test_overlong_alias__3028__';
+ CHECKPOINT_FRAMES[frameKey] = {
+ banner: longBanner,
+ instruction: 'Type `pass` or describe what\'s wrong.',
+ };
+ CHECKPOINT_LANGUAGE_ALIASES[aliasKey] = frameKey;
+ t.after(() => {
+ delete CHECKPOINT_FRAMES[frameKey];
+ delete CHECKPOINT_LANGUAGE_ALIASES[aliasKey];
+ });
+ const currentTest = { number: 1, name: 'Sample', expected: 'Something happens.' };
+ const output = buildCheckpoint(currentTest, aliasKey);
+ assert.strictEqual(output.split('\n')[0], `### ${longBanner}`,
+ 'an over-long banner must be emitted in full, not truncated or wrapped');
+ assert.ok(!/[╔╗╚╝║]/.test(output), 'no box characters should appear regardless of banner length');
});
});
@@ -1224,7 +1205,9 @@ awaiting: user response
assert.strictEqual(result.success, true, `render-checkpoint failed: ${result.error}`);
assert.ok(result.output.includes('**Test 2: Submit form validation**'));
assert.ok(result.output.includes('Empty submit keeps controls visible.'));
- assert.ok(result.output.includes("Type `pass` or describe what's wrong."));
+ // The instruction line renders as a bold line preceded by a `---` thematic
+ // break, not inside a box border (#3028).
+ assert.ok(result.output.includes("---\n\n**Type `pass` or describe what's wrong.**"));
});
test('strips protocol leak lines from current test copy', () => {
@@ -1440,14 +1423,14 @@ awaiting: user response
assert.ok(result.output.includes('Verificación requerida'), 'banner should be in Spanish');
assert.ok(result.output.includes('Escribe `pass`'), 'instruction line should be in Spanish');
- // Structure/IDs stay untranslated: box borders, the Test N: name line, and the
- // expected content are preserved verbatim.
- assert.ok(result.output.includes('╔══════════════════════════════════════════════════════════════╗'));
- assert.ok(result.output.includes('╚══════════════════════════════════════════════════════════════╝'));
- assert.ok(result.output.includes('──────────────────────────────────────────────────────────────'));
+ // Structure/IDs stay untranslated: the heading marker, the `---` separator,
+ // the Test N: name line, and the expected content are preserved verbatim.
+ assert.ok(result.output.includes('### PUNTO DE CONTROL: Verificación requerida'));
+ assert.ok(result.output.includes('---'));
assert.ok(result.output.includes('**Test 2: Submit form validation**'));
assert.ok(result.output.includes('Empty submit keeps controls visible.'));
assert.ok(result.output.includes('Validation error copy is shown.'));
+ assert.ok(!/[╔╗╚╝║]/.test(result.output), 'the box border must be gone (#3028)');
});
// Regression guard for the "unset ⇒ byte-identical English" acceptance criterion.
@@ -1471,17 +1454,15 @@ awaiting: user response
assert.strictEqual(result.success, true, `render-checkpoint failed: ${result.error}`);
const expected = [
- '╔══════════════════════════════════════════════════════════════╗',
- '║ CHECKPOINT: Verification Required ║',
- '╚══════════════════════════════════════════════════════════════╝',
+ '### CHECKPOINT: Verification Required',
'',
'**Test 2: Submit form validation**',
'',
'Empty submit keeps controls visible.\nValidation error copy is shown.',
'',
- '──────────────────────────────────────────────────────────────',
- 'Type `pass` or describe what\'s wrong.',
- '──────────────────────────────────────────────────────────────',
+ '---',
+ '',
+ '**Type `pass` or describe what\'s wrong.**',
].join('\n');
assert.strictEqual(result.output, expected);