Skip to content

Port the epic filer to TypeScript #352

Description

@sameera

Epic: Port the epic filer to TypeScript

⚠️ Utilization risk: assessed L (1–2 weeks). Fills the sprint with no slack for overruns. Watch for scope creep.

Description

The epic filer is the capability that turns a drafted epic.md into the GitHub issue an epic ships and closes under. /nxs.epic reaches it twice, on two different paths: once to create a new epic issue, and once to populate a backlog stub in place so the number the scope was deferred under survives promotion. It is the last capability in Nexus still written in Python. While it stays there the release keeps declaring a Python interpreter as a runtime requirement, and a lead can lose a planning session to an interpreter that resolves to the wrong thing at the moment the approval gate is finally cleared.

Everything this port needs already exists in TypeScript. The toolkit shell and the shared delivery-configuration resolver moved in #351; the story filer moved in #353, and building it produced the platform wrapper around gh, the project lookups, the retrying runner and the issue-type probe. So this epic is composition rather than construction: the code that is genuinely new is the body pipeline that derives an issue body from a draft, the frontmatter link write-back, the promotion path, and the classification decision that chooses between a GitHub issue type and a label. When it lands, the registry's last delegating row stops delegating.

The bar is behaviour preservation, not improvement. Component bodies invoke this capability by name and read its output; the resolver reads back the hidden meta block it writes; /nxs.epic reads the link it writes into the draft. Every flag, every line of output and every exit code stays as it is. The existing Python tests are the specification and are carried across rather than reinvented, so a divergence surfaces as a failing test rather than as a mangled issue body on an epic someone has already approved. The one deliberate exception is named in #381 and in the assumptions below.

Success Metrics

  • nexus-gh create-epic files an epic to completion with no Python interpreter process spawned.
  • The behaviours asserted by the existing Python tests — test_stub_promotion, test_needs_design_label, and the epic filer half of test_writeback_integration — are asserted by TypeScript tests over the same cases.
  • For a corpus of epic drafts, the issue body the port derives is byte-identical to the body the Python filer derives from the same draft, including the hidden meta block.
  • An epic resolved by nexus epic-resolve from an issue filed by the port reconstructs the same field shape it reconstructs from an issue filed by the Python filer.
  • Every flag the capability accepts today is accepted with the same meaning, and every exit code is unchanged: 0 for a filed epic and for a declined overwrite, non-zero for a refused promotion, a missing draft, a draft outside the target root, an absent epic title, and an empty body.
  • A promotion that is refused leaves the target issue byte-identical to how it was found — no title, body, or label change.
  • The epic filer source defines no function of its own for reading configuration, resolving classification, project targets or repository targets, upserting labels, probing issue types, or writing the settings file back — each is reached through the shared module from Port the toolkit shell and the shared delivery-config resolver to TypeScript #351 or the platform modules from Port the story filer to TypeScript #353, and a test asserts that.

Personas

Per docs/product/context.md.

Terminology

Several near-synonyms meet in this capability and are easy to conflate. Throughout this epic:

  • draft — the epic.md file passed on the command line. It lives in session scratch and is not committed; the filer reads it and writes one field back into it. "The epic file" and "the draft" are the same thing.
  • filed body — the issue body the filer derives from the draft. It is not the draft's body: the frontmatter is stripped, non-durable pointers and the ## User Stories section are removed, and the hidden meta block is appended.
  • meta block — the nexus:epic-meta HTML comment carrying the draft's raw frontmatter verbatim onto the issue. Invisible when rendered, read back by the resolver. It is what makes an epic re-resolvable from its number alone.
  • classification — the mechanism by which a filed issue is marked as an epic: either a GitHub issue type applied after creation, or an epic classification label passed at creation. The mode (types, labels, legacy-auto) decides which. "Epic classification label" is the one spelling this epic uses for that label; "epic label" and "classification label" are not used.
  • unplanned label — the label marking an issue as scope identified but not yet planned. It is the sole legality test for promotion, and promotion removes it. It is a different label from the epic classification label, which promotion adds.
  • needs-design label — a third, unrelated label, applied from the epic's declared complexity, declaring that this epic warrants a decision record.
  • declared complexity — the complexity value in the draft's own frontmatter, and the only value the needs-design decision reads. /nxs.epic arrives at that value by rolling up its story sizes, but that rollup happens before the filer is invoked; the filer reads a declared field and never aggregates anything.
  • promotion — populating an existing unplanned epic issue in place. Nothing is created and nothing is closed. Contrast creation, which mints a new issue.
  • settings file — the repository's own delivery-configuration file, and the one spelling this epic uses for it. The publishing decisions it holds are read through the shared resolver and written through the shared writer; "settings block" and "publishing configuration" are not used.
  • write-back — persisting the publishing decisions the run reached into the settings file, so the fragile probe and discovery run at most once per repository. Distinct from the link write-back, which writes the issue number into the draft's frontmatter.

Assumptions

  • The existing Python create_epic module is the behavioural specification for this port. Where a Python test asserts a behaviour, the TypeScript port asserts the same behaviour; this is not an occasion to change what the behaviour is.
  • The single exception is the confirmation prompt with no terminal attached, where the Python filer has no defined behaviour because it calls input() unguarded. The epic issue is created and its number recorded on the draft #381 pins that case to an explicit refusal rather than preserving a traceback.
  • The port reuses the shared helpers ported in Port the toolkit shell and the shared delivery-config resolver to TypeScript #351 — the configuration reader, the precedence resolver, classification, project-target and repository-target resolution, label upsert, the issue-type probe and the settings writer — and re-implements none of them.
  • The port also reuses the platform modules built for the story filer in Port the story filer to TypeScript #353 — the wrapper around gh, the project lookups, the retrying runner and the issue-type resolution — rather than porting a second copy of them from Python.
  • The ported code lands as TypeScript beside the story filer, following the existing per-capability library convention rather than introducing a new packaging shape.
  • The handler, not the registry row, is the seam the tests drive, so the cut-over in The toolkit answers create-epic without spawning Python #386 is a one-line change with nothing left to re-assert. This follows the decision recorded for the story filer port.
  • The Python sources stay on disk after this epic; only the registry row stops delegating. Removing the interpreter, the Python modules and their packaging is Retire the Python runtime and fold the toolkit into one executable #354.
  • The hidden meta block keeps its current marker and its verbatim-frontmatter shape, so an epic filed before the cut-over resolves identically after it.

Out of Scope

Open Questions

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicEpic (created by nxs-gh-create-epic)in-progressDesign filed; approval is the close of the record sub-issue

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions