17 Migrating from Other Tools
Quarto-Needs provides conservative migration adapters for bringing requirements from other docs-as-code and requirements-management ecosystems into Quarto Markdown declarations. Migration is deliberately review-first: generating a plan never rewrites authored files.
17.1 Supported Tools
| Source Tool | Input Format | Plan command |
|---|---|---|
| Sphinx-Needs | needs.json |
quarto-needs migrate sphinx-needs <path> --output <plan.json> |
| Doorstop | Directory of YAML/JSON documents | quarto-needs migrate doorstop <dir> --output <plan.json> |
| StrictDoc |
.sdoc files / folder |
quarto-needs migrate strictdoc <path> --output <plan.json> |
| OpenFastTrace | XML trace exchange | quarto-needs migrate openfasttrace <path> --output <plan.json> |
| ReqIF 1.2 |
.reqif document |
quarto-needs migrate reqif <path> --output <plan.json> |
--output is the migration-plan JSON, not a destination .qmd file.
The ReqIF adapter recovers identity from the document’s identifiers, but when a quarto-needs.canonical-id value is present (as in a Quarto-Needs ReqIF export) it takes precedence, so an export/import round trip preserves canonical IDs, titles, statuses, bodies, rationales, relations and attributes. Typed values are recovered as text — enumerations as their LONG-NAME, XHTML flattened — and every lossy conversion is reported as a plan issue rather than applied silently. DOCTYPE/ENTITY declarations are refused.
17.2 Migration Workflow
-
Generate and review the migration plan. This does not modify destination files:
quarto-needs migrate sphinx-needs docs/needs.json \ --output .quarto-needs/migrations/sphinx-needs-plan.json -
Build a reviewed apply plan by assigning each source ID an explicit project-relative destination. Add any required type/relation mappings for the source model:
quarto-needs migrate sphinx-needs docs/needs.json \ --type-map req=functional-requirement \ --destination REQ_001=requirements.qmd \ --apply-planWithout
--write, this still does not change authored files; the apply plan is written to.quarto-needs/migrations/sphinx-needs-apply-plan.jsonby default. -
Write only a ready, reviewed plan by repeating the reviewed mappings with
--write:quarto-needs migrate sphinx-needs docs/needs.json \ --type-map req=functional-requirement \ --destination REQ_001=requirements.qmd \ --apply-plan --write -
Validate the converted project:
quarto-needs scan quarto-needs check
For more than one source object, repeat --destination SOURCE_ID=path.qmd. --id-map SOURCE_ID=CANONICAL_ID is available when preserving the source ID is not appropriate. Writes are create-only and reject unresolved mappings, collisions, or unsafe destination paths rather than inferring identity.
17.2.1 Updating a migrated project
Every written block records its source identity (source-tool, source-project when the source declares one, and source-id). Re-running with --update-plan matches the current upstream items against those markers and classifies each one without touching authored files:
quarto-needs migrate sphinx-needs docs/needs.json \
--type-map req=functional-requirement \
--update-planThe plan (.quarto-needs/migrations/sphinx-needs-update-plan.json) reports ready-create for new items, ready-update with the changed fields for matched items, no-change, and blocked for anything needing a decision — including a canonical ID that already exists without a source marker, which is never treated as an implicit update. Each matched item carries a digest of its authored file so a stale plan can be refused.
Applying a reviewed update plan is explicit:
quarto-needs migrate sphinx-needs docs/needs.json \
--type-map req=functional-requirement \
--update-plan --apply-update --writeWrites are all-or-nothing: every item must be ready-create, ready-update or no-change; each updated file must still carry the digest the plan recorded, each created canonical ID must still be absent, each block must be locatable, and the project is re-scanned afterwards — any structural failure restores every touched file and removes every file this run created before the command fails. An upstream that changed an item and one that gained an item apply in the same run (the new block is appended to its reviewed destination); --apply-plan --write remains the first-migration path. Re-running the sync finds everything no-change, or refuses a stale create whose ID now exists — that refusal is the idempotence contract. --apply-update cannot be combined with --apply-plan.
17.3 Worked example: keeping a migrated project in sync
The first migration writes the source marker into every block, which is what a later run matches on:
::: {.need #REQ_001 type="functional-requirement" status="approved" source-tool="sphinx-needs" source-project="Legacy docs" source-id="REQ_001"}
verified-by: TC_001
## Authenticate users
The system shall authenticate users.
:::Later, the upstream changes REQ_001 and gains REQ_002. Review the sync plan before touching anything:
$ quarto-needs migrate sphinx-needs needs.json \
--type-map req=functional-requirement --type-map test=test-case \
--relation-map tests=verified-by --update-plan
Sphinx-Needs update plan: 3 item(s), applicable=False
[ready-update] REQ_001 -> REQ_001 changes=title,body (requirements.qmd)
matched by source-marker; file digest 94e45ab02d73b53062bf0c30f1a2669f932bc3bca8091dccb4b8b9a4c4d568dd
[review-required] REQ_002 -> REQ_002 (no destination)
reasons: destination file requires explicit review
[no-change] TC_001 -> TC_001 (verification.qmd)
matched by source-marker; file digest bdeadc92c96e4fcf0e2d5851ad822fc7a2b6335beed8fef6a9158d152b6dd1d5
applicable=False is the decision point: the new item has no destination yet. Name it and apply the whole sync in one reviewed run:
$ quarto-needs migrate sphinx-needs needs.json \
--type-map req=functional-requirement --type-map test=test-case \
--relation-map tests=verified-by --destination REQ_002=requirements.qmd \
--update-plan --apply-update --write
Applied 2 change(s):
updated REQ_001 -> REQ_001 (requirements.qmd)
created REQ_002 -> REQ_002 (requirements.qmd)
Updates replace their block in place; creates append to the reviewed destination. Running the same plan again finds everything no-change — or refuses a create whose ID now exists.
17.3.1 ReqIF round trip
Exports import through the same flow. Export a project and import it into a fresh root:
$ quarto-needs export --format reqif --output graph.reqif
Wrote ReqIF 1.2 to graph.reqif (3 objects, 1 relations)
$ quarto-needs migrate reqif graph.reqif \
--type-map functional-requirement=functional-requirement \
--type-map test-case=test-case --relation-map verified-by=verified-by \
--destination REQ_001=requirements.qmd --destination REQ_002=requirements.qmd \
--destination TC_001=verification.qmd \
--update-plan --apply-update --write
Applied 3 change(s):
created REQ_001 -> REQ_001 (requirements.qmd)
created REQ_002 -> REQ_002 (requirements.qmd)
created TC_001 -> TC_001 (verification.qmd)
The re-import recovers canonical IDs (the export carries each object’s quarto-needs.canonical-id, which wins over the opaque ReqIF identifier), titles, statuses, bodies, rationales and relations, and writes source-tool="reqif" markers, so a following --update-plan reports no-change. Scalar source-specific extras (priority, owner, numbers, booleans) are authored as preamble attributes, and a tags value embedded in the export’s attributes payload is recovered as tags; values the grammar cannot represent — lists, mappings, multi-line text, or a name reserved by the grammar or the relation catalog — stay in the plan’s extras for review instead of being silently altered.
17.4 Mapping Principles
-
Stable Identity: Existing IDs (e.g.
REQ_001orSYS-042) are preserved by default; remapping is explicit. - Relation Translation: Source links are mapped into the Quarto-Needs canonical relation catalog through explicit relation mappings where needed.
-
Review Before Mutation: Planning, apply-planning and update-planning are non-mutating;
--writeis a separate, explicit step. -
Quarto-Native Output: Ready candidates are rendered as standard Pandoc Div declarations (
::: {.need ...} ... :::).