feat(#163): tighten gsd-roadmapper granularity defaults to reduce thin-phase fragmentation (#591)

* feat(#163): tighten gsd-roadmapper granularity defaults to reduce thin-phase fragmentation

Tighten the Granularity Calibration buckets in gsd-roadmapper (Coarse 3-5->2-4,
Standard 5-8->4-6, Fine 8-12->6-10) and append inline Key guidance naming the
thin-phase failure pattern (single requirement / internal-quality goal /
task-shaped success criteria) with instruction to fold into a neighbor rather
than create a standalone phase. Implements the maintainer-approved proposal
verbatim.

Update the canonical English docs that hardcoded the old phase-count numbers:
docs/CONFIGURATION.md and docs/FEATURES.md. Translated docs are
community-maintained and are not updated per-PR (CONTRIBUTING.md language
policy).

Prompt/doc text only; no code, format, or downstream-consumer changes. Agent
size-budget and skills-awareness tests pass; full suite green.

Closes #163

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#163): add Changed changeset for roadmapper granularity tightening

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(#163): lock tightened gsd-roadmapper granularity buckets

source-text-is-the-product test asserting the Granularity Calibration table
holds the tightened ranges (Coarse 2-4, Standard 4-6, Fine 6-10), that no row
maps to an old bucket, and that the Key paragraph carries the thin-phase
folding guidance. Would fail if the values regress.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-01 20:37:49 -04:00
committed by GitHub
parent 0c1084ba88
commit 9ffe45a7c3
5 changed files with 69 additions and 6 deletions

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 591
---
**`gsd-roadmapper` granularity defaults tightened to reduce thin-phase fragmentation.** Coarse 3-5 -> 2-4, Standard 4-6 (was 5-8), Fine 6-10 (was 8-12). New inline guidance below the Granularity Calibration table names the thin-phase failure pattern (single requirement, internal-quality goal, task-shaped success criteria) and instructs the agent to fold into the most-related neighbor rather than create a standalone phase. Affects `/gsd-new-project`, `/gsd-new-milestone`, and `/gsd-plan-milestone-gaps`.

View File

@@ -215,11 +215,11 @@ Read granularity from config.json. Granularity controls compression tolerance.
| Granularity | Typical Phases | What It Means |
|-------------|----------------|---------------|
| Coarse | 3-5 | Combine aggressively, critical path only |
| Standard | 5-8 | Balanced grouping |
| Fine | 8-12 | Let natural boundaries stand |
| Coarse | 2-4 | Combine aggressively, critical path only |
| Standard | 4-6 | Balanced grouping (tightened from 5-8 in 2026-05; downstream observation that the prior baseline encouraged ~15-20% over-fragmentation, often manifesting as thin "maintenance" phases that would have been better folded into a neighbor) |
| Fine | 6-10 | Let natural boundaries stand |
**Key:** Derive phases from work, then apply granularity as compression guidance. Don't pad small projects or compress complex ones.
**Key:** Derive phases from work, then apply granularity as compression guidance. Don't pad small projects or compress complex ones. When a phase you are about to write would have a single requirement, an internal-quality goal ("improve X", "refactor Y", "add tests for Z"), or success criteria that read as tasks rather than user-observable outcomes, prefer to fold it into the most-related neighbor instead of creating a standalone phase.
## Good Phase Patterns

View File

@@ -135,7 +135,7 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new
| Setting | Type | Options | Default | Description |
|---------|------|---------|---------|-------------|
| `mode` | enum | `interactive`, `yolo` | `interactive` | `yolo` auto-approves decisions; `interactive` confirms at each step |
| `granularity` | enum | `coarse`, `standard`, `fine` | `standard` | Controls phase count: `coarse` (3-5), `standard` (5-8), `fine` (8-12) |
| `granularity` | enum | `coarse`, `standard`, `fine` | `standard` | Controls phase count: `coarse` (2-4), `standard` (4-6), `fine` (6-10) |
| `model_profile` | enum | `quality`, `balanced`, `budget`, `adaptive`, `inherit` | `balanced` | Model tier for each agent (see [Model Profiles](#model-profiles)). `adaptive` was added per [#1713](https://github.com/open-gsd/gsd-core/issues/1713) / [#1806](https://github.com/open-gsd/gsd-core/issues/1806) and resolves the same way as the other tiers under runtime-aware profiles. |
| `runtime` | string | `claude`, `codex`, or any string | (none) | Active runtime for [runtime-aware profile resolution](#runtime-aware-profiles-2517). When set, profile tiers (opus/sonnet/haiku) resolve to runtime-native model IDs. Today only the Codex install path emits per-agent model IDs from this resolver; other runtimes (`opencode`, `gemini`, `qwen`, `copilot`, …) consume the resolver at spawn time and gain dedicated install-path support in [#2612](https://github.com/open-gsd/gsd-core/issues/2612). When unset (default), behavior is unchanged from prior versions. Added in v1.39 |
| `model_profile_overrides.<runtime>.<tier>` | string \| object | per-runtime tier override | (none) | Override the runtime-aware tier mapping for a specific `(runtime, tier)`. Tier is one of `opus`, `sonnet`, `haiku`. Value is either a model ID string (e.g. `"gpt-5-pro"`) or `{ model, reasoning_effort }`. See [Runtime-Aware Profiles](#runtime-aware-profiles-2517). Added in v1.39 |

View File

@@ -208,7 +208,7 @@
**Functional Requirements:**
- Questions adapt based on detected project type (web app, CLI, mobile, API, etc.)
- Research agents have web search capability for current ecosystem information
- Granularity setting controls phase count: `coarse` (3-5), `standard` (5-8), `fine` (8-12)
- Granularity setting controls phase count: `coarse` (2-4), `standard` (4-6), `fine` (6-10)
- `--auto` mode extracts all information from the provided document without interactive questioning
- Existing codebase context (from `/gsd-map-codebase`) is loaded if present

View File

@@ -0,0 +1,58 @@
// allow-test-rule: source-text-is-the-product
// agents/gsd-roadmapper.md is the installed agent — the Granularity Calibration
// table IS the deployed instruction. Asserting on its text asserts what runs in
// production. Locks the tightened phase-count buckets from #163.
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
function readAgent(name) {
return fs.readFileSync(path.join(AGENTS_DIR, `${name}.md`), 'utf8');
}
// Extract the "## Granularity Calibration" section (up to the next "## " heading)
// so number-range assertions are scoped and cannot be satisfied by unrelated text
// elsewhere in the agent file.
function granularitySection(content) {
const start = content.indexOf('## Granularity Calibration');
assert.ok(start !== -1, 'Granularity Calibration section must exist');
const rest = content.slice(start + '## Granularity Calibration'.length);
const nextHeading = rest.indexOf('\n## ');
return nextHeading === -1 ? rest : rest.slice(0, nextHeading);
}
describe('gsd-roadmapper granularity calibration (#163)', () => {
const section = granularitySection(readAgent('gsd-roadmapper'));
test('Coarse bucket is tightened to 2-4', () => {
assert.ok(/\|\s*Coarse\s*\|\s*2-4\s*\|/.test(section), 'Coarse must be 2-4');
});
test('Standard bucket is tightened to 4-6', () => {
assert.ok(/\|\s*Standard\s*\|\s*4-6\b/.test(section), 'Standard must be 4-6');
});
test('Fine bucket is tightened to 6-10', () => {
assert.ok(/\|\s*Fine\s*\|\s*6-10\s*\|/.test(section), 'Fine must be 6-10');
});
test('no granularity row maps to an old bucket (3-5 / 5-8 / 8-12)', () => {
// Scope to the second ("Typical Phases") column of each row so the approved
// explanatory footnote mentioning "5-8" in the third column does not false-fail.
assert.ok(!/\|\s*Coarse\s*\|\s*3-5\b/.test(section), 'Coarse must not map to 3-5');
assert.ok(!/\|\s*Standard\s*\|\s*5-8\b/.test(section), 'Standard must not map to 5-8');
assert.ok(!/\|\s*Fine\s*\|\s*8-12\b/.test(section), 'Fine must not map to 8-12');
});
test('Key paragraph names the thin-phase pattern and prefers folding into a neighbor', () => {
assert.ok(
section.includes('fold it into the most-related neighbor'),
'Key guidance must instruct folding thin phases into the most-related neighbor'
);
});
});