Skip to content

ReqIF/Flexo requirements viewpoints (M1: vendor mbse::requirements to ufo-types) - #36

Merged
elasticdotventures merged 18 commits into
mainfrom
feature/flexo-reqif-implementation
Sep 19, 2026
Merged

elasticdotventures merged 18 commits into
mainfrom
feature/flexo-reqif-implementation

Conversation

@elasticdotventures

Copy link
Copy Markdown
Member

Summary

  • Stateless requirements-view slice: normalized requirements graph, five induced viewpoints (decomposition, traceability, impact, behaviour, verification coverage), HTTP view/render routes on the existing D2/Kroki cache. See docs/HANDOFF-2026-09-19-reqif-flexo.md for full context and boundaries.
  • M1 complete: the requirements semantic model is vendored upstream as ufo-types::mbse::requirements (ufo-types v0.15.0, PromptExecution/ufo-types#28). kr0ki-core re-exports it rather than owning a copy — zero drift, zero duplication. docs/TODO.md updated.
  • Draft: box 2/3 upstream work (StrictDoc ReqIF adapter, reqif-opa-mcp refactor, Flexo baseline adapter, Playb00k workspace, MCP tools) is still open — see docs/TODO.md.

Test plan

  • cargo build --workspace
  • cargo clippy -p kr0ki-core -p kr0ki-server -- -D warnings
  • cargo test -p kr0ki-core -p kr0ki-server (148 + 44 tests)

elasticdotventures and others added 18 commits September 19, 2026 07:06
… transparency

Cherry of 249837b onto post-#34 main:
- project_store.py: per-thread project (title/goal/requirements/locked, verbatim
  prompt log, LLM thinking log, QA history) persisted beside charts; endpoints
  GET /projects[/{id}], POST /projects/rename
- ask_user tool: multiple-choice refinement questions as AG-UI interrupts
  (options ride responseSchema defaults); answers recorded as QA, run continues
- system prompt workflow: THINK → ASK → 'Locked in:' → RENDER; server-side lock detection
- THINKING_* events: reasoning_content as 💭 thinking blocks, captured server-side
- UI: project banner (editable title, locked badge, QA, expandable prompt+thinking log)
- live E2E verified: 2 refinement rounds -> locked in -> k8s panel rendered
- tests: 38 python, 15 vitest
The client's state machine rejects TOOL_CALL_END/RESULT without a prior
TOOL_CALL_START ('No active tool call found'), aborting the run before the
interrupt reached the UI. ask_user now emits START → ARGS → END → RESULT;
propose_draft_change (which emitted no tool-call events at all) gets the
same lifecycle. Validated 6/6 against the @ag-ui/core event union; live
verified: question interrupt now reaches the browser and answers flow.
…e inputs

- autoResumeInterrupts: false — the client's auto-resume fired resume() the
  moment an interrupt was answered, colliding with our explicit chat.send()
  ('A run is already in progress'); continuation is now driven solely by
  submitAnswer()
- question + options render immediately (no 'Answer question' gate click);
  submitAnswer targets the interrupt by id
- dark theme: composer/prompt-editor/freetext/project-title inputs got UA
  white defaults on the dark app — now #091127 bg + #edf5ff text; message
  bubbles, interrupt cards, error/lock/requirement chips re-themed to match
  the app's palette
…o one line

'storyb00k' is an internal codename and stays out of the UI nav; the editor
view's header paragraph was a wall of text — now one sentence.
…opy trimmed

- answering an ask_user interrupt now calls chat.resume(), which replays the
  interrupted run with the recorded response — chat.send() started a fresh
  run and the agent rejected it ('Thread has N pending interrupt(s) not
  addressed by resume')
- sidebar copy: 'Example fixtures for every supported format — or clear the
  source and render your own.' (internal jargon out)
The client replays identical messages on resume, so the LLM never saw the
answer and re-asked. Each run now injects answered QA (and any locked-in
requirements) into the system prompt as PROJECT MEMORY — 'never ask the same
or an equivalent question again' — and appends unanswered QA as explicit user
turns. 39/39 python (new regression test captures the second run's LLM
messages and asserts the memory + answer turn are present).
Intent-first ergonomics: users don't know the diagram type they need. Gallery
browse-by-use-case (filter defaults to All, distinct addressible typeIds,
per-card Agent button pre-filling a typed sample prompt); agent 'discover'
question series (audience -> intent shape -> fidelity -> recommend 1+1 ->
confirm, with visual 'show me both') parallel to requirement refinement; type
lock-in into PROJECT MEMORY; per-syntax skills loaded only when the type is
locked.
Step 1 catalog: kr0ki-core/src/catalog.rs — 24 addressible diagram types with
intent tags from a controlled vocabulary, sample prompts naming the type,
discovery guide; /api/catalog route (+http test); 6 unit tests.

Step 2 gallery: intent filter bar (defaults to All, auto-resets), type cards
with Test/Edit/Agent buttons; Agent handoff prefills the Agent composer with
the type's sample prompt (never auto-sent) via forwardedProps-ready prop.

Step 3 discover mode: recommend_diagram_type tool — agent must commit to 1–2
type recommendations with rationales after ≤3 intent questions; confirm
interrupt locks lockedType into the project; REFINE vs DISCOVER mode rules in
the system prompt; project memory carries 'Type chosen: …'.

Step 4 skills: skills/types/{sequence,activity,erd,component}.md loaded ONLY
when the type is locked (replaces generic dump).

Gates: 40/40 python, 15/15 vitest, 19/19 cargo suites, just check clean.
Live acceptance: alert→resolution prompt with no syntax named → 2 discovery
questions → recommend interrupt (State diagram | Flowchart | Show me both) →
confirm locks type → next run is refine-mode with Locked in: summary.
…ads tolerated

A recommend_diagram_type interrupt (rec-*) fell into the generic
Approve-draft branch because isQuestionInterrupt only matched ask-* —
posting {approved: true} to a question endpoint 400s. rec-* now renders the
multiple-choice UI like ask-*; and the server tolerates approval-style
payloads on question interrupts by choosing the primary option. 41/41
python.
Pill-tab strip in a bordered tray: 'I want to show' label, hover/focus
states, gradient active pill with glow, per-tag type counts, aria-pressed,
and an empty-state when a filter matches nothing.
…headline trimmed

Ten PlantUML types share one fixture, so keying Test artifacts by example id
lit up every PlantUML card at once. Type cards now use their own
typeStatus/typeArtifacts/typeErrors maps keyed by typeId. Hero headline cut
to 'Diagram-as-code, rendered.'
- After 2 answered questions the project banner offers '⚡ Diagram now':
  arms fast-track server-side (POST /projects/fasttrack) and sends a
  best-judgement instruction. Fast-track overrides the question-mode rules
  entirely (FAST-TRACK MODE: asking is a contract violation); the agent
  states 'Best judgement:' choices and renders in the same run.
- Dashboard shows a spinner + 'Generating diagram…' while a run streams,
  next to the panel count.
- Live-verified: fast-track armed -> no questions, best-judgement lock-in,
  immediate render. 42/42 python, 15/15 vitest.
…s (M1)

kr0ki-core no longer owns the requirements semantic model — it now
re-exports ufo_types::mbse::requirements (released as ufo-types v0.15.0,
PromptExecution/ufo-types#28) as kr0ki_core::requirements. Deletes the
873-line local copy; requirements_render.rs and kr0ki-server's HTTP
routes are unchanged, since they resolve through the re-export.

Zero duplication, zero drift: the semantic model has one home now.
Verified: cargo build --workspace, clippy -D warnings, and the full
kr0ki-core/kr0ki-server test suite (148 + 44 tests) all pass against
the released tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJ5M1bGQJB1VS8Epr7AGLa
Addresses PR #36 review, P1 findings 1 and 2.

- crates/kr0ki-core/src/b00t_graph.rs: d2_escape_label only neutralized
  newlines, leaving D2's structural characters ({, }, :, ...) live in
  label position. A ReqIF requirement title like "normal requirement {"
  emits invalid D2 ("maps must be terminated with }") or, worse, a
  title like "{shape: circle}" gets interpreted as D2 structure instead
  of label text. Fixed at the shared helper (used by requirements_render,
  b00t_graph's own D2Emitter, and sysml_render — all three had the
  identical defect), so labels are now quoted/escaped the same way
  d2_quote already handles ids. Added adversarial-title coverage in all
  three call sites' test modules; updated the two existing tests that
  had encoded the old unquoted behavior as expected output.
- .gitmodules: vendor/assistant-ui had only `branch`, no `path`/`url`,
  so `actions/checkout` with `submodules: recursive` fails before any
  test runs — blocking every CI check regardless of this PR's content.
  Added the missing path/url (https://github.com/elasticdotventures/
  assistant-ui.git, matching the already-checked-out working tree's
  own remote and the pinned gitlink commit). Verified: `git ls-remote`
  resolves the pinned commit on that url/branch unauthenticated, and
  `git submodule init && git submodule sync` succeeds locally.

Verified: cargo build --workspace, cargo clippy --workspace -- -D
warnings, cargo test -p kr0ki-core -p kr0ki-server (150 + 44 passing).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJ5M1bGQJB1VS8Epr7AGLa
CI's cargo fmt --all -- --check caught a multi-line assert_eq! rustfmt
collapses onto one line; missed locally in the prior commit since I
verified build/clippy/test there but not fmt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJ5M1bGQJB1VS8Epr7AGLa
@elasticdotventures
elasticdotventures marked this pull request as ready for review September 19, 2026 22:52
@elasticdotventures
elasticdotventures merged commit 852e396 into main Sep 19, 2026
2 checks passed
@elasticdotventures
elasticdotventures deleted the feature/flexo-reqif-implementation branch September 19, 2026 22:52
elasticdotventures added a commit that referenced this pull request Sep 19, 2026
… CI (#37)

* fix(docs,ci): correct stale test-count claim; wire StoryB00k/Vue into CI

Addresses PR #36 review, P2 findings.

- docs/HANDOFF-2026-09-19-reqif-flexo.md: "cargo test -p kr0ki-core
  requirements # 5 passed" went stale in the same PR that made it true
  — M1 moved the semantic tests to ufo-types, leaving only the D2
  adapter test local. Corrected to the actual current counts for every
  command listed, each re-run and verified before writing it down:
  4 (ufo-types' own mbse::requirements tests), 5 (new consumer-level
  contract tests below), 2 (requirements_render's D2-adapter tests),
  1 (the server HTTP test).
- crates/kr0ki-core/tests/requirements_contract.rs (new): the
  "consumer-level contract tests for the re-exported model/view
  behavior" the review asked for — an integration test exercising
  kr0ki_core::requirements and requirements_render::to_d2 through
  kr0ki-core's public API only, proving the ufo-types re-export and
  the one D2 adapter kr0ki-core owns actually compose the way
  kr0ki-server's HTTP routes depend on. 5 tests: promotion isolation,
  bounded impact/cycles, verification coverage, validation errors, and
  view-to-D2 lowering.
- .github/workflows/ci.yml, justfile: wire the StoryB00k Python suite
  (7 files, 43 tests) and the playbook Vue/Vite suite (test + build)
  into `check`/`just test`, alongside the existing Rust and
  kr0ki-mcp-Python coverage. Both needed real fixes to actually pass,
  not just a wiring change:
    - playbook: `pnpm --dir playbook test` failed on a clean install —
      @assistant-ui/tap's core module graph statically imports `react`
      even on the Vue-only path, though tap declares react as an
      *optional* peer dependency. Added `react` as a playbook
      devDependency (upstream packaging quirk in a transitive
      dependency, not something fixable from this repo or the vendored
      assistant-ui fork). Committed playbook/pnpm-lock.yaml, which had
      never been committed despite pnpm being the tool actually in use
      (only a stale npm package-lock.json existed) — CI needs a
      lockfile to install deterministically with --frozen-lockfile.
    - StoryB00k agent: a bare `pip install` hits PEP 668
      "externally-managed-environment" on Debian/Ubuntu (including
      GitHub's ubuntu-latest runners) unless run inside a venv. Both
      the CI step and `just test` now create one.
  Verified end-to-end locally with a clean `pnpm install
  --frozen-lockfile` and a fresh venv: 43 Python tests, 15 Vitest
  tests, and the Vite production build all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJ5M1bGQJB1VS8Epr7AGLa

* fix(storyb00k-agent): isolate test_planning_flow.py from a live kr0ki-server

Wiring this suite into CI (previous commit) surfaced a real,
pre-existing test-isolation gap: PlanningFlowTest mocked the LLM client
but not fetch_manifest(), the one real network call server._run() makes
(to KR0KI_URL, default http://127.0.0.1:8787) before ever reaching the
mocked LLM. It passed locally only because a real kr0ki-server happened
to be listening on that port in dev — on a clean CI runner it hit
"Connection refused" and failed 4 tests + 1 assertion mismatch.
test_server.py already has this exact mock (`@patch("server.
fetch_manifest", return_value=[])`) on its two tests that call _run();
this applies the same pattern in setUp so it covers every test in the
class.

Verified with KR0KI_URL pointed at a guaranteed-unreachable port
(http://127.0.0.1:1), not just by having a server happen to be up:
7/7 in this file, 43/43 in the full containers/kr0ki-storyb00k-agent
suite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJ5M1bGQJB1VS8Epr7AGLa

* fix(ci): drop the playbook Vite build from CI; exclude its one vendor-gated test

Per review discussion on PR #37: making `pnpm --dir playbook run build`
pass in CI would require building the vendored @assistant-ui/vue
package's dist/, which requires installing the ENTIRE
elasticdotventures/assistant-ui monorepo -- 2,347 packages, ~2GB,
2m45s (Next.js, Nuxt, SvelteKit, docs tooling, none of which kr0ki
needs) -- just to produce one small package's output. Confirmed by
actually running the install. Decided against taking that on right
now; dropped the build step from CI and `just test`.

That also means `pnpm --dir playbook test` itself fails on a clean CI
checkout: assistant-ui-vendor.test.js statically imports
"@assistant-ui/vue", and Vite's import-analysis can't resolve it
without dist/ present -- confirmed against the actual CI failure log,
not assumed. describe.skipIf can't help here since the import fails at
module-load time, before any test-level skip logic runs. Fixed via a
CI-conditional `exclude` in playbook/vite.config.js instead (using
vitest's own configDefaults.exclude as the base, not a hand-rolled
duplicate of its defaults), so the file is never even collected when
CI=true. It still runs for any developer who has built the vendored
package locally.

Verified all three states directly, including with vendor/assistant-ui/
packages/vue/dist/ actually absent (not just unset env), matching a
real clean checkout:
  - dist/ present, no CI env: 3 files / 15 tests pass
  - dist/ absent, CI=true:    2 files / 13 tests pass (vendor file skipped)
  - dist/ absent, no CI env:  correctly fails (accurate signal for a
    local dev who hasn't built the vendored package -- pre-existing
    behavior, now just correctly scoped to not affect CI)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJ5M1bGQJB1VS8Epr7AGLa

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant