You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️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.
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.
Epic: Port the epic filer to TypeScript
Description
The epic filer is the capability that turns a drafted
epic.mdinto the GitHub issue an epic ships and closes under./nxs.epicreaches 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.epicreads thelinkit 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-epicfiles an epic to completion with no Python interpreter process spawned.test_stub_promotion,test_needs_design_label, and the epic filer half oftest_writeback_integration— are asserted by TypeScript tests over the same cases.nexus epic-resolvefrom an issue filed by the port reconstructs the same field shape it reconstructs from an issue filed by the Python filer.epictitle, and an empty body.Personas
Per
docs/product/context.md.Terminology
Several near-synonyms meet in this capability and are easy to conflate. Throughout this epic:
epic.mdfile 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.## User Storiessection are removed, and the hidden meta block is appended.nexus:epic-metaHTML 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.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.complexityvalue in the draft's own frontmatter, and the only value the needs-design decision reads./nxs.epicarrives 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.Assumptions
create_epicmodule 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.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.gh, the project lookups, the retrying runner and the issue-type resolution — rather than porting a second copy of them from Python.Out of Scope
nexusexecutable's own verbs, or tocreate-story./nxs.epicproduces.Open Questions