Skip to content

Stable anchors for design decisions so other artifacts can link to them #2056

Open

Description

@pierreboissinot

The problem, in one or two sentences

When proposal.md or tasks.md mentions a design decision, it does so in free text ("as decided above", "per decision 3"). A reviewer can't jump to that decision, and an agent working in a fresh context can't reliably tell which decision a task implements. Nothing flags a mention whose decision has since been renumbered, renamed or removed.

Who this affects

Everyone who uses the spec-driven schema and writes a design.md, with any agent. Reviewers are hit hardest when they read a change on GitHub/GitLab or in an editor's Markdown preview. So are agents running /opsx:apply in a new session, because they rely on the written artifacts and not on the conversation that produced them.

What you tried

I tested this in a small playground repo: pierreboissinot/openspec-playground. It has a ~40-line bookmark CLI, OpenSpec 1.14.1 and the Claude Code integration. A fresh headless Claude Code session ran /opsx:propose once per variant with the same feature prompt: tags plus a Netscape HTML export, with backward-compatible storage.

1. Default spec-driven schema (baseline): artifacts

  • design.md had six decisions, each under its own heading, with no IDs.
  • Nothing linked to a decision: 0 links across proposal.md, tasks.md and design.md.
  • tasks.md cited spec scenarios by quoted name, but restated the design choices inline. For example, task 2.1 repeats the whole --tag parsing decision.
  • One risk ("hand-rolled parsing can drift") is the trade-off of a decision, with no link between them.

2. Same prompt, plus per-artifact rules in openspec/config.yaml: artifacts (decision headings ### D<n>: <title>; risks and tasks link with relative Markdown links)

  • The agent followed the rules: decisions D1–D6, 4 risks and the migration plan link to decisions, and 6 tasks link to decisions without restating them.
  • A small script applying GitHub's heading-slug rules found that all 11 links resolve.
  • Two problems showed up:
    • YAML pitfall. Writing the convention as a rule (### D<n>: <short title>) makes YAML read the item as a key/value pair. The rule is dropped and the CLI prints only a warning. It works once the item is quoted.
    • Silent breakage. Retitling one decision (D5: Export rendering → D5: Render the export by building strings with html.escape) broke 2 links. openspec validate --strict still reports the change as valid.

So a convention carried only by rules works, but each team has to rediscover it (including the quoting trap), and nothing catches links that break later. The default design.md template gives no structure per decision: ## Decisions holds only a placeholder comment.

Caveats: one run per variant, one agent (Claude Code), one model.

What you have in mind

This is a sketch to discuss, not a fixed design.

Step 1: template and instructions only (no CLI change)

  • In schemas/spec-driven/templates/design.md and the design instruction in schema.yaml, give each decision its own heading with a stable ID that is never reused, e.g. ### D1: Scope model in global config.
  • In the tasks instruction (and wherever design.md content is referenced), ask for links to decisions as standard relative Markdown links: [D1](./design.md#d1-scope-model-in-global-config). These are plain links, so they work in every agent, renderer and editor, with nothing to install.

Step 2: optional, a separate change proposal if step 1 is worth it

Open questions

  1. What the anchor points at.
    • (a) The generated heading slug: zero syntax, but it changes when the title changes.
    • (b) An explicit ID such as <a id="d1"></a>: stable, but it puts HTML into the artifacts.
    • (c) The ID alone (D1), resolved by OpenSpec and not by the renderer: stable, but it isn't a clickable link everywhere.
  2. Scope. Links between files in the same change survive archiving, because the files move together. A link from a live spec to a decision in an archived design.md doesn't survive. I'd keep specs out of scope here. Decisions that outlive their change belong to the broader question in Exploration: giving project knowledge beyond behavior a home in OpenSpec #1980 (and Discussion 1553 on ADRs).

Related: #1980, #783, #1979, #2029, Discussion 1553, Discussion 1564

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions