◐ Off-By-One · answer catalog

go-dispatcher-spec-decomposition

2 answer(s)godockergodocker

go-dispatcher-spec-decomposition

📦 Source in repository (JSON)

Answer 1

The bug (GAP-007): DecomposeSpec only recognized ## Phase / ## Feature H2 headings as task sections, but the repo's own specs use # Helix Feature N H1 titles whose H2 sections (## Overview, ## Requirements, ## Acceptance Criteria) contain neither keyword — so helix dispatcher list-tasks --spec specs/*.md found zero sections and errored on every spec.

The fix (in decompose.go) adds one case-insensitive H1 prefix matcher and one switch branch; existing H2 detection and the ErrNoSections path are untouched:

var (
    // h2Section matches existing "## Phase N" / "## Feature X" H2 headings.
    h2Section = regexp.MustCompile(`(?i)^##\s+.*(?:phase|feature).*$`)
    // h1Feature matches the repo's "# Helix Feature N" H1 title convention
    // (case-insensitive, e.g. "# HELIX FEATURE 1 — Persistent Job Queue").
    h1Feature = regexp.MustCompile(`(?i)^#\s+helix\s+feature\b`)
)

func DecomposeSpec(markdown string) ([]Task, error) {
    var tasks []Task
    var cur *Task

    start := func(heading string) {
        if cur != nil {
            cur.Body = strings.TrimSpace(cur.Body)
            tasks = append(tasks, *cur)
        }
        cur = &Task{Title: heading, Priority: len(tasks) + 1}
    }

    for _, line := range strings.Split(markdown, "\n") {
        trimmed := strings.TrimSpace(line)
        switch {
        case h1Feature.MatchString(trimmed):
            start(strings.TrimSpace(trimmed[1:])) // drop leading '#'
        case h2Section.MatchString(trimmed):
            start(strings.TrimSpace(trimmed[2:])) // drop leading '##'
        default:
            if cur != nil {
                cur.Body += line + "\n"
            }
        }
    }
    if cur != nil {
        cur.Body = strings.TrimSpace(cur.Body)
        tasks = append(tasks, *cur)
    }

    if len(tasks) == 0 {
        return nil, ErrNoSections
    }
    return tasks, nil
}

Semantics: each # Helix Feature… H1 (matched case-insensitively, word-boundary so # Helix Features doesn't match) starts one task; priority is the 1-based document order; H1 and H2 markers can be mixed and interleave in order; content before the first marker is skipped; original heading case is preserved in Task.Title.

Repo: ~/helix-dispatcher — decompose.go (fix, 68 lines incl. docs), cmd/helix/main.go (CLI), specs/feature-1.md … feature-6.md (the 6 repo-convention specs), decompose_test.go (3 existing-behavior tests + 1 new test).

Evidence & signatures

**New test** `TestDecomposeSpecHelixFeatureH1` (1 new test function; existing tests untouched) covers: `# HELIX FEATURE 1` all-caps matching, title casing preserved, priority-by-order (1, 2), body split between sections, mixed `# Helix Feature` H1 + `## Phase` H2 order preservation, and a live loop asserting all 6 `specs/*.md` decompose to exactly 1 task each.

```
go test -race -count=1 ./...   →  ok  helix  1.013s
  PASS TestDecomposeSpecH2Phase / TestDecomposeSpecH2Feature / TestDecomposeSpecNoSections
  PASS TestDecomposeSpecHelixFeatureH1
```

**Live CLI run of the originally failing command** — all 6 feature specs now decompose (previously: `helix: … spec contains no task sections` per file):

```
$ helix dispatcher list-tasks --spec specs/*.md
specs/feature-1.md: 1 task(s)   [1] Helix Feature 1 — Persistent Job Queue
specs/feature-2.md: 1 task(s)   [1] Helix Feature 2 — Retry with Backoff
specs/feature-3.md: 1 task(s)   [1] Helix Feature 3 — Priority Scheduling
specs/feature-4.md: 1 task(s)   [1] Helix Feature 4 — Cancellation API
specs/feature-5.md: 1 task(s)   [1] Helix Feature 5 — Observability Metrics
specs/feature-6.md: 1 task(s)   [1] Helix Feature 6 — Plugin Sandbox
```

**Edge cases tested:**
- No-section doc → `ErrNoSections`, `tasks == nil`, CLI exits 1 with `helix: no-sections.md: spec contains no task sections` (error path preserved).
- `# HELIX FEATURE` (all caps, no number) → matches, exit 0.
- `# Helix Features` (plural) → correctly NOT a marker, exit 1 (word boundary).
- Quoted glob `--spec "specs/*.md"` → expanded by CLI, works.
- Existing `## Phase 1 — Design` / `## Feature: Retry` H2 decomposition unchanged.
{"model": "deepseek-v4-flash", "problem_class": "go-dispatcher-spec-decomposition", "result": "passed", "tests": 4}

Answer 2

The bug (GAP-007): DecomposeSpec only recognized ## Phase / ## Feature H2 headings as task sections, but the repo's own specs use # Helix Feature N H1 titles whose H2 sections (## Overview, ## Requirements, ## Acceptance Criteria) contain neither keyword — so helix dispatcher list-tasks --spec specs/*.md found zero sections and errored on every spec.

The fix (in decompose.go) adds one case-insensitive H1 prefix matcher and one switch branch; existing H2 detection and the ErrNoSections path are untouched:

var (
    // h2Section matches existing "## Phase N" / "## Feature X" H2 headings.
    h2Section = regexp.MustCompile(`(?i)^##\s+.*(?:phase|feature).*$`)
    // h1Feature matches the repo's "# Helix Feature N" H1 title convention
    // (case-insensitive, e.g. "# HELIX FEATURE 1 — Persistent Job Queue").
    h1Feature = regexp.MustCompile(`(?i)^#\s+helix\s+feature\b`)
)

func DecomposeSpec(markdown string) ([]Task, error) {
    var tasks []Task
    var cur *Task

    start := func(heading string) {
        if cur != nil {
            cur.Body = strings.TrimSpace(cur.Body)
            tasks = append(tasks, *cur)
        }
        cur = &Task{Title: heading, Priority: len(tasks) + 1}
    }

    for _, line := range strings.Split(markdown, "\n") {
        trimmed := strings.TrimSpace(line)
        switch {
        case h1Feature.MatchString(trimmed):
            start(strings.TrimSpace(trimmed[1:])) // drop leading '#'
        case h2Section.MatchString(trimmed):
            start(strings.TrimSpace(trimmed[2:])) // drop leading '##'
        default:
            if cur != nil {
                cur.Body += line + "\n"
            }
        }
    }
    if cur != nil {
        cur.Body = strings.TrimSpace(cur.Body)
        tasks = append(tasks, *cur)
    }

    if len(tasks) == 0 {
        return nil, ErrNoSections
    }
    return tasks, nil
}

Semantics: each # Helix Feature… H1 (matched case-insensitively, word-boundary so # Helix Features doesn't match) starts one task; priority is the 1-based document order; H1 and H2 markers can be mixed and interleave in order; content before the first marker is skipped; original heading case is preserved in Task.Title.

Repo: ~/helix-dispatcher — decompose.go (fix, 68 lines incl. docs), cmd/helix/main.go (CLI), specs/feature-1.md … feature-6.md (the 6 repo-convention specs), decompose_test.go (3 existing-behavior tests + 1 new test).

Evidence & signatures

**New test** `TestDecomposeSpecHelixFeatureH1` (1 new test function; existing tests untouched) covers: `# HELIX FEATURE 1` all-caps matching, title casing preserved, priority-by-order (1, 2), body split between sections, mixed `# Helix Feature` H1 + `## Phase` H2 order preservation, and a live loop asserting all 6 `specs/*.md` decompose to exactly 1 task each.

```
go test -race -count=1 ./...   →  ok  helix  1.013s
  PASS TestDecomposeSpecH2Phase / TestDecomposeSpecH2Feature / TestDecomposeSpecNoSections
  PASS TestDecomposeSpecHelixFeatureH1
```

**Live CLI run of the originally failing command** — all 6 feature specs now decompose (previously: `helix: … spec contains no task sections` per file):

```
$ helix dispatcher list-tasks --spec specs/*.md
specs/feature-1.md: 1 task(s)   [1] Helix Feature 1 — Persistent Job Queue
specs/feature-2.md: 1 task(s)   [1] Helix Feature 2 — Retry with Backoff
specs/feature-3.md: 1 task(s)   [1] Helix Feature 3 — Priority Scheduling
specs/feature-4.md: 1 task(s)   [1] Helix Feature 4 — Cancellation API
specs/feature-5.md: 1 task(s)   [1] Helix Feature 5 — Observability Metrics
specs/feature-6.md: 1 task(s)   [1] Helix Feature 6 — Plugin Sandbox
```

**Edge cases tested:**
- No-section doc → `ErrNoSections`, `tasks == nil`, CLI exits 1 with `helix: no-sections.md: spec contains no task sections` (error path preserved).
- `# HELIX FEATURE` (all caps, no number) → matches, exit 0.
- `# Helix Features` (plural) → correctly NOT a marker, exit 1 (word boundary).
- Quoted glob `--spec "specs/*.md"` → expanded by CLI, works.
- Existing `## Phase 1 — Design` / `## Feature: Retry` H2 decomposition unchanged.
{"model": "deepseek-v4-flash", "problem_class": "go-dispatcher-spec-decomposition", "result": "passed", "tests": 4}
Generated from the verified corpus · MIT licensedBack to the catalog