Skip to content

Feat/strictdoc sdoc support#191

Merged
fasterthanlime merged 4 commits into
bearcove:mainfrom
kraln:feat/strictdoc-sdoc-support
May 15, 2026
Merged

Feat/strictdoc sdoc support#191
fasterthanlime merged 4 commits into
bearcove:mainfrom
kraln:feat/strictdoc-sdoc-support

Conversation

@kraln
Copy link
Copy Markdown
Contributor

@kraln kraln commented May 15, 2026

Adds StrictDoc support to tracey: load .sdoc files as specs alongside .md, and recognize @relation(UID, scope=..., role=...) source annotations alongside r[impl <id>].

The parser is a standalone strictdoc-parser crate (github.com/kraln/strictdoc-parser) which essentially exists to support the PR (happy to push to crates.io if that's important)

I tried to keep the change surface as small as possible, so there were some compromises along the way:

  • Format detection is extension-only
  • HTML for .sdoc OPTIONS.MARKUP: Markdown routes through marq; everything else (RST/Text/absent) is HTML-escaped
  • role=Refines emits a warning and produces no reference. There's not a good match for it in Tracey.

and maybe the most controversial one:

  • Lexer now accepts uppercase rule-ID bodies--r[foo] and r[FOO] are now distinct rule IDs

This could also be structured as a feature flag if that makes more sense.

kraln and others added 2 commits May 15, 2026 19:40
Two related fixes:

* Probe pnpm presence with `pnpm --version` instead of `pnpm version`.
  The subcommand form expects a version argument and fails with
  ERR_PNPM_INVALID_VERSION_BUMP outside a node project, so
  `output.status.success()` is false and the build script panics. The
  flag form matches every other "is X installed?" probe in the file.

* allowBuilds for @parcel/watcher and esbuild in
  dashboard/pnpm-workspace.yaml. pnpm 11 refuses to run third-party
  postinstall scripts by default and exits non-zero with
  ERR_PNPM_IGNORED_BUILDS; these two are required for vite/esbuild's
  native binaries.
Two related changes to the comment scanner:

1. Relax the rule-ID body character class from is_ascii_lowercase to
   is_ascii_alphabetic, in both the text-based extractor (lexer.rs) and the
   tree-sitter-driven one (code_units.rs). The prefix lowercase rule still
   stands — tracey's normative spec (markdown.syntax.marker+2) only
   constrains the prefix, and the rule-ID body restriction was an
   undocumented implementation invariant. parse_rule_id already preserves
   case, so existing case-sensitive equality and Hash/Ord behaviour are
   unchanged: r[foo] and r[FOO] remain distinct.

2. Add a sibling scan for StrictDoc-style @relation(UID[, UID...][,
   scope=...][, role=...]) source-comment annotations. Implementation
   parser comes from the strictdoc-parser crate. Role mapping:

       no role / role=Implements  ->  RefVerb::Impl
       role=Verifies              ->  RefVerb::Verify
       role=Refines               ->  warning + skipped (v1)
       role=<anything else>       ->  warning + skipped (v1)

   Multi-UID annotations expand to one ReqReference per UID, sharing the
   call's span. References use a synthetic prefix "r" so they pair with
   r[...]-style markdown specs without per-spec configuration.

Adds 7 lexer unit tests covering uppercase/mixed-case rule IDs and every
@relation shape (single UID, role=Verifies, multi-UID, Refines-warns).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kraln kraln force-pushed the feat/strictdoc-sdoc-support branch from 8f8c70e to eb323f1 Compare May 15, 2026 17:41
Copy link
Copy Markdown
Contributor

@fasterthanlime fasterthanlime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just need a crate publish, then we're good to go. I think!

Comment thread crates/tracey/build.rs
}

// Check if pnpm is available
let pnpm_check = shell_command("pnpm").arg("version").output();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woops

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<insert generic javascript bashing comment here, adding nothing to the PR but making myself feel smug>

Comment thread Cargo.toml Outdated
pulldown-cmark = "0.13"

# StrictDoc parser for .sdoc spec files
strictdoc-parser = { git = "https://github.com/kraln/strictdoc-parser", tag = "v0.1.0" }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so I can't actually merge until this is published. Because that would prevent publishing tracey.

Well, I guess I can, but I won't.

Copy link
Copy Markdown
Contributor Author

@kraln kraln May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📦

kraln and others added 2 commits May 15, 2026 20:40
Adds StrictDoc (.sdoc) ingest as a first-class peer of markdown specs.
File extension drives the choice of parser: .md keeps going through marq,
.sdoc routes to a new sdoc module that bridges strictdoc-parser onto
tracey's existing ReqDefinition / ExtractedRule types.

Spec rule fields are mapped from a RequirementView as follows:

    id           parse_rule_id(view.uid())   (preserves UID case)
    anchor_id    "r--<UID>"
    line         RequirementView.span.line
    span         covering the whole [REQUIREMENT] block
    raw          source slice of the same range
    metadata     ReqMetadata::default() (STATUS/MARKUP wiring is v2)
    html         see below

Requirements without a UID are silently skipped, matching strictdoc's own
behaviour for unidentified requirements (in the upstream 0.21.0 corpus
that's 424 of 1893 reqs across docs that aren't traceability sources).

HTML rendering for v1:

    OPTIONS.MARKUP = "Markdown"  ->  route STATEMENT through marq
    everything else (Text / RST / absent)  ->  HTML-escape and wrap in <p>

This makes the dashboard and `query` paths show readable prose without
pulling in an RST renderer in v1; the structured fields still surface in
`query rule <UID>` via the raw source slice.

The bridge plugs in at four call sites: the CLI spec loader
(load_rules_from_glob), the daemon's cached spec extractor
(extract_sdoc_rules_cached, mirroring the markdown one), bump.rs
(parse_spec_rules now dispatches on .sdoc), and the LSP diagnostic
clear-on-close path. A new tracey_core::is_spec_extension helper
centralises the "is this a .md or .sdoc?" predicate.

Source-side, .sdoc projects can use either the existing r[impl <UID>]
syntax or the StrictDoc-native @relation(<UID>, scope=function) form
added in the prior commit — both produce ReqReferences with prefix "r"
that match the synthetic prefix used by the bridge.

Tests:

  - crates/tracey/tests/sdoc_integration.rs (2 tests): end-to-end via
    Engine::new on a fixture with a 3-req .sdoc (BR-001 MARKUP:Markdown,
    BR-002/BR-003 default) and a Rust source file exercising every
    annotation shape plus a legacy r[impl ...] marker.

  - crates/tracey/tests/sdoc_corpus_smoke.rs (#[ignore]d): walks an
    out-of-tree StrictDoc corpus pointed at by $STRICTDOC_CORPUS and
    asserts the bridge handles >1000 docs without panicking. Tested
    against strictdoc-project/strictdoc 0.21.0 locally: 1434/1436 docs
    bridge cleanly (same two failures as strictdoc-parser's own parser
    CI — a deliberately-malformed fixture and a deep heredoc-in-heredoc
    case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small doc adds matching the feature:

- README.md: broaden the "(in markdown)" framing to include StrictDoc.
- guide/writing-specs.md: append a "StrictDoc format (.sdoc)" section
  covering UID conventions, the synthetic source-side prefix, MARKUP
  rendering rules, and the supported subset of the grammar.
- guide/annotating-code.md: add a "StrictDoc-style markers (@relation)"
  section with the role-to-verb mapping, multi-UID syntax, and a note
  on co-existing with r[...] markers.

tracey's normative spec (docs/content/spec/tracey.md) is untouched —
its requirement-ID design feels like a maintainer call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kraln kraln force-pushed the feat/strictdoc-sdoc-support branch from eb323f1 to 4b4fd57 Compare May 15, 2026 18:40
Copy link
Copy Markdown
Contributor

@fasterthanlime fasterthanlime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fasterthanlime fasterthanlime added this pull request to the merge queue May 15, 2026
Merged via the queue into bearcove:main with commit 0ddee4f May 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants